From 02f1f8a54d98b1bd68213b00f88693d40250ba11 Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Mon, 13 Jun 2022 15:04:53 +0200 Subject: [PATCH] Various style fixes with nil checks, added PushC opcode, added support for calling function objects --- src/backend/vm.nim | 7 ++ src/config.nim | 2 +- src/frontend/compiler.nim | 118 +++++++++++++++++++-------------- src/frontend/meta/bytecode.nim | 6 +- src/main.nim | 2 +- 5 files changed, 82 insertions(+), 53 deletions(-) diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 69f30e8..08156bd 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -357,6 +357,8 @@ proc dispatch*(self: PeonVM) = self.push(self.constReadFloat64(int(self.readLong()))) of LoadFunction: self.pushc(PeonObject(kind: Function, ip: self.readLong())) + of LoadFunctionObj: + self.push(PeonObject(kind: Function, ip: self.readLong())) of LoadReturnAddress: self.pushc(PeonObject(kind: UInt32, uInt: self.readUInt())) of Call: @@ -433,7 +435,12 @@ proc dispatch*(self: PeonVM) = discard self.popc() of Pop: discard self.pop() + of PushC: + self.pushc(self.pop()) of PopRepl: + if self.frames.len() > 1: + discard self.pop() + continue let popped = self.pop() case popped.kind: of Int64: 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 e40f8c3..ae8d41e 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -401,7 +401,7 @@ proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDe ## each time a name is referenced in order for closed-over variables ## to be emitted properly, otherwise the runtime may behave ## unpredictably or crash - if name == nil or name.depth == 0: + if name.isNil() or name.depth == 0: return elif name.depth < depth and not name.isClosedOver: # Ding! The given name is closed over: we need to @@ -426,10 +426,10 @@ proc compareTypes(self: Compiler, a, b: Type): bool = # The nil code here is for void functions (when # we compare their return types) - if a == nil: - return b == nil or b.kind == Any - elif b == nil: - return a == nil or a.kind == Any + if a.isNil(): + return b.isNil() or b.kind == Any + elif b.isNil(): + return a.isNil() or a.kind == Any elif a.kind == Any or b.kind == Any: # This is needed internally: user code # cannot generate code for matching @@ -517,7 +517,7 @@ proc toIntrinsic(name: string): Type = proc inferType(self: Compiler, node: LiteralExpr): Type = ## Infers the type of a given literal expression - if node == nil: + if node.isNil(): return nil case node.kind: of intExpr, binExpr, octExpr, hexExpr: @@ -559,13 +559,13 @@ proc inferType(self: Compiler, node: LiteralExpr): Type = proc inferType(self: Compiler, node: Expression): Type = ## Infers the type of a given expression and ## returns it - if node == nil: + if node.isNil(): return nil case node.kind: of identExpr: let node = IdentExpr(node) let name = self.resolve(node) - if name != nil: + if not name.isNil(): return name.valueType else: result = node.name.lexeme.toIntrinsic() @@ -586,7 +586,7 @@ proc inferType(self: Compiler, node: Expression): Type = of lambdaExpr: var node = LambdaExpr(node) result = Type(kind: Function, returnType: nil, args: @[], isLambda: true) - if node.returnType != nil: + if not node.returnType.isNil(): result.returnType = self.inferType(node.returnType) for argument in node.arguments: result.args.add((argument.name.token.lexeme, self.inferType(argument.valueType))) @@ -595,9 +595,9 @@ proc inferType(self: Compiler, node: Expression): Type = case node.callee.kind: of identExpr: let resolved = self.resolve(IdentExpr(node.callee)) - if resolved != nil: + if not resolved.isNil(): result = resolved.valueType.returnType - if result == nil: + if result.isNil(): result = Type(kind: Any) else: result = nil @@ -612,18 +612,18 @@ proc inferType(self: Compiler, node: Expression): Type = proc inferType(self: Compiler, node: Declaration): Type = ## Infers the type of a given declaration ## and returns it - if node == nil: + if node.isNil(): return nil case node.kind: of funDecl: var node = FunDecl(node) let resolved = self.resolve(node.name) - if resolved != nil: + if not resolved.isNil(): return resolved.valueType of NodeKind.varDecl: var node = VarDecl(node) let resolved = self.resolve(node.name) - if resolved != nil: + if not resolved.isNil(): return resolved.valueType else: return self.inferType(node.value) @@ -653,7 +653,7 @@ proc typeToStr(self: Compiler, typ: Type): string = if i < typ.args.len() - 1: result &= ", " result &= ")" - if typ.returnType != nil: + if not typ.returnType.isNil(): result &= &": {self.typeToStr(typ.returnType)}" else: discard @@ -824,6 +824,28 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) = self.patchReturnAddress(pos) +proc generateObjCall(self: Compiler, args: seq[Expression]) = + ## Small wrapper that abstracts emitting a call instruction + ## for a given function already loaded on the operand stack + self.emitByte(PushC) # Pops the function off the operand stack onto the call stack + self.emitByte(LoadReturnAddress) + let pos = self.chunk.code.len() + self.emitBytes(0.toQuad()) + for argument in args: + self.expression(argument) + self.emitByte(Call) # Creates a new call frame + var size = 2 # We start at 2 because each call frame + # contains at least 2 elements (function + # object and return address) + for name in reversed(self.names): + # Then, for each local variable + # we increase the frame size by 1 + if name.depth == self.scopeDepth: + inc(size) + self.emitBytes(size.toTriple()) + self.patchReturnAddress(pos) + + proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) = ## Emits the code to call a unary operator self.generateCall(fn, @[op.a]) @@ -971,13 +993,13 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = elif argument.isPtr: name.valueType = Type(kind: Pointer, value: name.valueType) # We check if the argument's type is a generic - if name.valueType == nil and argument.valueType.kind == identExpr: + if name.valueType.isNil() and argument.valueType.kind == identExpr: for gen in node.generics: if gen.name == IdentExpr(argument.valueType): name.valueType = Type(kind: Generic) break # If it's still nil, it's an error! - if name.valueType == nil: + if name.valueType.isNil(): self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'") fn.valueType.args.add((argument.name.token.lexeme, name.valueType)) else: @@ -987,18 +1009,18 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = proc identifier(self: Compiler, node: IdentExpr) = ## Compiles access to identifiers let s = self.resolve(node) - if s == nil: + if s.isNil(): self.error(&"reference to undeclared name '{node.token.lexeme}'") elif s.isConst: # Constants are always emitted as Load* instructions # no matter the scope depth self.emitConstant(node, self.inferType(node)) else: - if s.valueType.kind == Function: - self.emitByte(LoadFunction) - self.emitBytes(s.codePos.toTriple()) self.detectClosureVariable(s) - if not s.isClosedOver: + if s.valueType.kind == Function: + self.emitByte(LoadFunctionObj) + self.emitBytes(s.codePos.toTriple()) + elif not s.isClosedOver: # Static name resolution, loads value at index in the stack. Very fast. Much wow. self.emitByte(LoadVar) # No need to check for -1 here: we already did a nil-check above! @@ -1009,8 +1031,7 @@ proc identifier(self: Compiler, node: IdentExpr) = # not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway) self.emitByte(LoadClosure) self.emitBytes(self.closedOver.high().toTriple()) - if s.valueType.kind == Function: - self.emitByte(PopC) + proc assignment(self: Compiler, node: ASTNode) = @@ -1020,7 +1041,7 @@ proc assignment(self: Compiler, node: ASTNode) = let node = AssignExpr(node) let name = IdentExpr(node.name) let r = self.resolve(name) - if r == nil: + if r.isNil(): self.error(&"assignment to undeclared name '{name.token.lexeme}'") elif r.isConst: self.error(&"cannot assign to '{name.token.lexeme}' (constant)") @@ -1040,7 +1061,7 @@ proc assignment(self: Compiler, node: ASTNode) = of setItemExpr: let node = SetItemExpr(node) let typ = self.inferType(node) - if typ == nil: + if typ.isNil(): self.error(&"cannot determine the type of '{node.name.token.lexeme}'") # TODO else: @@ -1117,7 +1138,7 @@ proc ifStmt(self: Compiler, node: IfStmt) = ## execution of code var cond = self.inferType(node.condition) if not self.compareTypes(cond, Type(kind: Bool)): - if cond == nil: + if cond.isNil(): if node.condition.kind == identExpr: self.error(&"reference to undeclared identifier '{IdentExpr(node.condition).name.lexeme}'") elif node.condition.kind == callExpr and CallExpr(node.condition).callee.kind == identExpr: @@ -1138,7 +1159,7 @@ proc ifStmt(self: Compiler, node: IfStmt) = self.statement(node.thenBranch) let jump2 = self.emitJump(JumpForwards) self.patchJump(jump) - if node.elseBranch != nil: + if not node.elseBranch.isNil(): self.statement(node.elseBranch) self.patchJump(jump2) @@ -1203,7 +1224,7 @@ proc checkCallIsPure(self: Compiler, node: ASTnode): bool = return false -proc callExpr(self: Compiler, node: CallExpr): Name = +proc callExpr(self: Compiler, node: CallExpr) = ## Compiles code to call a function var args: seq[tuple[name: string, kind: Type]] = @[] var argExpr: seq[Expression] = @[] @@ -1211,7 +1232,7 @@ proc callExpr(self: Compiler, node: CallExpr): Name = # TODO: Keyword arguments for i, argument in node.arguments.positionals: kind = self.inferType(argument) - if kind == nil: + if kind.isNil(): if argument.kind == identExpr: self.error(&"reference to undeclared identifier '{IdentExpr(argument).name.lexeme}'") self.error(&"cannot infer the type of argument {i + 1} in function call") @@ -1228,26 +1249,26 @@ proc callExpr(self: Compiler, node: CallExpr): Name = of NodeKind.callExpr: var node = node.callee while node.kind == callExpr: - funct = self.callExpr(CallExpr(node)) + self.callExpr(CallExpr(node)) node = CallExpr(node).callee - - # funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args)) else: - discard # TODO: Lambdas - self.generateCall(funct, argExpr) + discard # TODO: Calling expressions + if not funct.isNil(): + self.generateCall(funct, argExpr) + else: + self.generateObjCall(argExpr) if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee): if not self.currentFunction.name.isNil(): self.error(&"cannot make sure that calls to '{self.currentFunction.name.token.lexeme}' are side-effect free") else: self.error(&"cannot make sure that call is side-effect free") - result = funct proc expression(self: Compiler, node: Expression) = ## Compiles all expressions case node.kind: of NodeKind.callExpr: - discard self.callExpr(CallExpr(node)) # TODO + self.callExpr(CallExpr(node)) # TODO of getItemExpr: discard # TODO: Get rid of this of pragmaExpr: @@ -1425,13 +1446,12 @@ proc statement(self: Compiler, node: Statement) = of exprStmt: var expression = ExprStmt(node).expression self.expression(expression) - if expression.kind == callExpr and self.inferType(CallExpr(expression).callee).returnType == nil: + if expression.kind == callExpr and self.inferType(CallExpr(expression).callee).returnType.isNil(): # The expression has no type, so we don't have to # pop anything discard else: - # We only print top-level expressions - if self.replMode and self.scopeDepth == 0: + if self.replMode: self.emitByte(PopRepl) else: self.emitByte(Pop) @@ -1478,19 +1498,19 @@ proc varDecl(self: Compiler, node: VarDecl) = ## Compiles variable declarations let expected = self.inferType(node.valueType) let actual = self.inferType(node.value) - if expected == nil and actual == nil: + if expected.isNil() and actual.isNil(): if node.value.kind == identExpr: self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'") self.error(&"'{node.name.token.lexeme}' has no type") - elif expected != nil and expected.kind == Mutable: # I mean, variables *are* already mutable (some of them anyway) + 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") elif not self.compareTypes(expected, actual): - if expected != nil: + if not expected.isNil(): self.error(&"expected value of type '{self.typeToStr(expected)}', but '{node.name.token.lexeme}' is of type '{self.typeToStr(actual)}'") self.expression(node.value) self.declareName(node, mutable=node.token.kind == Var) self.emitByte(StoreVar) - self.emitBytes(self.names.high().toTriple()) + self.emitBytes((self.names.high() + 1).toTriple()) proc typeDecl(self: Compiler, node: TypeDecl) = @@ -1521,7 +1541,7 @@ proc funDecl(self: Compiler, node: FunDecl) = if not isGeneric: self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'") # TODO: Forward declarations - if node.body != nil: + if not node.body.isNil(): if BlockStmt(node.body).code.len() == 0: self.error("cannot declare function with empty body") let fnType = self.inferType(node) @@ -1562,12 +1582,12 @@ proc funDecl(self: Compiler, node: FunDecl) = hasVal = LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn else: discard # Unreachable - if hasVal and self.currentFunction.returnType == nil and typ.returnType != nil: + if hasVal and self.currentFunction.returnType.isNil() and not typ.returnType.isNil(): self.error("non-empty return statement is not allowed in void functions") - elif not hasVal and self.currentFunction.returnType != nil: + elif not hasVal and not self.currentFunction.returnType.isNil(): self.error("function has an explicit return type, but no return statement was found") self.endFunctionBeforeReturn() - hasVal = hasVal and typ.returnType != nil + hasVal = hasVal and not typ.returnType.isNil() self.endScope(deleteNames=true, fromFunc=true) self.emitByte(OpCode.Return) if hasVal: @@ -1579,7 +1599,7 @@ proc funDecl(self: Compiler, node: FunDecl) = self.chunk.cfi.add(self.chunk.code.high().toTriple()) self.chunk.cfi.add(self.frames[^1].toTriple()) self.chunk.cfi.add(uint8(node.arguments.len())) - if not system.`==`(node.name, nil): + if not node.name.isNil(): self.chunk.cfi.add(node.name.token.lexeme.len().toDouble()) var s = node.name.token.lexeme if node.name.token.lexeme.len() >= uint16.high().int: diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index b35fa61..d5dcacd 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -126,7 +126,9 @@ type Assert, # Raises an AssertionFailed exception if x is false NoOp, # Just a no-op LoadArgument, - PopC + LoadFunctionObj, + PopC, + PushC # We group instructions by their operation/operand types for easier handling when debugging @@ -139,7 +141,7 @@ const simpleInstructions* = {Return, LoadNil, BeginTry, FinishTry, Yield, Await, NoOp, PopClosure, SetResult, LoadArgument, - PopC} + PopC, LoadFunctionObj, PushC} # Constant instructions are instructions that operate on the bytecode constant table const constantInstructions* = {LoadInt64, LoadUInt64, diff --git a/src/main.nim b/src/main.nim index fad3aad..3de6480 100644 --- a/src/main.nim +++ b/src/main.nim @@ -76,7 +76,7 @@ proc repl(vm: PeonVM = newPeonVM()) = elif current == "#clear": stdout.write("\x1Bc") continue - input &= &"\n{current}" + input &= &"\n{current}\n" tokens = tokenizer.lex(input, "stdin") if tokens.len() == 0: continue