diff --git a/projects/shoggoth/src/python/shoggoth/analyzer/impl.py b/projects/shoggoth/src/python/shoggoth/analyzer.py similarity index 61% rename from projects/shoggoth/src/python/shoggoth/analyzer/impl.py rename to projects/shoggoth/src/python/shoggoth/analyzer.py index b9f777c..efd7538 100644 --- a/projects/shoggoth/src/python/shoggoth/analyzer/impl.py +++ b/projects/shoggoth/src/python/shoggoth/analyzer.py @@ -86,7 +86,7 @@ class FnExpr(Expr): BOOTSTRAP = "lang.shoggoth.v0.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("fn*"): None, }) @@ -110,31 +110,36 @@ class Analyzer: def _analyze(e): return self.analyze(module, e) - if isinstance(expr, (int, float, str, Keyword)): - return ConstExpr(expr) + match expr: + case int() | float() | str(): + return ConstExpr(expr) - if isinstance(expr, Vec): - return ListExpr([self.analyze(module, e) for e in expr]) + case Keyword(): + raise ValueError() - if isinstance(expr, List) and len(expr) > 1 and isinstance(expr[0], Symbol): - if (target := self.resolve(module, expr[0])): - match target: - case Symbol("if*", BOOTSTRAP): - assert len(expr) == 4 - return IfExpr(_analyze(expr[1]), _analyze(expr[2]), _analyze(expr[3])) + case List() if len(expr) > 1 and isinstance(expr[0], Symbol): + if (target := self.resolve(module, expr[0])): + match target: + case Symbol("if*", BOOTSTRAP): + assert len(expr) == 4 + return IfExpr(_analyze(expr[1]), _analyze(expr[2]), _analyze(expr[3])) - case Symbol("let*", BOOTSTRAP): - assert len(expr) == 3 - return LetExpr([(name, _analyze(e)) for name, e in expr[1]], _analyze(expr[2])) + case Symbol("let*", BOOTSTRAP): + assert len(expr) == 3 + return LetExpr([(name, _analyze(e)) for name, e in expr[1]], _analyze(expr[2])) - case Symbol("fn*", BOOTSTRAP): - assert len(expr) == 3 - return FnExpr(expr[1], _analyze(expr[2])) + case Symbol("fn*", BOOTSTRAP): + assert len(expr) == 3 + return FnExpr(expr[1], _analyze(expr[2])) - # FIXME: Macros go here? Or is macroexpansion separate? + # FIXME: Macros go here? Or is macroexpansion separate? - case _: - return InvokeExpr(_analyze(target), [_analyze(e) for e in expr[1:]]) + case _: + 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)})") diff --git a/projects/shoggoth/src/python/shoggoth/analyzer/__init__.py b/projects/shoggoth/src/python/shoggoth/analyzer/__init__.py deleted file mode 100644 index c7113b2..0000000 --- a/projects/shoggoth/src/python/shoggoth/analyzer/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python3 - -"""A syntax analyzer for Shogoth.""" - diff --git a/projects/shoggoth/src/python/shoggoth/client/__main__.py b/projects/shoggoth/src/python/shoggoth/client/__main__.py deleted file mode 100644 index e69de29..0000000 diff --git a/projects/shoggoth/src/python/shoggoth/parser/impl.py b/projects/shoggoth/src/python/shoggoth/parser.py similarity index 89% rename from projects/shoggoth/src/python/shoggoth/parser/impl.py rename to projects/shoggoth/src/python/shoggoth/parser.py index 1c5db2c..c0a6508 100644 --- a/projects/shoggoth/src/python/shoggoth/parser/impl.py +++ b/projects/shoggoth/src/python/shoggoth/parser.py @@ -15,7 +15,7 @@ mapping: "{" kv* "}" kv: expr 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 keyword: ":" SYMBOL @@ -30,9 +30,12 @@ STRING: /".*?"/ pattern: PATTERN PATTERN: /\/.*?\// -true: /true/ -false: /false/ -nil: /nil/ +bool: TRUE | FALSE +TRUE: /true/ +FALSE: /false/ + +nil: NIL +NIL: /nil/ // NOTE: order-prescidence matters here, 0x, 0b, 0o etc. require lookahead diff --git a/projects/shoggoth/src/python/shoggoth/parser/__init__.py b/projects/shoggoth/src/python/shoggoth/parser/__init__.py deleted file mode 100644 index 6edc5f0..0000000 --- a/projects/shoggoth/src/python/shoggoth/parser/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Published interface to the shoggoth parser.""" - -from .impl import parse - - -__all__ = ["parse"] diff --git a/projects/shoggoth/src/python/shoggoth/reader.py b/projects/shoggoth/src/python/shoggoth/reader.py new file mode 100644 index 0000000..fd7c214 --- /dev/null +++ b/projects/shoggoth/src/python/shoggoth/reader.py @@ -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)) diff --git a/projects/shoggoth/src/python/shoggoth/reader/__init__.py b/projects/shoggoth/src/python/shoggoth/reader/__init__.py deleted file mode 100644 index 63f77b6..0000000 --- a/projects/shoggoth/src/python/shoggoth/reader/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 - diff --git a/projects/shoggoth/src/python/shoggoth/reader/impl.py b/projects/shoggoth/src/python/shoggoth/reader/impl.py deleted file mode 100644 index 52b283a..0000000 --- a/projects/shoggoth/src/python/shoggoth/reader/impl.py +++ /dev/null @@ -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)) diff --git a/projects/shoggoth/src/python/shoggoth/server/__main__.py b/projects/shoggoth/src/python/shoggoth/server/__main__.py deleted file mode 100644 index e69de29..0000000 diff --git a/projects/shoggoth/src/python/shoggoth/server/impl.py b/projects/shoggoth/src/python/shoggoth/server/impl.py deleted file mode 100644 index bf2580c..0000000 --- a/projects/shoggoth/src/python/shoggoth/server/impl.py +++ /dev/null @@ -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 diff --git a/projects/shoggoth/src/python/shoggoth/types.py b/projects/shoggoth/src/python/shoggoth/types.py new file mode 100644 index 0000000..f525a14 --- /dev/null +++ b/projects/shoggoth/src/python/shoggoth/types.py @@ -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