Create a generic interpreter API
This commit is contained in:
parent
66d90b1d44
commit
559e334e85
2 changed files with 120 additions and 65 deletions
|
@ -56,6 +56,94 @@ class InterpreterRestart(Exception):
|
|||
self.state = state
|
||||
|
||||
|
||||
class BaseInterpreter(object):
|
||||
"""A generic base class providing a common interface for Ichor interpreters/evaluators."""
|
||||
|
||||
def __init__(self, bootstrap_module: Module):
|
||||
self.bootstrap = bootstrap_module
|
||||
|
||||
def pre_instr(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||
"""Hook. Runs after fetch and before every instruction.
|
||||
|
||||
Expected to return the working interpreter state, but may transform it if needed.
|
||||
Implementations MUST call the superclass implementation.
|
||||
|
||||
"""
|
||||
|
||||
return state
|
||||
|
||||
def post_instr(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||
"""Hook. Runs after instruction handling and before the cycle completes.
|
||||
|
||||
Expected to return the working interpreter state, but may transform it if needed.
|
||||
Implementations MUST call the superclass implementation.
|
||||
|
||||
"""
|
||||
|
||||
state.clock += 1
|
||||
return state
|
||||
|
||||
def handle_fault(self, state, opcode, message, cause=None) -> InterpreterState:
|
||||
"""Handler for interpreter errors.
|
||||
|
||||
Called when the interpreter encounters an illegal or incorrect state.
|
||||
Not called for unhandled exceptions.
|
||||
May perform arbitrary effects or interface with the user.
|
||||
May resume the interpreter by raising an InterpreterRestart, or returning.
|
||||
Need not call the superclass implementation.
|
||||
|
||||
"""
|
||||
|
||||
raise InterpreterError(state, message, cause)
|
||||
|
||||
def handle_unknown(self, state, opcode) -> InterpreterState:
|
||||
"""Handler for unknown opcodes.
|
||||
|
||||
Called when the interpreter encounters an unsupported operation.
|
||||
May perform arbitrary effects or interface with the user.
|
||||
May resume the interpreter by raising an InterpreterRestart, or returning.
|
||||
Need not call the superclass implementation.
|
||||
|
||||
"""
|
||||
|
||||
raise InterpreterError(state, "Unsupported operation: {opcode}")
|
||||
|
||||
def step(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||
"""Execute an instruction, producing the next state of the VM."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def run(self, opcodes, stack=[]):
|
||||
"""Directly interpret some opcodes in the configured environment."""
|
||||
|
||||
_mod = self.bootstrap.copy()
|
||||
_main = _mod.define_function(";<main>;;", opcodes)
|
||||
_main_fun = _mod.functions[_main]
|
||||
_main_ip = _mod.labels[_main]
|
||||
|
||||
state = InterpreterState(
|
||||
_mod, Stackframe(_main_fun, _main_ip, stack)
|
||||
)
|
||||
|
||||
while True:
|
||||
try:
|
||||
opcode = state.module.codepage[state.stackframe._ip]
|
||||
self.pre_instr(state, opcode)
|
||||
state = self.step(state, opcode)
|
||||
self.post_instr(state, opcode)
|
||||
|
||||
# FIXME: This case analysis isn't super obvious.
|
||||
except InterpreterReturn as r:
|
||||
return r.val
|
||||
|
||||
except InterpreterRestart as r:
|
||||
state = r.state
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
def handledispatch(func):
|
||||
dispatcher = singledispatch(func)
|
||||
def wrapper(self, state, opcode):
|
||||
|
@ -67,31 +155,28 @@ def handledispatch(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
class Interpreter(object):
|
||||
"""A shit .interpretere instruction pointer based interpreter."""
|
||||
def __init__(self, bootstrap_module: 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
|
||||
class Interpreter(BaseInterpreter):
|
||||
"""A fairly naive concrete interpreter based on singledispatch."""
|
||||
|
||||
def step(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||
return self.handle_opcode(state, opcode)
|
||||
"""Execute an instruction, producing the next state of the VM."""
|
||||
|
||||
def handle_unknown(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||
raise InterpreterError(state, "Unsupported operation: {opcode}")
|
||||
return self._handle_opcode(state, opcode)
|
||||
|
||||
def handle_fault(self, state, opcode, message, cause=None) -> InterpreterState:
|
||||
raise InterpreterError(state, message, cause)
|
||||
####################################################################################################################
|
||||
# BEGIN INSTRUCTION IMPLEMENTATIONS AT LENGTH
|
||||
####################################################################################################################
|
||||
|
||||
@handledispatch
|
||||
def handle_opcode(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||
def _handle_opcode(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
||||
"""Generic entrypoint. Exists to establish a type signature and provide singledispatch handling."""
|
||||
return self.handle_unknown(state, opcode)
|
||||
|
||||
@handle_opcode.register(isa.IDENTIFIERC)
|
||||
@_handle_opcode.register(object)
|
||||
def _handle_unknown(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
|
||||
|
@ -103,7 +188,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.TYPEREF)
|
||||
@_handle_opcode.register(isa.TYPEREF)
|
||||
def _handle_typeref(self, state, opcode) -> InterpreterState:
|
||||
id = state.stackframe.pop()
|
||||
if not isinstance(id, Identifier):
|
||||
|
@ -116,7 +201,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.ARMREF)
|
||||
@_handle_opcode.register(isa.ARMREF)
|
||||
def _handle_armref(self, state, opcode) -> InterpreterState:
|
||||
id: Identifier = state.stackframe.pop()
|
||||
if not isinstance(id, Identifier):
|
||||
|
@ -134,7 +219,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.ARM)
|
||||
@_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):
|
||||
|
@ -155,7 +240,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.ATEST)
|
||||
@_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):
|
||||
|
@ -172,14 +257,14 @@ class Interpreter(object):
|
|||
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.GOTO)
|
||||
@_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)
|
||||
@_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")
|
||||
|
@ -188,7 +273,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.ROT)
|
||||
@_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")
|
||||
|
@ -197,7 +282,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.DROP)
|
||||
@_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")
|
||||
|
@ -206,7 +291,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.SLOT)
|
||||
@_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")
|
||||
|
@ -218,7 +303,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.FUNREF)
|
||||
@_handle_opcode.register(isa.FUNREF)
|
||||
def _handle_funref(self, state, opcode) -> InterpreterState:
|
||||
id = state.stackframe.pop()
|
||||
if not isinstance(id, Identifier):
|
||||
|
@ -232,7 +317,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.CALLF)
|
||||
@_handle_opcode.register(isa.CALLF)
|
||||
def _handle_callf(self, state, opcode: isa.CALLF) -> InterpreterState:
|
||||
sig = state.stackframe.pop()
|
||||
if not isinstance(sig, FunctionRef):
|
||||
|
@ -253,7 +338,7 @@ class Interpreter(object):
|
|||
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.RETURN)
|
||||
@_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)):
|
||||
|
@ -268,7 +353,7 @@ class Interpreter(object):
|
|||
state.stackframe = state.stackframe.ret(n)
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.CLOSUREF)
|
||||
@_handle_opcode.register(isa.CLOSUREF)
|
||||
def _handle_closuref(self, state: InterpreterState, opcode: isa.CLOSUREF) -> InterpreterState:
|
||||
n = opcode.nargs
|
||||
|
||||
|
@ -292,7 +377,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.CLOSUREC)
|
||||
@_handle_opcode.register(isa.CLOSUREC)
|
||||
def _handle_closurec(self, state, opcode: isa.CLOSUREC) -> InterpreterState:
|
||||
n = opcode.nargs
|
||||
|
||||
|
@ -316,7 +401,7 @@ class Interpreter(object):
|
|||
state.stackframe._ip += 1
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.CALLC)
|
||||
@_handle_opcode.register(isa.CALLC)
|
||||
def _handle_callc(self, state, opcode: isa.CALLC) -> InterpreterState:
|
||||
n = opcode.nargs
|
||||
c = state.stackframe.pop()
|
||||
|
@ -340,37 +425,6 @@ class Interpreter(object):
|
|||
state.stackframe = state.stackframe.call(fun, ip)
|
||||
return state
|
||||
|
||||
@handle_opcode.register(isa.BREAK)
|
||||
@_handle_opcode.register(isa.BREAK)
|
||||
def _handle_break(self, state, _) -> InterpreterState:
|
||||
raise InterpreterReturn(state.stackframe._stack)
|
||||
|
||||
def run(self, opcodes, stack=[]):
|
||||
"""Directly interpret some opcodes in the configured environment."""
|
||||
|
||||
_mod = self.bootstrap.copy()
|
||||
_main = _mod.define_function(";<main>;;", opcodes)
|
||||
_main_fun = _mod.functions[_main]
|
||||
_main_ip = _mod.labels[_main]
|
||||
|
||||
state = InterpreterState(
|
||||
_mod, Stackframe(_main_fun, _main_ip, stack)
|
||||
)
|
||||
|
||||
while True:
|
||||
try:
|
||||
opcode = state.module.codepage[state.stackframe._ip]
|
||||
self.pre_instr(state, opcode)
|
||||
state = self.handle_opcode(state, opcode)
|
||||
self.post_instr(state, opcode)
|
||||
state.clock += 1
|
||||
|
||||
# FIXME: This case analysis isn't super obvious.
|
||||
except InterpreterReturn as r:
|
||||
return r.val
|
||||
|
||||
except InterpreterRestart as r:
|
||||
state = r.state
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
|
|
@ -5,6 +5,7 @@ from textwrap import indent
|
|||
from ichor import isa
|
||||
from ichor.bootstrap import BOOTSTRAP
|
||||
from ichor.interpreter import (
|
||||
BaseInterpreter,
|
||||
Interpreter,
|
||||
InterpreterState,
|
||||
)
|
||||
|
@ -25,5 +26,5 @@ class LoggingInterpreter(Interpreter):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def vm():
|
||||
def vm() -> BaseInterpreter:
|
||||
return LoggingInterpreter(BOOTSTRAP)
|
||||
|
|
Loading…
Reference in a new issue