175 lines
4.3 KiB
Nim
175 lines
4.3 KiB
Nim
|
import strformat
|
||
|
import tables
|
||
|
|
||
|
import value
|
||
|
import chunk
|
||
|
import config
|
||
|
|
||
|
type
|
||
|
VM* = ref object
|
||
|
chunk: Chunk
|
||
|
ii: int
|
||
|
stack: seq[KonValue]
|
||
|
line: int
|
||
|
hadError: bool
|
||
|
globals: Table[string, KonValue]
|
||
|
|
||
|
InterpretResult* = enum
|
||
|
irOK, irRuntimeError
|
||
|
|
||
|
|
||
|
proc newVM*(ch: Chunk): VM =
|
||
|
VM(chunk: ch, ii: 0, stack: @[])
|
||
|
|
||
|
proc push(vm: VM, val: KonValue) =
|
||
|
vm.stack.add(val)
|
||
|
|
||
|
proc pop(vm: VM): KonValue =
|
||
|
vm.stack.pop()
|
||
|
|
||
|
proc peek(vm: VM): KonValue =
|
||
|
vm.stack[vm.stack.high]
|
||
|
|
||
|
proc runtimeError(vm: VM, msg: string) =
|
||
|
write stderr, &"[line: {vm.line}] {msg}\n"
|
||
|
vm.hadError = true
|
||
|
|
||
|
proc pushSafe(vm: VM, val: KonValue): bool =
|
||
|
## returns if the value is not a runtime error
|
||
|
## prints the error if it is a runtime error
|
||
|
## pushes it to the stack if it is not an error
|
||
|
if val.isError():
|
||
|
vm.runtimeError($val)
|
||
|
return false
|
||
|
else:
|
||
|
vm.push(val)
|
||
|
return true
|
||
|
|
||
|
proc advance(vm: VM): uint8 =
|
||
|
vm.ii.inc
|
||
|
vm.line = vm.chunk.lines[vm.ii-1]
|
||
|
vm.chunk.code[vm.ii-1]
|
||
|
|
||
|
proc readTriple(vm: VM): int =
|
||
|
[vm.advance, vm.advance, vm.advance].toInt
|
||
|
|
||
|
proc readConstant(vm: VM): KonValue =
|
||
|
let index = vm.readTriple()
|
||
|
vm.chunk.constants[index]
|
||
|
|
||
|
proc binary(op: OpCode, left: KonValue, right: KonValue): KonValue =
|
||
|
case op:
|
||
|
of opAdd:
|
||
|
return left.add(right)
|
||
|
of opSubtract:
|
||
|
return left.subtract(right)
|
||
|
of opMultiply:
|
||
|
return left.multiply(right)
|
||
|
of opDivide:
|
||
|
return left.divide(right)
|
||
|
else:
|
||
|
discard #unreachable
|
||
|
|
||
|
proc run*(vm: VM): InterpretResult =
|
||
|
|
||
|
while true:
|
||
|
when debugVM:
|
||
|
var msg = &"[{vm.ii}]"
|
||
|
msg &= " Stack: [ "
|
||
|
for e in vm.stack:
|
||
|
msg &= &"{e} "
|
||
|
msg &= "]"
|
||
|
echo msg
|
||
|
|
||
|
let ins = vm.advance.OpCode
|
||
|
case ins:
|
||
|
of opConstant:
|
||
|
let val: KonValue = vm.readConstant()
|
||
|
vm.push(val)
|
||
|
of opNegate:
|
||
|
let val = vm.pop.negate
|
||
|
if not vm.pushSafe(val):
|
||
|
break
|
||
|
of {opAdd, opSubtract, opMultiply, opDivide}:
|
||
|
let right = vm.pop()
|
||
|
let left = vm.pop()
|
||
|
if not vm.pushSafe(binary(ins, left, right)):
|
||
|
break
|
||
|
of opReturn:
|
||
|
discard vm.pop()
|
||
|
break
|
||
|
of opTrue:
|
||
|
vm.push(toKonValue(true))
|
||
|
of opFalse:
|
||
|
vm.push(toKonValue(false))
|
||
|
of opNil:
|
||
|
vm.push(toKonValue())
|
||
|
of opNot:
|
||
|
vm.push(toKonValue(vm.pop.isFalsey))
|
||
|
of opEqual:
|
||
|
vm.push(toKonValue(vm.pop.equal(vm.pop())))
|
||
|
of opLess:
|
||
|
if not vm.pushSafe(vm.pop() > vm.pop()):
|
||
|
break
|
||
|
of opGreater:
|
||
|
if not vm.pushSafe(vm.pop() < vm.pop()):
|
||
|
break
|
||
|
of opPrint:
|
||
|
echo $vm.peek()
|
||
|
of opDefineGlobal:
|
||
|
let name = vm.readConstant().stringValue
|
||
|
if not vm.globals.hasKey(name):
|
||
|
vm.globals[name] = vm.pop()
|
||
|
else:
|
||
|
vm.runtimeError("Attempt to redefine an existing global variable.")
|
||
|
break
|
||
|
of opGetGlobal:
|
||
|
let name = vm.readConstant().stringValue
|
||
|
if vm.globals.hasKey(name):
|
||
|
vm.push(vm.globals[name])
|
||
|
else:
|
||
|
vm.runtimeError(&"Undefined global variable {name}.")
|
||
|
break
|
||
|
of opSetGlobal:
|
||
|
let name = vm.readConstant().stringValue
|
||
|
if vm.globals.hasKey(name):
|
||
|
vm.globals[name] = vm.peek()
|
||
|
else:
|
||
|
vm.runtimeError(&"Attempt to set undefined global variable {name}.")
|
||
|
break
|
||
|
of opPop:
|
||
|
discard vm.pop()
|
||
|
of opGetLocal:
|
||
|
let slot = vm.readTriple()
|
||
|
vm.push(vm.stack[slot])
|
||
|
of opSetLocal:
|
||
|
let slot = vm.readTriple()
|
||
|
vm.stack[slot] = vm.peek()
|
||
|
of opJumpIfFalse:
|
||
|
let offset = vm.readTriple()
|
||
|
if vm.peek.isFalsey:
|
||
|
vm.ii += offset
|
||
|
of opJumpIfFalsePop:
|
||
|
let offset = vm.readTriple()
|
||
|
if vm.pop.isFalsey:
|
||
|
vm.ii += offset
|
||
|
of opJump:
|
||
|
let offset = vm.readTriple()
|
||
|
vm.ii += offset
|
||
|
of opLoop:
|
||
|
let offset = vm.readTriple()
|
||
|
vm.ii -= offset
|
||
|
|
||
|
when assertionsVM:
|
||
|
if not vm.hadError and vm.stack.len > 0:
|
||
|
vm.runtimeError(&"VM Assertion failed: stack is of non-zero length {vm.stack.len} after execution has finished.")
|
||
|
if vm.hadError:
|
||
|
irRuntimeError
|
||
|
else:
|
||
|
irOK
|
||
|
|
||
|
proc interpretChunk*(ch: Chunk): InterpretResult =
|
||
|
let vm = newVM(ch)
|
||
|
return vm.run()
|
||
|
|