From d24133304735c4e85e0cd31dabe41cde5d03131b Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Mon, 13 Jun 2022 17:28:05 +0200 Subject: [PATCH] Initial work on closures (again?) --- src/backend/vm.nim | 6 ++++-- src/config.nim | 2 +- src/frontend/compiler.nim | 23 +++++++++++++++++------ src/frontend/meta/bytecode.nim | 8 ++++---- tests/closures.pn | 13 +++++++------ 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 08156bd..b28b141 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -416,9 +416,11 @@ proc dispatch*(self: PeonVM) = # variable let idx = self.readLong().int if idx > self.closedOver.high(): - self.closedOver.add(self.pop()) + # Note: we *peek* the stack, but we + # don't pop! + self.closedOver.add(self.peek()) else: - self.closedOver[idx] = self.pop() + self.closedOver[idx] = self.peek() of LoadClosure: # Loads a closed-over variable onto the # stack diff --git a/src/config.nim b/src/config.nim index 218807b..12c2291 100644 --- a/src/config.nim +++ b/src/config.nim @@ -27,7 +27,7 @@ when len(PEON_COMMIT_HASH) != 40: const PEON_BRANCH* = "master" when len(PEON_BRANCH) > 255: {.fatal: "The git branch name's length must be less than or equal to 255 characters".} -const DEBUG_TRACE_VM* = true # Traces VM execution +const DEBUG_TRACE_VM* = false # 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 568b4cc..2fff82f 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -1499,8 +1499,11 @@ proc varDecl(self: Compiler, node: VarDecl) = let expected = self.inferType(node.valueType) let actual = self.inferType(node.value) if expected.isNil() and actual.isNil(): - if node.value.kind == identExpr: - self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'") + if node.value.kind == identExpr or node.value.kind == callExpr and CallExpr(node.value).callee.kind == identExpr: + var name = node.value.token.lexeme + if node.value.kind == callExpr: + name = CallExpr(node.value).callee.token.lexeme + self.error(&"reference to undeclared identifier '{name}'") self.error(&"'{node.name.token.lexeme}' has no type") elif not expected.isNil() and expected.kind == Mutable: # I mean, variables *are* already mutable (some of them anyway) self.error(&"invalid type '{self.typeToStr(expected)}' for var") @@ -1510,35 +1513,42 @@ proc varDecl(self: Compiler, node: VarDecl) = self.expression(node.value) self.declareName(node, mutable=node.token.kind == Var) self.emitByte(StoreVar) - self.emitBytes((self.names.high() + 1).toTriple()) + self.emitBytes(self.names.len().toTriple()) proc typeDecl(self: Compiler, node: TypeDecl) = ## Compiles type declarations - + # TODO proc funDecl(self: Compiler, node: FunDecl) = ## Compiles function declarations - # A function's code is just compiled linearly - # and then jumped over + var function = self.currentFunction self.declareName(node) self.frames.add(self.names.high()) let fn = self.names[^(node.arguments.len() + 1)] + # A function's code is just compiled linearly + # and then jumped over let jmp = self.emitJump(JumpForwards) + # Function's code starts after the jump fn.codePos = self.chunk.code.len() for argument in node.arguments: + # Pops off the operand stack onto the + # call stack self.emitByte(LoadArgument) if not node.returnType.isNil() and self.inferType(node.returnType).isNil(): + # Are we returning a generic type? var isGeneric = false if node.returnType.kind == identExpr: let name = IdentExpr(node.returnType) for g in node.generics: if name == g.name: + # Yep! isGeneric = true break if not isGeneric: + # Nope self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'") # TODO: Forward declarations if not node.body.isNil(): @@ -1589,6 +1599,7 @@ proc funDecl(self: Compiler, node: FunDecl) = self.endFunctionBeforeReturn() hasVal = hasVal and not typ.returnType.isNil() self.endScope(deleteNames=true, fromFunc=true) + # Terminates the function's context self.emitByte(OpCode.Return) if hasVal: self.emitByte(1) diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index d4601fa..043ffbf 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -125,10 +125,10 @@ type ## Misc Assert, # Raises an AssertionFailed exception if x is false NoOp, # Just a no-op - LoadArgument, - LoadFunctionObj, - PopC, - PushC + LoadArgument, # The caller uses this to pop off the argument from the operand stack onto the call stack + LoadFunctionObj, # Creates and pushes a function object onto the stack with ip X + PopC, # Pop off the call stack onto the operand stack + PushC # Pop off the operand stack onto the call stack # We group instructions by their operation/operand types for easier handling when debugging diff --git a/tests/closures.pn b/tests/closures.pn index 9059841..a707252 100644 --- a/tests/closures.pn +++ b/tests/closures.pn @@ -1,9 +1,10 @@ -fn outer { - var x = 5; - fn inner { - var y = x; +fn makeClosure(n: int): fn: int { + var x = n; + fn inner: int { + return x; } - inner(); + return inner; } -outer(); + +makeClosure(1)();