mirror of
https://github.com/Z3Prover/z3
synced 2026-06-01 06:37:49 +00:00
Fixed the "partial automaton" after we push regex unwinding to ITE splitting
This commit is contained in:
parent
7ec3bf55ff
commit
fb6b05aa83
2 changed files with 116 additions and 6 deletions
|
|
@ -1451,8 +1451,23 @@ namespace seq {
|
||||||
set_conflict(backtrack_reason::regex, mem.m_dep);
|
set_conflict(backtrack_reason::regex, mem.m_dep);
|
||||||
return simplify_result::conflict;
|
return simplify_result::conflict;
|
||||||
}
|
}
|
||||||
if (fwd)
|
if (fwd) {
|
||||||
m_graph.record_partial_derivative_edge(src_re, tok, deriv);
|
if (tok->is_char()) {
|
||||||
|
// concrete char: record single edge directly
|
||||||
|
m_graph.record_partial_derivative_edge(src_re, tok, deriv);
|
||||||
|
} else if (src_re->is_ground()) {
|
||||||
|
// symbolic unit: record all concrete minterm edges for src_re
|
||||||
|
// so cycle_decomp can detect SCCs lazily (mirrors old concrete
|
||||||
|
// per-minterm var_split behaviour).
|
||||||
|
euf::snode_vector mts;
|
||||||
|
sg.compute_minterms(src_re, mts);
|
||||||
|
for (euf::snode* mt : mts) {
|
||||||
|
euf::snode* mt_deriv = sg.brzozowski_deriv(src_re, mt);
|
||||||
|
if (mt_deriv && !mt_deriv->is_fail() && mt_deriv->is_ground())
|
||||||
|
m_graph.record_partial_derivative_edge(src_re, mt, mt_deriv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
mem.m_str = dir_drop(sg, mem.m_str, 1, fwd);
|
mem.m_str = dir_drop(sg, mem.m_str, 1, fwd);
|
||||||
mem.m_regex = deriv;
|
mem.m_regex = deriv;
|
||||||
}
|
}
|
||||||
|
|
@ -1471,6 +1486,7 @@ namespace seq {
|
||||||
if (!tok || !tok->is_char_or_unit())
|
if (!tok || !tok->is_char_or_unit())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
euf::snode* src_re = mem.m_regex;
|
||||||
seq_rewriter rw(m);
|
seq_rewriter rw(m);
|
||||||
expr_ref d(rw.mk_derivative(mem.m_regex->get_expr()), m);
|
expr_ref d(rw.mk_derivative(mem.m_regex->get_expr()), m);
|
||||||
|
|
||||||
|
|
@ -1484,6 +1500,19 @@ namespace seq {
|
||||||
th_rewriter thrw(m);
|
th_rewriter thrw(m);
|
||||||
thrw(d);
|
thrw(d);
|
||||||
|
|
||||||
|
// Record concrete minterm edges for src_re so cycle_decomp can
|
||||||
|
// detect SCCs lazily (mirrors the incremental DFA building from
|
||||||
|
// concrete char consumption in the loop above).
|
||||||
|
if (src_re->is_ground()) {
|
||||||
|
euf::snode_vector mts;
|
||||||
|
sg.compute_minterms(src_re, mts);
|
||||||
|
for (euf::snode* mt : mts) {
|
||||||
|
euf::snode* mt_deriv = sg.brzozowski_deriv(src_re, mt);
|
||||||
|
if (mt_deriv && !mt_deriv->is_fail())
|
||||||
|
m_graph.record_partial_derivative_edge(src_re, mt, mt_deriv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto next = sg.mk(d);
|
auto next = sg.mk(d);
|
||||||
if (next->is_fail()) {
|
if (next->is_fail()) {
|
||||||
TRACE(seq, tout << "empty regex" << spp(mem.m_regex, m) << " d: " << d << "\n");
|
TRACE(seq, tout << "empty regex" << spp(mem.m_regex, m) << " d: " << d << "\n");
|
||||||
|
|
@ -3056,6 +3085,72 @@ namespace seq {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Helper: precompute_partial_dfa
|
||||||
|
// BFS of Brzozowski derivatives from root_re up to `depth` steps, recording
|
||||||
|
// all concrete minterm edges in the partial DFA. Called eagerly from
|
||||||
|
// apply_cycle_decomposition (priority 6) so that SCC detection can fire
|
||||||
|
// before apply_regex_var_split (priority 10) creates a symbolic child whose
|
||||||
|
// ite-derivative would otherwise delay SCC detection by one more level.
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
void nielsen_graph::precompute_partial_dfa(euf::snode* root_re, unsigned depth) {
|
||||||
|
if (!root_re || !root_re->is_ground())
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct work_item { euf::snode* re; unsigned d; };
|
||||||
|
svector<work_item> queue;
|
||||||
|
queue.push_back({root_re, depth});
|
||||||
|
uint_set visited;
|
||||||
|
visited.insert(root_re->id());
|
||||||
|
|
||||||
|
while (!queue.empty()) {
|
||||||
|
auto [re, d] = queue.back();
|
||||||
|
queue.pop_back();
|
||||||
|
euf::snode_vector mts;
|
||||||
|
m_sg.compute_minterms(re, mts);
|
||||||
|
for (euf::snode* mt : mts) {
|
||||||
|
euf::snode* deriv = m_sg.brzozowski_deriv(re, mt);
|
||||||
|
if (!deriv || deriv->is_fail())
|
||||||
|
continue;
|
||||||
|
record_partial_derivative_edge(re, mt, deriv);
|
||||||
|
if (d > 0 && deriv->is_ground() && !visited.contains(deriv->id())) {
|
||||||
|
visited.insert(deriv->id());
|
||||||
|
queue.push_back({deriv, d - 1});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Helper: record_dfa_edges_from_ite
|
||||||
|
// Walk an ite-structured symbolic derivative and record a concrete DFA edge
|
||||||
|
// for each non-fail branch. The ite has the form:
|
||||||
|
// ite(in_re(char_var, minterm_re), branch_re, rest_ite)
|
||||||
|
// Each (minterm_re, branch_re) pair gives one DFA edge src_re→branch_re.
|
||||||
|
// Called from simplify_and_init so that cycle_decomp can detect SCCs lazily
|
||||||
|
// as symbolic chars are consumed (mirroring the old concrete-char approach).
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
void nielsen_graph::record_dfa_edges_from_ite(euf::snode* src_re, expr* ite_deriv) {
|
||||||
|
if (!src_re || !ite_deriv)
|
||||||
|
return;
|
||||||
|
expr *c, *th, *el;
|
||||||
|
if (!m.is_ite(ite_deriv, c, th, el))
|
||||||
|
return;
|
||||||
|
expr *char_ex, *minterm_re;
|
||||||
|
if (m_seq.str.is_in_re(c, char_ex, minterm_re)) {
|
||||||
|
if (!m_seq.re.is_empty(th)) {
|
||||||
|
euf::snode* dst = m_sg.mk(th);
|
||||||
|
if (dst && !dst->is_fail() && dst->is_ground()) {
|
||||||
|
euf::snode* label = m_sg.mk(minterm_re);
|
||||||
|
record_partial_derivative_edge(src_re, label, dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
record_dfa_edges_from_ite(src_re, el);
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Helper: get_current_stabilizer
|
// Helper: get_current_stabilizer
|
||||||
// Returns the current partial DFA stabilizer s* for root_re without the
|
// Returns the current partial DFA stabilizer s* for root_re without the
|
||||||
|
|
@ -3163,6 +3258,11 @@ namespace seq {
|
||||||
euf::snode* x = first;
|
euf::snode* x = first;
|
||||||
euf::snode* stabilizer_re = nullptr;
|
euf::snode* stabilizer_re = nullptr;
|
||||||
|
|
||||||
|
// Eagerly precompute partial DFA edges from this regex so that
|
||||||
|
// collect_scc_for_projection can detect cycles without waiting
|
||||||
|
// for apply_regex_var_split to create per-minterm children.
|
||||||
|
precompute_partial_dfa(mem.m_regex, 2);
|
||||||
|
|
||||||
if (!try_extract_partial_projection(mem.m_regex, stabilizer_re))
|
if (!try_extract_partial_projection(mem.m_regex, stabilizer_re))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -3891,9 +3991,7 @@ namespace seq {
|
||||||
child->apply_subst(m_sg, s);
|
child->apply_subst(m_sg, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Branch 2+: for each minterm m_i, x → ?c · x
|
// Branch 2: x → ?c · x, where ?c is a fresh symbolic char.
|
||||||
// where ?c is a symbolic char constrained by the minterm
|
|
||||||
|
|
||||||
// for variables at mod_count 0 and other terms, use symbolic (str.len expr)
|
// for variables at mod_count 0 and other terms, use symbolic (str.len expr)
|
||||||
// NSB review:
|
// NSB review:
|
||||||
// it really is seq.nth (length-of-prefix that was chopped of for first)
|
// it really is seq.nth (length-of-prefix that was chopped of for first)
|
||||||
|
|
|
||||||
|
|
@ -1190,6 +1190,18 @@ namespace seq {
|
||||||
// (bypasses the novelty guard used by try_extract_partial_projection).
|
// (bypasses the novelty guard used by try_extract_partial_projection).
|
||||||
euf::snode* get_current_stabilizer(euf::snode* root_re);
|
euf::snode* get_current_stabilizer(euf::snode* 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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
// generalized power introduction: for an equation where one head is
|
// generalized power introduction: for an equation where one head is
|
||||||
// a variable v and the other side has ground prefix + a variable x
|
// a variable v and the other side has ground prefix + a variable x
|
||||||
// forming a cycle back to v, introduce v = base^n · suffix.
|
// forming a cycle back to v, introduce v = base^n · suffix.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue