nondescript/vm.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()