Initial work on a two-stack design
This commit is contained in:
parent
f8ab292c27
commit
099f733db6
|
@ -25,7 +25,6 @@ A peon bytecode file starts with the header, which is structured as follows:
|
||||||
- The branch name of the repository the compiler was built from, prepended with its length as a 1 byte integer
|
- The branch name of the repository the compiler was built from, prepended with its length as a 1 byte integer
|
||||||
- The commit hash (encoded as a 40-byte hex-encoded string) in the aforementioned branch from which the compiler was built from (particularly useful in development builds)
|
- The commit hash (encoded as a 40-byte hex-encoded string) in the aforementioned branch from which the compiler was built from (particularly useful in development builds)
|
||||||
- An 8-byte UNIX timestamp (with Epoch 0 starting at 1/1/1970 12:00 AM) representing the exact date and time of when the file was generated
|
- An 8-byte UNIX timestamp (with Epoch 0 starting at 1/1/1970 12:00 AM) representing the exact date and time of when the file was generated
|
||||||
- A 32-byte, hex-encoded SHA256 hash of the source file's content, used to track file changes
|
|
||||||
|
|
||||||
## Debug information
|
## Debug information
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ type
|
||||||
PeonObject* = object
|
PeonObject* = object
|
||||||
## A generic Peon object
|
## A generic Peon object
|
||||||
case kind*: ObjectKind:
|
case kind*: ObjectKind:
|
||||||
|
of String:
|
||||||
|
str*: string
|
||||||
of Bool:
|
of Bool:
|
||||||
boolean*: bool
|
boolean*: bool
|
||||||
of Inf:
|
of Inf:
|
||||||
|
@ -50,5 +52,11 @@ type
|
||||||
discard
|
discard
|
||||||
of CustomType:
|
of CustomType:
|
||||||
fields*: seq[PeonObject]
|
fields*: seq[PeonObject]
|
||||||
|
of Float32:
|
||||||
|
halfFloat*: float32
|
||||||
|
of Float64:
|
||||||
|
`float`*: float
|
||||||
|
of Function:
|
||||||
|
ip*: uint32
|
||||||
else:
|
else:
|
||||||
discard # TODO
|
discard # TODO
|
||||||
|
|
|
@ -26,12 +26,14 @@ export types
|
||||||
type
|
type
|
||||||
PeonVM* = ref object
|
PeonVM* = ref object
|
||||||
## The Peon Virtual Machine
|
## The Peon Virtual Machine
|
||||||
stack: seq[PeonObject]
|
calls: seq[PeonObject] # Our call stack
|
||||||
ip: int # Instruction pointer
|
operands: seq[PeonObject] # Our operand stack
|
||||||
|
ip: uint32 # Instruction pointer
|
||||||
cache: array[6, PeonObject] # Singletons cache
|
cache: array[6, PeonObject] # Singletons cache
|
||||||
chunk: Chunk # Piece of bytecode to execute
|
chunk: Chunk # Piece of bytecode to execute
|
||||||
frames: seq[int] # Stores the bottom of stack frames
|
frames: seq[int] # Stores the bottom of stack frames
|
||||||
heapVars: seq[PeonObject] # Stores variables that do not have stack semantics
|
closedOver: seq[PeonObject] # Stores variables that do not have stack semantics
|
||||||
|
results: seq[PeonObject] # Stores function's results
|
||||||
|
|
||||||
|
|
||||||
proc initCache*(self: PeonVM) =
|
proc initCache*(self: PeonVM) =
|
||||||
|
@ -51,7 +53,8 @@ proc newPeonVM*: PeonVM =
|
||||||
new(result)
|
new(result)
|
||||||
result.ip = 0
|
result.ip = 0
|
||||||
result.frames = @[]
|
result.frames = @[]
|
||||||
result.stack = newSeq[PeonObject]()
|
result.calls = newSeq[PeonObject]()
|
||||||
|
result.operands = newSeq[PeonObject]()
|
||||||
result.initCache()
|
result.initCache()
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,38 +81,70 @@ proc getNan*(self: PeonVM): PeonObject {.inline.} = self.cache[5]
|
||||||
|
|
||||||
proc push(self: PeonVM, obj: PeonObject) =
|
proc push(self: PeonVM, obj: PeonObject) =
|
||||||
## Pushes a Peon object onto the
|
## Pushes a Peon object onto the
|
||||||
## stack
|
## operand stack
|
||||||
self.stack.add(obj)
|
self.operands.add(obj)
|
||||||
|
|
||||||
|
|
||||||
proc pop(self: PeonVM): PeonObject =
|
proc pop(self: PeonVM): PeonObject =
|
||||||
## Pops a Peon object off the
|
## Pops a Peon object off the
|
||||||
## stack, decreasing the stack
|
## operand stack. The object
|
||||||
## pointer. The object is returned
|
## is returned
|
||||||
return self.stack.pop()
|
return self.operands.pop()
|
||||||
|
|
||||||
|
|
||||||
proc peek(self: PeonVM): PeonObject =
|
proc peek(self: PeonVM, distance: int = 0): PeonObject =
|
||||||
## Returns the Peon object at the top
|
## Returns the Peon object at the
|
||||||
## of the stack without consuming
|
## given distance from the top of
|
||||||
## it
|
## the operand stack without consuming it
|
||||||
return self.stack[^1]
|
return self.operands[self.operands.high() + distance]
|
||||||
|
|
||||||
|
|
||||||
proc get(self: PeonVM, idx: int): PeonObject =
|
proc get(self: PeonVM, idx: int): PeonObject =
|
||||||
## Accessor method that abstracts
|
## Accessor method that abstracts
|
||||||
## stack indexing through stack
|
## indexing the through stack
|
||||||
## frames
|
## frames
|
||||||
return self.stack[idx + self.frames[^1]]
|
return self.operands[idx + self.frames[^1]]
|
||||||
|
|
||||||
|
|
||||||
proc set(self: PeonVM, idx: int, val: PeonObject) =
|
proc set(self: PeonVM, idx: int, val: PeonObject) =
|
||||||
## Setter method that abstracts
|
## Setter method that abstracts
|
||||||
## stack indexing through stack
|
## indexing through stack
|
||||||
## frames
|
## frames
|
||||||
self.stack[idx + self.frames[^1]] = val
|
self.operands[idx + self.frames[^1]] = val
|
||||||
|
|
||||||
|
|
||||||
|
proc pushc(self: PeonVM, val: PeonObject) =
|
||||||
|
## Pushes a new object to the
|
||||||
|
## call stack
|
||||||
|
self.calls.add(val)
|
||||||
|
|
||||||
|
|
||||||
|
proc popc(self: PeonVM): PeonObject =
|
||||||
|
## Pops an object off the call
|
||||||
|
## stack and returns it
|
||||||
|
return self.calls.pop()
|
||||||
|
|
||||||
|
|
||||||
|
proc peekc(self: PeonVM, distance: int = 0): PeonObject =
|
||||||
|
## Returns the Peon object at the
|
||||||
|
## given distance from the top of
|
||||||
|
## the call stack without consuming it
|
||||||
|
return self.calls[self.calls.high() + distance]
|
||||||
|
|
||||||
|
|
||||||
|
proc getc(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Accessor method that abstracts
|
||||||
|
## indexing our call stack through stack
|
||||||
|
## frames
|
||||||
|
return self.calls[idx + self.frames[^1]]
|
||||||
|
|
||||||
|
|
||||||
|
proc setc(self: PeonVM, idx: int, val: PeonObject) =
|
||||||
|
## Setter method that abstracts
|
||||||
|
## indexing our call stack through stack
|
||||||
|
## frames
|
||||||
|
self.calls[idx + self.frames[^1]] = val
|
||||||
|
|
||||||
## Byte-level primitives to read/decode
|
## Byte-level primitives to read/decode
|
||||||
## bytecode
|
## bytecode
|
||||||
|
|
||||||
|
@ -139,7 +174,15 @@ proc readLong(self: PeonVM): uint32 =
|
||||||
return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple())
|
return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple())
|
||||||
|
|
||||||
|
|
||||||
proc readInt64(self: PeonVM, idx: int): PeonObject =
|
proc readUInt(self: PeonVM): uint32 =
|
||||||
|
## Reads three bytes from the
|
||||||
|
## bytecode and returns them
|
||||||
|
## as an unsigned 32 bit
|
||||||
|
## integer
|
||||||
|
return uint32([self.readByte(), self.readByte(), self.readByte(), self.readByte()].fromQuad())
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadInt64(self: PeonVM, idx: int): PeonObject =
|
||||||
## Reads a constant from the
|
## Reads a constant from the
|
||||||
## chunk's constant table and
|
## chunk's constant table and
|
||||||
## returns a Peon object. Assumes
|
## returns a Peon object. Assumes
|
||||||
|
@ -153,7 +196,7 @@ proc readInt64(self: PeonVM, idx: int): PeonObject =
|
||||||
copyMem(result.long.addr, arr.addr, sizeof(arr))
|
copyMem(result.long.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
proc readUInt64(self: PeonVM, idx: int): PeonObject =
|
proc constReadUInt64(self: PeonVM, idx: int): PeonObject =
|
||||||
## Reads a constant from the
|
## Reads a constant from the
|
||||||
## chunk's constant table and
|
## chunk's constant table and
|
||||||
## returns a Peon object. Assumes
|
## returns a Peon object. Assumes
|
||||||
|
@ -167,7 +210,7 @@ proc readUInt64(self: PeonVM, idx: int): PeonObject =
|
||||||
copyMem(result.uLong.addr, arr.addr, sizeof(arr))
|
copyMem(result.uLong.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
proc readUInt32(self: PeonVM, idx: int): PeonObject =
|
proc constReadUInt32(self: PeonVM, idx: int): PeonObject =
|
||||||
## Reads a constant from the
|
## Reads a constant from the
|
||||||
## chunk's constant table and
|
## chunk's constant table and
|
||||||
## returns a Peon object. Assumes
|
## returns a Peon object. Assumes
|
||||||
|
@ -178,7 +221,7 @@ proc readUInt32(self: PeonVM, idx: int): PeonObject =
|
||||||
copyMem(result.uInt.addr, arr.addr, sizeof(arr))
|
copyMem(result.uInt.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
proc readInt32(self: PeonVM, idx: int): PeonObject =
|
proc constReadInt32(self: PeonVM, idx: int): PeonObject =
|
||||||
## Reads a constant from the
|
## Reads a constant from the
|
||||||
## chunk's constant table and
|
## chunk's constant table and
|
||||||
## returns a Peon object. Assumes
|
## returns a Peon object. Assumes
|
||||||
|
@ -189,18 +232,95 @@ proc readInt32(self: PeonVM, idx: int): PeonObject =
|
||||||
copyMem(result.`int`.addr, arr.addr, sizeof(arr))
|
copyMem(result.`int`.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadInt16(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Reads a constant from the
|
||||||
|
## chunk's constant table and
|
||||||
|
## returns a Peon object. Assumes
|
||||||
|
## the constant is an Int16
|
||||||
|
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]]
|
||||||
|
result = PeonObject(kind: Int16)
|
||||||
|
copyMem(result.short.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadUInt16(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Reads a constant from the
|
||||||
|
## chunk's constant table and
|
||||||
|
## returns a Peon object. Assumes
|
||||||
|
## the constant is an UInt16
|
||||||
|
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]]
|
||||||
|
result = PeonObject(kind: UInt16)
|
||||||
|
copyMem(result.uShort.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadInt8(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Reads a constant from the
|
||||||
|
## chunk's constant table and
|
||||||
|
## returns a Peon object. Assumes
|
||||||
|
## the constant is an Int8
|
||||||
|
result = PeonObject(kind: Int8, tiny: self.chunk.consts[idx])
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadUInt8(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Reads a constant from the
|
||||||
|
## chunk's constant table and
|
||||||
|
## returns a Peon object. Assumes
|
||||||
|
## the constant is an UInt8
|
||||||
|
result = PeonObject(kind: UInt8, uTiny: self.chunk.consts[idx])
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadString(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Reads a constant from the
|
||||||
|
## chunk's constant table and
|
||||||
|
## returns a Peon object. Assumes
|
||||||
|
## the constant is a string
|
||||||
|
let size = self.readLong()
|
||||||
|
result = PeonObject(kind: String, str: self.chunk.consts[idx..<size].fromBytes())
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadFloat32(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Reads a constant from the
|
||||||
|
## chunk's constant table and
|
||||||
|
## returns a Peon object. Assumes
|
||||||
|
## the constant is a Float32
|
||||||
|
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
|
||||||
|
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
|
||||||
|
result = PeonObject(kind: Float32)
|
||||||
|
copyMem(result.halfFloat.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
|
proc constReadFloat64(self: PeonVM, idx: int): PeonObject =
|
||||||
|
## Reads a constant from the
|
||||||
|
## chunk's constant table and
|
||||||
|
## returns a Peon object. Assumes
|
||||||
|
## the constant is a Float64
|
||||||
|
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
|
||||||
|
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
|
||||||
|
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
|
||||||
|
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7]]
|
||||||
|
result = PeonObject(kind: Float64)
|
||||||
|
copyMem(result.`float`.addr, arr.addr, sizeof(arr))
|
||||||
|
|
||||||
|
|
||||||
proc dispatch*(self: PeonVM) =
|
proc dispatch*(self: PeonVM) =
|
||||||
## Main bytecode dispatch loop
|
## Main bytecode dispatch loop
|
||||||
var instruction: OpCode
|
var instruction {.register.}: OpCode
|
||||||
while true:
|
while true:
|
||||||
instruction = OpCode(self.readByte())
|
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
echo &"IP: {self.ip}"
|
echo &"IP: {self.ip}"
|
||||||
echo &"Instruction: {instruction}"
|
echo &"Instruction: {OpCode(self.chunk.code[self.ip])}"
|
||||||
echo &"Stack: {self.stack}"
|
if self.calls.len() > 0:
|
||||||
echo &"Current Frame: {self.stack[self.frames[^1]..^1]}"
|
echo &"Call Stack: {self.calls}"
|
||||||
echo &"Heap Vars: {self.heapVars}"
|
if self.operands.len() > 0:
|
||||||
|
echo &"Operand Stack: {self.operands}"
|
||||||
|
if self.frames.len() > 0:
|
||||||
|
echo &"Current Frame: {self.calls[self.frames[^1]..^1]}"
|
||||||
|
echo &"Frames: {self.frames}"
|
||||||
|
if self.closedOver.len() > 0:
|
||||||
|
echo &"Closure Array: {self.closedOver}"
|
||||||
|
if self.results.len() > 0:
|
||||||
|
echo &"Results: {self.results}"
|
||||||
discard readLine stdin
|
discard readLine stdin
|
||||||
|
instruction = OpCode(self.readByte())
|
||||||
case instruction:
|
case instruction:
|
||||||
# Constant loading
|
# Constant loading
|
||||||
of LoadTrue:
|
of LoadTrue:
|
||||||
|
@ -214,73 +334,103 @@ proc dispatch*(self: PeonVM) =
|
||||||
of LoadInf:
|
of LoadInf:
|
||||||
self.push(self.getInf(true))
|
self.push(self.getInf(true))
|
||||||
of LoadInt64:
|
of LoadInt64:
|
||||||
self.push(self.readInt64(int(self.readLong())))
|
self.push(self.constReadInt64(int(self.readLong())))
|
||||||
of LoadUInt64:
|
of LoadUInt64:
|
||||||
self.push(self.readUInt64(int(self.readLong())))
|
self.push(self.constReadUInt64(int(self.readLong())))
|
||||||
of LoadUInt32:
|
of LoadUInt32:
|
||||||
self.push(self.readUInt32(int(self.readLong())))
|
self.push(self.constReadUInt32(int(self.readLong())))
|
||||||
of LoadInt32:
|
of LoadInt32:
|
||||||
self.push(self.readInt32(int(self.readLong())))
|
self.push(self.constReadInt32(int(self.readLong())))
|
||||||
|
of LoadInt16:
|
||||||
|
self.push(self.constReadInt16(int(self.readLong())))
|
||||||
|
of LoadUInt16:
|
||||||
|
self.push(self.constReadUInt16(int(self.readLong())))
|
||||||
|
of LoadInt8:
|
||||||
|
self.push(self.constReadInt8(int(self.readLong())))
|
||||||
|
of LoadUInt8:
|
||||||
|
self.push(self.constReadUInt8(int(self.readLong())))
|
||||||
|
of LoadString:
|
||||||
|
self.push(self.constReadString(int(self.readLong())))
|
||||||
|
of LoadFloat32:
|
||||||
|
self.push(self.constReadFloat32(int(self.readLong())))
|
||||||
|
of LoadFloat64:
|
||||||
|
self.push(self.constReadFloat64(int(self.readLong())))
|
||||||
|
of LoadFunction:
|
||||||
|
self.pushc(PeonObject(kind: Function, ip: self.readLong()))
|
||||||
|
of LoadReturnAddress:
|
||||||
|
self.pushc(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 return address sits
|
# functions is pretty simple: the first item in the
|
||||||
# at the bottom of the stack frame, then follow the
|
# frame is a function object which contains the new
|
||||||
# arguments and all temporaries/local variables
|
# instruction pointer to jump to, followed by a 32-bit
|
||||||
let newIp = self.readLong()
|
# return address. After that, all arguments and locals
|
||||||
# We store it because if we immediately changed
|
# follow
|
||||||
# the instruction pointer, we'd read the wrong
|
var size {.used.} = self.readLong().int
|
||||||
# value for the argument count. Storing it and
|
self.frames.add(self.calls.len() - 2)
|
||||||
# changing it later fixes this issue
|
self.ip = self.peekc(-1).ip
|
||||||
self.frames.add(int(self.readLong()))
|
self.results.add(self.getNil())
|
||||||
self.ip = int(newIp)
|
# TODO: Use the frame size once
|
||||||
|
# we have more control over the
|
||||||
|
# memory
|
||||||
|
#[while size > 0:
|
||||||
|
dec(size)
|
||||||
|
self.pushc(self.getNil())
|
||||||
|
]#
|
||||||
|
of LoadArgument:
|
||||||
|
self.pushc(self.pop())
|
||||||
of OpCode.Return:
|
of OpCode.Return:
|
||||||
# Returns from a void function
|
# Returns from a function.
|
||||||
let frame = self.frames.pop()
|
# Every peon program is wrapped
|
||||||
for i in 0..<frame - 1:
|
# in a hidden function, so this
|
||||||
discard self.pop()
|
# will also exit the VM if we're
|
||||||
self.ip = self.pop().uInt.int
|
# at the end of the program
|
||||||
discard self.pop() # Nil
|
let ret = self.popc()
|
||||||
of ProgExit:
|
discard self.popc() # Function object
|
||||||
# Exits the VM's loop
|
if self.readByte() == 1:
|
||||||
while self.stack.len() > 0:
|
# Function is non-void!
|
||||||
discard self.pop()
|
self.push(self.results.pop())
|
||||||
|
else:
|
||||||
|
discard self.results.pop()
|
||||||
discard self.frames.pop()
|
discard self.frames.pop()
|
||||||
return
|
if self.frames.len() == 0:
|
||||||
of ReturnValue:
|
# End of the program!
|
||||||
# Returns from a function which has a return value,
|
return
|
||||||
# pushing it on the stack
|
self.ip = ret.uInt
|
||||||
let retVal = self.pop()
|
of SetResult:
|
||||||
let frame = self.frames.pop()
|
# Sets the result of the
|
||||||
for i in 0..<frame:
|
# current function
|
||||||
discard self.pop()
|
self.results[self.frames.high()] = self.pop()
|
||||||
self.ip = int(self.pop().uInt)
|
|
||||||
discard self.pop() # Nil
|
|
||||||
self.push(retVal)
|
|
||||||
of StoreVar:
|
of StoreVar:
|
||||||
# Stores the value at the top of the stack
|
# Stores the value at the top of the operand stack
|
||||||
# into the given stack index
|
# into the given call stack index
|
||||||
self.set(int(self.readLong()), self.pop())
|
let idx = int(self.readLong())
|
||||||
|
if idx <= self.calls.high():
|
||||||
|
self.setc(idx, self.pop())
|
||||||
|
else:
|
||||||
|
self.calls.add(self.pop())
|
||||||
of StoreClosure:
|
of StoreClosure:
|
||||||
# Stores/updates the value of a closed-over
|
# Stores/updates the value of a closed-over
|
||||||
# variable
|
# variable
|
||||||
let idx = self.readLong().int
|
let idx = self.readLong().int
|
||||||
if idx > self.heapVars.high():
|
if idx > self.closedOver.high():
|
||||||
self.heapVars.add(self.pop())
|
self.closedOver.add(self.pop())
|
||||||
else:
|
else:
|
||||||
self.heapVars[idx] = self.pop()
|
self.closedOver[idx] = self.pop()
|
||||||
of LoadClosure:
|
of LoadClosure:
|
||||||
# Loads a closed-over variable onto the
|
# Loads a closed-over variable onto the
|
||||||
# stack
|
# stack
|
||||||
self.push(self.heapVars[self.readLong()])
|
self.push(self.closedOver[self.readLong()])
|
||||||
of PopClosure:
|
of PopClosure:
|
||||||
# Pops a closed-over variable off the closure
|
# Pops a closed-over variable off the closure
|
||||||
# array
|
# array
|
||||||
discard self.heapVars.pop()
|
discard self.closedOver.pop()
|
||||||
of LoadVar:
|
of LoadVar:
|
||||||
# Stores/updates the value of a local variable
|
self.push(self.getc(int(self.readLong())))
|
||||||
self.push(self.get(int(self.readLong())))
|
|
||||||
of NoOp:
|
of NoOp:
|
||||||
continue
|
continue
|
||||||
|
of PopC:
|
||||||
|
discard self.popc()
|
||||||
of Pop:
|
of Pop:
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
of PopRepl:
|
of PopRepl:
|
||||||
|
@ -302,6 +452,10 @@ proc dispatch*(self: PeonVM) =
|
||||||
echo &"{popped.tiny}'i8"
|
echo &"{popped.tiny}'i8"
|
||||||
of UInt8:
|
of UInt8:
|
||||||
echo &"{popped.uTiny}'u8"
|
echo &"{popped.uTiny}'u8"
|
||||||
|
of Float32:
|
||||||
|
echo &"{popped.halfFloat}'f32"
|
||||||
|
of Float64:
|
||||||
|
echo &"{popped.`float`}'f64"
|
||||||
of ObjectKind.Inf:
|
of ObjectKind.Inf:
|
||||||
if popped.positive:
|
if popped.positive:
|
||||||
echo "inf"
|
echo "inf"
|
||||||
|
@ -313,45 +467,45 @@ proc dispatch*(self: PeonVM) =
|
||||||
discard
|
discard
|
||||||
of PopN:
|
of PopN:
|
||||||
for _ in 0..<int(self.readShort()):
|
for _ in 0..<int(self.readShort()):
|
||||||
discard self.pop()
|
discard self.popc()
|
||||||
# Jump opcodes
|
# Jump opcodes
|
||||||
of Jump:
|
of Jump:
|
||||||
self.ip = int(self.readShort())
|
self.ip = self.readShort()
|
||||||
of JumpForwards:
|
of JumpForwards:
|
||||||
self.ip += int(self.readShort())
|
self.ip += self.readShort()
|
||||||
of JumpBackwards:
|
of JumpBackwards:
|
||||||
self.ip -= int(self.readShort())
|
self.ip -= self.readShort()
|
||||||
of JumpIfFalse:
|
of JumpIfFalse:
|
||||||
if not self.peek().boolean:
|
if not self.peek().boolean:
|
||||||
self.ip += int(self.readShort())
|
self.ip += self.readShort()
|
||||||
of JumpIfTrue:
|
of JumpIfTrue:
|
||||||
if self.peek().boolean:
|
if self.peek().boolean:
|
||||||
self.ip += int(self.readShort())
|
self.ip += self.readShort()
|
||||||
of JumpIfFalsePop:
|
of JumpIfFalsePop:
|
||||||
if not self.peek().boolean:
|
if not self.peek().boolean:
|
||||||
self.ip += int(self.readShort())
|
self.ip += self.readShort()
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
of JumpIfFalseOrPop:
|
of JumpIfFalseOrPop:
|
||||||
if not self.peek().boolean:
|
if not self.peek().boolean:
|
||||||
self.ip += int(self.readShort())
|
self.ip += self.readShort()
|
||||||
else:
|
else:
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
of LongJumpIfFalse:
|
of LongJumpIfFalse:
|
||||||
if not self.peek().boolean:
|
if not self.peek().boolean:
|
||||||
self.ip += int(self.readLong())
|
self.ip += self.readLong()
|
||||||
of LongJumpIfFalsePop:
|
of LongJumpIfFalsePop:
|
||||||
if not self.peek().boolean:
|
if not self.peek().boolean:
|
||||||
self.ip += int(self.readLong())
|
self.ip += self.readLong()
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
of LongJumpForwards:
|
of LongJumpForwards:
|
||||||
self.ip += int(self.readLong())
|
self.ip += self.readLong()
|
||||||
of LongJumpBackwards:
|
of LongJumpBackwards:
|
||||||
self.ip -= int(self.readLong())
|
self.ip -= self.readLong()
|
||||||
of LongJump:
|
of LongJump:
|
||||||
self.ip = int(self.readLong())
|
self.ip = self.readLong()
|
||||||
of LongJumpIfFalseOrPop:
|
of LongJumpIfFalseOrPop:
|
||||||
if not self.peek().boolean:
|
if not self.peek().boolean:
|
||||||
self.ip += int(self.readLong())
|
self.ip += self.readLong()
|
||||||
else:
|
else:
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
else:
|
else:
|
||||||
|
@ -361,7 +515,8 @@ proc dispatch*(self: PeonVM) =
|
||||||
proc run*(self: PeonVM, chunk: Chunk) =
|
proc run*(self: PeonVM, chunk: Chunk) =
|
||||||
## Executes a piece of Peon bytecode.
|
## Executes a piece of Peon bytecode.
|
||||||
self.chunk = chunk
|
self.chunk = chunk
|
||||||
self.frames = @[0]
|
self.frames = @[]
|
||||||
self.stack = @[]
|
self.calls = @[]
|
||||||
|
self.operands = @[]
|
||||||
self.ip = 0
|
self.ip = 0
|
||||||
self.dispatch()
|
self.dispatch()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -90,6 +90,8 @@ type
|
||||||
codePos: int
|
codePos: int
|
||||||
# Is the name closed over (i.e. used in a closure)?
|
# Is the name closed over (i.e. used in a closure)?
|
||||||
isClosedOver: bool
|
isClosedOver: bool
|
||||||
|
# Is this a function argument?
|
||||||
|
isFunctionArgument: bool
|
||||||
# Where is this node declared in the file?
|
# Where is this node declared in the file?
|
||||||
line: int
|
line: int
|
||||||
Loop = object
|
Loop = object
|
||||||
|
@ -123,8 +125,6 @@ type
|
||||||
# runtime to load variables that have stack
|
# runtime to load variables that have stack
|
||||||
# behavior more efficiently
|
# behavior more efficiently
|
||||||
names: seq[Name]
|
names: seq[Name]
|
||||||
# Beginning of stack frames for function calls
|
|
||||||
frames: seq[int]
|
|
||||||
# The current scope depth. If > 0, we're
|
# The current scope depth. If > 0, we're
|
||||||
# in a local scope, otherwise it's global
|
# in a local scope, otherwise it's global
|
||||||
scopeDepth: int
|
scopeDepth: int
|
||||||
|
@ -158,6 +158,11 @@ type
|
||||||
deferred: seq[uint8]
|
deferred: seq[uint8]
|
||||||
# List of closed-over variables
|
# List of closed-over variables
|
||||||
closedOver: seq[Name]
|
closedOver: seq[Name]
|
||||||
|
frames: seq[int]
|
||||||
|
|
||||||
|
|
||||||
|
proc `$`(self: Name): string =
|
||||||
|
result &= &"Name(name='{self.name.name.lexeme}', depth={self.depth}, codePos={self.codePos})"
|
||||||
|
|
||||||
|
|
||||||
proc newCompiler*(enableOptimizations: bool = true, replMode: bool = false): Compiler =
|
proc newCompiler*(enableOptimizations: bool = true, replMode: bool = false): Compiler =
|
||||||
|
@ -172,7 +177,6 @@ proc newCompiler*(enableOptimizations: bool = true, replMode: bool = false): Com
|
||||||
result.enableOptimizations = enableOptimizations
|
result.enableOptimizations = enableOptimizations
|
||||||
result.replMode = replMode
|
result.replMode = replMode
|
||||||
result.currentModule = ""
|
result.currentModule = ""
|
||||||
result.frames = @[]
|
|
||||||
|
|
||||||
|
|
||||||
## Forward declarations
|
## Forward declarations
|
||||||
|
@ -187,7 +191,7 @@ 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): seq[Name]
|
proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
|
||||||
proc compareTypes(self: Compiler, a, b: Type): bool
|
proc compareTypes(self: Compiler, a, b: Type): bool
|
||||||
proc patchReturnAddress(self: Compiler, retAddr: int)
|
proc patchReturnAddress(self: Compiler, pos: int)
|
||||||
## End of forward declarations
|
## End of forward declarations
|
||||||
|
|
||||||
## Public getter for nicer error formatting
|
## Public getter for nicer error formatting
|
||||||
|
@ -261,6 +265,16 @@ proc makeConstant(self: Compiler, val: Expression, typ: Type): array[3, uint8] =
|
||||||
result = self.chunk.writeConstant(v.toQuad())
|
result = self.chunk.writeConstant(v.toQuad())
|
||||||
of Int64, UInt64:
|
of Int64, UInt64:
|
||||||
result = self.chunk.writeConstant(v.toLong())
|
result = self.chunk.writeConstant(v.toLong())
|
||||||
|
of String:
|
||||||
|
result = self.chunk.writeConstant(v.toBytes())
|
||||||
|
of Float32:
|
||||||
|
var f: float = 0.0
|
||||||
|
discard parseFloat(val.token.lexeme, f)
|
||||||
|
result = self.chunk.writeConstant(cast[array[4, uint8]](float32(f)))
|
||||||
|
of Float64:
|
||||||
|
var f: float = 0.0
|
||||||
|
discard parseFloat(val.token.lexeme, f)
|
||||||
|
result = self.chunk.writeConstant(cast[array[8, uint8]](f))
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
@ -268,90 +282,58 @@ proc makeConstant(self: Compiler, val: Expression, typ: Type): array[3, uint8] =
|
||||||
proc emitConstant(self: Compiler, obj: Expression, kind: Type) =
|
proc emitConstant(self: Compiler, obj: Expression, kind: Type) =
|
||||||
## Emits a constant instruction along
|
## Emits a constant instruction along
|
||||||
## with its operand
|
## with its operand
|
||||||
case self.inferType(obj).kind:
|
case kind.kind:
|
||||||
of Int64:
|
of Int64:
|
||||||
self.emitByte(LoadInt64)
|
self.emitByte(LoadInt64)
|
||||||
of UInt64:
|
of UInt64:
|
||||||
self.emitByte(LoadUInt64)
|
self.emitByte(LoadUInt64)
|
||||||
of Int32:
|
of Int32:
|
||||||
self.emitByte(LoadInt32)
|
self.emitByte(LoadInt32)
|
||||||
|
of UInt32:
|
||||||
|
self.emitByte(LoadUInt32)
|
||||||
|
of Int16:
|
||||||
|
self.emitByte(LoadInt16)
|
||||||
|
of UInt16:
|
||||||
|
self.emitByte(LoadUInt16)
|
||||||
|
of Int8:
|
||||||
|
self.emitByte(LoadInt8)
|
||||||
|
of UInt8:
|
||||||
|
self.emitByte(LoadUInt8)
|
||||||
|
of String:
|
||||||
|
self.emitByte(LoadString)
|
||||||
|
let str = LiteralExpr(obj).literal.lexeme
|
||||||
|
if str.len() >= 16777216:
|
||||||
|
self.error("string constants cannot be larger than 16777216 bytes")
|
||||||
|
self.emitBytes(LiteralExpr(obj).literal.lexeme.len().toTriple())
|
||||||
|
of Float32:
|
||||||
|
self.emitByte(LoadFloat32)
|
||||||
|
of Float64:
|
||||||
|
self.emitByte(LoadFloat64)
|
||||||
else:
|
else:
|
||||||
discard # TODO
|
discard # TODO
|
||||||
self.emitBytes(self.makeConstant(obj, kind))
|
self.emitBytes(self.makeConstant(obj, kind))
|
||||||
|
|
||||||
|
|
||||||
proc emitJump(self: Compiler, opcode: OpCode): int =
|
proc emitJump(self: Compiler, opcode: OpCode): int =
|
||||||
## Emits a dummy jump offset to be patched later. Assumes
|
## Emits a dummy jump offset to be patched later
|
||||||
## the largest offset (emits 4 bytes, one for the given jump
|
## and returns the absolute index into the chunk's
|
||||||
## opcode, while the other 3 are for the jump offset, which
|
## bytecode array where the given placeholder
|
||||||
## is set to the maximum unsigned 24 bit integer). If the shorter
|
## instruction was written
|
||||||
## 16 bit alternative is later found to be better suited, patchJump
|
|
||||||
## will fix this. Returns the absolute index into the chunk's
|
|
||||||
## bytecode array where the given placeholder instruction was written
|
|
||||||
self.emitByte(opcode)
|
self.emitByte(opcode)
|
||||||
self.emitBytes((0xffffff).toTriple())
|
self.emitBytes(0.toTriple())
|
||||||
result = self.chunk.code.len() - 4
|
result = self.chunk.code.len() - 4
|
||||||
|
|
||||||
|
|
||||||
proc patchJump(self: Compiler, offset: int) =
|
proc patchJump(self: Compiler, offset: int) =
|
||||||
## Patches a previously emitted relative
|
## Patches a previously emitted relative
|
||||||
## jump using emitJump. Since emitJump assumes
|
## jump using emitJump
|
||||||
## a long jump, this also shrinks the jump
|
|
||||||
## offset and changes the bytecode instruction
|
|
||||||
## if possible (i.e. jump is in 16 bit range),
|
|
||||||
## but the converse is also true (i.e. it might
|
|
||||||
## change a regular jump into a long one)
|
|
||||||
var jump: int = self.chunk.code.len() - offset
|
var jump: int = self.chunk.code.len() - offset
|
||||||
if jump > 16777215:
|
if jump > 16777215:
|
||||||
self.error("cannot jump more than 16777216 bytecode instructions")
|
self.error("cannot jump more than 16777216 bytecode instructions")
|
||||||
if jump < uint16.high().int:
|
let offsetArray = (jump - 4).toTriple()
|
||||||
case OpCode(self.chunk.code[offset]):
|
self.chunk.code[offset + 1] = offsetArray[0]
|
||||||
of LongJumpForwards:
|
self.chunk.code[offset + 2] = offsetArray[1]
|
||||||
self.chunk.code[offset] = JumpForwards.uint8()
|
self.chunk.code[offset + 3] = offsetArray[2]
|
||||||
# We do this because a relative jump
|
|
||||||
# does not take its argument into account
|
|
||||||
# because it is hardcoded in the bytecode
|
|
||||||
# itself
|
|
||||||
jump -= 4
|
|
||||||
of LongJumpBackwards:
|
|
||||||
self.chunk.code[offset] = JumpBackwards.uint8()
|
|
||||||
jump -= 4
|
|
||||||
of LongJumpIfFalse:
|
|
||||||
self.chunk.code[offset] = JumpIfFalse.uint8()
|
|
||||||
of LongJumpIfFalsePop:
|
|
||||||
self.chunk.code[offset] = JumpIfFalsePop.uint8()
|
|
||||||
of LongJumpIfFalseOrPop:
|
|
||||||
self.chunk.code[offset] = JumpIfFalseOrPop.uint8()
|
|
||||||
of JumpForwards, JumpBackwards:
|
|
||||||
jump -= 3
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
self.chunk.code.delete(offset + 1) # Discards the first 8 bits of the jump offset (which are empty)
|
|
||||||
let offsetArray = (jump - 1).toDouble() # -1 since we got rid of 1 byte!
|
|
||||||
self.chunk.code[offset + 1] = offsetArray[0]
|
|
||||||
self.chunk.code[offset + 2] = offsetArray[1]
|
|
||||||
else:
|
|
||||||
case OpCode(self.chunk.code[offset]):
|
|
||||||
of JumpForwards:
|
|
||||||
self.chunk.code[offset] = LongJumpForwards.uint8()
|
|
||||||
jump -= 3
|
|
||||||
of JumpBackwards:
|
|
||||||
self.chunk.code[offset] = LongJumpBackwards.uint8()
|
|
||||||
jump -= 3
|
|
||||||
of JumpIfFalse:
|
|
||||||
self.chunk.code[offset] = LongJumpIfFalse.uint8()
|
|
||||||
of JumpIfFalsePop:
|
|
||||||
self.chunk.code[offset] = LongJumpIfFalsePop.uint8()
|
|
||||||
of JumpIfFalseOrPop:
|
|
||||||
self.chunk.code[offset] = LongJumpIfFalseOrPop.uint8()
|
|
||||||
of LongJumpForwards, LongJumpBackwards:
|
|
||||||
jump -= 4
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
let offsetArray = jump.toTriple()
|
|
||||||
self.chunk.code[offset + 1] = offsetArray[0]
|
|
||||||
self.chunk.code[offset + 2] = offsetArray[1]
|
|
||||||
self.chunk.code[offset + 3] = offsetArray[2]
|
|
||||||
|
|
||||||
|
|
||||||
proc resolve(self: Compiler, name: IdentExpr,
|
proc resolve(self: Compiler, name: IdentExpr,
|
||||||
|
@ -371,34 +353,44 @@ proc resolve(self: Compiler, name: IdentExpr,
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
|
||||||
proc getStackPos(self: Compiler, name: IdentExpr,
|
proc getStackPos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int =
|
||||||
depth: int = self.scopeDepth): tuple[closedOver: bool, pos: int] =
|
## Returns the predicted call stack position of a given name, relative
|
||||||
## Iterates the internal list of declared names backwards and
|
## to the current frame
|
||||||
## returns a tuple (closedOver, pos) that tells the caller whether the
|
result = 2
|
||||||
## the name is to be emitted as a closure as well as its predicted
|
var found = false
|
||||||
## stack/closure array position. Returns (false, -1) if the variable's
|
|
||||||
## location can not be determined at compile time (this is an error!).
|
|
||||||
## Note that private names declared in other modules will not be resolved!
|
|
||||||
var i: int = self.names.high()
|
|
||||||
for variable in reversed(self.names):
|
for variable in reversed(self.names):
|
||||||
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 or variable.depth == 0:
|
elif variable.depth == depth or variable.depth == 0:
|
||||||
# variable.depth == 0 for globals!
|
# variable.depth == 0 for globals!
|
||||||
return (false, i)
|
found = true
|
||||||
elif variable.depth > 0:
|
break
|
||||||
var j: int = self.closedOver.high()
|
inc(result)
|
||||||
for closure in reversed(self.closedOver):
|
if not found:
|
||||||
if closure.name.token.lexeme == name.name.lexeme:
|
return -1
|
||||||
return (true, j)
|
|
||||||
inc(j)
|
|
||||||
dec(i)
|
|
||||||
return (false, -1)
|
|
||||||
|
|
||||||
|
|
||||||
proc detectClosureVariable(self: Compiler, name: Name,
|
proc getClosurePos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int =
|
||||||
depth: int = self.scopeDepth) =
|
## 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
|
||||||
|
## are private in other modules)
|
||||||
|
result = self.closedOver.high()
|
||||||
|
var found = false
|
||||||
|
for variable in reversed(self.closedOver):
|
||||||
|
if name.name.lexeme == variable.name.name.lexeme:
|
||||||
|
if variable.isPrivate and variable.owner != self.currentModule:
|
||||||
|
continue
|
||||||
|
elif variable.depth == depth:
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
dec(result)
|
||||||
|
if not found:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDepth) =
|
||||||
## 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
|
||||||
|
@ -407,15 +399,14 @@ proc detectClosureVariable(self: Compiler, name: Name,
|
||||||
## each time a name is referenced in order for closed-over variables
|
## each time a name is referenced in order for closed-over variables
|
||||||
## to be emitted properly, otherwise the runtime may behave
|
## to be emitted properly, otherwise the runtime may behave
|
||||||
## unpredictably or crash
|
## unpredictably or crash
|
||||||
if name == nil:
|
if name == nil or name.depth == 0:
|
||||||
return
|
return
|
||||||
if name.depth > 0 and name.depth < depth:
|
elif name.depth < depth and not name.isClosedOver:
|
||||||
# Ding! The given name is closed over: we need to
|
# Ding! The given name is closed over: we need to
|
||||||
# change the dummy Jump instruction that self.declareName
|
# change the dummy Jump instruction that self.declareName
|
||||||
# put in place for us into a StoreHeap. We don't need to change
|
# put in place for us into a StoreClosure. We also update
|
||||||
# other pieces of code because self.identifier() already
|
# the name's isClosedOver field so that self.identifier()
|
||||||
# emits LoadHeap if it detects the variable is closed over,
|
# can emit a LoadClosure instruction instead of a LoadVar
|
||||||
# whether or not this function is called
|
|
||||||
self.closedOver.add(name)
|
self.closedOver.add(name)
|
||||||
let idx = self.closedOver.high().toTriple()
|
let idx = self.closedOver.high().toTriple()
|
||||||
if self.closedOver.len() >= 16777216:
|
if self.closedOver.len() >= 16777216:
|
||||||
|
@ -694,7 +685,8 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
discard parseInt(y.literal.lexeme, x)
|
discard parseInt(y.literal.lexeme, x)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.error("integer value out of range")
|
self.error("integer value out of range")
|
||||||
self.emitConstant(y, Type(kind: Int64))
|
|
||||||
|
self.emitConstant(y, self.inferType(y))
|
||||||
of hexExpr:
|
of hexExpr:
|
||||||
var x: int
|
var x: int
|
||||||
var y = HexExpr(node)
|
var y = HexExpr(node)
|
||||||
|
@ -707,7 +699,7 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
stop: y.token.pos.start + len($x))
|
stop: y.token.pos.start + len($x))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.emitConstant(node, Type(kind: Int64))
|
self.emitConstant(node, self.inferType(y))
|
||||||
of binExpr:
|
of binExpr:
|
||||||
var x: int
|
var x: int
|
||||||
var y = BinExpr(node)
|
var y = BinExpr(node)
|
||||||
|
@ -720,7 +712,7 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
stop: y.token.pos.start + len($x))
|
stop: y.token.pos.start + len($x))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.emitConstant(node, Type(kind: Int64))
|
self.emitConstant(node, self.inferType(y))
|
||||||
of octExpr:
|
of octExpr:
|
||||||
var x: int
|
var x: int
|
||||||
var y = OctExpr(node)
|
var y = OctExpr(node)
|
||||||
|
@ -733,7 +725,7 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
stop: y.token.pos.start + len($x))
|
stop: y.token.pos.start + len($x))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.emitConstant(node, Type(kind: Int64))
|
self.emitConstant(node, self.inferType(y))
|
||||||
of floatExpr:
|
of floatExpr:
|
||||||
var x: float
|
var x: float
|
||||||
var y = FloatExpr(node)
|
var y = FloatExpr(node)
|
||||||
|
@ -741,7 +733,7 @@ proc literal(self: Compiler, node: ASTNode) =
|
||||||
discard parseFloat(y.literal.lexeme, x)
|
discard parseFloat(y.literal.lexeme, x)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.error("floating point value out of range")
|
self.error("floating point value out of range")
|
||||||
self.emitConstant(y, Type(kind: Float64))
|
self.emitConstant(y, self.inferType(y))
|
||||||
of awaitExpr:
|
of awaitExpr:
|
||||||
var y = AwaitExpr(node)
|
var y = AwaitExpr(node)
|
||||||
self.expression(y.expression)
|
self.expression(y.expression)
|
||||||
|
@ -798,20 +790,40 @@ proc matchImpl(self: Compiler, name: string, kind: Type): Name =
|
||||||
return impl[0]
|
return impl[0]
|
||||||
|
|
||||||
|
|
||||||
|
proc emitFunction(self: Compiler, node: Name) =
|
||||||
|
## Wrapper to emit LoadFunction instructions
|
||||||
|
self.emitByte(LoadFunction)
|
||||||
|
self.emitBytes((node.codePos + 4).toTriple())
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
self.emitByte(LoadNil) # Stack alignment
|
self.emitFunction(fn)
|
||||||
self.emitByte(LoadUInt32)
|
self.emitByte(LoadReturnAddress)
|
||||||
# We patch it later!
|
let pos = self.chunk.code.len()
|
||||||
let idx = self.chunk.consts.len()
|
self.emitBytes(0.toQuad())
|
||||||
self.emitBytes(self.chunk.writeConstant((0xffffffff'u32).toQuad()))
|
for argument in reversed(args):
|
||||||
for argument in args:
|
# We pass the arguments in reverse because
|
||||||
self.expression(argument) # Pushes the arguments onto the stack
|
# we delegate the callee with popping them
|
||||||
self.emitByte(Call) # Creates a stack frame
|
# off the operand stack when they're invoked,
|
||||||
self.emitBytes(fn.codePos.toTriple())
|
# rather than doing it ourselves. The reason
|
||||||
self.emitBytes((args.len()).toTriple())
|
# for this is that the VM can always find the
|
||||||
self.patchReturnAddress(idx)
|
# function object at a constant location in the
|
||||||
|
# stack frame instead of needing extra math to
|
||||||
|
# skip the arguments
|
||||||
|
self.expression(argument)
|
||||||
|
self.emitByte(Call) # Creates a new call frame
|
||||||
|
var size = 2 # We start at 2 because each call frame
|
||||||
|
# contains at least 2 elements (function
|
||||||
|
# object and return address)
|
||||||
|
for name in reversed(self.names):
|
||||||
|
# Then, for each local variable
|
||||||
|
# we increase the frame size by 1
|
||||||
|
if name.depth == self.scopeDepth:
|
||||||
|
inc(size)
|
||||||
|
self.emitBytes(size.toTriple())
|
||||||
|
self.patchReturnAddress(pos)
|
||||||
|
|
||||||
|
|
||||||
proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) =
|
proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) =
|
||||||
|
@ -881,8 +893,10 @@ proc declareName(self: Compiler, node: Declaration) =
|
||||||
# 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 16777216 variables at a time")
|
self.error("cannot declare more than 16777216 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}:
|
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!
|
# Trying to redeclare a variable in the same module 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}")
|
self.error(&"attempt to redeclare '{node.name.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}")
|
||||||
self.names.add(Name(depth: self.scopeDepth,
|
self.names.add(Name(depth: self.scopeDepth,
|
||||||
name: node.name,
|
name: node.name,
|
||||||
|
@ -920,7 +934,7 @@ proc declareName(self: Compiler, node: Declaration) =
|
||||||
returnType: self.inferType(
|
returnType: self.inferType(
|
||||||
node.returnType),
|
node.returnType),
|
||||||
args: @[]),
|
args: @[]),
|
||||||
codePos: self.chunk.code.high(),
|
codePos: self.chunk.code.len(),
|
||||||
name: node.name,
|
name: node.name,
|
||||||
isLet: false,
|
isLet: false,
|
||||||
isClosedOver: false,
|
isClosedOver: false,
|
||||||
|
@ -930,7 +944,7 @@ proc declareName(self: Compiler, node: Declaration) =
|
||||||
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 16777216 variables at a time")
|
self.error("cannot declare more than 16777216 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,
|
||||||
|
@ -941,7 +955,9 @@ proc declareName(self: Compiler, node: Declaration) =
|
||||||
valueType: nil,
|
valueType: nil,
|
||||||
codePos: 0,
|
codePos: 0,
|
||||||
isLet: false,
|
isLet: false,
|
||||||
isClosedOver: false)
|
isClosedOver: false,
|
||||||
|
line: argument.name.token.line,
|
||||||
|
isFunctionArgument: true)
|
||||||
self.names.add(name)
|
self.names.add(name)
|
||||||
name.valueType = self.inferType(argument.valueType)
|
name.valueType = self.inferType(argument.valueType)
|
||||||
if argument.mutable:
|
if argument.mutable:
|
||||||
|
@ -975,20 +991,15 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
||||||
self.emitConstant(node, self.inferType(node))
|
self.emitConstant(node, self.inferType(node))
|
||||||
else:
|
else:
|
||||||
self.detectClosureVariable(s)
|
self.detectClosureVariable(s)
|
||||||
let t = self.getStackPos(node)
|
if not s.isClosedOver:
|
||||||
var index = t.pos
|
|
||||||
# We don't check if index is -1 because if it
|
|
||||||
# were, self.resolve() would have returned nil
|
|
||||||
if not t.closedOver:
|
|
||||||
# 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.
|
||||||
if self.scopeDepth > 0:
|
|
||||||
inc(index, 1)
|
|
||||||
self.emitByte(LoadVar)
|
self.emitByte(LoadVar)
|
||||||
self.emitBytes((index - self.frames[^1]).toTriple())
|
# No need to check for -1 here: we already did a nil-check above!
|
||||||
|
self.emitBytes(self.getStackPos(s.name).toTriple())
|
||||||
else:
|
else:
|
||||||
# Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics
|
# Loads a closure variable. Stored in a separate "closure array" in the VM that does not
|
||||||
# and where the no-effect invariant is not kept. This makes closures work as expected and is not much slower than
|
# align its semantics with the call stack. This makes closures work as expected and is
|
||||||
# indexing our stack (since they're both dynamic arrays at runtime anyway)
|
# not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway)
|
||||||
self.emitByte(LoadClosure)
|
self.emitByte(LoadClosure)
|
||||||
self.emitBytes(self.closedOver.high().toTriple())
|
self.emitBytes(self.closedOver.high().toTriple())
|
||||||
|
|
||||||
|
@ -1007,16 +1018,16 @@ proc assignment(self: Compiler, node: ASTNode) =
|
||||||
elif r.isLet:
|
elif r.isLet:
|
||||||
self.error(&"cannot reassign '{name.token.lexeme}'")
|
self.error(&"cannot reassign '{name.token.lexeme}'")
|
||||||
self.expression(node.value)
|
self.expression(node.value)
|
||||||
let t = self.getStackPos(name)
|
self.detectClosureVariable(r)
|
||||||
let index = t.pos
|
if not r.isClosedOver:
|
||||||
if index != -1:
|
self.emitByte(StoreVar)
|
||||||
if not t.closedOver:
|
self.emitBytes(self.getStackPos(name).toTriple())
|
||||||
self.emitByte(StoreVar)
|
|
||||||
else:
|
|
||||||
self.emitByte(StoreClosure)
|
|
||||||
self.emitBytes(index.toTriple())
|
|
||||||
else:
|
else:
|
||||||
self.error(&"reference to undeclared name '{node.token.lexeme}'")
|
# Loads a closure variable. Stored in a separate "closure array" in the VM that does not
|
||||||
|
# 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(StoreClosure)
|
||||||
|
self.emitBytes(self.getClosurePos(name).toTriple())
|
||||||
of setItemExpr:
|
of setItemExpr:
|
||||||
let node = SetItemExpr(node)
|
let node = SetItemExpr(node)
|
||||||
let typ = self.inferType(node)
|
let typ = self.inferType(node)
|
||||||
|
@ -1033,7 +1044,7 @@ proc beginScope(self: Compiler) =
|
||||||
inc(self.scopeDepth)
|
inc(self.scopeDepth)
|
||||||
|
|
||||||
|
|
||||||
proc endScope(self: Compiler, fromFunc: bool = false) =
|
proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false) =
|
||||||
## 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)")
|
||||||
|
@ -1045,7 +1056,7 @@ proc endScope(self: Compiler, fromFunc: bool = false) =
|
||||||
if not self.enableOptimizations and not fromFunc:
|
if not self.enableOptimizations and not fromFunc:
|
||||||
# All variables with a scope depth larger than the current one
|
# All variables with a scope depth larger than the current one
|
||||||
# are now out of scope. Begone, you're now homeless!
|
# are now out of scope. Begone, you're now homeless!
|
||||||
self.emitByte(Pop)
|
self.emitByte(PopC)
|
||||||
if self.enableOptimizations and len(names) > 1 and not fromFunc:
|
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
|
||||||
|
@ -1059,27 +1070,28 @@ proc endScope(self: Compiler, fromFunc: bool = false) =
|
||||||
if len(names) > uint16.high().int():
|
if len(names) > uint16.high().int():
|
||||||
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(Pop)
|
self.emitByte(PopC)
|
||||||
elif len(names) == 1 and not fromFunc:
|
elif len(names) == 1 and not fromFunc:
|
||||||
# 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(Pop)
|
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 ¯\_(ツ)_/¯
|
||||||
var idx = 0
|
if deleteNames:
|
||||||
while idx < self.names.len():
|
var idx = 0
|
||||||
for name in names:
|
while idx < self.names.len():
|
||||||
if self.names[idx] == name:
|
for name in names:
|
||||||
self.names.delete(idx)
|
if self.names[idx] == name:
|
||||||
inc(idx)
|
self.names.delete(idx)
|
||||||
idx = 0
|
inc(idx)
|
||||||
while idx < self.closedOver.len():
|
idx = 0
|
||||||
for name in names:
|
while idx < self.closedOver.len():
|
||||||
if name.isClosedOver:
|
for name in names:
|
||||||
self.closedOver.delete(idx)
|
if name.isClosedOver:
|
||||||
self.emitByte(PopClosure)
|
self.closedOver.delete(idx)
|
||||||
inc(idx)
|
self.emitByte(PopClosure)
|
||||||
|
inc(idx)
|
||||||
|
|
||||||
|
|
||||||
proc blockStmt(self: Compiler, node: BlockStmt) =
|
proc blockStmt(self: Compiler, node: BlockStmt) =
|
||||||
|
@ -1176,11 +1188,6 @@ proc callExpr(self: Compiler, node: CallExpr) =
|
||||||
|
|
||||||
proc expression(self: Compiler, node: Expression) =
|
proc expression(self: Compiler, node: Expression) =
|
||||||
## Compiles all expressions
|
## Compiles all expressions
|
||||||
if self.inferType(node) == nil:
|
|
||||||
if node.kind != identExpr:
|
|
||||||
# So we can raise a more appropriate
|
|
||||||
# error in self.identifier()
|
|
||||||
self.error("expression has no type")
|
|
||||||
case node.kind:
|
case node.kind:
|
||||||
of NodeKind.callExpr:
|
of NodeKind.callExpr:
|
||||||
self.callExpr(CallExpr(node)) # TODO
|
self.callExpr(CallExpr(node)) # TODO
|
||||||
|
@ -1237,7 +1244,24 @@ proc deferStmt(self: Compiler, node: DeferStmt) =
|
||||||
self.expression(node.expression)
|
self.expression(node.expression)
|
||||||
for i in countup(current, self.chunk.code.high()):
|
for i in countup(current, self.chunk.code.high()):
|
||||||
self.deferred.add(self.chunk.code[i])
|
self.deferred.add(self.chunk.code[i])
|
||||||
self.chunk.code.del(i)
|
self.chunk.code.delete(i) # TODO: Do not change bytecode size
|
||||||
|
|
||||||
|
|
||||||
|
proc endFunctionBeforeReturn(self: Compiler) =
|
||||||
|
## Emits code to clear a function's
|
||||||
|
## stack frame right before executing
|
||||||
|
## its return instruction
|
||||||
|
var popped = 0
|
||||||
|
for name in self.names:
|
||||||
|
if name.depth == self.scopeDepth and name.valueType.kind != Function:
|
||||||
|
inc(popped)
|
||||||
|
if self.enableOptimizations and popped > 1:
|
||||||
|
self.emitByte(PopN)
|
||||||
|
self.emitBytes(popped.toDouble())
|
||||||
|
dec(popped, uint16.high().int)
|
||||||
|
while popped > 0:
|
||||||
|
self.emitByte(PopC)
|
||||||
|
dec(popped)
|
||||||
|
|
||||||
|
|
||||||
proc returnStmt(self: Compiler, node: ReturnStmt) =
|
proc returnStmt(self: Compiler, node: ReturnStmt) =
|
||||||
|
@ -1247,8 +1271,11 @@ proc returnStmt(self: Compiler, node: ReturnStmt) =
|
||||||
let typ = self.inferType(self.currentFunction)
|
let typ = self.inferType(self.currentFunction)
|
||||||
## Having the return type
|
## Having the return type
|
||||||
if returnType == nil and typ.returnType != nil:
|
if returnType == nil and typ.returnType != nil:
|
||||||
if node.value.kind == identExpr:
|
if node.value != nil:
|
||||||
self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'")
|
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(typ.returnType)}', but expression has no type")
|
self.error(&"expected return value of type '{self.typeToStr(typ.returnType)}', but expression has no type")
|
||||||
elif typ.returnType == nil and returnType != nil:
|
elif typ.returnType == nil and returnType != nil:
|
||||||
self.error("non-empty return statement is not allowed in void functions")
|
self.error("non-empty return statement is not allowed in void functions")
|
||||||
|
@ -1256,9 +1283,13 @@ proc returnStmt(self: Compiler, node: ReturnStmt) =
|
||||||
self.error(&"expected return value of type '{self.typeToStr(typ.returnType)}', got '{self.typeToStr(returnType)}' instead")
|
self.error(&"expected return value of type '{self.typeToStr(typ.returnType)}', got '{self.typeToStr(returnType)}' instead")
|
||||||
if node.value != nil:
|
if node.value != nil:
|
||||||
self.expression(node.value)
|
self.expression(node.value)
|
||||||
self.emitByte(OpCode.ReturnValue)
|
self.emitByte(OpCode.SetResult)
|
||||||
|
self.endFunctionBeforeReturn()
|
||||||
|
self.emitByte(OpCode.Return)
|
||||||
|
if node.value != nil:
|
||||||
|
self.emitByte(1)
|
||||||
else:
|
else:
|
||||||
self.emitByte(OpCode.Return)
|
self.emitByte(0)
|
||||||
|
|
||||||
|
|
||||||
proc yieldStmt(self: Compiler, node: YieldStmt) =
|
proc yieldStmt(self: Compiler, node: YieldStmt) =
|
||||||
|
@ -1322,11 +1353,16 @@ proc statement(self: Compiler, node: Statement) =
|
||||||
of exprStmt:
|
of exprStmt:
|
||||||
var expression = ExprStmt(node).expression
|
var expression = ExprStmt(node).expression
|
||||||
self.expression(expression)
|
self.expression(expression)
|
||||||
# We only print top-level expressions
|
if expression.kind == callExpr and self.inferType(CallExpr(expression).callee).returnType == nil:
|
||||||
if self.replMode and self.scopeDepth == 0:
|
# The expression has no type, so we don't have to
|
||||||
self.emitByte(PopRepl)
|
# pop anything
|
||||||
|
discard
|
||||||
else:
|
else:
|
||||||
self.emitByte(Pop) # Expression statements discard their value. Their main use case is side effects in function calls
|
# We only print top-level expressions
|
||||||
|
if self.replMode and self.scopeDepth == 0:
|
||||||
|
self.emitByte(PopRepl)
|
||||||
|
else:
|
||||||
|
self.emitByte(Pop)
|
||||||
of NodeKind.ifStmt:
|
of NodeKind.ifStmt:
|
||||||
self.ifStmt(IfStmt(node))
|
self.ifStmt(IfStmt(node))
|
||||||
of NodeKind.assertStmt:
|
of NodeKind.assertStmt:
|
||||||
|
@ -1371,6 +1407,8 @@ proc varDecl(self: Compiler, node: VarDecl) =
|
||||||
let expected = self.inferType(node.valueType)
|
let expected = self.inferType(node.valueType)
|
||||||
let actual = self.inferType(node.value)
|
let actual = self.inferType(node.value)
|
||||||
if expected == nil and actual == nil:
|
if expected == nil and actual == nil:
|
||||||
|
if node.value.kind == identExpr:
|
||||||
|
self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'")
|
||||||
self.error(&"'{node.name.token.lexeme}' has no type")
|
self.error(&"'{node.name.token.lexeme}' has no type")
|
||||||
elif expected != nil and expected.kind == Mutable: # I mean, variables *are* already mutable (some of them anyway)
|
elif expected != nil and expected.kind == Mutable: # I mean, variables *are* already mutable (some of them anyway)
|
||||||
self.error(&"invalid type '{self.typeToStr(expected)}' for var")
|
self.error(&"invalid type '{self.typeToStr(expected)}' for var")
|
||||||
|
@ -1379,27 +1417,24 @@ 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)
|
self.declareName(node)
|
||||||
|
self.emitByte(StoreVar)
|
||||||
|
self.emitBytes(self.names.high().toTriple())
|
||||||
|
|
||||||
|
|
||||||
proc funDecl(self: Compiler, node: FunDecl) =
|
proc funDecl(self: Compiler, node: FunDecl) =
|
||||||
## Compiles function declarations
|
## Compiles function declarations
|
||||||
# A function's code is just compiled linearly
|
# A function's code is just compiled linearly
|
||||||
# and then jumped over
|
# and then jumped over
|
||||||
|
|
||||||
# While the compiler stores functions as if they
|
|
||||||
# were on the stack, in reality there is nothing
|
|
||||||
# at runtime that represents them (instead, we
|
|
||||||
# compile everything in a single blob and jump back
|
|
||||||
# and forth). If we didn't align the stack to account
|
|
||||||
# for this behavior, all of our offsets would be off
|
|
||||||
# by how many functions we have declared. We could fix
|
|
||||||
# this by storing functions in a separate list of identifiers,
|
|
||||||
# but that's rather unelegant and requires specialized functions
|
|
||||||
# for looking identifiers vs functions, which is not ideal
|
|
||||||
let jmp = self.emitJump(JumpForwards)
|
|
||||||
var function = self.currentFunction
|
var function = self.currentFunction
|
||||||
self.declareName(node)
|
self.declareName(node)
|
||||||
self.frames.add(self.names.len() - 1)
|
self.frames.add(self.names.high())
|
||||||
|
let fn = self.names[^(node.arguments.len() + 1)]
|
||||||
|
fn.codePos = self.chunk.code.len()
|
||||||
|
let jmp = self.emitJump(LongJumpForwards)
|
||||||
|
for argument in node.arguments:
|
||||||
|
self.emitByte(LoadArgument)
|
||||||
|
if node.returnType != nil and self.inferType(node.returnType) == nil:
|
||||||
|
self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'")
|
||||||
# TODO: Forward declarations
|
# TODO: Forward declarations
|
||||||
if node.body != nil:
|
if node.body != nil:
|
||||||
if BlockStmt(node.body).code.len() == 0:
|
if BlockStmt(node.body).code.len() == 0:
|
||||||
|
@ -1428,26 +1463,32 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
||||||
var deferStart = self.deferred.len()
|
var deferStart = self.deferred.len()
|
||||||
# We let our debugger know a function is starting
|
# We let our debugger know a function is starting
|
||||||
let start = self.chunk.code.high()
|
let start = self.chunk.code.high()
|
||||||
|
|
||||||
self.beginScope()
|
self.beginScope()
|
||||||
# self.emitByte(LoadNil)
|
|
||||||
for decl in BlockStmt(node.body).code:
|
for decl in BlockStmt(node.body).code:
|
||||||
self.declaration(decl)
|
self.declaration(decl)
|
||||||
self.endScope(fromFunc=true)
|
var typ: Type
|
||||||
|
var hasVal: bool = false
|
||||||
case self.currentFunction.kind:
|
case self.currentFunction.kind:
|
||||||
of NodeKind.funDecl:
|
of NodeKind.funDecl:
|
||||||
if not self.currentFunction.hasExplicitReturn:
|
typ = self.inferType(self.currentFunction)
|
||||||
let typ = self.inferType(self.currentFunction)
|
hasVal = self.currentFunction.hasExplicitReturn
|
||||||
if self.currentFunction.returnType == nil and typ.returnType != nil:
|
|
||||||
self.error("non-empty return statement is not allowed in void functions")
|
|
||||||
if self.currentFunction.returnType != nil:
|
|
||||||
self.error("function has an explicit return type, but no return statement was found")
|
|
||||||
self.emitByte(OpCode.Return)
|
|
||||||
of NodeKind.lambdaExpr:
|
of NodeKind.lambdaExpr:
|
||||||
if not LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn:
|
typ = self.inferType(LambdaExpr(Declaration(self.currentFunction)))
|
||||||
self.emitByte(OpCode.Return)
|
hasVal = LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
|
if hasVal and self.currentFunction.returnType == nil and typ.returnType != nil:
|
||||||
|
self.error("non-empty return statement is not allowed in void functions")
|
||||||
|
elif not hasVal and self.currentFunction.returnType != nil:
|
||||||
|
self.error("function has an explicit return type, but no return statement was found")
|
||||||
|
# self.endFunctionBeforeReturn()
|
||||||
|
hasVal = hasVal and typ.returnType != nil
|
||||||
|
self.endScope(deleteNames=true, fromFunc=false)
|
||||||
|
self.emitByte(OpCode.Return)
|
||||||
|
if hasVal:
|
||||||
|
self.emitByte(1)
|
||||||
|
else:
|
||||||
|
self.emitByte(0)
|
||||||
# Function is ending!
|
# Function is ending!
|
||||||
self.chunk.cfi.add(start.toTriple())
|
self.chunk.cfi.add(start.toTriple())
|
||||||
self.chunk.cfi.add(self.chunk.code.high().toTriple())
|
self.chunk.cfi.add(self.chunk.code.high().toTriple())
|
||||||
|
@ -1472,15 +1513,15 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
||||||
discard self.frames.pop()
|
discard self.frames.pop()
|
||||||
|
|
||||||
|
|
||||||
proc patchReturnAddress(self: Compiler, retAddr: int) =
|
proc patchReturnAddress(self: Compiler, pos: int) =
|
||||||
## Patches the return address of a function
|
## Patches the return address of a function
|
||||||
## call. This is called at each iteration of
|
## call
|
||||||
## the compiler's loop
|
|
||||||
let address = self.chunk.code.len().toQuad()
|
let address = self.chunk.code.len().toQuad()
|
||||||
self.chunk.consts[retAddr] = address[0]
|
self.chunk.code[pos] = address[0]
|
||||||
self.chunk.consts[retAddr + 1] = address[1]
|
self.chunk.code[pos + 1] = address[1]
|
||||||
self.chunk.consts[retAddr + 2] = address[2]
|
self.chunk.code[pos + 2] = address[2]
|
||||||
self.chunk.consts[retAddr + 3] = address[3]
|
self.chunk.code[pos + 3] = address[3]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc declaration(self: Compiler, node: Declaration) =
|
proc declaration(self: Compiler, node: Declaration) =
|
||||||
|
@ -1505,12 +1546,41 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk =
|
||||||
self.currentFunction = nil
|
self.currentFunction = nil
|
||||||
self.currentModule = self.file.extractFilename()
|
self.currentModule = self.file.extractFilename()
|
||||||
self.current = 0
|
self.current = 0
|
||||||
self.frames = @[0]
|
# Every peon program has a hidden entry point in
|
||||||
|
# which user code is wrapped. Think of it as if
|
||||||
|
# peon is implicitly writing the main() function
|
||||||
|
# of your program and putting all of your code in
|
||||||
|
# there. While we call our entry point just like
|
||||||
|
# any regular peon function, we can't use our handy
|
||||||
|
# helper generateCall() because we need to keep track
|
||||||
|
# of where our program ends (which we don't know yet).
|
||||||
|
# To fix this, we emit dummy offsets and patch them
|
||||||
|
# later, once we know the boundaries of our hidden main()
|
||||||
|
var main = Name(depth: 0,
|
||||||
|
isPrivate: true,
|
||||||
|
isConst: false,
|
||||||
|
isLet: false,
|
||||||
|
isClosedOver: false,
|
||||||
|
owner: self.currentModule,
|
||||||
|
valueType: Type(kind: Function,
|
||||||
|
name: "",
|
||||||
|
returnType: nil,
|
||||||
|
args: @[]),
|
||||||
|
codePos: 13,
|
||||||
|
name: newIdentExpr(Token(lexeme: "", kind: Identifier)),
|
||||||
|
line: -1)
|
||||||
|
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())
|
||||||
while not self.done():
|
while not self.done():
|
||||||
self.declaration(Declaration(self.step()))
|
self.declaration(Declaration(self.step()))
|
||||||
if self.ast.len() > 0:
|
self.endScope(fromFunc=true)
|
||||||
# *Technically* an empty program is a valid program
|
self.patchReturnAddress(pos)
|
||||||
self.emitByte(ProgExit)
|
self.emitByte(OpCode.Return)
|
||||||
|
self.emitByte(0)
|
||||||
result = self.chunk
|
result = self.chunk
|
||||||
if self.ast.len() > 0 and self.scopeDepth != 0:
|
|
||||||
self.error(&"invalid state: invalid scopeDepth value (expected 0, got {self.scopeDepth}), did you forget to call endScope/beginScope?")
|
|
||||||
|
|
|
@ -82,6 +82,8 @@ type
|
||||||
LoadFloat64,
|
LoadFloat64,
|
||||||
LoadFloat32,
|
LoadFloat32,
|
||||||
LoadString,
|
LoadString,
|
||||||
|
LoadFunction,
|
||||||
|
LoadReturnAddress,
|
||||||
## Singleton opcodes (each of them pushes a constant singleton on the stack)
|
## Singleton opcodes (each of them pushes a constant singleton on the stack)
|
||||||
LoadNil,
|
LoadNil,
|
||||||
LoadTrue,
|
LoadTrue,
|
||||||
|
@ -118,8 +120,8 @@ type
|
||||||
LongJumpBackwards,
|
LongJumpBackwards,
|
||||||
## Functions
|
## Functions
|
||||||
Call, # Calls a function and initiates a new stack frame
|
Call, # Calls a function and initiates a new stack frame
|
||||||
Return, # Terminates the current function without popping off the stack
|
Return, # Terminates the current function
|
||||||
ReturnValue, # Pops a return value off the stack and terminates the current function
|
SetResult, # Sets the result of the current function
|
||||||
## Exception handling
|
## Exception handling
|
||||||
Raise, # Raises exception x or re-raises active exception if x is nil
|
Raise, # Raises exception x or re-raises active exception if x is nil
|
||||||
BeginTry, # Initiates an exception handling context
|
BeginTry, # Initiates an exception handling context
|
||||||
|
@ -131,7 +133,8 @@ type
|
||||||
## 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
|
||||||
ProgExit, # Terminates the whole program
|
LoadArgument,
|
||||||
|
PopC
|
||||||
|
|
||||||
|
|
||||||
# We group instructions by their operation/operand types for easier handling when debugging
|
# We group instructions by their operation/operand types for easier handling when debugging
|
||||||
|
@ -142,8 +145,9 @@ const simpleInstructions* = {Return, LoadNil,
|
||||||
LoadNan, LoadInf,
|
LoadNan, LoadInf,
|
||||||
Pop, PopRepl, Raise,
|
Pop, PopRepl, Raise,
|
||||||
BeginTry, FinishTry, Yield,
|
BeginTry, FinishTry, Yield,
|
||||||
Await, NoOp, ReturnValue,
|
Await, NoOp, PopClosure,
|
||||||
PopClosure, ProgExit}
|
SetResult, LoadArgument,
|
||||||
|
PopC}
|
||||||
|
|
||||||
# Constant instructions are instructions that operate on the bytecode constant table
|
# Constant instructions are instructions that operate on the bytecode constant table
|
||||||
const constantInstructions* = {LoadInt64, LoadUInt64,
|
const constantInstructions* = {LoadInt64, LoadUInt64,
|
||||||
|
|
25
src/main.nim
25
src/main.nim
|
@ -3,7 +3,6 @@ import strformat
|
||||||
import strutils
|
import strutils
|
||||||
import terminal
|
import terminal
|
||||||
import parseopt
|
import parseopt
|
||||||
import nimSHA2
|
|
||||||
import times
|
import times
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -15,7 +14,6 @@ import jale/plugin/editor_history
|
||||||
import jale/keycodes
|
import jale/keycodes
|
||||||
import jale/multiline
|
import jale/multiline
|
||||||
|
|
||||||
|
|
||||||
# Our stuff
|
# Our stuff
|
||||||
import frontend/lexer as l
|
import frontend/lexer as l
|
||||||
import frontend/parser as p
|
import frontend/parser as p
|
||||||
|
@ -32,12 +30,12 @@ proc getLineEditor: LineEditor
|
||||||
# Handy dandy compile-time constants
|
# Handy dandy compile-time constants
|
||||||
const debugLexer = false
|
const debugLexer = false
|
||||||
const debugParser = false
|
const debugParser = false
|
||||||
const debugCompiler = false
|
const debugCompiler = true
|
||||||
const debugSerializer = false
|
const debugSerializer = false
|
||||||
const debugRuntime = false
|
const debugRuntime = false
|
||||||
|
|
||||||
|
|
||||||
proc repl =
|
|
||||||
|
proc repl(vm: PeonVM = newPeonVM()) =
|
||||||
styledEcho fgMagenta, "Welcome into the peon REPL!"
|
styledEcho fgMagenta, "Welcome into the peon REPL!"
|
||||||
var
|
var
|
||||||
keep = true
|
keep = true
|
||||||
|
@ -50,7 +48,6 @@ proc repl =
|
||||||
compiler = newCompiler(replMode=true)
|
compiler = newCompiler(replMode=true)
|
||||||
debugger = newDebugger()
|
debugger = newDebugger()
|
||||||
serializer = newSerializer()
|
serializer = newSerializer()
|
||||||
vm = newPeonVM()
|
|
||||||
editor = getLineEditor()
|
editor = getLineEditor()
|
||||||
input: string
|
input: string
|
||||||
current: string
|
current: string
|
||||||
|
@ -110,7 +107,6 @@ proc repl =
|
||||||
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: "
|
||||||
styledEcho fgBlue, &"\t- File hash: ", fgYellow, serialized.fileHash, fgBlue, " (", if hashMatches: fgGreen else: fgRed, if hashMatches: "OK" else: "Fail", fgBlue, ")"
|
|
||||||
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
|
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
|
||||||
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
|
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
|
||||||
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
|
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
|
||||||
|
@ -163,7 +159,6 @@ proc repl =
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
styledEcho fgBlue, "Source line: " , fgDefault, line
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
|
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
|
||||||
except CompileError:
|
except CompileError:
|
||||||
input = ""
|
|
||||||
let exc = CompileError(getCurrentException())
|
let exc = CompileError(getCurrentException())
|
||||||
let lexeme = exc.node.token.lexeme
|
let lexeme = exc.node.token.lexeme
|
||||||
let lineNo = exc.node.token.line
|
let lineNo = exc.node.token.line
|
||||||
|
@ -184,7 +179,7 @@ proc repl =
|
||||||
quit(0)
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
proc runFile(f: string) =
|
proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
|
||||||
var
|
var
|
||||||
tokens: seq[Token] = @[]
|
tokens: seq[Token] = @[]
|
||||||
tree: seq[Declaration] = @[]
|
tree: seq[Declaration] = @[]
|
||||||
|
@ -199,7 +194,13 @@ proc runFile(f: string) =
|
||||||
input: string
|
input: string
|
||||||
tokenizer.fillSymbolTable()
|
tokenizer.fillSymbolTable()
|
||||||
try:
|
try:
|
||||||
input = readFile(f)
|
var f = f
|
||||||
|
if not fromString:
|
||||||
|
if not f.endsWith(".pn"):
|
||||||
|
f &= ".pn"
|
||||||
|
input = readFile(f)
|
||||||
|
else:
|
||||||
|
input = f
|
||||||
tokens = tokenizer.lex(input, f)
|
tokens = tokenizer.lex(input, f)
|
||||||
if tokens.len() == 0:
|
if tokens.len() == 0:
|
||||||
return
|
return
|
||||||
|
@ -302,6 +303,8 @@ proc runFile(f: string) =
|
||||||
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}")
|
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}")
|
||||||
except OSError:
|
except OSError:
|
||||||
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
|
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
|
||||||
|
if interactive:
|
||||||
|
repl(vm)
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
|
@ -353,7 +356,7 @@ when isMainModule:
|
||||||
if file == "":
|
if file == "":
|
||||||
repl()
|
repl()
|
||||||
else:
|
else:
|
||||||
runFile(file)
|
runFile(file, interactive, fromString)
|
||||||
|
|
||||||
|
|
||||||
proc fillSymbolTable(tokenizer: Lexer) =
|
proc fillSymbolTable(tokenizer: Lexer) =
|
||||||
|
|
|
@ -71,7 +71,7 @@ proc checkFrameStart(self: Debugger, n: int) =
|
||||||
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
|
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
|
||||||
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
|
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
|
||||||
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
|
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
|
||||||
styledEcho fgGreen, "\t- Stack bottom: ", fgYellow, $e.bottom
|
styledEcho fgGreen, "\t- Frame bottom: ", fgYellow, $e.bottom
|
||||||
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc
|
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,18 +83,24 @@ proc checkFrameEnd(self: Debugger, n: int) =
|
||||||
|
|
||||||
|
|
||||||
proc simpleInstruction(self: Debugger, instruction: OpCode) =
|
proc simpleInstruction(self: Debugger, instruction: OpCode) =
|
||||||
printInstruction(instruction)
|
printInstruction(instruction, true)
|
||||||
nl()
|
|
||||||
self.current += 1
|
self.current += 1
|
||||||
if instruction in {Return, ReturnValue}:
|
if instruction == Return:
|
||||||
|
printDebug("Void: ")
|
||||||
|
if self.chunk.code[self.current] == 0:
|
||||||
|
stdout.styledWriteLine(fgYellow, "Yes")
|
||||||
|
else:
|
||||||
|
stdout.styledWriteLine(fgYellow, "No")
|
||||||
|
self.current += 1
|
||||||
|
self.checkFrameEnd(self.current - 2)
|
||||||
self.checkFrameEnd(self.current - 1)
|
self.checkFrameEnd(self.current - 1)
|
||||||
self.checkFrameEnd(self.current)
|
self.checkFrameEnd(self.current)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc stackTripleInstruction(self: Debugger, instruction: OpCode) =
|
proc stackTripleInstruction(self: Debugger, instruction: OpCode) =
|
||||||
## Debugs instructions that operate on a single value on the stack using a 24-bit operand
|
## Debugs instructions that operate on a single value on the stack using a 24-bit operand
|
||||||
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[
|
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||||
self.current + 3]].fromTriple()
|
|
||||||
printInstruction(instruction)
|
printInstruction(instruction)
|
||||||
stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
|
stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
|
||||||
self.current += 4
|
self.current += 4
|
||||||
|
@ -127,25 +133,34 @@ proc argumentTripleInstruction(self: Debugger, instruction: OpCode) =
|
||||||
|
|
||||||
proc callInstruction(self: Debugger, instruction: OpCode) =
|
proc callInstruction(self: Debugger, instruction: OpCode) =
|
||||||
## Debugs function calls
|
## Debugs function calls
|
||||||
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||||
var args = [self.chunk.code[self.current + 4], self.chunk.code[self.current + 5], self.chunk.code[self.current + 6]].fromTriple()
|
|
||||||
printInstruction(instruction)
|
printInstruction(instruction)
|
||||||
stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument")
|
styledEcho fgGreen, &", creates frame of size ", fgYellow, $size
|
||||||
if args > 1 or args == 0:
|
self.current += 4
|
||||||
stdout.styledWrite(fgGreen, "s")
|
|
||||||
nl()
|
|
||||||
self.current += 7
|
proc functionInstruction(self: Debugger, instruction: OpCode) =
|
||||||
|
## Debugs function calls
|
||||||
|
var address = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||||
|
printInstruction(instruction)
|
||||||
|
styledEcho fgGreen, &", loads function at address ", fgYellow, $address
|
||||||
|
self.current += 4
|
||||||
|
|
||||||
|
|
||||||
|
proc loadAddressInstruction(self: Debugger, instruction: OpCode) =
|
||||||
|
## Debugs LoadReturnAddress instructions
|
||||||
|
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
|
||||||
|
self.current += 5
|
||||||
|
|
||||||
|
|
||||||
proc constantInstruction(self: Debugger, instruction: OpCode) =
|
proc constantInstruction(self: Debugger, instruction: OpCode) =
|
||||||
## Debugs instructions that operate on the constant table
|
## Debugs instructions that operate on the constant table
|
||||||
var constant = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[
|
var constant = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||||
self.current + 3]].fromTriple()
|
|
||||||
printInstruction(instruction)
|
printInstruction(instruction)
|
||||||
stdout.styledWrite(fgGreen, &", points to constant at position ", fgYellow, $constant)
|
stdout.styledWriteLine(fgGreen, &", points to constant at position ", fgYellow, $constant)
|
||||||
nl()
|
|
||||||
printDebug("Operand: ")
|
|
||||||
stdout.styledWriteLine(fgYellow, &"{self.chunk.consts[constant]}")
|
|
||||||
self.current += 4
|
self.current += 4
|
||||||
|
|
||||||
|
|
||||||
|
@ -158,8 +173,7 @@ proc jumpInstruction(self: Debugger, instruction: OpCode) =
|
||||||
jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble().int()
|
jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble().int()
|
||||||
of LongJump, LongJumpIfFalse, LongJumpIfTrue, LongJumpIfFalsePop,
|
of LongJump, LongJumpIfFalse, LongJumpIfTrue, LongJumpIfFalsePop,
|
||||||
LongJumpForwards, LongJumpBackwards:
|
LongJumpForwards, LongJumpBackwards:
|
||||||
jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[
|
jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple().int()
|
||||||
self.current + 3]].fromTriple().int()
|
|
||||||
self.current += 1
|
self.current += 1
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
|
@ -172,10 +186,11 @@ proc jumpInstruction(self: Debugger, instruction: OpCode) =
|
||||||
self.checkFrameStart(i)
|
self.checkFrameStart(i)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc disassembleInstruction*(self: Debugger) =
|
proc disassembleInstruction*(self: Debugger) =
|
||||||
## Takes one bytecode instruction and prints it
|
## Takes one bytecode instruction and prints it
|
||||||
printDebug("Offset: ")
|
printDebug("Offset: ")
|
||||||
stdout.styledWriteLine(fgYellow, $self.current)
|
stdout.styledWriteLine(fgYellow, $(self.current))
|
||||||
printDebug("Line: ")
|
printDebug("Line: ")
|
||||||
stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(self.current)}")
|
stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(self.current)}")
|
||||||
var opcode = OpCode(self.chunk.code[self.current])
|
var opcode = OpCode(self.chunk.code[self.current])
|
||||||
|
@ -196,6 +211,10 @@ proc disassembleInstruction*(self: Debugger) =
|
||||||
self.callInstruction(opcode)
|
self.callInstruction(opcode)
|
||||||
of jumpInstructions:
|
of jumpInstructions:
|
||||||
self.jumpInstruction(opcode)
|
self.jumpInstruction(opcode)
|
||||||
|
of LoadFunction:
|
||||||
|
self.functionInstruction(opcode)
|
||||||
|
of LoadReturnAddress:
|
||||||
|
self.loadAddressInstruction(opcode)
|
||||||
else:
|
else:
|
||||||
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
|
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
|
||||||
self.current += 1
|
self.current += 1
|
||||||
|
|
|
@ -20,7 +20,6 @@ import ../config
|
||||||
|
|
||||||
import strformat
|
import strformat
|
||||||
import strutils
|
import strutils
|
||||||
import nimSHA2
|
|
||||||
import times
|
import times
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +35,6 @@ type
|
||||||
## the Serializer.read*
|
## the Serializer.read*
|
||||||
## procedures to store
|
## procedures to store
|
||||||
## metadata
|
## metadata
|
||||||
fileHash*: string
|
|
||||||
version*: tuple[major, minor, patch: int]
|
version*: tuple[major, minor, patch: int]
|
||||||
branch*: string
|
branch*: string
|
||||||
commit*: string
|
commit*: string
|
||||||
|
@ -45,7 +43,7 @@ type
|
||||||
|
|
||||||
|
|
||||||
proc `$`*(self: Serialized): string =
|
proc `$`*(self: Serialized): string =
|
||||||
result = &"Serialized(fileHash={self.fileHash}, version={self.version.major}.{self.version.minor}.{self.version.patch}, branch={self.branch}), commitHash={self.commit}, date={self.compileDate}, chunk={self.chunk[]}"
|
result = &"Serialized(version={self.version.major}.{self.version.minor}.{self.version.patch}, branch={self.branch}), commitHash={self.commit}, date={self.compileDate}, chunk={self.chunk[]}"
|
||||||
|
|
||||||
|
|
||||||
proc error(self: Serializer, message: string) =
|
proc error(self: Serializer, message: string) =
|
||||||
|
@ -72,7 +70,6 @@ proc writeHeaders(self: Serializer, stream: var seq[byte], file: string) =
|
||||||
stream.extend(PEON_BRANCH.toBytes())
|
stream.extend(PEON_BRANCH.toBytes())
|
||||||
stream.extend(PEON_COMMIT_HASH.toBytes())
|
stream.extend(PEON_COMMIT_HASH.toBytes())
|
||||||
stream.extend(getTime().toUnixFloat().int().toBytes())
|
stream.extend(getTime().toUnixFloat().int().toBytes())
|
||||||
stream.extend(computeSHA256(file).toBytes())
|
|
||||||
|
|
||||||
|
|
||||||
proc writeLineData(self: Serializer, stream: var seq[byte]) =
|
proc writeLineData(self: Serializer, stream: var seq[byte]) =
|
||||||
|
@ -129,8 +126,7 @@ proc readHeaders(self: Serializer, stream: seq[byte], serialized: Serialized): i
|
||||||
stream[3], stream[4], stream[5], stream[6], stream[7]]))
|
stream[3], stream[4], stream[5], stream[6], stream[7]]))
|
||||||
stream = stream[8..^1]
|
stream = stream[8..^1]
|
||||||
result += 8
|
result += 8
|
||||||
serialized.fileHash = stream[0..<32].fromBytes().toHex().toLowerAscii()
|
|
||||||
result += 32
|
|
||||||
|
|
||||||
|
|
||||||
proc readLineData(self: Serializer, stream: seq[byte]): int =
|
proc readLineData(self: Serializer, stream: seq[byte]): int =
|
||||||
|
|
Loading…
Reference in New Issue