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))
|
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) =
|
proc dispatch*(self: PeonVM) =
|
||||||
## Main bytecode dispatch loop
|
## Main bytecode dispatch loop
|
||||||
var instruction {.register.}: OpCode
|
var instruction {.register.}: OpCode
|
||||||
|
@ -342,11 +377,13 @@ proc dispatch*(self: PeonVM) =
|
||||||
of LoadFloat64:
|
of LoadFloat64:
|
||||||
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()))
|
# Loads a function onto the operand stack by reading its
|
||||||
of LoadFunctionObj:
|
# instruction pointer
|
||||||
self.push(PeonObject(kind: Function, ip: self.readLong()))
|
self.push(PeonObject(kind: Function, ip: self.readLong()))
|
||||||
of LoadReturnAddress:
|
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:
|
of Call:
|
||||||
# Calls a function. The calling convention for peon
|
# Calls a function. The calling convention for peon
|
||||||
# functions is pretty simple: the first item in the
|
# 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
|
# instruction pointer to jump to, followed by a 32-bit
|
||||||
# return address. After that, all arguments and locals
|
# return address. After that, all arguments and locals
|
||||||
# follow
|
# follow
|
||||||
var size {.used.} = self.readLong().int
|
var argc {.used.} = self.readLong().int
|
||||||
self.frames.add(self.calls.len() - 2)
|
let retAddr = self.peek(-argc) # Return address
|
||||||
self.ip = self.peekc(-1).ip
|
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())
|
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
|
# we have more control over the
|
||||||
# memory
|
# memory
|
||||||
#[while size > 0:
|
#[while argc > 0:
|
||||||
dec(size)
|
dec(argc)
|
||||||
self.pushc(self.getNil())
|
self.pushc(self.getNil())
|
||||||
]#
|
]#
|
||||||
of LoadArgument:
|
of Return:
|
||||||
self.pushc(self.pop())
|
|
||||||
of OpCode.Return:
|
|
||||||
# Returns from a function.
|
# Returns from a function.
|
||||||
# Every peon program is wrapped
|
# Every peon program is wrapped
|
||||||
# in a hidden function, so this
|
# in a hidden function, so this
|
||||||
|
@ -380,6 +431,7 @@ proc dispatch*(self: PeonVM) =
|
||||||
self.push(self.results.pop())
|
self.push(self.results.pop())
|
||||||
else:
|
else:
|
||||||
discard self.results.pop()
|
discard self.results.pop()
|
||||||
|
# Discard a stack frame
|
||||||
discard self.frames.pop()
|
discard self.frames.pop()
|
||||||
if self.frames.len() == 0:
|
if self.frames.len() == 0:
|
||||||
# End of the program!
|
# End of the program!
|
||||||
|
@ -392,11 +444,11 @@ proc dispatch*(self: PeonVM) =
|
||||||
of StoreVar:
|
of StoreVar:
|
||||||
# Stores the value at the top of the operand stack
|
# Stores the value at the top of the operand stack
|
||||||
# into the given call stack index
|
# into the given call stack index
|
||||||
let idx = int(self.readLong()) + self.frames[^1]
|
let idx = int(self.readLong())
|
||||||
if idx <= self.calls.high():
|
if idx + self.frames[^1] <= self.calls.high():
|
||||||
self.setc(idx, self.pop())
|
self.setc(idx, self.pop())
|
||||||
else:
|
else:
|
||||||
self.calls.add(self.pop())
|
self.pushc(self.pop())
|
||||||
of StoreClosure:
|
of StoreClosure:
|
||||||
# Stores/updates the value of a closed-over
|
# Stores/updates the value of a closed-over
|
||||||
# variable
|
# variable
|
||||||
|
@ -411,56 +463,30 @@ proc dispatch*(self: PeonVM) =
|
||||||
# Loads a closed-over variable onto the
|
# Loads a closed-over variable onto the
|
||||||
# stack
|
# stack
|
||||||
self.push(self.closedOver[self.readLong()])
|
self.push(self.closedOver[self.readLong()])
|
||||||
of PopClosure:
|
|
||||||
# Removes a closed-over variable from the closure
|
|
||||||
# array
|
|
||||||
discard self.closedOver.pop()
|
|
||||||
of LoadVar:
|
of LoadVar:
|
||||||
|
# Pushes a variable onto the operand
|
||||||
|
# stack
|
||||||
self.push(self.getc(int(self.readLong())))
|
self.push(self.getc(int(self.readLong())))
|
||||||
of NoOp:
|
of NoOp:
|
||||||
|
# Does nothing
|
||||||
continue
|
continue
|
||||||
of PopC:
|
of PopC:
|
||||||
|
# Pops a value off the call stack
|
||||||
discard self.popc()
|
discard self.popc()
|
||||||
of Pop:
|
of Pop:
|
||||||
|
# Pops a value off the operand stack
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
of PushC:
|
of PushC:
|
||||||
|
# Pushes a value from the operand stack
|
||||||
|
# onto the call stack
|
||||||
self.pushc(self.pop())
|
self.pushc(self.pop())
|
||||||
of PopRepl:
|
of PopRepl:
|
||||||
if self.frames.len() > 1:
|
if self.frames.len() > 1:
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
continue
|
continue
|
||||||
let popped = self.pop()
|
echo 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
|
|
||||||
of PopN:
|
of PopN:
|
||||||
|
# Pops N elements off the call stack
|
||||||
for _ in 0..<int(self.readShort()):
|
for _ in 0..<int(self.readShort()):
|
||||||
discard self.popc()
|
discard self.popc()
|
||||||
# Jump opcodes
|
# Jump opcodes
|
||||||
|
|
|
@ -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* = 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_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
|
||||||
|
|
|
@ -102,6 +102,10 @@ type
|
||||||
isFunctionArgument: bool
|
isFunctionArgument: bool
|
||||||
# Where is this node declared in the file?
|
# Where is this node declared in the file?
|
||||||
line: int
|
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
|
Loop = object
|
||||||
## A "loop object" used
|
## A "loop object" used
|
||||||
## by the compiler to emit
|
## by the compiler to emit
|
||||||
|
@ -181,11 +185,11 @@ proc declaration(self: Compiler, node: Declaration)
|
||||||
proc peek(self: Compiler, distance: int = 0): ASTNode
|
proc peek(self: Compiler, distance: int = 0): ASTNode
|
||||||
proc identifier(self: Compiler, node: IdentExpr)
|
proc identifier(self: Compiler, node: IdentExpr)
|
||||||
proc varDecl(self: Compiler, node: VarDecl)
|
proc varDecl(self: Compiler, node: VarDecl)
|
||||||
proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): Type
|
proc inferType(self: Compiler, node: LiteralExpr): Type
|
||||||
proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Type
|
proc inferType(self: Compiler, node: Expression): Type
|
||||||
proc findByName(self: Compiler, name: string): seq[Name]
|
proc findByName(self: Compiler, name: string): seq[Name]
|
||||||
proc findByType(self: Compiler, name: string, kind: Type, strictMutable: bool = true): seq[Name]
|
proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
|
||||||
proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool
|
proc compareTypes(self: Compiler, a, b: Type): bool
|
||||||
proc patchReturnAddress(self: Compiler, pos: int)
|
proc patchReturnAddress(self: Compiler, pos: int)
|
||||||
proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode)
|
||||||
proc handlePurePragma(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 =
|
proc getStackPos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int =
|
||||||
## Returns the predicted call stack position of a given name, relative
|
## Returns the predicted call stack position of a given name, relative
|
||||||
## to the current frame
|
## to the current frame
|
||||||
result = 2
|
|
||||||
var found = false
|
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 name.name.lexeme == variable.name.name.lexeme:
|
||||||
if variable.isPrivate and variable.owner != self.currentModule:
|
if variable.isPrivate and variable.owner != self.currentModule:
|
||||||
continue
|
continue
|
||||||
if variable.depth == depth or variable.depth == 0:
|
elif variable.depth == depth or variable.depth == 0:
|
||||||
# variable.depth == 0 for globals!
|
# variable.depth == 0 for globals!
|
||||||
found = true
|
found = true
|
||||||
|
dec(result)
|
||||||
break
|
break
|
||||||
inc(result)
|
|
||||||
if not found:
|
if not found:
|
||||||
return -1
|
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
|
## Iterates the internal list of declared closure names backwards and
|
||||||
## returns the predicted closure array position of a given name.
|
## returns the predicted closure array position of a given name.
|
||||||
## Returns -1 if the name can't be found (this includes names that
|
## 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 name.name.lexeme == variable.name.name.lexeme:
|
||||||
if variable.isPrivate and variable.owner != self.currentModule:
|
if variable.isPrivate and variable.owner != self.currentModule:
|
||||||
continue
|
continue
|
||||||
elif variable.depth == depth:
|
else:
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
dec(result)
|
dec(result)
|
||||||
|
@ -423,7 +430,7 @@ proc resolve(self: Compiler, name: string,
|
||||||
return nil
|
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
|
## Detects if the given name is used in a local scope deeper
|
||||||
## than the given one and modifies the code emitted for it
|
## 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
|
## 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
|
## unpredictably or crash
|
||||||
if name.isNil() 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:
|
||||||
# Ding! The given name is closed over: we need to
|
# Ding! The given name is closed over: we need to
|
||||||
# change the dummy Jump instruction that self.declareName
|
# change the dummy Jump instruction that self.declareName
|
||||||
# put in place for us into a StoreClosure. We also update
|
# put in place for us into a StoreClosure. We also update
|
||||||
# the name's isClosedOver field so that self.identifier()
|
# the name's isClosedOver field so that self.identifier()
|
||||||
# can emit a LoadClosure instruction instead of a LoadVar
|
# can emit a LoadClosure instruction instead of a LoadVar
|
||||||
self.closedOver.add(name)
|
if not name.isClosedOver:
|
||||||
let idx = self.closedOver.high().toTriple()
|
self.closedOver.add(name)
|
||||||
if self.closedOver.len() >= 16777216:
|
if self.closedOver.len() >= 16777216:
|
||||||
self.error("too many consecutive closed-over variables (max is 16777215)")
|
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
|
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
|
## Compares two type objects
|
||||||
## for equality (works with nil!)
|
## for equality (works with nil!)
|
||||||
|
|
||||||
# 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.isNil():
|
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
|
# If they're different, then they can't
|
||||||
# be the same type!
|
# be the same type!
|
||||||
return false
|
return false
|
||||||
elif a.mutable != b.mutable and strictMutable:
|
|
||||||
# Are they both (im)mutable? If not,
|
|
||||||
# they're different
|
|
||||||
return false
|
|
||||||
case a.kind:
|
case a.kind:
|
||||||
# If all previous checks pass, it's time
|
# If all previous checks pass, it's time
|
||||||
# to go through each possible type peon
|
# 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):
|
elif not self.compareTypes(a.returnType, b.returnType):
|
||||||
return false
|
return false
|
||||||
for (argA, argB) in zip(a.args, b.args):
|
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 false
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
|
# TODO: Custom types
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
@ -553,7 +555,7 @@ proc toIntrinsic(name: string): Type =
|
||||||
return nil
|
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
|
## Infers the type of a given literal expression
|
||||||
if node.isNil():
|
if node.isNil():
|
||||||
return nil
|
return nil
|
||||||
|
@ -565,7 +567,7 @@ proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): T
|
||||||
if size.len() == 1:
|
if size.len() == 1:
|
||||||
return Type(kind: Int64)
|
return Type(kind: Int64)
|
||||||
let typ = size[1].toIntrinsic()
|
let typ = size[1].toIntrinsic()
|
||||||
if not self.compareTypes(typ, nil, strictMutable):
|
if not self.compareTypes(typ, nil):
|
||||||
return typ
|
return typ
|
||||||
else:
|
else:
|
||||||
self.error(&"invalid type specifier '{size[1]}' for int")
|
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":
|
if size.len() == 1 or size[1] == "f64":
|
||||||
return Type(kind: Float64)
|
return Type(kind: Float64)
|
||||||
let typ = size[1].toIntrinsic()
|
let typ = size[1].toIntrinsic()
|
||||||
if not self.compareTypes(typ, nil, strictMutable):
|
if not self.compareTypes(typ, nil):
|
||||||
return typ
|
return typ
|
||||||
else:
|
else:
|
||||||
self.error(&"invalid type specifier '{size[1]}' for float")
|
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
|
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
|
## Infers the type of a given expression and
|
||||||
## returns it
|
## returns it
|
||||||
if node.isNil():
|
if node.isNil():
|
||||||
|
@ -611,9 +613,9 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty
|
||||||
return self.inferType(UnaryExpr(node).a)
|
return self.inferType(UnaryExpr(node).a)
|
||||||
of binaryExpr:
|
of binaryExpr:
|
||||||
let node = BinaryExpr(node)
|
let node = BinaryExpr(node)
|
||||||
var a = self.inferType(node.a, strictMutable)
|
var a = self.inferType(node.a)
|
||||||
var b = self.inferType(node.b, strictMutable)
|
var b = self.inferType(node.b)
|
||||||
if not self.compareTypes(a, b, strictMutable):
|
if not self.compareTypes(a, b):
|
||||||
return nil
|
return nil
|
||||||
return a
|
return a
|
||||||
of {intExpr, hexExpr, binExpr, octExpr,
|
of {intExpr, hexExpr, binExpr, octExpr,
|
||||||
|
@ -627,7 +629,7 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty
|
||||||
if not node.returnType.isNil():
|
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, strictMutable)))
|
result.args.add((argument.name.token.lexeme, self.inferType(argument.valueType)))
|
||||||
of callExpr:
|
of callExpr:
|
||||||
var node = CallExpr(node)
|
var node = CallExpr(node)
|
||||||
case node.callee.kind:
|
case node.callee.kind:
|
||||||
|
@ -640,16 +642,16 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty
|
||||||
else:
|
else:
|
||||||
result = nil
|
result = nil
|
||||||
of lambdaExpr:
|
of lambdaExpr:
|
||||||
result = self.inferType(LambdaExpr(node.callee).returnType, strictMutable)
|
result = self.inferType(LambdaExpr(node.callee).returnType)
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
of varExpr:
|
of varExpr:
|
||||||
result = self.inferType(Var(node).value)
|
result = self.inferType(Var(node).value)
|
||||||
result.mutable = true
|
result.mutable = true
|
||||||
of refExpr:
|
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:
|
of ptrExpr:
|
||||||
result = Type(kind: Pointer, value: self.inferType(Ptr(node).value, strictMutable))
|
result = Type(kind: Pointer, value: self.inferType(Ptr(node).value))
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
|
|
||||||
|
@ -715,19 +717,29 @@ proc findByName(self: Compiler, name: string): seq[Name] =
|
||||||
result.add(obj)
|
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
|
## Looks for objects that have already been declared
|
||||||
## with the given name and type
|
## with the given name and type
|
||||||
for obj in self.findByName(name):
|
for obj in self.findByName(name):
|
||||||
if self.compareTypes(obj.valueType, kind, strictMutable):
|
if self.compareTypes(obj.valueType, kind):
|
||||||
result.add(obj)
|
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
|
## Tries to find a matching function implementation
|
||||||
## compatible with the given type and returns its
|
## compatible with the given type and returns its
|
||||||
## name object
|
## name object
|
||||||
let impl = self.findByType(name, kind, strictMutable)
|
let impl = self.findByType(name, kind)
|
||||||
if impl.len() == 0:
|
if impl.len() == 0:
|
||||||
var msg = &"cannot find a suitable implementation for '{name}'"
|
var msg = &"cannot find a suitable implementation for '{name}'"
|
||||||
let names = self.findByName(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) =
|
proc emitFunction(self: Compiler, name: Name) =
|
||||||
## Wrapper to emit LoadFunction instructions
|
## Wrapper to emit LoadFunction instructions
|
||||||
self.emitByte(LoadFunction)
|
if name.isFunDecl:
|
||||||
self.emitBytes(name.codePos.toTriple())
|
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
|
## End of utility functions
|
||||||
|
|
||||||
|
@ -850,11 +873,11 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
|
|
||||||
|
|
||||||
proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
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
|
## such as addition or subtraction
|
||||||
if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]:
|
if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]:
|
||||||
for argument in args:
|
self.expression(args[1])
|
||||||
self.expression(argument)
|
self.expression(args[0])
|
||||||
case fn.valueType.builtinOp:
|
case fn.valueType.builtinOp:
|
||||||
of "AddInt64":
|
of "AddInt64":
|
||||||
self.emitByte(AddInt64)
|
self.emitByte(AddInt64)
|
||||||
|
@ -936,29 +959,27 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||||
self.emitByte(DivFloat32)
|
self.emitByte(DivFloat32)
|
||||||
of "MulFloat32":
|
of "MulFloat32":
|
||||||
self.emitByte(MulFloat32)
|
self.emitByte(MulFloat32)
|
||||||
of "GenericLogicalOr":
|
of "LogicalOr":
|
||||||
self.expression(args[0])
|
self.expression(args[0])
|
||||||
let jump = self.emitJump(JumpIfTrue)
|
let jump = self.emitJump(JumpIfTrue)
|
||||||
self.expression(args[1])
|
self.expression(args[1])
|
||||||
self.patchJump(jump)
|
self.patchJump(jump)
|
||||||
of "GenericLogicalAnd":
|
of "LogicalAnd":
|
||||||
self.expression(args[0])
|
self.expression(args[0])
|
||||||
var jump: int
|
var jump = self.emitJump(JumpIfFalseOrPop)
|
||||||
if self.enableOptimizations:
|
|
||||||
jump = self.emitJump(JumpIfFalseOrPop)
|
|
||||||
else:
|
|
||||||
jump = self.emitJump(JumpIfFalse)
|
|
||||||
self.emitByte(Pop)
|
|
||||||
self.expression(args[1])
|
self.expression(args[1])
|
||||||
self.patchJump(jump)
|
self.patchJump(jump)
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
self.error(&"unknown built-in: '{fn.valueType.builtinOp}'")
|
||||||
|
|
||||||
|
|
||||||
proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) =
|
proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||||
## Small wrapper that abstracts emitting a call instruction
|
## Small wrapper that abstracts emitting a call instruction
|
||||||
## for a given function
|
## for a given function
|
||||||
if fn.valueType.isBuiltinFunction:
|
if fn.valueType.isBuiltinFunction:
|
||||||
|
# Builtins map to individual instructions
|
||||||
|
# (usually 1, but some use more) so we handle
|
||||||
|
# them differently
|
||||||
self.handleBuiltinFunction(fn, args)
|
self.handleBuiltinFunction(fn, args)
|
||||||
return
|
return
|
||||||
if any(fn.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic):
|
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.emitFunction(fn)
|
||||||
self.emitByte(LoadReturnAddress)
|
self.emitByte(LoadReturnAddress)
|
||||||
let pos = self.chunk.code.len()
|
let pos = self.chunk.code.len()
|
||||||
|
# We initially emit a dummy return
|
||||||
|
# address. It is patched later
|
||||||
self.emitBytes(0.toQuad())
|
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.expression(argument)
|
||||||
self.emitByte(Call) # Creates a new call frame
|
# Creates a new call frame and jumps
|
||||||
var size = 2 # We start at 2 because each call frame
|
# to the function's first instruction
|
||||||
# contains at least 2 elements (function
|
# in the code
|
||||||
# object and return address)
|
self.emitByte(Call)
|
||||||
for name in reversed(self.names):
|
self.emitBytes(fn.valueType.args.len().toTriple())
|
||||||
# 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())
|
|
||||||
self.patchReturnAddress(pos)
|
self.patchReturnAddress(pos)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1013,23 +1012,22 @@ proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) =
|
||||||
|
|
||||||
proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) =
|
proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) =
|
||||||
## Emits the code to call a binary operator
|
## Emits the code to call a binary operator
|
||||||
# Pushes the return address
|
|
||||||
self.generateCall(fn, @[op.a, op.b])
|
self.generateCall(fn, @[op.a, op.b])
|
||||||
|
|
||||||
|
|
||||||
proc unary(self: Compiler, node: UnaryExpr) =
|
proc unary(self: Compiler, node: UnaryExpr) =
|
||||||
## Compiles unary expressions such as decimal
|
## Compiles unary expressions such as decimal
|
||||||
## and bitwise negation
|
## and bitwise negation
|
||||||
let valueType = self.inferType(node.a, strictMutable=false)
|
let valueType = self.inferType(node.a)
|
||||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]), strictMutable=false)
|
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]))
|
||||||
self.callUnaryOp(funct, node)
|
self.callUnaryOp(funct, node)
|
||||||
|
|
||||||
|
|
||||||
proc binary(self: Compiler, node: BinaryExpr) =
|
proc binary(self: Compiler, node: BinaryExpr) =
|
||||||
## Compiles all binary expressions
|
## Compiles all binary expressions
|
||||||
let typeOfA = self.inferType(node.a, strictMutable=false)
|
let typeOfA = self.inferType(node.a)
|
||||||
let typeOfB = self.inferType(node.b, strictMutable=false)
|
let typeOfB = self.inferType(node.b)
|
||||||
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]), strictMutable=false)
|
let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]))
|
||||||
self.callBinaryOp(funct, node)
|
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
|
# 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")
|
self.error("cannot declare more than 16777215 variables at a time")
|
||||||
for name in self.findByName(node.name.token.lexeme):
|
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:
|
if name.depth == self.scopeDepth and not name.isFunctionArgument:
|
||||||
# Trying to redeclare a variable in the same module is an error, but it's okay
|
# 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
|
# if it's a function argument (for example, if you want to copy a number to
|
||||||
# mutate it)
|
# mutate it)
|
||||||
self.error(&"attempt to redeclare '{node.name.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}")
|
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))
|
line: node.token.line))
|
||||||
if mutable:
|
if mutable:
|
||||||
self.names[^1].valueType.mutable = true
|
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:
|
of NodeKind.funDecl:
|
||||||
var node = FunDecl(node)
|
var node = FunDecl(node)
|
||||||
# We declare the generics before the function so we
|
# 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,
|
name: node.name,
|
||||||
isLet: false,
|
isLet: false,
|
||||||
isClosedOver: false,
|
isClosedOver: false,
|
||||||
line: node.token.line))
|
line: node.token.line,
|
||||||
|
isFunDecl: true))
|
||||||
let fn = self.names[^1]
|
let fn = self.names[^1]
|
||||||
var name: Name
|
var name: Name
|
||||||
for argument in node.arguments:
|
for argument in node.arguments:
|
||||||
if self.names.high() > 16777215:
|
if self.names.high() > 16777215:
|
||||||
self.error("cannot declare more than 16777215 variables at a time")
|
self.error("cannot declare more than 16777215 variables at a time")
|
||||||
# wait, no LoadVar? Yes! That's because when calling functions,
|
# 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
|
# load them here
|
||||||
name = Name(depth: self.scopeDepth + 1,
|
name = Name(depth: self.scopeDepth + 1,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
|
@ -1138,7 +1122,7 @@ 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)
|
var s = self.resolve(node)
|
||||||
if s.isNil():
|
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:
|
||||||
|
@ -1148,11 +1132,11 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
||||||
else:
|
else:
|
||||||
self.detectClosureVariable(s)
|
self.detectClosureVariable(s)
|
||||||
if s.valueType.kind == Function:
|
if s.valueType.kind == Function:
|
||||||
if not s.valueType.isBuiltinFunction:
|
# Functions have no runtime
|
||||||
self.emitByte(LoadFunctionObj)
|
# representation, so we need
|
||||||
self.emitBytes(s.codePos.toTriple())
|
# to create one on the fly
|
||||||
else:
|
self.emitByte(LoadFunction)
|
||||||
self.emitByte(LoadNil)
|
self.emitBytes(s.codePos.toTriple())
|
||||||
elif not s.isClosedOver:
|
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)
|
||||||
|
@ -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
|
# 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)
|
# 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.getClosurePos(s.name).toTriple())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1173,7 +1157,7 @@ proc assignment(self: Compiler, node: ASTNode) =
|
||||||
of assignExpr:
|
of assignExpr:
|
||||||
let node = AssignExpr(node)
|
let node = AssignExpr(node)
|
||||||
let name = IdentExpr(node.name)
|
let name = IdentExpr(node.name)
|
||||||
let r = self.resolve(name)
|
var r = self.resolve(name)
|
||||||
if r.isNil():
|
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:
|
||||||
|
@ -1207,7 +1191,7 @@ proc beginScope(self: Compiler) =
|
||||||
inc(self.scopeDepth)
|
inc(self.scopeDepth)
|
||||||
|
|
||||||
|
|
||||||
proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false) =
|
proc endScope(self: Compiler) =
|
||||||
## Ends the current local scope
|
## Ends the current local scope
|
||||||
if self.scopeDepth < 0:
|
if self.scopeDepth < 0:
|
||||||
self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)")
|
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:
|
for name in self.names:
|
||||||
if name.depth > self.scopeDepth:
|
if name.depth > self.scopeDepth:
|
||||||
names.add(name)
|
names.add(name)
|
||||||
if not self.enableOptimizations and not fromFunc:
|
if len(names) > 1:
|
||||||
# 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 we're popping less than 65535 variables, then
|
# If we're popping less than 65535 variables, then
|
||||||
# we can emit a PopN instruction. This is true for
|
# we can emit a PopN instruction. This is true for
|
||||||
# 99.99999% of the use cases of the language (who the
|
# 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()):
|
for i in countdown(self.names.high(), len(names) - uint16.high().int()):
|
||||||
if self.names[i].depth > self.scopeDepth:
|
if self.names[i].depth > self.scopeDepth:
|
||||||
self.emitByte(PopC)
|
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
|
# We only emit PopN if we're popping more than one value
|
||||||
self.emitByte(PopC)
|
self.emitByte(PopC)
|
||||||
# This seems *really* slow, but
|
# This seems *really* slow, but
|
||||||
# what else should I do? Nim doesn't
|
# what else should I do? Nim doesn't
|
||||||
# allow the removal of items during
|
# allow the removal of items during
|
||||||
# seq iteration so ¯\_(ツ)_/¯
|
# seq iteration so ¯\_(ツ)_/¯
|
||||||
if deleteNames:
|
var idx = 0
|
||||||
var idx = 0
|
while idx < self.names.len():
|
||||||
while idx < self.names.len():
|
for name in names:
|
||||||
for name in names:
|
if self.names[idx] == name:
|
||||||
if self.names[idx] == name:
|
self.names.delete(idx)
|
||||||
self.names.delete(idx)
|
inc(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)
|
|
||||||
|
|
||||||
|
|
||||||
proc blockStmt(self: Compiler, node: BlockStmt) =
|
proc blockStmt(self: Compiler, node: BlockStmt) =
|
||||||
## Compiles block statements, which create a new
|
## Compiles block statements, which create a new
|
||||||
## local scope.
|
## local scope
|
||||||
self.beginScope()
|
self.beginScope()
|
||||||
for decl in node.code:
|
for decl in node.code:
|
||||||
self.declaration(decl)
|
self.declaration(decl)
|
||||||
|
@ -1270,25 +1243,13 @@ proc ifStmt(self: Compiler, node: IfStmt) =
|
||||||
## Compiles if/else statements for conditional
|
## Compiles if/else statements for conditional
|
||||||
## execution of code
|
## execution of code
|
||||||
var cond = self.inferType(node.condition)
|
var cond = self.inferType(node.condition)
|
||||||
|
self.expression(node.condition)
|
||||||
if not self.compareTypes(cond, Type(kind: Bool)):
|
if not self.compareTypes(cond, Type(kind: Bool)):
|
||||||
if cond.isNil():
|
if cond.isNil():
|
||||||
if node.condition.kind == identExpr:
|
self.error(&"expecting value of type 'bool', but expression has no type")
|
||||||
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")
|
|
||||||
else:
|
else:
|
||||||
self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead")
|
self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead")
|
||||||
self.expression(node.condition)
|
let jump = self.emitJump(JumpIfFalsePop)
|
||||||
var jumpCode: OpCode
|
|
||||||
if self.enableOptimizations:
|
|
||||||
jumpCode = JumpIfFalsePop
|
|
||||||
else:
|
|
||||||
jumpCode = JumpIfFalse
|
|
||||||
let jump = self.emitJump(jumpCode)
|
|
||||||
if not self.enableOptimizations:
|
|
||||||
self.emitByte(Pop)
|
|
||||||
self.statement(node.thenBranch)
|
self.statement(node.thenBranch)
|
||||||
let jump2 = self.emitJump(JumpForwards)
|
let jump2 = self.emitJump(JumpForwards)
|
||||||
self.patchJump(jump)
|
self.patchJump(jump)
|
||||||
|
@ -1310,51 +1271,23 @@ proc emitLoop(self: Compiler, begin: int) =
|
||||||
proc whileStmt(self: Compiler, node: WhileStmt) =
|
proc whileStmt(self: Compiler, node: WhileStmt) =
|
||||||
## Compiles C-style while loops and
|
## Compiles C-style while loops and
|
||||||
## desugared C-style for loops
|
## desugared C-style for loops
|
||||||
let start = self.chunk.code.len()
|
var cond = self.inferType(node.condition)
|
||||||
self.expression(node.condition)
|
self.expression(node.condition)
|
||||||
var jump: int
|
if not self.compareTypes(cond, Type(kind: Bool)):
|
||||||
if self.enableOptimizations:
|
if cond.isNil():
|
||||||
jump = self.emitJump(JumpIfFalsePop)
|
self.error(&"expecting value of type 'bool', but expression has no type")
|
||||||
else:
|
else:
|
||||||
jump = self.emitJump(JumpIfFalse)
|
self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead")
|
||||||
self.emitByte(Pop)
|
let start = self.chunk.code.len()
|
||||||
|
var jump = self.emitJump(JumpIfFalsePop)
|
||||||
self.statement(node.body)
|
self.statement(node.body)
|
||||||
self.patchJump(jump)
|
self.patchJump(jump)
|
||||||
self.emitLoop(start)
|
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 =
|
proc checkCallIsPure(self: Compiler, node: ASTnode): bool =
|
||||||
## Checks if a call has any side effects
|
## Checks if a call has any side effects
|
||||||
if not self.isPure(node):
|
return true # TODO
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
proc callExpr(self: Compiler, node: CallExpr) =
|
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 args: seq[tuple[name: string, kind: Type]] = @[]
|
||||||
var argExpr: seq[Expression] = @[]
|
var argExpr: seq[Expression] = @[]
|
||||||
var kind: Type
|
var kind: Type
|
||||||
var strictMutable = true
|
|
||||||
# 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)
|
||||||
|
@ -1370,8 +1302,6 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
||||||
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")
|
||||||
if kind.mutable:
|
|
||||||
strictMutable = false
|
|
||||||
args.add(("", kind))
|
args.add(("", kind))
|
||||||
argExpr.add(argument)
|
argExpr.add(argument)
|
||||||
for argument in node.arguments.keyword:
|
for argument in node.arguments.keyword:
|
||||||
|
@ -1381,7 +1311,7 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
||||||
var funct: Name
|
var funct: Name
|
||||||
case node.callee.kind:
|
case node.callee.kind:
|
||||||
of identExpr:
|
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:
|
of NodeKind.callExpr:
|
||||||
var node = node.callee
|
var node = node.callee
|
||||||
while node.kind == callExpr:
|
while node.kind == callExpr:
|
||||||
|
@ -1394,13 +1324,15 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
||||||
self.error(&"expression has no type")
|
self.error(&"expression has no type")
|
||||||
else:
|
else:
|
||||||
self.error(&"object of type '{self.typeToStr(typ)}' is not callable")
|
self.error(&"object of type '{self.typeToStr(typ)}' is not callable")
|
||||||
if not funct.isNil():
|
if any(funct.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic):
|
||||||
if funct.valueType.isBuiltinFunction:
|
# The function has generic arguments! We need to compile a version
|
||||||
self.handleBuiltinFunction(funct, argExpr)
|
# of it with the right type data
|
||||||
else:
|
self.funDecl(nil, funct, argExpr)
|
||||||
self.generateCall(funct, argExpr)
|
# TODO: What next?
|
||||||
|
elif funct.valueType.isBuiltinFunction:
|
||||||
|
self.handleBuiltinFunction(funct, argExpr)
|
||||||
else:
|
else:
|
||||||
self.generateObjCall(argExpr)
|
self.generateCall(funct, argExpr)
|
||||||
if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee):
|
if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee):
|
||||||
if self.currentFunction.name != "":
|
if self.currentFunction.name != "":
|
||||||
self.error(&"cannot make sure that calls to '{self.currentFunction.name}' are side-effect free")
|
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
|
## its return instruction
|
||||||
var popped = 0
|
var popped = 0
|
||||||
for name in self.names:
|
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)
|
inc(popped)
|
||||||
if self.enableOptimizations and popped > 1:
|
if popped > 1:
|
||||||
self.emitByte(PopN)
|
self.emitByte(PopN)
|
||||||
self.emitBytes(popped.toDouble())
|
self.emitBytes(popped.toDouble())
|
||||||
dec(popped, uint16.high().int)
|
dec(popped, uint16.high().int)
|
||||||
|
@ -1491,17 +1423,19 @@ proc endFunctionBeforeReturn(self: Compiler) =
|
||||||
proc returnStmt(self: Compiler, node: ReturnStmt) =
|
proc returnStmt(self: Compiler, node: ReturnStmt) =
|
||||||
## Compiles return statements
|
## Compiles return statements
|
||||||
let actual = self.inferType(node.value)
|
let actual = self.inferType(node.value)
|
||||||
let expected = self.currentFunction
|
var expected = self.currentFunction.returnType
|
||||||
if actual.isNil() and not expected.returnType.isNil():
|
if not expected.isNil() and expected.kind == Generic:
|
||||||
|
expected = actual
|
||||||
|
if actual.isNil() and not expected.isNil():
|
||||||
if not node.value.isNil():
|
if not node.value.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}'")
|
||||||
elif node.value.kind == callExpr and CallExpr(node.value).callee.kind == identExpr:
|
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(&"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")
|
self.error(&"expected return value of type '{self.typeToStr(expected)}', but expression has no type")
|
||||||
elif expected.returnType.isNil() and not actual.isNil():
|
elif expected.isNil() and not actual.isNil():
|
||||||
self.error("non-empty return statement is not allowed in void functions")
|
self.error("empty return statement is only allowed in void functions")
|
||||||
elif not self.compareTypes(actual, expected.returnType):
|
elif not self.compareTypes(actual, expected):
|
||||||
self.error(&"expected return value of type '{self.typeToStr(expected)}', got '{self.typeToStr(actual)}' instead")
|
self.error(&"expected return value of type '{self.typeToStr(expected)}', got '{self.typeToStr(actual)}' instead")
|
||||||
if not node.value.isNil():
|
if not node.value.isNil():
|
||||||
self.expression(node.value)
|
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.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 == TokenType.Var)
|
self.declareName(node, mutable=node.token.kind == TokenType.Var)
|
||||||
|
var r = self.names[^1]
|
||||||
|
self.detectClosureVariable(r, decl=true)
|
||||||
self.emitByte(StoreVar)
|
self.emitByte(StoreVar)
|
||||||
self.emitBytes(self.names.len().toTriple())
|
self.emitBytes((self.getStackPos(r.name) + 1).toTriple())
|
||||||
|
|
||||||
|
|
||||||
proc typeDecl(self: Compiler, node: TypeDecl) =
|
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 =
|
proc fixGenericFunc(self: Compiler, name: Name, args: seq[Expression]): Type =
|
||||||
## Specializes generic arguments in functions
|
## Specializes generic arguments in functions
|
||||||
var fn = name.valueType
|
var fn = name.valueType.deepCopy()
|
||||||
result = fn.deepCopy()
|
result = fn
|
||||||
var node = fn.fun
|
var typ: Type
|
||||||
for i in 0..args.high():
|
for i in 0..args.high():
|
||||||
if fn.args[i].kind.kind == Generic:
|
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] = @[]) =
|
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)
|
self.dispatchPragmas(node)
|
||||||
var node = node
|
var node = node
|
||||||
var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn
|
var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn
|
||||||
if fn.valueType.returnType.isNil():
|
if fn.valueType.isBuiltinFunction:
|
||||||
self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'")
|
# We take the arguments off of our name list
|
||||||
if not fn.valueType.isBuiltinFunction:
|
# 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 function = self.currentFunction
|
||||||
var jmp: int
|
var jmp: int
|
||||||
# Builtin functions map to a single
|
# 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()
|
fn.codePos = self.chunk.code.len()
|
||||||
# We store the current function
|
# We store the current function
|
||||||
self.currentFunction = fn.valueType
|
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():
|
if node.isNil():
|
||||||
# We got called back with more specific type
|
# We got called back with more specific type
|
||||||
# arguments: time to fix them!
|
# 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
|
hasVal = LambdaExpr(Declaration(self.currentFunction.fun)).hasExplicitReturn
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
if hasVal and self.currentFunction.returnType.isNil() and not typ.returnType.isNil():
|
if not hasVal and not typ.isNil():
|
||||||
self.error("non-empty return statement is not allowed in void functions")
|
# There is no explicit return statement anywhere in the function's
|
||||||
elif not hasVal and not self.currentFunction.returnType.isNil():
|
# 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.error("function has an explicit return type, but no return statement was found")
|
||||||
self.endFunctionBeforeReturn()
|
|
||||||
hasVal = hasVal and not typ.isNil()
|
hasVal = hasVal and not typ.isNil()
|
||||||
self.endScope(deleteNames=true, fromFunc=true)
|
self.endScope()
|
||||||
# Terminates the function's context
|
# Terminates the function's context
|
||||||
self.emitByte(OpCode.Return)
|
self.emitByte(OpCode.Return)
|
||||||
if hasVal:
|
if hasVal:
|
||||||
|
@ -1895,15 +1834,16 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk =
|
||||||
self.names.add(main)
|
self.names.add(main)
|
||||||
self.emitByte(LoadFunction)
|
self.emitByte(LoadFunction)
|
||||||
self.emitBytes(main.codePos.toTriple())
|
self.emitBytes(main.codePos.toTriple())
|
||||||
|
|
||||||
self.emitByte(LoadReturnAddress)
|
self.emitByte(LoadReturnAddress)
|
||||||
let pos = self.chunk.code.len()
|
let pos = self.chunk.code.len()
|
||||||
self.emitBytes(0.toQuad())
|
self.emitBytes(0.toQuad())
|
||||||
self.emitByte(Call)
|
self.emitByte(Call)
|
||||||
self.emitBytes(2.toTriple())
|
self.emitBytes(0.toTriple())
|
||||||
while not self.done():
|
while not self.done():
|
||||||
self.declaration(Declaration(self.step()))
|
self.declaration(Declaration(self.step()))
|
||||||
self.endScope(fromFunc=true)
|
self.endFunctionBeforeReturn()
|
||||||
self.patchReturnAddress(pos)
|
self.patchReturnAddress(pos)
|
||||||
self.emitByte(OpCode.Return)
|
self.emitByte(OpCode.Return)
|
||||||
self.emitByte(0)
|
self.emitByte(0) # Entry point has no return value
|
||||||
result = self.chunk
|
result = self.chunk
|
||||||
|
|
|
@ -138,15 +138,13 @@ type
|
||||||
## Basic stack operations
|
## Basic stack operations
|
||||||
Pop, # Pops an element off the stack and discards it
|
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)
|
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 call stack (optimization for exiting local scopes which usually pop many elements)
|
||||||
PopN, # Pops x elements off the stack (optimization for exiting local scopes which usually pop many elements)
|
|
||||||
## Name resolution/handling
|
## Name resolution/handling
|
||||||
LoadAttribute, # Pushes the attribute b of object a onto the stack
|
LoadAttribute, # Pushes the attribute b of object a onto the stack
|
||||||
LoadVar, # Pushes the object at position x in the stack 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
|
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
|
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
|
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
|
## Looping and jumping
|
||||||
Jump, # Absolute, unconditional jump into the bytecode
|
Jump, # Absolute, unconditional jump into the bytecode
|
||||||
JumpForwards, # Relative, unconditional, positive jump in the bytecode
|
JumpForwards, # Relative, unconditional, positive jump in the bytecode
|
||||||
|
@ -168,10 +166,8 @@ type
|
||||||
## Coroutines
|
## Coroutines
|
||||||
Await, # Calls an asynchronous function
|
Await, # Calls an asynchronous function
|
||||||
## Misc
|
## Misc
|
||||||
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, # 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
|
|
||||||
PopC, # Pop off the call stack onto the operand stack
|
PopC, # Pop off the call stack onto the operand stack
|
||||||
PushC # Pop off the operand stack onto the call stack
|
PushC # Pop off the operand stack onto the call stack
|
||||||
|
|
||||||
|
@ -185,7 +181,7 @@ const simpleInstructions* = {Return, LoadNil,
|
||||||
Pop, PopRepl, Raise,
|
Pop, PopRepl, Raise,
|
||||||
BeginTry, FinishTry, Yield,
|
BeginTry, FinishTry, Yield,
|
||||||
Await, NoOp, SetResult,
|
Await, NoOp, SetResult,
|
||||||
LoadArgument, PopC, PushC,
|
PopC, PushC,
|
||||||
AddInt64, AddUInt64, AddInt32,
|
AddInt64, AddUInt64, AddInt32,
|
||||||
AddUInt32, AddInt16, AddUInt16,
|
AddUInt32, AddInt16, AddUInt16,
|
||||||
AddInt8, AddUInt8, SubInt64,
|
AddInt8, AddUInt8, SubInt64,
|
||||||
|
@ -222,7 +218,7 @@ const stackDoubleInstructions* = {}
|
||||||
const argumentDoubleInstructions* = {PopN, }
|
const argumentDoubleInstructions* = {PopN, }
|
||||||
|
|
||||||
# Argument double argument instructions take hardcoded arguments as 24 bit integers
|
# Argument double argument instructions take hardcoded arguments as 24 bit integers
|
||||||
const argumentTripleInstructions* = {LoadFunctionObj, StoreClosure, PopClosure}
|
const argumentTripleInstructions* = {StoreClosure}
|
||||||
|
|
||||||
# Instructions that call functions
|
# Instructions that call functions
|
||||||
const callInstructions* = {Call, }
|
const callInstructions* = {Call, }
|
||||||
|
|
|
@ -314,7 +314,7 @@ proc primary(self: Parser): Expression =
|
||||||
self.expect(RightParen, "unterminated parenthesized expression")
|
self.expect(RightParen, "unterminated parenthesized expression")
|
||||||
of Yield:
|
of Yield:
|
||||||
let tok = self.step()
|
let tok = self.step()
|
||||||
if self.currentFunction == nil:
|
if self.currentFunction.isNil():
|
||||||
self.error("'yield' cannot be used outside functions")
|
self.error("'yield' cannot be used outside functions")
|
||||||
elif self.currentFunction.token.kind != Generator:
|
elif self.currentFunction.token.kind != Generator:
|
||||||
# It's easier than doing conversions for lambda/funDecl
|
# It's easier than doing conversions for lambda/funDecl
|
||||||
|
@ -327,7 +327,7 @@ proc primary(self: Parser): Expression =
|
||||||
result = newYieldExpr(newNilExpr(Token()), tok)
|
result = newYieldExpr(newNilExpr(Token()), tok)
|
||||||
of Await:
|
of Await:
|
||||||
let tok = self.step()
|
let tok = self.step()
|
||||||
if self.currentFunction == nil:
|
if self.currentFunction.isNil():
|
||||||
self.error("'await' cannot be used outside functions")
|
self.error("'await' cannot be used outside functions")
|
||||||
if self.currentFunction.token.kind != Coroutine:
|
if self.currentFunction.token.kind != Coroutine:
|
||||||
self.error("'await' can only be used inside coroutines")
|
self.error("'await' can only be used inside coroutines")
|
||||||
|
@ -549,7 +549,7 @@ proc blockStmt(self: Parser): Statement =
|
||||||
var code: seq[Declaration] = @[]
|
var code: seq[Declaration] = @[]
|
||||||
while not self.check(RightBrace) and not self.done():
|
while not self.check(RightBrace) and not self.done():
|
||||||
code.add(self.declaration())
|
code.add(self.declaration())
|
||||||
if code[^1] == nil:
|
if code[^1].isNil():
|
||||||
code.delete(code.high())
|
code.delete(code.high())
|
||||||
self.expect(RightBrace, "expecting '}'")
|
self.expect(RightBrace, "expecting '}'")
|
||||||
result = newBlockStmt(code, tok)
|
result = newBlockStmt(code, tok)
|
||||||
|
@ -568,7 +568,7 @@ proc breakStmt(self: Parser): Statement =
|
||||||
proc deferStmt(self: Parser): Statement =
|
proc deferStmt(self: Parser): Statement =
|
||||||
## Parses defer statements
|
## Parses defer statements
|
||||||
let tok = self.peek(-1)
|
let tok = self.peek(-1)
|
||||||
if self.currentFunction == nil:
|
if self.currentFunction.isNil():
|
||||||
self.error("'defer' cannot be used outside functions")
|
self.error("'defer' cannot be used outside functions")
|
||||||
result = newDeferStmt(self.expression(), tok)
|
result = newDeferStmt(self.expression(), tok)
|
||||||
endOfLine("missing semicolon after defer statement")
|
endOfLine("missing semicolon after defer statement")
|
||||||
|
@ -586,7 +586,7 @@ proc continueStmt(self: Parser): Statement =
|
||||||
proc returnStmt(self: Parser): Statement =
|
proc returnStmt(self: Parser): Statement =
|
||||||
## Parses return statements
|
## Parses return statements
|
||||||
let tok = self.peek(-1)
|
let tok = self.peek(-1)
|
||||||
if self.currentFunction == nil:
|
if self.currentFunction.isNil():
|
||||||
self.error("'return' cannot be used outside functions")
|
self.error("'return' cannot be used outside functions")
|
||||||
var value: Expression
|
var value: Expression
|
||||||
if not self.check(Semicolon):
|
if not self.check(Semicolon):
|
||||||
|
@ -606,7 +606,7 @@ proc returnStmt(self: Parser): Statement =
|
||||||
proc yieldStmt(self: Parser): Statement =
|
proc yieldStmt(self: Parser): Statement =
|
||||||
## Parses yield statements
|
## Parses yield statements
|
||||||
let tok = self.peek(-1)
|
let tok = self.peek(-1)
|
||||||
if self.currentFunction == nil:
|
if self.currentFunction.isNil():
|
||||||
self.error("'yield' cannot be outside functions")
|
self.error("'yield' cannot be outside functions")
|
||||||
elif self.currentFunction.token.kind != Generator:
|
elif self.currentFunction.token.kind != Generator:
|
||||||
self.error("'yield' can only be used inside generators")
|
self.error("'yield' can only be used inside generators")
|
||||||
|
@ -620,7 +620,7 @@ proc yieldStmt(self: Parser): Statement =
|
||||||
proc awaitStmt(self: Parser): Statement =
|
proc awaitStmt(self: Parser): Statement =
|
||||||
## Parses await statements
|
## Parses await statements
|
||||||
let tok = self.peek(-1)
|
let tok = self.peek(-1)
|
||||||
if self.currentFunction == nil:
|
if self.currentFunction.isNil():
|
||||||
self.error("'await' cannot be used outside functions")
|
self.error("'await' cannot be used outside functions")
|
||||||
if self.currentFunction.token.kind != Coroutine:
|
if self.currentFunction.token.kind != Coroutine:
|
||||||
self.error("'await' can only be used inside coroutines")
|
self.error("'await' can only be used inside coroutines")
|
||||||
|
@ -690,10 +690,10 @@ proc tryStmt(self: Parser): Statement =
|
||||||
elseClause = self.statement()
|
elseClause = self.statement()
|
||||||
if self.match(Finally):
|
if self.match(Finally):
|
||||||
finallyClause = self.statement()
|
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")
|
self.error("expecting 'except', 'finally' or 'else' statement after 'try' block")
|
||||||
for i, handler in handlers:
|
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")
|
self.error("catch-all exception handler with bare 'except' must come last in try statement")
|
||||||
result = newTryStmt(body, handlers, finallyClause, elseClause, tok)
|
result = newTryStmt(body, handlers, finallyClause, elseClause, tok)
|
||||||
|
|
||||||
|
@ -737,18 +737,18 @@ proc forStmt(self: Parser): Statement =
|
||||||
increment = self.expression()
|
increment = self.expression()
|
||||||
self.expect(RightParen, "unterminated for loop increment")
|
self.expect(RightParen, "unterminated for loop increment")
|
||||||
var body = self.statement()
|
var body = self.statement()
|
||||||
if increment != nil:
|
if not increment.isNil():
|
||||||
# The increment runs after each iteration, so we
|
# The increment runs after each iteration, so we
|
||||||
# inject it into the block as the last statement
|
# inject it into the block as the last statement
|
||||||
body = newBlockStmt(@[Declaration(body), newExprStmt(increment,
|
body = newBlockStmt(@[Declaration(body), newExprStmt(increment,
|
||||||
increment.token)], tok)
|
increment.token)], tok)
|
||||||
if condition == nil:
|
if condition.isNil():
|
||||||
## An empty condition is functionally
|
## An empty condition is functionally
|
||||||
## equivalent to "true"
|
## equivalent to "true"
|
||||||
condition = newTrueExpr(Token(lexeme: "true"))
|
condition = newTrueExpr(Token(lexeme: "true"))
|
||||||
# We can use a while loop, which in this case works just as well
|
# We can use a while loop, which in this case works just as well
|
||||||
body = newWhileStmt(condition, body, tok)
|
body = newWhileStmt(condition, body, tok)
|
||||||
if initializer != nil:
|
if not initializer.isNil():
|
||||||
# Nested blocks, so the initializer is
|
# Nested blocks, so the initializer is
|
||||||
# only executed once
|
# only executed once
|
||||||
body = newBlockStmt(@[Declaration(initializer), Declaration(body)], tok)
|
body = newBlockStmt(@[Declaration(initializer), Declaration(body)], tok)
|
||||||
|
@ -777,7 +777,7 @@ proc ifStmt(self: Parser): Statement =
|
||||||
var condition = self.expression()
|
var condition = self.expression()
|
||||||
self.expect(RightParen, "expecting ')' after if condition")
|
self.expect(RightParen, "expecting ')' after if condition")
|
||||||
var thenBranch = self.statement()
|
var thenBranch = self.statement()
|
||||||
var elseBranch: Statement = nil
|
var elseBranch: Statement
|
||||||
if self.match(Else):
|
if self.match(Else):
|
||||||
elseBranch = self.statement()
|
elseBranch = self.statement()
|
||||||
result = newIfStmt(condition, thenBranch, elseBranch, tok)
|
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")
|
self.error("cannot bind public names inside local scopes")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc parsePragmas(self: Parser): seq[Pragma] =
|
proc parsePragmas(self: Parser): seq[Pragma] =
|
||||||
## Parses pragmas
|
## Parses pragmas
|
||||||
var
|
var
|
||||||
|
@ -871,7 +870,7 @@ proc varDecl(self: Parser, isLet: bool = false,
|
||||||
isLet = isLet, valueType = valueType, pragmas = (@[]))
|
isLet = isLet, valueType = valueType, pragmas = (@[]))
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
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")
|
self.error("expecting initializer or explicit type declaration, but neither was found")
|
||||||
result.pragmas = pragmas
|
result.pragmas = pragmas
|
||||||
|
|
||||||
|
@ -904,7 +903,7 @@ proc parseDeclArguments(self: Parser, arguments: var seq[tuple[name: IdentExpr,
|
||||||
break
|
break
|
||||||
self.expect(RightParen)
|
self.expect(RightParen)
|
||||||
for argument in arguments:
|
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")
|
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"
|
styledEcho fgCyan, "Compilation step:\n"
|
||||||
debugger.disassembleChunk(compiled, f)
|
debugger.disassembleChunk(compiled, f)
|
||||||
echo ""
|
echo ""
|
||||||
|
var path = splitFile(f).dir
|
||||||
serializer.dumpFile(compiled, f, splitFile(f).dir & "/" & splitFile(f).name & ".pbc")
|
if path.len() > 0:
|
||||||
serialized = serializer.loadFile(splitFile(f).dir & "/" & splitFile(f).name & ".pbc")
|
path &= "/"
|
||||||
|
path &= splitFile(f).name & ".pbc"
|
||||||
|
serializer.dumpFile(compiled, f, path)
|
||||||
|
serialized = serializer.loadFile(path)
|
||||||
when debugSerializer:
|
when debugSerializer:
|
||||||
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
|
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
|
||||||
styledEcho fgCyan, "Serialization step: "
|
styledEcho fgCyan, "Serialization step: "
|
||||||
|
|
|
@ -63,8 +63,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
|
||||||
|
|
||||||
|
|
||||||
proc checkFrameStart(self: Debugger, n: int) =
|
proc checkFrameStart(self: Debugger, n: int) =
|
||||||
## Todo: Checking frame end offsets needs
|
## Checks if a call frame begins at the given
|
||||||
## fixes
|
## bytecode offset
|
||||||
for i, e in self.cfiData:
|
for i, e in self.cfiData:
|
||||||
if n == e.start and not (e.started or e.stopped):
|
if n == e.start and not (e.started or e.stopped):
|
||||||
e.started = true
|
e.started = true
|
||||||
|
@ -76,6 +76,8 @@ proc checkFrameStart(self: Debugger, n: int) =
|
||||||
|
|
||||||
|
|
||||||
proc checkFrameEnd(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:
|
for i, e in self.cfiData:
|
||||||
if n == e.stop and e.started and not e.stopped:
|
if n == e.stop and e.started and not e.stopped:
|
||||||
e.stopped = true
|
e.stopped = true
|
||||||
|
@ -83,6 +85,7 @@ proc checkFrameEnd(self: Debugger, n: int) =
|
||||||
|
|
||||||
|
|
||||||
proc simpleInstruction(self: Debugger, instruction: OpCode) =
|
proc simpleInstruction(self: Debugger, instruction: OpCode) =
|
||||||
|
## Debugs simple instructions
|
||||||
printInstruction(instruction, true)
|
printInstruction(instruction, true)
|
||||||
self.current += 1
|
self.current += 1
|
||||||
if instruction == Return:
|
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],
|
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()
|
self.chunk.code[self.current + 4]].fromQuad()
|
||||||
printInstruction(instruction)
|
printInstruction(instruction)
|
||||||
styledEcho fgGreen, &" loads address ", fgYellow, $address
|
styledEcho fgGreen, &", loads address ", fgYellow, $address
|
||||||
self.current += 5
|
self.current += 5
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue