Local token matched even though starting token is not present

hi marijn!

yet another question, this time related to local token groups.
what I have is the following:

@top Filter { expression* }

expression { FilterGroup | FilterStatement | space }

FilterGroup { OpeningBracket (FilterStatement | FilterGroup | space)* ClosingBracket }
FilterStatement { FilterKey space+ ComparisonOperator space+ FilterSimpleValue }

FilterKey { Identifier | String }
FilterSimpleValue[@isGroup=FilterValue] { Identifier | String }

@skip {} {
  String { DoubleQuotes (StringContent | EscapedStringCharacter)* StringEnd }
}

@local tokens {
  StringEnd[@name='DoubleQuotes'] { '"' }
  EscapedStringCharacter { "\\" _ }
  @else StringContent
}

@tokens {
  space { @whitespace }
  newLine { '\n'}
  escapedCharacter { "\\" _ }

  DoubleQuotes[closedBy="DoubleQuotes"] { "\"" }
  OpeningBracket[closedBy="ClosingBracket"] { "(" }
  ClosingBracket { ")" }

  ComparisonOperator { '=' | '!=' | '<' | '<=' | '>' | '>=' }
  Identifier { ((![ *,\\()] (newLine | std.eof)?) | escapedCharacter )+ }

  @precedence { space, OpeningBracket, ClosingBracket, DoubleQuotes, ComparisonOperator, Identifier }
}

with input a = (b) , I get the following tree as expected
Screenshot 2024-10-14 at 17.15.09

as soon as I add another character after the space (a = (b) c), the tree switches to
Screenshot 2024-10-14 at 17.15.19
even though there are no double quotes anywhere in the input and those should be required to start matching the local tokens if I understood correctly.

how is it possible that the String is still matching here?

I would have expected that I still get a statement with an error node (a =), a group (including a statement with an error node, (b)), and then another statement with an error node (c).

It looks like the parser inserted the opening quote token as part of its error recovery because it didn’t find any matching tokens at that position.

what’s interesting, though, is that if I remove the closing bracket of the group, it does detect the opening bracket.
Screenshot 2024-10-15 at 09.28.15

but as soon as I add the closing, it “merges” everything into a string.
maybe I can work around that issue by adding something like an IncompleteStatement to make it more predictable, unless you have another idea?