Simplified calling convention, added for PeonObject, added some comments, fixed bug with StoreVar in stack frames, fixed issues with functions assigned to variables, changed the way closures are emitted so that empty jumps are no longer needed
This commit is contained in:
parent
2c6325d33b
commit
cc0aab850e
|
@ -287,6 +287,41 @@ proc constReadFloat64(self: PeonVM, idx: int): PeonObject =
|
|||
copyMem(result.`float`.addr, arr.addr, sizeof(arr))
|
||||
|
||||
|
||||
proc `$`(self: PeonObject): string =
|
||||
case self.kind:
|
||||
of Int64:
|
||||
result = &"{self.long}'i64"
|
||||
of UInt64:
|
||||
result = &"{self.uLong}'u64"
|
||||
of Int32:
|
||||
result = &"{self.`int`}'i32"
|
||||
of UInt32:
|
||||
result = &"{self.uInt}'u32"
|
||||
of Int16:
|
||||
result = &"{self.short}'i16"
|
||||
of UInt16:
|
||||
result = &"{self.uShort}'u16"
|
||||
of Int8:
|
||||
result = &"{self.tiny}'i8"
|
||||
of UInt8:
|
||||
result = &"{self.uTiny}'u8"
|
||||
of Float32:
|
||||
result = &"{self.halfFloat}'f32"
|
||||
of Float64:
|
||||
result = &"{self.`float`}'f64"
|
||||
of ObjectKind.Inf:
|
||||
if self.positive:
|
||||
result ="inf"
|
||||
else:
|
||||
result ="-inf"
|
||||
of ObjectKind.Nan, Nil:
|
||||
result =($self.kind).toLowerAscii()
|
||||
of Function:
|
||||
result = &"fn(ip: {self.ip})"
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc dispatch*(self: PeonVM) =
|
||||
## Main bytecode dispatch loop
|
||||
var instruction {.register.}: OpCode
|
||||
|
@ -342,11 +377,13 @@ proc dispatch*(self: PeonVM) =
|
|||
of LoadFloat64:
|
||||
self.push(self.constReadFloat64(int(self.readLong())))
|
||||
of LoadFunction:
|
||||
self.pushc(PeonObject(kind: Function, ip: self.readLong()))
|
||||
of LoadFunctionObj:
|
||||
# Loads a function onto the operand stack by reading its
|
||||
# instruction pointer
|
||||
self.push(PeonObject(kind: Function, ip: self.readLong()))
|
||||
of LoadReturnAddress:
|
||||
self.pushc(PeonObject(kind: UInt32, uInt: self.readUInt()))
|
||||
# Loads a 32-bit unsigned integer. Used to load function
|
||||
# return addresses
|
||||
self.push(PeonObject(kind: UInt32, uInt: self.readUInt()))
|
||||
of Call:
|
||||
# Calls a function. The calling convention for peon
|
||||
# functions is pretty simple: the first item in the
|
||||
|
@ -354,20 +391,34 @@ proc dispatch*(self: PeonVM) =
|
|||
# instruction pointer to jump to, followed by a 32-bit
|
||||
# return address. After that, all arguments and locals
|
||||
# follow
|
||||
var size {.used.} = self.readLong().int
|
||||
self.frames.add(self.calls.len() - 2)
|
||||
self.ip = self.peekc(-1).ip
|
||||
var argc {.used.} = self.readLong().int
|
||||
let retAddr = self.peek(-argc) # Return address
|
||||
let fnObj = self.peek(-argc - 1) # Function object
|
||||
self.ip = fnObj.ip
|
||||
self.pushc(fnObj)
|
||||
self.pushc(retAddr)
|
||||
# Creates a new result slot for the
|
||||
# function's return value
|
||||
self.results.add(self.getNil())
|
||||
# TODO: Use the frame size once
|
||||
# Creates a new call frame
|
||||
self.frames.add(self.calls.len() - 2)
|
||||
# Loads the arguments onto the stack
|
||||
for _ in 0..<argc:
|
||||
self.pushc(self.pop())
|
||||
# Pops the function object and
|
||||
# return address off the operand
|
||||
# stack since they're not needed
|
||||
# there anymore
|
||||
discard self.pop()
|
||||
discard self.pop()
|
||||
# TODO: Use the frame's initial size once
|
||||
# we have more control over the
|
||||
# memory
|
||||
#[while size > 0:
|
||||
dec(size)
|
||||
#[while argc > 0:
|
||||
dec(argc)
|
||||
self.pushc(self.getNil())
|
||||
]#
|
||||
of LoadArgument:
|
||||
self.pushc(self.pop())
|
||||
of OpCode.Return:
|
||||
of Return:
|
||||
# Returns from a function.
|
||||
# Every peon program is wrapped
|
||||
# in a hidden function, so this
|
||||
|
@ -380,6 +431,7 @@ proc dispatch*(self: PeonVM) =
|
|||
self.push(self.results.pop())
|
||||
else:
|
||||
discard self.results.pop()
|
||||
# Discard a stack frame
|
||||
discard self.frames.pop()
|
||||
if self.frames.len() == 0:
|
||||
# End of the program!
|
||||
|
@ -392,11 +444,11 @@ proc dispatch*(self: PeonVM) =
|
|||
of StoreVar:
|
||||
# Stores the value at the top of the operand stack
|
||||
# into the given call stack index
|
||||
let idx = int(self.readLong()) + self.frames[^1]
|
||||
if idx <= self.calls.high():
|
||||
let idx = int(self.readLong())
|
||||
if idx + self.frames[^1] <= self.calls.high():
|
||||
self.setc(idx, self.pop())
|
||||
else:
|
||||
self.calls.add(self.pop())
|
||||
self.pushc(self.pop())
|
||||
of StoreClosure:
|
||||
# Stores/updates the value of a closed-over
|
||||
# variable
|
||||
|
@ -411,56 +463,30 @@ proc dispatch*(self: PeonVM) =
|
|||
# Loads a closed-over variable onto the
|
||||
# stack
|
||||
self.push(self.closedOver[self.readLong()])
|
||||
of PopClosure:
|
||||
# Removes a closed-over variable from the closure
|
||||
# array
|
||||
discard self.closedOver.pop()
|
||||
of LoadVar:
|
||||
# Pushes a variable onto the operand
|
||||
# stack
|
||||
self.push(self.getc(int(self.readLong())))
|
||||
of NoOp:
|
||||
# Does nothing
|
||||
continue
|
||||
of PopC:
|
||||
# Pops a value off the call stack
|
||||
discard self.popc()
|
||||
of Pop:
|
||||
# Pops a value off the operand stack
|
||||
discard self.pop()
|
||||
of PushC:
|
||||
# Pushes a value from the operand stack
|
||||
# onto the call stack
|
||||
self.pushc(self.pop())
|
||||
of PopRepl:
|
||||
if self.frames.len() > 1:
|
||||
discard self.pop()
|
||||
continue
|
||||
let popped = self.pop()
|
||||
case popped.kind:
|
||||
of Int64:
|
||||
echo &"{popped.long}'i64"
|
||||
of UInt64:
|
||||
echo &"{popped.uLong}'u64"
|
||||
of Int32:
|
||||
echo &"{popped.`int`}'i32"
|
||||
of UInt32:
|
||||
echo &"{popped.uInt}'u32"
|
||||
of Int16:
|
||||
echo &"{popped.short}'i16"
|
||||
of UInt16:
|
||||
echo &"{popped.uShort}'u16"
|
||||
of Int8:
|
||||
echo &"{popped.tiny}'i8"
|
||||
of UInt8:
|
||||
echo &"{popped.uTiny}'u8"
|
||||
of Float32:
|
||||
echo &"{popped.halfFloat}'f32"
|
||||
of Float64:
|
||||
echo &"{popped.`float`}'f64"
|
||||
of ObjectKind.Inf:
|
||||
if popped.positive:
|
||||
echo "inf"
|
||||
else:
|
||||
echo "-inf"
|
||||
of ObjectKind.Nan, Nil:
|
||||
echo ($popped.kind).toLowerAscii()
|
||||
else:
|
||||
discard
|
||||
echo self.pop()
|
||||
of PopN:
|
||||
# Pops N elements off the call stack
|
||||
for _ in 0..<int(self.readShort()):
|
||||
discard self.popc()
|
||||
# Jump opcodes
|
||||
|
|
|
@ -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* = false # Traces VM execution
|
||||
const DEBUG_TRACE_VM* = true # 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
|
||||
|
|
|
@ -102,6 +102,10 @@ type
|
|||
isFunctionArgument: bool
|
||||
# Where is this node declared in the file?
|
||||
line: int
|
||||
# Is this a function declaration or a variable
|
||||
# with a function as value? (The distinction *is*
|
||||
# important! Check emitFunction())
|
||||
isFunDecl: bool
|
||||
Loop = object
|
||||
## A "loop object" used
|
||||
## by the compiler to emit
|
||||
|
@ -181,11 +185,11 @@ proc declaration(self: Compiler, node: Declaration)
|
|||
proc peek(self: Compiler, distance: int = 0): ASTNode
|
||||
proc identifier(self: Compiler, node: IdentExpr)
|
||||
proc varDecl(self: Compiler, node: VarDecl)
|
||||
proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): Type
|
||||
proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Type
|
||||
proc inferType(self: Compiler, node: LiteralExpr): Type
|
||||
proc inferType(self: Compiler, node: Expression): Type
|
||||
proc findByName(self: Compiler, name: string): seq[Name]
|
||||
proc findByType(self: Compiler, name: string, kind: Type, strictMutable: bool = true): seq[Name]
|
||||
proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool
|
||||
proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
|
||||
proc compareTypes(self: Compiler, a, b: Type): bool
|
||||
proc patchReturnAddress(self: Compiler, pos: int)
|
||||
proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
||||
proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
||||
|
@ -372,22 +376,25 @@ proc resolve(self: Compiler, name: IdentExpr,
|
|||
proc getStackPos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int =
|
||||
## Returns the predicted call stack position of a given name, relative
|
||||
## to the current frame
|
||||
result = 2
|
||||
var found = false
|
||||
for variable in reversed(self.names):
|
||||
result = 2
|
||||
for variable in self.names:
|
||||
if variable.valueType.kind == Function:
|
||||
continue
|
||||
inc(result)
|
||||
if name.name.lexeme == variable.name.name.lexeme:
|
||||
if variable.isPrivate and variable.owner != self.currentModule:
|
||||
continue
|
||||
if variable.depth == depth or variable.depth == 0:
|
||||
elif variable.depth == depth or variable.depth == 0:
|
||||
# variable.depth == 0 for globals!
|
||||
found = true
|
||||
dec(result)
|
||||
break
|
||||
inc(result)
|
||||
if not found:
|
||||
return -1
|
||||
|
||||
|
||||
proc getClosurePos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int =
|
||||
proc getClosurePos(self: Compiler, name: IdentExpr): int =
|
||||
## Iterates the internal list of declared closure names backwards and
|
||||
## returns the predicted closure array position of a given name.
|
||||
## Returns -1 if the name can't be found (this includes names that
|
||||
|
@ -398,7 +405,7 @@ proc getClosurePos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth
|
|||
if name.name.lexeme == variable.name.name.lexeme:
|
||||
if variable.isPrivate and variable.owner != self.currentModule:
|
||||
continue
|
||||
elif variable.depth == depth:
|
||||
else:
|
||||
found = true
|
||||
break
|
||||
dec(result)
|
||||
|
@ -423,7 +430,7 @@ proc resolve(self: Compiler, name: string,
|
|||
return nil
|
||||
|
||||
|
||||
proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDepth) =
|
||||
proc detectClosureVariable(self: Compiler, name: var Name, depth: int = self.scopeDepth, decl: bool = false) =
|
||||
## Detects if the given name is used in a local scope deeper
|
||||
## than the given one and modifies the code emitted for it
|
||||
## to store it as a closure variable if it is. Does nothing if the name
|
||||
|
@ -434,27 +441,25 @@ proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDe
|
|||
## unpredictably or crash
|
||||
if name.isNil() or name.depth == 0:
|
||||
return
|
||||
elif name.depth < depth and not name.isClosedOver:
|
||||
elif name.depth < depth:
|
||||
# Ding! The given name is closed over: we need to
|
||||
# change the dummy Jump instruction that self.declareName
|
||||
# put in place for us into a StoreClosure. We also update
|
||||
# the name's isClosedOver field so that self.identifier()
|
||||
# can emit a LoadClosure instruction instead of a LoadVar
|
||||
self.closedOver.add(name)
|
||||
let idx = self.closedOver.high().toTriple()
|
||||
if not name.isClosedOver:
|
||||
self.closedOver.add(name)
|
||||
if self.closedOver.len() >= 16777216:
|
||||
self.error("too many consecutive closed-over variables (max is 16777215)")
|
||||
self.chunk.code[name.codePos] = StoreClosure.uint8
|
||||
self.chunk.code[name.codePos + 1] = idx[0]
|
||||
self.chunk.code[name.codePos + 2] = idx[1]
|
||||
self.chunk.code[name.codePos + 3] = idx[2]
|
||||
name.isClosedOver = true
|
||||
if decl:
|
||||
self.emitByte(StoreClosure)
|
||||
self.emitBytes(self.closedOver.high().toTriple())
|
||||
|
||||
|
||||
proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool =
|
||||
proc compareTypes(self: Compiler, a, b: Type): bool =
|
||||
## Compares two type objects
|
||||
## for equality (works with nil!)
|
||||
|
||||
# The nil code here is for void functions (when
|
||||
# we compare their return types)
|
||||
if a.isNil():
|
||||
|
@ -477,10 +482,6 @@ proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool
|
|||
# If they're different, then they can't
|
||||
# be the same type!
|
||||
return false
|
||||
elif a.mutable != b.mutable and strictMutable:
|
||||
# Are they both (im)mutable? If not,
|
||||
# they're different
|
||||
return false
|
||||
case a.kind:
|
||||
# If all previous checks pass, it's time
|
||||
# to go through each possible type peon
|
||||
|
@ -504,10 +505,11 @@ proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool
|
|||
elif not self.compareTypes(a.returnType, b.returnType):
|
||||
return false
|
||||
for (argA, argB) in zip(a.args, b.args):
|
||||
if not self.compareTypes(argA.kind, argB.kind, strictMutable):
|
||||
if not self.compareTypes(argA.kind, argB.kind):
|
||||
return false
|
||||
return true
|
||||
else:
|
||||
# TODO: Custom types
|
||||
discard
|
||||
|
||||
|
||||
|
@ -553,7 +555,7 @@ proc toIntrinsic(name: string): Type =
|
|||
return nil
|
||||
|
||||
|
||||
proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): Type =
|
||||
proc inferType(self: Compiler, node: LiteralExpr): Type =
|
||||
## Infers the type of a given literal expression
|
||||
if node.isNil():
|
||||
return nil
|
||||
|
@ -565,7 +567,7 @@ proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): T
|
|||
if size.len() == 1:
|
||||
return Type(kind: Int64)
|
||||
let typ = size[1].toIntrinsic()
|
||||
if not self.compareTypes(typ, nil, strictMutable):
|
||||
if not self.compareTypes(typ, nil):
|
||||
return typ
|
||||
else:
|
||||
self.error(&"invalid type specifier '{size[1]}' for int")
|
||||
|
@ -576,7 +578,7 @@ proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): T
|
|||
if size.len() == 1 or size[1] == "f64":
|
||||
return Type(kind: Float64)
|
||||
let typ = size[1].toIntrinsic()
|
||||
if not self.compareTypes(typ, nil, strictMutable):
|
||||
if not self.compareTypes(typ, nil):
|
||||
return typ
|
||||
else:
|
||||
self.error(&"invalid type specifier '{size[1]}' for float")
|
||||
|
@ -594,7 +596,7 @@ proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): T
|
|||
discard # TODO
|
||||
|
||||
|
||||
proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Type =
|
||||
proc inferType(self: Compiler, node: Expression): Type =
|
||||
## Infers the type of a given expression and
|
||||
## returns it
|
||||
if node.isNil():
|
||||
|
@ -611,9 +613,9 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty
|
|||
return self.inferType(UnaryExpr(node).a)
|
||||
of binaryExpr:
|
||||
let node = BinaryExpr(node)
|
||||
var a = self.inferType(node.a, strictMutable)
|
||||
var b = self.inferType(node.b, strictMutable)
|
||||
if not self.compareTypes(a, b, strictMutable):
|
||||
var a = self.inferType(node.a)
|
||||
var b = self.inferType(node.b)
|
||||
if not self.compareTypes(a, b):
|
||||
return nil
|
||||
return a
|
||||
of {intExpr, hexExpr, binExpr, octExpr,
|
||||
|
@ -627,7 +629,7 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty
|
|||
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, strictMutable)))
|
||||
result.args.add((argument.name.token.lexeme, self.inferType(argument.valueType)))
|
||||
of callExpr:
|
||||
var node = CallExpr(node)
|
||||
case node.callee.kind:
|
||||
|
@ -640,16 +642,16 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty
|
|||
else:
|
||||
result = nil
|
||||
of lambdaExpr:
|
||||
result = self.inferType(LambdaExpr(node.callee).returnType, strictMutable)
|
||||
result = self.inferType(LambdaExpr(node.callee).returnType)
|
||||
else:
|
||||
discard # Unreachable
|
||||
of varExpr:
|
||||
result = self.inferType(Var(node).value)
|
||||
result.mutable = true
|
||||
of refExpr:
|
||||
result = Type(kind: Reference, value: self.inferType(Ref(node).value, strictMutable))
|
||||
result = Type(kind: Reference, value: self.inferType(Ref(node).value))
|
||||
of ptrExpr:
|
||||
result = Type(kind: Pointer, value: self.inferType(Ptr(node).value, strictMutable))
|
||||
result = Type(kind: Pointer, value: self.inferType(Ptr(node).value))
|
||||
else:
|
||||
discard # Unreachable
|
||||
|
||||
|
@ -715,19 +717,29 @@ proc findByName(self: Compiler, name: string): seq[Name] =
|
|||
result.add(obj)
|
||||
|
||||
|
||||
proc findByType(self: Compiler, name: string, kind: Type, strictMutable: bool = true): seq[Name] =
|
||||
proc findByType(self: Compiler, name: string, kind: Type): seq[Name] =
|
||||
## Looks for objects that have already been declared
|
||||
## with the given name and type
|
||||
for obj in self.findByName(name):
|
||||
if self.compareTypes(obj.valueType, kind, strictMutable):
|
||||
if self.compareTypes(obj.valueType, kind):
|
||||
result.add(obj)
|
||||
|
||||
#[
|
||||
proc findAtDepth(self: Compiler, name: string, depth: int): seq[Name] =
|
||||
## Looks for objects that have been already declared
|
||||
## with the given name at the given scope depth.
|
||||
## Returns all objects that apply
|
||||
for obj in self.findByName(name):
|
||||
if obj.depth == depth:
|
||||
result.add(obj)
|
||||
]#
|
||||
|
||||
proc matchImpl(self: Compiler, name: string, kind: Type, strictMutable: bool = true): Name =
|
||||
|
||||
proc matchImpl(self: Compiler, name: string, kind: Type): Name =
|
||||
## Tries to find a matching function implementation
|
||||
## compatible with the given type and returns its
|
||||
## name object
|
||||
let impl = self.findByType(name, kind, strictMutable)
|
||||
let impl = self.findByType(name, kind)
|
||||
if impl.len() == 0:
|
||||
var msg = &"cannot find a suitable implementation for '{name}'"
|
||||
let names = self.findByName(name)
|
||||
|
@ -761,9 +773,20 @@ proc matchImpl(self: Compiler, name: string, kind: Type, strictMutable: bool = t
|
|||
|
||||
proc emitFunction(self: Compiler, name: Name) =
|
||||
## Wrapper to emit LoadFunction instructions
|
||||
self.emitByte(LoadFunction)
|
||||
self.emitBytes(name.codePos.toTriple())
|
||||
|
||||
if name.isFunDecl:
|
||||
self.emitByte(LoadFunction)
|
||||
self.emitBytes(name.codePos.toTriple())
|
||||
# If we're not loading a statically declared
|
||||
# function, then it must be a function object
|
||||
# created by previous LoadFunction instructions
|
||||
# that is now bound to some variable, so we just
|
||||
# load it
|
||||
elif not name.isClosedOver:
|
||||
self.emitByte(LoadVar)
|
||||
self.emitBytes(self.getStackPos(name.name).toTriple())
|
||||
else:
|
||||
self.emitByte(LoadClosure)
|
||||
self.emitBytes(self.getClosurePos(name.name).toTriple())
|
||||
|
||||
## End of utility functions
|
||||
|
||||
|
@ -850,11 +873,11 @@ proc literal(self: Compiler, node: ASTNode) =
|
|||
|
||||
|
||||
proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||
## Emits single instructions for builtin functions
|
||||
## Emits instructions for builtin functions
|
||||
## such as addition or subtraction
|
||||
if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]:
|
||||
for argument in args:
|
||||
self.expression(argument)
|
||||
self.expression(args[1])
|
||||
self.expression(args[0])
|
||||
case fn.valueType.builtinOp:
|
||||
of "AddInt64":
|
||||
self.emitByte(AddInt64)
|
||||
|
@ -936,29 +959,27 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
|||
self.emitByte(DivFloat32)
|
||||
of "MulFloat32":
|
||||
self.emitByte(MulFloat32)
|
||||
of "GenericLogicalOr":
|
||||
of "LogicalOr":
|
||||
self.expression(args[0])
|
||||
let jump = self.emitJump(JumpIfTrue)
|
||||
self.expression(args[1])
|
||||
self.patchJump(jump)
|
||||
of "GenericLogicalAnd":
|
||||
of "LogicalAnd":
|
||||
self.expression(args[0])
|
||||
var jump: int
|
||||
if self.enableOptimizations:
|
||||
jump = self.emitJump(JumpIfFalseOrPop)
|
||||
else:
|
||||
jump = self.emitJump(JumpIfFalse)
|
||||
self.emitByte(Pop)
|
||||
var jump = self.emitJump(JumpIfFalseOrPop)
|
||||
self.expression(args[1])
|
||||
self.patchJump(jump)
|
||||
else:
|
||||
discard # Unreachable
|
||||
self.error(&"unknown built-in: '{fn.valueType.builtinOp}'")
|
||||
|
||||
|
||||
proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||
## Small wrapper that abstracts emitting a call instruction
|
||||
## for a given function
|
||||
if fn.valueType.isBuiltinFunction:
|
||||
# Builtins map to individual instructions
|
||||
# (usually 1, but some use more) so we handle
|
||||
# them differently
|
||||
self.handleBuiltinFunction(fn, args)
|
||||
return
|
||||
if any(fn.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic):
|
||||
|
@ -968,41 +989,19 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) =
|
|||
self.emitFunction(fn)
|
||||
self.emitByte(LoadReturnAddress)
|
||||
let pos = self.chunk.code.len()
|
||||
# We initially emit a dummy return
|
||||
# address. It is patched later
|
||||
self.emitBytes(0.toQuad())
|
||||
for argument in args:
|
||||
for argument in reversed(args):
|
||||
# We pass the arguments in reverse
|
||||
# because of how stack semantics
|
||||
# work. They'll be fixed at runtime
|
||||
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 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())
|
||||
# Creates a new call frame and jumps
|
||||
# to the function's first instruction
|
||||
# in the code
|
||||
self.emitByte(Call)
|
||||
self.emitBytes(fn.valueType.args.len().toTriple())
|
||||
self.patchReturnAddress(pos)
|
||||
|
||||
|
||||
|
@ -1013,23 +1012,22 @@ proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) =
|
|||
|
||||
proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) =
|
||||
## Emits the code to call a binary operator
|
||||
# Pushes the return address
|
||||
self.generateCall(fn, @[op.a, op.b])
|
||||
|
||||
|
||||
proc unary(self: Compiler, node: UnaryExpr) =
|
||||
## Compiles unary expressions such as decimal
|
||||
## and bitwise negation
|
||||
let valueType = self.inferType(node.a, strictMutable=false)
|
||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]), strictMutable=false)
|
||||
let valueType = self.inferType(node.a)
|
||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]))
|
||||
self.callUnaryOp(funct, node)
|
||||
|
||||
|
||||
proc binary(self: Compiler, node: BinaryExpr) =
|
||||
## Compiles all binary expressions
|
||||
let typeOfA = self.inferType(node.a, strictMutable=false)
|
||||
let typeOfB = self.inferType(node.b, strictMutable=false)
|
||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]), strictMutable=false)
|
||||
let typeOfA = self.inferType(node.a)
|
||||
let typeOfB = self.inferType(node.b)
|
||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]))
|
||||
self.callBinaryOp(funct, node)
|
||||
|
||||
|
||||
|
@ -1049,8 +1047,8 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
|||
# slap myself 100 times with a sign saying "I'm dumb". Mark my words
|
||||
self.error("cannot declare more than 16777215 variables at a time")
|
||||
for name in self.findByName(node.name.token.lexeme):
|
||||
if name.depth == self.scopeDepth and name.valueType.kind notin {Function, CustomType} and not name.isFunctionArgument:
|
||||
# Trying to redeclare a variable in the same module is an error, but it's okay
|
||||
if name.depth == self.scopeDepth and not name.isFunctionArgument:
|
||||
# Trying to redeclare a variable in the same scope/context is an error, but it's okay
|
||||
# if it's a function argument (for example, if you want to copy a number to
|
||||
# mutate it)
|
||||
self.error(&"attempt to redeclare '{node.name.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}")
|
||||
|
@ -1066,21 +1064,6 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
|||
line: node.token.line))
|
||||
if mutable:
|
||||
self.names[^1].valueType.mutable = true
|
||||
# We emit a jump of 0 because this may become a
|
||||
# StoreHeap instruction. If they variable is
|
||||
# not closed over, we'll sadly be wasting a
|
||||
# VM cycle. The previous implementation used 4 no-op
|
||||
# instructions, which wasted 4 times as many clock
|
||||
# cycles.
|
||||
# TODO: Optimize this. It's a bit tricky because
|
||||
# deleting bytecode would render all of our
|
||||
# jump offsets and other absolute indeces in the
|
||||
# bytecode wrong
|
||||
if self.scopeDepth > 0:
|
||||
# Closure variables are only used in local
|
||||
# scopes
|
||||
self.emitByte(JumpForwards)
|
||||
self.emitBytes(0.toTriple())
|
||||
of NodeKind.funDecl:
|
||||
var node = FunDecl(node)
|
||||
# We declare the generics before the function so we
|
||||
|
@ -1106,14 +1089,15 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
|||
name: node.name,
|
||||
isLet: false,
|
||||
isClosedOver: false,
|
||||
line: node.token.line))
|
||||
line: node.token.line,
|
||||
isFunDecl: true))
|
||||
let fn = self.names[^1]
|
||||
var name: Name
|
||||
for argument in node.arguments:
|
||||
if self.names.high() > 16777215:
|
||||
self.error("cannot declare more than 16777215 variables at a time")
|
||||
# wait, no LoadVar? Yes! That's because when calling functions,
|
||||
# arguments will already be on the stack so there's no need to
|
||||
# arguments will already be on the stack, so there's no need to
|
||||
# load them here
|
||||
name = Name(depth: self.scopeDepth + 1,
|
||||
isPrivate: true,
|
||||
|
@ -1138,7 +1122,7 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
|||
|
||||
proc identifier(self: Compiler, node: IdentExpr) =
|
||||
## Compiles access to identifiers
|
||||
let s = self.resolve(node)
|
||||
var s = self.resolve(node)
|
||||
if s.isNil():
|
||||
self.error(&"reference to undeclared name '{node.token.lexeme}'")
|
||||
elif s.isConst:
|
||||
|
@ -1148,11 +1132,11 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
else:
|
||||
self.detectClosureVariable(s)
|
||||
if s.valueType.kind == Function:
|
||||
if not s.valueType.isBuiltinFunction:
|
||||
self.emitByte(LoadFunctionObj)
|
||||
self.emitBytes(s.codePos.toTriple())
|
||||
else:
|
||||
self.emitByte(LoadNil)
|
||||
# Functions have no runtime
|
||||
# representation, so we need
|
||||
# to create one on the fly
|
||||
self.emitByte(LoadFunction)
|
||||
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)
|
||||
|
@ -1163,7 +1147,7 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
# align its semantics with the call stack. This makes closures work as expected and is
|
||||
# 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())
|
||||
self.emitBytes(self.getClosurePos(s.name).toTriple())
|
||||
|
||||
|
||||
|
||||
|
@ -1173,7 +1157,7 @@ proc assignment(self: Compiler, node: ASTNode) =
|
|||
of assignExpr:
|
||||
let node = AssignExpr(node)
|
||||
let name = IdentExpr(node.name)
|
||||
let r = self.resolve(name)
|
||||
var r = self.resolve(name)
|
||||
if r.isNil():
|
||||
self.error(&"assignment to undeclared name '{name.token.lexeme}'")
|
||||
elif r.isConst:
|
||||
|
@ -1207,7 +1191,7 @@ proc beginScope(self: Compiler) =
|
|||
inc(self.scopeDepth)
|
||||
|
||||
|
||||
proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false) =
|
||||
proc endScope(self: Compiler) =
|
||||
## Ends the current local scope
|
||||
if self.scopeDepth < 0:
|
||||
self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)")
|
||||
|
@ -1216,11 +1200,7 @@ proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false)
|
|||
for name in self.names:
|
||||
if name.depth > self.scopeDepth:
|
||||
names.add(name)
|
||||
if not self.enableOptimizations and not fromFunc:
|
||||
# All variables with a scope depth larger than the current one
|
||||
# are now out of scope. Begone, you're now homeless!
|
||||
self.emitByte(PopC)
|
||||
if self.enableOptimizations and len(names) > 1 and not fromFunc:
|
||||
if len(names) > 1:
|
||||
# If we're popping less than 65535 variables, then
|
||||
# we can emit a PopN instruction. This is true for
|
||||
# 99.99999% of the use cases of the language (who the
|
||||
|
@ -1234,32 +1214,25 @@ proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false)
|
|||
for i in countdown(self.names.high(), len(names) - uint16.high().int()):
|
||||
if self.names[i].depth > self.scopeDepth:
|
||||
self.emitByte(PopC)
|
||||
elif len(names) == 1 and not fromFunc:
|
||||
elif len(names) == 1:
|
||||
# We only emit PopN if we're popping more than one value
|
||||
self.emitByte(PopC)
|
||||
# This seems *really* slow, but
|
||||
# what else should I do? Nim doesn't
|
||||
# allow the removal of items during
|
||||
# seq iteration so ¯\_(ツ)_/¯
|
||||
if deleteNames:
|
||||
var idx = 0
|
||||
while idx < self.names.len():
|
||||
for name in names:
|
||||
if self.names[idx] == name:
|
||||
self.names.delete(idx)
|
||||
inc(idx)
|
||||
idx = 0
|
||||
while idx < self.closedOver.len():
|
||||
for name in names:
|
||||
if name.isClosedOver:
|
||||
self.closedOver.delete(idx)
|
||||
self.emitByte(PopClosure)
|
||||
inc(idx)
|
||||
var idx = 0
|
||||
while idx < self.names.len():
|
||||
for name in names:
|
||||
if self.names[idx] == name:
|
||||
self.names.delete(idx)
|
||||
inc(idx)
|
||||
|
||||
|
||||
|
||||
proc blockStmt(self: Compiler, node: BlockStmt) =
|
||||
## Compiles block statements, which create a new
|
||||
## local scope.
|
||||
## local scope
|
||||
self.beginScope()
|
||||
for decl in node.code:
|
||||
self.declaration(decl)
|
||||
|
@ -1270,25 +1243,13 @@ proc ifStmt(self: Compiler, node: IfStmt) =
|
|||
## Compiles if/else statements for conditional
|
||||
## execution of code
|
||||
var cond = self.inferType(node.condition)
|
||||
self.expression(node.condition)
|
||||
if not self.compareTypes(cond, Type(kind: Bool)):
|
||||
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:
|
||||
self.error(&"reference to undeclared identifier '{IdentExpr(CallExpr(node.condition).callee).name.lexeme}'")
|
||||
else:
|
||||
self.error(&"expecting value of type 'bool', but expression has no type")
|
||||
self.error(&"expecting value of type 'bool', but expression has no type")
|
||||
else:
|
||||
self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead")
|
||||
self.expression(node.condition)
|
||||
var jumpCode: OpCode
|
||||
if self.enableOptimizations:
|
||||
jumpCode = JumpIfFalsePop
|
||||
else:
|
||||
jumpCode = JumpIfFalse
|
||||
let jump = self.emitJump(jumpCode)
|
||||
if not self.enableOptimizations:
|
||||
self.emitByte(Pop)
|
||||
let jump = self.emitJump(JumpIfFalsePop)
|
||||
self.statement(node.thenBranch)
|
||||
let jump2 = self.emitJump(JumpForwards)
|
||||
self.patchJump(jump)
|
||||
|
@ -1310,51 +1271,23 @@ proc emitLoop(self: Compiler, begin: int) =
|
|||
proc whileStmt(self: Compiler, node: WhileStmt) =
|
||||
## Compiles C-style while loops and
|
||||
## desugared C-style for loops
|
||||
let start = self.chunk.code.len()
|
||||
var cond = self.inferType(node.condition)
|
||||
self.expression(node.condition)
|
||||
var jump: int
|
||||
if self.enableOptimizations:
|
||||
jump = self.emitJump(JumpIfFalsePop)
|
||||
else:
|
||||
jump = self.emitJump(JumpIfFalse)
|
||||
self.emitByte(Pop)
|
||||
if not self.compareTypes(cond, Type(kind: Bool)):
|
||||
if cond.isNil():
|
||||
self.error(&"expecting value of type 'bool', but expression has no type")
|
||||
else:
|
||||
self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead")
|
||||
let start = self.chunk.code.len()
|
||||
var jump = self.emitJump(JumpIfFalsePop)
|
||||
self.statement(node.body)
|
||||
self.patchJump(jump)
|
||||
self.emitLoop(start)
|
||||
|
||||
|
||||
proc isPure(self: Compiler, node: ASTNode): bool =
|
||||
## Checks if a function has any side effects
|
||||
var pragmas: seq[Pragma]
|
||||
case node.kind:
|
||||
of lambdaExpr:
|
||||
pragmas = LambdaExpr(node).pragmas
|
||||
else:
|
||||
pragmas = Declaration(node).pragmas
|
||||
if pragmas.len() == 0:
|
||||
return false
|
||||
for pragma in pragmas:
|
||||
if pragma.name.name.lexeme == "pure":
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
proc checkCallIsPure(self: Compiler, node: ASTnode): bool =
|
||||
## Checks if a call has any side effects
|
||||
if not self.isPure(node):
|
||||
return true
|
||||
var pragmas: seq[Pragma]
|
||||
case node.kind:
|
||||
of lambdaExpr:
|
||||
pragmas = LambdaExpr(node).pragmas
|
||||
else:
|
||||
pragmas = Declaration(node).pragmas
|
||||
if pragmas.len() == 0:
|
||||
return false
|
||||
for pragma in pragmas:
|
||||
if pragma.name.name.lexeme == "pure":
|
||||
return true
|
||||
return false
|
||||
return true # TODO
|
||||
|
||||
|
||||
proc callExpr(self: Compiler, node: CallExpr) =
|
||||
|
@ -1362,7 +1295,6 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
|||
var args: seq[tuple[name: string, kind: Type]] = @[]
|
||||
var argExpr: seq[Expression] = @[]
|
||||
var kind: Type
|
||||
var strictMutable = true
|
||||
# TODO: Keyword arguments
|
||||
for i, argument in node.arguments.positionals:
|
||||
kind = self.inferType(argument)
|
||||
|
@ -1370,8 +1302,6 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
|||
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")
|
||||
if kind.mutable:
|
||||
strictMutable = false
|
||||
args.add(("", kind))
|
||||
argExpr.add(argument)
|
||||
for argument in node.arguments.keyword:
|
||||
|
@ -1381,7 +1311,7 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
|||
var funct: Name
|
||||
case node.callee.kind:
|
||||
of identExpr:
|
||||
funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args), strictMutable)
|
||||
funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args))
|
||||
of NodeKind.callExpr:
|
||||
var node = node.callee
|
||||
while node.kind == callExpr:
|
||||
|
@ -1394,13 +1324,15 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
|||
self.error(&"expression has no type")
|
||||
else:
|
||||
self.error(&"object of type '{self.typeToStr(typ)}' is not callable")
|
||||
if not funct.isNil():
|
||||
if funct.valueType.isBuiltinFunction:
|
||||
self.handleBuiltinFunction(funct, argExpr)
|
||||
else:
|
||||
self.generateCall(funct, argExpr)
|
||||
if any(funct.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic):
|
||||
# The function has generic arguments! We need to compile a version
|
||||
# of it with the right type data
|
||||
self.funDecl(nil, funct, argExpr)
|
||||
# TODO: What next?
|
||||
elif funct.valueType.isBuiltinFunction:
|
||||
self.handleBuiltinFunction(funct, argExpr)
|
||||
else:
|
||||
self.generateObjCall(argExpr)
|
||||
self.generateCall(funct, argExpr)
|
||||
if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee):
|
||||
if self.currentFunction.name != "":
|
||||
self.error(&"cannot make sure that calls to '{self.currentFunction.name}' are side-effect free")
|
||||
|
@ -1477,9 +1409,9 @@ proc endFunctionBeforeReturn(self: Compiler) =
|
|||
## its return instruction
|
||||
var popped = 0
|
||||
for name in self.names:
|
||||
if name.depth == self.scopeDepth and name.valueType.kind notin {Function, Generic}:
|
||||
if name.depth == self.scopeDepth and name.valueType.kind notin {Function, Generic, CustomType} and not name.isClosedOver:
|
||||
inc(popped)
|
||||
if self.enableOptimizations and popped > 1:
|
||||
if popped > 1:
|
||||
self.emitByte(PopN)
|
||||
self.emitBytes(popped.toDouble())
|
||||
dec(popped, uint16.high().int)
|
||||
|
@ -1491,17 +1423,19 @@ proc endFunctionBeforeReturn(self: Compiler) =
|
|||
proc returnStmt(self: Compiler, node: ReturnStmt) =
|
||||
## Compiles return statements
|
||||
let actual = self.inferType(node.value)
|
||||
let expected = self.currentFunction
|
||||
if actual.isNil() and not expected.returnType.isNil():
|
||||
var expected = self.currentFunction.returnType
|
||||
if not expected.isNil() and expected.kind == Generic:
|
||||
expected = actual
|
||||
if actual.isNil() and not expected.isNil():
|
||||
if not node.value.isNil():
|
||||
if node.value.kind == identExpr:
|
||||
self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'")
|
||||
elif node.value.kind == callExpr and CallExpr(node.value).callee.kind == identExpr:
|
||||
self.error(&"call to undeclared function '{CallExpr(node.value).callee.token.lexeme}'")
|
||||
self.error(&"expected return value of type '{self.typeToStr(expected.returnType)}', but expression has no type")
|
||||
elif expected.returnType.isNil() and not actual.isNil():
|
||||
self.error("non-empty return statement is not allowed in void functions")
|
||||
elif not self.compareTypes(actual, expected.returnType):
|
||||
self.error(&"expected return value of type '{self.typeToStr(expected)}', but expression has no type")
|
||||
elif expected.isNil() and not actual.isNil():
|
||||
self.error("empty return statement is only allowed in void functions")
|
||||
elif not self.compareTypes(actual, expected):
|
||||
self.error(&"expected return value of type '{self.typeToStr(expected)}', got '{self.typeToStr(actual)}' instead")
|
||||
if not node.value.isNil():
|
||||
self.expression(node.value)
|
||||
|
@ -1653,8 +1587,10 @@ proc varDecl(self: Compiler, node: VarDecl) =
|
|||
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 == TokenType.Var)
|
||||
var r = self.names[^1]
|
||||
self.detectClosureVariable(r, decl=true)
|
||||
self.emitByte(StoreVar)
|
||||
self.emitBytes(self.names.len().toTriple())
|
||||
self.emitBytes((self.getStackPos(r.name) + 1).toTriple())
|
||||
|
||||
|
||||
proc typeDecl(self: Compiler, node: TypeDecl) =
|
||||
|
@ -1708,12 +1644,16 @@ proc dispatchPragmas(self: Compiler, node: ASTnode) =
|
|||
|
||||
proc fixGenericFunc(self: Compiler, name: Name, args: seq[Expression]): Type =
|
||||
## Specializes generic arguments in functions
|
||||
var fn = name.valueType
|
||||
result = fn.deepCopy()
|
||||
var node = fn.fun
|
||||
var fn = name.valueType.deepCopy()
|
||||
result = fn
|
||||
var typ: Type
|
||||
for i in 0..args.high():
|
||||
if fn.args[i].kind.kind == Generic:
|
||||
self.resolve(fn.args[i].name).valueType = self.inferType(args[i])
|
||||
typ = self.inferType(args[i])
|
||||
fn.args[i].kind = typ
|
||||
self.resolve(fn.args[i].name).valueType = typ
|
||||
if fn.args[i].kind.isNil():
|
||||
self.error(&"cannot specialize generic function: argument {i + 1} has no type")
|
||||
|
||||
|
||||
proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) =
|
||||
|
@ -1728,9 +1668,12 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression
|
|||
self.dispatchPragmas(node)
|
||||
var node = node
|
||||
var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn
|
||||
if fn.valueType.returnType.isNil():
|
||||
self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'")
|
||||
if not fn.valueType.isBuiltinFunction:
|
||||
if fn.valueType.isBuiltinFunction:
|
||||
# We take the arguments off of our name list
|
||||
# because they become temporaries on the stack
|
||||
for i in self.names.high() - node.arguments.high()..self.names.high():
|
||||
self.names.delete(i)
|
||||
else:
|
||||
var function = self.currentFunction
|
||||
var jmp: int
|
||||
# Builtin functions map to a single
|
||||
|
@ -1747,11 +1690,6 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression
|
|||
fn.codePos = self.chunk.code.len()
|
||||
# We store the current function
|
||||
self.currentFunction = fn.valueType
|
||||
let argLen = if node.isNil(): fn.valueType.args.len() else: node.arguments.len()
|
||||
for _ in 0..<argLen:
|
||||
# Pops off the operand stack onto the
|
||||
# call stack
|
||||
self.emitByte(LoadArgument)
|
||||
if node.isNil():
|
||||
# We got called back with more specific type
|
||||
# arguments: time to fix them!
|
||||
|
@ -1795,13 +1733,14 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression
|
|||
hasVal = LambdaExpr(Declaration(self.currentFunction.fun)).hasExplicitReturn
|
||||
else:
|
||||
discard # Unreachable
|
||||
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 not self.currentFunction.returnType.isNil():
|
||||
if not hasVal and not typ.isNil():
|
||||
# There is no explicit return statement anywhere in the function's
|
||||
# body: while this is not a tremendously useful piece of information (since
|
||||
# the presence of at least one doesn't mean all control flow cases are
|
||||
# covered), it definitely is an error worth reporting
|
||||
self.error("function has an explicit return type, but no return statement was found")
|
||||
self.endFunctionBeforeReturn()
|
||||
hasVal = hasVal and not typ.isNil()
|
||||
self.endScope(deleteNames=true, fromFunc=true)
|
||||
self.endScope()
|
||||
# Terminates the function's context
|
||||
self.emitByte(OpCode.Return)
|
||||
if hasVal:
|
||||
|
@ -1895,15 +1834,16 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk =
|
|||
self.names.add(main)
|
||||
self.emitByte(LoadFunction)
|
||||
self.emitBytes(main.codePos.toTriple())
|
||||
|
||||
self.emitByte(LoadReturnAddress)
|
||||
let pos = self.chunk.code.len()
|
||||
self.emitBytes(0.toQuad())
|
||||
self.emitByte(Call)
|
||||
self.emitBytes(2.toTriple())
|
||||
self.emitBytes(0.toTriple())
|
||||
while not self.done():
|
||||
self.declaration(Declaration(self.step()))
|
||||
self.endScope(fromFunc=true)
|
||||
self.endFunctionBeforeReturn()
|
||||
self.patchReturnAddress(pos)
|
||||
self.emitByte(OpCode.Return)
|
||||
self.emitByte(0)
|
||||
self.emitByte(0) # Entry point has no return value
|
||||
result = self.chunk
|
||||
|
|
|
@ -138,15 +138,13 @@ type
|
|||
## Basic stack operations
|
||||
Pop, # Pops an element off the stack and discards it
|
||||
PopRepl, # Same as Pop, but also prints the value of what's popped (used in REPL mode)
|
||||
Push, # Pushes x onto the stack
|
||||
PopN, # Pops x elements off the stack (optimization for exiting local scopes which usually pop many elements)
|
||||
PopN, # Pops x elements off the call stack (optimization for exiting local scopes which usually pop many elements)
|
||||
## Name resolution/handling
|
||||
LoadAttribute, # Pushes the attribute b of object a onto the stack
|
||||
LoadVar, # Pushes the object at position x in the stack onto the stack
|
||||
StoreVar, # Stores the value of b at position a in the stack
|
||||
LoadClosure, # Pushes the object position x in the closure array onto the stack
|
||||
StoreClosure, # Stores the value of b at position a in the closure array
|
||||
PopClosure, # Pops a closed-over variable from the closure array
|
||||
## Looping and jumping
|
||||
Jump, # Absolute, unconditional jump into the bytecode
|
||||
JumpForwards, # Relative, unconditional, positive jump in the bytecode
|
||||
|
@ -168,10 +166,8 @@ type
|
|||
## Coroutines
|
||||
Await, # Calls an asynchronous function
|
||||
## Misc
|
||||
Assert, # Raises an AssertionFailed exception if x is false
|
||||
NoOp, # Just a no-op
|
||||
LoadArgument, # The caller uses this to pop off the argument from the operand stack onto the call stack
|
||||
LoadFunctionObj, # Creates and pushes a function object onto the stack with ip X
|
||||
Assert, # Raises an AssertionFailed exception if x is false
|
||||
NoOp, # Just a no-op
|
||||
PopC, # Pop off the call stack onto the operand stack
|
||||
PushC # Pop off the operand stack onto the call stack
|
||||
|
||||
|
@ -185,7 +181,7 @@ const simpleInstructions* = {Return, LoadNil,
|
|||
Pop, PopRepl, Raise,
|
||||
BeginTry, FinishTry, Yield,
|
||||
Await, NoOp, SetResult,
|
||||
LoadArgument, PopC, PushC,
|
||||
PopC, PushC,
|
||||
AddInt64, AddUInt64, AddInt32,
|
||||
AddUInt32, AddInt16, AddUInt16,
|
||||
AddInt8, AddUInt8, SubInt64,
|
||||
|
@ -222,7 +218,7 @@ const stackDoubleInstructions* = {}
|
|||
const argumentDoubleInstructions* = {PopN, }
|
||||
|
||||
# Argument double argument instructions take hardcoded arguments as 24 bit integers
|
||||
const argumentTripleInstructions* = {LoadFunctionObj, StoreClosure, PopClosure}
|
||||
const argumentTripleInstructions* = {StoreClosure}
|
||||
|
||||
# Instructions that call functions
|
||||
const callInstructions* = {Call, }
|
||||
|
|
|
@ -314,7 +314,7 @@ proc primary(self: Parser): Expression =
|
|||
self.expect(RightParen, "unterminated parenthesized expression")
|
||||
of Yield:
|
||||
let tok = self.step()
|
||||
if self.currentFunction == nil:
|
||||
if self.currentFunction.isNil():
|
||||
self.error("'yield' cannot be used outside functions")
|
||||
elif self.currentFunction.token.kind != Generator:
|
||||
# It's easier than doing conversions for lambda/funDecl
|
||||
|
@ -327,7 +327,7 @@ proc primary(self: Parser): Expression =
|
|||
result = newYieldExpr(newNilExpr(Token()), tok)
|
||||
of Await:
|
||||
let tok = self.step()
|
||||
if self.currentFunction == nil:
|
||||
if self.currentFunction.isNil():
|
||||
self.error("'await' cannot be used outside functions")
|
||||
if self.currentFunction.token.kind != Coroutine:
|
||||
self.error("'await' can only be used inside coroutines")
|
||||
|
@ -549,7 +549,7 @@ proc blockStmt(self: Parser): Statement =
|
|||
var code: seq[Declaration] = @[]
|
||||
while not self.check(RightBrace) and not self.done():
|
||||
code.add(self.declaration())
|
||||
if code[^1] == nil:
|
||||
if code[^1].isNil():
|
||||
code.delete(code.high())
|
||||
self.expect(RightBrace, "expecting '}'")
|
||||
result = newBlockStmt(code, tok)
|
||||
|
@ -568,7 +568,7 @@ proc breakStmt(self: Parser): Statement =
|
|||
proc deferStmt(self: Parser): Statement =
|
||||
## Parses defer statements
|
||||
let tok = self.peek(-1)
|
||||
if self.currentFunction == nil:
|
||||
if self.currentFunction.isNil():
|
||||
self.error("'defer' cannot be used outside functions")
|
||||
result = newDeferStmt(self.expression(), tok)
|
||||
endOfLine("missing semicolon after defer statement")
|
||||
|
@ -586,7 +586,7 @@ proc continueStmt(self: Parser): Statement =
|
|||
proc returnStmt(self: Parser): Statement =
|
||||
## Parses return statements
|
||||
let tok = self.peek(-1)
|
||||
if self.currentFunction == nil:
|
||||
if self.currentFunction.isNil():
|
||||
self.error("'return' cannot be used outside functions")
|
||||
var value: Expression
|
||||
if not self.check(Semicolon):
|
||||
|
@ -606,7 +606,7 @@ proc returnStmt(self: Parser): Statement =
|
|||
proc yieldStmt(self: Parser): Statement =
|
||||
## Parses yield statements
|
||||
let tok = self.peek(-1)
|
||||
if self.currentFunction == nil:
|
||||
if self.currentFunction.isNil():
|
||||
self.error("'yield' cannot be outside functions")
|
||||
elif self.currentFunction.token.kind != Generator:
|
||||
self.error("'yield' can only be used inside generators")
|
||||
|
@ -620,7 +620,7 @@ proc yieldStmt(self: Parser): Statement =
|
|||
proc awaitStmt(self: Parser): Statement =
|
||||
## Parses await statements
|
||||
let tok = self.peek(-1)
|
||||
if self.currentFunction == nil:
|
||||
if self.currentFunction.isNil():
|
||||
self.error("'await' cannot be used outside functions")
|
||||
if self.currentFunction.token.kind != Coroutine:
|
||||
self.error("'await' can only be used inside coroutines")
|
||||
|
@ -690,10 +690,10 @@ proc tryStmt(self: Parser): Statement =
|
|||
elseClause = self.statement()
|
||||
if self.match(Finally):
|
||||
finallyClause = self.statement()
|
||||
if handlers.len() == 0 and elseClause == nil and finallyClause == nil:
|
||||
if handlers.len() == 0 and elseClause.isNil() and finallyClause.isNil():
|
||||
self.error("expecting 'except', 'finally' or 'else' statement after 'try' block")
|
||||
for i, handler in handlers:
|
||||
if handler.exc == nil and i != handlers.high():
|
||||
if handler.exc.isNil() and i != handlers.high():
|
||||
self.error("catch-all exception handler with bare 'except' must come last in try statement")
|
||||
result = newTryStmt(body, handlers, finallyClause, elseClause, tok)
|
||||
|
||||
|
@ -737,18 +737,18 @@ proc forStmt(self: Parser): Statement =
|
|||
increment = self.expression()
|
||||
self.expect(RightParen, "unterminated for loop increment")
|
||||
var body = self.statement()
|
||||
if increment != nil:
|
||||
if not increment.isNil():
|
||||
# The increment runs after each iteration, so we
|
||||
# inject it into the block as the last statement
|
||||
body = newBlockStmt(@[Declaration(body), newExprStmt(increment,
|
||||
increment.token)], tok)
|
||||
if condition == nil:
|
||||
if condition.isNil():
|
||||
## An empty condition is functionally
|
||||
## equivalent to "true"
|
||||
condition = newTrueExpr(Token(lexeme: "true"))
|
||||
# We can use a while loop, which in this case works just as well
|
||||
body = newWhileStmt(condition, body, tok)
|
||||
if initializer != nil:
|
||||
if not initializer.isNil():
|
||||
# Nested blocks, so the initializer is
|
||||
# only executed once
|
||||
body = newBlockStmt(@[Declaration(initializer), Declaration(body)], tok)
|
||||
|
@ -777,7 +777,7 @@ proc ifStmt(self: Parser): Statement =
|
|||
var condition = self.expression()
|
||||
self.expect(RightParen, "expecting ')' after if condition")
|
||||
var thenBranch = self.statement()
|
||||
var elseBranch: Statement = nil
|
||||
var elseBranch: Statement
|
||||
if self.match(Else):
|
||||
elseBranch = self.statement()
|
||||
result = newIfStmt(condition, thenBranch, elseBranch, tok)
|
||||
|
@ -790,7 +790,6 @@ template checkDecl(self: Parser, isPrivate: bool) =
|
|||
self.error("cannot bind public names inside local scopes")
|
||||
|
||||
|
||||
|
||||
proc parsePragmas(self: Parser): seq[Pragma] =
|
||||
## Parses pragmas
|
||||
var
|
||||
|
@ -871,7 +870,7 @@ proc varDecl(self: Parser, isLet: bool = false,
|
|||
isLet = isLet, valueType = valueType, pragmas = (@[]))
|
||||
else:
|
||||
discard # Unreachable
|
||||
if not hasInit and VarDecl(result).valueType == nil:
|
||||
if not hasInit and VarDecl(result).valueType.isNil():
|
||||
self.error("expecting initializer or explicit type declaration, but neither was found")
|
||||
result.pragmas = pragmas
|
||||
|
||||
|
@ -904,7 +903,7 @@ proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr,
|
|||
break
|
||||
self.expect(RightParen)
|
||||
for argument in arguments:
|
||||
if argument.valueType == nil:
|
||||
if argument.valueType.isNil():
|
||||
self.error(&"missing type declaration for '{argument.name.token.lexeme}' in function declaration")
|
||||
|
||||
|
||||
|
|
|
@ -227,9 +227,12 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
|
|||
styledEcho fgCyan, "Compilation step:\n"
|
||||
debugger.disassembleChunk(compiled, f)
|
||||
echo ""
|
||||
|
||||
serializer.dumpFile(compiled, f, splitFile(f).dir & "/" & splitFile(f).name & ".pbc")
|
||||
serialized = serializer.loadFile(splitFile(f).dir & "/" & splitFile(f).name & ".pbc")
|
||||
var path = splitFile(f).dir
|
||||
if path.len() > 0:
|
||||
path &= "/"
|
||||
path &= splitFile(f).name & ".pbc"
|
||||
serializer.dumpFile(compiled, f, path)
|
||||
serialized = serializer.loadFile(path)
|
||||
when debugSerializer:
|
||||
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
|
||||
styledEcho fgCyan, "Serialization step: "
|
||||
|
|
|
@ -63,8 +63,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
|
|||
|
||||
|
||||
proc checkFrameStart(self: Debugger, n: int) =
|
||||
## Todo: Checking frame end offsets needs
|
||||
## fixes
|
||||
## Checks if a call frame begins at the given
|
||||
## bytecode offset
|
||||
for i, e in self.cfiData:
|
||||
if n == e.start and not (e.started or e.stopped):
|
||||
e.started = true
|
||||
|
@ -76,6 +76,8 @@ proc checkFrameStart(self: Debugger, n: int) =
|
|||
|
||||
|
||||
proc checkFrameEnd(self: Debugger, n: int) =
|
||||
## Checks if a call frame ends at the given
|
||||
## bytecode offset
|
||||
for i, e in self.cfiData:
|
||||
if n == e.stop and e.started and not e.stopped:
|
||||
e.stopped = true
|
||||
|
@ -83,6 +85,7 @@ proc checkFrameEnd(self: Debugger, n: int) =
|
|||
|
||||
|
||||
proc simpleInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs simple instructions
|
||||
printInstruction(instruction, true)
|
||||
self.current += 1
|
||||
if instruction == Return:
|
||||
|
@ -152,7 +155,7 @@ proc loadAddressInstruction(self: Debugger, instruction: OpCode) =
|
|||
var address = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3],
|
||||
self.chunk.code[self.current + 4]].fromQuad()
|
||||
printInstruction(instruction)
|
||||
styledEcho fgGreen, &" loads address ", fgYellow, $address
|
||||
styledEcho fgGreen, &", loads address ", fgYellow, $address
|
||||
self.current += 5
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue