diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 6eb3785..1ad05bf 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -16,9 +16,8 @@ import std/math import std/segfaults import std/strutils -import std/sequtils import std/sets -import std/times +import std/monotimes import ../config @@ -28,6 +27,7 @@ import ../util/multibyte when debugVM or debugMem or debugGC: import std/strformat + import std/sequtils import std/terminal @@ -58,6 +58,7 @@ type envs: seq[uint64] # Stores variables that do not have stack semantics results: seq[uint64] # Stores function's results (return values) gc: PeonGC # Our memory manager + breakpoints: seq[uint64] # Breakpoints where we call our debugger ObjectKind* = enum ## A tag for heap-allocated ## peon objects @@ -500,7 +501,7 @@ proc readLong(self: PeonVM): uint32 = return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple()) -proc readUInt(self: PeonVM): uint32 = +proc readUInt(self: PeonVM): uint32 {.used.} = ## Reads three bytes from the ## bytecode and returns them ## as an unsigned 32 bit @@ -681,7 +682,8 @@ proc dispatch*(self: PeonVM) = while true: {.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma when debugVM: - self.debug() + if self.ip in self.breakpoints or self.breakpoints.len() == 0: + self.debug() instruction = OpCode(self.readByte()) case instruction: # Constant loading instructions @@ -1020,19 +1022,20 @@ proc dispatch*(self: PeonVM) = stdout.write(s.str[i]) stdout.write("\n") of SysClock64: - self.push(cast[uint64](cpuTime())) + self.push(cast[uint64](getMonoTime().ticks.float() / 1_000_000_000)) of LogicalNot: self.push(uint64(not self.pop().bool)) else: discard -proc run*(self: PeonVM, chunk: Chunk) = +proc run*(self: PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) = ## Executes a piece of Peon bytecode self.chunk = chunk self.frames = @[] self.calls = @[] self.operands = @[] + self.breakpoints = breakpoints self.results = @[] self.ip = 0 # Sorry, but there only is enough space diff --git a/src/config.nim b/src/config.nim index cc9d333..579cd81 100644 --- a/src/config.nim +++ b/src/config.nim @@ -51,6 +51,8 @@ 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 + Command-line options -------------------- @@ -58,7 +60,7 @@ Command-line options -h, --help Shows this help text and exits -v, --version Prints the peon version number and exits -s, --string Executes the passed string as if it was a file --i, --interactive Enables interactive mode, which opens a REPL session after execution of a file or source string --c, --nocache Disables dumping the result of bytecode compilation to files for caching --d, --cache-delay Configures the bytecode cache invalidation threshold, in minutes (defaults to 60) +-i, --interactive Enables interactive mode: a REPL session is opened after execution +-d, --nodump Disables dumping the result of bytecode compilation to files +-b, --breakpoints Pauses execution of the peon virtual machine and runs the debugger at specific bytecode offsets """ diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index cd55a3d..216b9f8 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -558,7 +558,9 @@ proc getStackPos(self: Compiler, name: Name): int = var found = false result = 2 for variable in self.names: - if variable.kind notin [NameKind.Var, NameKind.Argument]: + if variable.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum, NameKind.Function]: + continue + elif variable.kind == NameKind.Argument and variable.depth > self.scopeDepth: continue elif not variable.belongsTo.isNil() and variable.belongsTo.valueType.isBuiltinFunction: continue @@ -566,12 +568,11 @@ proc getStackPos(self: Compiler, name: Name): int = continue elif variable.owner != self.currentModule: continue - if variable.depth > self.scopeDepth: - continue if name.ident == variable.ident: found = true break inc(result) + echo variable if not found: return -1 @@ -1223,7 +1224,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = )) if mutable: self.names[^1].valueType.mutable = true - return self.names[^1] + result = self.names[^1] of NodeKind.funDecl: var node = FunDecl(node) var generics: seq[Name] = @[] @@ -1266,12 +1267,12 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = # wait, no LoadVar? Yes! That's because when calling functions, # arguments will already be on the stack, so there's no need to # load them here - name = Name(depth: self.scopeDepth + 1, + name = Name(depth: fn.depth + 1, isPrivate: true, owner: self.currentModule, isConst: false, ident: argument.name, - valueType: nil, + valueType: self.infer(argument.valueType), codePos: 0, isLet: false, line: argument.name.token.line, @@ -1279,14 +1280,13 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = kind: NameKind.Argument ) self.names.add(name) - name.valueType = self.infer(argument.valueType) - # If it's still nil, it's an error! + # If it's nil, it's an error! if name.valueType.isNil(): self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'", argument.name) fn.valueType.args.add((argument.name.token.lexeme, name.valueType)) if generics.len() > 0: fn.valueType.isGeneric = true - return fn + result = fn of NodeKind.importStmt: var node = ImportStmt(node) var name = node.moduleName.token.lexeme.extractFilename().replace(".pn", "") @@ -1301,12 +1301,14 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = kind: NameKind.Module, isPrivate: false )) - return self.names[^1] + result = self.names[^1] else: discard # TODO: Types, enums for name in self.findByName(declaredName): - if (name.kind == NameKind.Var and name.depth == self.scopeDepth) or name.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum]: - self.error(&"attempt to redeclare '{name}', which was previously defined in '{name.owner}' at line {name.line}") + if name == result: + continue + elif (name.kind == NameKind.Var and name.depth == self.scopeDepth) or name.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum]: + self.error(&"attempt to redeclare '{name.ident.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}") proc emitLoop(self: Compiler, begin: int, line: int) = @@ -1746,7 +1748,7 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name = for (argExpr, argName) in zip(args, result.valueType.args): if self.names.high() > 16777215: self.error("cannot declare more than 16777215 variables at a time") - self.names.add(Name(depth: self.scopeDepth + 1, + self.names.add(Name(depth: name.depth + 1, isPrivate: true, owner: self.currentModule, isConst: false, diff --git a/src/main.nim b/src/main.nim index e90ed8d..8b296b4 100644 --- a/src/main.nim +++ b/src/main.nim @@ -191,7 +191,7 @@ proc repl = quit(0) -proc runFile(f: string, interactive: bool = false, fromString: bool = false) = +proc runFile(f: string, interactive: bool = false, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[]) = var tokens: seq[Token] = @[] tree: seq[Declaration] = @[] @@ -242,10 +242,11 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) = if path.len() > 0: path &= "/" path &= splitFile(f).name & ".pbc" - serializer.dumpFile(compiled, f, path) - serialized = serializer.loadFile(path) - if fromString: - discard tryRemoveFile(".pbc") + if dump or not fromString: + serializer.dumpFile(compiled, f, path) + serialized = serializer.loadFile(path) + else: + serialized = serializer.loadBytes(serializer.dumpBytes(compiled, f)) when debugSerializer: styledEcho fgCyan, "Serialization step: " styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch @@ -270,7 +271,7 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) = styledEcho fgGreen, "OK" else: styledEcho fgRed, "Corrupted" - vm.run(serialized.chunk) + vm.run(serialized.chunk, breakpoints) except LexingError: var exc = LexingError(getCurrentException()) let relPos = exc.lexer.getRelPos(exc.line) @@ -326,6 +327,8 @@ when isMainModule: var file: string = "" var fromString: bool = false var interactive: bool = false + var dump: bool = true + var breaks: seq[uint64] = @[] for kind, key, value in optParser.getopt(): case kind: of cmdArgument: @@ -343,6 +346,18 @@ when isMainModule: fromString = true of "interactive": interactive = true + of "no-dump": + dump = false + of "breakpoints": + when not debugVM: + echo "error: 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}'" + quit() else: echo &"error: unkown option '{key}'" quit() @@ -359,6 +374,18 @@ when isMainModule: fromString = true of "i": interactive = true + of "d": + dump = false + of "b": + when not debugVM: + echo "error: 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}'" + quit() else: echo &"error: unkown option '{key}'" quit() @@ -369,7 +396,7 @@ when isMainModule: if file == "": repl() else: - runFile(file, interactive, fromString) + runFile(file, interactive, fromString, dump, breaks) diff --git a/src/peon/stdlib/builtins/misc.pn b/src/peon/stdlib/builtins/misc.pn index 0d03642..0970be7 100644 --- a/src/peon/stdlib/builtins/misc.pn +++ b/src/peon/stdlib/builtins/misc.pn @@ -1,4 +1,6 @@ -# Assignment operators +# Various miscellaneous utilities + +# Assignment operator operator `=`*[T: all](a: var T, b: T) { # TODO: This is just a placeholder right now #pragma[magic: "GenericAssign"] diff --git a/tests/fib.pn b/tests/fib.pn index d562c0e..d8041da 100644 --- a/tests/fib.pn +++ b/tests/fib.pn @@ -11,6 +11,6 @@ fn fib(n: int): int { print("Computing the value of fib(37)"); var x = clock(); -print(fib(37)); +print(fib(33)); print(clock() - x); print("Done!"); diff --git a/tests/generics.pn b/tests/generics.pn index e9c8ac9..389d777 100644 --- a/tests/generics.pn +++ b/tests/generics.pn @@ -6,6 +6,6 @@ fn sum[T: int | int32](a, b: T): T { } -print(sum(1, 2)); -print(sum(1'i32, 2'i32)); +print(sum(1, 2)); # Prints 3 +print(sum(1'i32, 2'i32)); # Also prints 3! # print(sum(1'i16, 2'i16)); # Will not work if uncommented!