319 lines
9.1 KiB
Nim
319 lines
9.1 KiB
Nim
import strformat
|
|
|
|
import chunk
|
|
import config
|
|
import pointerutils
|
|
|
|
import types/stack
|
|
import types/ndstring
|
|
import bitops # needed for value's templates
|
|
import types/value
|
|
import types/hashtable
|
|
import types/ndlist
|
|
|
|
|
|
when profileInstructions:
|
|
import times
|
|
import std/monotimes
|
|
|
|
# not a perfect profiling approach, doing random stacktrace dumps
|
|
# x amount of times per second to get the current source line
|
|
# would be better
|
|
var durations: array[OpCode, Duration]
|
|
var runcounts: array[OpCode, float64]
|
|
|
|
type
|
|
Frame = object
|
|
stackBottom: int # the absolute index of where 0 inside the frame is
|
|
returnIp: ptr uint8
|
|
|
|
InterpretResult* = enum
|
|
irOK, irRuntimeError
|
|
|
|
proc run*(chunk: Chunk): InterpretResult =
|
|
|
|
var
|
|
ip: ptr uint8 = chunk.code[0].unsafeAddr
|
|
stack: Stack[NdValue] = newStack[NdValue](256)
|
|
hadError: bool
|
|
globals: Table[NdValue, NdValue]
|
|
frames: Stack[Frame] = newStack[Frame](4)
|
|
|
|
proc runtimeError(msg: string) =
|
|
let ii = ip.pdiff(chunk.code[0].unsafeAddr)
|
|
let line = chunk.lines[ii]
|
|
write stderr, &"[line: {line}] {msg}\n"
|
|
hadError = true
|
|
|
|
frames.add(Frame(stackBottom: 0))
|
|
|
|
template popn(stack: var Stack[NdValue], amt: int) =
|
|
stack.deleteTopN(amt)
|
|
|
|
proc readUI8(): int =
|
|
result = ip[].int
|
|
ip = ip.padd(1)
|
|
|
|
proc readDU8(): int =
|
|
result = ip.DU8ptrToInt
|
|
ip = ip.padd(argSize)
|
|
|
|
proc readConstant(): NdValue =
|
|
let index = readDU8()
|
|
chunk.constants[index]
|
|
|
|
template frameBottom: int = frames.peek().stackBottom
|
|
|
|
while true:
|
|
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
|
|
|
|
let ins = ip[].OpCode
|
|
ip = ip.padd(1)
|
|
|
|
when debugVM:
|
|
let ii = ip.pdiff(chunk.code[0].unsafeAddr) - 1
|
|
let opname = ($ins)
|
|
var msg = &"[{ii:04}] {opname}"
|
|
msg &= " Stack: [ "
|
|
for i in 0 .. stack.high():
|
|
let e = stack[i]
|
|
if i == frameBottom:
|
|
msg &= &"<{e}> "
|
|
else:
|
|
msg &= &"{e} "
|
|
msg &= "]"
|
|
echo msg
|
|
|
|
when profileInstructions:
|
|
let startTime = getMonoTime()
|
|
runcounts[ins] += 1f
|
|
|
|
case ins:
|
|
of opPop:
|
|
discard stack.pop()
|
|
of opPopA:
|
|
let amt = readDU8()
|
|
stack.popn(amt)
|
|
of opPopSA:
|
|
let amt = readUI8()
|
|
stack.popn(amt)
|
|
of opConstant:
|
|
let val: NdValue = readConstant()
|
|
stack.add(val)
|
|
of opNegate:
|
|
let res = stack.peek().negate()
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opAdd:
|
|
let right = stack.pop()
|
|
let res = stack.peek().add(right)
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opSubtract:
|
|
let right = stack.pop()
|
|
let res = stack.peek().subtract(right)
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opMultiply:
|
|
let right = stack.pop()
|
|
let res = stack.peek().multiply(right)
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opDivide:
|
|
let right = stack.pop()
|
|
let res = stack.peek().divide(right)
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opReturn:
|
|
if frames.len() == 1:
|
|
break
|
|
else:
|
|
ip = frames.pop().returnIp # remove frame that's over
|
|
of opTrue:
|
|
stack.add(fromBool(true))
|
|
of opFalse:
|
|
stack.add(fromBool(false))
|
|
of opNil:
|
|
stack.add(fromNil())
|
|
of opNot: # TODO hayago += optimization
|
|
stack.add(fromBool(stack.pop().isFalsey()))
|
|
of opEqual:
|
|
stack.add(fromBool(stack.pop().equal(stack.pop())))
|
|
of opLess:
|
|
let right = stack.pop()
|
|
let res = stack.peek().less(right)
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opGreater:
|
|
let right = stack.pop()
|
|
let res = stack.peek().greater(right)
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opPrint:
|
|
echo $stack.peek()
|
|
of opDefineGlobal:
|
|
let name = readConstant().asString()
|
|
let existed = globals.tableSet(name.fromNdString(), stack.pop())
|
|
if existed:
|
|
runtimeError("Attempt to redefine an existing global variable.")
|
|
break
|
|
of opGetGlobal:
|
|
let name = readConstant().asString()
|
|
var val: NdValue
|
|
let existed = globals.tableGet(name.fromNdString(), val)
|
|
if existed:
|
|
stack.add(val)
|
|
else:
|
|
runtimeError(&"Undefined global variable {name}.")
|
|
break
|
|
of opSetGlobal:
|
|
let name = readConstant().asString()
|
|
let existed = globals.tableSet(name.fromNdString(), stack.peek())
|
|
if not existed:
|
|
runtimeError("Attempt to redefine an existing global variable.")
|
|
break
|
|
of opGetLocal:
|
|
let slot = readDU8()
|
|
stack.add(stack[slot + frameBottom])
|
|
of opSetLocal:
|
|
let slot = readDU8()
|
|
stack[slot + frameBottom] = stack.peek()
|
|
of opJumpIfFalse:
|
|
let offset = readDU8()
|
|
if stack.peek().isFalsey():
|
|
ip = ip.padd(offset)
|
|
of opJumpIfFalsePop:
|
|
let offset = readDU8()
|
|
if stack.pop().isFalsey():
|
|
ip = ip.padd(offset)
|
|
of opJump:
|
|
let offset = readDU8()
|
|
ip = ip.padd(offset)
|
|
of opLoop:
|
|
let offset = readDU8()
|
|
ip = ip.psub(offset)
|
|
of opFunctionDef:
|
|
let offset = readDU8()
|
|
let faddr: ptr uint8 = ip
|
|
ip = ip.padd(offset)
|
|
stack.push(faddr.fromFunct())
|
|
of opCheckArity:
|
|
let arity = readUI8()
|
|
let argcount = stack.high() - frameBottom
|
|
if arity != argcount:
|
|
runtimeError(&"Wrong number of arguments, expected {arity}, got {argcount}.")
|
|
break
|
|
of opCall:
|
|
# create the call env
|
|
# current stack before opCall:
|
|
# ... <funct obj> <arg1> <arg2> <arg3>
|
|
# opCall converts it to this
|
|
# ... <ret val> <arg1> <arg2> <arg3>
|
|
let argcount = readUI8()
|
|
let funct = stack.getIndexNeg(argcount)
|
|
if not funct.isFunct():
|
|
runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
|
|
break
|
|
|
|
stack.setIndexNeg(argcount, fromNil()) # replace the function with nil: this is the return value slot
|
|
|
|
# create new frame
|
|
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
|
|
|
ip = funct.asFunct() # jump to the entry point
|
|
of opCreateList:
|
|
let listLen = readDU8()
|
|
if listLen == 0:
|
|
stack.push(newList[NdValue]().fromList())
|
|
else:
|
|
let start = stack.getIndexNeg(listLen - 1).addr
|
|
var list = newListCopymem[NdValue](start, listLen)
|
|
stack.deleteTopN(listLen)
|
|
stack.push(list.fromList())
|
|
of opCreateTable:
|
|
let tblLen = readDU8()
|
|
var tbl = newNdTable[NdValue, NdValue](tblLen)
|
|
for i in countup(0, tblLen - 1):
|
|
let val = stack.pop()
|
|
let key = stack.pop()
|
|
if tbl[].tableSet(key, val):
|
|
runtimeError("Attempt to redefine an existing value inside table declaration.")
|
|
break
|
|
stack.deleteTopN(tblLen * 2)
|
|
stack.push(tbl.fromTable())
|
|
of opLen:
|
|
let res = stack.peek().getLength()
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opGetIndex:
|
|
let index = stack.pop()
|
|
let res = stack.peek().getIndex(index) # getIndex modifies the top value of the stack
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opSetIndex:
|
|
let value = stack.pop()
|
|
let index = stack.pop()
|
|
let res = stack.peek().setIndex(index, value)
|
|
if not res.ok:
|
|
runtimeError(res.msg)
|
|
break
|
|
of opChr:
|
|
let val = stack.peek()
|
|
if not val.isFloat():
|
|
runtimeError("chr on not float")
|
|
break
|
|
let floatval = val.asFloat()
|
|
if floatval > 255f or floatval < 0f:
|
|
runtimeError("chr on a float out of range")
|
|
break
|
|
let chr = floatval.char()
|
|
stack.settip((chr).newString().fromNdString())
|
|
of opInt:
|
|
let val = stack.peek()
|
|
if not val.isString():
|
|
runtimeError("int on non string")
|
|
break
|
|
let strval = val.asString()
|
|
if strval.getLength() == 0:
|
|
runtimeError("int on empty string")
|
|
break
|
|
let code = val.asString().getIndexAsChar(0).float()
|
|
stack.settip(code.fromFloat())
|
|
of opPutchar:
|
|
write stdout, $stack.peek()
|
|
|
|
|
|
|
|
when profileInstructions:
|
|
durations[ins] += getMonoTime() - startTime
|
|
|
|
when assertionsVM:
|
|
if not hadError and stack.len > 0:
|
|
runtimeError(&"VM Assertion failed: stack is of non-zero length {stack.len} after execution has finished.")
|
|
|
|
when profileInstructions:
|
|
for op in OpCode:
|
|
let dur = durations[op].inMilliseconds
|
|
let times = runcounts[op]
|
|
echo &"OpCode: {op} total duration {dur} ms {times} times"
|
|
|
|
stack.free()
|
|
frames.free()
|
|
globals.free()
|
|
|
|
if hadError:
|
|
irRuntimeError
|
|
else:
|
|
irOK
|
|
|
|
|