Branch data Line data Source code
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-2025 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/node_algorithm.h"
19 : : #include "expr/skolem_manager.h"
20 : : #include "options/smt_options.h"
21 : : #include "options/strings_options.h"
22 : : #include "proof/proof_node_algorithm.h"
23 : : #include "proof/proof_node_manager.h"
24 : : #include "smt/env.h"
25 : : #include "theory/builtin/proof_checker.h"
26 : : #include "theory/rewriter.h"
27 : : #include "theory/strings/core_solver.h"
28 : : #include "theory/strings/regexp_operation.h"
29 : : #include "theory/strings/theory_strings_utils.h"
30 : : #include "theory/strings/word.h"
31 : : #include "util/statistics_registry.h"
32 : :
33 : : using namespace cvc5::internal::kind;
34 : :
35 : : namespace cvc5::internal {
36 : : namespace theory {
37 : : namespace strings {
38 : :
39 : : /**
40 : : * Counts the number of times we traverse beneath a "non-core" operator.
41 : : * This is used to reason about substitutions that assume reasoning about
42 : : * concatentation and (dis)equalities only.
43 : : */
44 : : class StringCoreTermContext : public TermContext
45 : : {
46 : : public:
47 : 5565 : StringCoreTermContext() {}
48 : : /** The initial value: not nested. */
49 : 11130 : uint32_t initialValue() const override { return 0; }
50 : : /** Compute the value of the index^th child of t whose hash is tval */
51 : 153372 : uint32_t computeValue(TNode t,
52 : : uint32_t tval,
53 : : CVC5_UNUSED size_t index) const override
54 : : {
55 [ + + ]: 153372 : if (tval < 2)
56 : : {
57 : 122578 : Kind k = t.getKind();
58 : : // kinds we wish to substitute beneath
59 [ + + ][ + + ]: 122578 : if (k == Kind::NOT || k == Kind::EQUAL || k == Kind::STRING_CONCAT)
[ + + ]
60 : : {
61 : 80356 : return tval;
62 : : }
63 : 42222 : return tval + 1;
64 : : }
65 : 30794 : return 2;
66 : : }
67 : : };
68 : :
69 : 20276 : InferProofCons::InferProofCons(Env& env, context::Context* c)
70 : 20276 : : EnvObj(env), d_lazyFactMap(c)
71 : : {
72 : 20276 : }
73 : :
74 : 37521 : void InferProofCons::notifyFact(const InferInfo& ii)
75 : : {
76 : 37521 : Node fact = ii.d_conc;
77 [ + - ]: 75042 : Trace("strings-ipc-notify")
78 : 37521 : << "InferProofCons::notifyFact: " << ii << std::endl;
79 [ + + ]: 37521 : if (d_lazyFactMap.find(fact) != d_lazyFactMap.end())
80 : : {
81 [ + - ]: 399 : Trace("strings-ipc-notify") << "...duplicate!" << std::endl;
82 : 399 : return;
83 : : }
84 : 37122 : Node symFact = CDProof::getSymmFact(fact);
85 [ + + ][ + + ]: 37122 : if (!symFact.isNull() && d_lazyFactMap.find(symFact) != d_lazyFactMap.end())
[ + + ]
86 : : {
87 [ + - ]: 50 : Trace("strings-ipc-notify") << "...duplicate (sym)!" << std::endl;
88 : 50 : return;
89 : : }
90 : 37072 : std::shared_ptr<InferInfo> iic = std::make_shared<InferInfo>(ii);
91 : 37072 : d_lazyFactMap.insert(ii.d_conc, iic);
92 [ + + ][ + + ]: 37571 : }
93 : :
94 : 18252 : void InferProofCons::notifyLemma(const InferInfo& ii)
95 : : {
96 : 18252 : d_lazyFactMap[ii.d_conc] = std::make_shared<InferInfo>(ii);
97 : 18252 : }
98 : :
99 : 25212 : void InferProofCons::packArgs(Node conc,
100 : : InferenceId infer,
101 : : bool isRev,
102 : : const std::vector<Node>& exp,
103 : : std::vector<Node>& args)
104 : : {
105 : 25212 : NodeManager* nm = conc.getNodeManager();
106 : 25212 : args.push_back(conc);
107 : 25212 : args.push_back(mkInferenceIdNode(nm, infer));
108 : 25212 : args.push_back(nm->mkConst(isRev));
109 : : // The vector exp is stored as arguments; its flatten form are premises. We
110 : : // need both since the grouping of exp is important, e.g. { (and a b), c }
111 : : // is different from { a, b, c } in the convert routine, since positions
112 : : // of formulas in exp have special meaning.
113 : 25212 : args.insert(args.end(), exp.begin(), exp.end());
114 : 25212 : }
115 : :
116 : 8619 : bool InferProofCons::unpackArgs(const std::vector<Node>& args,
117 : : Node& conc,
118 : : InferenceId& infer,
119 : : bool& isRev,
120 : : std::vector<Node>& exp)
121 : : {
122 [ - + ][ - + ]: 8619 : Assert(args.size() >= 3);
[ - - ]
123 : 8619 : conc = args[0];
124 [ - + ]: 8619 : if (!getInferenceId(args[1], infer))
125 : : {
126 : 0 : return false;
127 : : }
128 : 8619 : isRev = args[2].getConst<bool>();
129 : 8619 : exp.insert(exp.end(), args.begin() + 3, args.end());
130 : 8619 : return true;
131 : : }
132 : :
133 : : /** convert
134 : : *
135 : : * This method converts this call to instructions on what the proof rule
136 : : * step(s) are for concluding the conclusion of the inference. This
137 : : * information is either:
138 : : *
139 : : * (A) stored in the argument ps, which consists of:
140 : : * - A proof rule identifier (ProofStep::d_rule).
141 : : * - The premises of the proof step (ProofStep::d_children).
142 : : * - Arguments to the proof step (ProofStep::d_args).
143 : : *
144 : : * (B) If the proof for the inference cannot be captured by a single
145 : : * step, then the d_rule field of ps is not set, and useBuffer is set to
146 : : * true. In this case, the argument psb is updated to contain (possibly
147 : : * multiple) proof steps for how to construct a proof for the given inference.
148 : : * In particular, psb will contain a set of steps that form a proof
149 : : * whose conclusion is conc and whose free assumptions are exp.
150 : : */
151 : 8619 : bool InferProofCons::convert(Env& env,
152 : : InferenceId infer,
153 : : bool isRev,
154 : : Node conc,
155 : : const std::vector<Node>& exp,
156 : : CDProof* pf)
157 : : {
158 : : // now go back and convert it to proof steps and add to proof
159 : 8619 : bool useBuffer = false;
160 : 8619 : ProofStep ps;
161 : : // ensure proof steps are unique
162 : 8619 : TheoryProofStepBuffer psb(pf->getManager()->getChecker(), true);
163 : : // Must flatten children with respect to AND to be ready to explain.
164 : : // We store the index where each flattened vector begins, since some
165 : : // explanations are grouped together using AND.
166 : 8619 : std::vector<size_t> startExpIndex;
167 [ + + ]: 28483 : for (const Node& ec : exp)
168 : : {
169 : : // store the index in the flattened vector
170 : 19864 : startExpIndex.push_back(ps.d_children.size());
171 : 19864 : utils::flattenOp(Kind::AND, ec, ps.d_children);
172 : : }
173 : : // debug print
174 [ - + ]: 8619 : if (TraceIsOn("strings-ipc-debug"))
175 : : {
176 [ - - ]: 0 : Trace("strings-ipc-debug") << "InferProofCons::convert: " << infer
177 [ - - ]: 0 : << (isRev ? " :rev " : " ") << conc << std::endl;
178 [ - - ]: 0 : for (const Node& ec : ps.d_children)
179 : : {
180 [ - - ]: 0 : Trace("strings-ipc-debug") << " e: " << ec << std::endl;
181 : : }
182 : : }
183 : : // try to find a set of proof steps to incorporate into the buffer
184 : 8619 : psb.clear();
185 : : // explicitly add ASSUME steps to the proof step buffer for premises of the
186 : : // inference, so that they will not be overwritten in the reconstruction
187 : : // below
188 [ + + ]: 28783 : for (const Node& ec : ps.d_children)
189 : : {
190 [ + - ]: 20164 : Trace("strings-ipc-debug") << "Explicit add " << ec << std::endl;
191 : 40328 : psb.addStep(ProofRule::ASSUME, {}, {ec}, ec);
192 : : }
193 : 8619 : NodeManager* nm = conc.getNodeManager();
194 : 8619 : Node nodeIsRev = nm->mkConst(isRev);
195 [ + + ][ + + ]: 8619 : switch (infer)
[ + + ][ - + ]
[ + + ][ + + ]
[ + + ][ + + ]
[ + ]
196 : : {
197 : : // ========================== equal by substitution+rewriting
198 : 2481 : case InferenceId::STRINGS_EXTF:
199 : : case InferenceId::STRINGS_EXTF_N:
200 : : case InferenceId::STRINGS_I_NORM_S:
201 : : case InferenceId::STRINGS_I_CONST_MERGE:
202 : : case InferenceId::STRINGS_I_NORM:
203 : : case InferenceId::STRINGS_LEN_NORM:
204 : : case InferenceId::STRINGS_NORMAL_FORM:
205 : : case InferenceId::STRINGS_CODE_PROXY:
206 : : case InferenceId::STRINGS_EXTF_REW_SAME:
207 : : case InferenceId::STRINGS_I_CYCLE_CONFLICT:
208 : : {
209 : 2481 : size_t idMax = 0;
210 : : // These inferences assume the substitution is applied to the
211 : : // *arguments* of extended functions and the length function, so we
212 : : // will allow the substitutions to fire in term context value one.
213 [ + + ]: 2481 : if (infer == InferenceId::STRINGS_EXTF
214 [ + + ]: 1995 : || infer == InferenceId::STRINGS_EXTF_N
215 [ + + ]: 1353 : || infer == InferenceId::STRINGS_LEN_NORM
216 [ + + ]: 1313 : || infer == InferenceId::STRINGS_EXTF_REW_SAME)
217 : : {
218 : 1216 : idMax = 1;
219 : : }
220 : : // apply the substitution to conclude conc = conc', where conc' is the
221 : : // result of applying the substitution to conc'. This method further
222 : : // concludes conc from conc'. It then remains to prove conc' below.
223 : : Node concr =
224 : 2481 : convertCoreSubs(env, pf, psb, conc, ps.d_children, 0, idMax, true);
225 [ + - ]: 2481 : Trace("strings-ipc-core") << "Rewrote conclusion" << std::endl;
226 [ + - ]: 2481 : Trace("strings-ipc-core") << "- " << conc << std::endl;
227 [ + - ]: 2481 : Trace("strings-ipc-core") << "- to " << concr << std::endl;
228 [ + - ]: 2481 : if (psb.applyPredIntro(concr,
229 : : {},
230 : : MethodId::SB_DEFAULT,
231 : : MethodId::SBA_SEQUENTIAL,
232 : : MethodId::RW_EXT_REWRITE))
233 : : {
234 : : // maybe extended rewrite
235 : 2481 : useBuffer = true;
236 : : }
237 : 2481 : }
238 : 2481 : break;
239 : : // ========================== substitution + rewriting
240 : 68 : case InferenceId::STRINGS_RE_NF_CONFLICT:
241 : : case InferenceId::STRINGS_EXTF_D:
242 : : case InferenceId::STRINGS_EXTF_D_N:
243 : : case InferenceId::STRINGS_I_CONST_CONFLICT:
244 : : case InferenceId::STRINGS_UNIT_CONST_CONFLICT:
245 : : case InferenceId::STRINGS_ARITH_BOUND_CONFLICT:
246 : : {
247 [ + - ]: 68 : if (!ps.d_children.empty())
248 : : {
249 : 68 : std::vector<Node> exps(ps.d_children.begin(), ps.d_children.end() - 1);
250 : 68 : Node psrc = ps.d_children[ps.d_children.size() - 1];
251 : : // we apply the substitution on the purified form to get the
252 : : // original conclusion
253 [ + + ]: 68 : if (psb.applyPredTransform(psrc, conc, exps))
254 : : {
255 : 20 : useBuffer = true;
256 : : }
257 : : else
258 : : {
259 : : // More aggressive: lift to original form and use extended rewriting.
260 : : // A common case that this covers is arithmetic bound conflicts like
261 : : // (= (str.len @purifyN) 5) where @purifyN is the purification skolem
262 : : // for (str.++ "ABCDEF" x).
263 : 48 : Node psrco = SkolemManager::getOriginalForm(psrc);
264 [ + + ]: 48 : if (psb.applyPredTransform(psrco,
265 : : conc,
266 : : exps,
267 : : MethodId::SB_DEFAULT,
268 : : MethodId::SBA_SEQUENTIAL,
269 : : MethodId::RW_EXT_REWRITE))
270 : : {
271 : 40 : useBuffer = psb.applyPredTransform(psrc, psrco, {});
272 : : }
273 : 48 : }
274 : : // Maybe involves AND_ELIM?
275 [ + + ]: 68 : if (!useBuffer)
276 : : {
277 : 8 : Node res = psb.applyPredElim(psrc, exps);
278 : 8 : useBuffer = convertAndElim(nm, res, conc, psb);
279 : 8 : }
280 : 68 : }
281 : : else
282 : : {
283 : : // use the predicate version?
284 : 0 : ps.d_args.push_back(conc);
285 : 0 : ps.d_rule = ProofRule::MACRO_SR_PRED_INTRO;
286 : : }
287 : : }
288 : 68 : break;
289 : : // ========================== rewrite pred
290 : 276 : case InferenceId::STRINGS_EXTF_EQ_REW:
291 : : {
292 : : // the last child is the predicate we are operating on, move to front
293 : 276 : Node src = ps.d_children[ps.d_children.size() - 1];
294 [ + - ]: 552 : Trace("strings-ipc-core")
295 : 0 : << "Generate proof for STRINGS_EXTF_EQ_REW, starting with " << src
296 : 276 : << std::endl;
297 : : // apply the substitution using the proper contextual information
298 : : // using the utility method
299 : 276 : std::vector<Node> expe(ps.d_children.begin(), ps.d_children.end() - 1);
300 : 276 : Node mainEqSRew = convertCoreSubs(env, pf, psb, src, expe, 1, 1);
301 [ + - ]: 276 : Trace("strings-ipc-core") << "...after subs: " << mainEqSRew << std::endl;
302 : 276 : mainEqSRew = psb.applyPredElim(mainEqSRew, {});
303 [ + - ]: 552 : Trace("strings-ipc-core")
304 : 276 : << "...after pred elim: " << mainEqSRew << std::endl;
305 [ - + ]: 276 : if (mainEqSRew == conc)
306 : : {
307 [ - - ]: 0 : Trace("strings-ipc-core") << "...success" << std::endl;
308 : 0 : useBuffer = true;
309 : 0 : break;
310 : : }
311 [ - + ]: 276 : else if (mainEqSRew.getKind() != Kind::EQUAL)
312 : : {
313 : : // Note this can happen in rare cases where substitution+rewriting
314 : : // is more powerful than congruence+rewriting. We fail to reconstruct
315 : : // the proof in this case.
316 [ - - ]: 0 : Trace("strings-ipc-core")
317 : 0 : << "...failed, not equality after rewriting" << std::endl;
318 : 0 : break;
319 : : }
320 : : // may need the "extended equality rewrite"
321 : : Node mainEqSRew2 = psb.applyPredElim(mainEqSRew,
322 : : {},
323 : : MethodId::SB_DEFAULT,
324 : : MethodId::SBA_SEQUENTIAL,
325 : 552 : MethodId::RW_REWRITE_EQ_EXT);
326 [ + - ]: 552 : Trace("strings-ipc-core")
327 : 276 : << "...after extended equality rewrite: " << mainEqSRew2 << std::endl;
328 : : // it may have rewritten to an AND, in which case we get the conjunct
329 [ + + ]: 276 : if (convertAndElim(nm, mainEqSRew2, conc, psb))
330 : : {
331 : 220 : useBuffer = true;
332 : 220 : break;
333 : : }
334 : : // rewrite again with default rewriter
335 : 112 : Node mainEqSRew3 = psb.applyPredElim(mainEqSRew2, {});
336 : 56 : useBuffer = (mainEqSRew3 == conc);
337 [ + + ][ + + ]: 936 : }
[ + + ][ + + ]
338 : 56 : break;
339 : : // ========================== extensionality
340 : 32 : case InferenceId::STRINGS_DEQ_EXTENSIONALITY:
341 : : {
342 : 32 : ps.d_rule = ProofRule::STRING_EXT;
343 : : }
344 : 32 : break;
345 : : // ========================== substitution+rewriting, CONCAT_EQ, ...
346 : 3685 : case InferenceId::STRINGS_F_CONST:
347 : : case InferenceId::STRINGS_F_UNIFY:
348 : : case InferenceId::STRINGS_F_ENDPOINT_EMP:
349 : : case InferenceId::STRINGS_F_ENDPOINT_EQ:
350 : : case InferenceId::STRINGS_F_NCTN:
351 : : case InferenceId::STRINGS_N_EQ_CONF:
352 : : case InferenceId::STRINGS_N_CONST:
353 : : case InferenceId::STRINGS_N_UNIFY:
354 : : case InferenceId::STRINGS_N_ENDPOINT_EMP:
355 : : case InferenceId::STRINGS_N_ENDPOINT_EQ:
356 : : case InferenceId::STRINGS_N_NCTN:
357 : : case InferenceId::STRINGS_SSPLIT_CST_PROP:
358 : : case InferenceId::STRINGS_SSPLIT_VAR_PROP:
359 : : case InferenceId::STRINGS_SSPLIT_CST:
360 : : case InferenceId::STRINGS_SSPLIT_VAR:
361 : : {
362 [ + - ]: 7370 : Trace("strings-ipc-core") << "Generate core rule for " << infer
363 : 3685 : << " (rev=" << isRev << ")" << std::endl;
364 : : // All of the above inferences have the form:
365 : : // (explanation for why t and s have the same prefix/suffix) ^
366 : : // t = s ^
367 : : // (length constraint)?
368 : : // We call t=s the "main equality" below. The length constraint is
369 : : // optional, which we split on below.
370 : 3685 : size_t nchild = ps.d_children.size();
371 : 3685 : size_t mainEqIndex = 0;
372 : 3685 : bool mainEqIndexSet = false;
373 : : // the length constraint
374 : 3685 : std::vector<Node> lenConstraint;
375 : : // these inferences have a length constraint as the last explain
376 [ + + ][ + + ]: 3685 : if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY
377 [ + + ][ + + ]: 2418 : || infer == InferenceId::STRINGS_SSPLIT_CST || infer == InferenceId::STRINGS_SSPLIT_VAR
378 [ + + ]: 1771 : || infer == InferenceId::STRINGS_SSPLIT_VAR_PROP
379 [ + + ]: 1468 : || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
380 : : {
381 [ + - ]: 2353 : if (exp.size() >= 2)
382 : : {
383 [ - + ][ - + ]: 2353 : Assert(exp.size() <= startExpIndex.size());
[ - - ]
384 : : // The index of the "main" equality is the last equality before
385 : : // the length explanation.
386 : 2353 : mainEqIndex = startExpIndex[exp.size() - 1] - 1;
387 : 2353 : mainEqIndexSet = true;
388 : : // the remainder is the length constraint
389 : 4706 : lenConstraint.insert(lenConstraint.end(),
390 : 4706 : ps.d_children.begin() + mainEqIndex + 1,
391 : : ps.d_children.end());
392 : : }
393 : : }
394 [ + - ]: 1332 : else if (nchild >= 1)
395 : : {
396 : : // The index of the main equality is the last child.
397 : 1332 : mainEqIndex = nchild - 1;
398 : 1332 : mainEqIndexSet = true;
399 : : }
400 : 3685 : Node mainEq;
401 [ + - ]: 3685 : if (mainEqIndexSet)
402 : : {
403 : 3685 : mainEq = ps.d_children[mainEqIndex];
404 [ + - ]: 7370 : Trace("strings-ipc-core") << "Main equality " << mainEq << " at index "
405 : 3685 : << mainEqIndex << std::endl;
406 : : }
407 [ + - ][ - + ]: 3685 : if (mainEq.isNull() || mainEq.getKind() != Kind::EQUAL)
[ - + ]
408 : : {
409 [ - - ]: 0 : Trace("strings-ipc-core")
410 : 0 : << "...failed to find main equality" << std::endl;
411 : 0 : break;
412 : : }
413 : : // apply MACRO_SR_PRED_ELIM using equalities up to the main eq
414 : : // we purify the core substitution
415 : : std::vector<Node> pcsr(ps.d_children.begin(),
416 : 3685 : ps.d_children.begin() + mainEqIndex);
417 : 3685 : Node pmainEq = mainEq;
418 : : // if there are substitutions to apply
419 [ + + ]: 3685 : if (mainEqIndex > 0)
420 : : {
421 : : // Compute which equalities we want to flip their substitution.
422 : : // Currently this is only an issue if e.g. (= (str.++ a a) (str.++ b c))
423 : : // where we conclude (= a c) from an explanation (= a b) via
424 : : // STRINGS_F_UNIFY, which would otherwise conclude (= b c) if a -> b
425 : : // was processed as a substitution.
426 : : // In contrast, normal form inferences are truly processed as
427 : : // substitutions in the strings core solver, whereas flat form
428 : : // inferences simply consider unification without substitutions, leading
429 : : // to issues like the one above.
430 : : std::vector<Node> rexp(ps.d_children.begin(),
431 : 2808 : ps.d_children.begin() + mainEqIndex);
432 [ + + ]: 2808 : if (infer == InferenceId::STRINGS_F_UNIFY
433 [ + + ]: 2657 : || infer == InferenceId::STRINGS_F_ENDPOINT_EQ)
434 : : {
435 [ + - ]: 756 : Trace("strings-ipc-core")
436 : 378 : << "...check reorient substitution" << std::endl;
437 [ - + ][ - + ]: 378 : Assert(conc.getKind() == Kind::EQUAL);
[ - - ]
438 : : // maybe reorient?
439 [ + + ]: 872 : for (size_t i = 0; i < mainEqIndex; i++)
440 : : {
441 [ - + ][ - + ]: 494 : Assert(rexp[i].getKind() == Kind::EQUAL);
[ - - ]
442 : 494 : if (rexp[i][0] == conc[0] || rexp[i][0] == conc[1])
443 : : {
444 : 7 : rexp[i] = rexp[i][1].eqNode(rexp[i][0]);
445 [ + - ]: 14 : Trace("strings-ipc-core")
446 : 7 : << "...reorient to " << rexp[i] << std::endl;
447 : : }
448 : : }
449 : : }
450 : : // apply substitution using the util method below
451 : 2808 : pmainEq = convertCoreSubs(env, pf, psb, mainEq, rexp, 0, 0);
452 : 2808 : }
453 [ + - ]: 7370 : Trace("strings-ipc-core")
454 : 3685 : << "Main equality after subs " << pmainEq << std::endl;
455 : : // now, conclude the proper equality
456 : 7370 : Node mainEqSRew = psb.applyPredElim(pmainEq, {});
457 [ - + ]: 3685 : if (mainEqSRew == conc)
458 : : {
459 [ - - ]: 0 : Trace("strings-ipc-core") << "...success after rewrite!" << std::endl;
460 : 0 : useBuffer = true;
461 : 0 : break;
462 : : }
463 [ + - ]: 7370 : Trace("strings-ipc-core")
464 : 3685 : << "Main equality after subs+rewrite " << mainEqSRew << std::endl;
465 : : // may need to splice constants
466 : : mainEqSRew =
467 : 3685 : spliceConstants(ProofRule::CONCAT_EQ, psb, mainEqSRew, conc, isRev);
468 : : // now, apply CONCAT_EQ to get the remainder
469 : 3685 : std::vector<Node> childrenCeq;
470 : 3685 : childrenCeq.push_back(mainEqSRew);
471 : 3685 : std::vector<Node> argsCeq;
472 : 3685 : argsCeq.push_back(nodeIsRev);
473 : 3685 : Node mainEqCeq = psb.tryStep(ProofRule::CONCAT_EQ, childrenCeq, argsCeq);
474 [ + - ]: 7370 : Trace("strings-ipc-core")
475 : 3685 : << "Main equality after CONCAT_EQ " << mainEqCeq << std::endl;
476 [ + - ][ - + ]: 3685 : if (mainEqCeq.isNull() || mainEqCeq.getKind() != Kind::EQUAL)
[ - + ]
477 : : {
478 : : // fail
479 : 0 : break;
480 : : }
481 : : // get the heads of the equality
482 : 3685 : std::vector<Node> tvec;
483 : 3685 : std::vector<Node> svec;
484 : 3685 : theory::strings::utils::getConcat(mainEqCeq[0], tvec);
485 : 3685 : theory::strings::utils::getConcat(mainEqCeq[1], svec);
486 [ + + ]: 3685 : Node t0 = tvec[isRev ? tvec.size() - 1 : 0];
487 [ + + ]: 3685 : Node s0 = svec[isRev ? svec.size() - 1 : 0];
488 : : // Now, mainEqCeq is an equality t ++ ... == s ++ ... where the
489 : : // inference involved t and s.
490 [ + + ]: 3685 : if (infer == InferenceId::STRINGS_N_ENDPOINT_EQ
491 [ + - ]: 3075 : || infer == InferenceId::STRINGS_N_ENDPOINT_EMP
492 [ + + ]: 3075 : || infer == InferenceId::STRINGS_F_ENDPOINT_EQ
493 [ - + ]: 2808 : || infer == InferenceId::STRINGS_F_ENDPOINT_EMP)
494 : : {
495 : : // Should be equal to conclusion already, or rewrite to it.
496 : : // Notice that this step is necessary to handle the "rproc"
497 : : // optimization in processSimpleNEq. Alternatively, this could
498 : : // possibly be done by CONCAT_EQ with !isRev.
499 : 877 : std::vector<Node> cexp;
500 [ + - ]: 877 : if (psb.applyPredTransform(mainEqCeq,
501 : : conc,
502 : : cexp,
503 : : MethodId::SB_DEFAULT,
504 : : MethodId::SBA_SEQUENTIAL,
505 : : MethodId::RW_REWRITE_EQ_EXT))
506 : : {
507 [ + - ]: 1754 : Trace("strings-ipc-core") << "Transformed to " << conc
508 : 877 : << " via pred transform" << std::endl;
509 : : // success
510 : 877 : useBuffer = true;
511 [ + - ]: 877 : Trace("strings-ipc-core") << "...success!" << std::endl;
512 : : }
513 : : // Otherwise, note that EMP rules conclude ti = "" where
514 : : // t1 ++ ... ++ tn == "". However, these are very rarely applied, let
515 : : // alone for 2+ children. This case is intentionally unhandled here.
516 : 877 : }
517 [ + + ]: 2808 : else if (infer == InferenceId::STRINGS_F_NCTN
518 [ + + ]: 2645 : || infer == InferenceId::STRINGS_N_NCTN)
519 : : {
520 : : // May require extended equality rewrite, applied after the rewrite
521 : : // above. Notice we need both in sequence since ext equality rewriting
522 : : // is not recursive.
523 : 307 : std::vector<Node> argsERew;
524 : 307 : addMethodIds(nm,
525 : : argsERew,
526 : : MethodId::SB_DEFAULT,
527 : : MethodId::SBA_SEQUENTIAL,
528 : : MethodId::RW_REWRITE_EQ_EXT);
529 : : Node mainEqERew =
530 : 1228 : psb.tryStep(ProofRule::MACRO_SR_PRED_ELIM, {mainEqCeq}, argsERew);
531 [ + - ]: 307 : if (mainEqERew == conc)
532 : : {
533 : 307 : useBuffer = true;
534 [ + - ]: 307 : Trace("strings-ipc-core") << "...success!" << std::endl;
535 : : }
536 : 307 : }
537 : : else
538 : : {
539 : : // may need to apply symmetry
540 : 5002 : if ((infer == InferenceId::STRINGS_SSPLIT_CST
541 [ + + ]: 1902 : || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
542 [ + + ][ + + ]: 4403 : && t0.isConst())
[ + + ]
543 : : {
544 [ - + ][ - + ]: 523 : Assert(!s0.isConst());
[ - - ]
545 : 1046 : mainEqCeq = psb.tryStep(ProofRule::SYMM, {mainEqCeq}, {});
546 : 523 : std::swap(t0, s0);
547 : : }
548 [ + + ][ + + ]: 2501 : if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY)
549 : : {
550 [ - + ]: 1267 : if (conc.getKind() != Kind::EQUAL)
551 : : {
552 : 0 : break;
553 : : }
554 : : // one side should match, the other side could be a split constant
555 : 1267 : if (conc[0] != t0 && conc[1] != s0)
556 : : {
557 : 1350 : mainEqCeq = psb.tryStep(ProofRule::SYMM, {mainEqCeq}, {});
558 : 675 : std::swap(t0, s0);
559 : : }
560 [ - + ][ - + ]: 1267 : Assert(conc[0].isConst() == t0.isConst());
[ - - ]
561 [ - + ][ - + ]: 1267 : Assert(conc[1].isConst() == s0.isConst());
[ - - ]
562 : : }
563 : 2501 : ProofRule rule = ProofRule::UNKNOWN;
564 : : // the form of the required length constraint expected by the proof
565 : 2501 : Node lenReq;
566 : 2501 : bool lenSuccess = false;
567 [ + + ][ + + ]: 2501 : if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY)
568 : : {
569 : : // first, splice if necessary
570 : 2534 : mainEqCeq = spliceConstants(
571 : 1267 : ProofRule::CONCAT_UNIFY, psb, mainEqCeq, conc, isRev);
572 : : // the required premise for unify is always len(x) = len(y),
573 : : // however the explanation may not be literally this. Thus, we
574 : : // need to reconstruct a proof from the given explanation.
575 : : // it should be the case that lenConstraint => lenReq.
576 : : // We use terms in the conclusion equality, not t0, s0 here.
577 : 2534 : lenReq = nm->mkNode(Kind::STRING_LENGTH, conc[0])
578 : 1267 : .eqNode(nm->mkNode(Kind::STRING_LENGTH, conc[1]));
579 : 1267 : lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
580 : 1267 : rule = ProofRule::CONCAT_UNIFY;
581 : : }
582 [ + + ]: 1234 : else if (infer == InferenceId::STRINGS_SSPLIT_VAR)
583 : : {
584 : : // may have to flip
585 : 48 : Assert(conc.getKind() == Kind::AND && conc[0].getKind() == Kind::OR
586 : : && conc[0][0].getKind() == Kind::EQUAL);
587 [ + + ]: 48 : if (conc[0][0][0] != t0)
588 : : {
589 : 60 : mainEqCeq = psb.tryStep(ProofRule::SYMM, {mainEqCeq}, {});
590 : 30 : std::swap(t0, s0);
591 : : }
592 : : // it should be the case that lenConstraint => lenReq
593 : 96 : lenReq = nm->mkNode(Kind::STRING_LENGTH, t0)
594 : 96 : .eqNode(nm->mkNode(Kind::STRING_LENGTH, s0))
595 : 48 : .notNode();
596 : 48 : lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
597 : 48 : rule = ProofRule::CONCAT_SPLIT;
598 : : }
599 [ + + ]: 1186 : else if (infer == InferenceId::STRINGS_SSPLIT_CST)
600 : : {
601 : : // first, splice if necessary
602 : 1198 : mainEqCeq = spliceConstants(
603 : 599 : ProofRule::CONCAT_CSPLIT, psb, mainEqCeq, conc, isRev);
604 : : // it should be the case that lenConstraint => lenReq
605 : 1198 : lenReq = nm->mkNode(Kind::STRING_LENGTH, t0)
606 : 1198 : .eqNode(nm->mkConstInt(Rational(0)))
607 : 599 : .notNode();
608 : 599 : lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
609 : 599 : rule = ProofRule::CONCAT_CSPLIT;
610 : : }
611 [ + + ]: 587 : else if (infer == InferenceId::STRINGS_SSPLIT_VAR_PROP)
612 : : {
613 : : // it should be the case that lenConstraint => lenReq
614 [ + - ]: 431 : for (unsigned r = 0; r < 2; r++)
615 : : {
616 : 1293 : lenReq = nm->mkNode(Kind::GT,
617 : 862 : nm->mkNode(Kind::STRING_LENGTH, t0),
618 : 1293 : nm->mkNode(Kind::STRING_LENGTH, s0));
619 [ + + ]: 431 : if (convertLengthPf(lenReq, lenConstraint, psb))
620 : : {
621 : 303 : lenSuccess = true;
622 : 303 : break;
623 : : }
624 [ + - ]: 128 : if (r == 0)
625 : : {
626 : : // may be the other direction
627 : 256 : mainEqCeq = psb.tryStep(ProofRule::SYMM, {mainEqCeq}, {});
628 : 128 : std::swap(t0, s0);
629 : : }
630 : : }
631 : 303 : rule = ProofRule::CONCAT_LPROP;
632 : : }
633 [ + + ]: 284 : else if (infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
634 : : {
635 : : // it should be the case that lenConstraint => lenReq
636 : 272 : lenReq = nm->mkNode(Kind::STRING_LENGTH, t0)
637 : 272 : .eqNode(nm->mkConstInt(Rational(0)))
638 : 136 : .notNode();
639 : 136 : lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
640 : 136 : rule = ProofRule::CONCAT_CPROP;
641 : : }
642 [ + + ]: 148 : else if (infer == InferenceId::STRINGS_N_CONST
643 [ - + ]: 92 : || infer == InferenceId::STRINGS_F_CONST
644 [ - - ]: 0 : || infer == InferenceId::STRINGS_N_EQ_CONF)
645 : : {
646 : : // first, splice if necessary
647 : 296 : mainEqCeq = spliceConstants(
648 : 148 : ProofRule::CONCAT_UNIFY, psb, mainEqCeq, conc, isRev);
649 : : // Should be a constant conflict. We use CONCAT_UNIFY to infer an
650 : : // equality between string or sequence values, which will rewrite to
651 : : // false below, justifed by EVALUATE or DISTINCT_VALUES after
652 : : // elaboration.
653 : 148 : rule = ProofRule::CONCAT_UNIFY;
654 : 148 : std::vector<Node> tvecs, svecs;
655 : 148 : theory::strings::utils::getConcat(mainEqCeq[0], tvecs);
656 : 148 : theory::strings::utils::getConcat(mainEqCeq[1], svecs);
657 [ + + ]: 148 : t0 = tvecs[isRev ? tvecs.size() - 1 : 0];
658 [ + + ]: 148 : s0 = svecs[isRev ? svecs.size() - 1 : 0];
659 : : // add length requirement, which due to the splicing above should hold
660 : 296 : lenReq = nm->mkNode(Kind::STRING_LENGTH, t0)
661 : 148 : .eqNode(nm->mkNode(Kind::STRING_LENGTH, s0));
662 : : // should be shown by evaluation
663 : 148 : lenSuccess = psb.applyPredIntro(lenReq, {});
664 : : // will conclude an equality between string/sequence values, which
665 : : // will rewrite to false.
666 : 148 : }
667 [ - + ]: 2501 : if (!lenSuccess)
668 : : {
669 [ - - ]: 0 : Trace("strings-ipc-core")
670 : 0 : << "...failed due to length constraint" << std::endl;
671 : 0 : break;
672 : : }
673 [ + - ]: 2501 : if (rule != ProofRule::UNKNOWN)
674 : : {
675 [ + - ]: 5002 : Trace("strings-ipc-core")
676 : 2501 : << "Core rule length requirement is " << lenReq << std::endl;
677 : : // apply the given rule
678 : 2501 : std::vector<Node> childrenMain;
679 : 2501 : childrenMain.push_back(mainEqCeq);
680 : 2501 : childrenMain.push_back(lenReq);
681 : 2501 : std::vector<Node> argsMain;
682 : 2501 : argsMain.push_back(nodeIsRev);
683 : 2501 : Node mainEqMain = psb.tryStep(rule, childrenMain, argsMain);
684 [ + - ]: 5002 : Trace("strings-ipc-core") << "Main equality after " << rule << " "
685 : 2501 : << mainEqMain << std::endl;
686 : : // either equal or rewrites to it
687 [ + - ]: 2501 : if (applyPredTransformConversion(mainEqMain, conc, psb))
688 : : {
689 : : // requires that length success is also true
690 : 2501 : useBuffer = true;
691 [ + - ]: 2501 : Trace("strings-ipc-core") << "...success" << std::endl;
692 : : }
693 : : else
694 : : {
695 [ - - ]: 0 : Trace("strings-ipc-core") << "...fail" << std::endl;
696 : : }
697 : 2501 : }
698 : : else
699 : : {
700 : : // should always have given a rule to try above
701 : 0 : DebugUnhandled() << "No reconstruction rule given for " << infer;
702 : : }
703 [ + - ]: 2501 : }
704 [ + - ][ + - ]: 3685 : }
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
705 : 3685 : break;
706 : : // ========================== Disequalities
707 : 24 : case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_STRING_SPLIT:
708 : : case InferenceId::STRINGS_DEQ_DISL_STRINGS_SPLIT:
709 : : {
710 [ + - ]: 24 : if (conc.getKind() != Kind::AND || conc.getNumChildren() != 2
711 [ + - ][ - + ]: 48 : || conc[0].getKind() != Kind::EQUAL
[ - - ]
712 : 48 : || !conc[0][0].getType().isStringLike()
713 [ + - ][ + - ]: 48 : || conc[1].getKind() != Kind::EQUAL
[ - - ]
714 : 48 : || conc[1][0].getKind() != Kind::STRING_LENGTH)
715 : : {
716 [ - - ]: 0 : Trace("strings-ipc-deq") << "malformed application" << std::endl;
717 : 0 : DebugUnhandled() << "unexpected conclusion " << conc << " for " << infer;
718 : : }
719 : : else
720 : : {
721 : : Node lenReq = nm->mkNode(
722 : 48 : Kind::GEQ, nm->mkNode(Kind::STRING_LENGTH, conc[0][0]), conc[1][1]);
723 [ + - ]: 48 : Trace("strings-ipc-deq")
724 : 24 : << "length requirement is " << lenReq << std::endl;
725 [ + - ]: 24 : if (convertLengthPf(lenReq, ps.d_children, psb))
726 : : {
727 [ + - ]: 24 : Trace("strings-ipc-deq") << "...success length" << std::endl;
728 : : Node nPos =
729 : 48 : nm->mkNode(Kind::GEQ, conc[1][1], nm->mkConstInt(Rational(0)));
730 : 24 : psb.applyPredIntro(nPos,
731 : : {},
732 : : MethodId::SB_DEFAULT,
733 : : MethodId::SBA_SEQUENTIAL,
734 : : MethodId::RW_EXT_REWRITE);
735 : : // make the proof
736 : 24 : std::vector<Node> childrenMain;
737 : 24 : childrenMain.push_back(nPos);
738 : 24 : childrenMain.push_back(lenReq);
739 : 24 : std::vector<Node> argsMain;
740 : 24 : argsMain.push_back(nodeIsRev);
741 : : Node mainConc =
742 : 24 : psb.tryStep(ProofRule::STRING_DECOMPOSE, childrenMain, argsMain);
743 [ + - ]: 48 : Trace("strings-ipc-deq")
744 : 24 : << "...main conclusion is " << mainConc << std::endl;
745 : 24 : useBuffer = (mainConc == conc);
746 [ + - ]: 24 : if (!useBuffer)
747 : : {
748 : : // Should be made equal by transformation. This step is necessary
749 : : // if rewriting was used to change the skolem introduced in the
750 : : // conclusion.
751 : 24 : useBuffer = psb.applyPredTransform(mainConc, conc, {});
752 : : }
753 [ + - ]: 48 : Trace("strings-ipc-deq")
754 : 24 : << "...success is " << useBuffer << std::endl;
755 : 24 : }
756 : : else
757 : : {
758 : 0 : DebugUnhandled()
759 : 0 : << "Failed to convert length " << lenReq << " " << ps.d_children;
760 : : Trace("strings-ipc-deq") << "...fail length" << std::endl;
761 : : }
762 : 24 : }
763 : : }
764 : 24 : break;
765 : : // ========================== Boolean split
766 : 0 : case InferenceId::STRINGS_CARD_SP:
767 : : case InferenceId::STRINGS_LEN_SPLIT:
768 : : case InferenceId::STRINGS_LEN_SPLIT_EMP:
769 : : case InferenceId::STRINGS_DEQ_DISL_EMP_SPLIT:
770 : : case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_EQ_SPLIT:
771 : : case InferenceId::STRINGS_DEQ_STRINGS_EQ:
772 : : case InferenceId::STRINGS_DEQ_LENS_EQ:
773 : : case InferenceId::STRINGS_DEQ_LENGTH_SP:
774 : : case InferenceId::STRINGS_UNIT_SPLIT:
775 : : {
776 [ - - ]: 0 : if (conc.getKind() != Kind::OR)
777 : : {
778 : : // This should never happen. If it does, we resort to using
779 : : // THEORY_INFERENCE_STRINGS below (in production mode).
780 : 0 : DebugUnhandled() << "Expected OR conclusion for " << infer;
781 : : }
782 : : else
783 : : {
784 : 0 : ps.d_rule = ProofRule::SPLIT;
785 : 0 : Assert(ps.d_children.empty());
786 : 0 : ps.d_args.push_back(conc[0]);
787 : : }
788 : : }
789 : 0 : break;
790 : : // ========================== Regular expression unfolding
791 : 253 : case InferenceId::STRINGS_RE_UNFOLD_POS:
792 : : case InferenceId::STRINGS_RE_UNFOLD_NEG:
793 : : {
794 [ - + ][ - + ]: 253 : Assert (!ps.d_children.empty());
[ - - ]
795 : 253 : size_t nchild = ps.d_children.size();
796 : 253 : Node mem = ps.d_children[nchild-1];
797 [ - + ]: 253 : if (nchild>1)
798 : : {
799 : : // if more than one, apply MACRO_SR_PRED_ELIM
800 : 0 : std::vector<Node> tcs;
801 : 0 : tcs.insert(tcs.end(),
802 : : ps.d_children.begin(),
803 : 0 : ps.d_children.begin() + (nchild-1));
804 : 0 : mem = psb.applyPredElim(mem, tcs);
805 : 0 : useBuffer = true;
806 : 0 : }
807 : 253 : ProofRule r = ProofRule::UNKNOWN;
808 : 253 : std::vector<Node> args;
809 [ - + ]: 253 : if (mem.isNull())
810 : : {
811 : : // failed to eliminate above
812 : 0 : DebugUnhandled() << "Failed to apply MACRO_SR_PRED_ELIM for RE unfold";
813 : : useBuffer = false;
814 : : }
815 [ + + ]: 253 : else if (infer == InferenceId::STRINGS_RE_UNFOLD_POS)
816 : : {
817 : 225 : r = ProofRule::RE_UNFOLD_POS;
818 : : }
819 : : else
820 : : {
821 : 28 : r = ProofRule::RE_UNFOLD_NEG;
822 : : // it may be an optimized form of concat simplification
823 : 28 : Assert(mem.getKind() == Kind::NOT
824 : : && mem[0].getKind() == Kind::STRING_IN_REGEXP);
825 [ + - ]: 28 : if (mem[0][1].getKind() == Kind::REGEXP_CONCAT)
826 : : {
827 : : bool isCRev;
828 : 56 : Node reLen = RegExpOpr::getRegExpConcatFixed(mem[0][1], isCRev);
829 : : // if we can find a fixed length for a component, use the optimized
830 : : // version
831 [ + - ]: 28 : if (!reLen.isNull())
832 : : {
833 : 28 : r = ProofRule::RE_UNFOLD_NEG_CONCAT_FIXED;
834 : 28 : args.push_back(nm->mkConst(isCRev));
835 : : }
836 : 28 : }
837 : : }
838 [ - + ]: 253 : if (useBuffer)
839 : : {
840 : 0 : mem = psb.tryStep(r, {mem}, args);
841 : : // should match the conclusion
842 : 0 : useBuffer = (mem==conc);
843 : : }
844 : : else
845 : : {
846 : 253 : ps.d_rule = r;
847 : 253 : ps.d_args = args;
848 : : }
849 : 253 : }
850 : 253 : break;
851 : : // ========================== Reduction
852 : 124 : case InferenceId::STRINGS_CTN_POS:
853 : : case InferenceId::STRINGS_CTN_NEG_EQUAL:
854 : : {
855 [ - + ]: 124 : if (ps.d_children.size() != 1)
856 : : {
857 : 0 : break;
858 : : }
859 : 124 : bool polarity = ps.d_children[0].getKind() != Kind::NOT;
860 [ + - ]: 124 : Node atom = polarity ? ps.d_children[0] : ps.d_children[0][0];
861 : 124 : std::vector<Node> args;
862 : 124 : args.push_back(atom);
863 : 248 : Node res = psb.tryStep(ProofRule::STRING_EAGER_REDUCTION, {}, args);
864 [ - + ]: 124 : if (res.isNull())
865 : : {
866 : 0 : break;
867 : : }
868 : : // ite( contains(x,t), x = k1 ++ t ++ k2, x != t )
869 : 124 : std::vector<Node> tiChildren;
870 : 124 : tiChildren.push_back(ps.d_children[0]);
871 : : Node ctnt =
872 : : psb.tryStep(polarity ? ProofRule::TRUE_INTRO : ProofRule::FALSE_INTRO,
873 : : tiChildren,
874 [ + - ]: 248 : {});
875 [ + - ][ - + ]: 124 : if (ctnt.isNull() || ctnt.getKind() != Kind::EQUAL)
[ - + ]
876 : : {
877 : 0 : break;
878 : : }
879 : 124 : std::vector<Node> tchildren;
880 : 124 : tchildren.push_back(ctnt);
881 : : // apply substitution { contains(x,t) -> true|false } and rewrite to get
882 : : // conclusion x = k1 ++ t ++ k2 or x != t.
883 [ + - ]: 124 : if (psb.applyPredTransform(res, conc, tchildren))
884 : : {
885 : 124 : useBuffer = true;
886 : : }
887 [ + - ][ + - ]: 124 : }
[ + - ][ + - ]
[ + - ]
888 : 124 : break;
889 : :
890 : 1184 : case InferenceId::STRINGS_REDUCTION:
891 : : {
892 : 1184 : size_t nchild = conc.getNumChildren();
893 : 1184 : Node mainEq;
894 [ - + ]: 1184 : if (conc.getKind() == Kind::EQUAL)
895 : : {
896 : 0 : mainEq = conc;
897 : : }
898 : 3552 : else if (conc.getKind() == Kind::AND
899 [ + - ][ + - ]: 1184 : && conc[nchild - 1].getKind() == Kind::EQUAL)
[ + - ][ + - ]
[ - - ]
900 : : {
901 : 1184 : mainEq = conc[nchild - 1];
902 : : }
903 [ - + ]: 1184 : if (mainEq.isNull())
904 : : {
905 [ - - ]: 0 : Trace("strings-ipc-red") << "Bad Reduction: " << conc << std::endl;
906 : 0 : DebugUnhandled() << "Unexpected reduction " << conc;
907 : : break;
908 : : }
909 : 1184 : std::vector<Node> argsRed;
910 : : // the left hand side of the last conjunct is the term we are reducing
911 : 1184 : argsRed.push_back(mainEq[0]);
912 : 2368 : Node red = psb.tryStep(ProofRule::STRING_REDUCTION, {}, argsRed);
913 [ + - ]: 1184 : Trace("strings-ipc-red") << "Reduction : " << red << std::endl;
914 [ + - ]: 1184 : if (!red.isNull())
915 : : {
916 [ + + ]: 1184 : if (red == conc)
917 : : {
918 [ + - ]: 114 : Trace("strings-ipc-red") << "...success!" << std::endl;
919 : 114 : useBuffer = true;
920 : : }
921 [ + - ]: 1070 : else if (applyPredTransformConversion(red, conc, psb))
922 : : {
923 [ + - ]: 1070 : Trace("strings-ipc-red") << "...success!" << std::endl;
924 : 1070 : useBuffer = true;
925 : : }
926 : : else
927 : : {
928 [ - - ]: 0 : Trace("strings-ipc-red") << "...failed to reduce" << std::endl;
929 : : }
930 : : }
931 [ + - ]: 1184 : }
932 : 1184 : break;
933 : : // ========================== code injectivity
934 : 55 : case InferenceId::STRINGS_CODE_INJ:
935 : : {
936 : 55 : ps.d_rule = ProofRule::STRING_CODE_INJ;
937 : 55 : Assert(conc.getKind() == Kind::OR && conc.getNumChildren() == 3
938 : : && conc[2].getKind() == Kind::EQUAL);
939 : 55 : ps.d_args.push_back(conc[2][0]);
940 : 55 : ps.d_args.push_back(conc[2][1]);
941 : : }
942 : 55 : break;
943 : : // ========================== unit injectivity
944 : 40 : case InferenceId::STRINGS_UNIT_INJ:
945 : : {
946 [ - + ][ - + ]: 40 : Assert(conc.getKind() == Kind::EQUAL);
[ - - ]
947 [ + - ][ + - ]: 40 : Assert(ps.d_children.size() == 1
[ - + ][ - + ]
[ - - ]
948 : : && ps.d_children[0].getKind() == Kind::EQUAL);
949 : : Node concS =
950 : 80 : psb.tryStep(ProofRule::STRING_SEQ_UNIT_INJ, ps.d_children, {});
951 [ + - ]: 40 : if (!concS.isNull())
952 : : {
953 : : // may need to apply symmetry
954 [ + + ]: 40 : if (concS != conc)
955 : : {
956 : 32 : Node ss = psb.tryStep(ProofRule::SYMM, {concS}, {});
957 : 8 : useBuffer = (ss == conc);
958 : 8 : }
959 : : else
960 : : {
961 : 32 : useBuffer = true;
962 : : }
963 : : }
964 : 40 : }
965 : 40 : break;
966 : : // ========================== prefix conflict
967 : 170 : case InferenceId::STRINGS_PREFIX_CONFLICT:
968 : : case InferenceId::STRINGS_PREFIX_CONFLICT_MIN:
969 : : {
970 [ + - ]: 170 : Trace("strings-ipc-prefix") << "Prefix conflict..." << std::endl;
971 : 170 : std::vector<Node> eqs;
972 [ + + ]: 462 : for (const Node& e : ps.d_children)
973 : : {
974 : 292 : Kind ek = e.getKind();
975 [ + + ]: 292 : if (ek == Kind::EQUAL)
976 : : {
977 [ + - ]: 206 : Trace("strings-ipc-prefix") << "- equality : " << e << std::endl;
978 : 206 : eqs.push_back(e);
979 : : }
980 [ + - ]: 86 : else if (ek == Kind::STRING_IN_REGEXP)
981 : : {
982 : : // unfold it and extract the equality
983 : 86 : std::vector<Node> children;
984 : 86 : children.push_back(e);
985 : 86 : std::vector<Node> args;
986 : 86 : Node eunf = psb.tryStep(ProofRule::RE_UNFOLD_POS, children, args);
987 [ + - ]: 172 : Trace("strings-ipc-prefix")
988 : 86 : << "--- " << e << " unfolds to " << eunf << std::endl;
989 [ - + ]: 86 : if (eunf.isNull())
990 : : {
991 : 0 : continue;
992 : : }
993 [ + - ]: 86 : else if (eunf.getKind() == Kind::AND)
994 : : {
995 : : // equality is the first conjunct
996 : 86 : std::vector<Node> childrenAE;
997 : 86 : childrenAE.push_back(eunf);
998 : 86 : std::vector<Node> argsAE;
999 : 86 : argsAE.push_back(nm->mkConstInt(Rational(0)));
1000 : 86 : Node eunfAE = psb.tryStep(ProofRule::AND_ELIM, childrenAE, argsAE);
1001 [ + - ]: 172 : Trace("strings-ipc-prefix")
1002 : 86 : << "--- and elim to " << eunfAE << std::endl;
1003 [ + - ][ - + ]: 86 : if (eunfAE.isNull() || eunfAE.getKind() != Kind::EQUAL)
[ - + ]
1004 : : {
1005 : 0 : DebugUnhandled()
1006 : 0 : << "Unexpected unfolded premise " << eunf << " for " << infer;
1007 : : continue;
1008 : : }
1009 [ + - ]: 172 : Trace("strings-ipc-prefix")
1010 : 86 : << "- equality : " << eunfAE << std::endl;
1011 : 86 : eqs.push_back(eunfAE);
1012 [ + - ][ + - ]: 86 : }
[ + - ]
1013 [ - - ]: 0 : else if (eunf.getKind() == Kind::EQUAL)
1014 : : {
1015 [ - - ]: 0 : Trace("strings-ipc-prefix") << "- equality : " << eunf << std::endl;
1016 : 0 : eqs.push_back(eunf);
1017 : : }
1018 [ + - ][ + - ]: 86 : }
[ + - ]
1019 : : else
1020 : : {
1021 : : // not sure how to use this assumption
1022 : 0 : DebugUnhandled() << "Unexpected premise " << e << " for " << infer;
1023 : : }
1024 : : }
1025 [ - + ]: 170 : if (eqs.empty())
1026 : : {
1027 : 0 : break;
1028 : : }
1029 : : // connect via transitivity
1030 : 170 : Node curr = eqs[0];
1031 : 170 : std::vector<Node> subs;
1032 [ + + ]: 292 : for (size_t i = 1, esize = eqs.size(); i < esize; i++)
1033 : : {
1034 : 122 : Node prev = curr;
1035 : 122 : curr = convertTrans(curr, eqs[i], psb);
1036 : : // if it is not a transitive step, it corresponds to a substitution
1037 [ + + ]: 122 : if (curr.isNull())
1038 : : {
1039 : 36 : curr = prev;
1040 : : // This is an equality between a variable and a concatention or
1041 : : // constant term (for example see below).
1042 : : // orient the substitution properly
1043 [ - + ][ - - ]: 72 : if (!eqs[i][1].isConst()
1044 [ - + ][ - - ]: 72 : && eqs[i][1].getKind() != Kind::STRING_CONCAT)
[ - + ][ + - ]
[ - - ]
1045 : : {
1046 : 0 : subs.push_back(eqs[i][1].eqNode(eqs[i][0]));
1047 : : }
1048 : : else
1049 : : {
1050 : 36 : subs.push_back(eqs[i]);
1051 : : }
1052 : 36 : continue;
1053 : : }
1054 [ + - ]: 86 : Trace("strings-ipc-prefix") << "- Via trans: " << curr << std::endl;
1055 [ + + ]: 122 : }
1056 [ - + ]: 170 : if (curr.isNull())
1057 : : {
1058 : 0 : break;
1059 : : }
1060 : : // Substitution is applied in reverse order
1061 : : // An example of this inference that uses a substituion is the conflict:
1062 : : // (str.in_re w (re.++ (re.* re.allchar) (str.to_re "ABC")))
1063 : : // (= w (str.++ z y x))
1064 : : // (= x "D")
1065 : : // where we apply w -> (str.++ z y x), then x -> "D" to the first
1066 : : // predicate to obtain a conflict by rewriting (predicate elim).
1067 : 170 : std::reverse(subs.begin(), subs.end());
1068 [ + - ]: 340 : Trace("strings-ipc-prefix")
1069 : 170 : << "- Possible conflicting equality : " << curr << std::endl;
1070 : : Node concE = psb.applyPredElim(curr,
1071 : : subs,
1072 : : MethodId::SB_DEFAULT,
1073 : : MethodId::SBA_SEQUENTIAL,
1074 : 170 : MethodId::RW_EXT_REWRITE);
1075 [ + - ]: 340 : Trace("strings-ipc-prefix")
1076 : 170 : << "- After pred elim: " << concE << std::endl;
1077 [ + - ]: 170 : if (concE == conc)
1078 : : {
1079 [ + - ]: 170 : Trace("strings-ipc-prefix") << "...success!" << std::endl;
1080 : 170 : useBuffer = true;
1081 : : }
1082 [ + - ][ + - ]: 170 : }
[ + - ]
1083 : 170 : break;
1084 : : // ========================== regular expressions
1085 : 28 : case InferenceId::STRINGS_RE_INTER_INCLUDE:
1086 : : case InferenceId::STRINGS_RE_INTER_CONF:
1087 : : case InferenceId::STRINGS_RE_INTER_INFER:
1088 : : {
1089 : 28 : std::vector<Node> reiExp;
1090 : 28 : std::vector<Node> reis;
1091 : 28 : std::vector<Node> reiChildren;
1092 : 28 : std::vector<Node> reiChildrenOrig;
1093 : 28 : Node x;
1094 : : // make the regular expression intersection that summarizes all
1095 : : // memberships in the explanation
1096 [ + + ]: 84 : for (const Node& c : ps.d_children)
1097 : : {
1098 : 56 : bool polarity = c.getKind() != Kind::NOT;
1099 [ + + ]: 56 : Node catom = polarity ? c : c[0];
1100 [ - + ]: 56 : if (catom.getKind() != Kind::STRING_IN_REGEXP)
1101 : : {
1102 : 0 : Assert(c.getKind() == Kind::EQUAL);
1103 [ - - ]: 0 : if (c.getKind() == Kind::EQUAL)
1104 : : {
1105 : 0 : reiExp.push_back(c);
1106 : : }
1107 : 0 : continue;
1108 : : }
1109 [ + + ]: 56 : if (x.isNull())
1110 : : {
1111 : : // just take the first LHS; others should be equated to it by exp
1112 : 28 : x = catom[0];
1113 : : }
1114 : : Node rcurr =
1115 : 84 : polarity ? catom[1] : nm->mkNode(Kind::REGEXP_COMPLEMENT, catom[1]);
1116 : 56 : reis.push_back(rcurr);
1117 : 112 : Node mem = nm->mkNode(Kind::STRING_IN_REGEXP, catom[0], rcurr);
1118 : 56 : reiChildren.push_back(mem);
1119 : 56 : reiChildrenOrig.push_back(c);
1120 [ + - ]: 56 : }
1121 : : // go back and justify each premise
1122 : 28 : bool successChildren = true;
1123 [ + + ]: 84 : for (size_t i = 0, nchild = reiChildren.size(); i < nchild; i++)
1124 : : {
1125 [ - + ]: 56 : if (!psb.applyPredTransform(reiChildrenOrig[i], reiChildren[i], reiExp))
1126 : : {
1127 [ - - ]: 0 : Trace("strings-ipc-re")
1128 : 0 : << "... failed to justify child " << reiChildren[i] << " from "
1129 : 0 : << reiChildrenOrig[i] << std::endl;
1130 : 0 : successChildren = false;
1131 : 0 : break;
1132 : : }
1133 : : }
1134 [ - + ]: 28 : if (!successChildren)
1135 : : {
1136 : 0 : break;
1137 : : }
1138 : 56 : Node mem = psb.tryStep(ProofRule::RE_INTER, reiChildren, {});
1139 [ + - ]: 56 : Trace("strings-ipc-re")
1140 : 28 : << "Regular expression summary: " << mem << std::endl;
1141 : : // the conclusion is rewritable to the premises via rewriting?
1142 [ + - ]: 28 : if (psb.applyPredTransform(mem, conc, {}))
1143 : : {
1144 [ + - ]: 28 : Trace("strings-ipc-re") << "... success!" << std::endl;
1145 : 28 : useBuffer = true;
1146 : : }
1147 : : else
1148 : : {
1149 [ - - ]: 0 : Trace("strings-ipc-re")
1150 : 0 : << "...failed to rewrite to conclusion" << std::endl;
1151 : : }
1152 [ + - ][ + - ]: 28 : }
[ + - ][ + - ]
[ + - ]
1153 : 28 : break;
1154 : 4 : case InferenceId::STRINGS_I_CYCLE_E:
1155 : : {
1156 [ - + ][ - + ]: 4 : Assert(ps.d_children.size() == 1);
[ - - ]
1157 : 4 : Node concE = psb.applyPredElim(ps.d_children[0],
1158 : : {},
1159 : : MethodId::SB_DEFAULT,
1160 : : MethodId::SBA_SEQUENTIAL,
1161 : 12 : MethodId::RW_EXT_REWRITE);
1162 [ + - ]: 4 : Trace("strings-ipc-debug") << "... elim to " << concE << std::endl;
1163 [ - + ]: 4 : if (concE != conc)
1164 : : {
1165 [ - - ]: 0 : if (concE.getKind() == Kind::AND)
1166 : : {
1167 [ - - ]: 0 : for (size_t i = 0, nchild = concE.getNumChildren(); i < nchild; i++)
1168 : : {
1169 [ - - ]: 0 : if (concE[i] == conc)
1170 : : {
1171 : 0 : Node ni = nm->mkConstInt(Rational(i));
1172 : 0 : psb.addStep(ProofRule::AND_ELIM, {concE}, {ni}, conc);
1173 : 0 : useBuffer = true;
1174 : 0 : break;
1175 : 0 : }
1176 : : }
1177 : : }
1178 : : }
1179 : : else
1180 : : {
1181 : 4 : useBuffer = true;
1182 : : }
1183 : 4 : }
1184 : 4 : break;
1185 : 8 : case InferenceId::STRINGS_CTN_DECOMPOSE:
1186 : : {
1187 [ - + ]: 8 : if (ps.d_children.size() != 2)
1188 : : {
1189 : 0 : break;
1190 : : }
1191 : 8 : Node ctn = ps.d_children[0];
1192 [ - + ]: 8 : if (ctn.getKind() != Kind::STRING_CONTAINS)
1193 : : {
1194 : 0 : break;
1195 : : }
1196 : 32 : Node pconc = psb.tryStep(ProofRule::STRING_EAGER_REDUCTION, {}, {ctn});
1197 [ + - ]: 8 : Trace("strings-ipc-cons") << "Eager reduction: " << pconc << std::endl;
1198 : 24 : Node pelim = psb.applyPredElim(pconc, {ctn}, MethodId::SB_LITERAL);
1199 [ + - ]: 8 : Trace("strings-ipc-cons") << "After rewriting: " << pelim << std::endl;
1200 [ - + ]: 8 : if (pelim.getKind() != Kind::EQUAL)
1201 : : {
1202 : 0 : break;
1203 : : }
1204 : 8 : Node tgt = ps.d_children[1];
1205 : 24 : Node pelim2 = psb.applyPredElim(tgt, {pelim});
1206 [ + - ]: 8 : Trace("strings-ipc-cons") << "After elim: " << pelim << std::endl;
1207 [ + - ]: 8 : if (pelim2 == conc)
1208 : : {
1209 : 8 : useBuffer = true;
1210 : : }
1211 [ + - ][ + - ]: 8 : }
[ + - ]
1212 : 8 : break;
1213 : : // ========================== unknown and currently unsupported
1214 : 187 : case InferenceId::STRINGS_CARDINALITY:
1215 : : case InferenceId::STRINGS_I_CYCLE:
1216 : : case InferenceId::STRINGS_INFER_EMP:
1217 : : case InferenceId::STRINGS_RE_DELTA:
1218 : : case InferenceId::STRINGS_RE_DELTA_CONF:
1219 : : case InferenceId::STRINGS_RE_DERIVE:
1220 : : case InferenceId::STRINGS_FLOOP:
1221 : : case InferenceId::STRINGS_FLOOP_CONFLICT:
1222 : : case InferenceId::STRINGS_DEQ_NORM_EMP:
1223 : : case InferenceId::STRINGS_CTN_TRANS:
1224 : : default:
1225 : : // do nothing, these will be converted to THEORY_INFERENCE_STRINGS below
1226 : : // since the rule is unknown.
1227 : 187 : break;
1228 : : }
1229 : :
1230 : : // now see if we would succeed with the checker-to-try
1231 : 8619 : bool success = false;
1232 [ + + ]: 8619 : if (ps.d_rule != ProofRule::UNKNOWN)
1233 : : {
1234 [ + - ]: 680 : Trace("strings-ipc") << "For " << infer << ", try proof rule " << ps.d_rule
1235 : 340 : << "...";
1236 [ - + ][ - + ]: 340 : Assert(ps.d_rule != ProofRule::UNKNOWN);
[ - - ]
1237 : 340 : Node pconc = psb.tryStep(ps.d_rule, ps.d_children, ps.d_args);
1238 [ + - ][ - + ]: 340 : if (pconc.isNull() || pconc != conc)
[ - + ]
1239 : : {
1240 [ - - ]: 0 : Trace("strings-ipc") << "failed, pconc is " << pconc << " (expected "
1241 : 0 : << conc << ")" << std::endl;
1242 : 0 : ps.d_rule = ProofRule::UNKNOWN;
1243 : : }
1244 : : else
1245 : : {
1246 : : // successfully set up a single step proof in ps
1247 : 340 : success = true;
1248 [ + - ]: 340 : Trace("strings-ipc") << "success!" << std::endl;
1249 : : }
1250 : 340 : }
1251 [ + + ]: 8279 : else if (useBuffer)
1252 : : {
1253 : : // successfully set up a multi step proof in psb
1254 : 8084 : success = true;
1255 : : }
1256 : : else
1257 : : {
1258 [ + - ]: 390 : Trace("strings-ipc") << "For " << infer << " " << conc
1259 : 195 : << ", no proof rule, failed" << std::endl;
1260 : : }
1261 [ + + ]: 8619 : if (!success)
1262 : : {
1263 : : // debug print
1264 [ - + ]: 195 : if (TraceIsOn("strings-ipc-fail"))
1265 : : {
1266 [ - - ]: 0 : Trace("strings-ipc-fail")
1267 : 0 : << "InferProofCons::convert: Failed " << infer
1268 [ - - ]: 0 : << (isRev ? " :rev " : " ") << conc << std::endl;
1269 [ - - ]: 0 : for (const Node& ec : exp)
1270 : : {
1271 [ - - ]: 0 : Trace("strings-ipc-fail") << " e: " << ec << std::endl;
1272 : : }
1273 : : }
1274 : : // untrustworthy conversion, the argument of THEORY_INFERENCE_STRINGS is
1275 : : // its conclusion
1276 : 195 : ps.d_args.clear();
1277 : 195 : ps.d_args.push_back(mkTrustId(nm, TrustId::THEORY_INFERENCE_STRINGS));
1278 : 195 : ps.d_args.push_back(conc);
1279 : : // use the trust rule
1280 : 195 : ps.d_rule = ProofRule::TRUST;
1281 : : }
1282 [ - + ]: 8619 : if (TraceIsOn("strings-ipc-debug"))
1283 : : {
1284 [ - - ]: 0 : if (useBuffer)
1285 : : {
1286 [ - - ]: 0 : Trace("strings-ipc-debug")
1287 : 0 : << "InferProofCons::convert returned buffer with "
1288 : 0 : << psb.getNumSteps() << " steps:" << std::endl;
1289 : 0 : const std::vector<std::pair<Node, ProofStep>>& steps = psb.getSteps();
1290 [ - - ]: 0 : for (const std::pair<Node, ProofStep>& step : steps)
1291 : : {
1292 [ - - ]: 0 : Trace("strings-ipc-debug")
1293 : 0 : << "- " << step.first << " via " << step.second << std::endl;
1294 : : }
1295 : : }
1296 : : else
1297 : : {
1298 [ - - ]: 0 : Trace("strings-ipc-debug")
1299 : 0 : << "InferProofCons::convert returned " << ps << std::endl;
1300 : : }
1301 : : }
1302 : : // make the proof based on the step or the buffer
1303 [ + + ]: 8619 : if (useBuffer)
1304 : : {
1305 [ - + ]: 8084 : if (!pf->addSteps(psb))
1306 : : {
1307 : 0 : return false;
1308 : : }
1309 : : }
1310 : : else
1311 : : {
1312 [ - + ]: 535 : if (!pf->addStep(conc, ps))
1313 : : {
1314 : 0 : return false;
1315 : : }
1316 : : }
1317 : 8619 : return true;
1318 : 8619 : }
1319 : :
1320 : 2505 : bool InferProofCons::convertLengthPf(Node lenReq,
1321 : : const std::vector<Node>& lenExp,
1322 : : TheoryProofStepBuffer& psb)
1323 : : {
1324 [ + + ]: 4723 : for (const Node& le : lenExp)
1325 : : {
1326 [ + + ]: 3367 : if (lenReq == le)
1327 : : {
1328 : 1149 : return true;
1329 : : }
1330 : : }
1331 [ + - ]: 2712 : Trace("strings-ipc-len") << "Must explain " << lenReq << " by " << lenExp
1332 : 1356 : << std::endl;
1333 [ + + ]: 1740 : for (const Node& le : lenExp)
1334 : : {
1335 : : // probably rewrites to it?
1336 : 1612 : std::vector<Node> exp;
1337 [ + + ]: 1612 : if (psb.applyPredTransform(le, lenReq, exp))
1338 : : {
1339 [ + - ]: 711 : Trace("strings-ipc-len") << "...success by rewrite" << std::endl;
1340 : 711 : return true;
1341 : : }
1342 : : // maybe x != "" => len(x) != 0
1343 : 901 : std::vector<Node> children;
1344 : 901 : children.push_back(le);
1345 : 901 : std::vector<Node> args;
1346 : 901 : Node res = psb.tryStep(ProofRule::STRING_LENGTH_NON_EMPTY, children, args);
1347 [ + + ]: 901 : if (res == lenReq)
1348 : : {
1349 [ + - ]: 517 : Trace("strings-ipc-len") << "...success by LENGTH_NON_EMPTY" << std::endl;
1350 : 517 : return true;
1351 : : }
1352 [ + + ][ + + ]: 3163 : }
[ + + ][ + + ]
1353 [ + - ]: 128 : Trace("strings-ipc-len") << "...failed" << std::endl;
1354 : 128 : return false;
1355 : : }
1356 : :
1357 : 122 : Node InferProofCons::convertTrans(Node eqa,
1358 : : Node eqb,
1359 : : TheoryProofStepBuffer& psb)
1360 : : {
1361 [ + - ][ - + ]: 122 : if (eqa.getKind() != Kind::EQUAL || eqb.getKind() != Kind::EQUAL)
[ - + ]
1362 : : {
1363 : 0 : return Node::null();
1364 : : }
1365 [ + + ]: 194 : for (uint32_t i = 0; i < 2; i++)
1366 : : {
1367 : 280 : Node eqaSym = i == 0 ? eqa[1].eqNode(eqa[0]) : eqa;
1368 [ + + ]: 303 : for (uint32_t j = 0; j < 2; j++)
1369 : : {
1370 : 304 : Node eqbSym = j == 0 ? eqb : eqb[1].eqNode(eqb[0]);
1371 [ + + ]: 231 : if (eqa[i] == eqb[j])
1372 : : {
1373 : 86 : std::vector<Node> children;
1374 : 86 : children.push_back(eqaSym);
1375 : 86 : children.push_back(eqbSym);
1376 : 86 : return psb.tryStep(ProofRule::TRANS, children, {});
1377 : 86 : }
1378 [ + + ]: 231 : }
1379 [ + + ]: 158 : }
1380 : 36 : return Node::null();
1381 : : }
1382 : :
1383 : 284 : bool InferProofCons::convertAndElim(NodeManager* nm,
1384 : : const Node& src,
1385 : : const Node& tgt,
1386 : : TheoryProofStepBuffer& psb)
1387 : : {
1388 [ + + ]: 284 : if (src == tgt)
1389 : : {
1390 : 208 : return true;
1391 : : }
1392 [ + - ]: 152 : Trace("strings-ipc-debug")
1393 : 76 : << "AND_ELIM " << src << " => " << tgt << "?" << std::endl;
1394 : 76 : Node stgt;
1395 [ - + ][ - - ]: 76 : if (src.getKind() == Kind::NOT && src[0].getKind() == Kind::OR)
[ - + ][ - + ]
[ - - ]
1396 : : {
1397 : : // handles case of ~(L1 or ... or Ln) where tgt is ~Li.
1398 [ - - ]: 0 : for (size_t i = 0, nchild = src[0].getNumChildren(); i < nchild; i++)
1399 : : {
1400 : 0 : Node sn = src[0][i].negate();
1401 [ - - ]: 0 : if (CDProof::isSame(sn, tgt))
1402 : : {
1403 : 0 : Node snn = src[0][i].notNode();
1404 : 0 : Node ni = nm->mkConstInt(Rational(i));
1405 : 0 : psb.addStep(ProofRule::NOT_OR_ELIM, {src}, {ni}, snn);
1406 : : // double negation elimination if necessary
1407 [ - - ]: 0 : if (snn != sn)
1408 : : {
1409 : 0 : psb.addStep(ProofRule::NOT_NOT_ELIM, {snn}, {}, sn);
1410 : : }
1411 : 0 : stgt = sn;
1412 : 0 : break;
1413 : 0 : }
1414 [ - - ]: 0 : }
1415 : : }
1416 [ + + ]: 76 : else if (src.getKind() == Kind::AND)
1417 : : {
1418 : : // otherwise check case of (L1 and ... and Ln) => Li
1419 [ + - ]: 16 : for (size_t i = 0, nchild = src.getNumChildren(); i < nchild; i++)
1420 : : {
1421 [ + + ]: 16 : if (CDProof::isSame(src[i], tgt))
1422 : : {
1423 : 12 : Node ni = nm->mkConstInt(Rational(i));
1424 : 36 : psb.addStep(ProofRule::AND_ELIM, {src}, {ni}, src[i]);
1425 : 12 : stgt = src[i];
1426 : 12 : break;
1427 : 12 : }
1428 : : }
1429 : : }
1430 [ + + ]: 76 : if (!stgt.isNull())
1431 : : {
1432 [ - + ][ - + ]: 12 : Assert(CDProof::isSame(stgt, tgt));
[ - - ]
1433 [ + + ]: 12 : if (stgt != tgt)
1434 : : {
1435 : 8 : psb.addStep(ProofRule::SYMM, {stgt}, {}, tgt);
1436 : : }
1437 : 12 : return true;
1438 : : }
1439 : 64 : return false;
1440 : 76 : }
1441 : :
1442 : 5565 : Node InferProofCons::convertCoreSubs(Env& env,
1443 : : CDProof* pf,
1444 : : TheoryProofStepBuffer& psb,
1445 : : const Node& src,
1446 : : const std::vector<Node>& exp,
1447 : : size_t minIndex,
1448 : : size_t maxIndex,
1449 : : bool proveSrc)
1450 : : {
1451 : : // set up the conversion proof generator with string core term context
1452 : 5565 : StringCoreTermContext sctc;
1453 : : TConvProofGenerator tconv(env,
1454 : : nullptr,
1455 : : TConvPolicy::FIXPOINT,
1456 : : TConvCachePolicy::NEVER,
1457 : : "StrTConv",
1458 : 11130 : &sctc);
1459 : : // add the rewrites for nested contexts up to idMax.
1460 [ + + ]: 12346 : for (size_t i = minIndex; i <= maxIndex; i++)
1461 : : {
1462 [ + + ]: 21407 : for (const Node& s : exp)
1463 : : {
1464 [ + - ]: 29252 : Trace("strings-ipc-subs")
1465 : 14626 : << "--- rewrite " << s << ", id " << i << std::endl;
1466 [ - + ][ - + ]: 14626 : Assert(s.getKind() == Kind::EQUAL);
[ - - ]
1467 [ + - ]: 14626 : tconv.addRewriteStep(s[0], s[1], pf, false, TrustId::NONE, false, i);
1468 : : }
1469 : : }
1470 : 5565 : std::shared_ptr<ProofNode> pfn = tconv.getProofForRewriting(src);
1471 : 5565 : Node res = pfn->getResult();
1472 [ - + ][ - + ]: 5565 : Assert(res.getKind() == Kind::EQUAL);
[ - - ]
1473 [ + + ]: 5565 : if (res[0] != res[1])
1474 : : {
1475 [ - + ][ - + ]: 5431 : Assert(res[0] == src);
[ - - ]
1476 [ + - ]: 5431 : Trace("strings-ipc-subs") << "Substitutes: " << res << std::endl;
1477 : 5431 : pf->addProof(pfn);
1478 : : // The proof step buffer is tracking unique conclusions, we (dummy) mark
1479 : : // that we have a proof of res via the proof above to ensure we do not
1480 : : // reprove it.
1481 : 10862 : psb.addStep(ProofRule::ASSUME, {}, {res}, res);
1482 [ + + ]: 5431 : if (proveSrc)
1483 : : {
1484 [ + + ][ - - ]: 7329 : psb.addStep(ProofRule::EQ_RESOLVE, {res[1], res[1].eqNode(src)}, {}, src);
1485 : : }
1486 : : else
1487 : : {
1488 [ + + ][ - - ]: 8964 : psb.addStep(ProofRule::EQ_RESOLVE, {src, res}, {}, res[1]);
1489 : : }
1490 : 5431 : return res[1];
1491 : : }
1492 : 134 : return src;
1493 : 5565 : }
1494 : :
1495 : 5699 : Node InferProofCons::spliceConstants(ProofRule rule,
1496 : : TheoryProofStepBuffer& psb,
1497 : : const Node& eq,
1498 : : const Node& conc,
1499 : : bool isRev)
1500 : : {
1501 [ - + ][ - + ]: 5699 : Assert(eq.getKind() == Kind::EQUAL);
[ - - ]
1502 [ + - ]: 11398 : Trace("strings-ipc-splice")
1503 : 5699 : << "Splice " << rule << " (" << isRev << ") for " << eq << std::endl;
1504 : 5699 : std::vector<Node> tvec;
1505 : 5699 : std::vector<Node> svec;
1506 : 5699 : theory::strings::utils::getConcat(eq[0], tvec);
1507 : 5699 : theory::strings::utils::getConcat(eq[1], svec);
1508 : 5699 : size_t nts = tvec.size();
1509 : 5699 : size_t nss = svec.size();
1510 [ + + ]: 5699 : size_t n = nts > nss ? nss : nts;
1511 [ + - ]: 7596 : for (size_t i = 0; i < n; i++)
1512 : : {
1513 [ + + ]: 7596 : size_t ti = isRev ? nts - i - 1 : i;
1514 [ + + ]: 7596 : size_t si = isRev ? nss - i - 1 : i;
1515 : 7596 : Node currT = tvec[ti];
1516 : 7596 : Node currS = svec[si];
1517 [ + + ]: 7596 : if (currT == currS)
1518 : : {
1519 : 1897 : continue;
1520 : : }
1521 [ + + ]: 5699 : if (rule == ProofRule::CONCAT_EQ)
1522 : : {
1523 [ + + ][ + + ]: 3685 : if (!currT.isConst() || !currS.isConst())
[ + + ]
1524 : : {
1525 : : // no need to splice
1526 : 6540 : return eq;
1527 : : }
1528 : : // remove the common prefix
1529 : : // get the equal prefix/suffix, strip and add the remainders
1530 : : size_t sindex;
1531 : 1010 : Node currR = Word::splitConstant(currT, currS, sindex, isRev);
1532 [ + + ]: 505 : if (currR.isNull())
1533 : : {
1534 : : // no need to splice
1535 : 180 : return eq;
1536 : : }
1537 [ + + ]: 325 : size_t index = sindex == 1 ? si : ti;
1538 [ + + ]: 325 : std::vector<Node>& vec = sindex == 1 ? svec : tvec;
1539 [ + + ]: 325 : Node o = sindex == 1 ? currT : currS;
1540 : 325 : vec[index] = o;
1541 [ + + ]: 325 : vec.insert(vec.begin() + index + (isRev ? 0 : 1), currR);
1542 [ + + ]: 505 : }
1543 [ + - ]: 1415 : else if (rule == ProofRule::CONCAT_UNIFY && !conc.isNull()
1544 [ + + ][ + + ]: 3429 : && conc.getKind() == Kind::EQUAL)
[ + + ]
1545 : : {
1546 [ + - ]: 2534 : Trace("strings-ipc-splice")
1547 : 0 : << "Splice cprop at " << currT << " / " << currS
1548 : 1267 : << ", for conclusion " << conc << std::endl;
1549 [ + + ]: 3801 : for (size_t j = 0; j < 2; j++)
1550 : : {
1551 [ + + ]: 2534 : Node src = j == 0 ? currT : currS;
1552 [ + + ]: 2534 : Node tgt = j == 0 ? conc[0] : conc[1];
1553 [ + + ]: 2534 : if (src == tgt)
1554 : : {
1555 : 2526 : continue;
1556 : : }
1557 [ + - ][ - + ]: 8 : if (!src.isConst() || !tgt.isConst())
[ - + ]
1558 : : {
1559 : 0 : DebugUnhandled() << "Non-constant for unify";
1560 : : return eq;
1561 : : }
1562 [ + - ]: 8 : size_t index = j == 0 ? ti : si;
1563 [ + - ]: 8 : std::vector<Node>& vec = j == 0 ? tvec : svec;
1564 : 8 : size_t lentgt = Word::getLength(tgt);
1565 : 8 : size_t len = Word::getLength(src);
1566 [ - + ]: 8 : if (len <= lentgt)
1567 : : {
1568 : 0 : DebugUnhandled() << "Smaller source for unify";
1569 : : return eq;
1570 : : }
1571 [ + - ]: 8 : if (isRev)
1572 : : {
1573 : 8 : vec[index] = Word::suffix(src, lentgt);
1574 : 8 : vec.insert(vec.begin() + index, Word::prefix(src, len - lentgt));
1575 : : }
1576 : : else
1577 : : {
1578 : 0 : vec[index] = Word::prefix(src, lentgt);
1579 : 0 : vec.insert(vec.begin() + index + 1, Word::suffix(src, len - lentgt));
1580 : : }
1581 [ + + ][ - + ]: 5060 : }
[ + - ]
1582 : : }
1583 [ + + ]: 747 : else if (rule == ProofRule::CONCAT_CSPLIT)
1584 : : {
1585 [ - + ]: 599 : if (!currS.isConst())
1586 : : {
1587 : 0 : DebugUnhandled() << "Non-constant for csplit";
1588 : : return eq;
1589 : : }
1590 : : // split the first character
1591 : 599 : size_t len = Word::getLength(currS);
1592 [ + + ]: 599 : if (len == 1)
1593 : : {
1594 : : // not needed
1595 : 442 : return eq;
1596 : : }
1597 [ + + ]: 157 : if (isRev)
1598 : : {
1599 : 97 : svec[si] = Word::suffix(currS, 1);
1600 : 97 : svec.insert(svec.begin() + si, Word::prefix(currS, len - 1));
1601 : : }
1602 : : else
1603 : : {
1604 : 60 : svec[si] = Word::prefix(currS, 1);
1605 : 60 : svec.insert(svec.begin() + si + 1, Word::suffix(currS, len - 1));
1606 : : }
1607 : : }
1608 [ + - ]: 148 : else if (rule == ProofRule::CONCAT_UNIFY && conc.isConst()
1609 [ + - ][ + - ]: 296 : && !conc.getConst<bool>())
[ + - ]
1610 : : {
1611 [ + - ][ - + ]: 148 : if (!currT.isConst() || !currS.isConst())
[ - + ]
1612 : : {
1613 : 0 : DebugUnhandled() << "Non-constants for concat conflict";
1614 : 84 : return eq;
1615 : : }
1616 : : // isolate a disequal prefix by taking maximal prefix/suffix
1617 : 148 : size_t lens = Word::getLength(currS);
1618 : 148 : size_t lent = Word::getLength(currT);
1619 [ + + ]: 148 : if (lens == lent)
1620 : : {
1621 : : // no need
1622 : 84 : return eq;
1623 : : }
1624 [ + + ]: 64 : std::vector<Node>& vec = lens > lent ? svec : tvec;
1625 [ + + ]: 64 : Node curr = lens > lent ? currS : currT;
1626 [ + + ]: 64 : size_t index = lens > lent ? si : ti;
1627 [ + + ]: 64 : size_t smallLen = lens > lent ? lent : lens;
1628 [ + + ]: 64 : size_t diffLen = lens > lent ? (lens - lent) : (lent - lens);
1629 : 64 : vec[index] =
1630 : 128 : isRev ? Word::suffix(curr, smallLen) : Word::prefix(curr, smallLen);
1631 : 64 : vec.insert(
1632 [ + + ]: 64 : vec.begin() + index + (isRev ? 0 : 1),
1633 : 128 : isRev ? Word::prefix(curr, diffLen) : Word::suffix(curr, diffLen));
1634 : 64 : }
1635 : : else
1636 : : {
1637 : 0 : DebugUnhandled() << "Unknown rule to splice " << rule;
1638 : : return eq;
1639 : : }
1640 : 1813 : TypeNode stype = eq[0].getType();
1641 : 1813 : Node tr = utils::mkConcat(tvec, stype);
1642 : 1813 : Node sr = utils::mkConcat(svec, stype);
1643 : 1813 : Node eqr = tr.eqNode(sr);
1644 [ + - ]: 1813 : Trace("strings-ipc-splice") << "...splice to " << eqr << std::endl;
1645 : 1813 : std::vector<Node> cexp;
1646 [ - + ]: 1813 : if (!psb.applyPredTransform(eq, eqr, cexp))
1647 : : {
1648 : 0 : DebugUnhandled() << "Failed to show " << eqr << " spliced from " << eq;
1649 : : return eq;
1650 : : }
1651 : 1813 : return eqr;
1652 [ + + ][ + + ]: 17005 : }
1653 : : // no change
1654 : 0 : return eq;
1655 : 5699 : }
1656 : :
1657 : 25212 : std::shared_ptr<ProofNode> InferProofCons::getProofFor(Node fact)
1658 : : {
1659 : : // get the inference
1660 : 25212 : NodeInferInfoMap::iterator it = d_lazyFactMap.find(fact);
1661 [ - + ]: 25212 : if (it == d_lazyFactMap.end())
1662 : : {
1663 : 0 : Node factSym = CDProof::getSymmFact(fact);
1664 [ - - ]: 0 : if (!factSym.isNull())
1665 : : {
1666 : : // Use the symmetric fact. There is no need to explictly make a
1667 : : // SYMM proof, as this is handled by CDProof::getProofFor below.
1668 : 0 : it = d_lazyFactMap.find(factSym);
1669 : : }
1670 : 0 : }
1671 [ - + ][ - + ]: 25212 : AlwaysAssert(it != d_lazyFactMap.end());
[ - - ]
1672 : 25212 : std::shared_ptr<InferInfo> ii = (*it).second;
1673 [ - + ][ - + ]: 25212 : Assert(ii->d_conc == fact);
[ - - ]
1674 : : // make a placeholder proof using STRINGS_INFERENCE, which is reconstructed
1675 : : // during post-process
1676 : 50424 : CDProof pf(d_env);
1677 : 25212 : std::vector<Node> args;
1678 : 25212 : packArgs(ii->d_conc, ii->getId(), ii->d_idRev, ii->d_premises, args);
1679 : : // must flatten
1680 : 25212 : std::vector<Node> exp;
1681 [ + + ]: 78402 : for (const Node& ec : ii->d_premises)
1682 : : {
1683 : 53190 : utils::flattenOp(Kind::AND, ec, exp);
1684 : : }
1685 : 25212 : pf.addStep(fact, ProofRule::MACRO_STRING_INFERENCE, exp, args);
1686 : 50424 : return pf.getProofFor(fact);
1687 : 25212 : }
1688 : :
1689 : 0 : std::string InferProofCons::identify() const
1690 : : {
1691 : 0 : return "strings::InferProofCons";
1692 : : }
1693 : :
1694 : 3571 : bool InferProofCons::applyPredTransformConversion(const Node& a,
1695 : : const Node& b,
1696 : : TheoryProofStepBuffer& psb)
1697 : : {
1698 : 3571 : std::vector<Node> cexp;
1699 : : // get the equalities where the reduction is different
1700 : 3571 : std::vector<Node> matchConds;
1701 : 3571 : expr::getConversionConditions(a, b, matchConds);
1702 [ + - ]: 3571 : Trace("strings-ipc-red") << "...need to prove " << matchConds << std::endl;
1703 : : // To simplify the proof transformation step below, we manually
1704 : : // unpurify skolems from the concluded reduction. This
1705 : : // make it more likely the applyPredTransform step does not have to
1706 : : // resort to original forms. In particular, the strings rewriter
1707 : : // currently does not respect the property that if
1708 : : // t ---> c for constant c, then getOriginalForm(t) ---> c. This
1709 : : // means we should attempt to replay the term which was used by the
1710 : : // strings skolem cache to justify k = c, which is its unpurified
1711 : : // form t, not its original form.
1712 [ + + ]: 6340 : for (const Node& mc : matchConds)
1713 : : {
1714 : 2769 : Node mcu = SkolemManager::getUnpurifiedForm(mc[0]);
1715 [ + + ]: 2769 : if (mcu != mc[0])
1716 : : {
1717 : 2621 : Node mceq = mc[0].eqNode(mcu);
1718 : 5242 : psb.addStep(ProofRule::SKOLEM_INTRO, {}, {mc[0]}, mceq);
1719 : 2621 : cexp.push_back(mceq);
1720 : 2621 : }
1721 : 2769 : }
1722 : : // either equal or rewrites to it
1723 : 7142 : return psb.applyPredTransform(a, b, cexp);
1724 : 3571 : }
1725 : : } // namespace strings
1726 : : } // namespace theory
1727 : : } // namespace cvc5::internal
|