lexer.nim 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # This lexer is handwritten for efficiency. I used an elegant buffering
  10. # scheme which I have not seen anywhere else:
  11. # We guarantee that a whole line is in the buffer. Thus only when scanning
  12. # the \n or \r character we have to check whether we need to read in the next
  13. # chunk. (\n or \r already need special handling for incrementing the line
  14. # counter; choosing both \n and \r allows the lexer to properly read Unix,
  15. # DOS or Macintosh text files, even when it is not the native format.
  16. import
  17. hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream,
  18. wordrecg, lineinfos, pathutils, parseutils
  19. when defined(nimPreviewSlimSystem):
  20. import std/[assertions, formatfloat]
  21. const
  22. MaxLineLength* = 80 # lines longer than this lead to a warning
  23. numChars*: set[char] = {'0'..'9', 'a'..'z', 'A'..'Z'}
  24. SymChars*: set[char] = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF'}
  25. SymStartChars*: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'}
  26. OpChars*: set[char] = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.',
  27. '|', '=', '%', '&', '$', '@', '~', ':'}
  28. UnaryMinusWhitelist = {' ', '\t', '\n', '\r', ',', ';', '(', '[', '{'}
  29. # don't forget to update the 'highlite' module if these charsets should change
  30. type
  31. TokType* = enum
  32. tkInvalid = "tkInvalid", tkEof = "[EOF]", # order is important here!
  33. tkSymbol = "tkSymbol", # keywords:
  34. tkAddr = "addr", tkAnd = "and", tkAs = "as", tkAsm = "asm",
  35. tkBind = "bind", tkBlock = "block", tkBreak = "break", tkCase = "case", tkCast = "cast",
  36. tkConcept = "concept", tkConst = "const", tkContinue = "continue", tkConverter = "converter",
  37. tkDefer = "defer", tkDiscard = "discard", tkDistinct = "distinct", tkDiv = "div", tkDo = "do",
  38. tkElif = "elif", tkElse = "else", tkEnd = "end", tkEnum = "enum", tkExcept = "except", tkExport = "export",
  39. tkFinally = "finally", tkFor = "for", tkFrom = "from", tkFunc = "func",
  40. tkIf = "if", tkImport = "import", tkIn = "in", tkInclude = "include", tkInterface = "interface",
  41. tkIs = "is", tkIsnot = "isnot", tkIterator = "iterator",
  42. tkLet = "let",
  43. tkMacro = "macro", tkMethod = "method", tkMixin = "mixin", tkMod = "mod", tkNil = "nil", tkNot = "not", tkNotin = "notin",
  44. tkObject = "object", tkOf = "of", tkOr = "or", tkOut = "out",
  45. tkProc = "proc", tkPtr = "ptr", tkRaise = "raise", tkRef = "ref", tkReturn = "return",
  46. tkShl = "shl", tkShr = "shr", tkStatic = "static",
  47. tkTemplate = "template",
  48. tkTry = "try", tkTuple = "tuple", tkType = "type", tkUsing = "using",
  49. tkVar = "var", tkWhen = "when", tkWhile = "while", tkXor = "xor",
  50. tkYield = "yield", # end of keywords
  51. tkIntLit = "tkIntLit", tkInt8Lit = "tkInt8Lit", tkInt16Lit = "tkInt16Lit",
  52. tkInt32Lit = "tkInt32Lit", tkInt64Lit = "tkInt64Lit",
  53. tkUIntLit = "tkUIntLit", tkUInt8Lit = "tkUInt8Lit", tkUInt16Lit = "tkUInt16Lit",
  54. tkUInt32Lit = "tkUInt32Lit", tkUInt64Lit = "tkUInt64Lit",
  55. tkFloatLit = "tkFloatLit", tkFloat32Lit = "tkFloat32Lit",
  56. tkFloat64Lit = "tkFloat64Lit", tkFloat128Lit = "tkFloat128Lit",
  57. tkStrLit = "tkStrLit", tkRStrLit = "tkRStrLit", tkTripleStrLit = "tkTripleStrLit",
  58. tkGStrLit = "tkGStrLit", tkGTripleStrLit = "tkGTripleStrLit", tkCharLit = "tkCharLit",
  59. tkCustomLit = "tkCustomLit",
  60. tkParLe = "(", tkParRi = ")", tkBracketLe = "[",
  61. tkBracketRi = "]", tkCurlyLe = "{", tkCurlyRi = "}",
  62. tkBracketDotLe = "[.", tkBracketDotRi = ".]",
  63. tkCurlyDotLe = "{.", tkCurlyDotRi = ".}",
  64. tkParDotLe = "(.", tkParDotRi = ".)",
  65. tkComma = ",", tkSemiColon = ";",
  66. tkColon = ":", tkColonColon = "::", tkEquals = "=",
  67. tkDot = ".", tkDotDot = "..", tkBracketLeColon = "[:",
  68. tkOpr, tkComment, tkAccent = "`",
  69. # these are fake tokens used by renderer.nim
  70. tkSpaces, tkInfixOpr, tkPrefixOpr, tkPostfixOpr, tkHideableStart, tkHideableEnd
  71. TokTypes* = set[TokType]
  72. const
  73. weakTokens = {tkComma, tkSemiColon, tkColon,
  74. tkParRi, tkParDotRi, tkBracketRi, tkBracketDotRi,
  75. tkCurlyRi} # \
  76. # tokens that should not be considered for previousToken
  77. tokKeywordLow* = succ(tkSymbol)
  78. tokKeywordHigh* = pred(tkIntLit)
  79. type
  80. NumericalBase* = enum
  81. base10, # base10 is listed as the first element,
  82. # so that it is the correct default value
  83. base2, base8, base16
  84. TokenSpacing* = enum
  85. tsNone, tsTrailing, tsEof
  86. Token* = object # a Nim token
  87. tokType*: TokType # the type of the token
  88. indent*: int # the indentation; != -1 if the token has been
  89. # preceded with indentation
  90. ident*: PIdent # the parsed identifier
  91. iNumber*: BiggestInt # the parsed integer literal
  92. fNumber*: BiggestFloat # the parsed floating point literal
  93. base*: NumericalBase # the numerical base; only valid for int
  94. # or float literals
  95. strongSpaceA*: bool # leading spaces of an operator
  96. strongSpaceB*: TokenSpacing # trailing spaces of an operator
  97. literal*: string # the parsed (string) literal; and
  98. # documentation comments are here too
  99. line*, col*: int
  100. when defined(nimpretty):
  101. offsetA*, offsetB*: int # used for pretty printing so that literals
  102. # like 0b01 or r"\L" are unaffected
  103. commentOffsetA*, commentOffsetB*: int
  104. ErrorHandler* = proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string)
  105. Lexer* = object of TBaseLexer
  106. fileIdx*: FileIndex
  107. indentAhead*: int # if > 0 an indentation has already been read
  108. # this is needed because scanning comments
  109. # needs so much look-ahead
  110. currLineIndent*: int
  111. strongSpaces*, allowTabs*: bool
  112. errorHandler*: ErrorHandler
  113. cache*: IdentCache
  114. when defined(nimsuggest):
  115. previousToken: TLineInfo
  116. config*: ConfigRef
  117. proc getLineInfo*(L: Lexer, tok: Token): TLineInfo {.inline.} =
  118. result = newLineInfo(L.fileIdx, tok.line, tok.col)
  119. when defined(nimpretty):
  120. result.offsetA = tok.offsetA
  121. result.offsetB = tok.offsetB
  122. result.commentOffsetA = tok.commentOffsetA
  123. result.commentOffsetB = tok.commentOffsetB
  124. proc isKeyword*(kind: TokType): bool =
  125. (kind >= tokKeywordLow) and (kind <= tokKeywordHigh)
  126. template ones(n): untyped = ((1 shl n)-1) # for utf-8 conversion
  127. proc isNimIdentifier*(s: string): bool =
  128. let sLen = s.len
  129. if sLen > 0 and s[0] in SymStartChars:
  130. var i = 1
  131. while i < sLen:
  132. if s[i] == '_': inc(i)
  133. if i < sLen and s[i] notin SymChars: return
  134. inc(i)
  135. result = true
  136. proc `$`*(tok: Token): string =
  137. case tok.tokType
  138. of tkIntLit..tkInt64Lit: $tok.iNumber
  139. of tkFloatLit..tkFloat64Lit: $tok.fNumber
  140. of tkInvalid, tkStrLit..tkCharLit, tkComment: tok.literal
  141. of tkParLe..tkColon, tkEof, tkAccent: $tok.tokType
  142. else:
  143. if tok.ident != nil:
  144. tok.ident.s
  145. else:
  146. ""
  147. proc prettyTok*(tok: Token): string =
  148. if isKeyword(tok.tokType): "keyword " & tok.ident.s
  149. else: $tok
  150. proc printTok*(conf: ConfigRef; tok: Token) =
  151. # xxx factor with toLocation
  152. msgWriteln(conf, $tok.line & ":" & $tok.col & "\t" & $tok.tokType & " " & $tok)
  153. proc initToken*(L: var Token) =
  154. L.tokType = tkInvalid
  155. L.iNumber = 0
  156. L.indent = 0
  157. L.strongSpaceA = false
  158. L.literal = ""
  159. L.fNumber = 0.0
  160. L.base = base10
  161. L.ident = nil
  162. when defined(nimpretty):
  163. L.commentOffsetA = 0
  164. L.commentOffsetB = 0
  165. proc fillToken(L: var Token) =
  166. L.tokType = tkInvalid
  167. L.iNumber = 0
  168. L.indent = 0
  169. L.strongSpaceA = false
  170. setLen(L.literal, 0)
  171. L.fNumber = 0.0
  172. L.base = base10
  173. L.ident = nil
  174. when defined(nimpretty):
  175. L.commentOffsetA = 0
  176. L.commentOffsetB = 0
  177. proc openLexer*(lex: var Lexer, fileIdx: FileIndex, inputstream: PLLStream;
  178. cache: IdentCache; config: ConfigRef) =
  179. openBaseLexer(lex, inputstream)
  180. lex.fileIdx = fileIdx
  181. lex.indentAhead = -1
  182. lex.currLineIndent = 0
  183. inc(lex.lineNumber, inputstream.lineOffset)
  184. lex.cache = cache
  185. when defined(nimsuggest):
  186. lex.previousToken.fileIndex = fileIdx
  187. lex.config = config
  188. proc openLexer*(lex: var Lexer, filename: AbsoluteFile, inputstream: PLLStream;
  189. cache: IdentCache; config: ConfigRef) =
  190. openLexer(lex, fileInfoIdx(config, filename), inputstream, cache, config)
  191. proc closeLexer*(lex: var Lexer) =
  192. if lex.config != nil:
  193. inc(lex.config.linesCompiled, lex.lineNumber)
  194. closeBaseLexer(lex)
  195. proc getLineInfo(L: Lexer): TLineInfo =
  196. result = newLineInfo(L.fileIdx, L.lineNumber, getColNumber(L, L.bufpos))
  197. proc dispMessage(L: Lexer; info: TLineInfo; msg: TMsgKind; arg: string) =
  198. if L.errorHandler.isNil:
  199. msgs.message(L.config, info, msg, arg)
  200. else:
  201. L.errorHandler(L.config, info, msg, arg)
  202. proc lexMessage*(L: Lexer, msg: TMsgKind, arg = "") =
  203. L.dispMessage(getLineInfo(L), msg, arg)
  204. proc lexMessageTok*(L: Lexer, msg: TMsgKind, tok: Token, arg = "") =
  205. var info = newLineInfo(L.fileIdx, tok.line, tok.col)
  206. L.dispMessage(info, msg, arg)
  207. proc lexMessagePos(L: var Lexer, msg: TMsgKind, pos: int, arg = "") =
  208. var info = newLineInfo(L.fileIdx, L.lineNumber, pos - L.lineStart)
  209. L.dispMessage(info, msg, arg)
  210. proc matchTwoChars(L: Lexer, first: char, second: set[char]): bool =
  211. result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second)
  212. template tokenBegin(tok, pos) {.dirty.} =
  213. when defined(nimsuggest):
  214. var colA = getColNumber(L, pos)
  215. when defined(nimpretty):
  216. tok.offsetA = L.offsetBase + pos
  217. template tokenEnd(tok, pos) {.dirty.} =
  218. when defined(nimsuggest):
  219. let colB = getColNumber(L, pos)+1
  220. if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
  221. L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
  222. L.config.m.trackPos.col = colA.int16
  223. colA = 0
  224. when defined(nimpretty):
  225. tok.offsetB = L.offsetBase + pos
  226. template tokenEndIgnore(tok, pos) =
  227. when defined(nimsuggest):
  228. let colB = getColNumber(L, pos)
  229. if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
  230. L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
  231. L.config.m.trackPos.fileIndex = trackPosInvalidFileIdx
  232. L.config.m.trackPos.line = 0'u16
  233. colA = 0
  234. when defined(nimpretty):
  235. tok.offsetB = L.offsetBase + pos
  236. template tokenEndPrevious(tok, pos) =
  237. when defined(nimsuggest):
  238. # when we detect the cursor in whitespace, we attach the track position
  239. # to the token that came before that, but only if we haven't detected
  240. # the cursor in a string literal or comment:
  241. let colB = getColNumber(L, pos)
  242. if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
  243. L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
  244. L.config.m.trackPos = L.previousToken
  245. L.config.m.trackPosAttached = true
  246. colA = 0
  247. when defined(nimpretty):
  248. tok.offsetB = L.offsetBase + pos
  249. template eatChar(L: var Lexer, t: var Token, replacementChar: char) =
  250. t.literal.add(replacementChar)
  251. inc(L.bufpos)
  252. template eatChar(L: var Lexer, t: var Token) =
  253. t.literal.add(L.buf[L.bufpos])
  254. inc(L.bufpos)
  255. proc getNumber(L: var Lexer, result: var Token) =
  256. proc matchUnderscoreChars(L: var Lexer, tok: var Token, chars: set[char]): Natural =
  257. var pos = L.bufpos # use registers for pos, buf
  258. result = 0
  259. while true:
  260. if L.buf[pos] in chars:
  261. tok.literal.add(L.buf[pos])
  262. inc(pos)
  263. inc(result)
  264. else:
  265. break
  266. if L.buf[pos] == '_':
  267. if L.buf[pos+1] notin chars:
  268. lexMessage(L, errGenerated,
  269. "only single underscores may occur in a token and token may not " &
  270. "end with an underscore: e.g. '1__1' and '1_' are invalid")
  271. break
  272. tok.literal.add('_')
  273. inc(pos)
  274. L.bufpos = pos
  275. proc matchChars(L: var Lexer, tok: var Token, chars: set[char]) =
  276. var pos = L.bufpos # use registers for pos, buf
  277. while L.buf[pos] in chars:
  278. tok.literal.add(L.buf[pos])
  279. inc(pos)
  280. L.bufpos = pos
  281. proc lexMessageLitNum(L: var Lexer, msg: string, startpos: int, msgKind = errGenerated) =
  282. # Used to get slightly human friendlier err messages.
  283. const literalishChars = {'A'..'Z', 'a'..'z', '0'..'9', '_', '.', '\''}
  284. var msgPos = L.bufpos
  285. var t: Token
  286. t.literal = ""
  287. L.bufpos = startpos # Use L.bufpos as pos because of matchChars
  288. matchChars(L, t, literalishChars)
  289. # We must verify +/- specifically so that we're not past the literal
  290. if L.buf[L.bufpos] in {'+', '-'} and
  291. L.buf[L.bufpos - 1] in {'e', 'E'}:
  292. t.literal.add(L.buf[L.bufpos])
  293. inc(L.bufpos)
  294. matchChars(L, t, literalishChars)
  295. if L.buf[L.bufpos] in literalishChars:
  296. t.literal.add(L.buf[L.bufpos])
  297. inc(L.bufpos)
  298. matchChars(L, t, {'0'..'9'})
  299. L.bufpos = msgPos
  300. lexMessage(L, msgKind, msg % t.literal)
  301. var
  302. xi: BiggestInt
  303. isBase10 = true
  304. numDigits = 0
  305. const
  306. # 'c', 'C' is deprecated
  307. baseCodeChars = {'X', 'x', 'o', 'b', 'B', 'c', 'C'}
  308. literalishChars = baseCodeChars + {'A'..'F', 'a'..'f', '0'..'9', '_', '\''}
  309. floatTypes = {tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit}
  310. result.tokType = tkIntLit # int literal until we know better
  311. result.literal = ""
  312. result.base = base10
  313. tokenBegin(result, L.bufpos)
  314. var isPositive = true
  315. if L.buf[L.bufpos] == '-':
  316. eatChar(L, result)
  317. isPositive = false
  318. let startpos = L.bufpos
  319. template setNumber(field, value) =
  320. field = (if isPositive: value else: -value)
  321. # First stage: find out base, make verifications, build token literal string
  322. # {'c', 'C'} is added for deprecation reasons to provide a clear error message
  323. if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'c', 'C', 'O'}:
  324. isBase10 = false
  325. eatChar(L, result, '0')
  326. case L.buf[L.bufpos]
  327. of 'c', 'C':
  328. lexMessageLitNum(L,
  329. "$1 will soon be invalid for oct literals; Use '0o' " &
  330. "for octals. 'c', 'C' prefix",
  331. startpos,
  332. warnDeprecated)
  333. eatChar(L, result, 'c')
  334. numDigits = matchUnderscoreChars(L, result, {'0'..'7'})
  335. of 'O':
  336. lexMessageLitNum(L, "$1 is an invalid int literal; For octal literals " &
  337. "use the '0o' prefix.", startpos)
  338. of 'x', 'X':
  339. eatChar(L, result, 'x')
  340. numDigits = matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'})
  341. of 'o':
  342. eatChar(L, result, 'o')
  343. numDigits = matchUnderscoreChars(L, result, {'0'..'7'})
  344. of 'b', 'B':
  345. eatChar(L, result, 'b')
  346. numDigits = matchUnderscoreChars(L, result, {'0'..'1'})
  347. else:
  348. internalError(L.config, getLineInfo(L), "getNumber")
  349. if numDigits == 0:
  350. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  351. else:
  352. discard matchUnderscoreChars(L, result, {'0'..'9'})
  353. if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}):
  354. result.tokType = tkFloatLit
  355. eatChar(L, result, '.')
  356. discard matchUnderscoreChars(L, result, {'0'..'9'})
  357. if L.buf[L.bufpos] in {'e', 'E'}:
  358. result.tokType = tkFloatLit
  359. eatChar(L, result)
  360. if L.buf[L.bufpos] in {'+', '-'}:
  361. eatChar(L, result)
  362. discard matchUnderscoreChars(L, result, {'0'..'9'})
  363. let endpos = L.bufpos
  364. # Second stage, find out if there's a datatype suffix and handle it
  365. var postPos = endpos
  366. if L.buf[postPos] in {'\'', 'f', 'F', 'd', 'D', 'i', 'I', 'u', 'U'}:
  367. let errPos = postPos
  368. var customLitPossible = false
  369. if L.buf[postPos] == '\'':
  370. inc(postPos)
  371. customLitPossible = true
  372. if L.buf[postPos] in SymChars:
  373. var suffix = newStringOfCap(10)
  374. while true:
  375. suffix.add L.buf[postPos]
  376. inc postPos
  377. if L.buf[postPos] notin SymChars+{'_'}: break
  378. let suffixAsLower = suffix.toLowerAscii
  379. case suffixAsLower
  380. of "f", "f32": result.tokType = tkFloat32Lit
  381. of "d", "f64": result.tokType = tkFloat64Lit
  382. of "f128": result.tokType = tkFloat128Lit
  383. of "i8": result.tokType = tkInt8Lit
  384. of "i16": result.tokType = tkInt16Lit
  385. of "i32": result.tokType = tkInt32Lit
  386. of "i64": result.tokType = tkInt64Lit
  387. of "u": result.tokType = tkUIntLit
  388. of "u8": result.tokType = tkUInt8Lit
  389. of "u16": result.tokType = tkUInt16Lit
  390. of "u32": result.tokType = tkUInt32Lit
  391. of "u64": result.tokType = tkUInt64Lit
  392. elif customLitPossible:
  393. # remember the position of the `'` so that the parser doesn't
  394. # have to reparse the custom literal:
  395. result.iNumber = len(result.literal)
  396. result.literal.add '\''
  397. result.literal.add suffix
  398. result.tokType = tkCustomLit
  399. else:
  400. lexMessageLitNum(L, "invalid number suffix: '$1'", errPos)
  401. else:
  402. lexMessageLitNum(L, "invalid number suffix: '$1'", errPos)
  403. # Is there still a literalish char awaiting? Then it's an error!
  404. if L.buf[postPos] in literalishChars or
  405. (L.buf[postPos] == '.' and L.buf[postPos + 1] in {'0'..'9'}):
  406. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  407. if result.tokType != tkCustomLit:
  408. # Third stage, extract actual number
  409. L.bufpos = startpos # restore position
  410. var pos = startpos
  411. try:
  412. if (L.buf[pos] == '0') and (L.buf[pos + 1] in baseCodeChars):
  413. inc(pos, 2)
  414. xi = 0 # it is a base prefix
  415. case L.buf[pos - 1]
  416. of 'b', 'B':
  417. result.base = base2
  418. while pos < endpos:
  419. if L.buf[pos] != '_':
  420. xi = `shl`(xi, 1) or (ord(L.buf[pos]) - ord('0'))
  421. inc(pos)
  422. # 'c', 'C' is deprecated (a warning is issued elsewhere)
  423. of 'o', 'c', 'C':
  424. result.base = base8
  425. while pos < endpos:
  426. if L.buf[pos] != '_':
  427. xi = `shl`(xi, 3) or (ord(L.buf[pos]) - ord('0'))
  428. inc(pos)
  429. of 'x', 'X':
  430. result.base = base16
  431. while pos < endpos:
  432. case L.buf[pos]
  433. of '_':
  434. inc(pos)
  435. of '0'..'9':
  436. xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('0'))
  437. inc(pos)
  438. of 'a'..'f':
  439. xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('a') + 10)
  440. inc(pos)
  441. of 'A'..'F':
  442. xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('A') + 10)
  443. inc(pos)
  444. else:
  445. break
  446. else:
  447. internalError(L.config, getLineInfo(L), "getNumber")
  448. case result.tokType
  449. of tkIntLit, tkInt64Lit: setNumber result.iNumber, xi
  450. of tkInt8Lit: setNumber result.iNumber, ashr(xi shl 56, 56)
  451. of tkInt16Lit: setNumber result.iNumber, ashr(xi shl 48, 48)
  452. of tkInt32Lit: setNumber result.iNumber, ashr(xi shl 32, 32)
  453. of tkUIntLit, tkUInt64Lit: setNumber result.iNumber, xi
  454. of tkUInt8Lit: setNumber result.iNumber, xi and 0xff
  455. of tkUInt16Lit: setNumber result.iNumber, xi and 0xffff
  456. of tkUInt32Lit: setNumber result.iNumber, xi and 0xffffffff
  457. of tkFloat32Lit:
  458. setNumber result.fNumber, (cast[ptr float32](addr(xi)))[]
  459. # note: this code is endian neutral!
  460. # XXX: Test this on big endian machine!
  461. of tkFloat64Lit, tkFloatLit:
  462. setNumber result.fNumber, (cast[ptr float64](addr(xi)))[]
  463. else: internalError(L.config, getLineInfo(L), "getNumber")
  464. # Bounds checks. Non decimal literals are allowed to overflow the range of
  465. # the datatype as long as their pattern don't overflow _bitwise_, hence
  466. # below checks of signed sizes against uint*.high is deliberate:
  467. # (0x80'u8 = 128, 0x80'i8 = -128, etc == OK)
  468. if result.tokType notin floatTypes:
  469. let outOfRange =
  470. case result.tokType
  471. of tkUInt8Lit, tkUInt16Lit, tkUInt32Lit: result.iNumber != xi
  472. of tkInt8Lit: (xi > BiggestInt(uint8.high))
  473. of tkInt16Lit: (xi > BiggestInt(uint16.high))
  474. of tkInt32Lit: (xi > BiggestInt(uint32.high))
  475. else: false
  476. if outOfRange:
  477. #echo "out of range num: ", result.iNumber, " vs ", xi
  478. lexMessageLitNum(L, "number out of range: '$1'", startpos)
  479. else:
  480. case result.tokType
  481. of floatTypes:
  482. result.fNumber = parseFloat(result.literal)
  483. of tkUInt64Lit, tkUIntLit:
  484. var iNumber: uint64
  485. var len: int
  486. try:
  487. len = parseBiggestUInt(result.literal, iNumber)
  488. except ValueError:
  489. raise newException(OverflowDefect, "number out of range: " & result.literal)
  490. if len != result.literal.len:
  491. raise newException(ValueError, "invalid integer: " & result.literal)
  492. result.iNumber = cast[int64](iNumber)
  493. else:
  494. var iNumber: int64
  495. var len: int
  496. try:
  497. len = parseBiggestInt(result.literal, iNumber)
  498. except ValueError:
  499. raise newException(OverflowDefect, "number out of range: " & result.literal)
  500. if len != result.literal.len:
  501. raise newException(ValueError, "invalid integer: " & result.literal)
  502. result.iNumber = iNumber
  503. # Explicit bounds checks.
  504. let outOfRange =
  505. case result.tokType
  506. of tkInt8Lit: result.iNumber > int8.high or result.iNumber < int8.low
  507. of tkUInt8Lit: result.iNumber > BiggestInt(uint8.high) or result.iNumber < 0
  508. of tkInt16Lit: result.iNumber > int16.high or result.iNumber < int16.low
  509. of tkUInt16Lit: result.iNumber > BiggestInt(uint16.high) or result.iNumber < 0
  510. of tkInt32Lit: result.iNumber > int32.high or result.iNumber < int32.low
  511. of tkUInt32Lit: result.iNumber > BiggestInt(uint32.high) or result.iNumber < 0
  512. else: false
  513. if outOfRange:
  514. lexMessageLitNum(L, "number out of range: '$1'", startpos)
  515. # Promote int literal to int64? Not always necessary, but more consistent
  516. if result.tokType == tkIntLit:
  517. if result.iNumber > high(int32) or result.iNumber < low(int32):
  518. result.tokType = tkInt64Lit
  519. except ValueError:
  520. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  521. except OverflowDefect, RangeDefect:
  522. lexMessageLitNum(L, "number out of range: '$1'", startpos)
  523. tokenEnd(result, postPos-1)
  524. L.bufpos = postPos
  525. proc handleHexChar(L: var Lexer, xi: var int; position: range[0..4]) =
  526. template invalid() =
  527. lexMessage(L, errGenerated,
  528. "expected a hex digit, but found: " & L.buf[L.bufpos] &
  529. "; maybe prepend with 0")
  530. case L.buf[L.bufpos]
  531. of '0'..'9':
  532. xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('0'))
  533. inc(L.bufpos)
  534. of 'a'..'f':
  535. xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('a') + 10)
  536. inc(L.bufpos)
  537. of 'A'..'F':
  538. xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('A') + 10)
  539. inc(L.bufpos)
  540. of '"', '\'':
  541. if position <= 1: invalid()
  542. # do not progress the bufpos here.
  543. if position == 0: inc(L.bufpos)
  544. else:
  545. invalid()
  546. # Need to progress for `nim check`
  547. inc(L.bufpos)
  548. proc handleDecChars(L: var Lexer, xi: var int) =
  549. while L.buf[L.bufpos] in {'0'..'9'}:
  550. xi = (xi * 10) + (ord(L.buf[L.bufpos]) - ord('0'))
  551. inc(L.bufpos)
  552. proc addUnicodeCodePoint(s: var string, i: int) =
  553. let i = cast[uint](i)
  554. # inlined toUTF-8 to avoid unicode and strutils dependencies.
  555. let pos = s.len
  556. if i <= 127:
  557. s.setLen(pos+1)
  558. s[pos+0] = chr(i)
  559. elif i <= 0x07FF:
  560. s.setLen(pos+2)
  561. s[pos+0] = chr((i shr 6) or 0b110_00000)
  562. s[pos+1] = chr((i and ones(6)) or 0b10_0000_00)
  563. elif i <= 0xFFFF:
  564. s.setLen(pos+3)
  565. s[pos+0] = chr(i shr 12 or 0b1110_0000)
  566. s[pos+1] = chr(i shr 6 and ones(6) or 0b10_0000_00)
  567. s[pos+2] = chr(i and ones(6) or 0b10_0000_00)
  568. elif i <= 0x001FFFFF:
  569. s.setLen(pos+4)
  570. s[pos+0] = chr(i shr 18 or 0b1111_0000)
  571. s[pos+1] = chr(i shr 12 and ones(6) or 0b10_0000_00)
  572. s[pos+2] = chr(i shr 6 and ones(6) or 0b10_0000_00)
  573. s[pos+3] = chr(i and ones(6) or 0b10_0000_00)
  574. elif i <= 0x03FFFFFF:
  575. s.setLen(pos+5)
  576. s[pos+0] = chr(i shr 24 or 0b111110_00)
  577. s[pos+1] = chr(i shr 18 and ones(6) or 0b10_0000_00)
  578. s[pos+2] = chr(i shr 12 and ones(6) or 0b10_0000_00)
  579. s[pos+3] = chr(i shr 6 and ones(6) or 0b10_0000_00)
  580. s[pos+4] = chr(i and ones(6) or 0b10_0000_00)
  581. elif i <= 0x7FFFFFFF:
  582. s.setLen(pos+6)
  583. s[pos+0] = chr(i shr 30 or 0b1111110_0)
  584. s[pos+1] = chr(i shr 24 and ones(6) or 0b10_0000_00)
  585. s[pos+2] = chr(i shr 18 and ones(6) or 0b10_0000_00)
  586. s[pos+3] = chr(i shr 12 and ones(6) or 0b10_0000_00)
  587. s[pos+4] = chr(i shr 6 and ones(6) or 0b10_0000_00)
  588. s[pos+5] = chr(i and ones(6) or 0b10_0000_00)
  589. proc getEscapedChar(L: var Lexer, tok: var Token) =
  590. inc(L.bufpos) # skip '\'
  591. case L.buf[L.bufpos]
  592. of 'n', 'N':
  593. tok.literal.add('\L')
  594. inc(L.bufpos)
  595. of 'p', 'P':
  596. if tok.tokType == tkCharLit:
  597. lexMessage(L, errGenerated, "\\p not allowed in character literal")
  598. tok.literal.add(L.config.target.tnl)
  599. inc(L.bufpos)
  600. of 'r', 'R', 'c', 'C':
  601. tok.literal.add(CR)
  602. inc(L.bufpos)
  603. of 'l', 'L':
  604. tok.literal.add(LF)
  605. inc(L.bufpos)
  606. of 'f', 'F':
  607. tok.literal.add(FF)
  608. inc(L.bufpos)
  609. of 'e', 'E':
  610. tok.literal.add(ESC)
  611. inc(L.bufpos)
  612. of 'a', 'A':
  613. tok.literal.add(BEL)
  614. inc(L.bufpos)
  615. of 'b', 'B':
  616. tok.literal.add(BACKSPACE)
  617. inc(L.bufpos)
  618. of 'v', 'V':
  619. tok.literal.add(VT)
  620. inc(L.bufpos)
  621. of 't', 'T':
  622. tok.literal.add('\t')
  623. inc(L.bufpos)
  624. of '\'', '\"':
  625. tok.literal.add(L.buf[L.bufpos])
  626. inc(L.bufpos)
  627. of '\\':
  628. tok.literal.add('\\')
  629. inc(L.bufpos)
  630. of 'x', 'X':
  631. inc(L.bufpos)
  632. var xi = 0
  633. handleHexChar(L, xi, 1)
  634. handleHexChar(L, xi, 2)
  635. tok.literal.add(chr(xi))
  636. of 'u', 'U':
  637. if tok.tokType == tkCharLit:
  638. lexMessage(L, errGenerated, "\\u not allowed in character literal")
  639. inc(L.bufpos)
  640. var xi = 0
  641. if L.buf[L.bufpos] == '{':
  642. inc(L.bufpos)
  643. var start = L.bufpos
  644. while L.buf[L.bufpos] != '}':
  645. handleHexChar(L, xi, 0)
  646. if start == L.bufpos:
  647. lexMessage(L, errGenerated,
  648. "Unicode codepoint cannot be empty")
  649. inc(L.bufpos)
  650. if xi > 0x10FFFF:
  651. let hex = ($L.buf)[start..L.bufpos-2]
  652. lexMessage(L, errGenerated,
  653. "Unicode codepoint must be lower than 0x10FFFF, but was: " & hex)
  654. else:
  655. handleHexChar(L, xi, 1)
  656. handleHexChar(L, xi, 2)
  657. handleHexChar(L, xi, 3)
  658. handleHexChar(L, xi, 4)
  659. addUnicodeCodePoint(tok.literal, xi)
  660. of '0'..'9':
  661. if matchTwoChars(L, '0', {'0'..'9'}):
  662. lexMessage(L, warnOctalEscape)
  663. var xi = 0
  664. handleDecChars(L, xi)
  665. if (xi <= 255): tok.literal.add(chr(xi))
  666. else: lexMessage(L, errGenerated, "invalid character constant")
  667. else: lexMessage(L, errGenerated, "invalid character constant")
  668. proc handleCRLF(L: var Lexer, pos: int): int =
  669. template registerLine =
  670. let col = L.getColNumber(pos)
  671. when not defined(nimpretty):
  672. if col > MaxLineLength:
  673. lexMessagePos(L, hintLineTooLong, pos)
  674. case L.buf[pos]
  675. of CR:
  676. registerLine()
  677. result = nimlexbase.handleCR(L, pos)
  678. of LF:
  679. registerLine()
  680. result = nimlexbase.handleLF(L, pos)
  681. else: result = pos
  682. type
  683. StringMode = enum
  684. normal,
  685. raw,
  686. generalized
  687. proc getString(L: var Lexer, tok: var Token, mode: StringMode) =
  688. var pos = L.bufpos
  689. var line = L.lineNumber # save linenumber for better error message
  690. tokenBegin(tok, pos - ord(mode == raw))
  691. inc pos # skip "
  692. if L.buf[pos] == '\"' and L.buf[pos+1] == '\"':
  693. tok.tokType = tkTripleStrLit # long string literal:
  694. inc(pos, 2) # skip ""
  695. # skip leading newline:
  696. if L.buf[pos] in {' ', '\t'}:
  697. var newpos = pos+1
  698. while L.buf[newpos] in {' ', '\t'}: inc newpos
  699. if L.buf[newpos] in {CR, LF}: pos = newpos
  700. pos = handleCRLF(L, pos)
  701. while true:
  702. case L.buf[pos]
  703. of '\"':
  704. if L.buf[pos+1] == '\"' and L.buf[pos+2] == '\"' and
  705. L.buf[pos+3] != '\"':
  706. tokenEndIgnore(tok, pos+2)
  707. L.bufpos = pos + 3 # skip the three """
  708. break
  709. tok.literal.add('\"')
  710. inc(pos)
  711. of CR, LF:
  712. tokenEndIgnore(tok, pos)
  713. pos = handleCRLF(L, pos)
  714. tok.literal.add("\n")
  715. of nimlexbase.EndOfFile:
  716. tokenEndIgnore(tok, pos)
  717. var line2 = L.lineNumber
  718. L.lineNumber = line
  719. lexMessagePos(L, errGenerated, L.lineStart, "closing \"\"\" expected, but end of file reached")
  720. L.lineNumber = line2
  721. L.bufpos = pos
  722. break
  723. else:
  724. tok.literal.add(L.buf[pos])
  725. inc(pos)
  726. else:
  727. # ordinary string literal
  728. if mode != normal: tok.tokType = tkRStrLit
  729. else: tok.tokType = tkStrLit
  730. while true:
  731. var c = L.buf[pos]
  732. if c == '\"':
  733. if mode != normal and L.buf[pos+1] == '\"':
  734. inc(pos, 2)
  735. tok.literal.add('"')
  736. else:
  737. tokenEndIgnore(tok, pos)
  738. inc(pos) # skip '"'
  739. break
  740. elif c in {CR, LF, nimlexbase.EndOfFile}:
  741. tokenEndIgnore(tok, pos)
  742. lexMessage(L, errGenerated, "closing \" expected")
  743. break
  744. elif (c == '\\') and mode == normal:
  745. L.bufpos = pos
  746. getEscapedChar(L, tok)
  747. pos = L.bufpos
  748. else:
  749. tok.literal.add(c)
  750. inc(pos)
  751. L.bufpos = pos
  752. proc getCharacter(L: var Lexer; tok: var Token) =
  753. tokenBegin(tok, L.bufpos)
  754. let startPos = L.bufpos
  755. inc(L.bufpos) # skip '
  756. var c = L.buf[L.bufpos]
  757. case c
  758. of '\0'..pred(' '), '\'':
  759. lexMessage(L, errGenerated, "invalid character literal")
  760. tok.literal = $c
  761. of '\\': getEscapedChar(L, tok)
  762. else:
  763. tok.literal = $c
  764. inc(L.bufpos)
  765. if L.buf[L.bufpos] == '\'':
  766. tokenEndIgnore(tok, L.bufpos)
  767. inc(L.bufpos) # skip '
  768. else:
  769. if startPos > 0 and L.buf[startPos-1] == '`':
  770. tok.literal = "'"
  771. L.bufpos = startPos+1
  772. else:
  773. lexMessage(L, errGenerated, "missing closing ' for character literal")
  774. tokenEndIgnore(tok, L.bufpos)
  775. const
  776. UnicodeOperatorStartChars = {'\226', '\194', '\195'}
  777. # the allowed unicode characters ("∙ ∘ × ★ ⊗ ⊘ ⊙ ⊛ ⊠ ⊡ ∩ ∧ ⊓ ± ⊕ ⊖ ⊞ ⊟ ∪ ∨ ⊔")
  778. # all start with one of these.
  779. type
  780. UnicodeOprPred = enum
  781. Mul, Add
  782. proc unicodeOprLen(buf: cstring; pos: int): (int8, UnicodeOprPred) =
  783. template m(len): untyped = (int8(len), Mul)
  784. template a(len): untyped = (int8(len), Add)
  785. result = 0.m
  786. case buf[pos]
  787. of '\226':
  788. if buf[pos+1] == '\136':
  789. if buf[pos+2] == '\152': result = 3.m # ∘
  790. elif buf[pos+2] == '\153': result = 3.m # ∙
  791. elif buf[pos+2] == '\167': result = 3.m # ∧
  792. elif buf[pos+2] == '\168': result = 3.a # ∨
  793. elif buf[pos+2] == '\169': result = 3.m # ∩
  794. elif buf[pos+2] == '\170': result = 3.a # ∪
  795. elif buf[pos+1] == '\138':
  796. if buf[pos+2] == '\147': result = 3.m # ⊓
  797. elif buf[pos+2] == '\148': result = 3.a # ⊔
  798. elif buf[pos+2] == '\149': result = 3.a # ⊕
  799. elif buf[pos+2] == '\150': result = 3.a # ⊖
  800. elif buf[pos+2] == '\151': result = 3.m # ⊗
  801. elif buf[pos+2] == '\152': result = 3.m # ⊘
  802. elif buf[pos+2] == '\153': result = 3.m # ⊙
  803. elif buf[pos+2] == '\155': result = 3.m # ⊛
  804. elif buf[pos+2] == '\158': result = 3.a # ⊞
  805. elif buf[pos+2] == '\159': result = 3.a # ⊟
  806. elif buf[pos+2] == '\160': result = 3.m # ⊠
  807. elif buf[pos+2] == '\161': result = 3.m # ⊡
  808. elif buf[pos+1] == '\152' and buf[pos+2] == '\133': result = 3.m # ★
  809. of '\194':
  810. if buf[pos+1] == '\177': result = 2.a # ±
  811. of '\195':
  812. if buf[pos+1] == '\151': result = 2.m # ×
  813. else:
  814. discard
  815. proc getSymbol(L: var Lexer, tok: var Token) =
  816. var h: Hash = 0
  817. var pos = L.bufpos
  818. tokenBegin(tok, pos)
  819. var suspicious = false
  820. while true:
  821. var c = L.buf[pos]
  822. case c
  823. of 'a'..'z', '0'..'9':
  824. h = h !& ord(c)
  825. inc(pos)
  826. of 'A'..'Z':
  827. c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
  828. h = h !& ord(c)
  829. inc(pos)
  830. suspicious = true
  831. of '_':
  832. if L.buf[pos+1] notin SymChars:
  833. lexMessage(L, errGenerated, "invalid token: trailing underscore")
  834. break
  835. inc(pos)
  836. suspicious = true
  837. of '\x80'..'\xFF':
  838. if c in UnicodeOperatorStartChars and unicodeOprLen(L.buf, pos)[0] != 0:
  839. break
  840. else:
  841. h = h !& ord(c)
  842. inc(pos)
  843. else: break
  844. tokenEnd(tok, pos-1)
  845. h = !$h
  846. tok.ident = L.cache.getIdent(cast[cstring](addr(L.buf[L.bufpos])), pos - L.bufpos, h)
  847. if (tok.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or
  848. (tok.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)):
  849. tok.tokType = tkSymbol
  850. else:
  851. tok.tokType = TokType(tok.ident.id + ord(tkSymbol))
  852. if suspicious and {optStyleHint, optStyleError} * L.config.globalOptions != {}:
  853. lintReport(L.config, getLineInfo(L), tok.ident.s.normalize, tok.ident.s)
  854. L.bufpos = pos
  855. proc endOperator(L: var Lexer, tok: var Token, pos: int,
  856. hash: Hash) {.inline.} =
  857. var h = !$hash
  858. tok.ident = L.cache.getIdent(cast[cstring](addr(L.buf[L.bufpos])), pos - L.bufpos, h)
  859. if (tok.ident.id < oprLow) or (tok.ident.id > oprHigh): tok.tokType = tkOpr
  860. else: tok.tokType = TokType(tok.ident.id - oprLow + ord(tkColon))
  861. L.bufpos = pos
  862. proc getOperator(L: var Lexer, tok: var Token) =
  863. var pos = L.bufpos
  864. tokenBegin(tok, pos)
  865. var h: Hash = 0
  866. while true:
  867. var c = L.buf[pos]
  868. if c in OpChars:
  869. h = h !& ord(c)
  870. inc(pos)
  871. elif c in UnicodeOperatorStartChars:
  872. let oprLen = unicodeOprLen(L.buf, pos)[0]
  873. if oprLen == 0: break
  874. for i in 0..<oprLen:
  875. h = h !& ord(L.buf[pos])
  876. inc pos
  877. else:
  878. break
  879. endOperator(L, tok, pos, h)
  880. tokenEnd(tok, pos-1)
  881. # advance pos but don't store it in L.bufpos so the next token (which might
  882. # be an operator too) gets the preceding spaces:
  883. tok.strongSpaceB = tsNone
  884. while L.buf[pos] == ' ':
  885. inc pos
  886. if tok.strongSpaceB != tsTrailing:
  887. tok.strongSpaceB = tsTrailing
  888. if L.buf[pos] in {CR, LF, nimlexbase.EndOfFile}:
  889. tok.strongSpaceB = tsEof
  890. proc getPrecedence*(tok: Token): int =
  891. ## Calculates the precedence of the given token.
  892. const
  893. MulPred = 9
  894. PlusPred = 8
  895. case tok.tokType
  896. of tkOpr:
  897. let relevantChar = tok.ident.s[0]
  898. # arrow like?
  899. if tok.ident.s.len > 1 and tok.ident.s[^1] == '>' and
  900. tok.ident.s[^2] in {'-', '~', '='}: return 0
  901. template considerAsgn(value: untyped) =
  902. result = if tok.ident.s[^1] == '=': 1 else: value
  903. case relevantChar
  904. of '$', '^': considerAsgn(10)
  905. of '*', '%', '/', '\\': considerAsgn(MulPred)
  906. of '~': result = 8
  907. of '+', '-', '|': considerAsgn(PlusPred)
  908. of '&': considerAsgn(7)
  909. of '=', '<', '>', '!': result = 5
  910. of '.': considerAsgn(6)
  911. of '?': result = 2
  912. of UnicodeOperatorStartChars:
  913. if tok.ident.s[^1] == '=':
  914. result = 1
  915. else:
  916. let (len, pred) = unicodeOprLen(cstring(tok.ident.s), 0)
  917. if len != 0:
  918. result = if pred == Mul: MulPred else: PlusPred
  919. else:
  920. result = 2
  921. else: considerAsgn(2)
  922. of tkDiv, tkMod, tkShl, tkShr: result = 9
  923. of tkDotDot: result = 6
  924. of tkIn, tkNotin, tkIs, tkIsnot, tkOf, tkAs, tkFrom: result = 5
  925. of tkAnd: result = 4
  926. of tkOr, tkXor, tkPtr, tkRef: result = 3
  927. else: return -10
  928. proc newlineFollows*(L: Lexer): bool =
  929. var pos = L.bufpos
  930. while true:
  931. case L.buf[pos]
  932. of ' ', '\t':
  933. inc(pos)
  934. of CR, LF:
  935. result = true
  936. break
  937. of '#':
  938. inc(pos)
  939. if L.buf[pos] == '#': inc(pos)
  940. if L.buf[pos] != '[': return true
  941. else:
  942. break
  943. proc skipMultiLineComment(L: var Lexer; tok: var Token; start: int;
  944. isDoc: bool) =
  945. var pos = start
  946. var toStrip = 0
  947. tokenBegin(tok, pos)
  948. # detect the amount of indentation:
  949. if isDoc:
  950. toStrip = getColNumber(L, pos)
  951. while L.buf[pos] == ' ':
  952. inc pos
  953. inc toStrip
  954. while L.buf[pos] in {CR, LF}: # skip blank lines
  955. pos = handleCRLF(L, pos)
  956. toStrip = 0
  957. while L.buf[pos] == ' ':
  958. inc pos
  959. inc toStrip
  960. var nesting = 0
  961. while true:
  962. case L.buf[pos]
  963. of '#':
  964. if isDoc:
  965. if L.buf[pos+1] == '#' and L.buf[pos+2] == '[':
  966. inc nesting
  967. tok.literal.add '#'
  968. elif L.buf[pos+1] == '[':
  969. inc nesting
  970. inc pos
  971. of ']':
  972. if isDoc:
  973. if L.buf[pos+1] == '#' and L.buf[pos+2] == '#':
  974. if nesting == 0:
  975. tokenEndIgnore(tok, pos+2)
  976. inc(pos, 3)
  977. break
  978. dec nesting
  979. tok.literal.add ']'
  980. elif L.buf[pos+1] == '#':
  981. if nesting == 0:
  982. tokenEndIgnore(tok, pos+1)
  983. inc(pos, 2)
  984. break
  985. dec nesting
  986. inc pos
  987. of CR, LF:
  988. tokenEndIgnore(tok, pos)
  989. pos = handleCRLF(L, pos)
  990. # strip leading whitespace:
  991. when defined(nimpretty): tok.literal.add "\L"
  992. if isDoc:
  993. when not defined(nimpretty): tok.literal.add "\n"
  994. inc tok.iNumber
  995. var c = toStrip
  996. while L.buf[pos] == ' ' and c > 0:
  997. inc pos
  998. dec c
  999. of nimlexbase.EndOfFile:
  1000. tokenEndIgnore(tok, pos)
  1001. lexMessagePos(L, errGenerated, pos, "end of multiline comment expected")
  1002. break
  1003. else:
  1004. if isDoc or defined(nimpretty): tok.literal.add L.buf[pos]
  1005. inc(pos)
  1006. L.bufpos = pos
  1007. when defined(nimpretty):
  1008. tok.commentOffsetB = L.offsetBase + pos - 1
  1009. proc scanComment(L: var Lexer, tok: var Token) =
  1010. var pos = L.bufpos
  1011. tok.tokType = tkComment
  1012. # iNumber contains the number of '\n' in the token
  1013. tok.iNumber = 0
  1014. assert L.buf[pos+1] == '#'
  1015. when defined(nimpretty):
  1016. tok.commentOffsetA = L.offsetBase + pos
  1017. if L.buf[pos+2] == '[':
  1018. skipMultiLineComment(L, tok, pos+3, true)
  1019. return
  1020. tokenBegin(tok, pos)
  1021. inc(pos, 2)
  1022. var toStrip = 0
  1023. var stripInit = false
  1024. while true:
  1025. if not stripInit: # find baseline indentation inside comment
  1026. while L.buf[pos] == ' ':
  1027. inc pos
  1028. inc toStrip
  1029. if L.buf[pos] in {CR, LF}: # don't set toStrip in blank comment lines
  1030. toStrip = 0
  1031. else: # found first non-whitespace character
  1032. stripInit = true
  1033. var lastBackslash = -1
  1034. while L.buf[pos] notin {CR, LF, nimlexbase.EndOfFile}:
  1035. if L.buf[pos] == '\\': lastBackslash = pos+1
  1036. tok.literal.add(L.buf[pos])
  1037. inc(pos)
  1038. tokenEndIgnore(tok, pos)
  1039. pos = handleCRLF(L, pos)
  1040. var indent = 0
  1041. while L.buf[pos] == ' ':
  1042. inc(pos)
  1043. inc(indent)
  1044. if L.buf[pos] == '#' and L.buf[pos+1] == '#':
  1045. tok.literal.add "\n"
  1046. inc(pos, 2)
  1047. if stripInit:
  1048. var c = toStrip
  1049. while L.buf[pos] == ' ' and c > 0:
  1050. inc pos
  1051. dec c
  1052. inc tok.iNumber
  1053. else:
  1054. if L.buf[pos] > ' ':
  1055. L.indentAhead = indent
  1056. tokenEndIgnore(tok, pos)
  1057. break
  1058. L.bufpos = pos
  1059. when defined(nimpretty):
  1060. tok.commentOffsetB = L.offsetBase + pos - 1
  1061. proc skip(L: var Lexer, tok: var Token) =
  1062. var pos = L.bufpos
  1063. tokenBegin(tok, pos)
  1064. tok.strongSpaceA = false
  1065. when defined(nimpretty):
  1066. var hasComment = false
  1067. var commentIndent = L.currLineIndent
  1068. tok.commentOffsetA = L.offsetBase + pos
  1069. tok.commentOffsetB = tok.commentOffsetA
  1070. tok.line = -1
  1071. while true:
  1072. case L.buf[pos]
  1073. of ' ':
  1074. inc(pos)
  1075. if not tok.strongSpaceA:
  1076. tok.strongSpaceA = true
  1077. of '\t':
  1078. if not L.allowTabs: lexMessagePos(L, errGenerated, pos, "tabs are not allowed, use spaces instead")
  1079. inc(pos)
  1080. of CR, LF:
  1081. tokenEndPrevious(tok, pos)
  1082. pos = handleCRLF(L, pos)
  1083. var indent = 0
  1084. while true:
  1085. if L.buf[pos] == ' ':
  1086. inc(pos)
  1087. inc(indent)
  1088. elif L.buf[pos] == '#' and L.buf[pos+1] == '[':
  1089. when defined(nimpretty):
  1090. hasComment = true
  1091. if tok.line < 0:
  1092. tok.line = L.lineNumber
  1093. commentIndent = indent
  1094. skipMultiLineComment(L, tok, pos+2, false)
  1095. pos = L.bufpos
  1096. else:
  1097. break
  1098. tok.strongSpaceA = false
  1099. when defined(nimpretty):
  1100. if L.buf[pos] == '#' and tok.line < 0: commentIndent = indent
  1101. if L.buf[pos] > ' ' and (L.buf[pos] != '#' or L.buf[pos+1] == '#'):
  1102. tok.indent = indent
  1103. L.currLineIndent = indent
  1104. break
  1105. of '#':
  1106. # do not skip documentation comment:
  1107. if L.buf[pos+1] == '#': break
  1108. when defined(nimpretty):
  1109. hasComment = true
  1110. if tok.line < 0:
  1111. tok.line = L.lineNumber
  1112. if L.buf[pos+1] == '[':
  1113. skipMultiLineComment(L, tok, pos+2, false)
  1114. pos = L.bufpos
  1115. else:
  1116. tokenBegin(tok, pos)
  1117. while L.buf[pos] notin {CR, LF, nimlexbase.EndOfFile}:
  1118. when defined(nimpretty): tok.literal.add L.buf[pos]
  1119. inc(pos)
  1120. tokenEndIgnore(tok, pos+1)
  1121. when defined(nimpretty):
  1122. tok.commentOffsetB = L.offsetBase + pos + 1
  1123. else:
  1124. break # EndOfFile also leaves the loop
  1125. tokenEndPrevious(tok, pos-1)
  1126. L.bufpos = pos
  1127. when defined(nimpretty):
  1128. if hasComment:
  1129. tok.commentOffsetB = L.offsetBase + pos - 1
  1130. tok.tokType = tkComment
  1131. tok.indent = commentIndent
  1132. proc rawGetTok*(L: var Lexer, tok: var Token) =
  1133. template atTokenEnd() {.dirty.} =
  1134. when defined(nimsuggest):
  1135. # we attach the cursor to the last *strong* token
  1136. if tok.tokType notin weakTokens:
  1137. L.previousToken.line = tok.line.uint16
  1138. L.previousToken.col = tok.col.int16
  1139. fillToken(tok)
  1140. if L.indentAhead >= 0:
  1141. tok.indent = L.indentAhead
  1142. L.currLineIndent = L.indentAhead
  1143. L.indentAhead = -1
  1144. else:
  1145. tok.indent = -1
  1146. skip(L, tok)
  1147. when defined(nimpretty):
  1148. if tok.tokType == tkComment:
  1149. L.indentAhead = L.currLineIndent
  1150. return
  1151. var c = L.buf[L.bufpos]
  1152. tok.line = L.lineNumber
  1153. tok.col = getColNumber(L, L.bufpos)
  1154. if c in SymStartChars - {'r', 'R'} - UnicodeOperatorStartChars:
  1155. getSymbol(L, tok)
  1156. else:
  1157. case c
  1158. of UnicodeOperatorStartChars:
  1159. if unicodeOprLen(L.buf, L.bufpos)[0] != 0:
  1160. getOperator(L, tok)
  1161. else:
  1162. getSymbol(L, tok)
  1163. of '#':
  1164. scanComment(L, tok)
  1165. of '*':
  1166. # '*:' is unfortunately a special case, because it is two tokens in
  1167. # 'var v*: int'.
  1168. if L.buf[L.bufpos+1] == ':' and L.buf[L.bufpos+2] notin OpChars:
  1169. var h = 0 !& ord('*')
  1170. endOperator(L, tok, L.bufpos+1, h)
  1171. else:
  1172. getOperator(L, tok)
  1173. of ',':
  1174. tok.tokType = tkComma
  1175. inc(L.bufpos)
  1176. of 'r', 'R':
  1177. if L.buf[L.bufpos + 1] == '\"':
  1178. inc(L.bufpos)
  1179. getString(L, tok, raw)
  1180. else:
  1181. getSymbol(L, tok)
  1182. of '(':
  1183. inc(L.bufpos)
  1184. if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
  1185. tok.tokType = tkParDotLe
  1186. inc(L.bufpos)
  1187. else:
  1188. tok.tokType = tkParLe
  1189. when defined(nimsuggest):
  1190. if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col < L.config.m.trackPos.col and
  1191. tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideCon:
  1192. L.config.m.trackPos.col = tok.col.int16
  1193. of ')':
  1194. tok.tokType = tkParRi
  1195. inc(L.bufpos)
  1196. of '[':
  1197. inc(L.bufpos)
  1198. if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
  1199. tok.tokType = tkBracketDotLe
  1200. inc(L.bufpos)
  1201. elif L.buf[L.bufpos] == ':':
  1202. tok.tokType = tkBracketLeColon
  1203. inc(L.bufpos)
  1204. else:
  1205. tok.tokType = tkBracketLe
  1206. of ']':
  1207. tok.tokType = tkBracketRi
  1208. inc(L.bufpos)
  1209. of '.':
  1210. when defined(nimsuggest):
  1211. if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col+1 == L.config.m.trackPos.col and
  1212. tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideSug:
  1213. tok.tokType = tkDot
  1214. L.config.m.trackPos.col = tok.col.int16
  1215. inc(L.bufpos)
  1216. atTokenEnd()
  1217. return
  1218. if L.buf[L.bufpos+1] == ']':
  1219. tok.tokType = tkBracketDotRi
  1220. inc(L.bufpos, 2)
  1221. elif L.buf[L.bufpos+1] == '}':
  1222. tok.tokType = tkCurlyDotRi
  1223. inc(L.bufpos, 2)
  1224. elif L.buf[L.bufpos+1] == ')':
  1225. tok.tokType = tkParDotRi
  1226. inc(L.bufpos, 2)
  1227. else:
  1228. getOperator(L, tok)
  1229. of '{':
  1230. inc(L.bufpos)
  1231. if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
  1232. tok.tokType = tkCurlyDotLe
  1233. inc(L.bufpos)
  1234. else:
  1235. tok.tokType = tkCurlyLe
  1236. of '}':
  1237. tok.tokType = tkCurlyRi
  1238. inc(L.bufpos)
  1239. of ';':
  1240. tok.tokType = tkSemiColon
  1241. inc(L.bufpos)
  1242. of '`':
  1243. tok.tokType = tkAccent
  1244. inc(L.bufpos)
  1245. of '_':
  1246. inc(L.bufpos)
  1247. if L.buf[L.bufpos] notin SymChars+{'_'}:
  1248. tok.tokType = tkSymbol
  1249. tok.ident = L.cache.getIdent("_")
  1250. else:
  1251. tok.literal = $c
  1252. tok.tokType = tkInvalid
  1253. lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')')
  1254. of '\"':
  1255. # check for generalized raw string literal:
  1256. let mode = if L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars: generalized else: normal
  1257. getString(L, tok, mode)
  1258. if mode == generalized:
  1259. # tkRStrLit -> tkGStrLit
  1260. # tkTripleStrLit -> tkGTripleStrLit
  1261. inc(tok.tokType, 2)
  1262. of '\'':
  1263. tok.tokType = tkCharLit
  1264. getCharacter(L, tok)
  1265. tok.tokType = tkCharLit
  1266. of '0'..'9':
  1267. getNumber(L, tok)
  1268. let c = L.buf[L.bufpos]
  1269. if c in SymChars+{'_'}:
  1270. if c in UnicodeOperatorStartChars and
  1271. unicodeOprLen(L.buf, L.bufpos)[0] != 0:
  1272. discard
  1273. else:
  1274. lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
  1275. of '-':
  1276. if L.buf[L.bufpos+1] in {'0'..'9'} and
  1277. (L.bufpos-1 == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
  1278. # x)-23 # binary minus
  1279. # ,-23 # unary minus
  1280. # \n-78 # unary minus? Yes.
  1281. # =-3 # parsed as `=-` anyway
  1282. getNumber(L, tok)
  1283. let c = L.buf[L.bufpos]
  1284. if c in SymChars+{'_'}:
  1285. if c in UnicodeOperatorStartChars and
  1286. unicodeOprLen(L.buf, L.bufpos)[0] != 0:
  1287. discard
  1288. else:
  1289. lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
  1290. else:
  1291. getOperator(L, tok)
  1292. else:
  1293. if c in OpChars:
  1294. getOperator(L, tok)
  1295. elif c == nimlexbase.EndOfFile:
  1296. tok.tokType = tkEof
  1297. tok.indent = 0
  1298. else:
  1299. tok.literal = $c
  1300. tok.tokType = tkInvalid
  1301. lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')')
  1302. inc(L.bufpos)
  1303. atTokenEnd()
  1304. proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream;
  1305. cache: IdentCache; config: ConfigRef): int =
  1306. var lex: Lexer
  1307. var tok: Token
  1308. initToken(tok)
  1309. openLexer(lex, fileIdx, inputstream, cache, config)
  1310. var prevToken = tkEof
  1311. while tok.tokType != tkEof:
  1312. rawGetTok(lex, tok)
  1313. if tok.indent > 0 and prevToken in {tkColon, tkEquals, tkType, tkConst, tkLet, tkVar, tkUsing}:
  1314. result = tok.indent
  1315. if result > 0: break
  1316. prevToken = tok.tokType
  1317. closeLexer(lex)
  1318. proc getPrecedence*(ident: PIdent): int =
  1319. ## assumes ident is binary operator already
  1320. var tok: Token
  1321. initToken(tok)
  1322. tok.ident = ident
  1323. tok.tokType =
  1324. if tok.ident.id in ord(tokKeywordLow) - ord(tkSymbol)..ord(tokKeywordHigh) - ord(tkSymbol):
  1325. TokType(tok.ident.id + ord(tkSymbol))
  1326. else: tkOpr
  1327. getPrecedence(tok)