Major error formatting refactoring. Improved warning system
This commit is contained in:
parent
43d67562f1
commit
9567319c40
|
@ -687,10 +687,11 @@ when debugVM: # So nim shuts up
|
||||||
styledEcho fgMagenta, "]"
|
styledEcho fgMagenta, "]"
|
||||||
of "f", "frame":
|
of "f", "frame":
|
||||||
stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[")
|
stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[")
|
||||||
for i, e in self.calls[self.frames[^1]..^1]:
|
if self.frames.len() > 0:
|
||||||
stdout.styledWrite(fgYellow, $e)
|
for i, e in self.calls[self.frames[^1]..^1]:
|
||||||
if i < (self.calls.high() - self.frames[^1].int):
|
stdout.styledWrite(fgYellow, $e)
|
||||||
stdout.styledWrite(fgYellow, ", ")
|
if i < (self.calls.high() - self.frames[^1].int):
|
||||||
|
stdout.styledWrite(fgYellow, ", ")
|
||||||
styledEcho fgMagenta, "]", fgCyan
|
styledEcho fgMagenta, "]", fgCyan
|
||||||
of "frames":
|
of "frames":
|
||||||
stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[")
|
stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[")
|
||||||
|
@ -721,7 +722,7 @@ when debugVM: # So nim shuts up
|
||||||
stdout.styledWrite(fgYellow, ", ")
|
stdout.styledWrite(fgYellow, ", ")
|
||||||
styledEcho fgMagenta, "]"
|
styledEcho fgMagenta, "]"
|
||||||
of "clear":
|
of "clear":
|
||||||
stdout.write("\x1Bc")
|
stdout.write("\x1Bc")
|
||||||
else:
|
else:
|
||||||
styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'")
|
styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'")
|
||||||
|
|
||||||
|
|
|
@ -35,29 +35,33 @@ when HeapGrowFactor <= 1:
|
||||||
const PeonVersion* = (major: 0, minor: 1, patch: 0)
|
const PeonVersion* = (major: 0, minor: 1, patch: 0)
|
||||||
const PeonRelease* = "alpha"
|
const PeonRelease* = "alpha"
|
||||||
const PeonCommitHash* = staticExec("git rev-parse HEAD")
|
const PeonCommitHash* = staticExec("git rev-parse HEAD")
|
||||||
const PeonBranch* = staticExec("""git symbolic-ref HEAD 2>/dev/null | cut -d"/" -f 3 """")
|
const PeonBranch* = staticExec("git symbolic-ref HEAD 2>/dev/null | cut -f 3 -d /")
|
||||||
const PeonVersionString* = &"Peon {PeonVersion.major}.{PeonVersion.minor}.{PeonVersion.patch} {PeonRelease} ({PeonBranch[0..8]}, {CompileDate}, {CompileTime}, {PeonCommitHash}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
|
const PeonVersionString* = &"Peon {PeonVersion.major}.{PeonVersion.minor}.{PeonVersion.patch} {PeonRelease} ({PeonBranch[0..(if len(PeonBranch) > 8: 8 else: PeonBranch.high())]}, {CompileDate}, {CompileTime}, {PeonCommitHash}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
|
||||||
const HelpMessage* = """The peon programming language, Copyright (C) 2022 Mattia Giambirtone & All Contributors
|
const HelpMessage* = """The peon programming language, Copyright (C) 2022 Mattia Giambirtone & All Contributors
|
||||||
|
|
||||||
This program is free software, see the license distributed with this program or check
|
This program is free software, see the license distributed with this program or check
|
||||||
http://www.apache.org/licenses/LICENSE-2.0 for more info.
|
http://www.apache.org/licenses/LICENSE-2.0 for more info.
|
||||||
|
|
||||||
Basic usage
|
Basic Usage
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
$ peon Opens an interactive session (REPL)
|
$ peon Open an interactive session (REPL)
|
||||||
$ peon file.pn Runs the given Peon source file
|
$ peon file.pn Run the given Peon source file
|
||||||
$ peon file.pbc Runs the given Peon bytecode file
|
$ peon file.pbc Run the given Peon bytecode file
|
||||||
|
|
||||||
|
|
||||||
Command-line options
|
Options
|
||||||
--------------------
|
-------
|
||||||
|
|
||||||
-h, --help Show this help text and exits
|
-h, --help Show this help text and exits
|
||||||
-v, --version Print the peon version number and exits
|
-v, --version Print the current peon version and exits
|
||||||
-s, --string Execute the passed string as if it was a file
|
-s, --string Execute the passed string as if it was a file
|
||||||
-n, --nodump Don't dump the result of compilation to a *.pbc file
|
-n, --nodump Don't dump the result of compilation to a *.pbc file
|
||||||
-b, --breakpoints Run the debugger at specific bytecode offsets (comma-separated).
|
-b, --breakpoints Run the debugger at specific bytecode offsets (comma-separated).
|
||||||
Only available when compiled with -d:debugVM
|
Only available when compiled with VM debugging on
|
||||||
-d, --disassemble Disassemble the given bytecode file instead of executing it
|
-d, --disassemble Disassemble the given bytecode file instead of executing it
|
||||||
|
--warnings Turn warnings on/off (default: on). Acceptable values are
|
||||||
|
yes/on and no/off
|
||||||
|
--noWarn Disable a specific warning (for example, --noWarn unusedVariable)
|
||||||
|
--showMismatches Show all mismatches when dispatching function calls (quite verbose!)
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -92,7 +92,7 @@ export bytecode
|
||||||
type
|
type
|
||||||
WarningKind* {.pure.} = enum
|
WarningKind* {.pure.} = enum
|
||||||
## A warning enumeration type
|
## A warning enumeration type
|
||||||
UnreachableCode, UnusedName,
|
UnreachableCode, UnusedName, ShadowOuterScope
|
||||||
NameKind {.pure.} = enum
|
NameKind {.pure.} = enum
|
||||||
## A name enumeration type
|
## A name enumeration type
|
||||||
None, Module, Argument, Var, Function, CustomType, Enum
|
None, Module, Argument, Var, Function, CustomType, Enum
|
||||||
|
@ -106,6 +106,8 @@ type
|
||||||
kind: NameKind
|
kind: NameKind
|
||||||
# Owner of the identifier (module)
|
# Owner of the identifier (module)
|
||||||
owner: string
|
owner: string
|
||||||
|
# File where the name is declared
|
||||||
|
file: string
|
||||||
# Scope depth
|
# Scope depth
|
||||||
depth: int
|
depth: int
|
||||||
# Is this name private?
|
# Is this name private?
|
||||||
|
@ -167,8 +169,6 @@ type
|
||||||
# The current scope depth. If > 0, we're
|
# The current scope depth. If > 0, we're
|
||||||
# in a local scope, otherwise it's global
|
# in a local scope, otherwise it's global
|
||||||
depth: int
|
depth: int
|
||||||
# Scope ownership data
|
|
||||||
scopeOwners: seq[tuple[owner: Name, depth: int]]
|
|
||||||
# The current function being compiled
|
# The current function being compiled
|
||||||
currentFunction: Name
|
currentFunction: Name
|
||||||
# The current loop being compiled (used to
|
# The current loop being compiled (used to
|
||||||
|
@ -199,7 +199,7 @@ type
|
||||||
# List of closed-over variables
|
# List of closed-over variables
|
||||||
closures: seq[Name]
|
closures: seq[Name]
|
||||||
# Compiler procedures called by pragmas
|
# Compiler procedures called by pragmas
|
||||||
compilerProcs: TableRef[string, proc (self: Compiler, pragma: Pragma, name: Name)]
|
compilerProcs: TableRef[string, CompilerFunc]
|
||||||
# Stores line data for error reporting
|
# Stores line data for error reporting
|
||||||
lines: seq[tuple[start, stop: int]]
|
lines: seq[tuple[start, stop: int]]
|
||||||
# The source of the current module,
|
# The source of the current module,
|
||||||
|
@ -222,16 +222,27 @@ type
|
||||||
forwarded: seq[tuple[name: Name, pos: int]]
|
forwarded: seq[tuple[name: Name, pos: int]]
|
||||||
# List of disabled warnings
|
# List of disabled warnings
|
||||||
disabledWarnings: seq[WarningKind]
|
disabledWarnings: seq[WarningKind]
|
||||||
|
# Whether to show detailed info about type
|
||||||
|
# mismatches when we dispatch with matchImpl()
|
||||||
|
showMismatches: bool
|
||||||
|
PragmaKind = enum
|
||||||
|
## An enumeration of pragma types
|
||||||
|
Immediate,
|
||||||
|
Delayed
|
||||||
|
CompilerFunc = object
|
||||||
|
## An internal compiler function called
|
||||||
|
## by pragmas
|
||||||
|
kind: PragmaKind
|
||||||
|
handler: proc (self: Compiler, pragma: Pragma, name: Name)
|
||||||
CompileError* = ref object of PeonException
|
CompileError* = ref object of PeonException
|
||||||
compiler*: Compiler
|
compiler*: Compiler
|
||||||
node*: ASTNode
|
node*: ASTNode
|
||||||
file*: string
|
|
||||||
module*: string
|
module*: string
|
||||||
|
|
||||||
|
|
||||||
# Forward declarations
|
# Forward declarations
|
||||||
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
||||||
incremental: bool = false, isMainModule: bool = true, disabledWarnings: seq[WarningKind] = @[]): Chunk
|
incremental: bool = false, isMainModule: bool = true, disabledWarnings: seq[WarningKind] = @[], showMismatches: bool = false): Chunk
|
||||||
proc expression(self: Compiler, node: Expression)
|
proc expression(self: Compiler, node: Expression)
|
||||||
proc statement(self: Compiler, node: Statement)
|
proc statement(self: Compiler, node: Statement)
|
||||||
proc declaration(self: Compiler, node: Declaration)
|
proc declaration(self: Compiler, node: Declaration)
|
||||||
|
@ -250,7 +261,9 @@ proc compare(self: Compiler, a, b: Type): bool
|
||||||
proc patchReturnAddress(self: Compiler, pos: int)
|
proc patchReturnAddress(self: Compiler, pos: int)
|
||||||
proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name)
|
proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name)
|
||||||
proc handlePurePragma(self: Compiler, pragma: Pragma, name: Name)
|
proc handlePurePragma(self: Compiler, pragma: Pragma, name: Name)
|
||||||
|
proc handleErrorPragma(self: Compiler, pragma: Pragma, name: Name)
|
||||||
proc dispatchPragmas(self: Compiler, name: Name)
|
proc dispatchPragmas(self: Compiler, name: Name)
|
||||||
|
proc dispatchDelayedPragmas(self: Compiler, name: Name)
|
||||||
proc funDecl(self: Compiler, node: FunDecl, name: Name)
|
proc funDecl(self: Compiler, node: FunDecl, name: Name)
|
||||||
proc typeDecl(self: Compiler, node: TypeDecl, name: Name)
|
proc typeDecl(self: Compiler, node: TypeDecl, name: Name)
|
||||||
proc compileModule(self: Compiler, moduleName: string)
|
proc compileModule(self: Compiler, moduleName: string)
|
||||||
|
@ -271,11 +284,11 @@ proc newCompiler*(replMode: bool = false): Compiler =
|
||||||
result.currentFunction = nil
|
result.currentFunction = nil
|
||||||
result.replMode = replMode
|
result.replMode = replMode
|
||||||
result.currentModule = ""
|
result.currentModule = ""
|
||||||
result.compilerProcs = newTable[string, proc (self: Compiler, pragma: Pragma, name: Name)]()
|
result.compilerProcs = newTable[string, CompilerFunc]()
|
||||||
result.compilerProcs["magic"] = handleMagicPragma
|
result.compilerProcs["magic"] = CompilerFunc(kind: Immediate, handler: handleMagicPragma)
|
||||||
result.compilerProcs["pure"] = handlePurePragma
|
result.compilerProcs["pure"] = CompilerFunc(kind: Immediate, handler: handlePurePragma)
|
||||||
|
result.compilerProcs["error"] = CompilerFunc(kind: Delayed, handler: handleErrorPragma)
|
||||||
result.source = ""
|
result.source = ""
|
||||||
result.scopeOwners = @[]
|
|
||||||
result.lexer = newLexer()
|
result.lexer = newLexer()
|
||||||
result.lexer.fillSymbolTable()
|
result.lexer.fillSymbolTable()
|
||||||
result.parser = newParser()
|
result.parser = newParser()
|
||||||
|
@ -294,6 +307,7 @@ proc getModule*(self: Compiler): string {.inline.} = self.currentModule
|
||||||
proc getLines*(self: Compiler): seq[tuple[start, stop: int]] = self.lines
|
proc getLines*(self: Compiler): seq[tuple[start, stop: int]] = self.lines
|
||||||
proc getSource*(self: Compiler): string = self.source
|
proc getSource*(self: Compiler): string = self.source
|
||||||
proc getRelPos*(self: Compiler, line: int): tuple[start, stop: int] = self.lines[line - 1]
|
proc getRelPos*(self: Compiler, line: int): tuple[start, stop: int] = self.lines[line - 1]
|
||||||
|
proc getCurrentToken*(self: Compiler): Token = self.getCurrentNode().token
|
||||||
|
|
||||||
## Utility functions
|
## Utility functions
|
||||||
|
|
||||||
|
@ -321,22 +335,32 @@ proc done(self: Compiler): bool {.inline.} =
|
||||||
|
|
||||||
proc error(self: Compiler, message: string, node: ASTNode = nil) {.raises: [CompileError], inline.} =
|
proc error(self: Compiler, message: string, node: ASTNode = nil) {.raises: [CompileError], inline.} =
|
||||||
## Raises a CompileError exception
|
## Raises a CompileError exception
|
||||||
raise CompileError(msg: message, node: if node.isNil(): self.getCurrentNode() else: node, file: self.file, module: self.currentModule, compiler: self)
|
|
||||||
|
|
||||||
|
|
||||||
proc warning(self: Compiler, kind: WarningKind, message: string, node: ASTNode = nil) =
|
|
||||||
## Raises a warning
|
|
||||||
let node = if node.isNil(): self.getCurrentNode() else: node
|
let node = if node.isNil(): self.getCurrentNode() else: node
|
||||||
let fn = self.getCurrentFunction()
|
raise CompileError(msg: message, node: node, line: node.token.line, file: self.file, module: self.currentModule, compiler: self)
|
||||||
var msg = &" (raised at line {node.token.line} in module '{self.currentModule}'"
|
|
||||||
if not fn.isNil() and fn.kind != lambdaExpr:
|
|
||||||
msg &= &", in function '{FunDecl(fn).name.token.lexeme}'"
|
proc warning(self: Compiler, kind: WarningKind, message: string, name: Name = nil) =
|
||||||
msg &= ")"
|
## Raises a warning
|
||||||
|
var node: ASTNode
|
||||||
|
var fn: Declaration
|
||||||
|
if name.isNil():
|
||||||
|
node = self.getCurrentNode()
|
||||||
|
fn = self.getCurrentFunction()
|
||||||
|
else:
|
||||||
|
node = name.node
|
||||||
|
if node.isNil():
|
||||||
|
node = self.getCurrentNode()
|
||||||
|
if not name.belongsTo.isNil():
|
||||||
|
fn = name.belongsTo.node
|
||||||
|
var file = self.file
|
||||||
|
var pos = node.getRelativeBoundaries()
|
||||||
|
if file notin ["<string>", ""]:
|
||||||
|
file = relativePath(file, getCurrentDir())
|
||||||
if kind notin self.disabledWarnings:
|
if kind notin self.disabledWarnings:
|
||||||
stderr.styledWrite(fgYellow, &"Warning: ", fgCyan, "line ", fgRed, $node.token.line, fgCyan, ", module ", fgRed, &"'{self.currentModule}'")
|
stderr.styledWrite(fgYellow, styleBright, "Warning in ", fgRed, &"{file}:{node.token.line}:{pos.start}")
|
||||||
if not fn.isNil() and fn.kind != lambdaExpr:
|
if not fn.isNil() and fn.kind == funDecl:
|
||||||
stderr.styledWrite(fgCyan, ", function ", fgRed, &"'{FunDecl(fn).name.token.lexeme}'")
|
stderr.styledWrite(fgYellow, styleBright, " in function ", fgRed, FunDecl(fn).name.token.lexeme)
|
||||||
stderr.styledWriteLine(fgCyan, " -> ", fgYellow, message)
|
stderr.styledWriteLine(styleBright, fgDefault, ": ", message)
|
||||||
|
|
||||||
|
|
||||||
proc step(self: Compiler): ASTNode {.inline.} =
|
proc step(self: Compiler): ASTNode {.inline.} =
|
||||||
|
@ -1004,9 +1028,20 @@ proc findByType(self: Compiler, name: string, kind: Type, depth: int = -1): seq[
|
||||||
## with the given name and type. If depth is not -1,
|
## with the given name and type. If depth is not -1,
|
||||||
## it also compares the name's scope depth. Returns
|
## it also compares the name's scope depth. Returns
|
||||||
## all objects that apply
|
## all objects that apply
|
||||||
for obj in self.findByName(name):
|
for obj in self.findByName(name, resolve=false):
|
||||||
if self.compare(obj.valueType, kind) and (depth == -1 or depth == obj.depth):
|
if self.compare(obj.valueType, kind) and (depth == -1 or depth == obj.depth):
|
||||||
result.add(obj)
|
result.add(obj)
|
||||||
|
if not obj.resolved:
|
||||||
|
obj.resolved = true
|
||||||
|
case obj.kind:
|
||||||
|
of NameKind.CustomType:
|
||||||
|
self.typeDecl(TypeDecl(obj.node), obj)
|
||||||
|
of NameKind.Function:
|
||||||
|
if not obj.valueType.isGeneric:
|
||||||
|
self.funDecl(FunDecl(obj.node), obj)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc findAtDepth(self: Compiler, name: string, depth: int): seq[Name] {.used.} =
|
proc findAtDepth(self: Compiler, name: string, depth: int): seq[Name] {.used.} =
|
||||||
|
@ -1025,35 +1060,42 @@ proc matchImpl(self: Compiler, name: string, kind: Type, node: ASTNode = nil, al
|
||||||
var impl = self.findByType(name, kind)
|
var impl = self.findByType(name, kind)
|
||||||
if impl.len() == 0:
|
if impl.len() == 0:
|
||||||
var msg = &"cannot find a suitable implementation for '{name}'"
|
var msg = &"cannot find a suitable implementation for '{name}'"
|
||||||
let names = self.findByName(name)
|
let names = self.findByName(name, resolve=false)
|
||||||
if names.len() > 0:
|
if names.len() > 0:
|
||||||
msg &= &", found {len(names)} potential candidate"
|
msg &= &", found {len(names)} potential candidate"
|
||||||
if names.len() > 1:
|
if names.len() > 1:
|
||||||
msg &= "s"
|
msg &= "s"
|
||||||
msg &= ": "
|
if self.showMismatches:
|
||||||
for name in names:
|
msg &= ": "
|
||||||
msg &= &"\n - in module '{name.owner}' at line {name.ident.token.line} of type '{self.typeToStr(name.valueType)}'"
|
for name in names:
|
||||||
if name.valueType.kind != Function:
|
msg &= &"\n - in '{relativePath(name.file, getCurrentDir())}', line {name.ident.token.line}: '{self.typeToStr(name.valueType)}'"
|
||||||
msg &= ", not a callable"
|
if name.valueType.kind != Function:
|
||||||
elif kind.args.len() != name.valueType.args.len():
|
msg &= ", not a callable"
|
||||||
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
elif kind.args.len() != name.valueType.args.len():
|
||||||
else:
|
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
||||||
for i, arg in kind.args:
|
else:
|
||||||
if name.valueType.args[i].kind.mutable and not arg.kind.mutable:
|
for i, arg in kind.args:
|
||||||
msg &= &", first mismatch at position {i + 1}: {name.valueType.args[i].name} is immutable, not 'var'"
|
if name.valueType.args[i].kind.mutable and not arg.kind.mutable:
|
||||||
break
|
msg &= &", first mismatch at position {i + 1}: {name.valueType.args[i].name} is immutable, not 'var'"
|
||||||
elif not self.compare(arg.kind, name.valueType.args[i].kind):
|
break
|
||||||
msg &= &", first mismatch at position {i + 1}: expected argument of type '{self.typeToStr(name.valueType.args[i].kind)}', got '{self.typeToStr(arg.kind)}' instead"
|
elif not self.compare(arg.kind, name.valueType.args[i].kind):
|
||||||
break
|
msg &= &", first mismatch at position {i + 1}: expected argument of type '{self.typeToStr(name.valueType.args[i].kind)}', got '{self.typeToStr(arg.kind)}' instead"
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
msg &= " (compile with --showMismatches for more details)"
|
||||||
self.error(msg, node)
|
self.error(msg, node)
|
||||||
if impl.len() > 1:
|
if impl.len() > 1:
|
||||||
# Forward declarations don't count when looking for a function
|
# Forward declarations don't count when looking for a function
|
||||||
impl = filterIt(impl, not it.valueType.forwarded)
|
impl = filterIt(impl, not it.valueType.forwarded)
|
||||||
if impl.len() > 1:
|
if impl.len() > 1:
|
||||||
# If it's *still* more than one match, then it's an error
|
# If it's *still* more than one match, then it's an error
|
||||||
var msg = &"multiple matching implementations of '{name}' found:\n"
|
var msg = &"multiple matching implementations of '{name}' found\n"
|
||||||
for fn in reversed(impl):
|
if self.showMismatches:
|
||||||
msg &= &"- in module '{fn.owner}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
msg &= ":"
|
||||||
|
for fn in reversed(impl):
|
||||||
|
msg &= &"- in module '{fn.owner}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||||
|
else:
|
||||||
|
msg &= " (compile with --showMismatches for more details)"
|
||||||
self.error(msg, node)
|
self.error(msg, node)
|
||||||
if impl[0].valueType.forwarded and not allowFwd:
|
if impl[0].valueType.forwarded and not allowFwd:
|
||||||
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner}' at line {impl[0].ident.token.line} of type '{self.typeToStr(impl[0].valueType)}'")
|
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner}' at line {impl[0].ident.token.line} of type '{self.typeToStr(impl[0].valueType)}'")
|
||||||
|
@ -1161,7 +1203,6 @@ proc beginScope(self: Compiler) =
|
||||||
## Begins a new local scope by incrementing the current
|
## Begins a new local scope by incrementing the current
|
||||||
## scope's depth
|
## scope's depth
|
||||||
inc(self.depth)
|
inc(self.depth)
|
||||||
self.scopeOwners.add((self.currentFunction, self.depth))
|
|
||||||
|
|
||||||
|
|
||||||
# Flattens our weird function tree into a linear
|
# Flattens our weird function tree into a linear
|
||||||
|
@ -1200,33 +1241,23 @@ proc endScope(self: Compiler) =
|
||||||
## Ends the current local scope
|
## Ends the current local scope
|
||||||
if self.depth < 0:
|
if self.depth < 0:
|
||||||
self.error("cannot call endScope with depth < 0 (This is an internal error and most likely a bug)")
|
self.error("cannot call endScope with depth < 0 (This is an internal error and most likely a bug)")
|
||||||
discard self.scopeOwners.pop()
|
|
||||||
dec(self.depth)
|
dec(self.depth)
|
||||||
|
# We keep track both of which names are going out of scope
|
||||||
|
# and how many actually need to be popped off the call stack
|
||||||
|
# at runtime (since only variables and function arguments
|
||||||
|
# actually materialize at runtime)
|
||||||
var names: seq[Name] = @[]
|
var names: seq[Name] = @[]
|
||||||
var popCount = 0
|
var popCount = 0
|
||||||
if self.depth == -1 and not self.isMainModule:
|
|
||||||
# When we're compiling another module, we don't
|
|
||||||
# close its global scope because self.compileModule()
|
|
||||||
# needs access to it
|
|
||||||
return
|
|
||||||
for name in self.names:
|
for name in self.names:
|
||||||
|
# We only pop names in scopes deeper than ours
|
||||||
if name.depth > self.depth:
|
if name.depth > self.depth:
|
||||||
names.add(name)
|
if name.depth == 0 and not self.isMainModule:
|
||||||
if name.owner != self.currentModule and self.depth > -1:
|
# Global names coming from other modules only go out of scope
|
||||||
# Names coming from other modules only go out of scope
|
# when the global scope of the main module is closed (i.e. at
|
||||||
# when the global scope is closed (i.e. at the end of
|
# the end of the whole program)
|
||||||
# the module)
|
|
||||||
continue
|
continue
|
||||||
if not name.resolved and not self.replMode:
|
names.add(name)
|
||||||
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used")
|
if name.kind == NameKind.Function and name.valueType.children.len() > 0 and name.depth == 0:
|
||||||
if name.kind == NameKind.Var:
|
|
||||||
inc(popCount)
|
|
||||||
elif name.kind == NameKind.Argument:
|
|
||||||
if not name.belongsTo.valueType.isBuiltinFunction and name.belongsTo.resolved and not name.belongsTo.valueType.isGeneric:
|
|
||||||
# We don't pop arguments to builtin functions because those don't
|
|
||||||
# actually have scopes: their arguments are temporaries on the stack
|
|
||||||
inc(popCount)
|
|
||||||
elif name.kind == NameKind.Function and name.valueType.children.len() > 0 and name.depth == 0:
|
|
||||||
# When a closure goes out of scope, its environment is reclaimed.
|
# When a closure goes out of scope, its environment is reclaimed.
|
||||||
# This includes the environments of every other closure that may
|
# This includes the environments of every other closure that may
|
||||||
# have been contained within it, too
|
# have been contained within it, too
|
||||||
|
@ -1253,6 +1284,40 @@ proc endScope(self: Compiler) =
|
||||||
self.emitByte(PopClosure, self.peek().token.line)
|
self.emitByte(PopClosure, self.peek().token.line)
|
||||||
self.emitBytes((y + i).toTriple(), self.peek().token.line)
|
self.emitBytes((y + i).toTriple(), self.peek().token.line)
|
||||||
inc(i)
|
inc(i)
|
||||||
|
# Now we have to actually emit the pop instructions. First
|
||||||
|
# off, we skip the names that will not exist at runtime,
|
||||||
|
# because there's no need to emit any instructions to pop them
|
||||||
|
# (we still remove them from the name list later so they can't
|
||||||
|
# be referenced anymore, of course)
|
||||||
|
if name.kind notin [NameKind.Var, NameKind.Argument]:
|
||||||
|
continue
|
||||||
|
elif name.kind == NameKind.Argument:
|
||||||
|
if name.belongsTo.valueType.isBuiltinFunction:
|
||||||
|
# Arguments to builtin functions become temporaries on the
|
||||||
|
# stack and are popped automatically
|
||||||
|
continue
|
||||||
|
if not name.belongsTo.resolved:
|
||||||
|
# Function hasn't been compiled yet,
|
||||||
|
# so we can't get rid of its arguments
|
||||||
|
# (it may need them later)
|
||||||
|
names.delete(names.high())
|
||||||
|
continue
|
||||||
|
if not name.resolved and not self.replMode:
|
||||||
|
# We don't emit this warning in replMode because
|
||||||
|
# a variable might be declared on one line and then
|
||||||
|
# used on the next
|
||||||
|
case name.kind:
|
||||||
|
of NameKind.Var:
|
||||||
|
if not name.ident.token.lexeme.startsWith("_"):
|
||||||
|
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
|
||||||
|
of NameKind.Argument:
|
||||||
|
if not name.ident.token.lexeme.startsWith("_"):
|
||||||
|
if not name.belongsTo.valueType.isBuiltinFunction:
|
||||||
|
# Builtin functions never use their arguments
|
||||||
|
self.warning(UnusedName, &"argument '{name.ident.token.lexeme}' is unused (add '_' prefix to silence warning)", name)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
inc(popCount)
|
||||||
if popCount > 1:
|
if popCount > 1:
|
||||||
# If we're popping more than one variable,
|
# If we're popping more than one variable,
|
||||||
# we emit a bunch of PopN instructions until
|
# we emit a bunch of PopN instructions until
|
||||||
|
@ -1322,6 +1387,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
||||||
ident: node.name,
|
ident: node.name,
|
||||||
isPrivate: node.isPrivate,
|
isPrivate: node.isPrivate,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
isConst: node.isConst,
|
isConst: node.isConst,
|
||||||
valueType: nil, # Done later
|
valueType: nil, # Done later
|
||||||
isLet: node.isLet,
|
isLet: node.isLet,
|
||||||
|
@ -1340,6 +1406,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
||||||
isPrivate: node.isPrivate,
|
isPrivate: node.isPrivate,
|
||||||
isConst: false,
|
isConst: false,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
valueType: Type(kind: Function,
|
valueType: Type(kind: Function,
|
||||||
returnType: nil, # We check it later
|
returnType: nil, # We check it later
|
||||||
args: @[],
|
args: @[],
|
||||||
|
@ -1367,7 +1434,8 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
||||||
line: fn.node.token.line,
|
line: fn.node.token.line,
|
||||||
belongsTo: fn,
|
belongsTo: fn,
|
||||||
ident: gen.name,
|
ident: gen.name,
|
||||||
owner: self.currentModule))
|
owner: self.currentModule,
|
||||||
|
file: self.file))
|
||||||
constraints = @[]
|
constraints = @[]
|
||||||
if not node.returnType.isNil():
|
if not node.returnType.isNil():
|
||||||
fn.valueType.returnType = self.inferOrError(node.returnType, allowGeneric=true)
|
fn.valueType.returnType = self.inferOrError(node.returnType, allowGeneric=true)
|
||||||
|
@ -1380,6 +1448,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
||||||
self.names.add(Name(depth: fn.depth + 1,
|
self.names.add(Name(depth: fn.depth + 1,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
isConst: false,
|
isConst: false,
|
||||||
ident: argument.name,
|
ident: argument.name,
|
||||||
valueType: self.inferOrError(argument.valueType, allowGeneric=true),
|
valueType: self.inferOrError(argument.valueType, allowGeneric=true),
|
||||||
|
@ -1387,7 +1456,8 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
||||||
isLet: false,
|
isLet: false,
|
||||||
line: argument.name.token.line,
|
line: argument.name.token.line,
|
||||||
belongsTo: fn,
|
belongsTo: fn,
|
||||||
kind: NameKind.Argument
|
kind: NameKind.Argument,
|
||||||
|
node: argument.name
|
||||||
))
|
))
|
||||||
fn.valueType.args.add((self.names[^1].ident.token.lexeme, self.names[^1].valueType))
|
fn.valueType.args.add((self.names[^1].ident.token.lexeme, self.names[^1].valueType))
|
||||||
if node.generics.len() > 0:
|
if node.generics.len() > 0:
|
||||||
|
@ -1398,6 +1468,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
||||||
declaredName = name
|
declaredName = name
|
||||||
self.names.add(Name(depth: self.depth,
|
self.names.add(Name(depth: self.depth,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
ident: newIdentExpr(Token(kind: Identifier, lexeme: name, line: node.moduleName.token.line)),
|
ident: newIdentExpr(Token(kind: Identifier, lexeme: name, line: node.moduleName.token.line)),
|
||||||
line: node.moduleName.token.line,
|
line: node.moduleName.token.line,
|
||||||
kind: NameKind.Module,
|
kind: NameKind.Module,
|
||||||
|
@ -1410,9 +1481,9 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
||||||
for name in self.findByName(declaredName, resolve=false):
|
for name in self.findByName(declaredName, resolve=false):
|
||||||
if name == n:
|
if name == n:
|
||||||
continue
|
continue
|
||||||
elif (name.kind == NameKind.Var and name.depth == self.depth) or name.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum]:
|
elif name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum] and name.depth < self.depth:
|
||||||
# We don't check for clashing functions here: self.matchImpl() takes care of that
|
# We don't check for clashing functions here: self.matchImpl() takes care of that
|
||||||
self.error(&"attempt to redeclare '{name.ident.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}")
|
self.warning(WarningKind.ShadowOuterScope, &"'{name.ident.token.lexeme}' shadows a name from an outer scope")
|
||||||
|
|
||||||
|
|
||||||
proc emitLoop(self: Compiler, begin: int, line: int) =
|
proc emitLoop(self: Compiler, begin: int, line: int) =
|
||||||
|
@ -1452,6 +1523,17 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name) =
|
||||||
node.body = nil
|
node.body = nil
|
||||||
|
|
||||||
|
|
||||||
|
proc handleErrorPragma(self: Compiler, pragma: Pragma, name: Name) =
|
||||||
|
## Handles the "error" pragma
|
||||||
|
if pragma.args.len() != 1:
|
||||||
|
self.error("'error' pragma: wrong number of arguments")
|
||||||
|
elif pragma.args[0].kind != strExpr:
|
||||||
|
self.error("'error' pragma: wrong type of argument (constant string expected)")
|
||||||
|
elif not name.isNil() and name.node.kind != NodeKind.funDecl:
|
||||||
|
self.error("'error' pragma is not valid in this context")
|
||||||
|
self.error(pragma.args[0].token.lexeme[1..^2])
|
||||||
|
|
||||||
|
|
||||||
proc handlePurePragma(self: Compiler, pragma: Pragma, name: Name) =
|
proc handlePurePragma(self: Compiler, pragma: Pragma, name: Name) =
|
||||||
## Handles the "pure" pragma
|
## Handles the "pure" pragma
|
||||||
case name.node.kind:
|
case name.node.kind:
|
||||||
|
@ -1475,10 +1557,31 @@ proc dispatchPragmas(self: Compiler, name: Name) =
|
||||||
pragmas = LambdaExpr(name.node).pragmas
|
pragmas = LambdaExpr(name.node).pragmas
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
|
var f: CompilerFunc
|
||||||
for pragma in pragmas:
|
for pragma in pragmas:
|
||||||
if pragma.name.token.lexeme notin self.compilerProcs:
|
if pragma.name.token.lexeme notin self.compilerProcs:
|
||||||
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
|
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
|
||||||
self.compilerProcs[pragma.name.token.lexeme](self, pragma, name)
|
f = self.compilerProcs[pragma.name.token.lexeme]
|
||||||
|
if f.kind != Immediate:
|
||||||
|
continue
|
||||||
|
f.handler(self, pragma, name)
|
||||||
|
|
||||||
|
|
||||||
|
proc dispatchDelayedPragmas(self: Compiler, name: Name) =
|
||||||
|
## Dispatches pragmas bound to objects once they
|
||||||
|
## are called. Only applies to functions
|
||||||
|
if name.node.isNil():
|
||||||
|
return
|
||||||
|
var pragmas: seq[Pragma] = @[]
|
||||||
|
pragmas = Declaration(name.node).pragmas
|
||||||
|
var f: CompilerFunc
|
||||||
|
for pragma in pragmas:
|
||||||
|
if pragma.name.token.lexeme notin self.compilerProcs:
|
||||||
|
self.error(&"unknown pragma '{pragma.name.token.lexeme}'")
|
||||||
|
f = self.compilerProcs[pragma.name.token.lexeme]
|
||||||
|
if f.kind == Immediate:
|
||||||
|
continue
|
||||||
|
f.handler(self, pragma, name)
|
||||||
|
|
||||||
|
|
||||||
proc patchReturnAddress(self: Compiler, pos: int) =
|
proc patchReturnAddress(self: Compiler, pos: int) =
|
||||||
|
@ -1525,6 +1628,7 @@ proc beginProgram(self: Compiler): int =
|
||||||
isConst: false,
|
isConst: false,
|
||||||
isLet: false,
|
isLet: false,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
valueType: Type(kind: Function,
|
valueType: Type(kind: Function,
|
||||||
returnType: nil,
|
returnType: nil,
|
||||||
args: @[],
|
args: @[],
|
||||||
|
@ -1535,7 +1639,6 @@ proc beginProgram(self: Compiler): int =
|
||||||
resolved: true,
|
resolved: true,
|
||||||
line: -1)
|
line: -1)
|
||||||
self.names.add(main)
|
self.names.add(main)
|
||||||
self.scopeOwners.add((main, 0))
|
|
||||||
self.emitByte(LoadUInt64, 1)
|
self.emitByte(LoadUInt64, 1)
|
||||||
self.emitBytes(self.chunk.writeConstant(main.codePos.toLong()), 1)
|
self.emitBytes(self.chunk.writeConstant(main.codePos.toLong()), 1)
|
||||||
self.emitByte(LoadUInt64, 1)
|
self.emitByte(LoadUInt64, 1)
|
||||||
|
@ -1587,9 +1690,10 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
self.error("integer value out of range")
|
self.error("integer value out of range")
|
||||||
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
||||||
pos: (start: y.token.pos.start,
|
pos: (start: y.token.pos.start,
|
||||||
stop: y.token.pos.start + len($x))
|
stop: y.token.pos.start + len($x)),
|
||||||
|
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
self.emitConstant(node, self.infer(y))
|
self.emitConstant(node, self.infer(y))
|
||||||
of binExpr:
|
of binExpr:
|
||||||
var x: int
|
var x: int
|
||||||
|
@ -1600,7 +1704,8 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
self.error("integer value out of range")
|
self.error("integer value out of range")
|
||||||
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
||||||
pos: (start: y.token.pos.start,
|
pos: (start: y.token.pos.start,
|
||||||
stop: y.token.pos.start + len($x))
|
stop: y.token.pos.start + len($x)),
|
||||||
|
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.emitConstant(node, self.infer(y))
|
self.emitConstant(node, self.infer(y))
|
||||||
|
@ -1613,7 +1718,8 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
self.error("integer value out of range")
|
self.error("integer value out of range")
|
||||||
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
||||||
pos: (start: y.token.pos.start,
|
pos: (start: y.token.pos.start,
|
||||||
stop: y.token.pos.start + len($x))
|
stop: y.token.pos.start + len($x)),
|
||||||
|
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.emitConstant(node, self.infer(y))
|
self.emitConstant(node, self.infer(y))
|
||||||
|
@ -1633,30 +1739,25 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)")
|
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)")
|
||||||
|
|
||||||
|
|
||||||
proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) =
|
proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) {.inline.} =
|
||||||
## Emits the code to call a unary operator
|
## Emits the code to call a unary operator
|
||||||
self.generateCall(fn, @[op.a], fn.line)
|
self.generateCall(fn, @[op.a], fn.line)
|
||||||
|
|
||||||
|
|
||||||
proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) =
|
proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) {.inline.} =
|
||||||
## Emits the code to call a binary operator
|
## Emits the code to call a binary operator
|
||||||
self.generateCall(fn, @[op.a, op.b], fn.line)
|
self.generateCall(fn, @[op.a, op.b], fn.line)
|
||||||
|
|
||||||
|
|
||||||
proc unary(self: Compiler, node: UnaryExpr) =
|
proc unary(self: Compiler, node: UnaryExpr) {.inline.} =
|
||||||
## Compiles unary expressions such as decimal
|
## Compiles unary expressions such as decimal
|
||||||
## and bitwise negation
|
## and bitwise negation
|
||||||
let valueType = self.infer(node.a)
|
self.callUnaryOp(self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a))]), node), node)
|
||||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]), node)
|
|
||||||
self.callUnaryOp(funct, node)
|
|
||||||
|
|
||||||
|
|
||||||
proc binary(self: Compiler, node: BinaryExpr) =
|
proc binary(self: Compiler, node: BinaryExpr) {.inline.} =
|
||||||
## Compiles all binary expression
|
## Compiles all binary expression
|
||||||
let typeOfA = self.infer(node.a)
|
self.callBinaryOp(self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a)), ("", self.inferOrError(node.b))]), node), node)
|
||||||
let typeOfB = self.infer(node.b)
|
|
||||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]), node)
|
|
||||||
self.callBinaryOp(funct, node)
|
|
||||||
|
|
||||||
|
|
||||||
proc identifier(self: Compiler, node: IdentExpr) =
|
proc identifier(self: Compiler, node: IdentExpr) =
|
||||||
|
@ -1674,7 +1775,7 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
||||||
# they're referenced
|
# they're referenced
|
||||||
self.emitByte(LoadUInt64, node.token.line)
|
self.emitByte(LoadUInt64, node.token.line)
|
||||||
self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line)
|
self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line)
|
||||||
elif s.depth > 0 and self.depth > 0 and not self.currentFunction.isNil() and s.depth != self.depth and self.scopeOwners[s.depth].owner != self.currentFunction:
|
elif s.depth > 0 and self.depth > 1 and not self.currentFunction.isNil():
|
||||||
# Loads a closure variable. Stored in a separate "closure array" in the VM that does not
|
# Loads a closure variable. Stored in a separate "closure array" in the VM that does not
|
||||||
# align its semantics with the call stack. This makes closures work as expected and is
|
# align its semantics with the call stack. This makes closures work as expected and is
|
||||||
# not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway)
|
# not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway)
|
||||||
|
@ -1819,6 +1920,7 @@ proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) =
|
||||||
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
|
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
|
||||||
## Small wrapper that abstracts emitting a call instruction
|
## Small wrapper that abstracts emitting a call instruction
|
||||||
## for a given function
|
## for a given function
|
||||||
|
self.dispatchDelayedPragmas(fn)
|
||||||
if fn.valueType.isBuiltinFunction:
|
if fn.valueType.isBuiltinFunction:
|
||||||
self.handleBuiltinFunction(fn.valueType, args, line)
|
self.handleBuiltinFunction(fn.valueType, args, line)
|
||||||
return
|
return
|
||||||
|
@ -1876,6 +1978,7 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name =
|
||||||
self.names.add(Name(depth: name.depth + 1,
|
self.names.add(Name(depth: name.depth + 1,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
owner: self.currentModule,
|
owner: self.currentModule,
|
||||||
|
file: self.file,
|
||||||
isConst: false,
|
isConst: false,
|
||||||
ident: newIdentExpr(Token(lexeme: argName.name)),
|
ident: newIdentExpr(Token(lexeme: argName.name)),
|
||||||
valueType: argName.kind,
|
valueType: argName.kind,
|
||||||
|
@ -1887,7 +1990,6 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name =
|
||||||
))
|
))
|
||||||
if result.valueType.returnType.kind == Generic:
|
if result.valueType.returnType.kind == Generic:
|
||||||
result.valueType.returnType = mapping[result.valueType.returnType.name]
|
result.valueType.returnType = mapping[result.valueType.returnType.name]
|
||||||
# self.funDecl(FunDecl(result.node), result)
|
|
||||||
else:
|
else:
|
||||||
discard # TODO: Custom user-defined types
|
discard # TODO: Custom user-defined types
|
||||||
|
|
||||||
|
@ -2251,11 +2353,11 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
||||||
var jmp: int
|
var jmp: int
|
||||||
# We store the current function
|
# We store the current function
|
||||||
var function = self.currentFunction
|
var function = self.currentFunction
|
||||||
if not self.currentFunction.isNil():
|
|
||||||
self.currentFunction.valueType.children.add(name.valueType)
|
|
||||||
name.valueType.parent = function.valueType
|
|
||||||
self.currentFunction = name
|
|
||||||
if not node.body.isNil(): # We ignore forward declarations
|
if not node.body.isNil(): # We ignore forward declarations
|
||||||
|
if not self.currentFunction.isNil():
|
||||||
|
self.currentFunction.valueType.children.add(name.valueType)
|
||||||
|
name.valueType.parent = function.valueType
|
||||||
|
self.currentFunction = name
|
||||||
# A function's code is just compiled linearly
|
# A function's code is just compiled linearly
|
||||||
# and then jumped over
|
# and then jumped over
|
||||||
jmp = self.emitJump(JumpForwards, node.token.line)
|
jmp = self.emitJump(JumpForwards, node.token.line)
|
||||||
|
@ -2357,7 +2459,7 @@ proc declaration(self: Compiler, node: Declaration) =
|
||||||
|
|
||||||
|
|
||||||
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
||||||
incremental: bool = false, isMainModule: bool = true, disabledWarnings: seq[WarningKind] = @[]): Chunk =
|
incremental: bool = false, isMainModule: bool = true, disabledWarnings: seq[WarningKind] = @[], showMismatches: bool = false): Chunk =
|
||||||
## Compiles a sequence of AST nodes into a chunk
|
## Compiles a sequence of AST nodes into a chunk
|
||||||
## object
|
## object
|
||||||
if chunk.isNil():
|
if chunk.isNil():
|
||||||
|
@ -2374,6 +2476,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
|
||||||
self.source = source
|
self.source = source
|
||||||
self.isMainModule = isMainModule
|
self.isMainModule = isMainModule
|
||||||
self.disabledWarnings = disabledWarnings
|
self.disabledWarnings = disabledWarnings
|
||||||
|
self.showMismatches = showMismatches
|
||||||
if not incremental:
|
if not incremental:
|
||||||
self.jumps = @[]
|
self.jumps = @[]
|
||||||
let pos = self.beginProgram()
|
let pos = self.beginProgram()
|
||||||
|
@ -2412,7 +2515,7 @@ proc compileModule(self: Compiler, moduleName: string) =
|
||||||
path, self.lexer.getLines(),
|
path, self.lexer.getLines(),
|
||||||
source, persist=true),
|
source, persist=true),
|
||||||
path, self.lexer.getLines(), source, chunk=self.chunk, incremental=true,
|
path, self.lexer.getLines(), source, chunk=self.chunk, incremental=true,
|
||||||
isMainModule=false, self.disabledWarnings)
|
isMainModule=false, self.disabledWarnings, self.showMismatches)
|
||||||
self.depth = 0
|
self.depth = 0
|
||||||
self.current = current
|
self.current = current
|
||||||
self.ast = ast
|
self.ast = ast
|
||||||
|
|
|
@ -51,13 +51,13 @@ type
|
||||||
file: string
|
file: string
|
||||||
lines: seq[tuple[start, stop: int]]
|
lines: seq[tuple[start, stop: int]]
|
||||||
lastLine: int
|
lastLine: int
|
||||||
|
linePos: int
|
||||||
|
lineCurrent: int
|
||||||
spaces: int
|
spaces: int
|
||||||
LexingError* = ref object of PeonException
|
LexingError* = ref object of PeonException
|
||||||
## A lexing error
|
## A lexing exception
|
||||||
lexer*: Lexer
|
lexer*: Lexer
|
||||||
file*: string
|
pos*: tuple[start, stop: int]
|
||||||
lexeme*: string
|
|
||||||
line*: int
|
|
||||||
|
|
||||||
|
|
||||||
proc newSymbolTable: SymbolTable =
|
proc newSymbolTable: SymbolTable =
|
||||||
|
@ -116,7 +116,10 @@ proc getToken(self: Lexer, lexeme: string): Token =
|
||||||
result.kind = kind
|
result.kind = kind
|
||||||
result.lexeme = self.source[self.start..<self.current]
|
result.lexeme = self.source[self.start..<self.current]
|
||||||
result.line = self.line
|
result.line = self.line
|
||||||
result.pos = (start: self.start, stop: self.current)
|
result.pos = (start: self.start, stop: self.current - 1)
|
||||||
|
result.relPos = (start: self.linePos - result.lexeme.high() - 1, stop: self.linePos - 1)
|
||||||
|
result.spaces = self.spaces
|
||||||
|
self.spaces = 0
|
||||||
|
|
||||||
|
|
||||||
proc getMaxSymbolSize(self: SymbolTable): int =
|
proc getMaxSymbolSize(self: SymbolTable): int =
|
||||||
|
@ -182,6 +185,8 @@ proc newLexer*(self: Lexer = nil): Lexer =
|
||||||
result.file = ""
|
result.file = ""
|
||||||
result.lines = @[]
|
result.lines = @[]
|
||||||
result.lastLine = 0
|
result.lastLine = 0
|
||||||
|
result.linePos = 0
|
||||||
|
result.lineCurrent = 0
|
||||||
result.symbols = newSymbolTable()
|
result.symbols = newSymbolTable()
|
||||||
result.spaces = 0
|
result.spaces = 0
|
||||||
|
|
||||||
|
@ -198,6 +203,7 @@ proc incLine(self: Lexer) =
|
||||||
self.lines.add((self.lastLine, self.current))
|
self.lines.add((self.lastLine, self.current))
|
||||||
self.lastLine = self.current
|
self.lastLine = self.current
|
||||||
self.line += 1
|
self.line += 1
|
||||||
|
self.linePos = 0
|
||||||
|
|
||||||
|
|
||||||
proc step(self: Lexer, n: int = 1): string =
|
proc step(self: Lexer, n: int = 1): string =
|
||||||
|
@ -211,6 +217,7 @@ proc step(self: Lexer, n: int = 1): string =
|
||||||
else:
|
else:
|
||||||
result.add(self.source[self.current])
|
result.add(self.source[self.current])
|
||||||
inc(self.current)
|
inc(self.current)
|
||||||
|
inc(self.linePos)
|
||||||
|
|
||||||
|
|
||||||
proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
|
proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
|
||||||
|
@ -235,9 +242,9 @@ proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
|
||||||
|
|
||||||
|
|
||||||
proc error(self: Lexer, message: string) =
|
proc error(self: Lexer, message: string) =
|
||||||
## Raises a lexing error with info
|
## Raises a lexing error with the
|
||||||
## for error messages
|
## appropriate metadata
|
||||||
raise LexingError(msg: message, line: self.line, file: self.file, lexeme: self.peek(), lexer: self)
|
raise LexingError(msg: message, line: self.line, file: self.file, lexer: self, pos: (self.lineCurrent, self.linePos - 1))
|
||||||
|
|
||||||
|
|
||||||
proc check(self: Lexer, s: string, distance: int = 0): bool =
|
proc check(self: Lexer, s: string, distance: int = 0): bool =
|
||||||
|
@ -297,16 +304,17 @@ proc createToken(self: Lexer, tokenType: TokenType) =
|
||||||
tok.spaces = self.spaces
|
tok.spaces = self.spaces
|
||||||
self.spaces = 0
|
self.spaces = 0
|
||||||
tok.pos = (start: self.start, stop: self.current - 1)
|
tok.pos = (start: self.start, stop: self.current - 1)
|
||||||
|
tok.relPos = (start: self.linePos - tok.lexeme.high() - 1, stop: self.linePos - 1)
|
||||||
self.tokens.add(tok)
|
self.tokens.add(tok)
|
||||||
|
|
||||||
|
|
||||||
proc parseEscape(self: Lexer) =
|
proc parseEscape(self: Lexer) =
|
||||||
# Boring escape sequence parsing. For more info check out
|
## Boring escape sequence parsing. For more info check out
|
||||||
# https://en.wikipedia.org/wiki/Escape_sequences_in_C.
|
## https://en.wikipedia.org/wiki/Escape_sequences_in_C.
|
||||||
# As of now, \u and \U are not supported, but they'll
|
## As of now, \u and \U are not supported, but they'll
|
||||||
# likely be soon. Another notable limitation is that
|
## likely be soon. Another notable limitation is that
|
||||||
# \xhhh and \nnn are limited to the size of a char
|
## \xhhh and \nnn are limited to the size of a char
|
||||||
# (i.e. uint8, or 256 values)
|
## (i.e. uint8, or 256 values)
|
||||||
case self.peek()[0]: # We use a char instead of a string because of how case statements handle ranges with strings
|
case self.peek()[0]: # We use a char instead of a string because of how case statements handle ranges with strings
|
||||||
# (i.e. not well, given they crash the C code generator)
|
# (i.e. not well, given they crash the C code generator)
|
||||||
of 'a':
|
of 'a':
|
||||||
|
@ -568,8 +576,9 @@ proc next(self: Lexer) =
|
||||||
elif self.match(" "):
|
elif self.match(" "):
|
||||||
# Whitespaces
|
# Whitespaces
|
||||||
inc(self.spaces)
|
inc(self.spaces)
|
||||||
elif self.match("\r"):
|
inc(self.start, 2)
|
||||||
self.error("tabs are not allowed in peon code")
|
elif self.match("\t"):
|
||||||
|
self.error("tabs are not allowed in peon code, use spaces for indentation instead")
|
||||||
elif self.match("\n"):
|
elif self.match("\n"):
|
||||||
# New line
|
# New line
|
||||||
self.incLine()
|
self.incLine()
|
||||||
|
@ -650,9 +659,13 @@ proc lex*(self: Lexer, source, file: string): seq[Token] =
|
||||||
self.source = source
|
self.source = source
|
||||||
self.file = file
|
self.file = file
|
||||||
self.lines = @[]
|
self.lines = @[]
|
||||||
|
self.lastLine = 0
|
||||||
|
self.linePos = 0
|
||||||
|
self.lineCurrent = 0
|
||||||
while not self.done():
|
while not self.done():
|
||||||
self.next()
|
self.next()
|
||||||
self.start = self.current
|
self.start = self.current
|
||||||
|
self.lineCurrent = self.linePos
|
||||||
self.tokens.add(Token(kind: EndOfFile, lexeme: "",
|
self.tokens.add(Token(kind: EndOfFile, lexeme: "",
|
||||||
line: self.line, pos: (self.current, self.current)))
|
line: self.line, pos: (self.current, self.current)))
|
||||||
self.incLine()
|
self.incLine()
|
||||||
|
|
|
@ -784,3 +784,28 @@ proc `$`*(self: ASTNode): string =
|
||||||
|
|
||||||
|
|
||||||
proc `==`*(self, other: IdentExpr): bool {.inline.} = self.token == other.token
|
proc `==`*(self, other: IdentExpr): bool {.inline.} = self.token == other.token
|
||||||
|
|
||||||
|
|
||||||
|
proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] =
|
||||||
|
## Gets the location of a node relative to its line
|
||||||
|
case self.kind:
|
||||||
|
of exprStmt:
|
||||||
|
result = getRelativeBoundaries(ExprStmt(self).expression)
|
||||||
|
of unaryExpr:
|
||||||
|
var self = UnaryExpr(self)
|
||||||
|
result = (self.operator.relPos.start, getRelativeBoundaries(self.a).stop)
|
||||||
|
of intExpr, binExpr, hexExpr, octExpr, strExpr:
|
||||||
|
var self = LiteralExpr(self)
|
||||||
|
result = self.literal.relPos
|
||||||
|
of pragmaExpr:
|
||||||
|
var self = Pragma(self)
|
||||||
|
let start = self.token.relPos.start
|
||||||
|
var stop = 0
|
||||||
|
if self.args.len() > 0:
|
||||||
|
stop = self.args[^1].token.relPos.stop + 1
|
||||||
|
else:
|
||||||
|
stop = self.token.relPos.stop + 1
|
||||||
|
result = (self.token.relPos.start - 8, stop)
|
||||||
|
else:
|
||||||
|
result = (0, 0)
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
## Nim exceptions for internal Peon failures
|
|
||||||
PeonException* = ref object of CatchableError
|
PeonException* = ref object of CatchableError
|
||||||
SerializationError* = ref object of PeonException
|
## A Nim exception for a generic internal
|
||||||
file*: string
|
## peon failure (not to be used directly)
|
||||||
|
file*: string # The file where the error occurred
|
||||||
|
line*: int # The line where the error occurred
|
||||||
|
|
|
@ -63,24 +63,21 @@ type
|
||||||
Symbol, # A generic symbol
|
Symbol, # A generic symbol
|
||||||
Pragma,
|
Pragma,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Token* = ref object
|
Token* = ref object
|
||||||
## A token object
|
## A token object
|
||||||
kind*: TokenType # Type of the token
|
kind*: TokenType # The type of the token
|
||||||
lexeme*: string # The lexeme associated to the token
|
lexeme*: string # The lexeme associated to the token
|
||||||
line*: int # The line where the token appears
|
line*: int # The line where the token appears
|
||||||
pos*: tuple[start, stop: int] # The absolute position in the source file
|
pos*: tuple[start, stop: int] # The absolute position in the source file
|
||||||
# (0-indexed and inclusive at the beginning)
|
relPos*: tuple[start, stop: int] # The relative position in the source line
|
||||||
spaces*: int # Number of spaces before this token
|
spaces*: int # Number of spaces before this token
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc `$`*(self: Token): string =
|
proc `$`*(self: Token): string =
|
||||||
## Strinfifies
|
## Strinfifies
|
||||||
if self != nil:
|
if self != nil:
|
||||||
result = &"Token(kind={self.kind}, lexeme={self.lexeme.escape()}, line={self.line}, pos=({self.pos.start}, {self.pos.stop}), spaces={self.spaces})"
|
result = &"Token(kind={self.kind}, lexeme={self.lexeme.escape()}, line={self.line}, pos=({self.pos.start}, {self.pos.stop}), relpos=({self.relPos.start}, {self.relPos.stop}), spaces={self.spaces})"
|
||||||
else:
|
else:
|
||||||
result = "nil"
|
result = "nil"
|
||||||
|
|
||||||
|
|
|
@ -98,21 +98,12 @@ type
|
||||||
# Keeps track of imported modules
|
# Keeps track of imported modules
|
||||||
modules: seq[tuple[name: string, loaded: bool]]
|
modules: seq[tuple[name: string, loaded: bool]]
|
||||||
ParseError* = ref object of PeonException
|
ParseError* = ref object of PeonException
|
||||||
|
## A parsing exception
|
||||||
parser*: Parser
|
parser*: Parser
|
||||||
file*: string
|
|
||||||
token*: Token
|
token*: Token
|
||||||
module*: string
|
module*: string
|
||||||
|
|
||||||
|
|
||||||
proc newOperatorTable: OperatorTable =
|
|
||||||
## Initializes a new OperatorTable
|
|
||||||
## object
|
|
||||||
new(result)
|
|
||||||
result.tokens = @[]
|
|
||||||
for prec in Precedence:
|
|
||||||
result.precedence[prec] = @[]
|
|
||||||
|
|
||||||
|
|
||||||
proc addOperator(self: OperatorTable, lexeme: string) =
|
proc addOperator(self: OperatorTable, lexeme: string) =
|
||||||
## Adds an operator to the table. Its precedence
|
## Adds an operator to the table. Its precedence
|
||||||
## is inferred from the operator's lexeme (the
|
## is inferred from the operator's lexeme (the
|
||||||
|
@ -142,6 +133,16 @@ proc addOperator(self: OperatorTable, lexeme: string) =
|
||||||
self.precedence[prec].add(lexeme)
|
self.precedence[prec].add(lexeme)
|
||||||
|
|
||||||
|
|
||||||
|
proc newOperatorTable: OperatorTable =
|
||||||
|
## Initializes a new OperatorTable
|
||||||
|
## object
|
||||||
|
new(result)
|
||||||
|
result.tokens = @[]
|
||||||
|
for prec in Precedence:
|
||||||
|
result.precedence[prec] = @[]
|
||||||
|
result.addOperator("=") # Assignment is the only builtin
|
||||||
|
|
||||||
|
|
||||||
proc getPrecedence(self: OperatorTable, lexeme: string): Precedence =
|
proc getPrecedence(self: OperatorTable, lexeme: string): Precedence =
|
||||||
## Gets the precedence of a given operator
|
## Gets the precedence of a given operator
|
||||||
for (prec, operators) in self.precedence.pairs():
|
for (prec, operators) in self.precedence.pairs():
|
||||||
|
@ -214,7 +215,8 @@ proc step(self: Parser, n: int = 1): Token =
|
||||||
|
|
||||||
proc error(self: Parser, message: string, token: Token = nil) {.raises: [ParseError].} =
|
proc error(self: Parser, message: string, token: Token = nil) {.raises: [ParseError].} =
|
||||||
## Raises a ParseError exception
|
## Raises a ParseError exception
|
||||||
raise ParseError(msg: message, token: if token.isNil(): self.getCurrentToken() else: token, file: self.file, module: self.getModule(), parser: self)
|
let token = if token.isNil(): self.getCurrentToken() else: token
|
||||||
|
raise ParseError(msg: message, token: token, line: token.line, file: self.file, module: self.getModule(), parser: self)
|
||||||
|
|
||||||
|
|
||||||
# Why do we allow strings or enum members of TokenType? Well, it's simple:
|
# Why do we allow strings or enum members of TokenType? Well, it's simple:
|
||||||
|
|
122
src/main.nim
122
src/main.nim
|
@ -22,6 +22,7 @@ import backend/vm as v
|
||||||
import util/serializer as s
|
import util/serializer as s
|
||||||
import util/debugger
|
import util/debugger
|
||||||
import util/symbols
|
import util/symbols
|
||||||
|
import util/fmterr
|
||||||
import config
|
import config
|
||||||
|
|
||||||
# Builtins & external libs
|
# Builtins & external libs
|
||||||
|
@ -140,54 +141,18 @@ proc repl =
|
||||||
styledEcho fgRed, "Corrupted"
|
styledEcho fgRed, "Corrupted"
|
||||||
vm.run(serialized.chunk)
|
vm.run(serialized.chunk)
|
||||||
except LexingError:
|
except LexingError:
|
||||||
var exc = LexingError(getCurrentException())
|
print(LexingError(getCurrentException()))
|
||||||
let relPos = exc.lexer.getRelPos(exc.line)
|
|
||||||
let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
|
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
|
|
||||||
fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'",
|
|
||||||
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
|
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(exc.lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(exc.lexeme)))
|
|
||||||
except ParseError:
|
except ParseError:
|
||||||
let exc = ParseError(getCurrentException())
|
print(ParseError(getCurrentException()))
|
||||||
let lexeme = exc.token.lexeme
|
|
||||||
var lineNo = exc.token.line
|
|
||||||
if exc.token.kind == EndOfFile:
|
|
||||||
dec(lineNo)
|
|
||||||
let relPos = exc.parser.getRelPos(lineNo)
|
|
||||||
let fn = exc.parser.getCurrentFunction()
|
|
||||||
let line = exc.parser.getSource().splitLines()[lineNo - 1].strip()
|
|
||||||
var fnMsg = ""
|
|
||||||
if fn != nil and fn.kind == funDecl:
|
|
||||||
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
|
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
|
|
||||||
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
|
|
||||||
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
|
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme)))
|
|
||||||
except CompileError:
|
except CompileError:
|
||||||
let exc = CompileError(getCurrentException())
|
print(CompileError(getCurrentException()))
|
||||||
let lexeme = exc.node.token.lexeme
|
|
||||||
var lineNo = exc.node.token.line
|
|
||||||
let relPos = exc.compiler.getRelPos(lineNo)
|
|
||||||
let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
|
|
||||||
var fn = exc.compiler.getCurrentFunction()
|
|
||||||
var fnMsg = ""
|
|
||||||
if fn != nil and fn.kind == funDecl:
|
|
||||||
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
|
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
|
|
||||||
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
|
|
||||||
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
|
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme)))
|
|
||||||
except SerializationError:
|
except SerializationError:
|
||||||
let exc = SerializationError(getCurrentException())
|
print(SerializationError(getCurrentException()))
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
|
|
||||||
quit(0)
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false,
|
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false,
|
||||||
warnings: seq[WarningKind] = @[]) =
|
warnings: seq[WarningKind] = @[], mismatches: bool = false) =
|
||||||
var
|
var
|
||||||
tokens: seq[Token] = @[]
|
tokens: seq[Token] = @[]
|
||||||
tree: seq[Declaration] = @[]
|
tree: seq[Declaration] = @[]
|
||||||
|
@ -232,7 +197,7 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
|
||||||
for node in tree:
|
for node in tree:
|
||||||
styledEcho fgGreen, "\t", $node
|
styledEcho fgGreen, "\t", $node
|
||||||
echo ""
|
echo ""
|
||||||
compiled = compiler.compile(tree, f, tokenizer.getLines(), input, disabledWarnings=warnings)
|
compiled = compiler.compile(tree, f, tokenizer.getLines(), input, disabledWarnings=warnings, showMismatches=mismatches)
|
||||||
when debugCompiler:
|
when debugCompiler:
|
||||||
styledEcho fgCyan, "Compilation step:\n"
|
styledEcho fgCyan, "Compilation step:\n"
|
||||||
debugger.disassembleChunk(compiled, f)
|
debugger.disassembleChunk(compiled, f)
|
||||||
|
@ -272,53 +237,17 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
|
||||||
styledEcho fgRed, "Corrupted"
|
styledEcho fgRed, "Corrupted"
|
||||||
vm.run(serialized.chunk, breakpoints)
|
vm.run(serialized.chunk, breakpoints)
|
||||||
except LexingError:
|
except LexingError:
|
||||||
var exc = LexingError(getCurrentException())
|
print(LexingError(getCurrentException()))
|
||||||
let relPos = exc.lexer.getRelPos(exc.line)
|
|
||||||
let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
|
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
|
|
||||||
fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'",
|
|
||||||
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
|
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(exc.lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(exc.lexeme)))
|
|
||||||
except ParseError:
|
except ParseError:
|
||||||
let exc = ParseError(getCurrentException())
|
print(ParseError(getCurrentException()))
|
||||||
let lexeme = exc.token.lexeme
|
|
||||||
var lineNo = exc.token.line
|
|
||||||
if exc.token.kind == EndOfFile:
|
|
||||||
dec(lineNo)
|
|
||||||
let relPos = exc.parser.getRelPos(lineNo)
|
|
||||||
let fn = parser.getCurrentFunction()
|
|
||||||
let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
|
|
||||||
var fnMsg = ""
|
|
||||||
if fn != nil and fn.kind == funDecl:
|
|
||||||
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
|
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
|
|
||||||
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
|
|
||||||
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
|
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme)))
|
|
||||||
except CompileError:
|
except CompileError:
|
||||||
let exc = CompileError(getCurrentException())
|
print(CompileError(getCurrentException()))
|
||||||
let lexeme = exc.node.token.lexeme
|
|
||||||
var lineNo = exc.node.token.line
|
|
||||||
let relPos = exc.compiler.getRelPos(lineNo)
|
|
||||||
let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
|
|
||||||
var fn = exc.compiler.getCurrentFunction()
|
|
||||||
var fnMsg = ""
|
|
||||||
if fn != nil and fn.kind == funDecl:
|
|
||||||
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
|
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
|
|
||||||
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
|
|
||||||
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
|
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme)))
|
|
||||||
except SerializationError:
|
except SerializationError:
|
||||||
let exc = SerializationError(getCurrentException())
|
print(SerializationError(getCurrentException()))
|
||||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
|
|
||||||
except IOError:
|
except IOError:
|
||||||
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}")
|
print(IOError(getCurrentException()[]), f)
|
||||||
except OSError:
|
except OSError:
|
||||||
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
|
print(OSError(getCurrentException()[]), f, osLastError())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -331,6 +260,7 @@ when isMainModule:
|
||||||
var warnings: seq[WarningKind] = @[]
|
var warnings: seq[WarningKind] = @[]
|
||||||
var breaks: seq[uint64] = @[]
|
var breaks: seq[uint64] = @[]
|
||||||
var dis: bool = false
|
var dis: bool = false
|
||||||
|
var mismatches: bool = false
|
||||||
for kind, key, value in optParser.getopt():
|
for kind, key, value in optParser.getopt():
|
||||||
case kind:
|
case kind:
|
||||||
of cmdArgument:
|
of cmdArgument:
|
||||||
|
@ -355,31 +285,35 @@ when isMainModule:
|
||||||
for warning in WarningKind:
|
for warning in WarningKind:
|
||||||
warnings.add(warning)
|
warnings.add(warning)
|
||||||
else:
|
else:
|
||||||
stderr.writeLine("error: invalid value for option 'warnings' (valid options: yes, on, no, off)")
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid value for option 'warnings' (valid options are: yes, on, no, off)")
|
||||||
quit()
|
quit()
|
||||||
of "no-warn":
|
of "showMismatches":
|
||||||
|
mismatches = true
|
||||||
|
of "noWarn":
|
||||||
case value:
|
case value:
|
||||||
of "unusedVariable":
|
of "unusedVariable":
|
||||||
warnings.add(WarningKind.UnusedName)
|
warnings.add(WarningKind.UnusedName)
|
||||||
of "unreachableCode":
|
of "unreachableCode":
|
||||||
warnings.add(WarningKind.UnreachableCode)
|
warnings.add(WarningKind.UnreachableCode)
|
||||||
|
of "shadowingOuterScope":
|
||||||
|
warnings.add(WarningKind.ShadowOuterScope)
|
||||||
else:
|
else:
|
||||||
stderr.writeLine("error: invalid warning name for option 'warnings'")
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid warning name for option 'warnings'")
|
||||||
quit()
|
quit()
|
||||||
of "breakpoints":
|
of "breakpoints":
|
||||||
when not debugVM:
|
when not debugVM:
|
||||||
echo "error: cannot set breakpoints in release mode"
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "cannot set breakpoints in release mode")
|
||||||
quit()
|
quit()
|
||||||
for point in value.strip(chars={' '}).split(","):
|
for point in value.strip(chars={' '}).split(","):
|
||||||
try:
|
try:
|
||||||
breaks.add(parseBiggestUInt(point))
|
breaks.add(parseBiggestUInt(point))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo &"error: invalid breakpoint value '{point}'"
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
|
||||||
quit()
|
quit()
|
||||||
of "disassemble":
|
of "disassemble":
|
||||||
dis = true
|
dis = true
|
||||||
else:
|
else:
|
||||||
echo &"error: unkown option '{key}'"
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'")
|
||||||
quit()
|
quit()
|
||||||
of cmdShortOption:
|
of cmdShortOption:
|
||||||
case key:
|
case key:
|
||||||
|
@ -396,18 +330,18 @@ when isMainModule:
|
||||||
dump = false
|
dump = false
|
||||||
of "b":
|
of "b":
|
||||||
when not debugVM:
|
when not debugVM:
|
||||||
stderr.writeLine("error: cannot set breakpoints in release mode")
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "cannot set breakpoints in release mode")
|
||||||
quit()
|
quit()
|
||||||
for point in value.strip(chars={' '}).split(","):
|
for point in value.strip(chars={' '}).split(","):
|
||||||
try:
|
try:
|
||||||
breaks.add(parseBiggestUInt(point))
|
breaks.add(parseBiggestUInt(point))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
stderr.writeLine(&"error: invalid breakpoint value '{point}'")
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
|
||||||
quit()
|
quit()
|
||||||
of "d":
|
of "d":
|
||||||
dis = true
|
dis = true
|
||||||
else:
|
else:
|
||||||
stderr.writeLine(&"error: unkown option '{key}'")
|
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"unkown option '{key}'")
|
||||||
quit()
|
quit()
|
||||||
else:
|
else:
|
||||||
echo "usage: peon [options] [filename.pn]"
|
echo "usage: peon [options] [filename.pn]"
|
||||||
|
@ -415,7 +349,7 @@ when isMainModule:
|
||||||
if file == "":
|
if file == "":
|
||||||
repl()
|
repl()
|
||||||
else:
|
else:
|
||||||
runFile(file, fromString, dump, breaks, dis, warnings)
|
runFile(file, fromString, dump, breaks, dis, warnings, mismatches)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,11 @@ operator `-`*[T: int | int32 | int16 | int8](a: T): T {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
operator `-`*[T: uint64 | uint32 | uint16 | uint8](a: T): T {
|
||||||
|
#pragma[error: "unsigned integer cannot be negative"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
operator `-`*(a: float64): float64 {
|
operator `-`*(a: float64): float64 {
|
||||||
#pragma[magic: "NegateFloat64", pure]
|
#pragma[magic: "NegateFloat64", pure]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
# Various miscellaneous utilities
|
# Various miscellaneous utilities
|
||||||
|
|
||||||
# Assignment operator
|
|
||||||
|
|
||||||
operator `=`*[T: all](a: var T, b: T) { # TODO: This is just a placeholder right now
|
|
||||||
#pragma[magic: "GenericAssign"]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Some useful builtins
|
# Some useful builtins
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ type
|
||||||
commit*: string
|
commit*: string
|
||||||
compileDate*: int
|
compileDate*: int
|
||||||
chunk*: Chunk
|
chunk*: Chunk
|
||||||
|
SerializationError* = ref object of PeonException
|
||||||
|
|
||||||
|
|
||||||
proc `$`*(self: Serialized): string =
|
proc `$`*(self: Serialized): string =
|
||||||
|
|
|
@ -16,4 +16,4 @@ fn fooBar(a, b: int): int {
|
||||||
|
|
||||||
|
|
||||||
noReturn(1);
|
noReturn(1);
|
||||||
print(fooBar(1, 3)); # 1
|
print(fooBar(1, 3) == 1); # true
|
||||||
|
|
|
@ -18,8 +18,9 @@ fn makeClosureTwo(y: int): fn: int {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# These should all print true!
|
||||||
var closure = makeClosure(42);
|
var closure = makeClosure(42);
|
||||||
print(closure()); # 42
|
print(closure() == 42);
|
||||||
print(makeClosureTwo(38)()); # 38
|
print(makeClosureTwo(38)() == 38);
|
||||||
var closureTwo = makeClosureTwo(420);
|
var closureTwo = makeClosureTwo(420);
|
||||||
print(closureTwo()); # 420
|
print(closureTwo() == 420);
|
|
@ -9,4 +9,4 @@ fn makeAdder(x: int): fn (n: int): int {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
print(makeAdder(5)(2)); # 7
|
print(makeAdder(5)(2) == 7); # true
|
||||||
|
|
|
@ -17,5 +17,4 @@ fn second(x: int): int {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
print(second(0) == 2); # true
|
||||||
print(second(0)); # 2
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import std;
|
import std;
|
||||||
|
|
||||||
|
|
||||||
var x = 100000;
|
var x = 10000000;
|
||||||
var y = "just a test";
|
var y = "just a test";
|
||||||
print(y);
|
print(y);
|
||||||
print("Starting GC torture test");
|
print("Starting GC torture test");
|
||||||
|
|
Loading…
Reference in New Issue