Skip to content

Commit bcfbaad

Browse files
committed
Memoize some rules to avoid exponential time in deep nesting
Additionally, fix the script to calculate the nesting level using the generated extension.
1 parent 242114a commit bcfbaad

File tree

3 files changed

+25
-34
lines changed

3 files changed

+25
-34
lines changed

Grammar/python.gram

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ star_targets[expr_ty]:
461461
| a=star_target b=(',' c=star_target { c })* [','] {
462462
_Py_Tuple(CHECK(seq_insert_in_front(p, a, b)), Store, EXTRA) }
463463
star_targets_seq[asdl_seq*]: a=','.star_target+ [','] { a }
464-
star_target[expr_ty]:
464+
star_target[expr_ty] (memo):
465465
| '*' a=bitwise_or { _Py_Starred(CHECK(set_expr_context(p, a, Store)), Store, EXTRA) }
466466
| a=t_primary '.' b=NAME !t_lookahead { _Py_Attribute(a, b->v.Name.id, Store, EXTRA) }
467467
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Store, EXTRA) }
@@ -482,7 +482,7 @@ ann_assign_subscript_attribute_target[expr_ty]:
482482
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Store, EXTRA) }
483483

484484
del_targets[asdl_seq*]: a=','.del_target+ [','] { a }
485-
del_target[expr_ty]:
485+
del_target[expr_ty] (memo):
486486
| a=t_primary '.' b=NAME !t_lookahead { _Py_Attribute(a, b->v.Name.id, Del, EXTRA) }
487487
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Del, EXTRA) }
488488
| del_t_atom
@@ -493,7 +493,7 @@ del_t_atom[expr_ty]:
493493
| '[' a=[del_targets] ']' { _Py_List(a, Del, EXTRA) }
494494

495495
targets[asdl_seq*]: a=','.target+ [','] { a }
496-
target[expr_ty]:
496+
target[expr_ty] (memo):
497497
| a=t_primary '.' b=NAME !t_lookahead { _Py_Attribute(a, b->v.Name.id, Store, EXTRA) }
498498
| a=t_primary '[' b=slices ']' !t_lookahead { _Py_Subscript(a, b, Store, EXTRA) }
499499
| t_atom

Parser/pegen/parse.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7766,6 +7766,8 @@ static expr_ty
77667766
star_target_rule(Parser *p)
77677767
{
77687768
expr_ty res = NULL;
7769+
if (is_memoized(p, star_target_type, &res))
7770+
return res;
77697771
int mark = p->mark;
77707772
if (p->mark == p->fill && fill_token(p) < 0) {
77717773
return NULL;
@@ -7875,6 +7877,7 @@ star_target_rule(Parser *p)
78757877
}
78767878
res = NULL;
78777879
done:
7880+
insert_memo(p, mark, star_target_type, res);
78787881
return res;
78797882
}
78807883

@@ -8168,6 +8171,8 @@ static expr_ty
81688171
del_target_rule(Parser *p)
81698172
{
81708173
expr_ty res = NULL;
8174+
if (is_memoized(p, del_target_type, &res))
8175+
return res;
81718176
int mark = p->mark;
81728177
if (p->mark == p->fill && fill_token(p) < 0) {
81738178
return NULL;
@@ -8252,6 +8257,7 @@ del_target_rule(Parser *p)
82528257
}
82538258
res = NULL;
82548259
done:
8260+
insert_memo(p, mark, del_target_type, res);
82558261
return res;
82568262
}
82578263

@@ -8399,6 +8405,8 @@ static expr_ty
83998405
target_rule(Parser *p)
84008406
{
84018407
expr_ty res = NULL;
8408+
if (is_memoized(p, target_type, &res))
8409+
return res;
84028410
int mark = p->mark;
84038411
if (p->mark == p->fill && fill_token(p) < 0) {
84048412
return NULL;
@@ -8483,6 +8491,7 @@ target_rule(Parser *p)
84838491
}
84848492
res = NULL;
84858493
done:
8494+
insert_memo(p, mark, target_type, res);
84868495
return res;
84878496
}
84888497

Tools/peg_generator/scripts/find_max_nesting.py

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
from pathlib import Path
2020
from typing import Any
2121

22-
sys.path.insert(0, ".")
23-
from pegen.build import build_parser
24-
from pegen.testutil import generate_parser, generate_parser_c_extension, make_parser, parse_string
22+
from peg_parser import parse_string
2523

2624
GRAMMAR_FILE = "data/python.gram"
2725
INITIAL_NESTING_DEPTH = 10
@@ -32,47 +30,31 @@
3230
ENDC = "\033[0m"
3331

3432

35-
def check_nested_expr(nesting_depth: int, parser: Any, language: str) -> bool:
33+
def check_nested_expr(nesting_depth: int) -> bool:
3634
expr = f"{'(' * nesting_depth}0{')' * nesting_depth}"
3735

3836
try:
39-
if language == "Python":
40-
parse_string(expr, parser)
41-
else:
42-
parser.parse_string(expr)
43-
44-
print(f"({language}) Nesting depth of {nesting_depth} is successful")
45-
37+
parse_string(expr)
38+
print(f"Nesting depth of {nesting_depth} is successful")
4639
return True
4740
except Exception as err:
48-
print(f"{FAIL}({language}) Failed with nesting depth of {nesting_depth}{ENDC}")
41+
print(f"{FAIL}(Failed with nesting depth of {nesting_depth}{ENDC}")
4942
print(f"{FAIL}\t{err}{ENDC}")
5043
return False
5144

5245

5346
def main() -> None:
5447
print(f"Testing {GRAMMAR_FILE} starting at nesting depth of {INITIAL_NESTING_DEPTH}...")
5548

56-
with TemporaryDirectory() as tmp_dir:
57-
nesting_depth = INITIAL_NESTING_DEPTH
58-
rules, parser, tokenizer = build_parser(GRAMMAR_FILE)
59-
python_parser = generate_parser(rules)
60-
c_parser = generate_parser_c_extension(rules, Path(tmp_dir))
61-
62-
c_succeeded = True
63-
python_succeeded = True
64-
65-
while c_succeeded or python_succeeded:
66-
expr = f"{'(' * nesting_depth}0{')' * nesting_depth}"
67-
68-
if c_succeeded:
69-
c_succeeded = check_nested_expr(nesting_depth, c_parser, "C")
70-
if python_succeeded:
71-
python_succeeded = check_nested_expr(nesting_depth, python_parser, "Python")
72-
73-
nesting_depth += NESTED_INCR_AMT
49+
nesting_depth = INITIAL_NESTING_DEPTH
50+
succeeded = True
51+
while succeeded:
52+
expr = f"{'(' * nesting_depth}0{')' * nesting_depth}"
53+
if succeeded:
54+
succeeded = check_nested_expr(nesting_depth)
55+
nesting_depth += NESTED_INCR_AMT
7456

75-
sys.exit(1)
57+
sys.exit(1)
7658

7759

7860
if __name__ == "__main__":

0 commit comments

Comments
 (0)