lexer.nim 43 KB

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