[runtime] When all else fails ask the author
Better error messages by allowing the author to customize the string. (This kinda works actually.)
This commit is contained in:
parent
c44083b610
commit
13f1353134
2 changed files with 48 additions and 8 deletions
|
|
@ -552,8 +552,9 @@ class ParseTable:
|
|||
actions: list[dict[str, ParseAction]]
|
||||
gotos: list[dict[str, int]]
|
||||
trivia: set[str]
|
||||
error_names: dict[str, str]
|
||||
|
||||
def format(self):
|
||||
def format(self) -> str:
|
||||
"""Format a parser table so pretty."""
|
||||
|
||||
def format_action(actions: dict[str, ParseAction], terminal: str):
|
||||
|
|
@ -642,7 +643,7 @@ class TableBuilder(object):
|
|||
if error is not None:
|
||||
raise error
|
||||
|
||||
return ParseTable(actions=self.actions, gotos=self.gotos, trivia=set())
|
||||
return ParseTable(actions=self.actions, gotos=self.gotos, trivia=set(), error_names={})
|
||||
|
||||
def new_row(self, config_set: ItemSet):
|
||||
"""Start a new row, processing the given config set. Call this before
|
||||
|
|
@ -1582,12 +1583,21 @@ class Terminal(Rule):
|
|||
pattern: "str | Re"
|
||||
meta: dict[str, typing.Any]
|
||||
regex: bool
|
||||
error_name: str | None
|
||||
|
||||
def __init__(self, pattern: "str|Re", *, name: str | None = None, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
pattern: "str|Re",
|
||||
*,
|
||||
name: str | None = None,
|
||||
error_name: str | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.name = name
|
||||
self.pattern = pattern
|
||||
self.meta = kwargs
|
||||
self.regex = isinstance(pattern, Re)
|
||||
self.error_name = error_name
|
||||
|
||||
def flatten(
|
||||
self, with_metadata: bool = False
|
||||
|
|
@ -1611,12 +1621,14 @@ class NonTerminal(Rule):
|
|||
fn: typing.Callable[["Grammar"], Rule]
|
||||
name: str
|
||||
transparent: bool
|
||||
error_name: str | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fn: typing.Callable[["Grammar"], Rule],
|
||||
name: str | None = None,
|
||||
transparent: bool = False,
|
||||
error_name: str | None = None,
|
||||
):
|
||||
"""Create a new NonTerminal.
|
||||
|
||||
|
|
@ -1624,10 +1636,16 @@ class NonTerminal(Rule):
|
|||
right-hand-side of this production; it will be flattened with `flatten`.
|
||||
`name` is the name of the production- if unspecified (or `None`) it will
|
||||
be replaced with the `__name__` of the provided fn.
|
||||
|
||||
error_name is a human-readable name, to be shown in error messages. Use
|
||||
this to fine-tune error messages. (For example, maybe you want your
|
||||
nonterminal to be named "expr" but in error messages it should be
|
||||
be spelled out: "expression".)
|
||||
"""
|
||||
self.fn = fn
|
||||
self.name = name or fn.__name__
|
||||
self.transparent = transparent
|
||||
self.error_name = error_name
|
||||
|
||||
def generate_body(self, grammar) -> list[list[str | Terminal]]:
|
||||
"""Generate the body of the non-terminal.
|
||||
|
|
@ -1763,12 +1781,16 @@ def rule(f: typing.Callable, /) -> Rule: ...
|
|||
|
||||
@typing.overload
|
||||
def rule(
|
||||
name: str | None = None, transparent: bool | None = None
|
||||
name: str | None = None,
|
||||
transparent: bool | None = None,
|
||||
error_name: str | None = None,
|
||||
) -> typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]: ...
|
||||
|
||||
|
||||
def rule(
|
||||
name: str | None | typing.Callable = None, transparent: bool | None = None
|
||||
name: str | None | typing.Callable = None,
|
||||
transparent: bool | None = None,
|
||||
error_name: str | 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.
|
||||
|
|
@ -1783,6 +1805,7 @@ def rule(
|
|||
def wrapper(f: typing.Callable[[typing.Any], Rule]):
|
||||
nonlocal name
|
||||
nonlocal transparent
|
||||
nonlocal error_name
|
||||
|
||||
if name is None:
|
||||
name = f.__name__
|
||||
|
|
@ -1791,7 +1814,7 @@ def rule(
|
|||
if transparent is None:
|
||||
transparent = name.startswith("_")
|
||||
|
||||
return NonTerminal(f, name, transparent)
|
||||
return NonTerminal(f, name, transparent, error_name)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
|
@ -2969,6 +2992,17 @@ class Grammar:
|
|||
assert t.name is not None
|
||||
table.trivia.add(t.name)
|
||||
|
||||
for nt in self._nonterminals.values():
|
||||
if nt.error_name is not None:
|
||||
table.error_names[nt.name] = nt.error_name
|
||||
|
||||
for t in self._terminals.values():
|
||||
if t.name is not None:
|
||||
if t.error_name is not None:
|
||||
table.error_names[t.name] = t.error_name
|
||||
elif isinstance(t.pattern, str):
|
||||
table.error_names[t.name] = f'"{t.pattern}"'
|
||||
|
||||
return table
|
||||
|
||||
def compile_lexer(self) -> LexerTable:
|
||||
|
|
|
|||
|
|
@ -404,6 +404,9 @@ class Parser:
|
|||
def __init__(self, table: parser.ParseTable):
|
||||
self.table = table
|
||||
|
||||
def readable(self, token_kind: str) -> str:
|
||||
return self.table.error_names.get(token_kind, token_kind)
|
||||
|
||||
def parse(self, tokens: TokenStream) -> typing.Tuple[Tree | None, list[str]]:
|
||||
"""Parse a token stream into a tree, returning both the root of the tree
|
||||
(if any could be found) and a list of errors that were encountered during
|
||||
|
|
@ -527,7 +530,8 @@ class Parser:
|
|||
# See if we can figure out what we were working on here,
|
||||
# for the error message.
|
||||
if production_message is None and len(repair.reductions) > 0:
|
||||
production_message = f"while parsing {repair.reductions[-1]}"
|
||||
reduction = repair.reductions[-1]
|
||||
production_message = f"while parsing {self.readable(reduction)}"
|
||||
|
||||
match repair.repair:
|
||||
case RepairAction.Base:
|
||||
|
|
@ -553,7 +557,9 @@ class Parser:
|
|||
cursor += 1
|
||||
|
||||
if token_message is None:
|
||||
token_message = f"(Did you forget {repair.value}?)"
|
||||
token_message = (
|
||||
f"(Did you forget {self.readable(repair.value)}?)"
|
||||
)
|
||||
|
||||
case RepairAction.Delete:
|
||||
del input[cursor]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue