Various fixes to module system

This commit is contained in:
Mattia Giambirtone 2022-10-21 16:10:00 +02:00
parent 1c54243d43
commit 9573769868
7 changed files with 69 additions and 33 deletions

View File

@ -16,9 +16,8 @@
import std/math import std/math
import std/segfaults import std/segfaults
import std/strutils import std/strutils
import std/sequtils
import std/sets import std/sets
import std/times import std/monotimes
import ../config import ../config
@ -28,6 +27,7 @@ import ../util/multibyte
when debugVM or debugMem or debugGC: when debugVM or debugMem or debugGC:
import std/strformat import std/strformat
import std/sequtils
import std/terminal import std/terminal
@ -58,6 +58,7 @@ type
envs: seq[uint64] # Stores variables that do not have stack semantics envs: seq[uint64] # Stores variables that do not have stack semantics
results: seq[uint64] # Stores function's results (return values) results: seq[uint64] # Stores function's results (return values)
gc: PeonGC # Our memory manager gc: PeonGC # Our memory manager
breakpoints: seq[uint64] # Breakpoints where we call our debugger
ObjectKind* = enum ObjectKind* = enum
## A tag for heap-allocated ## A tag for heap-allocated
## peon objects ## peon objects
@ -500,7 +501,7 @@ proc readLong(self: PeonVM): uint32 =
return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple()) 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 ## Reads three bytes from the
## bytecode and returns them ## bytecode and returns them
## as an unsigned 32 bit ## as an unsigned 32 bit
@ -681,7 +682,8 @@ proc dispatch*(self: PeonVM) =
while true: while true:
{.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma {.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
when debugVM: when debugVM:
self.debug() if self.ip in self.breakpoints or self.breakpoints.len() == 0:
self.debug()
instruction = OpCode(self.readByte()) instruction = OpCode(self.readByte())
case instruction: case instruction:
# Constant loading instructions # Constant loading instructions
@ -1020,19 +1022,20 @@ proc dispatch*(self: PeonVM) =
stdout.write(s.str[i]) stdout.write(s.str[i])
stdout.write("\n") stdout.write("\n")
of SysClock64: of SysClock64:
self.push(cast[uint64](cpuTime())) self.push(cast[uint64](getMonoTime().ticks.float() / 1_000_000_000))
of LogicalNot: of LogicalNot:
self.push(uint64(not self.pop().bool)) self.push(uint64(not self.pop().bool))
else: else:
discard discard
proc run*(self: PeonVM, chunk: Chunk) = proc run*(self: PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) =
## Executes a piece of Peon bytecode ## Executes a piece of Peon bytecode
self.chunk = chunk self.chunk = chunk
self.frames = @[] self.frames = @[]
self.calls = @[] self.calls = @[]
self.operands = @[] self.operands = @[]
self.breakpoints = breakpoints
self.results = @[] self.results = @[]
self.ip = 0 self.ip = 0
# Sorry, but there only is enough space # Sorry, but there only is enough space

View File

@ -51,6 +51,8 @@ Basic usage
$ peon Opens an interactive session (REPL) $ peon Opens an interactive session (REPL)
$ peon file.pn Runs the given Peon source file $ peon file.pn Runs the given Peon source file
$ peon file.pbc Runs the given Peon bytecode file
Command-line options Command-line options
-------------------- --------------------
@ -58,7 +60,7 @@ Command-line options
-h, --help Shows this help text and exits -h, --help Shows this help text and exits
-v, --version Prints the peon version number and exits -v, --version Prints the peon version number and exits
-s, --string Executes the passed string as if it was a file -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 -i, --interactive Enables interactive mode: a REPL session is opened after execution
-c, --nocache Disables dumping the result of bytecode compilation to files for caching -d, --nodump Disables dumping the result of bytecode compilation to files
-d, --cache-delay Configures the bytecode cache invalidation threshold, in minutes (defaults to 60) -b, --breakpoints Pauses execution of the peon virtual machine and runs the debugger at specific bytecode offsets
""" """

View File

@ -558,7 +558,9 @@ proc getStackPos(self: Compiler, name: Name): int =
var found = false var found = false
result = 2 result = 2
for variable in self.names: 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 continue
elif not variable.belongsTo.isNil() and variable.belongsTo.valueType.isBuiltinFunction: elif not variable.belongsTo.isNil() and variable.belongsTo.valueType.isBuiltinFunction:
continue continue
@ -566,12 +568,11 @@ proc getStackPos(self: Compiler, name: Name): int =
continue continue
elif variable.owner != self.currentModule: elif variable.owner != self.currentModule:
continue continue
if variable.depth > self.scopeDepth:
continue
if name.ident == variable.ident: if name.ident == variable.ident:
found = true found = true
break break
inc(result) inc(result)
echo variable
if not found: if not found:
return -1 return -1
@ -1223,7 +1224,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name =
)) ))
if mutable: if mutable:
self.names[^1].valueType.mutable = true self.names[^1].valueType.mutable = true
return self.names[^1] result = self.names[^1]
of NodeKind.funDecl: of NodeKind.funDecl:
var node = FunDecl(node) var node = FunDecl(node)
var generics: seq[Name] = @[] 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, # wait, no LoadVar? Yes! That's because when calling functions,
# arguments will already be on the stack, so there's no need to # arguments will already be on the stack, so there's no need to
# load them here # load them here
name = Name(depth: self.scopeDepth + 1, name = Name(depth: fn.depth + 1,
isPrivate: true, isPrivate: true,
owner: self.currentModule, owner: self.currentModule,
isConst: false, isConst: false,
ident: argument.name, ident: argument.name,
valueType: nil, valueType: self.infer(argument.valueType),
codePos: 0, codePos: 0,
isLet: false, isLet: false,
line: argument.name.token.line, line: argument.name.token.line,
@ -1279,14 +1280,13 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name =
kind: NameKind.Argument kind: NameKind.Argument
) )
self.names.add(name) self.names.add(name)
name.valueType = self.infer(argument.valueType) # If it's nil, it's an error!
# If it's still nil, it's an error!
if name.valueType.isNil(): if name.valueType.isNil():
self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'", argument.name) self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'", argument.name)
fn.valueType.args.add((argument.name.token.lexeme, name.valueType)) fn.valueType.args.add((argument.name.token.lexeme, name.valueType))
if generics.len() > 0: if generics.len() > 0:
fn.valueType.isGeneric = true fn.valueType.isGeneric = true
return fn result = fn
of NodeKind.importStmt: of NodeKind.importStmt:
var node = ImportStmt(node) var node = ImportStmt(node)
var name = node.moduleName.token.lexeme.extractFilename().replace(".pn", "") 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, kind: NameKind.Module,
isPrivate: false isPrivate: false
)) ))
return self.names[^1] result = self.names[^1]
else: else:
discard # TODO: Types, enums discard # TODO: Types, enums
for name in self.findByName(declaredName): 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]: if name == result:
self.error(&"attempt to redeclare '{name}', which was previously defined in '{name.owner}' at line {name.line}") 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) = 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): for (argExpr, argName) in zip(args, result.valueType.args):
if self.names.high() > 16777215: if self.names.high() > 16777215:
self.error("cannot declare more than 16777215 variables at a time") 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, isPrivate: true,
owner: self.currentModule, owner: self.currentModule,
isConst: false, isConst: false,

View File

@ -191,7 +191,7 @@ proc repl =
quit(0) 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 var
tokens: seq[Token] = @[] tokens: seq[Token] = @[]
tree: seq[Declaration] = @[] tree: seq[Declaration] = @[]
@ -242,10 +242,11 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
if path.len() > 0: if path.len() > 0:
path &= "/" path &= "/"
path &= splitFile(f).name & ".pbc" path &= splitFile(f).name & ".pbc"
serializer.dumpFile(compiled, f, path) if dump or not fromString:
serialized = serializer.loadFile(path) serializer.dumpFile(compiled, f, path)
if fromString: serialized = serializer.loadFile(path)
discard tryRemoveFile("<string>.pbc") else:
serialized = serializer.loadBytes(serializer.dumpBytes(compiled, f))
when debugSerializer: when debugSerializer:
styledEcho fgCyan, "Serialization step: " 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 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" styledEcho fgGreen, "OK"
else: else:
styledEcho fgRed, "Corrupted" styledEcho fgRed, "Corrupted"
vm.run(serialized.chunk) vm.run(serialized.chunk, breakpoints)
except LexingError: except LexingError:
var exc = LexingError(getCurrentException()) var exc = LexingError(getCurrentException())
let relPos = exc.lexer.getRelPos(exc.line) let relPos = exc.lexer.getRelPos(exc.line)
@ -326,6 +327,8 @@ when isMainModule:
var file: string = "" var file: string = ""
var fromString: bool = false var fromString: bool = false
var interactive: bool = false var interactive: bool = false
var dump: bool = true
var breaks: seq[uint64] = @[]
for kind, key, value in optParser.getopt(): for kind, key, value in optParser.getopt():
case kind: case kind:
of cmdArgument: of cmdArgument:
@ -343,6 +346,18 @@ when isMainModule:
fromString = true fromString = true
of "interactive": of "interactive":
interactive = true 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: else:
echo &"error: unkown option '{key}'" echo &"error: unkown option '{key}'"
quit() quit()
@ -359,6 +374,18 @@ when isMainModule:
fromString = true fromString = true
of "i": of "i":
interactive = true 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: else:
echo &"error: unkown option '{key}'" echo &"error: unkown option '{key}'"
quit() quit()
@ -369,7 +396,7 @@ when isMainModule:
if file == "": if file == "":
repl() repl()
else: else:
runFile(file, interactive, fromString) runFile(file, interactive, fromString, dump, breaks)

View File

@ -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 operator `=`*[T: all](a: var T, b: T) { # TODO: This is just a placeholder right now
#pragma[magic: "GenericAssign"] #pragma[magic: "GenericAssign"]

View File

@ -11,6 +11,6 @@ fn fib(n: int): int {
print("Computing the value of fib(37)"); print("Computing the value of fib(37)");
var x = clock(); var x = clock();
print(fib(37)); print(fib(33));
print(clock() - x); print(clock() - x);
print("Done!"); print("Done!");

View File

@ -6,6 +6,6 @@ fn sum[T: int | int32](a, b: T): T {
} }
print(sum(1, 2)); print(sum(1, 2)); # Prints 3
print(sum(1'i32, 2'i32)); print(sum(1'i32, 2'i32)); # Also prints 3!
# print(sum(1'i16, 2'i16)); # Will not work if uncommented! # print(sum(1'i16, 2'i16)); # Will not work if uncommented!