Full Grammar Specification & The PEG Parser¶
Python's syntax is formally defined by a formal grammar specification. Since Python 3.9 (PEP 617), CPython has used a Parsing Expression Grammar (PEG) parser, replacing the original LL(1)-based parser that governed Python syntax for nearly thirty years.
The Transition to the PEG Parser (PEP 617)¶
The original LL(1) parser suffered from severe architectural limitations: - It could only look ahead one token (k=1) when deciding which grammar rule to parse. - Left recursion was prohibited, forcing language designers to use unnatural hacks or delay modern syntax features (such as parenthesized context managers).
The modern PEG parser introduced in Python 3.9 provides: - Arbitrary Lookahead: The parser can look ahead multiple tokens or evaluate semantic actions before committing to a branch. - Ordered Choices: In PEG, choices (A | B) are evaluated sequentially. The first matching rule succeeds, eliminating grammar ambiguities. - Direct Support for Left Recursion: Through memoization (Packrat parsing), PEG handles left-recursive grammar rules cleanly and efficiently.
Grammar Notation¶
The CPython grammar file (Grammar/python.gram) is written in a PEG notation with semantic actions:
| Symbol | Meaning | Example |
|---|---|---|
rule: e | Rule definition | file: statements? $ |
e1 e2 | Sequence: match e1 then e2 | NAME '=' expr |
e1 \| e2 | Ordered choice: try e1; if it fails, try e2 | 'if' \| 'while' |
e? | Optional: match zero or one time | ('as' NAME)? |
e* | Zero or more repetitions | statement* |
e+ | One or more repetitions | digit+ |
&e | Positive lookahead: match e without consuming tokens | &'async' |
!e | Negative lookahead: succeed only if e does not match | !'else' |
s.e+ | Repetition separated by separator s | ','.NAME+ |
Core Grammar Definitions (Abridged)¶
Below is an abridged representation of the core top-level grammar rules governing Python:
# Top-level entry points
file: [statements] $
interactive: [statement_newline]
eval: expressions NEWLINE* $
# Statements
statements: statement+
statement: compound_stmt | simple_stmts
simple_stmts:
| simple_stmt !';' NEWLINE
| ';'.simple_stmt+ [';'] NEWLINE
simple_stmt:
| assignment
| type_alias
| star_expressions
| return_stmt
| import_stmt
| raise_stmt
| 'pass'
| del_stmt
| yield_stmt
| assert_stmt
| 'break'
| 'continue'
| global_stmt
| nonlocal_stmt
compound_stmt:
| function_def
| if_stmt
| class_def
| with_stmt
| for_stmt
| try_stmt
| while_stmt
| match_stmt
# Control flow compound statements
if_stmt:
| 'if' named_expression ':' block elif_stmt
| 'if' named_expression ':' block [else_block]
while_stmt:
| 'while' named_expression ':' block [else_block]
for_stmt:
| 'for' star_targets 'in' ~ star_expressions ':' [TYPE_COMMENT] block [else_block]
| ASYNC 'for' star_targets 'in' ~ star_expressions ':' [TYPE_COMMENT] block [else_block]
with_stmt:
| 'with' '(' ','.with_item+ [','] ')' ':' block
| 'with' ','.with_item+ ':' [TYPE_COMMENT] block
| ASYNC 'with' '(' ','.with_item+ [','] ')' ':' block
| ASYNC 'with' ','.with_item+ ':' [TYPE_COMMENT] block
match_stmt:
| "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
# Expressions & Operators
expressions:
| expression (',' expression )+ [',']
| expression ','
| expression
expression:
| disjunction 'if' disjunction 'else' expression
| disjunction
| lambdef
disjunction:
| conjunction ('or' conjunction )+
| conjunction
conjunction:
| inversion ('and' inversion )+
| inversion
inversion:
| 'not' inversion
| comparison
comparison:
| bitwise_or compare_op_bitwise_or_pair+
| bitwise_or
sum:
| sum '+' term
| sum '-' term
| term
term:
| term '*' factor
| term '/' factor
| term '//' factor
| term '%' factor
| term '@' factor
| factor
factor:
| '+' factor
| '-' factor
| '~' factor
| power
power:
| await_primary '**' factor
| await_primary
primary:
| primary '.' NAME
| primary '(' [arguments] ')'
| primary '[' slices ']'
| atom
atom:
| NAME
| 'True'
| 'False'
| 'None'
| strings
| NUMBER
| (tuple | group | genexp)
| (list | listcomp)
| (dict | set | dictcomp | setcomp)
| '...'
Inspecting the Syntax Tree with the ast Module¶
Python exposes its internal parser and Abstract Syntax Tree (AST) directly to user code via the built-in ast module:
import ast
code_snippet = "result = (a + b) * 2"
# Parse code string into AST
tree = ast.parse(code_snippet)
# Inspect formatted AST structure
print(ast.dump(tree, indent=2))
Using ast.parse(), tools like linters, static analyzers, and code formatters (like Black and Ruff) can safely inspect and manipulate Python code according to its formal grammar rules.