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(
name = "milkshake",
lib_deps = [
py_requirement("attrs"),
py_requirement("lark"),
],
)

View file

@ -3,6 +3,7 @@
from importlib.resources import files
from attrs import define
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()
@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)
class T(Transformer):
"""A prepackaged transformer that cleans up the quoting details."""
@ -25,7 +39,9 @@ class T(Transformer):
qq_map = un_qq
qq_set = 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):
return self.quote(self.un_qq(obj))
@ -37,6 +53,9 @@ class T(Transformer):
unquote = quote
unquote_splicing = quote
def symbol(self, obj):
return Symbol(obj.children[0].value)
PARSER = Lark(GRAMMAR, start=["module", "expr"])

View file

@ -56,7 +56,11 @@ string: /"([^"]|\\")+"/
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
// This because _anything more specific_ should be chosen over symbol

View file

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

View file

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

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3
from collections import deque
import typing as t
import attrs
@ -40,5 +41,5 @@ class Frame:
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)
op_stack: deque = attrs.field(factory=deque)
data_stack: deque = attrs.field(factory=deque)