Skip to content

Conversation

yhabteab
Copy link
Member

@yhabteab yhabteab commented Sep 26, 2025

This PR fixes a DSL config parsing ambiguity discovered by @julianbrost by using this expression () => { {{ {{{foo}}} }} }(). First, I thought that only the last two rules were conflicting each other due to the same assigned dynamic precedence. However, after adding this expression as a test case, I found that the entire lterm_items_inner rule was conflicting due to the inconsistent precedence assignments in its alternate rules. I've now changed the precedence assignments so that it always prefers the rule that reduces on/shifts rterm_no_side_effect first, so that when you have pure expressions like the trigger of this bug, it reports an error about unused value instead of a parsing error. The precedence only needs to be consistent with the rules on the same level, i.e. a rule that can directly be reduced to lterm_items_inner without a recursion doesn't need to have a smaller or higher precedence than a rule that has left recursion, because they will never conflict.

Expand Me
Ambiguity detected.
Option 1,
  lterm_items_inner -> <Rule 6, tokens 1 .. 10>
    lterm -> <Rule 70, tokens 1 .. 10>
      rterm_side_effect -> <Rule 88, tokens 1 .. 10>
        rterm -> <Rule 146, tokens 1 .. 8>
          rterm_no_side_effect -> <Rule 143, tokens 1 .. 8>
            rterm_no_side_effect_no_dict -> <Rule 114, tokens 1 .. 8>
              '(' <tokens 1 .. 1>
              identifier_items <empty>
              ')' <tokens 2 .. 2>
              use_specifier <empty>
              "=> (T_FOLLOWS)" <tokens 3 .. 3>
              @15 -> <Rule 113, empty>
              rterm_scope -> <Rule 84, tokens 4 .. 8>
                '{' <tokens 4 .. 4>
                @13 -> <Rule 83, empty>
                statements -> <Rule 2, tokens 5 .. 7>
                  optional_newlines -> <Rule 169, empty>
                  lterm_items -> <Rule 4, tokens 5 .. 7>
                    lterm_items_inner -> <Rule 7, tokens 5 .. 7>
                      rterm_no_side_effect -> <Rule 143, tokens 5 .. 7>
                        rterm_no_side_effect_no_dict -> <Rule 142, tokens 5 .. 7>
                          "{{ (T_NULLARY_LAMBDA_BEGIN)" <tokens 5 .. 5>
                          @18 -> <Rule 141, empty>
                          statements -> <Rule 2, tokens 6 .. 6>
                            optional_newlines -> <Rule 169, empty>
                            lterm_items -> <Rule 4, tokens 6 .. 6>
                              lterm_items_inner -> <Rule 7, tokens 6 .. 6>
                                rterm_no_side_effect -> <Rule 143, tokens 6 .. 6>
                                  rterm_no_side_effect_no_dict -> <Rule 92, tokens 6 .. 6>
                                    T_STRING <tokens 6 .. 6>
                          "}} (T_NULLARY_LAMBDA_END)" <tokens 7 .. 7>
                '}' <tokens 8 .. 8>
        '(' <tokens 9 .. 9>
        rterm_items -> <Rule 71, empty>
        ')' <tokens 10 .. 10>

Option 2,
  lterm_items_inner -> <Rule 7, tokens 1 .. 10>
    rterm_no_side_effect -> <Rule 143, tokens 1 .. 10>
      rterm_no_side_effect_no_dict -> <Rule 115, tokens 1 .. 10>
        '(' <tokens 1 .. 1>
        identifier_items <empty>
        ')' <tokens 2 .. 2>
        use_specifier <empty>
        "=> (T_FOLLOWS)" <tokens 3 .. 3>
        rterm -> <Rule 145, tokens 4 .. 10>
          rterm_side_effect -> <Rule 88, tokens 4 .. 10>
            rterm -> <Rule 146, tokens 4 .. 8>
              rterm_no_side_effect -> <Rule 144, tokens 4 .. 8>
                rterm_dict -> <Rule 80, tokens 4 .. 8>
                  '{' <tokens 4 .. 4>
                  @11 -> <Rule 79, empty>
                  statements -> <Rule 2, tokens 5 .. 7>
                    optional_newlines -> <Rule 169, empty>
                    lterm_items -> <Rule 4, tokens 5 .. 7>
                      lterm_items_inner -> <Rule 7, tokens 5 .. 7>
                        rterm_no_side_effect -> <Rule 143, tokens 5 .. 7>
                          rterm_no_side_effect_no_dict -> <Rule 142, tokens 5 .. 7>
                            "{{ (T_NULLARY_LAMBDA_BEGIN)" <tokens 5 .. 5>
                            @18 -> <Rule 141, empty>
                            statements -> <Rule 2, tokens 6 .. 6>
                              optional_newlines -> <Rule 169, empty>
                              lterm_items -> <Rule 4, tokens 6 .. 6>
                                lterm_items_inner -> <Rule 7, tokens 6 .. 6>
                                  rterm_no_side_effect -> <Rule 143, tokens 6 .. 6>
                                    rterm_no_side_effect_no_dict -> <Rule 92, tokens 6 .. 6>
                                      T_STRING <tokens 6 .. 6>
                            "}} (T_NULLARY_LAMBDA_END)" <tokens 7 .. 7>
                  '}' <tokens 8 .. 8>
            '(' <tokens 9 .. 9>
            rterm_items -> <Rule 71, empty>
            ')' <tokens 10 .. 10>

[2025-09-26 12:05:53 +0200] critical/config: Error: syntax is ambiguous
Location: in icinga2.conf: 1:0-1:26
icinga2.conf(1): () => { {{ {{{foo}}} }} }()
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
icinga2.conf(2):
[2025-09-26 12:05:53 +0200] critical/cli: Config validation failed. Re-run with 'icinga2 daemon -C' after fixing the config.

@yhabteab yhabteab added bug Something isn't working area/configuration DSL, parser, compiler, error handling labels Sep 26, 2025
@cla-bot cla-bot bot added the cla/signed label Sep 26, 2025
@yhabteab yhabteab force-pushed the fix-ambiguous-grammar-rules branch from dfb9173 to 03cf3b1 Compare September 26, 2025 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/configuration DSL, parser, compiler, error handling bug Something isn't working cla/signed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant