diff --git a/src/backend/vm.nim b/src/backend/vm.nim index be312d6..5167c65 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -35,7 +35,7 @@ import ../frontend/compiler/targets/bytecode/opcodes import ../frontend/compiler/targets/bytecode/util/multibyte -when debugVM or debugMem or debugGC: +when debugVM or debugMem or debugGC or debugAlloc: import std/strformat import std/sequtils import std/terminal diff --git a/src/frontend/compiler/targets/bytecode/target.nim b/src/frontend/compiler/targets/bytecode/target.nim index fffb03d..dd09b45 100644 --- a/src/frontend/compiler/targets/bytecode/target.nim +++ b/src/frontend/compiler/targets/bytecode/target.nim @@ -587,11 +587,11 @@ proc endScope(self: BytecodeCompiler) = # Automatic functions do not materialize # at runtime, so their arguments don't either continue - # This name has been generated internally by the - # compiler and is a copy of an already existing - # one, so we only need to pop its "real" counterpart - if not name.isReal: - continue + # This name has been generated internally by the + # compiler and is a copy of an already existing + # one, so we only need to pop its "real" counterpart + if not name.isReal: + continue inc(popCount) if not name.resolved: # We emit warnings for names that are declared but never used @@ -935,6 +935,45 @@ proc prepareAutoFunction(self: BytecodeCompiler, fn: Name, args: seq[tuple[name: return fn +proc prepareAutoLambda(self: BytecodeCompiler, fn: Type, args: seq[tuple[name: string, kind: Type, default: Expression]]): Type = + ## "Prepares" an automatic lambda function + ## by instantiating a concrete version of it + ## along with its arguments + let idx = self.stackIndex + self.stackIndex = 1 + var default: Expression + var node = LambdaExpr(fn.fun) + var fn = deepCopy(fn) + fn.isAuto = false + fn.compiled = false + # We now declare and typecheck the function's + # arguments + for (argument, val) in zip(node.arguments, args): + if self.names.high() > 16777215: + self.error("cannot declare more than 16777215 variables at a time") + inc(self.stackIndex) + self.names.add(Name(depth: self.depth + 1, + isPrivate: true, + owner: self.currentModule, + file: self.file, + isConst: false, + ident: argument.name, + valueType: val.kind, + codePos: 0, + isLet: false, + line: argument.name.token.line, + belongsTo: self.currentFunction, + kind: NameKind.Argument, + node: argument.name, + position: self.stackIndex, + isReal: true + )) + fn.args = args + fn.location = self.stackIndex + self.stackIndex = idx + return fn + + proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int) = ## Small wrapper that abstracts emitting a call instruction ## for a given function @@ -1274,7 +1313,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type var default: Expression var kind: Type for i, argument in node.arguments.positionals: - kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message + kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message later if kind.isNil(): if argument.kind == NodeKind.identExpr: self.error(&"reference to undefined name '{argument.token.lexeme}'", argument) @@ -1298,9 +1337,13 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type result = self.specialize(result, argExpr) if impl.valueType.isAuto: impl = self.prepareAutoFunction(impl, args) - result = impl.valueType + result = impl.valueType if not impl.valueType.compiled: - self.funDecl(FunDecl(result.fun), impl) + case result.fun.kind: + of NodeKind.lambdaExpr: + self.lambdaExpr(LambdaExpr(result.fun), compile=compile) + else: + self.funDecl(FunDecl(result.fun), impl) result = result.returnType if compile: if impl.valueType.fun.kind == funDecl and FunDecl(impl.valueType.fun).isTemplate: @@ -1312,7 +1355,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type self.declaration(decl) else: # The last expression in a template - # is its return type, so we compute + # is its "return value", so we compute # it, but don't pop it off the stack if decl.kind == exprStmt: self.expression(ExprStmt(decl).expression) @@ -1346,6 +1389,15 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type result = self.specialize(result, argExpr) result = result.returnType self.generateCall(impl, argExpr, node.token.line) + of NodeKind.lambdaExpr: + var node = LambdaExpr(node.callee) + var impl = self.lambdaExpr(node, compile=false) + if impl.isAuto: + impl = self.prepareAutoLambda(impl, args) + result = impl + result = result.returnType + if compile: + self.generateCall(impl, argExpr, node.token.line) # TODO: Calling lambdas on-the-fly (i.e. on the same line) else: let typ = self.infer(node) @@ -1388,9 +1440,27 @@ method getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = tr self.error("invalid syntax", node) +proc blockStmt(self: BytecodeCompiler, node: BlockStmt, compile: bool = true) = + ## Compiles block statements, which create + ## a new local scope + self.beginScope() + var last: Declaration + for decl in node.code: + if not last.isNil(): + case last.kind: + of breakStmt, continueStmt: + self.warning(UnreachableCode, &"code after '{last.token.lexeme}' statement is unreachable", nil, decl) + else: + discard + self.declaration(decl) + last = decl + self.endScope() + + method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true): Type {.discardable.} = ## Compiles lambda functions as expressions - result = Type(kind: Function, isLambda: true, fun: node, location: self.chunk.code.high(), compiled: true) + result = Type(kind: Function, isLambda: true, fun: node, location: 0) + let function = self.currentFunction self.beginScope() var constraints: seq[tuple[match: bool, kind: Type]] = @[] for gen in node.generics: @@ -1438,10 +1508,75 @@ method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true # The function needs a return type too! if not node.returnType.isNil(): result.returnType = self.inferOrError(node.returnType) - self.endScope() - if not compile: - return - # TODO + self.currentFunction = Name(depth: self.depth, + isPrivate: true, + isConst: false, + owner: self.currentModule, + file: self.file, + valueType: Type(kind: Function, + returnType: result.returnType, + args: result.args, + fun: node, + forwarded: false, + isAuto: false), + ident: nil, + node: node, + isLet: false, + line: node.token.line, + kind: NameKind.Function, + belongsTo: self.currentFunction, + isReal: true) + if compile: + result.compiled = true + let stackIdx = self.stackIndex + self.stackIndex = name.position + let jmp = self.emitJump(JumpForwards, node.token.line) + if BlockStmt(node.body).code.len() == 0: + self.error("cannot construct lambda with empty body") + var last: Declaration + self.beginScope() + result.location = self.chunk.code.len() + for decl in BlockStmt(node.body).code: + if not last.isNil(): + if last.kind == returnStmt: + self.warning(UnreachableCode, "code after 'return' statement is unreachable", nil, decl) + self.declaration(decl) + last = decl + let typ = self.currentFunction.valueType.returnType + var hasVal: bool = false + case self.currentFunction.valueType.fun.kind: + of NodeKind.funDecl: + hasVal = FunDecl(self.currentFunction.valueType.fun).hasExplicitReturn + of NodeKind.lambdaExpr: + hasVal = LambdaExpr(self.currentFunction.valueType.fun).hasExplicitReturn + else: + discard # Unreachable + if not hasVal and not typ.isNil(): + # There is no explicit return statement anywhere in the function's + # body: while this is not a tremendously useful piece of information + # (since the presence of at least one doesn't mean all control flow + # cases are covered), it definitely is an error worth reporting + self.error("function has an explicit return type, but no return statement was found", node) + hasVal = hasVal and not typ.isNil() + for jump in self.currentFunction.valueType.retJumps: + self.patchJump(jump) + self.endScope() + # Terminates the function's context + self.emitByte(OpCode.Return, self.peek().token.line) + if hasVal: + self.emitByte(1, self.peek().token.line) + else: + self.emitByte(0, self.peek().token.line) + # Well, we've compiled everything: time to patch + # the jump offset + self.patchJump(jmp) + # Restores the enclosing function (if any). + # Makes nested calls work (including recursion) + self.currentFunction = function + self.stackIndex = stackIdx + self.endScope() + self.emitByte(LoadUInt64, node.token.line) + self.emitBytes(self.chunk.writeConstant(result.location.toLong()), node.token.line) method expression(self: BytecodeCompiler, node: Expression, compile: bool = true): Type {.discardable.} = @@ -1484,23 +1619,6 @@ method expression(self: BytecodeCompiler, node: Expression, compile: bool = true self.error(&"invalid AST node of kind {node.kind} at expression(): {node} (This is an internal error and most likely a bug)") -proc blockStmt(self: BytecodeCompiler, node: BlockStmt, compile: bool = true) = - ## Compiles block statements, which create - ## a new local scope - self.beginScope() - var last: Declaration - for decl in node.code: - if not last.isNil(): - case last.kind: - of breakStmt, continueStmt: - self.warning(UnreachableCode, &"code after '{last.token.lexeme}' statement is unreachable", nil, decl) - else: - discard - self.declaration(decl) - last = decl - self.endScope() - - proc ifStmt(self: BytecodeCompiler, node: IfStmt) = ## Compiles if/else statements for conditional ## execution of code diff --git a/src/frontend/parsing/parser.nim b/src/frontend/parsing/parser.nim index d66f4b3..4c2750e 100644 --- a/src/frontend/parsing/parser.nim +++ b/src/frontend/parsing/parser.nim @@ -966,10 +966,7 @@ proc varDecl(self: Parser, isLet: bool = false, valueType = newIdentExpr(self.peek(-1), self.scopeDepth) if self.match("="): hasInit = true - if self.match([Function, Coroutine, Generator]): - value = self.parseFunExpr() - else: - value = self.expression() + value = self.expression() if isConst and not value.isConst(): self.error("constant initializer is not a constant") elif tok.kind != TokenType.Var: diff --git a/src/util/fmterr.nim b/src/util/fmterr.nim index 7205a39..1f2f2be 100644 --- a/src/util/fmterr.nim +++ b/src/util/fmterr.nim @@ -34,8 +34,12 @@ proc printError(file, line: string, lineNo: int, pos: tuple[start, stop: int], f stderr.styledWriteLine(styleBright, fgDefault, ": ", msg) if line.len() > 0: stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..