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 = [
|
||||
"//components/milkshake",
|
||||
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
|
||||
|
||||
"""
|
||||
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