Create a generic interpreter API
This commit is contained in:
parent
9feae8df4c
commit
ce23c6b43b
2 changed files with 120 additions and 65 deletions
|
@ -56,6 +56,94 @@ class InterpreterRestart(Exception):
|
||||||
self.state = state
|
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):
|
def handledispatch(func):
|
||||||
dispatcher = singledispatch(func)
|
dispatcher = singledispatch(func)
|
||||||
def wrapper(self, state, opcode):
|
def wrapper(self, state, opcode):
|
||||||
|
@ -67,31 +155,28 @@ def handledispatch(func):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class Interpreter(object):
|
class Interpreter(BaseInterpreter):
|
||||||
"""A shit .interpretere instruction pointer based interpreter."""
|
"""A fairly naive concrete interpreter based on singledispatch."""
|
||||||
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
|
|
||||||
|
|
||||||
def step(self, state: InterpreterState, opcode: isa.Opcode) -> InterpreterState:
|
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:
|
return self._handle_opcode(state, opcode)
|
||||||
raise InterpreterError(state, "Unsupported operation: {opcode}")
|
|
||||||
|
|
||||||
def handle_fault(self, state, opcode, message, cause=None) -> InterpreterState:
|
####################################################################################################################
|
||||||
raise InterpreterError(state, message, cause)
|
# BEGIN INSTRUCTION IMPLEMENTATIONS AT LENGTH
|
||||||
|
####################################################################################################################
|
||||||
|
|
||||||
@handledispatch
|
@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)
|
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:
|
def _handle_identifierc(self, state: InterpreterState, opcode: isa.IDENTIFIERC) -> InterpreterState:
|
||||||
name = opcode.val
|
name = opcode.val
|
||||||
if not (name in state.module.functions
|
if not (name in state.module.functions
|
||||||
|
@ -103,7 +188,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.TYPEREF)
|
@_handle_opcode.register(isa.TYPEREF)
|
||||||
def _handle_typeref(self, state, opcode) -> InterpreterState:
|
def _handle_typeref(self, state, opcode) -> InterpreterState:
|
||||||
id = state.stackframe.pop()
|
id = state.stackframe.pop()
|
||||||
if not isinstance(id, Identifier):
|
if not isinstance(id, Identifier):
|
||||||
|
@ -116,7 +201,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.ARMREF)
|
@_handle_opcode.register(isa.ARMREF)
|
||||||
def _handle_armref(self, state, opcode) -> InterpreterState:
|
def _handle_armref(self, state, opcode) -> InterpreterState:
|
||||||
id: Identifier = state.stackframe.pop()
|
id: Identifier = state.stackframe.pop()
|
||||||
if not isinstance(id, Identifier):
|
if not isinstance(id, Identifier):
|
||||||
|
@ -134,7 +219,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.ARM)
|
@_handle_opcode.register(isa.ARM)
|
||||||
def _handle_arm(self, state: InterpreterState, opcode: isa.ARM) -> InterpreterState:
|
def _handle_arm(self, state: InterpreterState, opcode: isa.ARM) -> InterpreterState:
|
||||||
armref: VariantRef = state.stackframe.pop()
|
armref: VariantRef = state.stackframe.pop()
|
||||||
if not isinstance(armref, VariantRef):
|
if not isinstance(armref, VariantRef):
|
||||||
|
@ -155,7 +240,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.ATEST)
|
@_handle_opcode.register(isa.ATEST)
|
||||||
def _handle_atest(self, state: InterpreterState, opcode: isa.ATEST) -> InterpreterState:
|
def _handle_atest(self, state: InterpreterState, opcode: isa.ATEST) -> InterpreterState:
|
||||||
armref: VariantRef = state.stackframe.pop()
|
armref: VariantRef = state.stackframe.pop()
|
||||||
if not isinstance(armref, VariantRef):
|
if not isinstance(armref, VariantRef):
|
||||||
|
@ -172,14 +257,14 @@ class Interpreter(object):
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.GOTO)
|
@_handle_opcode.register(isa.GOTO)
|
||||||
def _handle_goto(self, state, opcode: isa.GOTO) -> InterpreterState:
|
def _handle_goto(self, state, opcode: isa.GOTO) -> InterpreterState:
|
||||||
if (opcode.target < 0):
|
if (opcode.target < 0):
|
||||||
return self.handle_fault(state, opcode, "Illegal branch target")
|
return self.handle_fault(state, opcode, "Illegal branch target")
|
||||||
state.stackframe.goto(opcode.target)
|
state.stackframe.goto(opcode.target)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.DUP)
|
@_handle_opcode.register(isa.DUP)
|
||||||
def _handle_dupe(self, state, opcode: isa.DUP) -> InterpreterState:
|
def _handle_dupe(self, state, opcode: isa.DUP) -> InterpreterState:
|
||||||
if (opcode.nargs > len(state.stackframe)):
|
if (opcode.nargs > len(state.stackframe)):
|
||||||
return self.handle_fault(state, opcode, "Stack size violation")
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
@ -188,7 +273,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.ROT)
|
@_handle_opcode.register(isa.ROT)
|
||||||
def _handle_rot(self, state, opcode: isa.DUP) -> InterpreterState:
|
def _handle_rot(self, state, opcode: isa.DUP) -> InterpreterState:
|
||||||
if (opcode.nargs > len(state.stackframe)):
|
if (opcode.nargs > len(state.stackframe)):
|
||||||
return self.handle_fault(state, opcode, "Stack size violation")
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
@ -197,7 +282,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.DROP)
|
@_handle_opcode.register(isa.DROP)
|
||||||
def _handle_drop(self, state, opcode: isa.DROP) -> InterpreterState:
|
def _handle_drop(self, state, opcode: isa.DROP) -> InterpreterState:
|
||||||
if (opcode.nargs > len(state.stackframe)):
|
if (opcode.nargs > len(state.stackframe)):
|
||||||
return self.handle_fault(state, opcode, "Stack size violation")
|
return self.handle_fault(state, opcode, "Stack size violation")
|
||||||
|
@ -206,7 +291,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.SLOT)
|
@_handle_opcode.register(isa.SLOT)
|
||||||
def _handle_slot(self, state, opcode: isa.SLOT) -> InterpreterState:
|
def _handle_slot(self, state, opcode: isa.SLOT) -> InterpreterState:
|
||||||
if (opcode.target < 0):
|
if (opcode.target < 0):
|
||||||
return self.handle_fault(state, opcode, "SLOT must have a positive reference")
|
return self.handle_fault(state, opcode, "SLOT must have a positive reference")
|
||||||
|
@ -218,7 +303,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.FUNREF)
|
@_handle_opcode.register(isa.FUNREF)
|
||||||
def _handle_funref(self, state, opcode) -> InterpreterState:
|
def _handle_funref(self, state, opcode) -> InterpreterState:
|
||||||
id = state.stackframe.pop()
|
id = state.stackframe.pop()
|
||||||
if not isinstance(id, Identifier):
|
if not isinstance(id, Identifier):
|
||||||
|
@ -232,7 +317,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.CALLF)
|
@_handle_opcode.register(isa.CALLF)
|
||||||
def _handle_callf(self, state, opcode: isa.CALLF) -> InterpreterState:
|
def _handle_callf(self, state, opcode: isa.CALLF) -> InterpreterState:
|
||||||
sig = state.stackframe.pop()
|
sig = state.stackframe.pop()
|
||||||
if not isinstance(sig, FunctionRef):
|
if not isinstance(sig, FunctionRef):
|
||||||
|
@ -253,7 +338,7 @@ class Interpreter(object):
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.RETURN)
|
@_handle_opcode.register(isa.RETURN)
|
||||||
def _handle_return(self, state, opcode: isa.RETURN) -> InterpreterState:
|
def _handle_return(self, state, opcode: isa.RETURN) -> InterpreterState:
|
||||||
n = 1 # FIXME: clean this up
|
n = 1 # FIXME: clean this up
|
||||||
if (n > len(state.stackframe)):
|
if (n > len(state.stackframe)):
|
||||||
|
@ -268,7 +353,7 @@ class Interpreter(object):
|
||||||
state.stackframe = state.stackframe.ret(n)
|
state.stackframe = state.stackframe.ret(n)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.CLOSUREF)
|
@_handle_opcode.register(isa.CLOSUREF)
|
||||||
def _handle_closuref(self, state: InterpreterState, opcode: isa.CLOSUREF) -> InterpreterState:
|
def _handle_closuref(self, state: InterpreterState, opcode: isa.CLOSUREF) -> InterpreterState:
|
||||||
n = opcode.nargs
|
n = opcode.nargs
|
||||||
|
|
||||||
|
@ -292,7 +377,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.CLOSUREC)
|
@_handle_opcode.register(isa.CLOSUREC)
|
||||||
def _handle_closurec(self, state, opcode: isa.CLOSUREC) -> InterpreterState:
|
def _handle_closurec(self, state, opcode: isa.CLOSUREC) -> InterpreterState:
|
||||||
n = opcode.nargs
|
n = opcode.nargs
|
||||||
|
|
||||||
|
@ -316,7 +401,7 @@ class Interpreter(object):
|
||||||
state.stackframe._ip += 1
|
state.stackframe._ip += 1
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.CALLC)
|
@_handle_opcode.register(isa.CALLC)
|
||||||
def _handle_callc(self, state, opcode: isa.CALLC) -> InterpreterState:
|
def _handle_callc(self, state, opcode: isa.CALLC) -> InterpreterState:
|
||||||
n = opcode.nargs
|
n = opcode.nargs
|
||||||
c = state.stackframe.pop()
|
c = state.stackframe.pop()
|
||||||
|
@ -340,37 +425,6 @@ class Interpreter(object):
|
||||||
state.stackframe = state.stackframe.call(fun, ip)
|
state.stackframe = state.stackframe.call(fun, ip)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@handle_opcode.register(isa.BREAK)
|
@_handle_opcode.register(isa.BREAK)
|
||||||
def _handle_break(self, state, _) -> InterpreterState:
|
def _handle_break(self, state, _) -> InterpreterState:
|
||||||
raise InterpreterReturn(state.stackframe._stack)
|
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 import isa
|
||||||
from ichor.bootstrap import BOOTSTRAP
|
from ichor.bootstrap import BOOTSTRAP
|
||||||
from ichor.interpreter import (
|
from ichor.interpreter import (
|
||||||
|
BaseInterpreter,
|
||||||
Interpreter,
|
Interpreter,
|
||||||
InterpreterState,
|
InterpreterState,
|
||||||
)
|
)
|
||||||
|
@ -25,5 +26,5 @@ class LoggingInterpreter(Interpreter):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def vm():
|
def vm() -> BaseInterpreter:
|
||||||
return LoggingInterpreter(BOOTSTRAP)
|
return LoggingInterpreter(BOOTSTRAP)
|
||||||
|
|
Loading…
Reference in a new issue