Compare commits
No commits in common. "55c4675fe578c4e39b78f14a9846663cc7a3bd59" and "d93b7795387db29949d20ae7723a776c3b7a09fb" have entirely different histories.
55c4675fe5
...
d93b779538
3 changed files with 68 additions and 88 deletions
82
grammar.py
82
grammar.py
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
67
parser.py
67
parser.py
|
|
@ -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(
|
errors.append("\n".join(lines))
|
||||||
Ambiguity(path=path_str, symbol=alphabet[symbol], actions=tuple(actions))
|
|
||||||
)
|
|
||||||
|
|
||||||
return AmbiguityError(errors)
|
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):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue