Branch data Line data Source code
1 : : /******************************************************************************
2 : : * Top contributors (to current version):
3 : : * Andrew Reynolds, Aina Niemetz, Andres Noetzli
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 term registry for the theory of strings.
14 : : */
15 : :
16 : : #include "theory/strings/term_registry.h"
17 : :
18 : : #include "options/smt_options.h"
19 : : #include "options/strings_options.h"
20 : : #include "printer/smt2/smt2_printer.h"
21 : : #include "smt/logic_exception.h"
22 : : #include "theory/rewriter.h"
23 : : #include "theory/strings/inference_manager.h"
24 : : #include "theory/strings/regexp_entail.h"
25 : : #include "theory/strings/theory_strings_utils.h"
26 : : #include "theory/strings/word.h"
27 : : #include "theory/theory.h"
28 : : #include "util/rational.h"
29 : : #include "util/string.h"
30 : :
31 : : using namespace std;
32 : : using namespace cvc5::context;
33 : : using namespace cvc5::internal::kind;
34 : :
35 : : namespace cvc5::internal {
36 : : namespace theory {
37 : : namespace strings {
38 : :
39 : 49899 : TermRegistry::TermRegistry(Env& env, Theory& t, SolverState& s)
40 : : : EnvObj(env),
41 : 49899 : d_theory(t),
42 : 49899 : d_state(s),
43 : 49899 : d_im(nullptr),
44 : 49899 : d_hasStrCode(false),
45 : 49899 : d_hasSeqUpdate(false),
46 : 49899 : d_skCache(nodeManager(), env.getRewriter()),
47 : 49899 : d_aent(nodeManager(), env.getRewriter()),
48 : 49899 : d_functionsTerms(context()),
49 : 49899 : d_inputVars(userContext()),
50 : 49899 : d_preregisteredTerms(context()),
51 : 49899 : d_registeredTerms(userContext()),
52 : 49899 : d_registeredTypes(userContext()),
53 : 49899 : d_proxyVar(userContext()),
54 : 49899 : d_proxyVarToLength(userContext()),
55 : 49899 : d_lengthLemmaTermsCache(userContext()),
56 : 70177 : d_epg(env.isTheoryProofProducing()
57 : : ? new EagerProofGenerator(
58 : : env,
59 : 10139 : userContext(),
60 : 10139 : "strings::TermRegistry::EagerProofGenerator")
61 : : : nullptr),
62 : 99798 : d_inFullEffortCheck(false)
63 : : {
64 : 49899 : NodeManager* nm = nodeManager();
65 : 49899 : d_zero = nm->mkConstInt(Rational(0));
66 : 49899 : d_one = nm->mkConstInt(Rational(1));
67 : 49899 : d_negOne = nm->mkConstInt(Rational(-1));
68 [ - + ][ - + ]: 49899 : Assert(options().strings.stringsAlphaCard <= String::num_codes());
[ - - ]
69 : 49899 : d_alphaCard = options().strings.stringsAlphaCard;
70 : 49899 : }
71 : :
72 : 49554 : TermRegistry::~TermRegistry() {}
73 : :
74 : 111380 : uint32_t TermRegistry::getAlphabetCardinality() const { return d_alphaCard; }
75 : :
76 : 49899 : void TermRegistry::finishInit(InferenceManager* im) { d_im = im; }
77 : :
78 : 142143 : Node TermRegistry::eagerReduce(Node t, SkolemCache* sc, uint32_t alphaCard)
79 : : {
80 : 142143 : NodeManager* nm = t.getNodeManager();
81 : 142143 : Node lemma;
82 : 142143 : Kind tk = t.getKind();
83 [ + + ]: 142143 : if (tk == Kind::STRING_TO_CODE)
84 : : {
85 : : // ite( str.len(s)==1, 0 <= str.code(s) < |A|, str.code(s)=-1 )
86 : 4036 : Node len = nm->mkNode(Kind::STRING_LENGTH, t[0]);
87 : 4036 : Node code_len = len.eqNode(nm->mkConstInt(Rational(1)));
88 : 4036 : Node code_eq_neg1 = t.eqNode(nm->mkConstInt(Rational(-1)));
89 : 2018 : Node code_range = utils::mkCodeRange(t, alphaCard);
90 : 2018 : lemma = nm->mkNode(Kind::ITE, code_len, code_range, code_eq_neg1);
91 : 2018 : }
92 [ + + ]: 140125 : else if (tk == Kind::SEQ_NTH)
93 : : {
94 [ - + ]: 2358 : if (t[0].getType().isString())
95 : : {
96 : 0 : Node s = t[0];
97 : 0 : Node n = t[1];
98 : : // start point is greater than or equal zero
99 : 0 : Node c1 = nm->mkNode(Kind::GEQ, n, nm->mkConstInt(0));
100 : : // start point is less than end of string
101 : 0 : Node c2 = nm->mkNode(Kind::GT, nm->mkNode(Kind::STRING_LENGTH, s), n);
102 : : // check whether this application of seq.nth is defined.
103 : 0 : Node cond = nm->mkNode(Kind::AND, c1, c2);
104 : 0 : Node code_range = utils::mkCodeRange(t, alphaCard);
105 : : // the lemma for `seq.nth`
106 : 0 : lemma = nm->mkNode(
107 : 0 : Kind::ITE, cond, code_range, t.eqNode(nm->mkConstInt(Rational(-1))));
108 : : // IF: n >=0 AND n < len( s )
109 : : // THEN: 0 <= (seq.nth s n) < |A|
110 : : // ELSE: (seq.nth s n) = -1
111 : 0 : }
112 : : }
113 [ + + ][ + + ]: 137767 : else if (tk == Kind::STRING_INDEXOF || tk == Kind::STRING_INDEXOF_RE)
114 : : {
115 : : // (and
116 : : // (or (= (f x y n) (- 1)) (>= (f x y n) n))
117 : : // (<= (f x y n) (str.len x)))
118 : : //
119 : : // where f in { str.indexof, str.indexof_re }
120 : 1928 : Node l = nm->mkNode(Kind::STRING_LENGTH, t[0]);
121 : 2892 : lemma = nm->mkNode(Kind::AND,
122 : 2892 : nm->mkNode(Kind::OR,
123 : 1928 : t.eqNode(nm->mkConstInt(Rational(-1))),
124 : 1928 : nm->mkNode(Kind::GEQ, t, t[2])),
125 : 2892 : nm->mkNode(Kind::LEQ, t, l));
126 : 964 : }
127 [ + + ]: 136803 : else if (tk == Kind::STRING_STOI)
128 : : {
129 : : // (>= (str.to_int x) (- 1))
130 : 206 : lemma = nm->mkNode(Kind::GEQ, t, nm->mkConstInt(Rational(-1)));
131 : : }
132 [ + + ]: 136597 : else if (tk == Kind::STRING_CONTAINS)
133 : : {
134 : : // ite( (str.contains s r), (= s (str.++ sk1 r sk2)), (not (= s r)))
135 : : Node sk1 =
136 : 1496 : sc->mkSkolemCached(t[0], t[1], SkolemCache::SK_FIRST_CTN_PRE, "sc1");
137 : : Node sk2 =
138 : 1496 : sc->mkSkolemCached(t[0], t[1], SkolemCache::SK_FIRST_CTN_POST, "sc2");
139 : 748 : lemma = t[0].eqNode(nm->mkNode(Kind::STRING_CONCAT, sk1, t[1], sk2));
140 : 748 : lemma = nm->mkNode(Kind::ITE, t, lemma, t[0].eqNode(t[1]).notNode());
141 : 748 : }
142 [ + + ]: 135849 : else if (tk == Kind::STRING_IN_REGEXP)
143 : : {
144 : : // for (str.in_re t R), if R has a fixed length L, then we infer the lemma:
145 : : // (str.in_re t R) => (= (str.len t) L).
146 : 6212 : Node len = RegExpEntail::getFixedLengthForRegexp(t[1]);
147 [ + + ]: 3106 : if (!len.isNull())
148 : : {
149 : 820 : lemma = nm->mkNode(
150 : 1230 : Kind::IMPLIES, t, nm->mkNode(Kind::STRING_LENGTH, t[0]).eqNode(len));
151 : : }
152 : 3106 : }
153 [ + + ]: 132743 : else if (tk == Kind::STRING_FROM_CODE)
154 : : {
155 : : // str.from_code(t) ---> ite(0 <= t < |A|, t = str.to_code(k), k = "")
156 : 128 : Node k = sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "kFromCode");
157 : 128 : Node tc = t[0];
158 : 128 : Node card = nm->mkConstInt(Rational(alphaCard));
159 : : Node cond = nm->mkNode(Kind::AND,
160 : 256 : nm->mkNode(Kind::LEQ, nm->mkConstInt(0), tc),
161 : 512 : nm->mkNode(Kind::LT, tc, card));
162 : 128 : Node emp = Word::mkEmptyWord(t.getType());
163 : 384 : lemma = nm->mkNode(Kind::ITE,
164 : : cond,
165 : 256 : tc.eqNode(nm->mkNode(Kind::STRING_TO_CODE, k)),
166 : 384 : k.eqNode(emp));
167 : 128 : }
168 : 142143 : return lemma;
169 : 0 : }
170 : :
171 : 34791 : Node TermRegistry::lengthPositive(Node t)
172 : : {
173 : 34791 : NodeManager* nm = t.getNodeManager();
174 : 34791 : Node zero = nm->mkConstInt(Rational(0));
175 : 34791 : Node emp = Word::mkEmptyWord(t.getType());
176 : 34791 : Node tlen = nm->mkNode(Kind::STRING_LENGTH, t);
177 : 34791 : Node tlenEqZero = tlen.eqNode(zero);
178 : 34791 : Node tEqEmp = t.eqNode(emp);
179 : 69582 : Node caseEmpty = nm->mkNode(Kind::AND, tlenEqZero, tEqEmp);
180 : 69582 : Node caseNEmpty = nm->mkNode(Kind::GT, tlen, zero);
181 : : // (or (and (= (str.len t) 0) (= t "")) (> (str.len t) 0))
182 : 69582 : return nm->mkNode(Kind::OR, caseEmpty, caseNEmpty);
183 : 34791 : }
184 : :
185 : 351948 : void TermRegistry::preRegisterTerm(TNode n)
186 : : {
187 [ + + ]: 351948 : if (d_preregisteredTerms.find(n) != d_preregisteredTerms.end())
188 : : {
189 : 2449 : return;
190 : : }
191 : 349499 : eq::EqualityEngine* ee = d_state.getEqualityEngine();
192 : 349499 : d_preregisteredTerms.insert(n);
193 [ + - ]: 698998 : Trace("strings-preregister")
194 : 349499 : << "TheoryString::preregister : " << n << std::endl;
195 : : // check for logic exceptions
196 : 349499 : Kind k = n.getKind();
197 : 349499 : if (k == Kind::EQUAL && n[0].getType().isRegExp())
198 : : {
199 : : // if an equality between regular expressions was introduced during solving,
200 : : // e.g. by theory combination, we send the equivalance for its quantified
201 : : // reduction here, e.g.
202 : : // (R1 = R2) = (forall s. (s in R1) = (s in R2)).
203 : : Node res =
204 : 0 : d_env.getRewriter()->rewriteViaRule(ProofRewriteRule::RE_EQ_ELIM, n);
205 : 0 : Node lem = nodeManager()->mkNode(Kind::EQUAL, n, res);
206 : 0 : d_im->lemma(lem, InferenceId::STRINGS_RE_EQ_ELIM_EQUIV);
207 : 0 : }
208 [ + + ]: 349499 : if (k == Kind::STRING_IN_REGEXP)
209 : : {
210 : 4851 : d_im->preferPhase(n, true);
211 : : }
212 [ + + ]: 344648 : else if (k == Kind::STRING_TO_CODE)
213 : : {
214 : 3571 : d_hasStrCode = true;
215 : : }
216 [ + + ][ + + ]: 341077 : else if (k == Kind::SEQ_NTH || k == Kind::STRING_UPDATE)
217 : : {
218 : 4905 : d_hasSeqUpdate = true;
219 : : }
220 [ + + ]: 336172 : else if (k == Kind::CONST_SEQUENCE)
221 : : {
222 : : // If we are a constant sequence that has "nested" constant sequences
223 : : // implicitly, e.g. for sequences of sequences, then we must ensure that
224 : : // all subterms of this constant are also considered as terms by the
225 : : // solver. Otherwise, these terms would be hidden inside of the sequence
226 : : // constant. To do so, we ensure a purify skolem is introduced for each
227 : : // subterm. For example, for the sequence constant t:
228 : : // (str.++ (as seq.empty (Seq Int)) (seq.unit (str.++ 0 1)))
229 : : // We add the lemma:
230 : : // k1 = (as seq.empty (Seq Int)) ^ k2 = (seq.unit (str.++ 0 1)) ^
231 : : // t = (str.++ k1 k2).
232 : : // The right hand sides of the first two equalties will lead to
233 : : // preregistering these sequence constants in the same way.
234 : : // These lemmas can be justified trivially by MACRO_SR_PRED_INTRO.
235 : 853 : Node nc = utils::mkConcatForConstSequence(n);
236 : 853 : Kind nck = nc.getKind();
237 [ + + ]: 853 : if (nck != Kind::CONST_SEQUENCE)
238 : : {
239 : 380 : std::vector<Node> eqs;
240 : 380 : std::vector<Node> children;
241 [ + + ]: 915 : for (const Node& ncc : nc)
242 : : {
243 [ + + ]: 535 : if (ncc.getKind() == Kind::CONST_SEQUENCE)
244 : : {
245 : 108 : Node skolem = SkolemManager::mkPurifySkolem(ncc);
246 : 108 : children.push_back(skolem);
247 : 108 : eqs.push_back(skolem.eqNode(ncc));
248 : 108 : }
249 : : else
250 : : {
251 : 427 : children.push_back(ncc);
252 : : }
253 : 535 : }
254 : 380 : Node ret = nodeManager()->mkNode(nck, children);
255 : 380 : eqs.push_back(n.eqNode(ret));
256 : 380 : Node lem = nodeManager()->mkAnd(eqs);
257 [ + - ]: 760 : Trace("strings-preregister")
258 : 380 : << "Const sequence lemma: " << lem << std::endl;
259 : 380 : d_im->lemma(lem, InferenceId::STRINGS_CONST_SEQ_PURIFY);
260 : 380 : }
261 : 853 : }
262 [ + + ]: 349499 : if (options().strings.stringEagerReg)
263 : : {
264 : 348796 : registerTerm(n);
265 : : }
266 : 349499 : TypeNode tn = n.getType();
267 : 349499 : registerType(tn);
268 [ + + ][ - + ]: 349499 : if (tn.isRegExp() && n.isVar())
[ - + ]
269 : : {
270 : 0 : std::stringstream ss;
271 : 0 : ss << "Regular expression variables are not supported.";
272 : 0 : throw LogicException(ss.str());
273 : 0 : }
274 [ + + ]: 349499 : if (tn.isBoolean())
275 : : {
276 : : // All kinds that we do congruence over that may return a Boolean go here
277 [ + + ][ + + ]: 142188 : if (k == Kind::STRING_CONTAINS || k == Kind::STRING_LEQ
278 [ + + ][ + + ]: 137872 : || k == Kind::SEQ_NTH || k == Kind::EQUAL)
279 : : {
280 : : // Get triggered for both equal and dis-equal
281 : 137280 : d_state.addEqualityEngineTriggerPredicate(n);
282 : : }
283 : : }
284 : : else
285 : : {
286 : : // Function applications/predicates
287 : 207311 : ee->addTerm(n);
288 : : }
289 : : // Set d_functionsTerms stores all function applications that are
290 : : // relevant to theory combination. Notice that this is a subset of
291 : : // the applications whose kinds are function kinds in the equality
292 : : // engine. This means it does not include applications of operators
293 : : // like re.++, which is not a function kind in the equality engine.
294 : : // Concatenation terms do not need to be considered here because
295 : : // their arguments have string type and do not introduce any shared
296 : : // terms.
297 [ + + ]: 617818 : if (n.hasOperator() && ee->isFunctionKind(k)
298 [ + - ][ + + ]: 127213 : && kindToTheoryId(k) == THEORY_STRINGS && k != Kind::STRING_CONCAT
299 [ + + ][ + + ]: 617818 : && k != Kind::STRING_IN_REGEXP)
[ + + ]
300 : : {
301 : 97244 : d_functionsTerms.push_back(n);
302 : : }
303 : 349499 : }
304 : :
305 : 532 : void TermRegistry::preRegisterInputVar(TNode n) { d_inputVars.insert(n); }
306 : :
307 : 1741 : void TermRegistry::registerSubterms(Node n)
308 : : {
309 : 1741 : std::unordered_set<TNode> visited;
310 : 1741 : std::vector<TNode> visit;
311 : 1741 : TNode cur;
312 : 1741 : visit.push_back(n);
313 : : do
314 : : {
315 : 2658 : cur = visit.back();
316 : 2658 : visit.pop_back();
317 [ + + ]: 2658 : if (d_registeredTerms.find(cur) == d_registeredTerms.end())
318 : : {
319 : 648 : registerTermInternal(cur);
320 : 648 : Kind k = cur.getKind();
321 : : // only traverse beneath operators belonging to strings
322 [ + + ][ + + ]: 648 : if (k == Kind::EQUAL || theory::kindToTheoryId(k) == THEORY_STRINGS)
[ + + ]
323 : : {
324 : : // strings does not have any closure kinds
325 [ - + ][ - + ]: 520 : Assert (!cur.isClosure());
[ - - ]
326 : 520 : visit.insert(visit.end(), cur.begin(), cur.end());
327 : : }
328 : : }
329 [ + + ]: 2658 : } while (!visit.empty());
330 : 1741 : }
331 : :
332 : 526615 : void TermRegistry::registerTerm(Node n)
333 : : {
334 [ + + ]: 526615 : if (d_registeredTerms.find(n) != d_registeredTerms.end())
335 : : {
336 : 315478 : return;
337 : : }
338 : 211137 : registerTermInternal(n);
339 : : }
340 : :
341 : 211785 : void TermRegistry::registerTermInternal(Node n)
342 : : {
343 [ - + ][ - + ]: 211785 : Assert(d_registeredTerms.find(n) == d_registeredTerms.end());
[ - - ]
344 [ + - ]: 423570 : Trace("strings-register")
345 : 211785 : << "TheoryStrings::registerTermInternal() " << n << std::endl;
346 : 211785 : d_registeredTerms.insert(n);
347 : : // ensure the type is registered
348 : 211785 : TypeNode tn = n.getType();
349 : 211785 : TrustNode regTermLem;
350 [ + + ]: 211785 : if (tn.isStringLike())
351 : : {
352 : : // register length information:
353 : : // for variables, split on empty vs positive length
354 : : // for concat/const/replace, introduce proxy var and state length relation
355 : 68590 : regTermLem = getRegisterTermLemma(n);
356 : : }
357 : : else
358 : : {
359 : : // we don't send out eager reduction lemma for str.contains currently
360 : 143195 : bool doEagerReduce = true;
361 : 143195 : Kind k = n.getKind();
362 [ + + ]: 143195 : if (k == Kind::STRING_CONTAINS)
363 : : {
364 : 2384 : doEagerReduce = false;
365 : : }
366 [ + + ]: 140811 : else if (k == Kind::STRING_TO_CODE)
367 : : {
368 : : // code for proxy are implied
369 : 2033 : Node c = SkolemManager::getOriginalForm(n[0]);
370 : 2033 : doEagerReduce = !c.isConst();
371 : 2033 : }
372 [ + + ]: 143195 : if (doEagerReduce)
373 : : {
374 : 140287 : regTermLem = eagerReduceTrusted(n);
375 : : }
376 : : }
377 [ + + ]: 211785 : if (!regTermLem.isNull())
378 : : {
379 [ + - ]: 49942 : Trace("strings-lemma") << "Strings::Lemma REG-TERM : " << regTermLem
380 : 24971 : << std::endl;
381 [ + - ]: 49942 : Trace("strings-assert")
382 [ - + ][ - - ]: 24971 : << "(assert " << regTermLem.getNode() << ")" << std::endl;
383 : 24971 : d_im->trustedLemma(regTermLem, InferenceId::STRINGS_REGISTER_TERM);
384 : : }
385 : 211785 : }
386 : :
387 : 140386 : TrustNode TermRegistry::eagerReduceTrusted(const Node& n)
388 : : {
389 : 140386 : TrustNode regTermLem;
390 : 140386 : Node eagerRedLemma = eagerReduce(n, &d_skCache, d_alphaCard);
391 [ + + ]: 140386 : if (!eagerRedLemma.isNull())
392 : : {
393 [ + + ]: 2717 : if (d_epg != nullptr)
394 : : {
395 : 2757 : regTermLem = d_epg->mkTrustNode(
396 : 919 : eagerRedLemma, ProofRule::STRING_EAGER_REDUCTION, {}, {n});
397 : : }
398 : : else
399 : : {
400 : 1798 : regTermLem = TrustNode::mkTrustLemma(eagerRedLemma, nullptr);
401 : : }
402 : : }
403 : 280772 : return regTermLem;
404 : 140386 : }
405 : :
406 : 349499 : void TermRegistry::registerType(TypeNode tn)
407 : : {
408 [ + + ]: 349499 : if (d_registeredTypes.find(tn) != d_registeredTypes.end())
409 : : {
410 : 341676 : return;
411 : : }
412 : 7823 : d_registeredTypes.insert(tn);
413 [ + + ]: 7823 : if (tn.isStringLike())
414 : : {
415 : : // preregister the empty word for the type
416 : 2451 : Node emp = Word::mkEmptyWord(tn);
417 : : // always preregister and register unconditionally eagerly
418 : 2451 : preRegisterTerm(emp);
419 : 2451 : registerTerm(emp);
420 : 2451 : }
421 : : }
422 : :
423 : 68590 : TrustNode TermRegistry::getRegisterTermLemma(Node n)
424 : : {
425 [ - + ][ - + ]: 68590 : Assert(n.getType().isStringLike());
[ - - ]
426 : 68590 : NodeManager* nm = nodeManager();
427 : : // register length information:
428 : : // for variables, split on empty vs positive length
429 : : // for concat/const/replace, introduce proxy var and state length relation
430 : 68590 : Node lsum;
431 : 68590 : Kind nk = n.getKind();
432 [ + + ][ + + ]: 68590 : if (nk != Kind::STRING_CONCAT && !n.isConst())
[ + + ]
433 : : {
434 : 47479 : Node lsumb = nm->mkNode(Kind::STRING_LENGTH, n);
435 : 47479 : lsum = rewrite(lsumb);
436 : : // can register length term if it does not rewrite
437 [ + + ]: 47479 : if (lsum == lsumb)
438 : : {
439 : 46237 : registerTermAtomic(n, LENGTH_SPLIT);
440 : 46237 : return TrustNode::null();
441 : : }
442 [ + + ]: 47479 : }
443 : 22353 : Node sk = d_skCache.mkSkolemCached(n, SkolemCache::SK_PURIFY, "lsym");
444 : 44706 : Node eq = rewrite(sk.eqNode(n));
445 : 22353 : d_proxyVar[n] = sk;
446 : : // If we are introducing a proxy for a constant or concat term, we do not
447 : : // need to send lemmas about its length, since its length is already
448 : : // implied.
449 [ + + ][ + + ]: 22353 : if (n.isConst() || nk == Kind::STRING_CONCAT)
[ + + ]
450 : : {
451 : : // do not send length lemma for sk.
452 : 21111 : registerTermAtomic(sk, LENGTH_IGNORE);
453 : : }
454 : 22353 : Node skl = nm->mkNode(Kind::STRING_LENGTH, sk);
455 [ + + ]: 22353 : if (nk == Kind::STRING_CONCAT)
456 : : {
457 : 13443 : std::vector<Node> nodeVec;
458 : 13443 : NodeNodeMap::const_iterator itl;
459 [ + + ]: 46356 : for (const Node& nc : n)
460 : : {
461 : 32913 : itl = d_proxyVarToLength.find(nc);
462 [ + + ]: 32913 : if (itl != d_proxyVarToLength.end())
463 : : {
464 : 105 : nodeVec.push_back(itl->second);
465 : : }
466 : : else
467 : : {
468 : 32808 : Node lni = nm->mkNode(Kind::STRING_LENGTH, nc);
469 : 32808 : nodeVec.push_back(lni);
470 : 32808 : }
471 : 32913 : }
472 : 13443 : lsum = nm->mkNode(Kind::ADD, nodeVec);
473 : 13443 : lsum = rewrite(lsum);
474 : 13443 : }
475 [ + + ]: 8910 : else if (n.isConst())
476 : : {
477 : 7668 : lsum = nm->mkConstInt(Rational(Word::getLength(n)));
478 : : }
479 [ - + ][ - + ]: 22353 : Assert(!lsum.isNull());
[ - - ]
480 : 22353 : d_proxyVarToLength[sk] = lsum;
481 : 44706 : Node ceq = rewrite(skl.eqNode(lsum));
482 : :
483 : 44706 : Node ret = nm->mkNode(Kind::AND, eq, ceq);
484 : :
485 : : // it is a simple rewrite to justify this
486 [ + + ]: 22353 : if (d_epg != nullptr)
487 : : {
488 : 17336 : return d_epg->mkTrustNode(ret, ProofRule::MACRO_SR_PRED_INTRO, {}, {ret});
489 : : }
490 : 13685 : return TrustNode::mkTrustLemma(ret, nullptr);
491 : 68590 : }
492 : :
493 : 69924 : void TermRegistry::registerTermAtomic(Node n, LengthStatus s)
494 : : {
495 [ + + ]: 69924 : if (d_lengthLemmaTermsCache.find(n) != d_lengthLemmaTermsCache.end())
496 : : {
497 : 44755 : return;
498 : : }
499 : 46246 : d_lengthLemmaTermsCache.insert(n);
500 : :
501 [ + + ]: 46246 : if (s == LENGTH_IGNORE)
502 : : {
503 : : // ignore it
504 : 21077 : return;
505 : : }
506 : 25169 : std::map<Node, bool> reqPhase;
507 : 25169 : TrustNode lenLem = getRegisterTermAtomicLemma(n, s, reqPhase);
508 [ + + ]: 25169 : if (!lenLem.isNull())
509 : : {
510 [ + - ]: 50320 : Trace("strings-lemma") << "Strings::Lemma REGISTER-TERM-ATOMIC : " << lenLem
511 : 25160 : << std::endl;
512 [ + - ]: 50320 : Trace("strings-assert")
513 [ - + ][ - - ]: 25160 : << "(assert " << lenLem.getNode() << ")" << std::endl;
514 : 25160 : d_im->trustedLemma(lenLem, InferenceId::STRINGS_REGISTER_TERM_ATOMIC);
515 : : }
516 [ + + ]: 75489 : for (const std::pair<const Node, bool>& rp : reqPhase)
517 : : {
518 : 50320 : d_im->preferPhase(rp.first, rp.second);
519 : : }
520 : 25169 : }
521 : :
522 : 120966 : SkolemCache* TermRegistry::getSkolemCache() { return &d_skCache; }
523 : :
524 : 27369 : const context::CDList<TNode>& TermRegistry::getFunctionTerms() const
525 : : {
526 : 27369 : return d_functionsTerms;
527 : : }
528 : :
529 : 85 : const context::CDHashSet<Node>& TermRegistry::getInputVars() const
530 : : {
531 : 85 : return d_inputVars;
532 : : }
533 : :
534 : 42312 : bool TermRegistry::hasStringCode() const { return d_hasStrCode; }
535 : :
536 : 804 : bool TermRegistry::hasSeqUpdate() const { return d_hasSeqUpdate; }
537 : :
538 : 1649 : bool TermRegistry::isHandledUpdateOrSubstr(Node n)
539 : : {
540 [ + + ][ + - ]: 1649 : Assert(n.getKind() == Kind::STRING_UPDATE
[ - + ][ - + ]
[ - - ]
541 : : || n.getKind() == Kind::STRING_SUBSTR);
542 : 1649 : NodeManager* nm = nodeManager();
543 : 1649 : Node lenN = n[2];
544 [ + + ]: 1649 : if (n.getKind() == Kind::STRING_UPDATE)
545 : : {
546 : 1614 : lenN = nm->mkNode(Kind::STRING_LENGTH, n[2]);
547 : : }
548 : 1649 : Node one = nm->mkConstInt(Rational(1));
549 : 3298 : return d_aent.checkEq(lenN, one);
550 : 1649 : }
551 : :
552 : 0 : Node TermRegistry::getUpdateBase(Node n)
553 : : {
554 [ - - ]: 0 : while (n.getKind() == Kind::STRING_UPDATE)
555 : : {
556 : 0 : n = n[0];
557 : : }
558 : 0 : return n;
559 : : }
560 : :
561 : 25169 : TrustNode TermRegistry::getRegisterTermAtomicLemma(
562 : : Node n, LengthStatus s, std::map<Node, bool>& reqPhase)
563 : : {
564 [ + + ]: 25169 : if (n.isConst())
565 : : {
566 : : // No need to send length for constant terms. This case may be triggered
567 : : // for cases where the skolem cache automatically replaces a skolem by
568 : : // a constant.
569 : 9 : return TrustNode::null();
570 : : }
571 [ - + ][ - + ]: 25160 : Assert(n.getType().isStringLike());
[ - - ]
572 : 25160 : NodeManager* nm = nodeManager();
573 : 25160 : Node n_len = nm->mkNode(Kind::STRING_LENGTH, n);
574 : 25160 : Node emp = Word::mkEmptyWord(n.getType());
575 [ - + ]: 25160 : if (s == LENGTH_GEQ_ONE)
576 : : {
577 : 0 : Node neq_empty = n.eqNode(emp).negate();
578 : 0 : Node len_n_gt_z = nm->mkNode(Kind::GT, n_len, d_zero);
579 : 0 : Node len_geq_one = nm->mkNode(Kind::AND, neq_empty, len_n_gt_z);
580 [ - - ]: 0 : Trace("strings-lemma") << "Strings::Lemma SK-GEQ-ONE : " << len_geq_one
581 : 0 : << std::endl;
582 [ - - ]: 0 : Trace("strings-assert") << "(assert " << len_geq_one << ")" << std::endl;
583 : 0 : return TrustNode::mkTrustLemma(len_geq_one, nullptr);
584 : 0 : }
585 : :
586 [ - + ]: 25160 : if (s == LENGTH_ONE)
587 : : {
588 : 0 : Node len_one = n_len.eqNode(d_one);
589 [ - - ]: 0 : Trace("strings-lemma") << "Strings::Lemma SK-ONE : " << len_one
590 : 0 : << std::endl;
591 [ - - ]: 0 : Trace("strings-assert") << "(assert " << len_one << ")" << std::endl;
592 : 0 : return TrustNode::mkTrustLemma(len_one, nullptr);
593 : 0 : }
594 [ - + ][ - + ]: 25160 : Assert(s == LENGTH_SPLIT);
[ - - ]
595 : :
596 : : // get the positive length lemma
597 : 25160 : Node lenLemma = lengthPositive(n);
598 : : // split whether the string is empty
599 : 25160 : Node n_len_eq_z = n_len.eqNode(d_zero);
600 : 25160 : Node n_len_eq_z_2 = n.eqNode(emp);
601 : 50320 : Node case_empty = nm->mkNode(Kind::AND, n_len_eq_z, n_len_eq_z_2);
602 : 25160 : Node case_emptyr = rewrite(case_empty);
603 [ + - ]: 25160 : if (!case_emptyr.isConst())
604 : : {
605 : : // prefer trying the empty case first
606 : : // notice that preferPhase must only be called on rewritten literals that
607 : : // occur in the CNF stream.
608 : 25160 : n_len_eq_z = rewrite(n_len_eq_z);
609 [ - + ][ - + ]: 25160 : Assert(!n_len_eq_z.isConst());
[ - - ]
610 : 25160 : reqPhase[n_len_eq_z] = true;
611 : 25160 : n_len_eq_z_2 = rewrite(n_len_eq_z_2);
612 [ - + ][ - + ]: 25160 : Assert(!n_len_eq_z_2.isConst());
[ - - ]
613 : 25160 : reqPhase[n_len_eq_z_2] = true;
614 : : }
615 : : else
616 : : {
617 : : // If n = "" ---> true or len( n ) = 0 ----> true, then we expect that
618 : : // n ---> "". Since this method is only called on non-constants n, it must
619 : : // be that n = "" ^ len( n ) = 0 does not rewrite to true.
620 : 0 : Assert(!case_emptyr.getConst<bool>());
621 : : }
622 : :
623 [ + + ]: 25160 : if (d_epg != nullptr)
624 : : {
625 : 18310 : return d_epg->mkTrustNode(lenLemma, ProofRule::STRING_LENGTH_POS, {}, {n});
626 : : }
627 : 16005 : return TrustNode::mkTrustLemma(lenLemma, nullptr);
628 : 25160 : }
629 : :
630 : 413489 : Node TermRegistry::getSymbolicDefinition(Node n, std::vector<Node>& exp) const
631 : : {
632 [ + + ]: 413489 : if (n.getNumChildren() == 0)
633 : : {
634 : 232069 : Node pn = getProxyVariableFor(n);
635 [ + + ]: 232069 : if (pn.isNull())
636 : : {
637 : 37130 : return Node::null();
638 : : }
639 : 194939 : Node eq = n.eqNode(pn);
640 : 194939 : eq = rewrite(eq);
641 [ + + ]: 194939 : if (std::find(exp.begin(), exp.end(), eq) == exp.end())
642 : : {
643 : 181513 : exp.push_back(eq);
644 : : }
645 : 194939 : return pn;
646 : 232069 : }
647 : 181420 : std::vector<Node> children;
648 [ + + ]: 181420 : if (n.getMetaKind() == metakind::PARAMETERIZED)
649 : : {
650 : 563 : children.push_back(n.getOperator());
651 : : }
652 [ + + ]: 450031 : for (const Node& nc : n)
653 : : {
654 [ + + ]: 326938 : if (n.getType().isRegExp())
655 : : {
656 : 46949 : children.push_back(nc);
657 : : }
658 : : else
659 : : {
660 : 279989 : Node ns = getSymbolicDefinition(nc, exp);
661 [ + + ]: 279989 : if (ns.isNull())
662 : : {
663 : 58327 : return Node::null();
664 : : }
665 : : else
666 : : {
667 : 221662 : children.push_back(ns);
668 : : }
669 [ + + ]: 279989 : }
670 [ + + ]: 326938 : }
671 : 123093 : return nodeManager()->mkNode(n.getKind(), children);
672 : 181420 : }
673 : :
674 : 436696 : Node TermRegistry::getProxyVariableFor(Node n) const
675 : : {
676 : 436696 : NodeNodeMap::const_iterator it = d_proxyVar.find(n);
677 [ + + ]: 436696 : if (it != d_proxyVar.end())
678 : : {
679 : 254566 : return (*it).second;
680 : : }
681 : 182130 : return Node::null();
682 : : }
683 : :
684 : 6367 : Node TermRegistry::ensureProxyVariableFor(Node n)
685 : : {
686 : 6367 : Node proxy = getProxyVariableFor(n);
687 [ + + ]: 6367 : if (proxy.isNull())
688 : : {
689 : 54 : registerTerm(n);
690 : 54 : proxy = getProxyVariableFor(n);
691 : : }
692 [ - + ][ - + ]: 6367 : Assert(!proxy.isNull());
[ - - ]
693 : 6367 : return proxy;
694 : 0 : }
695 : :
696 : 207201 : void TermRegistry::removeProxyEqs(Node n, std::vector<Node>& unproc) const
697 : : {
698 [ + + ]: 207201 : if (n.getKind() == Kind::AND)
699 : : {
700 [ + + ]: 1150 : for (const Node& nc : n)
701 : : {
702 : 819 : removeProxyEqs(nc, unproc);
703 : 819 : }
704 : 331 : return;
705 : : }
706 [ + - ]: 206870 : Trace("strings-subs-proxy") << "Input : " << n << std::endl;
707 : 206870 : Node ns = rewrite(n);
708 [ + + ]: 206870 : if (ns.getKind() == Kind::EQUAL)
709 : : {
710 [ + + ]: 618342 : for (size_t i = 0; i < 2; i++)
711 : : {
712 : : // determine whether this side has a proxy variable
713 [ + + ]: 412228 : if (d_proxyVar.find(ns[i]) != d_proxyVar.end())
714 : : {
715 [ - + ]: 198186 : if (getProxyVariableFor(ns[1 - i]) == ns[i])
716 : : {
717 [ - - ]: 0 : Trace("strings-subs-proxy")
718 : 0 : << "...trivial definition via " << ns[i] << std::endl;
719 : : // it is a trivial equality, e.g. between a proxy variable
720 : : // and its definition
721 : 0 : return;
722 : : }
723 : : }
724 : : }
725 : : }
726 [ + + ][ - + ]: 206870 : if (!n.isConst() || !n.getConst<bool>())
[ + + ]
727 : : {
728 [ + - ]: 206814 : Trace("strings-subs-proxy") << "...unprocessed" << std::endl;
729 : 206814 : unproc.push_back(n);
730 : : }
731 [ + - ]: 206870 : }
732 : :
733 : 65629 : void TermRegistry::notifyStartFullEffortCheck()
734 : : {
735 : 65629 : d_inFullEffortCheck = true;
736 : 65629 : d_relevantTerms.clear();
737 : : // get the asserted terms
738 : 65629 : std::set<Kind> irrKinds;
739 : 65629 : d_theory.collectAssertedTerms(d_relevantTerms, true, irrKinds);
740 : : // also, get the additionally relevant terms
741 : 65629 : d_theory.computeRelevantTerms(d_relevantTerms);
742 : 65629 : }
743 : :
744 : 65629 : void TermRegistry::notifyEndFullEffortCheck() { d_inFullEffortCheck = false; }
745 : :
746 : 2019849 : const std::set<Node>& TermRegistry::getRelevantTermSet() const
747 : : {
748 : : // must be in full effort check for relevant terms to be valid
749 [ - + ][ - + ]: 2019849 : Assert(d_inFullEffortCheck);
[ - - ]
750 : 2019849 : return d_relevantTerms;
751 : : }
752 : :
753 : 1164 : Node TermRegistry::mkNConcat(Node n1, Node n2) const
754 : : {
755 : 1164 : return rewrite(NodeManager::mkNode(Kind::STRING_CONCAT, n1, n2));
756 : : }
757 : :
758 : 0 : Node TermRegistry::mkNConcat(Node n1, Node n2, Node n3) const
759 : : {
760 : 0 : return rewrite(NodeManager::mkNode(Kind::STRING_CONCAT, n1, n2, n3));
761 : : }
762 : :
763 : 1289685 : Node TermRegistry::mkNConcat(const std::vector<Node>& c, TypeNode tn) const
764 : : {
765 : 1289685 : return rewrite(utils::mkConcat(c, tn));
766 : : }
767 : :
768 : : } // namespace strings
769 : : } // namespace theory
770 : : } // namespace cvc5::internal
|