diff --git a/components/typhon/BUILD b/components/typhon/BUILD index 7552004..fa555c3 100644 --- a/components/typhon/BUILD +++ b/components/typhon/BUILD @@ -3,5 +3,9 @@ py_project( lib_deps = [ "//components/milkshake", py_requirement("attrs"), - ] + ], + main_deps = [ + py_requirement("click"), + ], + main = "src/python/typhon/__main__.py", ) diff --git a/components/typhon/__init__.py b/components/typhon/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/components/typhon/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/components/typhon/src/python/typhon/__init__.py b/components/typhon/src/python/typhon/__init__.py index da55d3e..e5a0d9b 100644 --- a/components/typhon/src/python/typhon/__init__.py +++ b/components/typhon/src/python/typhon/__init__.py @@ -1,147 +1 @@ #!/usr/bin/env python3 - -""" -A prototype 'flat' interpreter for a continuation based Lisp. - -Not intended to be anything real, just intended to serve as a prototype for how -to implement a flat, stackless, serializable interpreter. -""" - -import operator -import typing as t - -import attrs - - -@attrs.define() -class Vm: - """ - :field log: All evaluated expressions, in order - :field mod_ns: A map from symbols to currently bound expressions - :field continuations: A list of coroutines/continuations of control - """ - - log: t.List[list] - mod_ns: t.Dict[str, t.Any] = attrs.field(factory=dict) - continuations: t.List["Cont"] = attrs.field(factory=list) - - -@attrs.define() -class Cont: - """ - Continuations represent sequences of evaluation. - - :field frames: A list of call frames - """ - - frames: t.List["Frame"] = attrs.field(factory=list) - - -@attrs.define() -class Frame: - """ - Frames represent function call boundaries. - - :field pc: Program points within the AST being interpreted - :field frame_ns: Frame-local bindings - """ - - 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) - - -def chain(its): - for it in its: - yield from it - - -def get_in(idxable, idxs): - for idx in idxs: - idxable = idxable[idx] - return idxable - - -class Operator: - """Stack operators.""" - - -class Task: - @attrs.define() - class Complete: - """Signals program termination.""" - - pass - - @attrs.define() - class Eval: - """Evaluate the current term.""" - - pass - - @attrs.define() - class Apply: - """Apply an operator to the stack.""" - - operator: "Operator" = attrs.field() - - @attrs.define() - class Next: - """Advance the program counter to the 'next' statement.""" - - pc: list - - -def step(vm: Vm, cont: Cont, frame: Frame): - """Step moves the VM forwards one "cycle". - - This VM is built as a semi-VM. - - Interpretation occurs at two levels - the first over a direct syntax tree. - This allows the interpreter to forego a formal VM or any compilation step. - - This model however poses a challenge for AST nodes with data dependencies. - Sub-expressions are the most obivous example. For these, we need semi-VM - behavior where the AST node is translated into a sequence of concrete - sub-step demanded operations which can actually be executed. Note that this - provides a neat natural definition of a tracing JIT or optimizing compiler. - - This makes the execution mode well bimodal. - - - On the one hand if we have demanded operations on the stack (`op_stack`) - then we want to execute the "top" demanded operation. - - - On the other, if we have no demanded operations we want to - - """ - - match expr: - case ["native", native_fn, arg_exprs, vararg_expr]: - pass - - case ["invoke", fn_expr, arg_exprs, vararg_expr]: - pass - - case ["var", name]: - pass - - case int() | float() | str() | bool(): - pass - - -def run(program, state=None): - vm = state or Vm([], {}, []) - replc = Cont([Frame([0], {})]) - vm.continuations.append(replc) - - for form in program: - # Enter the form into the log - vm.log.append(form) - # Set the REPL continuation's point to the new form - replc.frames = [Frame([len(vm.log) - 1])] - # Evaluate the continuation to completion (or something) - while True: - match (state := step(vm, replc, replc.frames[-1])): - case _: - pass diff --git a/components/typhon/src/python/typhon/__main__.py b/components/typhon/src/python/typhon/__main__.py new file mode 100644 index 0000000..f95c2a0 --- /dev/null +++ b/components/typhon/src/python/typhon/__main__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +from pathlib import Path + +from typhon.evaluator import run +from typhon.models import Vm + +from milkshake import slurp + +import click + + +@click.argument("progn", type=Path) +def main(progn): + vm = Vm([], {}, []) + + with open(progn) as fp: + for expr in slurp(fp): + run(expr, state=vm) diff --git a/components/typhon/src/python/typhon/evaluator.py b/components/typhon/src/python/typhon/evaluator.py new file mode 100644 index 0000000..1e04309 --- /dev/null +++ b/components/typhon/src/python/typhon/evaluator.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +""" +A prototype 'flat' interpreter for a continuation based Lisp. + +Not intended to be anything real, just intended to serve as a prototype for how +to implement a flat, stackless, serializable interpreter. +""" + +import typing as t + +from .models import * +from .util import * + +import attrs + + +class Operator: + """Stack operators.""" + + +class Task: + @attrs.define() + class Complete: + """Signals program termination.""" + + pass + + @attrs.define() + class Eval: + """Evaluate the current term.""" + + pass + + @attrs.define() + class Apply: + """Apply an operator to the stack.""" + + operator: "Operator" = attrs.field() + + @attrs.define() + class Next: + """Advance the program counter to the 'next' statement.""" + + pc: list + + +def step(vm: Vm, cont: Cont, frame: Frame): + """Step moves the VM forwards one "cycle". + + This VM is built as a semi-VM. + + Interpretation occurs at two levels - the first over a direct syntax tree. + This allows the interpreter to forego a formal VM or any compilation step. + + This model however poses a challenge for AST nodes with data dependencies. + Sub-expressions are the most obivous example. For these, we need semi-VM + behavior where the AST node is translated into a sequence of concrete + sub-step demanded operations which can actually be executed. Note that this + provides a neat natural definition of a tracing JIT or optimizing compiler. + + This makes the execution mode well bimodal. + + - On the one hand if we have demanded operations on the stack (`op_stack`) + then we want to execute the "top" demanded operation. + + - On the other, if we have no demanded operations we want to + + """ + + match expr: + case ["native", native_fn, arg_exprs, vararg_expr]: + pass + + case ["invoke", fn_expr, arg_exprs, vararg_expr]: + pass + + case ["var", name]: + pass + + case int() | float() | str() | bool(): + pass + + +def run(program, state=None): + vm = state or Vm([], {}, []) + replc = Cont([Frame([0], {})]) + vm.continuations.append(replc) + + for form in program: + # Enter the form into the log + vm.log.append(form) + # Set the REPL continuation's point to the new form + replc.frames = [Frame([len(vm.log) - 1])] + # Evaluate the continuation to completion (or something) + while True: + match (state := step(vm, replc, replc.frames[-1])): + case _: + pass diff --git a/components/typhon/src/python/typhon/models.py b/components/typhon/src/python/typhon/models.py new file mode 100644 index 0000000..f334909 --- /dev/null +++ b/components/typhon/src/python/typhon/models.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import typing as t + +import attrs + + +@attrs.define() +class Vm: + """ + :field log: All evaluated expressions, in order + :field mod_ns: A map from symbols to currently bound expressions + :field continuations: A list of coroutines/continuations of control + """ + + log: t.List[list] + mod_ns: t.Dict[str, t.Any] = attrs.field(factory=dict) + continuations: t.List["Cont"] = attrs.field(factory=list) + + +@attrs.define() +class Cont: + """ + Continuations represent sequences of evaluation. + + :field frames: A list of call frames + """ + + frames: t.List["Frame"] = attrs.field(factory=list) + + +@attrs.define() +class Frame: + """ + Frames represent function call boundaries. + + :field pc: Program points within the AST being interpreted + :field frame_ns: Frame-local bindings + """ + + 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) diff --git a/components/typhon/src/python/typhon/util.py b/components/typhon/src/python/typhon/util.py new file mode 100644 index 0000000..20ca9b8 --- /dev/null +++ b/components/typhon/src/python/typhon/util.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + + +def chain(its): + for it in its: + yield from it + + +def get_in(idxable, idxs): + for idx in idxs: + idxable = idxable[idx] + return idxable