Various style fixes with nil checks, added PushC opcode, added support for calling function objects
This commit is contained in:
parent
11b15abc01
commit
02f1f8a54d
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue