Compare commits

...

3 commits

Author SHA1 Message Date
55c4675fe5 Use precedence and not a lot of different rules 2024-05-31 08:32:05 -07:00
fee1c68dea Wrap errors
Maybe people want to see them
2024-05-31 08:31:47 -07:00
275d8afe26 Raise special errors on ambiguities
Maybe people want to see them
2024-05-31 08:31:30 -07:00
3 changed files with 89 additions and 69 deletions

View file

@ -75,6 +75,11 @@ class FineGrammar(Grammar):
# statement or an expression, prefer the statement.
#
(Assoc.NONE, [self.if_statement]),
#
# If there's confusion about whether to make an IS expression
# or something else, prefer IS.
#
(Assoc.NONE, [self.is_expression]),
],
)
@ -224,59 +229,32 @@ class FineGrammar(Grammar):
return seq(self.expression, SEMICOLON)
# Expressions
@rule
@rule(transparent=True)
def expression(self) -> Rule:
return self.assignment_expression
return self.binary_expression | self.is_expression | self.primary_expression
@rule
def assignment_expression(self) -> Rule:
return seq(self.or_expression, EQUAL, self.assignment_expression) | self.or_expression
@rule("BinaryExpression")
def binary_expression(self) -> Rule:
return (
seq(self.expression, EQUAL, self.expression)
| seq(self.expression, OR, self.expression)
| seq(self.expression, IS, self.pattern)
| seq(self.expression, AND, self.expression)
| seq(self.expression, EQUALEQUAL, self.expression)
| seq(self.expression, BANGEQUAL, self.expression)
| seq(self.expression, LESS, self.expression)
| seq(self.expression, LESSEQUAL, self.expression)
| seq(self.expression, GREATER, self.expression)
| seq(self.expression, GREATEREQUAL, self.expression)
| seq(self.expression, PLUS, self.expression)
| seq(self.expression, MINUS, self.expression)
| seq(self.expression, STAR, self.expression)
| seq(self.expression, SLASH, self.expression)
)
@rule
def or_expression(self) -> Rule:
return seq(self.or_expression, OR, self.is_expression) | self.is_expression
@rule
@rule("IsExpression")
def is_expression(self) -> Rule:
return seq(self.is_expression, IS, self.pattern) | self.and_expression
@rule
def and_expression(self) -> Rule:
return seq(self.and_expression, AND, self.equality_expression) | self.equality_expression
@rule
def equality_expression(self) -> Rule:
return (
seq(self.equality_expression, EQUALEQUAL, self.relation_expression)
| seq(self.equality_expression, BANGEQUAL, self.relation_expression)
| self.relation_expression
)
@rule
def relation_expression(self) -> Rule:
return (
seq(self.relation_expression, LESS, self.additive_expression)
| seq(self.relation_expression, LESSEQUAL, self.additive_expression)
| seq(self.relation_expression, GREATER, self.additive_expression)
| seq(self.relation_expression, GREATEREQUAL, self.additive_expression)
| self.additive_expression
)
@rule
def additive_expression(self) -> Rule:
return (
seq(self.additive_expression, PLUS, self.multiplication_expression)
| seq(self.additive_expression, MINUS, self.multiplication_expression)
| self.multiplication_expression
)
@rule
def multiplication_expression(self) -> Rule:
return (
seq(self.multiplication_expression, STAR, self.primary_expression)
| seq(self.multiplication_expression, SLASH, self.primary_expression)
| self.primary_expression
)
return seq(self.expression, IS, self.pattern)
@rule
def primary_expression(self) -> Rule:
@ -343,12 +321,16 @@ class FineGrammar(Grammar):
@rule("Pattern")
def pattern(self) -> Rule:
return (
seq(self.variable_binding, self._pattern_core, AND, self.and_expression)
seq(self.variable_binding, self._pattern_core, self.pattern_predicate)
| seq(self.variable_binding, self._pattern_core)
| seq(self._pattern_core, AND, self.and_expression)
| seq(self._pattern_core, self.pattern_predicate)
| self._pattern_core
)
@rule(transparent=True)
def pattern_predicate(self) -> Rule:
return seq(AND, self.expression)
@rule
def _pattern_core(self) -> Rule:
return self.type_expression | self.wildcard_pattern

View file

@ -7,6 +7,7 @@ import os
import select
import sys
import termios
import textwrap
import time
import traceback
import tty
@ -390,8 +391,10 @@ class Harness:
for line in lines[: rows - 3]:
print(line[:cols] + "\r")
else:
for error in self.errors[: rows - 3]:
print(error[:cols] + "\r")
wrapper = textwrap.TextWrapper(width=cols)
lines = [line for error in self.errors for line in wrapper.wrap(error)]
for line in lines[: rows - 3]:
print(line + "\r")
sys.stdout.flush()
sys.stdout.buffer.flush()

View file

@ -420,6 +420,43 @@ class Error(Action):
pass
@dataclasses.dataclass
class PossibleAction:
name: str
rule: str
action_str: str
def __str__(self):
return f"We are in the rule `{self.name}: {self.rule}` and we should {self.action_str}"
@dataclasses.dataclass
class Ambiguity:
path: str
symbol: str
actions: typing.Tuple[PossibleAction]
def __str__(self):
lines = []
lines.append(
f"When we have parsed '{self.path}' and see '{self.symbol}' we don't know whether:"
)
lines.extend(f"- {action}" for action in self.actions)
return "\n".join(lines)
class AmbiguityError(Exception):
ambiguities: list[Ambiguity]
def __init__(self, ambiguities):
self.ambiguities = ambiguities
def __str__(self):
return "The grammar is ambiguous:\n\n" + "\n\n".join(
str(ambiguity) for ambiguity in self.ambiguities
)
class ErrorCollection:
"""A collection of errors. The errors are grouped by config set and alphabet
symbol, so that we can group the error strings appropriately when we format
@ -463,11 +500,11 @@ class ErrorCollection:
symbol_errors[config] = action
def format(
def gen_exception(
self,
alphabet: list[str],
all_sets: ConfigurationSetInfo,
) -> str | None:
) -> AmbiguityError | None:
"""Format all the errors into a string, or return None if there are no
errors.
@ -475,7 +512,7 @@ class ErrorCollection:
readable, and all the sets to trace a path to where the errors were
encountered.
"""
if len(self.errors) is None:
if len(self.errors) == 0:
return None
errors = []
@ -484,10 +521,7 @@ class ErrorCollection:
path_str = " ".join(alphabet[s] for s in path)
for symbol, symbol_errors in set_errors.items():
lines = []
lines.append(
f"When we have parsed '{path_str}' and see '{alphabet[symbol]}' we don't know whether:"
)
actions = []
for config, action in symbol_errors.items():
name = alphabet[config.name]
rule = " ".join(
@ -499,7 +533,7 @@ class ErrorCollection:
match action:
case Reduce(name=name, count=count, transparent=transparent):
name_str = name if not transparent else "transparent node"
name_str = name if not transparent else f"transparent node ({name})"
action_str = f"pop {count} values off the stack and make a {name_str}"
case Shift():
action_str = "consume the token and keep going"
@ -508,13 +542,13 @@ class ErrorCollection:
case _:
raise Exception(f"unknown action type {action}")
lines.append(
f" - We are in the rule `{name}: {rule}` and we should {action_str}"
)
actions.append(PossibleAction(name, rule, action_str))
errors.append("\n".join(lines))
errors.append(
Ambiguity(path=path_str, symbol=alphabet[symbol], actions=tuple(actions))
)
return "\n\n".join(errors)
return AmbiguityError(errors)
@dataclasses.dataclass
@ -607,9 +641,10 @@ class TableBuilder(object):
Raises ValueError if there were any conflicts during construction.
"""
self._flush_row()
if self.errors.any():
errors = self.errors.format(self.alphabet, all_sets)
raise ValueError(f"Errors building the table:\n\n{errors}")
error = self.errors.gen_exception(self.alphabet, all_sets)
if error is not None:
raise error
return ParseTable(actions=self.actions, gotos=self.gotos)
def new_row(self, config_set: ConfigSet):