feat: bootstrap skeleton + lexer (Phase 0)
This commit is contained in:
+234
@@ -0,0 +1,234 @@
|
||||
import std/[os, strutils, terminal, strformat]
|
||||
import lexer, manifest
|
||||
|
||||
type
|
||||
ColorMode* = enum
|
||||
cmAuto
|
||||
cmOn
|
||||
cmOff
|
||||
|
||||
GlobalOptions* = object
|
||||
color*: ColorMode
|
||||
quiet*: bool
|
||||
verbose*: bool
|
||||
|
||||
proc printUsage*() =
|
||||
echo """Bux Programming Language (bootstrap compiler)
|
||||
|
||||
Usage: bux [options] <command> [command-options]
|
||||
|
||||
Commands:
|
||||
new <name> Create a new Bux package
|
||||
init Initialize a Bux package in the current directory
|
||||
build Build the current package
|
||||
run Build and run the current package
|
||||
check Type-check the current package
|
||||
clean Remove build artifacts
|
||||
help Show this help message
|
||||
version Show version
|
||||
|
||||
Global options:
|
||||
--color <auto|on|off> Control colored output (default: auto)
|
||||
-q, --quiet Suppress non-error output
|
||||
-v, --verbose Verbose output
|
||||
"""
|
||||
|
||||
proc parseGlobalOptions(args: seq[string]): tuple[opts: GlobalOptions, rest: seq[string], ok: bool] =
|
||||
result.opts = GlobalOptions(color: cmAuto, quiet: false, verbose: false)
|
||||
result.rest = @[]
|
||||
result.ok = true
|
||||
var i = 0
|
||||
while i < args.len:
|
||||
let arg = args[i]
|
||||
if arg == "--color":
|
||||
if i + 1 >= args.len:
|
||||
stderr.writeLine("error: --color requires an argument")
|
||||
result.ok = false
|
||||
return
|
||||
inc i
|
||||
case args[i].toLowerAscii()
|
||||
of "auto": result.opts.color = cmAuto
|
||||
of "on": result.opts.color = cmOn
|
||||
of "off": result.opts.color = cmOff
|
||||
else:
|
||||
stderr.writeLine(&"error: unknown --color value '{args[i]}'")
|
||||
result.ok = false
|
||||
return
|
||||
elif arg == "-q" or arg == "--quiet":
|
||||
result.opts.quiet = true
|
||||
elif arg == "-v" or arg == "--verbose":
|
||||
result.opts.verbose = true
|
||||
else:
|
||||
result.rest.add(arg)
|
||||
inc i
|
||||
|
||||
proc shouldUseColor(opts: GlobalOptions): bool =
|
||||
case opts.color
|
||||
of cmOn: true
|
||||
of cmOff: false
|
||||
of cmAuto: terminal.isatty(stdout)
|
||||
|
||||
proc printError(msg: string, useColor: bool) =
|
||||
if useColor:
|
||||
stdout.setForegroundColor(fgRed)
|
||||
stdout.write("error: ")
|
||||
stdout.resetAttributes()
|
||||
stdout.writeLine(msg)
|
||||
else:
|
||||
stderr.writeLine("error: " & msg)
|
||||
|
||||
proc printInfo(msg: string, useColor: bool) =
|
||||
if useColor:
|
||||
stdout.setForegroundColor(fgCyan)
|
||||
stdout.write("info: ")
|
||||
stdout.resetAttributes()
|
||||
stdout.writeLine(msg)
|
||||
else:
|
||||
echo("info: " & msg)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc cmdNew*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
if args.len < 1:
|
||||
printError("'new' requires a package name", useColor)
|
||||
return 1
|
||||
let name = args[0]
|
||||
let root = getCurrentDir() / name
|
||||
if dirExists(root):
|
||||
printError(&"directory '{name}' already exists", useColor)
|
||||
return 1
|
||||
createDir(root / "src")
|
||||
writeFile(root / "bux.toml", &"""[Package]
|
||||
Name = "{name}"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
""")
|
||||
writeFile(root / "src" / "Main.bux", """import Std::Io::PrintLine;
|
||||
|
||||
func Main() -> int {
|
||||
PrintLine(c8"Hello, Bux!");
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
if not opts.quiet:
|
||||
printInfo(&"Created Bux package '{name}'", useColor)
|
||||
return 0
|
||||
|
||||
proc cmdInit*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
let root = getCurrentDir()
|
||||
if fileExists(root / "bux.toml"):
|
||||
printError("bux.toml already exists", useColor)
|
||||
return 1
|
||||
let name = splitPath(root).tail
|
||||
writeFile(root / "bux.toml", &"""[Package]
|
||||
Name = "{name}"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
""")
|
||||
if not dirExists(root / "src"):
|
||||
createDir(root / "src")
|
||||
if not opts.quiet:
|
||||
printInfo(&"Initialized Bux package '{name}'", useColor)
|
||||
return 0
|
||||
|
||||
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
let root = getCurrentDir()
|
||||
let manifestPath = root / "bux.toml"
|
||||
if not fileExists(manifestPath):
|
||||
printError("no bux.toml found", useColor)
|
||||
return 1
|
||||
let man = loadManifest(manifestPath)
|
||||
let srcDir = root / "src"
|
||||
if not dirExists(srcDir):
|
||||
printError("no src/ directory found", useColor)
|
||||
return 1
|
||||
|
||||
var foundMain = false
|
||||
for kind, path in walkDir(srcDir):
|
||||
if kind == pcFile and path.endsWith(".bux"):
|
||||
let source = readFile(path)
|
||||
let res = tokenize(source, path)
|
||||
if res.hasErrors:
|
||||
printError(&"lex errors in {path}", useColor)
|
||||
for d in res.diagnostics:
|
||||
echo $d
|
||||
return 1
|
||||
if opts.verbose:
|
||||
printInfo(&"lexed {path} ({res.tokens.len} tokens)", useColor)
|
||||
if splitFile(path).name == "Main":
|
||||
foundMain = true
|
||||
|
||||
if not foundMain:
|
||||
printError("no Main.bux found in src/", useColor)
|
||||
return 1
|
||||
|
||||
if not opts.quiet:
|
||||
printInfo("check passed (lexer only)", useColor)
|
||||
return 0
|
||||
|
||||
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
# For now, just type-check
|
||||
let checkRes = cmdCheck(args, opts)
|
||||
if checkRes != 0:
|
||||
return checkRes
|
||||
if not opts.quiet:
|
||||
printInfo("build: nothing to do yet (no codegen)", useColor)
|
||||
return 0
|
||||
|
||||
proc cmdRun*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
let buildRes = cmdBuild(args, opts)
|
||||
if buildRes != 0:
|
||||
return buildRes
|
||||
printError("run: no executable produced yet (no codegen)", useColor)
|
||||
return 1
|
||||
|
||||
proc cmdClean*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
# Placeholder
|
||||
if not opts.quiet:
|
||||
printInfo("clean: nothing to do yet", useColor)
|
||||
return 0
|
||||
|
||||
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
|
||||
echo "bux 0.1.0 (bootstrap)"
|
||||
return 0
|
||||
|
||||
proc runCli*(args: seq[string]): int =
|
||||
let (opts, rest, ok) = parseGlobalOptions(args)
|
||||
if not ok:
|
||||
return 1
|
||||
if rest.len == 0:
|
||||
printUsage()
|
||||
return 0
|
||||
|
||||
let cmd = rest[0]
|
||||
let cmdArgs = if rest.len > 1: rest[1..^1] else: @[]
|
||||
|
||||
case cmd
|
||||
of "new": return cmdNew(cmdArgs, opts)
|
||||
of "init": return cmdInit(cmdArgs, opts)
|
||||
of "build": return cmdBuild(cmdArgs, opts)
|
||||
of "run": return cmdRun(cmdArgs, opts)
|
||||
of "check": return cmdCheck(cmdArgs, opts)
|
||||
of "clean": return cmdClean(cmdArgs, opts)
|
||||
of "version", "--version", "-v": return cmdVersion(cmdArgs, opts)
|
||||
of "help", "--help", "-h":
|
||||
printUsage()
|
||||
return 0
|
||||
else:
|
||||
let useColor = shouldUseColor(opts)
|
||||
printError(&"unknown command '{cmd}'", useColor)
|
||||
return 1
|
||||
+565
@@ -0,0 +1,565 @@
|
||||
import std/[strutils, strformat]
|
||||
import token, source_location
|
||||
|
||||
type
|
||||
LexerDiagnosticSeverity* = enum
|
||||
ldsWarning
|
||||
ldsError
|
||||
|
||||
LexerDiagnostic* = object
|
||||
severity*: LexerDiagnosticSeverity
|
||||
loc*: SourceLocation
|
||||
message*: string
|
||||
|
||||
LexerResult* = object
|
||||
tokens*: seq[Token]
|
||||
diagnostics*: seq[LexerDiagnostic]
|
||||
|
||||
proc hasErrors*(res: LexerResult): bool =
|
||||
for d in res.diagnostics:
|
||||
if d.severity == ldsError:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc `$`*(d: LexerDiagnostic): string =
|
||||
let sev = if d.severity == ldsError: "error" else: "warning"
|
||||
result = &"{sev}: {d.message} at {d.loc}"
|
||||
|
||||
type
|
||||
Lexer* = object
|
||||
source: string
|
||||
sourceName: string
|
||||
pos: int
|
||||
line: uint32
|
||||
col: uint32
|
||||
tokens: seq[Token]
|
||||
diagnostics: seq[LexerDiagnostic]
|
||||
|
||||
proc initLexer*(source, sourceName: string): Lexer =
|
||||
result.source = source
|
||||
result.sourceName = sourceName
|
||||
result.pos = 0
|
||||
result.line = 1
|
||||
result.col = 1
|
||||
|
||||
proc isAtEnd(lex: Lexer): bool =
|
||||
lex.pos >= lex.source.len
|
||||
|
||||
proc peek(lex: Lexer, ahead: int = 0): char =
|
||||
let i = lex.pos + ahead
|
||||
if i < lex.source.len:
|
||||
return lex.source[i]
|
||||
return '\0'
|
||||
|
||||
proc advance(lex: var Lexer): char =
|
||||
result = lex.peek()
|
||||
if not lex.isAtEnd():
|
||||
inc lex.pos
|
||||
if result == '\n':
|
||||
inc lex.line
|
||||
lex.col = 1
|
||||
else:
|
||||
inc lex.col
|
||||
|
||||
proc match(lex: var Lexer, expected: char): bool =
|
||||
if lex.isAtEnd(): return false
|
||||
if lex.peek() != expected: return false
|
||||
discard lex.advance()
|
||||
return true
|
||||
|
||||
proc matchStr(lex: var Lexer, s: string): bool =
|
||||
for i, c in s:
|
||||
if lex.peek(i) != c:
|
||||
return false
|
||||
for _ in s:
|
||||
discard lex.advance()
|
||||
return true
|
||||
|
||||
proc currentLocation(lex: Lexer): SourceLocation =
|
||||
result = SourceLocation(line: lex.line, column: lex.col, offset: uint32(lex.pos))
|
||||
|
||||
proc emitError(lex: var Lexer, loc: SourceLocation, message: string) =
|
||||
lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message))
|
||||
|
||||
proc emitWarning(lex: var Lexer, loc: SourceLocation, message: string) =
|
||||
lex.diagnostics.add(LexerDiagnostic(severity: ldsWarning, loc: loc, message: message))
|
||||
|
||||
proc makeToken(lex: Lexer, kind: TokenKind, startLoc: SourceLocation, startPos: int): Token =
|
||||
let text = lex.source[startPos ..< lex.pos]
|
||||
result = Token(kind: kind, text: text, loc: startLoc)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Whitespace / comments
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc skipLineComment(lex: var Lexer) =
|
||||
while not lex.isAtEnd() and lex.peek() != '\n':
|
||||
discard lex.advance()
|
||||
|
||||
proc skipBlockComment(lex: var Lexer) =
|
||||
let startLoc = lex.currentLocation()
|
||||
var depth = 1
|
||||
while not lex.isAtEnd() and depth > 0:
|
||||
if lex.peek() == '/' and lex.peek(1) == '*':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
inc depth
|
||||
elif lex.peek() == '*' and lex.peek(1) == '/':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
dec depth
|
||||
else:
|
||||
discard lex.advance()
|
||||
if depth > 0:
|
||||
lex.emitError(startLoc, "unterminated block comment")
|
||||
|
||||
proc skipWhitespace(lex: var Lexer) =
|
||||
while not lex.isAtEnd():
|
||||
let c = lex.peek()
|
||||
if c in {' ', '\t', '\r'}:
|
||||
discard lex.advance()
|
||||
elif c == '/' and lex.peek(1) == '/':
|
||||
lex.skipLineComment()
|
||||
elif c == '/' and lex.peek(1) == '*':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
lex.skipBlockComment()
|
||||
else:
|
||||
break
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Identifiers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc isIdentStart(c: char): bool =
|
||||
c in {'a'..'z', 'A'..'Z', '_'}
|
||||
|
||||
proc isIdentChar(c: char): bool =
|
||||
c in {'a'..'z', 'A'..'Z', '0'..'9', '_'}
|
||||
|
||||
proc scanIdent(lex: var Lexer, startLoc: SourceLocation): Token =
|
||||
let startPos = lex.pos
|
||||
while not lex.isAtEnd() and isIdentChar(lex.peek()):
|
||||
discard lex.advance()
|
||||
let text = lex.source[startPos ..< lex.pos]
|
||||
let kind = keywordKind(text)
|
||||
result = Token(kind: kind, text: text, loc: startLoc)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Numbers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc scanIntSuffix(lex: var Lexer) =
|
||||
# i8, i16, i32, i64, u8, u16, u32, u64, f32, f64
|
||||
if lex.isAtEnd(): return
|
||||
let c = lex.peek()
|
||||
if c in {'i', 'u', 'f'}:
|
||||
discard lex.advance()
|
||||
while not lex.isAtEnd() and lex.peek() in {'0'..'9'}:
|
||||
discard lex.advance()
|
||||
|
||||
proc scanHexDigits(lex: var Lexer) =
|
||||
while not lex.isAtEnd() and lex.peek() in {'0'..'9', 'a'..'f', 'A'..'F'}:
|
||||
discard lex.advance()
|
||||
|
||||
proc scanBinDigits(lex: var Lexer) =
|
||||
while not lex.isAtEnd() and lex.peek() in {'0', '1'}:
|
||||
discard lex.advance()
|
||||
|
||||
proc scanOctDigits(lex: var Lexer) =
|
||||
while not lex.isAtEnd() and lex.peek() in {'0'..'7'}:
|
||||
discard lex.advance()
|
||||
|
||||
proc scanDecDigits(lex: var Lexer) =
|
||||
while not lex.isAtEnd() and lex.peek() in {'0'..'9'}:
|
||||
discard lex.advance()
|
||||
|
||||
proc scanNumber(lex: var Lexer, startLoc: SourceLocation): Token =
|
||||
let startPos = lex.pos
|
||||
var isFloat = false
|
||||
|
||||
if lex.peek() == '0' and lex.peek(1) in {'x', 'X', 'b', 'B', 'o', 'O'}:
|
||||
discard lex.advance() # '0'
|
||||
let prefix = lex.advance()
|
||||
case prefix
|
||||
of 'x', 'X': lex.scanHexDigits()
|
||||
of 'b', 'B': lex.scanBinDigits()
|
||||
of 'o', 'O': lex.scanOctDigits()
|
||||
else: discard
|
||||
lex.scanIntSuffix()
|
||||
return lex.makeToken(tkIntLiteral, startLoc, startPos)
|
||||
|
||||
lex.scanDecDigits()
|
||||
|
||||
# Fractional part
|
||||
if lex.peek() == '.' and lex.peek(1) in {'0'..'9'}:
|
||||
isFloat = true
|
||||
discard lex.advance() # '.'
|
||||
lex.scanDecDigits()
|
||||
|
||||
# Exponent
|
||||
if lex.peek() in {'e', 'E'}:
|
||||
isFloat = true
|
||||
discard lex.advance()
|
||||
if lex.peek() in {'+', '-'}:
|
||||
discard lex.advance()
|
||||
if lex.peek() notin {'0'..'9'}:
|
||||
lex.emitError(lex.currentLocation(), "expected digits in exponent")
|
||||
else:
|
||||
lex.scanDecDigits()
|
||||
|
||||
if isFloat:
|
||||
# Optional f32/f64 suffix
|
||||
if lex.peek() == 'f' and lex.peek(1) in {'3', '6'}:
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
result = lex.makeToken(tkFloatLiteral, startLoc, startPos)
|
||||
else:
|
||||
lex.scanIntSuffix()
|
||||
result = lex.makeToken(tkIntLiteral, startLoc, startPos)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Strings and chars
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc scanEscapeSequence(lex: var Lexer): string =
|
||||
if lex.isAtEnd():
|
||||
return ""
|
||||
let c = lex.advance()
|
||||
case c
|
||||
of '\\': result = "\\"
|
||||
of '"': result = "\""
|
||||
of '\'': result = "\'"
|
||||
of 'n': result = "\n"
|
||||
of 'r': result = "\r"
|
||||
of 't': result = "\t"
|
||||
of '0': result = "\0"
|
||||
of 'x':
|
||||
var hexVal = ""
|
||||
for _ in 0..<2:
|
||||
if lex.isAtEnd() or lex.peek() notin {'0'..'9', 'a'..'f', 'A'..'F'}:
|
||||
lex.emitError(lex.currentLocation(), "expected two hex digits after \\x")
|
||||
break
|
||||
hexVal.add(lex.advance())
|
||||
if hexVal.len == 2:
|
||||
try:
|
||||
let code = parseHexInt(hexVal)
|
||||
result = $chr(code)
|
||||
except ValueError:
|
||||
lex.emitError(lex.currentLocation(), &"invalid hex escape \\x{hexVal}")
|
||||
result = ""
|
||||
else:
|
||||
result = ""
|
||||
else:
|
||||
lex.emitError(lex.currentLocation(), &"unknown escape sequence \\\\{c}")
|
||||
result = $c
|
||||
|
||||
proc scanString(lex: var Lexer, startLoc: SourceLocation, prefixLen: int): Token =
|
||||
let startPos = lex.pos - prefixLen
|
||||
# prefixLen characters before the opening quote were already consumed by caller
|
||||
# but we need to handle the quote itself
|
||||
if lex.peek() == '"':
|
||||
discard lex.advance()
|
||||
while not lex.isAtEnd() and lex.peek() != '"':
|
||||
if lex.peek() == '\n':
|
||||
lex.emitError(lex.currentLocation(), "unterminated string literal")
|
||||
break
|
||||
if lex.peek() == '\\':
|
||||
discard lex.advance()
|
||||
discard lex.scanEscapeSequence()
|
||||
else:
|
||||
discard lex.advance()
|
||||
if lex.isAtEnd():
|
||||
lex.emitError(startLoc, "unterminated string literal")
|
||||
else:
|
||||
discard lex.advance() # closing "
|
||||
result = lex.makeToken(tkStringLiteral, startLoc, startPos)
|
||||
|
||||
proc scanChar(lex: var Lexer, startLoc: SourceLocation, prefixLen: int): Token =
|
||||
let startPos = lex.pos - prefixLen
|
||||
if lex.peek() == '\'':
|
||||
discard lex.advance()
|
||||
if lex.isAtEnd():
|
||||
lex.emitError(startLoc, "unterminated char literal")
|
||||
return lex.makeToken(tkCharLiteral, startLoc, startPos)
|
||||
if lex.peek() == '\n':
|
||||
lex.emitError(lex.currentLocation(), "newline in char literal")
|
||||
elif lex.peek() == '\\':
|
||||
discard lex.advance()
|
||||
discard lex.scanEscapeSequence()
|
||||
else:
|
||||
discard lex.advance()
|
||||
if lex.isAtEnd() or lex.peek() != '\'':
|
||||
lex.emitError(lex.currentLocation(), "expected closing ' for char literal")
|
||||
else:
|
||||
discard lex.advance()
|
||||
result = lex.makeToken(tkCharLiteral, startLoc, startPos)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Symbols / operators
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc scanSymbol(lex: var Lexer, startLoc: SourceLocation): Token =
|
||||
let startPos = lex.pos
|
||||
let c1 = lex.advance()
|
||||
|
||||
template check2(c2: char, kind2: TokenKind, kind1: TokenKind) =
|
||||
if lex.peek() == c2:
|
||||
discard lex.advance()
|
||||
return lex.makeToken(kind2, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(kind1, startLoc, startPos)
|
||||
|
||||
template check3(c2: char, kind2: TokenKind, c3: char, kind3: TokenKind, kind1: TokenKind) =
|
||||
if lex.peek() == c2:
|
||||
discard lex.advance()
|
||||
if lex.peek() == c3:
|
||||
discard lex.advance()
|
||||
return lex.makeToken(kind3, startLoc, startPos)
|
||||
return lex.makeToken(kind2, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(kind1, startLoc, startPos)
|
||||
|
||||
template checkEq(c2: char, kind2: TokenKind, kind1: TokenKind) =
|
||||
check2(c2, kind2, kind1)
|
||||
|
||||
case c1
|
||||
of '(': return lex.makeToken(tkLParen, startLoc, startPos)
|
||||
of ')': return lex.makeToken(tkRParen, startLoc, startPos)
|
||||
of '{': return lex.makeToken(tkLBrace, startLoc, startPos)
|
||||
of '}': return lex.makeToken(tkRBrace, startLoc, startPos)
|
||||
of '[': return lex.makeToken(tkLBracket, startLoc, startPos)
|
||||
of ']': return lex.makeToken(tkRBracket, startLoc, startPos)
|
||||
of ',': return lex.makeToken(tkComma, startLoc, startPos)
|
||||
of ';': return lex.makeToken(tkSemicolon, startLoc, startPos)
|
||||
of '@': return lex.makeToken(tkAt, startLoc, startPos)
|
||||
of '?': return lex.makeToken(tkQuestion, startLoc, startPos)
|
||||
of '~': return lex.makeToken(tkTilde, startLoc, startPos)
|
||||
of ':': check2(':', tkColonColon, tkColon)
|
||||
of '.':
|
||||
if lex.peek() == '.' and lex.peek(1) == '.':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkDotDotDot, startLoc, startPos)
|
||||
elif lex.peek() == '.' and lex.peek(1) == '=':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkDotDotEqual, startLoc, startPos)
|
||||
elif lex.peek() == '.':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkDotDot, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkDot, startLoc, startPos)
|
||||
of '-':
|
||||
if lex.peek() == '>':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkArrow, startLoc, startPos)
|
||||
elif lex.peek() == '-':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkMinusMinus, startLoc, startPos)
|
||||
elif lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkMinusAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkMinus, startLoc, startPos)
|
||||
of '+':
|
||||
if lex.peek() == '+':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkPlusPlus, startLoc, startPos)
|
||||
elif lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkPlusAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkPlus, startLoc, startPos)
|
||||
of '*':
|
||||
if lex.peek() == '*':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkStarStar, startLoc, startPos)
|
||||
elif lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkStarAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkStar, startLoc, startPos)
|
||||
of '/':
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkSlashAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkSlash, startLoc, startPos)
|
||||
of '%':
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkPercentAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkPercent, startLoc, startPos)
|
||||
of '=':
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkEq, startLoc, startPos)
|
||||
elif lex.peek() == '>':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkFatArrow, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkAssign, startLoc, startPos)
|
||||
of '!':
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkNe, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkBang, startLoc, startPos)
|
||||
of '<':
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkLe, startLoc, startPos)
|
||||
elif lex.peek() == '<':
|
||||
discard lex.advance()
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkShlAssign, startLoc, startPos)
|
||||
return lex.makeToken(tkShl, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkLt, startLoc, startPos)
|
||||
of '>':
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkGe, startLoc, startPos)
|
||||
elif lex.peek() == '>':
|
||||
discard lex.advance()
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkShrAssign, startLoc, startPos)
|
||||
return lex.makeToken(tkShr, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkGt, startLoc, startPos)
|
||||
of '&':
|
||||
if lex.peek() == '&':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkAmpAmp, startLoc, startPos)
|
||||
elif lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkAmpAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkAmp, startLoc, startPos)
|
||||
of '|':
|
||||
if lex.peek() == '|':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkPipePipe, startLoc, startPos)
|
||||
elif lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkPipeAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkPipe, startLoc, startPos)
|
||||
of '^':
|
||||
if lex.peek() == '=':
|
||||
discard lex.advance()
|
||||
return lex.makeToken(tkCaretAssign, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkCaret, startLoc, startPos)
|
||||
of '#':
|
||||
# Check for intrinsics: #line, #column, #file, #function, #date, #time, #module
|
||||
let afterHash = lex.peek()
|
||||
if afterHash == 'l' and lex.matchStr("line"):
|
||||
return lex.makeToken(tkHashLine, startLoc, startPos)
|
||||
elif afterHash == 'c' and lex.matchStr("column"):
|
||||
return lex.makeToken(tkHashColumn, startLoc, startPos)
|
||||
elif afterHash == 'f' and lex.peek(1) == 'i' and lex.peek(2) == 'l' and lex.peek(3) == 'e':
|
||||
discard lex.matchStr("file")
|
||||
return lex.makeToken(tkHashFile, startLoc, startPos)
|
||||
elif afterHash == 'f' and lex.peek(1) == 'u' and lex.peek(2) == 'n':
|
||||
discard lex.matchStr("function")
|
||||
return lex.makeToken(tkHashFunction, startLoc, startPos)
|
||||
elif afterHash == 'd' and lex.matchStr("date"):
|
||||
return lex.makeToken(tkHashDate, startLoc, startPos)
|
||||
elif afterHash == 't' and lex.matchStr("time"):
|
||||
return lex.makeToken(tkHashTime, startLoc, startPos)
|
||||
elif afterHash == 'm' and lex.matchStr("module"):
|
||||
return lex.makeToken(tkHashModule, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(tkHash, startLoc, startPos)
|
||||
else:
|
||||
lex.emitError(startLoc, &"unexpected character '{c1}'")
|
||||
return lex.makeToken(tkUnknown, startLoc, startPos)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main scanning loop
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc nextToken(lex: var Lexer): Token =
|
||||
lex.skipWhitespace()
|
||||
let startLoc = lex.currentLocation()
|
||||
let startPos = lex.pos
|
||||
|
||||
if lex.isAtEnd():
|
||||
return Token(kind: tkEndOfFile, text: "", loc: startLoc)
|
||||
|
||||
let c = lex.peek()
|
||||
|
||||
if c == '\n':
|
||||
discard lex.advance()
|
||||
return Token(kind: tkNewLine, text: "\n", loc: startLoc)
|
||||
|
||||
# String prefixes: c8" c16" c32" — must come before ident check
|
||||
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
|
||||
let d = lex.peek(1)
|
||||
if d == '8' and lex.peek(2) == '"':
|
||||
discard lex.advance() # c
|
||||
discard lex.advance() # 8
|
||||
return lex.scanString(startLoc, 2)
|
||||
elif d == '1' and lex.peek(2) == '6' and lex.peek(3) == '"':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
return lex.scanString(startLoc, 3)
|
||||
elif d == '3' and lex.peek(2) == '2' and lex.peek(3) == '"':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
return lex.scanString(startLoc, 3)
|
||||
|
||||
if c == '"':
|
||||
return lex.scanString(startLoc, 0)
|
||||
|
||||
# Char prefixes: c8' c16' c32' — must come before ident check
|
||||
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
|
||||
let d = lex.peek(1)
|
||||
if d == '8' and lex.peek(2) == '\'':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
return lex.scanChar(startLoc, 2)
|
||||
elif d == '1' and lex.peek(2) == '6' and lex.peek(3) == '\'':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
return lex.scanChar(startLoc, 3)
|
||||
elif d == '3' and lex.peek(2) == '2' and lex.peek(3) == '\'':
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
discard lex.advance()
|
||||
return lex.scanChar(startLoc, 3)
|
||||
|
||||
if c == '\'':
|
||||
return lex.scanChar(startLoc, 0)
|
||||
|
||||
if isIdentStart(c):
|
||||
return lex.scanIdent(startLoc)
|
||||
|
||||
if c in {'0'..'9'}:
|
||||
return lex.scanNumber(startLoc)
|
||||
|
||||
return lex.scanSymbol(startLoc)
|
||||
|
||||
proc tokenize*(lex: var Lexer): LexerResult =
|
||||
while true:
|
||||
let tok = lex.nextToken()
|
||||
lex.tokens.add(tok)
|
||||
if tok.kind == tkEndOfFile:
|
||||
break
|
||||
result = LexerResult(tokens: lex.tokens, diagnostics: lex.diagnostics)
|
||||
|
||||
proc tokenize*(source, sourceName: string): LexerResult =
|
||||
var lex = initLexer(source, sourceName)
|
||||
result = lex.tokenize()
|
||||
|
||||
proc dumpTokens*(res: LexerResult): string =
|
||||
for tok in res.tokens:
|
||||
result.add($tok & "\n")
|
||||
@@ -0,0 +1,6 @@
|
||||
import std/os
|
||||
import cli
|
||||
|
||||
when isMainModule:
|
||||
let args = commandLineParams()
|
||||
quit(runCli(args))
|
||||
@@ -0,0 +1,79 @@
|
||||
import std/[strutils, os, tables]
|
||||
|
||||
type
|
||||
PackageType* = enum
|
||||
ptExecutable
|
||||
ptSharedLibrary
|
||||
ptStaticLibrary
|
||||
ptSource
|
||||
|
||||
Manifest* = object
|
||||
name*: string
|
||||
version*: string
|
||||
pkgType*: PackageType
|
||||
output*: string ## from [Build] Output
|
||||
dependencies*: OrderedTableRef[string, string]
|
||||
|
||||
type
|
||||
TomlValueKind = enum
|
||||
tvkString, tvkTable
|
||||
TomlValue = object
|
||||
case kind*: TomlValueKind
|
||||
of tvkString:
|
||||
strVal*: string
|
||||
of tvkTable:
|
||||
tableVal*: OrderedTableRef[string, TomlValue]
|
||||
|
||||
proc parseSimpleToml(path: string): OrderedTableRef[string, TomlValue] =
|
||||
result = newOrderedTable[string, TomlValue]()
|
||||
if not fileExists(path):
|
||||
return result
|
||||
let content = readFile(path)
|
||||
var currentTable = ""
|
||||
for rawLine in content.splitLines():
|
||||
let line = rawLine.strip()
|
||||
if line.len == 0 or line.startsWith("#"):
|
||||
continue
|
||||
if line.startsWith("[") and line.endsWith("]"):
|
||||
currentTable = line[1 ..< ^1]
|
||||
if not result.hasKey(currentTable):
|
||||
result[currentTable] = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]())
|
||||
else:
|
||||
let eqIdx = line.find('=')
|
||||
if eqIdx >= 0:
|
||||
let key = line[0 ..< eqIdx].strip()
|
||||
var val = line[eqIdx + 1 .. ^1].strip()
|
||||
# Remove surrounding quotes
|
||||
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
|
||||
val = val[1 ..< ^1]
|
||||
if currentTable != "" and result.hasKey(currentTable):
|
||||
result[currentTable].tableVal[key] = TomlValue(kind: tvkString, strVal: val)
|
||||
else:
|
||||
result[key] = TomlValue(kind: tvkString, strVal: val)
|
||||
|
||||
proc loadManifest*(path: string): Manifest =
|
||||
let data = parseSimpleToml(path)
|
||||
result.name = ""
|
||||
result.version = "0.1.0"
|
||||
result.pkgType = ptExecutable
|
||||
result.output = "Bin"
|
||||
result.dependencies = newOrderedTable[string, string]()
|
||||
|
||||
if data.hasKey("Package"):
|
||||
let pkg = data["Package"].tableVal
|
||||
if pkg.hasKey("Name"):
|
||||
result.name = pkg["Name"].strVal
|
||||
if pkg.hasKey("Version"):
|
||||
result.version = pkg["Version"].strVal
|
||||
if pkg.hasKey("Type"):
|
||||
case pkg["Type"].strVal.toLowerAscii()
|
||||
of "bin", "executable": result.pkgType = ptExecutable
|
||||
of "shared", "sharedlibrary", "dll", "so", "dylib": result.pkgType = ptSharedLibrary
|
||||
of "static", "staticlibrary", "lib", "a": result.pkgType = ptStaticLibrary
|
||||
of "source": result.pkgType = ptSource
|
||||
else: result.pkgType = ptExecutable
|
||||
|
||||
if data.hasKey("Build"):
|
||||
let bld = data["Build"].tableVal
|
||||
if bld.hasKey("Output"):
|
||||
result.output = bld["Output"].strVal
|
||||
@@ -0,0 +1,8 @@
|
||||
type
|
||||
SourceLocation* = object
|
||||
line*: uint32 ## 1-based
|
||||
column*: uint32 ## 1-based (UTF-8 byte offset in line)
|
||||
offset*: uint32 ## byte offset from start of file
|
||||
|
||||
proc `$`*(loc: SourceLocation): string =
|
||||
$loc.line & ":" & $loc.column
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
import source_location
|
||||
|
||||
type
|
||||
TokenKind* = enum
|
||||
##Literals
|
||||
tkIntLiteral # 42 0xFF 0b1010 0o77
|
||||
tkFloatLiteral # 3.14 1.0e-9
|
||||
tkStringLiteral # "hello" c8"hello" c16"hello" c32"hello"
|
||||
tkCharLiteral # 'A' c8'A' c16'A' c32'A'
|
||||
tkBoolLiteral # true false
|
||||
|
||||
##Identifiers
|
||||
tkIdent # foo Bar _x
|
||||
|
||||
##Control flow keywords
|
||||
tkIf # if
|
||||
tkElse # else
|
||||
tkWhile # while
|
||||
tkDo # do
|
||||
tkLoop # loop
|
||||
tkFor # for
|
||||
tkIn # in
|
||||
tkBreak # break
|
||||
tkContinue # continue
|
||||
tkReturn # return
|
||||
tkMatch # match
|
||||
|
||||
##Declaration keywords
|
||||
tkFunc # func
|
||||
tkLet # let
|
||||
tkVar # var
|
||||
tkConst # const
|
||||
tkType # type
|
||||
tkStruct # struct
|
||||
tkEnum # enum
|
||||
tkUnion # union
|
||||
tkInterface # interface
|
||||
tkExtend # extend
|
||||
tkModule # module
|
||||
tkImport # import
|
||||
tkPub # pub
|
||||
tkExtern # extern
|
||||
|
||||
##Other keywords
|
||||
tkAs # as
|
||||
tkIs # is
|
||||
tkNull # null
|
||||
tkSelf # self
|
||||
tkSuper # super
|
||||
|
||||
##Punctuation
|
||||
tkLParen # (
|
||||
tkRParen # )
|
||||
tkLBrace # {
|
||||
tkRBrace # }
|
||||
tkLBracket # [
|
||||
tkRBracket # ]
|
||||
tkComma # ,
|
||||
tkSemicolon # ;
|
||||
tkColon # :
|
||||
tkColonColon # ::
|
||||
tkDot # .
|
||||
tkDotDot # ..
|
||||
tkDotDotDot # ...
|
||||
tkDotDotEqual # ..=
|
||||
tkArrow # ->
|
||||
tkFatArrow # =>
|
||||
tkAt # @
|
||||
tkHash # #
|
||||
tkQuestion # ?
|
||||
|
||||
##Arithmetic operators
|
||||
tkPlus # +
|
||||
tkMinus # -
|
||||
tkStar # *
|
||||
tkSlash # /
|
||||
tkPercent # %
|
||||
tkStarStar # **
|
||||
tkPlusPlus # ++
|
||||
tkMinusMinus # --
|
||||
|
||||
##Bitwise operators
|
||||
tkAmp # &
|
||||
tkPipe # |
|
||||
tkCaret # ^
|
||||
tkTilde # ~
|
||||
tkShl # <<
|
||||
tkShr # >>
|
||||
|
||||
##Logical operators
|
||||
tkAmpAmp # &&
|
||||
tkPipePipe # ||
|
||||
tkBang # !
|
||||
|
||||
##Comparison operators
|
||||
tkEq # ==
|
||||
tkNe # !=
|
||||
tkLt # <
|
||||
tkLe # <=
|
||||
tkGt # >
|
||||
tkGe # >=
|
||||
|
||||
##Assignment operators
|
||||
tkAssign # =
|
||||
tkPlusAssign # +=
|
||||
tkMinusAssign # -=
|
||||
tkStarAssign # *=
|
||||
tkSlashAssign # /=
|
||||
tkPercentAssign # %=
|
||||
tkAmpAssign # &=
|
||||
tkPipeAssign # |=
|
||||
tkCaretAssign # ^=
|
||||
tkShlAssign # <<=
|
||||
tkShrAssign # >>=
|
||||
|
||||
##Compile-time intrinsics
|
||||
tkHashLine # #line
|
||||
tkHashColumn # #column
|
||||
tkHashFile # #file
|
||||
tkHashFunction # #function
|
||||
tkHashDate # #date
|
||||
tkHashTime # #time
|
||||
tkHashModule # #module
|
||||
|
||||
##Special
|
||||
tkNewLine # significant newline (if grammar uses them)
|
||||
tkEndOfFile # end of file
|
||||
tkUnknown # unrecognized character
|
||||
|
||||
Token* = object
|
||||
kind*: TokenKind
|
||||
text*: string # original source spelling
|
||||
loc*: SourceLocation
|
||||
|
||||
proc isKeyword*(kind: TokenKind): bool =
|
||||
case kind
|
||||
of tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn,
|
||||
tkBreak, tkContinue, tkReturn, tkMatch,
|
||||
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
|
||||
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
|
||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper:
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
proc isLiteral*(kind: TokenKind): bool =
|
||||
case kind
|
||||
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
proc isOperator*(kind: TokenKind): bool =
|
||||
case kind
|
||||
of tkPlus..tkShrAssign:
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
proc isEof*(kind: TokenKind): bool = kind == tkEndOfFile
|
||||
|
||||
proc keywordKind*(text: string): TokenKind =
|
||||
case text
|
||||
of "func": tkFunc
|
||||
of "let": tkLet
|
||||
of "var": tkVar
|
||||
of "const": tkConst
|
||||
of "type": tkType
|
||||
of "struct": tkStruct
|
||||
of "enum": tkEnum
|
||||
of "union": tkUnion
|
||||
of "interface": tkInterface
|
||||
of "extend": tkExtend
|
||||
of "module": tkModule
|
||||
of "import": tkImport
|
||||
of "pub": tkPub
|
||||
of "extern": tkExtern
|
||||
of "if": tkIf
|
||||
of "else": tkElse
|
||||
of "while": tkWhile
|
||||
of "do": tkDo
|
||||
of "loop": tkLoop
|
||||
of "for": tkFor
|
||||
of "in": tkIn
|
||||
of "break": tkBreak
|
||||
of "continue": tkContinue
|
||||
of "return": tkReturn
|
||||
of "match": tkMatch
|
||||
of "as": tkAs
|
||||
of "is": tkIs
|
||||
of "null": tkNull
|
||||
of "self": tkSelf
|
||||
of "super": tkSuper
|
||||
of "true", "false": tkBoolLiteral
|
||||
else: tkIdent
|
||||
|
||||
proc tokenKindName*(kind: TokenKind): string =
|
||||
case kind
|
||||
of tkIntLiteral: "integer literal"
|
||||
of tkFloatLiteral: "float literal"
|
||||
of tkStringLiteral: "string literal"
|
||||
of tkCharLiteral: "char literal"
|
||||
of tkBoolLiteral: "boolean literal"
|
||||
of tkIdent: "identifier"
|
||||
of tkIf: "'if'"
|
||||
of tkElse: "'else'"
|
||||
of tkWhile: "'while'"
|
||||
of tkDo: "'do'"
|
||||
of tkLoop: "'loop'"
|
||||
of tkFor: "'for'"
|
||||
of tkIn: "'in'"
|
||||
of tkBreak: "'break'"
|
||||
of tkContinue: "'continue'"
|
||||
of tkReturn: "'return'"
|
||||
of tkMatch: "'match'"
|
||||
of tkFunc: "'func'"
|
||||
of tkLet: "'let'"
|
||||
of tkVar: "'var'"
|
||||
of tkConst: "'const'"
|
||||
of tkType: "'type'"
|
||||
of tkStruct: "'struct'"
|
||||
of tkEnum: "'enum'"
|
||||
of tkUnion: "'union'"
|
||||
of tkInterface: "'interface'"
|
||||
of tkExtend: "'extend'"
|
||||
of tkModule: "'module'"
|
||||
of tkImport: "'import'"
|
||||
of tkPub: "'pub'"
|
||||
of tkExtern: "'extern'"
|
||||
of tkAs: "'as'"
|
||||
of tkIs: "'is'"
|
||||
of tkNull: "'null'"
|
||||
of tkSelf: "'self'"
|
||||
of tkSuper: "'super'"
|
||||
of tkLParen: "'('"
|
||||
of tkRParen: "')'"
|
||||
of tkLBrace: "'{'"
|
||||
of tkRBrace: "'}'"
|
||||
of tkLBracket: "'['"
|
||||
of tkRBracket: "']'"
|
||||
of tkComma: "','"
|
||||
of tkSemicolon: "';'"
|
||||
of tkColon: "':'"
|
||||
of tkColonColon: "'::'"
|
||||
of tkDot: "'.'"
|
||||
of tkDotDot: "'..'"
|
||||
of tkDotDotDot: "'...'"
|
||||
of tkDotDotEqual: "'..='"
|
||||
of tkArrow: "'->'"
|
||||
of tkFatArrow: "'=>'"
|
||||
of tkAt: "'@'"
|
||||
of tkHash: "'#'"
|
||||
of tkQuestion: "'?'"
|
||||
of tkPlus: "'+'"
|
||||
of tkMinus: "'-'"
|
||||
of tkStar: "'*'"
|
||||
of tkSlash: "'/'"
|
||||
of tkPercent: "'%'"
|
||||
of tkStarStar: "'**'"
|
||||
of tkPlusPlus: "'++'"
|
||||
of tkMinusMinus: "'--'"
|
||||
of tkAmp: "'&'"
|
||||
of tkPipe: "'|'"
|
||||
of tkCaret: "'^'"
|
||||
of tkTilde: "'~'"
|
||||
of tkShl: "'<<'"
|
||||
of tkShr: "'>>'"
|
||||
of tkAmpAmp: "'&&'"
|
||||
of tkPipePipe: "'||'"
|
||||
of tkBang: "'!'"
|
||||
of tkEq: "'=='"
|
||||
of tkNe: "'!='"
|
||||
of tkLt: "'<'"
|
||||
of tkLe: "'<='"
|
||||
of tkGt: "'>'"
|
||||
of tkGe: "'>='"
|
||||
of tkAssign: "'='"
|
||||
of tkPlusAssign: "'+='"
|
||||
of tkMinusAssign: "'-='"
|
||||
of tkStarAssign: "'*='"
|
||||
of tkSlashAssign: "'/='"
|
||||
of tkPercentAssign: "'%='"
|
||||
of tkAmpAssign: "'&='"
|
||||
of tkPipeAssign: "'|='"
|
||||
of tkCaretAssign: "'^='"
|
||||
of tkShlAssign: "'<<='"
|
||||
of tkShrAssign: "'>>='"
|
||||
of tkHashLine: "'#line'"
|
||||
of tkHashColumn: "'#column'"
|
||||
of tkHashFile: "'#file'"
|
||||
of tkHashFunction: "'#function'"
|
||||
of tkHashDate: "'#date'"
|
||||
of tkHashTime: "'#time'"
|
||||
of tkHashModule: "'#module'"
|
||||
of tkNewLine: "newline"
|
||||
of tkEndOfFile: "end of file"
|
||||
of tkUnknown: "unknown token"
|
||||
|
||||
proc `$`*(tok: Token): string =
|
||||
result = tokenKindName(tok.kind) & " '" & tok.text & "' @ " & $tok.loc
|
||||
Reference in New Issue
Block a user