mirror of
https://github.com/Z3Prover/z3
synced 2026-06-19 23:26:30 +00:00
Use lookahead for regex decomposition
Make snode const
This commit is contained in:
parent
671dfedebe
commit
be627007e1
22 changed files with 1868 additions and 2066 deletions
|
|
@ -52,7 +52,7 @@ namespace euf {
|
|||
}
|
||||
|
||||
unsigned enode_concat_hash::operator()(enode* n) const {
|
||||
snode* sn = sg.find(n->get_expr());
|
||||
snode const* sn = sg.find(n->get_expr());
|
||||
if (sn && sn->has_cached_hash())
|
||||
return sn->assoc_hash();
|
||||
if (!is_any_concat(n, seq))
|
||||
|
|
@ -70,8 +70,8 @@ namespace euf {
|
|||
if (!is_any_concat(a, seq) || !is_any_concat(b, seq))
|
||||
return false;
|
||||
// fast-path: snode length check (O(1), avoids leaf allocation)
|
||||
snode* sa = sg.find(a->get_expr());
|
||||
snode* sb = sg.find(b->get_expr());
|
||||
snode const* sa = sg.find(a->get_expr());
|
||||
snode const* sb = sg.find(b->get_expr());
|
||||
if (sa && sb && sa->length() != sb->length())
|
||||
return false;
|
||||
// fast-path: cached associativity hash (O(1))
|
||||
|
|
|
|||
|
|
@ -154,8 +154,8 @@ namespace euf {
|
|||
|
||||
case snode_kind::s_concat: {
|
||||
SASSERT(n->num_args() == 2);
|
||||
snode* l = n->arg(0);
|
||||
snode* r = n->arg(1);
|
||||
const snode* l = n->arg(0);
|
||||
const snode* r = n->arg(1);
|
||||
n->m_ground = l->is_ground() && r->is_ground();
|
||||
n->m_regex_free = l->is_regex_free() && r->is_regex_free();
|
||||
n->m_is_classical = l->is_classical() && r->is_classical();
|
||||
|
|
@ -171,7 +171,7 @@ namespace euf {
|
|||
// NSB review: SASSERT(n->num_args() == 2); and simplify code
|
||||
// NSB review: is this the correct definition of ground what about the exponent?
|
||||
SASSERT(n->num_args() >= 1);
|
||||
snode* base = n->arg(0);
|
||||
snode const* base = n->arg(0);
|
||||
n->m_ground = base->is_ground();
|
||||
n->m_regex_free = base->is_regex_free();
|
||||
n->m_is_classical = base->is_classical();
|
||||
|
|
@ -336,8 +336,8 @@ namespace euf {
|
|||
n->m_hash_matrix[1][1] = 1;
|
||||
}
|
||||
else if (n->is_concat()) {
|
||||
snode* l = n->arg(0);
|
||||
snode* r = n->arg(1);
|
||||
snode const* l = n->arg(0);
|
||||
snode const* r = n->arg(1);
|
||||
if (l->has_cached_hash() && r->has_cached_hash()) {
|
||||
// 2x2 matrix multiplication: M(L) * M(R)
|
||||
n->m_hash_matrix[0][0] = l->m_hash_matrix[0][0] * r->m_hash_matrix[0][0] + l->m_hash_matrix[0][1] * r->m_hash_matrix[1][0];
|
||||
|
|
@ -357,9 +357,9 @@ namespace euf {
|
|||
}
|
||||
}
|
||||
|
||||
snode *sgraph::mk_snode(expr *e, snode_kind k, unsigned num_args, snode *const *args) {
|
||||
snode* sgraph::mk_snode(expr *e, const snode_kind k, const unsigned num_args, snode const** args) {
|
||||
SASSERT(e);
|
||||
unsigned id = m_nodes.size();
|
||||
const unsigned id = m_nodes.size();
|
||||
snode *n = snode::mk(m_region, e, k, id, num_args, args);
|
||||
compute_metadata(n);
|
||||
compute_hash_matrix(n);
|
||||
|
|
@ -379,10 +379,10 @@ namespace euf {
|
|||
return n;
|
||||
}
|
||||
|
||||
snode* sgraph::mk(expr* e) {
|
||||
snode const* sgraph::mk(expr* e) {
|
||||
SASSERT(e);
|
||||
expr_ref _e(e, m); // pin locally to not clash with character creation, never needed if we use mk_enode early.
|
||||
snode* n = find(e);
|
||||
snode const* n = find(e);
|
||||
if (n)
|
||||
return n;
|
||||
|
||||
|
|
@ -390,7 +390,7 @@ namespace euf {
|
|||
// so that Nielsen graph can do prefix matching on them
|
||||
zstring s;
|
||||
if (m_seq.str.is_string(e, s) && !s.empty()) {
|
||||
snode* result = mk_char(s[s.length() - 1]);
|
||||
snode const* result = mk_char(s[s.length() - 1]);
|
||||
for (unsigned i = s.length() - 1; i-- > 0; )
|
||||
result = mk_concat(mk_char(s[i]), result);
|
||||
// register the original string expression as an alias
|
||||
|
|
@ -402,13 +402,13 @@ namespace euf {
|
|||
return result;
|
||||
}
|
||||
|
||||
snode_kind k = classify(e);
|
||||
const snode_kind k = classify(e);
|
||||
|
||||
if (!is_app(e))
|
||||
return mk_snode(e, k, 0, nullptr);
|
||||
|
||||
app* a = to_app(e);
|
||||
unsigned arity = a->get_num_args();
|
||||
const unsigned arity = a->get_num_args();
|
||||
|
||||
// recursively register children
|
||||
// for seq/re children, create classified snodes
|
||||
|
|
@ -416,14 +416,14 @@ namespace euf {
|
|||
snode_vector child_nodes;
|
||||
for (unsigned i = 0; i < arity; ++i) {
|
||||
expr* ch = a->get_arg(i);
|
||||
snode* cn = mk(ch);
|
||||
snode const* cn = mk(ch);
|
||||
child_nodes.push_back(cn);
|
||||
}
|
||||
|
||||
return mk_snode(e, k, child_nodes.size(), child_nodes.data());
|
||||
}
|
||||
|
||||
snode* sgraph::find(expr* e) const {
|
||||
snode const* sgraph::find(expr* e) const {
|
||||
if (!e)
|
||||
return nullptr;
|
||||
unsigned eid = e->get_id();
|
||||
|
|
@ -456,13 +456,13 @@ namespace euf {
|
|||
if (num_scopes == 0)
|
||||
return;
|
||||
SASSERT(num_scopes <= m_num_scopes);
|
||||
unsigned new_lvl = m_num_scopes - num_scopes;
|
||||
unsigned old_sz = m_scopes[new_lvl];
|
||||
const unsigned new_lvl = m_num_scopes - num_scopes;
|
||||
const unsigned old_sz = m_scopes[new_lvl];
|
||||
|
||||
for (unsigned i = m_nodes.size(); i-- > old_sz; ) {
|
||||
snode* n = m_nodes[i];
|
||||
snode const* n = m_nodes[i];
|
||||
if (n->get_expr()) {
|
||||
unsigned eid = n->get_expr()->get_id();
|
||||
const unsigned eid = n->get_expr()->get_id();
|
||||
if (eid < m_expr2snode.size())
|
||||
m_expr2snode[eid] = nullptr;
|
||||
}
|
||||
|
|
@ -470,9 +470,9 @@ namespace euf {
|
|||
m_nodes.shrink(old_sz);
|
||||
m_scopes.shrink(new_lvl);
|
||||
// undo alias entries (string constant decompositions)
|
||||
unsigned alias_old = m_alias_trail_lim[new_lvl];
|
||||
const unsigned alias_old = m_alias_trail_lim[new_lvl];
|
||||
for (unsigned i = m_alias_trail.size(); i-- > alias_old; ) {
|
||||
unsigned eid = m_alias_trail[i];
|
||||
const unsigned eid = m_alias_trail[i];
|
||||
if (eid < m_expr2snode.size())
|
||||
m_expr2snode[eid] = nullptr;
|
||||
}
|
||||
|
|
@ -482,23 +482,23 @@ namespace euf {
|
|||
m_egraph.pop(num_scopes);
|
||||
}
|
||||
|
||||
snode* sgraph::mk_var(symbol const& name, sort* s) {
|
||||
expr_ref e(m.mk_const(name, s), m);
|
||||
snode const* sgraph::mk_var(symbol const& name, sort* s) {
|
||||
const expr_ref e(m.mk_const(name, s), m);
|
||||
return mk(e);
|
||||
}
|
||||
|
||||
snode* sgraph::mk_char(unsigned ch) {
|
||||
expr_ref c(m_seq.str.mk_char(ch), m);
|
||||
expr_ref u(m_seq.str.mk_unit(c), m);
|
||||
snode const* sgraph::mk_char(unsigned ch) {
|
||||
const expr_ref c(m_seq.str.mk_char(ch), m);
|
||||
const expr_ref u(m_seq.str.mk_unit(c), m);
|
||||
return mk(u);
|
||||
}
|
||||
|
||||
snode* sgraph::mk_empty_seq(sort* s) {
|
||||
expr_ref e(m_seq.str.mk_empty(s), m);
|
||||
snode const* sgraph::mk_empty_seq(sort* s) {
|
||||
const expr_ref e(m_seq.str.mk_empty(s), m);
|
||||
return mk(e);
|
||||
}
|
||||
|
||||
snode* sgraph::mk_concat(snode* a, snode* b) {
|
||||
snode const* sgraph::mk_concat(snode const* a, snode const* b) {
|
||||
if (a->is_empty()) return b;
|
||||
if (b->is_empty()) return a;
|
||||
if (m_seq.is_re(a->get_expr()))
|
||||
|
|
@ -506,29 +506,29 @@ namespace euf {
|
|||
return mk(expr_ref(m_seq.str.mk_concat(a->get_expr(), b->get_expr()), m));
|
||||
}
|
||||
|
||||
snode* sgraph::drop_first(snode* n) {
|
||||
snode const* sgraph::drop_first(snode const* n) {
|
||||
if (n->is_empty() || n->is_token())
|
||||
return mk_empty_seq(n->get_sort());
|
||||
SASSERT(n->is_concat());
|
||||
snode* l = n->arg(0);
|
||||
snode* r = n->arg(1);
|
||||
snode const* l = n->arg(0);
|
||||
snode const* r = n->arg(1);
|
||||
if (l->is_token() || l->is_empty())
|
||||
return r;
|
||||
return mk_concat(drop_first(l), r);
|
||||
}
|
||||
|
||||
snode* sgraph::drop_last(snode* n) {
|
||||
snode const* sgraph::drop_last(snode const* n) {
|
||||
if (n->is_empty() || n->is_token())
|
||||
return mk_empty_seq(n->get_sort());
|
||||
SASSERT(n->is_concat());
|
||||
snode* l = n->arg(0);
|
||||
snode* r = n->arg(1);
|
||||
snode const* l = n->arg(0);
|
||||
snode const* r = n->arg(1);
|
||||
if (r->is_token() || r->is_empty())
|
||||
return l;
|
||||
return mk_concat(l, drop_last(r));
|
||||
}
|
||||
|
||||
snode* sgraph::drop_left(snode* n, unsigned count) {
|
||||
snode const* sgraph::drop_left(snode const* n, unsigned count) {
|
||||
if (count == 0 || n->is_empty()) return n;
|
||||
if (count >= n->length()) return mk_empty_seq(n->get_sort());
|
||||
SASSERT(n->is_concat());
|
||||
|
|
@ -538,7 +538,7 @@ namespace euf {
|
|||
return drop_left(n->arg(1), count - left_len);
|
||||
}
|
||||
|
||||
snode* sgraph::drop_right(snode* n, unsigned count) {
|
||||
snode const* sgraph::drop_right(snode const * n, unsigned count) {
|
||||
if (count == 0 || n->is_empty()) return n;
|
||||
if (count >= n->length()) return mk_empty_seq(n->get_sort());
|
||||
SASSERT(n->is_concat());
|
||||
|
|
@ -548,7 +548,7 @@ namespace euf {
|
|||
return drop_right(n->arg(0), count - right_len);
|
||||
}
|
||||
|
||||
snode* sgraph::subst(snode* n, snode* var, snode* replacement) {
|
||||
snode const* sgraph::subst(snode const* n, snode const* var, snode const* replacement) {
|
||||
if (n == var)
|
||||
return replacement;
|
||||
if (n->is_empty() || n->is_char())
|
||||
|
|
@ -653,7 +653,7 @@ namespace euf {
|
|||
return l_false;
|
||||
}
|
||||
|
||||
lbool sgraph::re_nullable(snode* re) {
|
||||
lbool sgraph::re_nullable(snode const* re) {
|
||||
if (!re)
|
||||
return l_undef;
|
||||
// Projection-free regexes: defer to the standard regex info.
|
||||
|
|
@ -693,7 +693,7 @@ namespace euf {
|
|||
}
|
||||
}
|
||||
|
||||
snode* sgraph::deriv_proj(snode* re, expr* ch) {
|
||||
snode const* sgraph::deriv_proj(snode const* re, expr* ch) {
|
||||
SASSERT(re && re->get_expr());
|
||||
expr* re_expr = re->get_expr();
|
||||
sort* re_sort = re_expr->get_sort();
|
||||
|
|
@ -787,7 +787,7 @@ namespace euf {
|
|||
// else ⊥. The gate is on the *current* state (paper §3.3).
|
||||
if (!m_proj_oracle || !m_proj_oracle->projection_state_in_Q(state, nu))
|
||||
return mk(m_seq.re.mk_empty(re_sort));
|
||||
snode* dstate = deriv_proj(re->arg(0), ch); // arg(0) ≡ state
|
||||
snode const* dstate = deriv_proj(re->arg(0), ch); // arg(0) ≡ state
|
||||
if (!dstate || dstate->is_fail() || m_seq.re.is_empty(dstate->get_expr()))
|
||||
return mk(m_seq.re.mk_empty(re_sort));
|
||||
// δ(state) may be concrete (one state) or an ite-term (symbolic
|
||||
|
|
@ -797,42 +797,42 @@ namespace euf {
|
|||
case snode_kind::s_ite: {
|
||||
// ite-structured residual (from a symbolic-character derivative):
|
||||
// δ_a(ite(c, th, el)) = ite(c, δ_a(th), δ_a(el)).
|
||||
snode* dth = deriv_proj(re->arg(1), ch);
|
||||
snode* del = deriv_proj(re->arg(2), ch);
|
||||
snode const* dth = deriv_proj(re->arg(1), ch);
|
||||
snode const* del = deriv_proj(re->arg(2), ch);
|
||||
return mk(expr_ref(m.mk_ite(re->arg(0)->get_expr(), dth->get_expr(), del->get_expr()), m));
|
||||
}
|
||||
case snode_kind::s_complement: {
|
||||
snode* d = deriv_proj(re->arg(0), ch);
|
||||
snode const* d = deriv_proj(re->arg(0), ch);
|
||||
return mk(expr_ref(mk_compl(d->get_expr()), m));
|
||||
}
|
||||
case snode_kind::s_intersect: {
|
||||
snode* d0 = deriv_proj(re->arg(0), ch);
|
||||
snode* d1 = deriv_proj(re->arg(1), ch);
|
||||
snode const* d0 = deriv_proj(re->arg(0), ch);
|
||||
snode const* d1 = deriv_proj(re->arg(1), ch);
|
||||
return mk(expr_ref(mk_inter(d0->get_expr(), d1->get_expr()), m));
|
||||
}
|
||||
case snode_kind::s_union: {
|
||||
snode* d0 = deriv_proj(re->arg(0), ch);
|
||||
snode* d1 = deriv_proj(re->arg(1), ch);
|
||||
snode const* d0 = deriv_proj(re->arg(0), ch);
|
||||
snode const* d1 = deriv_proj(re->arg(1), ch);
|
||||
return mk(expr_ref(mk_union(d0->get_expr(), d1->get_expr()), m));
|
||||
}
|
||||
case snode_kind::s_concat: {
|
||||
// δ_a(R·S) = δ_a(R)·S ⊔ (nullable(R) ? δ_a(S) : ∅)
|
||||
snode* d0 = deriv_proj(re->arg(0), ch);
|
||||
snode const* d0 = deriv_proj(re->arg(0), ch);
|
||||
expr* head = mk_concat(d0->get_expr(), re->arg(1)->get_expr());
|
||||
if (re_nullable(re->arg(0)) == l_true) {
|
||||
snode* d1 = deriv_proj(re->arg(1), ch);
|
||||
snode const* d1 = deriv_proj(re->arg(1), ch);
|
||||
head = mk_union(head, d1->get_expr());
|
||||
}
|
||||
return mk(expr_ref(head, m));
|
||||
}
|
||||
case snode_kind::s_star: {
|
||||
// δ_a(R*) = δ_a(R)·R*
|
||||
snode* d = deriv_proj(re->arg(0), ch);
|
||||
snode const* d = deriv_proj(re->arg(0), ch);
|
||||
return mk(expr_ref(mk_concat(d->get_expr(), re_expr), m));
|
||||
}
|
||||
case snode_kind::s_plus: {
|
||||
// δ_a(R+) = δ_a(R)·R*
|
||||
snode* d = deriv_proj(re->arg(0), ch);
|
||||
snode const* d = deriv_proj(re->arg(0), ch);
|
||||
expr_ref star(m_seq.re.mk_star(re->arg(0)->get_expr()), m);
|
||||
return mk(expr_ref(mk_concat(d->get_expr(), star), m));
|
||||
}
|
||||
|
|
@ -843,7 +843,7 @@ namespace euf {
|
|||
}
|
||||
}
|
||||
|
||||
snode* sgraph::brzozowski_deriv(snode* re, snode* elem) {
|
||||
snode const* sgraph::brzozowski_deriv(snode const* re, snode const* elem) {
|
||||
expr* re_expr = re->get_expr();
|
||||
expr* elem_expr = elem->get_expr();
|
||||
SASSERT(re_expr);
|
||||
|
|
@ -900,23 +900,24 @@ namespace euf {
|
|||
// derivative states get distinct snode ids and BFS emptiness checks
|
||||
// fail to deduplicate, exploring an exploded state space.
|
||||
if (re->has_projection()) {
|
||||
snode* d = deriv_proj(re, elem_expr);
|
||||
snode const* d = deriv_proj(re, elem_expr);
|
||||
expr_ref e(d->get_expr(), m);
|
||||
th_rewriter trw(m);
|
||||
trw(e);
|
||||
return mk(e);
|
||||
}
|
||||
|
||||
expr_ref result = m_rewriter.mk_derivative(elem_expr, re_expr);
|
||||
std::cout << "Derivative of " << mk_pp(re_expr, m) << "\nwith respect to " << mk_pp(elem_expr, m) << std::endl;
|
||||
const expr_ref result = m_rewriter.mk_derivative(elem_expr, re_expr);
|
||||
SASSERT(result);
|
||||
return mk(result);
|
||||
}
|
||||
|
||||
bool sgraph::are_unit_distinct(snode* a, snode* b) const {
|
||||
bool sgraph::are_unit_distinct(snode const* a, snode const* b) const {
|
||||
return a->is_char_or_unit() && b->is_char_or_unit() && m.are_distinct(a->get_expr(), b->get_expr());
|
||||
}
|
||||
|
||||
void sgraph::collect_re_predicates(snode* re, expr_ref_vector& preds) {
|
||||
void sgraph::collect_re_predicates(snode const* re, expr_ref_vector& preds) {
|
||||
if (!re)
|
||||
return;
|
||||
expr* e = re->get_expr();
|
||||
|
|
@ -983,14 +984,14 @@ namespace euf {
|
|||
}
|
||||
}
|
||||
|
||||
void sgraph::compute_minterms(snode* re, snode_vector& minterms) {
|
||||
void sgraph::compute_minterms(snode const* re, snode_vector& minterms) {
|
||||
expr_ref_vector preds(m);
|
||||
collect_re_predicates(re, preds);
|
||||
|
||||
unsigned max_c = m_seq.max_char();
|
||||
const unsigned max_c = m_seq.max_char();
|
||||
|
||||
if (preds.empty()) {
|
||||
expr_ref fc(m_seq.re.mk_full_char(m_str_sort), m);
|
||||
const expr_ref fc(m_seq.re.mk_full_char(m_str_sort), m);
|
||||
minterms.push_back(mk(fc));
|
||||
return;
|
||||
}
|
||||
|
|
@ -1106,7 +1107,7 @@ namespace euf {
|
|||
}
|
||||
return "?";
|
||||
};
|
||||
for (snode* n : m_nodes) {
|
||||
for (snode const* n : m_nodes) {
|
||||
out << "snode[" << n->id() << "] "
|
||||
<< kind_str(n->kind())
|
||||
<< " level=" << n->level()
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ namespace euf {
|
|||
expr_ref_vector m_pin;
|
||||
|
||||
// maps expression id to snode
|
||||
ptr_vector<snode> m_expr2snode;
|
||||
ptr_vector<snode const> m_expr2snode;
|
||||
|
||||
// trail of alias entries (string constant → decomposed snode) for pop
|
||||
unsigned_vector m_alias_trail; // expression ids
|
||||
|
|
@ -124,11 +124,11 @@ namespace euf {
|
|||
// Oracle answering "state ∈ Q_nu" for projection derivatives. Not owned.
|
||||
projection_oracle* m_proj_oracle = nullptr;
|
||||
|
||||
snode* mk_snode(expr* e, snode_kind k, unsigned num_args, snode* const* args);
|
||||
snode* mk_snode(expr* e, snode_kind k, unsigned num_args, snode const** args);
|
||||
snode_kind classify(expr* e) const;
|
||||
void compute_metadata(snode* n);
|
||||
void compute_hash_matrix(snode* n);
|
||||
void collect_re_predicates(snode* re, expr_ref_vector& preds);
|
||||
void collect_re_predicates(snode const* re, expr_ref_vector& preds);
|
||||
|
||||
public:
|
||||
sgraph(ast_manager& m, egraph& eg, bool add_plugin = true);
|
||||
|
|
@ -140,10 +140,10 @@ namespace euf {
|
|||
egraph const& get_egraph() const { return m_egraph; }
|
||||
|
||||
// register an expression and return its snode
|
||||
snode* mk(expr* e);
|
||||
snode const* mk(expr* e);
|
||||
|
||||
// lookup an already-registered expression
|
||||
snode* find(expr* e) const;
|
||||
snode const* find(expr* e) const;
|
||||
|
||||
// register expression in both sgraph and egraph
|
||||
enode* mk_enode(expr* e);
|
||||
|
|
@ -151,27 +151,27 @@ namespace euf {
|
|||
sort* get_str_sort() const { return m_str_sort; }
|
||||
|
||||
// return true if a, b are of the same length and distinct
|
||||
bool are_unit_distinct(snode *a, snode *b) const;
|
||||
bool are_unit_distinct(snode const* a, snode const* b) const;
|
||||
|
||||
// factory methods for creating snodes with corresponding expressions
|
||||
snode* mk_var(symbol const& name, sort* s);
|
||||
snode* mk_char(unsigned ch);
|
||||
snode *mk_empty_seq(sort *s);
|
||||
snode* mk_concat(snode* a, snode* b);
|
||||
snode const* mk_var(symbol const& name, sort* s);
|
||||
snode const* mk_char(unsigned ch);
|
||||
snode const *mk_empty_seq(sort *s);
|
||||
snode const* mk_concat(snode const* a, snode const* b);
|
||||
|
||||
// drop operations: remove tokens from the front/back of a concat tree
|
||||
snode* drop_first(snode* n);
|
||||
snode* drop_last(snode* n);
|
||||
snode* drop_left(snode* n, unsigned count);
|
||||
snode* drop_right(snode* n, unsigned count);
|
||||
snode const* drop_first(snode const* n);
|
||||
snode const* drop_last(snode const* n);
|
||||
snode const* drop_left(snode const* n, unsigned count);
|
||||
snode const* drop_right(const snode* n, unsigned count);
|
||||
|
||||
// substitution: replace all occurrences of var in n by replacement
|
||||
snode* subst(snode* n, snode* var, snode* replacement);
|
||||
snode const* subst(snode const* n, snode const* var, snode const* replacement);
|
||||
|
||||
// Brzozowski derivative of regex re with respect to element elem.
|
||||
// allowed_range can explicitly provide a concrete character or range to use
|
||||
// for deriving symbolic variables.
|
||||
snode* brzozowski_deriv(snode* re, snode* elem);
|
||||
snode const* brzozowski_deriv(snode const* re, snode const* elem);
|
||||
|
||||
// Register the oracle consulted when deriving projection operators.
|
||||
// Passing nullptr unregisters. Not owned.
|
||||
|
|
@ -190,19 +190,19 @@ namespace euf {
|
|||
expr_ref wrap_proj(expr* e, expr* root, unsigned nu);
|
||||
// Projection-aware Brzozowski derivative w.r.t. a character expr
|
||||
// (concrete or symbolic).
|
||||
snode* deriv_proj(snode* re, expr* ch);
|
||||
snode const* deriv_proj(snode const* re, expr* ch);
|
||||
|
||||
// Projection-aware nullability: lifts re.get_info().nullable to regexes
|
||||
// that may contain projection operators. Returns l_true / l_false
|
||||
// (l_undef only if an underlying projection-free subterm is undef).
|
||||
lbool re_nullable(snode* re);
|
||||
lbool re_nullable(snode const* re);
|
||||
|
||||
// Decode a character expression that may be represented as a const-char,
|
||||
// a unit string containing a const-char, or a one-character string literal.
|
||||
bool decode_re_char(expr* ex, unsigned& out) const;
|
||||
|
||||
// compute minterms (character class partition) from a regex
|
||||
void compute_minterms(snode* re, snode_vector& minterms);
|
||||
void compute_minterms(snode const* re, snode_vector& minterms);
|
||||
|
||||
// scope management for backtracking
|
||||
void push();
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ namespace euf {
|
|||
class sgraph;
|
||||
class snode;
|
||||
|
||||
typedef ptr_vector<snode> snode_vector;
|
||||
typedef ptr_vector<snode const> snode_vector;
|
||||
|
||||
enum class snode_kind {
|
||||
s_empty, // empty string (OP_SEQ_EMPTY or empty string constant)
|
||||
|
|
@ -85,7 +85,7 @@ namespace euf {
|
|||
// all zeros means not cached, non-zero means cached
|
||||
unsigned m_hash_matrix[2][2] = {{0, 0}, {0, 0}};
|
||||
|
||||
snode *m_args[0]; // variable-length array, allocated via get_snode_size(num_args)
|
||||
snode const* m_args[0]; // variable-length array, allocated via get_snode_size(num_args)
|
||||
|
||||
friend class sgraph;
|
||||
|
||||
|
|
@ -93,9 +93,9 @@ namespace euf {
|
|||
return sizeof(snode) + num_args * sizeof(snode *);
|
||||
}
|
||||
|
||||
static snode *mk(region &r, expr *e, snode_kind k, unsigned id, unsigned num_args, snode *const *args) {
|
||||
static snode *mk(region &r, expr *e, snode_kind k, unsigned id, unsigned num_args, snode const** args) {
|
||||
void *mem = r.allocate(get_snode_size(num_args));
|
||||
snode *n = new (mem) snode();
|
||||
snode * n = new (mem) snode();
|
||||
n->m_expr = e;
|
||||
n->m_kind = k;
|
||||
n->m_id = id;
|
||||
|
|
@ -107,7 +107,7 @@ namespace euf {
|
|||
}
|
||||
|
||||
public:
|
||||
expr *get_expr() const {
|
||||
expr* get_expr() const {
|
||||
return m_expr; // assumed to be non-null
|
||||
}
|
||||
snode_kind kind() const {
|
||||
|
|
@ -121,12 +121,12 @@ namespace euf {
|
|||
return m_num_args;
|
||||
}
|
||||
|
||||
snode* arg(const unsigned i) const {
|
||||
snode const* arg(const unsigned i) const {
|
||||
SASSERT(i < m_num_args);
|
||||
return m_args[i];
|
||||
}
|
||||
|
||||
snode* arg0() const {
|
||||
snode const* arg0() const {
|
||||
return arg(0);
|
||||
}
|
||||
|
||||
|
|
@ -137,11 +137,11 @@ namespace euf {
|
|||
// O(1) amortized per token, O(tree height) auxiliary memory.
|
||||
class token_iterator {
|
||||
snode_vector m_stack; // pending subtrees, top == back()
|
||||
snode *m_current = nullptr; // current token, nullptr == end
|
||||
snode const* m_current = nullptr; // current token, nullptr == end
|
||||
|
||||
void advance() {
|
||||
while (!m_stack.empty()) {
|
||||
snode *n = m_stack.back();
|
||||
snode const* n = m_stack.back();
|
||||
m_stack.pop_back();
|
||||
if (n->is_concat()) {
|
||||
m_stack.push_back(n->arg(1));
|
||||
|
|
@ -168,7 +168,7 @@ namespace euf {
|
|||
advance();
|
||||
}
|
||||
|
||||
snode *operator*() const { return m_current; }
|
||||
snode const* operator*() const { return m_current; }
|
||||
token_iterator &operator++() { advance(); return *this; }
|
||||
token_iterator operator++(int) { token_iterator t = *this; advance(); return t; }
|
||||
bool operator==(token_iterator const &o) const { return m_current == o.m_current; }
|
||||
|
|
@ -286,7 +286,7 @@ namespace euf {
|
|||
if (!is_concat())
|
||||
return false;
|
||||
str.reset();
|
||||
for (const snode* c : *this) {
|
||||
for (snode const* c : *this) {
|
||||
unsigned val;
|
||||
if (!c->is_char())
|
||||
return false;
|
||||
|
|
@ -309,34 +309,34 @@ namespace euf {
|
|||
}
|
||||
}
|
||||
|
||||
sort *get_sort() const {
|
||||
sort* get_sort() const {
|
||||
return m_expr ? m_expr->get_sort() : nullptr;
|
||||
}
|
||||
|
||||
// analogous to ZIPT's Str.First / Str.Last
|
||||
snode const *first() const {
|
||||
snode const *s = this;
|
||||
snode const* s = this;
|
||||
while (s->is_concat())
|
||||
s = s->arg(0);
|
||||
return s;
|
||||
}
|
||||
|
||||
snode const *last() const {
|
||||
snode const *s = this;
|
||||
snode const* s = this;
|
||||
while (s->is_concat())
|
||||
s = s->arg(1);
|
||||
return s;
|
||||
}
|
||||
|
||||
snode *first() {
|
||||
snode *s = this;
|
||||
snode const* first() {
|
||||
snode const* s = this;
|
||||
while (s->is_concat())
|
||||
s = s->arg(0);
|
||||
return s;
|
||||
}
|
||||
|
||||
snode *last() {
|
||||
snode *s = this;
|
||||
snode const* last() {
|
||||
snode const* s = this;
|
||||
while (s->is_concat())
|
||||
s = s->arg(1);
|
||||
return s;
|
||||
|
|
@ -349,7 +349,7 @@ namespace euf {
|
|||
arg(1)->collect_tokens(tokens);
|
||||
}
|
||||
else if (!is_empty())
|
||||
tokens.push_back(const_cast<snode *>(this));
|
||||
tokens.push_back(this);
|
||||
}
|
||||
|
||||
snode_vector collect_tokens() const {
|
||||
|
|
@ -360,7 +360,7 @@ namespace euf {
|
|||
|
||||
// access the i-th token (0-based, left-to-right order)
|
||||
// returns nullptr if i >= length()
|
||||
snode *at(unsigned i) const {
|
||||
snode const* at(const unsigned i) const {
|
||||
if (is_concat()) {
|
||||
unsigned left_len = arg(0)->length();
|
||||
if (i < left_len)
|
||||
|
|
@ -369,16 +369,16 @@ namespace euf {
|
|||
}
|
||||
if (is_empty())
|
||||
return nullptr;
|
||||
return i == 0 ? const_cast<snode *>(this) : nullptr;
|
||||
return i == 0 ? this : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
struct spp {
|
||||
euf::snode *n;
|
||||
euf::snode const* n;
|
||||
ast_manager &m;
|
||||
spp(euf::snode *n, ast_manager &m) : n(n), m(m) {}
|
||||
spp(euf::snode const* n, ast_manager &m) : n(n), m(m) {}
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, spp const&p) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ void seq_split::push(split_set& out, split_oracle const& oracle, expr* d, expr*
|
|||
// Pairs where any component is bottom (the empty regex) are dropped.
|
||||
bool seq_split::intersect(split_set const& s1, split_set const& s2, split_set& result,
|
||||
unsigned threshold, split_oracle const& oracle) {
|
||||
seq_util::rex& r = re();
|
||||
const seq_util::rex& r = re();
|
||||
for (auto const& p1 : s1) {
|
||||
for (auto const& p2 : s2) {
|
||||
if (r.is_empty(p1.m_d) || r.is_empty(p2.m_d) ||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -27,202 +27,6 @@ Abstract:
|
|||
-- nielsen_node: graph node with constraint set and outgoing edges
|
||||
-- nielsen_graph: the overall Nielsen transformation graph
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
ZIPT PORT COMPARISON SUMMARY
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
The ZIPT reference is organized as follows (all under ZIPT/Constraints/):
|
||||
NielsenGraph.cs -- the graph manager class
|
||||
NielsenNode.cs -- node class + BacktrackReasons enum
|
||||
NielsenEdge.cs -- edge class with string and character substitutions
|
||||
ConstraintElement/
|
||||
Constraint.cs -- abstract base for all constraints
|
||||
StrEqBase.cs -- abstract base for StrEq and StrMem
|
||||
StrEq.cs -- string equality with full simplification/splitting
|
||||
StrMem.cs -- regex membership with Brzozowski derivatives
|
||||
IntEq.cs -- integer equality over length polynomials
|
||||
IntLe.cs -- integer inequality over length polynomials
|
||||
Modifier/ -- ~15 modifier types driving graph expansion
|
||||
|
||||
A. PORTED FAITHFULLY
|
||||
--------------------
|
||||
1. backtrack_reason enum (BacktrackReasons): all eleven values (Unevaluated,
|
||||
Extended, SymbolClash, ParikhImage, Subsumption, Arithmetic, Regex,
|
||||
RegexWidening, CharacterRange, SMT, ChildrenFailed) are present with
|
||||
identical semantics.
|
||||
|
||||
2. simplify_result enum (SimplifyResult): all five values (Proceed, Conflict,
|
||||
Satisfied, Restart, RestartAndSatisfied) are present with identical semantics.
|
||||
Note: RestartAndSatisfied is declared but not yet exercised in this port.
|
||||
|
||||
3. nielsen_node status fields and accessors: m_is_general_conflict,
|
||||
m_is_extended, m_reason, m_eval_idx map directly to IsGeneralConflict,
|
||||
IsExtended, CurrentReason, evalIdx. The is_currently_conflict() predicate
|
||||
faithfully mirrors IsCurrentlyConflict (GeneralConflict || (reason !=
|
||||
Unevaluated && IsExtended)).
|
||||
|
||||
4. nielsen_node::reset_counter() mirrors NielsenNode.ResetCounter() exactly.
|
||||
|
||||
5. nielsen_node::clone_from() mirrors the copy constructor
|
||||
NielsenNode(graph, parent) for str_eq and str_mem constraints.
|
||||
|
||||
6. nielsen_edge identity (operator==) mirrors NielsenEdge.Equals(): both
|
||||
compare by source and target node pointer identity.
|
||||
|
||||
7. nielsen_graph::inc_run_idx() mirrors the RunIdx increment in NielsenGraph.
|
||||
Check(), including the UINT_MAX overflow guard that calls reset_counter()
|
||||
on all nodes.
|
||||
|
||||
8. str_eq::sort() mirrors StrEqBase.SortStr(): swaps lhs/rhs when lhs > rhs.
|
||||
(Z3 compares by snode id; ZIPT compares Str lexicographically.)
|
||||
|
||||
9. str_eq::is_trivial() mirrors the trivially-satisfied check when both sides
|
||||
are empty.
|
||||
|
||||
10. str_mem fields (m_str, m_regex, m_history, m_id, m_dep) mirror StrMem
|
||||
fields (Str, Regex, History, Id, Reason) faithfully, including the unique
|
||||
identifier used for cycle tracking.
|
||||
|
||||
11. str_mem::is_primitive() mirrors StrMem.IsPrimitiveRegex(): single variable
|
||||
on the left side of the membership constraint.
|
||||
|
||||
12. nielsen_subst::is_eliminating() mirrors the logic behind
|
||||
NielsenEdge.BumpedModCount: a substitution is non-eliminating (bumps the
|
||||
modification counter) when the substituted variable appears in the
|
||||
replacement.
|
||||
|
||||
13. nielsen_graph::mk_edge() faithfully mirrors NielsenEdge construction: it
|
||||
links src to tgt and registers the outgoing edge.
|
||||
|
||||
B. PORTED WITH ALGORITHMIC CHANGES
|
||||
------------------------------------
|
||||
1. dep_tracker (DependencyTracker): ZIPT's DependencyTracker is a .NET
|
||||
class using a BitArray-like structure for tracking constraint origins.
|
||||
Z3 uses scoped_dependency_manager<dep_source> (an arena-based binary
|
||||
join tree from util/dependency.h) where each leaf carries a dep_source
|
||||
value identifying the originating eq or mem constraint by kind and index.
|
||||
|
||||
2. Substitution application (nielsen_node::apply_subst): ZIPT uses an
|
||||
immutable, functional style -- Apply() returns a new constraint if
|
||||
changed, using C# reference equality to detect no-ops. Z3 uses
|
||||
in-place mutation via sgraph::subst(), modifying the constraint vectors
|
||||
directly. The functional change also propagates the substitution's
|
||||
dependency to the merged constraint.
|
||||
|
||||
3. Node constraint containers: ZIPT's NielsenNode stores str_eq constraints
|
||||
in NList<StrEq> (a sorted list for O(log n) subsumption lookup) and str_mem
|
||||
constraints in Dictionary<uint, StrMem> (keyed by id for O(1) cycle lookup).
|
||||
Z3 uses plain vector<str_eq> and vector<str_mem>, which is simpler.
|
||||
|
||||
4. nielsen_edge substitution list: ZIPT's NielsenEdge carries two substitution
|
||||
lists -- Subst (string-level, mapping string variables to strings) and
|
||||
SubstC (character-level, mapping symbolic character variables to concrete
|
||||
characters). Z3's nielsen_edge carries a single vector<nielsen_subst>,
|
||||
covering only string-level substitutions; character substitutions are not
|
||||
represented.
|
||||
|
||||
5. nielsen_graph node registry: ZIPT keeps nodes in a HashSet<NielsenNode> plus
|
||||
a Dictionary<NList<StrEq>, List<NielsenNode>> for subsumption candidate
|
||||
lookup. Z3 uses a ptr_vector<nielsen_node>, simplifying memory management.
|
||||
|
||||
6. nielsen_graph::display() vs NielsenGraph.ToDot(): ZIPT outputs a DOT-format
|
||||
graph with color highlighting for the current satisfying path. Z3 outputs
|
||||
plain human-readable text with node/edge details but no DOT syntax or path
|
||||
highlighting.
|
||||
|
||||
7. str_eq::contains_var() / str_mem::contains_var(): ZIPT performs occurrence
|
||||
checks through StrManager.Subst() (which uses hash-consing and reference
|
||||
equality). Z3 walks the snode tree via collect_tokens(), which is correct
|
||||
but re-traverses the DAG on every call.
|
||||
|
||||
C. NOT PORTED
|
||||
-------------
|
||||
The following ZIPT components are absent from this implementation.
|
||||
They represent the algorithmic core of the search procedure and
|
||||
are expected to be ported in subsequent work.
|
||||
|
||||
Constraint simplification and propagation:
|
||||
- Constraint.SimplifyAndPropagate() / SimplifyAndPropagateInternal(): the
|
||||
main constraint-driven simplification loop is not ported. str_eq and
|
||||
str_mem have no Simplify methods.
|
||||
- StrEq.SimplifyDir() / SimplifyFinal() / AddDefinition(): forward/backward
|
||||
simplification passes, including Makanin-style prefix cancellation, power
|
||||
token handling, and variable definition propagation.
|
||||
- StrEq.GetNielsenDep() / SplitEq(): the Nielsen dependency analysis and
|
||||
equation-splitting heuristic used to choose the best split point.
|
||||
- StrMem.SimplifyCharRegex() / SimplifyDir(): Brzozowski derivative-based
|
||||
simplification consuming ground prefixes/suffixes of the string.
|
||||
- StrMem.TrySubsume(): stabilizer-based subsumption (not ported, not needed).
|
||||
- StrMem.ExtractCycle() / StabilizerFromCycle(): cycle detection over the
|
||||
search path and extraction of a Kleene-star stabilizer to generalize the
|
||||
cycle. This is the key termination argument for regex membership.
|
||||
- StrMem.Extend(): the splitting driver that produces the next modifier
|
||||
(RegexVarSplitModifier, RegexCharSplitModifier, StarIntrModifier, etc.).
|
||||
|
||||
Integer constraints:
|
||||
- IntEq / IntLe: integer equality and inequality constraints over Presburger
|
||||
arithmetic polynomials (PDD<BigInteger>) are entirely absent. The Z3 port
|
||||
has no ConstraintsIntEq or ConstraintsIntLe in nielsen_node.
|
||||
- IntBounds / VarBoundWatcher: ZIPT-style cached interval maps and eager
|
||||
watcher propagation are not stored in nielsen_node; bounds are queried
|
||||
from the arithmetic subsolver on demand.
|
||||
- AddLowerIntBound() / AddHigherIntBound(): incremental interval tightening
|
||||
— PORTED as the above add_lower/upper_int_bound methods.
|
||||
|
||||
Character-level handling:
|
||||
- CharSubst: character-level variable substitution (symbolic char -> concrete
|
||||
char) is absent. ZIPT uses this to handle symbolic character tokens
|
||||
(SymCharToken) that represent a single unknown character.
|
||||
- SymCharToken / CharacterSet: symbolic character tokens with associated
|
||||
character range constraints (CharRanges) are not ported.
|
||||
- DisEqualities: per-node character disequality constraints used for conflict
|
||||
detection during character substitution are not ported.
|
||||
|
||||
Modifier hierarchy (Constraints/Modifier/):
|
||||
- 13 Modifier subclasses driving graph expansion are ported as
|
||||
apply_* methods in generate_extensions, matching ZIPT's TypeOrder
|
||||
priority: DetModifier(1), PowerEpsilonModifier(2), NumCmpModifier(3),
|
||||
ConstNumUnwindingModifier(4), EqSplitModifier(5), StarIntrModifier(6),
|
||||
GPowerIntrModifier(7), ConstNielsenModifier(8), RegexCharSplitModifier(9),
|
||||
RegexVarSplitModifier(10), PowerSplitModifier(11), VarNielsenModifier(12),
|
||||
VarNumUnwindingModifier(13).
|
||||
- NOT PORTED: DirectedNielsenModifier, DecomposeModifier, CombinedModifier.
|
||||
- NumCmp, ConstNumUnwinding, VarNumUnwinding are approximated (no PDD
|
||||
integer polynomial infrastructure; power tokens are replaced with ε
|
||||
or peeled with fresh variables instead of exact exponent arithmetic).
|
||||
|
||||
Search procedure:
|
||||
- NielsenGraph.Check() / NielsenNode.GraphExpansion(): ported as
|
||||
nielsen_graph::solve() (iterative deepening, starting at depth 3,
|
||||
incrementing by 1 per failure, bounded by smt.nseq.max_depth) and
|
||||
search_dfs() (depth-bounded DFS with eval_idx cycle detection and
|
||||
node status tracking).
|
||||
- NielsenNode.SimplifyAndInit(): ported as
|
||||
nielsen_node::simplify_and_init() with prefix matching, symbol clash,
|
||||
empty propagation, and Brzozowski derivative consumption.
|
||||
- NielsenGraph.FindExisting() / subsumption cache lookup: not ported,
|
||||
not needed.
|
||||
|
||||
Auxiliary infrastructure:
|
||||
- LocalInfo: thread-local search bookkeeping (current path, modification
|
||||
counts, regex occurrence cache for cycle detection, current node pointer)
|
||||
is not ported.
|
||||
- NielsenGraph.SubSolver / InnerStringPropagator: the auxiliary Z3 solver
|
||||
for arithmetic lemma generation and the inner string propagator for
|
||||
model-based refinement are not ported.
|
||||
- PowerToken: word-repetition tokens of the form u^n (distinct from regex
|
||||
Kleene star) are not represented in Z3's snode.
|
||||
- GetSignature(): the constraint-pair signature used for subsumption
|
||||
candidate matching is not ported.
|
||||
- Constraint.Shared: the flag indicating whether a constraint should be
|
||||
forwarded to the outer solver — PORTED as
|
||||
nielsen_graph::assert_root_constraints_to_solver(), called at the start
|
||||
of solve() to make all root-level length/Parikh constraints immediately
|
||||
visible to m_solver.
|
||||
- Interpretation: the model-extraction class mapping string and integer
|
||||
variables to concrete values is not ported.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Author:
|
||||
|
||||
Clemens Eisenhofer 2026-03-02
|
||||
|
|
@ -316,8 +120,6 @@ namespace seq {
|
|||
using enode_pair_vector = svector<enode_pair>;
|
||||
using dep_source = std::variant<sat::literal, enode_pair>;
|
||||
|
||||
|
||||
|
||||
// Arena-based dependency manager: builds an immutable tree of dep_source
|
||||
// leaves joined by binary join nodes. Memory is managed via a region;
|
||||
// call dep_manager::reset() to release all allocations at once.
|
||||
|
|
@ -375,14 +177,26 @@ namespace seq {
|
|||
// and arithmetic <= dependencies.
|
||||
void deps_to_lits(dep_manager &dep_mgr, dep_tracker deps, svector<enode_pair> &eqs, svector<sat::literal> &lits);
|
||||
|
||||
// decompose a membership constraint into a set of pairs of regex splits
|
||||
std::pair<euf::snode const*, euf::snode const*> split_membership(euf::snode const* str, euf::snode const* regex, euf::sgraph& sg, unsigned threshold, split_set& result);
|
||||
|
||||
// Lookahead oracle for the split engine: is the split's right component
|
||||
// `n_regex` prefix-compatible with the constant character sequence `c`?
|
||||
// The factorization picks a boundary so the tail starts with c, hence the
|
||||
// tail-regex ∇ must be able to match c as a prefix. We use a *prefix* test
|
||||
// (not strict "starts-with"): we accept as soon as N accepts a prefix of c
|
||||
// (a suffix appended downstream can complete it). This is sound to apply
|
||||
// during split generation — it never drops a viable split.
|
||||
bool split_lookahead_viable(expr* n_regex, euf::sgraph& sg, zstring const& c);
|
||||
|
||||
// string equality constraint: lhs = rhs
|
||||
// mirrors ZIPT's StrEq (both sides are regex-free snode trees)
|
||||
struct str_eq {
|
||||
euf::snode* m_lhs; // assumed to be non-null
|
||||
euf::snode* m_rhs; // assumed to be non-null
|
||||
euf::snode const* m_lhs; // assumed to be non-null
|
||||
euf::snode const* m_rhs; // assumed to be non-null
|
||||
dep_tracker m_dep;
|
||||
|
||||
str_eq(euf::snode* lhs, euf::snode* rhs, dep_tracker const& dep):
|
||||
str_eq(euf::snode const* lhs, euf::snode const* rhs, dep_tracker const& dep):
|
||||
m_lhs(lhs), m_rhs(rhs), m_dep(dep) {
|
||||
SASSERT(well_formed());
|
||||
}
|
||||
|
|
@ -398,7 +212,7 @@ namespace seq {
|
|||
bool is_trivial() const;
|
||||
|
||||
// check if the constraint contains a given variable
|
||||
bool contains_var(euf::snode* var) const;
|
||||
bool contains_var(euf::snode const* var) const;
|
||||
|
||||
bool well_formed() const {
|
||||
// assumed to be always true
|
||||
|
|
@ -420,11 +234,11 @@ namespace seq {
|
|||
|
||||
// string disequality constraint: lhs != rhs
|
||||
struct str_deq {
|
||||
euf::snode* m_lhs; // assumed to be non-null
|
||||
euf::snode* m_rhs; // assumed to be non-null
|
||||
euf::snode const* m_lhs; // assumed to be non-null
|
||||
euf::snode const* m_rhs; // assumed to be non-null
|
||||
dep_tracker m_dep;
|
||||
|
||||
str_deq(euf::snode* lhs, euf::snode* rhs, dep_tracker const& dep):
|
||||
str_deq(euf::snode const* lhs, euf::snode const* rhs, dep_tracker const& dep):
|
||||
m_lhs(lhs), m_rhs(rhs), m_dep(dep) {
|
||||
SASSERT(well_formed());
|
||||
}
|
||||
|
|
@ -439,7 +253,7 @@ namespace seq {
|
|||
}
|
||||
}
|
||||
|
||||
bool contains_var(euf::snode* var) const {
|
||||
bool contains_var(euf::snode const* var) const {
|
||||
return m_lhs->collect_tokens().contains(var) || m_rhs->collect_tokens().contains(var);
|
||||
}
|
||||
|
||||
|
|
@ -464,11 +278,11 @@ namespace seq {
|
|||
// regex membership constraint: str in regex
|
||||
// mirrors ZIPT's StrMem
|
||||
struct str_mem {
|
||||
euf::snode* m_str; // assumed to be non-null
|
||||
euf::snode* m_regex; // assumed to be non-null
|
||||
euf::snode const* m_str; // assumed to be non-null
|
||||
euf::snode const* m_regex; // assumed to be non-null
|
||||
dep_tracker m_dep;
|
||||
|
||||
str_mem(euf::snode* str, euf::snode* regex, dep_tracker const& dep):
|
||||
str_mem(euf::snode const* str, euf::snode const* regex, dep_tracker const& dep):
|
||||
m_str(str), m_regex(regex), m_dep(dep) {}
|
||||
|
||||
bool operator==(str_mem const& other) const {
|
||||
|
|
@ -484,7 +298,7 @@ namespace seq {
|
|||
bool is_contradiction(nielsen_node const* n) const;
|
||||
|
||||
// check if the constraint contains a given variable
|
||||
bool contains_var(euf::snode* var) const;
|
||||
bool contains_var(euf::snode const* var) const;
|
||||
|
||||
bool well_formed() const {
|
||||
// assumed to be always true
|
||||
|
|
@ -508,12 +322,12 @@ namespace seq {
|
|||
// (can be used as well to substitute arbitrary nodes - like powers)
|
||||
// mirrors ZIPT's Subst
|
||||
struct nielsen_subst {
|
||||
euf::snode* m_var;
|
||||
euf::snode* m_replacement;
|
||||
euf::snode const* m_var;
|
||||
euf::snode const* m_replacement;
|
||||
dep_tracker m_dep;
|
||||
|
||||
nielsen_subst(): m_var(nullptr), m_replacement(nullptr), m_dep(nullptr) {}
|
||||
nielsen_subst(euf::snode* var, euf::snode* repl, dep_tracker const& dep):
|
||||
nielsen_subst(euf::snode const* var, euf::snode const* repl, dep_tracker const& dep):
|
||||
m_var(var), m_replacement(repl), m_dep(dep) {
|
||||
SASSERT(var != nullptr);
|
||||
SASSERT(repl != nullptr);
|
||||
|
|
@ -718,7 +532,7 @@ namespace seq {
|
|||
|
||||
// add a character range constraint for a symbolic char.
|
||||
// intersects with existing range; sets conflict if result is empty.
|
||||
void add_char_range(euf::snode* sym_char, char_set const& range, dep_tracker dep);
|
||||
void add_char_range(euf::snode const* sym_char, char_set const& range, dep_tracker dep);
|
||||
|
||||
// edge access
|
||||
ptr_vector<nielsen_edge> const& outgoing() const { return m_outgoing; }
|
||||
|
|
@ -817,7 +631,7 @@ namespace seq {
|
|||
// Collects tokens from non_empty_side; if any token causes a conflict
|
||||
// (is a concrete character or an unexpected kind), sets conflict flags
|
||||
// and returns true. Otherwise returns false.
|
||||
bool check_empty_side_conflict(euf::sgraph& sg, euf::snode* non_empty_side,
|
||||
bool check_empty_side_conflict(euf::sgraph& sg, euf::snode const* non_empty_side,
|
||||
dep_tracker const& dep);
|
||||
|
||||
// Length bounds are queried from the arithmetic subsolver when needed.
|
||||
|
|
@ -868,7 +682,7 @@ namespace seq {
|
|||
friend class nielsen_node;
|
||||
friend class nielsen_edge;
|
||||
|
||||
// Edge endpoints are stored as expr* (not snode*) because the cache
|
||||
// Edge endpoints are stored as expr* (not snode const*) because the cache
|
||||
// must survive sgraph pops. snodes are allocated in a region that is
|
||||
// never freed, but their m_expr field is owned by the egraph trail and
|
||||
// becomes dangling on pop. We pin the referenced expressions via
|
||||
|
|
@ -1036,14 +850,14 @@ namespace seq {
|
|||
ptr_vector<nielsen_edge> const& sat_path() const { return m_sat_path; }
|
||||
|
||||
// add constraints to the root node from external solver
|
||||
void add_str_eq(euf::snode* lhs, euf::snode* rhs, smt::enode* l, smt::enode* r) const;
|
||||
void add_str_deq(euf::snode* lhs, euf::snode* rhs, sat::literal l) const;
|
||||
void add_str_mem(euf::snode* str, euf::snode* regex, sat::literal l) const;
|
||||
void add_str_eq(euf::snode const* lhs, euf::snode const* rhs, smt::enode* l, smt::enode* r) const;
|
||||
void add_str_deq(euf::snode const* lhs, euf::snode const* rhs, sat::literal l) const;
|
||||
void add_str_mem(euf::snode const* str, euf::snode const* regex, sat::literal l) const;
|
||||
|
||||
// test-friendly overloads (no external dependency tracking)
|
||||
void add_str_eq(euf::snode* lhs, euf::snode* rhs);
|
||||
void add_str_deq(euf::snode* lhs, euf::snode* rhs);
|
||||
void add_str_mem(euf::snode* str, euf::snode* regex);
|
||||
void add_str_eq(euf::snode const* lhs, euf::snode const* rhs);
|
||||
void add_str_deq(euf::snode const* lhs, euf::snode const* rhs);
|
||||
void add_str_mem(euf::snode const* str, euf::snode const* regex);
|
||||
|
||||
// access all nodes
|
||||
ptr_vector<nielsen_node> const& nodes() const { return m_nodes; }
|
||||
|
|
@ -1075,9 +889,9 @@ namespace seq {
|
|||
|
||||
std::string to_dot() const;
|
||||
|
||||
std::ostream& partial_dfa_to_dot(std::ostream& out, euf::snode* start_state, bool keep_names) const;
|
||||
std::ostream& partial_dfa_to_dot(std::ostream& out, euf::snode const* start_state, bool keep_names) const;
|
||||
|
||||
std::string partial_dfa_to_dot(euf::snode* start_state, bool keep_names) const;
|
||||
std::string partial_dfa_to_dot(euf::snode const* start_state, bool keep_names) const;
|
||||
|
||||
// reset all nodes and state
|
||||
void reset();
|
||||
|
|
@ -1118,12 +932,12 @@ namespace seq {
|
|||
// build an arithmetic expression representing the length of an snode tree.
|
||||
// concatenations are expanded to sums, chars to 1, empty to 0,
|
||||
// variables to (str.len var_expr).
|
||||
expr_ref compute_length_expr(euf::snode* n);
|
||||
expr_ref compute_length_expr(euf::snode const* n);
|
||||
|
||||
// compute Parikh length interval [min_len, max_len] for a regex snode.
|
||||
// uses seq_util::rex min_length/max_length on the underlying expression.
|
||||
// max_len == UINT_MAX means unbounded.
|
||||
void compute_regex_length_interval(euf::snode* regex, unsigned& min_len, unsigned& max_len) const;
|
||||
void compute_regex_length_interval(euf::snode const* regex, unsigned& min_len, unsigned& max_len) const;
|
||||
|
||||
// accessor for the seq_regex module
|
||||
seq_regex* seq_regex_module() const { return m_seq_regex; }
|
||||
|
|
@ -1175,15 +989,15 @@ namespace seq {
|
|||
// Record a discovered derivative edge in the global partial DFA.
|
||||
// The `label` may be a concrete string token (converted to to_re)
|
||||
// or an already-regular-expression minterm.
|
||||
void record_partial_derivative_edge(euf::snode* src_re, euf::snode* label, euf::snode* dst_re);
|
||||
void record_partial_derivative_edge(euf::snode const* src_re, euf::snode const* label, euf::snode const* dst_re);
|
||||
|
||||
// Convert a transition label (string token or regex minterm) into a
|
||||
// one-character regex snode used by the partial DFA.
|
||||
euf::snode* to_partial_label_regex(euf::snode* label) const;
|
||||
euf::snode const* to_partial_label_regex(euf::snode const* label) const;
|
||||
|
||||
// Collect the SCC containing root_re in the current partial DFA.
|
||||
// Returns false if no cyclic SCC containing root_re exists.
|
||||
bool collect_scc_for_projection(euf::snode* root_re, uint_set& scc) const;
|
||||
bool collect_scc_for_projection(euf::snode const* root_re, uint_set& scc) const;
|
||||
|
||||
// Mark SCC edges with a monotone extraction index and return the
|
||||
// currently covered edge count for this extraction.
|
||||
|
|
@ -1194,17 +1008,17 @@ namespace seq {
|
|||
// snapshot index nu. This is the stabilizer of root_re kept symbolically
|
||||
// (the projection's derivative/nullability are evaluated lazily by the
|
||||
// sgraph consulting projection_state_in_Q).
|
||||
euf::snode* mk_projection_term(euf::snode* root_re, unsigned nu);
|
||||
euf::snode const* mk_projection_term(euf::snode const* root_re, unsigned nu);
|
||||
|
||||
// Try to extract a stronger projection for root_re. Returns true and
|
||||
// stores it in projection_re iff SCC coverage has grown.
|
||||
bool try_extract_partial_projection(euf::snode* root_re, euf::snode*& projection_re);
|
||||
bool try_extract_partial_projection(euf::snode const* root_re, euf::snode const*& projection_re);
|
||||
|
||||
euf::snode* get_slice(euf::snode* v, expr* left, expr* right);
|
||||
euf::snode const* get_slice(euf::snode const* v, expr* left, expr* right);
|
||||
|
||||
euf::snode* get_tail(euf::snode* v, expr* cnt, bool fwd = true);
|
||||
euf::snode const* get_tail(euf::snode const* v, expr* cnt, bool fwd = true);
|
||||
|
||||
euf::snode* get_tail(euf::snode* v, unsigned cnt, bool fwd = true);
|
||||
euf::snode const* get_tail(euf::snode const* v, unsigned cnt, bool fwd = true);
|
||||
|
||||
// Apply the Parikh image filter to a node: generate modular length
|
||||
// constraints from regex memberships and append them to the node's
|
||||
|
|
@ -1217,10 +1031,10 @@ namespace seq {
|
|||
void apply_parikh_to_node(nielsen_node& node) const;
|
||||
|
||||
// simplify expression and create a node from simplified expression.
|
||||
euf::snode *mk_rewrite(expr *e) const;
|
||||
euf::snode const* mk_rewrite(expr *e) const;
|
||||
|
||||
// create a fresh variable with a unique name and the given sequence sort
|
||||
euf::snode* mk_fresh_var(sort* s);
|
||||
euf::snode const* mk_fresh_var(sort* s);
|
||||
|
||||
// deterministic modifier: var = ε, same-head cancel
|
||||
bool apply_det_modifier(nielsen_node* node);
|
||||
|
|
@ -1238,10 +1052,10 @@ namespace seq {
|
|||
|
||||
// helper: classify whether a token has variable (symbolic) length
|
||||
// returns true for variables, powers, etc.; false for chars, units, string literals
|
||||
bool token_has_variable_length(euf::snode* tok) const;
|
||||
bool token_has_variable_length(euf::snode const* tok) const;
|
||||
|
||||
// helper: get the constant length of a token (only valid when !token_has_variable_length)
|
||||
unsigned token_const_length(euf::snode* tok) const;
|
||||
unsigned token_const_length(euf::snode const* tok) const;
|
||||
|
||||
// helper: find a split point in a regex-free equation.
|
||||
// ports ZIPT's StrEq.SplitEq algorithm.
|
||||
|
|
@ -1293,19 +1107,19 @@ namespace seq {
|
|||
|
||||
// Return the current stabilizer s* for root_re from the partial DFA
|
||||
// (bypasses the novelty guard used by try_extract_partial_projection).
|
||||
euf::snode* get_current_stabilizer(euf::snode* root_re);
|
||||
euf::snode const* get_current_stabilizer(euf::snode const* root_re);
|
||||
|
||||
// BFS of Brzozowski derivatives from root_re up to `depth` steps,
|
||||
// eagerly recording concrete minterm edges in the partial DFA so that
|
||||
// collect_scc_for_projection can find cycles without first waiting for
|
||||
// concrete children to record them one level at a time.
|
||||
void precompute_partial_dfa(euf::snode* root_re, unsigned depth);
|
||||
void precompute_partial_dfa(euf::snode const* root_re, unsigned depth);
|
||||
|
||||
// Walk an ite-structured symbolic derivative expression and record
|
||||
// concrete DFA edges for each non-fail branch.
|
||||
// Called from simplify_and_init when a symbolic character is consumed,
|
||||
// so that cycle_decomp can detect SCCs lazily (as with concrete chars).
|
||||
void record_dfa_edges_from_ite(euf::snode* src_re, expr* ite_deriv);
|
||||
void record_dfa_edges_from_ite(euf::snode const* src_re, expr* ite_deriv);
|
||||
|
||||
// generalized power introduction: for an equation where one head is
|
||||
// a variable v and the other side has ground prefix + a variable x
|
||||
|
|
@ -1316,18 +1130,11 @@ namespace seq {
|
|||
// generalized regex factorization (Boolean closure derivation rule)
|
||||
bool apply_regex_factorization(nielsen_node* node);
|
||||
|
||||
// Lookahead oracle for apply_regex_factorization's split() call: returns
|
||||
// true iff the split's right component `n_regex` is prefix-compatible with
|
||||
// the constant character sequence `c` (the tail of the factorization starts
|
||||
// with c). Prunes splits whose tail-regex can never match c. Sound to
|
||||
// apply during split generation (prefix-, not strict-, match).
|
||||
bool split_lookahead_viable(expr* n_regex, zstring const& c);
|
||||
|
||||
// helper for apply_gpower_intr: fires the substitution.
|
||||
// `fwd=true` uses left-to-right decomposition; `fwd=false` mirrors ZIPT's
|
||||
// backward (right-to-left) direction.
|
||||
bool fire_gpower_intro(nielsen_node* node, str_eq const& eq,
|
||||
euf::snode* var, euf::snode_vector const& ground_prefix_orig, bool fwd);
|
||||
euf::snode const* var, euf::snode_vector const& ground_prefix_orig, bool fwd);
|
||||
|
||||
// heuristic string equation splitting. Left to right scanning for shortest prefix with matching variables.
|
||||
bool apply_signature_split(nielsen_node* node);
|
||||
|
|
@ -1353,17 +1160,17 @@ namespace seq {
|
|||
bool axiomatize_diseq(nielsen_node* node);
|
||||
|
||||
// find the first power token in any str_eq at this node
|
||||
static euf::snode* find_power_token(nielsen_node* node);
|
||||
static euf::snode const* find_power_token(nielsen_node* node);
|
||||
|
||||
// find a power token facing a constant (char/non-var) token at either end
|
||||
// of an equation; returns orientation via `fwd` (true=head, false=tail).
|
||||
static bool find_power_vs_non_var(nielsen_node* node, euf::snode*& power, euf::snode*& other_head, str_eq const*& eq_out, bool& fwd);
|
||||
static bool find_power_vs_non_var(nielsen_node* node, euf::snode const*& power, euf::snode const*& other_head, str_eq const*& eq_out, bool& fwd);
|
||||
|
||||
// find a power token facing a variable token at either end of an
|
||||
// equation; returns orientation via `fwd` (true=head, false=tail).
|
||||
static bool find_power_vs_var(nielsen_node* node, euf::snode*& power, euf::snode*& var_head, str_eq const*& eq_out, bool& fwd);
|
||||
static bool find_power_vs_var(nielsen_node* node, euf::snode const*& power, euf::snode const*& var_head, str_eq const*& eq_out, bool& fwd);
|
||||
|
||||
static bool find_power_vs_var(nielsen_node* node, euf::snode*& power, str_mem const*& mem_out, bool& fwd);
|
||||
static bool find_power_vs_var(nielsen_node* node, euf::snode const*& power, str_mem const*& mem_out, bool& fwd);
|
||||
|
||||
// -----------------------------------------------
|
||||
// Integer feasibility subsolver methods
|
||||
|
|
@ -1404,7 +1211,7 @@ namespace seq {
|
|||
constraint mk_constraint(expr *fml, dep_tracker const &dep) const;
|
||||
|
||||
// get the exponent expression from a power snode (arg(1))
|
||||
static expr * get_power_exponent(euf::snode* power);
|
||||
static expr * get_power_exponent(euf::snode const* power);
|
||||
|
||||
// -----------------------------------------------
|
||||
// Modification counter methods for substitution length tracking.
|
||||
|
|
@ -1413,13 +1220,13 @@ namespace seq {
|
|||
// -----------------------------------------------
|
||||
|
||||
// Get or create a fresh symbolic character variable for the given variable
|
||||
expr_ref get_or_create_char_var(euf::snode* var);
|
||||
expr_ref get_or_create_char_var(euf::snode const* var);
|
||||
|
||||
// Get or create a fresh integer variable for gpower n (full exponent) for the given variable
|
||||
expr_ref get_or_create_gpower_n_var(euf::snode* var);
|
||||
expr_ref get_or_create_gpower_n_var(euf::snode const* var);
|
||||
|
||||
// Get or create a fresh integer variable for gpower m (partial exponent) for the given variable
|
||||
expr_ref get_or_create_gpower_m_var(euf::snode* var);
|
||||
expr_ref get_or_create_gpower_m_var(euf::snode const* var);
|
||||
|
||||
// Compute and add |x| = |u| length constraints to an edge for all
|
||||
// its non-eliminating substitutions. Uses current m_mod_cnt.
|
||||
|
|
|
|||
|
|
@ -627,12 +627,12 @@ namespace seq {
|
|||
return;
|
||||
|
||||
// collect the original variables present in the root node's constraints
|
||||
ptr_vector<euf::snode> vars;
|
||||
euf::snode_vector vars;
|
||||
uint_set seen;
|
||||
auto add_vars = [&](euf::snode* s) {
|
||||
auto add_vars = [&](euf::snode const* s) {
|
||||
if (!s)
|
||||
return;
|
||||
for (euf::snode* t : s->collect_tokens())
|
||||
for (euf::snode const* t : s->collect_tokens())
|
||||
if (t->is_var() && !seen.contains(t->id())) {
|
||||
seen.insert(t->id());
|
||||
vars.push_back(t);
|
||||
|
|
@ -657,12 +657,14 @@ namespace seq {
|
|||
}
|
||||
|
||||
bool any = false;
|
||||
for (euf::snode* var : vars) {
|
||||
euf::snode* val = var;
|
||||
for (euf::snode const* var : vars) {
|
||||
euf::snode const* val = var;
|
||||
// apply substitutions in root-to-node order (path is node-to-root)
|
||||
for (unsigned i = path.size(); i-- > 0; )
|
||||
for (nielsen_subst const& s : path[i]->subst())
|
||||
for (unsigned i = path.size(); i-- > 0; ) {
|
||||
for (nielsen_subst const& s : path[i]->subst()) {
|
||||
val = sg.subst(val, s.m_var, s.m_replacement);
|
||||
}
|
||||
}
|
||||
if (val == var)
|
||||
continue; // unchanged: variable is still free at this node
|
||||
if (!any) { out << "<br/>Subst:<br/>"; any = true; }
|
||||
|
|
@ -763,7 +765,7 @@ namespace seq {
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
std::ostream& nielsen_graph::partial_dfa_to_dot(std::ostream& out, euf::snode* start_state, bool keep_names) const {
|
||||
std::ostream& nielsen_graph::partial_dfa_to_dot(std::ostream& out, euf::snode const* start_state, bool keep_names) const {
|
||||
out << "digraph G {\n";
|
||||
out << " node [shape=box];\n";
|
||||
|
||||
|
|
@ -833,8 +835,8 @@ namespace seq {
|
|||
|
||||
bool accepting = false;
|
||||
if (node_expr) {
|
||||
euf::snode* sn = m_sg.mk(node_expr);
|
||||
accepting = (const_cast<euf::sgraph&>(m_sg).re_nullable(sn) == l_true);
|
||||
euf::snode const* sn = m_sg.mk(node_expr);
|
||||
accepting = m_sg.re_nullable(sn) == l_true;
|
||||
}
|
||||
|
||||
out << " N" << node_id << " [";
|
||||
|
|
@ -873,7 +875,7 @@ namespace seq {
|
|||
return out;
|
||||
}
|
||||
|
||||
std::string nielsen_graph::partial_dfa_to_dot(euf::snode* start_state, bool keep_names) const {
|
||||
std::string nielsen_graph::partial_dfa_to_dot(euf::snode const* start_state, bool keep_names) const {
|
||||
std::stringstream ss;
|
||||
partial_dfa_to_dot(ss, start_state, keep_names);
|
||||
return ss.str();
|
||||
|
|
|
|||
|
|
@ -87,22 +87,22 @@ namespace seq {
|
|||
m_self_stabilizing.reset();
|
||||
}
|
||||
|
||||
void seq_regex::add_stabilizer(euf::snode* regex, euf::snode* stabilizer) {
|
||||
void seq_regex::add_stabilizer(euf::snode const* regex, euf::snode const* stabilizer) {
|
||||
if (!regex || !stabilizer)
|
||||
return;
|
||||
|
||||
unsigned id = regex->id();
|
||||
auto& stabs = m_stabilizers.insert_if_not_there(id, ptr_vector<euf::snode>());
|
||||
const unsigned id = regex->id();
|
||||
auto& stabs = m_stabilizers.insert_if_not_there(id, euf::snode_vector());
|
||||
|
||||
// De-duplicate by pointer equality (mirrors ZIPT Environment.AddStabilizer
|
||||
// which checks reference equality before adding).
|
||||
for (euf::snode* s : stabs)
|
||||
for (euf::snode const* s : stabs)
|
||||
if (s == stabilizer)
|
||||
return;
|
||||
stabs.push_back(stabilizer);
|
||||
}
|
||||
|
||||
euf::snode* seq_regex::get_stabilizer_union(euf::snode* regex) {
|
||||
euf::snode const* seq_regex::get_stabilizer_union(euf::snode const* regex) {
|
||||
if (!regex)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ namespace seq {
|
|||
|
||||
// Multiple stabilizers: build re.union chain.
|
||||
// union(s1, union(s2, ... union(sN-1, sN)...))
|
||||
euf::snode* result = stabs[stabs.size() - 1];
|
||||
euf::snode const* result = stabs[stabs.size() - 1];
|
||||
for (unsigned i = stabs.size() - 1; i-- > 0; ) {
|
||||
expr* lhs = stabs[i]->get_expr();
|
||||
expr* rhs = result->get_expr();
|
||||
|
|
@ -128,7 +128,7 @@ namespace seq {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool seq_regex::has_stabilizers(euf::snode* regex) const {
|
||||
bool seq_regex::has_stabilizers(euf::snode const* regex) const {
|
||||
if (!regex)
|
||||
return false;
|
||||
if (!m_stabilizers.contains(regex->id()))
|
||||
|
|
@ -136,7 +136,7 @@ namespace seq {
|
|||
return !m_stabilizers[regex->id()].empty();
|
||||
}
|
||||
|
||||
ptr_vector<euf::snode> const* seq_regex::get_stabilizers(euf::snode* regex) const {
|
||||
euf::snode_vector const* seq_regex::get_stabilizers(euf::snode const* regex) const {
|
||||
if (!regex)
|
||||
return nullptr;
|
||||
if (!m_stabilizers.contains(regex->id()))
|
||||
|
|
@ -144,12 +144,12 @@ namespace seq {
|
|||
return &m_stabilizers[regex->id()];
|
||||
}
|
||||
|
||||
void seq_regex::set_self_stabilizing(euf::snode* regex) {
|
||||
void seq_regex::set_self_stabilizing(euf::snode const* regex) {
|
||||
if (regex)
|
||||
m_self_stabilizing.insert(regex->id());
|
||||
}
|
||||
|
||||
bool seq_regex::is_self_stabilizing(euf::snode* regex) const {
|
||||
bool seq_regex::is_self_stabilizing(euf::snode const* regex) const {
|
||||
return regex && m_self_stabilizing.contains(regex->id());
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ namespace seq {
|
|||
// Self-stabilizing auto-detection
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool seq_regex::compute_self_stabilizing(euf::snode* regex) const {
|
||||
bool seq_regex::compute_self_stabilizing(euf::snode const* regex) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ namespace seq {
|
|||
// Self-stabilizing propagation through derivatives
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void seq_regex::propagate_self_stabilizing(euf::snode* parent, euf::snode* deriv) {
|
||||
void seq_regex::propagate_self_stabilizing(euf::snode const* parent, euf::snode const* deriv) {
|
||||
if (!parent || !deriv)
|
||||
return;
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ namespace seq {
|
|||
// If S is self-stabilizing, the D(c,R)·S branch inherits it.
|
||||
// If the whole parent R·S is self-stabilizing, the derivative is too.
|
||||
if (parent->is_concat() && parent->num_args() == 2) {
|
||||
euf::snode* tail = parent->arg(1);
|
||||
euf::snode const* tail = parent->arg(1);
|
||||
bool tail_ss = is_self_stabilizing(tail) || compute_self_stabilizing(tail);
|
||||
if (tail_ss || parent_ss) {
|
||||
set_self_stabilizing(deriv);
|
||||
|
|
@ -217,8 +217,8 @@ namespace seq {
|
|||
// D(c, R|S) = D(c,R) | D(c,S).
|
||||
// Self-stabilizing if both children are self-stabilizing.
|
||||
if (parent->is_union() && parent->num_args() == 2) {
|
||||
euf::snode* lhs = parent->arg(0);
|
||||
euf::snode* rhs = parent->arg(1);
|
||||
euf::snode const* lhs = parent->arg(0);
|
||||
euf::snode const* rhs = parent->arg(1);
|
||||
bool lhs_ss = is_self_stabilizing(lhs) || compute_self_stabilizing(lhs);
|
||||
bool rhs_ss = is_self_stabilizing(rhs) || compute_self_stabilizing(rhs);
|
||||
if (lhs_ss && rhs_ss) {
|
||||
|
|
@ -231,8 +231,8 @@ namespace seq {
|
|||
// D(c, R∩S) = D(c,R) ∩ D(c,S).
|
||||
// Self-stabilizing if both children are self-stabilizing.
|
||||
if (parent->is_intersect() && parent->num_args() == 2) {
|
||||
euf::snode* lhs = parent->arg(0);
|
||||
euf::snode* rhs = parent->arg(1);
|
||||
euf::snode const* lhs = parent->arg(0);
|
||||
euf::snode const* rhs = parent->arg(1);
|
||||
bool lhs_ss = is_self_stabilizing(lhs) || compute_self_stabilizing(lhs);
|
||||
bool rhs_ss = is_self_stabilizing(rhs) || compute_self_stabilizing(rhs);
|
||||
if (lhs_ss && rhs_ss) {
|
||||
|
|
@ -245,7 +245,7 @@ namespace seq {
|
|||
// D(c, ~R) = ~D(c, R).
|
||||
// Preserves self-stabilizing from R.
|
||||
if (parent->is_complement() && parent->num_args() == 1) {
|
||||
euf::snode* inner = parent->arg(0);
|
||||
euf::snode const* inner = parent->arg(0);
|
||||
bool inner_ss = is_self_stabilizing(inner) || compute_self_stabilizing(inner);
|
||||
if (inner_ss) {
|
||||
set_self_stabilizing(deriv);
|
||||
|
|
@ -266,10 +266,10 @@ namespace seq {
|
|||
// Derivative with propagation
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::derivative_with_propagation(euf::snode* re, euf::snode* elem) {
|
||||
euf::snode const* seq_regex::derivative_with_propagation(euf::snode const* re, euf::snode const* elem) {
|
||||
if (!re || !elem)
|
||||
return nullptr;
|
||||
euf::snode* deriv = derivative(re, elem);
|
||||
euf::snode const* deriv = derivative(re, elem);
|
||||
if (deriv)
|
||||
propagate_self_stabilizing(re, deriv);
|
||||
return deriv;
|
||||
|
|
@ -279,7 +279,7 @@ namespace seq {
|
|||
// Uniform derivative (symbolic character consumption)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::try_uniform_derivative(euf::snode* regex) {
|
||||
euf::snode const* seq_regex::try_uniform_derivative(euf::snode const* regex) const {
|
||||
if (!regex)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -303,11 +303,11 @@ namespace seq {
|
|||
// Compute the derivative for each non-empty minterm. If all produce
|
||||
// the same result, the derivative is independent of the character
|
||||
// value and we can consume a symbolic character deterministically.
|
||||
euf::snode* uniform = nullptr;
|
||||
for (euf::snode* mt : minterms) {
|
||||
euf::snode const* uniform = nullptr;
|
||||
for (euf::snode const* mt : minterms) {
|
||||
if (!mt || mt->is_fail())
|
||||
continue; // empty character class — no character belongs to it
|
||||
euf::snode* deriv = m_sg.brzozowski_deriv(regex, mt);
|
||||
euf::snode const* deriv = m_sg.brzozowski_deriv(regex, mt);
|
||||
if (!deriv)
|
||||
return nullptr; // derivative computation failed
|
||||
if (!uniform) {
|
||||
|
|
@ -323,7 +323,7 @@ namespace seq {
|
|||
// Ground prefix consumption
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool seq_regex::is_empty_regex(euf::snode* re) const {
|
||||
bool seq_regex::is_empty_regex(euf::snode const* re) const {
|
||||
SASSERT(re);
|
||||
// direct empty language constant
|
||||
if (re->is_fail())
|
||||
|
|
@ -369,7 +369,7 @@ namespace seq {
|
|||
// BFS regex emptiness check — helper: collect character boundaries
|
||||
// This is faster than computing the actual minterms but probably not minimal
|
||||
// -----------------------------------------------------------------------
|
||||
void seq_regex::collect_char_boundaries(euf::snode* re, unsigned_vector& bounds) const {
|
||||
void seq_regex::collect_char_boundaries(euf::snode const* re, unsigned_vector& bounds) const {
|
||||
SASSERT(re && re->get_expr());
|
||||
|
||||
expr* e = re->get_expr();
|
||||
|
|
@ -425,7 +425,7 @@ namespace seq {
|
|||
// BFS regex emptiness check — helper: alphabet representatives
|
||||
// Faster alternative of computing all min-terms and taking representatives of them
|
||||
// -----------------------------------------------------------------------
|
||||
bool seq_regex::get_alphabet_representatives(euf::snode* re, euf::snode_vector& reps) {
|
||||
bool seq_regex::get_alphabet_representatives(euf::snode const* re, euf::snode_vector& reps) {
|
||||
if (!re || !re->get_expr())
|
||||
return false;
|
||||
|
||||
|
|
@ -466,7 +466,7 @@ namespace seq {
|
|||
|
||||
// NSB review: we have similar functionality in seq_rewriter::some_seq_in_re
|
||||
// currently both these versions only relly work for strings not general sequences
|
||||
lbool seq_regex::is_empty_bfs(euf::snode* re, unsigned max_states) {
|
||||
lbool seq_regex::is_empty_bfs(euf::snode const* re, unsigned max_states) {
|
||||
SASSERT(re);
|
||||
const expr* e = re->get_expr();
|
||||
SASSERT(e);
|
||||
|
|
@ -517,7 +517,7 @@ namespace seq {
|
|||
if (states_explored >= max_states)
|
||||
return l_undef; // also don't cache
|
||||
|
||||
euf::snode* current = worklist.back();
|
||||
euf::snode const* current = worklist.back();
|
||||
worklist.pop_back();
|
||||
++states_explored;
|
||||
|
||||
|
|
@ -533,11 +533,11 @@ namespace seq {
|
|||
// Nothing found = dead-end
|
||||
continue;
|
||||
|
||||
for (euf::snode* ch : reps) {
|
||||
for (euf::snode const* ch : reps) {
|
||||
if (!m.inc())
|
||||
return l_undef; // don't cache
|
||||
// std::cout << "Deriving by " << snode_label_html(ch, sg().get_manager()) << std::endl;
|
||||
euf::snode* deriv = m_sg.brzozowski_deriv(current, ch);
|
||||
euf::snode const* deriv = m_sg.brzozowski_deriv(current, ch);
|
||||
SASSERT(deriv);
|
||||
if (is_nullable(deriv))
|
||||
return cache_and_return(l_false); // found an accepting state
|
||||
|
|
@ -560,7 +560,7 @@ namespace seq {
|
|||
// Mirrors ZIPT NielsenNode.CheckEmptiness (NielsenNode.cs:1429-1469)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
lbool seq_regex::check_intersection_emptiness(ptr_vector<euf::snode> const& regexes, unsigned max_states) {
|
||||
lbool seq_regex::check_intersection_emptiness(euf::snode_vector const& regexes, unsigned max_states) {
|
||||
|
||||
if (regexes.empty())
|
||||
return l_false; // empty intersection = full language (vacuously non-empty)
|
||||
|
|
@ -569,7 +569,7 @@ namespace seq {
|
|||
if (regexes.size() == 1)
|
||||
return is_empty_bfs(regexes[0], max_states);
|
||||
|
||||
euf::snode* result = regexes[0];
|
||||
euf::snode const* result = regexes[0];
|
||||
for (unsigned i = 1; i < regexes.size(); ++i) {
|
||||
expr* r1 = result->get_expr();
|
||||
expr* r2 = regexes[i]->get_expr();
|
||||
|
|
@ -587,7 +587,7 @@ namespace seq {
|
|||
// Mirrors ZIPT NielsenNode.IsLanguageSubset (NielsenNode.cs:1382-1385)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
lbool seq_regex::is_language_subset(euf::snode* subset_re, euf::snode* superset_re) {
|
||||
lbool seq_regex::is_language_subset(euf::snode const* subset_re, euf::snode const* superset_re) {
|
||||
if (!subset_re || !superset_re)
|
||||
return l_undef;
|
||||
|
||||
|
|
@ -601,13 +601,13 @@ namespace seq {
|
|||
|
||||
// Build complement(superset)
|
||||
expr* sup_expr = superset_re->get_expr();
|
||||
euf::snode *comp_sn = m_sg.mk(seq.re.mk_complement(sup_expr));
|
||||
euf::snode const* comp_sn = m_sg.mk(seq.re.mk_complement(sup_expr));
|
||||
|
||||
// Build intersection and check emptiness
|
||||
// subset ∩ complement(superset) should be empty for subset relation
|
||||
expr* sub_expr = subset_re->get_expr();
|
||||
auto inter = seq.re.mk_inter(sub_expr, comp_sn->get_expr());
|
||||
euf::snode* inter_sn = m_sg.mk(inter);
|
||||
euf::snode const* inter_sn = m_sg.mk(inter);
|
||||
return is_empty_bfs(inter_sn);
|
||||
}
|
||||
|
||||
|
|
@ -615,17 +615,17 @@ namespace seq {
|
|||
// Collect primitive regex intersection for a variable
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::collect_primitive_regex_intersection(
|
||||
euf::snode* var, nielsen_node const& node, dep_manager& dep_mgr, dep_tracker& dep) const {
|
||||
euf::snode const* seq_regex::collect_primitive_regex_intersection(
|
||||
euf::snode const* var, nielsen_node const& node, dep_manager& dep_mgr, dep_tracker& dep) const {
|
||||
SASSERT(var);
|
||||
|
||||
euf::snode* result = nullptr;
|
||||
euf::snode const* result = nullptr;
|
||||
|
||||
for (auto const& mem : node.str_mems()) {
|
||||
// Primitive constraint: str is a single variable
|
||||
if (!mem.is_primitive())
|
||||
continue;
|
||||
euf::snode *first = mem.m_str->first();
|
||||
euf::snode const* first = mem.m_str->first();
|
||||
// NSB review: why is this "first" and not mem.m_str?
|
||||
SASSERT(first);
|
||||
if (first != var)
|
||||
|
|
@ -666,11 +666,11 @@ namespace seq {
|
|||
return simplify_status::ok;
|
||||
|
||||
while (mem.m_str && !mem.m_str->is_empty()) {
|
||||
euf::snode* first = mem.m_str->first();
|
||||
euf::snode const* first = mem.m_str->first();
|
||||
if (!first || !first->is_char())
|
||||
break;
|
||||
euf::snode* parent_re = mem.m_regex;
|
||||
euf::snode* deriv = m_sg.brzozowski_deriv(parent_re, first);
|
||||
euf::snode const* parent_re = mem.m_regex;
|
||||
euf::snode const* deriv = m_sg.brzozowski_deriv(parent_re, first);
|
||||
if (!deriv)
|
||||
break;
|
||||
if (deriv->is_fail())
|
||||
|
|
@ -732,7 +732,7 @@ namespace seq {
|
|||
// Minterm computation with filtering
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void seq_regex::get_minterms(euf::snode* regex, euf::snode_vector& minterms) {
|
||||
void seq_regex::get_minterms(euf::snode const* regex, euf::snode_vector& minterms) {
|
||||
if (!regex)
|
||||
return;
|
||||
|
||||
|
|
@ -744,7 +744,7 @@ namespace seq {
|
|||
// note: minterms are regex character-class expressions, not concrete
|
||||
// characters, so we cannot compute Brzozowski derivatives with them.
|
||||
// callers should compute derivatives using concrete or fresh chars.
|
||||
for (euf::snode* mt : raw) {
|
||||
for (euf::snode const* mt : raw) {
|
||||
if (!mt || mt->is_fail())
|
||||
continue;
|
||||
minterms.push_back(mt);
|
||||
|
|
@ -763,8 +763,8 @@ namespace seq {
|
|||
return is_nullable(mem.m_regex);
|
||||
|
||||
// consume ground prefix: derive regex by each leading concrete char
|
||||
seq::str_mem working = mem;
|
||||
simplify_status st = simplify_ground_prefix(working);
|
||||
str_mem working = mem;
|
||||
const simplify_status st = simplify_ground_prefix(working);
|
||||
if (st == simplify_status::conflict)
|
||||
return false;
|
||||
if (st == simplify_status::satisfied)
|
||||
|
|
@ -773,9 +773,9 @@ namespace seq {
|
|||
// after ground prefix consumption, if the front is still a concrete
|
||||
// character we can take one more step (shouldn't happen after
|
||||
// simplify_ground_prefix, but guard defensively)
|
||||
euf::snode* first = working.m_str->first();
|
||||
euf::snode const* first = working.m_str->first();
|
||||
if (first && first->is_char()) {
|
||||
seq::str_mem derived = derive(working, first);
|
||||
const str_mem derived = derive(working, first);
|
||||
if (is_empty_regex(derived.m_regex))
|
||||
return false;
|
||||
out_mems.push_back(derived);
|
||||
|
|
@ -793,7 +793,7 @@ namespace seq {
|
|||
// History recording
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
seq::str_mem seq_regex::record_history(seq::str_mem const& mem, euf::snode* history_re) {
|
||||
seq::str_mem seq_regex::record_history(seq::str_mem const& mem, euf::snode const* history_re) {
|
||||
|
||||
return str_mem(mem.m_str, mem.m_regex, mem.m_dep);
|
||||
}
|
||||
|
|
@ -802,15 +802,15 @@ namespace seq {
|
|||
// Cycle detection
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::extract_cycle(seq::str_mem const& mem) const {
|
||||
euf::snode const* seq_regex::extract_cycle(seq::str_mem const& mem) const {
|
||||
#if 0
|
||||
// Walk the history chain looking for a repeated regex.
|
||||
// A cycle exists when the current regex matches a regex in the history.
|
||||
if (!mem.m_regex || !mem.m_history)
|
||||
return nullptr;
|
||||
|
||||
euf::snode* current = mem.m_regex;
|
||||
euf::snode* hist = mem.m_history;
|
||||
euf::snode const* current = mem.m_regex;
|
||||
euf::snode const* hist = mem.m_history;
|
||||
|
||||
// Walk the history chain up to a bounded depth.
|
||||
// The history is structured as a chain of regex snapshots connected
|
||||
|
|
@ -818,8 +818,8 @@ namespace seq {
|
|||
// and arg(1) is the tail. A leaf (non-concat) is a terminal entry.
|
||||
unsigned bound = 1000;
|
||||
while (hist && bound-- > 0) {
|
||||
euf::snode* entry = hist;
|
||||
euf::snode* tail = nullptr;
|
||||
euf::snode const* entry = hist;
|
||||
euf::snode const* tail = nullptr;
|
||||
|
||||
// If the history node is a regex concat, decompose it:
|
||||
// arg(0) is the regex snapshot, arg(1) is the rest of the chain
|
||||
|
|
@ -842,8 +842,8 @@ namespace seq {
|
|||
// Stabilizer from cycle
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::stabilizer_from_cycle(euf::snode* cycle_regex,
|
||||
euf::snode* current_regex) {
|
||||
euf::snode const* seq_regex::stabilizer_from_cycle(euf::snode const* cycle_regex,
|
||||
euf::snode const* current_regex) {
|
||||
if (!cycle_regex || !current_regex)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -856,7 +856,7 @@ namespace seq {
|
|||
// Extract cycle history tokens
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::extract_cycle_history(seq::str_mem const& current,
|
||||
euf::snode const* seq_regex::extract_cycle_history(seq::str_mem const& current,
|
||||
seq::str_mem const& ancestor) {
|
||||
// The history is built by simplify_and_init as a left-associative
|
||||
// string concat chain: concat(concat(concat(nil, c1), c2), c3).
|
||||
|
|
@ -869,21 +869,21 @@ namespace seq {
|
|||
// Mirrors ZIPT StrMem.GetFilteredStabilizerStar (StrMem.cs:228-243)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::get_filtered_stabilizer_star(euf::snode* re,
|
||||
euf::snode* excluded_char) {
|
||||
euf::snode const* seq_regex::get_filtered_stabilizer_star(euf::snode const* re,
|
||||
euf::snode const* excluded_char) const {
|
||||
if (!re)
|
||||
return nullptr;
|
||||
|
||||
ptr_vector<euf::snode> const* stabs = get_stabilizers(re);
|
||||
euf::snode_vector const* stabs = get_stabilizers(re);
|
||||
if (!stabs || stabs->empty())
|
||||
return nullptr;
|
||||
euf::snode* filtered_union = nullptr;
|
||||
euf::snode const* filtered_union = nullptr;
|
||||
|
||||
for (euf::snode* s : *stabs) {
|
||||
for (euf::snode const* s : *stabs) {
|
||||
if (!s)
|
||||
continue;
|
||||
// Keep only stabilizers whose language cannot start with excluded_char
|
||||
euf::snode* d = m_sg.brzozowski_deriv(s, excluded_char);
|
||||
euf::snode const* d = m_sg.brzozowski_deriv(s, excluded_char);
|
||||
if (d && d->is_fail()) {
|
||||
if (!filtered_union) {
|
||||
filtered_union = s;
|
||||
|
|
@ -913,8 +913,8 @@ namespace seq {
|
|||
// Mirrors ZIPT StrMem.StabilizerFromCycle (StrMem.cs:163-225)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* seq_regex::strengthened_stabilizer(euf::snode* cycle_regex,
|
||||
euf::snode* cycle_history) {
|
||||
euf::snode const* seq_regex::strengthened_stabilizer(euf::snode const* cycle_regex,
|
||||
euf::snode const* cycle_history) {
|
||||
if (!cycle_regex || !cycle_history)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -929,14 +929,14 @@ namespace seq {
|
|||
// A sub-cycle is detected when the derivative returns to cycle_regex.
|
||||
svector<std::pair<unsigned, unsigned>> sub_cycles;
|
||||
unsigned cycle_start = 0;
|
||||
euf::snode* current_re = cycle_regex;
|
||||
euf::snode const* current_re = cycle_regex;
|
||||
|
||||
for (unsigned i = 0; i < tokens.size(); ++i) {
|
||||
euf::snode* tok = tokens[i];
|
||||
euf::snode const* tok = tokens[i];
|
||||
if (!tok)
|
||||
return nullptr;
|
||||
|
||||
euf::snode* deriv = m_sg.brzozowski_deriv(current_re, tok);
|
||||
euf::snode const* deriv = m_sg.brzozowski_deriv(current_re, tok);
|
||||
if (!deriv)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -961,7 +961,7 @@ namespace seq {
|
|||
|
||||
// Build a stabilizer body for each sub-cycle.
|
||||
// body = to_re(t0) · [filteredStar(R1, t1)] · to_re(t1) · ... · to_re(t_{n-1})
|
||||
euf::snode* overall_union = nullptr;
|
||||
euf::snode const* overall_union = nullptr;
|
||||
|
||||
for (auto const& sc : sub_cycles) {
|
||||
unsigned start = sc.first;
|
||||
|
|
@ -969,17 +969,17 @@ namespace seq {
|
|||
if (start >= end)
|
||||
continue;
|
||||
|
||||
euf::snode* re_state = cycle_regex;
|
||||
euf::snode* body = nullptr;
|
||||
euf::snode const* re_state = cycle_regex;
|
||||
euf::snode const* body = nullptr;
|
||||
|
||||
for (unsigned i = start; i < end; ++i) {
|
||||
euf::snode* tok = tokens[i];
|
||||
euf::snode const* tok = tokens[i];
|
||||
if (!tok)
|
||||
break;
|
||||
|
||||
// Insert filtered stabilizer star before each token after the first
|
||||
if (i > start) {
|
||||
euf::snode* filtered = get_filtered_stabilizer_star(re_state, tok);
|
||||
euf::snode const* filtered = get_filtered_stabilizer_star(re_state, tok);
|
||||
if (filtered) {
|
||||
expr* fe = filtered->get_expr();
|
||||
if (fe) {
|
||||
|
|
@ -998,7 +998,7 @@ namespace seq {
|
|||
|
||||
expr_ref unit_str(seq.str.mk_unit(tok_expr), m);
|
||||
expr_ref tok_re(seq.re.mk_to_re(unit_str), m);
|
||||
euf::snode* tok_re_sn = m_sg.mk(tok_re);
|
||||
euf::snode const* tok_re_sn = m_sg.mk(tok_re);
|
||||
|
||||
if (!body) {
|
||||
body = tok_re_sn;
|
||||
|
|
@ -1012,7 +1012,7 @@ namespace seq {
|
|||
}
|
||||
|
||||
// Advance the regex state
|
||||
euf::snode* deriv = m_sg.brzozowski_deriv(re_state, tok);
|
||||
euf::snode const* deriv = m_sg.brzozowski_deriv(re_state, tok);
|
||||
if (!deriv)
|
||||
break;
|
||||
re_state = deriv;
|
||||
|
|
@ -1046,7 +1046,7 @@ namespace seq {
|
|||
SASSERT(mem.m_str && mem.m_regex);
|
||||
|
||||
// 1. Leading token must be a variable
|
||||
euf::snode* first = mem.m_str->first();
|
||||
euf::snode const* first = mem.m_str->first();
|
||||
if (!first || !first->is_var())
|
||||
return false;
|
||||
|
||||
|
|
@ -1055,16 +1055,16 @@ namespace seq {
|
|||
return false;
|
||||
|
||||
// 3. Build stabStar = star(union(all stabilizers for this regex))
|
||||
euf::snode* stab_union = get_stabilizer_union(mem.m_regex);
|
||||
euf::snode const* stab_union = get_stabilizer_union(mem.m_regex);
|
||||
if (!stab_union)
|
||||
return false;
|
||||
|
||||
expr* su_expr = stab_union->get_expr();
|
||||
expr_ref stab_star(seq.re.mk_star(su_expr), m);
|
||||
euf::snode* stab_star_sn = m_sg.mk(stab_star);
|
||||
euf::snode const* stab_star_sn = m_sg.mk(stab_star);
|
||||
|
||||
// 4. Collect all primitive regex constraints on variable `first`
|
||||
euf::snode* x_range = collect_primitive_regex_intersection(first, node, dep);
|
||||
euf::snode const* x_range = collect_primitive_regex_intersection(first, node, dep);
|
||||
if (!x_range)
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ namespace seq {
|
|||
// Maps regex snode id → list of stabilizer snodes.
|
||||
// Each regex may accumulate multiple stabilizers from different
|
||||
// cycle detections. The list is deduplicated by pointer equality.
|
||||
u_map<ptr_vector<euf::snode>> m_stabilizers;
|
||||
u_map<euf::snode_vector> m_stabilizers;
|
||||
|
||||
// Set of regex snode ids that are self-stabilizing, i.e., the
|
||||
// stabilizer for the regex is the regex itself (e.g., r*).
|
||||
|
|
@ -69,12 +69,12 @@ namespace seq {
|
|||
// to_re string literals in a regex. Boundaries partition the
|
||||
// alphabet into equivalence classes where all characters in
|
||||
// the same class produce identical derivatives.
|
||||
void collect_char_boundaries(euf::snode* re, unsigned_vector& bounds) const;
|
||||
void collect_char_boundaries(euf::snode const* re, unsigned_vector& bounds) const;
|
||||
|
||||
// Build a set of representative character snodes, one per
|
||||
// alphabet equivalence class, derived from the boundary points
|
||||
// of the given regex.
|
||||
bool get_alphabet_representatives(euf::snode* re, euf::snode_vector& reps);
|
||||
bool get_alphabet_representatives(euf::snode const* re, euf::snode_vector& reps);
|
||||
|
||||
public:
|
||||
|
||||
|
|
@ -114,28 +114,28 @@ namespace seq {
|
|||
|
||||
// Add a stabilizer for a regex. De-duplicates by pointer equality.
|
||||
// Mirrors ZIPT Environment.AddStabilizer (Environment.cs:114-123).
|
||||
void add_stabilizer(euf::snode* regex, euf::snode* stabilizer);
|
||||
void add_stabilizer(euf::snode const* regex, euf::snode const* stabilizer);
|
||||
|
||||
// Get the union of all stabilizers registered for a regex.
|
||||
// Returns a single re.union snode combining all stabilizers,
|
||||
// or nullptr if no stabilizers exist for the regex.
|
||||
// Mirrors ZIPT Environment.GetStabilizerUnion (Environment.cs:125-128).
|
||||
euf::snode* get_stabilizer_union(euf::snode* regex);
|
||||
euf::snode const* get_stabilizer_union(euf::snode const* regex);
|
||||
|
||||
// Check if any stabilizers have been registered for a regex.
|
||||
bool has_stabilizers(euf::snode* regex) const;
|
||||
bool has_stabilizers(euf::snode const* regex) const;
|
||||
|
||||
// Get raw stabilizer list for a regex (read-only).
|
||||
// Returns nullptr if no stabilizers exist.
|
||||
ptr_vector<euf::snode> const* get_stabilizers(euf::snode* regex) const;
|
||||
euf::snode_vector const* get_stabilizers(euf::snode const* regex) const;
|
||||
|
||||
// Mark a regex as self-stabilizing (stabilizer == regex itself).
|
||||
// Mirrors ZIPT Environment.SetSelfStabilizing (Environment.cs:143-146).
|
||||
void set_self_stabilizing(euf::snode* regex);
|
||||
void set_self_stabilizing(euf::snode const* regex);
|
||||
|
||||
// Check if a regex is marked as self-stabilizing.
|
||||
// Mirrors ZIPT Environment.IsSelfStabilizing (Environment.cs:134-141).
|
||||
bool is_self_stabilizing(euf::snode* regex) const;
|
||||
bool is_self_stabilizing(euf::snode const* regex) const;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Self-stabilizing auto-detection and propagation through derivatives
|
||||
|
|
@ -149,7 +149,7 @@ namespace seq {
|
|||
// - ∅ (fail/empty language): no live derivatives, trivially stable.
|
||||
// - Complement of full_seq (~Σ* = ∅): also trivially stable.
|
||||
// Does NOT mark the snode; call set_self_stabilizing to persist.
|
||||
bool compute_self_stabilizing(euf::snode* regex) const;
|
||||
bool compute_self_stabilizing(euf::snode const* regex) const;
|
||||
|
||||
// After computing a derivative of parent, propagate the self-
|
||||
// stabilizing flag to the derivative result if warranted.
|
||||
|
|
@ -162,12 +162,12 @@ namespace seq {
|
|||
// - If parent is R∩S and both are self-stabilizing → derivative is.
|
||||
// - If parent is ~R and R is self-stabilizing → derivative is.
|
||||
// Updates the internal self-stabilizing set for the derivative.
|
||||
void propagate_self_stabilizing(euf::snode* parent, euf::snode* deriv);
|
||||
void propagate_self_stabilizing(euf::snode const* parent, euf::snode const* deriv);
|
||||
|
||||
// Convenience: compute derivative and propagate self-stabilizing flags.
|
||||
// Equivalent to calling derivative() followed by
|
||||
// propagate_self_stabilizing().
|
||||
euf::snode* derivative_with_propagation(euf::snode* re, euf::snode* elem);
|
||||
euf::snode const* derivative_with_propagation(euf::snode const* re, euf::snode const* elem);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Basic regex predicates
|
||||
|
|
@ -176,7 +176,7 @@ namespace seq {
|
|||
// check if regex is the empty language (∅ / re.empty).
|
||||
// performs structural analysis beyond is_fail() to detect
|
||||
// derived emptiness (e.g., union of empties, concat with empty).
|
||||
bool is_empty_regex(euf::snode* re) const;
|
||||
bool is_empty_regex(euf::snode const* re) const;
|
||||
|
||||
// BFS emptiness check over the Brzozowski derivative automaton.
|
||||
// Explores reachable derivative states using representative
|
||||
|
|
@ -185,7 +185,7 @@ namespace seq {
|
|||
// l_false — regex is definitely non-empty (found a nullable state)
|
||||
// l_undef — inconclusive (hit exploration bound or failed derivative)
|
||||
// max_states caps the number of explored states to prevent blowup.
|
||||
lbool is_empty_bfs(euf::snode* re, unsigned max_states = 10000);
|
||||
lbool is_empty_bfs(euf::snode const* re, unsigned max_states = 10000);
|
||||
|
||||
// Check emptiness of the intersection of multiple regexes.
|
||||
// Uses BFS over the product of Brzozowski derivative automata.
|
||||
|
|
@ -193,33 +193,33 @@ namespace seq {
|
|||
// l_false — intersection is definitely non-empty
|
||||
// l_undef — inconclusive (hit exploration bound)
|
||||
// Mirrors ZIPT NielsenNode.CheckEmptiness (NielsenNode.cs:1429-1469)
|
||||
lbool check_intersection_emptiness(ptr_vector<euf::snode> const& regexes, unsigned max_states = UINT_MAX);
|
||||
lbool check_intersection_emptiness(euf::snode_vector const& regexes, unsigned max_states = UINT_MAX);
|
||||
|
||||
// Check if L(subset_re) ⊆ L(superset_re).
|
||||
// Computed as: subset_re ∩ complement(superset_re) = ∅.
|
||||
// Mirrors ZIPT NielsenNode.IsLanguageSubset (NielsenNode.cs:1382-1385)
|
||||
lbool is_language_subset(euf::snode* subset_re, euf::snode* superset_re);
|
||||
lbool is_language_subset(euf::snode const* subset_re, euf::snode const* superset_re);
|
||||
|
||||
// Collect all primitive regex constraints on variable `var` from
|
||||
// the node's str_mem list and return their intersection as a
|
||||
// single regex snode (using re.inter).
|
||||
// Returns nullptr if no primitive constraints found.
|
||||
euf::snode* collect_primitive_regex_intersection(
|
||||
euf::snode* var, nielsen_node const& node, dep_manager& dep_mgr, dep_tracker& dep) const;
|
||||
euf::snode const* collect_primitive_regex_intersection(
|
||||
euf::snode const* var, nielsen_node const& node, dep_manager& dep_mgr, dep_tracker& dep) const;
|
||||
|
||||
// check if regex is the full language (Σ* / re.all)
|
||||
bool is_full_regex(euf::snode* re) const {
|
||||
static bool is_full_regex(euf::snode const* re) {
|
||||
return re && re->is_full_seq();
|
||||
}
|
||||
|
||||
// check if regex accepts the empty string
|
||||
// (projection-aware: re may contain re.proj operators)
|
||||
bool is_nullable(euf::snode* re) const {
|
||||
bool is_nullable(euf::snode const* re) const {
|
||||
return re && m_sg.re_nullable(re) == l_true;
|
||||
}
|
||||
|
||||
// check if regex is ground (no string variables)
|
||||
bool is_ground(euf::snode* re) const {
|
||||
bool is_ground(euf::snode const* re) const {
|
||||
return re && re->is_ground();
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ namespace seq {
|
|||
|
||||
// compute Brzozowski derivative of regex w.r.t. character element.
|
||||
// returns nullptr on failure.
|
||||
euf::snode* derivative(euf::snode* re, euf::snode* elem) {
|
||||
euf::snode const* derivative(euf::snode const* re, euf::snode const* elem) {
|
||||
return m_sg.brzozowski_deriv(re, elem);
|
||||
}
|
||||
|
||||
|
|
@ -240,17 +240,17 @@ namespace seq {
|
|||
// of symbolic (variable) characters without branching.
|
||||
// Returns the uniform derivative if found, nullptr otherwise.
|
||||
// Mirrors ZIPT's SimplifyCharRegex uniform-derivative fast path.
|
||||
euf::snode* try_uniform_derivative(euf::snode* regex);
|
||||
euf::snode const* try_uniform_derivative(euf::snode const* regex) const;
|
||||
|
||||
// compute derivative of a str_mem constraint: advance past one character.
|
||||
// the string side is shortened by drop_first and the regex is derived.
|
||||
// Propagates self-stabilizing flags from the parent regex to the derivative.
|
||||
str_mem derive(str_mem const& mem, euf::snode* elem) {
|
||||
euf::snode* parent_re = mem.m_regex;
|
||||
euf::snode* deriv = m_sg.brzozowski_deriv(parent_re, elem);
|
||||
str_mem derive(str_mem const& mem, euf::snode const* elem) {
|
||||
euf::snode const* parent_re = mem.m_regex;
|
||||
euf::snode const* deriv = m_sg.brzozowski_deriv(parent_re, elem);
|
||||
if (deriv)
|
||||
propagate_self_stabilizing(parent_re, deriv);
|
||||
euf::snode* new_str = m_sg.drop_first(mem.m_str);
|
||||
euf::snode const* new_str = m_sg.drop_first(mem.m_str);
|
||||
return str_mem(new_str, deriv, mem.m_dep);
|
||||
}
|
||||
|
||||
|
|
@ -268,13 +268,13 @@ namespace seq {
|
|||
// - the string becomes empty and regex is nullable (satisfied)
|
||||
// - the string becomes empty and regex is not nullable (conflict)
|
||||
// modifies mem in-place.
|
||||
simplify_status simplify_ground_prefix(seq::str_mem& mem);
|
||||
simplify_status simplify_ground_prefix(str_mem& mem);
|
||||
|
||||
// consume ground characters from the back of mem.m_str by computing
|
||||
// reverse derivatives. modifies mem in-place.
|
||||
// (reverse derivatives require regex reversal; this is a best-effort
|
||||
// simplification that handles the common case of trailing constants.)
|
||||
simplify_status simplify_ground_suffix(seq::str_mem& mem);
|
||||
simplify_status simplify_ground_suffix(str_mem& mem);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Trivial checks
|
||||
|
|
@ -284,14 +284,14 @@ namespace seq {
|
|||
// returns 1 if satisfied (empty string in nullable regex, or full regex)
|
||||
// returns -1 if conflicting (empty string in non-nullable, or ∅ regex)
|
||||
// returns 0 if undetermined
|
||||
int check_trivial(seq::str_mem const& mem) const;
|
||||
int check_trivial(str_mem const& mem) const;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Minterm and character computation
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// compute minterms (character class partition) from regex
|
||||
void compute_minterms(euf::snode* re, euf::snode_vector& minterms) {
|
||||
void compute_minterms(euf::snode const* re, euf::snode_vector& minterms) {
|
||||
m_sg.compute_minterms(re, minterms);
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ namespace seq {
|
|||
// (fail) minterms. Minterms are regex character-class expressions
|
||||
// forming a partition of the alphabet; callers use them to drive
|
||||
// fresh-variable creation in character-split modifiers.
|
||||
void get_minterms(euf::snode* regex, euf::snode_vector& minterms);
|
||||
void get_minterms(euf::snode const* regex, euf::snode_vector& minterms);
|
||||
|
||||
// collect concrete first-position characters from a regex.
|
||||
// extracts characters reachable from to_re leaves and simple ranges.
|
||||
|
|
@ -314,8 +314,8 @@ namespace seq {
|
|||
// for the Nielsen graph to expand via character-split modifiers.
|
||||
// returns false if the constraint is immediately conflicting
|
||||
// (empty string in non-nullable regex, or derivative yields ∅).
|
||||
bool process_str_mem(seq::str_mem const& mem,
|
||||
vector<seq::str_mem>& out_mems);
|
||||
bool process_str_mem(str_mem const& mem,
|
||||
vector<str_mem>& out_mems);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Cycle detection and stabilizers
|
||||
|
|
@ -324,24 +324,24 @@ namespace seq {
|
|||
// record current regex in the derivation history of a str_mem.
|
||||
// the history tracks a chain of (regex, id) pairs for cycle detection.
|
||||
// returns the updated str_mem.
|
||||
seq::str_mem record_history(seq::str_mem const& mem, euf::snode* history_re);
|
||||
str_mem record_history(str_mem const& mem, euf::snode const* history_re);
|
||||
|
||||
// check if the derivation history of mem contains a cycle, i.e.,
|
||||
// the same regex id appears twice in the history chain.
|
||||
// if found, returns the cycle entry point regex; nullptr otherwise.
|
||||
euf::snode* extract_cycle(seq::str_mem const& mem) const;
|
||||
euf::snode const* extract_cycle(str_mem const& mem) const;
|
||||
|
||||
// check if the derivation history exhibits a cycle.
|
||||
// returns true when the current regex matches a previously seen regex
|
||||
// in the history chain. used to trigger stabilizer introduction.
|
||||
bool detect_cycle(seq::str_mem const& mem) const;
|
||||
bool detect_cycle(str_mem const& mem) const;
|
||||
|
||||
// compute a Kleene star stabilizer from a cycle.
|
||||
// given the regex at the cycle point and the current regex,
|
||||
// builds r* that over-approximates any number of cycle iterations.
|
||||
// returns nullptr if no stabilizer can be computed.
|
||||
euf::snode* stabilizer_from_cycle(euf::snode* cycle_regex,
|
||||
euf::snode* current_regex);
|
||||
euf::snode const* stabilizer_from_cycle(euf::snode const* cycle_regex,
|
||||
euf::snode const* current_regex);
|
||||
|
||||
// Strengthened stabilizer construction with sub-cycle detection.
|
||||
// Replays the consumed character tokens from cycle_history on the
|
||||
|
|
@ -352,8 +352,8 @@ namespace seq {
|
|||
// Returns a union of all sub-cycle stabilizer bodies, or nullptr
|
||||
// if no non-trivial stabilizer can be built.
|
||||
// Mirrors ZIPT StrMem.StabilizerFromCycle (StrMem.cs:163-225).
|
||||
euf::snode* strengthened_stabilizer(euf::snode* cycle_regex,
|
||||
euf::snode* cycle_history);
|
||||
euf::snode const* strengthened_stabilizer(euf::snode const* cycle_regex,
|
||||
euf::snode const* cycle_history);
|
||||
|
||||
// Get filtered stabilizer star: for regex state re, retrieve
|
||||
// existing stabilizers, filter out those whose language can
|
||||
|
|
@ -361,15 +361,15 @@ namespace seq {
|
|||
// remaining in star(union(...)).
|
||||
// Returns nullptr (or empty-equivalent) if no valid stabilizers.
|
||||
// Mirrors ZIPT StrMem.GetFilteredStabilizerStar (StrMem.cs:228-243).
|
||||
euf::snode* get_filtered_stabilizer_star(euf::snode* re,
|
||||
euf::snode* excluded_char);
|
||||
euf::snode const* get_filtered_stabilizer_star(euf::snode const* re,
|
||||
euf::snode const* excluded_char) const;
|
||||
|
||||
// Extract the cycle portion of a str_mem's history by comparing
|
||||
// the current history with an ancestor's history length.
|
||||
// Returns the sub-sequence of tokens consumed since the ancestor,
|
||||
// or nullptr if the history did not advance.
|
||||
euf::snode* extract_cycle_history(seq::str_mem const& current,
|
||||
seq::str_mem const& ancestor);
|
||||
euf::snode const* extract_cycle_history(str_mem const& current,
|
||||
str_mem const& ancestor);
|
||||
|
||||
// try to subsume a str_mem constraint using stabilizer-based
|
||||
// reasoning. Enhanced version: checks if the leading variable's
|
||||
|
|
@ -378,7 +378,7 @@ namespace seq {
|
|||
// Falls back to cycle-based pointer equality check.
|
||||
// returns true if the constraint can be dropped.
|
||||
// Mirrors ZIPT StrMem.TrySubsume (StrMem.cs:354-386).
|
||||
bool try_subsume(seq::str_mem const& mem, seq::nielsen_node const& node);
|
||||
bool try_subsume(str_mem const& mem, nielsen_node const& node);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,19 +29,19 @@ namespace smt {
|
|||
|
||||
struct tracked_str_eq : seq::str_eq {
|
||||
enode *m_l, *m_r;
|
||||
tracked_str_eq(euf::snode *lhs, euf::snode *rhs, enode *l, enode *r, seq::dep_tracker const &dep)
|
||||
tracked_str_eq(euf::snode const* lhs, euf::snode const* rhs, enode* l, enode* r, seq::dep_tracker const &dep)
|
||||
: str_eq(lhs, rhs, dep), m_l(l), m_r(r) {}
|
||||
};
|
||||
|
||||
struct tracked_str_deq : seq::str_deq {
|
||||
sat::literal lit;
|
||||
tracked_str_deq(euf::snode *lhs, euf::snode *rhs, const sat::literal lit, seq::dep_tracker const &dep)
|
||||
tracked_str_deq(euf::snode const* lhs, euf::snode const* rhs, const sat::literal lit, seq::dep_tracker const &dep)
|
||||
: str_deq(lhs, rhs, dep), lit(lit) {}
|
||||
};
|
||||
|
||||
struct tracked_str_mem : seq::str_mem {
|
||||
sat::literal lit;
|
||||
tracked_str_mem(euf::snode *str, euf::snode *regex, const sat::literal lit, seq::dep_tracker const &dep)
|
||||
tracked_str_mem(euf::snode const* str, euf::snode const* regex, const sat::literal lit, seq::dep_tracker const &dep)
|
||||
: str_mem(str, regex, dep), lit(lit) {}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ namespace smt {
|
|||
class seq_snode_value_proc : public model_value_proc {
|
||||
seq_model& m_owner;
|
||||
enode* m_node;
|
||||
euf::snode* m_snode;
|
||||
ptr_vector<enode> m_dependencies;
|
||||
euf::snode const* m_snode;
|
||||
enode_vector m_dependencies;
|
||||
|
||||
public:
|
||||
seq_snode_value_proc(seq_model& owner, enode* node, euf::snode* snode)
|
||||
seq_snode_value_proc(seq_model& owner, enode* node, euf::snode const* snode)
|
||||
: m_owner(owner), m_node(node), m_snode(snode) {
|
||||
m_owner.collect_dependencies(m_snode, m_dependencies);
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ namespace smt {
|
|||
// internalization and one re-created during the Nielsen search), so snode
|
||||
// ids are NOT a reliable key. Expressions are perfectly shared, so their
|
||||
// id is stable across all snodes that denote the same term.
|
||||
static unsigned var_key(euf::snode* n) {
|
||||
static unsigned var_key(euf::snode const* n) {
|
||||
return n->first()->get_expr()->get_id();
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ namespace smt {
|
|||
}
|
||||
|
||||
// look up snode for this expression
|
||||
euf::snode* sn = m_sg.find(e);
|
||||
euf::snode const* sn = m_sg.find(e);
|
||||
IF_VERBOSE(2, {
|
||||
verbose_stream() << "nseq mk_value: expr=" << mk_bounded_pp(e, m, 2);
|
||||
if (sn) verbose_stream() << " snode[" << sn->id() << "] kind=" << (int)sn->kind();
|
||||
|
|
@ -197,7 +197,7 @@ namespace smt {
|
|||
// When a new substitution (s.m_var -> s.m_replacement) is applied,
|
||||
// substitute s.m_var in all existing values, then record the new binding.
|
||||
|
||||
vector<std::pair<euf::snode*, euf::snode*>> bindings;
|
||||
vector<std::pair<euf::snode const*, euf::snode const*>> bindings;
|
||||
for (seq::nielsen_edge* e : sat_path) {
|
||||
for (seq::nielsen_subst const& s : e->subst()) {
|
||||
SASSERT(s.m_var);
|
||||
|
|
@ -226,9 +226,9 @@ namespace smt {
|
|||
}
|
||||
}
|
||||
|
||||
void seq_model::collect_dependencies(euf::snode *n, ptr_vector<enode> &deps) const {
|
||||
void seq_model::collect_dependencies(euf::snode const* n, enode_vector &deps) const {
|
||||
uint_set seen;
|
||||
buffer<euf::snode*> todo;
|
||||
buffer<euf::snode const*> todo;
|
||||
todo.push_back(n);
|
||||
while (!todo.empty()) {
|
||||
auto curr = todo.back();
|
||||
|
|
@ -259,7 +259,7 @@ namespace smt {
|
|||
// when using the dependencies to build a value for n we should
|
||||
// map the values that are passed in to the sub-terms that are listed as dependencies.
|
||||
// sub-terms are under concat, power and unit
|
||||
euf::snode *replacement = nullptr;
|
||||
euf::snode const* replacement = nullptr;
|
||||
if (m_var_replacement.find(var_key(curr), replacement))
|
||||
todo.push_back(replacement);
|
||||
}
|
||||
|
|
@ -278,7 +278,7 @@ namespace smt {
|
|||
// verbose_stream() << "collect " << mk_pp(n->get_expr(), m) << " " << deps.size() << "\n";
|
||||
}
|
||||
|
||||
expr_ref seq_model::snode_to_value(euf::snode *n, ptr_vector<enode> const &deps, expr_ref_vector const &values) {
|
||||
expr_ref seq_model::snode_to_value(euf::snode const* n, enode_vector const &deps, expr_ref_vector const &values) {
|
||||
// var2value: leaf deps keyed by expression ID (populated from `deps`/`values`).
|
||||
// node2value: computed nodes keyed by (snode_id * 2 + is_recursive).
|
||||
// The recursion flag is part of the key because the SAME variable snode
|
||||
|
|
@ -288,12 +288,12 @@ namespace smt {
|
|||
u_map<expr *> var2value;
|
||||
u_map<expr *> node2value;
|
||||
// resolve: check leaf deps by expression ID, computed nodes by (snode,recursive) key.
|
||||
auto resolve = [&](euf::snode* s, expr*& out) -> bool {
|
||||
auto resolve = [&](euf::snode const* s, expr*& out) -> bool {
|
||||
if (var2value.find(s->get_expr()->get_id(), out))
|
||||
return true;
|
||||
return node2value.find(s->id(), out);
|
||||
};
|
||||
buffer<euf::snode *> todo;
|
||||
buffer<euf::snode const* > todo;
|
||||
for (unsigned i = 0; i < deps.size(); ++i) {
|
||||
var2value.insert(deps[i]->get_expr_id(), values[i]);
|
||||
}
|
||||
|
|
@ -355,7 +355,7 @@ namespace smt {
|
|||
continue; // not all arguments processed yet, will retry after children
|
||||
}
|
||||
else if (curr->is_var()) {
|
||||
euf::snode *replacement = nullptr;
|
||||
euf::snode const* replacement = nullptr;
|
||||
if (m_var_replacement.find(var_key(curr), replacement)) {
|
||||
// outer variable: its value is the value of its replacement.
|
||||
expr* rv = nullptr;
|
||||
|
|
@ -386,7 +386,7 @@ namespace smt {
|
|||
return expr_ref(result, m);
|
||||
}
|
||||
|
||||
expr* seq_model::get_var_value(euf::snode* var) {
|
||||
expr* seq_model::get_var_value(euf::snode const* var) {
|
||||
SASSERT(var);
|
||||
const unsigned key = var_key(var);
|
||||
expr* val = nullptr;
|
||||
|
|
@ -434,13 +434,13 @@ namespace smt {
|
|||
return val;
|
||||
}
|
||||
|
||||
expr* seq_model::mk_fresh_value(euf::snode* var) {
|
||||
expr* seq_model::mk_fresh_value(euf::snode const* var) {
|
||||
SASSERT(var->get_expr());
|
||||
SASSERT(m_seq.is_seq(var->get_expr()));
|
||||
auto srt = var->get_expr()->get_sort();
|
||||
|
||||
// check if this variable has regex constraints
|
||||
euf::snode* re = nullptr;
|
||||
euf::snode const* re = nullptr;
|
||||
unsigned key = var_key(var);
|
||||
if (m_var_regex.find(key, re) && re) {
|
||||
expr* re_expr = re->get_expr();
|
||||
|
|
@ -517,9 +517,8 @@ namespace smt {
|
|||
return m_seq.str.mk_empty(srt);
|
||||
}
|
||||
|
||||
lbool seq_model::projection_witness(euf::snode* re0, expr_ref& witness) {
|
||||
if (!re0 || !re0->get_expr())
|
||||
return l_undef;
|
||||
lbool seq_model::projection_witness(euf::snode const* re0, expr_ref& witness) const {
|
||||
SASSERT(re0 && re0->get_expr());
|
||||
sort* seq_sort = nullptr;
|
||||
if (!m_seq.is_re(re0->get_expr(), seq_sort))
|
||||
return l_undef;
|
||||
|
|
@ -529,7 +528,7 @@ namespace smt {
|
|||
// accepting when re_nullable (projection-aware) reports nullable.
|
||||
// The regex carries the length intersection (∩ Σ^n), so an accepting
|
||||
// run has exactly the requested length and the search is finite.
|
||||
vector<std::pair<euf::snode*, zstring>> work;
|
||||
vector<std::pair<euf::snode const*, zstring>> work;
|
||||
work.push_back({re0, zstring()});
|
||||
uint_set visited;
|
||||
visited.insert(re0->id());
|
||||
|
|
@ -537,7 +536,7 @@ namespace smt {
|
|||
unsigned head = 0;
|
||||
const unsigned MAX_STATES = 100000;
|
||||
while (head < work.size() && head < MAX_STATES) {
|
||||
euf::snode* st = work[head].first;
|
||||
euf::snode const* st = work[head].first;
|
||||
zstring w = work[head].second;
|
||||
++head;
|
||||
|
||||
|
|
@ -550,8 +549,8 @@ namespace smt {
|
|||
|
||||
euf::snode_vector mts;
|
||||
m_sg.compute_minterms(st, mts);
|
||||
for (euf::snode* mt : mts) {
|
||||
euf::snode* d = m_sg.brzozowski_deriv(st, mt);
|
||||
for (euf::snode const* mt : mts) {
|
||||
euf::snode const* d = m_sg.brzozowski_deriv(st, mt);
|
||||
if (!d || d->is_fail())
|
||||
continue;
|
||||
if (visited.contains(d->id()))
|
||||
|
|
@ -584,7 +583,7 @@ namespace smt {
|
|||
continue; // empty string in nullable regex: already satisfied, no variable to constrain
|
||||
VERIFY(mem.is_primitive()); // everything else should have been eliminated already
|
||||
unsigned id = var_key(mem.m_str);
|
||||
euf::snode* existing = nullptr;
|
||||
euf::snode const* existing = nullptr;
|
||||
if (m_var_regex.find(id, existing) && existing) {
|
||||
// intersect with existing constraint:
|
||||
// build re.inter(existing, new_regex)
|
||||
|
|
@ -592,7 +591,7 @@ namespace smt {
|
|||
expr* e2 = mem.m_regex->get_expr();
|
||||
if (e1 && e2) {
|
||||
expr_ref inter(m_seq.re.mk_inter(e1, e2), m);
|
||||
euf::snode* inter_sn = m_sg.mk(inter);
|
||||
euf::snode const* inter_sn = m_sg.mk(inter);
|
||||
SASSERT(inter_sn);
|
||||
m_var_regex.insert(id, inter_sn);
|
||||
}
|
||||
|
|
@ -623,7 +622,7 @@ namespace smt {
|
|||
#if 0
|
||||
// retained in case we want to reconstruct small power unfoldings.
|
||||
|
||||
expr_ref seq_model::snode_to_value(euf::snode* n, expr_ref_vector const& values) {
|
||||
expr_ref seq_model::snode_to_value(euf::snode const* n, expr_ref_vector const& values) {
|
||||
SASSERT(n);
|
||||
if (n->is_empty()) {
|
||||
sort* srt = n->get_sort();
|
||||
|
|
@ -659,7 +658,7 @@ namespace smt {
|
|||
}
|
||||
|
||||
if (n->is_var()) {
|
||||
euf::snode *replacement = nullptr;
|
||||
euf::snode const* replacement = nullptr;
|
||||
if (!m_var_replacement.find(n->id(), replacement))
|
||||
return expr_ref(get_var_value(n), m);
|
||||
return mk_value_with_dependencies(replacement, values);
|
||||
|
|
|
|||
|
|
@ -61,14 +61,14 @@ namespace smt {
|
|||
// variable assignments extracted from the satisfying Nielsen node.
|
||||
// maps snode id -> expr* (concrete value)
|
||||
u_map<expr*> m_var_values;
|
||||
u_map<euf::snode*> m_var_replacement;
|
||||
u_map<euf::snode const*> m_var_replacement;
|
||||
|
||||
// trail for GC protection of generated expressions
|
||||
expr_ref_vector m_trail;
|
||||
|
||||
// per-variable regex constraints: maps snode id -> intersected regex snode.
|
||||
// collected during init() from the state's str_mem list.
|
||||
u_map<euf::snode*> m_var_regex;
|
||||
u_map<euf::snode const*> m_var_regex;
|
||||
|
||||
public:
|
||||
seq_model(ast_manager& m, context& ctx, seq_util& seq,
|
||||
|
|
@ -101,21 +101,21 @@ namespace smt {
|
|||
// Returns a concrete Z3 expression.
|
||||
// Optionally uses pre-evaluated model values for
|
||||
// enode dependencies (provided by model_generator).
|
||||
expr_ref snode_to_value(euf::snode *n, ptr_vector<smt::enode> const &nodes, expr_ref_vector const &values);
|
||||
expr_ref snode_to_value(euf::snode const* n, enode_vector const &nodes, expr_ref_vector const &values);
|
||||
|
||||
// Collect enode dependencies required to evaluate an snode value.
|
||||
void collect_dependencies(euf::snode* n, ptr_vector<enode>& deps) const;
|
||||
void collect_dependencies(euf::snode const* n, enode_vector& deps) const;
|
||||
|
||||
// look up or compute the value for an snode variable.
|
||||
// If no assignment exists, delegates to mk_fresh_value.
|
||||
expr* get_var_value(euf::snode* var);
|
||||
expr* get_var_value(euf::snode const* var);
|
||||
|
||||
// generate a fresh value for a variable, respecting regex
|
||||
// membership constraints. If the variable has associated
|
||||
// regex constraints (collected during init), generates a
|
||||
// witness satisfying the intersection; otherwise falls back
|
||||
// to a plain fresh value from the factory.
|
||||
expr* mk_fresh_value(euf::snode* var);
|
||||
expr* mk_fresh_value(euf::snode const* var);
|
||||
|
||||
// Witness extraction for regexes that contain a projection operator
|
||||
// (re.proj), which the standard seq_rewriter::some_seq_in_re cannot
|
||||
|
|
@ -123,7 +123,7 @@ namespace smt {
|
|||
// derivative automaton (m_sg) for a nullable (accepting) state,
|
||||
// building the accepting word. Returns l_true and sets `witness`
|
||||
// on success.
|
||||
lbool projection_witness(euf::snode* re, expr_ref& witness);
|
||||
lbool projection_witness(euf::snode const* re, expr_ref& witness) const;
|
||||
|
||||
// collect per-variable regex constraints from the state.
|
||||
// For each positive str_mem, records the regex (or intersects
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ namespace smt {
|
|||
m_rewriter(m),
|
||||
m_arith_value(m),
|
||||
m_egraph(m),
|
||||
m_sgraph(m, m_egraph),
|
||||
m_sg(m, m_egraph),
|
||||
m_length_solver(m),
|
||||
m_context_solver(ctx, [this](expr* e1, expr* e2) { m_axioms.diseq_axiom(e1, e2); }),
|
||||
m_nielsen(m_sgraph, m_length_solver, m_context_solver),
|
||||
m_nielsen(m_sg, m_length_solver, m_context_solver),
|
||||
m_axioms(m_th_rewriter),
|
||||
m_regex(m_sgraph),
|
||||
m_model(m, ctx, m_seq, m_rewriter, m_sgraph),
|
||||
m_regex(m_sg),
|
||||
m_model(m, ctx, m_seq, m_rewriter, m_sg),
|
||||
m_relevant_lengths(m)
|
||||
{
|
||||
std::function<void(expr_ref_vector const&)> add_clause =
|
||||
|
|
@ -220,8 +220,8 @@ namespace smt {
|
|||
}
|
||||
if (!m_seq.is_seq(e1))
|
||||
return;
|
||||
euf::snode *s1 = get_snode(e1);
|
||||
euf::snode *s2 = get_snode(e2);
|
||||
euf::snode const* s1 = get_snode(e1);
|
||||
euf::snode const* s2 = get_snode(e2);
|
||||
seq::dep_tracker dep = nullptr;
|
||||
ctx.push_trail(restore_vector(m_prop_queue));
|
||||
m_prop_queue.push_back(eq_item(s1, s2, get_enode(v1), get_enode(v2), dep));
|
||||
|
|
@ -266,8 +266,8 @@ namespace smt {
|
|||
if (get_fparams().m_nseq_axiomatize_diseq)
|
||||
m_axioms.diseq_axiom(e1, e2);
|
||||
else {
|
||||
euf::snode *s1 = get_snode(e1);
|
||||
euf::snode *s2 = get_snode(e2);
|
||||
euf::snode const* s1 = get_snode(e1);
|
||||
euf::snode const* s2 = get_snode(e2);
|
||||
const seq::dep_tracker dep = nullptr;
|
||||
ctx.push_trail(restore_vector(m_prop_queue));
|
||||
const expr_ref eq_expr(m.mk_eq(e1, e2), m);
|
||||
|
|
@ -284,7 +284,7 @@ namespace smt {
|
|||
// Boolean assignment notification
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void theory_nseq::assign_eh(bool_var v, bool is_true) {
|
||||
void theory_nseq::assign_eh(const bool_var v, const bool is_true) {
|
||||
try {
|
||||
expr* e = ctx.bool_var2expr(v);
|
||||
const literal lit(v, !is_true);
|
||||
|
|
@ -292,8 +292,8 @@ namespace smt {
|
|||
expr *s = nullptr, *re = nullptr, *a = nullptr, *b = nullptr;
|
||||
TRACE(seq, tout << (is_true ? "" : "¬") << mk_bounded_pp(e, m, 3) << "\n";);
|
||||
if (m_seq.str.is_in_re(e, s, re)) {
|
||||
euf::snode* sn_str = get_snode(s);
|
||||
euf::snode* sn_re = get_snode(re);
|
||||
euf::snode const* sn_str = get_snode(s);
|
||||
euf::snode const* sn_re = get_snode(re);
|
||||
const seq::dep_tracker dep = nullptr;
|
||||
if (is_true) {
|
||||
ctx.push_trail(restore_vector(m_prop_queue));
|
||||
|
|
@ -306,7 +306,7 @@ namespace smt {
|
|||
// so the Nielsen graph sees it uniformly; the original negative literal
|
||||
// is kept in mem_source for conflict reporting.
|
||||
const expr_ref re_compl(m_seq.re.mk_complement(re), m);
|
||||
euf::snode* sn_re_compl = get_snode(re_compl.get());
|
||||
euf::snode const* sn_re_compl = get_snode(re_compl.get());
|
||||
ctx.push_trail(restore_vector(m_prop_queue));
|
||||
m_prop_queue.push_back(mem_item(sn_str, sn_re_compl, lit, dep));
|
||||
m_last_constraint_added = ctx.get_scope_level();
|
||||
|
|
@ -385,13 +385,13 @@ namespace smt {
|
|||
if (n1->get_root() != n2->get_root()) {
|
||||
const auto v1 = mk_var(n1);
|
||||
const auto v2 = mk_var(n2);
|
||||
const literal lit(v, false);
|
||||
const literal l(v, false);
|
||||
ctx.mark_as_relevant(n1);
|
||||
ctx.mark_as_relevant(n2);
|
||||
TRACE(seq, tout << "is-eq " << mk_pp(a, m) << " == " << mk_pp(b, m) << "\n");
|
||||
justification* js = ctx.mk_justification(
|
||||
ext_theory_eq_propagation_justification(
|
||||
get_id(), ctx, 1, &lit, 0, nullptr, n1, n2));
|
||||
get_id(), ctx, 1, &l, 0, nullptr, n1, n2));
|
||||
ctx.assign_eq(n1, n2, eq_justification(js));
|
||||
new_eq_eh(v1, v2);
|
||||
}
|
||||
|
|
@ -421,14 +421,14 @@ namespace smt {
|
|||
|
||||
void theory_nseq::push_scope_eh() {
|
||||
theory::push_scope_eh();
|
||||
m_sgraph.push();
|
||||
m_sg.push();
|
||||
|
||||
}
|
||||
|
||||
void theory_nseq::pop_scope_eh(unsigned num_scopes) {
|
||||
try {
|
||||
theory::pop_scope_eh(num_scopes);
|
||||
m_sgraph.pop(num_scopes);
|
||||
m_sg.pop(num_scopes);
|
||||
// A pop may remove constraints and/or unassign forced Nielsen
|
||||
// literals; conservatively invalidate the cached SAT path.
|
||||
if (m_can_hot_restart && ctx.get_scope_level() - num_scopes < m_last_constraint_added)
|
||||
|
|
@ -506,15 +506,15 @@ namespace smt {
|
|||
|
||||
if (!mem.m_str->is_empty()) {
|
||||
if (mem.m_str->first()->is_char()) {
|
||||
euf::snode* re_node = mem.m_regex;
|
||||
euf::snode* str_node = mem.m_str;
|
||||
euf::snode const* re_node = mem.m_regex;
|
||||
euf::snode const* str_node = mem.m_str;
|
||||
do {
|
||||
// eliminate leading character by derivatives; derive by the
|
||||
// CURRENT leading char (str_node->first()), not the original
|
||||
// mem.m_str->first() — otherwise a multi-char prefix is derived
|
||||
// by its first char repeatedly (unsound).
|
||||
re_node = m_sgraph.brzozowski_deriv(re_node, str_node->first());
|
||||
str_node = m_sgraph.drop_first(str_node);
|
||||
re_node = m_sg.brzozowski_deriv(re_node, str_node->first());
|
||||
str_node = m_sg.drop_first(str_node);
|
||||
} while (!str_node->is_empty() && str_node->first()->is_char());
|
||||
|
||||
if (re_node->is_fail()) {
|
||||
|
|
@ -532,7 +532,7 @@ namespace smt {
|
|||
}
|
||||
else {
|
||||
// check nullability
|
||||
if (m_sgraph.re_nullable(mem.m_regex) == l_true) {
|
||||
if (m_sg.re_nullable(mem.m_regex) == l_true) {
|
||||
// empty string in nullable regex → trivially satisfied
|
||||
m_ignored_mem.insert(mem.lit);
|
||||
ctx.push_trail(insert_map(m_ignored_mem, mem.lit));
|
||||
|
|
@ -570,7 +570,7 @@ namespace smt {
|
|||
}
|
||||
|
||||
// empty string in non-nullable regex → conflict
|
||||
if (mem.m_str->is_empty() && m_sgraph.re_nullable(mem.m_regex) == l_false) {
|
||||
if (mem.m_str->is_empty() && m_sg.re_nullable(mem.m_regex) == l_false) {
|
||||
literal_vector lits;
|
||||
lits.push_back(mem.lit);
|
||||
set_conflict(lits);
|
||||
|
|
@ -607,25 +607,11 @@ namespace smt {
|
|||
if (get_fparams().m_nseq_regex_factorization_eager &&
|
||||
get_fparams().m_nseq_regex_factorization_threshold > 0 &&
|
||||
mem.m_str->is_concat()) {
|
||||
const app* const a = to_app(s);
|
||||
const unsigned na = a->get_num_args();
|
||||
SASSERT(na >= 2);
|
||||
|
||||
const expr_ref head(a->get_arg(0), m);
|
||||
const expr_ref tail(m_seq.str.mk_concat(na - 1, a->get_args() + 1, s->get_sort()), m);
|
||||
|
||||
const unsigned threshold = get_fparams().m_nseq_regex_factorization_threshold;
|
||||
|
||||
split_set pairs;
|
||||
if (!m_rewriter.split(mem.m_regex->get_expr(), pairs, threshold))
|
||||
// we give up
|
||||
return;
|
||||
|
||||
//std::cout << "Pairs:\n";
|
||||
//for (auto& pair: pairs) {
|
||||
// std::cout << mk_pp(pair.m_d, m) << " ; " << mk_pp(pair.m_n, m) << std::endl;
|
||||
//}
|
||||
|
||||
m_rewriter.simplify_split(pairs);
|
||||
auto [head, tail] = seq::split_membership(mem.m_str, mem.m_regex, m_sg, threshold, pairs);
|
||||
|
||||
if (pairs.empty()) {
|
||||
// no viable splits
|
||||
|
|
@ -638,24 +624,24 @@ namespace smt {
|
|||
TRACE(seq, tout << "eager regex fact: " << mk_pp(s, m) << " in "
|
||||
<< mk_pp(re, m) << " -> " << pairs.size() << " splits\n";);
|
||||
|
||||
if (!ctx.e_internalized(head))
|
||||
ctx.internalize(head, false);
|
||||
if (!ctx.e_internalized(tail))
|
||||
ctx.internalize(tail, false);
|
||||
if (!ctx.e_internalized(head->get_expr()))
|
||||
ctx.internalize(head->get_expr(), false);
|
||||
if (!ctx.e_internalized(tail->get_expr()))
|
||||
ctx.internalize(tail->get_expr(), false);
|
||||
|
||||
// forward direction; mk_literal Tseitin-encodes each conjunction
|
||||
literal_vector lits;
|
||||
lits.push_back(~mem.lit);
|
||||
//std::cout << "Decomposing into:\n";
|
||||
std::cout << "Decomposing into:\n";
|
||||
for (auto const& sp : pairs) {
|
||||
expr_ref mem_head(m_seq.re.mk_in_re(head, sp.m_d), m);
|
||||
expr_ref mem_tail(m_seq.re.mk_in_re(tail, sp.m_n), m);
|
||||
expr_ref mem_head(m_seq.re.mk_in_re(head->get_expr(), sp.m_d), m);
|
||||
expr_ref mem_tail(m_seq.re.mk_in_re(tail->get_expr(), sp.m_n), m);
|
||||
expr_ref conj(m.mk_and(mem_head, mem_tail), m);
|
||||
lits.push_back(mk_literal(conj));
|
||||
//seq::dep_tracker dep = nullptr;
|
||||
//std::cout << seq::mem_pp(seq::str_mem(m_sgraph.mk(head), m_sgraph.mk(sp.m_d), dep), m) << " && " << seq::mem_pp(seq::str_mem(m_sgraph.mk(tail), m_sgraph.mk(sp.m_n), dep), m) << "\n";
|
||||
seq::dep_tracker dep = nullptr;
|
||||
std::cout << seq::mem_pp(seq::str_mem(head, m_sg.mk(sp.m_d), dep), m) << " && " << seq::mem_pp(seq::str_mem(tail, m_sg.mk(sp.m_n), dep), m) << "\n";
|
||||
}
|
||||
//std::cout << std::endl;
|
||||
std::cout << std::endl;
|
||||
ctx.mk_th_axiom(get_id(), lits.size(), lits.data());
|
||||
m_ignored_mem.insert(mem.lit);
|
||||
ctx.push_trail(insert_map(m_ignored_mem, mem.lit));
|
||||
|
|
@ -1439,8 +1425,8 @@ namespace smt {
|
|||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
euf::snode* theory_nseq::get_snode(expr* e) {
|
||||
return m_sgraph.mk(e);
|
||||
euf::snode const* theory_nseq::get_snode(expr* e) {
|
||||
return m_sg.mk(e);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -1619,9 +1605,9 @@ namespace smt {
|
|||
// Check intersection emptiness for each variable.
|
||||
for (auto &[var_id, mem_indices] : var_to_mems) {
|
||||
|
||||
ptr_vector<euf::snode> regexes;
|
||||
for (unsigned i : mem_indices) {
|
||||
euf::snode* re = mems[i]->m_regex;
|
||||
euf::snode_vector regexes;
|
||||
for (const unsigned i : mem_indices) {
|
||||
euf::snode const* re = mems[i]->m_regex;
|
||||
SASSERT(re);
|
||||
regexes.push_back(re);
|
||||
}
|
||||
|
|
@ -1828,11 +1814,11 @@ namespace smt {
|
|||
|
||||
SASSERT(!var_to_mems.empty());
|
||||
|
||||
for (expr *len_expr : m_relevant_lengths) {
|
||||
expr *s = nullptr;
|
||||
for (expr* len_expr : m_relevant_lengths) {
|
||||
expr* s = nullptr;
|
||||
VERIFY(m_seq.str.is_length(len_expr, s));
|
||||
|
||||
euf::snode *s_node = m_sgraph.find(s);
|
||||
euf::snode const* s_node = m_sg.find(s);
|
||||
SASSERT(s_node);
|
||||
|
||||
unsigned var_id = s_node->id();
|
||||
|
|
@ -1850,7 +1836,7 @@ namespace smt {
|
|||
const unsigned l = val_l.get_unsigned();
|
||||
|
||||
unsigned_vector const &mem_indices = var_to_mems[var_id];
|
||||
ptr_vector<euf::snode> regexes;
|
||||
euf::snode_vector regexes;
|
||||
bool has_projection = false;
|
||||
for (auto i : mem_indices) {
|
||||
SASSERT(mems[i].well_formed());
|
||||
|
|
@ -1873,18 +1859,18 @@ namespace smt {
|
|||
|
||||
SASSERT(!regexes.empty());
|
||||
sort *ele_sort;
|
||||
VERIFY(m_seq.is_seq(m_sgraph.get_str_sort(), ele_sort));
|
||||
VERIFY(m_seq.is_seq(m_sg.get_str_sort(), ele_sort));
|
||||
unsigned g = 1;
|
||||
if (m_gradient_cache.contains(s))
|
||||
g = m_gradient_cache[s];
|
||||
else
|
||||
m_gradient_cache.insert(s, 1);
|
||||
|
||||
expr_ref allchar(m_seq.re.mk_full_char(m_seq.re.mk_re(m_sgraph.get_str_sort())), m);
|
||||
expr_ref allchar(m_seq.re.mk_full_char(m_seq.re.mk_re(m_sg.get_str_sort())), m);
|
||||
expr_ref l_expr(m_autil.mk_int(l), m);
|
||||
expr_ref loop_l(m_seq.re.mk_loop_proper(allchar.get(), l, l), m);
|
||||
|
||||
euf::snode *sigmal_node = get_snode(loop_l.get());
|
||||
euf::snode const* sigmal_node = get_snode(loop_l.get());
|
||||
regexes.push_back(sigmal_node);
|
||||
SASSERT(regexes.size() > 1);
|
||||
|
||||
|
|
@ -1899,7 +1885,7 @@ namespace smt {
|
|||
expr_ref star_g(m_seq.re.mk_star(loop_g.get()), m);
|
||||
expr_ref sigmal_g_expr(m_seq.re.mk_concat(loop_l.get(), star_g.get()), m);
|
||||
|
||||
euf::snode *sigmal_g_node = get_snode(sigmal_g_expr.get());
|
||||
euf::snode const* sigmal_g_node = get_snode(sigmal_g_expr.get());
|
||||
regexes.push_back(sigmal_g_node);
|
||||
|
||||
lbool result_g = m_regex.check_intersection_emptiness(regexes);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace smt {
|
|||
seq_rewriter m_rewriter;
|
||||
arith_value m_arith_value;
|
||||
euf::egraph m_egraph; // private egraph (not shared with smt context)
|
||||
euf::sgraph m_sgraph; // private sgraph
|
||||
euf::sgraph m_sg; // private sgraph
|
||||
// m_context_solver must be declared before m_nielsen: its address is passed
|
||||
// to the m_nielsen constructor and must remain stable for the object's lifetime.
|
||||
sub_solver m_length_solver;
|
||||
|
|
@ -147,7 +147,7 @@ namespace smt {
|
|||
}
|
||||
void set_propagate(enode_pair_vector const &eqs, literal_vector const &lits, literal p);
|
||||
bool add_nielsen_assumptions();
|
||||
euf::snode* get_snode(expr* e);
|
||||
euf::snode const* get_snode(expr* e);
|
||||
|
||||
// propagation dispatch helpers
|
||||
void propagate_eq(tracked_str_eq const& eq) const;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ static euf::enode* get_node(euf::egraph& g, seq_util& seq, expr* e) {
|
|||
if (n) return n;
|
||||
euf::enode_vector args;
|
||||
if (is_app(e))
|
||||
for (expr* arg : *to_app(e))
|
||||
for (expr* arg : *to_app(e)) {
|
||||
args.push_back(get_node(g, seq, arg));
|
||||
}
|
||||
n = g.mk(e, 0, args.size(), args.data());
|
||||
if (seq.is_seq(e) || seq.is_re(e))
|
||||
g.add_th_var(n, ++s_var, seq.get_family_id());
|
||||
|
|
@ -42,20 +43,20 @@ static void test_sgraph_basic() {
|
|||
expr_ref empty(seq.str.mk_empty(str_sort), m);
|
||||
expr_ref xy(seq.str.mk_concat(x, y), m);
|
||||
|
||||
euf::snode* sx = sg.mk(x);
|
||||
euf::snode const* sx = sg.mk(x);
|
||||
SASSERT(sx);
|
||||
SASSERT(sx->is_var());
|
||||
SASSERT(!sx->is_ground());
|
||||
SASSERT(sx->is_regex_free());
|
||||
SASSERT(sx->length() == 1);
|
||||
|
||||
euf::snode* se = sg.mk(empty);
|
||||
euf::snode const* se = sg.mk(empty);
|
||||
SASSERT(se);
|
||||
SASSERT(se->is_empty());
|
||||
SASSERT(se->is_ground());
|
||||
SASSERT(se->length() == 0);
|
||||
|
||||
euf::snode* sxy = sg.mk(xy);
|
||||
euf::snode const* sxy = sg.mk(xy);
|
||||
SASSERT(sxy);
|
||||
SASSERT(sxy->is_concat());
|
||||
SASSERT(!sxy->is_ground());
|
||||
|
|
@ -169,7 +170,7 @@ static void test_seq_plugin_star_merge() {
|
|||
|
||||
// register in sgraph
|
||||
sg.mk(star_star);
|
||||
euf::snode* s = sg.find(star_x);
|
||||
euf::snode const* s = sg.find(star_x);
|
||||
SASSERT(s && s->is_star());
|
||||
|
||||
std::cout << g << "\n";
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ static void test_sgraph_classify() {
|
|||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq_util seq(m);
|
||||
sort_ref str_sort(seq.str.mk_string_sort(), m);
|
||||
const seq_util seq(m);
|
||||
const sort_ref str_sort(seq.str.mk_string_sort(), m);
|
||||
|
||||
// string variable
|
||||
expr_ref x(m.mk_const("x", str_sort), m);
|
||||
euf::snode* sx = sg.mk(x);
|
||||
const expr_ref x(m.mk_const("x", str_sort), m);
|
||||
euf::snode const* sx = sg.mk(x);
|
||||
SASSERT(sx && sx->is_var());
|
||||
SASSERT(!sx->is_ground());
|
||||
SASSERT(sx->is_regex_free());
|
||||
|
|
@ -43,8 +43,8 @@ static void test_sgraph_classify() {
|
|||
SASSERT(sx->is_token());
|
||||
|
||||
// empty string
|
||||
expr_ref empty(seq.str.mk_empty(str_sort), m);
|
||||
euf::snode* se = sg.mk(empty);
|
||||
const expr_ref empty(seq.str.mk_empty(str_sort), m);
|
||||
euf::snode const* se = sg.mk(empty);
|
||||
SASSERT(se && se->is_empty());
|
||||
SASSERT(se->is_ground());
|
||||
SASSERT(se->level() == 0);
|
||||
|
|
@ -52,9 +52,9 @@ static void test_sgraph_classify() {
|
|||
SASSERT(!se->is_token());
|
||||
|
||||
// character unit with literal char
|
||||
expr_ref ch(seq.str.mk_char('A'), m);
|
||||
expr_ref unit_a(seq.str.mk_unit(ch), m);
|
||||
euf::snode* sca = sg.mk(unit_a);
|
||||
const expr_ref ch(seq.str.mk_char('A'), m);
|
||||
const expr_ref unit_a(seq.str.mk_unit(ch), m);
|
||||
euf::snode const* sca = sg.mk(unit_a);
|
||||
SASSERT(sca && sca->is_char());
|
||||
SASSERT(sca->is_ground());
|
||||
SASSERT(sca->level() == 1);
|
||||
|
|
@ -62,9 +62,9 @@ static void test_sgraph_classify() {
|
|||
SASSERT(sca->is_token());
|
||||
|
||||
// concat of two variables
|
||||
expr_ref y(m.mk_const("y", str_sort), m);
|
||||
expr_ref xy(seq.str.mk_concat(x, y), m);
|
||||
euf::snode* sxy = sg.mk(xy);
|
||||
const expr_ref y(m.mk_const("y", str_sort), m);
|
||||
const expr_ref xy(seq.str.mk_concat(x, y), m);
|
||||
euf::snode const* sxy = sg.mk(xy);
|
||||
SASSERT(sxy && sxy->is_concat());
|
||||
SASSERT(!sxy->is_ground());
|
||||
SASSERT(sxy->is_regex_free());
|
||||
|
|
@ -85,60 +85,60 @@ static void test_sgraph_regex() {
|
|||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq_util seq(m);
|
||||
sort_ref str_sort(seq.str.mk_string_sort(), m);
|
||||
const sort_ref str_sort(seq.str.mk_string_sort(), m);
|
||||
|
||||
expr_ref x(m.mk_const("x", str_sort), m);
|
||||
const expr_ref x(m.mk_const("x", str_sort), m);
|
||||
|
||||
// to_re
|
||||
expr_ref to_re_x(seq.re.mk_to_re(x), m);
|
||||
euf::snode* str = sg.mk(to_re_x);
|
||||
const expr_ref to_re_x(seq.re.mk_to_re(x), m);
|
||||
euf::snode const* str = sg.mk(to_re_x);
|
||||
SASSERT(str && str->is_to_re());
|
||||
SASSERT(!str->is_regex_free());
|
||||
SASSERT(str->num_args() == 1);
|
||||
|
||||
// star
|
||||
expr_ref star_x(seq.re.mk_star(to_re_x), m);
|
||||
euf::snode* ss = sg.mk(star_x);
|
||||
const expr_ref star_x(seq.re.mk_star(to_re_x), m);
|
||||
euf::snode const* ss = sg.mk(star_x);
|
||||
SASSERT(ss && ss->is_star());
|
||||
SASSERT(!ss->is_regex_free());
|
||||
SASSERT(ss->num_args() == 1);
|
||||
|
||||
// full_seq (.*)
|
||||
expr_ref full_seq(seq.re.mk_full_seq(str_sort), m);
|
||||
euf::snode* sfs = sg.mk(full_seq);
|
||||
const expr_ref full_seq(seq.re.mk_full_seq(str_sort), m);
|
||||
euf::snode const* sfs = sg.mk(full_seq);
|
||||
SASSERT(sfs && sfs->is_full_seq());
|
||||
SASSERT(sfs->is_ground());
|
||||
|
||||
// full_char (.)
|
||||
expr_ref full_char(seq.re.mk_full_char(str_sort), m);
|
||||
euf::snode* sfc = sg.mk(full_char);
|
||||
const expr_ref full_char(seq.re.mk_full_char(str_sort), m);
|
||||
euf::snode const* sfc = sg.mk(full_char);
|
||||
SASSERT(sfc && sfc->is_full_char());
|
||||
SASSERT(sfc->is_ground());
|
||||
|
||||
// empty set, fail
|
||||
sort_ref re_sort(seq.re.mk_re(str_sort), m);
|
||||
expr_ref empty_set(seq.re.mk_empty(re_sort), m);
|
||||
euf::snode* sfail = sg.mk(empty_set);
|
||||
const sort_ref re_sort(seq.re.mk_re(str_sort), m);
|
||||
const expr_ref empty_set(seq.re.mk_empty(re_sort), m);
|
||||
euf::snode const* sfail = sg.mk(empty_set);
|
||||
SASSERT(sfail && sfail->is_fail());
|
||||
|
||||
// union: to_re(x) | star(to_re(x)), nullable because star is
|
||||
expr_ref re_union(seq.re.mk_union(to_re_x, star_x), m);
|
||||
euf::snode* su = sg.mk(re_union);
|
||||
const expr_ref re_union(seq.re.mk_union(to_re_x, star_x), m);
|
||||
euf::snode const* su = sg.mk(re_union);
|
||||
SASSERT(su && su->is_union());
|
||||
|
||||
// intersection: to_re(x) & star(to_re(x)), nullable only if both are
|
||||
expr_ref re_inter(seq.re.mk_inter(to_re_x, star_x), m);
|
||||
euf::snode* si = sg.mk(re_inter);
|
||||
const expr_ref re_inter(seq.re.mk_inter(to_re_x, star_x), m);
|
||||
euf::snode const* si = sg.mk(re_inter);
|
||||
SASSERT(si && si->is_intersect());
|
||||
|
||||
// complement of to_re(x): nullable because to_re(x) is not nullable
|
||||
expr_ref re_comp(seq.re.mk_complement(to_re_x), m);
|
||||
euf::snode* sc = sg.mk(re_comp);
|
||||
const expr_ref re_comp(seq.re.mk_complement(to_re_x), m);
|
||||
euf::snode const* sc = sg.mk(re_comp);
|
||||
SASSERT(sc && sc->is_complement());
|
||||
|
||||
// in_re
|
||||
expr_ref in_re(seq.re.mk_in_re(x, star_x), m);
|
||||
euf::snode* sir = sg.mk(in_re);
|
||||
const expr_ref in_re(seq.re.mk_in_re(x, star_x), m);
|
||||
euf::snode const* sir = sg.mk(in_re);
|
||||
SASSERT(sir && sir->is_in_re());
|
||||
SASSERT(!sir->is_regex_free());
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ static void test_sgraph_power() {
|
|||
expr_ref n(arith.mk_int(3), m);
|
||||
expr_ref xn(seq.str.mk_power(x, n), m);
|
||||
|
||||
euf::snode* sp = sg.mk(xn);
|
||||
euf::snode const* sp = sg.mk(xn);
|
||||
SASSERT(sp && sp->is_power());
|
||||
SASSERT(!sp->is_ground()); // base x is not ground
|
||||
SASSERT(sp->is_regex_free());
|
||||
|
|
@ -258,8 +258,8 @@ static void test_sgraph_find_idempotent() {
|
|||
sort_ref str_sort(seq.str.mk_string_sort(), m);
|
||||
|
||||
expr_ref x(m.mk_const("x", str_sort), m);
|
||||
euf::snode* s1 = sg.mk(x);
|
||||
euf::snode* s2 = sg.mk(x); // calling mk again returns same node
|
||||
euf::snode const* s1 = sg.mk(x);
|
||||
euf::snode const* s2 = sg.mk(x); // calling mk again returns same node
|
||||
SASSERT(s1 == s2);
|
||||
SASSERT(s1 == sg.find(x));
|
||||
}
|
||||
|
|
@ -278,9 +278,9 @@ static void test_sgraph_mk_concat() {
|
|||
expr_ref y(m.mk_const("y", str_sort), m);
|
||||
expr_ref empty(seq.str.mk_empty(str_sort), m);
|
||||
|
||||
euf::snode* sx = sg.mk(x);
|
||||
euf::snode* sy = sg.mk(y);
|
||||
euf::snode* se = sg.mk(empty);
|
||||
euf::snode const* sx = sg.mk(x);
|
||||
euf::snode const* sy = sg.mk(y);
|
||||
euf::snode const* se = sg.mk(empty);
|
||||
|
||||
// concat with empty yields the non-empty side at sgraph level
|
||||
// (empty absorption is a property of the expression, checked via mk)
|
||||
|
|
@ -288,14 +288,14 @@ static void test_sgraph_mk_concat() {
|
|||
|
||||
// normal concat via expression
|
||||
expr_ref xy(seq.str.mk_concat(x, y), m);
|
||||
euf::snode* sxy = sg.mk(xy);
|
||||
euf::snode const* sxy = sg.mk(xy);
|
||||
SASSERT(sxy && sxy->is_concat());
|
||||
SASSERT(sxy->num_args() == 2);
|
||||
SASSERT(sxy->arg(0) == sx);
|
||||
SASSERT(sxy->arg(1) == sy);
|
||||
|
||||
// calling mk again with same expr returns same node
|
||||
euf::snode* sxy2 = sg.mk(xy);
|
||||
euf::snode const* sxy2 = sg.mk(xy);
|
||||
SASSERT(sxy == sxy2);
|
||||
}
|
||||
|
||||
|
|
@ -314,14 +314,14 @@ static void test_sgraph_mk_power() {
|
|||
expr_ref n(arith.mk_int(5), m);
|
||||
expr_ref xn(seq.str.mk_power(x, n), m);
|
||||
|
||||
euf::snode* sx = sg.mk(x);
|
||||
euf::snode* sp = sg.mk(xn);
|
||||
euf::snode const* sx = sg.mk(x);
|
||||
euf::snode const* sp = sg.mk(xn);
|
||||
SASSERT(sp && sp->is_power());
|
||||
SASSERT(sp->num_args() == 2);
|
||||
SASSERT(sp->arg(0) == sx);
|
||||
|
||||
// calling mk again returns same node
|
||||
euf::snode* sp2 = sg.mk(xn);
|
||||
euf::snode const* sp2 = sg.mk(xn);
|
||||
SASSERT(sp == sp2);
|
||||
}
|
||||
|
||||
|
|
@ -339,21 +339,21 @@ static void test_sgraph_first_last() {
|
|||
expr_ref b(m.mk_const("b", str_sort), m);
|
||||
expr_ref c(m.mk_const("c", str_sort), m);
|
||||
|
||||
euf::snode* sa = sg.mk(a);
|
||||
euf::snode* sb = sg.mk(b);
|
||||
euf::snode* sc = sg.mk(c);
|
||||
euf::snode const* sa = sg.mk(a);
|
||||
euf::snode const* sb = sg.mk(b);
|
||||
euf::snode const* sc = sg.mk(c);
|
||||
|
||||
// concat(concat(a,b),c): first=a, last=c
|
||||
expr_ref ab(seq.str.mk_concat(a, b), m);
|
||||
expr_ref ab_c(seq.str.mk_concat(ab, c), m);
|
||||
euf::snode* sab_c = sg.mk(ab_c);
|
||||
euf::snode const* sab_c = sg.mk(ab_c);
|
||||
SASSERT(sab_c->first() == sa);
|
||||
SASSERT(sab_c->last() == sc);
|
||||
|
||||
// concat(a,concat(b,c)): first=a, last=c
|
||||
expr_ref bc(seq.str.mk_concat(b, c), m);
|
||||
expr_ref a_bc(seq.str.mk_concat(a, bc), m);
|
||||
euf::snode* sa_bc = sg.mk(a_bc);
|
||||
euf::snode const* sa_bc = sg.mk(a_bc);
|
||||
SASSERT(sa_bc->first() == sa);
|
||||
SASSERT(sa_bc->last() == sc);
|
||||
|
||||
|
|
@ -378,13 +378,13 @@ static void test_sgraph_concat_metadata() {
|
|||
expr_ref ch(seq.str.mk_char('Z'), m);
|
||||
expr_ref unit_z(seq.str.mk_unit(ch), m);
|
||||
|
||||
euf::snode* sx = sg.mk(x);
|
||||
euf::snode* se = sg.mk(empty);
|
||||
euf::snode* sz = sg.mk(unit_z);
|
||||
euf::snode const* sx = sg.mk(x);
|
||||
euf::snode const* se = sg.mk(empty);
|
||||
euf::snode const* sz = sg.mk(unit_z);
|
||||
|
||||
// concat(x, unit('Z')): not ground (x is variable), regex_free, not nullable
|
||||
expr_ref xz(seq.str.mk_concat(x, unit_z), m);
|
||||
euf::snode* sxz = sg.mk(xz);
|
||||
euf::snode const* sxz = sg.mk(xz);
|
||||
SASSERT(!sxz->is_ground());
|
||||
SASSERT(sxz->is_regex_free());
|
||||
SASSERT(sxz->length() == 2);
|
||||
|
|
@ -392,14 +392,14 @@ static void test_sgraph_concat_metadata() {
|
|||
|
||||
// concat(empty, empty): nullable (both empty)
|
||||
expr_ref empty2(seq.str.mk_concat(empty, empty), m);
|
||||
euf::snode* see = sg.mk(empty2);
|
||||
euf::snode const* see = sg.mk(empty2);
|
||||
SASSERT(see->is_ground());
|
||||
SASSERT(see->length() == 0);
|
||||
|
||||
// deep chain: concat(concat(x,x),concat(x,x)) has level 3, length 4
|
||||
expr_ref xx(seq.str.mk_concat(x, x), m);
|
||||
expr_ref xxxx(seq.str.mk_concat(xx, xx), m);
|
||||
euf::snode* sxxxx = sg.mk(xxxx);
|
||||
euf::snode const* sxxxx = sg.mk(xxxx);
|
||||
SASSERT(sxxxx->level() == 3);
|
||||
SASSERT(sxxxx->length() == 4);
|
||||
}
|
||||
|
|
@ -437,40 +437,40 @@ static void test_sgraph_factory() {
|
|||
seq_util seq(m);
|
||||
|
||||
// mk_var
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
SASSERT(x && x->is_var());
|
||||
SASSERT(!x->is_ground());
|
||||
SASSERT(x->length() == 1);
|
||||
|
||||
// mk_char
|
||||
euf::snode* a = sg.mk_char('A');
|
||||
euf::snode const* a = sg.mk_char('A');
|
||||
SASSERT(a && a->is_char());
|
||||
SASSERT(a->is_ground());
|
||||
SASSERT(a->length() == 1);
|
||||
|
||||
// mk_empty
|
||||
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
|
||||
euf::snode const* e = sg.mk_empty_seq(seq.str.mk_string_sort());
|
||||
SASSERT(e && e->is_empty());
|
||||
SASSERT(e->length() == 0);
|
||||
|
||||
// mk_concat with empty absorption
|
||||
euf::snode* xe = sg.mk_concat(x, e);
|
||||
euf::snode const* xe = sg.mk_concat(x, e);
|
||||
SASSERT(xe == x);
|
||||
euf::snode* ex = sg.mk_concat(e, x);
|
||||
euf::snode const* ex = sg.mk_concat(e, x);
|
||||
SASSERT(ex == x);
|
||||
|
||||
// mk_concat of two variables
|
||||
euf::snode* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode* xy = sg.mk_concat(x, y);
|
||||
euf::snode const* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode const* xy = sg.mk_concat(x, y);
|
||||
SASSERT(xy && xy->is_concat());
|
||||
SASSERT(xy->length() == 2);
|
||||
SASSERT(xy->arg(0) == x);
|
||||
SASSERT(xy->arg(1) == y);
|
||||
|
||||
// mk_concat of multiple characters
|
||||
euf::snode* b = sg.mk_char('B');
|
||||
euf::snode* c = sg.mk_char('C');
|
||||
euf::snode* abc = sg.mk_concat(sg.mk_concat(a, b), c);
|
||||
euf::snode const* b = sg.mk_char('B');
|
||||
euf::snode const* c = sg.mk_char('C');
|
||||
euf::snode const* abc = sg.mk_concat(sg.mk_concat(a, b), c);
|
||||
SASSERT(abc->length() == 3);
|
||||
SASSERT(abc->is_ground());
|
||||
SASSERT(abc->first() == a);
|
||||
|
|
@ -486,15 +486,15 @@ static void test_sgraph_indexing() {
|
|||
euf::sgraph sg(m, eg);
|
||||
seq_util seq(m);
|
||||
|
||||
euf::snode* a = sg.mk_char('A');
|
||||
euf::snode* b = sg.mk_char('B');
|
||||
euf::snode* c = sg.mk_char('C');
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* a = sg.mk_char('A');
|
||||
euf::snode const* b = sg.mk_char('B');
|
||||
euf::snode const* c = sg.mk_char('C');
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
|
||||
// build concat(concat(a, b), concat(c, x)) => [A, B, C, x]
|
||||
euf::snode* ab = sg.mk_concat(a, b);
|
||||
euf::snode* cx = sg.mk_concat(c, x);
|
||||
euf::snode* abcx = sg.mk_concat(ab, cx);
|
||||
euf::snode const* ab = sg.mk_concat(a, b);
|
||||
euf::snode const* cx = sg.mk_concat(c, x);
|
||||
euf::snode const* abcx = sg.mk_concat(ab, cx);
|
||||
|
||||
SASSERT(abcx->length() == 4);
|
||||
|
||||
|
|
@ -519,7 +519,7 @@ static void test_sgraph_indexing() {
|
|||
SASSERT(a->at(1) == nullptr);
|
||||
|
||||
// empty: at(0) is nullptr
|
||||
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
|
||||
euf::snode const* e = sg.mk_empty_seq(seq.str.mk_string_sort());
|
||||
SASSERT(e->at(0) == nullptr);
|
||||
euf::snode_vector empty_tokens;
|
||||
e->collect_tokens(empty_tokens);
|
||||
|
|
@ -535,62 +535,62 @@ static void test_sgraph_drop() {
|
|||
euf::sgraph sg(m, eg);
|
||||
seq_util seq(m);
|
||||
|
||||
euf::snode* a = sg.mk_char('A');
|
||||
euf::snode* b = sg.mk_char('B');
|
||||
euf::snode* c = sg.mk_char('C');
|
||||
euf::snode* d = sg.mk_char('D');
|
||||
euf::snode const* a = sg.mk_char('A');
|
||||
euf::snode const* b = sg.mk_char('B');
|
||||
euf::snode const* c = sg.mk_char('C');
|
||||
euf::snode const* d = sg.mk_char('D');
|
||||
|
||||
// build concat(concat(a, b), concat(c, d)) => [A, B, C, D]
|
||||
euf::snode* ab = sg.mk_concat(a, b);
|
||||
euf::snode* cd = sg.mk_concat(c, d);
|
||||
euf::snode* abcd = sg.mk_concat(ab, cd);
|
||||
euf::snode const* ab = sg.mk_concat(a, b);
|
||||
euf::snode const* cd = sg.mk_concat(c, d);
|
||||
euf::snode const* abcd = sg.mk_concat(ab, cd);
|
||||
|
||||
SASSERT(abcd->length() == 4);
|
||||
|
||||
// drop_first: [A, B, C, D] => [B, C, D]
|
||||
euf::snode* bcd = sg.drop_first(abcd);
|
||||
euf::snode const* bcd = sg.drop_first(abcd);
|
||||
SASSERT(bcd->length() == 3);
|
||||
SASSERT(bcd->first() == b);
|
||||
SASSERT(bcd->last() == d);
|
||||
|
||||
// drop_last: [A, B, C, D] => [A, B, C]
|
||||
euf::snode* abc = sg.drop_last(abcd);
|
||||
euf::snode const* abc = sg.drop_last(abcd);
|
||||
SASSERT(abc->length() == 3);
|
||||
SASSERT(abc->first() == a);
|
||||
SASSERT(abc->last() == c);
|
||||
|
||||
// drop_left(2): [A, B, C, D] => [C, D]
|
||||
euf::snode* cd2 = sg.drop_left(abcd, 2);
|
||||
euf::snode const* cd2 = sg.drop_left(abcd, 2);
|
||||
SASSERT(cd2->length() == 2);
|
||||
SASSERT(cd2->first() == c);
|
||||
|
||||
// drop_left(1): [A, B, C, D] => [B, C, D]
|
||||
euf::snode* bcd2 = sg.drop_left(abcd, 1);
|
||||
euf::snode const* bcd2 = sg.drop_left(abcd, 1);
|
||||
SASSERT(bcd2->length() == 3);
|
||||
SASSERT(bcd2->first() == b);
|
||||
SASSERT(bcd2->last() == d);
|
||||
|
||||
// drop_right(2): [A, B, C, D] => [A, B]
|
||||
euf::snode* ab2 = sg.drop_right(abcd, 2);
|
||||
euf::snode const* ab2 = sg.drop_right(abcd, 2);
|
||||
SASSERT(ab2->length() == 2);
|
||||
SASSERT(ab2->last() == b);
|
||||
|
||||
// drop_right(1): [A, B, C, D] => [A, B, C]
|
||||
euf::snode* abc2 = sg.drop_right(abcd, 1);
|
||||
euf::snode const* abc2 = sg.drop_right(abcd, 1);
|
||||
SASSERT(abc2->length() == 3);
|
||||
SASSERT(abc2->first() == a);
|
||||
SASSERT(abc2->last() == c);
|
||||
|
||||
// drop all: [A, B, C, D] => empty
|
||||
euf::snode* empty = sg.drop_left(abcd, 4);
|
||||
euf::snode const* empty = sg.drop_left(abcd, 4);
|
||||
SASSERT(empty->is_empty());
|
||||
|
||||
// drop from single token: [A] => empty
|
||||
euf::snode* e = sg.drop_first(a);
|
||||
euf::snode const* e = sg.drop_first(a);
|
||||
SASSERT(e->is_empty());
|
||||
|
||||
// drop from empty: no change
|
||||
euf::snode* ee = sg.drop_first(sg.mk_empty_seq(seq.str.mk_string_sort()));
|
||||
euf::snode const* ee = sg.drop_first(sg.mk_empty_seq(seq.str.mk_string_sort()));
|
||||
SASSERT(ee->is_empty());
|
||||
}
|
||||
|
||||
|
|
@ -603,29 +603,29 @@ static void test_sgraph_subst() {
|
|||
euf::sgraph sg(m, eg);
|
||||
seq_util seq(m);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode* a = sg.mk_char('A');
|
||||
euf::snode* b = sg.mk_char('B');
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode const* a = sg.mk_char('A');
|
||||
euf::snode const* b = sg.mk_char('B');
|
||||
|
||||
// concat(x, concat(a, x)) with x -> b gives concat(b, concat(a, b))
|
||||
euf::snode* ax = sg.mk_concat(a, x);
|
||||
euf::snode* xax = sg.mk_concat(x, ax);
|
||||
euf::snode const* ax = sg.mk_concat(a, x);
|
||||
euf::snode const* xax = sg.mk_concat(x, ax);
|
||||
SASSERT(xax->length() == 3);
|
||||
|
||||
euf::snode* result = sg.subst(xax, x, b);
|
||||
euf::snode const* result = sg.subst(xax, x, b);
|
||||
SASSERT(result->length() == 3);
|
||||
SASSERT(result->first() == b);
|
||||
SASSERT(result->last() == b);
|
||||
SASSERT(result->at(1) == a); // middle is still 'A'
|
||||
|
||||
// substitution of non-occurring variable is identity
|
||||
euf::snode* same = sg.subst(xax, y, b);
|
||||
euf::snode const* same = sg.subst(xax, y, b);
|
||||
SASSERT(same == xax);
|
||||
|
||||
// substitution of variable with empty
|
||||
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
|
||||
euf::snode* collapsed = sg.subst(xax, x, e);
|
||||
euf::snode const* e = sg.mk_empty_seq(seq.str.mk_string_sort());
|
||||
euf::snode const* collapsed = sg.subst(xax, x, e);
|
||||
SASSERT(collapsed->length() == 1); // just 'a' remains
|
||||
SASSERT(collapsed == a);
|
||||
}
|
||||
|
|
@ -639,15 +639,15 @@ static void test_sgraph_complex_concat() {
|
|||
euf::sgraph sg(m, eg);
|
||||
|
||||
// build a string "HELLO" = concat(H, concat(E, concat(L, concat(L, O))))
|
||||
euf::snode* h = sg.mk_char('H');
|
||||
euf::snode* e = sg.mk_char('E');
|
||||
euf::snode* l = sg.mk_char('L');
|
||||
euf::snode* o = sg.mk_char('O');
|
||||
euf::snode const* h = sg.mk_char('H');
|
||||
euf::snode const* e = sg.mk_char('E');
|
||||
euf::snode const* l = sg.mk_char('L');
|
||||
euf::snode const* o = sg.mk_char('O');
|
||||
|
||||
euf::snode* lo = sg.mk_concat(l, o);
|
||||
euf::snode* llo = sg.mk_concat(l, lo);
|
||||
euf::snode* ello = sg.mk_concat(e, llo);
|
||||
euf::snode* hello = sg.mk_concat(h, ello);
|
||||
euf::snode const* lo = sg.mk_concat(l, o);
|
||||
euf::snode const* llo = sg.mk_concat(l, lo);
|
||||
euf::snode const* ello = sg.mk_concat(e, llo);
|
||||
euf::snode const* hello = sg.mk_concat(h, ello);
|
||||
|
||||
SASSERT(hello->length() == 5);
|
||||
SASSERT(hello->is_ground());
|
||||
|
|
@ -662,24 +662,24 @@ static void test_sgraph_complex_concat() {
|
|||
SASSERT(hello->at(4) == o);
|
||||
|
||||
// drop first 2 from "HELLO" => "LLO"
|
||||
euf::snode* llo2 = sg.drop_left(hello, 2);
|
||||
euf::snode const* llo2 = sg.drop_left(hello, 2);
|
||||
SASSERT(llo2->length() == 3);
|
||||
SASSERT(llo2->first() == l);
|
||||
|
||||
// drop last 3 from "HELLO" => "HE"
|
||||
euf::snode* he = sg.drop_right(hello, 3);
|
||||
euf::snode const* he = sg.drop_right(hello, 3);
|
||||
SASSERT(he->length() == 2);
|
||||
SASSERT(he->first() == h);
|
||||
SASSERT(he->last() == e);
|
||||
|
||||
// mixed variables and characters: concat(x, "AB", y)
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode* a = sg.mk_char('A');
|
||||
euf::snode* b = sg.mk_char('B');
|
||||
euf::snode* ab = sg.mk_concat(a, b);
|
||||
euf::snode* xab = sg.mk_concat(x, ab);
|
||||
euf::snode* xaby = sg.mk_concat(xab, y);
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode const* a = sg.mk_char('A');
|
||||
euf::snode const* b = sg.mk_char('B');
|
||||
euf::snode const* ab = sg.mk_concat(a, b);
|
||||
euf::snode const* xab = sg.mk_concat(x, ab);
|
||||
euf::snode const* xaby = sg.mk_concat(xab, y);
|
||||
|
||||
SASSERT(xaby->length() == 4);
|
||||
SASSERT(!xaby->is_ground());
|
||||
|
|
@ -706,18 +706,18 @@ static void test_sgraph_brzozowski() {
|
|||
expr_ref to_re_a(seq.re.mk_to_re(unit_a), m);
|
||||
expr_ref star_a(seq.re.mk_star(to_re_a), m);
|
||||
|
||||
euf::snode* s_star_a = sg.mk(star_a);
|
||||
euf::snode* s_unit_a = sg.mk(unit_a);
|
||||
euf::snode const* s_star_a = sg.mk(star_a);
|
||||
euf::snode const* s_unit_a = sg.mk(unit_a);
|
||||
|
||||
euf::snode* deriv = sg.brzozowski_deriv(s_star_a, s_unit_a);
|
||||
euf::snode const* deriv = sg.brzozowski_deriv(s_star_a, s_unit_a);
|
||||
SASSERT(deriv != nullptr);
|
||||
std::cout << " d/da(a*) kind: " << (int)deriv->kind() << "\n";
|
||||
|
||||
// derivative of re.empty w.r.t. 'a' should be re.empty
|
||||
sort_ref re_sort(seq.re.mk_re(str_sort), m);
|
||||
expr_ref re_empty(seq.re.mk_empty(re_sort), m);
|
||||
euf::snode* s_empty = sg.mk(re_empty);
|
||||
euf::snode* deriv_empty = sg.brzozowski_deriv(s_empty, s_unit_a);
|
||||
euf::snode const* s_empty = sg.mk(re_empty);
|
||||
euf::snode const* deriv_empty = sg.brzozowski_deriv(s_empty, s_unit_a);
|
||||
SASSERT(deriv_empty != nullptr);
|
||||
SASSERT(deriv_empty->is_fail()); // derivative of empty set is empty set
|
||||
std::cout << " d/da(empty) kind: " << (int)deriv_empty->kind() << "\n";
|
||||
|
|
@ -737,7 +737,7 @@ static void test_sgraph_minterms() {
|
|||
|
||||
// simple regex with no character predicates: re.all (.*)
|
||||
expr_ref re_all(seq.re.mk_full_seq(str_sort), m);
|
||||
euf::snode* s_re_all = sg.mk(re_all);
|
||||
euf::snode const* s_re_all = sg.mk(re_all);
|
||||
|
||||
euf::snode_vector minterms;
|
||||
sg.compute_minterms(s_re_all, minterms);
|
||||
|
|
@ -749,7 +749,7 @@ static void test_sgraph_minterms() {
|
|||
expr_ref evil(seq.re.mk_to_re(seq.str.mk_string(zstring("evil"))), m);
|
||||
expr_ref slash_evil(seq.re.mk_to_re(seq.str.mk_string(zstring("/evil"))), m);
|
||||
expr_ref union_re(seq.re.mk_union(evil, slash_evil), m);
|
||||
euf::snode* s_union_re = sg.mk(union_re);
|
||||
euf::snode const* s_union_re = sg.mk(union_re);
|
||||
|
||||
euf::snode_vector union_minterms;
|
||||
sg.compute_minterms(s_union_re, union_minterms);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ static void test_nseq_instantiation() {
|
|||
euf::sgraph sg(m, eg);
|
||||
nseq_basic_dummy_solver solver;
|
||||
seq::context_solver_i context_solver;
|
||||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
const seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
SASSERT(ng.root() == nullptr);
|
||||
SASSERT(ng.num_nodes() == 0);
|
||||
std::cout << " ok\n";
|
||||
|
|
@ -50,7 +50,7 @@ static void test_nseq_instantiation() {
|
|||
// Test 2: parameter validation accepts "nseq"
|
||||
static void test_nseq_param_validation() {
|
||||
std::cout << "test_nseq_param_validation\n";
|
||||
smt_params p;
|
||||
const smt_params p;
|
||||
// Should not throw
|
||||
try {
|
||||
p.validate_string_solver(symbol("nseq"));
|
||||
|
|
@ -72,9 +72,9 @@ static void test_nseq_param_validation() {
|
|||
// Test 2b: parameter validation rejects invalid variants of "nseq"
|
||||
static void test_nseq_param_validation_rejects_invalid() {
|
||||
std::cout << "test_nseq_param_validation_rejects_invalid\n";
|
||||
smt_params p;
|
||||
const smt_params p;
|
||||
static const char* invalid_variants[] = { "nseq2", "NSEQ", "nseqq", "nse", "Nseq", "nseq ", "" };
|
||||
for (auto s : invalid_variants) {
|
||||
for (const auto s : invalid_variants) {
|
||||
bool threw = false;
|
||||
try {
|
||||
p.validate_string_solver(symbol(s));
|
||||
|
|
@ -94,7 +94,7 @@ static void test_nseq_simplification() {
|
|||
std::cout << "test_nseq_simplification\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
seq_util su(m);
|
||||
const seq_util su(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
nseq_basic_dummy_solver solver;
|
||||
|
|
@ -102,12 +102,12 @@ static void test_nseq_simplification() {
|
|||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
|
||||
// Add a trivial equality: empty = empty
|
||||
euf::snode* empty1 = sg.mk_empty_seq(su.str.mk_string_sort());
|
||||
euf::snode* empty2 = sg.mk_empty_seq(su.str.mk_string_sort());
|
||||
euf::snode const* empty1 = sg.mk_empty_seq(su.str.mk_string_sort());
|
||||
euf::snode const* empty2 = sg.mk_empty_seq(su.str.mk_string_sort());
|
||||
|
||||
ng.add_str_eq(empty1, empty2);
|
||||
|
||||
seq::nielsen_graph::search_result r = ng.solve();
|
||||
const seq::nielsen_graph::search_result r = ng.solve();
|
||||
// empty = empty is trivially satisfied
|
||||
SASSERT(r == seq::nielsen_graph::search_result::sat);
|
||||
std::cout << " ok: trivial equality solved as sat\n";
|
||||
|
|
@ -118,7 +118,7 @@ static void test_nseq_node_satisfied() {
|
|||
std::cout << "test_nseq_node_satisfied\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
seq_util su(m);
|
||||
const seq_util su(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
nseq_basic_dummy_solver solver;
|
||||
|
|
@ -130,15 +130,15 @@ static void test_nseq_node_satisfied() {
|
|||
SASSERT(node->is_satisfied());
|
||||
|
||||
// add a trivial equality
|
||||
euf::snode *empty = sg.mk_empty_seq(su.str.mk_string_sort());
|
||||
seq::dep_tracker dep = nullptr;
|
||||
seq::str_eq eq(empty, empty, dep);
|
||||
const euf::snode *empty = sg.mk_empty_seq(su.str.mk_string_sort());
|
||||
const seq::dep_tracker dep = nullptr;
|
||||
const seq::str_eq eq(empty, empty, dep);
|
||||
node->add_str_eq(eq);
|
||||
SASSERT(node->str_eqs().size() == 1);
|
||||
SASSERT(!node->str_eqs()[0].is_trivial() || node->str_eqs()[0].m_lhs == node->str_eqs()[0].m_rhs);
|
||||
// After simplification, trivial equalities should be removed
|
||||
ptr_vector<seq::nielsen_edge> cur_path;
|
||||
seq::simplify_result sr = node->simplify_and_init(cur_path);
|
||||
const ptr_vector<seq::nielsen_edge> cur_path;
|
||||
const seq::simplify_result sr = node->simplify_and_init(cur_path);
|
||||
|
||||
VERIFY(sr == seq::simplify_result::satisfied || sr == seq::simplify_result::proceed);
|
||||
std::cout << " ok\n";
|
||||
|
|
@ -155,11 +155,11 @@ static void test_nseq_symbol_clash() {
|
|||
seq::context_solver_i context_solver;
|
||||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
|
||||
euf::snode* a = sg.mk_char('a');
|
||||
euf::snode* b = sg.mk_char('b');
|
||||
euf::snode const* a = sg.mk_char('a');
|
||||
euf::snode const* b = sg.mk_char('b');
|
||||
ng.add_str_eq(a, b);
|
||||
|
||||
auto r = ng.solve();
|
||||
const auto r = ng.solve();
|
||||
SASSERT(r == seq::nielsen_graph::search_result::unsat);
|
||||
|
||||
// verify conflict explanation returns the equality index
|
||||
|
|
@ -183,10 +183,10 @@ static void test_nseq_var_eq_self() {
|
|||
seq::context_solver_i context_solver;
|
||||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
ng.add_str_eq(x, x);
|
||||
|
||||
auto r = ng.solve();
|
||||
const auto r = ng.solve();
|
||||
SASSERT(r == seq::nielsen_graph::search_result::sat);
|
||||
std::cout << " ok: x = x solved as sat\n";
|
||||
}
|
||||
|
|
@ -202,14 +202,14 @@ static void test_nseq_prefix_clash() {
|
|||
seq::context_solver_i context_solver;
|
||||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode* a = sg.mk_char('a');
|
||||
euf::snode* b = sg.mk_char('b');
|
||||
euf::snode* xa = sg.mk_concat(x, a);
|
||||
euf::snode* xb = sg.mk_concat(x, b);
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* a = sg.mk_char('a');
|
||||
euf::snode const* b = sg.mk_char('b');
|
||||
euf::snode const* xa = sg.mk_concat(x, a);
|
||||
euf::snode const* xb = sg.mk_concat(x, b);
|
||||
|
||||
ng.add_str_eq(xa, xb);
|
||||
auto r = ng.solve();
|
||||
const auto r = ng.solve();
|
||||
SASSERT(r == seq::nielsen_graph::search_result::unsat);
|
||||
std::cout << " ok: x·a = x·b detected as unsat\n";
|
||||
}
|
||||
|
|
@ -225,14 +225,14 @@ static void test_nseq_const_nielsen_solvable() {
|
|||
seq::context_solver_i context_solver;
|
||||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode* a = sg.mk_char('a');
|
||||
euf::snode* ax = sg.mk_concat(a, x);
|
||||
euf::snode* ay = sg.mk_concat(a, y);
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* y = sg.mk_var(symbol("y"), sg.get_str_sort());
|
||||
euf::snode const* a = sg.mk_char('a');
|
||||
euf::snode const* ax = sg.mk_concat(a, x);
|
||||
euf::snode const* ay = sg.mk_concat(a, y);
|
||||
|
||||
ng.add_str_eq(ax, ay);
|
||||
auto r = ng.solve();
|
||||
const auto r = ng.solve();
|
||||
// a·x = a·y simplifies to x = y which is satisfiable (x = y = ε)
|
||||
SASSERT(r == seq::nielsen_graph::search_result::sat);
|
||||
std::cout << " ok: a·x = a·y solved as sat\n";
|
||||
|
|
@ -248,12 +248,12 @@ static void test_nseq_length_mismatch() {
|
|||
nseq_basic_dummy_solver solver;
|
||||
seq::context_solver_i context_solver;
|
||||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
euf::snode* a = sg.mk_char('a');
|
||||
euf::snode* b = sg.mk_char('b');
|
||||
euf::snode* ab = sg.mk_concat(a, b);
|
||||
euf::snode const* a = sg.mk_char('a');
|
||||
euf::snode const* b = sg.mk_char('b');
|
||||
euf::snode const* ab = sg.mk_concat(a, b);
|
||||
|
||||
ng.add_str_eq(ab, a);
|
||||
auto r = ng.solve();
|
||||
const auto r = ng.solve();
|
||||
SASSERT(r == seq::nielsen_graph::search_result::unsat);
|
||||
std::cout << " ok: ab = a detected as unsat\n";
|
||||
}
|
||||
|
|
@ -270,15 +270,15 @@ static void test_setup_seq_str_dispatches_nseq() {
|
|||
smt::context ctx(m, params);
|
||||
|
||||
// Assert a string equality to trigger string theory setup during check()
|
||||
seq_util su(m);
|
||||
const seq_util su(m);
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
app_ref x(m.mk_const(symbol("x_setup_test"), str_sort), m);
|
||||
app_ref eq(m.mk_eq(x.get(), x.get()), m);
|
||||
const app_ref x(m.mk_const(symbol("x_setup_test"), str_sort), m);
|
||||
const app_ref eq(m.mk_eq(x.get(), x.get()), m);
|
||||
ctx.assert_expr(eq);
|
||||
ctx.check();
|
||||
|
||||
// Verify that theory_nseq (not theory_seq) was registered for the "seq" family
|
||||
family_id seq_fid = m.mk_family_id("seq");
|
||||
const family_id seq_fid = m.mk_family_id("seq");
|
||||
SASSERT(ctx.get_theory(seq_fid) != nullptr);
|
||||
SASSERT(dynamic_cast<smt::theory_nseq*>(ctx.get_theory(seq_fid)) != nullptr);
|
||||
std::cout << " ok: setup_seq_str dispatched to setup_nseq for 'nseq'\n";
|
||||
|
|
|
|||
|
|
@ -60,23 +60,23 @@ public:
|
|||
struct str_builder {
|
||||
euf::sgraph& sg;
|
||||
seq_util& su;
|
||||
euf::snode* vars[26] = {}; // vars['A'-'A'] .. vars['Z'-'A']
|
||||
euf::snode const* vars[26] = {}; // vars['A'-'A'] .. vars['Z'-'A']
|
||||
|
||||
str_builder(euf::sgraph& sg, seq_util& su) : sg(sg), su(su) {}
|
||||
|
||||
euf::snode* var(char c) {
|
||||
int idx = c - 'A';
|
||||
euf::snode const* var(const char c) {
|
||||
const int idx = c - 'A';
|
||||
if (!vars[idx])
|
||||
vars[idx] = sg.mk_var(symbol(std::string(1, c).c_str()), su.str.mk_string_sort());
|
||||
return vars[idx];
|
||||
}
|
||||
|
||||
euf::snode* parse(const char* s) {
|
||||
euf::snode* result = nullptr;
|
||||
euf::snode const* parse(const char* s) {
|
||||
euf::snode const* result = nullptr;
|
||||
for (const char* p = s; *p; ++p) {
|
||||
euf::snode* tok = (*p >= 'A' && *p <= 'Z')
|
||||
euf::snode const* tok = (*p >= 'A' && *p <= 'Z')
|
||||
? var(*p)
|
||||
: sg.mk_char((unsigned)(unsigned char)*p);
|
||||
: sg.mk_char((unsigned char)*p);
|
||||
result = result ? sg.mk_concat(result, tok) : tok;
|
||||
}
|
||||
return result ? result : sg.mk_empty_seq(su.str.mk_string_sort());
|
||||
|
|
@ -99,20 +99,20 @@ struct regex_builder {
|
|||
re_sort = su.re.mk_re(su.str.mk_string_sort());
|
||||
}
|
||||
|
||||
euf::snode* parse(const char* s) {
|
||||
euf::snode const* parse(const char* s) {
|
||||
int pos = 0;
|
||||
expr_ref e = parse_union(s, pos, (int)strlen(s));
|
||||
const expr_ref e = parse_union(s, pos, (int)strlen(s));
|
||||
SASSERT(pos == (int)strlen(s));
|
||||
return sg.mk(e.get());
|
||||
}
|
||||
|
||||
private:
|
||||
expr_ref mk_char_re(char c) {
|
||||
zstring zs(std::string(1, c).c_str());
|
||||
expr_ref mk_char_re(const char c) const {
|
||||
const zstring zs(std::string(1, c).c_str());
|
||||
return expr_ref(su.re.mk_to_re(su.str.mk_string(zs)), m);
|
||||
}
|
||||
|
||||
expr_ref parse_union(const char* s, int& pos, int len) {
|
||||
expr_ref parse_union(const char* s, int& pos, const int len) {
|
||||
expr_ref left = parse_inter(s, pos, len);
|
||||
while (pos < len && s[pos] == '|') {
|
||||
++pos;
|
||||
|
|
@ -122,7 +122,7 @@ private:
|
|||
return left;
|
||||
}
|
||||
|
||||
expr_ref parse_inter(const char* s, int& pos, int len) {
|
||||
expr_ref parse_inter(const char* s, int& pos, const int len) {
|
||||
expr_ref left = parse_concat(s, pos, len);
|
||||
while (pos < len && s[pos] == '&') {
|
||||
++pos;
|
||||
|
|
@ -132,7 +132,7 @@ private:
|
|||
return left;
|
||||
}
|
||||
|
||||
expr_ref parse_concat(const char* s, int& pos, int len) {
|
||||
expr_ref parse_concat(const char* s, int& pos, const int len) {
|
||||
expr_ref acc(m);
|
||||
while (pos < len && s[pos] != ')' && s[pos] != '|' && s[pos] != '&') {
|
||||
expr_ref tok = parse_repeat(s, pos, len);
|
||||
|
|
@ -141,7 +141,7 @@ private:
|
|||
return acc ? acc : expr_ref(su.re.mk_to_re(su.str.mk_string(zstring(""))), m);
|
||||
}
|
||||
|
||||
expr_ref parse_repeat(const char* s, int& pos, int len) {
|
||||
expr_ref parse_repeat(const char* s, int& pos, const int len) {
|
||||
// collect leading tildes (complement)
|
||||
int tildes = 0;
|
||||
while (pos < len && s[pos] == '~') { ++tildes; ++pos; }
|
||||
|
|
@ -160,10 +160,10 @@ private:
|
|||
return base;
|
||||
}
|
||||
|
||||
expr_ref parse_primary(const char* s, int& pos, int len) {
|
||||
expr_ref parse_primary(const char* s, int& pos, const int len) {
|
||||
if (pos >= len)
|
||||
return expr_ref(su.re.mk_to_re(su.str.mk_string(zstring(""))), m);
|
||||
char c = s[pos];
|
||||
const char c = s[pos];
|
||||
if (c == '(') {
|
||||
++pos;
|
||||
expr_ref inner = parse_union(s, pos, len);
|
||||
|
|
@ -197,8 +197,8 @@ struct nseq_fixture {
|
|||
: eg(init(m)), sg(m, eg), dummy_solver(), context_solver(), ng(sg, dummy_solver, context_solver), su(m), sb(sg, su), rb(m, su, sg)
|
||||
{}
|
||||
|
||||
euf::snode* S(const char* s) { return sb.parse(s); }
|
||||
euf::snode* R(const char* s) { return rb.parse(s); }
|
||||
euf::snode const* S(const char* s) { return sb.parse(s); }
|
||||
euf::snode const* R(const char* s) { return rb.parse(s); }
|
||||
};
|
||||
|
||||
static constexpr int TEST_TIMEOUT_SEC = 2;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -318,9 +318,9 @@ static void test_generate_constraints_ab_star() {
|
|||
arith_util arith(m);
|
||||
seq::seq_parikh parikh(sg);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
expr_ref re = mk_ab_star(m, seq);
|
||||
euf::snode* regex = sg.mk(re);
|
||||
euf::snode const* regex = sg.mk(re);
|
||||
seq::dep_manager dm;
|
||||
sat::literal lit = sat::null_literal; // dummy literal for dependency tracking
|
||||
seq::dep_tracker dep = dm.mk_leaf(lit);
|
||||
|
|
@ -364,11 +364,11 @@ static void test_generate_constraints_bounded_loop() {
|
|||
arith_util arith(m);
|
||||
seq::seq_parikh parikh(sg);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
// loop("ab", 1, 3): min_len=2, max_len=6, stride=2
|
||||
expr_ref ab = mk_to_re_ab(m, seq);
|
||||
expr_ref re(seq.re.mk_loop(ab, 1, 3), m);
|
||||
euf::snode* regex = sg.mk(re);
|
||||
euf::snode const* regex = sg.mk(re);
|
||||
seq::dep_manager dm;
|
||||
seq::dep_tracker dep = dm.mk_leaf(sat::null_literal);
|
||||
seq::str_mem mem(x, regex, dep);
|
||||
|
|
@ -402,10 +402,10 @@ static void test_generate_constraints_stride_one() {
|
|||
seq::seq_parikh parikh(sg);
|
||||
sort_ref str_sort(seq.str.mk_string_sort(), m);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
// full_seq: stride=1 → no modular constraint
|
||||
expr_ref re(seq.re.mk_full_seq(str_sort), m);
|
||||
euf::snode* regex = sg.mk(re);
|
||||
euf::snode const* regex = sg.mk(re);
|
||||
seq::dep_manager dm;
|
||||
seq::dep_tracker dep = dm.mk_leaf(sat::null_literal);
|
||||
seq::str_mem mem(x, regex, dep);
|
||||
|
|
@ -426,9 +426,9 @@ static void test_generate_constraints_fixed_length() {
|
|||
seq_util seq(m);
|
||||
seq::seq_parikh parikh(sg);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
expr_ref re = mk_to_re_ab(m, seq); // fixed len 2
|
||||
euf::snode* regex = sg.mk(re);
|
||||
euf::snode const* regex = sg.mk(re);
|
||||
seq::dep_manager dm;
|
||||
seq::dep_tracker dep = dm.mk_leaf(sat::null_literal);
|
||||
seq::str_mem mem(x, regex, dep);
|
||||
|
|
@ -449,9 +449,9 @@ static void test_generate_constraints_dep_propagated() {
|
|||
seq_util seq(m);
|
||||
seq::seq_parikh parikh(sg);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
expr_ref re = mk_ab_star(m, seq);
|
||||
euf::snode* regex = sg.mk(re);
|
||||
euf::snode const* regex = sg.mk(re);
|
||||
seq::dep_manager dm;
|
||||
sat::literal lit(7);
|
||||
seq::dep_tracker dep = dm.mk_leaf(lit);
|
||||
|
|
@ -490,9 +490,9 @@ static void test_apply_to_node_adds_constraints() {
|
|||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
seq::seq_parikh parikh(sg);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
expr_ref re = mk_ab_star(m, seq); // stride 2 → generates constraints
|
||||
euf::snode* regex = sg.mk(re);
|
||||
euf::snode const* regex = sg.mk(re);
|
||||
ng.add_str_mem(x, regex);
|
||||
|
||||
// root node should have no int_constraints initially
|
||||
|
|
@ -523,9 +523,9 @@ static void test_apply_to_node_stride_one_no_constraints() {
|
|||
seq::nielsen_graph ng(sg, solver, context_solver);
|
||||
seq::seq_parikh parikh(sg);
|
||||
|
||||
euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
euf::snode const* x = sg.mk_var(symbol("x"), sg.get_str_sort());
|
||||
expr_ref re(seq.re.mk_full_seq(str_sort), m); // stride 1 → no constraints
|
||||
euf::snode* regex = sg.mk(re);
|
||||
euf::snode const* regex = sg.mk(re);
|
||||
ng.add_str_mem(x, regex);
|
||||
|
||||
unsigned before = ng.root()->constraints().size();
|
||||
|
|
|
|||
|
|
@ -42,13 +42,13 @@ static void test_seq_regex_is_empty() {
|
|||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
const seq::seq_regex nr(sg);
|
||||
|
||||
seq_util su(m);
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
// re.none is the empty language
|
||||
expr_ref none_e(su.re.mk_empty(su.re.mk_re(str_sort)), m);
|
||||
euf::snode* none_n = sg.mk(none_e.get());
|
||||
const expr_ref none_e(su.re.mk_empty(su.re.mk_re(str_sort)), m);
|
||||
euf::snode const* none_n = sg.mk(none_e.get());
|
||||
SASSERT(nr.is_empty_regex(none_n));
|
||||
std::cout << " ok: re.none recognized as empty\n";
|
||||
}
|
||||
|
|
@ -60,13 +60,13 @@ static void test_seq_regex_is_full() {
|
|||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
const seq::seq_regex nr(sg);
|
||||
|
||||
seq_util su(m);
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
// re.all (full sequence regex) is not empty
|
||||
expr_ref full_e(su.re.mk_full_seq(su.re.mk_re(str_sort)), m);
|
||||
euf::snode* full_n = sg.mk(full_e.get());
|
||||
const expr_ref full_e(su.re.mk_full_seq(su.re.mk_re(str_sort)), m);
|
||||
euf::snode const* full_n = sg.mk(full_e.get());
|
||||
SASSERT(!nr.is_empty_regex(full_n));
|
||||
std::cout << " ok: re.all not recognized as empty\n";
|
||||
}
|
||||
|
|
@ -85,8 +85,8 @@ static void test_strengthened_stabilizer_null() {
|
|||
seq_util su(m);
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode* full_re = sg.mk(full_e);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
|
||||
SASSERT(nr.strengthened_stabilizer(full_re, nullptr) == nullptr);
|
||||
SASSERT(nr.strengthened_stabilizer(nullptr, full_re) == nullptr);
|
||||
|
|
@ -106,14 +106,14 @@ static void test_strengthened_stabilizer_single_char() {
|
|||
seq_util su(m);
|
||||
|
||||
// Build a*
|
||||
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode* re_star_a = sg.mk(star_a);
|
||||
const expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode const* re_star_a = sg.mk(star_a);
|
||||
|
||||
// Build history = char 'a' (single token, no concat needed)
|
||||
euf::snode* tok_a = sg.mk_char('a');
|
||||
euf::snode* history = tok_a;
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* history = tok_a;
|
||||
|
||||
euf::snode* result = nr.strengthened_stabilizer(re_star_a, history);
|
||||
euf::snode const* result = nr.strengthened_stabilizer(re_star_a, history);
|
||||
// Should produce a non-null stabilizer body (to_re("a"))
|
||||
SASSERT(result != nullptr);
|
||||
std::cout << " ok: a* with history 'a' -> non-null stabilizer\n";
|
||||
|
|
@ -132,16 +132,16 @@ static void test_strengthened_stabilizer_two_char() {
|
|||
seq_util su(m);
|
||||
|
||||
// Build (ab)*
|
||||
expr_ref ab(su.re.mk_to_re(su.str.mk_string("ab")), m);
|
||||
expr_ref star_ab(su.re.mk_star(ab), m);
|
||||
euf::snode* re_star_ab = sg.mk(star_ab);
|
||||
const expr_ref ab(su.re.mk_to_re(su.str.mk_string("ab")), m);
|
||||
const expr_ref star_ab(su.re.mk_star(ab), m);
|
||||
euf::snode const* re_star_ab = sg.mk(star_ab);
|
||||
|
||||
// Build history: concat(char_a, char_b) using string concat
|
||||
euf::snode* tok_a = sg.mk_char('a');
|
||||
euf::snode* tok_b = sg.mk_char('b');
|
||||
euf::snode* history = sg.mk_concat(tok_a, tok_b);
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* tok_b = sg.mk_char('b');
|
||||
euf::snode const* history = sg.mk_concat(tok_a, tok_b);
|
||||
|
||||
euf::snode* result = nr.strengthened_stabilizer(re_star_ab, history);
|
||||
euf::snode const* result = nr.strengthened_stabilizer(re_star_ab, history);
|
||||
// Should produce a non-null stabilizer body
|
||||
SASSERT(result != nullptr);
|
||||
std::cout << " ok: (ab)* with history 'ab' -> non-null stabilizer\n";
|
||||
|
|
@ -154,16 +154,16 @@ static void test_filtered_stabilizer_star_empty() {
|
|||
reg_decl_plugins(m);
|
||||
euf::egraph eg(m);
|
||||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
const seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode* full_re = sg.mk(full_e);
|
||||
euf::snode* tok_a = sg.mk_char('a');
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
|
||||
euf::snode* result = nr.get_filtered_stabilizer_star(full_re, tok_a);
|
||||
euf::snode const* result = nr.get_filtered_stabilizer_star(full_re, tok_a);
|
||||
SASSERT(result == nullptr);
|
||||
std::cout << " ok: no stabilizers -> nullptr\n";
|
||||
}
|
||||
|
|
@ -179,17 +179,17 @@ static void test_filtered_stabilizer_star_with_stab() {
|
|||
seq_util su(m);
|
||||
|
||||
// Build a* as the regex state
|
||||
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode* re_star_a = sg.mk(star_a);
|
||||
const expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode const* re_star_a = sg.mk(star_a);
|
||||
|
||||
// Register a stabilizer: to_re("b") — only accepts "b"
|
||||
expr_ref stab_b(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode* stab_b_sn = sg.mk(stab_b);
|
||||
const expr_ref stab_b(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode const* stab_b_sn = sg.mk(stab_b);
|
||||
nr.add_stabilizer(re_star_a, stab_b_sn);
|
||||
|
||||
// Exclude char 'a': D('a', to_re("b")) should be fail
|
||||
euf::snode* tok_a = sg.mk_char('a');
|
||||
euf::snode* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
|
||||
// to_re("b") should pass the filter → result is star(to_re("b"))
|
||||
SASSERT(result != nullptr);
|
||||
SASSERT(result->is_star());
|
||||
|
|
@ -207,17 +207,17 @@ static void test_filtered_stabilizer_star_filtered() {
|
|||
seq_util su(m);
|
||||
|
||||
// Build a* as the regex state
|
||||
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode* re_star_a = sg.mk(star_a);
|
||||
const expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode const* re_star_a = sg.mk(star_a);
|
||||
|
||||
// Register a stabilizer: to_re("a") — accepts "a"
|
||||
expr_ref stab_a(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode* stab_a_sn = sg.mk(stab_a);
|
||||
const expr_ref stab_a(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode const* stab_a_sn = sg.mk(stab_a);
|
||||
nr.add_stabilizer(re_star_a, stab_a_sn);
|
||||
|
||||
// Exclude char 'a': D('a', to_re("a")) is NOT fail → filtered out
|
||||
euf::snode* tok_a = sg.mk_char('a');
|
||||
euf::snode* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
|
||||
SASSERT(result == nullptr);
|
||||
std::cout << " ok: filter removes to_re('a') when excluding 'a'\n";
|
||||
}
|
||||
|
|
@ -234,26 +234,26 @@ static void test_extract_cycle_history_basic() {
|
|||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode* full_re = sg.mk(full_e);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
|
||||
euf::snode* tok_a = sg.mk_char('a');
|
||||
euf::snode* tok_b = sg.mk_char('b');
|
||||
euf::snode* tok_c = sg.mk_char('c');
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* tok_b = sg.mk_char('b');
|
||||
euf::snode const* tok_c = sg.mk_char('c');
|
||||
|
||||
// Ancestor history: just 'a' (length 1)
|
||||
euf::snode* anc_hist = tok_a;
|
||||
euf::snode const* anc_hist = tok_a;
|
||||
|
||||
// Current history: concat(concat(a, b), c) = a,b,c (length 3)
|
||||
euf::snode* cur_hist = sg.mk_concat(sg.mk_concat(tok_a, tok_b), tok_c);
|
||||
euf::snode const* cur_hist = sg.mk_concat(sg.mk_concat(tok_a, tok_b), tok_c);
|
||||
|
||||
euf::snode* empty_str = sg.mk_empty_seq(str_sort);
|
||||
seq::dep_tracker empty_dep = nullptr;
|
||||
euf::snode const* empty_str = sg.mk_empty_seq(str_sort);
|
||||
const seq::dep_tracker empty_dep = nullptr;
|
||||
|
||||
seq::str_mem ancestor(empty_str, full_re, empty_dep);
|
||||
seq::str_mem current(empty_str, full_re, empty_dep);
|
||||
const seq::str_mem ancestor(empty_str, full_re, empty_dep);
|
||||
const seq::str_mem current(empty_str, full_re, empty_dep);
|
||||
|
||||
euf::snode* cycle = nr.extract_cycle_history(current, ancestor);
|
||||
euf::snode const* cycle = nr.extract_cycle_history(current, ancestor);
|
||||
// Should return the last 2 tokens (b, c)
|
||||
SASSERT(cycle != nullptr);
|
||||
SASSERT(cycle->length() == 2);
|
||||
|
|
@ -272,20 +272,20 @@ static void test_extract_cycle_history_null_ancestor() {
|
|||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode* full_re = sg.mk(full_e);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
|
||||
euf::snode* tok_a = sg.mk_char('a');
|
||||
euf::snode* tok_b = sg.mk_char('b');
|
||||
euf::snode* cur_hist = sg.mk_concat(tok_a, tok_b);
|
||||
euf::snode* empty_str = sg.mk_empty_seq(str_sort);
|
||||
seq::dep_tracker empty_dep = nullptr;
|
||||
euf::snode const* tok_a = sg.mk_char('a');
|
||||
euf::snode const* tok_b = sg.mk_char('b');
|
||||
euf::snode const* cur_hist = sg.mk_concat(tok_a, tok_b);
|
||||
euf::snode const* empty_str = sg.mk_empty_seq(str_sort);
|
||||
const seq::dep_tracker empty_dep = nullptr;
|
||||
|
||||
// Ancestor has no history (nullptr)
|
||||
seq::str_mem ancestor(empty_str, full_re, empty_dep);
|
||||
seq::str_mem current(empty_str, full_re, empty_dep);
|
||||
const seq::str_mem ancestor(empty_str, full_re, empty_dep);
|
||||
const seq::str_mem current(empty_str, full_re, empty_dep);
|
||||
|
||||
euf::snode* cycle = nr.extract_cycle_history(current, ancestor);
|
||||
euf::snode const* cycle = nr.extract_cycle_history(current, ancestor);
|
||||
// With null ancestor history, entire current history is the cycle
|
||||
SASSERT(cycle != nullptr);
|
||||
SASSERT(cycle->length() == 2);
|
||||
|
|
@ -304,9 +304,9 @@ static void test_bfs_empty_none() {
|
|||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
|
||||
expr_ref none_e(su.re.mk_empty(re_sort), m);
|
||||
euf::snode* none_re = sg.mk(none_e);
|
||||
lbool result = nr.is_empty_bfs(none_re);
|
||||
const expr_ref none_e(su.re.mk_empty(re_sort), m);
|
||||
euf::snode const* none_re = sg.mk(none_e);
|
||||
const lbool result = nr.is_empty_bfs(none_re);
|
||||
SASSERT(result == l_true);
|
||||
std::cout << " ok: re.none -> l_true (empty)\n";
|
||||
}
|
||||
|
|
@ -323,9 +323,9 @@ static void test_bfs_nonempty_full() {
|
|||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
|
||||
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode* full_re = sg.mk(full_e);
|
||||
lbool result = nr.is_empty_bfs(full_re);
|
||||
const expr_ref full_e(su.re.mk_full_seq(re_sort), m);
|
||||
euf::snode const* full_re = sg.mk(full_e);
|
||||
const lbool result = nr.is_empty_bfs(full_re);
|
||||
SASSERT(result == l_false);
|
||||
std::cout << " ok: full_seq -> l_false (non-empty)\n";
|
||||
}
|
||||
|
|
@ -340,9 +340,9 @@ static void test_bfs_nonempty_to_re() {
|
|||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
expr_ref to_re_abc(su.re.mk_to_re(su.str.mk_string("abc")), m);
|
||||
euf::snode* re_abc = sg.mk(to_re_abc);
|
||||
lbool result = nr.is_empty_bfs(re_abc);
|
||||
const expr_ref to_re_abc(su.re.mk_to_re(su.str.mk_string("abc")), m);
|
||||
euf::snode const* re_abc = sg.mk(to_re_abc);
|
||||
const lbool result = nr.is_empty_bfs(re_abc);
|
||||
SASSERT(result == l_false);
|
||||
std::cout << " ok: to_re(\"abc\") -> l_false (non-empty)\n";
|
||||
}
|
||||
|
|
@ -357,9 +357,9 @@ static void test_bfs_nonempty_star() {
|
|||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode* re_star_a = sg.mk(star_a);
|
||||
lbool result = nr.is_empty_bfs(re_star_a);
|
||||
const expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
|
||||
euf::snode const* re_star_a = sg.mk(star_a);
|
||||
const lbool result = nr.is_empty_bfs(re_star_a);
|
||||
SASSERT(result == l_false);
|
||||
std::cout << " ok: a* -> l_false (non-empty, accepts epsilon)\n";
|
||||
}
|
||||
|
|
@ -376,11 +376,11 @@ static void test_bfs_empty_union_of_empties() {
|
|||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
|
||||
expr_ref none1(su.re.mk_empty(re_sort), m);
|
||||
expr_ref none2(su.re.mk_empty(re_sort), m);
|
||||
expr_ref union_e(su.re.mk_union(none1, none2), m);
|
||||
euf::snode* re_union = sg.mk(union_e);
|
||||
lbool result = nr.is_empty_bfs(re_union);
|
||||
const expr_ref none1(su.re.mk_empty(re_sort), m);
|
||||
const expr_ref none2(su.re.mk_empty(re_sort), m);
|
||||
const expr_ref union_e(su.re.mk_union(none1, none2), m);
|
||||
euf::snode const* re_union = sg.mk(union_e);
|
||||
const lbool result = nr.is_empty_bfs(re_union);
|
||||
SASSERT(result == l_true);
|
||||
std::cout << " ok: union(none, none) -> l_true (empty)\n";
|
||||
}
|
||||
|
|
@ -396,11 +396,11 @@ static void test_bfs_nonempty_range() {
|
|||
seq_util su(m);
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
|
||||
expr_ref lo(su.mk_char('a'), m);
|
||||
expr_ref hi(su.mk_char('z'), m);
|
||||
expr_ref range_e(su.re.mk_range(su.str.mk_unit(lo), su.str.mk_unit(hi)), m);
|
||||
euf::snode* re_range = sg.mk(range_e);
|
||||
lbool result = nr.is_empty_bfs(re_range);
|
||||
const expr_ref lo(su.mk_char('a'), m);
|
||||
const expr_ref hi(su.mk_char('z'), m);
|
||||
const expr_ref range_e(su.re.mk_range(su.str.mk_unit(lo), su.str.mk_unit(hi)), m);
|
||||
euf::snode const* re_range = sg.mk(range_e);
|
||||
const lbool result = nr.is_empty_bfs(re_range);
|
||||
SASSERT(result == l_false);
|
||||
std::cout << " ok: range('a','z') -> l_false (non-empty)\n";
|
||||
}
|
||||
|
|
@ -417,9 +417,9 @@ static void test_bfs_empty_complement_full() {
|
|||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
|
||||
expr_ref comp_full(su.re.mk_complement(su.re.mk_full_seq(re_sort)), m);
|
||||
euf::snode* re_comp = sg.mk(comp_full);
|
||||
lbool result = nr.is_empty_bfs(re_comp);
|
||||
const expr_ref comp_full(su.re.mk_complement(su.re.mk_full_seq(re_sort)), m);
|
||||
euf::snode const* re_comp = sg.mk(comp_full);
|
||||
const lbool result = nr.is_empty_bfs(re_comp);
|
||||
SASSERT(result == l_true);
|
||||
std::cout << " ok: ~full_seq -> l_true (empty)\n";
|
||||
}
|
||||
|
|
@ -433,7 +433,7 @@ static void test_bfs_null_safety() {
|
|||
euf::sgraph sg(m, eg);
|
||||
seq::seq_regex nr(sg);
|
||||
|
||||
lbool result = nr.is_empty_bfs(nullptr);
|
||||
const lbool result = nr.is_empty_bfs(nullptr);
|
||||
SASSERT(result == l_undef);
|
||||
std::cout << " ok: nullptr -> l_undef\n";
|
||||
}
|
||||
|
|
@ -449,13 +449,13 @@ static void test_bfs_bounded() {
|
|||
seq_util su(m);
|
||||
|
||||
// (a|b)+ requires at least one char; with max_states=1 should bail
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
expr_ref ab_plus(su.re.mk_plus(ab_union), m);
|
||||
euf::snode* re_plus = sg.mk(ab_plus);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
const expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
const expr_ref ab_plus(su.re.mk_plus(ab_union), m);
|
||||
euf::snode const* re_plus = sg.mk(ab_plus);
|
||||
|
||||
lbool result = nr.is_empty_bfs(re_plus, 1);
|
||||
const lbool result = nr.is_empty_bfs(re_plus, 1);
|
||||
SASSERT(result == l_undef);
|
||||
std::cout << " ok: (a|b)+ with max_states=1 -> l_undef (bounded)\n";
|
||||
}
|
||||
|
|
@ -469,13 +469,13 @@ static void test_char_set_is_subset() {
|
|||
std::cout << "test_char_set_is_subset\n";
|
||||
|
||||
// {a} ⊆ {a,b,c} = [97,100)
|
||||
char_set cs1(char_range('a', 'b')); // {a}
|
||||
char_set cs2(char_range('a', 'd')); // {a,b,c}
|
||||
const char_set cs1(char_range('a', 'b')); // {a}
|
||||
const char_set cs2(char_range('a', 'd')); // {a,b,c}
|
||||
SASSERT(cs1.is_subset(cs2));
|
||||
SASSERT(!cs2.is_subset(cs1));
|
||||
|
||||
// empty ⊆ anything
|
||||
char_set empty;
|
||||
const char_set empty;
|
||||
SASSERT(empty.is_subset(cs1));
|
||||
SASSERT(empty.is_subset(cs2));
|
||||
|
||||
|
|
@ -484,7 +484,7 @@ static void test_char_set_is_subset() {
|
|||
SASSERT(cs2.is_subset(cs2));
|
||||
|
||||
// disjoint: {x} not ⊆ {a}
|
||||
char_set cs3(char_range('x', 'y'));
|
||||
const char_set cs3(char_range('x', 'y'));
|
||||
SASSERT(!cs3.is_subset(cs1));
|
||||
|
||||
std::cout << " ok\n";
|
||||
|
|
@ -500,10 +500,10 @@ static void test_stabilizer_store_basic() {
|
|||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode* a_sn = sg.mk(a_re);
|
||||
euf::snode* b_sn = sg.mk(b_re);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode const* a_sn = sg.mk(a_re);
|
||||
euf::snode const* b_sn = sg.mk(b_re);
|
||||
|
||||
SASSERT(!nr.has_stabilizers(a_sn));
|
||||
nr.add_stabilizer(a_sn, b_sn);
|
||||
|
|
@ -532,16 +532,16 @@ static void test_self_stabilizing() {
|
|||
seq::seq_regex nr(sg);
|
||||
seq_util su(m);
|
||||
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode* a_sn = sg.mk(a_re);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode const* a_sn = sg.mk(a_re);
|
||||
|
||||
SASSERT(!nr.is_self_stabilizing(a_sn));
|
||||
nr.set_self_stabilizing(a_sn);
|
||||
SASSERT(nr.is_self_stabilizing(a_sn));
|
||||
|
||||
// star should be detected as self-stabilizing
|
||||
expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
euf::snode* star_sn = sg.mk(star_a);
|
||||
const expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
euf::snode const* star_sn = sg.mk(star_a);
|
||||
SASSERT(nr.compute_self_stabilizing(star_sn));
|
||||
|
||||
std::cout << " ok\n";
|
||||
|
|
@ -558,19 +558,19 @@ static void test_check_intersection_sat() {
|
|||
seq_util su(m);
|
||||
|
||||
// a* ∩ (a|b)* should be non-empty (both accept "a")
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
const expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
const expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
|
||||
euf::snode* s1 = sg.mk(star_a);
|
||||
euf::snode* s2 = sg.mk(star_ab);
|
||||
ptr_vector<euf::snode> regexes;
|
||||
euf::snode const* s1 = sg.mk(star_a);
|
||||
euf::snode const* s2 = sg.mk(star_ab);
|
||||
euf::snode_vector regexes;
|
||||
regexes.push_back(s1);
|
||||
regexes.push_back(s2);
|
||||
|
||||
lbool result = nr.check_intersection_emptiness(regexes, UINT_MAX);
|
||||
const lbool result = nr.check_intersection_emptiness(regexes, UINT_MAX);
|
||||
SASSERT(result == l_false); // non-empty
|
||||
std::cout << " ok: a* ∩ (a|b)* is non-empty\n";
|
||||
}
|
||||
|
|
@ -587,15 +587,15 @@ static void test_check_intersection_unsat() {
|
|||
sort* str_sort = su.str.mk_string_sort();
|
||||
|
||||
// to_re("a") ∩ to_re("b") should be empty
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode* s1 = sg.mk(a_re);
|
||||
euf::snode* s2 = sg.mk(b_re);
|
||||
ptr_vector<euf::snode> regexes;
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
euf::snode const* s1 = sg.mk(a_re);
|
||||
euf::snode const* s2 = sg.mk(b_re);
|
||||
euf::snode_vector regexes;
|
||||
regexes.push_back(s1);
|
||||
regexes.push_back(s2);
|
||||
|
||||
lbool result = nr.check_intersection_emptiness(regexes, UINT_MAX);
|
||||
const lbool result = nr.check_intersection_emptiness(regexes, UINT_MAX);
|
||||
SASSERT(result == l_true); // empty
|
||||
std::cout << " ok: to_re(a) ∩ to_re(b) is empty\n";
|
||||
}
|
||||
|
|
@ -611,16 +611,16 @@ static void test_is_language_subset_true() {
|
|||
seq_util su(m);
|
||||
|
||||
// a* ⊆ (a|b)* should be true
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
const expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
const expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
|
||||
euf::snode* subset = sg.mk(star_a);
|
||||
euf::snode* superset = sg.mk(star_ab);
|
||||
euf::snode const* subset = sg.mk(star_a);
|
||||
euf::snode const* superset = sg.mk(star_ab);
|
||||
|
||||
lbool result = nr.is_language_subset(subset, superset);
|
||||
const lbool result = nr.is_language_subset(subset, superset);
|
||||
SASSERT(result == l_true);
|
||||
std::cout << " ok: a* ⊆ (a|b)*\n";
|
||||
}
|
||||
|
|
@ -636,16 +636,16 @@ static void test_is_language_subset_false() {
|
|||
seq_util su(m);
|
||||
|
||||
// (a|b)* ⊄ a* should be false (b ∈ (a|b)* but b ∉ a*)
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
const expr_ref star_a(su.re.mk_star(a_re), m);
|
||||
const expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
|
||||
const expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
|
||||
const expr_ref star_ab(su.re.mk_star(ab_union), m);
|
||||
|
||||
euf::snode* subset = sg.mk(star_ab);
|
||||
euf::snode* superset = sg.mk(star_a);
|
||||
euf::snode const* subset = sg.mk(star_ab);
|
||||
euf::snode const* superset = sg.mk(star_a);
|
||||
|
||||
lbool result = nr.is_language_subset(subset, superset);
|
||||
const lbool result = nr.is_language_subset(subset, superset);
|
||||
SASSERT(result == l_false);
|
||||
std::cout << " ok: (a|b)* ⊄ a*\n";
|
||||
}
|
||||
|
|
@ -662,15 +662,15 @@ static void test_is_language_subset_trivial() {
|
|||
sort* str_sort = su.str.mk_string_sort();
|
||||
|
||||
// ∅ ⊆ anything = true
|
||||
expr_ref none(su.re.mk_empty(su.re.mk_re(str_sort)), m);
|
||||
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode* empty_sn = sg.mk(none);
|
||||
euf::snode* a_sn = sg.mk(a_re);
|
||||
const expr_ref none(su.re.mk_empty(su.re.mk_re(str_sort)), m);
|
||||
const expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
|
||||
euf::snode const* empty_sn = sg.mk(none);
|
||||
euf::snode const* a_sn = sg.mk(a_re);
|
||||
SASSERT(nr.is_language_subset(empty_sn, a_sn) == l_true);
|
||||
|
||||
// anything ⊆ Σ* = true
|
||||
expr_ref full(su.re.mk_full_seq(su.re.mk_re(str_sort)), m);
|
||||
euf::snode* full_sn = sg.mk(full);
|
||||
const expr_ref full(su.re.mk_full_seq(su.re.mk_re(str_sort)), m);
|
||||
euf::snode const* full_sn = sg.mk(full);
|
||||
SASSERT(nr.is_language_subset(a_sn, full_sn) == l_true);
|
||||
|
||||
// L ⊆ L = true (same pointer)
|
||||
|
|
@ -689,14 +689,14 @@ static void test_some_seq_in_re_excluded_low_regression() {
|
|||
seq_rewriter rw(m);
|
||||
th_rewriter tr(m);
|
||||
|
||||
expr_ref low(su.mk_char('A'), m);
|
||||
expr_ref high(su.mk_char('Z'), m);
|
||||
expr_ref range_az(su.re.mk_range(su.str.mk_unit(low), su.str.mk_unit(high)), m);
|
||||
expr_ref not_a(su.re.mk_complement(su.re.mk_to_re(su.str.mk_string("A"))), m);
|
||||
expr_ref re_expr(su.re.mk_inter(not_a, range_az), m);
|
||||
const expr_ref low(su.mk_char('A'), m);
|
||||
const expr_ref high(su.mk_char('Z'), m);
|
||||
const expr_ref range_az(su.re.mk_range(su.str.mk_unit(low), su.str.mk_unit(high)), m);
|
||||
const expr_ref not_a(su.re.mk_complement(su.re.mk_to_re(su.str.mk_string("A"))), m);
|
||||
const expr_ref re_expr(su.re.mk_inter(not_a, range_az), m);
|
||||
|
||||
expr_ref witness(m);
|
||||
lbool wr = rw.some_seq_in_re(re_expr, witness);
|
||||
const lbool wr = rw.some_seq_in_re(re_expr, witness);
|
||||
SASSERT(wr == l_true);
|
||||
SASSERT(witness);
|
||||
|
||||
|
|
@ -704,7 +704,7 @@ static void test_some_seq_in_re_excluded_low_regression() {
|
|||
SASSERT(su.str.is_string(witness, ws));
|
||||
SASSERT(ws != zstring("A"));
|
||||
|
||||
expr_ref in_re(su.re.mk_in_re(witness, re_expr), m);
|
||||
const expr_ref in_re(su.re.mk_in_re(witness, re_expr), m);
|
||||
expr_ref in_re_simpl(m);
|
||||
tr(in_re, in_re_simpl);
|
||||
SASSERT(m.is_true(in_re_simpl));
|
||||
|
|
@ -730,8 +730,8 @@ static void test_some_seq_in_re_inter_loop_regression() {
|
|||
return expr_ref(su.re.mk_to_re(su.str.mk_string(s)), m);
|
||||
};
|
||||
auto mk_range = [&](const char* lo, const char* hi) -> expr_ref {
|
||||
expr_ref l(su.mk_char(lo[0]), m);
|
||||
expr_ref h(su.mk_char(hi[0]), m);
|
||||
const expr_ref l(su.mk_char(lo[0]), m);
|
||||
const expr_ref h(su.mk_char(hi[0]), m);
|
||||
return expr_ref(su.re.mk_range(su.str.mk_unit(l), su.str.mk_unit(h)), m);
|
||||
};
|
||||
auto cat = [&](expr* a, expr* b) -> expr_ref {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue