Commit 7ed13c71c2 for perl
commit 7ed13c71c28da09047f41270a8ad9dded87fb639
Author: Yves Orton <demerphq@gmail.com>
Date: Tue Oct 13 00:16:51 2026 +0200
regex: name the repeat bounds and capture range of CURLY nodes
Add CURLY_MIN_REPEAT(), CURLY_MAX_REPEAT(), CURLY_FIRST_CAPTURE() and
CURLY_LAST_CAPTURE(), with matching setters. Convert the numeric macros we use
right now into something with semantic value.
Add and use CURLY_HAS_CAPTURES() when code needs to test whether a CURLY node
has a capture range at all. CURLY_FIRST_CAPTURE() is always true if
CURLY_LAST_CAPTURE() is true[1], so we can just fetch the full word and use a
semantically better named macros while we do.
In a follow up commit we should create similar macros and add op-type asserts
to all of them at once. For now these are fairly obvious transformations.
[1] the first bound is npar_before + 1, and the last bound is always >= the
first bound. The last bound is only set in the same branch, so it cannot
be nonzero unless the first is. Testing the packed field is therefore
equivalent to testing the first bound.
diff --git a/regcomp.c b/regcomp.c
index cecb747844..3ce3153e08 100644
--- a/regcomp.c
+++ b/regcomp.c
@@ -2030,7 +2030,8 @@ Perl_re_op_compile(pTHX_ SV ** const patternp, int pat_count,
/* An OR of *one* alternative - should not happen now. */
(OP(first) == BRANCH && OP(first_next) != BRANCH) ||
/* An {n,m} with n > 0 */
- (REGNODE_TYPE(OP(first)) == CURLY && ARG1i(first) > 0) ||
+ (REGNODE_TYPE(OP(first)) == CURLY
+ && CURLY_MIN_REPEAT(first) > 0) ||
(OP(first) == NOTHING && REGNODE_TYPE(OP(first_next)) != END)
)){
break;
@@ -5216,18 +5217,18 @@ S_regpiece(pTHX_ RExC_state_t *pRExC_state, I32 *flagp, U32 depth)
/* Finish up the CURLY/CURLYX case */
FLAGS(REGNODE_p(ret)) = 0;
- ARG1i_SET(REGNODE_p(ret), min);
- ARG2i_SET(REGNODE_p(ret), max);
+ CURLY_MIN_REPEAT_SET(REGNODE_p(ret), min);
+ CURLY_MAX_REPEAT_SET(REGNODE_p(ret), max);
/* if we had a npar_after then we need to increment npar_before,
* we want to track the range of parens we need to reset each iteration
*/
if (npar_after!=npar_before) {
- ARG3a_SET(REGNODE_p(ret), (U16)npar_before+1);
- ARG3b_SET(REGNODE_p(ret), (U16)npar_after);
+ CURLY_FIRST_CAPTURE_SET(REGNODE_p(ret), (U16)npar_before+1);
+ CURLY_LAST_CAPTURE_SET(REGNODE_p(ret), (U16)npar_after);
} else {
- ARG3a_SET(REGNODE_p(ret), 0);
- ARG3b_SET(REGNODE_p(ret), 0);
+ CURLY_FIRST_CAPTURE_SET(REGNODE_p(ret), 0);
+ CURLY_LAST_CAPTURE_SET(REGNODE_p(ret), 0);
}
done_main_op:
diff --git a/regcomp.h b/regcomp.h
index 6b804e6af4..d1171ac9ce 100644
--- a/regcomp.h
+++ b/regcomp.h
@@ -318,10 +318,10 @@ struct regnode_2 {
* 32 bit value, or two 16 bit values. The first fields must match regnode_2.
* The extra field can be accessed as (U32)ARG3u() (I32)ARG3i() or (U16)ARG3a()
* and (U16)ARG3b().
- * Currently used for the CURLY style regops used to represent quantifers,
- * storing the min and of the quantifier via ARG1i() and ARG2i(), along with
- * ARG3a() and ARG3b() which are used to store information about the number of
- * parens before and inside the quantified expression. */
+ * Currently used for the CURLY style regops used to represent quantifiers,
+ * storing the repeat bounds via CURLY_MIN_REPEAT() and CURLY_MAX_REPEAT(),
+ * along with CURLY_FIRST_CAPTURE() and CURLY_LAST_CAPTURE() which store the
+ * range of captures inside the quantified expression. */
struct regnode_3 {
union regnode_head head;
union regnode_arg arg1;
@@ -487,6 +487,21 @@ struct regnode_ssc {
#define ARGp_SET(p, val) ARGp_SET_inline((p),(val))
+/* Repeat bounds for CURLY, CURLYN, CURLYM and CURLYX. STAR and PLUS have
+ * implicit bounds and do not have these fields. */
+#define CURLY_MIN_REPEAT(p) ARG1i(p)
+#define CURLY_MAX_REPEAT(p) ARG2i(p)
+#define CURLY_MIN_REPEAT_SET(p, val) ARG1i_SET((p), (val))
+#define CURLY_MAX_REPEAT_SET(p, val) ARG2i_SET((p), (val))
+
+/* Inclusive capture range in a quantified body. Both fields are zero when
+ * the body contains no captures. */
+#define CURLY_FIRST_CAPTURE(p) ARG3a(p)
+#define CURLY_LAST_CAPTURE(p) ARG3b(p)
+#define CURLY_HAS_CAPTURES(p) (ARG3u(p) != 0)
+#define CURLY_FIRST_CAPTURE_SET(p, val) ARG3a_SET((p), (val))
+#define CURLY_LAST_CAPTURE_SET(p, val) ARG3b_SET((p), (val))
+
/* the following define was set to 0xde in 075abff3
* as part of some linting logic. I have set it to 0
* as otherwise in every place where we /might/ set flags
diff --git a/regcomp_debug.c b/regcomp_debug.c
index 24c6523826..71a63fd455 100644
--- a/regcomp_debug.c
+++ b/regcomp_debug.c
@@ -633,9 +633,10 @@ Perl_regprop(pTHX_ const regexp *prog, SV *sv, const regnode *o, const regmatch_
sv_catpvf(sv, " (buf:%" IVdf "/%" IVdf ")",
(IV)trie->before_paren,(IV)trie->after_paren);
} else if (k == CURLY) {
- U32 lo = ARG1i(o), hi = ARG2i(o);
- if (ARG3u(o)) /* check both ARG3a and ARG3b at the same time */
- sv_catpvf(sv, "<%d:%d>", ARG3a(o),ARG3b(o)); /* paren before, paren after */
+ U32 lo = CURLY_MIN_REPEAT(o), hi = CURLY_MAX_REPEAT(o);
+ if (CURLY_HAS_CAPTURES(o))
+ sv_catpvf(sv, "<%d:%d>", CURLY_FIRST_CAPTURE(o),
+ CURLY_LAST_CAPTURE(o));
if (op == CURLYM || op == CURLYN || op == CURLYX)
sv_catpvf(sv, "[%d]", FLAGS(o)); /* Parenth number */
sv_catpvf(sv, "{%u,", (unsigned) lo);
diff --git a/regcomp_study.c b/regcomp_study.c
index 12958d5ca0..40eb2f95ae 100644
--- a/regcomp_study.c
+++ b/regcomp_study.c
@@ -2508,8 +2508,8 @@ Perl_study_chunk(pTHX_
mincount = 1;
maxcount = 1;
} else {
- mincount = ARG1i(scan);
- maxcount = ARG2i(scan);
+ mincount = CURLY_MIN_REPEAT(scan);
+ maxcount = CURLY_MAX_REPEAT(scan);
}
next = regnext(scan);
if (OP(scan) == CURLYX) {
@@ -2707,9 +2707,10 @@ Perl_study_chunk(pTHX_
/* CURLYM can emulate a capture around its whole body,
* but cannot restore captures inside a nested quantifier
* when an iteration fails or is backtracked. */
- && (!ARG3a(oscan)
+ && (!CURLY_HAS_CAPTURES(oscan)
|| ((data->flags & SF_IN_PAR)
- && ARG3a(oscan) == ARG3b(oscan)
+ && CURLY_FIRST_CAPTURE(oscan)
+ == CURLY_LAST_CAPTURE(oscan)
&& OP(REGNODE_AFTER_type(oscan,
tregnode_CURLYX)) == OPEN))
&& !deltanext /* atom is fixed width */
diff --git a/regexec.c b/regexec.c
index d3877b8fd2..eed547a922 100644
--- a/regexec.c
+++ b/regexec.c
@@ -184,7 +184,7 @@ static const char non_utf8_target_but_utf8_required[]
OP(rn) == SUSPEND || OP(rn) == IFMATCH || \
OP(rn) == PLUS || OP(rn) == MINMOD || \
OP(rn) == KEEPS || \
- (REGNODE_TYPE(OP(rn)) == CURLY && ARG1i(rn) > 0) \
+ (REGNODE_TYPE(OP(rn)) == CURLY && CURLY_MIN_REPEAT(rn) > 0) \
)
#define IS_EXACT(rn) (REGNODE_TYPE(OP(rn)) == EXACT)
@@ -9254,10 +9254,10 @@ NULL
assert(cur_curlyx); /* keep Coverity happy */
- min = ARG1i(cur_curlyx->u.curlyx.me);
- max = ARG2i(cur_curlyx->u.curlyx.me);
- /* first_paren = ARG3a(cur_curlyx->u.curlyx.me); */
- /* last_paren = ARG3b(cur_curlyx->u.curlyx.me); */
+ min = CURLY_MIN_REPEAT(cur_curlyx->u.curlyx.me);
+ max = CURLY_MAX_REPEAT(cur_curlyx->u.curlyx.me);
+ /* first_paren = CURLY_FIRST_CAPTURE(cur_curlyx->u.curlyx.me); */
+ /* last_paren = CURLY_LAST_CAPTURE(cur_curlyx->u.curlyx.me); */
A = REGNODE_AFTER(cur_curlyx->u.curlyx.me);
n = ++cur_curlyx->u.curlyx.count; /* how many A's matched */
ST.save_lastloc = cur_curlyx->u.curlyx.lastloc;
@@ -9399,12 +9399,12 @@ NULL
if (cur_curlyx->u.curlyx.minmod) {
if (!cur_curlyx->u.curlyx.count
- && ARG3a(cur_curlyx->u.curlyx.me))
+ && CURLY_HAS_CAPTURES(cur_curlyx->u.curlyx.me))
{
ST.cp = regcppush(rex, 0, maxopenparen);
REGCP_SET(ST.lastcp);
- CAPTURE_CLEAR(ARG3a(cur_curlyx->u.curlyx.me),
- ARG3b(cur_curlyx->u.curlyx.me),
+ CAPTURE_CLEAR(CURLY_FIRST_CAPTURE(cur_curlyx->u.curlyx.me),
+ CURLY_LAST_CAPTURE(cur_curlyx->u.curlyx.me),
"WHILEM zero repetitions");
}
ST.save_curlyx = cur_curlyx;
@@ -9442,7 +9442,7 @@ NULL
seen_nonregular |= ST.saved_seen_nonregular;
cur_curlyx = ST.save_curlyx;
if (!cur_curlyx->u.curlyx.count
- && ARG3a(cur_curlyx->u.curlyx.me))
+ && CURLY_HAS_CAPTURES(cur_curlyx->u.curlyx.me))
{
REGCP_UNWIND(ST.lastcp);
regcppop(rex, &maxopenparen);
@@ -9475,12 +9475,12 @@ NULL
* as CURLYN and CURLYM do. Save the incoming values so that
* backtracking can try the body with its original capture state. */
if (!cur_curlyx->u.curlyx.count
- && ARG3a(cur_curlyx->u.curlyx.me))
+ && CURLY_HAS_CAPTURES(cur_curlyx->u.curlyx.me))
{
ST.cp = regcppush(rex, 0, maxopenparen);
REGCP_SET(ST.lastcp);
- CAPTURE_CLEAR(ARG3a(cur_curlyx->u.curlyx.me),
- ARG3b(cur_curlyx->u.curlyx.me),
+ CAPTURE_CLEAR(CURLY_FIRST_CAPTURE(cur_curlyx->u.curlyx.me),
+ CURLY_LAST_CAPTURE(cur_curlyx->u.curlyx.me),
"WHILEM zero repetitions");
}
ST.save_curlyx = cur_curlyx;
@@ -9495,13 +9495,15 @@ NULL
seen_nonregular |= ST.saved_seen_nonregular;
cur_curlyx = ST.save_curlyx;
if (!cur_curlyx->u.curlyx.count
- && ARG3a(cur_curlyx->u.curlyx.me))
+ && CURLY_HAS_CAPTURES(cur_curlyx->u.curlyx.me))
{
REGCP_UNWIND(ST.lastcp);
regcppop(rex, &maxopenparen);
}
- if (cur_curlyx->u.curlyx.count >= /*max*/ARG2i(cur_curlyx->u.curlyx.me)) {
+ if (cur_curlyx->u.curlyx.count
+ >= CURLY_MAX_REPEAT(cur_curlyx->u.curlyx.me))
+ {
/* Maximum greed exceeded */
cur_curlyx->u.curlyx.count--;
CACHEsayNO;
@@ -9639,7 +9641,8 @@ NULL
ST.Binfo.count = -1;
REGCP_SET(ST.cp);
- if (!(ST.minmod ? ARG1i(ST.me) : ARG2i(ST.me))) /* min/max */
+ if (!(ST.minmod ? CURLY_MIN_REPEAT(ST.me)
+ : CURLY_MAX_REPEAT(ST.me)))
goto curlym_do_B;
curlym_do_A: /* execute the A in /A{m,n}B/ */
@@ -9662,7 +9665,8 @@ NULL
ST.alen = locinput - st->locinput;
}
if (ST.alen == 0)
- ST.count = ST.minmod ? ARG1i(ST.me) : ARG2i(ST.me);
+ ST.count = ST.minmod ? CURLY_MIN_REPEAT(ST.me)
+ : CURLY_MAX_REPEAT(ST.me);
}
DEBUG_EXECUTE_r(
re_exec_indentf("CURLYM now matched %" IVdf " times, len = %" IVdf "...\n",
@@ -9682,7 +9686,8 @@ NULL
if (!is_accepted) {
- I32 max = (ST.minmod ? ARG1i(ST.me) : ARG2i(ST.me));
+ I32 max = (ST.minmod ? CURLY_MIN_REPEAT(ST.me)
+ : CURLY_MAX_REPEAT(ST.me));
if ( max == REG_INFTY || ST.count < max )
goto curlym_do_A; /* try to match another A */
}
@@ -9692,7 +9697,7 @@ NULL
REGCP_UNWIND(ST.cp);
- if (ST.minmod || ST.count < ARG1i(ST.me) /* min*/
+ if (ST.minmod || ST.count < CURLY_MIN_REPEAT(ST.me)
|| EVAL_CLOSE_PAREN_IS_TRUE(cur_eval,(U32)FLAGS(ST.me)))
sayNO;
@@ -9773,13 +9778,13 @@ NULL
REGCP_UNWIND(ST.cp);
UNWIND_PAREN(ST.lastparen, ST.lastcloseparen);
if (ST.minmod) {
- I32 max = ARG2i(ST.me);
+ I32 max = CURLY_MAX_REPEAT(ST.me);
if (max != REG_INFTY && ST.count == max)
sayNO;
goto curlym_do_A; /* try to match a further A */
}
/* backtrack one A */
- if (ST.count == ARG1i(ST.me) /* min */)
+ if (ST.count == CURLY_MIN_REPEAT(ST.me))
sayNO;
ST.count--;
SET_locinput(HOPc(locinput, -ST.alen));
@@ -9821,8 +9826,8 @@ NULL
ST.lastcloseparen = RXp_LASTCLOSEPAREN(rex);
if (ST.paren > maxopenparen)
maxopenparen = ST.paren;
- ST.min = ARG1i(scan); /* min to match */
- ST.max = ARG2i(scan); /* max to match */
+ ST.min = CURLY_MIN_REPEAT(scan);
+ ST.max = CURLY_MAX_REPEAT(scan);
scan = regnext(REGNODE_AFTER_type(scan, tregnode_CURLYN));
/* handle the single-char capture called as a GOSUB etc */
@@ -9841,8 +9846,8 @@ NULL
case CURLY: /* /A{m,n}B/ where A is width 1 char */
ST.paren = 0;
- ST.min = ARG1i(scan); /* min to match */
- ST.max = ARG2i(scan); /* max to match */
+ ST.min = CURLY_MIN_REPEAT(scan);
+ ST.max = CURLY_MAX_REPEAT(scan);
scan = REGNODE_AFTER_type(scan, tregnode_CURLY);
repeat:
/*