1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2015 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- # This scanner is handwritten for efficiency. I used an elegant buffering
- # scheme which I have not seen anywhere else:
- # We guarantee that a whole line is in the buffer. Thus only when scanning
- # the \n or \r character we have to check wether we need to read in the next
- # chunk. (\n or \r already need special handling for incrementing the line
- # counter; choosing both \n and \r allows the scanner to properly read Unix,
- # DOS or Macintosh text files, even when it is not the native format.
- import
- hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream,
- wordrecg
- const
- MaxLineLength* = 80 # lines longer than this lead to a warning
- numChars*: set[char] = {'0'..'9', 'a'..'z', 'A'..'Z'}
- SymChars*: set[char] = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF'}
- SymStartChars*: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'}
- OpChars*: set[char] = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.',
- '|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'}
- # don't forget to update the 'highlite' module if these charsets should change
- type
- TTokType* = enum
- tkInvalid, tkEof, # order is important here!
- tkSymbol, # keywords:
- tkAddr, tkAnd, tkAs, tkAsm, tkAtomic,
- tkBind, tkBlock, tkBreak, tkCase, tkCast,
- tkConcept, tkConst, tkContinue, tkConverter,
- tkDefer, tkDiscard, tkDistinct, tkDiv, tkDo,
- tkElif, tkElse, tkEnd, tkEnum, tkExcept, tkExport,
- tkFinally, tkFor, tkFrom, tkFunc,
- tkGeneric, tkIf, tkImport, tkIn, tkInclude, tkInterface,
- tkIs, tkIsnot, tkIterator,
- tkLet,
- tkMacro, tkMethod, tkMixin, tkMod, tkNil, tkNot, tkNotin,
- tkObject, tkOf, tkOr, tkOut,
- tkProc, tkPtr, tkRaise, tkRef, tkReturn,
- tkShl, tkShr, tkStatic,
- tkTemplate,
- tkTry, tkTuple, tkType, tkUsing,
- tkVar, tkWhen, tkWhile, tkXor,
- tkYield, # end of keywords
- tkIntLit, tkInt8Lit, tkInt16Lit, tkInt32Lit, tkInt64Lit,
- tkUIntLit, tkUInt8Lit, tkUInt16Lit, tkUInt32Lit, tkUInt64Lit,
- tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit,
- tkStrLit, tkRStrLit, tkTripleStrLit,
- tkGStrLit, tkGTripleStrLit, tkCharLit, tkParLe, tkParRi, tkBracketLe,
- tkBracketRi, tkCurlyLe, tkCurlyRi,
- tkBracketDotLe, tkBracketDotRi, # [. and .]
- tkCurlyDotLe, tkCurlyDotRi, # {. and .}
- tkParDotLe, tkParDotRi, # (. and .)
- tkComma, tkSemiColon,
- tkColon, tkColonColon, tkEquals, tkDot, tkDotDot,
- tkOpr, tkComment, tkAccent,
- tkSpaces, tkInfixOpr, tkPrefixOpr, tkPostfixOpr
- TTokTypes* = set[TTokType]
- const
- weakTokens = {tkComma, tkSemiColon, tkColon,
- tkParRi, tkParDotRi, tkBracketRi, tkBracketDotRi,
- tkCurlyRi} # \
- # tokens that should not be considered for previousToken
- tokKeywordLow* = succ(tkSymbol)
- tokKeywordHigh* = pred(tkIntLit)
- TokTypeToStr*: array[TTokType, string] = ["tkInvalid", "[EOF]",
- "tkSymbol",
- "addr", "and", "as", "asm", "atomic",
- "bind", "block", "break", "case", "cast",
- "concept", "const", "continue", "converter",
- "defer", "discard", "distinct", "div", "do",
- "elif", "else", "end", "enum", "except", "export",
- "finally", "for", "from", "func", "generic", "if",
- "import", "in", "include", "interface", "is", "isnot", "iterator",
- "let",
- "macro", "method", "mixin", "mod",
- "nil", "not", "notin", "object", "of", "or",
- "out", "proc", "ptr", "raise", "ref", "return",
- "shl", "shr", "static",
- "template",
- "try", "tuple", "type", "using",
- "var", "when", "while", "xor",
- "yield",
- "tkIntLit", "tkInt8Lit", "tkInt16Lit", "tkInt32Lit", "tkInt64Lit",
- "tkUIntLit", "tkUInt8Lit", "tkUInt16Lit", "tkUInt32Lit", "tkUInt64Lit",
- "tkFloatLit", "tkFloat32Lit", "tkFloat64Lit", "tkFloat128Lit",
- "tkStrLit", "tkRStrLit",
- "tkTripleStrLit", "tkGStrLit", "tkGTripleStrLit", "tkCharLit", "(",
- ")", "[", "]", "{", "}", "[.", ".]", "{.", ".}", "(.", ".)",
- ",", ";",
- ":", "::", "=", ".", "..",
- "tkOpr", "tkComment", "`",
- "tkSpaces", "tkInfixOpr",
- "tkPrefixOpr", "tkPostfixOpr"]
- type
- TNumericalBase* = enum
- base10, # base10 is listed as the first element,
- # so that it is the correct default value
- base2, base8, base16
- CursorPosition* {.pure.} = enum ## XXX remove this again
- None, InToken, BeforeToken, AfterToken
- TToken* = object # a Nim token
- tokType*: TTokType # the type of the token
- indent*: int # the indentation; != -1 if the token has been
- # preceded with indentation
- ident*: PIdent # the parsed identifier
- iNumber*: BiggestInt # the parsed integer literal
- fNumber*: BiggestFloat # the parsed floating point literal
- base*: TNumericalBase # the numerical base; only valid for int
- # or float literals
- strongSpaceA*: int8 # leading spaces of an operator
- strongSpaceB*: int8 # trailing spaces of an operator
- literal*: string # the parsed (string) literal; and
- # documentation comments are here too
- line*, col*: int
- when defined(nimpretty):
- offsetA*, offsetB*: int # used for pretty printing so that literals
- # like 0b01 or r"\L" are unaffected
- commentOffsetA*, commentOffsetB*: int
- TErrorHandler* = proc (info: TLineInfo; msg: TMsgKind; arg: string)
- TLexer* = object of TBaseLexer
- fileIdx*: int32
- indentAhead*: int # if > 0 an indendation has already been read
- # this is needed because scanning comments
- # needs so much look-ahead
- currLineIndent*: int
- strongSpaces*, allowTabs*: bool
- cursor*: CursorPosition
- errorHandler*: TErrorHandler
- cache*: IdentCache
- when defined(nimsuggest):
- previousToken: TLineInfo
- when defined(nimpretty):
- var
- gIndentationWidth*: int
- var gLinesCompiled*: int # all lines that have been compiled
- proc getLineInfo*(L: TLexer, tok: TToken): TLineInfo {.inline.} =
- result = newLineInfo(L.fileIdx, tok.line, tok.col)
- when defined(nimpretty):
- result.offsetA = tok.offsetA
- result.offsetB = tok.offsetB
- result.commentOffsetA = tok.commentOffsetA
- result.commentOffsetB = tok.commentOffsetB
- proc isKeyword*(kind: TTokType): bool =
- result = (kind >= tokKeywordLow) and (kind <= tokKeywordHigh)
- template ones(n): untyped = ((1 shl n)-1) # for utf-8 conversion
- proc isNimIdentifier*(s: string): bool =
- if s[0] in SymStartChars:
- var i = 1
- var sLen = s.len
- while i < sLen:
- if s[i] == '_':
- inc(i)
- if s[i] notin SymChars: return
- inc(i)
- result = true
- proc tokToStr*(tok: TToken): string =
- case tok.tokType
- of tkIntLit..tkInt64Lit: result = $tok.iNumber
- of tkFloatLit..tkFloat64Lit: result = $tok.fNumber
- of tkInvalid, tkStrLit..tkCharLit, tkComment: result = tok.literal
- of tkParLe..tkColon, tkEof, tkAccent:
- result = TokTypeToStr[tok.tokType]
- else:
- if tok.ident != nil:
- result = tok.ident.s
- else:
- result = ""
- proc prettyTok*(tok: TToken): string =
- if isKeyword(tok.tokType): result = "keyword " & tok.ident.s
- else: result = tokToStr(tok)
- proc printTok*(tok: TToken) =
- msgWriteln($tok.line & ":" & $tok.col & "\t" &
- TokTypeToStr[tok.tokType] & " " & tokToStr(tok))
- proc initToken*(L: var TToken) =
- L.tokType = tkInvalid
- L.iNumber = 0
- L.indent = 0
- L.strongSpaceA = 0
- L.literal = ""
- L.fNumber = 0.0
- L.base = base10
- L.ident = nil
- when defined(nimpretty):
- L.commentOffsetA = 0
- L.commentOffsetB = 0
- proc fillToken(L: var TToken) =
- L.tokType = tkInvalid
- L.iNumber = 0
- L.indent = 0
- L.strongSpaceA = 0
- setLen(L.literal, 0)
- L.fNumber = 0.0
- L.base = base10
- L.ident = nil
- when defined(nimpretty):
- L.commentOffsetA = 0
- L.commentOffsetB = 0
- proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream;
- cache: IdentCache) =
- openBaseLexer(lex, inputstream)
- lex.fileIdx = fileidx
- lex.indentAhead = - 1
- lex.currLineIndent = 0
- inc(lex.lineNumber, inputstream.lineOffset)
- lex.cache = cache
- when defined(nimsuggest):
- lex.previousToken.fileIndex = fileIdx
- proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream;
- cache: IdentCache) =
- openLexer(lex, filename.fileInfoIdx, inputstream, cache)
- proc closeLexer*(lex: var TLexer) =
- inc(gLinesCompiled, lex.lineNumber)
- closeBaseLexer(lex)
- proc getLineInfo(L: TLexer): TLineInfo =
- result = newLineInfo(L.fileIdx, L.lineNumber, getColNumber(L, L.bufpos))
- proc dispMessage(L: TLexer; info: TLineInfo; msg: TMsgKind; arg: string) =
- if L.errorHandler.isNil:
- msgs.message(info, msg, arg)
- else:
- L.errorHandler(info, msg, arg)
- proc lexMessage*(L: TLexer, msg: TMsgKind, arg = "") =
- L.dispMessage(getLineInfo(L), msg, arg)
- proc lexMessageTok*(L: TLexer, msg: TMsgKind, tok: TToken, arg = "") =
- var info = newLineInfo(L.fileIdx, tok.line, tok.col)
- L.dispMessage(info, msg, arg)
- proc lexMessagePos(L: var TLexer, msg: TMsgKind, pos: int, arg = "") =
- var info = newLineInfo(L.fileIdx, L.lineNumber, pos - L.lineStart)
- L.dispMessage(info, msg, arg)
- proc matchTwoChars(L: TLexer, first: char, second: set[char]): bool =
- result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second)
- template tokenBegin(tok, pos) {.dirty.} =
- when defined(nimsuggest):
- var colA = getColNumber(L, pos)
- when defined(nimpretty):
- tok.offsetA = L.offsetBase + pos
- template tokenEnd(tok, pos) {.dirty.} =
- when defined(nimsuggest):
- let colB = getColNumber(L, pos)+1
- if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and
- L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}:
- L.cursor = CursorPosition.InToken
- gTrackPos.col = colA.int16
- colA = 0
- when defined(nimpretty):
- tok.offsetB = L.offsetBase + pos
- template tokenEndIgnore(tok, pos) =
- when defined(nimsuggest):
- let colB = getColNumber(L, pos)
- if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and
- L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}:
- gTrackPos.fileIndex = trackPosInvalidFileIdx
- gTrackPos.line = -1
- colA = 0
- when defined(nimpretty):
- tok.offsetB = L.offsetBase + pos
- template tokenEndPrevious(tok, pos) =
- when defined(nimsuggest):
- # when we detect the cursor in whitespace, we attach the track position
- # to the token that came before that, but only if we haven't detected
- # the cursor in a string literal or comment:
- let colB = getColNumber(L, pos)
- if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and
- L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}:
- L.cursor = CursorPosition.BeforeToken
- gTrackPos = L.previousToken
- gTrackPosAttached = true
- colA = 0
- when defined(nimpretty):
- tok.offsetB = L.offsetBase + pos
- {.push overflowChecks: off.}
- # We need to parse the largest uint literal without overflow checks
- proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int =
- var i = start
- if s[i] in {'0'..'9'}:
- b = 0
- while s[i] in {'0'..'9'}:
- b = b * 10 + (ord(s[i]) - ord('0'))
- inc(i)
- while s[i] == '_': inc(i) # underscores are allowed and ignored
- result = i - start
- {.pop.} # overflowChecks
- template eatChar(L: var TLexer, t: var TToken, replacementChar: char) =
- add(t.literal, replacementChar)
- inc(L.bufpos)
- template eatChar(L: var TLexer, t: var TToken) =
- add(t.literal, L.buf[L.bufpos])
- inc(L.bufpos)
- proc getNumber(L: var TLexer, result: var TToken) =
- proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]) =
- var pos = L.bufpos # use registers for pos, buf
- var buf = L.buf
- while true:
- if buf[pos] in chars:
- add(tok.literal, buf[pos])
- inc(pos)
- else:
- break
- if buf[pos] == '_':
- if buf[pos+1] notin chars:
- lexMessage(L, errInvalidToken, "_")
- break
- add(tok.literal, '_')
- inc(pos)
- L.bufpos = pos
- proc matchChars(L: var TLexer, tok: var TToken, chars: set[char]) =
- var pos = L.bufpos # use registers for pos, buf
- var buf = L.buf
- while buf[pos] in chars:
- add(tok.literal, buf[pos])
- inc(pos)
- L.bufpos = pos
- proc lexMessageLitNum(L: var TLexer, msg: TMsgKind, startpos: int) =
- # Used to get slightly human friendlier err messages.
- # Note: the erroneous 'O' char in the character set is intentional
- const literalishChars = {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x', 'o', 'O',
- 'c', 'C', 'b', 'B', '_', '.', '\'', 'd', 'i', 'u'}
- var msgPos = L.bufpos
- var t: TToken
- t.literal = ""
- L.bufpos = startpos # Use L.bufpos as pos because of matchChars
- matchChars(L, t, literalishChars)
- # We must verify +/- specifically so that we're not past the literal
- if L.buf[L.bufpos] in {'+', '-'} and
- L.buf[L.bufpos - 1] in {'e', 'E'}:
- add(t.literal, L.buf[L.bufpos])
- inc(L.bufpos)
- matchChars(L, t, literalishChars)
- if L.buf[L.bufpos] in {'\'', 'f', 'F', 'd', 'D', 'i', 'I', 'u', 'U'}:
- inc(L.bufpos)
- add(t.literal, L.buf[L.bufpos])
- matchChars(L, t, {'0'..'9'})
- L.bufpos = msgPos
- lexMessage(L, msg, t.literal)
- var
- startpos, endpos: int
- xi: BiggestInt
- isBase10 = true
- const
- baseCodeChars = {'X', 'x', 'o', 'c', 'C', 'b', 'B'}
- literalishChars = baseCodeChars + {'A'..'F', 'a'..'f', '0'..'9', '_', '\''}
- floatTypes = {tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit}
- result.tokType = tkIntLit # int literal until we know better
- result.literal = ""
- result.base = base10
- startpos = L.bufpos
- tokenBegin(result, startPos)
- # First stage: find out base, make verifications, build token literal string
- if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'O'}:
- isBase10 = false
- eatChar(L, result, '0')
- case L.buf[L.bufpos]
- of 'O':
- lexMessageLitNum(L, errInvalidNumberOctalCode, startpos)
- of 'x', 'X':
- eatChar(L, result, 'x')
- matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'})
- of 'o', 'c', 'C':
- eatChar(L, result, 'c')
- matchUnderscoreChars(L, result, {'0'..'7'})
- of 'b', 'B':
- eatChar(L, result, 'b')
- matchUnderscoreChars(L, result, {'0'..'1'})
- else:
- internalError(getLineInfo(L), "getNumber")
- else:
- matchUnderscoreChars(L, result, {'0'..'9'})
- if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}):
- result.tokType = tkFloatLit
- eatChar(L, result, '.')
- matchUnderscoreChars(L, result, {'0'..'9'})
- if L.buf[L.bufpos] in {'e', 'E'}:
- result.tokType = tkFloatLit
- eatChar(L, result, 'e')
- if L.buf[L.bufpos] in {'+', '-'}:
- eatChar(L, result)
- matchUnderscoreChars(L, result, {'0'..'9'})
- endpos = L.bufpos
- # Second stage, find out if there's a datatype suffix and handle it
- var postPos = endpos
- if L.buf[postPos] in {'\'', 'f', 'F', 'd', 'D', 'i', 'I', 'u', 'U'}:
- if L.buf[postPos] == '\'':
- inc(postPos)
- case L.buf[postPos]
- of 'f', 'F':
- inc(postPos)
- if (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
- result.tokType = tkFloat32Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
- result.tokType = tkFloat64Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '1') and
- (L.buf[postPos + 1] == '2') and
- (L.buf[postPos + 2] == '8'):
- result.tokType = tkFloat128Lit
- inc(postPos, 3)
- else: # "f" alone defaults to float32
- result.tokType = tkFloat32Lit
- of 'd', 'D': # ad hoc convenience shortcut for f64
- inc(postPos)
- result.tokType = tkFloat64Lit
- of 'i', 'I':
- inc(postPos)
- if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
- result.tokType = tkInt64Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
- result.tokType = tkInt32Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '1') and (L.buf[postPos + 1] == '6'):
- result.tokType = tkInt16Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '8'):
- result.tokType = tkInt8Lit
- inc(postPos)
- else:
- lexMessageLitNum(L, errInvalidNumber, startpos)
- of 'u', 'U':
- inc(postPos)
- if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
- result.tokType = tkUInt64Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
- result.tokType = tkUInt32Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '1') and (L.buf[postPos + 1] == '6'):
- result.tokType = tkUInt16Lit
- inc(postPos, 2)
- elif (L.buf[postPos] == '8'):
- result.tokType = tkUInt8Lit
- inc(postPos)
- else:
- result.tokType = tkUIntLit
- else:
- lexMessageLitNum(L, errInvalidNumber, startpos)
- # Is there still a literalish char awaiting? Then it's an error!
- if L.buf[postPos] in literalishChars or
- (L.buf[postPos] == '.' and L.buf[postPos + 1] in {'0'..'9'}):
- lexMessageLitNum(L, errInvalidNumber, startpos)
- # Third stage, extract actual number
- L.bufpos = startpos # restore position
- var pos: int = startpos
- try:
- if (L.buf[pos] == '0') and (L.buf[pos + 1] in baseCodeChars):
- inc(pos, 2)
- xi = 0 # it is a base prefix
- case L.buf[pos - 1]
- of 'b', 'B':
- result.base = base2
- while pos < endpos:
- if L.buf[pos] != '_':
- xi = `shl`(xi, 1) or (ord(L.buf[pos]) - ord('0'))
- inc(pos)
- of 'o', 'c', 'C':
- result.base = base8
- while pos < endpos:
- if L.buf[pos] != '_':
- xi = `shl`(xi, 3) or (ord(L.buf[pos]) - ord('0'))
- inc(pos)
- of 'x', 'X':
- result.base = base16
- while pos < endpos:
- case L.buf[pos]
- of '_':
- inc(pos)
- of '0'..'9':
- xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('0'))
- inc(pos)
- of 'a'..'f':
- xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('a') + 10)
- inc(pos)
- of 'A'..'F':
- xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('A') + 10)
- inc(pos)
- else:
- break
- else:
- internalError(getLineInfo(L), "getNumber")
- case result.tokType
- of tkIntLit, tkInt64Lit: result.iNumber = xi
- of tkInt8Lit: result.iNumber = BiggestInt(int8(toU8(int(xi))))
- of tkInt16Lit: result.iNumber = BiggestInt(int16(toU16(int(xi))))
- of tkInt32Lit: result.iNumber = BiggestInt(int32(toU32(int64(xi))))
- of tkUIntLit, tkUInt64Lit: result.iNumber = xi
- of tkUInt8Lit: result.iNumber = BiggestInt(uint8(toU8(int(xi))))
- of tkUInt16Lit: result.iNumber = BiggestInt(uint16(toU16(int(xi))))
- of tkUInt32Lit: result.iNumber = BiggestInt(uint32(toU32(int64(xi))))
- of tkFloat32Lit:
- result.fNumber = (cast[PFloat32](addr(xi)))[]
- # note: this code is endian neutral!
- # XXX: Test this on big endian machine!
- of tkFloat64Lit, tkFloatLit:
- result.fNumber = (cast[PFloat64](addr(xi)))[]
- else: internalError(getLineInfo(L), "getNumber")
- # Bounds checks. Non decimal literals are allowed to overflow the range of
- # the datatype as long as their pattern don't overflow _bitwise_, hence
- # below checks of signed sizes against uint*.high is deliberate:
- # (0x80'u8 = 128, 0x80'i8 = -128, etc == OK)
- if result.tokType notin floatTypes:
- let outOfRange = case result.tokType:
- of tkUInt8Lit, tkUInt16Lit, tkUInt32Lit: result.iNumber != xi
- of tkInt8Lit: (xi > BiggestInt(uint8.high))
- of tkInt16Lit: (xi > BiggestInt(uint16.high))
- of tkInt32Lit: (xi > BiggestInt(uint32.high))
- else: false
- if outOfRange:
- #echo "out of range num: ", result.iNumber, " vs ", xi
- lexMessageLitNum(L, errNumberOutOfRange, startpos)
- else:
- case result.tokType
- of floatTypes:
- result.fNumber = parseFloat(result.literal)
- of tkUint64Lit:
- xi = 0
- let len = unsafeParseUInt(result.literal, xi)
- if len != result.literal.len or len == 0:
- raise newException(ValueError, "invalid integer: " & $xi)
- result.iNumber = xi
- else:
- result.iNumber = parseBiggestInt(result.literal)
- # Explicit bounds checks
- let outOfRange = case result.tokType:
- of tkInt8Lit: (result.iNumber < int8.low or result.iNumber > int8.high)
- of tkUInt8Lit: (result.iNumber < BiggestInt(uint8.low) or
- result.iNumber > BiggestInt(uint8.high))
- of tkInt16Lit: (result.iNumber < int16.low or result.iNumber > int16.high)
- of tkUInt16Lit: (result.iNumber < BiggestInt(uint16.low) or
- result.iNumber > BiggestInt(uint16.high))
- of tkInt32Lit: (result.iNumber < int32.low or result.iNumber > int32.high)
- of tkUInt32Lit: (result.iNumber < BiggestInt(uint32.low) or
- result.iNumber > BiggestInt(uint32.high))
- else: false
- if outOfRange: lexMessageLitNum(L, errNumberOutOfRange, startpos)
- # Promote int literal to int64? Not always necessary, but more consistent
- if result.tokType == tkIntLit:
- if (result.iNumber < low(int32)) or (result.iNumber > high(int32)):
- result.tokType = tkInt64Lit
- except ValueError:
- lexMessageLitNum(L, errInvalidNumber, startpos)
- except OverflowError, RangeError:
- lexMessageLitNum(L, errNumberOutOfRange, startpos)
- tokenEnd(result, postPos-1)
- L.bufpos = postPos
- proc handleHexChar(L: var TLexer, xi: var int) =
- case L.buf[L.bufpos]
- of '0'..'9':
- xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('0'))
- inc(L.bufpos)
- of 'a'..'f':
- xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('a') + 10)
- inc(L.bufpos)
- of 'A'..'F':
- xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('A') + 10)
- inc(L.bufpos)
- else: discard
- proc handleDecChars(L: var TLexer, xi: var int) =
- while L.buf[L.bufpos] in {'0'..'9'}:
- xi = (xi * 10) + (ord(L.buf[L.bufpos]) - ord('0'))
- inc(L.bufpos)
- proc getEscapedChar(L: var TLexer, tok: var TToken) =
- inc(L.bufpos) # skip '\'
- case L.buf[L.bufpos]
- of 'n', 'N':
- if tok.tokType == tkCharLit: lexMessage(L, errNnotAllowedInCharacter)
- add(tok.literal, tnl)
- inc(L.bufpos)
- of 'r', 'R', 'c', 'C':
- add(tok.literal, CR)
- inc(L.bufpos)
- of 'l', 'L':
- add(tok.literal, LF)
- inc(L.bufpos)
- of 'f', 'F':
- add(tok.literal, FF)
- inc(L.bufpos)
- of 'e', 'E':
- add(tok.literal, ESC)
- inc(L.bufpos)
- of 'a', 'A':
- add(tok.literal, BEL)
- inc(L.bufpos)
- of 'b', 'B':
- add(tok.literal, BACKSPACE)
- inc(L.bufpos)
- of 'v', 'V':
- add(tok.literal, VT)
- inc(L.bufpos)
- of 't', 'T':
- add(tok.literal, '\t')
- inc(L.bufpos)
- of '\'', '\"':
- add(tok.literal, L.buf[L.bufpos])
- inc(L.bufpos)
- of '\\':
- add(tok.literal, '\\')
- inc(L.bufpos)
- of 'x', 'X', 'u', 'U':
- var tp = L.buf[L.bufpos]
- inc(L.bufpos)
- var xi = 0
- handleHexChar(L, xi)
- handleHexChar(L, xi)
- if tp in {'u', 'U'}:
- handleHexChar(L, xi)
- handleHexChar(L, xi)
- # inlined toUTF-8 to avoid unicode and strutils dependencies.
- if xi <=% 127:
- add(tok.literal, xi.char )
- elif xi <=% 0x07FF:
- add(tok.literal, ((xi shr 6) or 0b110_00000).char )
- add(tok.literal, ((xi and ones(6)) or 0b10_0000_00).char )
- elif xi <=% 0xFFFF:
- add(tok.literal, (xi shr 12 or 0b1110_0000).char )
- add(tok.literal, (xi shr 6 and ones(6) or 0b10_0000_00).char )
- add(tok.literal, (xi and ones(6) or 0b10_0000_00).char )
- else: # value is 0xFFFF
- add(tok.literal, "\xef\xbf\xbf" )
- else:
- add(tok.literal, chr(xi))
- of '0'..'9':
- if matchTwoChars(L, '0', {'0'..'9'}):
- lexMessage(L, warnOctalEscape)
- var xi = 0
- handleDecChars(L, xi)
- if (xi <= 255): add(tok.literal, chr(xi))
- else: lexMessage(L, errInvalidCharacterConstant)
- else: lexMessage(L, errInvalidCharacterConstant)
- proc newString(s: cstring, len: int): string =
- ## XXX, how come there is no support for this?
- result = newString(len)
- for i in 0 .. <len:
- result[i] = s[i]
- proc handleCRLF(L: var TLexer, pos: int): int =
- template registerLine =
- let col = L.getColNumber(pos)
- if col > MaxLineLength:
- lexMessagePos(L, hintLineTooLong, pos)
- if optEmbedOrigSrc in gGlobalOptions:
- let lineStart = cast[ByteAddress](L.buf) + L.lineStart
- let line = newString(cast[cstring](lineStart), col)
- addSourceLine(L.fileIdx, line)
- case L.buf[pos]
- of CR:
- registerLine()
- result = nimlexbase.handleCR(L, pos)
- of LF:
- registerLine()
- result = nimlexbase.handleLF(L, pos)
- else: result = pos
- proc getString(L: var TLexer, tok: var TToken, rawMode: bool) =
- var pos = L.bufpos
- var buf = L.buf # put `buf` in a register
- var line = L.lineNumber # save linenumber for better error message
- tokenBegin(tok, pos)
- inc pos # skip "
- if buf[pos] == '\"' and buf[pos+1] == '\"':
- tok.tokType = tkTripleStrLit # long string literal:
- inc(pos, 2) # skip ""
- # skip leading newline:
- if buf[pos] in {' ', '\t'}:
- var newpos = pos+1
- while buf[newpos] in {' ', '\t'}: inc newpos
- if buf[newpos] in {CR, LF}: pos = newpos
- pos = handleCRLF(L, pos)
- buf = L.buf
- while true:
- case buf[pos]
- of '\"':
- if buf[pos+1] == '\"' and buf[pos+2] == '\"' and
- buf[pos+3] != '\"':
- tokenEndIgnore(tok, pos+2)
- L.bufpos = pos + 3 # skip the three """
- break
- add(tok.literal, '\"')
- inc(pos)
- of CR, LF:
- tokenEndIgnore(tok, pos)
- pos = handleCRLF(L, pos)
- buf = L.buf
- add(tok.literal, tnl)
- of nimlexbase.EndOfFile:
- tokenEndIgnore(tok, pos)
- var line2 = L.lineNumber
- L.lineNumber = line
- lexMessagePos(L, errClosingTripleQuoteExpected, L.lineStart)
- L.lineNumber = line2
- L.bufpos = pos
- break
- else:
- add(tok.literal, buf[pos])
- inc(pos)
- else:
- # ordinary string literal
- if rawMode: tok.tokType = tkRStrLit
- else: tok.tokType = tkStrLit
- while true:
- var c = buf[pos]
- if c == '\"':
- if rawMode and buf[pos+1] == '\"':
- inc(pos, 2)
- add(tok.literal, '"')
- else:
- tokenEndIgnore(tok, pos)
- inc(pos) # skip '"'
- break
- elif c in {CR, LF, nimlexbase.EndOfFile}:
- tokenEndIgnore(tok, pos)
- lexMessage(L, errClosingQuoteExpected)
- break
- elif (c == '\\') and not rawMode:
- L.bufpos = pos
- getEscapedChar(L, tok)
- pos = L.bufpos
- else:
- add(tok.literal, c)
- inc(pos)
- L.bufpos = pos
- proc getCharacter(L: var TLexer, tok: var TToken) =
- tokenBegin(tok, L.bufpos)
- inc(L.bufpos) # skip '
- var c = L.buf[L.bufpos]
- case c
- of '\0'..pred(' '), '\'': lexMessage(L, errInvalidCharacterConstant)
- of '\\': getEscapedChar(L, tok)
- else:
- tok.literal = $c
- inc(L.bufpos)
- if L.buf[L.bufpos] != '\'': lexMessage(L, errMissingFinalQuote)
- tokenEndIgnore(tok, L.bufpos)
- inc(L.bufpos) # skip '
- proc getSymbol(L: var TLexer, tok: var TToken) =
- var h: Hash = 0
- var pos = L.bufpos
- var buf = L.buf
- tokenBegin(tok, pos)
- while true:
- var c = buf[pos]
- case c
- of 'a'..'z', '0'..'9', '\x80'..'\xFF':
- h = h !& ord(c)
- inc(pos)
- of 'A'..'Z':
- c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
- h = h !& ord(c)
- inc(pos)
- of '_':
- if buf[pos+1] notin SymChars:
- lexMessage(L, errInvalidToken, "_")
- break
- inc(pos)
- else: break
- tokenEnd(tok, pos-1)
- h = !$h
- tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
- L.bufpos = pos
- if (tok.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or
- (tok.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)):
- tok.tokType = tkSymbol
- else:
- tok.tokType = TTokType(tok.ident.id + ord(tkSymbol))
- proc endOperator(L: var TLexer, tok: var TToken, pos: int,
- hash: Hash) {.inline.} =
- var h = !$hash
- tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
- if (tok.ident.id < oprLow) or (tok.ident.id > oprHigh): tok.tokType = tkOpr
- else: tok.tokType = TTokType(tok.ident.id - oprLow + ord(tkColon))
- L.bufpos = pos
- proc getOperator(L: var TLexer, tok: var TToken) =
- var pos = L.bufpos
- var buf = L.buf
- tokenBegin(tok, pos)
- var h: Hash = 0
- while true:
- var c = buf[pos]
- if c notin OpChars: break
- h = h !& ord(c)
- inc(pos)
- endOperator(L, tok, pos, h)
- tokenEnd(tok, pos-1)
- # advance pos but don't store it in L.bufpos so the next token (which might
- # be an operator too) gets the preceding spaces:
- tok.strongSpaceB = 0
- while buf[pos] == ' ':
- inc pos
- inc tok.strongSpaceB
- if buf[pos] in {CR, LF, nimlexbase.EndOfFile}:
- tok.strongSpaceB = -1
- proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int;
- isDoc: bool) =
- var pos = start
- var buf = L.buf
- var toStrip = 0
- tokenBegin(tok, pos)
- # detect the amount of indentation:
- if isDoc:
- toStrip = getColNumber(L, pos)
- while buf[pos] == ' ': inc pos
- if buf[pos] in {CR, LF}:
- pos = handleCRLF(L, pos)
- buf = L.buf
- toStrip = 0
- while buf[pos] == ' ':
- inc pos
- inc toStrip
- var nesting = 0
- while true:
- case buf[pos]
- of '#':
- if isDoc:
- if buf[pos+1] == '#' and buf[pos+2] == '[':
- inc nesting
- tok.literal.add '#'
- elif buf[pos+1] == '[':
- inc nesting
- inc pos
- of ']':
- if isDoc:
- if buf[pos+1] == '#' and buf[pos+2] == '#':
- if nesting == 0:
- tokenEndIgnore(tok, pos+2)
- inc(pos, 3)
- break
- dec nesting
- tok.literal.add ']'
- elif buf[pos+1] == '#':
- if nesting == 0:
- tokenEndIgnore(tok, pos+1)
- inc(pos, 2)
- break
- dec nesting
- inc pos
- of CR, LF:
- tokenEndIgnore(tok, pos)
- pos = handleCRLF(L, pos)
- buf = L.buf
- # strip leading whitespace:
- when defined(nimpretty): tok.literal.add "\L"
- if isDoc:
- when not defined(nimpretty): tok.literal.add "\n"
- inc tok.iNumber
- var c = toStrip
- while buf[pos] == ' ' and c > 0:
- inc pos
- dec c
- of nimlexbase.EndOfFile:
- tokenEndIgnore(tok, pos)
- lexMessagePos(L, errGenerated, pos, "end of multiline comment expected")
- break
- else:
- if isDoc or defined(nimpretty): tok.literal.add buf[pos]
- inc(pos)
- L.bufpos = pos
- proc scanComment(L: var TLexer, tok: var TToken) =
- var pos = L.bufpos
- var buf = L.buf
- tok.tokType = tkComment
- # iNumber contains the number of '\n' in the token
- tok.iNumber = 0
- assert buf[pos+1] == '#'
- if buf[pos+2] == '[':
- skipMultiLineComment(L, tok, pos+3, true)
- return
- tokenBegin(tok, pos)
- inc(pos, 2)
- var toStrip = 0
- while buf[pos] == ' ':
- inc pos
- inc toStrip
- while true:
- var lastBackslash = -1
- while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}:
- if buf[pos] == '\\': lastBackslash = pos+1
- add(tok.literal, buf[pos])
- inc(pos)
- tokenEndIgnore(tok, pos)
- pos = handleCRLF(L, pos)
- buf = L.buf
- var indent = 0
- while buf[pos] == ' ':
- inc(pos)
- inc(indent)
- if buf[pos] == '#' and buf[pos+1] == '#':
- tok.literal.add "\n"
- inc(pos, 2)
- var c = toStrip
- while buf[pos] == ' ' and c > 0:
- inc pos
- dec c
- inc tok.iNumber
- else:
- if buf[pos] > ' ':
- L.indentAhead = indent
- tokenEndIgnore(tok, pos)
- break
- L.bufpos = pos
- proc skip(L: var TLexer, tok: var TToken) =
- var pos = L.bufpos
- var buf = L.buf
- tokenBegin(tok, pos)
- tok.strongSpaceA = 0
- while true:
- case buf[pos]
- of ' ':
- inc(pos)
- inc(tok.strongSpaceA)
- of '\t':
- if not L.allowTabs: lexMessagePos(L, errTabulatorsAreNotAllowed, pos)
- inc(pos)
- of CR, LF:
- tokenEndPrevious(tok, pos)
- pos = handleCRLF(L, pos)
- buf = L.buf
- var indent = 0
- while true:
- if buf[pos] == ' ':
- inc(pos)
- inc(indent)
- elif buf[pos] == '#' and buf[pos+1] == '[':
- skipMultiLineComment(L, tok, pos+2, false)
- pos = L.bufpos
- buf = L.buf
- else:
- break
- tok.strongSpaceA = 0
- if buf[pos] > ' ' and (buf[pos] != '#' or buf[pos+1] == '#'):
- tok.indent = indent
- L.currLineIndent = indent
- break
- of '#':
- # do not skip documentation comment:
- if buf[pos+1] == '#': break
- when defined(nimpretty):
- tok.commentOffsetA = L.offsetBase + pos
- if buf[pos+1] == '[':
- skipMultiLineComment(L, tok, pos+2, false)
- pos = L.bufpos
- buf = L.buf
- when defined(nimpretty):
- tok.commentOffsetB = L.offsetBase + pos
- else:
- tokenBegin(tok, pos)
- while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: inc(pos)
- tokenEndIgnore(tok, pos+1)
- when defined(nimpretty):
- tok.commentOffsetB = L.offsetBase + pos + 1
- else:
- break # EndOfFile also leaves the loop
- tokenEndPrevious(tok, pos-1)
- L.bufpos = pos
- when defined(nimpretty):
- if gIndentationWidth <= 0:
- gIndentationWidth = tok.indent
- proc rawGetTok*(L: var TLexer, tok: var TToken) =
- template atTokenEnd() {.dirty.} =
- when defined(nimsuggest):
- # we attach the cursor to the last *strong* token
- if tok.tokType notin weakTokens:
- L.previousToken.line = tok.line.int16
- L.previousToken.col = tok.col.int16
- when defined(nimsuggest):
- L.cursor = CursorPosition.None
- fillToken(tok)
- if L.indentAhead >= 0:
- tok.indent = L.indentAhead
- L.currLineIndent = L.indentAhead
- L.indentAhead = -1
- else:
- tok.indent = -1
- skip(L, tok)
- var c = L.buf[L.bufpos]
- tok.line = L.lineNumber
- tok.col = getColNumber(L, L.bufpos)
- if c in SymStartChars - {'r', 'R'}:
- getSymbol(L, tok)
- else:
- case c
- of '#':
- scanComment(L, tok)
- of '*':
- # '*:' is unfortunately a special case, because it is two tokens in
- # 'var v*: int'.
- if L.buf[L.bufpos+1] == ':' and L.buf[L.bufpos+2] notin OpChars:
- var h = 0 !& ord('*')
- endOperator(L, tok, L.bufpos+1, h)
- else:
- getOperator(L, tok)
- of ',':
- tok.tokType = tkComma
- inc(L.bufpos)
- of 'r', 'R':
- if L.buf[L.bufpos + 1] == '\"':
- inc(L.bufpos)
- getString(L, tok, true)
- else:
- getSymbol(L, tok)
- of '(':
- inc(L.bufpos)
- if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
- tok.tokType = tkParDotLe
- inc(L.bufpos)
- else:
- tok.tokType = tkParLe
- when defined(nimsuggest):
- if L.fileIdx == gTrackPos.fileIndex and tok.col < gTrackPos.col and
- tok.line == gTrackPos.line and gIdeCmd == ideCon:
- gTrackPos.col = tok.col.int16
- of ')':
- tok.tokType = tkParRi
- inc(L.bufpos)
- of '[':
- inc(L.bufpos)
- if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
- tok.tokType = tkBracketDotLe
- inc(L.bufpos)
- else:
- tok.tokType = tkBracketLe
- of ']':
- tok.tokType = tkBracketRi
- inc(L.bufpos)
- of '.':
- when defined(nimsuggest):
- if L.fileIdx == gTrackPos.fileIndex and tok.col+1 == gTrackPos.col and
- tok.line == gTrackPos.line and gIdeCmd == ideSug:
- tok.tokType = tkDot
- L.cursor = CursorPosition.InToken
- gTrackPos.col = tok.col.int16
- inc(L.bufpos)
- atTokenEnd()
- return
- if L.buf[L.bufpos+1] == ']':
- tok.tokType = tkBracketDotRi
- inc(L.bufpos, 2)
- elif L.buf[L.bufpos+1] == '}':
- tok.tokType = tkCurlyDotRi
- inc(L.bufpos, 2)
- elif L.buf[L.bufpos+1] == ')':
- tok.tokType = tkParDotRi
- inc(L.bufpos, 2)
- else:
- getOperator(L, tok)
- of '{':
- inc(L.bufpos)
- if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
- tok.tokType = tkCurlyDotLe
- inc(L.bufpos)
- else:
- tok.tokType = tkCurlyLe
- of '}':
- tok.tokType = tkCurlyRi
- inc(L.bufpos)
- of ';':
- tok.tokType = tkSemiColon
- inc(L.bufpos)
- of '`':
- tok.tokType = tkAccent
- inc(L.bufpos)
- of '_':
- inc(L.bufpos)
- if L.buf[L.bufpos] notin SymChars+{'_'}:
- tok.tokType = tkSymbol
- tok.ident = L.cache.getIdent("_")
- else:
- tok.literal = $c
- tok.tokType = tkInvalid
- lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
- of '\"':
- # check for extended raw string literal:
- var rawMode = L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars
- getString(L, tok, rawMode)
- if rawMode:
- # tkRStrLit -> tkGStrLit
- # tkTripleStrLit -> tkGTripleStrLit
- inc(tok.tokType, 2)
- of '\'':
- tok.tokType = tkCharLit
- getCharacter(L, tok)
- tok.tokType = tkCharLit
- of '0'..'9':
- getNumber(L, tok)
- let c = L.buf[L.bufpos]
- if c in SymChars+{'_'}:
- lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
- else:
- if c in OpChars:
- getOperator(L, tok)
- elif c == nimlexbase.EndOfFile:
- tok.tokType = tkEof
- tok.indent = 0
- else:
- tok.literal = $c
- tok.tokType = tkInvalid
- lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
- inc(L.bufpos)
- atTokenEnd()
|