from __future__ import annotations
from typing import (
AbstractSet,
Any,
Iterable,
Iterator,
List,
Optional,
Tuple,
Union,
)
class GrammarError(Exception):
pass
class GrammarVisitor:
def visit(self, node: Any, *args: Any, **kwargs: Any) -> Any:
"""Visit a node."""
method = "visit_" + node.__class__.__name__
visitor = getattr(self, method, self.generic_visit)
return visitor(node, *args, **kwargs)
def generic_visit(self, node: Iterable[Any], *args: Any, **kwargs: Any) -> Any:
"""Called if no explicit visitor function exists for a node."""
for value in node:
if isinstance(value, list):
for item in value:
self.visit(item, *args, **kwargs)
else:
self.visit(value, *args, **kwargs)
class Grammar:
def __init__(self, rules: Iterable[Rule], metas: Iterable[Tuple[str, Optional[str]]]):
# Check if there are repeated rules in "rules"
all_rules = {}
for rule in rules:
if rule.name in all_rules:
raise GrammarError(f"Repeated rule {rule.name!r}")
all_rules[rule.name] = rule
self.rules = all_rules
self.metas = dict(metas)
def __str__(self) -> str:
return "\n".join(str(rule) for name, rule in self.rules.items())
def __repr__(self) -> str:
lines = ["Grammar("]
lines.append(" [")
for rule in self.rules.values():
lines.append(f" {repr(rule)},")
lines.append(" ],")
lines.append(" {repr(list(self.metas.items()))}")
lines.append(")")
return "\n".join(lines)
def __iter__(self) -> Iterator[Rule]:
yield from self.rules.values()
# Global flag whether we want actions in __str__() -- default off.
SIMPLE_STR = True
class Rule:
def __init__(self, name: str, type: Optional[str], rhs: Rhs, memo: Optional[object] = None):
self.name = name
self.type = type
self.rhs = rhs
self.memo = bool(memo)
self.left_recursive = False
self.leader = False
def is_loop(self) -> bool:
return self.name.startswith("_loop")
def is_gather(self) -> bool:
return self.name.startswith("_gather")
def __str__(self) -> str:
if SIMPLE_STR or self.type is None:
res = f"{self.name}: {self.rhs}"
else:
res = f"{self.name}[{self.type}]: {self.rhs}"
if len(res) < 88:
return res
lines = [res.split(":")[0] + ":"]
lines += [f" | {alt}" for alt in self.rhs.alts]
return "\n".join(lines)
def __repr__(self) -> str:
return f"Rule({self.name!r}, {self.type!r}, {self.rhs!r})"
def __iter__(self) -> Iterator[Rhs]:
yield self.rhs
def flatten(self) -> Rhs:
# If it's a single parenthesized group, flatten it.
rhs = self.rhs
if (
not self.is_loop()
and len(rhs.alts) == 1
and len(rhs.alts[0].items) == 1
and isinstance(rhs.alts[0].items[0].item, Group)
):
rhs = rhs.alts[0].items[0].item.rhs
return rhs
class Leaf:
def __init__(self, value: str):
self.value = value
def __str__(self) -> str:
return self.value
def __iter__(self) -> Iterable[str]:
yield from ()
class NameLeaf(Leaf):
"""The value is the name."""
def __str__(self) -> str:
if self.value == "ENDMARKER":
return "$"
return super().__str__()
def __repr__(self) -> str:
return f"NameLeaf({self.value!r})"
class StringLeaf(Leaf):
"""The value is a string literal, including quotes."""
def __repr__(self) -> str:
return f"StringLeaf({self.value!r})"
class Rhs:
def __init__(self, alts: List[Alt]):
self.alts = alts
self.memo: Optional[Tuple[Optional[str], str]] = None
def __str__(self) -> str:
return " | ".join(str(alt) for alt in self.alts)
def __repr__(self) -> str:
return f"Rhs({self.alts!r})"
def __iter__(self) -> Iterator[List[Alt]]:
yield self.alts
@property
def can_be_inlined(self) -> bool:
if len(self.alts) != 1 or len(self.alts[0].items) != 1:
return False
# If the alternative has an action we cannot inline
if getattr(self.alts[0], "action", None) is not None:
return False
return True
class Alt:
def __init__(self, items: List[NamedItem], *, icut: int = -1, action: Optional[str] = None):
self.items = items
self.icut = icut
self.action = action
def __str__(self) -> str:
core = " ".join(str(item) for item in self.items)
if not SIMPLE_STR and self.action:
return f"{core} {{ {self.action} }}"
else:
return core
def __repr__(self) -> str:
args = [repr(self.items)]
if self.icut >= 0:
args.append(f"icut={self.icut}")
if self.action:
args.append(f"action={self.action!r}")
return f"Alt({', '.join(args)})"
def __iter__(self) -> Iterator[List[NamedItem]]:
yield self.items
class NamedItem:
def __init__(self, name: Optional[str], item: Item, type: Optional[str] = None):
self.name = name
self.item = item
self.type = type
def __str__(self) -> str:
if not SIMPLE_STR and self.name:
return f"{self.name}={self.item}"
else:
return str(self.item)
def __repr__(self) -> str:
return f"NamedItem({self.name!r}, {self.item!r})"
def __iter__(self) -> Iterator[Item]:
yield self.item
class Forced:
def __init__(self, node: Plain):
self.node = node
def __str__(self) -> str:
return f"&&{self.node}"
def __iter__(self) -> Iterator[Plain]:
yield self.node
class Lookahead:
def __init__(self, node: Plain, sign: str):
self.node = node
self.sign = sign
def __str__(self) -> str:
return f"{self.sign}{self.node}"
def __iter__(self) -> Iterator[Plain]:
yield self.node
class PositiveLookahead(Lookahead):
def __init__(self, node: Plain):
super().__init__(node, "&")
def __repr__(self) -> str:
return f"PositiveLookahead({self.node!r})"
class NegativeLookahead(Lookahead):
def __init__(self, node: Plain):
super().__init__(node, "!")
def __repr__(self) -> str:
return f"NegativeLookahead({self.node!r})"
class Opt:
def __init__(self, node: Item):
self.node = node
def __str__(self) -> str:
s = str(self.node)
# TODO: Decide whether to use [X] or X? based on type of X
if " " in s:
return f"[{s}]"
else:
return f"{s}?"
def __repr__(self) -> str:
return f"Opt({self.node!r})"
def __iter__(self) -> Iterator[Item]:
yield self.node
class Repeat:
"""Shared base class for x* and x+."""
def __init__(self, node: Plain):
self.node = node
self.memo: Optional[Tuple[Optional[str], str]] = None
def __iter__(self) -> Iterator[Plain]:
yield self.node
class Repeat0(Repeat):
def __str__(self) -> str:
s = str(self.node)
# TODO: Decide whether to use (X)* or X* based on type of X
if " " in s:
return f"({s})*"
else:
return f"{s}*"
def __repr__(self) -> str:
return f"Repeat0({self.node!r})"
class Repeat1(Repeat):
def __str__(self) -> str:
s = str(self.node)
# TODO: Decide whether to use (X)+ or X+ based on type of X
if " " in s:
return f"({s})+"
else:
return f"{s}+"
def __repr__(self) -> str:
return f"Repeat1({self.node!r})"
class Gather(Repeat):
def __init__(self, separator: Plain, node: Plain):
self.separator = separator
self.node = node
def __str__(self) -> str:
return f"{self.separator!s}.{self.node!s}+"
def __repr__(self) -> str:
return f"Gather({self.separator!r}, {self.node!r})"
class Group:
def __init__(self, rhs: Rhs):
self.rhs = rhs
def __str__(self) -> str:
return f"({self.rhs})"
def __repr__(self) -> str:
return f"Group({self.rhs!r})"
def __iter__(self) -> Iterator[Rhs]:
yield self.rhs
class Cut:
def __init__(self) -> None:
pass
def __repr__(self) -> str:
return f"Cut()"
def __str__(self) -> str:
return f"~"
def __iter__(self) -> Iterator[Tuple[str, str]]:
yield from ()
def __eq__(self, other: object) -> bool:
if not isinstance(other, Cut):
return NotImplemented
return True
def initial_names(self) -> AbstractSet[str]:
return set()
Plain = Union[Leaf, Group]
Item = Union[Plain, Opt, Repeat, Forced, Lookahead, Rhs, Cut]
RuleName = Tuple[str, Optional[str]]
MetaTuple = Tuple[str, Optional[str]]
MetaList = List[MetaTuple]
RuleList = List[Rule]
NamedItemList = List[NamedItem]
LookaheadOrCut = Union[Lookahead, Cut]