Skip to content

Commit 2dcd01c

Browse files
committed
gh-153568: Skip the expression precedence chain for single-token atoms
A bare name or number followed by a token that cannot extend an expression is now built directly, avoiding a dozen rule invocations per atom.
1 parent 106eb53 commit 2dcd01c

4 files changed

Lines changed: 95 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up parsing of simple expressions by recognizing single-token atoms
2+
without descending the full precedence rule chain.

Parser/parser.c

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

Parser/pegen.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,27 @@ expr_ty _PyPegen_soft_keyword_token(Parser *p);
159159
expr_ty _PyPegen_fstring_middle_token(Parser* p);
160160
Token *_PyPegen_get_last_nonnwhitespace_token(Parser *);
161161
int _PyPegen_fill_token(Parser *p);
162+
163+
// Token types that cannot start or continue a binary/postfix expression:
164+
// a single-token atom followed by one of these is a complete expression.
165+
static inline int
166+
_PyPegen_atom_follow_token(int type)
167+
{
168+
switch (type) {
169+
case COMMA:
170+
case RPAR:
171+
case RSQB:
172+
case RBRACE:
173+
case COLON:
174+
case NEWLINE:
175+
case SEMI:
176+
case EQUAL:
177+
case ENDMARKER:
178+
return 1;
179+
default:
180+
return 0;
181+
}
182+
}
162183
expr_ty _PyPegen_name_token(Parser *p);
163184
expr_ty _PyPegen_number_token(Parser *p);
164185
void *_PyPegen_string_token(Parser *p);

Tools/peg_generator/pegen/c_generator.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,12 +598,46 @@ def _set_up_rule_memoization(self, node: Rule, result_type: str) -> None:
598598
def _should_memoize(self, node: Rule) -> bool:
599599
return "memo" in node.flags and not node.left_recursive
600600

601+
# Rules that are pure entry points into the expression precedence tower:
602+
# when the current token is a bare NAME/NUMBER atom and the next token
603+
# cannot start or continue a binary/postfix expression, the whole tower
604+
# would provably consume exactly one token, so a two-token fast path is
605+
# emitted that builds the atom directly.
606+
ATOM_FAST_PATH_RULES = frozenset({"disjunction", "inversion"})
607+
608+
def _emit_atom_fast_path(self) -> None:
609+
# Only look one token ahead when the current token is NAME/NUMBER:
610+
# the tower fills it too in that case, so error reporting (which
611+
# keys off p->fill) is unperturbed.
612+
self.print("if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) {")
613+
with self.indent():
614+
self.print("p->error_indicator = 1;")
615+
self.add_return("NULL")
616+
self.print("}")
617+
self.print("Token *_fast_tok = p->tokens[p->mark];")
618+
self.print("if (_fast_tok->type == NAME || _fast_tok->type == NUMBER) {")
619+
with self.indent():
620+
self.print("if (p->mark + 1 == p->fill && _PyPegen_fill_token(p) < 0) {")
621+
with self.indent():
622+
self.print("p->error_indicator = 1;")
623+
self.add_return("NULL")
624+
self.print("}")
625+
self.print("if (_PyPegen_atom_follow_token(p->tokens[p->mark + 1]->type)) {")
626+
with self.indent():
627+
self.print("expr_ty _fast_res = (_fast_tok->type == NAME)")
628+
self.print(" ? _PyPegen_name_token(p) : _PyPegen_number_token(p);")
629+
self.add_return("_fast_res")
630+
self.print("}")
631+
self.print("}")
632+
601633
def _handle_default_rule_body(self, node: Rule, rhs: Rhs, result_type: str) -> None:
602634
memoize = self._should_memoize(node)
603635

604636
with self.indent():
605637
self.add_level()
606638
self._check_for_errors()
639+
if node.name in self.ATOM_FAST_PATH_RULES:
640+
self._emit_atom_fast_path()
607641
self.print(f"{result_type} _res = NULL;")
608642
if memoize:
609643
self.print(f"if (_PyPegen_is_memoized(p, {node.name}_type, &_res)) {{")

0 commit comments

Comments
 (0)