Skip to content

Commit 0a28a89

Browse files
committed
gh-138970: Add general metadata system to the peg generator
1 parent 32e1e06 commit 0a28a89

File tree

4 files changed

+134
-23
lines changed

4 files changed

+134
-23
lines changed

Lib/test/test_peg_generator/test_pegen.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,3 +1106,66 @@ def test_deep_nested_rule(self) -> None:
11061106
)
11071107

11081108
self.assertEqual(output, expected_output)
1109+
1110+
def test_rule_flags(self) -> None:
1111+
"""Test the new rule flags syntax that accepts arbitrary lists of flags."""
1112+
# Test grammar with various flag combinations
1113+
grammar_source = """
1114+
start: simple_rule
1115+
1116+
simple_rule (memo):
1117+
| "hello"
1118+
1119+
multi_flag_rule (memo, custom, test):
1120+
| "world"
1121+
1122+
single_custom_flag (custom):
1123+
| "test"
1124+
1125+
no_flags_rule:
1126+
| "plain"
1127+
"""
1128+
1129+
grammar: Grammar = parse_string(grammar_source, GrammarParser)
1130+
rules = grammar.rules
1131+
1132+
# Test memo-only rule
1133+
simple_rule = rules['simple_rule']
1134+
self.assertTrue(simple_rule.memo, "simple_rule should have memo=True")
1135+
self.assertEqual(simple_rule.flags, frozenset(['memo']),
1136+
f"simple_rule flags should be ['memo'], got {simple_rule.flags}")
1137+
1138+
# Test multi-flag rule
1139+
multi_flag_rule = rules['multi_flag_rule']
1140+
self.assertTrue(multi_flag_rule.memo, "multi_flag_rule should have memo=True")
1141+
self.assertEqual(multi_flag_rule.flags, frozenset({'memo', 'custom', 'test'}),
1142+
f"multi_flag_rule flags should contain memo, custom, test, got {multi_flag_rule.flags}")
1143+
1144+
# Test single custom flag rule
1145+
single_custom_rule = rules['single_custom_flag']
1146+
self.assertFalse(single_custom_rule.memo, "single_custom_flag should have memo=False")
1147+
self.assertEqual(single_custom_rule.flags, frozenset(['custom']),
1148+
f"single_custom_flag flags should be ['custom'], got {single_custom_rule.flags}")
1149+
1150+
# Test no flags rule
1151+
no_flags_rule = rules['no_flags_rule']
1152+
self.assertFalse(no_flags_rule.memo, "no_flags_rule should have memo=False")
1153+
self.assertEqual(no_flags_rule.flags, [],
1154+
f"no_flags_rule flags should be [], got {no_flags_rule.flags}")
1155+
1156+
def test_memo(self) -> None:
1157+
"""Test that the old (memo) syntax works with the flag system"""
1158+
grammar_source = """
1159+
start: memo_rule
1160+
1161+
memo_rule (memo):
1162+
| "test"
1163+
"""
1164+
1165+
grammar: Grammar = parse_string(grammar_source, GrammarParser)
1166+
rules = grammar.rules
1167+
1168+
memo_rule = rules['memo_rule']
1169+
self.assertTrue(memo_rule.memo, "memo_rule should have memo=True")
1170+
self.assertEqual(memo_rule.flags, frozenset(['memo']),
1171+
f"memo_rule flags should be ['memo'], got {memo_rule.flags}")

Tools/peg_generator/pegen/grammar.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,19 @@ def __iter__(self) -> Iterator[Rule]:
5858

5959

6060
class Rule:
61-
def __init__(self, name: str, type: str | None, rhs: Rhs, memo: object | None = None):
61+
def __init__(self, name: str, type: str | None, rhs: Rhs, memo: object | None = None, flags: list[str] | None = None):
6262
self.name = name
6363
self.type = type
6464
self.rhs = rhs
65-
self.memo = bool(memo)
65+
self.flags = flags or []
6666
self.left_recursive = False
6767
self.leader = False
6868

69+
@property
70+
def memo(self) -> bool:
71+
"""Check if this rule should be memoized by looking for 'memo' in flags."""
72+
return "memo" in self.flags
73+
6974
def is_loop(self) -> bool:
7075
return self.name.startswith("_loop")
7176

Tools/peg_generator/pegen/grammar_parser.py

Lines changed: 54 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tools/peg_generator/pegen/metagrammar.gram

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,21 @@ rules[RuleList]:
5050
| rule { [rule] }
5151

5252
rule[Rule]:
53-
| rulename memoflag? ":" alts NEWLINE INDENT more_alts DEDENT {
54-
Rule(rulename[0], rulename[1], Rhs(alts.alts + more_alts.alts), memo=opt) }
55-
| rulename memoflag? ":" NEWLINE INDENT more_alts DEDENT {
56-
Rule(rulename[0], rulename[1], more_alts, memo=opt) }
57-
| rulename memoflag? ":" alts NEWLINE { Rule(rulename[0], rulename[1], alts, memo=opt) }
53+
| rulename flags? ":" alts NEWLINE INDENT more_alts DEDENT {
54+
Rule(rulename[0], rulename[1], Rhs(alts.alts + more_alts.alts), flags=opt) }
55+
| rulename flags? ":" NEWLINE INDENT more_alts DEDENT {
56+
Rule(rulename[0], rulename[1], more_alts, flags=opt) }
57+
| rulename flags? ":" alts NEWLINE { Rule(rulename[0], rulename[1], alts, flags=opt) }
5858

5959
rulename[RuleName]:
6060
| NAME annotation { (name.string, annotation) }
6161
| NAME { (name.string, None) }
6262

63-
# In the future this may return something more complicated
64-
memoflag[str]:
65-
| '(' "memo" ')' { "memo" }
63+
flags[list]:
64+
| '(' a=','.flag+ ')' { frozenset(a) }
65+
66+
flag[str]:
67+
| NAME { name.string }
6668

6769
alts[Rhs]:
6870
| alt "|" alts { Rhs([alt] + alts.alts)}

0 commit comments

Comments
 (0)