Skip to content

Commit 456eb6f

Browse files
committed
gh-153568: Use FIRST sets to speed up parser choices
1 parent 89fe985 commit 456eb6f

4 files changed

Lines changed: 6738 additions & 77 deletions

File tree

Lib/test/test_peg_generator/test_c_parser.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,57 @@ def test_soft_keywords_lookahead(self) -> None:
533533
"""
534534
self.run_test(grammar_source, test_source)
535535

536+
def test_first_set_dispatch(self) -> None:
537+
grammar = parse_string(
538+
"""
539+
start: expr NEWLINE
540+
expr: NAME | NUMBER | '(' NAME ')'
541+
""",
542+
GrammarParser,
543+
)
544+
parser_source = generate_c_parser_source(grammar)
545+
self.assertIn("switch (_current_token_type)", parser_source)
546+
547+
overlapping_grammar = parse_string(
548+
"start: expr NEWLINE $\nexpr: NAME '+' NAME | NAME\n",
549+
GrammarParser,
550+
)
551+
parser_source = generate_c_parser_source(overlapping_grammar)
552+
self.assertIn("_first_set_mask", parser_source)
553+
self.run_test(
554+
"start: expr NEWLINE $\nexpr: NAME '+' NAME | NAME\n",
555+
'self.check_input_strings_for_grammar(["a\\n", "a + b\\n"])',
556+
)
557+
558+
nullable_grammar = parse_string(
559+
"start: expr NEWLINE\nexpr: ['+'] | NUMBER\n",
560+
GrammarParser,
561+
)
562+
parser_source = generate_c_parser_source(nullable_grammar)
563+
self.assertNotIn("switch (_current_token_type)", parser_source)
564+
565+
def test_first_set_dispatch_soft_keywords(self) -> None:
566+
grammar_source = 'start: ("foo" | "bar") NEWLINE $\n'
567+
grammar = parse_string(grammar_source, GrammarParser)
568+
parser_source = generate_c_parser_source(grammar)
569+
self.assertIn("_first_set_mask", parser_source)
570+
self.run_test(
571+
grammar_source,
572+
'self.check_input_strings_for_grammar(["foo\\n", "bar\\n"], ["baz\\n"])',
573+
)
574+
575+
def test_first_set_dispatch_invalid_rules(self) -> None:
576+
grammar_source = """
577+
start: value NEWLINE $
578+
value: NAME '+' NAME | invalid_value
579+
invalid_value: NAME { RAISE_SYNTAX_ERROR("expected an addition") }
580+
"""
581+
test_source = """
582+
with self.assertRaisesRegex(SyntaxError, "expected an addition"):
583+
parse.parse_string("name\\n", mode=0)
584+
"""
585+
self.run_test(grammar_source, test_source)
586+
536587
def test_forced(self) -> None:
537588
grammar_source = """
538589
start: NAME &&':' | NAME

0 commit comments

Comments
 (0)