Initial work on calls (with runtime support too)
This commit is contained in:
parent
50b0f98f6a
commit
5f43ea9490
|
@ -14,6 +14,7 @@
|
|||
|
||||
## The Peon runtime environment
|
||||
import types
|
||||
import strformat
|
||||
import ../config
|
||||
import ../frontend/meta/bytecode
|
||||
import ../util/multibyte
|
||||
|
@ -24,9 +25,9 @@ type
|
|||
## The Peon Virtual Machine
|
||||
stack: seq[PeonObject]
|
||||
ip: int # Instruction pointer
|
||||
sp: int # Stack pointer
|
||||
cache: array[6, PeonObject] # Singletons cache
|
||||
chunk: Chunk # Piece of bytecode to execute
|
||||
depth: int
|
||||
|
||||
|
||||
proc initCache*(self: PeonVM) =
|
||||
|
@ -45,11 +46,10 @@ proc newPeonVM*: PeonVM =
|
|||
## for executing Peon bytecode
|
||||
new(result)
|
||||
result.ip = 0
|
||||
result.sp = 0
|
||||
result.stack = newSeqOfCap[PeonObject](INITIAL_STACK_SIZE)
|
||||
result.depth = 0
|
||||
result.stack = newSeq[PeonObject]()
|
||||
result.initCache()
|
||||
for _ in 0..<INITIAL_STACK_SIZE:
|
||||
result.stack.add(result.cache[0])
|
||||
|
||||
|
||||
## Getters for singleton types (they are cached!)
|
||||
|
||||
|
@ -72,26 +72,21 @@ proc getNan*(self: PeonVM): PeonObject = self.cache[5]
|
|||
proc push(self: PeonVM, obj: PeonObject) =
|
||||
## Pushes a Peon object onto the
|
||||
## stack
|
||||
if self.sp >= self.stack.high():
|
||||
for _ in 0..self.stack.len():
|
||||
self.stack.add(self.getNil())
|
||||
self.stack[self.sp] = obj
|
||||
inc(self.sp)
|
||||
self.stack.add(obj)
|
||||
|
||||
|
||||
proc pop(self: PeonVM): PeonObject =
|
||||
## Pops a Peon object off the
|
||||
## stack, decreasing the stack
|
||||
## pointer. The object is returned
|
||||
dec(self.sp)
|
||||
return self.stack[self.sp]
|
||||
return self.stack.pop()
|
||||
|
||||
|
||||
proc peek(self: PeonVM): PeonObject =
|
||||
## Returns the element at the top
|
||||
## of the stack without consuming
|
||||
## it
|
||||
return self.stack[self.sp]
|
||||
return self.stack[^1]
|
||||
|
||||
|
||||
proc readByte(self: PeonVM): uint8 =
|
||||
|
@ -126,7 +121,10 @@ proc readInt64(self: PeonVM, idx: int): PeonObject =
|
|||
## returns a Peon object. Assumes
|
||||
## the constant is an Int64
|
||||
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 + 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: Int64)
|
||||
copyMem(result.long.addr, arr.addr, sizeof(arr))
|
||||
|
||||
|
@ -137,16 +135,36 @@ proc readUInt64(self: PeonVM, idx: int): PeonObject =
|
|||
## returns a Peon object. Assumes
|
||||
## the constant is an UInt64
|
||||
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 + 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: UInt64)
|
||||
copyMem(result.uLong.addr, arr.addr, sizeof(arr))
|
||||
|
||||
|
||||
proc readUInt32(self: PeonVM, idx: int): PeonObject =
|
||||
## Reads a constant from the
|
||||
## chunk's constant table and
|
||||
## returns a Peon object. Assumes
|
||||
## the constant is an UInt32
|
||||
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
|
||||
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
|
||||
result = PeonObject(kind: UInt32)
|
||||
copyMem(result.uInt.addr, arr.addr, sizeof(arr))
|
||||
|
||||
|
||||
proc dispatch*(self: PeonVM) =
|
||||
## Main bytecode dispatch loop
|
||||
var instruction: OpCode
|
||||
while true:
|
||||
instruction = OpCode(self.readByte())
|
||||
when DEBUG_TRACE_VM:
|
||||
echo &"Stack: {self.stack}"
|
||||
echo &"PC: {self.ip}"
|
||||
echo &"SP: {self.stack.high()}"
|
||||
echo &"Instruction: {instruction}"
|
||||
discard readLine stdin
|
||||
case instruction:
|
||||
of LoadTrue:
|
||||
self.push(self.getBool(true))
|
||||
|
@ -162,15 +180,25 @@ proc dispatch*(self: PeonVM) =
|
|||
self.push(self.readInt64(int(self.readLong())))
|
||||
of LoadUInt64:
|
||||
self.push(self.readUInt64(int(self.readLong())))
|
||||
of LoadUInt32:
|
||||
self.push(self.readUInt32(int(self.readLong())))
|
||||
of OpCode.Return:
|
||||
# TODO
|
||||
return
|
||||
if self.depth == 0:
|
||||
return
|
||||
else:
|
||||
self.ip = int(self.pop().uInt)
|
||||
of ReturnPop:
|
||||
var retVal = self.pop()
|
||||
self.ip = int(self.pop().uInt)
|
||||
self.push(retVal)
|
||||
of OpCode.LoadVar:
|
||||
self.push(self.stack[self.readLong()])
|
||||
of NoOp:
|
||||
continue
|
||||
of Pop:
|
||||
discard self.pop()
|
||||
of Call:
|
||||
self.ip = int(self.readLong())
|
||||
of Jump:
|
||||
self.ip = int(self.readShort())
|
||||
of JumpForwards:
|
||||
|
@ -217,6 +245,5 @@ proc dispatch*(self: PeonVM) =
|
|||
proc run*(self: PeonVM, chunk: Chunk) =
|
||||
## Executes a piece of Peon bytecode.
|
||||
self.chunk = chunk
|
||||
self.sp = 0
|
||||
self.ip = 0
|
||||
self.dispatch()
|
||||
|
|
|
@ -27,11 +27,10 @@ when len(PEON_COMMIT_HASH) != 40:
|
|||
const PEON_BRANCH* = "master"
|
||||
when len(PEON_BRANCH) > 255:
|
||||
{.fatal: "The git branch name's length must be less than or equal to 255 characters".}
|
||||
const DEBUG_TRACE_VM* = false # Traces VM execution
|
||||
const DEBUG_TRACE_VM* = true # Traces VM execution
|
||||
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
|
||||
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
|
||||
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
|
||||
const INITIAL_STACK_SIZE* = 0
|
||||
const PEON_VERSION_STRING* = &"Peon {PEON_VERSION.major}.{PEON_VERSION.minor}.{PEON_VERSION.patch} {PEON_RELEASE} ({PEON_BRANCH}, {CompileDate}, {CompileTime}, {PEON_COMMIT_HASH[0..8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
|
||||
const HELP_MESSAGE* = """The peon programming language, Copyright (C) 2022 Mattia Giambirtone & All Contributors
|
||||
|
||||
|
|
|
@ -140,6 +140,7 @@ type
|
|||
closedOver: seq[IdentExpr]
|
||||
|
||||
|
||||
|
||||
proc newCompiler*(enableOptimizations: bool = true): Compiler =
|
||||
## Initializes a new Compiler object
|
||||
new(result)
|
||||
|
@ -165,7 +166,7 @@ proc inferType(self: Compiler, node: Expression): Type
|
|||
proc findByName(self: Compiler, name: string): seq[Name]
|
||||
proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
|
||||
proc compareTypes(self: Compiler, a, b: Type): bool
|
||||
|
||||
proc patchReturnAddress(self: Compiler, retAddr: int)
|
||||
## End of forward declarations
|
||||
|
||||
## Public getter for nicer error formatting
|
||||
|
@ -724,6 +725,22 @@ proc unary(self: Compiler, node: UnaryExpr) =
|
|||
let impl = self.findByType(node.token.lexeme, Type(kind: Function, returnType: valueType, node: nil, args: @[valueType]))
|
||||
if impl.len() == 0:
|
||||
self.error(&"cannot find a suitable implementation for '{node.token.lexeme}'")
|
||||
elif impl.len() > 2:
|
||||
var msg = &"multiple matching implementations of '{node.token.lexeme}' found:\n"
|
||||
for fn in reversed(impl):
|
||||
var node = FunDecl(fn.valueType.node)
|
||||
discard self.typeToStr(fn.valueType)
|
||||
msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||
self.error(msg)
|
||||
else:
|
||||
# Pushes the return address
|
||||
self.emitByte(LoadUInt32)
|
||||
# We patch it later!
|
||||
let idx = self.chunk.consts.len()
|
||||
self.emitBytes(self.chunk.writeConstant((0xffffffff'u32).toQuad()))
|
||||
self.emitByte(Call)
|
||||
self.emitBytes(impl[0].codePos.toTriple())
|
||||
self.patchReturnAddress(idx)
|
||||
|
||||
|
||||
proc binary(self: Compiler, node: BinaryExpr) =
|
||||
|
@ -795,7 +812,7 @@ proc declareName(self: Compiler, node: Declaration) =
|
|||
returnType: self.inferType(
|
||||
node.returnType),
|
||||
args: @[]),
|
||||
codePos: self.chunk.code.len(),
|
||||
codePos: self.chunk.code.high(),
|
||||
name: node.name,
|
||||
isLet: false))
|
||||
let fn = self.names[^1]
|
||||
|
@ -813,8 +830,6 @@ proc declareName(self: Compiler, node: Declaration) =
|
|||
self.names[^1].valueType = self.inferType(argument.valueType)
|
||||
self.names[^1].valueType.node = argument.name
|
||||
fn.valueType.args.add(self.names[^1].valueType)
|
||||
self.emitByte(LoadVar)
|
||||
self.emitBytes(self.names.high().toTriple())
|
||||
else:
|
||||
discard # Unreachable
|
||||
|
||||
|
@ -839,7 +854,11 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
if not t.closedOver:
|
||||
# Static name resolution, loads value at index in the stack. Very fast. Much wow.
|
||||
self.emitByte(LoadVar)
|
||||
self.emitBytes(index.toTriple())
|
||||
if self.scopeDepth > 0:
|
||||
# Skips function's return address
|
||||
self.emitBytes((index - 1).toTriple())
|
||||
else:
|
||||
self.emitBytes(index.toTriple())
|
||||
else:
|
||||
if self.closedOver.len() == 0:
|
||||
self.error("error: closure variable array is empty but LoadHeap would be emitted (this is an internal error and most likely a bug)")
|
||||
|
@ -1277,6 +1296,16 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
|||
self.currentFunction = function
|
||||
|
||||
|
||||
proc patchReturnAddress(self: Compiler, retAddr: int) =
|
||||
## Patches the return address of a function
|
||||
## call. This is called at each iteration of
|
||||
## the compiler's loop
|
||||
let address = self.chunk.code.len().toQuad()
|
||||
self.chunk.consts[retAddr] = address[0]
|
||||
self.chunk.consts[retAddr + 1] = address[1]
|
||||
self.chunk.consts[retAddr + 2] = address[2]
|
||||
self.chunk.consts[retAddr + 3] = address[3]
|
||||
|
||||
|
||||
proc declaration(self: Compiler, node: Declaration) =
|
||||
## Compiles all declarations
|
||||
|
|
|
@ -147,9 +147,11 @@ const stackTripleInstructions* = {StoreVar, LoadVar, LoadHeap, StoreHeap}
|
|||
# of 16 bit integers
|
||||
const stackDoubleInstructions* = {}
|
||||
|
||||
# Argument double argument instructions take hardcoded arguments on the stack as 16 bit integers
|
||||
# Argument double argument instructions take hardcoded arguments as 16 bit integers
|
||||
const argumentDoubleInstructions* = {PopN, }
|
||||
|
||||
# Argument double argument instructions take hardcoded arguments as 24 bit integers
|
||||
const argumentTripleInstructions* = {Call, }
|
||||
# Jump instructions jump at relative or absolute bytecode offsets
|
||||
const jumpInstructions* = {Jump, LongJump, JumpIfFalse, JumpIfFalsePop,
|
||||
JumpForwards, JumpBackwards,
|
||||
|
|
66
src/main.nim
66
src/main.nim
|
@ -67,7 +67,6 @@ proc repl =
|
|||
input = editor.read()
|
||||
if input.len() == 0:
|
||||
continue
|
||||
# Currently the parser doesn't handle these tokens well
|
||||
tokens = tokenizer.lex(input, "stdin")
|
||||
if tokens.len() == 0:
|
||||
continue
|
||||
|
@ -168,13 +167,72 @@ proc repl =
|
|||
|
||||
proc runFile(f: string) =
|
||||
var
|
||||
tokens: seq[Token] = @[]
|
||||
tree: seq[Declaration] = @[]
|
||||
compiled: Chunk
|
||||
serialized: Serialized
|
||||
tokenizer = newLexer()
|
||||
parser = newParser()
|
||||
compiler = newCompiler()
|
||||
serializer = newSerializer()
|
||||
vm = newPeonVM()
|
||||
input: string
|
||||
tokenizer.fillSymbolTable()
|
||||
try:
|
||||
vm.run(compiler.compile(parser.parse(tokenizer.lex(readFile(f), f), f), f))
|
||||
input = readFile(f)
|
||||
tokens = tokenizer.lex(input, f)
|
||||
if tokens.len() == 0:
|
||||
return
|
||||
when debugLexer:
|
||||
styledEcho fgCyan, "Tokenization step:"
|
||||
for i, token in tokens:
|
||||
if i == tokens.high():
|
||||
# Who cares about EOF?
|
||||
break
|
||||
styledEcho fgGreen, "\t", $token
|
||||
echo ""
|
||||
tree = parser.parse(tokens, f)
|
||||
if tree.len() == 0:
|
||||
return
|
||||
when debugParser:
|
||||
styledEcho fgCyan, "Parsing step:"
|
||||
for node in tree:
|
||||
styledEcho fgGreen, "\t", $node
|
||||
echo ""
|
||||
compiled = compiler.compile(tree, f)
|
||||
when debugCompiler:
|
||||
styledEcho fgCyan, "Compilation step:"
|
||||
stdout.styledWrite(fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]")
|
||||
styledEcho fgCyan, "\n\nBytecode disassembler output below:\n"
|
||||
disassembleChunk(compiled, f)
|
||||
echo ""
|
||||
|
||||
serializer.dumpFile(compiled, input, f, splitFile(f).name & ".pbc")
|
||||
serialized = serializer.loadFile(splitFile(f).name & ".pbc")
|
||||
when debugSerializer:
|
||||
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
|
||||
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.peonVer.major}.{serialized.peonVer.minor}.{serialized.peonVer.patch}", fgBlue, " (commit ", fgYellow, serialized.commitHash[0..8], fgBlue, ") on branch ", fgYellow, serialized.peonBranch
|
||||
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
|
||||
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
|
||||
if serialized.chunk.consts == compiled.consts:
|
||||
styledEcho fgGreen, "OK"
|
||||
else:
|
||||
styledEcho fgRed, "Corrupted"
|
||||
stdout.styledWrite(fgBlue, &"\t- Code segment: ")
|
||||
if serialized.chunk.code == compiled.code:
|
||||
styledEcho fgGreen, "OK"
|
||||
else:
|
||||
styledEcho fgRed, "Corrupted"
|
||||
stdout.styledWrite(fgBlue, "\t- Line info segment: ")
|
||||
if serialized.chunk.lines == compiled.lines:
|
||||
styledEcho fgGreen, "OK"
|
||||
else:
|
||||
styledEcho fgRed, "Corrupted"
|
||||
when debugRuntime:
|
||||
styledEcho fgCyan, "\n\nExecution step: "
|
||||
vm.run(serialized.chunk)
|
||||
except LexingError:
|
||||
let exc = LexingError(getCurrentException())
|
||||
let relPos = tokenizer.getRelPos(exc.line)
|
||||
|
@ -218,9 +276,9 @@ proc runFile(f: string) =
|
|||
let exc = SerializationError(getCurrentException())
|
||||
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
|
||||
except IOError:
|
||||
stderr.styledWriteLine("An error occurred while trying to read ", fgYellow, &"'{f}'", fgRed, &": {getCurrentExceptionMsg()}")
|
||||
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {getCurrentExceptionMsg()}")
|
||||
except OSError:
|
||||
stderr.styledWriteLine("An error occurred while trying to read ", fgYellow, &"'{f}'", fgRed, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
|
||||
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
|
||||
|
||||
|
||||
when isMainModule:
|
||||
|
|
|
@ -3,4 +3,3 @@ operator `+`(a: int): int {
|
|||
}
|
||||
|
||||
+1; # Works: defined for int64
|
||||
+1'u8; # No definition for int8: error!
|
||||
|
|
|
@ -85,6 +85,17 @@ proc argumentDoubleInstruction(instruction: OpCode, chunk: Chunk, offset: int):
|
|||
return offset + 3
|
||||
|
||||
|
||||
proc argumentTripleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
||||
## Debugs instructions that operate on a hardcoded value on the stack using a 16-bit operand
|
||||
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
|
||||
printInstruction(instruction)
|
||||
stdout.styledWrite(fgGreen, &", has argument ")
|
||||
setForegroundColor(fgYellow)
|
||||
stdout.write(&"{slot}")
|
||||
nl()
|
||||
return offset + 4
|
||||
|
||||
|
||||
proc constantInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
||||
## Debugs instructions that operate on the constant table
|
||||
var constant = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
|
||||
|
@ -94,9 +105,8 @@ proc constantInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
|||
setForegroundColor(fgYellow)
|
||||
stdout.write(&"{constant}")
|
||||
nl()
|
||||
let obj = chunk.consts[constant]
|
||||
printDebug("Operand: ")
|
||||
stdout.styledWrite(fgYellow, &"{obj}\n")
|
||||
stdout.styledWriteLine(fgYellow, &"{chunk.consts[constant]}")
|
||||
return offset + 4
|
||||
|
||||
|
||||
|
@ -138,6 +148,8 @@ proc disassembleInstruction*(chunk: Chunk, offset: int): int =
|
|||
result = stackTripleInstruction(opcode, chunk, offset)
|
||||
of argumentDoubleInstructions:
|
||||
result = argumentDoubleInstruction(opcode, chunk, offset)
|
||||
of argumentTripleInstructions:
|
||||
result = argumentTripleInstruction(opcode, chunk, offset)
|
||||
of jumpInstructions:
|
||||
result = jumpInstruction(opcode, chunk, offset)
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue