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, "]"
|
||||
of "f", "frame":
|
||||
stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[")
|
||||
for i, e in self.calls[self.frames[^1]..^1]:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < (self.calls.high() - self.frames[^1].int):
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
if self.frames.len() > 0:
|
||||
for i, e in self.calls[self.frames[^1]..^1]:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < (self.calls.high() - self.frames[^1].int):
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]", fgCyan
|
||||
of "frames":
|
||||
stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[")
|
||||
|
@ -721,7 +722,7 @@ when debugVM: # So nim shuts up
|
|||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "clear":
|
||||
stdout.write("\x1Bc")
|
||||
stdout.write("\x1Bc")
|
||||
else:
|
||||
styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'")
|
||||
|
||||
|
|
|
@ -35,29 +35,33 @@ when HeapGrowFactor <= 1:
|
|||
const PeonVersion* = (major: 0, minor: 1, patch: 0)
|
||||
const PeonRelease* = "alpha"
|
||||
const PeonCommitHash* = staticExec("git rev-parse HEAD")
|
||||
const PeonBranch* = staticExec("""git symbolic-ref HEAD 2>/dev/null | cut -d"/" -f 3 """")
|
||||
const PeonVersionString* = &"Peon {PeonVersion.major}.{PeonVersion.minor}.{PeonVersion.patch} {PeonRelease} ({PeonBranch[0..8]}, {CompileDate}, {CompileTime}, {PeonCommitHash}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
|
||||
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..(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
|
||||
|
||||
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.
|
||||
|
||||
Basic usage
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
$ peon Opens an interactive session (REPL)
|
||||
$ peon file.pn Runs the given Peon source file
|
||||
$ peon file.pbc Runs the given Peon bytecode file
|
||||
$ peon Open an interactive session (REPL)
|
||||
$ peon file.pn Run the given Peon source file
|
||||
$ peon file.pbc Run the given Peon bytecode file
|
||||
|
||||
|
||||
Command-line options
|
||||
--------------------
|
||||
Options
|
||||
-------
|
||||
|
||||
-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
|
||||
-n, --nodump Don't dump the result of compilation to a *.pbc file
|
||||
-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
|
||||
--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
|
||||
WarningKind* {.pure.} = enum
|
||||
## A warning enumeration type
|
||||
UnreachableCode, UnusedName,
|
||||
UnreachableCode, UnusedName, ShadowOuterScope
|
||||
NameKind {.pure.} = enum
|
||||
## A name enumeration type
|
||||
None, Module, Argument, Var, Function, CustomType, Enum
|
||||
|
@ -106,6 +106,8 @@ type
|
|||
kind: NameKind
|
||||
# Owner of the identifier (module)
|
||||
owner: string
|
||||
# File where the name is declared
|
||||
file: string
|
||||
# Scope depth
|
||||
depth: int
|
||||
# Is this name private?
|
||||
|
@ -167,8 +169,6 @@ type
|
|||
# The current scope depth. If > 0, we're
|
||||
# in a local scope, otherwise it's global
|
||||
depth: int
|
||||
# Scope ownership data
|
||||
scopeOwners: seq[tuple[owner: Name, depth: int]]
|
||||
# The current function being compiled
|
||||
currentFunction: Name
|
||||
# The current loop being compiled (used to
|
||||
|
@ -199,7 +199,7 @@ type
|
|||
# List of closed-over variables
|
||||
closures: seq[Name]
|
||||
# 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
|
||||
lines: seq[tuple[start, stop: int]]
|
||||
# The source of the current module,
|
||||
|
@ -222,16 +222,27 @@ type
|
|||
forwarded: seq[tuple[name: Name, pos: int]]
|
||||
# List of disabled warnings
|
||||
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
|
||||
compiler*: Compiler
|
||||
node*: ASTNode
|
||||
file*: string
|
||||
module*: string
|
||||
|
||||
|
||||
# Forward declarations
|
||||
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 statement(self: Compiler, node: Statement)
|
||||
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 handleMagicPragma(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 dispatchDelayedPragmas(self: Compiler, name: Name)
|
||||
proc funDecl(self: Compiler, node: FunDecl, name: Name)
|
||||
proc typeDecl(self: Compiler, node: TypeDecl, name: Name)
|
||||
proc compileModule(self: Compiler, moduleName: string)
|
||||
|
@ -271,11 +284,11 @@ proc newCompiler*(replMode: bool = false): Compiler =
|
|||
result.currentFunction = nil
|
||||
result.replMode = replMode
|
||||
result.currentModule = ""
|
||||
result.compilerProcs = newTable[string, proc (self: Compiler, pragma: Pragma, name: Name)]()
|
||||
result.compilerProcs["magic"] = handleMagicPragma
|
||||
result.compilerProcs["pure"] = handlePurePragma
|
||||
result.compilerProcs = newTable[string, CompilerFunc]()
|
||||
result.compilerProcs["magic"] = CompilerFunc(kind: Immediate, handler: handleMagicPragma)
|
||||
result.compilerProcs["pure"] = CompilerFunc(kind: Immediate, handler: handlePurePragma)
|
||||
result.compilerProcs["error"] = CompilerFunc(kind: Delayed, handler: handleErrorPragma)
|
||||
result.source = ""
|
||||
result.scopeOwners = @[]
|
||||
result.lexer = newLexer()
|
||||
result.lexer.fillSymbolTable()
|
||||
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 getSource*(self: Compiler): string = self.source
|
||||
proc getRelPos*(self: Compiler, line: int): tuple[start, stop: int] = self.lines[line - 1]
|
||||
proc getCurrentToken*(self: Compiler): Token = self.getCurrentNode().token
|
||||
|
||||
## Utility functions
|
||||
|
||||
|
@ -321,22 +335,32 @@ proc done(self: Compiler): bool {.inline.} =
|
|||
|
||||
proc error(self: Compiler, message: string, node: ASTNode = nil) {.raises: [CompileError], inline.} =
|
||||
## 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 fn = self.getCurrentFunction()
|
||||
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}'"
|
||||
msg &= ")"
|
||||
raise CompileError(msg: message, node: node, line: node.token.line, file: self.file, module: self.currentModule, compiler: self)
|
||||
|
||||
|
||||
proc warning(self: Compiler, kind: WarningKind, message: string, name: Name = nil) =
|
||||
## 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:
|
||||
stderr.styledWrite(fgYellow, &"Warning: ", fgCyan, "line ", fgRed, $node.token.line, fgCyan, ", module ", fgRed, &"'{self.currentModule}'")
|
||||
if not fn.isNil() and fn.kind != lambdaExpr:
|
||||
stderr.styledWrite(fgCyan, ", function ", fgRed, &"'{FunDecl(fn).name.token.lexeme}'")
|
||||
stderr.styledWriteLine(fgCyan, " -> ", fgYellow, message)
|
||||
stderr.styledWrite(fgYellow, styleBright, "Warning in ", fgRed, &"{file}:{node.token.line}:{pos.start}")
|
||||
if not fn.isNil() and fn.kind == funDecl:
|
||||
stderr.styledWrite(fgYellow, styleBright, " in function ", fgRed, FunDecl(fn).name.token.lexeme)
|
||||
stderr.styledWriteLine(styleBright, fgDefault, ": ", message)
|
||||
|
||||
|
||||
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,
|
||||
## it also compares the name's scope depth. Returns
|
||||
## 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):
|
||||
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.} =
|
||||
|
@ -1025,35 +1060,42 @@ proc matchImpl(self: Compiler, name: string, kind: Type, node: ASTNode = nil, al
|
|||
var impl = self.findByType(name, kind)
|
||||
if impl.len() == 0:
|
||||
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:
|
||||
msg &= &", found {len(names)} potential candidate"
|
||||
if names.len() > 1:
|
||||
msg &= "s"
|
||||
msg &= ": "
|
||||
for name in names:
|
||||
msg &= &"\n - in module '{name.owner}' at line {name.ident.token.line} of type '{self.typeToStr(name.valueType)}'"
|
||||
if name.valueType.kind != Function:
|
||||
msg &= ", not a callable"
|
||||
elif kind.args.len() != name.valueType.args.len():
|
||||
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
||||
else:
|
||||
for i, arg in kind.args:
|
||||
if name.valueType.args[i].kind.mutable and not arg.kind.mutable:
|
||||
msg &= &", first mismatch at position {i + 1}: {name.valueType.args[i].name} is immutable, not 'var'"
|
||||
break
|
||||
elif not self.compare(arg.kind, name.valueType.args[i].kind):
|
||||
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
|
||||
if self.showMismatches:
|
||||
msg &= ": "
|
||||
for name in names:
|
||||
msg &= &"\n - in '{relativePath(name.file, getCurrentDir())}', line {name.ident.token.line}: '{self.typeToStr(name.valueType)}'"
|
||||
if name.valueType.kind != Function:
|
||||
msg &= ", not a callable"
|
||||
elif kind.args.len() != name.valueType.args.len():
|
||||
msg &= &", wrong number of arguments ({name.valueType.args.len()} expected, got {kind.args.len()})"
|
||||
else:
|
||||
for i, arg in kind.args:
|
||||
if name.valueType.args[i].kind.mutable and not arg.kind.mutable:
|
||||
msg &= &", first mismatch at position {i + 1}: {name.valueType.args[i].name} is immutable, not 'var'"
|
||||
break
|
||||
elif not self.compare(arg.kind, name.valueType.args[i].kind):
|
||||
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)
|
||||
if impl.len() > 1:
|
||||
# Forward declarations don't count when looking for a function
|
||||
impl = filterIt(impl, not it.valueType.forwarded)
|
||||
if impl.len() > 1:
|
||||
# If it's *still* more than one match, then it's an error
|
||||
var msg = &"multiple matching implementations of '{name}' found:\n"
|
||||
for fn in reversed(impl):
|
||||
msg &= &"- in module '{fn.owner}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||
var msg = &"multiple matching implementations of '{name}' found\n"
|
||||
if self.showMismatches:
|
||||
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)
|
||||
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)}'")
|
||||
|
@ -1161,7 +1203,6 @@ proc beginScope(self: Compiler) =
|
|||
## Begins a new local scope by incrementing the current
|
||||
## scope's depth
|
||||
inc(self.depth)
|
||||
self.scopeOwners.add((self.currentFunction, self.depth))
|
||||
|
||||
|
||||
# Flattens our weird function tree into a linear
|
||||
|
@ -1200,33 +1241,23 @@ proc endScope(self: Compiler) =
|
|||
## Ends the current local scope
|
||||
if self.depth < 0:
|
||||
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)
|
||||
# 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 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:
|
||||
# We only pop names in scopes deeper than ours
|
||||
if name.depth > self.depth:
|
||||
names.add(name)
|
||||
if name.owner != self.currentModule and self.depth > -1:
|
||||
# Names coming from other modules only go out of scope
|
||||
# when the global scope is closed (i.e. at the end of
|
||||
# the module)
|
||||
if name.depth == 0 and not self.isMainModule:
|
||||
# Global names coming from other modules only go out of scope
|
||||
# when the global scope of the main module is closed (i.e. at
|
||||
# the end of the whole program)
|
||||
continue
|
||||
if not name.resolved and not self.replMode:
|
||||
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used")
|
||||
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:
|
||||
names.add(name)
|
||||
if 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.
|
||||
# This includes the environments of every other closure that may
|
||||
# have been contained within it, too
|
||||
|
@ -1253,6 +1284,40 @@ proc endScope(self: Compiler) =
|
|||
self.emitByte(PopClosure, self.peek().token.line)
|
||||
self.emitBytes((y + i).toTriple(), self.peek().token.line)
|
||||
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 we're popping more than one variable,
|
||||
# we emit a bunch of PopN instructions until
|
||||
|
@ -1322,6 +1387,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
|||
ident: node.name,
|
||||
isPrivate: node.isPrivate,
|
||||
owner: self.currentModule,
|
||||
file: self.file,
|
||||
isConst: node.isConst,
|
||||
valueType: nil, # Done later
|
||||
isLet: node.isLet,
|
||||
|
@ -1340,6 +1406,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
|||
isPrivate: node.isPrivate,
|
||||
isConst: false,
|
||||
owner: self.currentModule,
|
||||
file: self.file,
|
||||
valueType: Type(kind: Function,
|
||||
returnType: nil, # We check it later
|
||||
args: @[],
|
||||
|
@ -1367,7 +1434,8 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
|||
line: fn.node.token.line,
|
||||
belongsTo: fn,
|
||||
ident: gen.name,
|
||||
owner: self.currentModule))
|
||||
owner: self.currentModule,
|
||||
file: self.file))
|
||||
constraints = @[]
|
||||
if not node.returnType.isNil():
|
||||
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,
|
||||
isPrivate: true,
|
||||
owner: self.currentModule,
|
||||
file: self.file,
|
||||
isConst: false,
|
||||
ident: argument.name,
|
||||
valueType: self.inferOrError(argument.valueType, allowGeneric=true),
|
||||
|
@ -1387,7 +1456,8 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
|||
isLet: false,
|
||||
line: argument.name.token.line,
|
||||
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))
|
||||
if node.generics.len() > 0:
|
||||
|
@ -1398,6 +1468,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
|||
declaredName = name
|
||||
self.names.add(Name(depth: self.depth,
|
||||
owner: self.currentModule,
|
||||
file: self.file,
|
||||
ident: newIdentExpr(Token(kind: Identifier, lexeme: name, line: node.moduleName.token.line)),
|
||||
line: node.moduleName.token.line,
|
||||
kind: NameKind.Module,
|
||||
|
@ -1410,9 +1481,9 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
|
|||
for name in self.findByName(declaredName, resolve=false):
|
||||
if name == n:
|
||||
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
|
||||
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) =
|
||||
|
@ -1452,6 +1523,17 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name) =
|
|||
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) =
|
||||
## Handles the "pure" pragma
|
||||
case name.node.kind:
|
||||
|
@ -1475,10 +1557,31 @@ proc dispatchPragmas(self: Compiler, name: Name) =
|
|||
pragmas = LambdaExpr(name.node).pragmas
|
||||
else:
|
||||
discard # Unreachable
|
||||
var f: CompilerFunc
|
||||
for pragma in pragmas:
|
||||
if pragma.name.token.lexeme notin self.compilerProcs:
|
||||
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) =
|
||||
|
@ -1525,6 +1628,7 @@ proc beginProgram(self: Compiler): int =
|
|||
isConst: false,
|
||||
isLet: false,
|
||||
owner: self.currentModule,
|
||||
file: self.file,
|
||||
valueType: Type(kind: Function,
|
||||
returnType: nil,
|
||||
args: @[],
|
||||
|
@ -1535,7 +1639,6 @@ proc beginProgram(self: Compiler): int =
|
|||
resolved: true,
|
||||
line: -1)
|
||||
self.names.add(main)
|
||||
self.scopeOwners.add((main, 0))
|
||||
self.emitByte(LoadUInt64, 1)
|
||||
self.emitBytes(self.chunk.writeConstant(main.codePos.toLong()), 1)
|
||||
self.emitByte(LoadUInt64, 1)
|
||||
|
@ -1587,9 +1690,10 @@ proc literal(self: Compiler, node: ASTNode) =
|
|||
self.error("integer value out of range")
|
||||
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
||||
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))
|
||||
of binExpr:
|
||||
var x: int
|
||||
|
@ -1600,7 +1704,8 @@ proc literal(self: Compiler, node: ASTNode) =
|
|||
self.error("integer value out of range")
|
||||
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
||||
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))
|
||||
|
@ -1613,7 +1718,8 @@ proc literal(self: Compiler, node: ASTNode) =
|
|||
self.error("integer value out of range")
|
||||
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
|
||||
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))
|
||||
|
@ -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!)")
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
## and bitwise negation
|
||||
let valueType = self.infer(node.a)
|
||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]), node)
|
||||
self.callUnaryOp(funct, node)
|
||||
self.callUnaryOp(self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a))]), node), node)
|
||||
|
||||
|
||||
proc binary(self: Compiler, node: BinaryExpr) =
|
||||
proc binary(self: Compiler, node: BinaryExpr) {.inline.} =
|
||||
## Compiles all binary expression
|
||||
let typeOfA = self.infer(node.a)
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
proc identifier(self: Compiler, node: IdentExpr) =
|
||||
|
@ -1674,7 +1775,7 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
# they're referenced
|
||||
self.emitByte(LoadUInt64, 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
|
||||
# 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)
|
||||
|
@ -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) =
|
||||
## Small wrapper that abstracts emitting a call instruction
|
||||
## for a given function
|
||||
self.dispatchDelayedPragmas(fn)
|
||||
if fn.valueType.isBuiltinFunction:
|
||||
self.handleBuiltinFunction(fn.valueType, args, line)
|
||||
return
|
||||
|
@ -1876,6 +1978,7 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name =
|
|||
self.names.add(Name(depth: name.depth + 1,
|
||||
isPrivate: true,
|
||||
owner: self.currentModule,
|
||||
file: self.file,
|
||||
isConst: false,
|
||||
ident: newIdentExpr(Token(lexeme: argName.name)),
|
||||
valueType: argName.kind,
|
||||
|
@ -1887,7 +1990,6 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name =
|
|||
))
|
||||
if result.valueType.returnType.kind == Generic:
|
||||
result.valueType.returnType = mapping[result.valueType.returnType.name]
|
||||
# self.funDecl(FunDecl(result.node), result)
|
||||
else:
|
||||
discard # TODO: Custom user-defined types
|
||||
|
||||
|
@ -2251,11 +2353,11 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
|||
var jmp: int
|
||||
# We store the current function
|
||||
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 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
|
||||
# and then jumped over
|
||||
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,
|
||||
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
|
||||
## object
|
||||
if chunk.isNil():
|
||||
|
@ -2374,6 +2476,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
|
|||
self.source = source
|
||||
self.isMainModule = isMainModule
|
||||
self.disabledWarnings = disabledWarnings
|
||||
self.showMismatches = showMismatches
|
||||
if not incremental:
|
||||
self.jumps = @[]
|
||||
let pos = self.beginProgram()
|
||||
|
@ -2412,7 +2515,7 @@ proc compileModule(self: Compiler, moduleName: string) =
|
|||
path, self.lexer.getLines(),
|
||||
source, persist=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.current = current
|
||||
self.ast = ast
|
||||
|
|
|
@ -51,13 +51,13 @@ type
|
|||
file: string
|
||||
lines: seq[tuple[start, stop: int]]
|
||||
lastLine: int
|
||||
linePos: int
|
||||
lineCurrent: int
|
||||
spaces: int
|
||||
LexingError* = ref object of PeonException
|
||||
## A lexing error
|
||||
## A lexing exception
|
||||
lexer*: Lexer
|
||||
file*: string
|
||||
lexeme*: string
|
||||
line*: int
|
||||
pos*: tuple[start, stop: int]
|
||||
|
||||
|
||||
proc newSymbolTable: SymbolTable =
|
||||
|
@ -116,7 +116,10 @@ proc getToken(self: Lexer, lexeme: string): Token =
|
|||
result.kind = kind
|
||||
result.lexeme = self.source[self.start..<self.current]
|
||||
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 =
|
||||
|
@ -182,6 +185,8 @@ proc newLexer*(self: Lexer = nil): Lexer =
|
|||
result.file = ""
|
||||
result.lines = @[]
|
||||
result.lastLine = 0
|
||||
result.linePos = 0
|
||||
result.lineCurrent = 0
|
||||
result.symbols = newSymbolTable()
|
||||
result.spaces = 0
|
||||
|
||||
|
@ -198,6 +203,7 @@ proc incLine(self: Lexer) =
|
|||
self.lines.add((self.lastLine, self.current))
|
||||
self.lastLine = self.current
|
||||
self.line += 1
|
||||
self.linePos = 0
|
||||
|
||||
|
||||
proc step(self: Lexer, n: int = 1): string =
|
||||
|
@ -211,6 +217,7 @@ proc step(self: Lexer, n: int = 1): string =
|
|||
else:
|
||||
result.add(self.source[self.current])
|
||||
inc(self.current)
|
||||
inc(self.linePos)
|
||||
|
||||
|
||||
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) =
|
||||
## Raises a lexing error with info
|
||||
## for error messages
|
||||
raise LexingError(msg: message, line: self.line, file: self.file, lexeme: self.peek(), lexer: self)
|
||||
## Raises a lexing error with the
|
||||
## appropriate metadata
|
||||
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 =
|
||||
|
@ -297,16 +304,17 @@ proc createToken(self: Lexer, tokenType: TokenType) =
|
|||
tok.spaces = self.spaces
|
||||
self.spaces = 0
|
||||
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)
|
||||
|
||||
|
||||
proc parseEscape(self: Lexer) =
|
||||
# Boring escape sequence parsing. For more info check out
|
||||
# https://en.wikipedia.org/wiki/Escape_sequences_in_C.
|
||||
# As of now, \u and \U are not supported, but they'll
|
||||
# likely be soon. Another notable limitation is that
|
||||
# \xhhh and \nnn are limited to the size of a char
|
||||
# (i.e. uint8, or 256 values)
|
||||
## Boring escape sequence parsing. For more info check out
|
||||
## https://en.wikipedia.org/wiki/Escape_sequences_in_C.
|
||||
## As of now, \u and \U are not supported, but they'll
|
||||
## likely be soon. Another notable limitation is that
|
||||
## \xhhh and \nnn are limited to the size of a char
|
||||
## (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
|
||||
# (i.e. not well, given they crash the C code generator)
|
||||
of 'a':
|
||||
|
@ -568,8 +576,9 @@ proc next(self: Lexer) =
|
|||
elif self.match(" "):
|
||||
# Whitespaces
|
||||
inc(self.spaces)
|
||||
elif self.match("\r"):
|
||||
self.error("tabs are not allowed in peon code")
|
||||
inc(self.start, 2)
|
||||
elif self.match("\t"):
|
||||
self.error("tabs are not allowed in peon code, use spaces for indentation instead")
|
||||
elif self.match("\n"):
|
||||
# New line
|
||||
self.incLine()
|
||||
|
@ -650,9 +659,13 @@ proc lex*(self: Lexer, source, file: string): seq[Token] =
|
|||
self.source = source
|
||||
self.file = file
|
||||
self.lines = @[]
|
||||
self.lastLine = 0
|
||||
self.linePos = 0
|
||||
self.lineCurrent = 0
|
||||
while not self.done():
|
||||
self.next()
|
||||
self.start = self.current
|
||||
self.lineCurrent = self.linePos
|
||||
self.tokens.add(Token(kind: EndOfFile, lexeme: "",
|
||||
line: self.line, pos: (self.current, self.current)))
|
||||
self.incLine()
|
||||
|
|
|
@ -784,3 +784,28 @@ proc `$`*(self: ASTNode): string =
|
|||
|
||||
|
||||
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
|
||||
## Nim exceptions for internal Peon failures
|
||||
PeonException* = ref object of CatchableError
|
||||
SerializationError* = ref object of PeonException
|
||||
file*: string
|
||||
## A Nim exception for a generic internal
|
||||
## 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
|
||||
Pragma,
|
||||
|
||||
|
||||
|
||||
|
||||
Token* = ref object
|
||||
## A token object
|
||||
kind*: TokenType # Type of the token
|
||||
lexeme*: string # The lexeme associated to the token
|
||||
line*: int # The line where the token appears
|
||||
pos*: tuple[start, stop: int] # The absolute position in the source file
|
||||
# (0-indexed and inclusive at the beginning)
|
||||
spaces*: int # Number of spaces before this token
|
||||
kind*: TokenType # The type of the token
|
||||
lexeme*: string # The lexeme associated to the token
|
||||
line*: int # The line where the token appears
|
||||
pos*: tuple[start, stop: int] # The absolute position in the source file
|
||||
relPos*: tuple[start, stop: int] # The relative position in the source line
|
||||
spaces*: int # Number of spaces before this token
|
||||
|
||||
|
||||
|
||||
proc `$`*(self: Token): string =
|
||||
## Strinfifies
|
||||
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:
|
||||
result = "nil"
|
||||
|
||||
|
|
|
@ -98,21 +98,12 @@ type
|
|||
# Keeps track of imported modules
|
||||
modules: seq[tuple[name: string, loaded: bool]]
|
||||
ParseError* = ref object of PeonException
|
||||
## A parsing exception
|
||||
parser*: Parser
|
||||
file*: string
|
||||
token*: Token
|
||||
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) =
|
||||
## Adds an operator to the table. Its precedence
|
||||
## is inferred from the operator's lexeme (the
|
||||
|
@ -142,6 +133,16 @@ proc addOperator(self: OperatorTable, lexeme: string) =
|
|||
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 =
|
||||
## Gets the precedence of a given operator
|
||||
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].} =
|
||||
## 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:
|
||||
|
|
122
src/main.nim
122
src/main.nim
|
@ -22,6 +22,7 @@ import backend/vm as v
|
|||
import util/serializer as s
|
||||
import util/debugger
|
||||
import util/symbols
|
||||
import util/fmterr
|
||||
import config
|
||||
|
||||
# Builtins & external libs
|
||||
|
@ -140,54 +141,18 @@ proc repl =
|
|||
styledEcho fgRed, "Corrupted"
|
||||
vm.run(serialized.chunk)
|
||||
except LexingError:
|
||||
var exc = 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)))
|
||||
print(LexingError(getCurrentException()))
|
||||
except ParseError:
|
||||
let exc = 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)))
|
||||
print(ParseError(getCurrentException()))
|
||||
except CompileError:
|
||||
let exc = 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)))
|
||||
print(CompileError(getCurrentException()))
|
||||
except SerializationError:
|
||||
let exc = SerializationError(getCurrentException())
|
||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
|
||||
print(SerializationError(getCurrentException()))
|
||||
quit(0)
|
||||
|
||||
|
||||
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
|
||||
tokens: seq[Token] = @[]
|
||||
tree: seq[Declaration] = @[]
|
||||
|
@ -232,7 +197,7 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
|
|||
for node in tree:
|
||||
styledEcho fgGreen, "\t", $node
|
||||
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:
|
||||
styledEcho fgCyan, "Compilation step:\n"
|
||||
debugger.disassembleChunk(compiled, f)
|
||||
|
@ -272,53 +237,17 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
|
|||
styledEcho fgRed, "Corrupted"
|
||||
vm.run(serialized.chunk, breakpoints)
|
||||
except LexingError:
|
||||
var exc = 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)))
|
||||
print(LexingError(getCurrentException()))
|
||||
except ParseError:
|
||||
let exc = 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)))
|
||||
print(ParseError(getCurrentException()))
|
||||
except CompileError:
|
||||
let exc = 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)))
|
||||
print(CompileError(getCurrentException()))
|
||||
except SerializationError:
|
||||
let exc = SerializationError(getCurrentException())
|
||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
|
||||
print(SerializationError(getCurrentException()))
|
||||
except IOError:
|
||||
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}")
|
||||
print(IOError(getCurrentException()[]), f)
|
||||
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 breaks: seq[uint64] = @[]
|
||||
var dis: bool = false
|
||||
var mismatches: bool = false
|
||||
for kind, key, value in optParser.getopt():
|
||||
case kind:
|
||||
of cmdArgument:
|
||||
|
@ -355,31 +285,35 @@ when isMainModule:
|
|||
for warning in WarningKind:
|
||||
warnings.add(warning)
|
||||
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()
|
||||
of "no-warn":
|
||||
of "showMismatches":
|
||||
mismatches = true
|
||||
of "noWarn":
|
||||
case value:
|
||||
of "unusedVariable":
|
||||
warnings.add(WarningKind.UnusedName)
|
||||
of "unreachableCode":
|
||||
warnings.add(WarningKind.UnreachableCode)
|
||||
of "shadowingOuterScope":
|
||||
warnings.add(WarningKind.ShadowOuterScope)
|
||||
else:
|
||||
stderr.writeLine("error: invalid warning name for option 'warnings'")
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid warning name for option 'warnings'")
|
||||
quit()
|
||||
of "breakpoints":
|
||||
when not debugVM:
|
||||
echo "error: cannot set breakpoints in release mode"
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "cannot set breakpoints in release mode")
|
||||
quit()
|
||||
for point in value.strip(chars={' '}).split(","):
|
||||
try:
|
||||
breaks.add(parseBiggestUInt(point))
|
||||
except ValueError:
|
||||
echo &"error: invalid breakpoint value '{point}'"
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
|
||||
quit()
|
||||
of "disassemble":
|
||||
dis = true
|
||||
else:
|
||||
echo &"error: unkown option '{key}'"
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'")
|
||||
quit()
|
||||
of cmdShortOption:
|
||||
case key:
|
||||
|
@ -396,18 +330,18 @@ when isMainModule:
|
|||
dump = false
|
||||
of "b":
|
||||
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()
|
||||
for point in value.strip(chars={' '}).split(","):
|
||||
try:
|
||||
breaks.add(parseBiggestUInt(point))
|
||||
except ValueError:
|
||||
stderr.writeLine(&"error: invalid breakpoint value '{point}'")
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'")
|
||||
quit()
|
||||
of "d":
|
||||
dis = true
|
||||
else:
|
||||
stderr.writeLine(&"error: unkown option '{key}'")
|
||||
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"unkown option '{key}'")
|
||||
quit()
|
||||
else:
|
||||
echo "usage: peon [options] [filename.pn]"
|
||||
|
@ -415,7 +349,7 @@ when isMainModule:
|
|||
if file == "":
|
||||
repl()
|
||||
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 {
|
||||
#pragma[magic: "NegateFloat64", pure]
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
# 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
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ type
|
|||
commit*: string
|
||||
compileDate*: int
|
||||
chunk*: Chunk
|
||||
SerializationError* = ref object of PeonException
|
||||
|
||||
|
||||
proc `$`*(self: Serialized): string =
|
||||
|
|
|
@ -16,4 +16,4 @@ fn fooBar(a, b: int): int {
|
|||
|
||||
|
||||
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);
|
||||
print(closure()); # 42
|
||||
print(makeClosureTwo(38)()); # 38
|
||||
print(closure() == 42);
|
||||
print(makeClosureTwo(38)() == 38);
|
||||
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
|
||||
print(second(0) == 2); # true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import std;
|
||||
|
||||
|
||||
var x = 100000;
|
||||
var x = 10000000;
|
||||
var y = "just a test";
|
||||
print(y);
|
||||
print("Starting GC torture test");
|
||||
|
|
Loading…
Reference in New Issue