Ditch the .impl pattern for now

This commit is contained in:
Reid 'arrdem' McKenzie 2022-07-02 01:47:11 -06:00
parent 7e5a558163
commit 6d9d9b3fd5
11 changed files with 170 additions and 160 deletions

View file

@ -86,7 +86,7 @@ class FnExpr(Expr):
BOOTSTRAP = "lang.shoggoth.v0.bootstrap" BOOTSTRAP = "lang.shoggoth.v0.bootstrap"
SPECIALS = Namespace(Symbol(BOOTSTRAP), { SPECIALS = Namespace(Symbol(BOOTSTRAP), {
Symbol("if*"): None, Symbol("if*"): None, # FIXME: This isn't really if anymore, it's MATCH or TEST. Something. Unless bool is in bootstrap.
Symbol("let*"): None, Symbol("let*"): None,
Symbol("fn*"): None, Symbol("fn*"): None,
}) })
@ -110,31 +110,36 @@ class Analyzer:
def _analyze(e): def _analyze(e):
return self.analyze(module, e) return self.analyze(module, e)
if isinstance(expr, (int, float, str, Keyword)): match expr:
return ConstExpr(expr) case int() | float() | str():
return ConstExpr(expr)
if isinstance(expr, Vec): case Keyword():
return ListExpr([self.analyze(module, e) for e in expr]) raise ValueError()
if isinstance(expr, List) and len(expr) > 1 and isinstance(expr[0], Symbol): case List() if len(expr) > 1 and isinstance(expr[0], Symbol):
if (target := self.resolve(module, expr[0])): if (target := self.resolve(module, expr[0])):
match target: match target:
case Symbol("if*", BOOTSTRAP): case Symbol("if*", BOOTSTRAP):
assert len(expr) == 4 assert len(expr) == 4
return IfExpr(_analyze(expr[1]), _analyze(expr[2]), _analyze(expr[3])) return IfExpr(_analyze(expr[1]), _analyze(expr[2]), _analyze(expr[3]))
case Symbol("let*", BOOTSTRAP): case Symbol("let*", BOOTSTRAP):
assert len(expr) == 3 assert len(expr) == 3
return LetExpr([(name, _analyze(e)) for name, e in expr[1]], _analyze(expr[2])) return LetExpr([(name, _analyze(e)) for name, e in expr[1]], _analyze(expr[2]))
case Symbol("fn*", BOOTSTRAP): case Symbol("fn*", BOOTSTRAP):
assert len(expr) == 3 assert len(expr) == 3
return FnExpr(expr[1], _analyze(expr[2])) return FnExpr(expr[1], _analyze(expr[2]))
# FIXME: Macros go here? Or is macroexpansion separate? # FIXME: Macros go here? Or is macroexpansion separate?
case _: case _:
return InvokeExpr(_analyze(target), [_analyze(e) for e in expr[1:]]) pass
return InvokeExpr(_analyze(target), [_analyze(e) for e in expr[1:]])
case List():
return ListExpr([self.analyze(module, e) for e in expr])
raise ValueError(f"Unable to analyze {expr!r} ({type(expr)})") raise ValueError(f"Unable to analyze {expr!r} ({type(expr)})")

View file

@ -1,4 +0,0 @@
#!/usr/bin/env python3
"""A syntax analyzer for Shogoth."""

View file

@ -15,7 +15,7 @@ mapping: "{" kv* "}"
kv: expr expr kv: expr expr
quote: "'" expr quote: "'" expr
atom: keyword | pattern | num | string | true | false | nil | symbol atom: keyword | pattern | num | string | bool | nil | symbol
num: hex | octal | bin | int | float num: hex | octal | bin | int | float
keyword: ":" SYMBOL keyword: ":" SYMBOL
@ -30,9 +30,12 @@ STRING: /".*?"/
pattern: PATTERN pattern: PATTERN
PATTERN: /\/.*?\// PATTERN: /\/.*?\//
true: /true/ bool: TRUE | FALSE
false: /false/ TRUE: /true/
nil: /nil/ FALSE: /false/
nil: NIL
NIL: /nil/
// NOTE: order-prescidence matters here, 0x, 0b, 0o etc. require lookahead // NOTE: order-prescidence matters here, 0x, 0b, 0o etc. require lookahead

View file

@ -1,6 +0,0 @@
"""Published interface to the shoggoth parser."""
from .impl import parse
__all__ = ["parse"]

View file

@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""The shoggoth reader."""
import sys
assert sys.version_info > (3, 10, 0), "`match` support is required"
import re
from typing import Any
from lark import Token, Tree
from shoggoth.parser import parse
from shoggoth.types import (
Keyword,
List,
Symbol,
Vec,
)
# Monkeypatching for py3.10 matching
Tree.__match_args__ = ("data", "children")
Token.__match_args__ = ("type", "value")
class Reader(object):
"""An extension of parsing that produces meaningful trees. Can be extended with userland hooks."""
def __init__(self):
pass
def _parse(self, buffer):
return parse(buffer)
def symbol(self, x):
return Symbol(x)
def keyword(self, x):
return Keyword(x)
def pattern(self, x):
return re.compile(x[1:-1].replace("\\/", "/"))
def _read(self, tree: Tree) -> Any:
match tree:
case Tree(Token("RULE", "expr"), children):
return self._read(children[0])
case Tree(Token("RULE", "plist"), children):
return List([self._read(c) for c in children])
case Tree(Token("RULE", "blist"), children):
return Vec([self._read(c) for c in children])
case Tree(Token("RULE", "mapping"), children):
return dict(self._read(c) for c in children)
case Tree(Token("RULE", "kv"), [k, v]):
return (self._read(k), self._read(v))
# Fallback case of handling rules
case Tree(Token("RULE", _), [a]):
return self._read(a)
# Handling individual tokens
case Token("INT", x):
return int(x)
case Token("FLOAT", x):
return float(x)
case Token("PATTERN", x):
return self.pattern(x)
case Token("TRUE", _):
return True
case Token("FALSE", _):
return False
case Token("NIL", _):
return None
# Symbol is very much the catch-all of the grammar
case Token("SYMBOL", x):
return self.symbol(x)
case Token("KEYWORD", x):
return self.keyword(x)
case _:
return tree
def read(self, buffer):
return self._read(self._parse(buffer))

View file

@ -1,2 +0,0 @@
#!/usr/bin/env python3

View file

@ -1,98 +0,0 @@
#!/usr/bin/env python3
"""The shoggoth reader."""
import sys
assert sys.version_info > (3, 10, 0), "`match` support is required"
import re
from typing import Any
from lark import Token, Tree
from shoggoth.parser import parse
from shoggoth.types import (
Keyword,
List,
Symbol,
Vec,
)
# Monkeypatching for py3.10 matching
Tree.__match_args__ = ("data", "children")
Token.__match_args__ = ("type", "value")
class Reader(object):
"""An extension of parsing that produces meaningful trees. Can be extended with userland hooks."""
def __init__(self):
pass
def _parse(self, buffer):
return parse(buffer)
def symbol(self, x):
return Symbol(x)
def keyword(self, x):
return Keyword(x)
def pattern(self, x):
return re.compile(x[1:-1].replace("\\/", "/"))
def _read(self, tree: Tree) -> Any:
match tree:
case Tree(Token("RULE", "expr"), children):
return self._read(children[0])
case Tree(Token("RULE", "plist"), children):
return List([self._read(c) for c in children])
case Tree(Token("RULE", "blist"), children):
return Vec([self._read(c) for c in children])
case Tree(Token("RULE", "mapping"), children):
return dict(self._read(c) for c in children)
case Tree(Token("RULE", "kv"), [k, v]):
return (self._read(k), self._read(v))
case Tree(Token("RULE", "atom"), [a]):
return self._read(a)
case Tree(Token("RULE", "num"), [a]):
return self._read(a)
case Token("INT", x):
return int(x)
case Token("FLOAT", x):
return float(x)
case Token("KEYWORD", x):
return self.keyword(x)
case Token("PATTERN", x):
return self.pattern(x)
case Token("TRUE", _):
return True
case Token("FALSE", _):
return False
case Token("NIL", _):
return None
# Symbol is very much the catch-all of the grammar
case Token("SYMBOL", x):
return self.symbol(x)
case _:
return tree
def read(self, buffer):
return self._read(self._parse(buffer))

View file

@ -1,26 +0,0 @@
#!/usr/bin/env python3
"""The implementation of a Shogoth evaluation server."""
import flask
app = flask.Flask(__name__)
@app.route("/api/v0/login", method=["POST"])
def login():
pass
@app.route("/api/v0/logout", method=["POST"])
def logout():
pass
@app.route("/api/v0/session", method=["GET"])
def get_session():
pass
@app.route("/api/v0/session", method=["POST"])
def make_session():
pass

View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
from dataclasses import dataclass
@dataclass
class Symbol(object):
name: str
namespace: "Symbol" = None
def __hash__(self):
return hash(self.name)
def qualify(self, name: str) -> "Symbol":
return Symbol(
name = self.name,
namespace = Symbol(name)
)
def unqualified(self):
return Symbol(self.name)
class Keyword(Symbol):
def qualify(self, name: str) -> "Keyword":
return Keyword(
name = self.name,
namespace = Keyword(name)
)
def unqualified(self):
return Keyword(self.name)
class List(list):
pass
class Vec(list):
pass