Compare commits
2 commits
10b143f2fa
...
0fbb6e4f88
Author | SHA1 | Date | |
---|---|---|---|
0fbb6e4f88 | |||
bcd50fe57f |
6 changed files with 109 additions and 37 deletions
|
@ -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"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
[
|
[
|
||||||
|
|
|
@ -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([], {}, [])
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue