diff --git a/src/backend/vm.nim b/src/backend/vm.nim index e8de298..d20ca42 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -21,6 +21,8 @@ import ../frontend/meta/bytecode import ../util/multibyte +export types + type PeonVM* = ref object ## The Peon Virtual Machine @@ -268,7 +270,7 @@ proc dispatch*(self: PeonVM) = of Pop: self.lastPop = self.pop() of PopN: - for _ in 0.. 255: {.fatal: "The git branch name's length must be less than or equal to 255 characters".} -const DEBUG_TRACE_VM* = false # Traces VM execution +const DEBUG_TRACE_VM* = true # Traces VM execution const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO) const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation const DEBUG_TRACE_COMPILER* = false # Traces the compiler diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 0a415cd..27116f4 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -403,7 +403,7 @@ proc detectClosureVariable(self: Compiler, name: Name, ## unpredictably or crash if name == nil: return - if name.depth < depth: + if name.depth > 0 and name.depth < depth: # Ding! The given name is closed over: we need to # change the NoOp instructions that self.declareName # put in place for us into a StoreHeap. We don't need to change @@ -595,7 +595,11 @@ proc inferType(self: Compiler, node: Expression): Type = var node = CallExpr(node) case node.callee.kind: of identExpr: - result = self.resolve(IdentExpr(node.callee)).valueType.returnType + let resolved = self.resolve(IdentExpr(node.callee)) + if resolved != nil: + result = resolved.valueType.returnType + else: + result = nil of lambdaExpr: result = self.inferType(LambdaExpr(node.callee).returnType) else: @@ -962,7 +966,9 @@ proc identifier(self: Compiler, node: IdentExpr) = if not t.closedOver: # Static name resolution, loads value at index in the stack. Very fast. Much wow. self.emitByte(LoadVar) - self.emitBytes((index - self.frames[self.scopeDepth]).toTriple()) + if self.frames.len() > 1: + inc(index, 2) + self.emitBytes((index - self.frames[^1]).toTriple()) else: # Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics. # This makes closures work as expected and is not much slower than indexing our stack (since they're both @@ -1019,7 +1025,7 @@ proc endScope(self: Compiler) = var names: seq[Name] = @[] for name in self.names: if name.depth > self.scopeDepth: - if name.valueType.kind != Function and OpCode(self.chunk.code[name.codePos]) == NoOp: + if name.valueType.kind != Function and OpCode(self.chunk.code[name.codePos]) == NoOp: for _ in countup(0, 3): # Since by deleting it the size of the # sequence decreases, we don't need to @@ -1369,10 +1375,11 @@ proc funDecl(self: Compiler, node: FunDecl) = ## Compiles function declarations # A function's code is just compiled linearly # and then jumped over + self.emitByte(LoadNil) # Aligns the stack let jmp = self.emitJump(JumpForwards) var function = self.currentFunction self.declareName(node) - self.frames.add(self.names.high()) + self.frames.add(self.names.len()) # TODO: Forward declarations if node.body != nil: if BlockStmt(node.body).code.len() == 0: @@ -1384,7 +1391,7 @@ proc funDecl(self: Compiler, node: FunDecl) = # the same function with the same name! Error! var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n" for fn in reversed(impl): - msg &= &"- '{fn.name}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n" + msg &= &"- '{fn.name.token.lexeme}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n" self.error(msg) # We store the current function self.currentFunction = node @@ -1490,8 +1497,9 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk = self.declaration(Declaration(self.step())) if self.ast.len() > 0: # *Technically* an empty program is a valid program + self.endScope() self.emitByte(OpCode.Return) # Exits the VM's main loop when used at the global scope result = self.chunk - if self.ast.len() > 0 and self.scopeDepth != 0: - self.error(&"invalid state: invalid scopeDepth value (expected 0, got {self.scopeDepth}), did you forget to call endScope/beginScope?") - self.endScope() + if self.ast.len() > 0 and self.scopeDepth != -1: + self.error(&"invalid state: invalid scopeDepth value (expected -1, got {self.scopeDepth}), did you forget to call endScope/beginScope?") + echo self.chunk.code \ No newline at end of file diff --git a/src/main.nim b/src/main.nim index 33e264b..e72b4ce 100644 --- a/src/main.nim +++ b/src/main.nim @@ -53,20 +53,33 @@ proc repl = vm = newPeonVM() editor = getLineEditor() input: string + current: string tokenizer.fillSymbolTable() editor.bindEvent(jeQuit): stdout.styledWriteLine(fgGreen, "Goodbye!") editor.prompt = "" keep = false + input = "" editor.bindKey("ctrl+a"): editor.content.home() editor.bindKey("ctrl+e"): editor.content.`end`() while keep: try: - input = editor.read() - if input.len() == 0: + # We incrementally add content to the input + # so that you can, for example, define a function + # then press enter and use it at the next iteration + # of the read loop + current = editor.read() + if current.len() == 0: continue + elif current == "#clearInput": + input = "" + continue + elif current == "#clear": + stdout.write("\x1Bc") + continue + input &= &"\n{current}" tokens = tokenizer.lex(input, "stdin") if tokens.len() == 0: continue @@ -123,8 +136,35 @@ proc repl = when debugRuntime: styledEcho fgCyan, "\n\nExecution step: " vm.run(serialized.chunk) - echo vm.lastPop + var popped = vm.lastPop + case popped.kind: + of Int64: + echo &"{popped.long}'i64" + of UInt64: + echo &"{popped.uLong}'u64" + of Int32: + echo &"{popped.`int`}'i32" + of UInt32: + echo &"{popped.uInt}'u32" + of Int16: + echo &"{popped.short}'i16" + of UInt16: + echo &"{popped.uShort}'u16" + of Int8: + echo &"{popped.tiny}'i8" + of UInt8: + echo &"{popped.uTiny}'u8" + of ObjectKind.Inf: + if popped.positive: + echo "inf" + else: + echo "-inf" + of ObjectKind.Nan, Nil: + echo ($popped.kind).toLowerAscii() + else: + discard except LexingError: + input = "" let exc = LexingError(getCurrentException()) let relPos = tokenizer.getRelPos(exc.line) let line = tokenizer.getSource().splitLines()[exc.line - 1].strip() @@ -134,6 +174,7 @@ proc repl = styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start) except ParseError: + input = "" let exc = ParseError(getCurrentException()) let lexeme = exc.token.lexeme let lineNo = exc.token.line @@ -149,6 +190,7 @@ proc repl = styledEcho fgBlue, "Source line: " , fgDefault, line styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start) except CompileError: + input = "" let exc = CompileError(getCurrentException()) let lexeme = exc.node.token.lexeme let lineNo = exc.node.token.line diff --git a/src/util/debugger.nim b/src/util/debugger.nim index d860dca..51cd058 100644 --- a/src/util/debugger.nim +++ b/src/util/debugger.nim @@ -63,7 +63,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) = proc checkFrameStart(self: Debugger, n: int) = - return + ## Todo: Checking frame end offsets needs + ## fixes for i, e in self.cfiData: if n == e.start and not (e.started or e.stopped): e.started = true @@ -131,7 +132,7 @@ proc callInstruction(self: Debugger, instruction: OpCode) = printInstruction(instruction) stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument") if args > 1: - stdout.styledWrite(fgYellow, "s") + stdout.styledWrite(fgGreen, "s") nl() self.current += 7