Skip to content

Commit 8202360

Browse files
committed
gh-153568: Factor shared prefixes in expression rules
1 parent a60343e commit 8202360

4 files changed

Lines changed: 1617 additions & 1769 deletions

File tree

Grammar/python.gram

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -708,10 +708,10 @@ type_param_starred_default[expr_ty]: '=' e=star_expression {
708708
# -----------
709709

710710
expressions[expr_ty]:
711-
| a=expression b=(',' c=expression { c })+ [','] {
712-
_PyAST_Tuple(CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)), Load, EXTRA) }
713-
| a=expression ',' { _PyAST_Tuple(CHECK(asdl_expr_seq*, _PyPegen_singleton_seq(p, a)), Load, EXTRA) }
714-
| expression
711+
| a=expression b=[(',' c=expression { c })+] trailing=[','] {
712+
(b ? b : trailing) ? _PyAST_Tuple(
713+
b ? CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)) :
714+
CHECK(asdl_expr_seq*, _PyPegen_singleton_seq(p, a)), Load, EXTRA) : a }
715715

716716
expression[expr_ty] (memo):
717717
| invalid_if_expression
@@ -729,10 +729,10 @@ yield_expr[expr_ty]:
729729
| 'yield' a=[star_expressions] { _PyAST_Yield(a, EXTRA) }
730730

731731
star_expressions[expr_ty]:
732-
| a=star_expression b=(',' c=star_expression { c })+ [','] {
733-
_PyAST_Tuple(CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)), Load, EXTRA) }
734-
| a=star_expression ',' { _PyAST_Tuple(CHECK(asdl_expr_seq*, _PyPegen_singleton_seq(p, a)), Load, EXTRA) }
735-
| star_expression
732+
| a=star_expression b=[(',' c=star_expression { c })+] trailing=[','] {
733+
(b ? b : trailing) ? _PyAST_Tuple(
734+
b ? CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)) :
735+
CHECK(asdl_expr_seq*, _PyPegen_singleton_seq(p, a)), Load, EXTRA) : a }
736736

737737
star_expression[expr_ty] (memo):
738738
| '*' a=bitwise_or { _PyAST_Starred(a, Load, EXTRA) }
@@ -761,18 +761,14 @@ named_expression[expr_ty]:
761761
| expression !':='
762762

763763
disjunction[expr_ty] (memo):
764-
| a=conjunction b=('or' c=conjunction { c })+ { _PyAST_BoolOp(
765-
Or,
766-
CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)),
767-
EXTRA) }
768-
| conjunction
764+
| a=conjunction b=[('or' c=conjunction { c })+] {
765+
b ? _PyAST_BoolOp(Or,
766+
CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)), EXTRA) : a }
769767

770768
conjunction[expr_ty] (memo):
771-
| a=inversion b=('and' c=inversion { c })+ { _PyAST_BoolOp(
772-
And,
773-
CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)),
774-
EXTRA) }
775-
| inversion
769+
| a=inversion b=[('and' c=inversion { c })+] {
770+
b ? _PyAST_BoolOp(And,
771+
CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)), EXTRA) : a }
776772

777773
inversion[expr_ty] (memo):
778774
| 'not' a=inversion { _PyAST_UnaryOp(Not, a, EXTRA) }
@@ -782,13 +778,10 @@ inversion[expr_ty] (memo):
782778
# --------------------
783779

784780
comparison[expr_ty]:
785-
| a=bitwise_or b=compare_op_bitwise_or_pair+ {
786-
_PyAST_Compare(
787-
a,
781+
| a=bitwise_or b=[compare_op_bitwise_or_pair+] {
782+
b ? _PyAST_Compare(a,
788783
CHECK(asdl_int_seq*, _PyPegen_get_cmpops(p, b)),
789-
CHECK(asdl_expr_seq*, _PyPegen_get_exprs(p, b)),
790-
EXTRA) }
791-
| bitwise_or
784+
CHECK(asdl_expr_seq*, _PyPegen_get_exprs(p, b)), EXTRA) : a }
792785

793786
compare_op_bitwise_or_pair[CmpopExprPair*]:
794787
| eq_bitwise_or
@@ -832,24 +825,24 @@ bitwise_and[expr_ty]:
832825
| shift_expr
833826

834827
shift_expr[expr_ty]:
835-
| a=shift_expr '<<' b=sum { _PyAST_BinOp(a, LShift, b, EXTRA) }
836-
| a=shift_expr '>>' b=sum { _PyAST_BinOp(a, RShift, b, EXTRA) }
828+
| a=shift_expr op[Token*]=('<<' | '>>') b=sum {
829+
_PyAST_BinOp(a, op->type == LEFTSHIFT ? LShift : RShift, b, EXTRA) }
837830
| sum
838831

839832
# Arithmetic operators
840833
# --------------------
841834

842835
sum[expr_ty]:
843-
| a=sum '+' b=term { _PyAST_BinOp(a, Add, b, EXTRA) }
844-
| a=sum '-' b=term { _PyAST_BinOp(a, Sub, b, EXTRA) }
836+
| a=sum op[Token*]=('+' | '-') b=term {
837+
_PyAST_BinOp(a, op->type == PLUS ? Add : Sub, b, EXTRA) }
845838
| invalid_arithmetic
846839
| term
847840

848841
term[expr_ty]:
849-
| a=term '*' b=factor { _PyAST_BinOp(a, Mult, b, EXTRA) }
850-
| a=term '/' b=factor { _PyAST_BinOp(a, Div, b, EXTRA) }
851-
| a=term '//' b=factor { _PyAST_BinOp(a, FloorDiv, b, EXTRA) }
852-
| a=term '%' b=factor { _PyAST_BinOp(a, Mod, b, EXTRA) }
842+
| a=term op[Token*]=('*' | '/' | '//' | '%') b=factor {
843+
_PyAST_BinOp(a, op->type == STAR ? Mult :
844+
op->type == SLASH ? Div :
845+
op->type == DOUBLESLASH ? FloorDiv : Mod, b, EXTRA) }
853846
| a=term '@' b=factor { CHECK_VERSION(expr_ty, 5, "The '@' operator is", _PyAST_BinOp(a, MatMult, b, EXTRA)) }
854847
| factor
855848

@@ -861,8 +854,8 @@ factor[expr_ty] (memo):
861854
| invalid_factor
862855

863856
power[expr_ty]:
864-
| a=await_primary '**' b=factor { _PyAST_BinOp(a, Pow, b, EXTRA) }
865-
| await_primary
857+
| a=await_primary b=['**' c=factor { c }] {
858+
b ? _PyAST_BinOp(a, Pow, b, EXTRA) : a }
866859

867860
# Primary elements
868861
# ----------------

Lib/test/test_ast/test_ast.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,50 @@ def test_issue18374_binop_col_offset(self):
696696
self.assertEqual(grandchild_binop.end_col_offset, 3)
697697
self.assertEqual(grandchild_binop.end_lineno, 1)
698698

699+
def test_mixed_binop_associativity_and_locations(self):
700+
cases = [
701+
('<<', '>>', ast.LShift, ast.RShift),
702+
('+', '-', ast.Add, ast.Sub),
703+
('-', '+', ast.Sub, ast.Add),
704+
('*', '/', ast.Mult, ast.Div),
705+
('/', '//', ast.Div, ast.FloorDiv),
706+
('//', '%', ast.FloorDiv, ast.Mod),
707+
('%', '@', ast.Mod, ast.MatMult),
708+
('@', '*', ast.MatMult, ast.Mult),
709+
]
710+
for first, second, first_op, second_op in cases:
711+
source = f'(a {first} b\n {second} c)'
712+
with self.subTest(source=source):
713+
node = ast.parse(source, mode='eval').body
714+
self.assertIsInstance(node, ast.BinOp)
715+
self.assertIsInstance(node.op, second_op)
716+
self.assertIsInstance(node.left, ast.BinOp)
717+
self.assertIsInstance(node.left.op, first_op)
718+
self.assertEqual(ast.get_source_segment(source, node), source[1:-1])
719+
self.assertEqual(ast.get_source_segment(source, node.left),
720+
f'a {first} b')
721+
722+
def test_expression_tail_locations(self):
723+
cases = [
724+
('a', ast.Name),
725+
('a,', ast.Tuple),
726+
('a, b', ast.Tuple),
727+
('a, b,', ast.Tuple),
728+
('a or b or c', ast.BoolOp),
729+
('a and b and c', ast.BoolOp),
730+
('a < b <= c', ast.Compare),
731+
('a ** b ** c', ast.BinOp),
732+
]
733+
for expression, node_type in cases:
734+
for mode in ('eval', 'exec'):
735+
with self.subTest(expression=expression, mode=mode):
736+
source = expression + ' # comment\n'
737+
tree = ast.parse(source, mode=mode)
738+
node = tree.body if mode == 'eval' else tree.body[0].value
739+
self.assertIsInstance(node, node_type)
740+
self.assertEqual(ast.get_source_segment(source, node),
741+
expression)
742+
699743
def test_issue39579_dotted_name_end_col_offset(self):
700744
tree = ast.parse('@a.b.c\ndef f(): pass')
701745
attr_b = tree.body[0].decorator_list[0].value
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up parsing expressions by factoring shared prefixes and optional tails
2+
in the grammar.

0 commit comments

Comments
 (0)