diff --git a/grammar.py b/grammar.py index a643b68..6c06772 100644 --- a/grammar.py +++ b/grammar.py @@ -116,11 +116,11 @@ class FineGrammar(Grammar): @rule def export_statement(self) -> Rule: - return alt( - seq(self.EXPORT, self.class_declaration), - seq(self.EXPORT, self.function_declaration), - seq(self.EXPORT, self.let_statement), - seq(self.EXPORT, self.export_list, self.SEMICOLON), + return ( + seq(self.EXPORT, self.class_declaration) + | seq(self.EXPORT, self.function_declaration) + | seq(self.EXPORT, self.let_statement) + | seq(self.EXPORT, self.export_list, self.SEMICOLON) ) @rule diff --git a/parser/wadler.py b/parser/wadler.py deleted file mode 100644 index fbcc2cc..0000000 --- a/parser/wadler.py +++ /dev/null @@ -1,277 +0,0 @@ -# A prettier printer. -import dataclasses -import typing - -from . import parser -from . import runtime - - -@dataclasses.dataclass(frozen=True) -class Cons: - left: "Document" - right: "Document" - - -@dataclasses.dataclass(frozen=True) -class NewLine: - pass - - -@dataclasses.dataclass(frozen=True) -class Indent: - amount: int - doc: "Document" - - -@dataclasses.dataclass(frozen=True) -class Text: - start: int - end: int - - -@dataclasses.dataclass(frozen=True) -class Group: - child: "Document" - - -@dataclasses.dataclass -class Lazy: - value: typing.Callable[[], "Document"] | "Document" - - def resolve(self) -> "Document": - if callable(self.value): - self.value = self.value() - return self.value - - -Document = None | Text | NewLine | Cons | Indent | Group | Lazy - - -def layout_document(doc: Document) -> typing.Generator[str, None, None]: - raise NotImplementedError() - - -@dataclasses.dataclass -class Match: - doc: Document - remaining: list[runtime.Tree | runtime.TokenValue] - - -class Matcher: - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - raise NotImplementedError() - - -class NonTerminalMatcher(Matcher): - name: str - printer: "Printer" - - def __init__(self, name: str, printer: "Printer"): - self.name = name - self.printer = printer - - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - if len(items) == 0: - return None - - item = items[0] - if isinstance(item, runtime.Tree) and item.name == self.name: - return Match( - doc=Lazy(value=lambda: self.printer.convert_tree_to_document(item)), - remaining=items[1:], - ) - - return None - - -class TerminalMatcher(Matcher): - name: str - - def __init__(self, name: str): - self.name = name - - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - if len(items) == 0: - return None - - item = items[0] - if isinstance(item, runtime.TokenValue) and item.kind == self.name: - return Match( - doc=Text(start=item.start, end=item.end), - remaining=items[1:], - ) - - return None - - -class IndentMatcher(Matcher): - amount: int - child: Matcher - - def __init__(self, amount: int, child: Matcher): - self.amount = amount - self.child = child - - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - result = self.child.match(items) - if result is not None: - result.doc = Indent(amount=self.amount, doc=result.doc) - - return result - - -class NewLineMatcher(Matcher): - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - return Match( - doc=NewLine(), - remaining=items, - ) - - -class GroupMatcher(Matcher): - child: Matcher - - def __init__(self, child: Matcher): - self.child = child - - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - result = self.child.match(items) - if result is not None: - result.doc = Group(result.doc) - - return result - - -class CompleteMatcher(Matcher): - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - if len(items) == 0: - return Match(doc=None, remaining=[]) - else: - return None - - -class AlternativeMatcher(Matcher): - children: list[Matcher] - - def __init__(self, children: list[Matcher] | None = None): - self.children = children or [] - - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - for child in self.children: - m = child.match(items) - if m is not None: - return m - - return None - - -class SequenceMatcher(Matcher): - children: list[Matcher] - - def __init__(self, children: list[Matcher] | None = None): - self.children = children or [] - - def match(self, items: list[runtime.Tree | runtime.TokenValue]) -> Match | None: - doc = None - for child in self.children: - m = child.match(items) - if m is None: - return None - - items = m.remaining - doc = Cons(doc, m.doc) - - return Match( - doc=doc, - remaining=items, - ) - - -class PrettyMeta(parser.SyntaxMeta): - newline: bool - indent: int | None - group: bool - - -class Printer: - grammar: parser.Grammar - matchers: dict[str, Matcher] - - def __init__(self, grammar: parser.Grammar): - self.grammar = grammar - - def lookup_nonterminal(self, name: str) -> parser.NonTerminal: - raise NotImplementedError() - - def production_to_matcher(self, production: parser.FlattenedWithMetadata) -> Matcher: - results = [] - for item in production: - if isinstance(item, str): - rule = self.lookup_nonterminal(item) - if rule.transparent: - # If it's transparent then we don't actually match a - # nonterminal here, we need to match against the contents - # of the rule, so we recurse. - results.append(self.rule_to_matcher(rule)) - else: - results.append(NonTerminalMatcher(item, self)) - - elif isinstance(item, parser.Terminal): - name = item.name - assert name is not None - results.append(TerminalMatcher(name)) - - else: - meta, children = item - - child = self.production_to_matcher(children) - - prettier = meta.get("prettier") - if isinstance(prettier, PrettyMeta): - if prettier.indent: - child = IndentMatcher(prettier.indent, child) - - if prettier.group: - child = GroupMatcher(child) - - results.append(child) - - if prettier.newline: - results.append(NewLineMatcher()) - - else: - results.append(child) - - return SequenceMatcher(results) - - def rule_to_matcher(self, rule: parser.NonTerminal) -> Matcher: - result = self.matchers.get(rule.name) - if result is None: - # Create the empty alternative, be sure to set up the - alts = AlternativeMatcher() - if rule.transparent: - result = alts - else: - result = SequenceMatcher(children=[alts, CompleteMatcher()]) - self.matchers[rule.name] = result - - for production in rule.fn(self.grammar).flatten(with_metadata=True): - alts.children.append(self.production_to_matcher(production)) - - return result - - def convert_tree_to_document(self, tree: runtime.Tree) -> Document: - name = tree.name - assert name is not None, "Cannot format a tree if it still has transparent nodes inside" - - rule = self.lookup_nonterminal(name) - matcher = self.rule_to_matcher(rule) - - m = matcher.match(list(tree.children)) - assert m is not None, "Could not match a valid tree" # TODO: Exception rather I think - - return m.doc - - def format_tree(self, tree: runtime.Tree) -> str: - doc = self.convert_tree_to_document(tree) - return next(layout_document(doc))