mirror of
https://github.com/Z3Prover/z3
synced 2026-03-20 03:53:10 +00:00
Regex bug fixes (still not there)
This commit is contained in:
parent
983379f5e2
commit
e7431400b4
4 changed files with 131 additions and 142 deletions
|
|
@ -379,8 +379,7 @@ namespace euf {
|
|||
}
|
||||
|
||||
snode* sgraph::find(expr* e) const {
|
||||
if (!e)
|
||||
return nullptr;
|
||||
SASSERT(e);
|
||||
unsigned eid = e->get_id();
|
||||
if (eid < m_expr2snode.size())
|
||||
return m_expr2snode[eid];
|
||||
|
|
@ -454,16 +453,18 @@ namespace euf {
|
|||
}
|
||||
|
||||
snode* sgraph::mk_concat(snode* a, snode* b) {
|
||||
if (a->is_empty()) return b;
|
||||
if (b->is_empty()) return a;
|
||||
if (a->is_empty())
|
||||
return b;
|
||||
if (b->is_empty())
|
||||
return a;
|
||||
if (m_seq.is_re(a->get_expr()))
|
||||
return mk(expr_ref(m_seq.re.mk_concat(a->get_expr(), b->get_expr()), m));
|
||||
else
|
||||
return mk(expr_ref(m_seq.str.mk_concat(a->get_expr(), b->get_expr()), m));
|
||||
return mk(expr_ref(m_seq.str.mk_concat(a->get_expr(), b->get_expr()), m));
|
||||
}
|
||||
|
||||
snode* sgraph::drop_first(snode* n) {
|
||||
if (n->is_empty() || n->is_token())
|
||||
SASSERT(!n->is_empty());
|
||||
if (n->is_token())
|
||||
return mk_empty_seq(n->get_sort());
|
||||
SASSERT(n->is_concat());
|
||||
snode* l = n->arg(0);
|
||||
|
|
@ -473,8 +474,10 @@ namespace euf {
|
|||
return mk_concat(drop_first(l), r);
|
||||
}
|
||||
|
||||
// TODO: Optimize
|
||||
snode* sgraph::drop_last(snode* n) {
|
||||
if (n->is_empty() || n->is_token())
|
||||
SASSERT(!n->is_empty());
|
||||
if (n->is_token())
|
||||
return mk_empty_seq(n->get_sort());
|
||||
SASSERT(n->is_concat());
|
||||
snode* l = n->arg(0);
|
||||
|
|
@ -484,19 +487,28 @@ namespace euf {
|
|||
return mk_concat(l, drop_last(r));
|
||||
}
|
||||
|
||||
// TODO: Optimize
|
||||
snode* sgraph::drop_left(snode* n, unsigned count) {
|
||||
if (count == 0 || n->is_empty()) return n;
|
||||
if (count >= n->length()) return mk_empty_seq(n->get_sort());
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
if (count == 0)
|
||||
return n;
|
||||
SASSERT(count <= n->length());
|
||||
if (count == n->length())
|
||||
return mk_empty_seq(n->get_sort());
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
n = drop_first(n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
snode* sgraph::drop_right(snode* n, unsigned count) {
|
||||
if (count == 0 || n->is_empty()) return n;
|
||||
if (count >= n->length()) return mk_empty_seq(n->get_sort());
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
if (count == 0)
|
||||
return n;
|
||||
SASSERT(count <= n->length());
|
||||
if (count == n->length())
|
||||
return mk_empty_seq(n->get_sort());
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
n = drop_last(n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
@ -516,8 +528,7 @@ namespace euf {
|
|||
snode* sgraph::brzozowski_deriv(snode* re, snode* elem, snode* allowed_range) {
|
||||
expr* re_expr = re->get_expr();
|
||||
expr* elem_expr = elem->get_expr();
|
||||
if (!re_expr || !elem_expr)
|
||||
return nullptr;
|
||||
SASSERT(re_expr && elem_expr);
|
||||
|
||||
// unwrap str.unit to get the character expression
|
||||
expr* ch = nullptr;
|
||||
|
|
@ -589,8 +600,7 @@ namespace euf {
|
|||
}
|
||||
|
||||
void sgraph::collect_re_predicates(snode* re, expr_ref_vector& preds) {
|
||||
if (!re || !re->get_expr())
|
||||
return;
|
||||
SASSERT(re && re->get_expr());
|
||||
expr* e = re->get_expr();
|
||||
expr* lo = nullptr, *hi = nullptr;
|
||||
// leaf regex predicates: character ranges and single characters
|
||||
|
|
|
|||
|
|
@ -718,7 +718,7 @@ namespace seq {
|
|||
static std::string arith_expr_html(expr* e, ast_manager& m) {
|
||||
if (!e) return "null";
|
||||
arith_util arith(m);
|
||||
seq_util seq(m);
|
||||
seq_util seq(m);
|
||||
rational val;
|
||||
if (arith.is_numeral(e, val))
|
||||
return val.to_string();
|
||||
|
|
@ -787,13 +787,17 @@ namespace seq {
|
|||
}
|
||||
|
||||
static std::string regex_expr_html(expr* e, ast_manager& m, seq_util& seq) {
|
||||
if (!e) return "null";
|
||||
if (!e)
|
||||
return "null";
|
||||
expr* a = nullptr, * b = nullptr;
|
||||
|
||||
if (seq.re.is_to_re(e, a)) {
|
||||
zstring s;
|
||||
if (seq.str.is_string(a, s)) {
|
||||
if (seq.str.is_string(a, s))
|
||||
return "\"" + dot_html_escape(s.encode()) + "\"";
|
||||
unsigned ch_val = 0;
|
||||
if (seq.str.is_unit(a) && seq.is_const_char(to_app(a)->get_arg(0), ch_val)) {
|
||||
return "\"" + dot_html_escape(zstring(ch_val).encode()) + "\"";
|
||||
}
|
||||
std::ostringstream os;
|
||||
os << mk_pp(a, m);
|
||||
|
|
@ -889,7 +893,8 @@ namespace seq {
|
|||
// shows s_power with superscripts, s_unit by its inner expression,
|
||||
// and falls back to mk_pp (HTML-escaped) for other token kinds.
|
||||
std::string snode_label_html(euf::snode const* n, ast_manager& m) {
|
||||
if (!n) return "null";
|
||||
if (!n)
|
||||
return "null";
|
||||
seq_util seq(m);
|
||||
|
||||
// collect all leaf tokens left-to-right
|
||||
|
|
@ -1227,7 +1232,7 @@ namespace seq {
|
|||
// Check if exponent b equals exponent a + diff for some rational constant diff.
|
||||
// Uses syntactic matching on Z3 expression structure: pointer equality
|
||||
// detects shared sub-expressions created during ConstNumUnwinding.
|
||||
//
|
||||
//
|
||||
static bool get_const_power_diff(expr* b, expr* a, arith_util& arith, rational& diff) {
|
||||
if (a == b) { diff = rational(0); return true; }
|
||||
// b = (+ a k) ?
|
||||
|
|
@ -1292,7 +1297,7 @@ namespace seq {
|
|||
|
||||
ast_manager& m = sg.get_manager();
|
||||
arith_util arith(m);
|
||||
seq_util seq(m);
|
||||
seq_util& seq = sg.get_seq_util();
|
||||
|
||||
bool merged = false;
|
||||
euf::snode_vector result;
|
||||
|
|
@ -1388,14 +1393,15 @@ namespace seq {
|
|||
// Simplify constant-exponent powers: base^0 → ε, base^1 → base.
|
||||
// Returns new snode if any simplification happened, nullptr otherwise.
|
||||
static euf::snode* simplify_const_powers(euf::sgraph& sg, euf::snode* side) {
|
||||
if (!side || side->is_empty()) return nullptr;
|
||||
if (!side || side->is_empty())
|
||||
return nullptr;
|
||||
|
||||
euf::snode_vector tokens;
|
||||
side->collect_tokens(tokens);
|
||||
|
||||
ast_manager& m = sg.get_manager();
|
||||
arith_util arith(m);
|
||||
seq_util seq(m);
|
||||
seq_util& seq = sg.get_seq_util();
|
||||
|
||||
bool simplified = false;
|
||||
euf::snode_vector result;
|
||||
|
|
@ -1424,12 +1430,15 @@ namespace seq {
|
|||
result.push_back(tok);
|
||||
}
|
||||
|
||||
if (!simplified) return nullptr;
|
||||
if (!simplified)
|
||||
return nullptr;
|
||||
|
||||
euf::snode* rebuilt = nullptr;
|
||||
for (euf::snode* tok : result)
|
||||
for (euf::snode* tok : result) {
|
||||
rebuilt = rebuilt ? sg.mk_concat(rebuilt, tok) : tok;
|
||||
if (!rebuilt) rebuilt = sg.mk_empty_seq(side->get_sort());
|
||||
}
|
||||
if (!rebuilt)
|
||||
rebuilt = sg.mk_empty_seq(side->get_sort());
|
||||
return rebuilt;
|
||||
}
|
||||
|
||||
|
|
@ -1439,9 +1448,8 @@ namespace seq {
|
|||
// Returns (count_expr, num_tokens_consumed). count_expr is nullptr
|
||||
// when no complete base-pattern match is found.
|
||||
static std::pair<expr_ref, unsigned> comm_power(
|
||||
euf::snode* base_sn, euf::snode* side, ast_manager& m, bool fwd) {
|
||||
euf::snode* base_sn, euf::snode* side, ast_manager& m, seq_util& seq, bool fwd) {
|
||||
arith_util arith(m);
|
||||
seq_util seq(m);
|
||||
euf::snode_vector base_tokens, side_tokens;
|
||||
collect_tokens_dir(base_sn, fwd, base_tokens);
|
||||
collect_tokens_dir(side, fwd, side_tokens);
|
||||
|
|
@ -1507,7 +1515,7 @@ namespace seq {
|
|||
euf::sgraph& sg = m_graph.sg();
|
||||
ast_manager& m = sg.get_manager();
|
||||
arith_util arith(m);
|
||||
seq_util seq(m);
|
||||
seq_util& seq = this->graph().seq();
|
||||
bool changed = true;
|
||||
|
||||
while (changed) {
|
||||
|
|
@ -1686,22 +1694,27 @@ namespace seq {
|
|||
// - If p ≤ c: pow_side := rest_pow, other_side := w^(c-p) · rest_other
|
||||
// - If c ≤ p: pow_side := w^(p-c) · rest_pow, other_side := rest_other
|
||||
// - If p = c: both reduce completely (handled by both conditions above).
|
||||
if (!eq.m_lhs || !eq.m_rhs) continue;
|
||||
SASSERT(eq.m_lhs && eq.m_rhs);
|
||||
bool comm_changed = false;
|
||||
for (int side = 0; side < 2 && !comm_changed; ++side) {
|
||||
euf::snode*& pow_side = (side == 0) ? eq.m_lhs : eq.m_rhs;
|
||||
euf::snode*& other_side = (side == 0) ? eq.m_rhs : eq.m_lhs;
|
||||
if (!pow_side || !other_side) continue;
|
||||
euf::snode*& pow_side = side == 0 ? eq.m_lhs : eq.m_rhs;
|
||||
euf::snode*& other_side = side == 0 ? eq.m_rhs : eq.m_lhs;
|
||||
if (!pow_side || !other_side)
|
||||
continue;
|
||||
for (unsigned od = 0; od < 2 && !comm_changed; ++od) {
|
||||
bool fwd = (od == 0);
|
||||
bool fwd = od == 0;
|
||||
euf::snode* end_tok = dir_token(pow_side, fwd);
|
||||
if (!end_tok || !end_tok->is_power()) continue;
|
||||
if (!end_tok || !end_tok->is_power())
|
||||
continue;
|
||||
euf::snode* base_sn = end_tok->arg(0);
|
||||
expr* pow_exp = get_power_exp_expr(end_tok, seq);
|
||||
if (!base_sn || !pow_exp) continue;
|
||||
if (!base_sn || !pow_exp)
|
||||
continue;
|
||||
|
||||
auto [count, consumed] = comm_power(base_sn, other_side, m, fwd);
|
||||
if (!count.get() || consumed == 0) continue;
|
||||
auto [count, consumed] =
|
||||
comm_power(base_sn, other_side, m, seq, fwd);
|
||||
if (!count.get() || consumed == 0)
|
||||
continue;
|
||||
|
||||
expr_ref norm_count = normalize_arith(m, count);
|
||||
bool pow_le_count = false, count_le_pow = false;
|
||||
|
|
@ -1714,7 +1727,8 @@ namespace seq {
|
|||
pow_le_count = m_graph.check_lp_le(pow_exp, norm_count, this, cur_path);
|
||||
count_le_pow = m_graph.check_lp_le(norm_count, pow_exp, this, cur_path);
|
||||
}
|
||||
if (!pow_le_count && !count_le_pow) continue;
|
||||
if (!pow_le_count && !count_le_pow)
|
||||
continue;
|
||||
|
||||
pow_side = dir_drop(sg, pow_side, 1, fwd);
|
||||
other_side = dir_drop(sg, other_side, consumed, fwd);
|
||||
|
|
@ -1752,7 +1766,7 @@ namespace seq {
|
|||
// power of the same base, cancel the common power prefix.
|
||||
// (Subsumed by 3c for many cases, but handles same-base-power
|
||||
// pairs that CommPower may miss when both leading tokens are powers.)
|
||||
if (!eq.m_lhs || !eq.m_rhs) continue;
|
||||
SASSERT(eq.m_lhs && eq.m_rhs);
|
||||
for (unsigned od = 0; od < 2 && !changed; ++od) {
|
||||
bool fwd = (od == 0);
|
||||
euf::snode* lh = dir_token(eq.m_lhs, fwd);
|
||||
|
|
@ -1819,13 +1833,12 @@ namespace seq {
|
|||
// consume concrete characters from str_mem via Brzozowski derivatives
|
||||
// in both directions (left-to-right, then right-to-left), mirroring ZIPT.
|
||||
for (str_mem& mem : m_str_mem) {
|
||||
if (!mem.m_str || !mem.m_regex)
|
||||
continue;
|
||||
SASSERT(mem.m_str && mem.m_regex);
|
||||
if (mem.is_primitive())
|
||||
continue;
|
||||
for (unsigned od = 0; od < 2; ++od) {
|
||||
bool fwd = od == 0;
|
||||
while (mem.m_str && !mem.m_str->is_empty()) {
|
||||
while (!mem.m_str->is_empty()) {
|
||||
euf::snode* tok = dir_token(mem.m_str, fwd);
|
||||
if (!tok || !tok->is_char())
|
||||
break;
|
||||
|
|
@ -1860,8 +1873,7 @@ namespace seq {
|
|||
// is deterministic for that token.
|
||||
// Mirrors ZIPT StrMem.SimplifyCharRegex lines 96-117.
|
||||
for (str_mem& mem : m_str_mem) {
|
||||
if (!mem.m_str || !mem.m_regex)
|
||||
continue;
|
||||
SASSERT(mem.m_str && mem.m_regex);
|
||||
if (mem.is_primitive())
|
||||
continue;
|
||||
while (mem.m_str && !mem.m_str->is_empty()) {
|
||||
|
|
@ -1940,8 +1952,7 @@ namespace seq {
|
|||
|
||||
// check for regex memberships that are immediately infeasible
|
||||
for (str_mem& mem : m_str_mem) {
|
||||
if (!mem.m_str || !mem.m_regex)
|
||||
continue;
|
||||
SASSERT(mem.m_str && mem.m_regex);
|
||||
if (mem.m_str->is_empty() && !mem.m_regex->is_nullable()) {
|
||||
m_is_general_conflict = true;
|
||||
m_reason = backtrack_reason::regex;
|
||||
|
|
@ -1949,33 +1960,21 @@ namespace seq {
|
|||
}
|
||||
}
|
||||
|
||||
// remove satisfied str_mem constraints (ε ∈ nullable regex)
|
||||
unsigned wi = 0;
|
||||
for (unsigned i = 0; i < m_str_mem.size(); ++i) {
|
||||
str_mem& mem = m_str_mem[i];
|
||||
if (mem.m_str && mem.m_str->is_empty() && mem.m_regex && mem.m_regex->is_nullable())
|
||||
continue; // satisfied, drop
|
||||
m_str_mem[wi++] = mem;
|
||||
}
|
||||
if (wi < m_str_mem.size())
|
||||
m_str_mem.shrink(wi);
|
||||
|
||||
// Regex widening: for each remaining str_mem, overapproximate
|
||||
// the string by replacing variables with their regex intersection
|
||||
// and check if the result intersected with the target regex is empty.
|
||||
// Detects infeasible constraints that would otherwise require
|
||||
// expensive exploration. Mirrors ZIPT NielsenNode.CheckRegexWidening.
|
||||
if (m_graph.m_seq_regex) {
|
||||
for (str_mem const& mem : m_str_mem) {
|
||||
if (!mem.m_str || !mem.m_regex)
|
||||
continue;
|
||||
if (mem.is_primitive())
|
||||
continue;
|
||||
if (m_graph.check_regex_widening(*this, mem.m_str, mem.m_regex)) {
|
||||
m_is_general_conflict = true;
|
||||
m_reason = backtrack_reason::regex;
|
||||
return simplify_result::conflict;
|
||||
}
|
||||
SASSERT(m_graph.m_seq_regex);
|
||||
for (str_mem const& mem : m_str_mem) {
|
||||
SASSERT(mem.m_str && mem.m_regex);
|
||||
if (mem.is_primitive())
|
||||
continue;
|
||||
if (m_graph.check_regex_widening(*this, mem.m_str, mem.m_regex)) {
|
||||
std::cout << "Widening conflict: " << mk_pp(mem.m_str->get_expr(), m) << " ∉ " << mk_pp(mem.m_regex->get_expr(), m) << std::endl;
|
||||
m_is_general_conflict = true;
|
||||
m_reason = backtrack_reason::regex;
|
||||
return simplify_result::conflict;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1990,7 +1989,8 @@ namespace seq {
|
|||
// assigned to x without splitting, by checking directional consistency.
|
||||
// Guard: skip if we already created a child (re-entry via iterative deepening).
|
||||
for (str_eq& eq : m_str_eq) {
|
||||
if (eq.is_trivial() || !eq.m_lhs || !eq.m_rhs)
|
||||
SASSERT(eq.m_lhs && eq.m_rhs);
|
||||
if (eq.is_trivial())
|
||||
continue;
|
||||
for (unsigned od = 0; od < 2; ++od) {
|
||||
bool fwd = od == 0;
|
||||
|
|
@ -2160,8 +2160,9 @@ namespace seq {
|
|||
|
||||
// Create a single deterministic child with directional substitution.
|
||||
euf::snode* prefix_sn = char_toks[0];
|
||||
for (unsigned j = 1; j < i; ++j)
|
||||
for (unsigned j = 1; j < i; ++j) {
|
||||
prefix_sn = dir_concat(sg, prefix_sn, char_toks[j], fwd);
|
||||
}
|
||||
euf::snode* replacement = dir_concat(sg, prefix_sn, var_node, fwd);
|
||||
nielsen_subst s(var_node, replacement, eq.m_dep);
|
||||
nielsen_node* child = m_graph.mk_child(this);
|
||||
|
|
@ -2175,7 +2176,6 @@ namespace seq {
|
|||
|
||||
if (is_satisfied())
|
||||
return simplify_result::satisfied;
|
||||
|
||||
return simplify_result::proceed;
|
||||
}
|
||||
|
||||
|
|
@ -3004,9 +3004,9 @@ namespace seq {
|
|||
str_eq const*& eq_out,
|
||||
bool& fwd) const {
|
||||
for (str_eq const& eq : node->str_eqs()) {
|
||||
if (eq.is_trivial())
|
||||
if (eq.is_trivial())
|
||||
continue;
|
||||
if (!eq.m_lhs || !eq.m_rhs)
|
||||
if (!eq.m_lhs || !eq.m_rhs)
|
||||
continue;
|
||||
for (unsigned od = 0; od < 2; ++od) {
|
||||
bool local_fwd = (od == 0);
|
||||
|
|
@ -3046,9 +3046,9 @@ namespace seq {
|
|||
str_eq const*& eq_out,
|
||||
bool& fwd) const {
|
||||
for (str_eq const& eq : node->str_eqs()) {
|
||||
if (eq.is_trivial())
|
||||
if (eq.is_trivial())
|
||||
continue;
|
||||
if (!eq.m_lhs || !eq.m_rhs)
|
||||
if (!eq.m_lhs || !eq.m_rhs)
|
||||
continue;
|
||||
for (unsigned od = 0; od < 2; ++od) {
|
||||
bool local_fwd = (od == 0);
|
||||
|
|
@ -3092,9 +3092,9 @@ namespace seq {
|
|||
euf::snode* power = nullptr;
|
||||
dep_tracker dep = m_dep_mgr.mk_empty();
|
||||
for (str_eq const& eq : node->str_eqs()) {
|
||||
if (eq.is_trivial())
|
||||
if (eq.is_trivial())
|
||||
continue;
|
||||
if (!eq.m_lhs || !eq.m_rhs)
|
||||
if (!eq.m_lhs || !eq.m_rhs)
|
||||
continue;
|
||||
if (eq.m_lhs->is_empty() && eq.m_rhs->first() && eq.m_rhs->first()->is_power()) {
|
||||
power = eq.m_rhs->first();
|
||||
|
|
@ -3215,7 +3215,7 @@ namespace seq {
|
|||
bool nielsen_graph::apply_split_power_elim(nielsen_node* node) {
|
||||
ast_manager& m = m_sg.get_manager();
|
||||
arith_util arith(m);
|
||||
seq_util seq(m);
|
||||
seq_util& seq = this->seq();
|
||||
|
||||
for (str_eq const& eq : node->str_eqs()) {
|
||||
if (eq.is_trivial())
|
||||
|
|
@ -3239,7 +3239,7 @@ namespace seq {
|
|||
if (!base_sn || !pow_exp)
|
||||
continue;
|
||||
|
||||
auto [count, consumed] = comm_power(base_sn, other_side, m, fwd);
|
||||
auto [count, consumed] = comm_power(base_sn, other_side, m, seq, fwd);
|
||||
if (!count.get() || consumed == 0)
|
||||
continue;
|
||||
|
||||
|
|
@ -4400,13 +4400,12 @@ namespace seq {
|
|||
|
||||
bool nielsen_graph::check_regex_widening(nielsen_node const& node,
|
||||
euf::snode* str, euf::snode* regex) {
|
||||
if (!str || !regex || !m_seq_regex)
|
||||
return false;
|
||||
SASSERT(str && regex && m_seq_regex);
|
||||
// Only apply to ground regexes with non-trivial strings
|
||||
if (!regex->is_ground())
|
||||
return false;
|
||||
|
||||
seq_util seq(m_sg.get_manager());
|
||||
seq_util& seq = m_sg.get_seq_util();
|
||||
ast_manager& mgr = m_sg.get_manager();
|
||||
|
||||
// Build the overapproximation regex for the string.
|
||||
|
|
@ -4425,8 +4424,7 @@ namespace seq {
|
|||
if (tok->is_char()) {
|
||||
// Concrete character → to_re(unit(c))
|
||||
expr* te = tok->get_expr();
|
||||
if (!te)
|
||||
return false;
|
||||
SASSERT(te);
|
||||
expr_ref tre(seq.re.mk_to_re(te), mgr);
|
||||
tok_re = m_sg.mk(tre);
|
||||
}
|
||||
|
|
@ -4456,9 +4454,8 @@ namespace seq {
|
|||
seq.str.mk_string(zstring(r.m_lo)),
|
||||
seq.str.mk_string(zstring(r.m_hi - 1))), mgr);
|
||||
euf::snode* rng_sn = m_sg.mk(rng);
|
||||
if (!range_re) {
|
||||
if (!range_re)
|
||||
range_re = rng_sn;
|
||||
}
|
||||
else {
|
||||
expr_ref u(seq.re.mk_union(range_re->get_expr(), rng_sn->get_expr()), mgr);
|
||||
range_re = m_sg.mk(u);
|
||||
|
|
@ -4481,17 +4478,14 @@ namespace seq {
|
|||
tok_re = m_sg.mk(all_re);
|
||||
}
|
||||
|
||||
if (!tok_re)
|
||||
return false;
|
||||
SASSERT(tok_re);
|
||||
|
||||
if (!approx)
|
||||
approx = tok_re;
|
||||
else {
|
||||
expr* ae = approx->get_expr();
|
||||
expr* te = tok_re->get_expr(
|
||||
);
|
||||
if (!ae || !te)
|
||||
return false;
|
||||
expr* te = tok_re->get_expr();
|
||||
SASSERT(ae && te);
|
||||
expr_ref cat(seq.re.mk_concat(ae, te), mgr);
|
||||
approx = m_sg.mk(cat);
|
||||
}
|
||||
|
|
@ -4504,13 +4498,11 @@ namespace seq {
|
|||
// Build intersection regex
|
||||
expr* ae = approx->get_expr();
|
||||
expr* re = regex->get_expr();
|
||||
if (!ae || !re)
|
||||
return false;
|
||||
SASSERT(ae && re);
|
||||
expr_ref inter(seq.re.mk_inter(ae, re), mgr);
|
||||
euf::snode* inter_sn = m_sg.mk(inter);
|
||||
if (!inter_sn)
|
||||
return false;
|
||||
|
||||
SASSERT(inter_sn);
|
||||
// std::cout << "HTML: " << snode_label_html(inter_sn, m()) << std::endl;
|
||||
lbool result = m_seq_regex->is_empty_bfs(inter_sn, 5000);
|
||||
return result == l_true;
|
||||
}
|
||||
|
|
@ -4585,3 +4577,4 @@ namespace seq {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ namespace seq {
|
|||
smt::enode *m_l, *m_r;
|
||||
dep_tracker m_dep;
|
||||
|
||||
str_eq() = default;
|
||||
str_eq(): m_lhs(nullptr), m_rhs(nullptr), m_dep(nullptr) {}
|
||||
str_eq(euf::snode* lhs, euf::snode* rhs, smt::enode* l, smt::enode* r, dep_tracker const& dep):
|
||||
m_lhs(lhs), m_rhs(rhs), m_l(l), m_r(r), m_dep(dep) {}
|
||||
|
||||
|
|
@ -395,7 +395,7 @@ 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;
|
||||
bool is_trivial() const;
|
||||
|
||||
// check if the constraint contains a given variable
|
||||
bool contains_var(euf::snode* var) const;
|
||||
|
|
@ -783,7 +783,7 @@ namespace seq {
|
|||
|
||||
// Regex membership module: stabilizers, emptiness checks, language
|
||||
// inclusion, derivatives. Allocated in the constructor; owned by this graph.
|
||||
seq_regex* m_seq_regex = nullptr;
|
||||
seq::seq_regex* m_seq_regex = nullptr;
|
||||
|
||||
// -----------------------------------------------
|
||||
// Modification counter for substitution length tracking.
|
||||
|
|
@ -835,10 +835,9 @@ namespace seq {
|
|||
nielsen_node* root() const { return m_root; }
|
||||
void set_root(nielsen_node* n) { m_root = n; }
|
||||
|
||||
void set_sat_node(nielsen_node* n) { m_sat_node = n; }
|
||||
|
||||
// satisfying leaf node (set by solve() when result is sat)
|
||||
nielsen_node* sat_node() const { return m_sat_node; }
|
||||
void set_sat_node(nielsen_node* n) { m_sat_node = n; }
|
||||
// path of edges from root to sat_node (set when sat_node is set)
|
||||
svector<nielsen_edge*> const& sat_path() const { return m_sat_path; }
|
||||
|
||||
|
|
|
|||
|
|
@ -357,8 +357,7 @@ namespace seq {
|
|||
// -----------------------------------------------------------------------
|
||||
|
||||
void seq_regex::collect_char_boundaries(euf::snode* re, unsigned_vector& bounds) const {
|
||||
if (!re || !re->get_expr())
|
||||
return;
|
||||
SASSERT(re && re->get_expr());
|
||||
|
||||
seq_util& seq = m_sg.get_seq_util();
|
||||
expr* e = re->get_expr();
|
||||
|
|
@ -394,8 +393,9 @@ namespace seq {
|
|||
return;
|
||||
|
||||
// Recurse into children (handles union, concat, star, loop, etc.)
|
||||
for (unsigned i = 0; i < re->num_args(); ++i)
|
||||
for (unsigned i = 0; i < re->num_args(); ++i) {
|
||||
collect_char_boundaries(re->arg(i), bounds);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -403,18 +403,19 @@ namespace seq {
|
|||
// -----------------------------------------------------------------------
|
||||
|
||||
void seq_regex::get_alphabet_representatives(euf::snode* re, euf::snode_vector& reps) {
|
||||
unsigned_vector bounds;
|
||||
bounds.push_back(0); // always include character 0
|
||||
collect_char_boundaries(re, bounds);
|
||||
euf::snode_vector minterms;
|
||||
m_sg.compute_minterms(re, minterms);
|
||||
|
||||
// Sort and deduplicate
|
||||
std::sort(bounds.begin(), bounds.end());
|
||||
unsigned prev = UINT_MAX;
|
||||
for (unsigned b : bounds) {
|
||||
if (b != prev) {
|
||||
reps.push_back(m_sg.mk_char(b));
|
||||
prev = b;
|
||||
}
|
||||
for (euf::snode* mt : minterms) {
|
||||
SASSERT(mt);
|
||||
if (mt->is_fail())
|
||||
continue;
|
||||
char_set cs = minterm_to_char_set(mt->get_expr());
|
||||
SASSERT(!cs.is_empty());
|
||||
|
||||
// Pick a concrete character from the character set to act as the representative
|
||||
unsigned rep_char = cs.ranges()[0].m_lo;
|
||||
reps.push_back(m_sg.mk_char(rep_char));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -423,8 +424,7 @@ namespace seq {
|
|||
// -----------------------------------------------------------------------
|
||||
|
||||
lbool seq_regex::is_empty_bfs(euf::snode* re, unsigned max_states) {
|
||||
if (!re || !re->get_expr())
|
||||
return l_undef;
|
||||
SASSERT(re && re->get_expr());
|
||||
if (re->is_fail())
|
||||
return l_true;
|
||||
if (re->is_nullable())
|
||||
|
|
@ -455,7 +455,6 @@ namespace seq {
|
|||
visited.insert(re->id());
|
||||
|
||||
unsigned states_explored = 0;
|
||||
bool had_failed_deriv = false;
|
||||
|
||||
while (!worklist.empty()) {
|
||||
if (states_explored >= max_states)
|
||||
|
|
@ -472,20 +471,14 @@ namespace seq {
|
|||
euf::snode_vector reps;
|
||||
get_alphabet_representatives(current, reps);
|
||||
|
||||
if (reps.empty()) {
|
||||
// No representatives means no character predicates;
|
||||
// use a default character to explore the single partition.
|
||||
reps.push_back(m_sg.mk_char('a'));
|
||||
}
|
||||
if (reps.empty())
|
||||
// Nothing found = dead-end
|
||||
continue;
|
||||
|
||||
for (euf::snode* ch : reps) {
|
||||
// std::cout << "Deriving by " << snode_label_html(ch, sg().get_manager()) << std::endl;
|
||||
euf::snode* deriv = m_sg.brzozowski_deriv(current, ch);
|
||||
if (!deriv) {
|
||||
// Derivative computation failed for this character.
|
||||
// Track the failure but continue with other characters.
|
||||
had_failed_deriv = true;
|
||||
continue;
|
||||
}
|
||||
SASSERT(deriv);
|
||||
if (deriv->is_nullable())
|
||||
return l_false; // found an accepting state
|
||||
if (deriv->is_fail())
|
||||
|
|
@ -495,16 +488,11 @@ namespace seq {
|
|||
if (!visited.contains(deriv->id())) {
|
||||
visited.insert(deriv->id());
|
||||
worklist.push_back(deriv);
|
||||
// std::cout << "Found [" << deriv->id() << "]: " << snode_label_html(deriv, sg().get_manager()) << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exhausted all reachable states without finding a nullable one.
|
||||
// If we had any failed derivative computations, the result is
|
||||
// inconclusive since we may have missed reachable states.
|
||||
if (had_failed_deriv)
|
||||
return l_undef;
|
||||
|
||||
return l_true;
|
||||
}
|
||||
|
||||
|
|
@ -588,8 +576,7 @@ namespace seq {
|
|||
|
||||
euf::snode* seq_regex::collect_primitive_regex_intersection(
|
||||
euf::snode* var, seq::nielsen_node const& node) {
|
||||
if (!var)
|
||||
return nullptr;
|
||||
SASSERT(var);
|
||||
|
||||
seq_util& seq = m_sg.get_seq_util();
|
||||
ast_manager& mgr = m_sg.get_manager();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue