mruby-regexp: keep the word class inside ASCII under /i - #7265
Conversation
Closing a character class under /i folded every ASCII bit alike, whichever form of the class had set it. The bits `\w`, `[:word:]` and `[:ascii:]` set were folded across the ASCII boundary with the rest, so `k` reached U+212A and `s` reached U+017F through them, and the negated forms rejected the two: ```ruby "ſ".match?(/[^\w]/i) # CRuby: true, mruby: false "K".match?(/[^\w]/i) # CRuby: true, mruby: false /[\w]/i =~ "ſ" # CRuby: nil, mruby: 0 /[[:ascii:]]/i =~ "ſ" # CRuby: nil, mruby: 0 ``` Each of the three is a set ASCII defines: `\w` is `[a-zA-Z0-9_]` and no more, so a fold that leaves ASCII leaves the set. CRuby keeps them out of the class it folds across the boundary from and folds everything else in the class, the other POSIX brackets included; there those brackets hold every Unicode member, so what the fold adds to them is a member already. `compile_charclass()` now holds what `\w`, `\W`, `[:word:]` and `[:ascii:]` add in a class of its own until the closure has run, and merges its bitmap and `utf8_any` into the class after. Each of them holds both cases of every letter it holds, so the ASCII part of the closure had nothing to add to them, and nothing beyond the two foldings above folds to what they hold, so holding them apart changes nothing else. `posix_class_bits()` reports which names the rule covers. A letter written out beside the shorthand still folds, since the class then holds it by name as well: ```ruby /[\ws]/i =~ "ſ" # CRuby: 0, mruby: 0 /[k\w]/i =~ "K" # CRuby: 0, mruby: 0 ``` Outside a class `\w` never folded, and the other POSIX brackets fold as before: `[[:lower:]]` under /i still reaches U+212A through `k`, and `[^[:alpha:]]` still rejects U+017F, both as CRuby answers. Every build carries the two foldings, so every build was affected the same way, and the test sits with the one about them in `regexp_syntax.rb`, where it runs on each.
|
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 (3)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe regexp compiler now preserves ASCII-defined shorthand and POSIX classes during ChangesASCII-aware regexp case folding
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This PR narrows case-insensitive character-class folding so ASCII-only classes remain within ASCII while preserving existing folding for explicit letters and other classes; the reported test suite passes, and no actionable merge-blocking risk remains. 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 |
Closing a character class under
/ifolded every ASCII bit alike, whichever form of the class had set it. The bits\w,[:word:]and[:ascii:]set were folded across the ASCII boundary with the rest, sokreached U+212A andsreached U+017F through them, and the negated forms rejected the two characters they have to accept:Each of the three is a set ASCII defines:
\wis[a-zA-Z0-9_]and no more, so a fold that leaves ASCII leaves the set. CRuby keeps them out of the class it folds across the boundary from and folds everything else in the class, the other POSIX brackets included; there those brackets hold every Unicode member, so what the fold adds to them is a member already.Fix
compile_charclass()now holds what\w,\W,[:word:]and[:ascii:]add in a class of its own until the closure under folding has run, and merges its bitmap andutf8_anyinto the class after. Each of them holds both cases of every letter it holds, so the ASCII part of the closure had nothing to add to them, and nothing beyond the two foldings above folds to what they hold, so holding them apart changes nothing else.posix_class_bits()reports which names the rule covers.A letter written out beside the shorthand still folds, since the class then holds it by name as well, and the other POSIX brackets fold as before:
Every build carries the two foldings (U+212A to
k, U+017F tos), so every build was affected the same way; theascii-casebuild takes the same fix through the#elsebranch of the closure.What stays as it was: this gem's POSIX brackets are ASCII sets, so
[[:word:]]and[[:alpha:]]do not holdſwhere CRuby's do; that is a separate divergence, and/ino longer changes the answer for[[:word:]]either way.Testing
New block
Regexp - /i keeps the word class inside ASCIIinmrbgems/mruby-regexp/test/regexp_syntax.rb, next to the existing block about the two ASCII-result foldings. It runs on every build that reads its strings as characters (the answers do not depend on the Unicode table, so it is not inunicode_case.rb), and covers:[\w],[^\w],[[:ascii:]],[^[:ascii:]],[\W],[^\W],\w,\Wagainst both U+212A and U+017F;[[:word:]]giving the same answer with and without/i; a letter beside the shorthand in either case and either order ([\ws],[\wS],[k\w],[\wa-z],[^\ws]); naming one letter folding only that letter ([\wk],[\ws]); a member above ASCII still reaching the letter's other case through the fold ([\wſ]matchingS); and[[:lower:]]/[[:alpha:]]/[^[:alpha:]]still folding. Every assertion in the block was run under CRuby 4.0.6 as well and agrees.README: one sentence in the case folding section.
Full suite green at every commit (single commit).
ci/gcc-clangfull-debugci/gcc-clangbintestci/gcc-clangcxx_abici/gcc-clangbyte-stringci/gcc-clangascii-caserake -m test)Environment
Machine, toolchain, and the compile line of every build
Actual compile line of
src/string.cin eachbuild_config/ci/gcc-clang.rbbuild (-MMD -c,-I, and-odropped).full-debugis-O0becauseenable_debugappends-g3 -O0after the toolchain's-g -O3;cxx_abicompiles C as C++ withgcc -x c++ -std=gnu++03, g++ only links.Summary by CodeRabbit
\w,\W,[:word:], and[:ascii:]./iflag.