Fix terminal priorities ignored in dynamic Earley lexer for recursive rules - #1634
Open
deepakganesh78 wants to merge 1 commit into
Open
Conversation
… rules When using the dynamic Earley lexer (earley/auto or earley/dynamic), terminal priorities were not being respected in recursive rules. For example, a terminal FLOAT.1 matching '2.0' would lose to INT matching '2' followed by FLOAT matching '.0', because the ForestSumVisitor priority-sum approach cannot distinguish the two derivations (both sum to 1). Root cause: the xearley scanner explores ALL possible terminal matches at each position and relies entirely on forest-level disambiguation. However, when two terminals match at the same position and one has strictly higher priority with a longer/equal match length, the priority-sum approach fails because both paths accumulate the same total priority. Fix: apply traditional 'longest match + highest priority' filtering in the scan phase of xearley.py. When a higher-priority terminal matches at position i with length >= a lower-priority terminal's match, suppress the lower-priority match. This is only applied when: - terminal priorities are in use (forest_sum_visitor is set) - complete_lex is False (dynamic_complete mode intentionally explores all sub-match lengths for full ambiguity) - the matched terminals have differing priorities (so grammars using only negative priorities to de-prioritize a terminal are also handled correctly) Fixes lark-parser#1441 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
deepakganesh78
force-pushed
the
fix/issue1441-terminal-priority-recursive
branch
from
August 2, 2026 11:19
7673703 to
79a4456
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1441
Terminal priorities (e.g.
FLOAT.1) were not being respected in recursive rules when using the dynamic Earley lexer (the default forparser='earley').Reproduction
Before fix (on master):
"2.0" is split into INT("2") + FLOAT(".0") = 4 children.
After fix:
"2.0" is correctly matched as FLOAT("2.0") = 3 children. Matches LALR and earley/basic behavior.
Root Cause
The dynamic Earley scanner (
xearley.py) explores ALL possible terminal matches at each position, building an SPPF forest. Disambiguation is then handled byForestSumVisitorwhich sums terminal priorities across packed nodes. However, for this grammar both derivations accumulate an identical priority sum of 1:So the visitor cannot distinguish them and the wrong derivation wins by insertion order.
Fix
Apply traditional "longest match + highest priority" filtering in the scan phase of
xearley.py:i, collect all terminal matches.Filtering rule in words: a lower-priority terminal's match is suppressed if and only if a higher-priority terminal also matches at the same position with length >= the lower-priority match. This mirrors the standard lexer behavior.
Conditions for filtering (to avoid breaking existing behavior):
forest_sum_visitoris set)complete_lexis False (dynamic_completemode intentionally explores all sub-match lengths for full ambiguity)best_priority > min_priority). This correctly handles the negative-priority idiom:INT.-1to de-prioritize a terminal also triggers filtering.Safe fallback: If a terminal name is missing from
terminals_by_name(e.g.%declare), its priority defaults to 0 via a helper function, avoidingAttributeError.Validation
Per-permutation test results (all green):
Priority-specific tests: 17 passed, 6 skipped (including
test_priority_vs_embedded,test_ignore_carryover_with_priority)Regression test added:
test_terminal_priority_in_recursive_rule— skipped forbasic(already works) anddynamic_complete(different semantics).Compatibility
forest_sum_visitoror all priorities equal).dynamic_completelexer mode: unaffected (filtering disabled).TERM.-1): works correctly, triggers filtering when priorities differ.%declareterminals: handled gracefully (default priority 0).