1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Andres Noetzli, Tianyi Liang |
4 |
|
* |
5 |
|
* This file is part of the cvc5 project. |
6 |
|
* |
7 |
|
* Copyright (c) 2009-2021 by the authors listed in the file AUTHORS |
8 |
|
* in the top-level source directory and their institutional affiliations. |
9 |
|
* All rights reserved. See the file COPYING in the top-level source |
10 |
|
* directory for licensing information. |
11 |
|
* **************************************************************************** |
12 |
|
* |
13 |
|
* Implementation of the theory of strings. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "theory/strings/core_solver.h" |
17 |
|
|
18 |
|
#include "base/configuration.h" |
19 |
|
#include "expr/sequence.h" |
20 |
|
#include "options/strings_options.h" |
21 |
|
#include "smt/logic_exception.h" |
22 |
|
#include "theory/rewriter.h" |
23 |
|
#include "theory/strings/sequences_rewriter.h" |
24 |
|
#include "theory/strings/strings_entail.h" |
25 |
|
#include "theory/strings/theory_strings_utils.h" |
26 |
|
#include "theory/strings/word.h" |
27 |
|
#include "util/rational.h" |
28 |
|
#include "util/string.h" |
29 |
|
|
30 |
|
using namespace std; |
31 |
|
using namespace cvc5::context; |
32 |
|
using namespace cvc5::kind; |
33 |
|
|
34 |
|
namespace cvc5 { |
35 |
|
namespace theory { |
36 |
|
namespace strings { |
37 |
|
|
38 |
18228 |
CoreInferInfo::CoreInferInfo(InferenceId id) : d_infer(id), d_index(0), d_rev(false) {} |
39 |
|
|
40 |
9917 |
CoreSolver::CoreSolver(SolverState& s, |
41 |
|
InferenceManager& im, |
42 |
|
TermRegistry& tr, |
43 |
9917 |
BaseSolver& bs) |
44 |
|
: d_state(s), |
45 |
|
d_im(im), |
46 |
|
d_termReg(tr), |
47 |
|
d_bsolver(bs), |
48 |
9917 |
d_nfPairs(s.getSatContext()) |
49 |
|
{ |
50 |
9917 |
d_zero = NodeManager::currentNM()->mkConst( Rational( 0 ) ); |
51 |
9917 |
d_one = NodeManager::currentNM()->mkConst( Rational( 1 ) ); |
52 |
9917 |
d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); |
53 |
9917 |
d_true = NodeManager::currentNM()->mkConst( true ); |
54 |
9917 |
d_false = NodeManager::currentNM()->mkConst( false ); |
55 |
9917 |
} |
56 |
|
|
57 |
9914 |
CoreSolver::~CoreSolver() { |
58 |
|
|
59 |
9914 |
} |
60 |
|
|
61 |
|
void CoreSolver::debugPrintFlatForms( const char * tc ){ |
62 |
|
for( unsigned k=0; k<d_strings_eqc.size(); k++ ){ |
63 |
|
Node eqc = d_strings_eqc[k]; |
64 |
|
if( d_eqc[eqc].size()>1 ){ |
65 |
|
Trace( tc ) << "EQC [" << eqc << "]" << std::endl; |
66 |
|
}else{ |
67 |
|
Trace( tc ) << "eqc [" << eqc << "]"; |
68 |
|
} |
69 |
|
Node c = d_bsolver.getConstantEqc(eqc); |
70 |
|
if( !c.isNull() ){ |
71 |
|
Trace( tc ) << " C: " << c; |
72 |
|
if( d_eqc[eqc].size()>1 ){ |
73 |
|
Trace( tc ) << std::endl; |
74 |
|
} |
75 |
|
} |
76 |
|
if( d_eqc[eqc].size()>1 ){ |
77 |
|
for( unsigned i=0; i<d_eqc[eqc].size(); i++ ){ |
78 |
|
Node n = d_eqc[eqc][i]; |
79 |
|
Trace( tc ) << " "; |
80 |
|
for( unsigned j=0; j<d_flat_form[n].size(); j++ ){ |
81 |
|
Node fc = d_flat_form[n][j]; |
82 |
|
Node fcc = d_bsolver.getConstantEqc(fc); |
83 |
|
Trace( tc ) << " "; |
84 |
|
if( !fcc.isNull() ){ |
85 |
|
Trace( tc ) << fcc; |
86 |
|
}else{ |
87 |
|
Trace( tc ) << fc; |
88 |
|
} |
89 |
|
} |
90 |
|
if( n!=eqc ){ |
91 |
|
Trace( tc ) << ", from " << n; |
92 |
|
} |
93 |
|
Trace( tc ) << std::endl; |
94 |
|
} |
95 |
|
}else{ |
96 |
|
Trace( tc ) << std::endl; |
97 |
|
} |
98 |
|
} |
99 |
|
Trace( tc ) << std::endl; |
100 |
|
} |
101 |
|
|
102 |
|
struct sortConstLength { |
103 |
|
std::map< Node, unsigned > d_const_length; |
104 |
|
bool operator() (Node i, Node j) { |
105 |
|
std::map< Node, unsigned >::iterator it_i = d_const_length.find( i ); |
106 |
|
std::map< Node, unsigned >::iterator it_j = d_const_length.find( j ); |
107 |
|
if( it_i==d_const_length.end() ){ |
108 |
|
if( it_j==d_const_length.end() ){ |
109 |
|
return i<j; |
110 |
|
}else{ |
111 |
|
return false; |
112 |
|
} |
113 |
|
}else{ |
114 |
|
if( it_j==d_const_length.end() ){ |
115 |
|
return true; |
116 |
|
}else{ |
117 |
|
return it_i->second<it_j->second; |
118 |
|
} |
119 |
|
} |
120 |
|
} |
121 |
|
}; |
122 |
|
|
123 |
32619 |
void CoreSolver::checkCycles() |
124 |
|
{ |
125 |
|
// first check for cycles, while building ordering of equivalence classes |
126 |
32619 |
d_flat_form.clear(); |
127 |
32619 |
d_flat_form_index.clear(); |
128 |
32619 |
d_eqc.clear(); |
129 |
|
// Rebuild strings eqc based on acyclic ordering, first copy the equivalence |
130 |
|
// classes from the base solver. |
131 |
32619 |
const std::vector<Node>& eqc = d_bsolver.getStringEqc(); |
132 |
32619 |
d_strings_eqc.clear(); |
133 |
610382 |
for (const Node& r : eqc) |
134 |
|
{ |
135 |
1155530 |
std::vector< Node > curr; |
136 |
1155530 |
std::vector< Node > exp; |
137 |
577767 |
checkCycles(r, curr, exp); |
138 |
577767 |
if (d_im.hasProcessed()) |
139 |
|
{ |
140 |
4 |
return; |
141 |
|
} |
142 |
|
} |
143 |
|
} |
144 |
|
|
145 |
32615 |
void CoreSolver::checkFlatForms() |
146 |
|
{ |
147 |
|
// debug print flat forms |
148 |
32615 |
if (Trace.isOn("strings-ff")) |
149 |
|
{ |
150 |
|
Trace("strings-ff") << "Flat forms : " << std::endl; |
151 |
|
debugPrintFlatForms("strings-ff"); |
152 |
|
} |
153 |
|
|
154 |
|
// inferences without recursively expanding flat forms |
155 |
|
|
156 |
|
//(1) approximate equality by containment, infer conflicts |
157 |
610282 |
for (const Node& eqc : d_strings_eqc) |
158 |
|
{ |
159 |
1155388 |
Node c = d_bsolver.getConstantEqc(eqc); |
160 |
577721 |
if (!c.isNull()) |
161 |
|
{ |
162 |
|
// if equivalence class is constant, all component constants in flat forms |
163 |
|
// must be contained in it, in order |
164 |
173124 |
std::map<Node, std::vector<Node> >::iterator it = d_eqc.find(eqc); |
165 |
173124 |
if (it != d_eqc.end()) |
166 |
|
{ |
167 |
63237 |
for (const Node& n : it->second) |
168 |
|
{ |
169 |
|
int firstc, lastc; |
170 |
33736 |
if (!StringsEntail::canConstantContainList( |
171 |
33736 |
c, d_flat_form[n], firstc, lastc)) |
172 |
|
{ |
173 |
108 |
Trace("strings-ff-debug") << "Flat form for " << n |
174 |
54 |
<< " cannot be contained in constant " |
175 |
54 |
<< c << std::endl; |
176 |
108 |
Trace("strings-ff-debug") << " indices = " << firstc << "/" |
177 |
54 |
<< lastc << std::endl; |
178 |
|
// conflict, explanation is n = base ^ base = c ^ relevant portion |
179 |
|
// of ( n = f[n] ) |
180 |
108 |
std::vector<Node> exp; |
181 |
140 |
for (int e = firstc; e <= lastc; e++) |
182 |
|
{ |
183 |
86 |
if (d_flat_form[n][e].isConst()) |
184 |
|
{ |
185 |
72 |
Assert(e >= 0 && e < (int)d_flat_form_index[n].size()); |
186 |
72 |
Assert(d_flat_form_index[n][e] >= 0 |
187 |
|
&& d_flat_form_index[n][e] < (int)n.getNumChildren()); |
188 |
216 |
d_im.addToExplanation( |
189 |
144 |
n[d_flat_form_index[n][e]], d_flat_form[n][e], exp); |
190 |
|
} |
191 |
|
} |
192 |
54 |
d_bsolver.explainConstantEqc(n, eqc, exp); |
193 |
108 |
Node conc = d_false; |
194 |
54 |
d_im.sendInference(exp, conc, InferenceId::STRINGS_F_NCTN); |
195 |
54 |
return; |
196 |
|
} |
197 |
|
} |
198 |
|
} |
199 |
|
} |
200 |
|
} |
201 |
|
|
202 |
|
//(2) scan lists, unification to infer conflicts and equalities |
203 |
607155 |
for (const Node& eqc : d_strings_eqc) |
204 |
|
{ |
205 |
574839 |
std::map<Node, std::vector<Node> >::iterator it = d_eqc.find(eqc); |
206 |
574839 |
if (it == d_eqc.end() || it->second.size() <= 1) |
207 |
|
{ |
208 |
530885 |
continue; |
209 |
|
} |
210 |
|
// iterate over start index |
211 |
157576 |
for (unsigned start = 0; start < it->second.size() - 1; start++) |
212 |
|
{ |
213 |
341281 |
for (unsigned r = 0; r < 2; r++) |
214 |
|
{ |
215 |
227659 |
bool isRev = r == 1; |
216 |
227659 |
checkFlatForm(it->second, start, isRev); |
217 |
227659 |
if (d_state.isInConflict()) |
218 |
|
{ |
219 |
490 |
return; |
220 |
|
} |
221 |
|
|
222 |
1887664 |
for (const Node& n : it->second) |
223 |
|
{ |
224 |
1660250 |
std::reverse(d_flat_form[n].begin(), d_flat_form[n].end()); |
225 |
1660250 |
std::reverse(d_flat_form_index[n].begin(), |
226 |
1660250 |
d_flat_form_index[n].end()); |
227 |
|
} |
228 |
|
} |
229 |
|
} |
230 |
|
} |
231 |
|
} |
232 |
|
|
233 |
227659 |
void CoreSolver::checkFlatForm(std::vector<Node>& eqc, |
234 |
|
size_t start, |
235 |
|
bool isRev) |
236 |
|
{ |
237 |
227659 |
size_t count = 0; |
238 |
|
// We check for flat form inferences involving `eqc[start]` and terms past |
239 |
|
// `start`. If there was a flat form inference involving `eqc[start]` and a |
240 |
|
// term at a smaller index `i`, we would have found it with when `start` was |
241 |
|
// `i`. Thus, we mark the preceeding terms in the equivalence class as |
242 |
|
// ineligible. |
243 |
455073 |
std::vector<Node> inelig(eqc.begin(), eqc.begin() + start + 1); |
244 |
455073 |
Node a = eqc[start]; |
245 |
455318 |
Trace("strings-ff-debug") |
246 |
227659 |
<< "Check flat form for a = " << a << ", whose flat form is " |
247 |
227659 |
<< d_flat_form[a] << ")" << std::endl; |
248 |
455073 |
Node b; |
249 |
|
// the length explanation |
250 |
455073 |
Node lant; |
251 |
66182 |
do |
252 |
|
{ |
253 |
575048 |
std::vector<Node> exp; |
254 |
575048 |
Node conc; |
255 |
293841 |
InferenceId infType = InferenceId::UNKNOWN; |
256 |
293841 |
size_t eqc_size = eqc.size(); |
257 |
293841 |
size_t asize = d_flat_form[a].size(); |
258 |
293841 |
if (count == asize) |
259 |
|
{ |
260 |
|
for (size_t i = start + 1; i < eqc_size; i++) |
261 |
|
{ |
262 |
|
b = eqc[i]; |
263 |
|
if (std::find(inelig.begin(), inelig.end(), b) != inelig.end()) |
264 |
|
{ |
265 |
|
continue; |
266 |
|
} |
267 |
|
|
268 |
|
size_t bsize = d_flat_form[b].size(); |
269 |
|
if (count < bsize) |
270 |
|
{ |
271 |
|
Trace("strings-ff-debug") |
272 |
|
<< "Found endpoint (in a) with non-empty b = " << b |
273 |
|
<< ", whose flat form is " << d_flat_form[b] << std::endl; |
274 |
|
// endpoint |
275 |
|
Node emp = Word::mkEmptyWord(a.getType()); |
276 |
|
std::vector<Node> conc_c; |
277 |
|
for (unsigned j = count; j < bsize; j++) |
278 |
|
{ |
279 |
|
conc_c.push_back(b[d_flat_form_index[b][j]].eqNode(emp)); |
280 |
|
} |
281 |
|
Assert(!conc_c.empty()); |
282 |
|
conc = utils::mkAnd(conc_c); |
283 |
|
infType = InferenceId::STRINGS_F_ENDPOINT_EMP; |
284 |
|
Assert(count > 0); |
285 |
|
// swap, will enforce is empty past current |
286 |
|
a = eqc[i]; |
287 |
|
b = eqc[start]; |
288 |
|
break; |
289 |
|
} |
290 |
|
inelig.push_back(eqc[i]); |
291 |
|
} |
292 |
|
} |
293 |
|
else |
294 |
|
{ |
295 |
587682 |
Node curr = d_flat_form[a][count]; |
296 |
587682 |
Node curr_c = d_bsolver.getConstantEqc(curr); |
297 |
587682 |
Node ac = a[d_flat_form_index[a][count]]; |
298 |
587682 |
std::vector<Node> lexp; |
299 |
587682 |
Node lcurr = d_state.getLength(ac, lexp); |
300 |
1480016 |
for (size_t i = start + 1; i < eqc_size; i++) |
301 |
|
{ |
302 |
1198809 |
b = eqc[i]; |
303 |
1198809 |
if (std::find(inelig.begin(), inelig.end(), b) != inelig.end()) |
304 |
|
{ |
305 |
288955 |
continue; |
306 |
|
} |
307 |
|
|
308 |
909854 |
if (count == d_flat_form[b].size()) |
309 |
|
{ |
310 |
|
inelig.push_back(b); |
311 |
|
Trace("strings-ff-debug") |
312 |
|
<< "Found endpoint in b = " << b << ", whose flat form is " |
313 |
|
<< d_flat_form[b] << std::endl; |
314 |
|
// endpoint |
315 |
|
Node emp = Word::mkEmptyWord(a.getType()); |
316 |
|
std::vector<Node> conc_c; |
317 |
|
for (size_t j = count; j < asize; j++) |
318 |
|
{ |
319 |
|
conc_c.push_back(a[d_flat_form_index[a][j]].eqNode(emp)); |
320 |
|
} |
321 |
|
Assert(!conc_c.empty()); |
322 |
|
conc = utils::mkAnd(conc_c); |
323 |
|
infType = InferenceId::STRINGS_F_ENDPOINT_EMP; |
324 |
|
Assert(count > 0); |
325 |
|
break; |
326 |
|
} |
327 |
|
else |
328 |
|
{ |
329 |
1807074 |
Node cc = d_flat_form[b][count]; |
330 |
909854 |
if (cc != curr) |
331 |
|
{ |
332 |
1630410 |
Node bc = b[d_flat_form_index[b][count]]; |
333 |
821522 |
inelig.push_back(b); |
334 |
821522 |
Assert(!d_state.areEqual(curr, cc)); |
335 |
1630410 |
Node cc_c = d_bsolver.getConstantEqc(cc); |
336 |
821522 |
if (!curr_c.isNull() && !cc_c.isNull()) |
337 |
|
{ |
338 |
|
// check for constant conflict |
339 |
|
size_t index; |
340 |
67339 |
Node s = Word::splitConstant(cc_c, curr_c, index, isRev); |
341 |
33792 |
if (s.isNull()) |
342 |
|
{ |
343 |
245 |
d_bsolver.explainConstantEqc(ac,curr,exp); |
344 |
245 |
d_bsolver.explainConstantEqc(bc,cc,exp); |
345 |
245 |
conc = d_false; |
346 |
245 |
infType = InferenceId::STRINGS_F_CONST; |
347 |
245 |
break; |
348 |
|
} |
349 |
|
} |
350 |
1575460 |
else if ((d_flat_form[a].size() - 1) == count |
351 |
787730 |
&& (d_flat_form[b].size() - 1) == count) |
352 |
|
{ |
353 |
5288 |
conc = ac.eqNode(bc); |
354 |
5288 |
infType = InferenceId::STRINGS_F_ENDPOINT_EQ; |
355 |
5288 |
break; |
356 |
|
} |
357 |
|
else |
358 |
|
{ |
359 |
|
// if lengths are the same, apply LengthEq |
360 |
1557783 |
std::vector<Node> lexp2; |
361 |
1557783 |
Node lcc = d_state.getLength(bc, lexp2); |
362 |
782442 |
if (d_state.areEqual(lcurr, lcc)) |
363 |
|
{ |
364 |
7101 |
if (Trace.isOn("strings-ff-debug")) |
365 |
|
{ |
366 |
|
Trace("strings-ff-debug") |
367 |
|
<< "Infer " << ac << " == " << bc << " since " << lcurr |
368 |
|
<< " == " << lcc << std::endl; |
369 |
|
Trace("strings-ff-debug") |
370 |
|
<< "Explanation for " << lcurr << " is "; |
371 |
|
for (size_t j = 0; j < lexp.size(); j++) |
372 |
|
{ |
373 |
|
Trace("strings-ff-debug") << lexp[j] << std::endl; |
374 |
|
} |
375 |
|
Trace("strings-ff-debug") |
376 |
|
<< "Explanation for " << lcc << " is "; |
377 |
|
for (size_t j = 0; j < lexp2.size(); j++) |
378 |
|
{ |
379 |
|
Trace("strings-ff-debug") << lexp2[j] << std::endl; |
380 |
|
} |
381 |
|
} |
382 |
14202 |
std::vector<Node> lexpc; |
383 |
7101 |
lexpc.insert(lexpc.end(), lexp.begin(), lexp.end()); |
384 |
7101 |
lexpc.insert(lexpc.end(), lexp2.begin(), lexp2.end()); |
385 |
7101 |
d_im.addToExplanation(lcurr, lcc, lexpc); |
386 |
7101 |
lant = utils::mkAnd(lexpc); |
387 |
7101 |
conc = ac.eqNode(bc); |
388 |
7101 |
infType = InferenceId::STRINGS_F_UNIFY; |
389 |
7101 |
break; |
390 |
|
} |
391 |
|
} |
392 |
|
} |
393 |
|
} |
394 |
|
} |
395 |
|
} |
396 |
293841 |
if (!conc.isNull()) |
397 |
|
{ |
398 |
25268 |
Trace("strings-ff-debug") << "Found inference (" << infType |
399 |
12634 |
<< "): " << conc << " based on equality " << a |
400 |
12634 |
<< " == " << b << ", " << isRev << std::endl; |
401 |
|
// explain why prefixes up to now were the same |
402 |
18914 |
for (size_t j = 0; j < count; j++) |
403 |
|
{ |
404 |
12560 |
Trace("strings-ff-debug") << "Add at " << d_flat_form_index[a][j] << " " |
405 |
6280 |
<< d_flat_form_index[b][j] << std::endl; |
406 |
18840 |
d_im.addToExplanation( |
407 |
12560 |
a[d_flat_form_index[a][j]], b[d_flat_form_index[b][j]], exp); |
408 |
|
} |
409 |
|
// explain why other components up to now are empty |
410 |
37902 |
for (unsigned t = 0; t < 2; t++) |
411 |
|
{ |
412 |
50536 |
Node c = t == 0 ? a : b; |
413 |
|
ssize_t jj; |
414 |
25268 |
if (infType == InferenceId::STRINGS_F_ENDPOINT_EQ |
415 |
14692 |
|| (t == 1 && infType == InferenceId::STRINGS_F_ENDPOINT_EMP)) |
416 |
|
{ |
417 |
|
// explain all the empty components for F_EndpointEq, all for |
418 |
|
// the short end for F_EndpointEmp |
419 |
10576 |
jj = isRev ? -1 : c.getNumChildren(); |
420 |
|
} |
421 |
|
else |
422 |
|
{ |
423 |
22038 |
jj = t == 0 ? d_flat_form_index[a][count] |
424 |
7346 |
: d_flat_form_index[b][count]; |
425 |
|
} |
426 |
25268 |
ssize_t startj = isRev ? jj + 1 : 0; |
427 |
25268 |
ssize_t endj = isRev ? c.getNumChildren() : jj; |
428 |
50536 |
Node emp = Word::mkEmptyWord(a.getType()); |
429 |
54478 |
for (ssize_t j = startj; j < endj; j++) |
430 |
|
{ |
431 |
29210 |
if (d_state.areEqual(c[j], emp)) |
432 |
|
{ |
433 |
6074 |
d_im.addToExplanation(c[j], emp, exp); |
434 |
|
} |
435 |
|
} |
436 |
|
} |
437 |
12634 |
d_im.addToExplanation(a, b, exp); |
438 |
12634 |
if (!lant.isNull()) |
439 |
|
{ |
440 |
|
// add the length explanation |
441 |
7101 |
exp.push_back(lant); |
442 |
|
} |
443 |
|
// Notice that F_EndpointEmp is not typically applied, since |
444 |
|
// strict prefix equality ( a.b = a ) where a,b non-empty |
445 |
|
// is conflicting by arithmetic len(a.b)=len(a)+len(b)!=len(a) |
446 |
|
// when len(b)!=0. Although if we do not infer this conflict eagerly, |
447 |
|
// it may be applied (see #3272). |
448 |
12634 |
d_im.sendInference(exp, conc, infType, isRev); |
449 |
12634 |
if (d_state.isInConflict()) |
450 |
|
{ |
451 |
245 |
return; |
452 |
|
} |
453 |
12389 |
break; |
454 |
|
} |
455 |
281207 |
count++; |
456 |
281207 |
} while (inelig.size() < eqc.size()); |
457 |
|
} |
458 |
|
|
459 |
1579020 |
Node CoreSolver::checkCycles( Node eqc, std::vector< Node >& curr, std::vector< Node >& exp ){ |
460 |
1579020 |
if( std::find( curr.begin(), curr.end(), eqc )!=curr.end() ){ |
461 |
|
// a loop |
462 |
|
return eqc; |
463 |
1579020 |
}else if( std::find( d_strings_eqc.begin(), d_strings_eqc.end(), eqc )==d_strings_eqc.end() ){ |
464 |
577767 |
curr.push_back( eqc ); |
465 |
1155530 |
Node emp = Word::mkEmptyWord(eqc.getType()); |
466 |
|
//look at all terms in this equivalence class |
467 |
577767 |
eq::EqualityEngine* ee = d_state.getEqualityEngine(); |
468 |
577767 |
eq::EqClassIterator eqc_i = eq::EqClassIterator( eqc, ee ); |
469 |
6171553 |
while( !eqc_i.isFinished() ) { |
470 |
5593790 |
Node n = (*eqc_i); |
471 |
2796897 |
if( !d_bsolver.isCongruent(n) ){ |
472 |
1367070 |
if( n.getKind() == kind::STRING_CONCAT ){ |
473 |
419181 |
Trace("strings-cycle") << eqc << " check term : " << n << " in " << eqc << std::endl; |
474 |
419181 |
if (eqc != emp) |
475 |
|
{ |
476 |
404550 |
d_eqc[eqc].push_back( n ); |
477 |
|
} |
478 |
1457163 |
for( unsigned i=0; i<n.getNumChildren(); i++ ){ |
479 |
2075968 |
Node nr = d_state.getRepresentative(n[i]); |
480 |
1037986 |
if (eqc == emp) |
481 |
|
{ |
482 |
|
//for empty eqc, ensure all components are empty |
483 |
36733 |
if (nr != emp) |
484 |
|
{ |
485 |
8 |
std::vector<Node> exps; |
486 |
4 |
exps.push_back(n.eqNode(emp)); |
487 |
8 |
d_im.sendInference( |
488 |
8 |
exps, n[i].eqNode(emp), InferenceId::STRINGS_I_CYCLE_E); |
489 |
4 |
return Node::null(); |
490 |
|
} |
491 |
|
}else{ |
492 |
1001253 |
if (nr != emp) |
493 |
|
{ |
494 |
954009 |
d_flat_form[n].push_back( nr ); |
495 |
954009 |
d_flat_form_index[n].push_back( i ); |
496 |
|
} |
497 |
|
//for non-empty eqc, recurse and see if we find a loop |
498 |
2002506 |
Node ncy = checkCycles( nr, curr, exp ); |
499 |
1001253 |
if( !ncy.isNull() ){ |
500 |
|
Trace("strings-cycle") << eqc << " cycle: " << ncy << " at " << n << "[" << i << "] : " << n[i] << std::endl; |
501 |
|
d_im.addToExplanation(n, eqc, exp); |
502 |
|
d_im.addToExplanation(nr, n[i], exp); |
503 |
|
if( ncy==eqc ){ |
504 |
|
//can infer all other components must be empty |
505 |
|
for( unsigned j=0; j<n.getNumChildren(); j++ ){ |
506 |
|
//take first non-empty |
507 |
|
if (j != i && !d_state.areEqual(n[j], emp)) |
508 |
|
{ |
509 |
|
d_im.sendInference( |
510 |
|
exp, n[j].eqNode(emp), InferenceId::STRINGS_I_CYCLE); |
511 |
|
return Node::null(); |
512 |
|
} |
513 |
|
} |
514 |
|
Trace("strings-error") << "Looping term should be congruent : " << n << " " << eqc << " " << ncy << std::endl; |
515 |
|
//should find a non-empty component, otherwise would have been singular congruent (I_Norm_S) |
516 |
|
Assert(false); |
517 |
|
}else{ |
518 |
|
return ncy; |
519 |
|
} |
520 |
|
}else{ |
521 |
1001253 |
if (d_im.hasProcessed()) |
522 |
|
{ |
523 |
|
return Node::null(); |
524 |
|
} |
525 |
|
} |
526 |
|
} |
527 |
|
} |
528 |
|
} |
529 |
|
} |
530 |
2796893 |
++eqc_i; |
531 |
|
} |
532 |
577763 |
curr.pop_back(); |
533 |
577763 |
Trace("strings-eqc") << "* add string eqc: " << eqc << std::endl; |
534 |
|
//now we can add it to the list of equivalence classes |
535 |
577763 |
d_strings_eqc.push_back( eqc ); |
536 |
|
}else{ |
537 |
|
//already processed |
538 |
|
} |
539 |
1579016 |
return Node::null(); |
540 |
|
} |
541 |
|
|
542 |
20896 |
void CoreSolver::checkNormalFormsEq() |
543 |
|
{ |
544 |
|
// calculate normal forms for each equivalence class, possibly adding |
545 |
|
// splitting lemmas |
546 |
20896 |
d_normal_form.clear(); |
547 |
32795 |
std::map<Node, Node> nf_to_eqc; |
548 |
32795 |
std::map<Node, Node> eqc_to_nf; |
549 |
32795 |
std::map<Node, Node> eqc_to_exp; |
550 |
265680 |
for (const Node& eqc : d_strings_eqc) |
551 |
|
{ |
552 |
498565 |
TypeNode stype = eqc.getType(); |
553 |
507562 |
Trace("strings-process-debug") << "- Verify normal forms are the same for " |
554 |
253781 |
<< eqc << std::endl; |
555 |
253781 |
normalizeEquivalenceClass(eqc, stype); |
556 |
253781 |
Trace("strings-debug") << "Finished normalizing eqc..." << std::endl; |
557 |
253781 |
if (d_im.hasProcessed()) |
558 |
|
{ |
559 |
8229 |
return; |
560 |
|
} |
561 |
245552 |
NormalForm& nfe = getNormalForm(eqc); |
562 |
490336 |
Node nf_term = utils::mkNConcat(nfe.d_nf, stype); |
563 |
245552 |
std::map<Node, Node>::iterator itn = nf_to_eqc.find(nf_term); |
564 |
245552 |
if (itn != nf_to_eqc.end()) |
565 |
|
{ |
566 |
768 |
NormalForm& nfe_eq = getNormalForm(itn->second); |
567 |
|
// two equivalence classes have same normal form, merge |
568 |
768 |
std::vector<Node> nf_exp(nfe.d_exp.begin(), nfe.d_exp.end()); |
569 |
768 |
Node eexp = eqc_to_exp[itn->second]; |
570 |
768 |
if (eexp != d_true) |
571 |
|
{ |
572 |
692 |
nf_exp.push_back(eexp); |
573 |
|
} |
574 |
768 |
Node eq = nfe.d_base.eqNode(nfe_eq.d_base); |
575 |
768 |
d_im.sendInference(nf_exp, eq, InferenceId::STRINGS_NORMAL_FORM); |
576 |
768 |
if (d_im.hasProcessed()) |
577 |
|
{ |
578 |
768 |
return; |
579 |
|
} |
580 |
|
} |
581 |
|
else |
582 |
|
{ |
583 |
244784 |
nf_to_eqc[nf_term] = eqc; |
584 |
244784 |
eqc_to_nf[eqc] = nf_term; |
585 |
244784 |
eqc_to_exp[eqc] = utils::mkAnd(nfe.d_exp); |
586 |
|
} |
587 |
489568 |
Trace("strings-process-debug") |
588 |
244784 |
<< "Done verifying normal forms are the same for " << eqc << std::endl; |
589 |
|
} |
590 |
11899 |
if (Trace.isOn("strings-nf")) |
591 |
|
{ |
592 |
|
Trace("strings-nf") << "**** Normal forms are : " << std::endl; |
593 |
|
for (std::map<Node, Node>::iterator it = eqc_to_exp.begin(); |
594 |
|
it != eqc_to_exp.end(); |
595 |
|
++it) |
596 |
|
{ |
597 |
|
NormalForm& nf = getNormalForm(it->first); |
598 |
|
Trace("strings-nf") << " N[" << it->first << "] (base " << nf.d_base |
599 |
|
<< ") = " << eqc_to_nf[it->first] << std::endl; |
600 |
|
Trace("strings-nf") << " exp: " << it->second << std::endl; |
601 |
|
} |
602 |
|
Trace("strings-nf") << std::endl; |
603 |
|
} |
604 |
|
} |
605 |
|
|
606 |
|
//compute d_normal_forms_(base,exp,exp_depend)[eqc] |
607 |
253781 |
void CoreSolver::normalizeEquivalenceClass(Node eqc, TypeNode stype) |
608 |
|
{ |
609 |
253781 |
Trace("strings-process-debug") << "Process equivalence class " << eqc << std::endl; |
610 |
499333 |
Node emp = Word::mkEmptyWord(stype); |
611 |
253781 |
if (d_state.areEqual(eqc, emp)) |
612 |
|
{ |
613 |
|
#ifdef CVC5_ASSERTIONS |
614 |
14713 |
for( unsigned j=0; j<d_eqc[eqc].size(); j++ ){ |
615 |
|
Node n = d_eqc[eqc][j]; |
616 |
|
for( unsigned i=0; i<n.getNumChildren(); i++ ){ |
617 |
|
Assert(d_state.areEqual(n[i], emp)); |
618 |
|
} |
619 |
|
} |
620 |
|
#endif |
621 |
|
//do nothing |
622 |
14713 |
Trace("strings-process-debug") << "Return process equivalence class " << eqc << " : empty." << std::endl; |
623 |
14713 |
d_normal_form[eqc].init(emp); |
624 |
|
} |
625 |
|
else |
626 |
|
{ |
627 |
|
// should not have computed the normal form of this equivalence class yet |
628 |
239068 |
Assert(d_normal_form.find(eqc) == d_normal_form.end()); |
629 |
|
// Normal forms for the relevant terms in the equivalence class of eqc |
630 |
469907 |
std::vector<NormalForm> normal_forms; |
631 |
|
// map each term to its index in the above vector |
632 |
469907 |
std::map<Node, unsigned> term_to_nf_index; |
633 |
|
// get the normal forms |
634 |
239068 |
getNormalForms(eqc, normal_forms, term_to_nf_index, stype); |
635 |
239068 |
if (d_im.hasProcessed()) |
636 |
|
{ |
637 |
|
return; |
638 |
|
} |
639 |
|
// process the normal forms |
640 |
239068 |
processNEqc(eqc, normal_forms, stype); |
641 |
239068 |
if (d_im.hasProcessed()) |
642 |
|
{ |
643 |
8229 |
return; |
644 |
|
} |
645 |
|
|
646 |
|
//construct the normal form |
647 |
230839 |
Assert(!normal_forms.empty()); |
648 |
230839 |
unsigned nf_index = 0; |
649 |
230839 |
std::map<Node, unsigned>::iterator it = term_to_nf_index.find(eqc); |
650 |
|
// we prefer taking the normal form whose base is the equivalence |
651 |
|
// class representative, since this leads to shorter explanations in |
652 |
|
// some cases. |
653 |
230839 |
if (it != term_to_nf_index.end()) |
654 |
|
{ |
655 |
147063 |
nf_index = it->second; |
656 |
|
} |
657 |
230839 |
d_normal_form[eqc] = normal_forms[nf_index]; |
658 |
461678 |
Trace("strings-process-debug") |
659 |
230839 |
<< "Return process equivalence class " << eqc |
660 |
230839 |
<< " : returned = " << d_normal_form[eqc].d_nf << std::endl; |
661 |
|
} |
662 |
|
} |
663 |
|
|
664 |
801851 |
NormalForm& CoreSolver::getNormalForm(Node n) |
665 |
|
{ |
666 |
801851 |
std::map<Node, NormalForm>::iterator itn = d_normal_form.find(n); |
667 |
801851 |
if (itn == d_normal_form.end()) |
668 |
|
{ |
669 |
|
Trace("strings-warn") << "WARNING: returning empty normal form for " << n |
670 |
|
<< std::endl; |
671 |
|
// Shouln't ask for normal forms of strings that weren't computed. This |
672 |
|
// likely means that n is not a representative or not a term in the current |
673 |
|
// context. We simply return a default normal form here in this case. |
674 |
|
Assert(false); |
675 |
|
return d_normal_form[n]; |
676 |
|
} |
677 |
801851 |
return itn->second; |
678 |
|
} |
679 |
|
|
680 |
53808 |
Node CoreSolver::getNormalString(Node x, std::vector<Node>& nf_exp) |
681 |
|
{ |
682 |
53808 |
if (!x.isConst()) |
683 |
|
{ |
684 |
42861 |
Node xr = d_state.getRepresentative(x); |
685 |
42861 |
TypeNode stype = xr.getType(); |
686 |
42861 |
std::map<Node, NormalForm>::iterator it = d_normal_form.find(xr); |
687 |
42861 |
if (it != d_normal_form.end()) |
688 |
|
{ |
689 |
42861 |
NormalForm& nf = it->second; |
690 |
85722 |
Node ret = utils::mkNConcat(nf.d_nf, stype); |
691 |
42861 |
nf_exp.insert(nf_exp.end(), nf.d_exp.begin(), nf.d_exp.end()); |
692 |
42861 |
d_im.addToExplanation(x, nf.d_base, nf_exp); |
693 |
85722 |
Trace("strings-debug") |
694 |
42861 |
<< "Term: " << x << " has a normal form " << ret << std::endl; |
695 |
42861 |
return ret; |
696 |
|
} |
697 |
|
// if x does not have a normal form, then it should not occur in the |
698 |
|
// equality engine and hence should be its own representative. |
699 |
|
Assert(xr == x); |
700 |
|
if (x.getKind() == kind::STRING_CONCAT) |
701 |
|
{ |
702 |
|
std::vector<Node> vec_nodes; |
703 |
|
for (unsigned i = 0; i < x.getNumChildren(); i++) |
704 |
|
{ |
705 |
|
Node nc = getNormalString(x[i], nf_exp); |
706 |
|
vec_nodes.push_back(nc); |
707 |
|
} |
708 |
|
return utils::mkNConcat(vec_nodes, stype); |
709 |
|
} |
710 |
|
} |
711 |
10947 |
return x; |
712 |
|
} |
713 |
|
|
714 |
11445 |
Node CoreSolver::getConclusion(Node x, |
715 |
|
Node y, |
716 |
|
PfRule rule, |
717 |
|
bool isRev, |
718 |
|
SkolemCache* skc, |
719 |
|
std::vector<Node>& newSkolems) |
720 |
|
{ |
721 |
22890 |
Trace("strings-csolver") << "CoreSolver::getConclusion: " << x << " " << y |
722 |
11445 |
<< " " << rule << " " << isRev << std::endl; |
723 |
11445 |
NodeManager* nm = NodeManager::currentNM(); |
724 |
11445 |
Node conc; |
725 |
11445 |
if (rule == PfRule::CONCAT_SPLIT || rule == PfRule::CONCAT_LPROP) |
726 |
|
{ |
727 |
9362 |
Node sk1; |
728 |
9362 |
Node sk2; |
729 |
4681 |
if (options::stringUnifiedVSpt()) |
730 |
|
{ |
731 |
|
// must compare so that we are agnostic to order of x/y |
732 |
9362 |
Node ux = x < y ? x : y; |
733 |
9362 |
Node uy = x < y ? y : x; |
734 |
|
Node sk = skc->mkSkolemCached(ux, |
735 |
|
uy, |
736 |
|
isRev ? SkolemCache::SK_ID_V_UNIFIED_SPT_REV |
737 |
|
: SkolemCache::SK_ID_V_UNIFIED_SPT, |
738 |
9362 |
"v_spt"); |
739 |
4681 |
newSkolems.push_back(sk); |
740 |
4681 |
sk1 = sk; |
741 |
4681 |
sk2 = sk; |
742 |
|
} |
743 |
|
else |
744 |
|
{ |
745 |
|
sk1 = skc->mkSkolemCached( |
746 |
|
x, |
747 |
|
y, |
748 |
|
isRev ? SkolemCache::SK_ID_V_SPT_REV : SkolemCache::SK_ID_V_SPT, |
749 |
|
"v_spt1"); |
750 |
|
sk2 = skc->mkSkolemCached( |
751 |
|
y, |
752 |
|
x, |
753 |
|
isRev ? SkolemCache::SK_ID_V_SPT_REV : SkolemCache::SK_ID_V_SPT, |
754 |
|
"v_spt2"); |
755 |
|
newSkolems.push_back(sk1); |
756 |
|
newSkolems.push_back(sk2); |
757 |
|
} |
758 |
9362 |
Node eq1 = x.eqNode(isRev ? nm->mkNode(STRING_CONCAT, sk1, y) |
759 |
9362 |
: nm->mkNode(STRING_CONCAT, y, sk1)); |
760 |
|
|
761 |
4681 |
if (rule == PfRule::CONCAT_LPROP) |
762 |
|
{ |
763 |
1855 |
conc = eq1; |
764 |
|
} |
765 |
|
else |
766 |
|
{ |
767 |
5652 |
Node eq2 = y.eqNode(isRev ? nm->mkNode(STRING_CONCAT, sk2, x) |
768 |
5652 |
: nm->mkNode(STRING_CONCAT, x, sk2)); |
769 |
|
// make agnostic to x/y |
770 |
2826 |
conc = x < y ? nm->mkNode(OR, eq1, eq2) : nm->mkNode(OR, eq2, eq1); |
771 |
|
} |
772 |
4681 |
if (options::stringUnifiedVSpt()) |
773 |
|
{ |
774 |
|
// we can assume its length is greater than zero |
775 |
9362 |
Node emp = Word::mkEmptyWord(sk1.getType()); |
776 |
14043 |
conc = nm->mkNode( |
777 |
|
AND, |
778 |
|
conc, |
779 |
9362 |
sk1.eqNode(emp).negate(), |
780 |
18724 |
nm->mkNode( |
781 |
18724 |
GT, nm->mkNode(STRING_LENGTH, sk1), nm->mkConst(Rational(0)))); |
782 |
4681 |
} |
783 |
|
} |
784 |
6764 |
else if (rule == PfRule::CONCAT_CSPLIT) |
785 |
|
{ |
786 |
5777 |
Assert(y.isConst()); |
787 |
5777 |
size_t yLen = Word::getLength(y); |
788 |
|
Node firstChar = |
789 |
11554 |
yLen == 1 ? y : (isRev ? Word::suffix(y, 1) : Word::prefix(y, 1)); |
790 |
|
Node sk = skc->mkSkolemCached( |
791 |
|
x, |
792 |
|
isRev ? SkolemCache::SK_ID_VC_SPT_REV : SkolemCache::SK_ID_VC_SPT, |
793 |
11554 |
"c_spt"); |
794 |
5777 |
newSkolems.push_back(sk); |
795 |
5777 |
conc = x.eqNode(isRev ? nm->mkNode(STRING_CONCAT, sk, firstChar) |
796 |
|
: nm->mkNode(STRING_CONCAT, firstChar, sk)); |
797 |
|
} |
798 |
987 |
else if (rule == PfRule::CONCAT_CPROP) |
799 |
|
{ |
800 |
|
// expect (str.++ z d) and c |
801 |
987 |
Assert(x.getKind() == STRING_CONCAT && x.getNumChildren() == 2); |
802 |
1974 |
Node z = x[isRev ? 1 : 0]; |
803 |
1974 |
Node d = x[isRev ? 0 : 1]; |
804 |
987 |
Assert(d.isConst()); |
805 |
1974 |
Node c = y; |
806 |
987 |
Assert(c.isConst()); |
807 |
987 |
size_t cLen = Word::getLength(c); |
808 |
987 |
size_t p = getSufficientNonEmptyOverlap(c, d, isRev); |
809 |
|
Node preC = |
810 |
1974 |
p == cLen ? c : (isRev ? Word::suffix(c, p) : Word::prefix(c, p)); |
811 |
|
Node sk = skc->mkSkolemCached( |
812 |
|
z, |
813 |
|
preC, |
814 |
|
isRev ? SkolemCache::SK_ID_C_SPT_REV : SkolemCache::SK_ID_C_SPT, |
815 |
1974 |
"c_spt"); |
816 |
987 |
newSkolems.push_back(sk); |
817 |
987 |
conc = z.eqNode(isRev ? nm->mkNode(STRING_CONCAT, sk, preC) |
818 |
|
: nm->mkNode(STRING_CONCAT, preC, sk)); |
819 |
|
} |
820 |
|
|
821 |
11445 |
return conc; |
822 |
|
} |
823 |
|
|
824 |
4014 |
size_t CoreSolver::getSufficientNonEmptyOverlap(Node c, Node d, bool isRev) |
825 |
|
{ |
826 |
4014 |
Assert(c.isConst() && c.getType().isStringLike()); |
827 |
4014 |
Assert(d.isConst() && d.getType().isStringLike()); |
828 |
|
size_t p; |
829 |
|
size_t p2; |
830 |
4014 |
size_t cLen = Word::getLength(c); |
831 |
4014 |
if (isRev) |
832 |
|
{ |
833 |
|
// Since non-empty, we start with character 1 |
834 |
4370 |
Node c1 = Word::prefix(c, cLen - 1); |
835 |
2185 |
p = cLen - Word::roverlap(c1, d); |
836 |
2185 |
p2 = Word::rfind(c1, d); |
837 |
|
} |
838 |
|
else |
839 |
|
{ |
840 |
3658 |
Node c1 = Word::substr(c, 1); |
841 |
1829 |
p = cLen - Word::overlap(c1, d); |
842 |
1829 |
p2 = Word::find(c1, d); |
843 |
|
} |
844 |
4014 |
return p2 == std::string::npos ? p : (p > p2 + 1 ? p2 + 1 : p); |
845 |
|
} |
846 |
|
|
847 |
42 |
Node CoreSolver::getDecomposeConclusion(Node x, |
848 |
|
Node l, |
849 |
|
bool isRev, |
850 |
|
SkolemCache* skc, |
851 |
|
std::vector<Node>& newSkolems) |
852 |
|
{ |
853 |
42 |
Assert(l.getType().isInteger()); |
854 |
42 |
NodeManager* nm = NodeManager::currentNM(); |
855 |
84 |
Node n = isRev ? nm->mkNode(MINUS, nm->mkNode(STRING_LENGTH, x), l) : l; |
856 |
84 |
Node sk1 = skc->mkSkolemCached(x, n, SkolemCache::SK_PREFIX, "dc_spt1"); |
857 |
42 |
newSkolems.push_back(sk1); |
858 |
84 |
Node sk2 = skc->mkSkolemCached(x, n, SkolemCache::SK_SUFFIX_REM, "dc_spt2"); |
859 |
42 |
newSkolems.push_back(sk2); |
860 |
84 |
Node conc = x.eqNode(nm->mkNode(STRING_CONCAT, sk1, sk2)); |
861 |
|
// add the length constraint to the conclusion |
862 |
84 |
Node lc = nm->mkNode(STRING_LENGTH, isRev ? sk2 : sk1).eqNode(l); |
863 |
84 |
return nm->mkNode(AND, conc, lc); |
864 |
|
} |
865 |
|
|
866 |
239068 |
void CoreSolver::getNormalForms(Node eqc, |
867 |
|
std::vector<NormalForm>& normal_forms, |
868 |
|
std::map<Node, unsigned>& term_to_nf_index, |
869 |
|
TypeNode stype) |
870 |
|
{ |
871 |
478136 |
Node emp = Word::mkEmptyWord(stype); |
872 |
|
//constant for equivalence class |
873 |
478136 |
Node eqc_non_c = eqc; |
874 |
239068 |
Trace("strings-process-debug") << "Get normal forms " << eqc << std::endl; |
875 |
239068 |
eq::EqualityEngine* ee = d_state.getEqualityEngine(); |
876 |
239068 |
eq::EqClassIterator eqc_i = eq::EqClassIterator( eqc, ee ); |
877 |
2327064 |
while( !eqc_i.isFinished() ){ |
878 |
2087996 |
Node n = (*eqc_i); |
879 |
1043998 |
if( !d_bsolver.isCongruent(n) ){ |
880 |
554260 |
Kind nk = n.getKind(); |
881 |
554260 |
bool isCLike = utils::isConstantLike(n); |
882 |
554260 |
if (isCLike || nk == STRING_CONCAT) |
883 |
|
{ |
884 |
235403 |
Trace("strings-process-debug") << "Get Normal Form : Process term " << n << " in eqc " << eqc << std::endl; |
885 |
470806 |
NormalForm nf_curr; |
886 |
235403 |
if (isCLike) |
887 |
|
{ |
888 |
64130 |
nf_curr.init(n); |
889 |
|
} |
890 |
171273 |
else if (nk == STRING_CONCAT) |
891 |
|
{ |
892 |
|
// set the base to n, we construct the other portions of nf_curr in |
893 |
|
// the following. |
894 |
171273 |
nf_curr.d_base = n; |
895 |
584714 |
for( unsigned i=0; i<n.getNumChildren(); i++ ) { |
896 |
826882 |
Node nr = ee->getRepresentative( n[i] ); |
897 |
|
// get the normal form for the component |
898 |
413441 |
NormalForm& nfr = getNormalForm(nr); |
899 |
413441 |
std::vector<Node>& nfrv = nfr.d_nf; |
900 |
826882 |
Trace("strings-process-debug") |
901 |
826882 |
<< "Normalizing subterm " << n[i] << " = " << nr |
902 |
413441 |
<< ", which is " << nfrv << std::endl; |
903 |
413441 |
unsigned orig_size = nf_curr.d_nf.size(); |
904 |
413441 |
unsigned add_size = nfrv.size(); |
905 |
|
//if not the empty string, add to current normal form |
906 |
413441 |
if (!nfrv.empty()) |
907 |
|
{ |
908 |
|
// if in a build with assertions, we run the following block, |
909 |
|
// which checks that normal forms do not have concat terms. |
910 |
398730 |
if (Configuration::isAssertionBuild()) |
911 |
|
{ |
912 |
1114139 |
for (const Node& nn : nfrv) |
913 |
|
{ |
914 |
715409 |
if (Trace.isOn("strings-error")) |
915 |
|
{ |
916 |
|
if (nn.getKind() == STRING_CONCAT) |
917 |
|
{ |
918 |
|
Trace("strings-error") |
919 |
|
<< "Strings::Error: From eqc = " << eqc << ", " << n |
920 |
|
<< " index " << i << ", bad normal form : "; |
921 |
|
for (unsigned rr = 0; rr < nfrv.size(); rr++) |
922 |
|
{ |
923 |
|
Trace("strings-error") << nfrv[rr] << " "; |
924 |
|
} |
925 |
|
Trace("strings-error") << std::endl; |
926 |
|
} |
927 |
|
} |
928 |
715409 |
Assert(nn.getKind() != kind::STRING_CONCAT); |
929 |
|
} |
930 |
|
} |
931 |
398730 |
nf_curr.d_nf.insert(nf_curr.d_nf.end(), nfrv.begin(), nfrv.end()); |
932 |
|
} |
933 |
|
// Track explanation for the normal form. This is in two parts. |
934 |
|
// First, we must carry the explanation of the normal form computed |
935 |
|
// for the representative nr. |
936 |
673761 |
for (const Node& exp : nfr.d_exp) |
937 |
|
{ |
938 |
|
// The explanation is only relevant for the subsegment it was |
939 |
|
// previously relevant for, shifted now based on its relative |
940 |
|
// placement in the normal form of n. |
941 |
520640 |
nf_curr.addToExplanation( |
942 |
|
exp, |
943 |
520640 |
orig_size + nfr.d_expDep[exp][false], |
944 |
520640 |
orig_size + (add_size - nfr.d_expDep[exp][true])); |
945 |
|
} |
946 |
|
// Second, must explain that the component n[i] is equal to the |
947 |
|
// base of the normal form for nr. |
948 |
826882 |
Node base = nfr.d_base; |
949 |
413441 |
if (base != n[i]) |
950 |
|
{ |
951 |
450866 |
Node eq = n[i].eqNode(base); |
952 |
|
// The equality is relevant for the entire current segment |
953 |
225433 |
nf_curr.addToExplanation(eq, orig_size, orig_size + add_size); |
954 |
|
} |
955 |
|
} |
956 |
|
// Now that we are finished with the loop, we convert forward indices |
957 |
|
// to reverse indices in the explanation dependency information |
958 |
171273 |
int total_size = nf_curr.d_nf.size(); |
959 |
482696 |
for (std::pair<const Node, std::map<bool, unsigned> >& ed : |
960 |
171273 |
nf_curr.d_expDep) |
961 |
|
{ |
962 |
482696 |
ed.second[true] = total_size - ed.second[true]; |
963 |
482696 |
Assert(ed.second[true] >= 0); |
964 |
|
} |
965 |
|
} |
966 |
|
//if not equal to self |
967 |
235403 |
std::vector<Node>& currv = nf_curr.d_nf; |
968 |
470806 |
if (currv.size() > 1 |
969 |
470806 |
|| (currv.size() == 1 && utils::isConstantLike(currv[0]))) |
970 |
|
{ |
971 |
|
// if in a build with assertions, check that normal form is acyclic |
972 |
235403 |
if (Configuration::isAssertionBuild()) |
973 |
|
{ |
974 |
235403 |
if (currv.size() > 1) |
975 |
|
{ |
976 |
886682 |
for (unsigned i = 0; i < currv.size(); i++) |
977 |
|
{ |
978 |
715409 |
if (Trace.isOn("strings-error")) |
979 |
|
{ |
980 |
|
Trace("strings-error") << "Cycle for normal form "; |
981 |
|
utils::printConcatTrace(currv, "strings-error"); |
982 |
|
Trace("strings-error") << "..." << currv[i] << std::endl; |
983 |
|
} |
984 |
715409 |
Assert(!d_state.areEqual(currv[i], n)); |
985 |
|
} |
986 |
|
} |
987 |
|
} |
988 |
235403 |
term_to_nf_index[n] = normal_forms.size(); |
989 |
235403 |
normal_forms.push_back(nf_curr); |
990 |
|
}else{ |
991 |
|
//this was redundant: combination of self + empty string(s) |
992 |
|
Node nn = currv.size() == 0 ? emp : currv[0]; |
993 |
|
Assert(d_state.areEqual(nn, eqc)); |
994 |
235403 |
} |
995 |
|
}else{ |
996 |
318857 |
eqc_non_c = n; |
997 |
|
} |
998 |
|
} |
999 |
|
else |
1000 |
|
{ |
1001 |
979476 |
Trace("strings-process-debug") |
1002 |
489738 |
<< "Get Normal Form : term " << n << " in eqc " << eqc |
1003 |
489738 |
<< " is congruent" << std::endl; |
1004 |
|
} |
1005 |
1043998 |
++eqc_i; |
1006 |
|
} |
1007 |
|
|
1008 |
239068 |
if( normal_forms.empty() ) { |
1009 |
63741 |
Trace("strings-solve-debug2") << "construct the normal form" << std::endl; |
1010 |
|
// This case happens when there are no non-trivial normal forms for this |
1011 |
|
// equivalence class. For example, given assertions: |
1012 |
|
// { x = y ++ z, x = y, z = "" } |
1013 |
|
// The equivalence class of { x, y, y ++ z } is such that the normal form |
1014 |
|
// of all terms is a variable (either x or y) in the equivalence class |
1015 |
|
// itself. Thus, the normal form of this equivalence class can be assigned |
1016 |
|
// to one of these variables. |
1017 |
|
// We use a non-concatenation term among the terms in this equivalence |
1018 |
|
// class, which is stored in eqc_non_c. The reason is this does not require |
1019 |
|
// an explanation, whereas e.g. y ++ z would require the explanation z = "" |
1020 |
|
// to justify its normal form is y. |
1021 |
63741 |
Assert(eqc_non_c.getKind() != STRING_CONCAT); |
1022 |
127482 |
NormalForm nf_triv; |
1023 |
63741 |
nf_triv.init(eqc_non_c); |
1024 |
63741 |
normal_forms.push_back(nf_triv); |
1025 |
|
}else{ |
1026 |
175327 |
if(Trace.isOn("strings-solve")) { |
1027 |
|
Trace("strings-solve") << "--- Normal forms for equivalance class " << eqc << " : " << std::endl; |
1028 |
|
for (unsigned i = 0, size = normal_forms.size(); i < size; i++) |
1029 |
|
{ |
1030 |
|
NormalForm& nf = normal_forms[i]; |
1031 |
|
Trace("strings-solve") << "#" << i << " (from " << nf.d_base << ") : "; |
1032 |
|
for (unsigned j = 0, sizej = nf.d_nf.size(); j < sizej; j++) |
1033 |
|
{ |
1034 |
|
if(j>0) { |
1035 |
|
Trace("strings-solve") << ", "; |
1036 |
|
} |
1037 |
|
Trace("strings-solve") << nf.d_nf[j]; |
1038 |
|
} |
1039 |
|
Trace("strings-solve") << std::endl; |
1040 |
|
Trace("strings-solve") << " Explanation is : "; |
1041 |
|
if (nf.d_exp.size() == 0) |
1042 |
|
{ |
1043 |
|
Trace("strings-solve") << "NONE"; |
1044 |
|
} else { |
1045 |
|
for (unsigned j = 0, sizej = nf.d_exp.size(); j < sizej; j++) |
1046 |
|
{ |
1047 |
|
if(j>0) { |
1048 |
|
Trace("strings-solve") << " AND "; |
1049 |
|
} |
1050 |
|
Trace("strings-solve") << nf.d_exp[j]; |
1051 |
|
} |
1052 |
|
Trace("strings-solve") << std::endl; |
1053 |
|
Trace("strings-solve") << "WITH DEPENDENCIES : " << std::endl; |
1054 |
|
for (unsigned j = 0, sizej = nf.d_exp.size(); j < sizej; j++) |
1055 |
|
{ |
1056 |
|
Node exp = nf.d_exp[j]; |
1057 |
|
Trace("strings-solve") << " " << exp << " -> "; |
1058 |
|
Trace("strings-solve") << nf.d_expDep[exp][false] << ","; |
1059 |
|
Trace("strings-solve") << nf.d_expDep[exp][true] << std::endl; |
1060 |
|
} |
1061 |
|
} |
1062 |
|
Trace("strings-solve") << std::endl; |
1063 |
|
} |
1064 |
|
} else { |
1065 |
175327 |
Trace("strings-solve") << "--- Single normal form for equivalence class " << eqc << std::endl; |
1066 |
|
} |
1067 |
|
} |
1068 |
239068 |
} |
1069 |
|
|
1070 |
239068 |
void CoreSolver::processNEqc(Node eqc, |
1071 |
|
std::vector<NormalForm>& normal_forms, |
1072 |
|
TypeNode stype) |
1073 |
|
{ |
1074 |
239068 |
if (normal_forms.size() <= 1) |
1075 |
|
{ |
1076 |
445545 |
return; |
1077 |
|
} |
1078 |
|
// if equivalence class is constant, approximate as containment, infer |
1079 |
|
// conflicts |
1080 |
32591 |
Node c = d_bsolver.getConstantEqc(eqc); |
1081 |
|
// the possible inferences |
1082 |
32591 |
std::vector<CoreInferInfo> pinfer; |
1083 |
|
// compute normal forms that are effectively unique |
1084 |
32591 |
std::unordered_map<Node, size_t> nfCache; |
1085 |
32591 |
std::vector<size_t> nfIndices; |
1086 |
29537 |
bool hasConstIndex = false; |
1087 |
118752 |
for (size_t i = 0, nnforms = normal_forms.size(); i < nnforms; i++) |
1088 |
|
{ |
1089 |
89421 |
NormalForm& nfi = normal_forms[i]; |
1090 |
178636 |
Node ni = utils::mkNConcat(nfi.d_nf, stype); |
1091 |
89421 |
if (nfCache.find(ni) == nfCache.end()) |
1092 |
|
{ |
1093 |
|
// If the equivalence class is entailed to be constant, check |
1094 |
|
// if the normal form cannot be contained in that constant. |
1095 |
43217 |
if (!c.isNull()) |
1096 |
|
{ |
1097 |
|
int firstc, lastc; |
1098 |
13955 |
if (!StringsEntail::canConstantContainList(c, nfi.d_nf, firstc, lastc)) |
1099 |
|
{ |
1100 |
412 |
Node n = nfi.d_base; |
1101 |
412 |
std::vector<Node> exp(nfi.d_exp.begin(), nfi.d_exp.end()); |
1102 |
|
//conflict |
1103 |
206 |
Trace("strings-solve") << "Normal form for " << n << " cannot be contained in constant " << c << std::endl; |
1104 |
|
// conflict, explanation is: |
1105 |
|
// n = base ^ base = c ^ relevant porition of ( n = N[n] ) |
1106 |
|
// Notice although not implemented, this can be minimized based on |
1107 |
|
// firstc/lastc, normal_forms_exp_depend. |
1108 |
206 |
d_bsolver.explainConstantEqc(n, eqc, exp); |
1109 |
206 |
d_im.sendInference(exp, d_false, InferenceId::STRINGS_N_NCTN); |
1110 |
|
// conflict, finished |
1111 |
206 |
return; |
1112 |
|
} |
1113 |
|
} |
1114 |
|
|
1115 |
43011 |
nfCache[ni] = i; |
1116 |
43011 |
if (ni.isConst()) |
1117 |
|
{ |
1118 |
12637 |
hasConstIndex = true; |
1119 |
12637 |
nfIndices.insert(nfIndices.begin(), i); |
1120 |
|
} |
1121 |
|
else |
1122 |
|
{ |
1123 |
30374 |
nfIndices.push_back(i); |
1124 |
|
} |
1125 |
|
} |
1126 |
|
} |
1127 |
29331 |
size_t nnfs = nfIndices.size(); |
1128 |
58662 |
Trace("strings-solve") << "CoreSolver::processNEqc: " << nnfs << "/" |
1129 |
58662 |
<< normal_forms.size() << " normal forms are unique." |
1130 |
29331 |
<< std::endl; |
1131 |
|
|
1132 |
|
// loop over all pairs |
1133 |
33110 |
for (unsigned i = 0; i < nnfs - 1; i++) |
1134 |
|
{ |
1135 |
|
//unify each normalform[j] with normal_forms[i] |
1136 |
17692 |
for (unsigned j = i + 1; j < nnfs; j++) |
1137 |
|
{ |
1138 |
13379 |
NormalForm& nfi = normal_forms[nfIndices[i]]; |
1139 |
13379 |
NormalForm& nfj = normal_forms[nfIndices[j]]; |
1140 |
|
//ensure that normal_forms[i] and normal_forms[j] are the same modulo equality, add to pinfer if not |
1141 |
13379 |
Trace("strings-solve") << "Strings: Process normal form #" << i << " against #" << j << "..." << std::endl; |
1142 |
13379 |
if (isNormalFormPair(nfi.d_base, nfj.d_base)) |
1143 |
|
{ |
1144 |
|
Trace("strings-solve") << "Strings: Already cached." << std::endl; |
1145 |
|
continue; |
1146 |
|
} |
1147 |
|
// process the reverse direction first (check for easy conflicts and |
1148 |
|
// inferences) |
1149 |
13379 |
unsigned rindex = 0; |
1150 |
13379 |
nfi.reverse(); |
1151 |
13379 |
nfj.reverse(); |
1152 |
13379 |
processSimpleNEq(nfi, nfj, rindex, true, 0, pinfer, stype); |
1153 |
13379 |
nfi.reverse(); |
1154 |
13379 |
nfj.reverse(); |
1155 |
13379 |
if (d_im.hasProcessed()) |
1156 |
|
{ |
1157 |
8530 |
break; |
1158 |
|
} |
1159 |
|
// AJR: for less aggressive endpoint inference |
1160 |
|
// rindex = 0; |
1161 |
|
|
1162 |
9818 |
unsigned index = 0; |
1163 |
9818 |
processSimpleNEq(nfi, nfj, index, false, rindex, pinfer, stype); |
1164 |
9818 |
if (d_im.hasProcessed()) |
1165 |
|
{ |
1166 |
1408 |
break; |
1167 |
|
} |
1168 |
|
} |
1169 |
9282 |
if (hasConstIndex || d_im.hasProcessed()) |
1170 |
|
{ |
1171 |
5503 |
break; |
1172 |
|
} |
1173 |
|
} |
1174 |
29331 |
if (d_state.isInConflict()) |
1175 |
|
{ |
1176 |
|
// conflict, we are done |
1177 |
100 |
return; |
1178 |
|
} |
1179 |
|
// Go back and check for normal form equality conflicts. These take |
1180 |
|
// precedence over any facts and lemmas. |
1181 |
57727 |
for (const std::pair<const Node, size_t>& ni : nfCache) |
1182 |
|
{ |
1183 |
88462 |
for (const std::pair<const Node, size_t>& nj : nfCache) |
1184 |
|
{ |
1185 |
55097 |
if (ni.first >= nj.first) |
1186 |
|
{ |
1187 |
|
// avoid duplicate comparisons |
1188 |
44969 |
continue; |
1189 |
|
} |
1190 |
20256 |
Node eq = ni.first.eqNode(nj.first); |
1191 |
10128 |
eq = Rewriter::rewrite(eq); |
1192 |
10128 |
if (eq == d_false) |
1193 |
|
{ |
1194 |
|
std::vector<Node> exp; |
1195 |
|
NormalForm& nfi = normal_forms[ni.second]; |
1196 |
|
NormalForm& nfj = normal_forms[nj.second]; |
1197 |
|
exp.insert(exp.end(), nfi.d_exp.begin(), nfi.d_exp.end()); |
1198 |
|
exp.insert(exp.end(), nfj.d_exp.begin(), nfj.d_exp.end()); |
1199 |
|
exp.push_back(nfi.d_base.eqNode(nfj.d_base)); |
1200 |
|
d_im.sendInference(exp, d_false, InferenceId::STRINGS_N_EQ_CONF); |
1201 |
|
return; |
1202 |
|
} |
1203 |
|
} |
1204 |
33365 |
if (d_im.hasProcessed()) |
1205 |
|
{ |
1206 |
4869 |
break; |
1207 |
|
} |
1208 |
|
} |
1209 |
29231 |
if (d_im.hasProcessed() || pinfer.empty()) |
1210 |
|
{ |
1211 |
|
// either already sent a lemma or fact, or there are no possible inferences |
1212 |
26177 |
return; |
1213 |
|
} |
1214 |
|
// now, determine which of the possible inferences we want to add |
1215 |
3054 |
unsigned use_index = 0; |
1216 |
3054 |
bool set_use_index = false; |
1217 |
6108 |
Trace("strings-solve") << "Possible inferences (" << pinfer.size() |
1218 |
3054 |
<< ") : " << std::endl; |
1219 |
3054 |
InferenceId min_id = InferenceId::UNKNOWN; |
1220 |
3054 |
unsigned max_index = 0; |
1221 |
15492 |
for (unsigned i = 0, psize = pinfer.size(); i < psize; i++) |
1222 |
|
{ |
1223 |
12438 |
CoreInferInfo& ipii = pinfer[i]; |
1224 |
12438 |
InferInfo& ii = ipii.d_infer; |
1225 |
24876 |
Trace("strings-solve") << "#" << i << ": From " << ipii.d_i << " / " |
1226 |
12438 |
<< ipii.d_j << " (rev=" << ipii.d_rev << ") : "; |
1227 |
12438 |
Trace("strings-solve") << ii.d_conc << " by " << ii.getId() << std::endl; |
1228 |
34260 |
if (!set_use_index || ii.getId() < min_id |
1229 |
20720 |
|| (ii.getId() == min_id && ipii.d_index > max_index)) |
1230 |
|
{ |
1231 |
4690 |
min_id = ii.getId(); |
1232 |
4690 |
max_index = ipii.d_index; |
1233 |
4690 |
use_index = i; |
1234 |
4690 |
set_use_index = true; |
1235 |
|
} |
1236 |
|
} |
1237 |
3054 |
Trace("strings-solve") << "...choose #" << use_index << std::endl; |
1238 |
3054 |
if (!processInferInfo(pinfer[use_index])) |
1239 |
|
{ |
1240 |
|
Unhandled() << "Failed to process infer info " << pinfer[use_index].d_infer |
1241 |
|
<< std::endl; |
1242 |
|
} |
1243 |
|
} |
1244 |
|
|
1245 |
23197 |
void CoreSolver::processSimpleNEq(NormalForm& nfi, |
1246 |
|
NormalForm& nfj, |
1247 |
|
unsigned& index, |
1248 |
|
bool isRev, |
1249 |
|
unsigned rproc, |
1250 |
|
std::vector<CoreInferInfo>& pinfer, |
1251 |
|
TypeNode stype) |
1252 |
|
{ |
1253 |
23197 |
NodeManager* nm = NodeManager::currentNM(); |
1254 |
46347 |
Node emp = Word::mkEmptyWord(stype); |
1255 |
|
|
1256 |
23197 |
const std::vector<Node>& nfiv = nfi.d_nf; |
1257 |
23197 |
const std::vector<Node>& nfjv = nfj.d_nf; |
1258 |
23197 |
Assert(rproc <= nfiv.size() && rproc <= nfjv.size()); |
1259 |
|
while (true) |
1260 |
|
{ |
1261 |
49757 |
bool lhsDone = (index == (nfiv.size() - rproc)); |
1262 |
49757 |
bool rhsDone = (index == (nfjv.size() - rproc)); |
1263 |
49757 |
if (lhsDone && rhsDone) |
1264 |
|
{ |
1265 |
|
// We are done with both normal forms |
1266 |
|
break; |
1267 |
|
} |
1268 |
49757 |
else if (lhsDone || rhsDone) |
1269 |
|
{ |
1270 |
|
// Only one side is done so the remainder of the other side must be empty |
1271 |
|
NormalForm& nfk = index == (nfiv.size() - rproc) ? nfj : nfi; |
1272 |
|
std::vector<Node>& nfkv = nfk.d_nf; |
1273 |
|
unsigned index_k = index; |
1274 |
|
std::vector<Node> curr_exp; |
1275 |
|
NormalForm::getExplanationForPrefixEq(nfi, nfj, -1, -1, curr_exp); |
1276 |
|
while (!d_state.isInConflict() && index_k < (nfkv.size() - rproc)) |
1277 |
|
{ |
1278 |
|
// can infer that this string must be empty |
1279 |
|
Node eq = nfkv[index_k].eqNode(emp); |
1280 |
|
Assert(!d_state.areEqual(emp, nfkv[index_k])); |
1281 |
|
d_im.sendInference(curr_exp, eq, InferenceId::STRINGS_N_ENDPOINT_EMP, isRev); |
1282 |
|
index_k++; |
1283 |
|
} |
1284 |
|
break; |
1285 |
|
} |
1286 |
|
|
1287 |
|
// We have inferred that the normal forms up to position `index` are |
1288 |
|
// equivalent. Below, we refer to the components at the current position of |
1289 |
|
// the normal form as `x` and `y`. |
1290 |
|
// |
1291 |
|
// E.g. x ++ ... = y ++ ... |
1292 |
49757 |
Node x = nfiv[index]; |
1293 |
49757 |
Node y = nfjv[index]; |
1294 |
99514 |
Trace("strings-solve-debug") |
1295 |
49757 |
<< "Process " << x << " ... " << y << std::endl; |
1296 |
|
|
1297 |
71092 |
if (x == y) |
1298 |
|
{ |
1299 |
|
// The normal forms have the same term at the current position. We just |
1300 |
|
// continue with the next index. By construction of the normal forms, we |
1301 |
|
// end up in this case if the two components are equal according to the |
1302 |
|
// equality engine (i.e. we cannot have different x and y terms that are |
1303 |
|
// equal in the equality engine). |
1304 |
|
// |
1305 |
|
// E.g. x ++ x' ++ ... = x ++ y' ++ ... ---> x' ++ ... = y' ++ ... |
1306 |
42670 |
Trace("strings-solve-debug") |
1307 |
21335 |
<< "Simple Case 1 : strings are equal" << std::endl; |
1308 |
21335 |
index++; |
1309 |
21335 |
continue; |
1310 |
|
} |
1311 |
28422 |
Assert(!d_state.areEqual(x, y)); |
1312 |
|
|
1313 |
28422 |
std::vector<Node> lenExp; |
1314 |
28422 |
Node xLenTerm = d_state.getLength(x, lenExp); |
1315 |
28422 |
Node yLenTerm = d_state.getLength(y, lenExp); |
1316 |
|
|
1317 |
28422 |
if (d_state.areEqual(xLenTerm, yLenTerm)) |
1318 |
|
{ |
1319 |
3214 |
std::vector<Node> ant; |
1320 |
3214 |
NormalForm::getExplanationForPrefixEq(nfi, nfj, index, index, ant); |
1321 |
3214 |
if (x.isConst() && y.isConst()) |
1322 |
|
{ |
1323 |
|
// if both are constant, it's just a constant conflict |
1324 |
47 |
d_im.sendInference(ant, d_false, InferenceId::STRINGS_N_CONST, isRev, true); |
1325 |
47 |
return; |
1326 |
|
} |
1327 |
|
// `x` and `y` have the same length. We infer that the two components |
1328 |
|
// have to be the same. |
1329 |
|
// |
1330 |
|
// E.g. x ++ ... = y ++ ... ^ len(x) = len(y) ---> x = y |
1331 |
6334 |
Trace("strings-solve-debug") |
1332 |
3167 |
<< "Simple Case 2 : string lengths are equal" << std::endl; |
1333 |
6334 |
Node eq = x.eqNode(y); |
1334 |
6334 |
Node leneq = xLenTerm.eqNode(yLenTerm); |
1335 |
3167 |
lenExp.push_back(leneq); |
1336 |
|
// set the explanation for length |
1337 |
6334 |
Node lant = utils::mkAnd(lenExp); |
1338 |
3167 |
ant.push_back(lant); |
1339 |
3167 |
d_im.sendInference(ant, eq, InferenceId::STRINGS_N_UNIFY, isRev); |
1340 |
3167 |
break; |
1341 |
|
} |
1342 |
64886 |
else if ((!x.isConst() && index == nfiv.size() - rproc - 1) |
1343 |
49798 |
|| (!y.isConst() && index == nfjv.size() - rproc - 1)) |
1344 |
|
{ |
1345 |
|
// We have reached the last component in one of the normal forms and it |
1346 |
|
// is not a constant. Thus, the last component must be equal to the |
1347 |
|
// remainder of the other normal form. |
1348 |
|
// |
1349 |
|
// E.g. x = y ++ y' ---> x = y ++ y' |
1350 |
3404 |
Trace("strings-solve-debug") |
1351 |
1702 |
<< "Simple Case 3 : at endpoint" << std::endl; |
1352 |
3404 |
Node eqn[2]; |
1353 |
5106 |
for (unsigned r = 0; r < 2; r++) |
1354 |
|
{ |
1355 |
3404 |
const NormalForm& nfk = r == 0 ? nfi : nfj; |
1356 |
3404 |
const std::vector<Node>& nfkv = nfk.d_nf; |
1357 |
6808 |
std::vector<Node> eqnc; |
1358 |
8840 |
for (unsigned i = index, size = (nfkv.size() - rproc); i < size; i++) |
1359 |
|
{ |
1360 |
5436 |
if (isRev) |
1361 |
|
{ |
1362 |
2642 |
eqnc.insert(eqnc.begin(), nfkv[i]); |
1363 |
|
} |
1364 |
|
else |
1365 |
|
{ |
1366 |
2794 |
eqnc.push_back(nfkv[i]); |
1367 |
|
} |
1368 |
|
} |
1369 |
3404 |
eqn[r] = utils::mkNConcat(eqnc, stype); |
1370 |
|
} |
1371 |
3404 |
Trace("strings-solve-debug") |
1372 |
1702 |
<< "Endpoint eq check: " << eqn[0] << " " << eqn[1] << std::endl; |
1373 |
1702 |
if (!d_state.areEqual(eqn[0], eqn[1])) |
1374 |
|
{ |
1375 |
3404 |
std::vector<Node> antec; |
1376 |
1702 |
NormalForm::getExplanationForPrefixEq(nfi, nfj, -1, -1, antec); |
1377 |
3404 |
d_im.sendInference(antec, |
1378 |
3404 |
eqn[0].eqNode(eqn[1]), |
1379 |
|
InferenceId::STRINGS_N_ENDPOINT_EQ, |
1380 |
|
isRev, |
1381 |
|
true); |
1382 |
|
} |
1383 |
|
else |
1384 |
|
{ |
1385 |
|
Assert(nfiv.size() == nfjv.size()); |
1386 |
|
index = nfiv.size() - rproc; |
1387 |
|
} |
1388 |
1702 |
break; |
1389 |
|
} |
1390 |
23506 |
else if (x.isConst() && y.isConst()) |
1391 |
|
{ |
1392 |
|
// Constants in both normal forms. |
1393 |
|
// |
1394 |
|
// E.g. "abc" ++ ... = "ab" ++ ... |
1395 |
5278 |
size_t lenX = Word::getLength(x); |
1396 |
5278 |
size_t lenY = Word::getLength(y); |
1397 |
10556 |
Trace("strings-solve-debug") |
1398 |
5278 |
<< "Simple Case 4 : Const Split : " << x << " vs " << y |
1399 |
5278 |
<< " at index " << index << ", isRev = " << isRev << std::endl; |
1400 |
5278 |
size_t minLen = std::min(lenX, lenY); |
1401 |
|
bool isSameFix = |
1402 |
5278 |
isRev ? Word::rstrncmp(x, y, minLen) : Word::strncmp(x, y, minLen); |
1403 |
5278 |
if (isSameFix) |
1404 |
|
{ |
1405 |
|
// The shorter constant is a prefix/suffix of the longer constant. We |
1406 |
|
// split the longer constant into the shared part and the remainder and |
1407 |
|
// continue from there. |
1408 |
|
// |
1409 |
|
// E.g. "abc" ++ x' ++ ... = "ab" ++ y' ++ ... ---> |
1410 |
|
// "c" ++ x' ++ ... = y' ++ ... |
1411 |
5225 |
bool xShorter = lenX < lenY; |
1412 |
5225 |
NormalForm& nfl = xShorter ? nfj : nfi; |
1413 |
10450 |
Node cl = xShorter ? y : x; |
1414 |
10450 |
Node ns = xShorter ? x : y; |
1415 |
|
|
1416 |
10450 |
Node remainderStr; |
1417 |
5225 |
if (isRev) |
1418 |
|
{ |
1419 |
4148 |
size_t newlen = std::max(lenX, lenY) - minLen; |
1420 |
4148 |
remainderStr = Word::substr(cl, 0, newlen); |
1421 |
|
} |
1422 |
|
else |
1423 |
|
{ |
1424 |
1077 |
remainderStr = Word::substr(cl, minLen); |
1425 |
|
} |
1426 |
10450 |
Trace("strings-solve-debug-test") |
1427 |
5225 |
<< "Break normal form of " << cl << " into " << ns << ", " |
1428 |
5225 |
<< remainderStr << std::endl; |
1429 |
5225 |
nfl.splitConstant(index, ns, remainderStr); |
1430 |
5225 |
index++; |
1431 |
5225 |
continue; |
1432 |
|
} |
1433 |
|
else |
1434 |
|
{ |
1435 |
|
// Conflict because the shorter constant is not a prefix/suffix of the |
1436 |
|
// other. |
1437 |
|
// |
1438 |
|
// E.g. "abc" ++ ... = "bc" ++ ... ---> conflict |
1439 |
106 |
std::vector<Node> antec; |
1440 |
53 |
NormalForm::getExplanationForPrefixEq(nfi, nfj, index, index, antec); |
1441 |
53 |
d_im.sendInference(antec, d_false, InferenceId::STRINGS_N_CONST, isRev, true); |
1442 |
53 |
break; |
1443 |
|
} |
1444 |
|
} |
1445 |
|
|
1446 |
|
// The candidate inference "info" |
1447 |
36456 |
CoreInferInfo info(InferenceId::UNKNOWN); |
1448 |
18228 |
InferInfo& iinfo = info.d_infer; |
1449 |
18228 |
info.d_index = index; |
1450 |
|
// for debugging |
1451 |
18228 |
info.d_i = nfi.d_base; |
1452 |
18228 |
info.d_j = nfj.d_base; |
1453 |
18228 |
info.d_rev = isRev; |
1454 |
18228 |
Assert(index < nfiv.size() - rproc && index < nfjv.size() - rproc); |
1455 |
84925 |
if (!d_state.areDisequal(xLenTerm, yLenTerm) && !d_state.areEqual(xLenTerm, yLenTerm) |
1456 |
12013 |
&& !x.isConst() |
1457 |
81077 |
&& !y.isConst()) // AJR: remove the latter 2 conditions? |
1458 |
|
{ |
1459 |
|
// We don't know whether `x` and `y` have the same length or not. We |
1460 |
|
// split on whether they are equal or not (note that splitting on |
1461 |
|
// equality between strings is worse since it is harder to process). |
1462 |
|
// |
1463 |
|
// E.g. x ++ ... = y ++ ... ---> (len(x) = len(y)) v (len(x) != len(y)) |
1464 |
13622 |
Trace("strings-solve-debug") |
1465 |
6811 |
<< "Non-simple Case 1 : string lengths neither equal nor disequal" |
1466 |
6811 |
<< std::endl; |
1467 |
13622 |
Node lenEq = nm->mkNode(EQUAL, xLenTerm, yLenTerm); |
1468 |
6811 |
lenEq = Rewriter::rewrite(lenEq); |
1469 |
6811 |
iinfo.d_conc = nm->mkNode(OR, lenEq, lenEq.negate()); |
1470 |
6811 |
iinfo.setId(InferenceId::STRINGS_LEN_SPLIT); |
1471 |
6811 |
info.d_pendingPhase[lenEq] = true; |
1472 |
6811 |
pinfer.push_back(info); |
1473 |
6811 |
break; |
1474 |
|
} |
1475 |
|
|
1476 |
22834 |
Trace("strings-solve-debug") |
1477 |
11417 |
<< "Non-simple Case 2 : must compare strings" << std::endl; |
1478 |
11417 |
int lhsLoopIdx = -1; |
1479 |
11417 |
int rhsLoopIdx = -1; |
1480 |
11417 |
if (detectLoop(nfi, nfj, index, lhsLoopIdx, rhsLoopIdx, rproc)) |
1481 |
|
{ |
1482 |
|
// We are dealing with a looping word equation. |
1483 |
|
// Note we could make this code also run in the reverse direction, but |
1484 |
|
// this is not implemented currently. |
1485 |
496 |
if (!isRev) |
1486 |
|
{ |
1487 |
|
// add temporarily to the antecedant of iinfo. |
1488 |
432 |
NormalForm::getExplanationForPrefixEq(nfi, nfj, -1, -1, iinfo.d_premises); |
1489 |
|
ProcessLoopResult plr = |
1490 |
1296 |
processLoop(lhsLoopIdx != -1 ? nfi : nfj, |
1491 |
432 |
lhsLoopIdx != -1 ? nfj : nfi, |
1492 |
432 |
lhsLoopIdx != -1 ? lhsLoopIdx : rhsLoopIdx, |
1493 |
432 |
index, |
1494 |
432 |
info); |
1495 |
432 |
if (plr == ProcessLoopResult::INFERENCE) |
1496 |
|
{ |
1497 |
432 |
pinfer.push_back(info); |
1498 |
432 |
break; |
1499 |
|
} |
1500 |
|
else if (plr == ProcessLoopResult::CONFLICT) |
1501 |
|
{ |
1502 |
|
break; |
1503 |
|
} |
1504 |
|
Assert(plr == ProcessLoopResult::SKIPPED); |
1505 |
|
// not processing an inference here, undo changes to ant |
1506 |
|
iinfo.d_premises.clear(); |
1507 |
|
} |
1508 |
|
} |
1509 |
|
|
1510 |
|
// AJR: length entailment here? |
1511 |
10985 |
if (x.isConst() || y.isConst()) |
1512 |
|
{ |
1513 |
|
// Below, we deal with the case where `x` or `y` is a constant string. We |
1514 |
|
// refer to the non-constant component as `nc` below. |
1515 |
|
// |
1516 |
|
// E.g. "abc" ++ ... = nc ++ ... |
1517 |
6442 |
Assert(!x.isConst() || !y.isConst()); |
1518 |
6442 |
NormalForm& nfc = x.isConst() ? nfi : nfj; |
1519 |
6442 |
const std::vector<Node>& nfcv = nfc.d_nf; |
1520 |
6442 |
NormalForm& nfnc = x.isConst() ? nfj : nfi; |
1521 |
6442 |
const std::vector<Node>& nfncv = nfnc.d_nf; |
1522 |
12884 |
Node nc = nfncv[index]; |
1523 |
6442 |
Assert(!nc.isConst()) << "Other string is not constant."; |
1524 |
6442 |
Assert(nc.getKind() != STRING_CONCAT) << "Other string is not CONCAT."; |
1525 |
|
// explanation for why nc is non-empty |
1526 |
12884 |
Node expNonEmpty = d_state.explainNonEmpty(nc); |
1527 |
6442 |
if (expNonEmpty.isNull()) |
1528 |
|
{ |
1529 |
|
// The non-constant side may be equal to the empty string. Split on |
1530 |
|
// whether it is. |
1531 |
|
// |
1532 |
|
// E.g. "abc" ++ ... = nc ++ ... ---> (nc = "") v (nc != "") |
1533 |
28 |
Node eq = nc.eqNode(emp); |
1534 |
14 |
eq = Rewriter::rewrite(eq); |
1535 |
14 |
if (eq.isConst()) |
1536 |
|
{ |
1537 |
|
// If the equality rewrites to a constant, we must use the |
1538 |
|
// purify variable for this string to communicate that |
1539 |
|
// we have inferred whether it is empty. |
1540 |
|
SkolemCache* skc = d_termReg.getSkolemCache(); |
1541 |
|
Node p = skc->mkSkolemCached(nc, SkolemCache::SK_PURIFY, "lsym"); |
1542 |
|
Node pEq = p.eqNode(emp); |
1543 |
|
// should not be constant |
1544 |
|
Assert(!Rewriter::rewrite(pEq).isConst()); |
1545 |
|
// infer the purification equality, and the (dis)equality |
1546 |
|
// with the empty string in the direction that the rewriter |
1547 |
|
// inferred |
1548 |
|
iinfo.d_conc = nm->mkNode( |
1549 |
|
AND, p.eqNode(nc), !eq.getConst<bool>() ? pEq.negate() : pEq); |
1550 |
|
iinfo.setId(InferenceId::STRINGS_INFER_EMP); |
1551 |
|
} |
1552 |
|
else |
1553 |
|
{ |
1554 |
14 |
iinfo.d_conc = nm->mkNode(OR, eq, eq.negate()); |
1555 |
14 |
iinfo.setId(InferenceId::STRINGS_LEN_SPLIT_EMP); |
1556 |
|
} |
1557 |
14 |
pinfer.push_back(info); |
1558 |
14 |
break; |
1559 |
|
} |
1560 |
|
|
1561 |
|
// At this point, we know that `nc` is non-empty, so we add expNonEmpty |
1562 |
|
// to our explanation below. We do this after adding other parts of the |
1563 |
|
// explanation for consistency with other inferences. |
1564 |
|
|
1565 |
6428 |
size_t ncIndex = index + 1; |
1566 |
12856 |
Node nextConstStr = nfnc.collectConstantStringAt(ncIndex); |
1567 |
6428 |
if (!nextConstStr.isNull()) |
1568 |
|
{ |
1569 |
|
// We are in the case where we have a constant after `nc`, so we |
1570 |
|
// split `nc`. |
1571 |
|
// |
1572 |
|
// E.g. "abc" ++ ... = nc ++ "b" ++ ... ---> nc = "a" ++ k |
1573 |
3027 |
size_t cIndex = index; |
1574 |
5094 |
Node stra = nfc.collectConstantStringAt(cIndex); |
1575 |
3027 |
Assert(!stra.isNull()); |
1576 |
5094 |
Node strb = nextConstStr; |
1577 |
|
|
1578 |
|
// Since `nc` is non-empty, we use the non-empty overlap |
1579 |
3027 |
size_t p = getSufficientNonEmptyOverlap(stra, strb, isRev); |
1580 |
|
|
1581 |
|
// If we can't split off more than a single character from the |
1582 |
|
// constant, we might as well do regular constant/non-constant |
1583 |
|
// inference (see below). |
1584 |
3027 |
if (p > 1) |
1585 |
|
{ |
1586 |
960 |
NormalForm::getExplanationForPrefixEq( |
1587 |
|
nfc, nfnc, cIndex, ncIndex, iinfo.d_premises); |
1588 |
960 |
iinfo.d_premises.push_back(expNonEmpty); |
1589 |
|
// make the conclusion |
1590 |
960 |
SkolemCache* skc = d_termReg.getSkolemCache(); |
1591 |
|
Node xcv = |
1592 |
1920 |
nm->mkNode(STRING_CONCAT, isRev ? strb : nc, isRev ? nc : strb); |
1593 |
1920 |
std::vector<Node> newSkolems; |
1594 |
1920 |
iinfo.d_conc = getConclusion( |
1595 |
960 |
xcv, stra, PfRule::CONCAT_CPROP, isRev, skc, newSkolems); |
1596 |
960 |
Assert(newSkolems.size() == 1); |
1597 |
960 |
iinfo.d_skolems[LENGTH_SPLIT].push_back(newSkolems[0]); |
1598 |
960 |
iinfo.setId(InferenceId::STRINGS_SSPLIT_CST_PROP); |
1599 |
960 |
iinfo.d_idRev = isRev; |
1600 |
960 |
pinfer.push_back(info); |
1601 |
960 |
break; |
1602 |
|
} |
1603 |
|
} |
1604 |
|
|
1605 |
|
// Since none of the other inferences apply, we just infer that `nc` has |
1606 |
|
// to start with the first character of the constant. |
1607 |
|
// |
1608 |
|
// E.g. "abc" ++ ... = nc ++ ... ---> nc = "a" ++ k |
1609 |
5468 |
SkolemCache* skc = d_termReg.getSkolemCache(); |
1610 |
10936 |
std::vector<Node> newSkolems; |
1611 |
16404 |
iinfo.d_conc = getConclusion( |
1612 |
10936 |
nc, nfcv[index], PfRule::CONCAT_CSPLIT, isRev, skc, newSkolems); |
1613 |
16404 |
NormalForm::getExplanationForPrefixEq( |
1614 |
10936 |
nfi, nfj, index, index, iinfo.d_premises); |
1615 |
5468 |
iinfo.d_premises.push_back(expNonEmpty); |
1616 |
5468 |
Assert(newSkolems.size() == 1); |
1617 |
5468 |
iinfo.d_skolems[LENGTH_SPLIT].push_back(newSkolems[0]); |
1618 |
5468 |
iinfo.setId(InferenceId::STRINGS_SSPLIT_CST); |
1619 |
5468 |
iinfo.d_idRev = isRev; |
1620 |
5468 |
pinfer.push_back(info); |
1621 |
5468 |
break; |
1622 |
|
} |
1623 |
|
|
1624 |
|
// Below, we deal with the case where `x` and `y` are two non-constant |
1625 |
|
// terms of different lengths. In this case, we have to split on which term |
1626 |
|
// is a prefix/suffix of the other. |
1627 |
|
// |
1628 |
|
// E.g. x ++ ... = y ++ ... ---> (x = y ++ k) v (y = x ++ k) |
1629 |
4543 |
Assert(d_state.areDisequal(xLenTerm, yLenTerm)); |
1630 |
4543 |
Assert(!x.isConst()); |
1631 |
4543 |
Assert(!y.isConst()); |
1632 |
|
|
1633 |
4543 |
int32_t lentTestSuccess = -1; |
1634 |
9086 |
Node lenConstraint; |
1635 |
4543 |
if (options::stringCheckEntailLen()) |
1636 |
|
{ |
1637 |
|
// If length entailment checks are enabled, we can save the case split by |
1638 |
|
// inferring that `x` has to be longer than `y` or vice-versa. |
1639 |
11277 |
for (size_t e = 0; e < 2; e++) |
1640 |
|
{ |
1641 |
15209 |
Node t = e == 0 ? x : y; |
1642 |
|
// do not infer constants are larger than variables |
1643 |
8475 |
if (!t.isConst()) |
1644 |
|
{ |
1645 |
15209 |
Node lt1 = e == 0 ? xLenTerm : yLenTerm; |
1646 |
15209 |
Node lt2 = e == 0 ? yLenTerm : xLenTerm; |
1647 |
15209 |
Node entLit = Rewriter::rewrite(nm->mkNode(GT, lt1, lt2)); |
1648 |
8475 |
std::pair<bool, Node> et = d_state.entailmentCheck( |
1649 |
15209 |
options::TheoryOfMode::THEORY_OF_TYPE_BASED, entLit); |
1650 |
8475 |
if (et.first) |
1651 |
|
{ |
1652 |
3482 |
Trace("strings-entail") |
1653 |
1741 |
<< "Strings entailment : " << entLit |
1654 |
1741 |
<< " is entailed in the current context." << std::endl; |
1655 |
3482 |
Trace("strings-entail") |
1656 |
1741 |
<< " explanation was : " << et.second << std::endl; |
1657 |
1741 |
lentTestSuccess = e; |
1658 |
1741 |
lenConstraint = entLit; |
1659 |
|
// Its not explained by the equality engine of this class, so its |
1660 |
|
// marked as not being explained. The length constraint is |
1661 |
|
// additionally being saved and added to the length constraint |
1662 |
|
// vector lcVec below, which is added to iinfo.d_ant below. Length |
1663 |
|
// constraints are being added as the last antecedant for the sake |
1664 |
|
// of proof reconstruction, which expect length constraints to come |
1665 |
|
// last. |
1666 |
1741 |
iinfo.d_noExplain.push_back(lenConstraint); |
1667 |
1741 |
break; |
1668 |
|
} |
1669 |
|
} |
1670 |
|
} |
1671 |
|
} |
1672 |
|
// lcVec stores the length constraint portion of the antecedant. |
1673 |
9086 |
std::vector<Node> lcVec; |
1674 |
4543 |
if (lenConstraint.isNull()) |
1675 |
|
{ |
1676 |
|
// will do split on length |
1677 |
2802 |
lenConstraint = nm->mkNode(EQUAL, xLenTerm, yLenTerm).negate(); |
1678 |
2802 |
lcVec.push_back(lenConstraint); |
1679 |
|
} |
1680 |
|
else |
1681 |
|
{ |
1682 |
1741 |
utils::flattenOp(AND, lenConstraint, lcVec); |
1683 |
|
} |
1684 |
|
|
1685 |
4543 |
NormalForm::getExplanationForPrefixEq(nfi, nfj, index, index, iinfo.d_premises); |
1686 |
|
// Add premises for x != "" ^ y != "" |
1687 |
13629 |
for (unsigned xory = 0; xory < 2; xory++) |
1688 |
|
{ |
1689 |
18172 |
Node t = xory == 0 ? x : y; |
1690 |
18172 |
Node tnz = d_state.explainNonEmpty(t); |
1691 |
9086 |
if (!tnz.isNull()) |
1692 |
|
{ |
1693 |
9086 |
lcVec.push_back(tnz); |
1694 |
|
} |
1695 |
|
else |
1696 |
|
{ |
1697 |
|
tnz = x.eqNode(emp).negate(); |
1698 |
|
lcVec.push_back(tnz); |
1699 |
|
iinfo.d_noExplain.push_back(tnz); |
1700 |
|
} |
1701 |
|
} |
1702 |
4543 |
SkolemCache* skc = d_termReg.getSkolemCache(); |
1703 |
9086 |
std::vector<Node> newSkolems; |
1704 |
|
// make the conclusion |
1705 |
4543 |
if (lentTestSuccess == -1) |
1706 |
|
{ |
1707 |
2802 |
iinfo.setId(InferenceId::STRINGS_SSPLIT_VAR); |
1708 |
2802 |
iinfo.d_conc = |
1709 |
5604 |
getConclusion(x, y, PfRule::CONCAT_SPLIT, isRev, skc, newSkolems); |
1710 |
|
} |
1711 |
1741 |
else if (lentTestSuccess == 0) |
1712 |
|
{ |
1713 |
611 |
iinfo.setId(InferenceId::STRINGS_SSPLIT_VAR_PROP); |
1714 |
611 |
iinfo.d_conc = |
1715 |
1222 |
getConclusion(x, y, PfRule::CONCAT_LPROP, isRev, skc, newSkolems); |
1716 |
|
} |
1717 |
|
else |
1718 |
|
{ |
1719 |
1130 |
Assert(lentTestSuccess == 1); |
1720 |
1130 |
iinfo.setId(InferenceId::STRINGS_SSPLIT_VAR_PROP); |
1721 |
1130 |
iinfo.d_conc = |
1722 |
2260 |
getConclusion(y, x, PfRule::CONCAT_LPROP, isRev, skc, newSkolems); |
1723 |
|
} |
1724 |
|
// add the length constraint(s) as the last antecedant |
1725 |
9086 |
Node lc = utils::mkAnd(lcVec); |
1726 |
4543 |
iinfo.d_premises.push_back(lc); |
1727 |
4543 |
iinfo.d_idRev = isRev; |
1728 |
4543 |
pinfer.push_back(info); |
1729 |
4543 |
break; |
1730 |
26560 |
} |
1731 |
|
} |
1732 |
|
|
1733 |
11417 |
bool CoreSolver::detectLoop(NormalForm& nfi, |
1734 |
|
NormalForm& nfj, |
1735 |
|
int index, |
1736 |
|
int& loop_in_i, |
1737 |
|
int& loop_in_j, |
1738 |
|
unsigned rproc) |
1739 |
|
{ |
1740 |
11417 |
int has_loop[2] = { -1, -1 }; |
1741 |
34251 |
for (unsigned r = 0; r < 2; r++) |
1742 |
|
{ |
1743 |
22834 |
NormalForm& nf = r == 0 ? nfi : nfj; |
1744 |
22834 |
NormalForm& nfo = r == 0 ? nfj : nfi; |
1745 |
22834 |
std::vector<Node>& nfv = nf.d_nf; |
1746 |
22834 |
std::vector<Node>& nfov = nfo.d_nf; |
1747 |
22834 |
if (nfov[index].isConst()) |
1748 |
|
{ |
1749 |
6488 |
continue; |
1750 |
|
} |
1751 |
57644 |
for (unsigned lp = index + 1, lpEnd = nfv.size() - rproc; lp < lpEnd; lp++) |
1752 |
|
{ |
1753 |
41794 |
if (nfv[lp] == nfov[index]) |
1754 |
|
{ |
1755 |
496 |
has_loop[r] = lp; |
1756 |
496 |
break; |
1757 |
|
} |
1758 |
|
} |
1759 |
|
} |
1760 |
11417 |
if( has_loop[0]!=-1 || has_loop[1]!=-1 ) { |
1761 |
496 |
loop_in_i = has_loop[0]; |
1762 |
496 |
loop_in_j = has_loop[1]; |
1763 |
496 |
return true; |
1764 |
|
} else { |
1765 |
10921 |
Trace("strings-solve-debug") << "No loops detected." << std::endl; |
1766 |
10921 |
return false; |
1767 |
|
} |
1768 |
|
} |
1769 |
|
|
1770 |
|
//xs(zy)=t(yz)xr |
1771 |
432 |
CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, |
1772 |
|
NormalForm& nfj, |
1773 |
|
int loop_index, |
1774 |
|
int index, |
1775 |
|
CoreInferInfo& info) |
1776 |
|
{ |
1777 |
432 |
NodeManager* nm = NodeManager::currentNM(); |
1778 |
864 |
Node conc; |
1779 |
432 |
const std::vector<Node>& veci = nfi.d_nf; |
1780 |
432 |
const std::vector<Node>& vecoi = nfj.d_nf; |
1781 |
|
|
1782 |
864 |
TypeNode stype = veci[loop_index].getType(); |
1783 |
|
|
1784 |
432 |
if (options::stringProcessLoopMode() == options::ProcessLoopMode::ABORT) |
1785 |
|
{ |
1786 |
|
throw LogicException("Looping word equation encountered."); |
1787 |
|
} |
1788 |
864 |
else if (options::stringProcessLoopMode() == options::ProcessLoopMode::NONE |
1789 |
432 |
|| stype.isSequence()) |
1790 |
|
{ |
1791 |
|
// note we cannot convert looping word equations into regular expressions if |
1792 |
|
// we are handling sequences, since there is no analog for regular |
1793 |
|
// expressions over sequences currently |
1794 |
|
d_im.setIncomplete(IncompleteId::STRINGS_LOOP_SKIP); |
1795 |
|
return ProcessLoopResult::SKIPPED; |
1796 |
|
} |
1797 |
|
|
1798 |
864 |
Trace("strings-loop") << "Detected possible loop for " << veci[loop_index] |
1799 |
432 |
<< std::endl; |
1800 |
432 |
Trace("strings-loop") << " ... (X)= " << vecoi[index] << std::endl; |
1801 |
432 |
Trace("strings-loop") << " ... T(Y.Z)= "; |
1802 |
864 |
std::vector<Node> vec_t(veci.begin() + index, veci.begin() + loop_index); |
1803 |
864 |
Node t_yz = utils::mkNConcat(vec_t, stype); |
1804 |
432 |
Trace("strings-loop") << " (" << t_yz << ")" << std::endl; |
1805 |
432 |
Trace("strings-loop") << " ... S(Z.Y)= "; |
1806 |
864 |
std::vector<Node> vec_s(vecoi.begin() + index + 1, vecoi.end()); |
1807 |
864 |
Node s_zy = utils::mkNConcat(vec_s, stype); |
1808 |
432 |
Trace("strings-loop") << s_zy << std::endl; |
1809 |
432 |
Trace("strings-loop") << " ... R= "; |
1810 |
864 |
std::vector<Node> vec_r(veci.begin() + loop_index + 1, veci.end()); |
1811 |
864 |
Node r = utils::mkNConcat(vec_r, stype); |
1812 |
432 |
Trace("strings-loop") << r << std::endl; |
1813 |
|
|
1814 |
864 |
Node emp = Word::mkEmptyWord(stype); |
1815 |
|
|
1816 |
432 |
InferInfo& iinfo = info.d_infer; |
1817 |
432 |
if (s_zy.isConst() && r.isConst() && r != emp) |
1818 |
|
{ |
1819 |
|
int c; |
1820 |
18 |
bool flag = true; |
1821 |
18 |
if (s_zy.getConst<String>().tailcmp(r.getConst<String>(), c)) |
1822 |
|
{ |
1823 |
18 |
if (c >= 0) |
1824 |
|
{ |
1825 |
18 |
s_zy = Word::substr(s_zy, 0, c); |
1826 |
18 |
r = emp; |
1827 |
18 |
vec_r.clear(); |
1828 |
36 |
Trace("strings-loop") << "Strings::Loop: Refactor S(Z.Y)= " << s_zy |
1829 |
18 |
<< ", c=" << c << std::endl; |
1830 |
18 |
flag = false; |
1831 |
|
} |
1832 |
|
} |
1833 |
18 |
if (flag) |
1834 |
|
{ |
1835 |
|
Trace("strings-loop") << "Strings::Loop: tails are different." |
1836 |
|
<< std::endl; |
1837 |
|
d_im.sendInference( |
1838 |
|
iinfo.d_premises, conc, InferenceId::STRINGS_FLOOP_CONFLICT, false, true); |
1839 |
|
return ProcessLoopResult::CONFLICT; |
1840 |
|
} |
1841 |
|
} |
1842 |
|
|
1843 |
864 |
Node split_eq; |
1844 |
1294 |
for (unsigned i = 0; i < 2; i++) |
1845 |
|
{ |
1846 |
1726 |
Node t = i == 0 ? veci[loop_index] : t_yz; |
1847 |
864 |
split_eq = t.eqNode(emp); |
1848 |
1726 |
Node split_eqr = Rewriter::rewrite(split_eq); |
1849 |
|
// the equality could rewrite to false |
1850 |
864 |
if (!split_eqr.isConst()) |
1851 |
|
{ |
1852 |
1646 |
Node expNonEmpty = d_state.explainNonEmpty(t); |
1853 |
824 |
if (expNonEmpty.isNull()) |
1854 |
|
{ |
1855 |
|
// no antecedants necessary |
1856 |
2 |
iinfo.d_premises.clear(); |
1857 |
|
// try to make t equal to empty to avoid loop |
1858 |
2 |
iinfo.d_conc = nm->mkNode(kind::OR, split_eq, split_eq.negate()); |
1859 |
2 |
iinfo.setId(InferenceId::STRINGS_LEN_SPLIT_EMP); |
1860 |
2 |
return ProcessLoopResult::INFERENCE; |
1861 |
|
} |
1862 |
|
else |
1863 |
|
{ |
1864 |
822 |
iinfo.d_premises.push_back(expNonEmpty); |
1865 |
|
} |
1866 |
|
} |
1867 |
|
else |
1868 |
|
{ |
1869 |
40 |
Assert(!split_eqr.getConst<bool>()); |
1870 |
|
} |
1871 |
|
} |
1872 |
|
|
1873 |
860 |
Node str_in_re; |
1874 |
880 |
if (s_zy == t_yz && r == emp && s_zy.isConst() |
1875 |
450 |
&& s_zy.getConst<String>().isRepeated()) |
1876 |
|
{ |
1877 |
28 |
Node rep_c = Word::substr(s_zy, 0, 1); |
1878 |
28 |
Trace("strings-loop") << "Special case (X)=" << vecoi[index] << " " |
1879 |
14 |
<< std::endl; |
1880 |
14 |
Trace("strings-loop") << "... (C)=" << rep_c << " " << std::endl; |
1881 |
|
// special case |
1882 |
28 |
str_in_re = nm->mkNode( |
1883 |
|
STRING_IN_REGEXP, |
1884 |
14 |
vecoi[index], |
1885 |
28 |
nm->mkNode(REGEXP_STAR, nm->mkNode(STRING_TO_REGEXP, rep_c))); |
1886 |
14 |
conc = str_in_re; |
1887 |
|
} |
1888 |
416 |
else if (t_yz.isConst()) |
1889 |
|
{ |
1890 |
52 |
Trace("strings-loop") << "Strings::Loop: Const Normal Breaking." |
1891 |
26 |
<< std::endl; |
1892 |
26 |
unsigned size = Word::getLength(t_yz); |
1893 |
52 |
std::vector<Node> vconc; |
1894 |
98 |
for (unsigned len = 1; len <= size; len++) |
1895 |
|
{ |
1896 |
124 |
Node y = Word::substr(t_yz, 0, len); |
1897 |
124 |
Node z = Word::substr(t_yz, len, size - len); |
1898 |
124 |
Node restr = s_zy; |
1899 |
124 |
Node cc; |
1900 |
72 |
if (r != emp) |
1901 |
|
{ |
1902 |
64 |
std::vector<Node> v2(vec_r); |
1903 |
32 |
v2.insert(v2.begin(), y); |
1904 |
32 |
v2.insert(v2.begin(), z); |
1905 |
32 |
restr = utils::mkNConcat(z, y); |
1906 |
32 |
cc = Rewriter::rewrite(s_zy.eqNode(utils::mkNConcat(v2, stype))); |
1907 |
|
} |
1908 |
|
else |
1909 |
|
{ |
1910 |
40 |
cc = Rewriter::rewrite(s_zy.eqNode(utils::mkNConcat(z, y))); |
1911 |
|
} |
1912 |
72 |
if (cc == d_false) |
1913 |
|
{ |
1914 |
20 |
continue; |
1915 |
|
} |
1916 |
|
Node conc2 = nm->mkNode( |
1917 |
|
STRING_IN_REGEXP, |
1918 |
52 |
vecoi[index], |
1919 |
208 |
nm->mkNode( |
1920 |
|
REGEXP_CONCAT, |
1921 |
104 |
nm->mkNode(STRING_TO_REGEXP, y), |
1922 |
260 |
nm->mkNode(REGEXP_STAR, nm->mkNode(STRING_TO_REGEXP, restr)))); |
1923 |
52 |
cc = cc == d_true ? conc2 : nm->mkNode(kind::AND, cc, conc2); |
1924 |
52 |
vconc.push_back(cc); |
1925 |
|
} |
1926 |
42 |
conc = vconc.size() == 0 ? Node::null() : vconc.size() == 1 |
1927 |
16 |
? vconc[0] |
1928 |
|
: nm->mkNode(kind::OR, vconc); |
1929 |
|
} |
1930 |
|
else |
1931 |
|
{ |
1932 |
780 |
if (options::stringProcessLoopMode() |
1933 |
390 |
== options::ProcessLoopMode::SIMPLE_ABORT) |
1934 |
|
{ |
1935 |
|
throw LogicException("Normal looping word equation encountered."); |
1936 |
|
} |
1937 |
780 |
else if (options::stringProcessLoopMode() |
1938 |
390 |
== options::ProcessLoopMode::SIMPLE) |
1939 |
|
{ |
1940 |
|
d_im.setIncomplete(IncompleteId::STRINGS_LOOP_SKIP); |
1941 |
|
return ProcessLoopResult::SKIPPED; |
1942 |
|
} |
1943 |
|
|
1944 |
780 |
Trace("strings-loop") << "Strings::Loop: Normal Loop Breaking." |
1945 |
390 |
<< std::endl; |
1946 |
|
// right |
1947 |
390 |
SkolemCache* skc = d_termReg.getSkolemCache(); |
1948 |
780 |
Node sk_w = skc->mkSkolem("w_loop"); |
1949 |
780 |
Node sk_y = skc->mkSkolem("y_loop"); |
1950 |
390 |
iinfo.d_skolems[LENGTH_GEQ_ONE].push_back(sk_y); |
1951 |
780 |
Node sk_z = skc->mkSkolem("z_loop"); |
1952 |
|
// t1 * ... * tn = y * z |
1953 |
780 |
Node conc1 = t_yz.eqNode(utils::mkNConcat(sk_y, sk_z)); |
1954 |
|
// s1 * ... * sk = z * y * r |
1955 |
390 |
vec_r.insert(vec_r.begin(), sk_y); |
1956 |
390 |
vec_r.insert(vec_r.begin(), sk_z); |
1957 |
780 |
Node conc2 = s_zy.eqNode(utils::mkNConcat(vec_r, stype)); |
1958 |
780 |
Node conc3 = vecoi[index].eqNode(utils::mkNConcat(sk_y, sk_w)); |
1959 |
780 |
Node restr = r == emp ? s_zy : utils::mkNConcat(sk_z, sk_y); |
1960 |
390 |
str_in_re = |
1961 |
780 |
nm->mkNode(kind::STRING_IN_REGEXP, |
1962 |
|
sk_w, |
1963 |
780 |
nm->mkNode(kind::REGEXP_STAR, |
1964 |
780 |
nm->mkNode(kind::STRING_TO_REGEXP, restr))); |
1965 |
|
|
1966 |
780 |
std::vector<Node> vec_conc; |
1967 |
390 |
vec_conc.push_back(conc1); |
1968 |
390 |
vec_conc.push_back(conc2); |
1969 |
390 |
vec_conc.push_back(conc3); |
1970 |
390 |
vec_conc.push_back(str_in_re); |
1971 |
390 |
conc = nm->mkNode(kind::AND, vec_conc); |
1972 |
|
} // normal case |
1973 |
|
|
1974 |
|
// we will be done |
1975 |
430 |
iinfo.d_conc = conc; |
1976 |
430 |
iinfo.setId(InferenceId::STRINGS_FLOOP); |
1977 |
430 |
info.d_nfPair[0] = nfi.d_base; |
1978 |
430 |
info.d_nfPair[1] = nfj.d_base; |
1979 |
430 |
return ProcessLoopResult::INFERENCE; |
1980 |
|
} |
1981 |
|
|
1982 |
7037 |
void CoreSolver::processDeq(Node ni, Node nj) |
1983 |
|
{ |
1984 |
7037 |
NodeManager* nm = NodeManager::currentNM(); |
1985 |
7037 |
NormalForm& nfni = getNormalForm(ni); |
1986 |
7037 |
NormalForm& nfnj = getNormalForm(nj); |
1987 |
|
|
1988 |
7037 |
if (nfni.d_nf.size() <= 1 && nfnj.d_nf.size() <= 1) |
1989 |
|
{ |
1990 |
|
// If normal forms have size <=1, then we are comparing either: |
1991 |
|
// (1) variable to variable, |
1992 |
|
// (2) variable to constant-like (empty, constant string/seq or SEQ_UNIT), |
1993 |
|
// (3) SEQ_UNIT to constant-like. |
1994 |
|
// We only have to process (3) here, since disequalities of the form of (1) |
1995 |
|
// and (2) are satisfied by construction. |
1996 |
15277 |
for (size_t i = 0; i < 2; i++) |
1997 |
|
{ |
1998 |
10194 |
NormalForm& nfc = i == 0 ? nfni : nfnj; |
1999 |
10194 |
if (nfc.d_nf.size() == 0 || nfc.d_nf[0].getKind() != SEQ_UNIT) |
2000 |
|
{ |
2001 |
|
// may need to look at the other side |
2002 |
10178 |
continue; |
2003 |
|
} |
2004 |
16 |
Node u = nfc.d_nf[0]; |
2005 |
|
// if the other side is constant like |
2006 |
16 |
NormalForm& nfo = i == 0 ? nfnj : nfni; |
2007 |
16 |
if (nfo.d_nf.size() == 0 || !utils::isConstantLike(nfo.d_nf[0])) |
2008 |
|
{ |
2009 |
6 |
break; |
2010 |
|
} |
2011 |
|
// v is the other side (a possibly constant seq.unit term) |
2012 |
10 |
Node v = nfo.d_nf[0]; |
2013 |
10 |
Node vc; |
2014 |
10 |
if (v.isConst()) |
2015 |
|
{ |
2016 |
|
// get the list of characters (strings of length 1) |
2017 |
20 |
std::vector<Node> vchars = Word::getChars(v); |
2018 |
10 |
if (vchars.size() != 1) |
2019 |
|
{ |
2020 |
|
// constant of size != 1, disequality is satisfied |
2021 |
|
break; |
2022 |
|
} |
2023 |
|
// get the element of the character |
2024 |
10 |
vc = vchars[0].getConst<Sequence>().getVec()[0]; |
2025 |
|
} |
2026 |
|
else |
2027 |
|
{ |
2028 |
|
Assert(v.getKind() == SEQ_UNIT); |
2029 |
|
vc = v[0]; |
2030 |
|
} |
2031 |
10 |
Assert(u[0].getType() == vc.getType()); |
2032 |
|
// if already disequal, we are done |
2033 |
10 |
if (d_state.areDisequal(u[0], vc)) |
2034 |
|
{ |
2035 |
6 |
break; |
2036 |
|
} |
2037 |
|
// seq.unit(x) != seq.unit(y) => x != y |
2038 |
|
// Notice this is a special case, since the code below would justify this |
2039 |
|
// disequality by reasoning that a component is disequal. However, the |
2040 |
|
// disequality components are the entire disequality. |
2041 |
8 |
Node deq = u.eqNode(v).notNode(); |
2042 |
8 |
std::vector<Node> premises; |
2043 |
4 |
premises.push_back(deq); |
2044 |
8 |
Node conc = u[0].eqNode(vc).notNode(); |
2045 |
4 |
d_im.sendInference(premises, conc, InferenceId::STRINGS_UNIT_INJ_DEQ, false, true); |
2046 |
4 |
return; |
2047 |
|
} |
2048 |
5095 |
Trace("strings-solve-debug") << "...trivial" << std::endl; |
2049 |
5095 |
return; |
2050 |
|
} |
2051 |
|
|
2052 |
3876 |
std::vector<Node> nfi = nfni.d_nf; |
2053 |
3876 |
std::vector<Node> nfj = nfnj.d_nf; |
2054 |
|
|
2055 |
|
// See if one side is constant, if so, the disequality ni != nj is satisfied |
2056 |
|
// if it cannot contain the other side. |
2057 |
|
// |
2058 |
|
// E.g. "abc" != x ++ "d" ++ y |
2059 |
5478 |
for (uint32_t i = 0; i < 2; i++) |
2060 |
|
{ |
2061 |
7341 |
Node c = d_bsolver.getConstantEqc(i == 0 ? ni : nj); |
2062 |
3801 |
if (!c.isNull()) |
2063 |
|
{ |
2064 |
|
int findex, lindex; |
2065 |
1355 |
if (!StringsEntail::canConstantContainList( |
2066 |
|
c, i == 0 ? nfj : nfi, findex, lindex)) |
2067 |
|
{ |
2068 |
522 |
Trace("strings-solve-debug") |
2069 |
261 |
<< "Disequality: constant cannot contain list" << std::endl; |
2070 |
261 |
return; |
2071 |
|
} |
2072 |
|
} |
2073 |
|
} |
2074 |
|
|
2075 |
1677 |
if (processReverseDeq(nfi, nfj, ni, nj)) |
2076 |
|
{ |
2077 |
501 |
Trace("strings-solve-debug") << "...processed reverse" << std::endl; |
2078 |
501 |
return; |
2079 |
|
} |
2080 |
|
|
2081 |
1176 |
nfi = nfni.d_nf; |
2082 |
1176 |
nfj = nfnj.d_nf; |
2083 |
|
|
2084 |
1176 |
size_t index = 0; |
2085 |
1176 |
while (index < nfi.size() || index < nfj.size()) |
2086 |
|
{ |
2087 |
1176 |
if (processSimpleDeq(nfi, nfj, ni, nj, index, false)) |
2088 |
|
{ |
2089 |
158 |
Trace("strings-solve-debug") << "...processed simple" << std::endl; |
2090 |
1334 |
return; |
2091 |
|
} |
2092 |
|
|
2093 |
|
// We have inferred that the normal forms up to position `index` are |
2094 |
|
// equivalent. Below, we refer to the components at the current position of |
2095 |
|
// the normal form as `x` and `y`. |
2096 |
|
// |
2097 |
|
// E.g. x ++ ... = y ++ ... |
2098 |
1018 |
Assert(index < nfi.size() && index < nfj.size()); |
2099 |
1018 |
Node x = nfi[index]; |
2100 |
1018 |
Node y = nfj[index]; |
2101 |
2036 |
Trace("strings-solve-debug") |
2102 |
1018 |
<< "...Processing(DEQ) " << x << " " << y << std::endl; |
2103 |
1018 |
if (d_state.areEqual(x, y)) |
2104 |
|
{ |
2105 |
|
// The normal forms have an equivalent term at `index` in the current |
2106 |
|
// context. We move to the next `index`. |
2107 |
|
// |
2108 |
|
// E.g. x ++ x' ++ ... != z ++ y' ++ ... ^ x = z |
2109 |
|
index++; |
2110 |
|
continue; |
2111 |
|
} |
2112 |
|
|
2113 |
1018 |
Assert(!x.isConst() || !y.isConst()); |
2114 |
1018 |
std::vector<Node> lenExp; |
2115 |
1018 |
Node xLenTerm = d_state.getLength(x, lenExp); |
2116 |
1018 |
Node yLenTerm = d_state.getLength(y, lenExp); |
2117 |
1018 |
if (d_state.areDisequal(xLenTerm, yLenTerm)) |
2118 |
|
{ |
2119 |
|
// Below, we deal with the cases where the components at the current |
2120 |
|
// index are of different lengths in the current context. |
2121 |
|
// |
2122 |
|
// E.g. x ++ x' ++ ... != y ++ y' ++ ... ^ len(x) != len(y) |
2123 |
932 |
if (x.isConst() || y.isConst()) |
2124 |
|
{ |
2125 |
924 |
Node ck = x.isConst() ? x : y; |
2126 |
924 |
Node nck = x.isConst() ? y : x; |
2127 |
924 |
Node nckLenTerm = x.isConst() ? yLenTerm : xLenTerm; |
2128 |
924 |
Node expNonEmpty = d_state.explainNonEmpty(nck); |
2129 |
924 |
if (expNonEmpty.isNull()) |
2130 |
|
{ |
2131 |
|
// Either `x` or `y` is a constant and the other may be equal to the |
2132 |
|
// empty string in the current context. We split on whether it |
2133 |
|
// actually is empty in that case. |
2134 |
|
// |
2135 |
|
// E.g. x ++ x' ++ ... != "abc" ++ y' ++ ... ^ len(x) != len(y) ---> |
2136 |
|
// x = "" v x != "" |
2137 |
|
Node emp = Word::mkEmptyWord(nck.getType()); |
2138 |
|
d_im.sendSplit(nck, emp, InferenceId::STRINGS_DEQ_DISL_EMP_SPLIT); |
2139 |
|
return; |
2140 |
|
} |
2141 |
|
|
2142 |
924 |
Node firstChar = Word::getLength(ck) == 1 ? ck : Word::prefix(ck, 1); |
2143 |
924 |
if (d_state.areEqual(nckLenTerm, d_one)) |
2144 |
|
{ |
2145 |
898 |
if (d_state.areDisequal(firstChar, nck)) |
2146 |
|
{ |
2147 |
|
// Either `x` or `y` is a constant and the other is a string of |
2148 |
|
// length one. If the non-constant is disequal to the first |
2149 |
|
// character of the constant in the current context, we satisfy the |
2150 |
|
// disequality and there is nothing else to do. |
2151 |
|
// |
2152 |
|
// E.g. "d" ++ x' ++ ... != "abc" ++ y' ++ ... ^ len(x) = 1 |
2153 |
848 |
return; |
2154 |
|
} |
2155 |
50 |
else if (!d_state.areEqual(firstChar, nck)) |
2156 |
|
{ |
2157 |
|
// Either `x` or `y` is a constant and the other is a string of |
2158 |
|
// length one. If we do not know whether the non-constant is equal |
2159 |
|
// or disequal to the first character in the constant, we split on |
2160 |
|
// whether it is. |
2161 |
|
// |
2162 |
|
// E.g. x ++ x' ++ ... != "abc" ++ y' ++ ... ^ len(x) = 1 ---> |
2163 |
|
// x = "a" v x != "a" |
2164 |
50 |
if (d_im.sendSplit(firstChar, |
2165 |
|
nck, |
2166 |
|
InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_EQ_SPLIT, |
2167 |
|
false)) |
2168 |
|
{ |
2169 |
50 |
return; |
2170 |
|
} |
2171 |
|
} |
2172 |
|
} |
2173 |
|
else |
2174 |
|
{ |
2175 |
|
// Either `x` or `y` is a constant and it is not know whether the |
2176 |
|
// non-empty non-constant is of length one. We split the non-constant |
2177 |
|
// into a string of length one and the remainder. |
2178 |
|
// |
2179 |
|
// len(x)>=1 => x = k1 ++ k2 ^ len(k1) = 1 |
2180 |
26 |
SkolemCache* skc = d_termReg.getSkolemCache(); |
2181 |
52 |
std::vector<Node> newSkolems; |
2182 |
|
Node conc = getDecomposeConclusion( |
2183 |
52 |
nck, d_one, false, skc, newSkolems); |
2184 |
26 |
Assert(newSkolems.size() == 2); |
2185 |
52 |
std::vector<Node> antecLen; |
2186 |
26 |
antecLen.push_back(nm->mkNode(GEQ, nckLenTerm, d_one)); |
2187 |
26 |
d_im.sendInference(antecLen, |
2188 |
|
antecLen, |
2189 |
|
conc, |
2190 |
|
InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_STRING_SPLIT, |
2191 |
|
false, |
2192 |
|
true); |
2193 |
26 |
return; |
2194 |
|
} |
2195 |
|
} |
2196 |
|
else |
2197 |
|
{ |
2198 |
|
// `x` and `y` have different lengths in the current context and they |
2199 |
|
// are both non-constants. We split them into parts that have the same |
2200 |
|
// lengths. |
2201 |
|
// |
2202 |
|
// len(x) > len(y) => x = k1 ++ k2 ^ len(k1) = len(y) |
2203 |
|
// len(y) > len(x) => y = k3 ++ k4 ^ len(k3) = len(x) |
2204 |
16 |
Trace("strings-solve") |
2205 |
8 |
<< "Non-Simple Case 1 : add lemmas " << std::endl; |
2206 |
8 |
SkolemCache* skc = d_termReg.getSkolemCache(); |
2207 |
|
|
2208 |
24 |
for (unsigned r = 0; r < 2; r++) |
2209 |
|
{ |
2210 |
32 |
Node ux = r == 0 ? x : y; |
2211 |
32 |
Node uy = r == 0 ? y : x; |
2212 |
32 |
Node uxLen = nm->mkNode(STRING_LENGTH, ux); |
2213 |
32 |
Node uyLen = nm->mkNode(STRING_LENGTH, uy); |
2214 |
|
// We always add the length constraint in the conclusion here |
2215 |
|
// because the skolem needs to have length `uyLen`. If we only assert |
2216 |
|
// that the skolem's length is greater or equal to one, we can end up |
2217 |
|
// in a loop: |
2218 |
|
// |
2219 |
|
// 1. Split: x = k1 ++ k2 ^ len(k1) >= 1 |
2220 |
|
// 2. Assume: k2 = "" |
2221 |
|
// 3. Deduce: x = k1 |
2222 |
|
// |
2223 |
|
// After step 3, `k1` is marked congruent because `x` is the older |
2224 |
|
// variable. So we get `x` in the normal form again. |
2225 |
32 |
std::vector<Node> newSkolems; |
2226 |
|
Node conc = |
2227 |
32 |
getDecomposeConclusion(ux, uyLen, false, skc, newSkolems); |
2228 |
16 |
Assert(newSkolems.size() == 2); |
2229 |
16 |
d_termReg.registerTermAtomic(newSkolems[1], LENGTH_GEQ_ONE); |
2230 |
32 |
std::vector<Node> antecLen; |
2231 |
16 |
antecLen.push_back(nm->mkNode(GT, uxLen, uyLen)); |
2232 |
16 |
d_im.sendInference(antecLen, |
2233 |
|
antecLen, |
2234 |
|
conc, |
2235 |
|
InferenceId::STRINGS_DEQ_DISL_STRINGS_SPLIT, |
2236 |
|
false, |
2237 |
|
true); |
2238 |
|
} |
2239 |
|
|
2240 |
8 |
return; |
2241 |
|
} |
2242 |
|
} |
2243 |
86 |
else if (d_state.areEqual(xLenTerm, yLenTerm)) |
2244 |
|
{ |
2245 |
|
// `x` and `y` have the same length in the current context. We split on |
2246 |
|
// whether they are actually equal. |
2247 |
|
// |
2248 |
|
// E.g. x ++ x' ++ ... != y ++ y' ++ ... ^ len(x) = len(y) ---> |
2249 |
|
// x = y v x != y |
2250 |
44 |
Assert(!d_state.areDisequal(x, y)); |
2251 |
44 |
if (d_im.sendSplit(x, y, InferenceId::STRINGS_DEQ_STRINGS_EQ, false)) |
2252 |
|
{ |
2253 |
44 |
return; |
2254 |
|
} |
2255 |
|
} |
2256 |
|
else |
2257 |
|
{ |
2258 |
|
// It is not known whether `x` and `y` have the same length in the |
2259 |
|
// current context. We split on whether they do. |
2260 |
|
// |
2261 |
|
// E.g. x ++ x' ++ ... != y ++ y' ++ ... ---> |
2262 |
|
// len(x) = len(y) v len(x) != len(y) |
2263 |
42 |
if (d_im.sendSplit(xLenTerm, yLenTerm, InferenceId::STRINGS_DEQ_LENS_EQ)) |
2264 |
|
{ |
2265 |
42 |
return; |
2266 |
|
} |
2267 |
|
} |
2268 |
|
} |
2269 |
|
Unreachable(); |
2270 |
|
} |
2271 |
|
|
2272 |
1677 |
bool CoreSolver::processReverseDeq(std::vector<Node>& nfi, |
2273 |
|
std::vector<Node>& nfj, |
2274 |
|
Node ni, |
2275 |
|
Node nj) |
2276 |
|
{ |
2277 |
|
// reverse normal form of i, j |
2278 |
1677 |
std::reverse(nfi.begin(), nfi.end()); |
2279 |
1677 |
std::reverse(nfj.begin(), nfj.end()); |
2280 |
|
|
2281 |
1677 |
size_t index = 0; |
2282 |
1677 |
bool ret = processSimpleDeq(nfi, nfj, ni, nj, index, true); |
2283 |
|
|
2284 |
|
// reverse normal form of i, j |
2285 |
1677 |
std::reverse(nfi.begin(), nfi.end()); |
2286 |
1677 |
std::reverse(nfj.begin(), nfj.end()); |
2287 |
|
|
2288 |
1677 |
return ret; |
2289 |
|
} |
2290 |
|
|
2291 |
2853 |
bool CoreSolver::processSimpleDeq(std::vector<Node>& nfi, |
2292 |
|
std::vector<Node>& nfj, |
2293 |
|
Node ni, |
2294 |
|
Node nj, |
2295 |
|
size_t& index, |
2296 |
|
bool isRev) |
2297 |
|
{ |
2298 |
5706 |
TypeNode stype = ni.getType(); |
2299 |
5706 |
Node emp = Word::mkEmptyWord(stype); |
2300 |
|
|
2301 |
2853 |
NormalForm& nfni = getNormalForm(ni); |
2302 |
2853 |
NormalForm& nfnj = getNormalForm(nj); |
2303 |
3807 |
while (index < nfi.size() || index < nfj.size()) |
2304 |
|
{ |
2305 |
3330 |
if (index >= nfi.size() || index >= nfj.size()) |
2306 |
|
{ |
2307 |
|
// We have reached the end of one of the normal forms. Note that this |
2308 |
|
// function only deals with two normal forms that have the same length, |
2309 |
|
// so the remainder of the longer normal form needs to be empty. This |
2310 |
|
// will lead to a conflict. |
2311 |
|
// |
2312 |
|
// E.g. px ++ x ++ ... != py ^ |
2313 |
|
// len(px ++ x ++ ...) = len(py) ---> |
2314 |
|
// x = "" ^ ... |
2315 |
4 |
Trace("strings-solve-debug") |
2316 |
2 |
<< "Disequality normalize empty" << std::endl; |
2317 |
|
// the antecedant |
2318 |
4 |
std::vector<Node> ant; |
2319 |
|
// the antecedant that is not explainable in this context |
2320 |
4 |
std::vector<Node> antn; |
2321 |
4 |
Node niLenTerm = d_state.getLengthExp(ni, ant, nfni.d_base); |
2322 |
4 |
Node njLenTerm = d_state.getLengthExp(nj, ant, nfnj.d_base); |
2323 |
|
// length is not guaranteed to hold |
2324 |
4 |
Node leq = niLenTerm.eqNode(njLenTerm); |
2325 |
2 |
ant.push_back(leq); |
2326 |
2 |
antn.push_back(leq); |
2327 |
2 |
ant.insert(ant.end(), nfni.d_exp.begin(), nfni.d_exp.end()); |
2328 |
2 |
ant.insert(ant.end(), nfnj.d_exp.begin(), nfnj.d_exp.end()); |
2329 |
4 |
std::vector<Node> cc; |
2330 |
2 |
std::vector<Node>& nfk = index >= nfi.size() ? nfj : nfi; |
2331 |
4 |
for (size_t k = index; k < nfk.size(); k++) |
2332 |
|
{ |
2333 |
2 |
cc.push_back(nfk[k].eqNode(emp)); |
2334 |
|
} |
2335 |
2 |
Node conc = cc.size() == 1 |
2336 |
2 |
? cc[0] |
2337 |
6 |
: NodeManager::currentNM()->mkNode(kind::AND, cc); |
2338 |
2 |
d_im.sendInference(ant, antn, conc, InferenceId::STRINGS_DEQ_NORM_EMP, isRev, true); |
2339 |
2 |
return true; |
2340 |
|
} |
2341 |
|
|
2342 |
|
// We have inferred that the normal forms up to position `index` are |
2343 |
|
// equivalent. Below, we refer to the components at the current position of |
2344 |
|
// the normal form as `x` and `y`. |
2345 |
|
// |
2346 |
|
// E.g. x ++ ... = y ++ ... |
2347 |
3544 |
Node x = nfi[index]; |
2348 |
3544 |
Node y = nfj[index]; |
2349 |
6656 |
Trace("strings-solve-debug") |
2350 |
3328 |
<< "...Processing(QED) " << x << " " << y << std::endl; |
2351 |
3589 |
if (d_state.areEqual(x, y)) |
2352 |
|
{ |
2353 |
|
// The normal forms have an equivalent term at `index` in the current |
2354 |
|
// context. We move to the next `index`. |
2355 |
|
// |
2356 |
|
// E.g. x ++ x' ++ ... != z ++ y' ++ ... ^ x = z |
2357 |
261 |
index++; |
2358 |
261 |
continue; |
2359 |
|
} |
2360 |
|
|
2361 |
3067 |
if (!x.isConst() || !y.isConst()) |
2362 |
|
{ |
2363 |
5288 |
std::vector<Node> lenExp; |
2364 |
5288 |
Node xLenTerm = d_state.getLength(x, lenExp); |
2365 |
5288 |
Node yLenTerm = d_state.getLength(y, lenExp); |
2366 |
2644 |
if (d_state.areEqual(xLenTerm, yLenTerm) && d_state.areDisequal(x, y)) |
2367 |
|
{ |
2368 |
|
// Either `x` or `y` is non-constant, the lengths are equal, and `x` |
2369 |
|
// and `y` are disequal in the current context. The disequality is |
2370 |
|
// satisfied and there is nothing else to do. |
2371 |
|
// |
2372 |
|
// E.g. x ++ x' ++ ... != y ++ y' ++ ... ^ len(x) = len(y) ^ x != y |
2373 |
900 |
Trace("strings-solve") |
2374 |
450 |
<< "Simple Case 2 : found equal length disequal sub strings " << x |
2375 |
450 |
<< " " << y << std::endl; |
2376 |
450 |
return true; |
2377 |
|
} |
2378 |
|
else |
2379 |
|
{ |
2380 |
|
// Either `x` or `y` is non-constant but it is not known whether they |
2381 |
|
// have the same length or are disequal. We bail out. |
2382 |
|
// |
2383 |
|
// E.g. x ++ x' ++ ... != y ++ y' ++ ... |
2384 |
2194 |
return false; |
2385 |
|
} |
2386 |
|
} |
2387 |
|
|
2388 |
|
// Below, we deal with the cases where both `x` and `y` are constant. |
2389 |
423 |
Assert(x.isConst() && y.isConst()); |
2390 |
423 |
size_t xLen = Word::getLength(x); |
2391 |
423 |
size_t yLen = Word::getLength(y); |
2392 |
423 |
size_t shortLen = std::min(xLen, yLen); |
2393 |
|
bool isSameFix = |
2394 |
423 |
isRev ? Word::rstrncmp(x, y, shortLen) : Word::strncmp(x, y, shortLen); |
2395 |
423 |
if (!isSameFix) |
2396 |
|
{ |
2397 |
|
// `x` and `y` are constants but do not share a prefix/suffix, thus |
2398 |
|
// satisfying the disequality. There is nothing else to do. |
2399 |
|
// |
2400 |
|
// E.g. "abc" ++ x' ++ ... != "d" ++ y' ++ ... |
2401 |
207 |
return true; |
2402 |
|
} |
2403 |
|
|
2404 |
|
// `x` and `y` are constants and share a prefix/suffix. We move the common |
2405 |
|
// prefix/suffix to a separate component in the normal form. |
2406 |
|
// |
2407 |
|
// E.g. "abc" ++ x' ++ ... != "ab" ++ y' ++ ... ---> |
2408 |
|
// "ab" ++ "c" ++ x' ++ ... != "ab" ++ y' ++ ... |
2409 |
432 |
Node nk = xLen < yLen ? x : y; |
2410 |
432 |
Node nl = xLen < yLen ? y : x; |
2411 |
432 |
Node remainderStr; |
2412 |
216 |
if (isRev) |
2413 |
|
{ |
2414 |
102 |
remainderStr = Word::substr(nl, 0, Word::getLength(nl) - shortLen); |
2415 |
204 |
Trace("strings-solve-debug-test") |
2416 |
102 |
<< "Rev. Break normal form of " << nl << " into " << nk << ", " |
2417 |
102 |
<< remainderStr << std::endl; |
2418 |
|
} |
2419 |
|
else |
2420 |
|
{ |
2421 |
114 |
remainderStr = Word::substr(nl, shortLen); |
2422 |
228 |
Trace("strings-solve-debug-test") |
2423 |
114 |
<< "Break normal form of " << nl << " into " << nk << ", " |
2424 |
114 |
<< remainderStr << std::endl; |
2425 |
|
} |
2426 |
216 |
if (xLen < yLen) |
2427 |
|
{ |
2428 |
108 |
nfj.insert(nfj.begin() + index + 1, remainderStr); |
2429 |
108 |
nfj[index] = nfi[index]; |
2430 |
|
} |
2431 |
|
else |
2432 |
|
{ |
2433 |
108 |
nfi.insert(nfi.begin() + index + 1, remainderStr); |
2434 |
108 |
nfi[index] = nfj[index]; |
2435 |
|
} |
2436 |
|
|
2437 |
216 |
index++; |
2438 |
|
} |
2439 |
|
return false; |
2440 |
|
} |
2441 |
|
|
2442 |
|
void CoreSolver::addNormalFormPair( Node n1, Node n2 ){ |
2443 |
|
if (n1>n2) |
2444 |
|
{ |
2445 |
|
addNormalFormPair(n2,n1); |
2446 |
|
return; |
2447 |
|
} |
2448 |
|
if( !isNormalFormPair( n1, n2 ) ){ |
2449 |
|
int index = 0; |
2450 |
|
NodeIntMap::const_iterator it = d_nfPairs.find(n1); |
2451 |
|
if (it != d_nfPairs.end()) |
2452 |
|
{ |
2453 |
|
index = (*it).second; |
2454 |
|
} |
2455 |
|
d_nfPairs[n1] = index + 1; |
2456 |
|
if( index<(int)d_nf_pairs_data[n1].size() ){ |
2457 |
|
d_nf_pairs_data[n1][index] = n2; |
2458 |
|
}else{ |
2459 |
|
d_nf_pairs_data[n1].push_back( n2 ); |
2460 |
|
} |
2461 |
|
Assert(isNormalFormPair(n1, n2)); |
2462 |
|
} else { |
2463 |
|
Trace("strings-nf-debug") << "Already a normal form pair " << n1 << " " << n2 << std::endl; |
2464 |
|
} |
2465 |
|
} |
2466 |
|
|
2467 |
18541 |
bool CoreSolver::isNormalFormPair( Node n1, Node n2 ) { |
2468 |
18541 |
if (n1>n2) |
2469 |
|
{ |
2470 |
5162 |
return isNormalFormPair(n2,n1); |
2471 |
|
} |
2472 |
|
//Trace("strings-debug") << "is normal form pair. " << n1 << " " << n2 << std::endl; |
2473 |
13379 |
NodeIntMap::const_iterator it = d_nfPairs.find(n1); |
2474 |
13379 |
if (it != d_nfPairs.end()) |
2475 |
|
{ |
2476 |
|
Assert(d_nf_pairs_data.find(n1) != d_nf_pairs_data.end()); |
2477 |
|
for( int i=0; i<(*it).second; i++ ){ |
2478 |
|
Assert(i < (int)d_nf_pairs_data[n1].size()); |
2479 |
|
if( d_nf_pairs_data[n1][i]==n2 ){ |
2480 |
|
return true; |
2481 |
|
} |
2482 |
|
} |
2483 |
|
} |
2484 |
13379 |
return false; |
2485 |
|
} |
2486 |
|
|
2487 |
11391 |
void CoreSolver::checkNormalFormsDeq() |
2488 |
|
{ |
2489 |
11391 |
eq::EqualityEngine* ee = d_state.getEqualityEngine(); |
2490 |
22301 |
std::map< Node, std::map< Node, bool > > processed; |
2491 |
|
|
2492 |
11391 |
const context::CDList<Node>& deqs = d_state.getDisequalityList(); |
2493 |
|
|
2494 |
|
//for each pair of disequal strings, must determine whether their lengths are equal or disequal |
2495 |
56029 |
for (const Node& eq : deqs) |
2496 |
|
{ |
2497 |
89276 |
Node n[2]; |
2498 |
133914 |
for( unsigned i=0; i<2; i++ ){ |
2499 |
89276 |
n[i] = ee->getRepresentative( eq[i] ); |
2500 |
|
} |
2501 |
44638 |
if( processed[n[0]].find( n[1] )==processed[n[0]].end() ){ |
2502 |
36036 |
processed[n[0]][n[1]] = true; |
2503 |
72072 |
Node lt[2]; |
2504 |
108108 |
for( unsigned i=0; i<2; i++ ){ |
2505 |
72072 |
EqcInfo* ei = d_state.getOrMakeEqcInfo(n[i], false); |
2506 |
72072 |
lt[i] = ei ? ei->d_lengthTerm : Node::null(); |
2507 |
72072 |
if( lt[i].isNull() ){ |
2508 |
|
lt[i] = eq[i]; |
2509 |
|
} |
2510 |
72072 |
lt[i] = NodeManager::currentNM()->mkNode( kind::STRING_LENGTH, lt[i] ); |
2511 |
|
} |
2512 |
36036 |
if (!d_state.areEqual(lt[0], lt[1]) && !d_state.areDisequal(lt[0], lt[1])) |
2513 |
|
{ |
2514 |
449 |
d_im.sendSplit(lt[0], lt[1], InferenceId::STRINGS_DEQ_LENGTH_SP); |
2515 |
|
} |
2516 |
|
} |
2517 |
|
} |
2518 |
|
|
2519 |
11391 |
if (d_im.hasProcessed()) |
2520 |
|
{ |
2521 |
|
// added splitting lemma above |
2522 |
305 |
return; |
2523 |
|
} |
2524 |
|
// otherwise, look at pairs of equivalence classes with equal lengths |
2525 |
21996 |
std::map<TypeNode, std::vector<std::vector<Node> > > colsT; |
2526 |
21996 |
std::map<TypeNode, std::vector<Node> > ltsT; |
2527 |
11086 |
d_state.separateByLength(d_strings_eqc, colsT, ltsT); |
2528 |
15809 |
for (std::pair<const TypeNode, std::vector<std::vector<Node> > >& ct : colsT) |
2529 |
|
{ |
2530 |
4899 |
std::vector<std::vector<Node> >& cols = ct.second; |
2531 |
36565 |
for( unsigned i=0; i<cols.size(); i++ ){ |
2532 |
31842 |
if (cols[i].size() > 1 && !d_im.hasPendingLemma()) |
2533 |
|
{ |
2534 |
6772 |
if (Trace.isOn("strings-solve")) |
2535 |
|
{ |
2536 |
|
Trace("strings-solve") << "- Verify disequalities are processed for " |
2537 |
|
<< cols[i][0] << ", normal form : "; |
2538 |
|
utils::printConcatTrace(getNormalForm(cols[i][0]).d_nf, "strings-solve"); |
2539 |
|
Trace("strings-solve") |
2540 |
|
<< "... #eql = " << cols[i].size() << std::endl; |
2541 |
|
} |
2542 |
|
//must ensure that normal forms are disequal |
2543 |
30656 |
for( unsigned j=0; j<cols[i].size(); j++ ){ |
2544 |
80405 |
for( unsigned k=(j+1); k<cols[i].size(); k++ ){ |
2545 |
|
//for strings that are disequal, but have the same length |
2546 |
56521 |
if (cols[i][j].isConst() && cols[i][k].isConst()) |
2547 |
|
{ |
2548 |
|
// if both are constants, they should be distinct, and its trivial |
2549 |
34026 |
Assert(cols[i][j] != cols[i][k]); |
2550 |
|
} |
2551 |
22495 |
else if (d_state.areDisequal(cols[i][j], cols[i][k])) |
2552 |
|
{ |
2553 |
7037 |
Assert(!d_state.isInConflict()); |
2554 |
7037 |
if (Trace.isOn("strings-solve")) |
2555 |
|
{ |
2556 |
|
Trace("strings-solve") << "- Compare " << cols[i][j] << ", nf "; |
2557 |
|
utils::printConcatTrace(getNormalForm(cols[i][j]).d_nf, |
2558 |
|
"strings-solve"); |
2559 |
|
Trace("strings-solve") << " against " << cols[i][k] << ", nf "; |
2560 |
|
utils::printConcatTrace(getNormalForm(cols[i][k]).d_nf, |
2561 |
|
"strings-solve"); |
2562 |
|
Trace("strings-solve") << "..." << std::endl; |
2563 |
|
} |
2564 |
7037 |
processDeq(cols[i][j], cols[i][k]); |
2565 |
7037 |
if (d_im.hasProcessed()) |
2566 |
|
{ |
2567 |
352 |
return; |
2568 |
|
} |
2569 |
|
} |
2570 |
|
} |
2571 |
|
} |
2572 |
|
} |
2573 |
|
} |
2574 |
|
} |
2575 |
|
} |
2576 |
|
|
2577 |
10486 |
void CoreSolver::checkLengthsEqc() { |
2578 |
53519 |
for (unsigned i = 0; i < d_strings_eqc.size(); i++) |
2579 |
|
{ |
2580 |
86066 |
TypeNode stype = d_strings_eqc[i].getType(); |
2581 |
43033 |
NormalForm& nfi = getNormalForm(d_strings_eqc[i]); |
2582 |
86066 |
Trace("strings-process-debug") |
2583 |
43033 |
<< "Process length constraints for " << d_strings_eqc[i] << std::endl; |
2584 |
|
// check if there is a length term for this equivalence class |
2585 |
43033 |
EqcInfo* ei = d_state.getOrMakeEqcInfo(d_strings_eqc[i], false); |
2586 |
86066 |
Node lt = ei ? ei->d_lengthTerm : Node::null(); |
2587 |
43033 |
if (lt.isNull()) |
2588 |
|
{ |
2589 |
|
Trace("strings-process-debug") |
2590 |
|
<< "No length term for eqc " << d_strings_eqc[i] << std::endl; |
2591 |
|
continue; |
2592 |
|
} |
2593 |
86066 |
Node llt = NodeManager::currentNM()->mkNode(kind::STRING_LENGTH, lt); |
2594 |
|
// now, check if length normalization has occurred |
2595 |
43033 |
if (ei->d_normalizedLength.get().isNull()) |
2596 |
|
{ |
2597 |
75264 |
Node nf = utils::mkNConcat(nfi.d_nf, stype); |
2598 |
37632 |
if (Trace.isOn("strings-process-debug")) |
2599 |
|
{ |
2600 |
|
Trace("strings-process-debug") |
2601 |
|
<< " normal form is " << nf << " from base " << nfi.d_base |
2602 |
|
<< std::endl; |
2603 |
|
Trace("strings-process-debug") << " normal form exp is: " << std::endl; |
2604 |
|
for (const Node& exp : nfi.d_exp) |
2605 |
|
{ |
2606 |
|
Trace("strings-process-debug") << " " << exp << std::endl; |
2607 |
|
} |
2608 |
|
} |
2609 |
|
|
2610 |
|
// if not, add the lemma |
2611 |
75264 |
std::vector<Node> ant; |
2612 |
37632 |
ant.insert(ant.end(), nfi.d_exp.begin(), nfi.d_exp.end()); |
2613 |
37632 |
ant.push_back(lt.eqNode(nfi.d_base)); |
2614 |
75264 |
Node lc = NodeManager::currentNM()->mkNode(kind::STRING_LENGTH, nf); |
2615 |
75264 |
Node lcr = Rewriter::rewrite(lc); |
2616 |
75264 |
Trace("strings-process-debug") |
2617 |
37632 |
<< "Rewrote length " << lc << " to " << lcr << std::endl; |
2618 |
37632 |
if (!d_state.areEqual(llt, lcr)) |
2619 |
|
{ |
2620 |
17762 |
Node eq = llt.eqNode(lc); |
2621 |
8881 |
ei->d_normalizedLength.set(eq); |
2622 |
8881 |
d_im.sendInference(ant, eq, InferenceId::STRINGS_LEN_NORM, false, true); |
2623 |
|
} |
2624 |
|
} |
2625 |
|
} |
2626 |
10486 |
} |
2627 |
|
|
2628 |
3054 |
bool CoreSolver::processInferInfo(CoreInferInfo& cii) |
2629 |
|
{ |
2630 |
3054 |
InferInfo& ii = cii.d_infer; |
2631 |
|
// rewrite the conclusion, ensure non-trivial |
2632 |
6108 |
Node concr = Rewriter::rewrite(ii.d_conc); |
2633 |
|
|
2634 |
3054 |
if (concr == d_true) |
2635 |
|
{ |
2636 |
|
// conclusion rewrote to true |
2637 |
|
return false; |
2638 |
|
} |
2639 |
|
// process the state change to this solver |
2640 |
3054 |
if (!cii.d_nfPair[0].isNull()) |
2641 |
|
{ |
2642 |
|
Assert(!cii.d_nfPair[1].isNull()); |
2643 |
|
addNormalFormPair(cii.d_nfPair[0], cii.d_nfPair[1]); |
2644 |
|
} |
2645 |
|
// send phase requirements |
2646 |
4040 |
for (const std::pair<const Node, bool>& pp : cii.d_pendingPhase) |
2647 |
|
{ |
2648 |
1972 |
Node ppr = Rewriter::rewrite(pp.first); |
2649 |
986 |
d_im.addPendingPhaseRequirement(ppr, pp.second); |
2650 |
|
} |
2651 |
|
|
2652 |
|
// send the inference, which is a lemma |
2653 |
3054 |
d_im.sendInference(ii, true); |
2654 |
|
|
2655 |
3054 |
return true; |
2656 |
|
} |
2657 |
|
|
2658 |
|
} // namespace strings |
2659 |
|
} // namespace theory |
2660 |
29514 |
} // namespace cvc5 |