3
0
Fork 0
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:
CEisenhofer 2026-06-11 15:34:25 +02:00
parent 671dfedebe
commit be627007e1
22 changed files with 1868 additions and 2066 deletions

View file

@ -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))

View file

@ -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()

View file

@ -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();

View file

@ -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) {

View file

@ -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

View file

@ -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.

View file

@ -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();

View file

@ -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;

View file

@ -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);
};
}

View file

@ -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) {}
};

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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";

View file

@ -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);

View file

@ -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";

View file

@ -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

View file

@ -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();

View file

@ -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 {