Split up the big file
This commit is contained in:
parent
2855d93870
commit
33be6c1fe9
7 changed files with 179 additions and 147 deletions
|
@ -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",
|
||||||
)
|
)
|
||||||
|
|
1
components/typhon/__init__.py
Normal file
1
components/typhon/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#!/usr/bin/env python3
|
|
@ -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
|
|
||||||
|
|
19
components/typhon/src/python/typhon/__main__.py
Normal file
19
components/typhon/src/python/typhon/__main__.py
Normal 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)
|
98
components/typhon/src/python/typhon/evaluator.py
Normal file
98
components/typhon/src/python/typhon/evaluator.py
Normal 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
|
44
components/typhon/src/python/typhon/models.py
Normal file
44
components/typhon/src/python/typhon/models.py
Normal 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)
|
12
components/typhon/src/python/typhon/util.py
Normal file
12
components/typhon/src/python/typhon/util.py
Normal 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
|
Loading…
Reference in a new issue