Interactive grammar editing
This commit is contained in:
parent
45a9303a27
commit
71078f76b4
2 changed files with 106 additions and 29 deletions
12
grammar.py
12
grammar.py
|
|
@ -82,10 +82,10 @@ class FineGrammar(Grammar):
|
||||||
|
|
||||||
@rule
|
@rule
|
||||||
def _file_statement_list(self):
|
def _file_statement_list(self):
|
||||||
return self.file_statement | (self._file_statement_list + self.file_statement)
|
return self._file_statement | (self._file_statement_list + self._file_statement)
|
||||||
|
|
||||||
@rule
|
@rule
|
||||||
def file_statement(self):
|
def _file_statement(self):
|
||||||
return (
|
return (
|
||||||
self.import_statement | self.class_declaration | self.export_statement | self.statement
|
self.import_statement | self.class_declaration | self.export_statement | self.statement
|
||||||
)
|
)
|
||||||
|
|
@ -100,14 +100,14 @@ class FineGrammar(Grammar):
|
||||||
|
|
||||||
@rule
|
@rule
|
||||||
def class_body(self):
|
def class_body(self):
|
||||||
return seq(LCURLY, RCURLY) | seq(LCURLY, self.class_members, RCURLY)
|
return seq(LCURLY, RCURLY) | seq(LCURLY, self._class_members, RCURLY)
|
||||||
|
|
||||||
@rule
|
@rule
|
||||||
def class_members(self):
|
def _class_members(self):
|
||||||
return self.class_member | seq(self.class_members, self.class_member)
|
return self._class_member | seq(self._class_members, self._class_member)
|
||||||
|
|
||||||
@rule
|
@rule
|
||||||
def class_member(self):
|
def _class_member(self):
|
||||||
return self.field_declaration | self.function_declaration
|
return self.field_declaration | self.function_declaration
|
||||||
|
|
||||||
@rule
|
@rule
|
||||||
|
|
|
||||||
123
harness.py
123
harness.py
|
|
@ -1,11 +1,15 @@
|
||||||
import bisect
|
import bisect
|
||||||
from dataclasses import dataclass
|
import importlib
|
||||||
|
import inspect
|
||||||
import enum
|
import enum
|
||||||
|
import os
|
||||||
import select
|
import select
|
||||||
import sys
|
import sys
|
||||||
import termios
|
import termios
|
||||||
|
import time
|
||||||
import tty
|
import tty
|
||||||
import typing
|
import typing
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import grammar
|
import grammar
|
||||||
import parser
|
import parser
|
||||||
|
|
@ -143,7 +147,7 @@ def CSI(x: bytes) -> bytes:
|
||||||
return ESC(b"[" + x)
|
return ESC(b"[" + x)
|
||||||
|
|
||||||
|
|
||||||
CLEAR = CSI(b"H") + CSI(b"0m")
|
CLEAR = CSI(b"2J")
|
||||||
|
|
||||||
|
|
||||||
def enter_alt_screen():
|
def enter_alt_screen():
|
||||||
|
|
@ -159,11 +163,10 @@ class Harness:
|
||||||
table: parser.ParseTable | None
|
table: parser.ParseTable | None
|
||||||
tree: Tree | None
|
tree: Tree | None
|
||||||
|
|
||||||
def __init__(self, lexer_func, grammar_func, start_rule, source_path):
|
def __init__(self, lexer_func, start_rule, source_path):
|
||||||
# self.generator = parser.GenerateLR1
|
# self.generator = parser.GenerateLR1
|
||||||
self.generator = parser.GenerateLALR
|
self.generator = parser.GenerateLALR
|
||||||
self.lexer_func = lexer_func
|
self.lexer_func = lexer_func
|
||||||
self.grammar_func = grammar_func
|
|
||||||
self.start_rule = start_rule
|
self.start_rule = start_rule
|
||||||
self.source_path = source_path
|
self.source_path = source_path
|
||||||
|
|
||||||
|
|
@ -173,6 +176,11 @@ class Harness:
|
||||||
self.tree = None
|
self.tree = None
|
||||||
self.errors = None
|
self.errors = None
|
||||||
|
|
||||||
|
self.grammar_file_name = "./grammar.py"
|
||||||
|
self.last_grammar_time = None
|
||||||
|
self.grammar_module = None
|
||||||
|
self.grammar_name = None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
i, _, _ = select.select([sys.stdin], [], [], 1)
|
i, _, _ = select.select([sys.stdin], [], [], 1)
|
||||||
|
|
@ -183,38 +191,108 @@ class Harness:
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def update(self):
|
# def should_reload_grammar(self):
|
||||||
if self.table is None:
|
|
||||||
self.table = self.grammar_func().build_table(
|
def load_grammar(self) -> parser.ParseTable:
|
||||||
start=self.start_rule, generator=self.generator
|
st = os.stat(self.grammar_file_name)
|
||||||
)
|
if self.last_grammar_time == st.st_mtime:
|
||||||
assert self.table is not None
|
assert self.table is not None
|
||||||
|
return self.table
|
||||||
|
|
||||||
|
self.table = None
|
||||||
|
|
||||||
|
if self.grammar_module is None:
|
||||||
|
mod_name = inspect.getmodulename(self.grammar_file_name)
|
||||||
|
if mod_name is None:
|
||||||
|
raise Exception(f"{self.grammar_file_name} does not seem to be a module")
|
||||||
|
self.grammar_module = importlib.import_module(mod_name)
|
||||||
|
else:
|
||||||
|
importlib.reload(self.grammar_module)
|
||||||
|
|
||||||
|
def is_grammar(cls):
|
||||||
|
if not inspect.isclass(cls):
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert self.grammar_module is not None
|
||||||
|
if cls.__module__ != self.grammar_module.__name__:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if getattr(cls, "build_table", None):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.grammar_name is None:
|
||||||
|
classes = inspect.getmembers(self.grammar_module, is_grammar)
|
||||||
|
if len(classes) == 0:
|
||||||
|
raise Exception(f"No grammars found in {self.grammar_file_name}")
|
||||||
|
if len(classes) > 1:
|
||||||
|
raise Exception(
|
||||||
|
f"{len(classes)} grammars found in {self.grammar_file_name}: {', '.join(c[0] for c in classes)}"
|
||||||
|
)
|
||||||
|
grammar_func = classes[0][1]
|
||||||
|
else:
|
||||||
|
cls = getattr(self.grammar_module, self.grammar_name)
|
||||||
|
if cls is None:
|
||||||
|
raise Exception(f"Cannot find {self.grammar_name} in {self.grammar_file_name}")
|
||||||
|
if not is_grammar(cls):
|
||||||
|
raise Exception(
|
||||||
|
f"{self.grammar_name} in {self.grammar_file_name} does not seem to be a grammar"
|
||||||
|
)
|
||||||
|
grammar_func = cls
|
||||||
|
|
||||||
|
self.table = grammar_func().build_table(start=self.start_rule, generator=self.generator)
|
||||||
|
self.last_grammar_time = st.st_mtime
|
||||||
|
|
||||||
|
assert self.table is not None
|
||||||
|
return self.table
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
start_time = time.time()
|
||||||
|
try:
|
||||||
|
table = self.load_grammar()
|
||||||
|
|
||||||
if self.tokens is None:
|
|
||||||
with open(self.source_path, "r", encoding="utf-8") as f:
|
with open(self.source_path, "r", encoding="utf-8") as f:
|
||||||
self.source = f.read()
|
self.source = f.read()
|
||||||
self.tokens = self.lexer_func(self.source)
|
|
||||||
|
|
||||||
# print(f"{tokens.lines}")
|
self.tokens = self.lexer_func(self.source)
|
||||||
# tokens.dump(end=5)
|
lex_time = time.time()
|
||||||
if self.tree is None and self.errors is None:
|
|
||||||
(tree, errors) = parse(self.table, self.tokens, trace=None)
|
# print(f"{tokens.lines}")
|
||||||
self.tree = tree
|
# tokens.dump(end=5)
|
||||||
self.errors = errors
|
(tree, errors) = parse(table, self.tokens, trace=None)
|
||||||
|
parse_time = time.time()
|
||||||
|
self.tree = tree
|
||||||
|
self.errors = errors
|
||||||
|
parse_elapsed = parse_time - lex_time
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.tree = None
|
||||||
|
self.errors = [f"Error loading grammar: {e}"]
|
||||||
|
parse_elapsed = time.time() - start_time
|
||||||
|
table = None
|
||||||
|
|
||||||
sys.stdout.buffer.write(CLEAR)
|
sys.stdout.buffer.write(CLEAR)
|
||||||
rows, cols = termios.tcgetwinsize(sys.stdout.fileno())
|
rows, cols = termios.tcgetwinsize(sys.stdout.fileno())
|
||||||
|
|
||||||
states = self.table.states
|
if table is not None:
|
||||||
average_entries = sum(len(row) for row in states) / len(states)
|
states = table.states
|
||||||
max_entries = max(len(row) for row in states)
|
average_entries = sum(len(row) for row in states) / len(states)
|
||||||
print(f"{len(states)} states - {average_entries} average, {max_entries} max\r")
|
max_entries = max(len(row) for row in states)
|
||||||
|
print(
|
||||||
|
f"{len(states)} states - {average_entries:.3} average, {max_entries} max - {parse_elapsed:.3}s \r"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("No table\r\n")
|
||||||
|
|
||||||
if self.tree is not None:
|
if self.tree is not None:
|
||||||
lines = []
|
lines = []
|
||||||
self.format_node(lines, self.tree)
|
self.format_node(lines, self.tree)
|
||||||
for line in lines[: rows - 2]:
|
for line in lines[: rows - 2]:
|
||||||
print(line[:cols] + "\r")
|
print(line[:cols] + "\r")
|
||||||
|
else:
|
||||||
|
for error in self.errors[: rows - 2]:
|
||||||
|
print(error[:cols] + "\r")
|
||||||
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
sys.stdout.buffer.flush()
|
sys.stdout.buffer.flush()
|
||||||
|
|
@ -243,7 +321,6 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
h = Harness(
|
h = Harness(
|
||||||
lexer_func=grammar.FineTokens,
|
lexer_func=grammar.FineTokens,
|
||||||
grammar_func=grammar.FineGrammar,
|
|
||||||
start_rule="file",
|
start_rule="file",
|
||||||
source_path=source_path,
|
source_path=source_path,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue