diff --git a/projects/shoggoth/src/python/ichor/__init__.py b/projects/shoggoth/src/python/ichor/__init__.py index ab10030..375fa7c 100644 --- a/projects/shoggoth/src/python/ichor/__init__.py +++ b/projects/shoggoth/src/python/ichor/__init__.py @@ -1,5 +1,12 @@ -# noqa +#!/usr/bin/env python -from .bootstrap import * # noqa -from .impl import * # noqa -from .isa import * # noqa +""" +Import everything out of the Ichor modules. + +FIXME: A real public API here would be great, but somewhat un-pythonic. +""" + +from ichor.bootstrap import * # noqa +from ichor.impl import * # noqa +from ichor.isa import * # noqa +from ichor.typing import * # noqa diff --git a/projects/shoggoth/src/python/ichor/bootstrap.py b/projects/shoggoth/src/python/ichor/bootstrap.py index 027d295..dbeed0b 100644 --- a/projects/shoggoth/src/python/ichor/bootstrap.py +++ b/projects/shoggoth/src/python/ichor/bootstrap.py @@ -4,8 +4,8 @@ Some utterly trivial functions and types that allow me to begin testing the VM. Hopefully no "real" interpreter ever uses this code, since it's obviously replaceable. """ -from .isa import Module, Opcode -from .typing import ProductExpr, SumExpr +from ichor.isa import Opcode +from ichor.state import Module BOOTSTRAP = Module() diff --git a/projects/shoggoth/src/python/ichor/impl.py b/projects/shoggoth/src/python/ichor/impl.py index 6dce03b..832e757 100644 --- a/projects/shoggoth/src/python/ichor/impl.py +++ b/projects/shoggoth/src/python/ichor/impl.py @@ -9,16 +9,10 @@ context (a virtual machine) which DOES have an easily introspected and serialize """ -import sys - -from ichor.typing import Identifier - - -assert sys.version_info > (3, 10, 0), "`match` support is required" - from copy import deepcopy -from .isa import Closure, FunctionRef, Opcode, Identifier +from ichor.isa import Opcode +from ichor.typing import Closure, FunctionRef, Identifier def rotate(l): @@ -39,7 +33,7 @@ class Stackframe(object): def pop(self): return self.stack.pop(0) - def call(self, signature: FunctionRef, ip): + def call(self, signature: FunctionRef, ip) -> "Stackframe": self.ip += 1 nargs = len(signature.args) args, self.stack = self.stack[:nargs], self.stack[nargs:] @@ -51,7 +45,7 @@ class Stackframe(object): depth=self.depth+1 ) - def ret(self, nargs): + def ret(self, nargs) -> "Stackframe": self.parent.stack = self.stack[:nargs] + self.parent.stack return self.parent @@ -88,95 +82,95 @@ class Interpreter(object): def run(self, opcodes, stack=[]): """Directly interpret some opcodes in the configured environment.""" - stack = Stackframe(stack=stack) + stackframe = Stackframe(stack=stack) mod = self.bootstrap.copy() - stack.ip = mod.functions[mod.define_function(";
;;", opcodes)] + stackframe.ip = mod.functions[mod.define_function(";
;;", opcodes)] 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(stack), msg) + raise InterpreterError(mod, deepcopy(stackframe), msg) while True: - op = mod.opcodes[stack.ip] - print("{0}{1: <50} {2}: {3}".format(" " * stack.depth, str(stack.stack), stack.ip, op)) + op = mod.opcodes[stackframe.ip] + print("{0}{1: <50} {2}: {3}".format(" " * stackframe.depth, str(stackframe.stack), stackframe.ip, op)) match op: case Opcode.TRUE(): - stack.push(True) + stackframe.push(True) case Opcode.FALSE(): - stack.push(False) + stackframe.push(False) case Opcode.IF(target): - if len(stack) < 1: + if len(stackframe) < 1: _error("Stack size violation") - val = stack.pop() + val = stackframe.pop() if val not in [True, False]: _error("Type violation") if val is False: - stack.ip = target + stackframe.ip = target continue case Opcode.GOTO(n): if (n < 0): _error("Illegal branch target") - stack.ip = n + stackframe.ip = n continue case Opcode.DUP(n): - if (n > len(stack)): + if (n > len(stackframe)): _error("Stack size violation") - stack.dup(n) + stackframe.dup(n) case Opcode.ROT(n): - if (n > len(stack)): + if (n > len(stackframe)): _error("Stack size violation") - stack.rot(n) + stackframe.rot(n) case Opcode.DROP(n): - if (n > len(stack)): + if (n > len(stackframe)): _error("Stack size violation") - stack.drop(n) + stackframe.drop(n) case Opcode.SLOT(n): if (n < 0): _error("SLOT must have a positive reference") - if (n > len(stack.stack) - 1): + if (n > len(stackframe.stack) - 1): _error("SLOT reference out of range") - stack.push(stack.stack[len(stack) - n - 1]) + stackframe.push(stackframe.stack[len(stackframe) - n - 1]) case Opcode.IDENTIFIERC(name): if not (name in mod.functions or name in mod.types): _error("IDENTIFIERC references unknown entity") - stack.push(Identifier(name)) + stackframe.push(Identifier(name)) case Opcode.FUNREF(): - id = stack.pop() + id = stackframe.pop() if not isinstance(id, Identifier): _error("FUNREF consumes an IDENTIFIER") try: # FIXME: Verify this statically - stack.push(FunctionRef.parse(id.name)) + stackframe.push(FunctionRef.parse(id.name)) except: _error("Invalid function ref") case Opcode.CALLF(n): - sig = stack.pop() + sig = stackframe.pop() if not isinstance(sig, FunctionRef): _error("CALLF requires a funref at top of stack") if n != len(sig.args): _error("CALLF target violation; argument count missmatch") - if n > len(stack): + if n > len(stackframe): _error("Stack size violation") try: @@ -184,69 +178,69 @@ class Interpreter(object): except KeyError: _error("Unknown target") - stack = stack.call(sig, ip) + stackframe = stackframe.call(sig, ip) continue case Opcode.RETURN(n): - if (n > len(stack)): + if (n > len(stackframe)): _error("Stack size violation") - if stack.depth == 0: - return stack[:n] + if stackframe.depth == 0: + return stackframe[:n] - sig = FunctionRef.parse(stack.name) + sig = FunctionRef.parse(stackframe.name) if (len(sig.ret) != n): _error("Signature violation") - stack = stack.ret(n) + stackframe = stackframe.ret(n) continue case Opcode.CLOSUREF(n): - sig = stack.pop() + sig = stackframe.pop() if not isinstance(sig, FunctionRef): _error("CLOSUREF requires a funref at top of stack") if not n <= len(sig.args): _error("CLOSUREF target violation; too many parameters provided") - if n > len(stack): + if n > len(stackframe): _error("Stack size violation") c = Closure( sig, - stack.stack[:n] + stackframe.stack[:n] ) - stack.drop(n) - stack.push(c) + stackframe.drop(n) + stackframe.push(c) case Opcode.CLOSUREC(n): - c = stack.pop() + c = stackframe.pop() if not isinstance(c, Closure): _error("CLOSUREC requires a closure at top of stack") if n + len(c.frag) > len(c.funref.args): _error("CLOSUREC target violation; too many parameters provided") - if n > len(stack): + if n > len(stackframe): _error("Stack size violation") c = Closure( c.funref, - stack.stack[:n] + c.frag + stackframe.stack[:n] + c.frag ) - stack.drop(n) - stack.push(c) + stackframe.drop(n) + stackframe.push(c) case Opcode.CALLC(n): - c = stack.pop() + c = stackframe.pop() if not isinstance(c, Closure): _error("CALLC requires a closure at top of stack") if n + len(c.frag) != len(c.funref.args): _error("CALLC target vionation; argument count missmatch") - if n > len(stack): + if n > len(stackframe): _error("Stack size violation") # Extract the function signature sig = c.funref # Push the closure's stack fragment - stack.stack = c.frag + stack.stack + stackframe.stack = c.frag + stackframe.stack # Perform a "normal" funref call try: @@ -254,28 +248,10 @@ class Interpreter(object): except KeyError: _error("Unknown target") - stack = stack.call(sig, ip) + stackframe = stackframe.call(sig, ip) continue - case Opcode.STRUCT(structref, n): - if not (t := module.types.get(structref)): - _error(f"STRUCT must reference a known type, {structref!r} is undefined") - if n > len(stack): - _error("Stack size violation") - - args = stack[:n] - stack.drop(n) - - # FIXME: typecheck - for arg, ftype in zip(args, t.children): - pass - - inst = Struct(structref, [], dict(zip(t.field_names, args))) - - stack.push(inst) - - case _: raise Exception(f"Unhandled interpreter state {op}") - stack.ip += 1 + stackframe.ip += 1 diff --git a/projects/shoggoth/src/python/ichor/isa.py b/projects/shoggoth/src/python/ichor/isa.py index 0b5c72b..c65b5d5 100644 --- a/projects/shoggoth/src/python/ichor/isa.py +++ b/projects/shoggoth/src/python/ichor/isa.py @@ -3,8 +3,6 @@ import typing as t -from .typing import * - class Opcode: #################################################################################################### @@ -231,61 +229,3 @@ class Opcode: class BREAK(t.NamedTuple): """Abort the interpreter.""" pass - - -class Module(t.NamedTuple): - opcodes: list = [] - functions: dict = {} - types: dict = {} - constants: dict = {} - - def copy(self): - return Module( - self.opcodes.copy(), - self.functions.copy(), - self.types.copy(), - self.constants.copy(), - ) - - @staticmethod - def translate(start: int, end: int, i: "Opcode"): - # FIXME: Consolidate bounds checks somehow - match i: - case Opcode.IF(t): - d = t + start - assert start <= d < end - return Opcode.IF(d) - - case Opcode.GOTO(t): - d = t + start - assert start <= d < end - return Opcode.GOTO(d) - - case _: - return i - - def define_function(self, name, opcodes): - try: - sig = FunctionRef.parse(name) - assert sig.name - except: - raise ValueError("Illegal name provided") - - start = len(self.opcodes) - self.functions[name] = start - for op in opcodes: - self.opcodes.append(self.translate(start, start + len(opcodes), op)) - return name - - def define_type(self, name, signature): - self.types[name] = signature - return name - - def __str__(self): - b = [] - marks = {v: k for k, v in self.functions.items()} - for i, o in zip(range(1<<64), self.opcodes): - if(i in marks): - b.append(f"{marks[i]}:") - b.append(f"{i: >10}: {o}") - return "\n".join(b) diff --git a/projects/shoggoth/test/python/ichor/fixtures.py b/projects/shoggoth/test/python/ichor/fixtures.py index 0d12c13..5257a3d 100644 --- a/projects/shoggoth/test/python/ichor/fixtures.py +++ b/projects/shoggoth/test/python/ichor/fixtures.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from ichor import * +from ichor import Interpreter, BOOTSTRAP import pytest