lexer.nim 47 KB

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