diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 84d3ad1..3dd0dd7 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -267,10 +267,11 @@ proc patchJump(self: Compiler, offset: int) = ## offset and changes the bytecode instruction if possible ## (i.e. jump is in 16 bit range), but the converse is also ## true (i.e. it might change a regular jump into a long one) - let jump: int = self.chunk.code.len() - offset + var jump: int = self.chunk.code.len() - offset if jump > 16777215: self.error("cannot jump more than 16777216 bytecode instructions") if jump < uint16.high().int: + jump -= 3 case OpCode(self.chunk.code[offset]): of LongJumpForwards: self.chunk.code[offset] = JumpForwards.uint8() @@ -289,6 +290,7 @@ proc patchJump(self: Compiler, offset: int) = self.chunk.code[offset + 1] = offsetArray[0] self.chunk.code[offset + 2] = offsetArray[1] else: + jump -= 4 case OpCode(self.chunk.code[offset]): of JumpForwards: self.chunk.code[offset] = LongJumpForwards.uint8() @@ -457,6 +459,8 @@ proc toIntrinsic(name: string): Type = proc inferType(self: Compiler, node: LiteralExpr): Type = ## Infers the type of a given literal expression + if node == nil: + return nil case node.kind: of intExpr, binExpr, octExpr, hexExpr: let size = node.token.lexeme.split("'") @@ -514,6 +518,8 @@ proc toIntrinsic(self: Compiler, typ: Expression): Type = proc inferType(self: Compiler, node: Expression): Type = ## Infers the type of a given expression and ## returns it + if node == nil: + return nil case node.kind: of identExpr: let node = IdentExpr(node) @@ -576,6 +582,8 @@ proc typeToStr(self: Compiler, typ: Type): string = proc inferType(self: Compiler, node: Declaration): Type = ## Infers the type of a given declaration ## and returns it + if node == nil: + return nil case node.kind: of funDecl: var node = FunDecl(node) @@ -1043,14 +1051,18 @@ proc returnStmt(self: Compiler, node: ReturnStmt) = ## implicitly returns nil let returnType = self.inferType(node.value) let typ = self.inferType(self.currentFunction) - if returnType == nil and self.currentFunction.returnType != nil: - self.error(&"expected return value of type '{self.currentFunction.returnType.token.lexeme}', but expression has no type") - elif self.currentFunction.returnType == nil and node.value.kind != nilExpr: - self.error("non-nil return value is not allowed in functions without an explicit return type") + ## Having the return type + if typ.returnType == nil and returnType != nil: + self.error("non-empty return statement is not allowed in functions without an explicit return type") + elif returnType == nil and typ.returnType != nil: + self.error(&"expected return value of type '{self.typeToStr(typ.returnType)}', but expression has no type") elif not self.compareTypes(returnType, typ.returnType): self.error(&"expected return value of type '{self.typeToStr(typ.returnType)}', got '{self.typeToStr(returnType)}' instead") - self.expression(node.value) - self.emitByte(OpCode.Return) + if node.value != nil: + self.expression(node.value) + self.emitByte(OpCode.ReturnPop) + else: + self.emitByte(OpCode.Return) proc yieldStmt(self: Compiler, node: YieldStmt) = @@ -1171,6 +1183,8 @@ proc funDecl(self: Compiler, node: FunDecl) = ## Compiles function declarations self.declareName(node) if node.body != nil: + if BlockStmt(node.body).code.len() == 0: + self.error("Cannot declare function with empty body") let fnType = self.inferType(node) let impl = self.findByType(node.name.token.lexeme, fnType) if impl.len() > 1: @@ -1178,9 +1192,9 @@ proc funDecl(self: Compiler, node: FunDecl) = # the same function! Error! var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n" for fn in reversed(impl): - var node = Declaration(fn.valueType.node) + var node = FunDecl(fn.valueType.node) discard self.typeToStr(fn.valueType) - msg &= &"- '{node.name.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n" + msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n" self.error(msg) # We store the current function var function = self.currentFunction diff --git a/src/frontend/meta/ast.nim b/src/frontend/meta/ast.nim index edae12e..3bee68a 100644 --- a/src/frontend/meta/ast.nim +++ b/src/frontend/meta/ast.nim @@ -96,7 +96,7 @@ type # work properly Declaration* = ref object of ASTNode ## A declaration - pragmas*: seq[Token] + pragmas*: seq[Pragma] Statement* = ref object of Declaration ## A statement Expression* = ref object of Statement @@ -252,6 +252,9 @@ type isPrivate*: bool isPure*: bool returnType*: Expression + Pragma* = ref object of Expression + name*: IdentExpr + args*: seq[LiteralExpr] proc isConst*(self: ASTNode): bool = @@ -524,7 +527,7 @@ proc newIfStmt*(condition: Expression, thenBranch, elseBranch: Statement, proc newVarDecl*(name: IdentExpr, value: Expression, isConst: bool = false, isPrivate: bool = true, token: Token, isLet: bool = false, - valueType: Expression, pragmas: seq[Token]): VarDecl = + valueType: Expression, pragmas: seq[Pragma]): VarDecl = result = VarDecl(kind: varDecl) result.name = name result.value = value @@ -538,7 +541,7 @@ proc newVarDecl*(name: IdentExpr, value: Expression, isConst: bool = false, proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression, mutable: bool, isRef: bool, isPtr: bool]], defaults: seq[Expression], body: Statement, isAsync, isGenerator: bool, - isPrivate: bool, token: Token, pragmas: seq[Token], + isPrivate: bool, token: Token, pragmas: seq[Pragma], returnType: Expression): FunDecl = result = FunDecl(kind: funDecl) result.name = name diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index 72e9b46..bbffdd9 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -111,8 +111,9 @@ type LongJumpForwards, LongJumpBackwards, ## Functions - Call, # Calls a function - Return # Returns from the current function + Call, # Calls a function and initiates a new stack frame + Return, # Terminates the current function without popping off the stack + ReturnPop, # Pops a return value off the stack and terminates the current function ## Exception handling Raise, # Raises exception x or re-raises active exception if x is nil BeginTry, # Initiates an exception handling context @@ -135,7 +136,8 @@ const simpleInstructions* = {OpCode.Return, LoadNil, Pop, OpCode.Raise, BeginTry, FinishTry, OpCode.Yield, OpCode.Await, - OpCode.NoOp} + OpCode.NoOp, OpCode.Return, + OpCode.ReturnPop} # Constant instructions are instructions that operate on the bytecode constant table const constantInstructions* = {LoadInt64, LoadUInt64, diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 9cb773e..f0093ee 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -568,10 +568,9 @@ proc returnStmt(self: Parser): Statement = let tok = self.peek(-1) if self.currentFunction == nil: self.error("'return' cannot be used outside functions") - var value: Expression = newNilExpr(Token(lexeme: "nil")) + var value: Expression if not self.check(Semicolon): # Since return can be used on its own too - # (in which case it implicitly returns nil), # we need to check if there's an actual value # to return or not value = self.expression() diff --git a/src/test.nim b/src/test.nim index f862739..c5b564c 100644 --- a/src/test.nim +++ b/src/test.nim @@ -25,9 +25,9 @@ proc getLineEditor: LineEditor # Handy dandy compile-time constants const debugLexer = false const debugParser = false -const debugCompiler = false +const debugCompiler = true const debugSerializer = false - +const debugRuntime = false when debugSerializer: import nimSHA2 @@ -119,30 +119,31 @@ when isMainModule: if i < len(serialized.chunk.code) - 1: stdout.write(", ") stdout.write(&"] (matches: {serialized.chunk.code == compiled.code})\n") - echo "Execution step: " + when debugRuntime: + echo "Execution step: " vm.run(serialized.chunk) except IOError: break # TODO: The code for error reporting completely # breaks down with multiline input, fix it except LexingError: - let lineNo = tokenizer.getLine() - let relPos = tokenizer.getRelPos(lineNo) - let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() + # let lineNo = tokenizer.getLine() + # let relPos = tokenizer.getRelPos(lineNo) + # let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() echo getCurrentExceptionMsg() # echo &"Source line: {line}" # echo " ".repeat(relPos.start + len("Source line: ")) & "^".repeat(relPos.stop - relPos.start) except ParseError: - let lineNo = parser.getCurrentToken().line - let relPos = tokenizer.getRelPos(lineNo) - let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() + # let lineNo = parser.getCurrentToken().line + # let relPos = tokenizer.getRelPos(lineNo) + # let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() echo getCurrentExceptionMsg() # echo &"Source line: {line}" # echo " ".repeat(relPos.start + len("Source line: ")) & "^".repeat(relPos.stop - parser.getCurrentToken().lexeme.len()) except CompileError: - let lineNo = compiler.getCurrentNode().token.line - let relPos = tokenizer.getRelPos(lineNo) - let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() + # let lineNo = compiler.getCurrentNode().token.line + # let relPos = tokenizer.getRelPos(lineNo) + # let line = tokenizer.getSource().splitLines()[lineNo - 1].strip() echo getCurrentExceptionMsg() # echo &"Source line: {line}" # echo " ".repeat(relPos.start + len("Source line: ")) & "^".repeat(relPos.stop - compiler.getCurrentNode().token.lexeme.len())