https://github.com/python/cpython/commit/89fe9857dd0832de9b597d63ecb11fa7a09984f1
commit: 89fe9857dd0832de9b597d63ecb11fa7a09984f1
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-09-14T20:26:18-04:00
summary:
gh-153568: Reuse shared prefixes in generated parser rules (#157465)
files:
A Lib/test/test_peg_generator/test_prefix.py
A
Misc/NEWS.d/next/Core_and_Builtins/2026-09-13-21-00-00.gh-issue-153568.prefix-cache.rst
M InternalDocs/parser.md
M Lib/test/test_peg_generator/test_c_parser.py
M Parser/parser.c
M Tools/peg_generator/pegen/c_generator.py
diff --git a/InternalDocs/parser.md b/InternalDocs/parser.md
index ff6426c4879f660..bdb130f7a5ccfb0 100644
--- a/InternalDocs/parser.md
+++ b/InternalDocs/parser.md
@@ -563,6 +563,23 @@ in the generated C parse code that allows to measure how
much each rule uses
memoization (check the [`Parser/pegen.c`](../Parser/pegen.c)
file for more information) but it needs to be manually activated.
+The C generator also reuses memoized prefixes within consecutive alternatives.
+For example, in `prefix ':' NAME | prefix ':' NUMBER`, failure after the first
+`':'` normally requires another call to `prefix` and another memo lookup. The
+generated code can keep the result and ending position in local variables and
+reuse them when trying the next alternative.
+
+This applies only when the shared first item is a memoized rule, including a
+left-recursion leader, that the generator can prove consumes input on success.
+The locals are reset on each rule-body invocation, including each seed-growing
+iteration. Alternative order, cuts, and suffix backtracking are preserved. When
+`call_invalid_rules` is enabled, the generated code uses the original rule
calls.
+
+The consumption analysis follows grammar items; it cannot inspect arbitrary C
+actions. As with memoization, actions must not invalidate cached results. In
+particular, suffix actions must not move the parser before their starting mark,
+rewrite buffered input, or replace memo entries for earlier positions.
+
Automatic variables
-------------------
diff --git a/Lib/test/test_peg_generator/test_c_parser.py
b/Lib/test/test_peg_generator/test_c_parser.py
index dc887693840a007..cd0b907667e4a27 100644
--- a/Lib/test/test_peg_generator/test_c_parser.py
+++ b/Lib/test/test_peg_generator/test_c_parser.py
@@ -145,6 +145,35 @@ def run_test(self, grammar_source, test_source):
TEST_TEMPLATE.format(extension_path=self.tmp_path,
test_source=test_source),
)
+ def test_prefix_reuses_position(self) -> None:
+ grammar_source = """
+ start:
+ | prefix ':' NAME NEWLINE? ENDMARKER
+ | prefix ':' NUMBER NEWLINE? ENDMARKER
+ | prefix '=' NUMBER NEWLINE? ENDMARKER
+ prefix (memo): NAME NAME
+ """
+ self.run_test(grammar_source, """
+ self.check_input_strings_for_grammar(
+ valid_cases=['one two : name', 'one two : 3', 'one two = 3'],
+ invalid_cases=['one = 3', 'one two = name', 'one two :'],
+ )
+ """)
+
+ def test_prefix_respects_cut(self) -> None:
+ grammar_source = """
+ start:
+ | prefix ':' ~ NAME NEWLINE? ENDMARKER
+ | prefix ':' NUMBER NEWLINE? ENDMARKER
+ prefix (memo): NAME NAME
+ """
+ self.run_test(grammar_source, """
+ self.check_input_strings_for_grammar(
+ valid_cases=['one two : name'],
+ invalid_cases=['one two : 3'],
+ )
+ """)
+
def test_c_parser(self) -> None:
grammar_source = """
start[mod_ty]: a[asdl_stmt_seq*]=stmt* $ { _PyAST_Module(a, NULL,
p->arena) }
diff --git a/Lib/test/test_peg_generator/test_prefix.py
b/Lib/test/test_peg_generator/test_prefix.py
new file mode 100644
index 000000000000000..b4d5128df0121c0
--- /dev/null
+++ b/Lib/test/test_peg_generator/test_prefix.py
@@ -0,0 +1,32 @@
+import unittest
+
+from test import test_tools
+
+with test_tools.imports_under_tool("peg_generator"):
+ from pegen.c_generator import consuming_rules
+ from pegen.testutil import GrammarParser, parse_string
+
+
+class ConsumingRuleTests(unittest.TestCase):
+ def test_predicates_cuts_and_nullable_repeats(self):
+ grammar = parse_string("""
+ start: NAME ENDMARKER
+ positive: &NAME
+ negative: !NAME
+ cut: ~ { _PyPegen_dummy_name(p) }
+ optional: [NAME]
+ empty_repeat: NAME*
+ nullable_repeat: optional+
+ consuming_repeat: NAME+
+ """, GrammarParser)
+ self.assertEqual(consuming_rules(grammar.rules), {'start',
'consuming_repeat'})
+
+ def test_fixed_point_and_mixed_alternatives(self):
+ grammar = parse_string("""
+ start: expression ENDMARKER
+ expression: expression '+' term | term
+ term: atom
+ atom: NAME | '(' expression ')'
+ nullable: NAME | &NAME
+ """, GrammarParser)
+ self.assertEqual(consuming_rules(grammar.rules), {'start',
'expression', 'term', 'atom'})
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-13-21-00-00.gh-issue-153568.prefix-cache.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-13-21-00-00.gh-issue-153568.prefix-cache.rst
new file mode 100644
index 000000000000000..0400f4431d996dc
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-13-21-00-00.gh-issue-153568.prefix-cache.rst
@@ -0,0 +1,2 @@
+Speed up parsing by reusing memoized rule prefixes across consecutive grammar
+alternatives, avoiding repeated rule calls and cache lookups.
diff --git a/Parser/parser.c b/Parser/parser.c
index 750e41a4f7d4337..6b7e7e35bf422cf 100644
--- a/Parser/parser.c
+++ b/Parser/parser.c
@@ -11409,6 +11409,8 @@ expressions_rule(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_0_result = NULL;
+ int _prefix_0_end = 0, _prefix_0_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -11429,7 +11431,7 @@ expressions_rule(Parser *p)
expr_ty a;
asdl_seq * b;
if (
- (a = expression_rule(p)) // expression
+ (a = ((!p->call_invalid_rules && _prefix_0_valid) ? (p->mark =
_prefix_0_end, _prefix_0_result) : (_prefix_0_result = expression_rule(p),
_prefix_0_end = p->mark, _prefix_0_valid = 1, _prefix_0_result))) // expression
&&
(b = _loop1_55_rule(p)) // ((',' expression))+
&&
@@ -11467,7 +11469,7 @@ expressions_rule(Parser *p)
Token * _literal;
expr_ty a;
if (
- (a = expression_rule(p)) // expression
+ (a = ((!p->call_invalid_rules && _prefix_0_valid) ? (p->mark =
_prefix_0_end, _prefix_0_result) : (_prefix_0_result = expression_rule(p),
_prefix_0_end = p->mark, _prefix_0_valid = 1, _prefix_0_result))) // expression
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
@@ -11502,7 +11504,7 @@ expressions_rule(Parser *p)
D(fprintf(stderr, "%*c> expressions[%d-%d]: %s\n", p->level, ' ',
_mark, p->mark, "expression"));
expr_ty expression_var;
if (
- (expression_var = expression_rule(p)) // expression
+ (expression_var = ((!p->call_invalid_rules && _prefix_0_valid) ?
(p->mark = _prefix_0_end, _prefix_0_result) : (_prefix_0_result =
expression_rule(p), _prefix_0_end = p->mark, _prefix_0_valid = 1,
_prefix_0_result))) // expression
)
{
D(fprintf(stderr, "%*c+ expressions[%d-%d]: %s succeeded!\n",
p->level, ' ', _mark, p->mark, "expression"));
@@ -11855,6 +11857,8 @@ star_expressions_rule(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_1_result = NULL;
+ int _prefix_1_end = 0, _prefix_1_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -11875,7 +11879,7 @@ star_expressions_rule(Parser *p)
expr_ty a;
asdl_seq * b;
if (
- (a = star_expression_rule(p)) // star_expression
+ (a = ((!p->call_invalid_rules && _prefix_1_valid) ? (p->mark =
_prefix_1_end, _prefix_1_result) : (_prefix_1_result = star_expression_rule(p),
_prefix_1_end = p->mark, _prefix_1_valid = 1, _prefix_1_result))) //
star_expression
&&
(b = _loop1_56_rule(p)) // ((',' star_expression))+
&&
@@ -11913,7 +11917,7 @@ star_expressions_rule(Parser *p)
Token * _literal;
expr_ty a;
if (
- (a = star_expression_rule(p)) // star_expression
+ (a = ((!p->call_invalid_rules && _prefix_1_valid) ? (p->mark =
_prefix_1_end, _prefix_1_result) : (_prefix_1_result = star_expression_rule(p),
_prefix_1_end = p->mark, _prefix_1_valid = 1, _prefix_1_result))) //
star_expression
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
@@ -11948,7 +11952,7 @@ star_expressions_rule(Parser *p)
D(fprintf(stderr, "%*c> star_expressions[%d-%d]: %s\n", p->level, ' ',
_mark, p->mark, "star_expression"));
expr_ty star_expression_var;
if (
- (star_expression_var = star_expression_rule(p)) // star_expression
+ (star_expression_var = ((!p->call_invalid_rules &&
_prefix_1_valid) ? (p->mark = _prefix_1_end, _prefix_1_result) :
(_prefix_1_result = star_expression_rule(p), _prefix_1_end = p->mark,
_prefix_1_valid = 1, _prefix_1_result))) // star_expression
)
{
D(fprintf(stderr, "%*c+ star_expressions[%d-%d]: %s succeeded!\n",
p->level, ' ', _mark, p->mark, "star_expression"));
@@ -12352,6 +12356,8 @@ disjunction_rule(Parser *p)
return _res;
}
int _mark = p->mark;
+ expr_ty _prefix_2_result = NULL;
+ int _prefix_2_end = 0, _prefix_2_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -12370,7 +12376,7 @@ disjunction_rule(Parser *p)
expr_ty a;
asdl_seq * b;
if (
- (a = conjunction_rule(p)) // conjunction
+ (a = ((!p->call_invalid_rules && _prefix_2_valid) ? (p->mark =
_prefix_2_end, _prefix_2_result) : (_prefix_2_result = conjunction_rule(p),
_prefix_2_end = p->mark, _prefix_2_valid = 1, _prefix_2_result))) //
conjunction
&&
(b = _loop1_59_rule(p)) // (('or' conjunction))+
)
@@ -12405,7 +12411,7 @@ disjunction_rule(Parser *p)
D(fprintf(stderr, "%*c> disjunction[%d-%d]: %s\n", p->level, ' ',
_mark, p->mark, "conjunction"));
expr_ty conjunction_var;
if (
- (conjunction_var = conjunction_rule(p)) // conjunction
+ (conjunction_var = ((!p->call_invalid_rules && _prefix_2_valid) ?
(p->mark = _prefix_2_end, _prefix_2_result) : (_prefix_2_result =
conjunction_rule(p), _prefix_2_end = p->mark, _prefix_2_valid = 1,
_prefix_2_result))) // conjunction
)
{
D(fprintf(stderr, "%*c+ disjunction[%d-%d]: %s succeeded!\n",
p->level, ' ', _mark, p->mark, "conjunction"));
@@ -12440,6 +12446,8 @@ conjunction_rule(Parser *p)
return _res;
}
int _mark = p->mark;
+ expr_ty _prefix_3_result = NULL;
+ int _prefix_3_end = 0, _prefix_3_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -12458,7 +12466,7 @@ conjunction_rule(Parser *p)
expr_ty a;
asdl_seq * b;
if (
- (a = inversion_rule(p)) // inversion
+ (a = ((!p->call_invalid_rules && _prefix_3_valid) ? (p->mark =
_prefix_3_end, _prefix_3_result) : (_prefix_3_result = inversion_rule(p),
_prefix_3_end = p->mark, _prefix_3_valid = 1, _prefix_3_result))) // inversion
&&
(b = _loop1_60_rule(p)) // (('and' inversion))+
)
@@ -12493,7 +12501,7 @@ conjunction_rule(Parser *p)
D(fprintf(stderr, "%*c> conjunction[%d-%d]: %s\n", p->level, ' ',
_mark, p->mark, "inversion"));
expr_ty inversion_var;
if (
- (inversion_var = inversion_rule(p)) // inversion
+ (inversion_var = ((!p->call_invalid_rules && _prefix_3_valid) ?
(p->mark = _prefix_3_end, _prefix_3_result) : (_prefix_3_result =
inversion_rule(p), _prefix_3_end = p->mark, _prefix_3_valid = 1,
_prefix_3_result))) // inversion
)
{
D(fprintf(stderr, "%*c+ conjunction[%d-%d]: %s succeeded!\n",
p->level, ' ', _mark, p->mark, "inversion"));
@@ -12612,6 +12620,8 @@ comparison_rule(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_4_result = NULL;
+ int _prefix_4_end = 0, _prefix_4_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -12630,7 +12640,7 @@ comparison_rule(Parser *p)
expr_ty a;
asdl_seq * b;
if (
- (a = bitwise_or_rule(p)) // bitwise_or
+ (a = ((!p->call_invalid_rules && _prefix_4_valid) ? (p->mark =
_prefix_4_end, _prefix_4_result) : (_prefix_4_result = bitwise_or_rule(p),
_prefix_4_end = p->mark, _prefix_4_valid = 1, _prefix_4_result))) // bitwise_or
&&
(b = _loop1_61_rule(p)) // compare_op_bitwise_or_pair+
)
@@ -12665,7 +12675,7 @@ comparison_rule(Parser *p)
D(fprintf(stderr, "%*c> comparison[%d-%d]: %s\n", p->level, ' ',
_mark, p->mark, "bitwise_or"));
expr_ty bitwise_or_var;
if (
- (bitwise_or_var = bitwise_or_rule(p)) // bitwise_or
+ (bitwise_or_var = ((!p->call_invalid_rules && _prefix_4_valid) ?
(p->mark = _prefix_4_end, _prefix_4_result) : (_prefix_4_result =
bitwise_or_rule(p), _prefix_4_end = p->mark, _prefix_4_valid = 1,
_prefix_4_result))) // bitwise_or
)
{
D(fprintf(stderr, "%*c+ comparison[%d-%d]: %s succeeded!\n",
p->level, ' ', _mark, p->mark, "bitwise_or"));
@@ -13448,6 +13458,8 @@ shift_expr_raw(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_5_result = NULL;
+ int _prefix_5_end = 0, _prefix_5_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -13467,7 +13479,7 @@ shift_expr_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = shift_expr_rule(p)) // shift_expr
+ (a = ((!p->call_invalid_rules && _prefix_5_valid) ? (p->mark =
_prefix_5_end, _prefix_5_result) : (_prefix_5_result = shift_expr_rule(p),
_prefix_5_end = p->mark, _prefix_5_valid = 1, _prefix_5_result))) // shift_expr
&&
(_literal = _PyPegen_expect_token(p, 33)) // token='<<'
&&
@@ -13506,7 +13518,7 @@ shift_expr_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = shift_expr_rule(p)) // shift_expr
+ (a = ((!p->call_invalid_rules && _prefix_5_valid) ? (p->mark =
_prefix_5_end, _prefix_5_result) : (_prefix_5_result = shift_expr_rule(p),
_prefix_5_end = p->mark, _prefix_5_valid = 1, _prefix_5_result))) // shift_expr
&&
(_literal = _PyPegen_expect_token(p, 34)) // token='>>'
&&
@@ -13611,6 +13623,8 @@ sum_raw(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_6_result = NULL;
+ int _prefix_6_end = 0, _prefix_6_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -13630,7 +13644,7 @@ sum_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = sum_rule(p)) // sum
+ (a = ((!p->call_invalid_rules && _prefix_6_valid) ? (p->mark =
_prefix_6_end, _prefix_6_result) : (_prefix_6_result = sum_rule(p),
_prefix_6_end = p->mark, _prefix_6_valid = 1, _prefix_6_result))) // sum
&&
(_literal = _PyPegen_expect_token(p, 14)) // token='+'
&&
@@ -13669,7 +13683,7 @@ sum_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = sum_rule(p)) // sum
+ (a = ((!p->call_invalid_rules && _prefix_6_valid) ? (p->mark =
_prefix_6_end, _prefix_6_result) : (_prefix_6_result = sum_rule(p),
_prefix_6_end = p->mark, _prefix_6_valid = 1, _prefix_6_result))) // sum
&&
(_literal = _PyPegen_expect_token(p, 15)) // token='-'
&&
@@ -13799,6 +13813,8 @@ term_raw(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_7_result = NULL;
+ int _prefix_7_end = 0, _prefix_7_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -13818,7 +13834,7 @@ term_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = term_rule(p)) // term
+ (a = ((!p->call_invalid_rules && _prefix_7_valid) ? (p->mark =
_prefix_7_end, _prefix_7_result) : (_prefix_7_result = term_rule(p),
_prefix_7_end = p->mark, _prefix_7_valid = 1, _prefix_7_result))) // term
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
@@ -13857,7 +13873,7 @@ term_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = term_rule(p)) // term
+ (a = ((!p->call_invalid_rules && _prefix_7_valid) ? (p->mark =
_prefix_7_end, _prefix_7_result) : (_prefix_7_result = term_rule(p),
_prefix_7_end = p->mark, _prefix_7_valid = 1, _prefix_7_result))) // term
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -13896,7 +13912,7 @@ term_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = term_rule(p)) // term
+ (a = ((!p->call_invalid_rules && _prefix_7_valid) ? (p->mark =
_prefix_7_end, _prefix_7_result) : (_prefix_7_result = term_rule(p),
_prefix_7_end = p->mark, _prefix_7_valid = 1, _prefix_7_result))) // term
&&
(_literal = _PyPegen_expect_token(p, 47)) // token='//'
&&
@@ -13935,7 +13951,7 @@ term_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = term_rule(p)) // term
+ (a = ((!p->call_invalid_rules && _prefix_7_valid) ? (p->mark =
_prefix_7_end, _prefix_7_result) : (_prefix_7_result = term_rule(p),
_prefix_7_end = p->mark, _prefix_7_valid = 1, _prefix_7_result))) // term
&&
(_literal = _PyPegen_expect_token(p, 24)) // token='%'
&&
@@ -13974,7 +13990,7 @@ term_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = term_rule(p)) // term
+ (a = ((!p->call_invalid_rules && _prefix_7_valid) ? (p->mark =
_prefix_7_end, _prefix_7_result) : (_prefix_7_result = term_rule(p),
_prefix_7_end = p->mark, _prefix_7_valid = 1, _prefix_7_result))) // term
&&
(_literal = _PyPegen_expect_token(p, 49)) // token='@'
&&
@@ -14220,6 +14236,8 @@ power_rule(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_8_result = NULL;
+ int _prefix_8_end = 0, _prefix_8_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -14239,7 +14257,7 @@ power_rule(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = await_primary_rule(p)) // await_primary
+ (a = ((!p->call_invalid_rules && _prefix_8_valid) ? (p->mark =
_prefix_8_end, _prefix_8_result) : (_prefix_8_result = await_primary_rule(p),
_prefix_8_end = p->mark, _prefix_8_valid = 1, _prefix_8_result))) //
await_primary
&&
(_literal = _PyPegen_expect_token(p, 35)) // token='**'
&&
@@ -14276,7 +14294,7 @@ power_rule(Parser *p)
D(fprintf(stderr, "%*c> power[%d-%d]: %s\n", p->level, ' ', _mark,
p->mark, "await_primary"));
expr_ty await_primary_var;
if (
- (await_primary_var = await_primary_rule(p)) // await_primary
+ (await_primary_var = ((!p->call_invalid_rules && _prefix_8_valid)
? (p->mark = _prefix_8_end, _prefix_8_result) : (_prefix_8_result =
await_primary_rule(p), _prefix_8_end = p->mark, _prefix_8_valid = 1,
_prefix_8_result))) // await_primary
)
{
D(fprintf(stderr, "%*c+ power[%d-%d]: %s succeeded!\n", p->level,
' ', _mark, p->mark, "await_primary"));
@@ -14437,6 +14455,8 @@ primary_raw(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_9_result = NULL;
+ int _prefix_9_end = 0, _prefix_9_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -14456,7 +14476,7 @@ primary_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = primary_rule(p)) // primary
+ (a = ((!p->call_invalid_rules && _prefix_9_valid) ? (p->mark =
_prefix_9_end, _prefix_9_result) : (_prefix_9_result = primary_rule(p),
_prefix_9_end = p->mark, _prefix_9_valid = 1, _prefix_9_result))) // primary
&&
(_literal = _PyPegen_expect_token(p, 23)) // token='.'
&&
@@ -14494,7 +14514,7 @@ primary_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = primary_rule(p)) // primary
+ (a = ((!p->call_invalid_rules && _prefix_9_valid) ? (p->mark =
_prefix_9_end, _prefix_9_result) : (_prefix_9_result = primary_rule(p),
_prefix_9_end = p->mark, _prefix_9_valid = 1, _prefix_9_result))) // primary
&&
(b = genexp_rule(p)) // genexp
)
@@ -14532,7 +14552,7 @@ primary_raw(Parser *p)
expr_ty a;
void *b;
if (
- (a = primary_rule(p)) // primary
+ (a = ((!p->call_invalid_rules && _prefix_9_valid) ? (p->mark =
_prefix_9_end, _prefix_9_result) : (_prefix_9_result = primary_rule(p),
_prefix_9_end = p->mark, _prefix_9_valid = 1, _prefix_9_result))) // primary
&&
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
@@ -14574,7 +14594,7 @@ primary_raw(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = primary_rule(p)) // primary
+ (a = ((!p->call_invalid_rules && _prefix_9_valid) ? (p->mark =
_prefix_9_end, _prefix_9_result) : (_prefix_9_result = primary_rule(p),
_prefix_9_end = p->mark, _prefix_9_valid = 1, _prefix_9_result))) // primary
&&
(_literal = _PyPegen_expect_token(p, 9)) // token='['
&&
@@ -18823,6 +18843,8 @@ star_targets_rule(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_10_result = NULL;
+ int _prefix_10_end = 0, _prefix_10_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->level--;
@@ -18840,7 +18862,7 @@ star_targets_rule(Parser *p)
D(fprintf(stderr, "%*c> star_targets[%d-%d]: %s\n", p->level, ' ',
_mark, p->mark, "star_target !','"));
expr_ty a;
if (
- (a = star_target_rule(p)) // star_target
+ (a = ((!p->call_invalid_rules && _prefix_10_valid) ? (p->mark =
_prefix_10_end, _prefix_10_result) : (_prefix_10_result = star_target_rule(p),
_prefix_10_end = p->mark, _prefix_10_valid = 1, _prefix_10_result))) //
star_target
&&
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 12) //
token=','
)
@@ -18869,7 +18891,7 @@ star_targets_rule(Parser *p)
expr_ty a;
asdl_seq * b;
if (
- (a = star_target_rule(p)) // star_target
+ (a = ((!p->call_invalid_rules && _prefix_10_valid) ? (p->mark =
_prefix_10_end, _prefix_10_result) : (_prefix_10_result = star_target_rule(p),
_prefix_10_end = p->mark, _prefix_10_valid = 1, _prefix_10_result))) //
star_target
&&
(b = _loop0_98_rule(p)) // ((',' star_target))*
&&
@@ -20013,6 +20035,8 @@ expression_without_invalid_rule(Parser *p)
}
expr_ty _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_11_result = NULL;
+ int _prefix_11_end = 0, _prefix_11_valid = 0;
if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
p->error_indicator = 1;
p->call_invalid_rules = _prev_call_invalid;
@@ -20036,7 +20060,7 @@ expression_without_invalid_rule(Parser *p)
expr_ty b;
expr_ty c;
if (
- (a = disjunction_rule(p)) // disjunction
+ (a = ((!p->call_invalid_rules && _prefix_11_valid) ? (p->mark =
_prefix_11_end, _prefix_11_result) : (_prefix_11_result = disjunction_rule(p),
_prefix_11_end = p->mark, _prefix_11_valid = 1, _prefix_11_result))) //
disjunction
&&
(_keyword = _PyPegen_expect_token(p, 700)) // token='if'
&&
@@ -20080,7 +20104,7 @@ expression_without_invalid_rule(Parser *p)
D(fprintf(stderr, "%*c> expression_without_invalid[%d-%d]: %s\n",
p->level, ' ', _mark, p->mark, "disjunction"));
expr_ty disjunction_var;
if (
- (disjunction_var = disjunction_rule(p)) // disjunction
+ (disjunction_var = ((!p->call_invalid_rules && _prefix_11_valid) ?
(p->mark = _prefix_11_end, _prefix_11_result) : (_prefix_11_result =
disjunction_rule(p), _prefix_11_end = p->mark, _prefix_11_valid = 1,
_prefix_11_result))) // disjunction
)
{
D(fprintf(stderr, "%*c+ expression_without_invalid[%d-%d]: %s
succeeded!\n", p->level, ' ', _mark, p->mark, "disjunction"));
@@ -20271,6 +20295,8 @@ invalid_expression_rule(Parser *p)
}
void * _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_12_result = NULL;
+ int _prefix_12_end = 0, _prefix_12_valid = 0;
{ // STRING ((!STRING expression_without_invalid))+ STRING
if (p->error_indicator) {
p->level--;
@@ -20340,7 +20366,7 @@ invalid_expression_rule(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = disjunction_rule(p)) // disjunction
+ (a = ((!p->call_invalid_rules && _prefix_12_valid) ? (p->mark =
_prefix_12_end, _prefix_12_result) : (_prefix_12_result = disjunction_rule(p),
_prefix_12_end = p->mark, _prefix_12_valid = 1, _prefix_12_result))) //
disjunction
&&
(_keyword = _PyPegen_expect_token(p, 700)) // token='if'
&&
@@ -20373,7 +20399,7 @@ invalid_expression_rule(Parser *p)
expr_ty a;
expr_ty b;
if (
- (a = disjunction_rule(p)) // disjunction
+ (a = ((!p->call_invalid_rules && _prefix_12_valid) ? (p->mark =
_prefix_12_end, _prefix_12_result) : (_prefix_12_result = disjunction_rule(p),
_prefix_12_end = p->mark, _prefix_12_valid = 1, _prefix_12_result))) //
disjunction
&&
(_keyword = _PyPegen_expect_token(p, 700)) // token='if'
&&
@@ -20520,6 +20546,8 @@ invalid_if_expression_rule(Parser *p)
}
void * _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_13_result = NULL;
+ int _prefix_13_end = 0, _prefix_13_valid = 0;
{ // disjunction 'if' disjunction 'else' '*'
if (p->error_indicator) {
p->level--;
@@ -20532,7 +20560,7 @@ invalid_if_expression_rule(Parser *p)
expr_ty b;
expr_ty disjunction_var;
if (
- (disjunction_var = disjunction_rule(p)) // disjunction
+ (disjunction_var = ((!p->call_invalid_rules && _prefix_13_valid) ?
(p->mark = _prefix_13_end, _prefix_13_result) : (_prefix_13_result =
disjunction_rule(p), _prefix_13_end = p->mark, _prefix_13_valid = 1,
_prefix_13_result))) // disjunction
&&
(_keyword = _PyPegen_expect_token(p, 700)) // token='if'
&&
@@ -20568,7 +20596,7 @@ invalid_if_expression_rule(Parser *p)
expr_ty b;
expr_ty disjunction_var;
if (
- (disjunction_var = disjunction_rule(p)) // disjunction
+ (disjunction_var = ((!p->call_invalid_rules && _prefix_13_valid) ?
(p->mark = _prefix_13_end, _prefix_13_result) : (_prefix_13_result =
disjunction_rule(p), _prefix_13_end = p->mark, _prefix_13_valid = 1,
_prefix_13_result))) // disjunction
&&
(_keyword = _PyPegen_expect_token(p, 700)) // token='if'
&&
@@ -25460,6 +25488,8 @@ invalid_kvpair_unpacking_rule(Parser *p)
}
void * _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_14_result = NULL;
+ int _prefix_14_end = 0, _prefix_14_valid = 0;
{ // '**' if_expression
if (p->error_indicator) {
p->level--;
@@ -25564,7 +25594,7 @@ invalid_kvpair_unpacking_rule(Parser *p)
expr_ty b;
expr_ty expression_var;
if (
- (expression_var = expression_rule(p)) // expression
+ (expression_var = ((!p->call_invalid_rules && _prefix_14_valid) ?
(p->mark = _prefix_14_end, _prefix_14_result) : (_prefix_14_result =
expression_rule(p), _prefix_14_end = p->mark, _prefix_14_valid = 1,
_prefix_14_result))) // expression
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -25597,7 +25627,7 @@ invalid_kvpair_unpacking_rule(Parser *p)
expr_ty b;
expr_ty expression_var;
if (
- (expression_var = expression_rule(p)) // expression
+ (expression_var = ((!p->call_invalid_rules && _prefix_14_valid) ?
(p->mark = _prefix_14_end, _prefix_14_result) : (_prefix_14_result =
expression_rule(p), _prefix_14_end = p->mark, _prefix_14_valid = 1,
_prefix_14_result))) // expression
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -25642,6 +25672,8 @@ invalid_kvpair_rule(Parser *p)
}
void * _res = NULL;
int _mark = p->mark;
+ expr_ty _prefix_15_result = NULL;
+ int _prefix_15_end = 0, _prefix_15_valid = 0;
{ // expression !(':')
if (p->error_indicator) {
p->level--;
@@ -25650,7 +25682,7 @@ invalid_kvpair_rule(Parser *p)
D(fprintf(stderr, "%*c> invalid_kvpair[%d-%d]: %s\n", p->level, ' ',
_mark, p->mark, "expression !(':')"));
expr_ty a;
if (
- (a = expression_rule(p)) // expression
+ (a = ((!p->call_invalid_rules && _prefix_15_valid) ? (p->mark =
_prefix_15_end, _prefix_15_result) : (_prefix_15_result = expression_rule(p),
_prefix_15_end = p->mark, _prefix_15_valid = 1, _prefix_15_result))) //
expression
&&
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 11) //
token=(':')
)
@@ -25679,7 +25711,7 @@ invalid_kvpair_rule(Parser *p)
expr_ty bitwise_or_var;
expr_ty expression_var;
if (
- (expression_var = expression_rule(p)) // expression
+ (expression_var = ((!p->call_invalid_rules && _prefix_15_valid) ?
(p->mark = _prefix_15_end, _prefix_15_result) : (_prefix_15_result =
expression_rule(p), _prefix_15_end = p->mark, _prefix_15_valid = 1,
_prefix_15_result))) // expression
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -25712,7 +25744,7 @@ invalid_kvpair_rule(Parser *p)
expr_ty bitwise_or_var;
expr_ty expression_var;
if (
- (expression_var = expression_rule(p)) // expression
+ (expression_var = ((!p->call_invalid_rules && _prefix_15_valid) ?
(p->mark = _prefix_15_end, _prefix_15_result) : (_prefix_15_result =
expression_rule(p), _prefix_15_end = p->mark, _prefix_15_valid = 1,
_prefix_15_result))) // expression
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -25743,7 +25775,7 @@ invalid_kvpair_rule(Parser *p)
Token * a;
expr_ty expression_var;
if (
- (expression_var = expression_rule(p)) // expression
+ (expression_var = ((!p->call_invalid_rules && _prefix_15_valid) ?
(p->mark = _prefix_15_end, _prefix_15_result) : (_prefix_15_result =
expression_rule(p), _prefix_15_end = p->mark, _prefix_15_valid = 1,
_prefix_15_result))) // expression
&&
(a = _PyPegen_expect_token(p, 11)) // token=':'
&&
diff --git a/Tools/peg_generator/pegen/c_generator.py
b/Tools/peg_generator/pegen/c_generator.py
index b233ea746330ee6..044366c3aac1405 100644
--- a/Tools/peg_generator/pegen/c_generator.py
+++ b/Tools/peg_generator/pegen/c_generator.py
@@ -375,6 +375,35 @@ def generate_call(self, node: Any) -> FunctionCall:
return super().visit(node)
+def consuming_rules(rules: dict[str, Rule]) -> set[str]:
+ """Conservatively prove which rules consume a token whenever they
succeed."""
+ consuming: set[str] = set()
+
+ def consumes(node: Any) -> bool:
+ if isinstance(node, NamedItem):
+ return consumes(node.item)
+ if isinstance(node, NameLeaf):
+ return node.value not in rules or node.value in consuming
+ if isinstance(node, StringLeaf):
+ return True
+ if isinstance(node, Group):
+ return consumes(node.rhs)
+ if isinstance(node, Rhs):
+ return bool(node.alts) and all(any(consumes(i) for i in alt.items)
for alt in node.alts)
+ if isinstance(node, (Forced, Repeat1, Gather)):
+ return consumes(node.node)
+ # Predicates, cuts, optional items, and zero-or-more items can succeed
+ # without consuming. Actions are assumed not to rewrite parser marks.
+ return False
+
+ while True:
+ added = {name for name, rule in rules.items()
+ if name not in consuming and consumes(rule.rhs)}
+ if not added:
+ return consuming
+ consuming.update(added)
+
+
class CParserGenerator(ParserGenerator, GrammarVisitor):
def __init__(
self,
@@ -394,6 +423,8 @@ def __init__(
self.debug = debug
self.skip_actions = skip_actions
self.cleanup_statements: list[str] = []
+ self.consuming = consuming_rules(self.rules)
+ self.prefix_calls: dict[int, tuple[str, str, str]] = {}
def add_level(self) -> None:
self.print("if (p->level++ == MAXSTACK || _PyPegen_stack_exhausted(p))
{")
@@ -615,6 +646,7 @@ def _handle_default_rule_body(self, node: Rule, rhs: Rhs,
result_type: str) -> N
self.add_return("_res")
self.print("}")
self.print("int _mark = p->mark;")
+ self.prepare_prefix_calls(rhs)
if any(alt.action and "EXTRA" in alt.action for alt in rhs.alts):
self._set_up_token_start_metadata_extraction()
self.visit(
@@ -674,7 +706,37 @@ def _handle_loop_rule_body(self, node: Rule, rhs: Rhs) ->
None:
self.print(f"_PyPegen_insert_memo(p, _start_mark,
{node.name}_type, _seq);")
self.add_return("_seq")
+ def prepare_prefix_calls(self, rhs: Rhs) -> None:
+ # Reuse a memoized, consuming prefix only within a consecutive group.
+ # Suffix parsing starts after the prefix and cannot revisit its start
+ # through ordinary grammar backtracking. Diagnostic calls are
unchanged.
+ def candidate(alt: Alt) -> Rule | None:
+ if not alt.items or not isinstance(alt.items[0].item, NameLeaf):
+ return None
+ rule = self.rules.get(alt.items[0].item.value)
+ if rule is None or rule.name not in self.consuming:
+ return None
+ if self._should_memoize(rule) or (rule.left_recursive and
rule.leader):
+ return rule
+ return None
+
+ i = 0
+ while i < len(rhs.alts):
+ rule = candidate(rhs.alts[i])
+ j = i + 1
+ while rule is not None and j < len(rhs.alts) and
candidate(rhs.alts[j]) is rule:
+ j += 1
+ if rule is not None and j - i > 1:
+ name = self.unique_varname("_prefix")
+ result, end, valid = name + "_result", name + "_end", name +
"_valid"
+ self.print(f"{rule.type or 'void *'} {result} = NULL;")
+ self.print(f"int {end} = 0, {valid} = 0;")
+ for alt in rhs.alts[i:j]:
+ self.prefix_calls[id(alt.items[0])] = result, end, valid
+ i = j
+
def visit_Rule(self, node: Rule) -> None:
+ self.prefix_calls = {}
is_loop = node.is_loop()
is_gather = node.is_gather()
rhs = node.flatten()
@@ -716,6 +778,15 @@ def visit_Rule(self, node: Rule) -> None:
def visit_NamedItem(self, node: NamedItem) -> None:
call = self.callmakervisitor.generate_call(node)
+ if id(node) in self.prefix_calls:
+ result, end, valid = self.prefix_calls[id(node)]
+ original = f"{call.function}({', '.join(map(str,
call.arguments))})"
+ call.function = (
+ f"((!p->call_invalid_rules && {valid}) ? "
+ f"(p->mark = {end}, {result}) : "
+ f"({result} = {original}, {end} = p->mark, {valid} = 1,
{result}))"
+ )
+ call.arguments = []
if call.assigned_variable:
call.assigned_variable = self.dedupe(call.assigned_variable)
self.print(call)
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]