]> granicus.if.org Git - postgresql/commitdiff
Fix the general case of quantified regex back-references.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 24 Feb 2012 06:40:18 +0000 (01:40 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 24 Feb 2012 06:41:03 +0000 (01:41 -0500)
Cases where a back-reference is part of a larger subexpression that
is quantified have never worked in Spencer's regex engine, because
he used a compile-time transformation that neglected the need to
check the back-reference match in iterations before the last one.
(That was okay for capturing parens, and we still do it if the
regex has *only* capturing parens ... but it's not okay for backrefs.)

To make this work properly, we have to add an "iteration" node type
to the regex engine's vocabulary of sub-regex nodes.  Since this is a
moderately large change with a fair risk of introducing new bugs of its
own, apply to HEAD only, even though it's a fix for a longstanding bug.

src/backend/regex/README
src/backend/regex/regcomp.c
src/backend/regex/regexec.c
src/include/regex/regguts.h
src/test/regress/expected/regex.out
src/test/regress/sql/regex.sql

index 3fd58c000119a24d61dff594baec28a27a4437f0..89ba6a62ea2f70bfda729f03efedba4b9b6fce9b 100644 (file)
@@ -102,15 +102,15 @@ consists of a tree of sub-expressions ("subre"s).  Leaf tree nodes are
 either plain regular expressions (which are executed as DFAs in the manner
 described above) or back-references (which try to match the input to some
 previous substring).  Non-leaf nodes are capture nodes (which save the
-location of the substring currently matching their child node) or
-concatenation or alternation nodes.  At execution time, the executor
-recursively scans the tree.  At concatenation or alternation nodes,
-it considers each possible alternative way of matching the input string,
-ie each place where the string could be split for a concatenation, or each
-child node for an alternation.  It tries the next alternative if the match
-fails according to the child nodes.  This is exactly the sort of
-backtracking search done by a traditional NFA regex engine.  If there are
-many tree levels it can get very slow.
+location of the substring currently matching their child node),
+concatenation, alternation, or iteration nodes.  At execution time, the
+executor recursively scans the tree.  At concatenation, alternation, or
+iteration nodes, it considers each possible alternative way of matching the
+input string, that is each place where the string could be split for a
+concatenation or iteration, or each child node for an alternation.  It
+tries the next alternative if the match fails according to the child nodes.
+This is exactly the sort of backtracking search done by a traditional NFA
+regex engine.  If there are many tree levels it can get very slow.
 
 But all is not lost: we can still be smarter than the average pure NFA
 engine.  To do this, each subre node has an associated DFA, which
index 6b80140e90940b4a348c342090e26fdd0bc82c8f..b84d0c3af55f04873bc61c8b843af3b99c865b4a 100644 (file)
@@ -1036,11 +1036,17 @@ parseqatom(struct vars * v,
        /*----------
         * Prepare a general-purpose state skeleton.
         *
-        *        ---> [s] ---prefix---> [begin] ---atom---> [end] ----rest---> [rp]
-        *       /                                                                                        /
-        * [lp] ----> [s2] ----bypass---------------------
+        * In the no-backrefs case, we want this:
         *
-        * where bypass is an empty, and prefix is some repetitions of atom
+        * [lp] ---> [s] ---prefix---> [begin] ---atom---> [end] ---rest---> [rp]
+        *
+        * where prefix is some repetitions of atom.  In the general case we need
+        *
+        * [lp] ---> [s] ---iterator---> [s2] ---rest---> [rp]
+        *
+        * where the iterator wraps around [begin] ---atom---> [end]
+        *
+        * We make the s state here for both cases; s2 is made below if needed
         *----------
         */
        s = newstate(v->nfa);           /* first, new endpoints for the atom */
@@ -1051,11 +1057,9 @@ parseqatom(struct vars * v,
        NOERR();
        atom->begin = s;
        atom->end = s2;
-       s = newstate(v->nfa);           /* and spots for prefix and bypass */
-       s2 = newstate(v->nfa);
+       s = newstate(v->nfa);           /* set up starting state */
        NOERR();
        EMPTYARC(lp, s);
-       EMPTYARC(lp, s2);
        NOERR();
 
        /* break remaining subRE into x{...} and what follows */
@@ -1089,28 +1093,9 @@ parseqatom(struct vars * v,
        }
 
        /*
-        * It's quantifier time.  If the atom is just a BACKREF, we'll let it deal
-        * with quantifiers internally.  Otherwise, the first step is to turn
-        * x{0,...} into x{1,...}|empty
+        * It's quantifier time.  If the atom is just a backref, we'll let it deal
+        * with quantifiers internally.
         */
-       if (m == 0 && atomtype != BACKREF)
-       {
-               EMPTYARC(s2, atom->end);        /* the bypass */
-               assert(PREF(qprefer) != 0);
-               f = COMBINE(qprefer, atom->flags);
-               t = subre(v, '|', f, lp, atom->end);
-               NOERR();
-               t->left = atom;
-               t->right = subre(v, '|', PREF(f), s2, atom->end);
-               NOERR();
-               t->right->left = subre(v, '=', 0, s2, atom->end);
-               NOERR();
-               *atomp = t;
-               atomp = &t->left;
-               m = 1;
-       }
-
-       /* deal with the rest of the quantifier */
        if (atomtype == BACKREF)
        {
                /* special case:  backrefs have internal quantifiers */
@@ -1120,17 +1105,25 @@ parseqatom(struct vars * v,
                atom->min = (short) m;
                atom->max = (short) n;
                atom->flags |= COMBINE(qprefer, atom->flags);
+               /* rest of branch can be strung starting from atom->end */
+               s2 = atom->end;
        }
        else if (m == 1 && n == 1)
        {
                /* no/vacuous quantifier:  done */
                EMPTYARC(s, atom->begin);               /* empty prefix */
+               /* rest of branch can be strung starting from atom->end */
+               s2 = atom->end;
        }
-       else
+       else if (m > 0 && !(atom->flags & BACKR))
        {
                /*
-                * Turn x{m,n} into x{m-1,n-1}x, with capturing parens in only the
-                * second x
+                * If there's no backrefs involved, we can turn x{m,n} into
+                * x{m-1,n-1}x, with capturing parens in only the second x.  This
+                * is valid because we only care about capturing matches from the
+                * final iteration of the quantifier.  It's a win because we can
+                * implement the backref-free left side as a plain DFA node, since
+                * we don't really care where its submatches are.
                 */
                dupnfa(v->nfa, atom->begin, atom->end, s, atom->begin);
                assert(m >= 1 && m != INFINITY && n >= 1);
@@ -1142,16 +1135,36 @@ parseqatom(struct vars * v,
                NOERR();
                t->right = atom;
                *atomp = t;
+               /* rest of branch can be strung starting from atom->end */
+               s2 = atom->end;
+       }
+       else
+       {
+               /* general case: need an iteration node */
+               s2 = newstate(v->nfa);
+               NOERR();
+               moveouts(v->nfa, atom->end, s2);
+               NOERR();
+               dupnfa(v->nfa, atom->begin, atom->end, s, s2);
+               repeat(v, s, s2, m, n);
+               f = COMBINE(qprefer, atom->flags);
+               t = subre(v, '*', f, s, s2);
+               NOERR();
+               t->min = (short) m;
+               t->max = (short) n;
+               t->left = atom;
+               *atomp = t;
+               /* rest of branch is to be strung from iteration's end state */
        }
 
        /* and finally, look after that postponed recursion */
        t = top->right;
        if (!(SEE('|') || SEE(stopper) || SEE(EOS)))
-               t->right = parsebranch(v, stopper, type, atom->end, rp, 1);
+               t->right = parsebranch(v, stopper, type, s2, rp, 1);
        else
        {
-               EMPTYARC(atom->end, rp);
-               t->right = subre(v, '=', 0, atom->end, rp);
+               EMPTYARC(s2, rp);
+               t->right = subre(v, '=', 0, s2, rp);
        }
        assert(SEE('|') || SEE(stopper) || SEE(EOS));
        t->flags |= COMBINE(t->flags, t->right->flags);
@@ -1214,6 +1227,9 @@ scannum(struct vars * v)
 /*
  * repeat - replicate subNFA for quantifiers
  *
+ * The sub-NFA strung from lp to rp is modified to represent m to n
+ * repetitions of its initial contents.
+ *
  * The duplication sequences used here are chosen carefully so that any
  * pointers starting out pointing into the subexpression end up pointing into
  * the last occurrence.  (Note that it may not be strung between the same
@@ -1229,7 +1245,7 @@ repeat(struct vars * v,
           int n)
 {
 #define  SOME   2
-#define  INF 3
+#define  INF    3
 #define  PAIR(x, y)  ((x)*4 + (y))
 #define  REDUCE(x)      ( ((x) == INFINITY) ? INF : (((x) > 1) ? SOME : (x)) )
        const int       rm = REDUCE(m);
@@ -1603,7 +1619,7 @@ subre(struct vars * v,
                v->treechain = ret;
        }
 
-       assert(strchr("|.b(=", op) != NULL);
+       assert(strchr("=b|.*(", op) != NULL);
 
        ret->op = op;
        ret->flags = flags;
index 224da5064b69b9577856b21793bd9a92afb03377..ea16e39a6eddfc6267e7c8fc2977afab7cb17e49 100644 (file)
@@ -140,11 +140,15 @@ static void subset(struct vars *, struct subre *, chr *, chr *);
 static int     dissect(struct vars *, struct subre *, chr *, chr *);
 static int     condissect(struct vars *, struct subre *, chr *, chr *);
 static int     altdissect(struct vars *, struct subre *, chr *, chr *);
+static int     iterdissect(struct vars *, struct subre *, chr *, chr *);
+static int     reviterdissect(struct vars *, struct subre *, chr *, chr *);
 static int     cdissect(struct vars *, struct subre *, chr *, chr *);
 static int     ccondissect(struct vars *, struct subre *, chr *, chr *);
 static int     crevdissect(struct vars *, struct subre *, chr *, chr *);
 static int     cbrdissect(struct vars *, struct subre *, chr *, chr *);
 static int     caltdissect(struct vars *, struct subre *, chr *, chr *);
+static int     citerdissect(struct vars *, struct subre *, chr *, chr *);
+static int     creviterdissect(struct vars *, struct subre *, chr *, chr *);
 
 /* === rege_dfa.c === */
 static chr *longest(struct vars *, struct dfa *, chr *, chr *, int *);
@@ -563,14 +567,17 @@ dissect(struct vars * v,
                case '=':                               /* terminal node */
                        assert(t->left == NULL && t->right == NULL);
                        return REG_OKAY;        /* no action, parent did the work */
-               case '|':                               /* alternation */
-                       assert(t->left != NULL);
-                       return altdissect(v, t, begin, end);
                case 'b':                               /* back ref -- shouldn't be calling us! */
                        return REG_ASSERT;
                case '.':                               /* concatenation */
                        assert(t->left != NULL && t->right != NULL);
                        return condissect(v, t, begin, end);
+               case '|':                               /* alternation */
+                       assert(t->left != NULL);
+                       return altdissect(v, t, begin, end);
+               case '*':                               /* iteration */
+                       assert(t->left != NULL);
+                       return iterdissect(v, t, begin, end);
                case '(':                               /* capturing */
                        assert(t->left != NULL && t->right == NULL);
                        assert(t->subno > 0);
@@ -696,6 +703,375 @@ altdissect(struct vars * v,
        return REG_ASSERT;                      /* none of them matched?!? */
 }
 
+/*
+ * iterdissect - iteration subexpression matches (uncomplicated)
+ */
+static int                                             /* regexec return code */
+iterdissect(struct vars * v,
+                       struct subre * t,
+                       chr *begin,                     /* beginning of relevant substring */
+                       chr *end)                       /* end of same */
+{
+       struct dfa *d;
+       chr               **endpts;
+       chr                *limit;
+       int                     min_matches;
+       size_t          max_matches;
+       int                     nverified;
+       int                     k;
+       int                     i;
+       int                     er;
+
+       assert(t->op == '*');
+       assert(t->left != NULL && t->left->cnfa.nstates > 0);
+       assert(begin <= end);
+
+       if (t->left->flags & SHORTER)           /* reverse scan */
+               return reviterdissect(v, t, begin, end);
+
+       /*
+        * If zero matches are allowed, and target string is empty, just declare
+        * victory.  OTOH, if target string isn't empty, zero matches can't work
+        * so we pretend the min is 1.
+        */
+       min_matches = t->min;
+       if (min_matches <= 0)
+       {
+               if (begin == end)
+                       return REG_OKAY;
+               min_matches = 1;
+       }
+
+       /*
+        * We need workspace to track the endpoints of each sub-match.  Normally
+        * we consider only nonzero-length sub-matches, so there can be at most
+        * end-begin of them.  However, if min is larger than that, we will also
+        * consider zero-length sub-matches in order to find enough matches.
+        *
+        * For convenience, endpts[0] contains the "begin" pointer and we store
+        * sub-match endpoints in endpts[1..max_matches].
+        */
+       max_matches = end - begin;
+       if (max_matches > t->max && t->max != INFINITY)
+               max_matches = t->max;
+       if (max_matches < min_matches)
+               max_matches = min_matches;
+       endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+       if (endpts == NULL)
+               return REG_ESPACE;
+       endpts[0] = begin;
+
+       d = newdfa(v, &t->left->cnfa, &v->g->cmap, DOMALLOC);
+       if (ISERR())
+       {
+               FREE(endpts);
+               return v->err;
+       }
+       MDEBUG(("iter %d\n", t->retry));
+
+       /*
+        * Our strategy is to first find a set of sub-match endpoints that are
+        * valid according to the child node's DFA, and then recursively dissect
+        * each sub-match to confirm validity.  If any validity check fails,
+        * backtrack the last sub-match and try again.  And, when we next try for
+        * a validity check, we need not recheck any successfully verified
+        * sub-matches that we didn't move the endpoints of.  nverified remembers
+        * how many sub-matches are currently known okay.
+        */
+
+       /* initialize to consider first sub-match */
+       nverified = 0;
+       k = 1;
+       limit = end;
+
+       /* iterate until satisfaction or failure */
+       while (k > 0)
+       {
+               /* try to find an endpoint for the k'th sub-match */
+               endpts[k] = longest(v, d, endpts[k - 1], limit, (int *) NULL);
+               if (endpts[k] == NULL)
+               {
+                       /* no match possible, so see if we can shorten previous one */
+                       k--;
+                       goto backtrack;
+               }
+               MDEBUG(("%d: working endpoint %d: %ld\n",
+                               t->retry, k, LOFF(endpts[k])));
+
+               /* k'th sub-match can no longer be considered verified */
+               if (nverified >= k)
+                       nverified = k - 1;
+
+               if (endpts[k] != end)
+               {
+                       /* haven't reached end yet, try another iteration if allowed */
+                       if (k >= max_matches)
+                       {
+                               /* must try to shorten some previous match */
+                               k--;
+                               goto backtrack;
+                       }
+
+                       /* reject zero-length match unless necessary to achieve min */
+                       if (endpts[k] == endpts[k - 1] &&
+                               (k >= min_matches || min_matches - k < end - endpts[k]))
+                               goto backtrack;
+
+                       k++;
+                       limit = end;
+                       continue;
+               }
+
+               /*
+                * We've identified a way to divide the string into k sub-matches
+                * that works so far as the child DFA can tell.  If k is an allowed
+                * number of matches, start the slow part: recurse to verify each
+                * sub-match.  We always have k <= max_matches, needn't check that.
+                */
+               if (k < min_matches)
+                       goto backtrack;
+
+               MDEBUG(("%d: verifying %d..%d\n", t->retry, nverified + 1, k));
+
+               for (i = nverified + 1; i <= k; i++)
+               {
+                       er = dissect(v, t->left, endpts[i - 1], endpts[i]);
+                       if (er == REG_OKAY)
+                       {
+                               nverified = i;
+                               continue;
+                       }
+                       if (er == REG_NOMATCH)
+                               break;
+                       /* oops, something failed */
+                       freedfa(d);
+                       FREE(endpts);
+                       return er;
+               }
+
+               if (i > k)
+               {
+                       /* satisfaction */
+                       MDEBUG(("%d successful\n", t->retry));
+                       freedfa(d);
+                       FREE(endpts);
+                       return REG_OKAY;
+               }
+
+               /* match failed to verify, so backtrack */
+
+backtrack:
+               /*
+                * Must consider shorter versions of the current sub-match.  However,
+                * we'll only ask for a zero-length match if necessary.
+                */
+               while (k > 0)
+               {
+                       chr        *prev_end = endpts[k - 1];
+
+                       if (endpts[k] > prev_end)
+                       {
+                               limit = endpts[k] - 1;
+                               if (limit > prev_end ||
+                                       (k < min_matches && min_matches - k >= end - prev_end))
+                               {
+                                       /* break out of backtrack loop, continue the outer one */
+                                       break;
+                               }
+                       }
+                       /* can't shorten k'th sub-match any more, consider previous one */
+                       k--;
+               }
+       }
+
+       /* all possibilities exhausted - shouldn't happen in uncomplicated mode */
+       MDEBUG(("%d failed\n", t->retry));
+       freedfa(d);
+       FREE(endpts);
+       return REG_ASSERT;
+}
+
+/*
+ * reviterdissect - shortest-first iteration subexpression matches
+ */
+static int                                             /* regexec return code */
+reviterdissect(struct vars * v,
+                          struct subre * t,
+                          chr *begin,          /* beginning of relevant substring */
+                          chr *end)            /* end of same */
+{
+       struct dfa *d;
+       chr               **endpts;
+       chr                *limit;
+       int                     min_matches;
+       size_t          max_matches;
+       int                     nverified;
+       int                     k;
+       int                     i;
+       int                     er;
+
+       assert(t->op == '*');
+       assert(t->left != NULL && t->left->cnfa.nstates > 0);
+       assert(t->left->flags & SHORTER);
+       assert(begin <= end);
+
+       /*
+        * If zero matches are allowed, and target string is empty, just declare
+        * victory.  OTOH, if target string isn't empty, zero matches can't work
+        * so we pretend the min is 1.
+        */
+       min_matches = t->min;
+       if (min_matches <= 0)
+       {
+               if (begin == end)
+                       return REG_OKAY;
+               min_matches = 1;
+       }
+
+       /*
+        * We need workspace to track the endpoints of each sub-match.  Normally
+        * we consider only nonzero-length sub-matches, so there can be at most
+        * end-begin of them.  However, if min is larger than that, we will also
+        * consider zero-length sub-matches in order to find enough matches.
+        *
+        * For convenience, endpts[0] contains the "begin" pointer and we store
+        * sub-match endpoints in endpts[1..max_matches].
+        */
+       max_matches = end - begin;
+       if (max_matches > t->max && t->max != INFINITY)
+               max_matches = t->max;
+       if (max_matches < min_matches)
+               max_matches = min_matches;
+       endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+       if (endpts == NULL)
+               return REG_ESPACE;
+       endpts[0] = begin;
+
+       d = newdfa(v, &t->left->cnfa, &v->g->cmap, DOMALLOC);
+       if (ISERR())
+       {
+               FREE(endpts);
+               return v->err;
+       }
+       MDEBUG(("reviter %d\n", t->retry));
+
+       /*
+        * Our strategy is to first find a set of sub-match endpoints that are
+        * valid according to the child node's DFA, and then recursively dissect
+        * each sub-match to confirm validity.  If any validity check fails,
+        * backtrack the last sub-match and try again.  And, when we next try for
+        * a validity check, we need not recheck any successfully verified
+        * sub-matches that we didn't move the endpoints of.  nverified remembers
+        * how many sub-matches are currently known okay.
+        */
+
+       /* initialize to consider first sub-match */
+       nverified = 0;
+       k = 1;
+       limit = begin;
+
+       /* iterate until satisfaction or failure */
+       while (k > 0)
+       {
+               /* disallow zero-length match unless necessary to achieve min */
+               if (limit == endpts[k - 1] &&
+                       limit != end &&
+                       (k >= min_matches || min_matches - k < end - limit))
+                       limit++;
+
+               /* try to find an endpoint for the k'th sub-match */
+               endpts[k] = shortest(v, d, endpts[k - 1], limit, end,
+                                                        (chr **) NULL, (int *) NULL);
+               if (endpts[k] == NULL)
+               {
+                       /* no match possible, so see if we can lengthen previous one */
+                       k--;
+                       goto backtrack;
+               }
+               MDEBUG(("%d: working endpoint %d: %ld\n",
+                               t->retry, k, LOFF(endpts[k])));
+
+               /* k'th sub-match can no longer be considered verified */
+               if (nverified >= k)
+                       nverified = k - 1;
+
+               if (endpts[k] != end)
+               {
+                       /* haven't reached end yet, try another iteration if allowed */
+                       if (k >= max_matches)
+                       {
+                               /* must try to lengthen some previous match */
+                               k--;
+                               goto backtrack;
+                       }
+
+                       k++;
+                       limit = endpts[k - 1];
+                       continue;
+               }
+
+               /*
+                * We've identified a way to divide the string into k sub-matches
+                * that works so far as the child DFA can tell.  If k is an allowed
+                * number of matches, start the slow part: recurse to verify each
+                * sub-match.  We always have k <= max_matches, needn't check that.
+                */
+               if (k < min_matches)
+                       goto backtrack;
+
+               MDEBUG(("%d: verifying %d..%d\n", t->retry, nverified + 1, k));
+
+               for (i = nverified + 1; i <= k; i++)
+               {
+                       er = dissect(v, t->left, endpts[i - 1], endpts[i]);
+                       if (er == REG_OKAY)
+                       {
+                               nverified = i;
+                               continue;
+                       }
+                       if (er == REG_NOMATCH)
+                               break;
+                       /* oops, something failed */
+                       freedfa(d);
+                       FREE(endpts);
+                       return er;
+               }
+
+               if (i > k)
+               {
+                       /* satisfaction */
+                       MDEBUG(("%d successful\n", t->retry));
+                       freedfa(d);
+                       FREE(endpts);
+                       return REG_OKAY;
+               }
+
+               /* match failed to verify, so backtrack */
+
+backtrack:
+               /*
+                * Must consider longer versions of the current sub-match.
+                */
+               while (k > 0)
+               {
+                       if (endpts[k] < end)
+                       {
+                               limit = endpts[k] + 1;
+                               /* break out of backtrack loop, continue the outer one */
+                               break;
+                       }
+                       /* can't lengthen k'th sub-match any more, consider previous one */
+                       k--;
+               }
+       }
+
+       /* all possibilities exhausted - shouldn't happen in uncomplicated mode */
+       MDEBUG(("%d failed\n", t->retry));
+       freedfa(d);
+       FREE(endpts);
+       return REG_ASSERT;
+}
+
 /*
  * cdissect - determine subexpression matches (with complications)
  * The retry memory stores the offset of the trial midpoint from begin,
@@ -717,15 +1093,18 @@ cdissect(struct vars * v,
                case '=':                               /* terminal node */
                        assert(t->left == NULL && t->right == NULL);
                        return REG_OKAY;        /* no action, parent did the work */
-               case '|':                               /* alternation */
-                       assert(t->left != NULL);
-                       return caltdissect(v, t, begin, end);
                case 'b':                               /* back reference */
                        assert(t->left == NULL && t->right == NULL);
                        return cbrdissect(v, t, begin, end);
                case '.':                               /* concatenation */
                        assert(t->left != NULL && t->right != NULL);
                        return ccondissect(v, t, begin, end);
+               case '|':                               /* alternation */
+                       assert(t->left != NULL);
+                       return caltdissect(v, t, begin, end);
+               case '*':                               /* iteration */
+                       assert(t->left != NULL);
+                       return citerdissect(v, t, begin, end);
                case '(':                               /* capturing */
                        assert(t->left != NULL && t->right == NULL);
                        assert(t->subno > 0);
@@ -847,7 +1226,7 @@ ccondissect(struct vars * v,
 }
 
 /*
- * crevdissect - determine backref shortest-first subexpression matches
+ * crevdissect - shortest-first concatenation subexpression matches
  * The retry memory stores the offset of the trial midpoint from begin,
  * plus 1 so that 0 uniquely means "clean slate".
  */
@@ -1088,6 +1467,377 @@ caltdissect(struct vars * v,
        return caltdissect(v, t->right, begin, end);
 }
 
+/*
+ * citerdissect - iteration subexpression matches (with complications)
+ */
+static int                                             /* regexec return code */
+citerdissect(struct vars * v,
+                        struct subre * t,
+                        chr *begin,            /* beginning of relevant substring */
+                        chr *end)                      /* end of same */
+{
+       struct dfa *d;
+       chr               **endpts;
+       chr                *limit;
+       int                     min_matches;
+       size_t          max_matches;
+       int                     nverified;
+       int                     k;
+       int                     i;
+       int                     er;
+
+       assert(t->op == '*');
+       assert(t->left != NULL && t->left->cnfa.nstates > 0);
+       assert(begin <= end);
+
+       if (t->left->flags & SHORTER)           /* reverse scan */
+               return creviterdissect(v, t, begin, end);
+
+       /*
+        * If zero matches are allowed, and target string is empty, just declare
+        * victory.  OTOH, if target string isn't empty, zero matches can't work
+        * so we pretend the min is 1.
+        */
+       min_matches = t->min;
+       if (min_matches <= 0)
+       {
+               if (begin == end)
+                       return REG_OKAY;
+               min_matches = 1;
+       }
+
+       /*
+        * We need workspace to track the endpoints of each sub-match.  Normally
+        * we consider only nonzero-length sub-matches, so there can be at most
+        * end-begin of them.  However, if min is larger than that, we will also
+        * consider zero-length sub-matches in order to find enough matches.
+        *
+        * For convenience, endpts[0] contains the "begin" pointer and we store
+        * sub-match endpoints in endpts[1..max_matches].
+        */
+       max_matches = end - begin;
+       if (max_matches > t->max && t->max != INFINITY)
+               max_matches = t->max;
+       if (max_matches < min_matches)
+               max_matches = min_matches;
+       endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+       if (endpts == NULL)
+               return REG_ESPACE;
+       endpts[0] = begin;
+
+       d = newdfa(v, &t->left->cnfa, &v->g->cmap, DOMALLOC);
+       if (ISERR())
+       {
+               FREE(endpts);
+               return v->err;
+       }
+       MDEBUG(("citer %d\n", t->retry));
+
+       /*
+        * Our strategy is to first find a set of sub-match endpoints that are
+        * valid according to the child node's DFA, and then recursively dissect
+        * each sub-match to confirm validity.  If any validity check fails,
+        * backtrack the last sub-match and try again.  And, when we next try for
+        * a validity check, we need not recheck any successfully verified
+        * sub-matches that we didn't move the endpoints of.  nverified remembers
+        * how many sub-matches are currently known okay.
+        */
+
+       /* initialize to consider first sub-match */
+       nverified = 0;
+       k = 1;
+       limit = end;
+
+       /* iterate until satisfaction or failure */
+       while (k > 0)
+       {
+               /* try to find an endpoint for the k'th sub-match */
+               endpts[k] = longest(v, d, endpts[k - 1], limit, (int *) NULL);
+               if (endpts[k] == NULL)
+               {
+                       /* no match possible, so see if we can shorten previous one */
+                       k--;
+                       goto backtrack;
+               }
+               MDEBUG(("%d: working endpoint %d: %ld\n",
+                               t->retry, k, LOFF(endpts[k])));
+
+               /* k'th sub-match can no longer be considered verified */
+               if (nverified >= k)
+                       nverified = k - 1;
+
+               if (endpts[k] != end)
+               {
+                       /* haven't reached end yet, try another iteration if allowed */
+                       if (k >= max_matches)
+                       {
+                               /* must try to shorten some previous match */
+                               k--;
+                               goto backtrack;
+                       }
+
+                       /* reject zero-length match unless necessary to achieve min */
+                       if (endpts[k] == endpts[k - 1] &&
+                               (k >= min_matches || min_matches - k < end - endpts[k]))
+                               goto backtrack;
+
+                       k++;
+                       limit = end;
+                       continue;
+               }
+
+               /*
+                * We've identified a way to divide the string into k sub-matches
+                * that works so far as the child DFA can tell.  If k is an allowed
+                * number of matches, start the slow part: recurse to verify each
+                * sub-match.  We always have k <= max_matches, needn't check that.
+                */
+               if (k < min_matches)
+                       goto backtrack;
+
+               MDEBUG(("%d: verifying %d..%d\n", t->retry, nverified + 1, k));
+
+               for (i = nverified + 1; i <= k; i++)
+               {
+                       zapmem(v, t->left);
+                       er = cdissect(v, t->left, endpts[i - 1], endpts[i]);
+                       if (er == REG_OKAY)
+                       {
+                               nverified = i;
+                               continue;
+                       }
+                       if (er == REG_NOMATCH)
+                               break;
+                       /* oops, something failed */
+                       freedfa(d);
+                       FREE(endpts);
+                       return er;
+               }
+
+               if (i > k)
+               {
+                       /* satisfaction */
+                       MDEBUG(("%d successful\n", t->retry));
+                       freedfa(d);
+                       FREE(endpts);
+                       return REG_OKAY;
+               }
+
+               /* match failed to verify, so backtrack */
+
+backtrack:
+               /*
+                * Must consider shorter versions of the current sub-match.  However,
+                * we'll only ask for a zero-length match if necessary.
+                */
+               while (k > 0)
+               {
+                       chr        *prev_end = endpts[k - 1];
+
+                       if (endpts[k] > prev_end)
+                       {
+                               limit = endpts[k] - 1;
+                               if (limit > prev_end ||
+                                       (k < min_matches && min_matches - k >= end - prev_end))
+                               {
+                                       /* break out of backtrack loop, continue the outer one */
+                                       break;
+                               }
+                       }
+                       /* can't shorten k'th sub-match any more, consider previous one */
+                       k--;
+               }
+       }
+
+       /* all possibilities exhausted */
+       MDEBUG(("%d failed\n", t->retry));
+       freedfa(d);
+       FREE(endpts);
+       return REG_NOMATCH;
+}
+
+/*
+ * creviterdissect - shortest-first iteration subexpression matches
+ */
+static int                                             /* regexec return code */
+creviterdissect(struct vars * v,
+                               struct subre * t,
+                               chr *begin,             /* beginning of relevant substring */
+                               chr *end)               /* end of same */
+{
+       struct dfa *d;
+       chr               **endpts;
+       chr                *limit;
+       int                     min_matches;
+       size_t          max_matches;
+       int                     nverified;
+       int                     k;
+       int                     i;
+       int                     er;
+
+       assert(t->op == '*');
+       assert(t->left != NULL && t->left->cnfa.nstates > 0);
+       assert(t->left->flags & SHORTER);
+       assert(begin <= end);
+
+       /*
+        * If zero matches are allowed, and target string is empty, just declare
+        * victory.  OTOH, if target string isn't empty, zero matches can't work
+        * so we pretend the min is 1.
+        */
+       min_matches = t->min;
+       if (min_matches <= 0)
+       {
+               if (begin == end)
+                       return REG_OKAY;
+               min_matches = 1;
+       }
+
+       /*
+        * We need workspace to track the endpoints of each sub-match.  Normally
+        * we consider only nonzero-length sub-matches, so there can be at most
+        * end-begin of them.  However, if min is larger than that, we will also
+        * consider zero-length sub-matches in order to find enough matches.
+        *
+        * For convenience, endpts[0] contains the "begin" pointer and we store
+        * sub-match endpoints in endpts[1..max_matches].
+        */
+       max_matches = end - begin;
+       if (max_matches > t->max && t->max != INFINITY)
+               max_matches = t->max;
+       if (max_matches < min_matches)
+               max_matches = min_matches;
+       endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+       if (endpts == NULL)
+               return REG_ESPACE;
+       endpts[0] = begin;
+
+       d = newdfa(v, &t->left->cnfa, &v->g->cmap, DOMALLOC);
+       if (ISERR())
+       {
+               FREE(endpts);
+               return v->err;
+       }
+       MDEBUG(("creviter %d\n", t->retry));
+
+       /*
+        * Our strategy is to first find a set of sub-match endpoints that are
+        * valid according to the child node's DFA, and then recursively dissect
+        * each sub-match to confirm validity.  If any validity check fails,
+        * backtrack the last sub-match and try again.  And, when we next try for
+        * a validity check, we need not recheck any successfully verified
+        * sub-matches that we didn't move the endpoints of.  nverified remembers
+        * how many sub-matches are currently known okay.
+        */
+
+       /* initialize to consider first sub-match */
+       nverified = 0;
+       k = 1;
+       limit = begin;
+
+       /* iterate until satisfaction or failure */
+       while (k > 0)
+       {
+               /* disallow zero-length match unless necessary to achieve min */
+               if (limit == endpts[k - 1] &&
+                       limit != end &&
+                       (k >= min_matches || min_matches - k < end - limit))
+                       limit++;
+
+               /* try to find an endpoint for the k'th sub-match */
+               endpts[k] = shortest(v, d, endpts[k - 1], limit, end,
+                                                        (chr **) NULL, (int *) NULL);
+               if (endpts[k] == NULL)
+               {
+                       /* no match possible, so see if we can lengthen previous one */
+                       k--;
+                       goto backtrack;
+               }
+               MDEBUG(("%d: working endpoint %d: %ld\n",
+                               t->retry, k, LOFF(endpts[k])));
+
+               /* k'th sub-match can no longer be considered verified */
+               if (nverified >= k)
+                       nverified = k - 1;
+
+               if (endpts[k] != end)
+               {
+                       /* haven't reached end yet, try another iteration if allowed */
+                       if (k >= max_matches)
+                       {
+                               /* must try to lengthen some previous match */
+                               k--;
+                               goto backtrack;
+                       }
+
+                       k++;
+                       limit = endpts[k - 1];
+                       continue;
+               }
+
+               /*
+                * We've identified a way to divide the string into k sub-matches
+                * that works so far as the child DFA can tell.  If k is an allowed
+                * number of matches, start the slow part: recurse to verify each
+                * sub-match.  We always have k <= max_matches, needn't check that.
+                */
+               if (k < min_matches)
+                       goto backtrack;
+
+               MDEBUG(("%d: verifying %d..%d\n", t->retry, nverified + 1, k));
+
+               for (i = nverified + 1; i <= k; i++)
+               {
+                       zapmem(v, t->left);
+                       er = cdissect(v, t->left, endpts[i - 1], endpts[i]);
+                       if (er == REG_OKAY)
+                       {
+                               nverified = i;
+                               continue;
+                       }
+                       if (er == REG_NOMATCH)
+                               break;
+                       /* oops, something failed */
+                       freedfa(d);
+                       FREE(endpts);
+                       return er;
+               }
+
+               if (i > k)
+               {
+                       /* satisfaction */
+                       MDEBUG(("%d successful\n", t->retry));
+                       freedfa(d);
+                       FREE(endpts);
+                       return REG_OKAY;
+               }
+
+               /* match failed to verify, so backtrack */
+
+backtrack:
+               /*
+                * Must consider longer versions of the current sub-match.
+                */
+               while (k > 0)
+               {
+                       if (endpts[k] < end)
+                       {
+                               limit = endpts[k] + 1;
+                               /* break out of backtrack loop, continue the outer one */
+                               break;
+                       }
+                       /* can't lengthen k'th sub-match any more, consider previous one */
+                       k--;
+               }
+       }
+
+       /* all possibilities exhausted */
+       MDEBUG(("%d failed\n", t->retry));
+       freedfa(d);
+       FREE(endpts);
+       return REG_NOMATCH;
+}
+
 
 
 #include "rege_dfa.c"
index fb6789b560f3899b7c77feef4e0e91ac1f2d9c5d..d420ea8316e18f2ff009af5c618027cf3fae8256 100644 (file)
@@ -372,10 +372,28 @@ struct cnfa
 
 /*
  * subexpression tree
+ *
+ * "op" is one of:
+ *             '='  plain regex without interesting substructure (implemented as DFA)
+ *             'b'  back-reference (has no substructure either)
+ *             '('  capture node: captures the match of its single child
+ *             '.'  concatenation: matches a match for left, then a match for right
+ *             '|'  alternation: matches a match for left or a match for right
+ *             '*'  iteration: matches some number of matches of its single child
+ *
+ * Note: the right child of an alternation must be another alternation or
+ * NULL; hence, an N-way branch requires N alternation nodes, not N-1 as you
+ * might expect.  This could stand to be changed.  Actually I'd rather see
+ * a single alternation node with N children, but that will take revising
+ * the representation of struct subre.
+ *
+ * Note: when a backref is directly quantified, we stick the min/max counts
+ * into the backref rather than plastering an iteration node on top.  This is
+ * for efficiency: there is no need to search for possible division points.
  */
 struct subre
 {
-       char            op;                             /* '|', '.' (concat), 'b' (backref), '(', '=' */
+       char            op;                             /* see type codes above */
        char            flags;
 #define  LONGER  01                            /* prefers longer match */
 #define  SHORTER 02                            /* prefers shorter match */
@@ -393,8 +411,8 @@ struct subre
 #define  COMBINE(f1, f2) (UP((f1)|(f2)) | PREF2(f1, f2))
        short           retry;                  /* index into retry memory */
        int                     subno;                  /* subexpression number (for 'b' and '(') */
-       short           min;                    /* min repetitions, for backref only */
-       short           max;                    /* max repetitions, for backref only */
+       short           min;                    /* min repetitions for iteration or backref */
+       short           max;                    /* max repetitions for iteration or backref */
        struct subre *left;                     /* left child, if any (also freelist chain) */
        struct subre *right;            /* right child, if any */
        struct state *begin;            /* outarcs from here... */
index 5694908163af856f78940a9e31d75a370b4bfe09..4acc4a47a03bca1ad6303765c519f414fdbf5c42 100644 (file)
@@ -34,3 +34,40 @@ select 'b' ~ '^([bc])\1*$' as t;
  t
 (1 row)
 
+-- Test quantified backref within a larger expression
+select 'abc abc abc' ~ '^(\w+)( \1)+$' as t;
+ t 
+---
+ t
+(1 row)
+
+select 'abc abd abc' ~ '^(\w+)( \1)+$' as f;
+ f 
+---
+ f
+(1 row)
+
+select 'abc abc abd' ~ '^(\w+)( \1)+$' as f;
+ f 
+---
+ f
+(1 row)
+
+select 'abc abc abc' ~ '^(.+)( \1)+$' as t;
+ t 
+---
+ t
+(1 row)
+
+select 'abc abd abc' ~ '^(.+)( \1)+$' as f;
+ f 
+---
+ f
+(1 row)
+
+select 'abc abc abd' ~ '^(.+)( \1)+$' as f;
+ f 
+---
+ f
+(1 row)
+
index 242a81ef3298a68d79cf4af971e17935f3630964..b5315a3df6ddda340d2bf6f27137aba1210267f1 100644 (file)
@@ -11,3 +11,11 @@ select 'ccc' ~ '^([bc])\1*$' as t;
 select 'xxx' ~ '^([bc])\1*$' as f;
 select 'bbc' ~ '^([bc])\1*$' as f;
 select 'b' ~ '^([bc])\1*$' as t;
+
+-- Test quantified backref within a larger expression
+select 'abc abc abc' ~ '^(\w+)( \1)+$' as t;
+select 'abc abd abc' ~ '^(\w+)( \1)+$' as f;
+select 'abc abc abd' ~ '^(\w+)( \1)+$' as f;
+select 'abc abc abc' ~ '^(.+)( \1)+$' as t;
+select 'abc abd abc' ~ '^(.+)( \1)+$' as f;
+select 'abc abc abd' ~ '^(.+)( \1)+$' as f;