Fixed an issue with jump offsets and with functions return types

This commit is contained in:
Mattia Giambirtone 2022-05-16 19:23:38 +02:00
parent e823a459c8
commit 620c56237a
5 changed files with 48 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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())