From 561dcd87ffd4e4bdf76a6df6a55560cd2c1ea8fb Mon Sep 17 00:00:00 2001 From: John Doty Date: Thu, 30 May 2024 19:15:20 -0700 Subject: [PATCH] Allow nonterminals to be renamed --- grammar.py | 4 ++-- harness.py | 16 +++++++++++++++- parser.py | 34 ++++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/grammar.py b/grammar.py index f59c532..b03cb36 100644 --- a/grammar.py +++ b/grammar.py @@ -76,7 +76,7 @@ class FineGrammar(Grammar): ] ) - @rule + @rule("File") def file(self) -> Rule: return self._file_statement_list @@ -94,7 +94,7 @@ class FineGrammar(Grammar): def import_statement(self) -> Rule: return seq(IMPORT, STRING, AS, IDENTIFIER, SEMICOLON) - @rule + @rule("ClassDeclaration") def class_declaration(self) -> Rule: return seq(CLASS, IDENTIFIER, self.class_body) diff --git a/harness.py b/harness.py index 1485935..2590415 100644 --- a/harness.py +++ b/harness.py @@ -18,6 +18,11 @@ import parser # from parser import Token, Grammar, rule, seq +############################################################################### +# Parsing Stuff +############################################################################### + + def trace_state(stack, input, input_index, action): print( "{stack: <20} {input: <50} {action: <5}".format( @@ -133,6 +138,10 @@ def parse(table: parser.ParseTable, tokens, trace=None) -> typing.Tuple[Tree | N raise ValueError(f"Unknown action type: {action}") +############################################################################### +# Screen Stuff +############################################################################### + # https://en.wikipedia.org/wiki/ANSI_escape_code # https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 @@ -176,6 +185,11 @@ def leave_alt_screen(): sys.stdout.buffer.write(CSI(b"?1049l")) +############################################################################### +# Dynamic Modules: Detect and Reload Modules when they Change +############################################################################### + + class DynamicModule: file_name: str member_name: str | None @@ -384,7 +398,7 @@ if __name__ == "__main__": enter_alt_screen() h = Harness( - start_rule="file", + start_rule="File", source_path=source_path, ) h.run() diff --git a/parser.py b/parser.py index 12dcf67..e79a48d 100644 --- a/parser.py +++ b/parser.py @@ -1745,15 +1745,19 @@ def seq(*args: Rule) -> Rule: return result -# @typing.overload -# def rule(f: None | str = None) -> typing.Callable[[typing.Callable], Rule]: ... +@typing.overload +def rule(f: typing.Callable, /) -> Rule: ... -# @typing.overload -# def rule(f: typing.Callable) -> Rule: ... +@typing.overload +def rule( + name: str | None = None, transparent: bool | None = None +) -> typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]: ... -def rule(f: typing.Callable) -> Rule: +def rule( + name: str | None | typing.Callable = None, transparent: bool | None = None +) -> Rule | typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]: """The decorator that marks a method in a Grammar object as a nonterminal rule. @@ -1761,9 +1765,23 @@ def rule(f: typing.Callable) -> Rule: If called with one argument, that argument is a name that overrides the name of the nonterminal, which defaults to the name of the function. """ - name = f.__name__ - transparent = name.startswith("_") - return NonTerminal(f, name, transparent) + if callable(name): + return rule()(name) + + def wrapper(f: typing.Callable[[typing.Any], Rule]): + nonlocal name + nonlocal transparent + + if name is None: + name = f.__name__ + assert isinstance(name, str) + + if transparent is None: + transparent = name.startswith("_") + + return NonTerminal(f, name, transparent) + + return wrapper PrecedenceList = list[typing.Tuple[Assoc, list[Rule]]]