LCOV - code coverage report
Current view: top level - buildbot/coverage/build/src/theory/strings - regexp_elim.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 287 311 92.3 %
Date: 2024-09-23 10:48:02 Functions: 7 7 100.0 %
Branches: 181 264 68.6 %

           Branch data     Line data    Source code
       1                 :            : /******************************************************************************
       2                 :            :  * Top contributors (to current version):
       3                 :            :  *   Andrew Reynolds, Aina Niemetz, Mathias Preiner
       4                 :            :  *
       5                 :            :  * This file is part of the cvc5 project.
       6                 :            :  *
       7                 :            :  * Copyright (c) 2009-2024 by the authors listed in the file AUTHORS
       8                 :            :  * in the top-level source directory and their institutional affiliations.
       9                 :            :  * All rights reserved.  See the file COPYING in the top-level source
      10                 :            :  * directory for licensing information.
      11                 :            :  * ****************************************************************************
      12                 :            :  *
      13                 :            :  * Implementation of techniques for eliminating regular expressions.
      14                 :            :  */
      15                 :            : 
      16                 :            : #include "theory/strings/regexp_elim.h"
      17                 :            : 
      18                 :            : #include "expr/bound_var_manager.h"
      19                 :            : #include "options/strings_options.h"
      20                 :            : #include "proof/proof_node_manager.h"
      21                 :            : #include "smt/env.h"
      22                 :            : #include "theory/rewriter.h"
      23                 :            : #include "theory/strings/regexp_entail.h"
      24                 :            : #include "theory/strings/theory_strings_utils.h"
      25                 :            : #include "theory/strings/word.h"
      26                 :            : #include "util/rational.h"
      27                 :            : #include "util/string.h"
      28                 :            : 
      29                 :            : using namespace cvc5::internal::kind;
      30                 :            : 
      31                 :            : namespace cvc5::internal {
      32                 :            : namespace theory {
      33                 :            : namespace strings {
      34                 :            : 
      35                 :            : /**
      36                 :            :  * Attributes used for constructing unique bound variables. The following
      37                 :            :  * attributes are used to construct (deterministic) bound variables for
      38                 :            :  * eliminations within eliminateConcat and eliminateStar respectively.
      39                 :            :  */
      40                 :            : struct ReElimConcatIndexAttributeId
      41                 :            : {
      42                 :            : };
      43                 :            : typedef expr::Attribute<ReElimConcatIndexAttributeId, Node>
      44                 :            :     ReElimConcatIndexAttribute;
      45                 :            : struct ReElimStarIndexAttributeId
      46                 :            : {
      47                 :            : };
      48                 :            : typedef expr::Attribute<ReElimStarIndexAttributeId, Node>
      49                 :            :     ReElimStarIndexAttribute;
      50                 :            : 
      51                 :      49172 : RegExpElimination::RegExpElimination(Env& env, bool isAgg, context::Context* c)
      52                 :            :     : EnvObj(env),
      53                 :            :       d_isAggressive(isAgg),
      54                 :      98344 :       d_epg(!env.isTheoryProofProducing()
      55                 :            :                 ? nullptr
      56                 :      49172 :                 : new EagerProofGenerator(env, c, "RegExpElimination::epg"))
      57                 :            : {
      58                 :      49172 : }
      59                 :            : 
      60                 :        310 : Node RegExpElimination::eliminate(Node atom, bool isAgg)
      61                 :            : {
      62 [ -  + ][ -  + ]:        310 :   Assert(atom.getKind() == Kind::STRING_IN_REGEXP);
                 [ -  - ]
      63         [ +  + ]:        310 :   if (atom[1].getKind() == Kind::REGEXP_CONCAT)
      64                 :            :   {
      65                 :        192 :     return eliminateConcat(atom, isAgg);
      66                 :            :   }
      67         [ +  - ]:        118 :   else if (atom[1].getKind() == Kind::REGEXP_STAR)
      68                 :            :   {
      69                 :        118 :     return eliminateStar(atom, isAgg);
      70                 :            :   }
      71                 :          0 :   return Node::null();
      72                 :            : }
      73                 :            : 
      74                 :        294 : TrustNode RegExpElimination::eliminateTrusted(Node atom)
      75                 :            : {
      76                 :        588 :   Node eatom = eliminate(atom, d_isAggressive);
      77         [ +  + ]:        294 :   if (!eatom.isNull())
      78                 :            :   {
      79                 :            :     // Currently aggressive doesnt work due to fresh bound variables
      80 [ +  + ][ +  + ]:        218 :     if (isProofEnabled() && !d_isAggressive)
                 [ +  + ]
      81                 :            :     {
      82                 :         20 :       ProofNodeManager* pnm = d_env.getProofNodeManager();
      83                 :         40 :       Node eq = atom.eqNode(eatom);
      84                 :         40 :       Node aggn = NodeManager::currentNM()->mkConst(d_isAggressive);
      85                 :            :       std::shared_ptr<ProofNode> pn =
      86                 :        100 :           pnm->mkNode(ProofRule::MACRO_RE_ELIM, {}, {atom, aggn}, eq);
      87                 :         20 :       d_epg->setProofFor(eq, pn);
      88         [ +  - ]:         20 :       return TrustNode::mkTrustRewrite(atom, eatom, d_epg.get());
      89                 :            :     }
      90                 :        198 :     return TrustNode::mkTrustRewrite(atom, eatom, nullptr);
      91                 :            :   }
      92                 :         76 :   return TrustNode::null();
      93                 :            : }
      94                 :            : 
      95                 :        192 : Node RegExpElimination::eliminateConcat(Node atom, bool isAgg)
      96                 :            : {
      97                 :        192 :   NodeManager* nm = NodeManager::currentNM();
      98                 :        192 :   BoundVarManager* bvm = nm->getBoundVarManager();
      99                 :        384 :   Node x = atom[0];
     100                 :        384 :   Node lenx = nm->mkNode(Kind::STRING_LENGTH, x);
     101                 :        384 :   Node re = atom[1];
     102                 :        384 :   Node zero = nm->mkConstInt(Rational(0));
     103                 :        384 :   std::vector<Node> children;
     104                 :        192 :   utils::getConcat(re, children);
     105                 :            : 
     106                 :            :   // If it can be reduced to memberships in fixed length regular expressions.
     107                 :            :   // This includes concatenations where at most one child is of the form
     108                 :            :   // (re.* re.allchar), which we abbreviate _* below, and all other children
     109                 :            :   // have a fixed length.
     110                 :            :   // The intuition why this is a "non-aggressive" rewrite is that membership
     111                 :            :   // into fixed length regular expressions are easy to handle.
     112                 :            :   // the index of _* in re
     113                 :        192 :   unsigned pivotIndex = 0;
     114                 :        192 :   bool hasPivotIndex = false;
     115                 :        192 :   bool hasFixedLength = true;
     116                 :        384 :   std::vector<Node> childLengths;
     117                 :        384 :   std::vector<Node> childLengthsPostPivot;
     118         [ +  + ]:        872 :   for (unsigned i = 0, size = children.size(); i < size; i++)
     119                 :            :   {
     120                 :       1360 :     Node c = children[i];
     121                 :       1360 :     Node fl = RegExpEntail::getFixedLengthForRegexp(c);
     122         [ +  + ]:        680 :     if (fl.isNull())
     123                 :            :     {
     124         [ +  + ]:        208 :       if (!hasPivotIndex && c.getKind() == Kind::REGEXP_STAR
     125 [ +  + ][ +  + ]:        464 :           && c[0].getKind() == Kind::REGEXP_ALLCHAR)
         [ +  + ][ +  + ]
                 [ -  - ]
     126                 :            :       {
     127                 :         40 :         hasPivotIndex = true;
     128                 :         40 :         pivotIndex = i;
     129                 :            :         // zero is used in sum below and is used for concat-fixed-len
     130                 :         40 :         fl = zero;
     131                 :            :       }
     132                 :            :       else
     133                 :            :       {
     134                 :        216 :         hasFixedLength = false;
     135                 :            :       }
     136                 :            :     }
     137         [ +  + ]:        680 :     if (!fl.isNull())
     138                 :            :     {
     139                 :        464 :       childLengths.push_back(fl);
     140         [ +  + ]:        464 :       if (hasPivotIndex)
     141                 :            :       {
     142                 :        190 :         childLengthsPostPivot.push_back(fl);
     143                 :            :       }
     144                 :            :     }
     145                 :            :   }
     146                 :        192 :   Node lenSum = childLengths.size() > 1
     147                 :            :                     ? nm->mkNode(Kind::ADD, childLengths)
     148 [ +  + ][ +  + ]:        384 :                     : (childLengths.empty() ? zero : childLengths[0]);
     149                 :            :   // if we have a fixed length
     150         [ +  + ]:        192 :   if (hasFixedLength)
     151                 :            :   {
     152 [ -  + ][ -  + ]:         34 :     Assert(re.getNumChildren() == children.size());
                 [ -  - ]
     153                 :         68 :     std::vector<Node> conc;
     154                 :         34 :     conc.push_back(
     155         [ +  + ]:         68 :         nm->mkNode(hasPivotIndex ? Kind::GEQ : Kind::EQUAL, lenx, lenSum));
     156                 :         68 :     Node currEnd = zero;
     157         [ +  + ]:        222 :     for (unsigned i = 0, size = childLengths.size(); i < size; i++)
     158                 :            :     {
     159 [ +  + ][ +  + ]:        188 :       if (hasPivotIndex && i == pivotIndex)
     160                 :            :       {
     161                 :          4 :         Node ppSum = childLengthsPostPivot.size() == 1
     162                 :          0 :                          ? childLengthsPostPivot[0]
     163         [ -  + ]:          4 :                          : nm->mkNode(Kind::ADD, childLengthsPostPivot);
     164                 :          4 :         currEnd = nm->mkNode(Kind::SUB, lenx, ppSum);
     165                 :            :       }
     166                 :            :       else
     167                 :            :       {
     168                 :            :         Node curr =
     169                 :        368 :             nm->mkNode(Kind::STRING_SUBSTR, x, currEnd, childLengths[i]);
     170                 :            :         // We do not need to include memberships of the form
     171                 :            :         //   (str.substr x n 1) in re.allchar
     172                 :            :         // since we know that by construction, n < len( x ).
     173         [ +  + ]:        184 :         if (re[i].getKind() != Kind::REGEXP_ALLCHAR)
     174                 :            :         {
     175                 :        306 :           Node currMem = nm->mkNode(Kind::STRING_IN_REGEXP, curr, re[i]);
     176                 :        102 :           conc.push_back(currMem);
     177                 :            :         }
     178                 :        184 :         currEnd = nm->mkNode(Kind::ADD, currEnd, childLengths[i]);
     179                 :            :       }
     180                 :            :     }
     181                 :         34 :     Node res = nm->mkNode(Kind::AND, conc);
     182                 :            :     // For example:
     183                 :            :     //   x in re.++(re.union(re.range("A", "J"), re.range("N", "Z")), "AB") -->
     184                 :            :     //   len( x ) = 3 ^
     185                 :            :     //   substr(x,0,1) in re.union(re.range("A", "J"), re.range("N", "Z")) ^
     186                 :            :     //   substr(x,1,2) in "AB"
     187                 :            :     // An example with a pivot index:
     188                 :            :     //   x in re.++( "AB" ++ _* ++ "C" ) -->
     189                 :            :     //   len( x ) >= 3 ^
     190                 :            :     //   substr( x, 0, 2 ) in "AB" ^
     191                 :            :     //   substr( x, len( x ) - 1, 1 ) in "C"
     192                 :         34 :     return returnElim(atom, res, "concat-fixed-len");
     193                 :            :   }
     194                 :            : 
     195                 :            :   // memberships of the form x in re.++ * s1 * ... * sn *, where * are
     196                 :            :   // any number of repetitions (exact or indefinite) of re.allchar.
     197         [ +  - ]:        158 :   Trace("re-elim-debug") << "Try re concat with gaps " << atom << std::endl;
     198                 :        158 :   bool onlySigmasAndConsts = true;
     199                 :        316 :   std::vector<Node> sep_children;
     200                 :        316 :   std::vector<unsigned> gap_minsize;
     201                 :        316 :   std::vector<bool> gap_exact;
     202                 :            :   // the first gap is initially strict zero
     203                 :        158 :   gap_minsize.push_back(0);
     204                 :        158 :   gap_exact.push_back(true);
     205         [ +  + ]:        432 :   for (const Node& c : children)
     206                 :            :   {
     207         [ +  - ]:        396 :     Trace("re-elim-debug") << "  " << c << std::endl;
     208                 :        396 :     onlySigmasAndConsts = false;
     209         [ +  + ]:        396 :     if (c.getKind() == Kind::STRING_TO_REGEXP)
     210                 :            :     {
     211                 :        178 :       onlySigmasAndConsts = true;
     212                 :        178 :       sep_children.push_back(c[0]);
     213                 :            :       // the next gap is initially strict zero
     214                 :        178 :       gap_minsize.push_back(0);
     215                 :        178 :       gap_exact.push_back(true);
     216                 :            :     }
     217                 :        654 :     else if (c.getKind() == Kind::REGEXP_STAR
     218 [ +  + ][ +  + ]:        218 :              && c[0].getKind() == Kind::REGEXP_ALLCHAR)
         [ +  + ][ +  + ]
                 [ -  - ]
     219                 :            :     {
     220                 :            :       // found a gap of any size
     221                 :         84 :       onlySigmasAndConsts = true;
     222                 :         84 :       gap_exact[gap_exact.size() - 1] = false;
     223                 :            :     }
     224         [ +  + ]:        134 :     else if (c.getKind() == Kind::REGEXP_ALLCHAR)
     225                 :            :     {
     226                 :            :       // add one to the minimum size of the gap
     227                 :         12 :       onlySigmasAndConsts = true;
     228                 :         12 :       gap_minsize[gap_minsize.size() - 1]++;
     229                 :            :     }
     230         [ +  + ]:        396 :     if (!onlySigmasAndConsts)
     231                 :            :     {
     232         [ +  - ]:        122 :       Trace("re-elim-debug") << "...cannot handle " << c << std::endl;
     233                 :        122 :       break;
     234                 :            :     }
     235                 :            :   }
     236                 :            :   // we should always rewrite concatenations that are purely re.allchar
     237                 :            :   // and re.*( re.allchar ).
     238 [ +  + ][ -  + ]:        158 :   Assert(!onlySigmasAndConsts || !sep_children.empty());
         [ -  + ][ -  - ]
     239 [ +  + ][ +  - ]:        158 :   if (onlySigmasAndConsts && !sep_children.empty())
                 [ +  + ]
     240                 :            :   {
     241                 :         36 :     bool canProcess = true;
     242                 :         36 :     std::vector<Node> conj;
     243                 :            :     // The following constructs a set of constraints that encodes that a
     244                 :            :     // set of string terms are found, in order, in string x.
     245                 :            :     // prev_end stores the current (symbolic) index in x that we are
     246                 :            :     // searching.
     247                 :         36 :     Node prev_end = zero;
     248                 :            :     // the symbolic index we start searching, for each child in sep_children.
     249                 :         36 :     std::vector<Node> prev_ends;
     250                 :         36 :     unsigned gap_minsize_end = gap_minsize.back();
     251                 :         36 :     bool gap_exact_end = gap_exact.back();
     252                 :         36 :     std::vector<Node> non_greedy_find_vars;
     253         [ +  + ]:        144 :     for (unsigned i = 0, size = sep_children.size(); i < size; i++)
     254                 :            :     {
     255         [ +  + ]:        108 :       if (gap_minsize[i] > 0)
     256                 :            :       {
     257                 :            :         // the gap to this child is at least gap_minsize[i]
     258                 :         24 :         prev_end = nm->mkNode(
     259                 :         36 :             Kind::ADD, prev_end, nm->mkConstInt(Rational(gap_minsize[i])));
     260                 :            :       }
     261                 :        108 :       prev_ends.push_back(prev_end);
     262                 :        108 :       Node sc = sep_children[i];
     263                 :        108 :       Node lensc = nm->mkNode(Kind::STRING_LENGTH, sc);
     264         [ +  + ]:        108 :       if (gap_exact[i])
     265                 :            :       {
     266                 :            :         // if the gap is exact, it is a substring constraint
     267                 :         72 :         Node curr = prev_end;
     268                 :         72 :         Node ss = nm->mkNode(Kind::STRING_SUBSTR, x, curr, lensc);
     269                 :         36 :         conj.push_back(ss.eqNode(sc));
     270                 :         36 :         prev_end = nm->mkNode(Kind::ADD, curr, lensc);
     271                 :            :       }
     272                 :            :       else
     273                 :            :       {
     274                 :            :         // otherwise, we can use indexof to represent some next occurrence
     275 [ +  + ][ -  + ]:         72 :         if (gap_exact[i + 1] && i + 1 != size)
                 [ -  + ]
     276                 :            :         {
     277         [ -  - ]:          0 :           if (!isAgg)
     278                 :            :           {
     279                 :          0 :             canProcess = false;
     280                 :          0 :             break;
     281                 :            :           }
     282                 :            :           // if the gap after this one is strict, we need a non-greedy find
     283                 :            :           // thus, we add a symbolic constant
     284                 :            :           Node cacheVal =
     285                 :          0 :               BoundVarManager::getCacheValue(atom, nm->mkConstInt(Rational(i)));
     286                 :          0 :           TypeNode intType = nm->integerType();
     287                 :            :           Node k =
     288                 :          0 :               bvm->mkBoundVar<ReElimConcatIndexAttribute>(cacheVal, intType);
     289                 :          0 :           non_greedy_find_vars.push_back(k);
     290                 :          0 :           prev_end = nm->mkNode(Kind::ADD, prev_end, k);
     291                 :            :         }
     292                 :        216 :         Node curr = nm->mkNode(Kind::STRING_INDEXOF, x, sc, prev_end);
     293                 :        144 :         Node idofFind = curr.eqNode(nm->mkConstInt(Rational(-1))).negate();
     294                 :         72 :         conj.push_back(idofFind);
     295                 :         72 :         prev_end = nm->mkNode(Kind::ADD, curr, lensc);
     296                 :            :       }
     297                 :            :     }
     298                 :            : 
     299         [ +  - ]:         36 :     if (canProcess)
     300                 :            :     {
     301                 :            :       // since sep_children is non-empty, conj is non-empty
     302 [ -  + ][ -  + ]:         36 :       Assert(!conj.empty());
                 [ -  - ]
     303                 :            :       // Process the last gap, if necessary.
     304                 :            :       // Notice that if the last gap is not exact and its minsize is zero,
     305                 :            :       // then the last indexof/substr constraint entails the following
     306                 :            :       // constraint, so it is not necessary to add.
     307                 :            :       // Below, we may write "A" for (str.to.re "A") and _ for re.allchar:
     308                 :         72 :       Node cEnd = nm->mkConstInt(Rational(gap_minsize_end));
     309         [ +  + ]:         36 :       if (gap_exact_end)
     310                 :            :       {
     311 [ -  + ][ -  + ]:         24 :         Assert(!sep_children.empty());
                 [ -  - ]
     312                 :            :         // if it is strict, it corresponds to a substr case.
     313                 :            :         // For example:
     314                 :            :         //     x in (re.++ "A" (re.* _) "B" _ _) --->
     315                 :            :         //        ... ^ "B" = substr( x, len( x ) - 3, 1 )  ^ ...
     316                 :         48 :         Node sc = sep_children.back();
     317                 :         48 :         Node lenSc = nm->mkNode(Kind::STRING_LENGTH, sc);
     318                 :            :         Node loc =
     319                 :         72 :             nm->mkNode(Kind::SUB, lenx, nm->mkNode(Kind::ADD, lenSc, cEnd));
     320                 :         72 :         Node scc = sc.eqNode(nm->mkNode(Kind::STRING_SUBSTR, x, loc, lenSc));
     321                 :            :         // We also must ensure that we fit. This constraint is necessary in
     322                 :            :         // addition to the constraint above. Take this example:
     323                 :            :         //     x in (re.++ "A" _ (re.* _) "B" _) --->
     324                 :            :         //       substr( x, 0, 1 ) = "A" ^             // find "A"
     325                 :            :         //       indexof( x, "B", 2 ) != -1 ^          // find "B" >=1 after "A"
     326                 :            :         //       substr( x, len(x)-2, 1 ) = "B" ^      // "B" is at end - 2.
     327                 :            :         //       indexof( x, "B", 2 ) <= len( x ) - 2
     328                 :            :         // The last constaint ensures that the second and third constraints
     329                 :            :         // may refer to the same "B". If it were not for the last constraint, it
     330                 :            :         // would have been the case than "ABB" would be a model for x, where
     331                 :            :         // the second constraint refers to the third position, and the third
     332                 :            :         // constraint refers to the second position.
     333                 :            :         //
     334                 :            :         // With respect to the above example, the following is an optimization.
     335                 :            :         // For that example, we instead produce:
     336                 :            :         //     x in (re.++ "A" _ (re.* _) "B" _) --->
     337                 :            :         //       substr( x, 0, 1 ) = "A" ^          // find "A"
     338                 :            :         //       substr( x, len(x)-2, 1 ) = "B" ^   // "B" is at end - 2
     339                 :            :         //       2 <= len( x ) - 2
     340                 :            :         // The intuition is that above, there are two constraints that insist
     341                 :            :         // that "B" is found, whereas we only need one. The last constraint
     342                 :            :         // above says that the "B" we find at end-2 can be found >=1 after
     343                 :            :         // the "A".
     344                 :         24 :         conj.pop_back();
     345                 :            :         Node fit = nm->mkNode(
     346                 :         48 :             gap_exact[sep_children.size() - 1] ? Kind::EQUAL : Kind::LEQ,
     347                 :         24 :             prev_ends.back(),
     348         [ -  + ]:        120 :             loc);
     349                 :            : 
     350                 :         24 :         conj.push_back(scc);
     351                 :         24 :         conj.push_back(fit);
     352                 :            :       }
     353         [ -  + ]:         12 :       else if (gap_minsize_end > 0)
     354                 :            :       {
     355                 :            :         // if it is non-strict, we are in a "greedy find" situtation where
     356                 :            :         // we just need to ensure that the next occurrence fits.
     357                 :            :         // For example:
     358                 :            :         //     x in (re.++ "A" (re.* _) "B" _ _ (re.* _)) --->
     359                 :            :         //        ... ^ indexof( x, "B", 1 ) + 2 <= len( x )
     360                 :            :         Node fit =
     361                 :          0 :             nm->mkNode(Kind::LEQ, nm->mkNode(Kind::ADD, prev_end, cEnd), lenx);
     362                 :          0 :         conj.push_back(fit);
     363                 :            :       }
     364                 :         36 :       Node res = nm->mkAnd(conj);
     365                 :            :       // process the non-greedy find variables
     366         [ -  + ]:         36 :       if (!non_greedy_find_vars.empty())
     367                 :            :       {
     368                 :          0 :         std::vector<Node> children2;
     369         [ -  - ]:          0 :         for (const Node& v : non_greedy_find_vars)
     370                 :            :         {
     371                 :            :           Node bound = nm->mkNode(Kind::AND,
     372                 :          0 :                                   nm->mkNode(Kind::LEQ, zero, v),
     373                 :          0 :                                   nm->mkNode(Kind::LT, v, lenx));
     374                 :          0 :           children2.push_back(bound);
     375                 :            :         }
     376                 :          0 :         children2.push_back(res);
     377                 :          0 :         Node body = nm->mkNode(Kind::AND, children2);
     378                 :          0 :         Node bvl = nm->mkNode(Kind::BOUND_VAR_LIST, non_greedy_find_vars);
     379                 :          0 :         res = utils::mkForallInternal(bvl, body.negate()).negate();
     380                 :            :       }
     381                 :            :       // must also give a minimum length requirement
     382                 :         36 :       res = nm->mkNode(Kind::AND, res, nm->mkNode(Kind::GEQ, lenx, lenSum));
     383                 :            :       // Examples of this elimination:
     384                 :            :       //   x in (re.++ "A" (re.* _) "B" (re.* _)) --->
     385                 :            :       //     substr(x,0,1)="A" ^ indexof(x,"B",1)!=-1
     386                 :            :       //   x in (re.++ (re.* _) "A" _ _ _ (re.* _) "B" _ _ (re.* _)) --->
     387                 :            :       //     indexof(x,"A",0)!=-1 ^
     388                 :            :       //     indexof( x, "B", indexof( x, "A", 0 ) + 1 + 3 ) != -1 ^
     389                 :            :       //     indexof( x, "B", indexof( x, "A", 0 ) + 1 + 3 )+1+2 <= len(x) ^
     390                 :            :       //     len(x) >= 7
     391                 :            : 
     392                 :            :       // An example of a non-greedy find:
     393                 :            :       //   x in re.++( re.*( _ ), "A", _, "B", re.*( _ ) ) --->
     394                 :            :       //     (exists k. 0 <= k < len( x ) ^
     395                 :            :       //               indexof( x, "A", k ) != -1 ^
     396                 :            :       //               substr( x, indexof( x, "A", k )+2, 1 ) = "B") ^
     397                 :            :       //     len(x) >= 3
     398                 :         36 :       return returnElim(atom, res, "concat-with-gaps");
     399                 :            :     }
     400                 :            :   }
     401                 :            : 
     402         [ +  + ]:        122 :   if (!isAgg)
     403                 :            :   {
     404                 :         40 :     return Node::null();
     405                 :            :   }
     406                 :            :   // only aggressive rewrites below here
     407                 :            : 
     408                 :            :   // if the first or last child is constant string, we can split the membership
     409                 :            :   // into a conjunction of two memberships.
     410                 :        164 :   Node sStartIndex = zero;
     411                 :        164 :   Node sLength = lenx;
     412                 :        164 :   std::vector<Node> sConstraints;
     413                 :        164 :   std::vector<Node> rexpElimChildren;
     414                 :         82 :   unsigned nchildren = children.size();
     415 [ -  + ][ -  + ]:         82 :   Assert(nchildren > 1);
                 [ -  - ]
     416         [ +  + ]:        246 :   for (unsigned r = 0; r < 2; r++)
     417                 :            :   {
     418         [ +  + ]:        164 :     unsigned index = r == 0 ? 0 : nchildren - 1;
     419                 :        328 :     Node c = children[index];
     420         [ +  + ]:        164 :     if (c.getKind() == Kind::STRING_TO_REGEXP)
     421                 :            :     {
     422 [ +  + ][ -  + ]:         74 :       Assert(children[index + (r == 0 ? 1 : -1)].getKind()
         [ -  + ][ -  - ]
     423                 :            :              != Kind::STRING_TO_REGEXP);
     424                 :        148 :       Node s = c[0];
     425                 :        148 :       Node lens = nm->mkNode(Kind::STRING_LENGTH, s);
     426                 :        160 :       Node sss = r == 0 ? zero : nm->mkNode(Kind::SUB, lenx, lens);
     427                 :        148 :       Node ss = nm->mkNode(Kind::STRING_SUBSTR, x, sss, lens);
     428                 :         74 :       sConstraints.push_back(ss.eqNode(s));
     429         [ +  + ]:         74 :       if (r == 0)
     430                 :            :       {
     431                 :         62 :         sStartIndex = lens;
     432                 :            :       }
     433 [ +  - ][ +  + ]:         12 :       else if (r == 1 && sConstraints.size() == 2)
                 [ +  + ]
     434                 :            :       {
     435                 :            :         // first and last children cannot overlap
     436                 :          6 :         Node bound = nm->mkNode(Kind::GEQ, sss, sStartIndex);
     437                 :          2 :         sConstraints.push_back(bound);
     438                 :            :       }
     439                 :         74 :       sLength = nm->mkNode(Kind::SUB, sLength, lens);
     440                 :            :     }
     441 [ +  + ][ +  + ]:        164 :     if (r == 1 && !sConstraints.empty())
                 [ +  + ]
     442                 :            :     {
     443                 :            :       // add the middle children
     444         [ +  + ]:         74 :       for (unsigned i = 1; i < (nchildren - 1); i++)
     445                 :            :       {
     446                 :          2 :         rexpElimChildren.push_back(children[i]);
     447                 :            :       }
     448                 :            :     }
     449         [ +  + ]:        164 :     if (c.getKind() != Kind::STRING_TO_REGEXP)
     450                 :            :     {
     451                 :         90 :       rexpElimChildren.push_back(c);
     452                 :            :     }
     453                 :            :   }
     454         [ +  + ]:         82 :   if (!sConstraints.empty())
     455                 :            :   {
     456                 :        216 :     Node ss = nm->mkNode(Kind::STRING_SUBSTR, x, sStartIndex, sLength);
     457 [ -  + ][ -  + ]:         72 :     Assert(!rexpElimChildren.empty());
                 [ -  - ]
     458                 :        144 :     Node regElim = utils::mkConcat(rexpElimChildren, nm->regExpType());
     459                 :         72 :     sConstraints.push_back(nm->mkNode(Kind::STRING_IN_REGEXP, ss, regElim));
     460                 :         72 :     Node ret = nm->mkNode(Kind::AND, sConstraints);
     461                 :            :     // e.g.
     462                 :            :     // x in re.++( "A", R ) ---> substr(x,0,1)="A" ^ substr(x,1,len(x)-1) in R
     463                 :         72 :     return returnElim(atom, ret, "concat-splice");
     464                 :            :   }
     465 [ -  + ][ -  + ]:         10 :   Assert(nchildren > 1);
                 [ -  - ]
     466         [ +  - ]:         20 :   for (unsigned i = 0; i < nchildren; i++)
     467                 :            :   {
     468         [ +  + ]:         20 :     if (children[i].getKind() == Kind::STRING_TO_REGEXP)
     469                 :            :     {
     470                 :         20 :       Node s = children[i][0];
     471                 :         20 :       Node lens = nm->mkNode(Kind::STRING_LENGTH, s);
     472                 :            :       // there exists an index in this string such that the substring is this
     473                 :         20 :       Node k;
     474                 :         20 :       std::vector<Node> echildren;
     475         [ -  + ]:         10 :       if (i == 0)
     476                 :            :       {
     477                 :          0 :         k = zero;
     478                 :            :       }
     479         [ -  + ]:         10 :       else if (i + 1 == nchildren)
     480                 :            :       {
     481                 :          0 :         k = nm->mkNode(Kind::SUB, lenx, lens);
     482                 :            :       }
     483                 :            :       else
     484                 :            :       {
     485                 :            :         Node cacheVal =
     486                 :         30 :             BoundVarManager::getCacheValue(atom, nm->mkConstInt(Rational(i)));
     487                 :         20 :         TypeNode intType = nm->integerType();
     488                 :         10 :         k = bvm->mkBoundVar<ReElimConcatIndexAttribute>(cacheVal, intType);
     489                 :            :         Node bound = nm->mkNode(
     490                 :            :             Kind::AND,
     491                 :         20 :             nm->mkNode(Kind::LEQ, zero, k),
     492                 :         50 :             nm->mkNode(Kind::LEQ, k, nm->mkNode(Kind::SUB, lenx, lens)));
     493                 :         10 :         echildren.push_back(bound);
     494                 :            :       }
     495                 :         30 :       Node substrEq = nm->mkNode(Kind::STRING_SUBSTR, x, k, lens).eqNode(s);
     496                 :         10 :       echildren.push_back(substrEq);
     497         [ +  - ]:         10 :       if (i > 0)
     498                 :            :       {
     499                 :         20 :         std::vector<Node> rprefix;
     500                 :         10 :         rprefix.insert(rprefix.end(), children.begin(), children.begin() + i);
     501                 :         20 :         Node rpn = utils::mkConcat(rprefix, nm->regExpType());
     502                 :            :         Node substrPrefix =
     503                 :            :             nm->mkNode(Kind::STRING_IN_REGEXP,
     504                 :         20 :                        nm->mkNode(Kind::STRING_SUBSTR, x, zero, k),
     505                 :         40 :                        rpn);
     506                 :         10 :         echildren.push_back(substrPrefix);
     507                 :            :       }
     508         [ +  - ]:         10 :       if (i + 1 < nchildren)
     509                 :            :       {
     510                 :         20 :         std::vector<Node> rsuffix;
     511                 :         10 :         rsuffix.insert(rsuffix.end(), children.begin() + i + 1, children.end());
     512                 :         20 :         Node rps = utils::mkConcat(rsuffix, nm->regExpType());
     513                 :         30 :         Node ks = nm->mkNode(Kind::ADD, k, lens);
     514                 :            :         Node substrSuffix = nm->mkNode(
     515                 :            :             Kind::STRING_IN_REGEXP,
     516                 :         20 :             nm->mkNode(
     517                 :         20 :                 Kind::STRING_SUBSTR, x, ks, nm->mkNode(Kind::SUB, lenx, ks)),
     518                 :         40 :             rps);
     519                 :         10 :         echildren.push_back(substrSuffix);
     520                 :            :       }
     521                 :         10 :       Node body = nm->mkNode(Kind::AND, echildren);
     522         [ +  - ]:         10 :       if (k.getKind() == Kind::BOUND_VARIABLE)
     523                 :            :       {
     524                 :         10 :         Node bvl = nm->mkNode(Kind::BOUND_VAR_LIST, k);
     525                 :         10 :         body = utils::mkForallInternal(bvl, body.negate()).negate();
     526                 :            :       }
     527                 :            :       // e.g. x in re.++( R1, "AB", R2 ) --->
     528                 :            :       //  exists k.
     529                 :            :       //    0 <= k <= (len(x)-2) ^
     530                 :            :       //    substr( x, k, 2 ) = "AB" ^
     531                 :            :       //    substr( x, 0, k ) in R1 ^
     532                 :            :       //    substr( x, k+2, len(x)-(k+2) ) in R2
     533                 :         10 :       return returnElim(atom, body, "concat-find");
     534                 :            :     }
     535                 :            :   }
     536                 :          0 :   return Node::null();
     537                 :            : }
     538                 :            : 
     539                 :        118 : Node RegExpElimination::eliminateStar(Node atom, bool isAgg)
     540                 :            : {
     541         [ +  + ]:        118 :   if (!isAgg)
     542                 :            :   {
     543                 :         22 :     return Node::null();
     544                 :            :   }
     545                 :            :   // only aggressive rewrites below here
     546                 :            : 
     547                 :         96 :   NodeManager* nm = NodeManager::currentNM();
     548                 :         96 :   BoundVarManager* bvm = nm->getBoundVarManager();
     549                 :        192 :   Node x = atom[0];
     550                 :        192 :   Node lenx = nm->mkNode(Kind::STRING_LENGTH, x);
     551                 :        192 :   Node re = atom[1];
     552                 :        192 :   Node zero = nm->mkConstInt(Rational(0));
     553                 :            :   // for regular expression star,
     554                 :            :   // if the period is a fixed constant, we can turn it into a bounded
     555                 :            :   // quantifier
     556                 :        192 :   std::vector<Node> disj;
     557         [ +  + ]:         96 :   if (re[0].getKind() == Kind::REGEXP_UNION)
     558                 :            :   {
     559         [ +  + ]:         30 :     for (const Node& r : re[0])
     560                 :            :     {
     561                 :         20 :       disj.push_back(r);
     562                 :            :     }
     563                 :            :   }
     564                 :            :   else
     565                 :            :   {
     566                 :         86 :     disj.push_back(re[0]);
     567                 :            :   }
     568                 :         96 :   bool lenOnePeriod = true;
     569                 :        192 :   std::vector<Node> char_constraints;
     570                 :        192 :   TypeNode intType = nm->integerType();
     571                 :        288 :   Node index = bvm->mkBoundVar<ReElimStarIndexAttribute>(atom, intType);
     572                 :            :   Node substr_ch =
     573                 :        288 :       nm->mkNode(Kind::STRING_SUBSTR, x, index, nm->mkConstInt(Rational(1)));
     574                 :            :   // handle the case where it is purely characters
     575         [ +  + ]:        148 :   for (const Node& r : disj)
     576                 :            :   {
     577 [ -  + ][ -  + ]:        106 :     Assert(r.getKind() != Kind::REGEXP_UNION);
                 [ -  - ]
     578 [ -  + ][ -  + ]:        106 :     Assert(r.getKind() != Kind::REGEXP_ALLCHAR);
                 [ -  - ]
     579                 :        106 :     lenOnePeriod = false;
     580                 :            :     // lenOnePeriod is true if this regular expression is a single character
     581                 :            :     // regular expression
     582         [ +  + ]:        106 :     if (r.getKind() == Kind::STRING_TO_REGEXP)
     583                 :            :     {
     584                 :        172 :       Node s = r[0];
     585 [ +  + ][ +  + ]:         86 :       if (s.isConst() && s.getConst<String>().size() == 1)
                 [ +  + ]
     586                 :            :       {
     587                 :         42 :         lenOnePeriod = true;
     588                 :            :       }
     589                 :            :     }
     590         [ +  + ]:         20 :     else if (r.getKind() == Kind::REGEXP_RANGE)
     591                 :            :     {
     592                 :         10 :       lenOnePeriod = true;
     593                 :            :     }
     594         [ +  + ]:        106 :     if (!lenOnePeriod)
     595                 :            :     {
     596                 :         54 :       break;
     597                 :            :     }
     598                 :            :     else
     599                 :            :     {
     600                 :        156 :       Node regexp_ch = nm->mkNode(Kind::STRING_IN_REGEXP, substr_ch, r);
     601                 :         52 :       char_constraints.push_back(regexp_ch);
     602                 :            :     }
     603                 :            :   }
     604         [ +  + ]:         96 :   if (lenOnePeriod)
     605                 :            :   {
     606 [ -  + ][ -  + ]:         42 :     Assert(!char_constraints.empty());
                 [ -  - ]
     607                 :            :     Node bound = nm->mkNode(Kind::AND,
     608                 :         84 :                             nm->mkNode(Kind::LEQ, zero, index),
     609                 :        210 :                             nm->mkNode(Kind::LT, index, lenx));
     610                 :         42 :     Node conc = char_constraints.size() == 1
     611                 :         42 :                     ? char_constraints[0]
     612         [ +  - ]:        126 :                     : nm->mkNode(Kind::OR, char_constraints);
     613                 :        126 :     Node body = nm->mkNode(Kind::OR, bound.negate(), conc);
     614                 :         84 :     Node bvl = nm->mkNode(Kind::BOUND_VAR_LIST, index);
     615                 :         84 :     Node res = utils::mkForallInternal(bvl, body);
     616                 :            :     // e.g.
     617                 :            :     //   x in (re.* (re.union "A" "B" )) --->
     618                 :            :     //   forall k. 0<=k<len(x) => (substr(x,k,1) in "A" OR substr(x,k,1) in "B")
     619                 :         42 :     return returnElim(atom, res, "star-char");
     620                 :            :   }
     621                 :            :   // otherwise, for stars of constant length these are periodic
     622         [ +  + ]:         54 :   if (disj.size() == 1)
     623                 :            :   {
     624                 :         44 :     Node r = disj[0];
     625         [ +  - ]:         44 :     if (r.getKind() == Kind::STRING_TO_REGEXP)
     626                 :            :     {
     627                 :         44 :       Node s = r[0];
     628         [ +  + ]:         44 :       if (s.isConst())
     629                 :            :       {
     630                 :        120 :         Node lens = nm->mkConstInt(Word::getLength(s));
     631 [ -  + ][ -  + ]:         40 :         Assert(lens.getConst<Rational>().sgn() > 0);
                 [ -  - ]
     632                 :         80 :         std::vector<Node> conj;
     633                 :            :         // lens is a positive constant, so it is safe to use total div/mod here.
     634                 :            :         Node bound = nm->mkNode(
     635                 :            :             Kind::AND,
     636                 :         80 :             nm->mkNode(Kind::LEQ, zero, index),
     637                 :         80 :             nm->mkNode(Kind::LT,
     638                 :            :                        index,
     639                 :        280 :                        nm->mkNode(Kind::INTS_DIVISION_TOTAL, lenx, lens)));
     640                 :        120 :         Node conc = nm->mkNode(Kind::STRING_SUBSTR,
     641                 :            :                                x,
     642                 :         80 :                                nm->mkNode(Kind::MULT, index, lens),
     643                 :            :                                lens)
     644                 :         80 :                         .eqNode(s);
     645                 :        120 :         Node body = nm->mkNode(Kind::OR, bound.negate(), conc);
     646                 :         80 :         Node bvl = nm->mkNode(Kind::BOUND_VAR_LIST, index);
     647                 :         80 :         Node res = utils::mkForallInternal(bvl, body);
     648                 :        120 :         res = nm->mkNode(
     649                 :            :             Kind::AND,
     650                 :         80 :             nm->mkNode(Kind::INTS_MODULUS_TOTAL, lenx, lens).eqNode(zero),
     651                 :         40 :             res);
     652                 :            :         // e.g.
     653                 :            :         //    x in ("abc")* --->
     654                 :            :         //    forall k. 0 <= k < (len( x ) div 3) => substr(x,3*k,3) = "abc" ^
     655                 :            :         //    len(x) mod 3 = 0
     656                 :         40 :         return returnElim(atom, res, "star-constant");
     657                 :            :       }
     658                 :            :     }
     659                 :            :   }
     660                 :         14 :   return Node::null();
     661                 :            : }
     662                 :            : 
     663                 :        234 : Node RegExpElimination::returnElim(Node atom, Node atomElim, const char* id)
     664                 :            : {
     665         [ +  - ]:        468 :   Trace("re-elim") << "re-elim: " << atom << " to " << atomElim << " by " << id
     666                 :        234 :                    << "." << std::endl;
     667                 :        234 :   return atomElim;
     668                 :            : }
     669                 :        218 : bool RegExpElimination::isProofEnabled() const
     670                 :            : {
     671                 :        218 :   return d_env.isTheoryProofProducing();
     672                 :            : }
     673                 :            : 
     674                 :            : }  // namespace strings
     675                 :            : }  // namespace theory
     676                 :            : }  // namespace cvc5::internal

Generated by: LCOV version 1.14