Initial work on a two-stack design

This commit is contained in:
Mattia Giambirtone 2022-06-02 01:33:56 +02:00
parent f8ab292c27
commit 099f733db6
9 changed files with 592 additions and 338 deletions

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -27,7 +27,7 @@ when len(PEON_COMMIT_HASH) != 40:
const PEON_BRANCH* = "master" const PEON_BRANCH* = "master"
when len(PEON_BRANCH) > 255: when len(PEON_BRANCH) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".} {.fatal: "The git branch name's length must be less than or equal to 255 characters".}
const DEBUG_TRACE_VM* = 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

View File

@ -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?")

View File

@ -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,

View File

@ -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) =

View File

@ -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

View File

@ -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 =