diff --git a/components/milkshake/src/python/milkshake/__init__.py b/components/milkshake/src/python/milkshake/__init__.py index 3aa544d..b0828f3 100644 --- a/components/milkshake/src/python/milkshake/__init__.py +++ b/components/milkshake/src/python/milkshake/__init__.py @@ -3,7 +3,7 @@ from importlib.resources import files -from attrs import define, field +from attrs import define from lark import Lark, Tree, Token, Transformer, v_args @@ -11,7 +11,7 @@ with files(__package__).joinpath("grammar.lark").open("r", encoding="utf-8") as GRAMMAR = fp.read() -@define +@define(frozen=True) class Symbol: name: str diff --git a/components/typhon/src/python/typhon/evaluator.py b/components/typhon/src/python/typhon/evaluator.py index 1e04309..e31f4d7 100644 --- a/components/typhon/src/python/typhon/evaluator.py +++ b/components/typhon/src/python/typhon/evaluator.py @@ -8,6 +8,8 @@ to implement a flat, stackless, serializable interpreter. import typing as t +from milkshake import Symbol + from .models import * from .util import * @@ -18,24 +20,24 @@ class Operator: """Stack operators.""" -class Task: +class Op: @attrs.define() class Complete: """Signals program termination.""" - pass + @attrs.define() + class Drop: + """Drop or void the top of the data stack.""" @attrs.define() class Eval: """Evaluate the current term.""" - pass - @attrs.define() - class Apply: - """Apply an operator to the stack.""" + class Invoke: + """Consuming the top N items of the stack of the format [... , <... args>] invoke fn with args.""" - operator: "Operator" = attrs.field() + n: int @attrs.define() class Next: @@ -67,19 +69,64 @@ def step(vm: Vm, cont: Cont, frame: Frame): """ + if frame.op_stack: + expr = frame.op_stack.pop() + + else: + expr = get_in(vm.log, frame.pc) + match expr: - case ["native", native_fn, arg_exprs, vararg_expr]: + # AST entries + case ["py", native_expr, arg_exprs, vararg_expr]: pass - case ["invoke", fn_expr, arg_exprs, vararg_expr]: + case ["lambda", arg_expr, body_expr]: + # "now this one;s hard" pass - case ["var", name]: + case ["do", *body_exprs]: pass - case int() | float() | str() | bool(): + case ["if", test_expr, then_expr, else_expr]: pass + case ["if", test_expr, then_expr]: + pass + + # Generalized "simple" invoke expression + case [fn_expr, *arg_exprs]: + pass + + # Literals/terminals + case Symbol(name): + # Resolve a value from the scope(s) + for frame in reversed(cont.frames): + if name in frame.frame_ns: + frame.data_stack.append(frame.frame_ns[name]) + break + else: + raise NameError(f"{name!r} is not bound") + + case int() | float() | str() | bool() | dict() | set(): + # Literals push + frame.data_stack.append(expr) + + # Semi-VM instructions + case Op.Drop(): + frame.data_stack.pop() + + case Op.Invoke(n): + data = [frame.data_stack.pop() for _ in range(n)] + fn = data[-1] + args = data[-1::] + + # Need to trampoline the call through the wrapping loop + return Op.Frame(fn, args) + + # FIXME: Do we still need this? + case Op.Complete(): + return expr + def run(program, state=None): vm = state or Vm([], {}, []) diff --git a/components/typhon/src/python/typhon/models.py b/components/typhon/src/python/typhon/models.py index f334909..8371a11 100644 --- a/components/typhon/src/python/typhon/models.py +++ b/components/typhon/src/python/typhon/models.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from collections import deque import typing as t import attrs @@ -40,5 +41,5 @@ class Frame: pc: list = attrs.field(factory=lambda: [0]) frame_ns: dict = attrs.field(factory=dict) - op_stack: list = attrs.field(factory=list) - data_stack: list = attrs.field(factory=list) + op_stack: deque = attrs.field(factory=deque) + data_stack: deque = attrs.field(factory=deque)