mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-regexp: fix non-greedy quantifiers (*?, +?, ??)
non-greedy patterns now correctly match the shortest possible string. patterns with non-greedy quantifiers are dispatched to the backtracking engine which naturally handles non-greedy semantics. the Pike VM continues to be used for purely greedy patterns (O(n*m) guarantee). Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,7 @@ typedef struct mrb_regexp_pattern {
|
||||
uint16_t num_captures; /* number of capture groups (including group 0) */
|
||||
uint32_t flags;
|
||||
mrb_bool has_backref; /* true if pattern uses \1-\9 */
|
||||
mrb_bool has_nongreedy; /* true if pattern uses *?, +?, ?? */
|
||||
} mrb_regexp_pattern;
|
||||
|
||||
/* Regexp flags */
|
||||
|
||||
@@ -26,6 +26,7 @@ typedef struct {
|
||||
uint16_t num_captures;
|
||||
uint32_t flags;
|
||||
mrb_bool has_backref;
|
||||
mrb_bool has_nongreedy;
|
||||
} re_compiler;
|
||||
|
||||
static void compile_alt(re_compiler *c); /* forward */
|
||||
@@ -427,7 +428,10 @@ compile_quantified(re_compiler *c)
|
||||
if (ch == '*' || ch == '+' || ch == '?') {
|
||||
next_char(c);
|
||||
mrb_bool nongreedy = (peek(c) == '?');
|
||||
if (nongreedy) next_char(c);
|
||||
if (nongreedy) {
|
||||
next_char(c);
|
||||
c->has_nongreedy = TRUE;
|
||||
}
|
||||
|
||||
|
||||
if (ch == '*') {
|
||||
@@ -458,7 +462,10 @@ compile_quantified(re_compiler *c)
|
||||
return; /* not a quantifier */
|
||||
}
|
||||
mrb_bool nongreedy = (peek(c) == '?');
|
||||
if (nongreedy) next_char(c);
|
||||
if (nongreedy) {
|
||||
next_char(c);
|
||||
c->has_nongreedy = TRUE;
|
||||
}
|
||||
|
||||
/* For {n,m}: repeat atom min times, then optional (max-min) times */
|
||||
uint32_t atom_end = c->code_len;
|
||||
@@ -595,6 +602,7 @@ re_compile(mrb_state *mrb, const char *pattern, mrb_int len, uint32_t flags)
|
||||
pat->num_captures = c.num_captures;
|
||||
pat->flags = flags;
|
||||
pat->has_backref = c.has_backref;
|
||||
pat->has_nongreedy = c.has_nongreedy;
|
||||
|
||||
return pat;
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ re_exec(mrb_state *mrb, const mrb_regexp_pattern *pat,
|
||||
const char *str, mrb_int len, mrb_int start,
|
||||
int *captures, int captures_size)
|
||||
{
|
||||
if (pat->has_backref) {
|
||||
if (pat->has_backref || pat->has_nongreedy) {
|
||||
return backtrack_exec(mrb, pat, str, len, start, captures, captures_size);
|
||||
}
|
||||
return pike_vm(mrb, pat, str, len, start, captures, captures_size);
|
||||
|
||||
@@ -178,9 +178,9 @@ assert("Regexp - nested captures") do
|
||||
end
|
||||
|
||||
assert("Regexp - non-greedy quantifiers") do
|
||||
# TODO: non-greedy a+? returns "aaa" instead of "a" (needs priority fix)
|
||||
assert_equal "aaa", /a+?/.match("aaa")[0]
|
||||
# non-greedy a*? test skipped: needs further debugging
|
||||
|
||||
assert_equal "a", /a+?/.match("aaa")[0]
|
||||
assert_equal "", /a*?/.match("aaa")[0]
|
||||
end
|
||||
|
||||
assert("Regexp - word boundary") do
|
||||
|
||||
Reference in New Issue
Block a user