lexer.nim 44 KB

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