From 618430ea228bf43b6a28eda95402127f8b3ccaa2 Mon Sep 17 00:00:00 2001 From: Henry Lee Date: Wed, 13 May 2026 01:11:24 +0000 Subject: [PATCH 1/2] fix(token): change default associative from "left" to "right" Without an explicit precedence declaration, S/R conflicts now resolve to shift (matching yacc/bison behavior) instead of reduce. Previously the "left" default caused reduce to always win at precedence 0, which made the dangling-else attach to the outer if instead of the inner one. Co-Authored-By: Claude Sonnet 4.6 --- plare/token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plare/token.py b/plare/token.py index 3d2206e..ae93e2a 100644 --- a/plare/token.py +++ b/plare/token.py @@ -50,7 +50,7 @@ class *and* share the same ``(lineno, offset)`` position. This is an have different hashes. """ - associative: assoc = "left" + associative: assoc = "right" precedence: int = 0 def __init__(self, value: str, *, lineno: int, offset: int) -> None: From 3418ef035379ff0cde71e4095a706aabf22fe1f8 Mon Sep 17 00:00:00 2001 From: Henry Lee Date: Wed, 13 May 2026 01:11:32 +0000 Subject: [PATCH 2/2] test: update S/R tests for new default associative behavior - Add explicit associative="left" to PLUS_SR so the left-associativity test continues to pass under the new "right" default - Update Section 2 header and docstring to reflect that left-assoc now requires an explicit declaration rather than being the default - Remove @pytest.mark.xfail from test_dangling_else_inner_if_gets_else; else now correctly binds to the inner if (shift wins at precedence 0) Co-Authored-By: Claude Sonnet 4.6 --- tests/test_grammar_logic.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/test_grammar_logic.py b/tests/test_grammar_logic.py index 8773b8a..d683d87 100644 --- a/tests/test_grammar_logic.py +++ b/tests/test_grammar_logic.py @@ -241,7 +241,7 @@ def test_calc_mixed_left_assoc_same_precedence() -> None: # --------------------------------------------------------------------------- -# Section 2: Default S/R conflict is left-associative +# Section 2: Explicit left-assoc declaration causes reduce to win in S/R conflict # --------------------------------------------------------------------------- @@ -252,7 +252,7 @@ def __init__(self, value: str, *, lineno: int, offset: int) -> None: class PLUS_SR(Token): - pass # no precedence set → default 0, associative="left" + associative = "left" class NumSR: @@ -267,15 +267,14 @@ def __init__(self, l: object, r: object) -> None: def test_default_sr_conflict_is_left_associative() -> None: - """With no explicit precedence, S/R conflict resolves to reduce (left-associative). + """With explicit associative="left", S/R conflict resolves to reduce (left-associative). When both item.precedence and symbol.precedence are 0, the parser condition: item.precedence == symbol.precedence and symbol.associative == "left" - is True (Token default associative="left"), so reduce wins. + is True, so reduce wins. This means 1 + 2 + 3 produces a LEFT-leaning tree AddSR(AddSR(1, 2), 3), - not a right-leaning one. The existing test_shift_reduce_resolution_prefers_shift - only tests the two-token case and makes no tree-shape assertion. + not a right-leaning one. """ p = Parser( { @@ -349,13 +348,6 @@ def __init__(self, cond: BASE_D, then: StmtD, else_: StmtD) -> None: self.else_ = else_ -@pytest.mark.xfail( - reason=( - "Default S/R resolution reduces (not shifts) when both precedences are 0 " - "and associative='left': else binds to the outer if instead of the inner if " - "as most languages (C, Java) expect." - ) -) def test_dangling_else_inner_if_gets_else() -> None: """The dangling-else problem: 'if c1 then if c2 then s1 else s2'.