GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/infer_proof_cons.cpp Lines: 524 619 84.7 %
Date: 2021-09-18 Branches: 1040 2528 41.1 %

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
1243
InferProofCons::InferProofCons(context::Context* c,
35
                               ProofNodeManager* pnm,
36
1243
                               SequencesStatistics& statistics)
37
1243
    : d_pnm(pnm), d_lazyFactMap(c), d_statistics(statistics)
38
{
39
1243
  Assert(d_pnm != nullptr);
40
1243
}
41
42
14480
void InferProofCons::notifyFact(const InferInfo& ii)
43
{
44
28616
  Node fact = ii.d_conc;
45
28960
  Trace("strings-ipc-debug")
46
14480
      << "InferProofCons::notifyFact: " << ii << std::endl;
47
14480
  if (d_lazyFactMap.find(fact) != d_lazyFactMap.end())
48
  {
49
331
    Trace("strings-ipc-debug") << "...duplicate!" << std::endl;
50
331
    return;
51
  }
52
28285
  Node symFact = CDProof::getSymmFact(fact);
53
14149
  if (!symFact.isNull() && d_lazyFactMap.find(symFact) != d_lazyFactMap.end())
54
  {
55
13
    Trace("strings-ipc-debug") << "...duplicate (sym)!" << std::endl;
56
13
    return;
57
  }
58
28272
  std::shared_ptr<InferInfo> iic = std::make_shared<InferInfo>(ii);
59
14136
  d_lazyFactMap.insert(ii.d_conc, iic);
60
}
61
62
987
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
987
  bool useBuffer = false;
70
1974
  ProofStep ps;
71
1974
  TheoryProofStepBuffer psb(pf->getManager()->getChecker());
72
  // run the conversion
73
987
  convert(infer, isRev, conc, exp, ps, psb, useBuffer);
74
  // make the proof based on the step or the buffer
75
987
  if (useBuffer)
76
  {
77
787
    if (!pf->addSteps(psb))
78
    {
79
      return false;
80
    }
81
  }
82
  else
83
  {
84
200
    if (!pf->addStep(conc, ps))
85
    {
86
      return false;
87
    }
88
  }
89
987
  return true;
90
}
91
92
6869
void InferProofCons::packArgs(Node conc,
93
                              InferenceId infer,
94
                              bool isRev,
95
                              const std::vector<Node>& exp,
96
                              std::vector<Node>& args)
97
{
98
6869
  args.push_back(conc);
99
6869
  args.push_back(mkInferenceIdNode(infer));
100
6869
  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
6869
  args.insert(args.end(), exp.begin(), exp.end());
106
6869
}
107
108
987
bool InferProofCons::unpackArgs(const std::vector<Node>& args,
109
                                Node& conc,
110
                                InferenceId& infer,
111
                                bool& isRev,
112
                                std::vector<Node>& exp)
113
{
114
987
  Assert(args.size() >= 3);
115
987
  conc = args[0];
116
987
  if (!getInferenceId(args[1], infer))
117
  {
118
    return false;
119
  }
120
987
  isRev = args[2].getConst<bool>();
121
987
  exp.insert(exp.end(), args.begin() + 3, args.end());
122
987
  return true;
123
}
124
125
987
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
987
  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
1974
  std::vector<size_t> startExpIndex;
139
3417
  for (const Node& ec : exp)
140
  {
141
    // store the index in the flattened vector
142
2430
    startExpIndex.push_back(ps.d_children.size());
143
2430
    utils::flattenOp(AND, ec, ps.d_children);
144
  }
145
  // debug print
146
987
  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
987
  psb.clear();
157
987
  NodeManager* nm = NodeManager::currentNM();
158
1974
  Node nodeIsRev = nm->mkConst(isRev);
159
987
  switch (infer)
160
  {
161
    // ========================== equal by substitution+rewriting
162
95
    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
190
      std::vector<Node> pcs = ps.d_children;
170
190
      Node pconc = conc;
171
      // purify core substitution proves conc from pconc if necessary,
172
      // we apply MACRO_SR_PRED_INTRO to prove pconc
173
95
      if (purifyCoreSubstitution(pconc, pcs, psb, false))
174
      {
175
95
        if (psb.applyPredIntro(pconc, pcs))
176
        {
177
93
          useBuffer = true;
178
        }
179
95
      }
180
    }
181
95
    break;
182
    // ========================== substitution + rewriting
183
143
    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
143
      if (!ps.d_children.empty())
192
      {
193
264
        std::vector<Node> exps(ps.d_children.begin(), ps.d_children.end() - 1);
194
264
        Node src = ps.d_children[ps.d_children.size() - 1];
195
132
        if (psb.applyPredTransform(src, conc, exps))
196
        {
197
59
          useBuffer = true;
198
        }
199
      }
200
      else
201
      {
202
        // use the predicate version?
203
11
        ps.d_args.push_back(conc);
204
11
        ps.d_rule = PfRule::MACRO_SR_PRED_INTRO;
205
      }
206
    }
207
143
    break;
208
    // ========================== rewrite pred
209
24
    case InferenceId::STRINGS_EXTF_EQ_REW:
210
    {
211
      // the last child is the predicate we are operating on, move to front
212
29
      Node src = ps.d_children[ps.d_children.size() - 1];
213
29
      std::vector<Node> expe(ps.d_children.begin(), ps.d_children.end() - 1);
214
      // start with a default rewrite
215
29
      Node mainEqSRew = psb.applyPredElim(src, expe);
216
24
      if (mainEqSRew == conc)
217
      {
218
1
        useBuffer = true;
219
1
        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
28
                                           MethodId::RW_REWRITE_EQ_EXT);
227
23
      if (mainEqSRew2 == conc)
228
      {
229
18
        useBuffer = true;
230
18
        break;
231
      }
232
      // rewrite again with default rewriter
233
10
      Node mainEqSRew3 = psb.applyPredElim(mainEqSRew2, {});
234
10
      useBuffer = (mainEqSRew3 == conc);
235
    }
236
5
    break;
237
    // ========================== substitution+rewriting, CONCAT_EQ, ...
238
476
    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
952
      Trace("strings-ipc-core") << "Generate core rule for " << infer
255
476
                                << " (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
476
      size_t nchild = ps.d_children.size();
263
476
      size_t mainEqIndex = 0;
264
476
      bool mainEqIndexSet = false;
265
      // the length constraint
266
935
      std::vector<Node> lenConstraint;
267
      // these inferences have a length constraint as the last explain
268
476
      if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY
269
294
          || infer == InferenceId::STRINGS_SSPLIT_CST || infer == InferenceId::STRINGS_SSPLIT_VAR
270
183
          || infer == InferenceId::STRINGS_SSPLIT_VAR_PROP
271
145
          || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
272
      {
273
680
        if (exp.size() >= 2)
274
        {
275
340
          Assert(exp.size() <= startExpIndex.size());
276
          // The index of the "main" equality is the last equality before
277
          // the length explanation.
278
340
          mainEqIndex = startExpIndex[exp.size() - 1] - 1;
279
340
          mainEqIndexSet = true;
280
          // the remainder is the length constraint
281
1020
          lenConstraint.insert(lenConstraint.end(),
282
680
                               ps.d_children.begin() + mainEqIndex + 1,
283
1360
                               ps.d_children.end());
284
        }
285
      }
286
136
      else if (nchild >= 1)
287
      {
288
        // The index of the main equality is the last child.
289
136
        mainEqIndex = nchild - 1;
290
136
        mainEqIndexSet = true;
291
      }
292
935
      Node mainEq;
293
476
      if (mainEqIndexSet)
294
      {
295
476
        mainEq = ps.d_children[mainEqIndex];
296
952
        Trace("strings-ipc-core") << "Main equality " << mainEq << " at index "
297
476
                                  << mainEqIndex << std::endl;
298
      }
299
476
      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
935
                             ps.d_children.begin() + mainEqIndex);
309
935
      Node pmainEq = mainEq;
310
      // we transform mainEq to pmainEq and then use this as the first
311
      // argument to MACRO_SR_PRED_ELIM.
312
476
      if (!purifyCoreSubstitution(pmainEq, pcsr, psb, true))
313
      {
314
        break;
315
      }
316
935
      std::vector<Node> childrenSRew;
317
476
      childrenSRew.push_back(pmainEq);
318
476
      childrenSRew.insert(childrenSRew.end(), pcsr.begin(), pcsr.end());
319
      // now, conclude the proper equality
320
      Node mainEqSRew =
321
935
          psb.tryStep(PfRule::MACRO_SR_PRED_ELIM, childrenSRew, {});
322
476
      if (CDProof::isSame(mainEqSRew, pmainEq))
323
      {
324
115
        Trace("strings-ipc-core") << "...undo step" << std::endl;
325
        // the rule added above was not necessary
326
115
        psb.popStep();
327
      }
328
361
      else if (mainEqSRew == conc)
329
      {
330
        Trace("strings-ipc-core") << "...success after rewrite!" << std::endl;
331
        useBuffer = true;
332
        break;
333
      }
334
952
      Trace("strings-ipc-core")
335
476
          << "Main equality after subs+rewrite " << mainEqSRew << std::endl;
336
      // now, apply CONCAT_EQ to get the remainder
337
935
      std::vector<Node> childrenCeq;
338
476
      childrenCeq.push_back(mainEqSRew);
339
935
      std::vector<Node> argsCeq;
340
476
      argsCeq.push_back(nodeIsRev);
341
935
      Node mainEqCeq = psb.tryStep(PfRule::CONCAT_EQ, childrenCeq, argsCeq);
342
952
      Trace("strings-ipc-core")
343
476
          << "Main equality after CONCAT_EQ " << mainEqCeq << std::endl;
344
476
      if (mainEqCeq.isNull() || mainEqCeq.getKind() != EQUAL)
345
      {
346
        // fail
347
        break;
348
      }
349
476
      else if (mainEqCeq == mainEqSRew)
350
      {
351
205
        Trace("strings-ipc-core") << "...undo step" << std::endl;
352
        // not necessary, probably first component of equality
353
205
        psb.popStep();
354
      }
355
      // Now, mainEqCeq is an equality t ++ ... == s ++ ... where the
356
      // inference involved t and s.
357
476
      if (infer == InferenceId::STRINGS_N_ENDPOINT_EQ
358
379
          || infer == InferenceId::STRINGS_N_ENDPOINT_EMP
359
379
          || infer == InferenceId::STRINGS_F_ENDPOINT_EQ
360
362
          || 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
228
        std::vector<Node> cexp;
367
114
        if (psb.applyPredTransform(mainEqCeq,
368
                                   conc,
369
                                   cexp,
370
                                   MethodId::SB_DEFAULT,
371
                                   MethodId::SBA_SEQUENTIAL,
372
                                   MethodId::RW_REWRITE_EQ_EXT))
373
        {
374
228
          Trace("strings-ipc-core") << "Transformed to " << conc
375
114
                                    << " via pred transform" << std::endl;
376
          // success
377
114
          useBuffer = true;
378
114
          Trace("strings-ipc-core") << "...success!" << std::endl;
379
114
        }
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
362
      else if (infer == InferenceId::STRINGS_N_CONST || infer == InferenceId::STRINGS_F_CONST
385
357
               || infer == InferenceId::STRINGS_N_EQ_CONF)
386
      {
387
        // should be a constant conflict
388
10
        std::vector<Node> childrenC;
389
5
        childrenC.push_back(mainEqCeq);
390
10
        std::vector<Node> argsC;
391
5
        argsC.push_back(nodeIsRev);
392
10
        Node mainEqC = psb.tryStep(PfRule::CONCAT_CONFLICT, childrenC, argsC);
393
5
        if (mainEqC == conc)
394
        {
395
5
          useBuffer = true;
396
5
          Trace("strings-ipc-core") << "...success!" << std::endl;
397
5
        }
398
      }
399
      else
400
      {
401
697
        std::vector<Node> tvec;
402
697
        std::vector<Node> svec;
403
357
        utils::getConcat(mainEqCeq[0], tvec);
404
357
        utils::getConcat(mainEqCeq[1], svec);
405
697
        Node t0 = tvec[isRev ? tvec.size() - 1 : 0];
406
697
        Node s0 = svec[isRev ? svec.size() - 1 : 0];
407
357
        bool applySym = false;
408
        // may need to apply symmetry
409
714
        if ((infer == InferenceId::STRINGS_SSPLIT_CST
410
254
             || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
411
469
            && t0.isConst())
412
        {
413
73
          Assert(!s0.isConst());
414
73
          applySym = true;
415
73
          std::swap(t0, s0);
416
        }
417
357
        if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY)
418
        {
419
182
          if (conc.getKind() != EQUAL)
420
          {
421
            break;
422
          }
423
          // one side should match, the other side could be a split constant
424
182
          if (conc[0] != t0 && conc[1] != s0)
425
          {
426
96
            applySym = true;
427
96
            std::swap(t0, s0);
428
          }
429
182
          Assert(conc[0].isConst() == t0.isConst());
430
182
          Assert(conc[1].isConst() == s0.isConst());
431
        }
432
357
        PfRule rule = PfRule::UNKNOWN;
433
        // the form of the required length constraint expected by the proof
434
697
        Node lenReq;
435
357
        bool lenSuccess = false;
436
357
        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
546
          lenReq = nm->mkNode(STRING_LENGTH, conc[0])
444
364
                       .eqNode(nm->mkNode(STRING_LENGTH, conc[1]));
445
182
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
446
182
          rule = PfRule::CONCAT_UNIFY;
447
        }
448
175
        else if (infer == InferenceId::STRINGS_SSPLIT_VAR)
449
        {
450
          // it should be the case that lenConstraint => lenReq
451
24
          lenReq = nm->mkNode(STRING_LENGTH, t0)
452
16
                       .eqNode(nm->mkNode(STRING_LENGTH, s0))
453
16
                       .notNode();
454
8
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
455
8
          rule = PfRule::CONCAT_SPLIT;
456
        }
457
167
        else if (infer == InferenceId::STRINGS_SSPLIT_CST)
458
        {
459
          // it should be the case that lenConstraint => lenReq
460
309
          lenReq = nm->mkNode(STRING_LENGTH, t0)
461
206
                       .eqNode(nm->mkConst(Rational(0)))
462
206
                       .notNode();
463
103
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
464
103
          rule = PfRule::CONCAT_CSPLIT;
465
        }
466
64
        else if (infer == InferenceId::STRINGS_SSPLIT_VAR_PROP)
467
        {
468
          // it should be the case that lenConstraint => lenReq
469
55
          for (unsigned r = 0; r < 2; r++)
470
          {
471
165
            lenReq = nm->mkNode(GT,
472
110
                                nm->mkNode(STRING_LENGTH, t0),
473
110
                                nm->mkNode(STRING_LENGTH, s0));
474
55
            if (convertLengthPf(lenReq, lenConstraint, psb))
475
            {
476
38
              lenSuccess = true;
477
38
              break;
478
            }
479
17
            if (r == 0)
480
            {
481
              // may be the other direction
482
17
              applySym = true;
483
17
              std::swap(t0, s0);
484
            }
485
          }
486
38
          rule = PfRule::CONCAT_LPROP;
487
        }
488
26
        else if (infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
489
        {
490
          // it should be the case that lenConstraint => lenReq
491
27
          lenReq = nm->mkNode(STRING_LENGTH, t0)
492
18
                       .eqNode(nm->mkConst(Rational(0)))
493
18
                       .notNode();
494
9
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
495
9
          rule = PfRule::CONCAT_CPROP;
496
        }
497
357
        if (!lenSuccess)
498
        {
499
34
          Trace("strings-ipc-core")
500
17
              << "...failed due to length constraint" << std::endl;
501
17
          break;
502
        }
503
        // apply symmetry if necessary
504
340
        if (applySym)
505
        {
506
372
          std::vector<Node> childrenSymm;
507
186
          childrenSymm.push_back(mainEqCeq);
508
          // note this explicit step may not be necessary
509
186
          mainEqCeq = psb.tryStep(PfRule::SYMM, childrenSymm, {});
510
372
          Trace("strings-ipc-core")
511
186
              << "Main equality after SYMM " << mainEqCeq << std::endl;
512
        }
513
340
        if (rule != PfRule::UNKNOWN)
514
        {
515
680
          Trace("strings-ipc-core")
516
340
              << "Core rule length requirement is " << lenReq << std::endl;
517
          // apply the given rule
518
680
          std::vector<Node> childrenMain;
519
340
          childrenMain.push_back(mainEqCeq);
520
340
          childrenMain.push_back(lenReq);
521
680
          std::vector<Node> argsMain;
522
340
          argsMain.push_back(nodeIsRev);
523
680
          Node mainEqMain = psb.tryStep(rule, childrenMain, argsMain);
524
680
          Trace("strings-ipc-core") << "Main equality after " << rule << " "
525
340
                                    << mainEqMain << std::endl;
526
340
          if (mainEqMain == mainEqCeq)
527
          {
528
1
            Trace("strings-ipc-core") << "...undo step" << std::endl;
529
            // not necessary, probably first component of equality
530
1
            psb.popStep();
531
          }
532
          // either equal or rewrites to it
533
680
          std::vector<Node> cexp;
534
340
          if (psb.applyPredTransform(mainEqMain, conc, cexp))
535
          {
536
            // requires that length success is also true
537
339
            useBuffer = true;
538
339
            Trace("strings-ipc-core") << "...success" << std::endl;
539
          }
540
          else
541
          {
542
1
            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
459
      }
551
    }
552
459
    break;
553
    // ========================== Disequalities
554
2
    case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_STRING_SPLIT:
555
    case InferenceId::STRINGS_DEQ_DISL_STRINGS_SPLIT:
556
    {
557
6
      if (conc.getKind() != AND || conc.getNumChildren() != 2
558
4
          || conc[0].getKind() != EQUAL || !conc[0][0].getType().isStringLike()
559
4
          || conc[1].getKind() != EQUAL
560
6
          || 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
4
            nm->mkNode(GEQ, nm->mkNode(STRING_LENGTH, conc[0][0]), conc[1][1]);
569
4
        Trace("strings-ipc-deq")
570
2
            << "length requirement is " << lenReq << std::endl;
571
2
        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
2
          Trace("strings-ipc-deq") << "...fail length" << std::endl;
590
        }
591
      }
592
    }
593
2
    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
38
    case InferenceId::STRINGS_RE_UNFOLD_POS:
620
    case InferenceId::STRINGS_RE_UNFOLD_NEG:
621
    {
622
38
      Assert (!ps.d_children.empty());
623
38
      size_t nchild = ps.d_children.size();
624
76
      Node mem = ps.d_children[nchild-1];
625
38
      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
38
      PfRule r = PfRule::UNKNOWN;
636
38
      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
38
      else if (infer == InferenceId::STRINGS_RE_UNFOLD_POS)
643
      {
644
28
        r = PfRule::RE_UNFOLD_POS;
645
      }
646
      else
647
      {
648
10
        r = PfRule::RE_UNFOLD_NEG;
649
        // it may be an optimized form of concat simplification
650
10
        Assert(mem.getKind() == NOT && mem[0].getKind() == STRING_IN_REGEXP);
651
10
        if (mem[0][1].getKind() == REGEXP_CONCAT)
652
        {
653
          size_t index;
654
20
          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
10
          if (!reLen.isNull())
658
          {
659
9
            r = PfRule::RE_UNFOLD_NEG_CONCAT_FIXED;
660
          }
661
        }
662
      }
663
38
      if (useBuffer)
664
      {
665
        mem = psb.tryStep(r, {mem}, {});
666
        // should match the conclusion
667
        useBuffer = (mem==conc);
668
      }
669
      else
670
      {
671
38
        ps.d_rule = r;
672
38
      }
673
    }
674
38
    break;
675
    // ========================== Reduction
676
9
    case InferenceId::STRINGS_CTN_POS:
677
    case InferenceId::STRINGS_CTN_NEG_EQUAL:
678
    {
679
9
      if (ps.d_children.size() != 1)
680
      {
681
        break;
682
      }
683
9
      bool polarity = ps.d_children[0].getKind() != NOT;
684
18
      Node atom = polarity ? ps.d_children[0] : ps.d_children[0][0];
685
18
      std::vector<Node> args;
686
9
      args.push_back(atom);
687
18
      Node res = psb.tryStep(PfRule::STRING_EAGER_REDUCTION, {}, args);
688
9
      if (res.isNull())
689
      {
690
        break;
691
      }
692
      // ite( contains(x,t), x = k1 ++ t ++ k2, x != t )
693
18
      std::vector<Node> tiChildren;
694
9
      tiChildren.push_back(ps.d_children[0]);
695
      Node ctnt = psb.tryStep(
696
18
          polarity ? PfRule::TRUE_INTRO : PfRule::FALSE_INTRO, tiChildren, {});
697
9
      if (ctnt.isNull() || ctnt.getKind() != EQUAL)
698
      {
699
        break;
700
      }
701
18
      std::vector<Node> tchildren;
702
9
      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
9
      if (psb.applyPredTransform(res, conc, tchildren))
706
      {
707
9
        useBuffer = true;
708
9
      }
709
    }
710
9
    break;
711
712
150
    case InferenceId::STRINGS_REDUCTION:
713
    {
714
150
      size_t nchild = conc.getNumChildren();
715
300
      Node mainEq;
716
150
      if (conc.getKind() == EQUAL)
717
      {
718
        mainEq = conc;
719
      }
720
150
      else if (conc.getKind() == AND && conc[nchild - 1].getKind() == EQUAL)
721
      {
722
150
        mainEq = conc[nchild - 1];
723
      }
724
150
      if (mainEq.isNull())
725
      {
726
        Trace("strings-ipc-red") << "Bad Reduction: " << conc << std::endl;
727
        Assert(false) << "Unexpected reduction " << conc;
728
        break;
729
      }
730
300
      std::vector<Node> argsRed;
731
      // the left hand side of the last conjunct is the term we are reducing
732
150
      argsRed.push_back(mainEq[0]);
733
300
      Node red = psb.tryStep(PfRule::STRING_REDUCTION, {}, argsRed);
734
150
      Trace("strings-ipc-red") << "Reduction : " << red << std::endl;
735
150
      if (!red.isNull())
736
      {
737
        // either equal or rewrites to it
738
300
        std::vector<Node> cexp;
739
150
        if (psb.applyPredTransform(red, conc, cexp))
740
        {
741
142
          Trace("strings-ipc-red") << "...success!" << std::endl;
742
142
          useBuffer = true;
743
        }
744
        else
745
        {
746
8
          Trace("strings-ipc-red") << "...failed to reduce" << std::endl;
747
        }
748
150
      }
749
    }
750
150
    break;
751
    // ========================== code injectivity
752
30
    case InferenceId::STRINGS_CODE_INJ:
753
    {
754
30
      ps.d_rule = PfRule::STRING_CODE_INJ;
755
30
      Assert(conc.getKind() == OR && conc.getNumChildren() == 3
756
             && conc[2].getKind() == EQUAL);
757
30
      ps.d_args.push_back(conc[2][0]);
758
30
      ps.d_args.push_back(conc[2][1]);
759
    }
760
30
    break;
761
    // ========================== unit injectivity
762
3
    case InferenceId::STRINGS_UNIT_INJ:
763
    {
764
3
      ps.d_rule = PfRule::STRING_SEQ_UNIT_INJ;
765
    }
766
3
    break;
767
    // ========================== prefix conflict
768
7
    case InferenceId::STRINGS_PREFIX_CONFLICT:
769
    {
770
7
      Trace("strings-ipc-prefix") << "Prefix conflict..." << std::endl;
771
14
      std::vector<Node> eqs;
772
18
      for (const Node& e : ps.d_children)
773
      {
774
11
        Kind ek = e.getKind();
775
11
        if (ek == EQUAL)
776
        {
777
7
          Trace("strings-ipc-prefix") << "- equality : " << e << std::endl;
778
7
          eqs.push_back(e);
779
        }
780
4
        else if (ek == STRING_IN_REGEXP)
781
        {
782
          // unfold it and extract the equality
783
8
          std::vector<Node> children;
784
4
          children.push_back(e);
785
8
          std::vector<Node> args;
786
8
          Node eunf = psb.tryStep(PfRule::RE_UNFOLD_POS, children, args);
787
8
          Trace("strings-ipc-prefix")
788
4
              << "--- " << e << " unfolds to " << eunf << std::endl;
789
4
          if (eunf.isNull())
790
          {
791
            continue;
792
          }
793
4
          else if (eunf.getKind() == AND)
794
          {
795
            // equality is the first conjunct
796
8
            std::vector<Node> childrenAE;
797
4
            childrenAE.push_back(eunf);
798
8
            std::vector<Node> argsAE;
799
4
            argsAE.push_back(nm->mkConst(Rational(0)));
800
8
            Node eunfAE = psb.tryStep(PfRule::AND_ELIM, childrenAE, argsAE);
801
8
            Trace("strings-ipc-prefix")
802
4
                << "--- and elim to " << eunfAE << std::endl;
803
4
            if (eunfAE.isNull() || eunfAE.getKind() != EQUAL)
804
            {
805
              Assert(false)
806
                  << "Unexpected unfolded premise " << eunf << " for " << infer;
807
              continue;
808
            }
809
8
            Trace("strings-ipc-prefix")
810
4
                << "- equality : " << eunfAE << std::endl;
811
4
            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
7
      if (eqs.empty())
826
      {
827
        break;
828
      }
829
      // connect via transitivity
830
14
      Node curr = eqs[0];
831
11
      for (size_t i = 1, esize = eqs.size(); i < esize; i++)
832
      {
833
8
        Node prev = curr;
834
4
        curr = convertTrans(curr, eqs[1], psb);
835
4
        if (curr.isNull())
836
        {
837
          break;
838
        }
839
4
        Trace("strings-ipc-prefix") << "- Via trans: " << curr << std::endl;
840
      }
841
7
      if (curr.isNull())
842
      {
843
        break;
844
      }
845
14
      Trace("strings-ipc-prefix")
846
7
          << "- Possible conflicting equality : " << curr << std::endl;
847
14
      std::vector<Node> emp;
848
14
      Node concE = psb.applyPredElim(curr, emp);
849
14
      Trace("strings-ipc-prefix")
850
7
          << "- After pred elim: " << concE << std::endl;
851
7
      if (concE == conc)
852
      {
853
        Trace("strings-ipc-prefix") << "...success!" << std::endl;
854
        useBuffer = true;
855
7
      }
856
    }
857
7
    break;
858
    // ========================== regular expressions
859
7
    case InferenceId::STRINGS_RE_INTER_INCLUDE:
860
    case InferenceId::STRINGS_RE_INTER_CONF:
861
    case InferenceId::STRINGS_RE_INTER_INFER:
862
    {
863
14
      std::vector<Node> reiExp;
864
14
      std::vector<Node> reis;
865
14
      std::vector<Node> reiChildren;
866
14
      std::vector<Node> reiChildrenOrig;
867
14
      Node x;
868
      // make the regular expression intersection that summarizes all
869
      // memberships in the explanation
870
23
      for (const Node& c : ps.d_children)
871
      {
872
16
        bool polarity = c.getKind() != NOT;
873
30
        Node catom = polarity ? c : c[0];
874
18
        if (catom.getKind() != STRING_IN_REGEXP)
875
        {
876
2
          Assert(c.getKind() == EQUAL);
877
2
          if (c.getKind() == EQUAL)
878
          {
879
2
            reiExp.push_back(c);
880
          }
881
2
          continue;
882
        }
883
14
        if (x.isNull())
884
        {
885
          // just take the first LHS; others should be equated to it by exp
886
7
          x = catom[0];
887
        }
888
        Node rcurr =
889
28
            polarity ? catom[1] : nm->mkNode(REGEXP_COMPLEMENT, catom[1]);
890
14
        reis.push_back(rcurr);
891
28
        Node mem = nm->mkNode(STRING_IN_REGEXP, catom[0], rcurr);
892
14
        reiChildren.push_back(mem);
893
14
        reiChildrenOrig.push_back(c);
894
      }
895
      // go back and justify each premise
896
7
      bool successChildren = true;
897
21
      for (size_t i = 0, nchild = reiChildren.size(); i < nchild; i++)
898
      {
899
14
        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
7
      if (!successChildren)
909
      {
910
        break;
911
      }
912
14
      Node mem = psb.tryStep(PfRule::RE_INTER, reiChildren, {});
913
14
      Trace("strings-ipc-re")
914
7
          << "Regular expression summary: " << mem << std::endl;
915
      // the conclusion is rewritable to the premises via rewriting?
916
7
      if (psb.applyPredTransform(mem, conc, {}))
917
      {
918
2
        Trace("strings-ipc-re") << "... success!" << std::endl;
919
2
        useBuffer = true;
920
      }
921
      else
922
      {
923
10
        Trace("strings-ipc-re")
924
5
            << "...failed to rewrite to conclusion" << std::endl;
925
7
      }
926
    }
927
7
    break;
928
    // ========================== unknown and currently unsupported
929
3
    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
3
      break;
945
  }
946
947
  // now see if we would succeed with the checker-to-try
948
987
  bool success = false;
949
987
  if (ps.d_rule != PfRule::UNKNOWN)
950
  {
951
164
    Trace("strings-ipc") << "For " << infer << ", try proof rule " << ps.d_rule
952
82
                         << "...";
953
82
    Assert(ps.d_rule != PfRule::UNKNOWN);
954
164
    Node pconc = psb.tryStep(ps.d_rule, ps.d_children, ps.d_args);
955
82
    if (pconc.isNull() || pconc != conc)
956
    {
957
2
      Trace("strings-ipc") << "failed, pconc is " << pconc << " (expected "
958
1
                           << conc << ")" << std::endl;
959
1
      ps.d_rule = PfRule::UNKNOWN;
960
    }
961
    else
962
    {
963
      // successfully set up a single step proof in ps
964
81
      success = true;
965
81
      Trace("strings-ipc") << "success!" << std::endl;
966
    }
967
  }
968
905
  else if (useBuffer)
969
  {
970
    // successfully set up a multi step proof in psb
971
787
    success = true;
972
  }
973
  else
974
  {
975
236
    Trace("strings-ipc") << "For " << infer << " " << conc
976
118
                         << ", no proof rule, failed" << std::endl;
977
  }
978
987
  if (!success)
979
  {
980
    // debug print
981
119
    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
119
    ps.d_args.clear();
994
119
    ps.d_args.push_back(conc);
995
238
    Node t = builtin::BuiltinProofRuleChecker::mkTheoryIdNode(THEORY_STRINGS);
996
119
    ps.d_args.push_back(t);
997
    // use the trust rule
998
119
    ps.d_rule = PfRule::THEORY_INFERENCE;
999
  }
1000
987
  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
987
}
1021
1022
359
bool InferProofCons::convertLengthPf(Node lenReq,
1023
                                     const std::vector<Node>& lenExp,
1024
                                     TheoryProofStepBuffer& psb)
1025
{
1026
653
  for (const Node& le : lenExp)
1027
  {
1028
483
    if (lenReq == le)
1029
    {
1030
189
      return true;
1031
    }
1032
  }
1033
340
  Trace("strings-ipc-len") << "Must explain " << lenReq << " by " << lenExp
1034
170
                           << std::endl;
1035
223
  for (const Node& le : lenExp)
1036
  {
1037
    // probably rewrites to it?
1038
257
    std::vector<Node> exp;
1039
204
    if (psb.applyPredTransform(le, lenReq, exp))
1040
    {
1041
86
      Trace("strings-ipc-len") << "...success by rewrite" << std::endl;
1042
86
      return true;
1043
    }
1044
    // maybe x != "" => len(x) != 0
1045
171
    std::vector<Node> children;
1046
118
    children.push_back(le);
1047
171
    std::vector<Node> args;
1048
171
    Node res = psb.tryStep(PfRule::STRING_LENGTH_NON_EMPTY, children, args);
1049
118
    if (res == lenReq)
1050
    {
1051
65
      Trace("strings-ipc-len") << "...success by LENGTH_NON_EMPTY" << std::endl;
1052
65
      return true;
1053
    }
1054
  }
1055
19
  Trace("strings-ipc-len") << "...failed" << std::endl;
1056
19
  return false;
1057
}
1058
1059
4
Node InferProofCons::convertTrans(Node eqa,
1060
                                  Node eqb,
1061
                                  TheoryProofStepBuffer& psb)
1062
{
1063
4
  if (eqa.getKind() != EQUAL || eqb.getKind() != EQUAL)
1064
  {
1065
    return Node::null();
1066
  }
1067
4
  for (uint32_t i = 0; i < 2; i++)
1068
  {
1069
4
    Node eqaSym = i == 0 ? eqa[1].eqNode(eqa[0]) : eqa;
1070
5
    for (uint32_t j = 0; j < 2; j++)
1071
    {
1072
6
      Node eqbSym = j == 0 ? eqb : eqb[1].eqNode(eqb[1]);
1073
5
      if (eqa[i] == eqb[j])
1074
      {
1075
8
        std::vector<Node> children;
1076
4
        children.push_back(eqaSym);
1077
4
        children.push_back(eqbSym);
1078
4
        return psb.tryStep(PfRule::TRANS, children, {});
1079
      }
1080
    }
1081
  }
1082
  return Node::null();
1083
}
1084
1085
6869
std::shared_ptr<ProofNode> InferProofCons::getProofFor(Node fact)
1086
{
1087
  // get the inference
1088
6869
  NodeInferInfoMap::iterator it = d_lazyFactMap.find(fact);
1089
6869
  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
6869
  AlwaysAssert(it != d_lazyFactMap.end());
1100
13738
  std::shared_ptr<InferInfo> ii = (*it).second;
1101
6869
  Assert(ii->d_conc == fact);
1102
  // make a placeholder proof using STRINGS_INFERENCE, which is reconstructed
1103
  // during post-process
1104
13738
  CDProof pf(d_pnm);
1105
13738
  std::vector<Node> args;
1106
6869
  packArgs(ii->d_conc, ii->getId(), ii->d_idRev, ii->d_premises, args);
1107
  // must flatten
1108
13738
  std::vector<Node> exp;
1109
26276
  for (const Node& ec : ii->d_premises)
1110
  {
1111
19407
    utils::flattenOp(AND, ec, exp);
1112
  }
1113
6869
  pf.addStep(fact, PfRule::STRING_INFERENCE, exp, args);
1114
13738
  return pf.getProofFor(fact);
1115
}
1116
1117
19887
std::string InferProofCons::identify() const
1118
{
1119
19887
  return "strings::InferProofCons";
1120
}
1121
1122
571
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
1142
  std::unordered_set<Node> termsToPurify;
1129
1688
  for (const Node& nc : children)
1130
  {
1131
1117
    Assert(nc.getKind() == EQUAL && nc[0].getType().isStringLike());
1132
1117
    termsToPurify.insert(nc[0]);
1133
  }
1134
  // now, purify each of the children of the substitution
1135
1688
  for (size_t i = 0, nchild = children.size(); i < nchild; i++)
1136
  {
1137
2234
    Node pnc = purifyCorePredicate(children[i], true, psb, termsToPurify);
1138
1117
    if (pnc.isNull())
1139
    {
1140
      return false;
1141
    }
1142
1117
    if (children[i] != pnc)
1143
    {
1144
138
      Trace("strings-ipc-pure-subs")
1145
69
          << "Converted: " << children[i] << " to " << pnc << std::endl;
1146
69
      children[i] = pnc;
1147
    }
1148
    // we now should have a substitution with only atomic terms
1149
1117
    Assert(children[i][0].getNumChildren() == 0);
1150
  }
1151
  // now, purify the target predicate
1152
571
  tgt = purifyCorePredicate(tgt, concludeTgtNew, psb, termsToPurify);
1153
571
  return !tgt.isNull();
1154
}
1155
1156
1688
Node InferProofCons::purifyCorePredicate(
1157
    Node lit,
1158
    bool concludeNew,
1159
    TheoryProofStepBuffer& psb,
1160
    std::unordered_set<Node>& termsToPurify)
1161
{
1162
1688
  bool pol = lit.getKind() != NOT;
1163
3376
  Node atom = pol ? lit : lit[0];
1164
1688
  if (atom.getKind() != EQUAL || !atom[0].getType().isStringLike())
1165
  {
1166
    // we only purify string (dis)equalities
1167
6
    return lit;
1168
  }
1169
  // purify both sides of the equality
1170
3364
  std::vector<Node> pcs;
1171
1682
  bool childChanged = false;
1172
5046
  for (const Node& lc : atom)
1173
  {
1174
6728
    Node plc = purifyCoreTerm(lc, termsToPurify);
1175
3364
    childChanged = childChanged || plc != lc;
1176
3364
    pcs.push_back(plc);
1177
  }
1178
1682
  if (!childChanged)
1179
  {
1180
1554
    return lit;
1181
  }
1182
128
  NodeManager* nm = NodeManager::currentNM();
1183
256
  Node newLit = nm->mkNode(EQUAL, pcs);
1184
128
  if (!pol)
1185
  {
1186
    newLit = newLit.notNode();
1187
  }
1188
128
  Assert(lit != newLit);
1189
  // prove by transformation, should always succeed
1190
128
  if (!psb.applyPredTransform(
1191
          concludeNew ? lit : newLit, concludeNew ? newLit : lit, {}))
1192
  {
1193
    // failed, return null
1194
    return Node::null();
1195
  }
1196
128
  return newLit;
1197
}
1198
1199
7307
Node InferProofCons::purifyCoreTerm(Node n,
1200
                                    std::unordered_set<Node>& termsToPurify)
1201
{
1202
7307
  Assert(n.getType().isStringLike());
1203
7307
  if (n.getNumChildren() == 0)
1204
  {
1205
5471
    return n;
1206
  }
1207
1836
  NodeManager* nm = NodeManager::currentNM();
1208
1836
  if (n.getKind() == STRING_CONCAT)
1209
  {
1210
3058
    std::vector<Node> pcs;
1211
5472
    for (const Node& nc : n)
1212
    {
1213
3943
      pcs.push_back(purifyCoreTerm(nc, termsToPurify));
1214
    }
1215
1529
    return nm->mkNode(STRING_CONCAT, pcs);
1216
  }
1217
307
  if (termsToPurify.find(n) == termsToPurify.end())
1218
  {
1219
    // did not need to purify
1220
173
    return n;
1221
  }
1222
134
  SkolemManager* sm = nm->getSkolemManager();
1223
268
  Node k = sm->mkPurifySkolem(n, "k");
1224
134
  return k;
1225
}
1226
1227
}  // namespace strings
1228
}  // namespace theory
1229
29574
}  // namespace cvc5