Compare commits

..

No commits in common. "55c4675fe578c4e39b78f14a9846663cc7a3bd59" and "d93b7795387db29949d20ae7723a776c3b7a09fb" have entirely different histories.

3 changed files with 68 additions and 88 deletions

View file

@ -75,11 +75,6 @@ class FineGrammar(Grammar):
# statement or an expression, prefer the statement. # statement or an expression, prefer the statement.
# #
(Assoc.NONE, [self.if_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) return seq(self.expression, SEMICOLON)
# Expressions # Expressions
@rule(transparent=True) @rule
def expression(self) -> Rule: def expression(self) -> Rule:
return self.binary_expression | self.is_expression | self.primary_expression return self.assignment_expression
@rule("BinaryExpression") @rule
def binary_expression(self) -> 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 ( return (
seq(self.expression, EQUAL, self.expression) seq(self.equality_expression, EQUALEQUAL, self.relation_expression)
| seq(self.expression, OR, self.expression) | seq(self.equality_expression, BANGEQUAL, self.relation_expression)
| seq(self.expression, IS, self.pattern) | self.relation_expression
| 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("IsExpression") @rule
def is_expression(self) -> Rule: def relation_expression(self) -> Rule:
return seq(self.expression, IS, self.pattern) 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 @rule
def primary_expression(self) -> Rule: def primary_expression(self) -> Rule:
@ -321,16 +343,12 @@ class FineGrammar(Grammar):
@rule("Pattern") @rule("Pattern")
def pattern(self) -> Rule: def pattern(self) -> Rule:
return ( 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.variable_binding, self._pattern_core)
| seq(self._pattern_core, self.pattern_predicate) | seq(self._pattern_core, AND, self.and_expression)
| self._pattern_core | self._pattern_core
) )
@rule(transparent=True)
def pattern_predicate(self) -> Rule:
return seq(AND, self.expression)
@rule @rule
def _pattern_core(self) -> Rule: def _pattern_core(self) -> Rule:
return self.type_expression | self.wildcard_pattern return self.type_expression | self.wildcard_pattern

View file

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

View file

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