@@ -209,6 +209,57 @@ and "hidden left-recursion" like:
209209 rule: 'optional'? rule '@' some_other_rule
210210```
211211
212+ Explicit operator loops
213+ ----------------------
214+
215+ A C grammar can request an operator loop for a particular left-recursive rule
216+ with the ` (operator_loop) ` flag:
217+
218+ ```
219+ sum[expr_ty] (operator_loop):
220+ | a=sum '+' b=term { _PyAST_BinOp(a, Add, b, EXTRA) }
221+ | a=sum '-' b=term { _PyAST_BinOp(a, Sub, b, EXTRA) }
222+ | term
223+ ```
224+
225+ Unmarked rules keep the general left-recursion algorithm. The generator checks
226+ each marked rule and raises a rule-specific error if its shape or operand
227+ dependency is unsupported. The flag takes no value and cannot be combined with
228+ other rule flags. In ` skip_actions ` mode the generator still validates marked
229+ rules, but emits the general algorithm instead of AST-folding loops.
230+
231+ The flag also asserts an action contract; the generator cannot prove properties
232+ of arbitrary C code. Actions reachable from a marked rule must preserve the
233+ grammar-derived token position and parser-control state when they succeed.
234+ They must not make hidden calls to generated parser rules, inspect or modify
235+ provisional left-recursion memo entries, or mutate the buffered input. The
236+ existing generated ` without_invalid ` scopes remain supported. AST constructors,
237+ feature checks, warnings and normal error reporting remain permitted. Existing
238+ restrictions on modifying shared AST nodes also apply. Changes to relevant
239+ actions and their C helpers must preserve this contract.
240+
241+ After excluding alternatives guarded by ` call_invalid_rules ` , each marked rule
242+ must consist of ` left=self exact-token right=operand ` alternatives followed by
243+ one plain operand alternative. Operator tokens must be distinct. The result and
244+ operand types must be ` expr_ty ` . Recursive actions must construct ` _PyAST_BinOp `
245+ using the bound operands and a constant operator kind, optionally wrapped in
246+ ` CHECK_VERSION ` . Cuts, predicates, other recursive actions and other alternative
247+ shapes are unsupported.
248+
249+ The operand must consume input and cannot call the marked rule again at the same
250+ position through the grammar. It must use ordinary ` (memo) ` , or also be marked
251+ ` (operator_loop) ` and pass validation. This preserves the
252+ original algorithm's final base retry as memo reuse, rather than executing an
253+ operand action again. The loop retains result memoization but does not publish
254+ intermediate seeds. Normal right-operand failure restores the position before
255+ the operator; fatal errors propagate.
256+
257+ Loops run when ` call_invalid_rules ` is false. The original leader and raw rule
258+ remain available while it is true. This includes diagnostic parsing: an
259+ ` expression_without_invalid ` scope temporarily disables invalid rules and may
260+ therefore use loops. The transformation changes stack usage and allocation
261+ order; the flag does not promise identical resource-limit boundaries.
262+
212263Variables in the grammar
213264------------------------
214265
0 commit comments