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