GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/infer_proof_cons.cpp Lines: 41 619 6.6 %
Date: 2021-09-29 Branches: 66 2528 2.6 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Gereon Kremer, Aina Niemetz
4
 *
5
 * This file is part of the cvc5 project.
6
 *
7
 * Copyright (c) 2009-2021 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 inference to proof conversion.
14
 */
15
16
#include "theory/strings/infer_proof_cons.h"
17
18
#include "expr/skolem_manager.h"
19
#include "options/smt_options.h"
20
#include "options/strings_options.h"
21
#include "proof/proof_node_manager.h"
22
#include "theory/builtin/proof_checker.h"
23
#include "theory/rewriter.h"
24
#include "theory/strings/regexp_operation.h"
25
#include "theory/strings/theory_strings_utils.h"
26
#include "util/statistics_registry.h"
27
28
using namespace cvc5::kind;
29
30
namespace cvc5 {
31
namespace theory {
32
namespace strings {
33
34
60
InferProofCons::InferProofCons(context::Context* c,
35
                               ProofNodeManager* pnm,
36
60
                               SequencesStatistics& statistics)
37
60
    : d_pnm(pnm), d_lazyFactMap(c), d_statistics(statistics)
38
{
39
60
  Assert(d_pnm != nullptr);
40
60
}
41
42
6639
void InferProofCons::notifyFact(const InferInfo& ii)
43
{
44
13012
  Node fact = ii.d_conc;
45
13278
  Trace("strings-ipc-debug")
46
6639
      << "InferProofCons::notifyFact: " << ii << std::endl;
47
6639
  if (d_lazyFactMap.find(fact) != d_lazyFactMap.end())
48
  {
49
254
    Trace("strings-ipc-debug") << "...duplicate!" << std::endl;
50
254
    return;
51
  }
52
12758
  Node symFact = CDProof::getSymmFact(fact);
53
6385
  if (!symFact.isNull() && d_lazyFactMap.find(symFact) != d_lazyFactMap.end())
54
  {
55
12
    Trace("strings-ipc-debug") << "...duplicate (sym)!" << std::endl;
56
12
    return;
57
  }
58
12746
  std::shared_ptr<InferInfo> iic = std::make_shared<InferInfo>(ii);
59
6373
  d_lazyFactMap.insert(ii.d_conc, iic);
60
}
61
62
bool InferProofCons::addProofTo(CDProof* pf,
63
                                Node conc,
64
                                InferenceId infer,
65
                                bool isRev,
66
                                const std::vector<Node>& exp)
67
{
68
  // now go back and convert it to proof steps and add to proof
69
  bool useBuffer = false;
70
  ProofStep ps;
71
  TheoryProofStepBuffer psb(pf->getManager()->getChecker());
72
  // run the conversion
73
  convert(infer, isRev, conc, exp, ps, psb, useBuffer);
74
  // make the proof based on the step or the buffer
75
  if (useBuffer)
76
  {
77
    if (!pf->addSteps(psb))
78
    {
79
      return false;
80
    }
81
  }
82
  else
83
  {
84
    if (!pf->addStep(conc, ps))
85
    {
86
      return false;
87
    }
88
  }
89
  return true;
90
}
91
92
3145
void InferProofCons::packArgs(Node conc,
93
                              InferenceId infer,
94
                              bool isRev,
95
                              const std::vector<Node>& exp,
96
                              std::vector<Node>& args)
97
{
98
3145
  args.push_back(conc);
99
3145
  args.push_back(mkInferenceIdNode(infer));
100
3145
  args.push_back(NodeManager::currentNM()->mkConst(isRev));
101
  // The vector exp is stored as arguments; its flatten form are premises. We
102
  // need both since the grouping of exp is important, e.g. { (and a b), c }
103
  // is different from { a, b, c } in the convert routine, since positions
104
  // of formulas in exp have special meaning.
105
3145
  args.insert(args.end(), exp.begin(), exp.end());
106
3145
}
107
108
bool InferProofCons::unpackArgs(const std::vector<Node>& args,
109
                                Node& conc,
110
                                InferenceId& infer,
111
                                bool& isRev,
112
                                std::vector<Node>& exp)
113
{
114
  Assert(args.size() >= 3);
115
  conc = args[0];
116
  if (!getInferenceId(args[1], infer))
117
  {
118
    return false;
119
  }
120
  isRev = args[2].getConst<bool>();
121
  exp.insert(exp.end(), args.begin() + 3, args.end());
122
  return true;
123
}
124
125
void InferProofCons::convert(InferenceId infer,
126
                             bool isRev,
127
                             Node conc,
128
                             const std::vector<Node>& exp,
129
                             ProofStep& ps,
130
                             TheoryProofStepBuffer& psb,
131
                             bool& useBuffer)
132
{
133
  // by default, don't use the buffer
134
  useBuffer = false;
135
  // Must flatten children with respect to AND to be ready to explain.
136
  // We store the index where each flattened vector begins, since some
137
  // explanations are grouped together using AND.
138
  std::vector<size_t> startExpIndex;
139
  for (const Node& ec : exp)
140
  {
141
    // store the index in the flattened vector
142
    startExpIndex.push_back(ps.d_children.size());
143
    utils::flattenOp(AND, ec, ps.d_children);
144
  }
145
  // debug print
146
  if (Trace.isOn("strings-ipc-debug"))
147
  {
148
    Trace("strings-ipc-debug") << "InferProofCons::convert: " << infer
149
                               << (isRev ? " :rev " : " ") << conc << std::endl;
150
    for (const Node& ec : ps.d_children)
151
    {
152
      Trace("strings-ipc-debug") << "    e: " << ec << std::endl;
153
    }
154
  }
155
  // try to find a set of proof steps to incorporate into the buffer
156
  psb.clear();
157
  NodeManager* nm = NodeManager::currentNM();
158
  Node nodeIsRev = nm->mkConst(isRev);
159
  switch (infer)
160
  {
161
    // ========================== equal by substitution+rewriting
162
    case InferenceId::STRINGS_I_NORM_S:
163
    case InferenceId::STRINGS_I_CONST_MERGE:
164
    case InferenceId::STRINGS_I_NORM:
165
    case InferenceId::STRINGS_LEN_NORM:
166
    case InferenceId::STRINGS_NORMAL_FORM:
167
    case InferenceId::STRINGS_CODE_PROXY:
168
    {
169
      std::vector<Node> pcs = ps.d_children;
170
      Node pconc = conc;
171
      // purify core substitution proves conc from pconc if necessary,
172
      // we apply MACRO_SR_PRED_INTRO to prove pconc
173
      if (purifyCoreSubstitution(pconc, pcs, psb, false))
174
      {
175
        if (psb.applyPredIntro(pconc, pcs))
176
        {
177
          useBuffer = true;
178
        }
179
      }
180
    }
181
    break;
182
    // ========================== substitution + rewriting
183
    case InferenceId::STRINGS_RE_NF_CONFLICT:
184
    case InferenceId::STRINGS_EXTF:
185
    case InferenceId::STRINGS_EXTF_N:
186
    case InferenceId::STRINGS_EXTF_D:
187
    case InferenceId::STRINGS_EXTF_D_N:
188
    case InferenceId::STRINGS_I_CONST_CONFLICT:
189
    case InferenceId::STRINGS_UNIT_CONST_CONFLICT:
190
    {
191
      if (!ps.d_children.empty())
192
      {
193
        std::vector<Node> exps(ps.d_children.begin(), ps.d_children.end() - 1);
194
        Node src = ps.d_children[ps.d_children.size() - 1];
195
        if (psb.applyPredTransform(src, conc, exps))
196
        {
197
          useBuffer = true;
198
        }
199
      }
200
      else
201
      {
202
        // use the predicate version?
203
        ps.d_args.push_back(conc);
204
        ps.d_rule = PfRule::MACRO_SR_PRED_INTRO;
205
      }
206
    }
207
    break;
208
    // ========================== rewrite pred
209
    case InferenceId::STRINGS_EXTF_EQ_REW:
210
    {
211
      // the last child is the predicate we are operating on, move to front
212
      Node src = ps.d_children[ps.d_children.size() - 1];
213
      std::vector<Node> expe(ps.d_children.begin(), ps.d_children.end() - 1);
214
      // start with a default rewrite
215
      Node mainEqSRew = psb.applyPredElim(src, expe);
216
      if (mainEqSRew == conc)
217
      {
218
        useBuffer = true;
219
        break;
220
      }
221
      // may need the "extended equality rewrite"
222
      Node mainEqSRew2 = psb.applyPredElim(mainEqSRew,
223
                                           {},
224
                                           MethodId::SB_DEFAULT,
225
                                           MethodId::SBA_SEQUENTIAL,
226
                                           MethodId::RW_REWRITE_EQ_EXT);
227
      if (mainEqSRew2 == conc)
228
      {
229
        useBuffer = true;
230
        break;
231
      }
232
      // rewrite again with default rewriter
233
      Node mainEqSRew3 = psb.applyPredElim(mainEqSRew2, {});
234
      useBuffer = (mainEqSRew3 == conc);
235
    }
236
    break;
237
    // ========================== substitution+rewriting, CONCAT_EQ, ...
238
    case InferenceId::STRINGS_F_CONST:
239
    case InferenceId::STRINGS_F_UNIFY:
240
    case InferenceId::STRINGS_F_ENDPOINT_EMP:
241
    case InferenceId::STRINGS_F_ENDPOINT_EQ:
242
    case InferenceId::STRINGS_F_NCTN:
243
    case InferenceId::STRINGS_N_EQ_CONF:
244
    case InferenceId::STRINGS_N_CONST:
245
    case InferenceId::STRINGS_N_UNIFY:
246
    case InferenceId::STRINGS_N_ENDPOINT_EMP:
247
    case InferenceId::STRINGS_N_ENDPOINT_EQ:
248
    case InferenceId::STRINGS_N_NCTN:
249
    case InferenceId::STRINGS_SSPLIT_CST_PROP:
250
    case InferenceId::STRINGS_SSPLIT_VAR_PROP:
251
    case InferenceId::STRINGS_SSPLIT_CST:
252
    case InferenceId::STRINGS_SSPLIT_VAR:
253
    {
254
      Trace("strings-ipc-core") << "Generate core rule for " << infer
255
                                << " (rev=" << isRev << ")" << std::endl;
256
      // All of the above inferences have the form:
257
      //   (explanation for why t and s have the same prefix/suffix) ^
258
      //   t = s ^
259
      //   (length constraint)?
260
      // We call t=s the "main equality" below. The length constraint is
261
      // optional, which we split on below.
262
      size_t nchild = ps.d_children.size();
263
      size_t mainEqIndex = 0;
264
      bool mainEqIndexSet = false;
265
      // the length constraint
266
      std::vector<Node> lenConstraint;
267
      // these inferences have a length constraint as the last explain
268
      if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY
269
          || infer == InferenceId::STRINGS_SSPLIT_CST || infer == InferenceId::STRINGS_SSPLIT_VAR
270
          || infer == InferenceId::STRINGS_SSPLIT_VAR_PROP
271
          || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
272
      {
273
        if (exp.size() >= 2)
274
        {
275
          Assert(exp.size() <= startExpIndex.size());
276
          // The index of the "main" equality is the last equality before
277
          // the length explanation.
278
          mainEqIndex = startExpIndex[exp.size() - 1] - 1;
279
          mainEqIndexSet = true;
280
          // the remainder is the length constraint
281
          lenConstraint.insert(lenConstraint.end(),
282
                               ps.d_children.begin() + mainEqIndex + 1,
283
                               ps.d_children.end());
284
        }
285
      }
286
      else if (nchild >= 1)
287
      {
288
        // The index of the main equality is the last child.
289
        mainEqIndex = nchild - 1;
290
        mainEqIndexSet = true;
291
      }
292
      Node mainEq;
293
      if (mainEqIndexSet)
294
      {
295
        mainEq = ps.d_children[mainEqIndex];
296
        Trace("strings-ipc-core") << "Main equality " << mainEq << " at index "
297
                                  << mainEqIndex << std::endl;
298
      }
299
      if (mainEq.isNull() || mainEq.getKind() != EQUAL)
300
      {
301
        Trace("strings-ipc-core")
302
            << "...failed to find main equality" << std::endl;
303
        break;
304
      }
305
      // apply MACRO_SR_PRED_ELIM using equalities up to the main eq
306
      // we purify the core substitution
307
      std::vector<Node> pcsr(ps.d_children.begin(),
308
                             ps.d_children.begin() + mainEqIndex);
309
      Node pmainEq = mainEq;
310
      // we transform mainEq to pmainEq and then use this as the first
311
      // argument to MACRO_SR_PRED_ELIM.
312
      if (!purifyCoreSubstitution(pmainEq, pcsr, psb, true))
313
      {
314
        break;
315
      }
316
      std::vector<Node> childrenSRew;
317
      childrenSRew.push_back(pmainEq);
318
      childrenSRew.insert(childrenSRew.end(), pcsr.begin(), pcsr.end());
319
      // now, conclude the proper equality
320
      Node mainEqSRew =
321
          psb.tryStep(PfRule::MACRO_SR_PRED_ELIM, childrenSRew, {});
322
      if (CDProof::isSame(mainEqSRew, pmainEq))
323
      {
324
        Trace("strings-ipc-core") << "...undo step" << std::endl;
325
        // the rule added above was not necessary
326
        psb.popStep();
327
      }
328
      else if (mainEqSRew == conc)
329
      {
330
        Trace("strings-ipc-core") << "...success after rewrite!" << std::endl;
331
        useBuffer = true;
332
        break;
333
      }
334
      Trace("strings-ipc-core")
335
          << "Main equality after subs+rewrite " << mainEqSRew << std::endl;
336
      // now, apply CONCAT_EQ to get the remainder
337
      std::vector<Node> childrenCeq;
338
      childrenCeq.push_back(mainEqSRew);
339
      std::vector<Node> argsCeq;
340
      argsCeq.push_back(nodeIsRev);
341
      Node mainEqCeq = psb.tryStep(PfRule::CONCAT_EQ, childrenCeq, argsCeq);
342
      Trace("strings-ipc-core")
343
          << "Main equality after CONCAT_EQ " << mainEqCeq << std::endl;
344
      if (mainEqCeq.isNull() || mainEqCeq.getKind() != EQUAL)
345
      {
346
        // fail
347
        break;
348
      }
349
      else if (mainEqCeq == mainEqSRew)
350
      {
351
        Trace("strings-ipc-core") << "...undo step" << std::endl;
352
        // not necessary, probably first component of equality
353
        psb.popStep();
354
      }
355
      // Now, mainEqCeq is an equality t ++ ... == s ++ ... where the
356
      // inference involved t and s.
357
      if (infer == InferenceId::STRINGS_N_ENDPOINT_EQ
358
          || infer == InferenceId::STRINGS_N_ENDPOINT_EMP
359
          || infer == InferenceId::STRINGS_F_ENDPOINT_EQ
360
          || infer == InferenceId::STRINGS_F_ENDPOINT_EMP)
361
      {
362
        // Should be equal to conclusion already, or rewrite to it.
363
        // Notice that this step is necessary to handle the "rproc"
364
        // optimization in processSimpleNEq. Alternatively, this could
365
        // possibly be done by CONCAT_EQ with !isRev.
366
        std::vector<Node> cexp;
367
        if (psb.applyPredTransform(mainEqCeq,
368
                                   conc,
369
                                   cexp,
370
                                   MethodId::SB_DEFAULT,
371
                                   MethodId::SBA_SEQUENTIAL,
372
                                   MethodId::RW_REWRITE_EQ_EXT))
373
        {
374
          Trace("strings-ipc-core") << "Transformed to " << conc
375
                                    << " via pred transform" << std::endl;
376
          // success
377
          useBuffer = true;
378
          Trace("strings-ipc-core") << "...success!" << std::endl;
379
        }
380
        // Otherwise, note that EMP rules conclude ti = "" where
381
        // t1 ++ ... ++ tn == "". However, these are very rarely applied, let
382
        // alone for 2+ children. This case is intentionally unhandled here.
383
      }
384
      else if (infer == InferenceId::STRINGS_N_CONST || infer == InferenceId::STRINGS_F_CONST
385
               || infer == InferenceId::STRINGS_N_EQ_CONF)
386
      {
387
        // should be a constant conflict
388
        std::vector<Node> childrenC;
389
        childrenC.push_back(mainEqCeq);
390
        std::vector<Node> argsC;
391
        argsC.push_back(nodeIsRev);
392
        Node mainEqC = psb.tryStep(PfRule::CONCAT_CONFLICT, childrenC, argsC);
393
        if (mainEqC == conc)
394
        {
395
          useBuffer = true;
396
          Trace("strings-ipc-core") << "...success!" << std::endl;
397
        }
398
      }
399
      else
400
      {
401
        std::vector<Node> tvec;
402
        std::vector<Node> svec;
403
        utils::getConcat(mainEqCeq[0], tvec);
404
        utils::getConcat(mainEqCeq[1], svec);
405
        Node t0 = tvec[isRev ? tvec.size() - 1 : 0];
406
        Node s0 = svec[isRev ? svec.size() - 1 : 0];
407
        bool applySym = false;
408
        // may need to apply symmetry
409
        if ((infer == InferenceId::STRINGS_SSPLIT_CST
410
             || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
411
            && t0.isConst())
412
        {
413
          Assert(!s0.isConst());
414
          applySym = true;
415
          std::swap(t0, s0);
416
        }
417
        if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY)
418
        {
419
          if (conc.getKind() != EQUAL)
420
          {
421
            break;
422
          }
423
          // one side should match, the other side could be a split constant
424
          if (conc[0] != t0 && conc[1] != s0)
425
          {
426
            applySym = true;
427
            std::swap(t0, s0);
428
          }
429
          Assert(conc[0].isConst() == t0.isConst());
430
          Assert(conc[1].isConst() == s0.isConst());
431
        }
432
        PfRule rule = PfRule::UNKNOWN;
433
        // the form of the required length constraint expected by the proof
434
        Node lenReq;
435
        bool lenSuccess = false;
436
        if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY)
437
        {
438
          // the required premise for unify is always len(x) = len(y),
439
          // however the explanation may not be literally this. Thus, we
440
          // need to reconstruct a proof from the given explanation.
441
          // it should be the case that lenConstraint => lenReq.
442
          // We use terms in the conclusion equality, not t0, s0 here.
443
          lenReq = nm->mkNode(STRING_LENGTH, conc[0])
444
                       .eqNode(nm->mkNode(STRING_LENGTH, conc[1]));
445
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
446
          rule = PfRule::CONCAT_UNIFY;
447
        }
448
        else if (infer == InferenceId::STRINGS_SSPLIT_VAR)
449
        {
450
          // it should be the case that lenConstraint => lenReq
451
          lenReq = nm->mkNode(STRING_LENGTH, t0)
452
                       .eqNode(nm->mkNode(STRING_LENGTH, s0))
453
                       .notNode();
454
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
455
          rule = PfRule::CONCAT_SPLIT;
456
        }
457
        else if (infer == InferenceId::STRINGS_SSPLIT_CST)
458
        {
459
          // it should be the case that lenConstraint => lenReq
460
          lenReq = nm->mkNode(STRING_LENGTH, t0)
461
                       .eqNode(nm->mkConst(Rational(0)))
462
                       .notNode();
463
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
464
          rule = PfRule::CONCAT_CSPLIT;
465
        }
466
        else if (infer == InferenceId::STRINGS_SSPLIT_VAR_PROP)
467
        {
468
          // it should be the case that lenConstraint => lenReq
469
          for (unsigned r = 0; r < 2; r++)
470
          {
471
            lenReq = nm->mkNode(GT,
472
                                nm->mkNode(STRING_LENGTH, t0),
473
                                nm->mkNode(STRING_LENGTH, s0));
474
            if (convertLengthPf(lenReq, lenConstraint, psb))
475
            {
476
              lenSuccess = true;
477
              break;
478
            }
479
            if (r == 0)
480
            {
481
              // may be the other direction
482
              applySym = true;
483
              std::swap(t0, s0);
484
            }
485
          }
486
          rule = PfRule::CONCAT_LPROP;
487
        }
488
        else if (infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
489
        {
490
          // it should be the case that lenConstraint => lenReq
491
          lenReq = nm->mkNode(STRING_LENGTH, t0)
492
                       .eqNode(nm->mkConst(Rational(0)))
493
                       .notNode();
494
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
495
          rule = PfRule::CONCAT_CPROP;
496
        }
497
        if (!lenSuccess)
498
        {
499
          Trace("strings-ipc-core")
500
              << "...failed due to length constraint" << std::endl;
501
          break;
502
        }
503
        // apply symmetry if necessary
504
        if (applySym)
505
        {
506
          std::vector<Node> childrenSymm;
507
          childrenSymm.push_back(mainEqCeq);
508
          // note this explicit step may not be necessary
509
          mainEqCeq = psb.tryStep(PfRule::SYMM, childrenSymm, {});
510
          Trace("strings-ipc-core")
511
              << "Main equality after SYMM " << mainEqCeq << std::endl;
512
        }
513
        if (rule != PfRule::UNKNOWN)
514
        {
515
          Trace("strings-ipc-core")
516
              << "Core rule length requirement is " << lenReq << std::endl;
517
          // apply the given rule
518
          std::vector<Node> childrenMain;
519
          childrenMain.push_back(mainEqCeq);
520
          childrenMain.push_back(lenReq);
521
          std::vector<Node> argsMain;
522
          argsMain.push_back(nodeIsRev);
523
          Node mainEqMain = psb.tryStep(rule, childrenMain, argsMain);
524
          Trace("strings-ipc-core") << "Main equality after " << rule << " "
525
                                    << mainEqMain << std::endl;
526
          if (mainEqMain == mainEqCeq)
527
          {
528
            Trace("strings-ipc-core") << "...undo step" << std::endl;
529
            // not necessary, probably first component of equality
530
            psb.popStep();
531
          }
532
          // either equal or rewrites to it
533
          std::vector<Node> cexp;
534
          if (psb.applyPredTransform(mainEqMain, conc, cexp))
535
          {
536
            // requires that length success is also true
537
            useBuffer = true;
538
            Trace("strings-ipc-core") << "...success" << std::endl;
539
          }
540
          else
541
          {
542
            Trace("strings-ipc-core") << "...fail" << std::endl;
543
          }
544
        }
545
        else
546
        {
547
          // should always have given a rule to try above
548
          Assert(false) << "No reconstruction rule given for " << infer;
549
        }
550
      }
551
    }
552
    break;
553
    // ========================== Disequalities
554
    case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_STRING_SPLIT:
555
    case InferenceId::STRINGS_DEQ_DISL_STRINGS_SPLIT:
556
    {
557
      if (conc.getKind() != AND || conc.getNumChildren() != 2
558
          || conc[0].getKind() != EQUAL || !conc[0][0].getType().isStringLike()
559
          || conc[1].getKind() != EQUAL
560
          || conc[1][0].getKind() != STRING_LENGTH)
561
      {
562
        Trace("strings-ipc-deq") << "malformed application" << std::endl;
563
        Assert(false) << "unexpected conclusion " << conc << " for " << infer;
564
      }
565
      else
566
      {
567
        Node lenReq =
568
            nm->mkNode(GEQ, nm->mkNode(STRING_LENGTH, conc[0][0]), conc[1][1]);
569
        Trace("strings-ipc-deq")
570
            << "length requirement is " << lenReq << std::endl;
571
        if (convertLengthPf(lenReq, ps.d_children, psb))
572
        {
573
          Trace("strings-ipc-deq") << "...success length" << std::endl;
574
          // make the proof
575
          std::vector<Node> childrenMain;
576
          childrenMain.push_back(lenReq);
577
          std::vector<Node> argsMain;
578
          argsMain.push_back(nodeIsRev);
579
          Node mainConc =
580
              psb.tryStep(PfRule::STRING_DECOMPOSE, childrenMain, argsMain);
581
          Trace("strings-ipc-deq")
582
              << "...main conclusion is " << mainConc << std::endl;
583
          useBuffer = (mainConc == conc);
584
          Trace("strings-ipc-deq")
585
              << "...success is " << useBuffer << std::endl;
586
        }
587
        else
588
        {
589
          Trace("strings-ipc-deq") << "...fail length" << std::endl;
590
        }
591
      }
592
    }
593
    break;
594
    // ========================== Boolean split
595
    case InferenceId::STRINGS_CARD_SP:
596
    case InferenceId::STRINGS_LEN_SPLIT:
597
    case InferenceId::STRINGS_LEN_SPLIT_EMP:
598
    case InferenceId::STRINGS_DEQ_DISL_EMP_SPLIT:
599
    case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_EQ_SPLIT:
600
    case InferenceId::STRINGS_DEQ_STRINGS_EQ:
601
    case InferenceId::STRINGS_DEQ_LENS_EQ:
602
    case InferenceId::STRINGS_DEQ_LENGTH_SP:
603
    {
604
      if (conc.getKind() != OR)
605
      {
606
        // This should never happen. If it does, we resort to using
607
        // THEORY_INFERENCE below (in production mode).
608
        Assert(false) << "Expected OR conclusion for " << infer;
609
      }
610
      else
611
      {
612
        ps.d_rule = PfRule::SPLIT;
613
        Assert(ps.d_children.empty());
614
        ps.d_args.push_back(conc[0]);
615
      }
616
    }
617
    break;
618
    // ========================== Regular expression unfolding
619
    case InferenceId::STRINGS_RE_UNFOLD_POS:
620
    case InferenceId::STRINGS_RE_UNFOLD_NEG:
621
    {
622
      Assert (!ps.d_children.empty());
623
      size_t nchild = ps.d_children.size();
624
      Node mem = ps.d_children[nchild-1];
625
      if (nchild>1)
626
      {
627
        // if more than one, apply MACRO_SR_PRED_ELIM
628
        std::vector<Node> tcs;
629
        tcs.insert(tcs.end(),
630
                          ps.d_children.begin(),
631
                          ps.d_children.begin() + (nchild-1));
632
        mem = psb.applyPredElim(mem, tcs);
633
        useBuffer = true;
634
      }
635
      PfRule r = PfRule::UNKNOWN;
636
      if (mem.isNull())
637
      {
638
        // failed to eliminate above
639
        Assert(false) << "Failed to apply MACRO_SR_PRED_ELIM for RE unfold";
640
        useBuffer = false;
641
      }
642
      else if (infer == InferenceId::STRINGS_RE_UNFOLD_POS)
643
      {
644
        r = PfRule::RE_UNFOLD_POS;
645
      }
646
      else
647
      {
648
        r = PfRule::RE_UNFOLD_NEG;
649
        // it may be an optimized form of concat simplification
650
        Assert(mem.getKind() == NOT && mem[0].getKind() == STRING_IN_REGEXP);
651
        if (mem[0][1].getKind() == REGEXP_CONCAT)
652
        {
653
          size_t index;
654
          Node reLen = RegExpOpr::getRegExpConcatFixed(mem[0][1], index);
655
          // if we can find a fixed length for a component, use the optimized
656
          // version
657
          if (!reLen.isNull())
658
          {
659
            r = PfRule::RE_UNFOLD_NEG_CONCAT_FIXED;
660
          }
661
        }
662
      }
663
      if (useBuffer)
664
      {
665
        mem = psb.tryStep(r, {mem}, {});
666
        // should match the conclusion
667
        useBuffer = (mem==conc);
668
      }
669
      else
670
      {
671
        ps.d_rule = r;
672
      }
673
    }
674
    break;
675
    // ========================== Reduction
676
    case InferenceId::STRINGS_CTN_POS:
677
    case InferenceId::STRINGS_CTN_NEG_EQUAL:
678
    {
679
      if (ps.d_children.size() != 1)
680
      {
681
        break;
682
      }
683
      bool polarity = ps.d_children[0].getKind() != NOT;
684
      Node atom = polarity ? ps.d_children[0] : ps.d_children[0][0];
685
      std::vector<Node> args;
686
      args.push_back(atom);
687
      Node res = psb.tryStep(PfRule::STRING_EAGER_REDUCTION, {}, args);
688
      if (res.isNull())
689
      {
690
        break;
691
      }
692
      // ite( contains(x,t), x = k1 ++ t ++ k2, x != t )
693
      std::vector<Node> tiChildren;
694
      tiChildren.push_back(ps.d_children[0]);
695
      Node ctnt = psb.tryStep(
696
          polarity ? PfRule::TRUE_INTRO : PfRule::FALSE_INTRO, tiChildren, {});
697
      if (ctnt.isNull() || ctnt.getKind() != EQUAL)
698
      {
699
        break;
700
      }
701
      std::vector<Node> tchildren;
702
      tchildren.push_back(ctnt);
703
      // apply substitution { contains(x,t) -> true|false } and rewrite to get
704
      // conclusion x = k1 ++ t ++ k2 or x != t.
705
      if (psb.applyPredTransform(res, conc, tchildren))
706
      {
707
        useBuffer = true;
708
      }
709
    }
710
    break;
711
712
    case InferenceId::STRINGS_REDUCTION:
713
    {
714
      size_t nchild = conc.getNumChildren();
715
      Node mainEq;
716
      if (conc.getKind() == EQUAL)
717
      {
718
        mainEq = conc;
719
      }
720
      else if (conc.getKind() == AND && conc[nchild - 1].getKind() == EQUAL)
721
      {
722
        mainEq = conc[nchild - 1];
723
      }
724
      if (mainEq.isNull())
725
      {
726
        Trace("strings-ipc-red") << "Bad Reduction: " << conc << std::endl;
727
        Assert(false) << "Unexpected reduction " << conc;
728
        break;
729
      }
730
      std::vector<Node> argsRed;
731
      // the left hand side of the last conjunct is the term we are reducing
732
      argsRed.push_back(mainEq[0]);
733
      Node red = psb.tryStep(PfRule::STRING_REDUCTION, {}, argsRed);
734
      Trace("strings-ipc-red") << "Reduction : " << red << std::endl;
735
      if (!red.isNull())
736
      {
737
        // either equal or rewrites to it
738
        std::vector<Node> cexp;
739
        if (psb.applyPredTransform(red, conc, cexp))
740
        {
741
          Trace("strings-ipc-red") << "...success!" << std::endl;
742
          useBuffer = true;
743
        }
744
        else
745
        {
746
          Trace("strings-ipc-red") << "...failed to reduce" << std::endl;
747
        }
748
      }
749
    }
750
    break;
751
    // ========================== code injectivity
752
    case InferenceId::STRINGS_CODE_INJ:
753
    {
754
      ps.d_rule = PfRule::STRING_CODE_INJ;
755
      Assert(conc.getKind() == OR && conc.getNumChildren() == 3
756
             && conc[2].getKind() == EQUAL);
757
      ps.d_args.push_back(conc[2][0]);
758
      ps.d_args.push_back(conc[2][1]);
759
    }
760
    break;
761
    // ========================== unit injectivity
762
    case InferenceId::STRINGS_UNIT_INJ:
763
    {
764
      ps.d_rule = PfRule::STRING_SEQ_UNIT_INJ;
765
    }
766
    break;
767
    // ========================== prefix conflict
768
    case InferenceId::STRINGS_PREFIX_CONFLICT:
769
    {
770
      Trace("strings-ipc-prefix") << "Prefix conflict..." << std::endl;
771
      std::vector<Node> eqs;
772
      for (const Node& e : ps.d_children)
773
      {
774
        Kind ek = e.getKind();
775
        if (ek == EQUAL)
776
        {
777
          Trace("strings-ipc-prefix") << "- equality : " << e << std::endl;
778
          eqs.push_back(e);
779
        }
780
        else if (ek == STRING_IN_REGEXP)
781
        {
782
          // unfold it and extract the equality
783
          std::vector<Node> children;
784
          children.push_back(e);
785
          std::vector<Node> args;
786
          Node eunf = psb.tryStep(PfRule::RE_UNFOLD_POS, children, args);
787
          Trace("strings-ipc-prefix")
788
              << "--- " << e << " unfolds to " << eunf << std::endl;
789
          if (eunf.isNull())
790
          {
791
            continue;
792
          }
793
          else if (eunf.getKind() == AND)
794
          {
795
            // equality is the first conjunct
796
            std::vector<Node> childrenAE;
797
            childrenAE.push_back(eunf);
798
            std::vector<Node> argsAE;
799
            argsAE.push_back(nm->mkConst(Rational(0)));
800
            Node eunfAE = psb.tryStep(PfRule::AND_ELIM, childrenAE, argsAE);
801
            Trace("strings-ipc-prefix")
802
                << "--- and elim to " << eunfAE << std::endl;
803
            if (eunfAE.isNull() || eunfAE.getKind() != EQUAL)
804
            {
805
              Assert(false)
806
                  << "Unexpected unfolded premise " << eunf << " for " << infer;
807
              continue;
808
            }
809
            Trace("strings-ipc-prefix")
810
                << "- equality : " << eunfAE << std::endl;
811
            eqs.push_back(eunfAE);
812
          }
813
          else if (eunf.getKind() == EQUAL)
814
          {
815
            Trace("strings-ipc-prefix") << "- equality : " << eunf << std::endl;
816
            eqs.push_back(eunf);
817
          }
818
        }
819
        else
820
        {
821
          // not sure how to use this assumption
822
          Assert(false) << "Unexpected premise " << e << " for " << infer;
823
        }
824
      }
825
      if (eqs.empty())
826
      {
827
        break;
828
      }
829
      // connect via transitivity
830
      Node curr = eqs[0];
831
      for (size_t i = 1, esize = eqs.size(); i < esize; i++)
832
      {
833
        Node prev = curr;
834
        curr = convertTrans(curr, eqs[1], psb);
835
        if (curr.isNull())
836
        {
837
          break;
838
        }
839
        Trace("strings-ipc-prefix") << "- Via trans: " << curr << std::endl;
840
      }
841
      if (curr.isNull())
842
      {
843
        break;
844
      }
845
      Trace("strings-ipc-prefix")
846
          << "- Possible conflicting equality : " << curr << std::endl;
847
      std::vector<Node> emp;
848
      Node concE = psb.applyPredElim(curr, emp);
849
      Trace("strings-ipc-prefix")
850
          << "- After pred elim: " << concE << std::endl;
851
      if (concE == conc)
852
      {
853
        Trace("strings-ipc-prefix") << "...success!" << std::endl;
854
        useBuffer = true;
855
      }
856
    }
857
    break;
858
    // ========================== regular expressions
859
    case InferenceId::STRINGS_RE_INTER_INCLUDE:
860
    case InferenceId::STRINGS_RE_INTER_CONF:
861
    case InferenceId::STRINGS_RE_INTER_INFER:
862
    {
863
      std::vector<Node> reiExp;
864
      std::vector<Node> reis;
865
      std::vector<Node> reiChildren;
866
      std::vector<Node> reiChildrenOrig;
867
      Node x;
868
      // make the regular expression intersection that summarizes all
869
      // memberships in the explanation
870
      for (const Node& c : ps.d_children)
871
      {
872
        bool polarity = c.getKind() != NOT;
873
        Node catom = polarity ? c : c[0];
874
        if (catom.getKind() != STRING_IN_REGEXP)
875
        {
876
          Assert(c.getKind() == EQUAL);
877
          if (c.getKind() == EQUAL)
878
          {
879
            reiExp.push_back(c);
880
          }
881
          continue;
882
        }
883
        if (x.isNull())
884
        {
885
          // just take the first LHS; others should be equated to it by exp
886
          x = catom[0];
887
        }
888
        Node rcurr =
889
            polarity ? catom[1] : nm->mkNode(REGEXP_COMPLEMENT, catom[1]);
890
        reis.push_back(rcurr);
891
        Node mem = nm->mkNode(STRING_IN_REGEXP, catom[0], rcurr);
892
        reiChildren.push_back(mem);
893
        reiChildrenOrig.push_back(c);
894
      }
895
      // go back and justify each premise
896
      bool successChildren = true;
897
      for (size_t i = 0, nchild = reiChildren.size(); i < nchild; i++)
898
      {
899
        if (!psb.applyPredTransform(reiChildrenOrig[i], reiChildren[i], reiExp))
900
        {
901
          Trace("strings-ipc-re")
902
              << "... failed to justify child " << reiChildren[i] << " from "
903
              << reiChildrenOrig[i] << std::endl;
904
          successChildren = false;
905
          break;
906
        }
907
      }
908
      if (!successChildren)
909
      {
910
        break;
911
      }
912
      Node mem = psb.tryStep(PfRule::RE_INTER, reiChildren, {});
913
      Trace("strings-ipc-re")
914
          << "Regular expression summary: " << mem << std::endl;
915
      // the conclusion is rewritable to the premises via rewriting?
916
      if (psb.applyPredTransform(mem, conc, {}))
917
      {
918
        Trace("strings-ipc-re") << "... success!" << std::endl;
919
        useBuffer = true;
920
      }
921
      else
922
      {
923
        Trace("strings-ipc-re")
924
            << "...failed to rewrite to conclusion" << std::endl;
925
      }
926
    }
927
    break;
928
    // ========================== unknown and currently unsupported
929
    case InferenceId::STRINGS_CARDINALITY:
930
    case InferenceId::STRINGS_I_CYCLE_E:
931
    case InferenceId::STRINGS_I_CYCLE:
932
    case InferenceId::STRINGS_INFER_EMP:
933
    case InferenceId::STRINGS_RE_DELTA:
934
    case InferenceId::STRINGS_RE_DELTA_CONF:
935
    case InferenceId::STRINGS_RE_DERIVE:
936
    case InferenceId::STRINGS_FLOOP:
937
    case InferenceId::STRINGS_FLOOP_CONFLICT:
938
    case InferenceId::STRINGS_DEQ_NORM_EMP:
939
    case InferenceId::STRINGS_CTN_TRANS:
940
    case InferenceId::STRINGS_CTN_DECOMPOSE:
941
    default:
942
      // do nothing, these will be converted to THEORY_INFERENCE below since the
943
      // rule is unknown.
944
      break;
945
  }
946
947
  // now see if we would succeed with the checker-to-try
948
  bool success = false;
949
  if (ps.d_rule != PfRule::UNKNOWN)
950
  {
951
    Trace("strings-ipc") << "For " << infer << ", try proof rule " << ps.d_rule
952
                         << "...";
953
    Assert(ps.d_rule != PfRule::UNKNOWN);
954
    Node pconc = psb.tryStep(ps.d_rule, ps.d_children, ps.d_args);
955
    if (pconc.isNull() || pconc != conc)
956
    {
957
      Trace("strings-ipc") << "failed, pconc is " << pconc << " (expected "
958
                           << conc << ")" << std::endl;
959
      ps.d_rule = PfRule::UNKNOWN;
960
    }
961
    else
962
    {
963
      // successfully set up a single step proof in ps
964
      success = true;
965
      Trace("strings-ipc") << "success!" << std::endl;
966
    }
967
  }
968
  else if (useBuffer)
969
  {
970
    // successfully set up a multi step proof in psb
971
    success = true;
972
  }
973
  else
974
  {
975
    Trace("strings-ipc") << "For " << infer << " " << conc
976
                         << ", no proof rule, failed" << std::endl;
977
  }
978
  if (!success)
979
  {
980
    // debug print
981
    if (Trace.isOn("strings-ipc-fail"))
982
    {
983
      Trace("strings-ipc-fail")
984
          << "InferProofCons::convert: Failed " << infer
985
          << (isRev ? " :rev " : " ") << conc << std::endl;
986
      for (const Node& ec : exp)
987
      {
988
        Trace("strings-ipc-fail") << "    e: " << ec << std::endl;
989
      }
990
    }
991
    // untrustworthy conversion, the argument of THEORY_INFERENCE is its
992
    // conclusion
993
    ps.d_args.clear();
994
    ps.d_args.push_back(conc);
995
    Node t = builtin::BuiltinProofRuleChecker::mkTheoryIdNode(THEORY_STRINGS);
996
    ps.d_args.push_back(t);
997
    // use the trust rule
998
    ps.d_rule = PfRule::THEORY_INFERENCE;
999
  }
1000
  if (Trace.isOn("strings-ipc-debug"))
1001
  {
1002
    if (useBuffer)
1003
    {
1004
      Trace("strings-ipc-debug")
1005
          << "InferProofCons::convert returned buffer with "
1006
          << psb.getNumSteps() << " steps:" << std::endl;
1007
      const std::vector<std::pair<Node, ProofStep>>& steps = psb.getSteps();
1008
      for (const std::pair<Node, ProofStep>& step : steps)
1009
      {
1010
        Trace("strings-ipc-debug")
1011
            << "- " << step.first << " via " << step.second << std::endl;
1012
      }
1013
    }
1014
    else
1015
    {
1016
      Trace("strings-ipc-debug")
1017
          << "InferProofCons::convert returned " << ps << std::endl;
1018
    }
1019
  }
1020
}
1021
1022
bool InferProofCons::convertLengthPf(Node lenReq,
1023
                                     const std::vector<Node>& lenExp,
1024
                                     TheoryProofStepBuffer& psb)
1025
{
1026
  for (const Node& le : lenExp)
1027
  {
1028
    if (lenReq == le)
1029
    {
1030
      return true;
1031
    }
1032
  }
1033
  Trace("strings-ipc-len") << "Must explain " << lenReq << " by " << lenExp
1034
                           << std::endl;
1035
  for (const Node& le : lenExp)
1036
  {
1037
    // probably rewrites to it?
1038
    std::vector<Node> exp;
1039
    if (psb.applyPredTransform(le, lenReq, exp))
1040
    {
1041
      Trace("strings-ipc-len") << "...success by rewrite" << std::endl;
1042
      return true;
1043
    }
1044
    // maybe x != "" => len(x) != 0
1045
    std::vector<Node> children;
1046
    children.push_back(le);
1047
    std::vector<Node> args;
1048
    Node res = psb.tryStep(PfRule::STRING_LENGTH_NON_EMPTY, children, args);
1049
    if (res == lenReq)
1050
    {
1051
      Trace("strings-ipc-len") << "...success by LENGTH_NON_EMPTY" << std::endl;
1052
      return true;
1053
    }
1054
  }
1055
  Trace("strings-ipc-len") << "...failed" << std::endl;
1056
  return false;
1057
}
1058
1059
Node InferProofCons::convertTrans(Node eqa,
1060
                                  Node eqb,
1061
                                  TheoryProofStepBuffer& psb)
1062
{
1063
  if (eqa.getKind() != EQUAL || eqb.getKind() != EQUAL)
1064
  {
1065
    return Node::null();
1066
  }
1067
  for (uint32_t i = 0; i < 2; i++)
1068
  {
1069
    Node eqaSym = i == 0 ? eqa[1].eqNode(eqa[0]) : eqa;
1070
    for (uint32_t j = 0; j < 2; j++)
1071
    {
1072
      Node eqbSym = j == 0 ? eqb : eqb[1].eqNode(eqb[1]);
1073
      if (eqa[i] == eqb[j])
1074
      {
1075
        std::vector<Node> children;
1076
        children.push_back(eqaSym);
1077
        children.push_back(eqbSym);
1078
        return psb.tryStep(PfRule::TRANS, children, {});
1079
      }
1080
    }
1081
  }
1082
  return Node::null();
1083
}
1084
1085
3145
std::shared_ptr<ProofNode> InferProofCons::getProofFor(Node fact)
1086
{
1087
  // get the inference
1088
3145
  NodeInferInfoMap::iterator it = d_lazyFactMap.find(fact);
1089
3145
  if (it == d_lazyFactMap.end())
1090
  {
1091
    Node factSym = CDProof::getSymmFact(fact);
1092
    if (!factSym.isNull())
1093
    {
1094
      // Use the symmetric fact. There is no need to explictly make a
1095
      // SYMM proof, as this is handled by CDProof::getProofFor below.
1096
      it = d_lazyFactMap.find(factSym);
1097
    }
1098
  }
1099
3145
  AlwaysAssert(it != d_lazyFactMap.end());
1100
6290
  std::shared_ptr<InferInfo> ii = (*it).second;
1101
3145
  Assert(ii->d_conc == fact);
1102
  // make a placeholder proof using STRINGS_INFERENCE, which is reconstructed
1103
  // during post-process
1104
6290
  CDProof pf(d_pnm);
1105
6290
  std::vector<Node> args;
1106
3145
  packArgs(ii->d_conc, ii->getId(), ii->d_idRev, ii->d_premises, args);
1107
  // must flatten
1108
6290
  std::vector<Node> exp;
1109
14731
  for (const Node& ec : ii->d_premises)
1110
  {
1111
11586
    utils::flattenOp(AND, ec, exp);
1112
  }
1113
3145
  pf.addStep(fact, PfRule::STRING_INFERENCE, exp, args);
1114
6290
  return pf.getProofFor(fact);
1115
}
1116
1117
8952
std::string InferProofCons::identify() const
1118
{
1119
8952
  return "strings::InferProofCons";
1120
}
1121
1122
bool InferProofCons::purifyCoreSubstitution(Node& tgt,
1123
                                            std::vector<Node>& children,
1124
                                            TheoryProofStepBuffer& psb,
1125
                                            bool concludeTgtNew)
1126
{
1127
  // collect the terms to purify, which are the LHS of the substitution
1128
  std::unordered_set<Node> termsToPurify;
1129
  for (const Node& nc : children)
1130
  {
1131
    Assert(nc.getKind() == EQUAL && nc[0].getType().isStringLike());
1132
    termsToPurify.insert(nc[0]);
1133
  }
1134
  // now, purify each of the children of the substitution
1135
  for (size_t i = 0, nchild = children.size(); i < nchild; i++)
1136
  {
1137
    Node pnc = purifyCorePredicate(children[i], true, psb, termsToPurify);
1138
    if (pnc.isNull())
1139
    {
1140
      return false;
1141
    }
1142
    if (children[i] != pnc)
1143
    {
1144
      Trace("strings-ipc-pure-subs")
1145
          << "Converted: " << children[i] << " to " << pnc << std::endl;
1146
      children[i] = pnc;
1147
    }
1148
    // we now should have a substitution with only atomic terms
1149
    Assert(children[i][0].getNumChildren() == 0);
1150
  }
1151
  // now, purify the target predicate
1152
  tgt = purifyCorePredicate(tgt, concludeTgtNew, psb, termsToPurify);
1153
  return !tgt.isNull();
1154
}
1155
1156
Node InferProofCons::purifyCorePredicate(
1157
    Node lit,
1158
    bool concludeNew,
1159
    TheoryProofStepBuffer& psb,
1160
    std::unordered_set<Node>& termsToPurify)
1161
{
1162
  bool pol = lit.getKind() != NOT;
1163
  Node atom = pol ? lit : lit[0];
1164
  if (atom.getKind() != EQUAL || !atom[0].getType().isStringLike())
1165
  {
1166
    // we only purify string (dis)equalities
1167
    return lit;
1168
  }
1169
  // purify both sides of the equality
1170
  std::vector<Node> pcs;
1171
  bool childChanged = false;
1172
  for (const Node& lc : atom)
1173
  {
1174
    Node plc = purifyCoreTerm(lc, termsToPurify);
1175
    childChanged = childChanged || plc != lc;
1176
    pcs.push_back(plc);
1177
  }
1178
  if (!childChanged)
1179
  {
1180
    return lit;
1181
  }
1182
  NodeManager* nm = NodeManager::currentNM();
1183
  Node newLit = nm->mkNode(EQUAL, pcs);
1184
  if (!pol)
1185
  {
1186
    newLit = newLit.notNode();
1187
  }
1188
  Assert(lit != newLit);
1189
  // prove by transformation, should always succeed
1190
  if (!psb.applyPredTransform(
1191
          concludeNew ? lit : newLit, concludeNew ? newLit : lit, {}))
1192
  {
1193
    // failed, return null
1194
    return Node::null();
1195
  }
1196
  return newLit;
1197
}
1198
1199
Node InferProofCons::purifyCoreTerm(Node n,
1200
                                    std::unordered_set<Node>& termsToPurify)
1201
{
1202
  Assert(n.getType().isStringLike());
1203
  if (n.getNumChildren() == 0)
1204
  {
1205
    return n;
1206
  }
1207
  NodeManager* nm = NodeManager::currentNM();
1208
  if (n.getKind() == STRING_CONCAT)
1209
  {
1210
    std::vector<Node> pcs;
1211
    for (const Node& nc : n)
1212
    {
1213
      pcs.push_back(purifyCoreTerm(nc, termsToPurify));
1214
    }
1215
    return nm->mkNode(STRING_CONCAT, pcs);
1216
  }
1217
  if (termsToPurify.find(n) == termsToPurify.end())
1218
  {
1219
    // did not need to purify
1220
    return n;
1221
  }
1222
  SkolemManager* sm = nm->getSkolemManager();
1223
  Node k = sm->mkPurifySkolem(n, "k");
1224
  return k;
1225
}
1226
1227
}  // namespace strings
1228
}  // namespace theory
1229
22746
}  // namespace cvc5