Rewrite using handlers, singledispatch, hooks
This commit is contained in:
parent
a79644aa23
commit
b1a309527a
4 changed files with 355 additions and 239 deletions
|
@ -45,7 +45,7 @@ class FuncBuilder(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
self._stack -= getattr(op, 'nargs', 0)
|
self._stack -= getattr(op, "nargs", 0)
|
||||||
self._stack += 1
|
self._stack += 1
|
||||||
|
|
||||||
def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]):
|
def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]):
|
||||||
|
|
|
@ -9,8 +9,12 @@ context (a virtual machine) which DOES have an easily introspected and serialize
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from copy import deepcopy
|
from dataclasses import dataclass
|
||||||
from textwrap import indent
|
from functools import (
|
||||||
|
singledispatch,
|
||||||
|
update_wrapper,
|
||||||
|
)
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from ichor import isa
|
from ichor import isa
|
||||||
from ichor.state import (
|
from ichor.state import (
|
||||||
|
@ -25,13 +29,42 @@ from ichor.state import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InterpreterState(object):
|
||||||
|
module: Module
|
||||||
|
stackframe: Stackframe
|
||||||
|
clock: int = 0
|
||||||
|
|
||||||
|
|
||||||
class InterpreterError(Exception):
|
class InterpreterError(Exception):
|
||||||
"""An error raised by the interpreter when something goes awry."""
|
"""An error raised by the interpreter when something goes awry."""
|
||||||
|
|
||||||
def __init__(self, module, stack, message=None):
|
def __init__(self, state: InterpreterState, message: str, cause: Optional[Exception] = None):
|
||||||
self.module = module
|
super().__init__(message, cause)
|
||||||
self.stack = stack
|
self.state = state
|
||||||
super().__init__(message)
|
|
||||||
|
|
||||||
|
class InterpreterReturn(Exception):
|
||||||
|
def __init__(self, val):
|
||||||
|
super().__init__()
|
||||||
|
self.val = val
|
||||||
|
|
||||||
|
|
||||||
|
class InterpreterRestart(Exception):
|
||||||
|
def __init__(self, state: InterpreterState):
|
||||||
|
super().__init__()
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
|
||||||
|
def handledispatch(func):
|
||||||
|
dispatcher = singledispatch(func)
|
||||||
|
def wrapper(self, state, opcode):
|
||||||
|
assert isinstance(state, InterpreterState)
|
||||||
|
assert isinstance(opcode, isa.Opcode)
|
||||||
|
return dispatcher.dispatch(opcode.__class__)(self, state, opcode)
|
||||||
|
wrapper.register = dispatcher.register
|
||||||
|
update_wrapper(wrapper, func)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class Interpreter(object):
|
class Interpreter(object):
|
||||||
|
@ -39,238 +72,305 @@ class Interpreter(object):
|
||||||
def __init__(self, bootstrap_module: Module):
|
def __init__(self, bootstrap_module: Module):
|
||||||
self.bootstrap = bootstrap_module
|
self.bootstrap = bootstrap_module
|
||||||
|
|
||||||
|
def pre_instr(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||||
|
return state
|
||||||
|
|
||||||
|
def post_instr(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||||
|
return state
|
||||||
|
|
||||||
|
def step(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||||
|
return self.handle_opcode(state, opcode)
|
||||||
|
|
||||||
|
def handle_unknown(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||||
|
raise InterpreterError(state, "Unsupported operation: {opcode}")
|
||||||
|
|
||||||
|
def handle_fault(self, state, opcode, message, cause=None) -> InterpreterState:
|
||||||
|
raise InterpreterError(state, message, cause)
|
||||||
|
|
||||||
|
@handledispatch
|
||||||
|
def handle_opcode(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||||
|
return self.handle_unknown(state, opcode)
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.IDENTIFIERC)
|
||||||
|
def _handle_identifierc(self, state: InterpreterState, opcode: isa.IDENTIFIERC) -> InterpreterState:
|
||||||
|
name = opcode.val
|
||||||
|
if not (name in state.module.functions
|
||||||
|
or name in state.module.types
|
||||||
|
or any(name in t.constructors for t in state.module.types.values())):
|
||||||
|
return self.handle_fault(state, opcode, "IDENTIFIERC references unknown entity")
|
||||||
|
|
||||||
|
state.stackframe.push(Identifier(name))
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.TYPEREF)
|
||||||
|
def _handle_typeref(self, state, opcode) -> InterpreterState:
|
||||||
|
id = state.stackframe.pop()
|
||||||
|
if not isinstance(id, Identifier):
|
||||||
|
return self.handle_fault(state, opcode, "TYPEREF consumes an identifier")
|
||||||
|
|
||||||
|
if not id.name in state.module.types:
|
||||||
|
return self.handle_fault(state, opcode, "TYPEREF must be given a valid type identifier")
|
||||||
|
|
||||||
|
state.stackframe.push(TypeRef(id.name))
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.ARMREF)
|
||||||
|
def _handle_armref(self, state, opcode) -> InterpreterState:
|
||||||
|
id: Identifier = state.stackframe.pop()
|
||||||
|
if not isinstance(id, Identifier):
|
||||||
|
return self.handle_fault(state, opcode, "VARIANTREF consumes an identifier and a typeref")
|
||||||
|
|
||||||
|
t: TypeRef = state.stackframe.pop()
|
||||||
|
if not isinstance(t, TypeRef):
|
||||||
|
return self.handle_fault(state, opcode, "VARIANTREF consumes an identifier and a typeref")
|
||||||
|
|
||||||
|
type = state.module.types[t.name]
|
||||||
|
if id.name not in type.constructors:
|
||||||
|
return self.handle_fault(state, opcode, f"VARIANTREF given {id.name!r} which does not name a constructor within {type!r}")
|
||||||
|
|
||||||
|
state.stackframe.push(VariantRef(t, id.name))
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.ARM)
|
||||||
|
def _handle_arm(self, state: InterpreterState, opcode: isa.ARM) -> InterpreterState:
|
||||||
|
armref: VariantRef = state.stackframe.pop()
|
||||||
|
if not isinstance(armref, VariantRef):
|
||||||
|
return self.handle_fault(state, opcode, "VARIANT must be given a valid constructor reference")
|
||||||
|
|
||||||
|
ctor = state.module.types[armref.type.name].constructors[armref.arm]
|
||||||
|
if opcode.nargs != len(ctor):
|
||||||
|
return self.handle_fault(state, opcode, "VARIANT given n-args inconsistent with the type constructor")
|
||||||
|
|
||||||
|
if opcode.nargs > len(state.stackframe):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
# FIXME: Where does type variable to type binding occur?
|
||||||
|
# Certainly needs to be AT LEAST here, where we also need to be doing some typechecking
|
||||||
|
v = Variant(armref.type.name, armref.arm, tuple(state.stackframe[:opcode.nargs]))
|
||||||
|
state.stackframe.drop(opcode.nargs)
|
||||||
|
state.stackframe.push(v)
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.ATEST)
|
||||||
|
def _handle_atest(self, state: InterpreterState, opcode: isa.ATEST) -> InterpreterState:
|
||||||
|
armref: VariantRef = state.stackframe.pop()
|
||||||
|
if not isinstance(armref, VariantRef):
|
||||||
|
return self.handle_fault(state, opcode, "VTEST must be given a variant reference")
|
||||||
|
|
||||||
|
inst: Variant = state.stackframe.pop()
|
||||||
|
if not isinstance(inst, Variant):
|
||||||
|
return self.handle_fault(state, opcode, "VTEST must be given an instance of a variant")
|
||||||
|
|
||||||
|
if inst.type == armref.type.name and inst.variant == armref.arm:
|
||||||
|
state.stackframe.goto(opcode.target)
|
||||||
|
else:
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.GOTO)
|
||||||
|
def _handle_goto(self, state, opcode: isa.GOTO) -> InterpreterState:
|
||||||
|
if (opcode.target < 0):
|
||||||
|
return self.handle_fault(state, opcode, "Illegal branch target")
|
||||||
|
state.stackframe.goto(opcode.target)
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.DUP)
|
||||||
|
def _handle_dupe(self, state, opcode: isa.DUP) -> InterpreterState:
|
||||||
|
if (opcode.nargs > len(state.stackframe)):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
state.stackframe.dup(opcode.nargs)
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.ROT)
|
||||||
|
def _handle_rot(self, state, opcode: isa.DUP) -> InterpreterState:
|
||||||
|
if (opcode.nargs > len(state.stackframe)):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
state.stackframe.rot(opcode.nargs)
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.DROP)
|
||||||
|
def _handle_drop(self, state, opcode: isa.DROP) -> InterpreterState:
|
||||||
|
if (opcode.nargs > len(state.stackframe)):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
state.stackframe.drop(opcode.nargs)
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.SLOT)
|
||||||
|
def _handle_slot(self, state, opcode: isa.SLOT) -> InterpreterState:
|
||||||
|
if (opcode.target < 0):
|
||||||
|
return self.handle_fault(state, opcode, "SLOT must have a positive reference")
|
||||||
|
|
||||||
|
if (opcode.target > len(state.stackframe) - 1):
|
||||||
|
return self.handle_fault(state, opcode, "SLOT reference out of range")
|
||||||
|
|
||||||
|
state.stackframe.slot(opcode.target)
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.FUNREF)
|
||||||
|
def _handle_funref(self, state, opcode) -> InterpreterState:
|
||||||
|
id = state.stackframe.pop()
|
||||||
|
if not isinstance(id, Identifier):
|
||||||
|
return self.handle_fault(state, opcode, "FUNREF consumes an IDENTIFIER")
|
||||||
|
try:
|
||||||
|
# FIXME: Verify this statically
|
||||||
|
state.stackframe.push(FunctionRef.parse(id.name))
|
||||||
|
except:
|
||||||
|
return self.handle_fault(state, opcode, "Invalid function ref")
|
||||||
|
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.CALLF)
|
||||||
|
def _handle_callf(self, state, opcode: isa.CALLF) -> InterpreterState:
|
||||||
|
sig = state.stackframe.pop()
|
||||||
|
if not isinstance(sig, FunctionRef):
|
||||||
|
return self.handle_fault(state, opcode, "CALLF requires a funref at top of stack")
|
||||||
|
|
||||||
|
fun = state.module.functions[sig.name]
|
||||||
|
if opcode.nargs != len(fun.arguments):
|
||||||
|
return self.handle_fault(state, opcode, "CALLF target violation; argument count missmatch")
|
||||||
|
|
||||||
|
if opcode.nargs > len(state.stackframe):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip = state.module.labels[fun.signature]
|
||||||
|
state.stackframe = state.stackframe.call(fun, ip)
|
||||||
|
except KeyError:
|
||||||
|
return self.handle_fault(state, opcode, "Unknown FUNREF target")
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.RETURN)
|
||||||
|
def _handle_return(self, state, opcode: isa.RETURN) -> InterpreterState:
|
||||||
|
n = 1 # FIXME: clean this up
|
||||||
|
if (n > len(state.stackframe)):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
if state.stackframe.depth == 0:
|
||||||
|
raise InterpreterReturn(state.stackframe[:n])
|
||||||
|
|
||||||
|
if (len(state.stackframe._fun.returns) != n):
|
||||||
|
return self.handle_fault(state, opcode, "Signature violation")
|
||||||
|
|
||||||
|
state.stackframe = state.stackframe.ret(n)
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.CLOSUREF)
|
||||||
|
def _handle_closuref(self, state: InterpreterState, opcode: isa.CLOSUREF) -> InterpreterState:
|
||||||
|
n = opcode.nargs
|
||||||
|
|
||||||
|
sig = state.stackframe.pop()
|
||||||
|
if not isinstance(sig, FunctionRef):
|
||||||
|
return self.handle_fault(state, opcode, "CLOSUREF requires a funref at top of stack")
|
||||||
|
|
||||||
|
fun = state.module.functions[sig.name]
|
||||||
|
if not n <= len(fun.arguments):
|
||||||
|
return self.handle_fault(state, opcode, "CLOSUREF target violation; too many parameters provided")
|
||||||
|
|
||||||
|
if n > len(state.stackframe):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
c = Closure(
|
||||||
|
sig,
|
||||||
|
state.stackframe[:n]
|
||||||
|
)
|
||||||
|
state.stackframe.drop(n)
|
||||||
|
state.stackframe.push(c)
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.CLOSUREC)
|
||||||
|
def _handle_closurec(self, state, opcode: isa.CLOSUREC) -> InterpreterState:
|
||||||
|
n = opcode.nargs
|
||||||
|
|
||||||
|
c = state.stackframe.pop()
|
||||||
|
if not isinstance(c, Closure):
|
||||||
|
return self.handle_fault(state, opcode, "CLOSUREC requires a closure at top of stack")
|
||||||
|
|
||||||
|
fun = state.module.functions[c.funref.name]
|
||||||
|
if n + len(c.frag) > len(fun.arguments):
|
||||||
|
return self.handle_fault(state, opcode, "CLOSUREC target violation; too many parameters provided")
|
||||||
|
|
||||||
|
if n > len(state.stackframe):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
c = Closure(
|
||||||
|
c.funref,
|
||||||
|
state.stackframe[:n] + c.frag
|
||||||
|
)
|
||||||
|
state.stackframe.drop(n)
|
||||||
|
state.stackframe.push(c)
|
||||||
|
state.stackframe._ip += 1
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.CALLC)
|
||||||
|
def _handle_callc(self, state, opcode: isa.CALLC) -> InterpreterState:
|
||||||
|
n = opcode.nargs
|
||||||
|
c = state.stackframe.pop()
|
||||||
|
if not isinstance(c, Closure):
|
||||||
|
return self.handle_fault(state, opcode, "CALLC requires a closure at top of stack")
|
||||||
|
fun = state.module.functions[c.funref.name]
|
||||||
|
if n + len(c.frag) != len(fun.arguments):
|
||||||
|
return self.handle_fault(state, opcode, "CALLC target vionation; argument count missmatch")
|
||||||
|
if n > len(state.stackframe):
|
||||||
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
|
||||||
|
# Push the closure's stack fragment
|
||||||
|
state.stackframe._stack = c.frag + state.stackframe._stack
|
||||||
|
|
||||||
|
# Perform a "normal" funref call
|
||||||
|
try:
|
||||||
|
ip = state.module.labels[fun.signature]
|
||||||
|
except KeyError:
|
||||||
|
return self.handle_fault(state, opcode, "Unknown target")
|
||||||
|
|
||||||
|
state.stackframe = state.stackframe.call(fun, ip)
|
||||||
|
return state
|
||||||
|
|
||||||
|
@handle_opcode.register(isa.BREAK)
|
||||||
|
def _handle_break(self, state, _) -> InterpreterState:
|
||||||
|
raise InterpreterReturn(state.stackframe._stack)
|
||||||
|
|
||||||
def run(self, opcodes, stack=[]):
|
def run(self, opcodes, stack=[]):
|
||||||
"""Directly interpret some opcodes in the configured environment."""
|
"""Directly interpret some opcodes in the configured environment."""
|
||||||
|
|
||||||
mod = self.bootstrap.copy()
|
_mod = self.bootstrap.copy()
|
||||||
main = mod.define_function(";<main>;;", opcodes)
|
_main = _mod.define_function(";<main>;;", opcodes)
|
||||||
main_fun = mod.functions[main]
|
_main_fun = _mod.functions[_main]
|
||||||
main_ip = mod.labels[main]
|
_main_ip = _mod.labels[_main]
|
||||||
stackframe = Stackframe(main_fun, main_ip, stack)
|
|
||||||
clock: int = 0
|
|
||||||
|
|
||||||
print(mod)
|
|
||||||
|
|
||||||
def _error(msg=None):
|
|
||||||
# Note this is pretty expensive because we have to snapshot the stack BEFORE we do anything
|
|
||||||
# And the stack object isn't immutable or otherwise designed for cheap snapshotting
|
|
||||||
raise InterpreterError(mod, deepcopy(stackframe), msg)
|
|
||||||
|
|
||||||
def _debug():
|
|
||||||
b = []
|
|
||||||
b.append(f"clock {clock}:")
|
|
||||||
b.append(" stack:")
|
|
||||||
for offset, it in zip(range(0, len(stackframe), 1), stackframe):
|
|
||||||
b.append(f" {offset: <3} {it}")
|
|
||||||
b.append(f" op: {op}")
|
|
||||||
print(indent("\n".join(b), " " * stackframe.depth))
|
|
||||||
|
|
||||||
|
state = InterpreterState(
|
||||||
|
_mod, Stackframe(_main_fun, _main_ip, stack)
|
||||||
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
op = mod.codepage[stackframe._ip]
|
try:
|
||||||
_debug()
|
opcode = state.module.codepage[state.stackframe._ip]
|
||||||
clock += 1
|
self.pre_instr(state, opcode)
|
||||||
|
state = self.handle_opcode(state, opcode)
|
||||||
|
self.post_instr(state, opcode)
|
||||||
|
state.clock += 1
|
||||||
|
|
||||||
match op:
|
# FIXME: This case analysis isn't super obvious.
|
||||||
case isa.IDENTIFIERC(name):
|
except InterpreterReturn as r:
|
||||||
if not (name in mod.functions
|
return r.val
|
||||||
or name in mod.types
|
|
||||||
or any(name in t.constructors for t in mod.types.values())):
|
|
||||||
_error("IDENTIFIERC references unknown entity")
|
|
||||||
|
|
||||||
stackframe.push(Identifier(name))
|
except InterpreterRestart as r:
|
||||||
|
state = r.state
|
||||||
|
continue
|
||||||
|
|
||||||
case isa.TYPEREF():
|
except Exception as e:
|
||||||
id = stackframe.pop()
|
raise e
|
||||||
if not isinstance(id, Identifier):
|
|
||||||
_error("TYPEREF consumes an identifier")
|
|
||||||
if not id.name in mod.types:
|
|
||||||
_error("TYPEREF must be given a valid type identifier")
|
|
||||||
|
|
||||||
stackframe.push(TypeRef(id.name))
|
|
||||||
|
|
||||||
case isa.ARMREF():
|
|
||||||
id: Identifier = stackframe.pop()
|
|
||||||
if not isinstance(id, Identifier):
|
|
||||||
_error("VARIANTREF consumes an identifier and a typeref")
|
|
||||||
|
|
||||||
t: TypeRef = stackframe.pop()
|
|
||||||
if not isinstance(t, TypeRef):
|
|
||||||
_error("VARIANTREF consumes an identifier and a typeref")
|
|
||||||
|
|
||||||
type = mod.types[t.name]
|
|
||||||
if id.name not in type.constructors:
|
|
||||||
_error(f"VARIANTREF given {id.name!r} which does not name a constructor within {type!r}")
|
|
||||||
|
|
||||||
stackframe.push(VariantRef(t, id.name))
|
|
||||||
|
|
||||||
case isa.ARM(n):
|
|
||||||
armref: VariantRef = stackframe.pop()
|
|
||||||
if not isinstance(armref, VariantRef):
|
|
||||||
_error("VARIANT must be given a valid constructor reference")
|
|
||||||
|
|
||||||
ctor = mod.types[armref.type.name].constructors[armref.arm]
|
|
||||||
if n != len(ctor):
|
|
||||||
_error("VARIANT given n-args inconsistent with the type constructor")
|
|
||||||
|
|
||||||
if n > len(stackframe):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
# FIXME: Where does type variable to type binding occur?
|
|
||||||
# Certainly needs to be AT LEAST here, where we also need to be doing some typechecking
|
|
||||||
v = Variant(armref.type.name, armref.arm, tuple(stackframe[:n]))
|
|
||||||
stackframe.drop(n)
|
|
||||||
stackframe.push(v)
|
|
||||||
|
|
||||||
case isa.ATEST(n):
|
|
||||||
armref: VariantRef = stackframe.pop()
|
|
||||||
if not isinstance(armref, VariantRef):
|
|
||||||
_error("VTEST must be given a variant reference")
|
|
||||||
|
|
||||||
inst: Variant = stackframe.pop()
|
|
||||||
if not isinstance(inst, Variant):
|
|
||||||
_error("VTEST must be given an instance of a variant")
|
|
||||||
|
|
||||||
if inst.type == armref.type.name and inst.variant == armref.arm:
|
|
||||||
stackframe.goto(n)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case isa.GOTO(n):
|
|
||||||
if (n < 0):
|
|
||||||
_error("Illegal branch target")
|
|
||||||
stackframe.goto(n)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case isa.DUP(n):
|
|
||||||
if (n > len(stackframe)):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
stackframe.dup(n)
|
|
||||||
|
|
||||||
case isa.ROT(n):
|
|
||||||
if (n > len(stackframe)):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
stackframe.rot(n)
|
|
||||||
|
|
||||||
case isa.DROP(n):
|
|
||||||
if (n > len(stackframe)):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
stackframe.drop(n)
|
|
||||||
|
|
||||||
case isa.SLOT(n):
|
|
||||||
if (n < 0):
|
|
||||||
_error("SLOT must have a positive reference")
|
|
||||||
if (n > len(stackframe) - 1):
|
|
||||||
_error("SLOT reference out of range")
|
|
||||||
stackframe.slot(n)
|
|
||||||
|
|
||||||
case isa.FUNREF():
|
|
||||||
id = stackframe.pop()
|
|
||||||
if not isinstance(id, Identifier):
|
|
||||||
_error("FUNREF consumes an IDENTIFIER")
|
|
||||||
try:
|
|
||||||
# FIXME: Verify this statically
|
|
||||||
stackframe.push(FunctionRef.parse(id.name))
|
|
||||||
except:
|
|
||||||
_error("Invalid function ref")
|
|
||||||
|
|
||||||
case isa.CALLF(n):
|
|
||||||
sig = stackframe.pop()
|
|
||||||
if not isinstance(sig, FunctionRef):
|
|
||||||
_error("CALLF requires a funref at top of stack")
|
|
||||||
fun = mod.functions[sig.name]
|
|
||||||
if n != len(fun.arguments):
|
|
||||||
_error("CALLF target violation; argument count missmatch")
|
|
||||||
if n > len(stackframe):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
try:
|
|
||||||
ip = mod.labels[fun.signature]
|
|
||||||
except KeyError:
|
|
||||||
_error("Unknown target")
|
|
||||||
|
|
||||||
stackframe = stackframe.call(fun, ip)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case isa.RETURN():
|
|
||||||
n = 1 # FIXME: clean this up
|
|
||||||
if (n > len(stackframe)):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
if stackframe.depth == 0:
|
|
||||||
return stackframe[:n]
|
|
||||||
|
|
||||||
if (len(stackframe._fun.returns) != n):
|
|
||||||
_error("Signature violation")
|
|
||||||
|
|
||||||
stackframe = stackframe.ret(n)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case isa.CLOSUREF(n):
|
|
||||||
sig = stackframe.pop()
|
|
||||||
if not isinstance(sig, FunctionRef):
|
|
||||||
_error("CLOSUREF requires a funref at top of stack")
|
|
||||||
fun = mod.functions[sig.name]
|
|
||||||
if not n <= len(fun.arguments):
|
|
||||||
_error("CLOSUREF target violation; too many parameters provided")
|
|
||||||
if n > len(stackframe):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
c = Closure(
|
|
||||||
sig,
|
|
||||||
stackframe[:n]
|
|
||||||
)
|
|
||||||
stackframe.drop(n)
|
|
||||||
stackframe.push(c)
|
|
||||||
|
|
||||||
case isa.CLOSUREC(n):
|
|
||||||
c = stackframe.pop()
|
|
||||||
if not isinstance(c, Closure):
|
|
||||||
_error("CLOSUREC requires a closure at top of stack")
|
|
||||||
fun = mod.functions[c.funref.name]
|
|
||||||
if n + len(c.frag) > len(fun.arguments):
|
|
||||||
_error("CLOSUREC target violation; too many parameters provided")
|
|
||||||
if n > len(stackframe):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
c = Closure(
|
|
||||||
c.funref,
|
|
||||||
stackframe[:n] + c.frag
|
|
||||||
)
|
|
||||||
stackframe.drop(n)
|
|
||||||
stackframe.push(c)
|
|
||||||
|
|
||||||
case isa.CALLC(n):
|
|
||||||
c = stackframe.pop()
|
|
||||||
if not isinstance(c, Closure):
|
|
||||||
_error("CALLC requires a closure at top of stack")
|
|
||||||
fun = mod.functions[c.funref.name]
|
|
||||||
if n + len(c.frag) != len(fun.arguments):
|
|
||||||
_error("CALLC target vionation; argument count missmatch")
|
|
||||||
if n > len(stackframe):
|
|
||||||
_error("Stack size violation")
|
|
||||||
|
|
||||||
# Extract the function signature
|
|
||||||
|
|
||||||
# Push the closure's stack fragment
|
|
||||||
stackframe._stack = c.frag + stackframe._stack
|
|
||||||
|
|
||||||
# Perform a "normal" funref call
|
|
||||||
try:
|
|
||||||
ip = mod.labels[fun.signature]
|
|
||||||
except KeyError:
|
|
||||||
_error("Unknown target")
|
|
||||||
|
|
||||||
stackframe = stackframe.call(fun, ip)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case isa.BREAK():
|
|
||||||
# FIXME: let users override this / set custom handlers
|
|
||||||
return stackframe._stack
|
|
||||||
|
|
||||||
case _:
|
|
||||||
raise Exception(f"Unhandled interpreter state {op}")
|
|
||||||
|
|
||||||
stackframe._ip += 1
|
|
||||||
|
|
|
@ -1,10 +1,29 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from textwrap import indent
|
||||||
|
|
||||||
|
from ichor import isa
|
||||||
from ichor.bootstrap import BOOTSTRAP
|
from ichor.bootstrap import BOOTSTRAP
|
||||||
from ichor.interpreter import Interpreter
|
from ichor.interpreter import (
|
||||||
|
Interpreter,
|
||||||
|
InterpreterState,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingInterpreter(Interpreter):
|
||||||
|
def pre_instr(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||||
|
b = []
|
||||||
|
b.append(f"clock {state.clock}:")
|
||||||
|
b.append(" stack:")
|
||||||
|
for offset, it in zip(range(0, len(state.stackframe), 1), state.stackframe):
|
||||||
|
b.append(f" {offset: <3} {it}")
|
||||||
|
b.append(f" op: {opcode}")
|
||||||
|
print(indent("\n".join(b), " " * state.stackframe.depth))
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def vm():
|
def vm():
|
||||||
return Interpreter(BOOTSTRAP)
|
return LoggingInterpreter(BOOTSTRAP)
|
||||||
|
|
|
@ -4,11 +4,8 @@ Tests coverign the VM interpreter
|
||||||
|
|
||||||
from .fixtures import * # noqa
|
from .fixtures import * # noqa
|
||||||
|
|
||||||
from ichor.bootstrap import (
|
|
||||||
FALSE,
|
|
||||||
TRUE,
|
|
||||||
)
|
|
||||||
from ichor import isa
|
from ichor import isa
|
||||||
|
from ichor.bootstrap import FALSE, TRUE
|
||||||
from ichor.interpreter import InterpreterError
|
from ichor.interpreter import InterpreterError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue