1from typing import Optional
2
3from pegen import grammar
4from pegen.grammar import Alt, GrammarVisitor, Rhs, Rule
5
6
7class ValidationError(Exception):
8    pass
9
10
11class GrammarValidator(GrammarVisitor):
12    def __init__(self, grammar: grammar.Grammar) -> None:
13        self.grammar = grammar
14        self.rulename: Optional[str] = None
15
16    def validate_rule(self, rulename: str, node: Rule) -> None:
17        self.rulename = rulename
18        self.visit(node)
19        self.rulename = None
20
21
22class SubRuleValidator(GrammarValidator):
23    def visit_Rhs(self, node: Rhs) -> None:
24        for index, alt in enumerate(node.alts):
25            alts_to_consider = node.alts[index + 1 :]
26            for other_alt in alts_to_consider:
27                self.check_intersection(alt, other_alt)
28
29    def check_intersection(self, first_alt: Alt, second_alt: Alt) -> None:
30        if str(second_alt).startswith(str(first_alt)):
31            raise ValidationError(
32                f"In {self.rulename} there is an alternative that will "
33                f"never be visited:\n{second_alt}"
34            )
35
36
37def validate_grammar(the_grammar: grammar.Grammar) -> None:
38    for validator_cls in GrammarValidator.__subclasses__():
39        validator = validator_cls(the_grammar)
40        for rule_name, rule in the_grammar.rules.items():
41            validator.validate_rule(rule_name, rule)
42