Various style fixes with nil checks, added PushC opcode, added support for calling function objects

This commit is contained in:
Mattia Giambirtone 2022-06-13 15:04:53 +02:00
parent 11b15abc01
commit 02f1f8a54d
5 changed files with 82 additions and 53 deletions

View File

@ -357,6 +357,8 @@ proc dispatch*(self: PeonVM) =
self.push(self.constReadFloat64(int(self.readLong()))) self.push(self.constReadFloat64(int(self.readLong())))
of LoadFunction: of LoadFunction:
self.pushc(PeonObject(kind: Function, ip: self.readLong())) self.pushc(PeonObject(kind: Function, ip: self.readLong()))
of LoadFunctionObj:
self.push(PeonObject(kind: Function, ip: self.readLong()))
of LoadReturnAddress: of LoadReturnAddress:
self.pushc(PeonObject(kind: UInt32, uInt: self.readUInt())) self.pushc(PeonObject(kind: UInt32, uInt: self.readUInt()))
of Call: of Call:
@ -433,7 +435,12 @@ proc dispatch*(self: PeonVM) =
discard self.popc() discard self.popc()
of Pop: of Pop:
discard self.pop() discard self.pop()
of PushC:
self.pushc(self.pop())
of PopRepl: of PopRepl:
if self.frames.len() > 1:
discard self.pop()
continue
let popped = self.pop() let popped = self.pop()
case popped.kind: case popped.kind:
of Int64: of Int64:

View File

@ -27,7 +27,7 @@ when len(PEON_COMMIT_HASH) != 40:
const PEON_BRANCH* = "master" const PEON_BRANCH* = "master"
when len(PEON_BRANCH) > 255: when len(PEON_BRANCH) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".} {.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_GC* = false # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = false # Traces the compiler const DEBUG_TRACE_COMPILER* = false # Traces the compiler

View File

@ -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 ## each time a name is referenced in order for closed-over variables
## to be emitted properly, otherwise the runtime may behave ## to be emitted properly, otherwise the runtime may behave
## unpredictably or crash ## unpredictably or crash
if name == nil or name.depth == 0: if name.isNil() or name.depth == 0:
return return
elif name.depth < depth and not name.isClosedOver: elif name.depth < depth and not name.isClosedOver:
# Ding! The given name is closed over: we need to # 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 # The nil code here is for void functions (when
# we compare their return types) # we compare their return types)
if a == nil: if a.isNil():
return b == nil or b.kind == Any return b.isNil() or b.kind == Any
elif b == nil: elif b.isNil():
return a == nil or a.kind == Any return a.isNil() or a.kind == Any
elif a.kind == Any or b.kind == Any: elif a.kind == Any or b.kind == Any:
# This is needed internally: user code # This is needed internally: user code
# cannot generate code for matching # cannot generate code for matching
@ -517,7 +517,7 @@ proc toIntrinsic(name: string): Type =
proc inferType(self: Compiler, node: LiteralExpr): Type = proc inferType(self: Compiler, node: LiteralExpr): Type =
## Infers the type of a given literal expression ## Infers the type of a given literal expression
if node == nil: if node.isNil():
return nil return nil
case node.kind: case node.kind:
of intExpr, binExpr, octExpr, hexExpr: of intExpr, binExpr, octExpr, hexExpr:
@ -559,13 +559,13 @@ proc inferType(self: Compiler, node: LiteralExpr): Type =
proc inferType(self: Compiler, node: Expression): Type = proc inferType(self: Compiler, node: Expression): Type =
## Infers the type of a given expression and ## Infers the type of a given expression and
## returns it ## returns it
if node == nil: if node.isNil():
return nil return nil
case node.kind: case node.kind:
of identExpr: of identExpr:
let node = IdentExpr(node) let node = IdentExpr(node)
let name = self.resolve(node) let name = self.resolve(node)
if name != nil: if not name.isNil():
return name.valueType return name.valueType
else: else:
result = node.name.lexeme.toIntrinsic() result = node.name.lexeme.toIntrinsic()
@ -586,7 +586,7 @@ proc inferType(self: Compiler, node: Expression): Type =
of lambdaExpr: of lambdaExpr:
var node = LambdaExpr(node) var node = LambdaExpr(node)
result = Type(kind: Function, returnType: nil, args: @[], isLambda: true) result = Type(kind: Function, returnType: nil, args: @[], isLambda: true)
if node.returnType != nil: if not node.returnType.isNil():
result.returnType = self.inferType(node.returnType) result.returnType = self.inferType(node.returnType)
for argument in node.arguments: for argument in node.arguments:
result.args.add((argument.name.token.lexeme, self.inferType(argument.valueType))) 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: case node.callee.kind:
of identExpr: of identExpr:
let resolved = self.resolve(IdentExpr(node.callee)) let resolved = self.resolve(IdentExpr(node.callee))
if resolved != nil: if not resolved.isNil():
result = resolved.valueType.returnType result = resolved.valueType.returnType
if result == nil: if result.isNil():
result = Type(kind: Any) result = Type(kind: Any)
else: else:
result = nil result = nil
@ -612,18 +612,18 @@ proc inferType(self: Compiler, node: Expression): Type =
proc inferType(self: Compiler, node: Declaration): Type = proc inferType(self: Compiler, node: Declaration): Type =
## Infers the type of a given declaration ## Infers the type of a given declaration
## and returns it ## and returns it
if node == nil: if node.isNil():
return nil return nil
case node.kind: case node.kind:
of funDecl: of funDecl:
var node = FunDecl(node) var node = FunDecl(node)
let resolved = self.resolve(node.name) let resolved = self.resolve(node.name)
if resolved != nil: if not resolved.isNil():
return resolved.valueType return resolved.valueType
of NodeKind.varDecl: of NodeKind.varDecl:
var node = VarDecl(node) var node = VarDecl(node)
let resolved = self.resolve(node.name) let resolved = self.resolve(node.name)
if resolved != nil: if not resolved.isNil():
return resolved.valueType return resolved.valueType
else: else:
return self.inferType(node.value) return self.inferType(node.value)
@ -653,7 +653,7 @@ proc typeToStr(self: Compiler, typ: Type): string =
if i < typ.args.len() - 1: if i < typ.args.len() - 1:
result &= ", " result &= ", "
result &= ")" result &= ")"
if typ.returnType != nil: if not typ.returnType.isNil():
result &= &": {self.typeToStr(typ.returnType)}" result &= &": {self.typeToStr(typ.returnType)}"
else: else:
discard discard
@ -824,6 +824,28 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) =
self.patchReturnAddress(pos) 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) = proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) =
## Emits the code to call a unary operator ## Emits the code to call a unary operator
self.generateCall(fn, @[op.a]) self.generateCall(fn, @[op.a])
@ -971,13 +993,13 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
elif argument.isPtr: elif argument.isPtr:
name.valueType = Type(kind: Pointer, value: name.valueType) name.valueType = Type(kind: Pointer, value: name.valueType)
# We check if the argument's type is a generic # 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: for gen in node.generics:
if gen.name == IdentExpr(argument.valueType): if gen.name == IdentExpr(argument.valueType):
name.valueType = Type(kind: Generic) name.valueType = Type(kind: Generic)
break break
# If it's still nil, it's an error! # 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}'") self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'")
fn.valueType.args.add((argument.name.token.lexeme, name.valueType)) fn.valueType.args.add((argument.name.token.lexeme, name.valueType))
else: else:
@ -987,18 +1009,18 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
proc identifier(self: Compiler, node: IdentExpr) = proc identifier(self: Compiler, node: IdentExpr) =
## Compiles access to identifiers ## Compiles access to identifiers
let s = self.resolve(node) let s = self.resolve(node)
if s == nil: if s.isNil():
self.error(&"reference to undeclared name '{node.token.lexeme}'") self.error(&"reference to undeclared name '{node.token.lexeme}'")
elif s.isConst: elif s.isConst:
# Constants are always emitted as Load* instructions # Constants are always emitted as Load* instructions
# no matter the scope depth # no matter the scope depth
self.emitConstant(node, self.inferType(node)) self.emitConstant(node, self.inferType(node))
else: else:
if s.valueType.kind == Function:
self.emitByte(LoadFunction)
self.emitBytes(s.codePos.toTriple())
self.detectClosureVariable(s) 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. # Static name resolution, loads value at index in the stack. Very fast. Much wow.
self.emitByte(LoadVar) self.emitByte(LoadVar)
# No need to check for -1 here: we already did a nil-check above! # 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) # not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway)
self.emitByte(LoadClosure) self.emitByte(LoadClosure)
self.emitBytes(self.closedOver.high().toTriple()) self.emitBytes(self.closedOver.high().toTriple())
if s.valueType.kind == Function:
self.emitByte(PopC)
proc assignment(self: Compiler, node: ASTNode) = proc assignment(self: Compiler, node: ASTNode) =
@ -1020,7 +1041,7 @@ proc assignment(self: Compiler, node: ASTNode) =
let node = AssignExpr(node) let node = AssignExpr(node)
let name = IdentExpr(node.name) let name = IdentExpr(node.name)
let r = self.resolve(name) let r = self.resolve(name)
if r == nil: if r.isNil():
self.error(&"assignment to undeclared name '{name.token.lexeme}'") self.error(&"assignment to undeclared name '{name.token.lexeme}'")
elif r.isConst: elif r.isConst:
self.error(&"cannot assign to '{name.token.lexeme}' (constant)") self.error(&"cannot assign to '{name.token.lexeme}' (constant)")
@ -1040,7 +1061,7 @@ proc assignment(self: Compiler, node: ASTNode) =
of setItemExpr: of setItemExpr:
let node = SetItemExpr(node) let node = SetItemExpr(node)
let typ = self.inferType(node) let typ = self.inferType(node)
if typ == nil: if typ.isNil():
self.error(&"cannot determine the type of '{node.name.token.lexeme}'") self.error(&"cannot determine the type of '{node.name.token.lexeme}'")
# TODO # TODO
else: else:
@ -1117,7 +1138,7 @@ proc ifStmt(self: Compiler, node: IfStmt) =
## execution of code ## execution of code
var cond = self.inferType(node.condition) var cond = self.inferType(node.condition)
if not self.compareTypes(cond, Type(kind: Bool)): if not self.compareTypes(cond, Type(kind: Bool)):
if cond == nil: if cond.isNil():
if node.condition.kind == identExpr: if node.condition.kind == identExpr:
self.error(&"reference to undeclared identifier '{IdentExpr(node.condition).name.lexeme}'") self.error(&"reference to undeclared identifier '{IdentExpr(node.condition).name.lexeme}'")
elif node.condition.kind == callExpr and CallExpr(node.condition).callee.kind == identExpr: 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) self.statement(node.thenBranch)
let jump2 = self.emitJump(JumpForwards) let jump2 = self.emitJump(JumpForwards)
self.patchJump(jump) self.patchJump(jump)
if node.elseBranch != nil: if not node.elseBranch.isNil():
self.statement(node.elseBranch) self.statement(node.elseBranch)
self.patchJump(jump2) self.patchJump(jump2)
@ -1203,7 +1224,7 @@ proc checkCallIsPure(self: Compiler, node: ASTnode): bool =
return false return false
proc callExpr(self: Compiler, node: CallExpr): Name = proc callExpr(self: Compiler, node: CallExpr) =
## Compiles code to call a function ## Compiles code to call a function
var args: seq[tuple[name: string, kind: Type]] = @[] var args: seq[tuple[name: string, kind: Type]] = @[]
var argExpr: seq[Expression] = @[] var argExpr: seq[Expression] = @[]
@ -1211,7 +1232,7 @@ proc callExpr(self: Compiler, node: CallExpr): Name =
# TODO: Keyword arguments # TODO: Keyword arguments
for i, argument in node.arguments.positionals: for i, argument in node.arguments.positionals:
kind = self.inferType(argument) kind = self.inferType(argument)
if kind == nil: if kind.isNil():
if argument.kind == identExpr: if argument.kind == identExpr:
self.error(&"reference to undeclared identifier '{IdentExpr(argument).name.lexeme}'") self.error(&"reference to undeclared identifier '{IdentExpr(argument).name.lexeme}'")
self.error(&"cannot infer the type of argument {i + 1} in function call") 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: of NodeKind.callExpr:
var node = node.callee var node = node.callee
while node.kind == callExpr: while node.kind == callExpr:
funct = self.callExpr(CallExpr(node)) self.callExpr(CallExpr(node))
node = CallExpr(node).callee node = CallExpr(node).callee
# funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args))
else: else:
discard # TODO: Lambdas discard # TODO: Calling expressions
self.generateCall(funct, argExpr) if not funct.isNil():
self.generateCall(funct, argExpr)
else:
self.generateObjCall(argExpr)
if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee): if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee):
if not self.currentFunction.name.isNil(): if not self.currentFunction.name.isNil():
self.error(&"cannot make sure that calls to '{self.currentFunction.name.token.lexeme}' are side-effect free") self.error(&"cannot make sure that calls to '{self.currentFunction.name.token.lexeme}' are side-effect free")
else: else:
self.error(&"cannot make sure that call is side-effect free") self.error(&"cannot make sure that call is side-effect free")
result = funct
proc expression(self: Compiler, node: Expression) = proc expression(self: Compiler, node: Expression) =
## Compiles all expressions ## Compiles all expressions
case node.kind: case node.kind:
of NodeKind.callExpr: of NodeKind.callExpr:
discard self.callExpr(CallExpr(node)) # TODO self.callExpr(CallExpr(node)) # TODO
of getItemExpr: of getItemExpr:
discard # TODO: Get rid of this discard # TODO: Get rid of this
of pragmaExpr: of pragmaExpr:
@ -1425,13 +1446,12 @@ proc statement(self: Compiler, node: Statement) =
of exprStmt: of exprStmt:
var expression = ExprStmt(node).expression var expression = ExprStmt(node).expression
self.expression(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 # The expression has no type, so we don't have to
# pop anything # pop anything
discard discard
else: else:
# We only print top-level expressions if self.replMode:
if self.replMode and self.scopeDepth == 0:
self.emitByte(PopRepl) self.emitByte(PopRepl)
else: else:
self.emitByte(Pop) self.emitByte(Pop)
@ -1478,19 +1498,19 @@ proc varDecl(self: Compiler, node: VarDecl) =
## Compiles variable declarations ## Compiles variable declarations
let expected = self.inferType(node.valueType) let expected = self.inferType(node.valueType)
let actual = self.inferType(node.value) let actual = self.inferType(node.value)
if expected == nil and actual == nil: if expected.isNil() and actual.isNil():
if node.value.kind == identExpr: if node.value.kind == identExpr:
self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'") self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'")
self.error(&"'{node.name.token.lexeme}' has no type") 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") self.error(&"invalid type '{self.typeToStr(expected)}' for var")
elif not self.compareTypes(expected, actual): 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.error(&"expected value of type '{self.typeToStr(expected)}', but '{node.name.token.lexeme}' is of type '{self.typeToStr(actual)}'")
self.expression(node.value) self.expression(node.value)
self.declareName(node, mutable=node.token.kind == Var) self.declareName(node, mutable=node.token.kind == Var)
self.emitByte(StoreVar) self.emitByte(StoreVar)
self.emitBytes(self.names.high().toTriple()) self.emitBytes((self.names.high() + 1).toTriple())
proc typeDecl(self: Compiler, node: TypeDecl) = proc typeDecl(self: Compiler, node: TypeDecl) =
@ -1521,7 +1541,7 @@ proc funDecl(self: Compiler, node: FunDecl) =
if not isGeneric: if not isGeneric:
self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'") self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'")
# TODO: Forward declarations # TODO: Forward declarations
if node.body != nil: if not node.body.isNil():
if BlockStmt(node.body).code.len() == 0: if BlockStmt(node.body).code.len() == 0:
self.error("cannot declare function with empty body") self.error("cannot declare function with empty body")
let fnType = self.inferType(node) let fnType = self.inferType(node)
@ -1562,12 +1582,12 @@ proc funDecl(self: Compiler, node: FunDecl) =
hasVal = LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn hasVal = LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn
else: else:
discard # Unreachable 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") 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.error("function has an explicit return type, but no return statement was found")
self.endFunctionBeforeReturn() self.endFunctionBeforeReturn()
hasVal = hasVal and typ.returnType != nil hasVal = hasVal and not typ.returnType.isNil()
self.endScope(deleteNames=true, fromFunc=true) self.endScope(deleteNames=true, fromFunc=true)
self.emitByte(OpCode.Return) self.emitByte(OpCode.Return)
if hasVal: 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.chunk.code.high().toTriple())
self.chunk.cfi.add(self.frames[^1].toTriple()) self.chunk.cfi.add(self.frames[^1].toTriple())
self.chunk.cfi.add(uint8(node.arguments.len())) 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()) self.chunk.cfi.add(node.name.token.lexeme.len().toDouble())
var s = node.name.token.lexeme var s = node.name.token.lexeme
if node.name.token.lexeme.len() >= uint16.high().int: if node.name.token.lexeme.len() >= uint16.high().int:

View File

@ -126,7 +126,9 @@ type
Assert, # Raises an AssertionFailed exception if x is false Assert, # Raises an AssertionFailed exception if x is false
NoOp, # Just a no-op NoOp, # Just a no-op
LoadArgument, LoadArgument,
PopC LoadFunctionObj,
PopC,
PushC
# We group instructions by their operation/operand types for easier handling when debugging # We group instructions by their operation/operand types for easier handling when debugging
@ -139,7 +141,7 @@ const simpleInstructions* = {Return, LoadNil,
BeginTry, FinishTry, Yield, BeginTry, FinishTry, Yield,
Await, NoOp, PopClosure, Await, NoOp, PopClosure,
SetResult, LoadArgument, SetResult, LoadArgument,
PopC} PopC, LoadFunctionObj, PushC}
# Constant instructions are instructions that operate on the bytecode constant table # Constant instructions are instructions that operate on the bytecode constant table
const constantInstructions* = {LoadInt64, LoadUInt64, const constantInstructions* = {LoadInt64, LoadUInt64,

View File

@ -76,7 +76,7 @@ proc repl(vm: PeonVM = newPeonVM()) =
elif current == "#clear": elif current == "#clear":
stdout.write("\x1Bc") stdout.write("\x1Bc")
continue continue
input &= &"\n{current}" input &= &"\n{current}\n"
tokens = tokenizer.lex(input, "stdin") tokens = tokenizer.lex(input, "stdin")
if tokens.len() == 0: if tokens.len() == 0:
continue continue