diff --git a/grammar.py b/grammar.py index 41c2064..9069ef3 100644 --- a/grammar.py +++ b/grammar.py @@ -75,11 +75,6 @@ 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]), ], ) @@ -229,32 +224,59 @@ class FineGrammar(Grammar): return seq(self.expression, SEMICOLON) # Expressions - @rule(transparent=True) + @rule def expression(self) -> Rule: - return self.binary_expression | self.is_expression | self.primary_expression + return self.assignment_expression - @rule("BinaryExpression") - def binary_expression(self) -> Rule: + @rule + def assignment_expression(self) -> Rule: + return seq(self.or_expression, EQUAL, self.assignment_expression) | self.or_expression + + @rule + def or_expression(self) -> Rule: + return seq(self.or_expression, OR, self.is_expression) | self.is_expression + + @rule + 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.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) + seq(self.equality_expression, EQUALEQUAL, self.relation_expression) + | seq(self.equality_expression, BANGEQUAL, self.relation_expression) + | self.relation_expression ) - @rule("IsExpression") - def is_expression(self) -> Rule: - return seq(self.expression, IS, self.pattern) + @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 + ) @rule def primary_expression(self) -> Rule: @@ -321,16 +343,12 @@ class FineGrammar(Grammar): @rule("Pattern") def pattern(self) -> Rule: return ( - seq(self.variable_binding, self._pattern_core, self.pattern_predicate) + seq(self.variable_binding, self._pattern_core, AND, self.and_expression) | seq(self.variable_binding, self._pattern_core) - | seq(self._pattern_core, self.pattern_predicate) + | seq(self._pattern_core, AND, self.and_expression) | 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 diff --git a/harness.py b/harness.py index 268b6cd..042d001 100644 --- a/harness.py +++ b/harness.py @@ -7,7 +7,6 @@ import os import select import sys import termios -import textwrap import time import traceback import tty @@ -391,10 +390,8 @@ class Harness: for line in lines[: rows - 3]: print(line[:cols] + "\r") else: - 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") + for error in self.errors[: rows - 3]: + print(error[:cols] + "\r") sys.stdout.flush() sys.stdout.buffer.flush() diff --git a/parser.py b/parser.py index 583d8bd..371b7e5 100644 --- a/parser.py +++ b/parser.py @@ -420,43 +420,6 @@ 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 @@ -500,11 +463,11 @@ class ErrorCollection: symbol_errors[config] = action - def gen_exception( + def format( self, alphabet: list[str], all_sets: ConfigurationSetInfo, - ) -> AmbiguityError | None: + ) -> str | None: """Format all the errors into a string, or return None if there are no errors. @@ -512,7 +475,7 @@ class ErrorCollection: readable, and all the sets to trace a path to where the errors were encountered. """ - if len(self.errors) == 0: + if len(self.errors) is None: return None errors = [] @@ -521,7 +484,10 @@ class ErrorCollection: path_str = " ".join(alphabet[s] for s in path) for symbol, symbol_errors in set_errors.items(): - actions = [] + lines = [] + lines.append( + f"When we have parsed '{path_str}' and see '{alphabet[symbol]}' we don't know whether:" + ) for config, action in symbol_errors.items(): name = alphabet[config.name] rule = " ".join( @@ -533,7 +499,7 @@ class ErrorCollection: match action: case Reduce(name=name, count=count, transparent=transparent): - name_str = name if not transparent else f"transparent node ({name})" + name_str = name if not transparent else "transparent node" action_str = f"pop {count} values off the stack and make a {name_str}" case Shift(): action_str = "consume the token and keep going" @@ -542,13 +508,13 @@ class ErrorCollection: case _: raise Exception(f"unknown action type {action}") - actions.append(PossibleAction(name, rule, action_str)) + lines.append( + f" - We are in the rule `{name}: {rule}` and we should {action_str}" + ) - errors.append( - Ambiguity(path=path_str, symbol=alphabet[symbol], actions=tuple(actions)) - ) + errors.append("\n".join(lines)) - return AmbiguityError(errors) + return "\n\n".join(errors) @dataclasses.dataclass @@ -641,10 +607,9 @@ class TableBuilder(object): Raises ValueError if there were any conflicts during construction. """ self._flush_row() - error = self.errors.gen_exception(self.alphabet, all_sets) - if error is not None: - raise error - + if self.errors.any(): + errors = self.errors.format(self.alphabet, all_sets) + raise ValueError(f"Errors building the table:\n\n{errors}") return ParseTable(actions=self.actions, gotos=self.gotos) def new_row(self, config_set: ConfigSet):