mruby-regexp: support (?#...) comment groups - #7055
Conversation
`(?#...)` is a comment: Ruby drops it from the pattern and compiles what is
left. The `(?` dispatch in `compile_atom()` has no `#` branch, so a pattern
holding one does not compile at all.
```ruby
/a(?#note)b/.match?("ab")
# CRuby: true
# mruby: RegexpError (undefined (?...) sequence: /a(?#note)b/)
Regexp.new("a (?#note) b", Regexp::EXTENDED).match?("ab")
# CRuby: true
# mruby: RegexpError (target of repeat operator is not specified: /a (?#note) b/)
```
Under `/x` the failure has a second cause. Extended mode is preprocessed over
the whole pattern before the parser runs, and its `#` branch skips to the end
of the line, so `a (?#note) b` loses `#note) b` and leaves a dangling `(?`
behind.
Remove the group in that preprocessing pass rather than in `compile_atom()`.
A comment group is not an atom: CRuby compiles `a(?#x)*` as `a*`, so the group
has to be gone before the quantifier is parsed, and an atom emitting no
instruction leaves the `*` with no target. `strip_extended()` becomes
`preprocess_pattern()`, taking the `/x` behaviour as a flag, and now runs for
any pattern that holds `(?#`. `has_comment_group()` gates that widening on a
`memchr()` scan, so an ordinary pattern still skips the pass and its
`mrb_malloc()`.
The removal sits after the backslash pass-through and after the character
class branch, so `a\(?#note)b` and `a[(?#c)]b` keep the meaning they have
now. The group ends at the first `)` not preceded by a backslash and does not
nest, as in CRuby. An unterminated group is copied through instead of being
dropped, so the new `#` branch in `compile_atom()` raises on it rather than
letting the rest of the pattern be swallowed silently.
|
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)
📝 WalkthroughWalkthroughThe regexp compiler now supports ChangesRegexp comment groups
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant RegexpCompiler
participant preprocess_pattern
participant RegexpParser
RegexpCompiler->>preprocess_pattern: preprocess comment groups and extended-mode syntax
preprocess_pattern-->>RegexpCompiler: return parser input
RegexpCompiler->>RegexpParser: parse preprocessed pattern
RegexpParser-->>RegexpCompiler: return regexp or parsing error
Possibly related PRs
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 |
(?#...)is a comment group: Ruby drops it from the pattern and compiles what isleft. The
(?dispatch incompile_atom()recognises(?:,(?=,(?!,(?<=,(?<!,(?<name>and the inline options, but has no#branch, so apattern holding a comment group falls through to
undefined (?...) sequenceanddoes not compile at all.
The group is not an atom, so a quantifier written after it repeats what came
before it:
Under
/xthe same pattern fails for a second reason. Extended mode ispreprocessed over the whole pattern before the parser runs, and its
#branchskips to the end of the line, so
a (?#note) bloses#note) band leaves adangling
(?at the end of the stripped buffer. A fix confined tocompile_atom()would leave this half broken, and comment groups writtenalongside
/xare where they are most common.Fix
Remove the group in the preprocessing pass rather than in the parser.
That is what lets
a(?#x)*compile asa*: the group has to be gone before thequantifier is parsed. An atom is too late.
compile_quantified()returns earlywhen
compile_atom()emitted no instruction, so a comment group handled therewould leave the
*with no target, and an atom that did emit one would changewhat the pattern matches.
strip_extended()becomespreprocess_pattern()and takes the/xbehaviouras a flag. Comment group removal runs unconditionally, whitespace and
#line-comment stripping only when
RE_FLAG_EXTENDEDis set.mrb_re_compile()now enters the pass when either the flag is set or
has_comment_group()finds(?#in the pattern, so an ordinary pattern without one still skips the passand its
mrb_malloc(). That gate is amemchr()scan for(.The new branch sits after the backslash pass-through and after the character
class branch, so the two ways of writing those bytes without meaning a comment
group keep the behaviour they have now:
The group ends at the first
)not preceded by a backslash, and it does notnest, so
x(?#a(?#b))ycloses at the first)and reports the second asunmatched, as CRuby does.
An unterminated group is copied through the pass instead of being dropped,
which is what the new
#branch incompile_atom()is for: reaching it meansthe group was never closed, and it raises rather than letting the rest of the
pattern be swallowed silently.
Error messages still quote the pattern as written, since
compile_error()already quotes
c->origrather than the preprocessed buffer.Scope
Nothing outside the pattern compiler changes. The literal form
/a(?#note)b/already passed through the parser without complaint, which is why the
reproduction raises
RegexpErrorrather than a parse-time error, somruby-compilerneeds no change.regexp.cis not involved either, since thefailure happens during compilation before any
Regexpmethod runs.(?~...)and(?(...)remain unimplemented and still raise from the samecatch-all.
Tests
mrbgems/mruby-regexp/test/regexp.rb:Regexp - comment groups (?#...), a new block next toRegexp - inline options (?i) / (?i:...), the nearest coverage of the samedispatch chain: the literal and constructor forms, leading, trailing and
empty comments, a newline inside one, a comment inside a capture group, the
quantifier target case,
\)inside a comment and the escaped backslash thatends one early, the non-nesting case, the unterminated case with its message,
the character class member, and the escaped
(.Regexp extended mode (x flag): a comment group under/x, one followed byan ordinary
#line comment on the same line, and the unterminated case.Verified on
x86_64-linux:aside.
rake test: 1976 total, 1958 OK, 0 KO, 0 crash, and bintest 105 OK.MRB_INT32build with clang and-Wall -Wextra: 1878 total, 1860 OK,0 KO, 0 crash, and no new warning from any
mruby-regexpfile.build_config/clang-asan.rb: 2153 total, 2143 OK, 0 KO, 0 crash, with noleak or invalid access reported.
Summary by CodeRabbit
New Features
(?#...)comment groups in regular expressions.Bug Fixes