Compare commits

...

2 commits

Author SHA1 Message Date
0fbb6e4f88 [NO TESTS] WIP 2023-05-10 22:42:17 -06:00
bcd50fe57f Create a Symbol value class 2023-05-08 17:27:04 -06:00
6 changed files with 109 additions and 37 deletions

View file

@ -1,6 +1,7 @@
py_project( py_project(
name = "milkshake", name = "milkshake",
lib_deps = [ lib_deps = [
py_requirement("attrs"),
py_requirement("lark"), py_requirement("lark"),
], ],
) )

View file

@ -3,6 +3,7 @@
from importlib.resources import files from importlib.resources import files
from attrs import define
from lark import Lark, Tree, Token, Transformer, v_args from lark import Lark, Tree, Token, Transformer, v_args
@ -10,6 +11,19 @@ with files(__package__).joinpath("grammar.lark").open("r", encoding="utf-8") as
GRAMMAR = fp.read() GRAMMAR = fp.read()
@define(frozen=True)
class Symbol:
name: str
def __eq__(self, other):
if isinstance(other, str):
return self.name == other
elif isinstance(other, type(self)):
return self.name == other.name
else:
return False
@v_args(tree=True) @v_args(tree=True)
class T(Transformer): class T(Transformer):
"""A prepackaged transformer that cleans up the quoting details.""" """A prepackaged transformer that cleans up the quoting details."""
@ -25,7 +39,9 @@ class T(Transformer):
qq_map = un_qq qq_map = un_qq
qq_set = un_qq qq_set = un_qq
qq_atom = un_qq qq_atom = un_qq
qq_symbol = un_qq
def qq_symbol(self, obj):
return self.symbol(self.un_qq(obj))
def qq_quote(self, obj): def qq_quote(self, obj):
return self.quote(self.un_qq(obj)) return self.quote(self.un_qq(obj))
@ -37,6 +53,9 @@ class T(Transformer):
unquote = quote unquote = quote
unquote_splicing = quote unquote_splicing = quote
def symbol(self, obj):
return Symbol(obj.children[0].value)
PARSER = Lark(GRAMMAR, start=["module", "expr"]) PARSER = Lark(GRAMMAR, start=["module", "expr"])

View file

@ -56,7 +56,11 @@ string: /"([^"]|\\")+"/
pattern: /\/([^\/]|\\\/)+\// pattern: /\/([^\/]|\\\/)+\//
number: /[+-]?(\d+r)?(\d[\d,_\.]*)([\.,][\d,_\.]*)?(e[+-]?\d+)?/ number: US_FORMAT_NUMBER | EU_FORMAT_NUMBER | UNDERSCORE_NUMBER
US_FORMAT_NUMBER: /[+-]?(\d+r)?(\d+(,\d{3})*)(\.\d+(,\d{3})*)?(e[+-]?\d+)?/
EU_FORMAT_NUMBER: /[+-]?(\d+r)?(\d+(\.\d{3})*)(,\d+(\.\d{3})*)?(e[+-]?\d+)?/
UNDERSCORE_NUMBER: /[+-]?(\d+r)?(\d+(_\d{3})*)([,\.]\d+(_\d{3})*)?(e[+-]?\d+)?/
// Note that we're demoting Symbol from the default parse priority of 0 to -1 // Note that we're demoting Symbol from the default parse priority of 0 to -1
// This because _anything more specific_ should be chosen over symbol // This because _anything more specific_ should be chosen over symbol

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from milkshake import slurp from milkshake import slurp, Symbol
from lark import Tree, Token from lark import Tree, Token
import pytest import pytest
@ -10,26 +10,26 @@ import pytest
"input, val", "input, val",
[ [
("()", Tree("list", [])), ("()", Tree("list", [])),
("nil", nil := Tree("symbol", ["nil"])), ("nil", nil := Symbol("nil")),
("(nil nil nil)", Tree("list", [nil, nil, nil])), ("(nil nil nil)", Tree("list", [nil, nil, nil])),
( (
"(/ + - * % ^ \\ & # @ ! = |)", "(/ + - * % ^ \\ & # @ ! = |)",
Tree( Tree(
"list", "list",
[ [
Tree("symbol", ["/"]), Symbol("/"),
Tree("symbol", ["+"]), Symbol("+"),
Tree("symbol", ["-"]), Symbol("-"),
Tree("symbol", ["*"]), Symbol("*"),
Tree("symbol", ["%"]), Symbol("%"),
Tree("symbol", ["^"]), Symbol("^"),
Tree("symbol", ["\\"]), Symbol("\\"),
Tree("symbol", ["&"]), Symbol("&"),
Tree("symbol", ["#"]), Symbol("#"),
Tree("symbol", ["@"]), Symbol("@"),
Tree("symbol", ["!"]), Symbol("!"),
Tree("symbol", ["="]), Symbol("="),
Tree("symbol", ["|"]), Symbol("|"),
], ],
), ),
), ),
@ -48,12 +48,12 @@ import pytest
Tree( Tree(
"list", "list",
[ [
Tree("symbol", ["+inf"]), Symbol("+inf"),
Tree("symbol", ["-inf"]), Symbol("-inf"),
Tree("symbol", ["inf"]), Symbol("inf"),
Tree("symbol", ["nan"]), Symbol("nan"),
Tree("symbol", ["+nan"]), Symbol("+nan"),
Tree("symbol", ["-nan"]), Symbol("-nan"),
], ],
), ),
), ),
@ -66,7 +66,7 @@ import pytest
Tree( Tree(
"list", "list",
[ [
Tree("symbol", ["nil"]), Symbol("nil"),
Tree( Tree(
"unquote", "unquote",
[ [

View file

@ -8,6 +8,8 @@ to implement a flat, stackless, serializable interpreter.
import typing as t import typing as t
from milkshake import Symbol
from .models import * from .models import *
from .util import * from .util import *
@ -18,24 +20,24 @@ class Operator:
"""Stack operators.""" """Stack operators."""
class Task: class Op:
@attrs.define() @attrs.define()
class Complete: class Complete:
"""Signals program termination.""" """Signals program termination."""
pass @attrs.define()
class Drop:
"""Drop or void the top of the data stack."""
@attrs.define() @attrs.define()
class Eval: class Eval:
"""Evaluate the current term.""" """Evaluate the current term."""
pass
@attrs.define() @attrs.define()
class Apply: class Invoke:
"""Apply an operator to the stack.""" """Consuming the top N items of the stack of the format [... <fn>, <... args>] invoke fn with args."""
operator: "Operator" = attrs.field() n: int
@attrs.define() @attrs.define()
class Next: 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: match expr:
case ["native", native_fn, arg_exprs, vararg_expr]: # AST entries
case ["py", native_expr, arg_exprs, vararg_expr]:
pass pass
case ["invoke", fn_expr, arg_exprs, vararg_expr]: case ["lambda", arg_expr, body_expr]:
# "now this one;s hard"
pass pass
case ["var", name]: case ["do", *body_exprs]:
pass pass
case int() | float() | str() | bool(): case ["if", test_expr, then_expr, else_expr]:
pass 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): def run(program, state=None):
vm = state or Vm([], {}, []) vm = state or Vm([], {}, [])

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from collections import deque
import typing as t import typing as t
import attrs import attrs
@ -40,5 +41,5 @@ class Frame:
pc: list = attrs.field(factory=lambda: [0]) pc: list = attrs.field(factory=lambda: [0])
frame_ns: dict = attrs.field(factory=dict) frame_ns: dict = attrs.field(factory=dict)
op_stack: list = attrs.field(factory=list) op_stack: deque = attrs.field(factory=deque)
data_stack: list = attrs.field(factory=list) data_stack: deque = attrs.field(factory=deque)