lexer.nim 48 KB

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