mruby-regexp: keep a character class open past [:name:] and a leading ] - #7041
Merged
Merged
Conversation
…ng `]`
`strip_extended()` ends a character class at the first `]` it sees, but
`compile_charclass()` does not. The `]` that closes a POSIX bracket is
consumed inside the class, and a `]` written first in a class is a literal
member. Under `/x` the pass therefore leaves the class early and applies
free-spacing to bytes the parser then reads as class members.
```ruby
Regexp.new("[[:alpha:] ]", Regexp::EXTENDED) =~ " "
# CRuby: 0
# mruby: nil
```
A `#` after the bracket costs more than a single character, because it
starts a line comment and the rest of the class goes with it.
```ruby
Regexp.new("[[:alpha:]#x]", Regexp::EXTENDED)
# CRuby: compiles, matches "#"
# mruby: RegexpError (unterminated character class)
```
The literal `]` diverges in both directions.
```ruby
Regexp.new("[] ]", Regexp::EXTENDED) =~ " "
# CRuby: 0
# mruby: nil
Regexp.new("[^] ]", Regexp::EXTENDED) =~ " "
# CRuby: nil
# mruby: 0
```
Teach the pass the two rules `compile_charclass()` already follows: copy a
POSIX bracket as a unit without touching `in_class`, and copy an optional
`^` and an optional `]` on entering a class. Both branches sit after the
backslash pass-through, so `\]` inside a class keeps its existing handling.
Only `/x` patterns reach the pass, so no other pattern changes.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughExtended-mode regexp preprocessing now keeps POSIX bracket expressions and initially literal ChangesRegexp character-class parsing
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Aug 9, 2026
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.
The
/xpreprocessing pass ends a character class at the first]it sees, soclass content written after a POSIX bracket or after a leading literal
]istreated as free-spacing text and stripped.
strip_extended()clears itsin_classflag on any].compile_charclass()does not: the
]that closes[:alpha:]is consumed inside the class, and a]written first in a class is a literal member. The two disagree, so the passhands the parser a pattern the user did not write, and the parser compiles it
without complaint.
A
#after the bracket starts a line comment, so the rest of the class is lostrather than a single character.
The literal
]diverges in both directions: one drops a character the class wasmeant to contain, the other keeps one it was meant to exclude.
Only
/xpatterns are affected.mrb_re_compile()runs the pass only whenRE_FLAG_EXTENDEDis set, so an ordinary pattern never reaches it.Fix
Both additions are written to agree with
compile_charclass()rather than tore-derive character class rules.
[followed by:,scan to the first
:or]; if the terminator is:], copy through itwithout touching
in_class. Otherwise fall through and copy the[as anordinary member, which is the same outcome the parser reaches when it resets
to its saved position.
^and then an optional]are copiedbefore returning to the loop, mirroring the
firstflag incompile_charclass().Both sit after the backslash pass-through, which stays first, so
\]inside aclass keeps its existing handling and never reaches either branch. The pass
keeps its single
in_classflag and its output buffer: neither addition emitsmore bytes than it consumes, so the buffer bound is unchanged.
Behaviour
Measured against CRuby 4.0.6.
Regexp::EXTENDED)[[:alpha:] ] =~ " "0nil0[[:digit:] ]+over" 1 "" 1 ""1"" 1 "[[:alpha:]#x] =~ "#"0RegexpError0[] ] =~ " "0nil0[^] ] =~ " "nil0nilCRuby warns
character class has ']' without escapefor the last two, and itsparser rejects the literal form
/[]]/outright, butRegexp.newaccepts thestring form and reads the
]as a literal, which is what mruby's parser doestoo.
Tests
The four cases above are added to
assert("Regexp extended mode (x flag)")inmrbgems/mruby-regexp/test/regexp.rb, next to the existing case for whitespaceinside a character class. The block's other assertions cover the regression
side: free-spacing outside a class must keep working.
rake testpasses.Summary by CodeRabbit
Bug Fixes
[:alpha:].#, and literal closing brackets inside character classes.Tests