Major error formatting refactoring. Improved warning system

This commit is contained in:
Mattia Giambirtone 2022-11-23 01:02:35 +01:00
parent 43d67562f1
commit 9567319c40
17 changed files with 338 additions and 257 deletions

View File

@ -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}'")

View File

@ -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!)
""" """

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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:

View File

@ -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)

View File

@ -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]
} }

View File

@ -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

View File

@ -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 =

View File

@ -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

View File

@ -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);

View File

@ -9,4 +9,4 @@ fn makeAdder(x: int): fn (n: int): int {
} }
print(makeAdder(5)(2)); # 7 print(makeAdder(5)(2) == 7); # true

View File

@ -17,5 +17,4 @@ fn second(x: int): int {
} }
print(second(0) == 2); # true
print(second(0)); # 2

View File

@ -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");