Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions mrbgems/mruby-regexp/include/re_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ enum re_opcode {
The executor rewinds by bytes against a binary subject
and by characters otherwise, and the sub-pattern body
starts past this instruction, at pc + 2. */
RE_ATOMIC, /* atomic group (?>...): offset = nesting depth of the
group, 1 for an outermost one. The body follows and
ends at the RE_ATOMIC_END with the same depth; once
the body has matched, a failure after it fails the
whole group rather than backtracking into it. */
RE_ATOMIC_END, /* end of an atomic group's body: offset = the depth of
RE_ATOMIC, /* atomic group (?>...): offset = the group's number,
1 for the first one the pattern opens and no two the
same. The body follows and ends at the RE_ATOMIC_END
with the same number; once the body has matched, a
failure after it fails the whole group rather than
backtracking into it. */
RE_ATOMIC_END, /* end of an atomic group's body: offset = the number of
the RE_ATOMIC it closes */
};

Expand Down
35 changes: 21 additions & 14 deletions mrbgems/mruby-regexp/src/re_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ typedef struct {
uint16_t num_groups; /* groups opened so far, counting the plain ones a
named pattern demotes: what decides whether
`\NN` is a backreference or an octal escape */
uint32_t atomic_depth; /* how many (?>...) groups enclose the parse point */
uint32_t num_cuts; /* atomic groups numbered so far, the possessive
repeats among them: each takes the next number,
so no two that nest share one; see RE_ATOMIC */
uint32_t atom_start; /* where the atom a quantifier binds to begins;
compile_quantified sets it to the position
before the atom, and a `\u{...}` list moves it
Expand Down Expand Up @@ -1405,16 +1407,21 @@ compile_atom(re_compiler *c)
else if (c->p[1] == '>') {
/* atomic group (?>...): the body is a non-capturing group whose
first match is its only one. The two instructions bracketing it
carry the group's nesting depth, which is how the executor pairs
the end of a body with the group it closes when a failure after
the body has to fail the group; see bt_match(). The depth counts
instructions the pattern holds, so it fits the field. */
carry a number of the group's own, which is how the executor
pairs the end of a body with the group it closes when a failure
after the body has to fail the group; see bt_match(). The number
is the count of groups numbered before it rather than the
nesting depth: a possessive repeat wraps a group around code
already emitted, and with depths that wrapper and a group inside
it, compiled with the same groups open around them, shared a
depth, so a cut of the wrapper was read as the inner group's.
Every numbered group emits two instructions, so the number fits
the field. */
next_char(c); next_char(c); /* skip ?> */
c->atomic_depth++;
emit(c, RE_ATOMIC, 0, (uint16_t)c->atomic_depth);
uint16_t cut = (uint16_t)++c->num_cuts;
emit(c, RE_ATOMIC, 0, cut);
compile_alt(c);
emit(c, RE_ATOMIC_END, 0, (uint16_t)c->atomic_depth);
c->atomic_depth--;
emit(c, RE_ATOMIC_END, 0, cut);
if (peek(c) != ')') compile_error(c, "unmatched '('");
next_char(c);
c->needs_backtrack = TRUE; /* the Pike VM cannot cut a thread */
Expand Down Expand Up @@ -1848,12 +1855,12 @@ compile_quantified(re_compiler *c)
int ch = peek(c);

if (greedy_rep && ch == '+') {
/* Possessive: what stands emitted becomes an atomic group of its own. */
/* Possessive: what stands emitted becomes an atomic group of its own,
numbered after every group inside it, since those are emitted. */
next_char(c);
c->atomic_depth++;
insert_inst(c, begin, RE_ATOMIC, 0, (uint16_t)c->atomic_depth);
emit(c, RE_ATOMIC_END, 0, (uint16_t)c->atomic_depth);
c->atomic_depth--;
uint16_t cut = (uint16_t)++c->num_cuts;
insert_inst(c, begin, RE_ATOMIC, 0, cut);
emit(c, RE_ATOMIC_END, 0, cut);
c->needs_backtrack = TRUE; /* the Pike VM cannot cut a thread */
greedy_rep = FALSE;
start = begin;
Expand Down
4 changes: 2 additions & 2 deletions mrbgems/mruby-regexp/src/re_exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ lookbehind_start(const mrb_regexp_pattern *pat, const char *str,
alternative of its own. The third answer is the cut of an atomic group:
the text after an RE_ATOMIC_END has failed, and no alternative inside the
group's body may be tried for it, so the frames between that end and the
RE_ATOMIC that opened the group hand BT_CUT of the group's depth up
RE_ATOMIC that opened the group hand BT_CUT of the group's number up
unchanged, undoing their captures as they go, and the frame that ran that
RE_ATOMIC turns it into BT_FAIL. A cut never reaches a lookaround from
inside its sub-pattern: the RE_ATOMIC that absorbs it is in there too.
Expand All @@ -645,7 +645,7 @@ lookbehind_start(const mrb_regexp_pattern *pat, const char *str,
#define BT_FAIL 0
#define BT_MATCH 1
#define BT_LIMIT 2
#define BT_CUT(atomic_depth) (-(int)(atomic_depth))
#define BT_CUT(cut) (-(int)(cut))

/* What one backtrack_exec() call shares between its bt_match() frames: the
pattern, the subject, the capture slots being written, the step count and
Expand Down
13 changes: 13 additions & 0 deletions mrbgems/mruby-regexp/test/regexp_syntax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,19 @@
assert_nil /(?>x(?>a)(?>b)y)/.match("xabz")
assert_equal 0, /(?>x(?>a)(?>b)y)/ =~ "xaby"

# A possessive repeat is an atomic group wrapped around what it repeats,
# and it cuts as a group of its own: a failure after it does not open the
# repeat to being skipped, whatever groups the repeated code holds.
assert_nil /(?>a)?+a/.match("a")
assert_nil /(?>a)*+a/.match("aa")
assert_nil /(?:(?>a)b?)?+a/.match("a")
assert_nil /(?:(?>a)?+)?+a/.match("a")
assert_equal 0, /(?>a)?+b/ =~ "ab"
assert_equal 0, /(?:(?>a)b?)?+c/ =~ "abc"
# A failure inside the repeat, before its end, still fails only the inner
# group, and the repeat is skipped as its `?` allows.
assert_equal 1, /(?:(?>a)b)?+c/ =~ "ac"

# A repetition whose body can match empty stops on its empty iteration
# inside the group as anywhere else, and takes the group's exit; a
# repetition of the group stops the same way, its lazy body still empty.
Expand Down
Loading