lexer.nim 44 KB

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