Skip to content

🥅 Add parser state and #detailed_message to ResponseParseError#599

Merged
nevans merged 4 commits intomasterfrom
response_parse_error-detailed_message
Feb 9, 2026
Merged

🥅 Add parser state and #detailed_message to ResponseParseError#599
nevans merged 4 commits intomasterfrom
response_parse_error-detailed_message

Conversation

@nevans
Copy link
Collaborator

@nevans nevans commented Feb 9, 2026

Adding the parser state ivars onto parse errors will aid in debugging, e.g allowing someone to inspect or print the parser's debug output in a different context (not merely as output to $stderr, and only when Net::IMAP.debug? is enabled).

Adding the context to ResponseParseError also allows us to move the parse error debug output into #detailed_message. This is a much more natural home for it. By default, it will only show the parser_state when Net::IMAP.debug? is true, and it won't show the parser_backtrace. ResponseParser explicitly sets both to true, when its own config.debug? is true.

This also modifies the original debug output in a few small ways: the first line uses super to get the standard Exception#detailed_message, including highlighting, and backtrace filtering is now done based on parser_class (rather than on file path). In my local tests, that seems to work okay, both for subclasses and for methods inherited from modules.

Additionally, I've added a failsafe, so an exception inside #detailed_message should still return something useful!

@nevans nevans force-pushed the response_parse_error-detailed_message branch 2 times, most recently from 59b9871 to 72f0023 Compare February 9, 2026 21:05
Adding this parser state ivars onto the parse error will aid in
debugging, e.g to print the parser's debug output in a different
context.
This allows `ResponseParseError#detailed_message` to be used to create
the parse error debug output _outside_ of the parser.  When
`Net::IMAP.debug` is true, `ResponseParseError#detailed_message`
automatically includes the `parser_state` debug info.  `ResponseParser`
explicitly sets `parser_state` when printing the detailed message,
because its own `config.debug?` is true.
Now _all_ of the parse_error debug message is available from
`ResponseParseError#detailed_message`.  This also makes a subtle change
to the original: filtering is now done based on `parser_class`, rather
than on file name.  This should allow the debug messages to work
correctly in custom parser subclasses.
@nevans nevans force-pushed the response_parse_error-detailed_message branch from 72f0023 to d13d88e Compare February 9, 2026 21:08
@nevans nevans merged commit 8fa80d2 into master Feb 9, 2026
39 checks passed
@nevans nevans deleted the response_parse_error-detailed_message branch February 9, 2026 21:16
@nevans nevans added the enhancement New feature or request label Feb 9, 2026
nevans added a commit that referenced this pull request Feb 10, 2026
`resp-text` is defined as:
```abnf
resp-text = ["[" resp-text-code "]" SP] text     ; RFC3501
resp-text = ["[" resp-text-code "]" SP] [text]   ; RFC9051
```
And our even more lenient interpretation (which incorporates RFC9051
errata), is this:
```abnf
resp-text = ["[" resp-text-code "]" [SP [text]] / [text]
```

And, although the `resp-text-code` grammar is elaborate, the final
alternative is a superset of every other alternative (in both RFCs):
```abnf
resp-text-code /= atom [SP 1*<any TEXT-CHAR except "]">]
```

Notice that `text` is a superset of `atom`, `SP`, `"["`, `"]"`, and
every other allowed character in `resp-text-code`.  So, even if
`resp-text-code` fails, `resp-text` may still succeed by parsing
everything as `text`.

However, (prior to this commit) the parser commits to `resp-text-code`
as soon as `"["` is encountered at the beginning of `resp-text`.  If
what follows is not `resp-text-code`, maybe it doesn't begin with an
`atom` or it doesn't have a closing `"]"`, then we fail to parse
`resp-text` correctly.

Fixing this requires either looking further ahead more than a single
token or backtracking when `resp-text-code` fails to parse.  In this
case, I think backtracking is the best approach.  We don't need to worry
about backtracking taking exponential time, because the fallback
(`text`) is exceptionally simple (does not call any other productions),
will parse all the way up to the next CRLF, and so nothing backtrackable
can be nested.

This uses `ResponseParser#current_state` that was added for #599 and
relies on #600 to ensure that debug error messages are not printed when
backtracking.

Fixes #597.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant