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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 {
#pragma[magic: "NegateFloat64", pure]
}

View File

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

View File

@ -40,6 +40,7 @@ type
commit*: string
compileDate*: int
chunk*: Chunk
SerializationError* = ref object of PeonException
proc `$`*(self: Serialized): string =

View File

@ -16,4 +16,4 @@ fn fooBar(a, b: int): int {
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);
print(closure()); # 42
print(makeClosureTwo(38)()); # 38
print(closure() == 42);
print(makeClosureTwo(38)() == 38);
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
print(second(0) == 2); # true

View File

@ -1,7 +1,7 @@
import std;
var x = 100000;
var x = 10000000;
var y = "just a test";
print(y);
print("Starting GC torture test");