From 9567319c401a4e2710dc640a0c0fde6e95708f69 Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Wed, 23 Nov 2022 01:02:35 +0100 Subject: [PATCH] Major error formatting refactoring. Improved warning system --- src/backend/vm.nim | 11 +- src/config.nim | 24 +- src/frontend/compiler.nim | 293 ++++++++++++++++-------- src/frontend/lexer.nim | 45 ++-- src/frontend/meta/ast.nim | 25 ++ src/frontend/meta/errors.nim | 7 +- src/frontend/meta/token.nim | 17 +- src/frontend/parser.nim | 24 +- src/main.nim | 122 +++------- src/peon/stdlib/builtins/arithmetics.pn | 5 + src/peon/stdlib/builtins/misc.pn | 5 - src/util/serializer.nim | 1 + tests/calls.pn | 2 +- tests/closures.pn | 7 +- tests/closures2.pn | 2 +- tests/cross_shadowing.pn | 3 +- tests/gc.pn | 2 +- 17 files changed, 338 insertions(+), 257 deletions(-) diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 10e5f75..da7c018 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -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}'") diff --git a/src/config.nim b/src/config.nim index 82bfb8c..194cf29 100644 --- a/src/config.nim +++ b/src/config.nim @@ -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!) """ diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index dee2be0..4c9d35e 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -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 ["", ""]: + 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 diff --git a/src/frontend/lexer.nim b/src/frontend/lexer.nim index 6d24a46..4180cd0 100644 --- a/src/frontend/lexer.nim +++ b/src/frontend/lexer.nim @@ -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.. 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) + \ No newline at end of file diff --git a/src/frontend/meta/errors.nim b/src/frontend/meta/errors.nim index f89a075..340270f 100644 --- a/src/frontend/meta/errors.nim +++ b/src/frontend/meta/errors.nim @@ -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 diff --git a/src/frontend/meta/token.nim b/src/frontend/meta/token.nim index 28ad9a5..1752818 100644 --- a/src/frontend/meta/token.nim +++ b/src/frontend/meta/token.nim @@ -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" diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 1e9daaa..27e853e 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -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: diff --git a/src/main.nim b/src/main.nim index af6dc5d..365dfda 100644 --- a/src/main.nim +++ b/src/main.nim @@ -22,6 +22,7 @@ import backend/vm as v import util/serializer as s import util/debugger import util/symbols +import util/fmterr import config # Builtins & external libs @@ -140,54 +141,18 @@ proc repl = styledEcho fgRed, "Corrupted" vm.run(serialized.chunk) except LexingError: - var exc = LexingError(getCurrentException()) - let relPos = exc.lexer.getRelPos(exc.line) - let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}) - stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", - fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'", - fgRed, ": ", fgGreen , getCurrentExceptionMsg()) - styledEcho fgBlue, "Source line: " , fgDefault, line - styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(exc.lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(exc.lexeme))) + print(LexingError(getCurrentException())) except ParseError: - let exc = ParseError(getCurrentException()) - let lexeme = exc.token.lexeme - var lineNo = exc.token.line - if exc.token.kind == EndOfFile: - dec(lineNo) - let relPos = exc.parser.getRelPos(lineNo) - let fn = exc.parser.getCurrentFunction() - let line = exc.parser.getSource().splitLines()[lineNo - 1].strip() - var fnMsg = "" - if fn != nil and fn.kind == funDecl: - fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" - stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", - fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", - fgRed, ": ", fgGreen , getCurrentExceptionMsg()) - styledEcho fgBlue, "Source line: " , fgDefault, line - styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) + print(ParseError(getCurrentException())) except CompileError: - let exc = CompileError(getCurrentException()) - let lexeme = exc.node.token.lexeme - var lineNo = exc.node.token.line - let relPos = exc.compiler.getRelPos(lineNo) - let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) - var fn = exc.compiler.getCurrentFunction() - var fnMsg = "" - if fn != nil and fn.kind == funDecl: - fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" - stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ", - fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", - fgRed, ": ", fgGreen , getCurrentExceptionMsg()) - styledEcho fgBlue, "Source line: " , fgDefault, line - styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) + print(CompileError(getCurrentException())) except SerializationError: - let exc = SerializationError(getCurrentException()) - stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg()) + print(SerializationError(getCurrentException())) quit(0) proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false, - warnings: seq[WarningKind] = @[]) = + warnings: seq[WarningKind] = @[], mismatches: bool = false) = var tokens: seq[Token] = @[] tree: seq[Declaration] = @[] @@ -232,7 +197,7 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints for node in tree: styledEcho fgGreen, "\t", $node echo "" - compiled = compiler.compile(tree, f, tokenizer.getLines(), input, disabledWarnings=warnings) + compiled = compiler.compile(tree, f, tokenizer.getLines(), input, disabledWarnings=warnings, showMismatches=mismatches) when debugCompiler: styledEcho fgCyan, "Compilation step:\n" debugger.disassembleChunk(compiled, f) @@ -272,53 +237,17 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints styledEcho fgRed, "Corrupted" vm.run(serialized.chunk, breakpoints) except LexingError: - var exc = LexingError(getCurrentException()) - let relPos = exc.lexer.getRelPos(exc.line) - let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}) - stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", - fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'", - fgRed, ": ", fgGreen , getCurrentExceptionMsg()) - styledEcho fgBlue, "Source line: " , fgDefault, line - styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(exc.lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(exc.lexeme))) + print(LexingError(getCurrentException())) except ParseError: - let exc = ParseError(getCurrentException()) - let lexeme = exc.token.lexeme - var lineNo = exc.token.line - if exc.token.kind == EndOfFile: - dec(lineNo) - let relPos = exc.parser.getRelPos(lineNo) - let fn = parser.getCurrentFunction() - let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) - var fnMsg = "" - if fn != nil and fn.kind == funDecl: - fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" - stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ", - fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", - fgRed, ": ", fgGreen , getCurrentExceptionMsg()) - styledEcho fgBlue, "Source line: " , fgDefault, line - styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) + print(ParseError(getCurrentException())) except CompileError: - let exc = CompileError(getCurrentException()) - let lexeme = exc.node.token.lexeme - var lineNo = exc.node.token.line - let relPos = exc.compiler.getRelPos(lineNo) - let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'}) - var fn = exc.compiler.getCurrentFunction() - var fnMsg = "" - if fn != nil and fn.kind == funDecl: - fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'" - stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ", - fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'", - fgRed, ": ", fgGreen , getCurrentExceptionMsg()) - styledEcho fgBlue, "Source line: " , fgDefault, line - styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme))) + print(CompileError(getCurrentException())) except SerializationError: - let exc = SerializationError(getCurrentException()) - stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg()) + print(SerializationError(getCurrentException())) except IOError: - stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}") + print(IOError(getCurrentException()[]), f) except OSError: - stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]") + print(OSError(getCurrentException()[]), f, osLastError()) @@ -331,6 +260,7 @@ when isMainModule: var warnings: seq[WarningKind] = @[] var breaks: seq[uint64] = @[] var dis: bool = false + var mismatches: bool = false for kind, key, value in optParser.getopt(): case kind: of cmdArgument: @@ -355,31 +285,35 @@ when isMainModule: for warning in WarningKind: warnings.add(warning) else: - stderr.writeLine("error: invalid value for option 'warnings' (valid options: yes, on, no, off)") + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid value for option 'warnings' (valid options are: yes, on, no, off)") quit() - of "no-warn": + of "showMismatches": + mismatches = true + of "noWarn": case value: of "unusedVariable": warnings.add(WarningKind.UnusedName) of "unreachableCode": warnings.add(WarningKind.UnreachableCode) + of "shadowingOuterScope": + warnings.add(WarningKind.ShadowOuterScope) else: - stderr.writeLine("error: invalid warning name for option 'warnings'") + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid warning name for option 'warnings'") quit() of "breakpoints": when not debugVM: - echo "error: cannot set breakpoints in release mode" + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "cannot set breakpoints in release mode") quit() for point in value.strip(chars={' '}).split(","): try: breaks.add(parseBiggestUInt(point)) except ValueError: - echo &"error: invalid breakpoint value '{point}'" + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'") quit() of "disassemble": dis = true else: - echo &"error: unkown option '{key}'" + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: unkown option '{key}'") quit() of cmdShortOption: case key: @@ -396,18 +330,18 @@ when isMainModule: dump = false of "b": when not debugVM: - stderr.writeLine("error: cannot set breakpoints in release mode") + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "cannot set breakpoints in release mode") quit() for point in value.strip(chars={' '}).split(","): try: breaks.add(parseBiggestUInt(point)) except ValueError: - stderr.writeLine(&"error: invalid breakpoint value '{point}'") + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"error: invalid breakpoint value '{point}'") quit() of "d": dis = true else: - stderr.writeLine(&"error: unkown option '{key}'") + stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, &"unkown option '{key}'") quit() else: echo "usage: peon [options] [filename.pn]" @@ -415,7 +349,7 @@ when isMainModule: if file == "": repl() else: - runFile(file, fromString, dump, breaks, dis, warnings) + runFile(file, fromString, dump, breaks, dis, warnings, mismatches) diff --git a/src/peon/stdlib/builtins/arithmetics.pn b/src/peon/stdlib/builtins/arithmetics.pn index 27134d2..11fbaee 100644 --- a/src/peon/stdlib/builtins/arithmetics.pn +++ b/src/peon/stdlib/builtins/arithmetics.pn @@ -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] } diff --git a/src/peon/stdlib/builtins/misc.pn b/src/peon/stdlib/builtins/misc.pn index 0970be7..45f56c7 100644 --- a/src/peon/stdlib/builtins/misc.pn +++ b/src/peon/stdlib/builtins/misc.pn @@ -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 diff --git a/src/util/serializer.nim b/src/util/serializer.nim index f985e0b..c7574a1 100644 --- a/src/util/serializer.nim +++ b/src/util/serializer.nim @@ -40,6 +40,7 @@ type commit*: string compileDate*: int chunk*: Chunk + SerializationError* = ref object of PeonException proc `$`*(self: Serialized): string = diff --git a/tests/calls.pn b/tests/calls.pn index 1b5195a..779f785 100644 --- a/tests/calls.pn +++ b/tests/calls.pn @@ -16,4 +16,4 @@ fn fooBar(a, b: int): int { noReturn(1); -print(fooBar(1, 3)); # 1 \ No newline at end of file +print(fooBar(1, 3) == 1); # true diff --git a/tests/closures.pn b/tests/closures.pn index 12c77f7..011bcb6 100644 --- a/tests/closures.pn +++ b/tests/closures.pn @@ -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 \ No newline at end of file +print(closureTwo() == 420); \ No newline at end of file diff --git a/tests/closures2.pn b/tests/closures2.pn index 2a64165..60867c6 100644 --- a/tests/closures2.pn +++ b/tests/closures2.pn @@ -9,4 +9,4 @@ fn makeAdder(x: int): fn (n: int): int { } -print(makeAdder(5)(2)); # 7 +print(makeAdder(5)(2) == 7); # true diff --git a/tests/cross_shadowing.pn b/tests/cross_shadowing.pn index 5f14908..9761322 100644 --- a/tests/cross_shadowing.pn +++ b/tests/cross_shadowing.pn @@ -17,5 +17,4 @@ fn second(x: int): int { } - -print(second(0)); # 2 +print(second(0) == 2); # true diff --git a/tests/gc.pn b/tests/gc.pn index d47bcc2..8d8e482 100644 --- a/tests/gc.pn +++ b/tests/gc.pn @@ -1,7 +1,7 @@ import std; -var x = 100000; +var x = 10000000; var y = "just a test"; print(y); print("Starting GC torture test");