diff --git a/src/ast/euf/euf_sgraph.cpp b/src/ast/euf/euf_sgraph.cpp index 579290b03..fc9dfbf66 100644 --- a/src/ast/euf/euf_sgraph.cpp +++ b/src/ast/euf/euf_sgraph.cpp @@ -111,7 +111,6 @@ namespace euf { case snode_kind::s_empty: n->m_ground = true; n->m_regex_free = true; - n->m_nullable = true; n->m_level = 0; n->m_length = 0; break; @@ -119,7 +118,6 @@ namespace euf { case snode_kind::s_char: n->m_ground = true; n->m_regex_free = true; - n->m_nullable = false; n->m_level = 1; n->m_length = 1; break; @@ -128,7 +126,6 @@ namespace euf { // NSB review: a variable node can be a "value". Should it be ground then? n->m_ground = false; n->m_regex_free = true; - n->m_nullable = false; n->m_level = 1; n->m_length = 1; n->m_is_classical = false; @@ -138,7 +135,6 @@ namespace euf { // NSB review: SASSERT(n->num_args() == 1); and simplify code n->m_ground = n->num_args() > 0 ? n->arg(0)->is_ground() : true; n->m_regex_free = true; - n->m_nullable = false; n->m_level = 1; n->m_length = 1; break; @@ -149,7 +145,6 @@ namespace euf { 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_nullable = l->is_nullable() && r->is_nullable(); n->m_is_classical = l->is_classical() && r->is_classical(); n->m_level = std::max(l->level(), r->level()) + 1; n->m_length = l->length() + r->length(); @@ -166,7 +161,6 @@ namespace euf { snode* base = n->arg(0); n->m_ground = base->is_ground(); n->m_regex_free = base->is_regex_free(); - n->m_nullable = base->is_nullable(); n->m_is_classical = base->is_classical(); n->m_level = 1; n->m_length = 1; @@ -178,7 +172,6 @@ namespace euf { SASSERT(n->num_args() == 1); n->m_ground = n->arg(0)->is_ground(); n->m_regex_free = false; - n->m_nullable = true; n->m_is_classical = n->arg(0)->is_classical(); n->m_level = 1; n->m_length = 1; @@ -196,7 +189,6 @@ namespace euf { if (n->get_expr() && !m_seq.re.is_loop(n->get_expr(), loop_body, lo, hi)) m_seq.re.is_loop(n->get_expr(), loop_body, lo); - n->m_nullable = (lo == 0); n->m_level = 1; n->m_length = 1; break; @@ -206,7 +198,6 @@ namespace euf { SASSERT(n->num_args() == 2); n->m_ground = n->arg(0)->is_ground() && n->arg(1)->is_ground(); n->m_regex_free = false; - n->m_nullable = n->arg(0)->is_nullable() || n->arg(1)->is_nullable(); n->m_is_classical = n->arg(0)->is_classical() && n->arg(1)->is_classical(); n->m_level = 1; n->m_length = 1; @@ -216,7 +207,6 @@ namespace euf { SASSERT(n->num_args() == 2); n->m_ground = n->arg(0)->is_ground() && n->arg(1)->is_ground(); n->m_regex_free = false; - n->m_nullable = n->arg(0)->is_nullable() && n->arg(1)->is_nullable(); n->m_is_classical = false; n->m_level = 1; n->m_length = 1; @@ -226,7 +216,6 @@ namespace euf { SASSERT(n->num_args() == 1); n->m_ground = n->arg(0)->is_ground(); n->m_regex_free = false; - n->m_nullable = !n->arg(0)->is_nullable(); n->m_is_classical = false; n->m_level = 1; n->m_length = 1; @@ -235,7 +224,6 @@ namespace euf { case snode_kind::s_fail: n->m_ground = true; n->m_regex_free = false; - n->m_nullable = false; n->m_is_classical = false; n->m_level = 1; n->m_length = 1; @@ -244,7 +232,6 @@ namespace euf { case snode_kind::s_full_char: n->m_ground = true; n->m_regex_free = false; - n->m_nullable = false; n->m_level = 1; n->m_length = 1; break; @@ -252,7 +239,6 @@ namespace euf { case snode_kind::s_full_seq: n->m_ground = true; n->m_regex_free = false; - n->m_nullable = true; n->m_level = 1; n->m_length = 1; break; @@ -261,7 +247,6 @@ namespace euf { SASSERT(n->num_args() == 2); n->m_ground = n->arg(0)->is_ground() && n->arg(1)->is_ground(); n->m_regex_free = false; - n->m_nullable = false; n->m_level = 1; n->m_length = 1; break; @@ -270,7 +255,6 @@ namespace euf { SASSERT(n->num_args() == 1); n->m_ground = n->arg(0)->is_ground(); n->m_regex_free = false; - n->m_nullable = n->arg(0)->is_nullable(); n->m_level = 1; n->m_length = 1; break; @@ -279,7 +263,6 @@ namespace euf { SASSERT(n->num_args() == 2); n->m_ground = n->arg(0)->is_ground() && n->arg(1)->is_ground(); n->m_regex_free = false; - n->m_nullable = false; n->m_level = 1; n->m_length = 1; break; @@ -289,7 +272,6 @@ namespace euf { // Is this UNREACHABLE()? n->m_ground = true; n->m_regex_free = true; - n->m_nullable = false; n->m_level = 1; n->m_length = 1; break; @@ -773,8 +755,7 @@ namespace euf { << " level=" << n->level() << " len=" << n->length() << " ground=" << n->is_ground() - << " rfree=" << n->is_regex_free() - << " nullable=" << n->is_nullable(); + << " rfree=" << n->is_regex_free(); if (n->num_args() > 0) { out << " args=("; for (unsigned i = 0; i < n->num_args(); ++i) { diff --git a/src/ast/euf/euf_snode.h b/src/ast/euf/euf_snode.h index 1efeaebc5..1f2559f60 100644 --- a/src/ast/euf/euf_snode.h +++ b/src/ast/euf/euf_snode.h @@ -66,7 +66,6 @@ namespace euf { // metadata flags, analogous to ZIPT's Str/StrToken properties bool m_ground = true; // no uninterpreted string variables bool m_regex_free = true; // no regex constructs - bool m_nullable = false; // accepts the empty string bool m_is_classical = true; // classical regular expression unsigned m_level = 0; // tree depth/level (0 for empty, 1 for singletons) unsigned m_length = 0; // token count, number of leaf tokens in the tree @@ -121,9 +120,6 @@ namespace euf { bool is_regex_free() const { return m_regex_free; } - bool is_nullable() const { - return m_nullable; - } bool is_classical() const { return m_is_classical; } diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index c20fe2175..7f714616f 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -1613,15 +1613,13 @@ bool seq_util::rex::has_valid_info(expr* r) const { seq_util::rex::info seq_util::rex::get_cached_info(expr* e) const { if (has_valid_info(e)) return m_infos[e->get_id()]; - else - return invalid_info; + return invalid_info; } /* Get the information value associated with the regular expression e */ -seq_util::rex::info seq_util::rex::get_info(expr* e) const -{ +seq_util::rex::info seq_util::rex::get_info(expr* e) const { SASSERT(u.is_re(e)); auto result = get_cached_info(e); if (result.is_valid()) diff --git a/src/smt/seq/seq_nielsen.cpp b/src/smt/seq/seq_nielsen.cpp index 28f860135..20554d11f 100644 --- a/src/smt/seq/seq_nielsen.cpp +++ b/src/smt/seq/seq_nielsen.cpp @@ -166,12 +166,18 @@ namespace seq { return m_str && m_str->length() == 1 && m_str->is_var() && m_regex->is_ground(); } - bool str_mem::is_trivial() const { - return m_str && m_regex && m_str->is_empty() && m_regex->is_nullable(); + bool str_mem::is_trivial(nielsen_node const* n) const { + if (!(m_str && m_regex && m_str->is_empty())) + return false; + const auto& info = n->graph().seq().re.get_info(m_regex->get_expr()); + return info.nullable == l_true; } - bool str_mem::is_contradiction() const { - return (m_str && m_regex && m_str->is_empty() && !m_regex->is_nullable()); + bool str_mem::is_contradiction(nielsen_node const* n) const { + if (!(m_str && m_regex && m_str->is_empty())) + return false; + const auto& info = n->graph().seq().re.get_info(m_regex->get_expr()); + return info.nullable == l_false; } bool str_mem::contains_var(euf::snode* var) const { @@ -825,7 +831,7 @@ namespace seq { unsigned wj = 0; for (unsigned j = 0; j < m_str_mem.size(); ++j) { str_mem& mem = m_str_mem[j]; - if (mem.is_trivial()) + if (mem.is_trivial(this)) continue; m_str_mem[wj++] = mem; } @@ -1136,7 +1142,7 @@ namespace seq { // check for regex memberships that are immediately infeasible for (str_mem& mem : m_str_mem) { - if (mem.is_contradiction()) { + if (mem.is_contradiction(this)) { TRACE(seq, tout << "contradiction " << mem_pp(m, mem) << "\n"); set_general_conflict(); set_conflict(backtrack_reason::regex, mem.m_dep); @@ -1175,7 +1181,7 @@ namespace seq { bool nielsen_node::is_satisfied() const { if (any_of(m_str_eq, [](auto const &eq) { return !eq.is_trivial(); })) return false; - if (any_of(m_str_mem, [](auto const &m) { return !m.is_trivial() && !m.is_primitive();})) + if (any_of(m_str_mem, [this](auto const &m) { return !m.is_trivial(this) && !m.is_primitive();})) return false; return true; } @@ -2418,7 +2424,7 @@ namespace seq { str_mem const*& mem_out, bool& fwd) const { for (str_mem const& mem : node->str_mems()) { - SASSERT(mem.m_str && mem.m_regex && !mem.is_trivial()); + SASSERT(mem.m_str && mem.m_regex && !mem.is_trivial(node)); for (unsigned od = 0; od < 2; ++od) { bool local_fwd = (od == 0); diff --git a/src/smt/seq/seq_nielsen.h b/src/smt/seq/seq_nielsen.h index 16b2314e6..67038e294 100644 --- a/src/smt/seq/seq_nielsen.h +++ b/src/smt/seq/seq_nielsen.h @@ -410,9 +410,10 @@ namespace seq { // check if the constraint has the form x in R with x a single variable bool is_primitive() const; - bool is_trivial() const; + // TODO: These two functions need to aware of the truth of the ite-guards of the symbolic derivations + bool is_trivial(nielsen_node const* n) const; - bool is_contradiction() const; + bool is_contradiction(nielsen_node const* n) const; // check if the constraint contains a given variable bool contains_var(euf::snode* var) const; diff --git a/src/smt/seq/seq_regex.cpp b/src/smt/seq/seq_regex.cpp index 2ae76188b..85f523a91 100644 --- a/src/smt/seq/seq_regex.cpp +++ b/src/smt/seq/seq_regex.cpp @@ -157,40 +157,6 @@ namespace seq { // ----------------------------------------------------------------------- bool seq_regex::compute_self_stabilizing(euf::snode* regex) const { - if (!regex) - return false; - - // R* is always self-stabilizing: D(c, R*) = D(c,R) · R*, - // so R* appears as the tail of every derivative and acts as - // its own stabilizer. - if (regex->is_star()) - return true; - - // Σ* (full_seq, i.e., re.all / .*) is self-stabilizing: - // D(c, Σ*) = Σ* for every character c. - if (regex->is_full_seq()) - return true; - - // ∅ (fail / empty language) is trivially self-stabilizing: - // it has no live derivatives, so the flag is vacuously true. - if (regex->is_fail()) - return true; - - // Complement of full_seq is ∅ (complement of Σ*), which is - // also trivially self-stabilizing. - if (regex->is_complement() && regex->num_args() == 1 && - regex->arg(0)->is_full_seq()) - return true; - - // Loop with lo=0 and no upper bound behaves like R* - // (r{0,} ≡ r*), so it is self-stabilizing. - if (regex->is_loop() && regex->is_nullable()) { - // A nullable loop with a star-like body: heuristic check. - // Only mark as self-stabilizing if the body is a Kleene closure. - // Loop(R, 0, ∞) ~ R* — but we rely on the sgraph to normalize - // these, so only catch exact star nodes above. - } - return false; } @@ -367,7 +333,7 @@ namespace seq { re->is_full_char() || re->is_full_seq()) return false; // loop with lo == 0 accepts ε - if (re->is_loop() && re->is_nullable()) + if (re->is_loop() && is_nullable(re)) return false; expr* e = re->get_expr(); @@ -394,7 +360,7 @@ namespace seq { return true; // loop(empty, lo, _) with lo > 0 is empty if (re->is_loop() && re->num_args() >= 1 && is_empty_regex(re->arg(0))) - return !re->is_nullable(); // empty if not nullable (i.e., lo > 0) + return !is_nullable(re); // empty if not nullable (i.e., lo > 0) return false; } @@ -497,7 +463,7 @@ namespace seq { return l_undef; if (re->is_fail()) return l_true; - if (re->is_nullable()) + if (is_nullable(re)) return l_false; // Structural quick checks for kinds that are never empty if (re->is_star() || re->is_full_char() || re->is_full_seq() || re->is_to_re() || re->is_range()) @@ -554,7 +520,7 @@ namespace seq { // std::cout << "Deriving by " << snode_label_html(ch, sg().get_manager()) << std::endl; euf::snode* deriv = m_sg.brzozowski_deriv(current, ch); SASSERT(deriv); - if (deriv->is_nullable()) + if (is_nullable(deriv)) return l_false; // found an accepting state if (deriv->is_fail()) continue; // dead-end, no need to explore further @@ -699,7 +665,7 @@ namespace seq { // check final state if (mem.m_str && mem.m_str->is_empty()) { - if (mem.m_regex->is_nullable()) + if (is_nullable(mem.m_regex)) return simplify_status::satisfied; return simplify_status::conflict; } @@ -737,7 +703,7 @@ namespace seq { return 1; // empty string checks if (mem.m_str->is_empty()) { - if (mem.m_regex->is_nullable()) + if (is_nullable(mem.m_regex)) return 1; return -1; } @@ -768,15 +734,15 @@ namespace seq { } // ----------------------------------------------------------------------- -// Membership processing + // Membership processing // ----------------------------------------------------------------------- - bool seq_regex::process_str_mem(seq::str_mem const& mem, - vector& out_mems) { + bool seq_regex::process_str_mem(str_mem const& mem, + vector& out_mems) { SASSERT(mem.m_str && mem.m_regex); // empty string: check nullable if (mem.m_str->is_empty()) - return mem.m_regex->is_nullable(); + return is_nullable(mem.m_regex); // consume ground prefix: derive regex by each leading concrete char seq::str_mem working = mem; diff --git a/src/smt/seq/seq_regex.h b/src/smt/seq/seq_regex.h index 5a9a9587b..6a41d89bb 100644 --- a/src/smt/seq/seq_regex.h +++ b/src/smt/seq/seq_regex.h @@ -211,7 +211,7 @@ namespace seq { // check if regex accepts the empty string bool is_nullable(euf::snode* re) const { - return re && re->is_nullable(); + return re && seq.re.get_info(re->get_expr()).nullable == l_true; } // check if regex is ground (no string variables) diff --git a/src/smt/seq_model.cpp b/src/smt/seq_model.cpp index 2bd176a30..d8790cdbd 100644 --- a/src/smt/seq_model.cpp +++ b/src/smt/seq_model.cpp @@ -558,7 +558,7 @@ namespace smt { SASSERT(sat_node); for (auto const& mem : sat_node->str_mems()) { SASSERT(mem.m_str && mem.m_regex); - if (mem.is_trivial()) + if (mem.is_trivial(sat_node)) continue; // empty string in nullable regex: already satisfied, no variable to constrain VERIFY(mem.is_primitive()); // everything else should have been eliminated already euf::snode* first = mem.m_str->first(); diff --git a/src/smt/theory_nseq.cpp b/src/smt/theory_nseq.cpp index 68cf41557..98b722e48 100644 --- a/src/smt/theory_nseq.cpp +++ b/src/smt/theory_nseq.cpp @@ -423,7 +423,7 @@ namespace smt { } // empty string in non-nullable regex → conflict - if (mem.m_str->is_empty() && !mem.m_regex->is_nullable()) { + if (mem.m_str->is_empty() && m_seq.re.get_info(mem.m_regex->get_expr()).nullable == l_false) { enode_pair_vector eqs; literal_vector lits; lits.push_back(mem.lit);