lexer.nim 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356
  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, parseutils
  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. proc getLineInfo*(L: TLexer, tok: TToken): TLineInfo {.inline.} =
  138. result = newLineInfo(L.fileIdx, tok.line, tok.col)
  139. when defined(nimpretty):
  140. result.offsetA = tok.offsetA
  141. result.offsetB = tok.offsetB
  142. result.commentOffsetA = tok.commentOffsetA
  143. result.commentOffsetB = tok.commentOffsetB
  144. proc isKeyword*(kind: TTokType): bool =
  145. result = (kind >= tokKeywordLow) and (kind <= tokKeywordHigh)
  146. template ones(n): untyped = ((1 shl n)-1) # for utf-8 conversion
  147. proc isNimIdentifier*(s: string): bool =
  148. let sLen = s.len
  149. if sLen > 0 and s[0] in SymStartChars:
  150. var i = 1
  151. while i < sLen:
  152. if s[i] == '_': inc(i)
  153. if i < sLen and s[i] notin SymChars: return
  154. inc(i)
  155. result = true
  156. proc `$`*(tok: TToken): string =
  157. case tok.tokType
  158. of tkIntLit..tkInt64Lit: result = $tok.iNumber
  159. of tkFloatLit..tkFloat64Lit: result = $tok.fNumber
  160. of tkInvalid, tkStrLit..tkCharLit, tkComment: result = tok.literal
  161. of tkParLe..tkColon, tkEof, tkAccent:
  162. result = TokTypeToStr[tok.tokType]
  163. else:
  164. if tok.ident != nil:
  165. result = tok.ident.s
  166. else:
  167. result = ""
  168. proc prettyTok*(tok: TToken): string =
  169. if isKeyword(tok.tokType): result = "keyword " & tok.ident.s
  170. else: result = $tok
  171. proc printTok*(conf: ConfigRef; tok: TToken) =
  172. msgWriteln(conf, $tok.line & ":" & $tok.col & "\t" &
  173. TokTypeToStr[tok.tokType] & " " & $tok)
  174. proc initToken*(L: var TToken) =
  175. L.tokType = tkInvalid
  176. L.iNumber = 0
  177. L.indent = 0
  178. L.strongSpaceA = 0
  179. L.literal = ""
  180. L.fNumber = 0.0
  181. L.base = base10
  182. L.ident = nil
  183. when defined(nimpretty):
  184. L.commentOffsetA = 0
  185. L.commentOffsetB = 0
  186. proc fillToken(L: var TToken) =
  187. L.tokType = tkInvalid
  188. L.iNumber = 0
  189. L.indent = 0
  190. L.strongSpaceA = 0
  191. setLen(L.literal, 0)
  192. L.fNumber = 0.0
  193. L.base = base10
  194. L.ident = nil
  195. when defined(nimpretty):
  196. L.commentOffsetA = 0
  197. L.commentOffsetB = 0
  198. proc openLexer*(lex: var TLexer, fileIdx: FileIndex, inputstream: PLLStream;
  199. cache: IdentCache; config: ConfigRef) =
  200. openBaseLexer(lex, inputstream)
  201. lex.fileIdx = fileIdx
  202. lex.indentAhead = -1
  203. lex.currLineIndent = 0
  204. inc(lex.lineNumber, inputstream.lineOffset)
  205. lex.cache = cache
  206. when defined(nimsuggest):
  207. lex.previousToken.fileIndex = fileIdx
  208. lex.config = config
  209. proc openLexer*(lex: var TLexer, filename: AbsoluteFile, inputstream: PLLStream;
  210. cache: IdentCache; config: ConfigRef) =
  211. openLexer(lex, fileInfoIdx(config, filename), inputstream, cache, config)
  212. proc closeLexer*(lex: var TLexer) =
  213. if lex.config != nil:
  214. inc(lex.config.linesCompiled, lex.lineNumber)
  215. closeBaseLexer(lex)
  216. proc getLineInfo(L: TLexer): TLineInfo =
  217. result = newLineInfo(L.fileIdx, L.lineNumber, getColNumber(L, L.bufpos))
  218. proc dispMessage(L: TLexer; info: TLineInfo; msg: TMsgKind; arg: string) =
  219. if L.errorHandler.isNil:
  220. msgs.message(L.config, info, msg, arg)
  221. else:
  222. L.errorHandler(L.config, info, msg, arg)
  223. proc lexMessage*(L: TLexer, msg: TMsgKind, arg = "") =
  224. L.dispMessage(getLineInfo(L), msg, arg)
  225. proc lexMessageTok*(L: TLexer, msg: TMsgKind, tok: TToken, arg = "") =
  226. var info = newLineInfo(L.fileIdx, tok.line, tok.col)
  227. L.dispMessage(info, msg, arg)
  228. proc lexMessagePos(L: var TLexer, msg: TMsgKind, pos: int, arg = "") =
  229. var info = newLineInfo(L.fileIdx, L.lineNumber, pos - L.lineStart)
  230. L.dispMessage(info, msg, arg)
  231. proc matchTwoChars(L: TLexer, first: char, second: set[char]): bool =
  232. result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second)
  233. template tokenBegin(tok, pos) {.dirty.} =
  234. when defined(nimsuggest):
  235. var colA = getColNumber(L, pos)
  236. when defined(nimpretty):
  237. tok.offsetA = L.offsetBase + pos
  238. template tokenEnd(tok, pos) {.dirty.} =
  239. when defined(nimsuggest):
  240. let colB = getColNumber(L, pos)+1
  241. if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
  242. L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
  243. L.cursor = CursorPosition.InToken
  244. L.config.m.trackPos.col = colA.int16
  245. colA = 0
  246. when defined(nimpretty):
  247. tok.offsetB = L.offsetBase + pos
  248. template tokenEndIgnore(tok, pos) =
  249. when defined(nimsuggest):
  250. let colB = getColNumber(L, pos)
  251. if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
  252. L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
  253. L.config.m.trackPos.fileIndex = trackPosInvalidFileIdx
  254. L.config.m.trackPos.line = 0'u16
  255. colA = 0
  256. when defined(nimpretty):
  257. tok.offsetB = L.offsetBase + pos
  258. template tokenEndPrevious(tok, pos) =
  259. when defined(nimsuggest):
  260. # when we detect the cursor in whitespace, we attach the track position
  261. # to the token that came before that, but only if we haven't detected
  262. # the cursor in a string literal or comment:
  263. let colB = getColNumber(L, pos)
  264. if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
  265. L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
  266. L.cursor = CursorPosition.BeforeToken
  267. L.config.m.trackPos = L.previousToken
  268. L.config.m.trackPosAttached = true
  269. colA = 0
  270. when defined(nimpretty):
  271. tok.offsetB = L.offsetBase + pos
  272. template eatChar(L: var TLexer, t: var TToken, replacementChar: char) =
  273. add(t.literal, replacementChar)
  274. inc(L.bufpos)
  275. template eatChar(L: var TLexer, t: var TToken) =
  276. add(t.literal, L.buf[L.bufpos])
  277. inc(L.bufpos)
  278. proc getNumber(L: var TLexer, result: var TToken) =
  279. proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]): Natural =
  280. var pos = L.bufpos # use registers for pos, buf
  281. result = 0
  282. while true:
  283. if L.buf[pos] in chars:
  284. add(tok.literal, L.buf[pos])
  285. inc(pos)
  286. inc(result)
  287. else:
  288. break
  289. if L.buf[pos] == '_':
  290. if L.buf[pos+1] notin chars:
  291. lexMessage(L, errGenerated,
  292. "only single underscores may occur in a token and token may not " &
  293. "end with an underscore: e.g. '1__1' and '1_' are invalid")
  294. break
  295. add(tok.literal, '_')
  296. inc(pos)
  297. L.bufpos = pos
  298. proc matchChars(L: var TLexer, tok: var TToken, chars: set[char]) =
  299. var pos = L.bufpos # use registers for pos, buf
  300. while L.buf[pos] in chars:
  301. add(tok.literal, L.buf[pos])
  302. inc(pos)
  303. L.bufpos = pos
  304. proc lexMessageLitNum(L: var TLexer, msg: string, startpos: int, msgKind = errGenerated) =
  305. # Used to get slightly human friendlier err messages.
  306. const literalishChars = {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x', 'o', 'O',
  307. 'c', 'C', 'b', 'B', '_', '.', '\'', 'd', 'i', 'u'}
  308. var msgPos = L.bufpos
  309. var t: TToken
  310. t.literal = ""
  311. L.bufpos = startpos # Use L.bufpos as pos because of matchChars
  312. matchChars(L, t, literalishChars)
  313. # We must verify +/- specifically so that we're not past the literal
  314. if L.buf[L.bufpos] in {'+', '-'} and
  315. L.buf[L.bufpos - 1] in {'e', 'E'}:
  316. add(t.literal, L.buf[L.bufpos])
  317. inc(L.bufpos)
  318. matchChars(L, t, literalishChars)
  319. if L.buf[L.bufpos] in {'\'', 'f', 'F', 'd', 'D', 'i', 'I', 'u', 'U'}:
  320. inc(L.bufpos)
  321. add(t.literal, L.buf[L.bufpos])
  322. matchChars(L, t, {'0'..'9'})
  323. L.bufpos = msgPos
  324. lexMessage(L, msgKind, msg % t.literal)
  325. var
  326. startpos, endpos: int
  327. xi: BiggestInt
  328. isBase10 = true
  329. numDigits = 0
  330. const
  331. # 'c', 'C' is deprecated
  332. baseCodeChars = {'X', 'x', 'o', 'b', 'B', 'c', 'C'}
  333. literalishChars = baseCodeChars + {'A'..'F', 'a'..'f', '0'..'9', '_', '\''}
  334. floatTypes = {tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit}
  335. result.tokType = tkIntLit # int literal until we know better
  336. result.literal = ""
  337. result.base = base10
  338. startpos = L.bufpos
  339. tokenBegin(result, startpos)
  340. # First stage: find out base, make verifications, build token literal string
  341. # {'c', 'C'} is added for deprecation reasons to provide a clear error message
  342. if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'c', 'C', 'O'}:
  343. isBase10 = false
  344. eatChar(L, result, '0')
  345. case L.buf[L.bufpos]
  346. of 'c', 'C':
  347. lexMessageLitNum(L,
  348. "$1 will soon be invalid for oct literals; Use '0o' " &
  349. "for octals. 'c', 'C' prefix",
  350. startpos,
  351. warnDeprecated)
  352. eatChar(L, result, 'c')
  353. numDigits = matchUnderscoreChars(L, result, {'0'..'7'})
  354. of 'O':
  355. lexMessageLitNum(L, "$1 is an invalid int literal; For octal literals " &
  356. "use the '0o' prefix.", startpos)
  357. of 'x', 'X':
  358. eatChar(L, result, 'x')
  359. numDigits = matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'})
  360. of 'o':
  361. eatChar(L, result, 'o')
  362. numDigits = matchUnderscoreChars(L, result, {'0'..'7'})
  363. of 'b', 'B':
  364. eatChar(L, result, 'b')
  365. numDigits = matchUnderscoreChars(L, result, {'0'..'1'})
  366. else:
  367. internalError(L.config, getLineInfo(L), "getNumber")
  368. if numDigits == 0:
  369. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  370. else:
  371. discard matchUnderscoreChars(L, result, {'0'..'9'})
  372. if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}):
  373. result.tokType = tkFloatLit
  374. eatChar(L, result, '.')
  375. discard matchUnderscoreChars(L, result, {'0'..'9'})
  376. if L.buf[L.bufpos] in {'e', 'E'}:
  377. result.tokType = tkFloatLit
  378. eatChar(L, result, 'e')
  379. if L.buf[L.bufpos] in {'+', '-'}:
  380. eatChar(L, result)
  381. discard matchUnderscoreChars(L, result, {'0'..'9'})
  382. endpos = L.bufpos
  383. # Second stage, find out if there's a datatype suffix and handle it
  384. var postPos = endpos
  385. if L.buf[postPos] in {'\'', 'f', 'F', 'd', 'D', 'i', 'I', 'u', 'U'}:
  386. if L.buf[postPos] == '\'':
  387. inc(postPos)
  388. case L.buf[postPos]
  389. of 'f', 'F':
  390. inc(postPos)
  391. if (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
  392. result.tokType = tkFloat32Lit
  393. inc(postPos, 2)
  394. elif (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
  395. result.tokType = tkFloat64Lit
  396. inc(postPos, 2)
  397. elif (L.buf[postPos] == '1') and
  398. (L.buf[postPos + 1] == '2') and
  399. (L.buf[postPos + 2] == '8'):
  400. result.tokType = tkFloat128Lit
  401. inc(postPos, 3)
  402. else: # "f" alone defaults to float32
  403. result.tokType = tkFloat32Lit
  404. of 'd', 'D': # ad hoc convenience shortcut for f64
  405. inc(postPos)
  406. result.tokType = tkFloat64Lit
  407. of 'i', 'I':
  408. inc(postPos)
  409. if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
  410. result.tokType = tkInt64Lit
  411. inc(postPos, 2)
  412. elif (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
  413. result.tokType = tkInt32Lit
  414. inc(postPos, 2)
  415. elif (L.buf[postPos] == '1') and (L.buf[postPos + 1] == '6'):
  416. result.tokType = tkInt16Lit
  417. inc(postPos, 2)
  418. elif (L.buf[postPos] == '8'):
  419. result.tokType = tkInt8Lit
  420. inc(postPos)
  421. else:
  422. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  423. of 'u', 'U':
  424. inc(postPos)
  425. if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
  426. result.tokType = tkUInt64Lit
  427. inc(postPos, 2)
  428. elif (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
  429. result.tokType = tkUInt32Lit
  430. inc(postPos, 2)
  431. elif (L.buf[postPos] == '1') and (L.buf[postPos + 1] == '6'):
  432. result.tokType = tkUInt16Lit
  433. inc(postPos, 2)
  434. elif (L.buf[postPos] == '8'):
  435. result.tokType = tkUInt8Lit
  436. inc(postPos)
  437. else:
  438. result.tokType = tkUIntLit
  439. else:
  440. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  441. # Is there still a literalish char awaiting? Then it's an error!
  442. if L.buf[postPos] in literalishChars or
  443. (L.buf[postPos] == '.' and L.buf[postPos + 1] in {'0'..'9'}):
  444. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  445. # Third stage, extract actual number
  446. L.bufpos = startpos # restore position
  447. var pos: int = startpos
  448. try:
  449. if (L.buf[pos] == '0') and (L.buf[pos + 1] in baseCodeChars):
  450. inc(pos, 2)
  451. xi = 0 # it is a base prefix
  452. case L.buf[pos - 1]
  453. of 'b', 'B':
  454. result.base = base2
  455. while pos < endpos:
  456. if L.buf[pos] != '_':
  457. xi = `shl`(xi, 1) or (ord(L.buf[pos]) - ord('0'))
  458. inc(pos)
  459. # 'c', 'C' is deprecated
  460. of 'o', 'c', 'C':
  461. result.base = base8
  462. while pos < endpos:
  463. if L.buf[pos] != '_':
  464. xi = `shl`(xi, 3) or (ord(L.buf[pos]) - ord('0'))
  465. inc(pos)
  466. of 'x', 'X':
  467. result.base = base16
  468. while pos < endpos:
  469. case L.buf[pos]
  470. of '_':
  471. inc(pos)
  472. of '0'..'9':
  473. xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('0'))
  474. inc(pos)
  475. of 'a'..'f':
  476. xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('a') + 10)
  477. inc(pos)
  478. of 'A'..'F':
  479. xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('A') + 10)
  480. inc(pos)
  481. else:
  482. break
  483. else:
  484. internalError(L.config, getLineInfo(L), "getNumber")
  485. case result.tokType
  486. of tkIntLit, tkInt64Lit: result.iNumber = xi
  487. of tkInt8Lit: result.iNumber = ashr(xi shl 56, 56)
  488. of tkInt16Lit: result.iNumber = ashr(xi shl 48, 48)
  489. of tkInt32Lit: result.iNumber = ashr(xi shl 32, 32)
  490. of tkUIntLit, tkUInt64Lit: result.iNumber = xi
  491. of tkUInt8Lit: result.iNumber = xi and 0xff
  492. of tkUInt16Lit: result.iNumber = xi and 0xffff
  493. of tkUInt32Lit: result.iNumber = xi and 0xffffffff
  494. of tkFloat32Lit:
  495. result.fNumber = (cast[PFloat32](addr(xi)))[]
  496. # note: this code is endian neutral!
  497. # XXX: Test this on big endian machine!
  498. of tkFloat64Lit, tkFloatLit:
  499. result.fNumber = (cast[PFloat64](addr(xi)))[]
  500. else: internalError(L.config, getLineInfo(L), "getNumber")
  501. # Bounds checks. Non decimal literals are allowed to overflow the range of
  502. # the datatype as long as their pattern don't overflow _bitwise_, hence
  503. # below checks of signed sizes against uint*.high is deliberate:
  504. # (0x80'u8 = 128, 0x80'i8 = -128, etc == OK)
  505. if result.tokType notin floatTypes:
  506. let outOfRange = case result.tokType:
  507. of tkUInt8Lit, tkUInt16Lit, tkUInt32Lit: result.iNumber != xi
  508. of tkInt8Lit: (xi > BiggestInt(uint8.high))
  509. of tkInt16Lit: (xi > BiggestInt(uint16.high))
  510. of tkInt32Lit: (xi > BiggestInt(uint32.high))
  511. else: false
  512. if outOfRange:
  513. #echo "out of range num: ", result.iNumber, " vs ", xi
  514. lexMessageLitNum(L, "number out of range: '$1'", startpos)
  515. else:
  516. case result.tokType
  517. of floatTypes:
  518. result.fNumber = parseFloat(result.literal)
  519. of tkUInt64Lit:
  520. var iNumber: uint64
  521. var len: int
  522. try:
  523. len = parseBiggestUInt(result.literal, iNumber)
  524. except ValueError:
  525. raise newException(OverflowError, "number out of range: " & $result.literal)
  526. if len != result.literal.len:
  527. raise newException(ValueError, "invalid integer: " & $result.literal)
  528. result.iNumber = cast[int64](iNumber)
  529. else:
  530. var iNumber: int64
  531. var len: int
  532. try:
  533. len = parseBiggestInt(result.literal, iNumber)
  534. except ValueError:
  535. raise newException(OverflowError, "number out of range: " & $result.literal)
  536. if len != result.literal.len:
  537. raise newException(ValueError, "invalid integer: " & $result.literal)
  538. result.iNumber = iNumber
  539. # Explicit bounds checks. Only T.high needs to be considered
  540. # since result.iNumber can't be negative.
  541. let outOfRange =
  542. case result.tokType
  543. of tkInt8Lit: result.iNumber > int8.high
  544. of tkUInt8Lit: result.iNumber > BiggestInt(uint8.high)
  545. of tkInt16Lit: result.iNumber > int16.high
  546. of tkUInt16Lit: result.iNumber > BiggestInt(uint16.high)
  547. of tkInt32Lit: result.iNumber > int32.high
  548. of tkUInt32Lit: result.iNumber > BiggestInt(uint32.high)
  549. else: false
  550. if outOfRange: lexMessageLitNum(L, "number out of range: '$1'", startpos)
  551. # Promote int literal to int64? Not always necessary, but more consistent
  552. if result.tokType == tkIntLit:
  553. if result.iNumber > high(int32):
  554. result.tokType = tkInt64Lit
  555. except ValueError:
  556. lexMessageLitNum(L, "invalid number: '$1'", startpos)
  557. except OverflowError, RangeError:
  558. lexMessageLitNum(L, "number out of range: '$1'", startpos)
  559. tokenEnd(result, postPos-1)
  560. L.bufpos = postPos
  561. proc handleHexChar(L: var TLexer, xi: var int; position: range[0..4]) =
  562. template invalid() =
  563. lexMessage(L, errGenerated,
  564. "expected a hex digit, but found: " & L.buf[L.bufpos] &
  565. "; maybe prepend with 0")
  566. case L.buf[L.bufpos]
  567. of '0'..'9':
  568. xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('0'))
  569. inc(L.bufpos)
  570. of 'a'..'f':
  571. xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('a') + 10)
  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 '"', '\'':
  577. if position <= 1: invalid()
  578. # do not progress the bufpos here.
  579. if position == 0: inc(L.bufpos)
  580. else:
  581. invalid()
  582. # Need to progress for `nim check`
  583. inc(L.bufpos)
  584. proc handleDecChars(L: var TLexer, xi: var int) =
  585. while L.buf[L.bufpos] in {'0'..'9'}:
  586. xi = (xi * 10) + (ord(L.buf[L.bufpos]) - ord('0'))
  587. inc(L.bufpos)
  588. proc addUnicodeCodePoint(s: var string, i: int) =
  589. let i = cast[uint](i)
  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, 1)
  675. handleHexChar(L, xi, 2)
  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, 0)
  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, 1)
  697. handleHexChar(L, xi, 2)
  698. handleHexChar(L, xi, 3)
  699. handleHexChar(L, xi, 4)
  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 line = L.lineNumber # save linenumber for better error message
  735. tokenBegin(tok, pos - ord(mode == raw))
  736. inc pos # skip "
  737. if L.buf[pos] == '\"' and L.buf[pos+1] == '\"':
  738. tok.tokType = tkTripleStrLit # long string literal:
  739. inc(pos, 2) # skip ""
  740. # skip leading newline:
  741. if L.buf[pos] in {' ', '\t'}:
  742. var newpos = pos+1
  743. while L.buf[newpos] in {' ', '\t'}: inc newpos
  744. if L.buf[newpos] in {CR, LF}: pos = newpos
  745. pos = handleCRLF(L, pos)
  746. while true:
  747. case L.buf[pos]
  748. of '\"':
  749. if L.buf[pos+1] == '\"' and L.buf[pos+2] == '\"' and
  750. L.buf[pos+3] != '\"':
  751. tokenEndIgnore(tok, pos+2)
  752. L.bufpos = pos + 3 # skip the three """
  753. break
  754. add(tok.literal, '\"')
  755. inc(pos)
  756. of CR, LF:
  757. tokenEndIgnore(tok, pos)
  758. pos = handleCRLF(L, pos)
  759. add(tok.literal, "\n")
  760. of nimlexbase.EndOfFile:
  761. tokenEndIgnore(tok, pos)
  762. var line2 = L.lineNumber
  763. L.lineNumber = line
  764. lexMessagePos(L, errGenerated, L.lineStart, "closing \"\"\" expected, but end of file reached")
  765. L.lineNumber = line2
  766. L.bufpos = pos
  767. break
  768. else:
  769. add(tok.literal, L.buf[pos])
  770. inc(pos)
  771. else:
  772. # ordinary string literal
  773. if mode != normal: tok.tokType = tkRStrLit
  774. else: tok.tokType = tkStrLit
  775. while true:
  776. var c = L.buf[pos]
  777. if c == '\"':
  778. if mode != normal and L.buf[pos+1] == '\"':
  779. inc(pos, 2)
  780. add(tok.literal, '"')
  781. else:
  782. tokenEndIgnore(tok, pos)
  783. inc(pos) # skip '"'
  784. break
  785. elif c in {CR, LF, nimlexbase.EndOfFile}:
  786. tokenEndIgnore(tok, pos)
  787. lexMessage(L, errGenerated, "closing \" expected")
  788. break
  789. elif (c == '\\') and mode == normal:
  790. L.bufpos = pos
  791. getEscapedChar(L, tok)
  792. pos = L.bufpos
  793. else:
  794. add(tok.literal, c)
  795. inc(pos)
  796. L.bufpos = pos
  797. proc getCharacter(L: var TLexer, tok: var TToken) =
  798. tokenBegin(tok, L.bufpos)
  799. inc(L.bufpos) # skip '
  800. var c = L.buf[L.bufpos]
  801. case c
  802. of '\0'..pred(' '), '\'': lexMessage(L, errGenerated, "invalid character literal")
  803. of '\\': getEscapedChar(L, tok)
  804. else:
  805. tok.literal = $c
  806. inc(L.bufpos)
  807. if L.buf[L.bufpos] != '\'':
  808. lexMessage(L, errGenerated, "missing closing ' for character literal")
  809. tokenEndIgnore(tok, L.bufpos)
  810. inc(L.bufpos) # skip '
  811. proc getSymbol(L: var TLexer, tok: var TToken) =
  812. var h: Hash = 0
  813. var pos = L.bufpos
  814. tokenBegin(tok, pos)
  815. var suspicious = false
  816. while true:
  817. var c = L.buf[pos]
  818. case c
  819. of 'a'..'z', '0'..'9', '\x80'..'\xFF':
  820. h = h !& ord(c)
  821. inc(pos)
  822. of 'A'..'Z':
  823. c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
  824. h = h !& ord(c)
  825. inc(pos)
  826. suspicious = true
  827. of '_':
  828. if L.buf[pos+1] notin SymChars:
  829. lexMessage(L, errGenerated, "invalid token: trailing underscore")
  830. break
  831. inc(pos)
  832. suspicious = true
  833. else: break
  834. tokenEnd(tok, pos-1)
  835. h = !$h
  836. tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
  837. if (tok.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or
  838. (tok.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)):
  839. tok.tokType = tkSymbol
  840. else:
  841. tok.tokType = TTokType(tok.ident.id + ord(tkSymbol))
  842. if suspicious and {optStyleHint, optStyleError} * L.config.globalOptions != {}:
  843. lintReport(L.config, getLineInfo(L), tok.ident.s.normalize, tok.ident.s)
  844. L.bufpos = pos
  845. proc endOperator(L: var TLexer, tok: var TToken, pos: int,
  846. hash: Hash) {.inline.} =
  847. var h = !$hash
  848. tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
  849. if (tok.ident.id < oprLow) or (tok.ident.id > oprHigh): tok.tokType = tkOpr
  850. else: tok.tokType = TTokType(tok.ident.id - oprLow + ord(tkColon))
  851. L.bufpos = pos
  852. proc getOperator(L: var TLexer, tok: var TToken) =
  853. var pos = L.bufpos
  854. tokenBegin(tok, pos)
  855. var h: Hash = 0
  856. while true:
  857. var c = L.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 L.buf[pos] == ' ':
  867. inc pos
  868. inc tok.strongSpaceB
  869. if L.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. while true:
  904. case L.buf[pos]
  905. of ' ', '\t':
  906. inc(pos)
  907. of CR, LF:
  908. result = true
  909. break
  910. of '#':
  911. inc(pos)
  912. if L.buf[pos] == '#': inc(pos)
  913. if L.buf[pos] != '[': return true
  914. else:
  915. break
  916. proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int;
  917. isDoc: bool) =
  918. var pos = start
  919. var toStrip = 0
  920. tokenBegin(tok, pos)
  921. # detect the amount of indentation:
  922. if isDoc:
  923. toStrip = getColNumber(L, pos)
  924. while L.buf[pos] == ' ': inc pos
  925. if L.buf[pos] in {CR, LF}:
  926. pos = handleCRLF(L, pos)
  927. toStrip = 0
  928. while L.buf[pos] == ' ':
  929. inc pos
  930. inc toStrip
  931. var nesting = 0
  932. while true:
  933. case L.buf[pos]
  934. of '#':
  935. if isDoc:
  936. if L.buf[pos+1] == '#' and L.buf[pos+2] == '[':
  937. inc nesting
  938. tok.literal.add '#'
  939. elif L.buf[pos+1] == '[':
  940. inc nesting
  941. inc pos
  942. of ']':
  943. if isDoc:
  944. if L.buf[pos+1] == '#' and L.buf[pos+2] == '#':
  945. if nesting == 0:
  946. tokenEndIgnore(tok, pos+2)
  947. inc(pos, 3)
  948. break
  949. dec nesting
  950. tok.literal.add ']'
  951. elif L.buf[pos+1] == '#':
  952. if nesting == 0:
  953. tokenEndIgnore(tok, pos+1)
  954. inc(pos, 2)
  955. break
  956. dec nesting
  957. inc pos
  958. of CR, LF:
  959. tokenEndIgnore(tok, pos)
  960. pos = handleCRLF(L, pos)
  961. # strip leading whitespace:
  962. when defined(nimpretty): tok.literal.add "\L"
  963. if isDoc:
  964. when not defined(nimpretty): tok.literal.add "\n"
  965. inc tok.iNumber
  966. var c = toStrip
  967. while L.buf[pos] == ' ' and c > 0:
  968. inc pos
  969. dec c
  970. of nimlexbase.EndOfFile:
  971. tokenEndIgnore(tok, pos)
  972. lexMessagePos(L, errGenerated, pos, "end of multiline comment expected")
  973. break
  974. else:
  975. if isDoc or defined(nimpretty): tok.literal.add L.buf[pos]
  976. inc(pos)
  977. L.bufpos = pos
  978. when defined(nimpretty):
  979. tok.commentOffsetB = L.offsetBase + pos - 1
  980. proc scanComment(L: var TLexer, tok: var TToken) =
  981. var pos = L.bufpos
  982. tok.tokType = tkComment
  983. # iNumber contains the number of '\n' in the token
  984. tok.iNumber = 0
  985. assert L.buf[pos+1] == '#'
  986. when defined(nimpretty):
  987. tok.commentOffsetA = L.offsetBase + pos - 1
  988. if L.buf[pos+2] == '[':
  989. skipMultiLineComment(L, tok, pos+3, true)
  990. return
  991. tokenBegin(tok, pos)
  992. inc(pos, 2)
  993. var toStrip = 0
  994. while L.buf[pos] == ' ':
  995. inc pos
  996. inc toStrip
  997. while true:
  998. var lastBackslash = -1
  999. while L.buf[pos] notin {CR, LF, nimlexbase.EndOfFile}:
  1000. if L.buf[pos] == '\\': lastBackslash = pos+1
  1001. add(tok.literal, L.buf[pos])
  1002. inc(pos)
  1003. tokenEndIgnore(tok, pos)
  1004. pos = handleCRLF(L, pos)
  1005. var indent = 0
  1006. while L.buf[pos] == ' ':
  1007. inc(pos)
  1008. inc(indent)
  1009. if L.buf[pos] == '#' and L.buf[pos+1] == '#':
  1010. tok.literal.add "\n"
  1011. inc(pos, 2)
  1012. var c = toStrip
  1013. while L.buf[pos] == ' ' and c > 0:
  1014. inc pos
  1015. dec c
  1016. inc tok.iNumber
  1017. else:
  1018. if L.buf[pos] > ' ':
  1019. L.indentAhead = indent
  1020. tokenEndIgnore(tok, pos)
  1021. break
  1022. L.bufpos = pos
  1023. when defined(nimpretty):
  1024. tok.commentOffsetB = L.offsetBase + pos - 1
  1025. proc skip(L: var TLexer, tok: var TToken) =
  1026. var pos = L.bufpos
  1027. tokenBegin(tok, pos)
  1028. tok.strongSpaceA = 0
  1029. when defined(nimpretty):
  1030. var hasComment = false
  1031. var commentIndent = L.currLineIndent
  1032. tok.commentOffsetA = L.offsetBase + pos
  1033. tok.commentOffsetB = tok.commentOffsetA
  1034. tok.line = -1
  1035. while true:
  1036. case L.buf[pos]
  1037. of ' ':
  1038. inc(pos)
  1039. inc(tok.strongSpaceA)
  1040. of '\t':
  1041. if not L.allowTabs: lexMessagePos(L, errGenerated, pos, "tabs are not allowed, use spaces instead")
  1042. inc(pos)
  1043. of CR, LF:
  1044. tokenEndPrevious(tok, pos)
  1045. pos = handleCRLF(L, pos)
  1046. var indent = 0
  1047. while true:
  1048. if L.buf[pos] == ' ':
  1049. inc(pos)
  1050. inc(indent)
  1051. elif L.buf[pos] == '#' and L.buf[pos+1] == '[':
  1052. when defined(nimpretty):
  1053. hasComment = true
  1054. if tok.line < 0:
  1055. tok.line = L.lineNumber
  1056. commentIndent = indent
  1057. skipMultiLineComment(L, tok, pos+2, false)
  1058. pos = L.bufpos
  1059. else:
  1060. break
  1061. tok.strongSpaceA = 0
  1062. when defined(nimpretty):
  1063. if L.buf[pos] == '#' and tok.line < 0: commentIndent = indent
  1064. if L.buf[pos] > ' ' and (L.buf[pos] != '#' or L.buf[pos+1] == '#'):
  1065. tok.indent = indent
  1066. L.currLineIndent = indent
  1067. break
  1068. of '#':
  1069. # do not skip documentation comment:
  1070. if L.buf[pos+1] == '#': break
  1071. when defined(nimpretty):
  1072. hasComment = true
  1073. if tok.line < 0:
  1074. tok.line = L.lineNumber
  1075. if L.buf[pos+1] == '[':
  1076. skipMultiLineComment(L, tok, pos+2, false)
  1077. pos = L.bufpos
  1078. else:
  1079. tokenBegin(tok, pos)
  1080. while L.buf[pos] notin {CR, LF, nimlexbase.EndOfFile}:
  1081. when defined(nimpretty): tok.literal.add L.buf[pos]
  1082. inc(pos)
  1083. tokenEndIgnore(tok, pos+1)
  1084. when defined(nimpretty):
  1085. tok.commentOffsetB = L.offsetBase + pos + 1
  1086. else:
  1087. break # EndOfFile also leaves the loop
  1088. tokenEndPrevious(tok, pos-1)
  1089. L.bufpos = pos
  1090. when defined(nimpretty):
  1091. if hasComment:
  1092. tok.commentOffsetB = L.offsetBase + pos - 1
  1093. tok.tokType = tkComment
  1094. tok.indent = commentIndent
  1095. proc rawGetTok*(L: var TLexer, tok: var TToken) =
  1096. template atTokenEnd() {.dirty.} =
  1097. when defined(nimsuggest):
  1098. # we attach the cursor to the last *strong* token
  1099. if tok.tokType notin weakTokens:
  1100. L.previousToken.line = tok.line.uint16
  1101. L.previousToken.col = tok.col.int16
  1102. when defined(nimsuggest):
  1103. L.cursor = CursorPosition.None
  1104. fillToken(tok)
  1105. if L.indentAhead >= 0:
  1106. tok.indent = L.indentAhead
  1107. L.currLineIndent = L.indentAhead
  1108. L.indentAhead = -1
  1109. else:
  1110. tok.indent = -1
  1111. skip(L, tok)
  1112. when defined(nimpretty):
  1113. if tok.tokType == tkComment:
  1114. L.indentAhead = L.currLineIndent
  1115. return
  1116. var c = L.buf[L.bufpos]
  1117. tok.line = L.lineNumber
  1118. tok.col = getColNumber(L, L.bufpos)
  1119. if c in SymStartChars - {'r', 'R'}:
  1120. getSymbol(L, tok)
  1121. else:
  1122. case c
  1123. of '#':
  1124. scanComment(L, tok)
  1125. of '*':
  1126. # '*:' is unfortunately a special case, because it is two tokens in
  1127. # 'var v*: int'.
  1128. if L.buf[L.bufpos+1] == ':' and L.buf[L.bufpos+2] notin OpChars:
  1129. var h = 0 !& ord('*')
  1130. endOperator(L, tok, L.bufpos+1, h)
  1131. else:
  1132. getOperator(L, tok)
  1133. of ',':
  1134. tok.tokType = tkComma
  1135. inc(L.bufpos)
  1136. of 'r', 'R':
  1137. if L.buf[L.bufpos + 1] == '\"':
  1138. inc(L.bufpos)
  1139. getString(L, tok, raw)
  1140. else:
  1141. getSymbol(L, tok)
  1142. of '(':
  1143. inc(L.bufpos)
  1144. if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
  1145. tok.tokType = tkParDotLe
  1146. inc(L.bufpos)
  1147. else:
  1148. tok.tokType = tkParLe
  1149. when defined(nimsuggest):
  1150. if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col < L.config.m.trackPos.col and
  1151. tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideCon:
  1152. L.config.m.trackPos.col = tok.col.int16
  1153. of ')':
  1154. tok.tokType = tkParRi
  1155. inc(L.bufpos)
  1156. of '[':
  1157. inc(L.bufpos)
  1158. if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
  1159. tok.tokType = tkBracketDotLe
  1160. inc(L.bufpos)
  1161. elif L.buf[L.bufpos] == ':':
  1162. tok.tokType = tkBracketLeColon
  1163. inc(L.bufpos)
  1164. else:
  1165. tok.tokType = tkBracketLe
  1166. of ']':
  1167. tok.tokType = tkBracketRi
  1168. inc(L.bufpos)
  1169. of '.':
  1170. when defined(nimsuggest):
  1171. if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col+1 == L.config.m.trackPos.col and
  1172. tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideSug:
  1173. tok.tokType = tkDot
  1174. L.cursor = CursorPosition.InToken
  1175. L.config.m.trackPos.col = tok.col.int16
  1176. inc(L.bufpos)
  1177. atTokenEnd()
  1178. return
  1179. if L.buf[L.bufpos+1] == ']':
  1180. tok.tokType = tkBracketDotRi
  1181. inc(L.bufpos, 2)
  1182. elif L.buf[L.bufpos+1] == '}':
  1183. tok.tokType = tkCurlyDotRi
  1184. inc(L.bufpos, 2)
  1185. elif L.buf[L.bufpos+1] == ')':
  1186. tok.tokType = tkParDotRi
  1187. inc(L.bufpos, 2)
  1188. else:
  1189. getOperator(L, tok)
  1190. of '{':
  1191. inc(L.bufpos)
  1192. if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
  1193. tok.tokType = tkCurlyDotLe
  1194. inc(L.bufpos)
  1195. else:
  1196. tok.tokType = tkCurlyLe
  1197. of '}':
  1198. tok.tokType = tkCurlyRi
  1199. inc(L.bufpos)
  1200. of ';':
  1201. tok.tokType = tkSemiColon
  1202. inc(L.bufpos)
  1203. of '`':
  1204. tok.tokType = tkAccent
  1205. inc(L.bufpos)
  1206. of '_':
  1207. inc(L.bufpos)
  1208. if L.buf[L.bufpos] notin SymChars+{'_'}:
  1209. tok.tokType = tkSymbol
  1210. tok.ident = L.cache.getIdent("_")
  1211. else:
  1212. tok.literal = $c
  1213. tok.tokType = tkInvalid
  1214. lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')')
  1215. of '\"':
  1216. # check for generalized raw string literal:
  1217. let mode = if L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars: generalized else: normal
  1218. getString(L, tok, mode)
  1219. if mode == generalized:
  1220. # tkRStrLit -> tkGStrLit
  1221. # tkTripleStrLit -> tkGTripleStrLit
  1222. inc(tok.tokType, 2)
  1223. of '\'':
  1224. tok.tokType = tkCharLit
  1225. getCharacter(L, tok)
  1226. tok.tokType = tkCharLit
  1227. of '0'..'9':
  1228. getNumber(L, tok)
  1229. let c = L.buf[L.bufpos]
  1230. if c in SymChars+{'_'}:
  1231. lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
  1232. else:
  1233. if c in OpChars:
  1234. getOperator(L, tok)
  1235. elif c == nimlexbase.EndOfFile:
  1236. tok.tokType = tkEof
  1237. tok.indent = 0
  1238. else:
  1239. tok.literal = $c
  1240. tok.tokType = tkInvalid
  1241. lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')')
  1242. inc(L.bufpos)
  1243. atTokenEnd()
  1244. proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream;
  1245. cache: IdentCache; config: ConfigRef): int =
  1246. var lex: TLexer
  1247. var tok: TToken
  1248. initToken(tok)
  1249. openLexer(lex, fileIdx, inputstream, cache, config)
  1250. var prevToken = tkEof
  1251. while tok.tokType != tkEof:
  1252. rawGetTok(lex, tok)
  1253. if tok.indent > 0 and prevToken in {tkColon, tkEquals, tkType, tkConst, tkLet, tkVar, tkUsing}:
  1254. result = tok.indent
  1255. if result > 0: break
  1256. prevToken = tok.tokType
  1257. closeLexer(lex)
  1258. proc getPrecedence*(ident: PIdent): int =
  1259. ## assumes ident is binary operator already
  1260. var tok: TToken
  1261. initToken(tok)
  1262. tok.ident = ident
  1263. tok.tokType =
  1264. if tok.ident.id in ord(tokKeywordLow) - ord(tkSymbol) .. ord(tokKeywordHigh) - ord(tkSymbol):
  1265. TTokType(tok.ident.id + ord(tkSymbol))
  1266. else: tkOpr
  1267. getPrecedence(tok, false)