Split up the big file

This commit is contained in:
Reid 'arrdem' McKenzie 2023-05-03 22:57:24 -06:00
parent 2855d93870
commit 33be6c1fe9
7 changed files with 179 additions and 147 deletions

View file

@ -3,5 +3,9 @@ py_project(
lib_deps = [ lib_deps = [
"//components/milkshake", "//components/milkshake",
py_requirement("attrs"), py_requirement("attrs"),
] ],
main_deps = [
py_requirement("click"),
],
main = "src/python/typhon/__main__.py",
) )

View file

@ -0,0 +1 @@
#!/usr/bin/env python3

View file

@ -1,147 +1 @@
#!/usr/bin/env python3 #!/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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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