From f034616950f86ee28c0b205264de904af4418274 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 25 Jun 2026 18:57:30 -0700 Subject: [PATCH] Revert "Derive with ranges" (#9964) Reverts Z3Prover/z3#9963 --- .gitignore | 3 - gmon.out | Bin 0 -> 14458562 bytes src/ast/rewriter/CMakeLists.txt | 4 - src/ast/rewriter/bool_rewriter.cpp | 29 +- src/ast/rewriter/bool_rewriter.h | 9 +- src/ast/rewriter/seq_derive.cpp | 1416 ---------------------- src/ast/rewriter/seq_derive.h | 265 ---- src/ast/rewriter/seq_range_collapse.cpp | 168 --- src/ast/rewriter/seq_range_collapse.h | 71 -- src/ast/rewriter/seq_range_predicate.cpp | 292 ----- src/ast/rewriter/seq_range_predicate.h | 127 -- src/ast/rewriter/seq_regex_bisim.cpp | 59 +- src/ast/rewriter/seq_regex_bisim.h | 1 + src/ast/rewriter/seq_rewriter.cpp | 1085 +++++++++++++---- src/ast/rewriter/seq_rewriter.h | 98 +- src/ast/rewriter/seq_subset.cpp | 45 +- src/ast/rewriter/seq_subset.h | 6 - src/smt/seq_regex.cpp | 140 +-- src/smt/seq_regex.h | 7 +- src/test/CMakeLists.txt | 3 - src/test/main.cpp | 3 - src/test/range_predicate.cpp | 260 ---- src/test/regex_range_collapse.cpp | 244 ---- src/test/seq_regex_bisim.cpp | 127 -- 24 files changed, 1000 insertions(+), 3462 deletions(-) create mode 100644 gmon.out delete mode 100644 src/ast/rewriter/seq_derive.cpp delete mode 100644 src/ast/rewriter/seq_derive.h delete mode 100644 src/ast/rewriter/seq_range_collapse.cpp delete mode 100644 src/ast/rewriter/seq_range_collapse.h delete mode 100644 src/ast/rewriter/seq_range_predicate.cpp delete mode 100644 src/ast/rewriter/seq_range_predicate.h delete mode 100644 src/test/range_predicate.cpp delete mode 100644 src/test/regex_range_collapse.cpp delete mode 100644 src/test/seq_regex_bisim.cpp diff --git a/.gitignore b/.gitignore index 8e5bd7294d..2d268c988f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,9 @@ *~ rebase.cmd -reports/ -crashes/ *.pyc *.pyo # Ignore callgrind files callgrind.out.* -gmon.out # .hpp files are automatically generated *.hpp .env diff --git a/gmon.out b/gmon.out new file mode 100644 index 0000000000000000000000000000000000000000..cba10a19b2a99a2517d591f2bf1b928974a77eb5 GIT binary patch literal 14458562 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 C(qiub literal 0 HcmV?d00001 diff --git a/src/ast/rewriter/CMakeLists.txt b/src/ast/rewriter/CMakeLists.txt index c118501926..6db1320ab9 100644 --- a/src/ast/rewriter/CMakeLists.txt +++ b/src/ast/rewriter/CMakeLists.txt @@ -39,11 +39,7 @@ z3_add_component(rewriter rewriter.cpp seq_axioms.cpp seq_eq_solver.cpp - seq_derive.cpp seq_subset.cpp - seq_derive.cpp - seq_range_collapse.cpp - seq_range_predicate.cpp seq_rewriter.cpp seq_regex_bisim.cpp seq_skolem.cpp diff --git a/src/ast/rewriter/bool_rewriter.cpp b/src/ast/rewriter/bool_rewriter.cpp index 945aa297ee..321bd6f47d 100644 --- a/src/ast/rewriter/bool_rewriter.cpp +++ b/src/ast/rewriter/bool_rewriter.cpp @@ -19,7 +19,6 @@ Notes: #include "ast/rewriter/bool_rewriter.h" #include "params/bool_rewriter_params.hpp" #include "ast/rewriter/rewriter_def.h" -#include "ast/rewriter/expr_safe_replace.h" #include "ast/ast_lt.h" #include "ast/for_each_expr.h" #include @@ -1186,30 +1185,4 @@ void bool_rewriter::mk_ge2(expr* a, expr* b, expr* c, expr_ref& r) { } -bool bool_rewriter::decompose_ite(expr *r, expr_ref &c, expr_ref &th, expr_ref &el) { - expr *cond = nullptr, *r1 = nullptr, *r2 = nullptr; - if (m().is_ite(r, cond, r1, r2)) { - c = cond; - th = r1; - el = r2; - return true; - } - for (expr *e : subterms::ground(expr_ref(r, m()))) { - if (m().is_ite(e, cond, r1, r2)) { - m_rep1.reset(); - m_rep2.reset(); - m_rep1.insert(e, r1); - m_rep2.insert(e, r2); - c = cond; - th = r; - el = r; - m_rep1(th); - m_rep2(el); - return true; - } - } - return false; -} - - -template class rewriter_tpl; \ No newline at end of file +template class rewriter_tpl; diff --git a/src/ast/rewriter/bool_rewriter.h b/src/ast/rewriter/bool_rewriter.h index 87c50f171e..2b52404e50 100644 --- a/src/ast/rewriter/bool_rewriter.h +++ b/src/ast/rewriter/bool_rewriter.h @@ -20,7 +20,6 @@ Notes: #include "ast/ast.h" #include "ast/rewriter/rewriter.h" -#include "ast/rewriter/expr_safe_replace.h" #include "util/params.h" /** @@ -65,7 +64,6 @@ class bool_rewriter { ptr_vector m_todo1, m_todo2; unsigned_vector m_counts1, m_counts2; expr_mark m_marked; - expr_safe_replace m_rep1, m_rep2; br_status mk_flat_and_core(unsigned num_args, expr * const * args, expr_ref & result); br_status mk_flat_or_core(unsigned num_args, expr * const * args, expr_ref & result); @@ -89,7 +87,7 @@ class bool_rewriter { expr_ref simplify_eq_ite(expr* value, expr* ite); public: - bool_rewriter(ast_manager & m, params_ref const & p = params_ref()):m_manager(m), m_local_ctx_cost(0), m_rep1(m), m_rep2(m) { + bool_rewriter(ast_manager & m, params_ref const & p = params_ref()):m_manager(m), m_local_ctx_cost(0) { updt_params(p); } ast_manager & m() const { return m_manager; } @@ -244,11 +242,6 @@ public: void mk_nand(expr * arg1, expr * arg2, expr_ref & result); void mk_nor(expr * arg1, expr * arg2, expr_ref & result); void mk_ge2(expr* a, expr* b, expr* c, expr_ref& result); - - // If r is, or contains, an if-then-else, decompose it into a top-level - // ite by hoisting the (first) inner ite condition: returns c, th, el such - // that r is equivalent to (ite c th el). Returns false if r has no ite. - bool decompose_ite(expr *r, expr_ref &c, expr_ref &th, expr_ref &el); }; struct bool_rewriter_cfg : public default_rewriter_cfg { diff --git a/src/ast/rewriter/seq_derive.cpp b/src/ast/rewriter/seq_derive.cpp deleted file mode 100644 index f99abb382f..0000000000 --- a/src/ast/rewriter/seq_derive.cpp +++ /dev/null @@ -1,1416 +0,0 @@ -/*++ -Copyright (c) 2026 Microsoft Corporation - -Module Name: - - seq_derive.cpp - -Abstract: - - Symbolic derivative computation for regular expressions. - Produces an ITE-tree (transition regex) representation following - the approach of RE# (Varatalu, Veanes, Ernits - POPL 2025). - - The symbolic derivative δ(r) maps each character to the resulting - derivative state via an ITE-tree. The free variable (:var 0) represents - the input character. - -Authors: - - Nikolaj Bjorner (nbjorner) 2026-06-03 - ---*/ - -#include "ast/rewriter/seq_derive.h" -#include "ast/rewriter/seq_rewriter.h" -#include "ast/rewriter/var_subst.h" -#include "ast/ast_pp.h" -#include "ast/array_decl_plugin.h" -#include "ast/rewriter/bool_rewriter.h" -#include "util/util.h" -#include - -namespace seq { - - derive::derive(ast_manager& m, seq_rewriter& re) : - m(m), - m_util(m), - m_autil(m), - m_br(m), - m_re(re), - m_trail(m), - m_ele(m), - m_path_expr(m) { - m_br.set_flat_and_or(false); - } - - void derive::reset() { - m_acache.reset(); - m_bcache.reset(); - m_atop_cache.reset(); - m_btop_cache.reset(); - reset_op_caches(); - m_trail.reset(); - m_ele = nullptr; - } - - // Reset only operation caches (union/inter/concat/complement) - // while preserving derivative caches (m_cache, m_top_cache) - // The op cache does index on m_ele so it has to be reset if m_ele changes. - void derive::reset_op_caches() { - m_aunion_cache.reset(); - m_ainter_cache.reset(); - m_aconcat_cache.reset(); - m_acomplement_cache.reset(); - m_bunion_cache.reset(); - m_binter_cache.reset(); - m_bconcat_cache.reset(); - m_bcomplement_cache.reset(); - m_ele = nullptr; - } - - expr_ref derive::operator()(derivative_kind k, expr* ele, expr* r) { - m_derivative_kind = k; - SASSERT(m_util.is_re(r)); - if (m_trail.size() > 500000) - reset(); - else if (m_trail.size() > 100000 || ele != m_ele) - reset_op_caches(); - sort *seq_sort = nullptr, *ele_sort = nullptr; - VERIFY(m_util.is_re(r, seq_sort)); - VERIFY(m_util.is_seq(seq_sort, ele_sort)); - // Check top-level cache (post-simplify result) - expr* cached = nullptr; - expr_ref result(m); - if (top_cache().find(ele, r, cached)) { - result = cached; - return result; - } - // Pin ele and r - m_trail.push_back(ele); - m_trail.push_back(r); - - // Always compute the SYMBOLIC derivative wrt the canonical - // variable v (so the cached result is reusable for any - // concrete ele via substitution below). Using the concrete - // `ele` here would bake it into the cached ITE-tree and - // poison future lookups for the same r with a different ele. - m_ele = ele; - m_depth = 0; - // Initialize path state for inline pruning - m_path.reset(); - m_intervals.reset(); - m_intervals.push_back({0u, u().max_char()}); - m_intervals_start = 0; - m_path_expr = m.mk_true(); - result = derive_rec(r); - top_cache().insert(ele, r, result); - - // pin the final result - m_trail.push_back(result); - return result; - } - - expr_ref derive::operator()(derivative_kind k, expr* r) { - SASSERT(m_util.is_re(r)); - sort* seq_sort = nullptr, * ele_sort = nullptr; - VERIFY(m_util.is_re(r, seq_sort)); - VERIFY(m_util.is_seq(seq_sort, ele_sort)); - expr_ref v(m.mk_var(0, ele_sort), m); - return (*this)(k,v, r); - } - - // ------------------------------------------------------- - // Core derivative computation - // ------------------------------------------------------- - - expr_ref derive::derive_rec(expr* r) { - SASSERT(m_util.is_re(r)); - - // Check cache (indexed by both m_ele and r) - expr* cached = nullptr; - if (cache().find(m_ele, r, cached)) - return expr_ref(cached, m); - - // Depth check - if (m_depth >= m_max_depth) { - // Return stuck derivative (the derivative operator applied symbolically) - return expr_ref(re().mk_derivative(m_ele, r), m); - } - - flet _scoped_depth(m_depth, m_depth + 1); - expr_ref result = derive_core(r); - - // Cache the result - cache().insert(m_ele, r, result); - m_trail.push_back(m_ele); - m_trail.push_back(r); - m_trail.push_back(result); - return result; - } - - // Forward declaration helper - expr_ref derive::derive_core(expr* r) { - sort* s = nullptr; - VERIFY(m_util.is_re(r, s)); - - auto nothing = [&]() { return expr_ref(re().mk_empty(r->get_sort()), m); }; - auto epsilon = [&]() { return expr_ref(re().mk_to_re(u().str.mk_empty(s)), m); }; - auto dotstar = [&]() { return expr_ref(re().mk_full_seq(r->get_sort()), m); }; - - expr* r1 = nullptr; - expr* r2 = nullptr; - expr* cond = nullptr; - unsigned lo = 0, hi = 0; - - // δ(∅) = ∅, δ(ε) = ∅ - if (re().is_empty(r) || re().is_epsilon(r)) - return nothing(); - - // δ(Σ*) = Σ*, δ(.+) = Σ* - if (re().is_full_seq(r) || re().is_dot_plus(r)) - return dotstar(); - - // δ(.) = ε (full char accepts any single character) - if (re().is_full_char(r)) - return epsilon(); - - // δ(str.to_re(s)) - derivative of a literal string - if (re().is_to_re(r, r1)) - return derive_to_re(r1, s); - - // δ(re.range(lo, hi)) - character range - if (re().is_range(r, r1, r2)) - return derive_range(r1, r2, s); - - // δ(re.of_pred(p)) - predicate-based regex - if (re().is_of_pred(r, r1)) - return derive_of_pred(r1, s); - - // δ(r1 · r2) = δ(r1) · r2 ∪ (if nullable(r1) then δ(r2) else ∅) - if (re().is_concat(r, r1, r2)) { - // Ensure right-associative form first. A left-nested concat - // (a·b)·r2 makes the head r1 a large sub-concat, so deriving it - // recurses through the whole left spine and can exceed - // m_max_depth, producing stuck symbolic re.derivative terms that - // accumulate across states and blow up. mk_concat right- - // associates in a single linear pass (without touching the - // derivative depth counter), keeping the head atomic. - if (re().is_concat(r1)) { - expr_ref rr = mk_concat(r1, r2); - if (rr != r) - return derive_rec(rr); - } - expr_ref d1 = derive_rec(r1); - expr_ref d1_r2 = mk_deriv_concat(d1, r2); - expr_ref nullable_r1 = is_nullable(r1); - if (m.is_true(nullable_r1)) - return mk_union(d1_r2, derive_rec(r2)); - if (m.is_false(nullable_r1)) - return d1_r2; - // Conditional: nullable is a Boolean expression - expr_ref d2 = derive_rec(r2); - expr_ref guarded = mk_ite(nullable_r1, d2, nothing()); - return mk_union(d1_r2, guarded); - } - - // δ(r1 ∪ r2) = δ(r1) ∪ δ(r2) - if (re().is_union(r, r1, r2)) { - expr_ref d1 = derive_rec(r1); - expr_ref d2 = derive_rec(r2); - return mk_union(d1, d2); - } - - // δ(r1 x r2) = δ(r1) x δ(r2) - if (re().is_xor(r, r1, r2)) { - expr_ref d1 = derive_rec(r1); - expr_ref d2 = derive_rec(r2); - return mk_xor(d1, d2); - } - - // δ(r1 ∩ r2) = δ(r1) ∩ δ(r2) - if (re().is_intersection(r, r1, r2)) { - expr_ref d1 = derive_rec(r1); - expr_ref d2 = derive_rec(r2); - return mk_inter(d1, d2); - } - - // δ(~r1) = ~δ(r1) - if (re().is_complement(r, r1)) { - expr_ref d1 = derive_rec(r1); - return mk_complement(d1); - } - - // δ(r1*) = δ(r1) · r1* - if (re().is_star(r, r1)) { - expr_ref d1 = derive_rec(r1); - expr_ref star_r1(re().mk_star(r1), m); - return mk_deriv_concat(d1, star_r1); - } - - // δ(r1+) = δ(r1) · r1* - if (re().is_plus(r, r1)) { - expr_ref d1 = derive_rec(r1); - expr_ref star_r1(re().mk_star(r1), m); - return mk_deriv_concat(d1, star_r1); - } - - // δ(r1?) = δ(r1) - if (re().is_opt(r, r1)) - return derive_rec(r1); - - // δ(r1{lo,hi}) - if (re().is_loop(r, r1, lo, hi)) { - if (hi == 0 || hi < lo) - return nothing(); - expr_ref d1 = derive_rec(r1); - expr_ref tail(re().mk_loop_proper(r1, (lo == 0 ? 0 : lo - 1), hi - 1), m); - return mk_deriv_concat(d1, tail); - } - - // δ(r1{lo,}) - unbounded loop - if (re().is_loop(r, r1, lo)) { - expr_ref d1 = derive_rec(r1); - expr_ref tail(re().mk_loop(r1, (lo == 0 ? 0 : lo - 1)), m); - return mk_deriv_concat(d1, tail); - } - - // δ(r1 \ r2) = δ(r1) ∩ ~δ(r2) - if (re().is_diff(r, r1, r2)) { - expr_ref d1 = derive_rec(r1); - expr_ref d2 = derive_rec(r2); - expr_ref neg_d2 = mk_complement(d2); - return mk_inter(d1, neg_d2); - } - - // δ(ite(c, r1, r2)) = ite(c, δ(r1), δ(r2)) - if (m.is_ite(r, cond, r1, r2)) { - expr_ref d1 = derive_rec(r1); - expr_ref d2 = derive_rec(r2); - return mk_ite(cond, d1, d2); - } - - // δ(reverse(r1)) - normalize by pushing reverse inward, then derive - if (re().is_reverse(r, r1)) { - expr_ref norm = mk_regex_reverse(r1); - if (norm != r) - return derive_rec(norm); - return expr_ref(re().mk_derivative(m_ele, r), m); - } - - // Stuck/uninterpreted case - return expr_ref(re().mk_derivative(m_ele, r), m); - } - - // ------------------------------------------------------- - // Derivative of specific regex constructs - // ------------------------------------------------------- - - expr_ref derive::derive_to_re(expr* s, sort* seq_sort) { - sort* re_sort = re().mk_re(seq_sort); - // δ(to_re("")) = ∅ - if (u().str.is_empty(s)) - return expr_ref(re().mk_empty(re_sort), m); - - // δ(to_re("c₁c₂...cₙ")) = ite(ele = c₁, to_re("c₂...cₙ"), ∅) - zstring zs; - if (u().str.is_string(s, zs)) { - if (zs.length() == 0) - return expr_ref(re().mk_empty(re_sort), m); - // First character - expr_ref head(m_util.mk_char(zs[0]), m); - expr_ref cond(m.mk_eq(m_ele, head), m); - // Tail string - expr_ref tail_str(u().str.mk_string(zs.extract(1, zs.length() - 1)), m); - expr_ref tail_re(re().mk_to_re(tail_str), m); - expr_ref empty(re().mk_empty(re_sort), m); - return mk_ite(cond, tail_re, empty); - } - - // δ(to_re(unit(c))) = ite(ele = c, ε, ∅) - expr* ch = nullptr; - if (u().str.is_unit(s, ch)) { - expr_ref eps(re().mk_to_re(u().str.mk_empty(seq_sort)), m); - expr_ref empty(re().mk_empty(re_sort), m); - expr_ref cond(m.mk_eq(m_ele, ch), m); - return mk_ite(cond, eps, empty); - } - - // δ(to_re(s1 ++ s2)) = ite(head matches, to_re(tail ++ s2), ∅) - expr* s1 = nullptr, * s2 = nullptr; - if (u().str.is_concat(s, s1, s2)) { - expr_ref hd(m), tl(m); - if (get_head_tail(s1, s2, hd, tl)) { - expr_ref cond(m.mk_eq(m_ele, hd), m); - expr_ref tail_re(re().mk_to_re(tl), m); - expr_ref empty(re().mk_empty(re_sort), m); - return mk_ite(cond, tail_re, empty); - } - } - - // δ(to_re(itos(n))) - derivative of integer-to-string - // itos(n) produces digits '0'-'9' when n >= 0, empty when n < 0 - expr* n = nullptr; - if (u().str.is_itos(s, n)) { - expr_ref empty(re().mk_empty(re_sort), m); - // Guard: n >= 0 and element is a digit and element = s[0] - expr_ref n_ge_0(m_autil.mk_ge(n, m_autil.mk_int(0)), m); - expr_ref char_0(m_util.mk_char('0'), m); - expr_ref char_9(m_util.mk_char('9'), m); - expr_ref ge_0(m_util.mk_le(char_0, m_ele), m); - expr_ref le_9(m_util.mk_le(m_ele, char_9), m); - expr_ref is_digit(m.mk_and(ge_0, le_9), m); - // First character of itos(n) matches ele - expr_ref zero_idx(m_autil.mk_int(0), m); - expr_ref first(u().str.mk_nth_i(s, zero_idx), m); - expr_ref eq_first(m.mk_eq(m_ele, first), m); - // Guard = n >= 0 && is_digit && ele = s[0] - expr_ref guard(m.mk_and(n_ge_0, m.mk_and(is_digit, eq_first)), m); - // Tail: to_re(substr(itos(n), 1, len(itos(n)) - 1)) - expr_ref one(m_autil.mk_int(1), m); - expr_ref len(u().str.mk_length(s), m); - expr_ref rest_len(m_autil.mk_sub(len, one), m); - expr_ref rest(u().str.mk_substr(s, one, rest_len), m); - expr_ref rest_re(re().mk_to_re(rest), m); - return mk_ite(guard, rest_re, empty); - } - - // Non-ground sequence: δ(to_re(s)) = ite(s ≠ "" ∧ ele = s[0], to_re(s[1:]), ∅) - expr_ref empty_seq(u().str.mk_empty(seq_sort), m); - expr_ref is_non_empty(m.mk_not(m.mk_eq(s, empty_seq)), m); - expr_ref zero(m_autil.mk_int(0), m); - expr_ref first(u().str.mk_nth_i(s, zero), m); - expr_ref eq_first(m.mk_eq(m_ele, first), m); - expr_ref guard(m.mk_and(is_non_empty, eq_first), m); - expr_ref one(m_autil.mk_int(1), m); - expr_ref len(u().str.mk_length(s), m); - expr_ref rest_len(m_autil.mk_sub(len, one), m); - expr_ref rest(u().str.mk_substr(s, one, rest_len), m); - expr_ref rest_re(re().mk_to_re(rest), m); - expr_ref empty(re().mk_empty(re_sort), m); - return mk_ite(guard, rest_re, empty); - } - - expr_ref derive::derive_range(expr* lo, expr* hi, sort* seq_sort) { - sort* re_sort = re().mk_re(seq_sort); - expr_ref empty(re().mk_empty(re_sort), m); - expr_ref eps(re().mk_to_re(u().str.mk_empty(seq_sort)), m); - - // Extract character values from unit strings - expr_ref c_lo(m), c_hi(m); - if (u().str.is_unit_string(lo, c_lo) && u().str.is_unit_string(hi, c_hi)) { - // Build range condition, simplifying trivial bounds - unsigned lo_val = 0, hi_val = 0; - bool lo_trivial = m_util.is_const_char(c_lo, lo_val) && lo_val == 0; - bool hi_trivial = m_util.is_const_char(c_hi, hi_val) && hi_val == u().max_char(); - - if (lo_trivial && hi_trivial) - return eps; // full charset range — always matches - - expr_ref in_range(m); - if (lo_trivial) - in_range = m_util.mk_le(m_ele, c_hi); - else if (hi_trivial) - in_range = m_util.mk_le(c_lo, m_ele); - else - in_range = m.mk_and(m_util.mk_le(c_lo, m_ele), m_util.mk_le(m_ele, c_hi)); - - return mk_ite(in_range, eps, empty); - } - - // Fallback: stuck derivative - return expr_ref(re().mk_derivative(m_ele, re().mk_range(lo, hi)), m); - } - - expr_ref derive::derive_of_pred(expr* pred, sort* seq_sort) { - sort* re_sort = re().mk_re(seq_sort); - expr_ref empty(re().mk_empty(re_sort), m); - expr_ref eps(re().mk_to_re(u().str.mk_empty(seq_sort)), m); - - // Apply predicate to the element - array_util autil(m); - expr* args[2] = { pred, m_ele }; - expr_ref cond(autil.mk_select(2, args), m); - return mk_ite(cond, eps, empty); - } - - // Extract head character and remaining tail from a sequence - // s1 is the first part, s2 is the continuation (from str.concat(s1, s2)) - bool derive::get_head_tail(expr* s1, expr* s2, expr_ref& hd, expr_ref& tl) { - expr* ch = nullptr; - expr* a = nullptr, * b = nullptr; - if (u().str.is_unit(s1, ch)) { - hd = ch; - tl = s2; - return true; - } - if (u().str.is_concat(s1, a, b)) { - expr_ref new_s2(u().str.mk_concat(b, s2), m); - return get_head_tail(a, new_s2, hd, tl); - } - zstring zs; - if (u().str.is_string(s1, zs) && zs.length() > 0) { - hd = m_util.mk_char(zs[0]); - if (zs.length() == 1) - tl = s2; - else { - expr_ref rest(u().str.mk_string(zs.extract(1, zs.length() - 1)), m); - tl = u().str.mk_concat(rest, s2); - } - return true; - } - return false; - } - - // ------------------------------------------------------- - // Normalize reverse - // ------------------------------------------------------- - - expr_ref derive::mk_regex_reverse(expr* r) { - expr* r1 = nullptr, * r2 = nullptr, * c = nullptr; - unsigned lo = 0, hi = 0; - expr_ref result(m); - if (re().is_empty(r) || re().is_range(r) || re().is_epsilon(r) || re().is_full_seq(r) || - re().is_full_char(r) || re().is_dot_plus(r) || re().is_of_pred(r)) - result = r; - else if (re().is_to_re(r)) - result = re().mk_reverse(r); - else if (re().is_reverse(r, r1)) - result = r1; - else if (re().is_concat(r, r1, r2)) - result = re().mk_concat(mk_regex_reverse(r2), mk_regex_reverse(r1)); - else if (m.is_ite(r, c, r1, r2)) - result = m.mk_ite(c, mk_regex_reverse(r1), mk_regex_reverse(r2)); - else if (re().is_union(r, r1, r2)) { - auto a1 = mk_regex_reverse(r1); - auto b1 = mk_regex_reverse(r2); - result = re().mk_union(a1, b1); - } - else if (re().is_intersection(r, r1, r2)) { - auto a1 = mk_regex_reverse(r1); - auto b1 = mk_regex_reverse(r2); - result = re().mk_inter(a1, b1); - } - else if (re().is_diff(r, r1, r2)) { - auto a1 = mk_regex_reverse(r1); - auto b1 = mk_regex_reverse(r2); - result = re().mk_diff(a1, b1); - } - else if (re().is_star(r, r1)) - result = re().mk_star(mk_regex_reverse(r1)); - else if (re().is_plus(r, r1)) - result = re().mk_plus(mk_regex_reverse(r1)); - else if (re().is_loop(r, r1, lo)) - result = re().mk_loop(mk_regex_reverse(r1), lo); - else if (re().is_loop(r, r1, lo, hi)) - result = re().mk_loop_proper(mk_regex_reverse(r1), lo, hi); - else if (re().is_opt(r, r1)) - result = re().mk_opt(mk_regex_reverse(r1)); - else if (re().is_complement(r, r1)) - result = re().mk_complement(mk_regex_reverse(r1)); - else - result = re().mk_reverse(r); - return result; - } - - // ------------------------------------------------------- - // Nullability - uses info class from seq_decl_plugin.h - // ------------------------------------------------------- - - expr_ref derive::is_nullable(expr* r) { - SASSERT(m_util.is_re(r) || m_util.is_seq(r)); - expr* r1 = nullptr, * r2 = nullptr, * cond = nullptr; - sort* seq_sort = nullptr; - unsigned lo = 0, hi = 0; - zstring s1; - if (m_util.is_re(r)) { - auto info = re().get_info(r); - switch (info.nullable) { - case l_true: return expr_ref(m.mk_true(), m); - case l_false: return expr_ref(m.mk_false(), m); - default: break; - } - } - expr_ref result(m); - if (re().is_concat(r, r1, r2) || - re().is_intersection(r, r1, r2)) { - m_br.mk_and(is_nullable(r1), is_nullable(r2), result); - } - else if (re().is_union(r, r1, r2) || re().is_antimirov_union(r, r1, r2)) { - m_br.mk_or(is_nullable(r1), is_nullable(r2), result); - } - else if (re().is_diff(r, r1, r2)) { - m_br.mk_not(is_nullable(r2), result); - m_br.mk_and(result, is_nullable(r1), result); - } - else if (re().is_xor(r, r1, r2)) { - m_br.mk_xor(is_nullable(r1), is_nullable(r2), result); - } - else if (re().is_star(r) || - re().is_opt(r) || - re().is_full_seq(r) || - re().is_epsilon(r) || - (re().is_loop(r, r1, lo) && lo == 0) || - (re().is_loop(r, r1, lo, hi) && lo == 0)) { - result = m.mk_true(); - } - else if (re().is_full_char(r) || - re().is_empty(r) || - re().is_of_pred(r) || - re().is_range(r)) { - result = m.mk_false(); - } - else if (re().is_plus(r, r1) || - (re().is_loop(r, r1, lo) && lo > 0) || - (re().is_loop(r, r1, lo, hi) && lo > 0) || - (re().is_reverse(r, r1))) { - result = is_nullable(r1); - } - else if (re().is_complement(r, r1)) { - m_br.mk_not(is_nullable(r1), result); - } - else if (re().is_to_re(r, r1)) { - result = is_nullable(r1); - } - else if (m.is_ite(r, cond, r1, r2)) { - m_br.mk_ite(cond, is_nullable(r1), is_nullable(r2), result); - } - else if (m_util.is_re(r, seq_sort)) { - result = is_nullable_symbolic_regex(r, seq_sort); - } - else if (u().str.is_concat(r, r1, r2)) { - m_br.mk_and(is_nullable(r1), is_nullable(r2), result); - } - else if (u().str.is_empty(r)) { - result = m.mk_true(); - } - else if (u().str.is_unit(r)) { - result = m.mk_false(); - } - else if (u().str.is_string(r, s1)) { - result = m.mk_bool_val(s1.length() == 0); - } - else { - SASSERT(m_util.is_seq(r)); - result = m.mk_eq(u().str.mk_empty(r->get_sort()), r); - } - return result; - } - - expr_ref derive::is_nullable_symbolic_regex(expr* r, sort* seq_sort) { - SASSERT(m_util.is_re(r)); - expr* elem = nullptr, * r1 = r, * r2 = nullptr, * s = nullptr; - expr_ref elems(u().str.mk_empty(seq_sort), m); - expr_ref result(m); - while (re().is_derivative(r1, elem, r2)) { - if (u().str.is_empty(elems)) - elems = u().str.mk_unit(elem); - else - elems = u().str.mk_concat(u().str.mk_unit(elem), elems); - r1 = r2; - } - if (re().is_to_re(r1, s)) { - result = m.mk_eq(elems, s); - return result; - } - result = re().mk_in_re(u().str.mk_empty(seq_sort), r); - return result; - } - - // ------------------------------------------------------- - // Smart constructors with simplification - // ------------------------------------------------------- - - - // Extract character range [lo, hi] from a derivative condition. - // Conditions are of the form: - // ele == c → range [c, c] - // char_le(lo_expr, ele) && char_le(ele, hi_expr) → range [lo, hi] - // char_le(lo_expr, ele) → range [lo, max_char] - // char_le(ele, hi_expr) → range [0, hi] - // Returns false if not a recognizable range condition. - // Predicate implication for character range conditions. - // Returns true if: whenever cond_a is true, cond_b must also be true. - // pred_implies(sign_a, a, sign_b, b): does (sign_a ? ¬a : a) imply (sign_b ? ¬b : b)? - bool derive::pred_implies(bool sign_a, expr* a, bool sign_b, expr* b) { - // Same atom: check sign compatibility - if (a == b) return sign_a == sign_b; - - // Both negated: ¬a → ¬b iff b → a, i.e. pred_implies(false, b, false, a) - if (sign_a && sign_b) - return pred_implies(false, b, false, a); - - unsigned lo_a, hi_a, lo_b, hi_b; - bool neg_a, neg_b; - - if (!sign_a && !sign_b) { - // a → b: range_a ⊆ range_b - if (u().is_char_const_range(m_ele, a, lo_a, hi_a, neg_a) && !neg_a && - u().is_char_const_range(m_ele, b, lo_b, hi_b, neg_b) && !neg_b) - return lo_b <= lo_a && hi_a <= hi_b; - } - else if (!sign_a && sign_b) { - // a → ¬b: range_a ∩ range_b = ∅ - if (u().is_char_const_range(m_ele, a, lo_a, hi_a, neg_a) && !neg_a && - u().is_char_const_range(m_ele, b, lo_b, hi_b, neg_b) && !neg_b) - return hi_a < lo_b || hi_b < lo_a; - } - else if (sign_a && !sign_b) { - // ¬a → b: complement of range_a ⊆ range_b - if (u().is_char_const_range(m_ele, a, lo_a, hi_a, neg_a) && !neg_a && - u().is_char_const_range(m_ele, b, lo_b, hi_b, neg_b) && !neg_b) - return lo_b == 0 && hi_b >= u().max_char(); - } - - return false; - } - - bool derive::pred_implies(expr* a, expr* b) { - bool sign_a = m.is_not(a, a); - bool sign_b = m.is_not(b, b); - return pred_implies(sign_a, a, sign_b, b); - } - - expr_ref derive::mk_xor(expr *a, expr *b) { - return mk_core(OP_RE_XOR, a, b); - } - - expr_ref derive::mk_xor_core(expr *a, expr *b) { - - return m_re.mk_xor0(a, b); - } - - expr_ref derive::mk_core(decl_kind k, expr* a, expr* b) { - expr *pe = get_path_expr(); - expr *cached = nullptr; - auto& cache = k == OP_RE_UNION ? union_cache() : k == OP_RE_INTERSECT ? inter_cache() : xor_cache(); - if (cache.find(a, b, pe, cached)) - return expr_ref(cached, m); - expr_ref result(m); - // ITE handling with path pruning - auto inter_op = [&](expr *x, expr *y) { return mk_inter(x, y); }; - auto union_op = [&](expr *x, expr *y) { return mk_union(x, y); }; - auto xor_op = [&](expr *x, expr *y) { return mk_xor(x, y); }; - switch (k) { - case OP_RE_UNION: - if (m_derivative_kind == derivative_kind::brzozowski_t) - result = hoist_ite(a, b, union_op); - if (!result) - result = mk_union_core(a, b); - break; - case OP_RE_INTERSECT: - result = hoist_ite(a, b, inter_op); - if (!result) - result = mk_inter_core(a, b); - break; - case OP_RE_XOR: - result = hoist_ite(a, b, xor_op); - if (!result) - result = mk_xor_core(a, b); - break; - default: - UNREACHABLE(); - break; - } - // Store in cache - cache.insert(a, b, pe, result); - m_trail.push_back(a); - m_trail.push_back(b); - m_trail.push_back(pe); - m_trail.push_back(result); - return result; - } - - expr_ref derive::mk_union(expr* a, expr* b) { - return mk_core(OP_RE_UNION, a, b); - } - - // Lightweight structural subsumption: checks if L(a) ⊆ L(b) - bool derive::is_subset(expr* a, expr* b) { - return m_re.is_subset(a, b); - } - - bool derive::are_complements(expr* a, expr* b) { - expr* c = nullptr; - if (re().is_complement(a, c) && c == b) return true; - if (re().is_complement(b, c) && c == a) return true; - return false; - } - - expr_ref derive::mk_union_core(expr* a, expr* b) { - - // Identity: none ∪ R = R (none is the unit of union) - // Idempotence: R ∪ R = R - // Absorption: Σ* ∪ R = Σ* - // Without these the derivative of an intersection accumulates - // un-simplified unions such as union(inter, union(none, none)), - // producing many syntactically distinct but semantically equal - // states. That defeats state dedup in the emptiness/bisim closure - // and makes contains-pattern intersections blow up. - if (re().is_empty(a)) return expr_ref(b, m); - if (re().is_empty(b)) return expr_ref(a, m); - if (a == b) return expr_ref(a, m); - if (re().is_full_seq(a) || re().is_full_seq(b)) - return expr_ref(re().mk_full_seq(a->get_sort()), m); - - // Prefix factoring: a·x ∪ a·y = a·(x ∪ y) - expr *a1, *a2, *b1, *b2; - if (re().is_concat(a, a1, a2) && re().is_concat(b, b1, b2) && a1 == b1) { - expr_ref tail = mk_union(a2, b2); - return mk_deriv_concat(a1, tail); - } - - // Subsumption: L(a) ⊆ L(b) ⇒ a ∪ b = b (and symmetrically). - // This collapses semantically-equal union states that arise in - // antimirov intersection derivatives, which is essential to keep the - // emptiness/bisim worklist from blowing up on contains-patterns. - if (is_subset(a, b)) return expr_ref(b, m); - if (is_subset(b, a)) return expr_ref(a, m); - - return m_re.mk_union(a, b); - } - - expr_ref derive::mk_inter(expr* a, expr* b) { - return mk_core(OP_RE_INTERSECT, a, b); - } - - expr_ref derive::mk_inter_core(expr* a, expr* b) { - - // Subsumption covers: a==b, empty(a), empty(b), full(a), full(b), etc. - if (is_subset(a, b)) return expr_ref(a, m); - if (is_subset(b, a)) return expr_ref(b, m); - - // Complement absorption: r ∩ ~r = ∅ - expr *c = nullptr, *d = nullptr; - if (re().is_complement(a, c) && c == b) - return expr_ref(re().mk_empty(a->get_sort()), m); - if (re().is_complement(b, c) && c == a) - return expr_ref(re().mk_empty(a->get_sort()), m); - if (re().is_complement(a, c) && re().is_complement(b, d)) - return expr_ref(re().mk_complement(mk_union_core(c, d)), m); - - - - // Distribution of intersection over union: (x ∪ y) ∩ b → (x ∩ b) ∪ (y ∩ b). - // This is essential for keeping the symbolic derivative a proper - // *transition regex*: with antimirov derivatives the operands of an - // intersection are unions of ITE-branches. If the intersection is left - // *above* the union/ITE structure, the solver's get_derivative_targets - // (which only descends through top-level ITE and union, not - // intersection) cannot decompose the transition regex into ground - // target states. The result is a handful of gigantic states still - // carrying the (:var 0) character conditions, on which the solver's - // cofactor/accept enumeration explodes. Distributing here pushes the - // intersection into the ITE leaves so states stay small and ground. - expr *u1 = nullptr, *u2 = nullptr; - if (re().is_union(a, u1, u2)) - return mk_union(mk_inter(u1, b), mk_inter(u2, b)); - if (re().is_union(b, u1, u2)) - return mk_union(mk_inter(a, u1), mk_inter(a, u2)); - - // Base case: build raw intersection - return m_re.mk_inter(a, b); - } - - - expr_ref derive::mk_concat(expr* a, expr* b) { - sort* seq_s = nullptr, * ele_s = nullptr; - VERIFY(m_util.is_re(a, seq_s)); - VERIFY(u().is_seq(seq_s, ele_s)); - if (re().is_empty(a)) return expr_ref(a, m); - if (re().is_empty(b)) return expr_ref(b, m); - if (re().is_epsilon(a)) return expr_ref(b, m); - if (re().is_epsilon(b)) return expr_ref(a, m); - if (re().is_full_seq(a) && re().is_full_seq(b)) - return expr_ref(a, m); - if (re().is_full_char(a) && re().is_full_seq(b)) - return expr_ref(re().mk_plus(re().mk_full_char(a->get_sort())), m); - if (re().is_full_seq(a) && re().is_full_char(b)) - return expr_ref(re().mk_plus(re().mk_full_char(a->get_sort())), m); - - // to_re(s1) · to_re(s2) → to_re(s1 ++ s2) - expr* s1 = nullptr, * s2 = nullptr; - if (re().is_to_re(a, s1) && re().is_to_re(b, s2)) - return expr_ref(re().mk_to_re(u().str.mk_concat(s1, s2)), m); - - // r* · r* → r* - - expr* a1 = nullptr, *a2 = nullptr, * b1 = nullptr; - - if (re().is_star(a, a1) && re().is_star(b, b1) && a1 == b1) - return expr_ref(a, m); - - // Right-associate: (a · b) · c → a · (b · c) - - if (re().is_concat(a, a1, a2)) - return mk_concat(a1, mk_concat(a2, b)); - - return expr_ref(re().mk_concat(a, b), m); - } - - expr_ref derive::mk_complement(expr* a) { - // Check path-aware op cache - expr* pe = get_path_expr(); - expr* cached = nullptr; - if (complement_cache().find(a, pe, cached)) - return expr_ref(cached, m); - - expr_ref result = mk_complement_core(a); - - // Store in cache - complement_cache().insert(a, pe, result); - m_trail.push_back(a); - m_trail.push_back(pe); - m_trail.push_back(result); - return result; - } - - expr_ref derive::mk_complement_core(expr* a) { - // ~~r → r - expr* r = nullptr; - if (re().is_complement(a, r)) - return expr_ref(r, m); - // ~∅ → Σ* - if (re().is_empty(a)) - return expr_ref(re().mk_full_seq(a->get_sort()), m); - // ~Σ* → ∅ - if (re().is_full_seq(a)) - return expr_ref(re().mk_empty(a->get_sort()), m); - - // Push through ITE with path pruning: ~(ite(c, t, e)) → ite(c, ~t, ~e) - expr* c, * t, * e; - if (m.is_ite(a, c, t, e)) { - auto comp_op = [&](expr* x) { return mk_complement(x); }; - expr_ref r = apply_ite(c, t, e, comp_op); - if (r) return r; - return expr_ref(re().mk_full_seq(a->get_sort()), m); - } - - // ~ε → .+ - sort* s = nullptr; - expr* r1 = nullptr; - if (re().is_to_re(a, r1) && u().str.is_empty(r1)) { - VERIFY(m_util.is_re(a, s)); - return expr_ref(re().mk_plus(re().mk_full_char(a->get_sort())), m); - } - - return expr_ref(re().mk_complement(a), m); - } - - expr_ref derive::mk_ite(expr* c, expr* t, expr* e) { - if (m.is_true(c) || t == e) - return expr_ref(t, m); - if (m.is_false(c)) - return expr_ref(e, m); - // Use path-aware condition evaluation - lbool cond_val = eval_path_cond(c); - if (cond_val == l_true) return expr_ref(t, m); - if (cond_val == l_false) return expr_ref(e, m); - return expr_ref(m.mk_ite(c, t, e), m); - } - - // ------------------------------------------------------- - // Distribute concat through ITE/union structure of derivative - // ------------------------------------------------------- - - expr_ref derive::mk_deriv_concat(expr* d, expr* tail) { - // Check op cache - expr* cached = nullptr; - if (concat_cache().find(d, tail, cached)) - return expr_ref(cached, m); - - expr_ref result = mk_deriv_concat_core(d, tail); - - // Store in cache - concat_cache().insert(d, tail, result); - m_trail.push_back(d); - m_trail.push_back(tail); - m_trail.push_back(result); - return result; - } - - expr_ref derive::mk_deriv_concat_core(expr* d, expr* tail) { - expr_ref _d(d, m), _tail(tail, m); - expr* c, * t, * e; - - if (re().is_empty(d)) - return expr_ref(d, m); - if (re().is_epsilon(d)) - return expr_ref(tail, m); - - if (m.is_ite(d, c, t, e)) { - expr_ref then_r = mk_deriv_concat(t, tail); - expr_ref else_r = mk_deriv_concat(e, tail); - return mk_ite(c, then_r, else_r); - } - - // (t ∪ e) · tail → (t · tail) ∪ (e · tail) - if (m_derivative_kind == derivative_kind::antimirov_t && re().is_union(d, t, e)) { - expr_ref left = mk_deriv_concat(t, tail); - expr_ref right = mk_deriv_concat(e, tail); - return mk_union(left, right); - } - - return mk_concat(d, tail); - } - - // ------------------------------------------------------- - // Path management for inline pruning - // ------------------------------------------------------- - - lbool derive::push(expr* c, bool sign) { - // Check if (c, sign) is already determined by the path - lbool cv = eval_path_cond(c); - if (cv == l_true && !sign) return l_true; // c implied true, push(c,false) is redundant - if (cv == l_false && sign) return l_true; // c implied false, push(c,true) is redundant - if (cv == l_true && sign) return l_false; // c implied true, push(c,true) contradicts - if (cv == l_false && !sign) return l_false; // c implied false, push(c,false) contradicts - - // Save current state - unsigned saved_path_sz = m_path.size(); - unsigned saved_intervals_sz = m_intervals.size(); - unsigned saved_intervals_start = m_intervals_start; - expr* saved_path_expr = m_path_expr; - - // Push atoms onto path and check for contradiction or implication - lbool result = push_path_atoms(c, sign); - if (result != l_undef) { - m_path.shrink(saved_path_sz); - m_intervals.shrink(saved_intervals_sz); - m_intervals_start = saved_intervals_start; - return result; - } - - // Update intervals - result = push_intervals_impl(c, sign); - if (result != l_undef) { - m_path.shrink(saved_path_sz); - m_intervals.shrink(saved_intervals_sz); - m_intervals_start = saved_intervals_start; - return result; - } - - // Update path expression - expr* atom = sign ? m.mk_not(c) : c; - m_path_expr = m.mk_and(m_path_expr, atom); - m_trail.push_back(m_path_expr); - - // Commit: save state for pop() - m_path_stack.push_back({ saved_path_sz, saved_intervals_sz, saved_intervals_start, saved_path_expr }); - return l_undef; - } - - void derive::pop() { - SASSERT(!m_path_stack.empty()); - auto const& saved = m_path_stack.back(); - m_path.shrink(saved.path_sz); - m_intervals.shrink(saved.intervals_sz); - m_intervals_start = saved.intervals_start; - m_path_expr = saved.path_expr; - m_path_stack.pop_back(); - } - - // Binary apply_ite: hoist ite(c, t, e) op r with path pruning - expr_ref derive::apply_ite(expr* c, expr* t, expr* e, expr* r, std::function apply_op) { - expr_ref then_br(m), else_br(m); - switch (push(c, false)) { - case l_true: return apply_op(t, r); - case l_undef: then_br = apply_op(t, r); pop(); break; - case l_false: break; - } - switch (push(c, true)) { - case l_true: return apply_op(e, r); - case l_undef: else_br = apply_op(e, r); pop(); break; - case l_false: break; - } - if (then_br && else_br) return mk_ite(c, then_br, else_br); - if (then_br) return then_br; - if (else_br) return else_br; - return expr_ref(nullptr, m); - } - - // Same-condition merge: ite(c, t1, e1) op ite(c, t2, e2) → ite(c, t1 op t2, e1 op e2) - expr_ref derive::apply_ite(expr* c, expr* t1, expr* e1, expr* t2, expr* e2, std::function apply_op) { - expr_ref then_br(m), else_br(m); - switch (push(c, false)) { - case l_true: return apply_op(t1, t2); - case l_undef: then_br = apply_op(t1, t2); pop(); break; - case l_false: break; - } - switch (push(c, true)) { - case l_true: return apply_op(e1, e2); - case l_undef: else_br = apply_op(e1, e2); pop(); break; - case l_false: break; - } - if (then_br && else_br) return mk_ite(c, then_br, else_br); - if (then_br) return then_br; - if (else_br) return else_br; - return expr_ref(nullptr, m); - } - - // Unary apply_ite: hoist ite(c, t, e) through unary op with path pruning - expr_ref derive::apply_ite(expr* c, expr* t, expr* e, std::function apply_op) { - expr_ref then_br(m), else_br(m); - switch (push(c, false)) { - case l_true: return apply_op(t); - case l_undef: then_br = apply_op(t); pop(); break; - case l_false: break; - } - switch (push(c, true)) { - case l_true: return apply_op(e); - case l_undef: else_br = apply_op(e); pop(); break; - case l_false: break; - } - if (then_br && else_br) return mk_ite(c, then_br, else_br); - if (then_br) return then_br; - if (else_br) return else_br; - return expr_ref(nullptr, m); - } - - // Common ITE dispatch for binary ops (union/inter). - // Returns nullptr if neither a nor b is ITE. - expr_ref derive::hoist_ite(expr* a, expr* b, std::function apply_op) { - expr *c1, *t1, *e1, *c2, *t2, *e2; - if (m.is_ite(a, c1, t1, e1) && m.is_ite(b, c2, t2, e2) && c1->get_id() > c2->get_id()) - std::swap(a, b); - if (m.is_ite(a, c1, t1, e1) && m.is_ite(b, c2, t2, e2)) { - expr_ref r(m); - if (c1 == c2) - r = apply_ite(c1, t1, e1, t2, e2, apply_op); - else - r = apply_ite(c1, t1, e1, b, apply_op); - if (r) return r; - return expr_ref(re().mk_empty(a->get_sort()), m); - } - // Single-ITE hoisting: must always recurse to maintain path-aware - // soundness — falling back to a non-path-aware structural rewrite - // here would bake unreachable branches into the result tree. - if (m.is_ite(a, c1, t1, e1)) { - expr_ref r = apply_ite(c1, t1, e1, b, apply_op); - if (r) return r; - return expr_ref(re().mk_empty(a->get_sort()), m); - } - if (m.is_ite(b, c2, t2, e2)) { - expr_ref r = apply_ite(c2, t2, e2, a, apply_op); - if (r) return r; - return expr_ref(re().mk_empty(a->get_sort()), m); - } - return expr_ref(nullptr, m); - } - - // Push signed atoms onto m_path. Returns l_true if implied, l_false if contradicted, l_undef if pushed. - lbool derive::push_path_atoms(expr* c, bool sign) { - // Check if (c, sign) is already determined by the path - for (auto const& [cond, csign] : m_path) { - if (c == cond) - return csign == sign ? l_true : l_false; - expr* lhs1 = nullptr, * rhs1 = nullptr, * lhs2 = nullptr, * rhs2 = nullptr; - // x = v, v != w |-> x != w - if (!csign && m.is_eq(cond, lhs1, rhs1) && m.is_eq(c, lhs2, rhs2)) { - if (m.is_value(lhs1)) std::swap(lhs1, rhs1); - if (m.is_value(lhs2)) std::swap(lhs2, rhs2); - if (lhs1 == lhs2 && m.are_distinct(rhs1, rhs2)) - return sign ? l_true : l_false; - } - } - - // Composite: conjunction assumed true, or disjunction assumed false - if ((!sign && m.is_and(c)) || (sign && m.is_or(c))) { - bool all_implied = true; - for (expr* arg : *to_app(c)) { - lbool r = push_path_atoms(arg, sign); - if (r == l_false) return l_false; - if (r == l_undef) all_implied = false; - } - return all_implied ? l_true : l_undef; - } - - // Atomic: push onto path - m_path.push_back({ c, sign }); - return l_undef; - } - - // Update m_intervals based on the condition. Returns l_true if implied, l_false if inconsistent, l_undef if pushed. - // Operates on the active suffix m_intervals[m_intervals_start..end]. - // On modification, appends new intervals and updates m_intervals_start. - lbool derive::push_intervals_impl(expr* c, bool sign) { - unsigned lo = 0, hi = 0; - bool negated = false; - if (m_util.is_char_const_range(m_ele, c, lo, hi, negated)) { - bool effective_neg = (negated != sign); - if (!effective_neg) { - if (lo <= hi) { - // Check if current intervals already imply [lo,hi] - bool already_subset = true; - for (unsigned i = m_intervals_start; i < m_intervals.size(); ++i) { - if (m_intervals[i].first < lo || m_intervals[i].second > hi) { already_subset = false; break; } - } - if (already_subset) return l_true; - intersect_intervals(lo, hi); - } else { - // lo > hi means empty range — contradiction - return l_false; - } - } else { - if (lo <= hi) { - // Check if current intervals already exclude [lo,hi] - bool already_excluded = true; - for (unsigned i = m_intervals_start; i < m_intervals.size(); ++i) { - if (m_intervals[i].first <= hi && m_intervals[i].second >= lo) { already_excluded = false; break; } - } - if (already_excluded) return l_true; - exclude_interval(lo, hi); - } - } - } else if ((!sign && m.is_and(c)) || (sign && m.is_or(c))) { - bool all_implied = true; - for (expr* arg : *to_app(c)) { - lbool r = push_intervals_impl(arg, sign); - if (r == l_false) return l_false; - if (r == l_undef) all_implied = false; - } - unsigned n = m_intervals.size() - m_intervals_start; - return all_implied ? l_true : (n == 0 ? l_false : l_undef); - } - unsigned n = m_intervals.size() - m_intervals_start; - return n == 0 ? l_false : l_undef; - } - - // Evaluate a condition against the current path and intervals. - lbool derive::eval_path_cond(expr* c) { - // First try static evaluation (concrete m_ele, tautologies) - lbool v = eval_cond(c); - if (v != l_undef) return v; - - // Check against path atoms - for (auto const& [cond, sign] : m_path) { - if (c == cond) - return sign ? l_false : l_true; - } - - // Check against intervals - v = eval_range_cond(c); - if (v != l_undef) return v; - - // Check pred_implies from path atoms - for (auto const& [cond, sign] : m_path) { - if (pred_implies(sign, cond, false, c)) - return l_true; - if (pred_implies(sign, cond, true, c)) - return l_false; - } - - return l_undef; - } - - // ------------------------------------------------------- - // Condition evaluation helpers - // ------------------------------------------------------- - - lbool derive::eval_cond(expr* cond) { - expr* e1 = nullptr; - - if (m.is_true(cond)) return l_true; - if (m.is_false(cond)) return l_false; - - // Use is_char_const_range to evaluate conditions involving m_ele - unsigned lo = 0, hi = 0, ele_val = 0; - bool negated = false; - if (m_util.is_char_const_range(m_ele, cond, lo, hi, negated) && u().is_const_char(m_ele, ele_val)) { - bool in_range = (lo <= ele_val && ele_val <= hi); - return (in_range != negated) ? l_true : l_false; - } - - // Handle self-equality and constant comparisons not involving m_ele - expr* lhs = nullptr, * rhs = nullptr; - if (m.is_eq(cond, lhs, rhs) && lhs == rhs) - return l_true; - - unsigned vl = 0, vr = 0; - if (u().is_char_le(cond, lhs, rhs)) { - if (u().is_const_char(lhs, vl) && u().is_const_char(rhs, vr)) - return vl <= vr ? l_true : l_false; - if (u().is_const_char(lhs, vl) && vl == 0) - return l_true; - if (u().is_const_char(rhs, vr) && vr == u().max_char()) - return l_true; - } - - // not(e1) - if (m.is_not(cond, e1)) - return ~eval_cond(e1); - - // and(...) - if (m.is_and(cond)) { - lbool r = l_true; - for (expr* arg : *to_app(cond)) { - lbool v = eval_cond(arg); - if (v == l_false) return l_false; - if (v == l_undef) r = l_undef; - } - return r; - } - - // or(...) - if (m.is_or(cond)) { - lbool r = l_false; - for (expr* arg : *to_app(cond)) { - lbool v = eval_cond(arg); - if (v == l_true) return l_true; - if (v == l_undef) r = l_undef; - } - return r; - } - - return l_undef; - } - - lbool derive::eval_range_cond(expr* c) { - unsigned n = m_intervals.size() - m_intervals_start; - if (n == 0) - return l_false; - unsigned lo = 0, hi = 0; - bool negated = false; - if (!m_util.is_char_const_range(m_ele, c, lo, hi, negated)) - return l_undef; - if (lo > hi) { - return negated ? l_true : l_false; - } - // Check if [lo, hi] overlaps with intervals and/or contains all intervals - bool any_overlap = false; - bool all_contained = true; - for (unsigned i = m_intervals_start; i < m_intervals.size(); ++i) { - auto [r_lo, r_hi] = m_intervals[i]; - if (std::max(r_lo, lo) <= std::min(r_hi, hi)) - any_overlap = true; - if (r_lo < lo || r_hi > hi) - all_contained = false; - } - if (!negated) { - if (!any_overlap) return l_false; - if (all_contained) return l_true; - } else { - if (all_contained) return l_false; - if (!any_overlap) return l_true; - } - return l_undef; - } - - // Intersect the active suffix m_intervals[m_intervals_start..end] with [lo, hi] - void derive::intersect_intervals(unsigned lo, unsigned hi) { - // Copy active suffix to end, update start, then filter - unsigned old_sz = m_intervals.size(); - for (unsigned i = m_intervals_start; i < old_sz; ++i) { - auto e = m_intervals[i]; - m_intervals.push_back(e); - } - m_intervals_start = old_sz; - // Filter in-place within new suffix: drop intervals disjoint from [lo,hi], - // keep the intersection for overlapping ones. - unsigned j = m_intervals_start; - for (unsigned i = m_intervals_start; i < m_intervals.size(); ++i) { - auto [lo1, hi1] = m_intervals[i]; - if (hi < lo1 || lo > hi1) - continue; // disjoint with this interval — drop it - m_intervals[j++] = {std::max(lo1, lo), std::min(hi1, hi)}; - } - m_intervals.shrink(j); - } - - // Exclude [lo, hi] from the active suffix m_intervals[m_intervals_start..end] - void derive::exclude_interval(unsigned lo, unsigned hi) { - unsigned max_char = u().max_char(); - if (lo == 0 && hi >= max_char) { m_intervals_start = m_intervals.size(); return; } - if (lo == 0) { intersect_intervals(hi + 1, max_char); return; } - if (hi >= max_char) { intersect_intervals(0, lo - 1); return; } - // Each interval [ilo, ihi] minus [lo, hi] → up to 2 pieces - // Append new results past the end, then move start - unsigned old_start = m_intervals_start; - unsigned old_sz = m_intervals.size(); - for (unsigned i = old_start; i < old_sz; ++i) { - auto [ilo, ihi] = m_intervals[i]; - if (ihi < lo || ilo > hi) { - auto e = m_intervals[i]; - m_intervals.push_back(e); - } else { - if (ilo < lo) - m_intervals.push_back({ilo, lo - 1}); - if (ihi > hi) - m_intervals.push_back({hi + 1, ihi}); - } - } - m_intervals_start = old_sz; - } - - // ------------------------------------------------------- - // Cofactor enumeration over a transition regex - // ------------------------------------------------------- - - expr_ref derive::clean_leaf(expr* r) { - expr* a = nullptr, * b = nullptr; - if (re().is_union(r, a, b)) - return mk_union(clean_leaf(a), clean_leaf(b)); - if (re().is_intersection(r, a, b)) - return mk_inter(clean_leaf(a), clean_leaf(b)); - return expr_ref(r, m); - } - - void derive::get_cofactors_rec(expr* r, expr_ref_pair_vector& result) { - // Hoist the (first) if-then-else condition to the top of r, splitting it - // into the equivalent ite(c, th, el); when r contains no ite it is a - // leaf of the transition regex. - expr_ref c(m), th(m), el(m); - if (!m_br.decompose_ite(r, c, th, el)) { - // Re-normalize the leaf: decompose_ite substitutes ITE branches - // structurally so the leaf may carry un-simplified union(_, none) - // / inter(_, none) nodes. Cleaning them keeps semantically equal - // states syntactically identical, which is essential for state - // dedup in the emptiness/bisim closure. - expr_ref cr = clean_leaf(r); - if (!re().is_empty(cr)) - result.push_back(get_path_expr(), cr); - return; - } - // Positive branch: c holds. - switch (push(c, false)) { - case l_true: get_cofactors_rec(th, result); break; - case l_undef: get_cofactors_rec(th, result); pop(); break; - case l_false: break; - } - // Negative branch: c does not hold. - switch (push(c, true)) { - case l_true: get_cofactors_rec(el, result); break; - case l_undef: get_cofactors_rec(el, result); pop(); break; - case l_false: break; - } - } - - void derive::get_cofactors(expr* ele, expr* r, expr_ref_pair_vector& result) { - SASSERT(m_util.is_re(r)); - if (ele != m_ele) - reset_op_caches(); - m_ele = ele; - m_trail.push_back(ele); - m_trail.push_back(r); - // Initialize a fresh path/interval context for this traversal. - m_path.reset(); - m_path_stack.reset(); - m_intervals.reset(); - m_intervals.push_back({0u, u().max_char()}); - m_intervals_start = 0; - m_path_expr = m.mk_true(); - get_cofactors_rec(r, result); - } - - void derive::derivative_cofactors(expr* r, expr_ref_pair_vector& result) { - // Compute the symbolic derivative wrt the canonical variable - // (:var 0); operator() sets m_ele to that variable. - expr_ref d = (*this)(derivative_kind::brzozowski_t, r); - // Enumerate the reachable, fully ITE-hoisted leaves of the - // transition regex. get_cofactors uses the SAME m_ele set above, - // so the (:var 0) conditions in d are matched and pruned. - get_cofactors(m_ele, d, result); - } - -} - diff --git a/src/ast/rewriter/seq_derive.h b/src/ast/rewriter/seq_derive.h deleted file mode 100644 index d3288dccf8..0000000000 --- a/src/ast/rewriter/seq_derive.h +++ /dev/null @@ -1,265 +0,0 @@ -/*++ -Copyright (c) 2026 Microsoft Corporation - -Module Name: - - seq_derive.h - -Abstract: - - Symbolic derivative computation for regular expressions. - Produces an ITE-tree (transition regex) representation where - the free variable is de Bruijn index 0 representing the input character. - - Based on the theory of symbolic derivatives and transition regexes: - - Veanes et al., "On Symbolic Derivatives and Transition Regexes" (LPAR 2024) - - Varatalu, Veanes, Ernits, "RE#" (POPL 2025) - - Stanford, Veanes, Bjørner, "Symbolic Boolean Derivatives" (PLDI 2021) - -Authors: - - Nikolaj Bjorner (nbjorner) 2025-06-03 - ---*/ - -#pragma once - -#include "ast/seq_decl_plugin.h" -#include "ast/arith_decl_plugin.h" -#include "ast/array_decl_plugin.h" -#include "ast/rewriter/bool_rewriter.h" -#include "util/obj_pair_hashtable.h" -#include "util/obj_triple_hashtable.h" -#include - -class seq_rewriter; - -namespace seq { - - enum class derivative_kind { antimirov_t, brzozowski_t }; - /** - * Symbolic derivative engine for regular expressions. - * - * Given a regex r, operator()(r) computes a symbolic derivative δ(r) - * represented as an ITE-tree over character predicates (using de Bruijn - * variable 0 for the character). Evaluating the ITE-tree for a concrete - * character 'a' yields the classical Brzozowski derivative δ_a(r). - * - * The ITE-tree structure implicitly defines minterms (equivalence classes - * of characters indistinguishable by the regex). - * - * Key properties: - * - Results are memoized for termination on cyclic derivative graphs - * - Union/intersection operands are sorted for ACI canonicalization - * - Depth-bounded to prevent stack overflow - */ - class derive { - ast_manager& m; - seq_util m_util; - arith_util m_autil; - bool_rewriter m_br; - seq_rewriter& m_re; - - // Cache: maps (ele, regex) pair to its derivative - obj_pair_map m_acache, m_bcache; - obj_pair_map m_atop_cache, m_btop_cache; // post-simplify cache - expr_ref_vector m_trail; // pin cached results - - // Op cache for ITE-hoisting operations (union, inter, concat, complement) - // Path-aware caches: key is (a, b, path_expr) for binary ops, (a, path_expr) for complement - obj_triple_map m_aunion_cache, m_bunion_cache, m_ainter_cache, m_binter_cache, m_axor_cache, m_bxor_cache; - obj_pair_map m_aconcat_cache, m_bconcat_cache; - obj_pair_map m_acomplement_cache, m_bcomplement_cache; - - // Depth limiting - unsigned m_depth { 0 }; - static const unsigned m_max_depth = 512; - - seq_util::rex& re() { return m_util.re; } - seq_util& u() { return m_util; } - - derivative_kind m_derivative_kind = derivative_kind::antimirov_t; - - // The element (character) for the current derivative computation - expr_ref m_ele; - - // Path state for inline pruning during mk_inter/mk_union/mk_complement - using intervals_t = svector>; - - // Path: vector of signed atoms - svector> m_path; - // Intervals: feasible character ranges under current path (append-only) - intervals_t m_intervals; - unsigned m_intervals_start { 0 }; - // Stack of saved states for push/pop - struct path_save { unsigned path_sz; unsigned intervals_sz; unsigned intervals_start; expr* path_expr; }; - svector m_path_stack; - // Boolean expression encoding of current path (for cache keys) - expr_ref m_path_expr; - - // Path interface - lbool push(expr* c, bool sign); // l_true: implied, l_undef: pushed (must pop), l_false: contradicts - void pop(); // restore state to matching push - expr* get_path_expr() { return m_path_expr; } - - obj_pair_map &cache() { - return m_derivative_kind == derivative_kind::antimirov_t ? m_acache : m_bcache; - } - - obj_pair_map &top_cache() { - return m_derivative_kind == derivative_kind::antimirov_t ? m_atop_cache : m_btop_cache; - } - - obj_triple_map &union_cache() { - return m_derivative_kind == derivative_kind::antimirov_t ? m_aunion_cache : m_bunion_cache; - } - - obj_triple_map &inter_cache() { - return m_derivative_kind == derivative_kind::antimirov_t ? m_ainter_cache : m_binter_cache; - } - - obj_triple_map &xor_cache() { - return m_derivative_kind == derivative_kind::antimirov_t ? m_axor_cache : m_bxor_cache; - } - - obj_pair_map &concat_cache() { - return m_derivative_kind == derivative_kind::antimirov_t ? m_aconcat_cache : m_bconcat_cache; - } - - obj_pair_map &complement_cache() { - return m_derivative_kind == derivative_kind::antimirov_t ? m_acomplement_cache : m_bcomplement_cache; - } - - // Hoist ITE: apply_op through ite(c, t, e) with path pruning - expr_ref apply_ite(expr* c, expr* t, expr* e, expr* r, std::function apply_op); - expr_ref apply_ite(expr* c, expr* t1, expr* e1, expr* t2, expr* e2, std::function apply_op); - expr_ref apply_ite(expr* c, expr* t, expr* e, std::function apply_op); - // Common ITE dispatch for binary ops (union/inter) - expr_ref hoist_ite(expr* a, expr* b, std::function apply_op); - - // Evaluate a condition against the current path/intervals - lbool eval_path_cond(expr* c); - - // Internal helpers for push - lbool push_path_atoms(expr* c, bool sign); - lbool push_intervals_impl(expr* c, bool sign); - - // Core derivative computation - expr_ref derive_rec(expr* r); - expr_ref derive_core(expr* r); - - // Helpers for specific regex constructs - expr_ref derive_to_re(expr* s, sort* seq_sort); - expr_ref derive_range(expr* lo, expr* hi, sort* seq_sort); - expr_ref derive_of_pred(expr* pred, sort* seq_sort); - - // Nullable check: returns a Boolean expression - expr_ref is_nullable(expr* r); - expr_ref is_nullable_symbolic_regex(expr* r, sort* seq_sort); - - // Smart constructors with path-aware simplification and ACI canonicalization - expr_ref mk_union(expr* a, expr* b); - bool are_complements(expr* a, expr* b); - unsigned union_id(expr* e); // complement-aware ID for sorting - bool is_subset(expr* a, expr* b); - expr_ref mk_union_core(expr* a, expr* b); - expr_ref mk_inter(expr* a, expr* b); - expr_ref mk_inter_core(expr* a, expr* b); - expr_ref mk_concat(expr* a, expr* b); - expr_ref mk_complement(expr* a); - expr_ref mk_complement_core(expr* a); - expr_ref mk_xor(expr *a, expr *b); - expr_ref mk_xor_core(expr *a, expr *b); - expr_ref mk_core(decl_kind k, expr* a, expr* b); - expr_ref mk_ite(expr* c, expr* t, expr* e); - - // Distribute concatenation through ITE/union in derivative - expr_ref mk_deriv_concat(expr* d, expr* tail); - expr_ref mk_deriv_concat_core(expr* d, expr* tail); - - // Extract head character and tail from a sequence expression - bool get_head_tail(expr* s1, expr* s2, expr_ref& hd, expr_ref& tl); - - // Predicate implication for character range conditions. - bool pred_implies(bool sign_a, expr* a, bool sign_b, expr* b); - bool pred_implies(expr* a, expr* b); - - // Normalize reverse(r) - expr_ref mk_regex_reverse(expr* r); - - // Condition evaluation helpers - lbool eval_cond(expr* cond); - lbool eval_range_cond(expr* c); - void intersect_intervals(unsigned lo, unsigned hi); - void exclude_interval(unsigned lo, unsigned hi); - - // Cofactor enumeration over a transition regex (ITE-tree). - void get_cofactors_rec(expr* r, expr_ref_pair_vector& result); - - // Re-apply union/intersection simplifications bottom-up to a cofactor - // leaf. decompose_ite substitutes ITE branch values structurally - // (no simplification), so leaves can contain un-normalized nodes such - // as union(R, none) or inter(R, none); this rebuilds them through - // mk_union/mk_inter so equal states share a canonical form. - expr_ref clean_leaf(expr* r); - - sort* re_sort(expr* r) { return r->get_sort(); } - sort* seq_sort(expr* r) { sort* s = nullptr; m_util.is_re(r, s); return s; } - sort* ele_sort(expr* r) { sort* s = seq_sort(r); sort* e = nullptr; m_util.is_seq(s, e); return e; } - - void reset(); - void reset_op_caches(); - - public: - derive(ast_manager& m, seq_rewriter& re); - - /** - * Compute the derivative of regex r with respect to element ele. - * When ele is a de Bruijn variable, produces a symbolic ITE-tree. - * When ele is a concrete character, produces the concrete derivative. - */ - expr_ref operator()(derivative_kind k, expr* ele, expr* r); - - /** - * Convenience: symbolic derivative using de Bruijn var 0. - */ - expr_ref operator()(derivative_kind k, expr* r); - - /** - * Nullable check: returns a Boolean expression that is true iff r accepts the empty string. - */ - expr_ref nullable(expr* r) { return is_nullable(r); } - - /** - * Enumerate the cofactors (min-terms) of a transition regex r taken with - * respect to element ele. r is an ITE-tree over character predicates on - * ele; for every feasible path through the tree this produces a pair - * (path_condition, leaf_regex). Infeasible character-interval - * combinations are pruned using the same path/interval context that the - * derivative engine uses while hoisting ITEs. - */ - void get_cofactors(expr* ele, expr* r, expr_ref_pair_vector& result); - - /** - * Compute the symbolic derivative of r and enumerate its reachable - * leaves in fully ITE-hoisted normal form. - * - * Concretely this returns, for every feasible minterm (character - * class) of δ(r), a pair (path_condition, target_regex). Every - * if-then-else over the input character (including ones that would - * otherwise be buried under a concat/union) is hoisted to the top - * via the same path/interval pruning used by the derivative engine, - * so each target_regex is free of (:var 0) and its nullability is - * always decidable. Unions are kept intact as single leaves (a - * union leaf denotes a single bisimulation state). Infeasible - * minterms are pruned, so all returned leaves are reachable. - * - * This is the entry point the regex_bisim equivalence procedure - * uses: it consumes the target_regex of each pair and ignores the - * (redundant) path condition. - */ - void derivative_cofactors(expr* r, expr_ref_pair_vector& result); - - }; - -} diff --git a/src/ast/rewriter/seq_range_collapse.cpp b/src/ast/rewriter/seq_range_collapse.cpp deleted file mode 100644 index 0c739e0c07..0000000000 --- a/src/ast/rewriter/seq_range_collapse.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/*++ -Copyright (c) 2026 Microsoft Corporation - -Module Name: - - seq_range_collapse.cpp - -Abstract: - - Implementation of regex <-> range_predicate translation for the - boolean-combination-of-ranges fragment. See header for the recognized - grammar and the canonical regex AST emitted by materialization. - -Authors: - - Margus Veanes (veanes) 2026 - ---*/ - -#include "ast/rewriter/seq_range_collapse.h" - -namespace seq { - - bool regex_to_range_predicate(seq_util& u, expr* r, range_predicate& out) { - // The range algebra only models sets of single characters over the - // unsigned character domain [0, max_char]. Guard against any regex - // whose element type is not a sequence of characters (e.g. a regex - // over (Seq Int) or (Seq (Seq Char))): for such regexes the - // re.range/re.union/... matchers below would silently fabricate a - // character-class predicate and change semantics. Reject them up - // front so callers fall back to the generic regex path. - sort* seq_sort = nullptr; - if (!u.is_re(r, seq_sort) || !u.is_string(seq_sort)) - return false; - - unsigned const max_char = u.max_char(); - auto& re = u.re; - - if (re.is_empty(r)) { - out = range_predicate::empty(max_char); - return true; - } - if (re.is_full_char(r)) { - out = range_predicate::top(max_char); - return true; - } - unsigned lo = 0, hi = 0; - expr* lo_e = nullptr; - expr* hi_e = nullptr; - if (re.is_range(r, lo_e, hi_e)) { - auto extract_char = [&](expr* e, unsigned& c) -> bool { - if (u.is_const_char(e, c)) return true; - expr* inner = nullptr; - if (u.str.is_unit(e, inner) && u.is_const_char(inner, c)) return true; - zstring s; - if (u.str.is_string(e, s) && s.length() == 1) { - c = s[0]; - return true; - } - return false; - }; - if (!extract_char(lo_e, lo) || !extract_char(hi_e, hi)) - return false; - // Empty/inverted range [lo > hi] is the empty regex. - if (lo > hi) { - out = range_predicate::empty(max_char); - return true; - } - out = range_predicate::range(lo, hi, max_char); - return true; - } - expr *a = nullptr, *b = nullptr, *c = nullptr; - if (re.is_union(r, a, b)) { - range_predicate pa(max_char), pb(max_char); - if (!regex_to_range_predicate(u, a, pa)) return false; - if (!regex_to_range_predicate(u, b, pb)) return false; - out = pa | pb; - return true; - } - auto mk_diff = [&](expr *a, expr *b) -> bool { - range_predicate pa(max_char), pb(max_char); - if (!regex_to_range_predicate(u, a, pa)) - return false; - if (!regex_to_range_predicate(u, b, pb)) - return false; - out = pa - pb; - return true; - }; - if (re.is_diff(r, a, b)) - return mk_diff(a, b); - - if (re.is_intersection(r, a, b) && re.is_complement(b, c)) - return mk_diff(a, c); - - if (re.is_intersection(r, a, b) && re.is_complement(a, c)) - return mk_diff(b, c); - - if (re.is_intersection(r, a, b)) { - range_predicate pa(max_char), pb(max_char); - if (!regex_to_range_predicate(u, a, pa)) return false; - if (!regex_to_range_predicate(u, b, pb)) return false; - out = pa & pb; - return true; - } - - - // NOTE: re.complement is intentionally NOT handled here. - // re.complement is the SEQUENCE-level complement: its language - // includes the empty string, strings of length >= 2, and any - // length-1 string outside the operand. A character-class - // range_predicate can only describe a set of length-1 strings, - // so collapsing re.complement(R) to ~R (character-level - // complement) would change semantics whenever R is wrapped in - // any sequence-level context (e.g. re.diff at the top level, - // or membership tests). De-Morgan equivalences and the - // special cases re.complement(re.empty) / re.complement(re.full) - // are already handled directly in seq_rewriter::mk_re_complement. - return false; - } - - static expr_ref mk_unit_string_from_char(seq_util& u, unsigned c) { - return expr_ref(u.str.mk_string(zstring(c)), u.get_manager()); - } - - static expr_ref mk_single_range_regex(seq_util& u, unsigned lo, unsigned hi, sort* re_sort) { - ast_manager& m = u.get_manager(); - if (lo == 0 && hi == u.max_char()) - return expr_ref(u.re.mk_full_char(re_sort), m); - // Use the canonical unit-character form (seq.unit (Char N)) for - // range bounds. This matches the shape used elsewhere in - // seq_rewriter and avoids creating duplicate AST nodes with - // different ids for semantically identical ranges. - expr_ref slo(u.str.mk_unit(u.str.mk_char(lo)), m); - expr_ref shi(u.str.mk_unit(u.str.mk_char(hi)), m); - return expr_ref(u.re.mk_range(slo, shi), m); - } - - expr_ref range_predicate_to_regex(seq_util& u, range_predicate const& p, sort* seq_sort) { - ast_manager& m = u.get_manager(); - sort* re_sort = u.re.mk_re(seq_sort); - if (p.is_empty()) - return expr_ref(u.re.mk_empty(re_sort), m); - unsigned const n = p.num_ranges(); - SASSERT(n > 0); - if (n == 1) { - auto [lo, hi] = p[0]; - return mk_single_range_regex(u, lo, hi, re_sort); - } - // Build single-range AST nodes first, then sort by expression id - // so the resulting right-associated union matches the canonical - // id-sorted shape that seq_rewriter::merge_regex_sets expects. - // Without this the merge algorithm produces incorrect unions - // when it has to combine our materialized output with another - // (id-sorted) regex set. - expr_ref_vector ranges(m); - for (unsigned i = 0; i < n; ++i) { - auto [lo, hi] = p[i]; - ranges.push_back(mk_single_range_regex(u, lo, hi, re_sort)); - } - std::sort(ranges.data(), ranges.data() + ranges.size(), - [](expr* a, expr* b) { return a->get_id() < b->get_id(); }); - expr_ref acc(ranges.get(n - 1), m); - for (unsigned i = n - 1; i-- > 0; ) - acc = expr_ref(u.re.mk_union(ranges.get(i), acc), m); - return acc; - } - -} diff --git a/src/ast/rewriter/seq_range_collapse.h b/src/ast/rewriter/seq_range_collapse.h deleted file mode 100644 index 16cd5fd67b..0000000000 --- a/src/ast/rewriter/seq_range_collapse.h +++ /dev/null @@ -1,71 +0,0 @@ -/*++ -Copyright (c) 2026 Microsoft Corporation - -Module Name: - - seq_range_collapse.h - -Abstract: - - Recognize regexes that are boolean combinations of character-class - primitives (re.empty, re.full_char, re.range with concrete chars, - and re.union/inter/comp/diff over translatable arguments), and - materialize a seq::range_predicate back into a canonical regex AST. - - Together with seq_rewriter integration, this lets any boolean - combination of character-class regexes collapse to a canonical - multi-range form, so that equivalent character classes share AST - identity, and downstream consumers (derivative, OneStep, caching) - can short-circuit them as pure range predicates. - -Authors: - - Margus Veanes (veanes) 2026 - ---*/ -#pragma once - -#include "ast/rewriter/seq_range_predicate.h" -#include "ast/seq_decl_plugin.h" - -namespace seq { - - /** - * If r is a boolean combination of character-class regex primitives - * over the unsigned character domain [0, max_char], compute the - * equivalent range_predicate and return true. Otherwise return false - * with out untouched. - * - * Recognized fragment (all character-class-preserving operations): - * re.empty -> empty - * re.full_char_set -> top - * re.range "c_lo" "c_hi" (concrete) -> [c_lo, c_hi] - * re.union r1 r2 -> p1 | p2 - * re.intersection r1 r2 -> p1 & p2 - * re.diff r1 r2 -> p1 - p2 - * - * Notably re.complement is NOT recognized: it is a SEQUENCE-level - * complement (over all of Σ*), not a character-class complement, so - * collapsing it would change semantics whenever the result is used - * in any non-character-class context. Sequence-level rewrites for - * re.complement (double-comp, deMorgan, etc.) are handled directly - * in seq_rewriter::mk_re_complement. - */ - bool regex_to_range_predicate(seq_util& u, expr* r, range_predicate& out); - - /** - * Canonical materialization of p as a regex AST over the given - * sequence sort. Two range_predicates with equal canonical - * representations produce structurally identical regex ASTs: - * - * empty -> re.empty - * top -> re.full_char_set - * single range [lo, hi] -> re.range "lo" "hi" - * multiple ranges -> right-associated re.union of single - * ranges, in increasing order of lo - * (matching the canonical range order - * held by range_predicate). - */ - expr_ref range_predicate_to_regex(seq_util& u, range_predicate const& p, sort* seq_sort); - -} diff --git a/src/ast/rewriter/seq_range_predicate.cpp b/src/ast/rewriter/seq_range_predicate.cpp deleted file mode 100644 index 7bb9eac821..0000000000 --- a/src/ast/rewriter/seq_range_predicate.cpp +++ /dev/null @@ -1,292 +0,0 @@ -/*++ -Copyright (c) 2026 Microsoft Corporation - -Module Name: - - seq_range_predicate.cpp - -Abstract: - - Implementation of the specialized range-algebra used by symbolic - derivative computation and regex rewriting. See seq_range_predicate.h - for the algebraic specification. - - All Boolean operations are implemented as single linear sweeps over - the canonical sorted range vectors and produce canonical output - (sorted, disjoint, non-adjacent). - -Authors: - - Margus Veanes (veanes) 2026 - ---*/ - -#include "ast/rewriter/seq_range_predicate.h" -#include "util/debug.h" -#include -#include - -namespace seq { - - // ----------------------------------------------------------------------- - // Factories - // ----------------------------------------------------------------------- - - range_predicate range_predicate::empty(unsigned max_char) { - return range_predicate(max_char); - } - - range_predicate range_predicate::top(unsigned max_char) { - range_predicate r(max_char); - r.m_ranges.push_back({0u, max_char}); - SASSERT(r.well_formed()); - return r; - } - - range_predicate range_predicate::singleton(unsigned c, unsigned max_char) { - SASSERT(c <= max_char); - range_predicate r(max_char); - r.m_ranges.push_back({c, c}); - SASSERT(r.well_formed()); - return r; - } - - range_predicate range_predicate::range(unsigned lo, unsigned hi, unsigned max_char) { - range_predicate r(max_char); - if (lo <= hi && lo <= max_char) { - unsigned clipped_hi = hi <= max_char ? hi : max_char; - r.m_ranges.push_back({lo, clipped_hi}); - } - SASSERT(r.well_formed()); - return r; - } - - // ----------------------------------------------------------------------- - // Invariants and observers - // ----------------------------------------------------------------------- - - bool range_predicate::well_formed() const { - for (unsigned i = 0; i < m_ranges.size(); ++i) { - auto [lo, hi] = m_ranges[i]; - if (lo > hi) return false; - if (hi > m_max_char) return false; - if (i > 0) { - unsigned prev_hi = m_ranges[i - 1].second; - // Non-adjacent and sorted: prev_hi + 1 < lo, with care - // around prev_hi == UINT_MAX which we never expect because - // hi <= m_max_char. - if (prev_hi + 1 >= lo) return false; - } - } - return true; - } - - bool range_predicate::contains(unsigned c) const { - // Binary search on first element of pairs. - unsigned lo = 0, hi = m_ranges.size(); - while (lo < hi) { - unsigned mid = lo + (hi - lo) / 2; - auto [a, b] = m_ranges[mid]; - if (c < a) hi = mid; - else if (c > b) lo = mid + 1; - else return true; - } - return false; - } - - uint64_t range_predicate::cardinality() const { - uint64_t n = 0; - for (auto [lo, hi] : m_ranges) - n += static_cast(hi) - static_cast(lo) + 1u; - return n; - } - - // ----------------------------------------------------------------------- - // Equality, ordering, hashing - // ----------------------------------------------------------------------- - - bool range_predicate::equals(range_predicate const& o) const { - if (m_max_char != o.m_max_char) return false; - if (m_ranges.size() != o.m_ranges.size()) return false; - for (unsigned i = 0; i < m_ranges.size(); ++i) - if (m_ranges[i] != o.m_ranges[i]) return false; - return true; - } - - bool range_predicate::operator<(range_predicate const& o) const { - if (m_max_char != o.m_max_char) - return m_max_char < o.m_max_char; - unsigned n = std::min(m_ranges.size(), o.m_ranges.size()); - for (unsigned i = 0; i < n; ++i) { - auto a = m_ranges[i]; - auto b = o.m_ranges[i]; - if (a.first != b.first) return a.first < b.first; - if (a.second != b.second) return a.second < b.second; - } - return m_ranges.size() < o.m_ranges.size(); - } - - unsigned range_predicate::hash() const { - // FNV-1a 32-bit over (max_char, then each (lo, hi)). - uint32_t h = 2166136261u; - auto step = [&](uint32_t x) { - h ^= x; - h *= 16777619u; - }; - step(m_max_char); - for (auto [lo, hi] : m_ranges) { - step(lo); - step(hi); - } - return h; - } - - // ----------------------------------------------------------------------- - // Boolean operations - // ----------------------------------------------------------------------- - - namespace { - // Append (lo, hi) to result, merging with the previous range if - // adjacent or overlapping. Maintains canonical form. - inline void append_merged(svector>& result, - unsigned lo, unsigned hi) { - SASSERT(lo <= hi); - if (!result.empty() && result.back().second + 1 >= lo) { - if (result.back().second < hi) - result.back().second = hi; - } else { - result.push_back({lo, hi}); - } - } - } - - range_predicate range_predicate::operator|(range_predicate const& o) const { - SASSERT(m_max_char == o.m_max_char); - range_predicate r(m_max_char); - unsigned i = 0, j = 0; - const unsigned n = m_ranges.size(); - const unsigned m = o.m_ranges.size(); - while (i < n && j < m) { - auto a = m_ranges[i]; - auto b = o.m_ranges[j]; - if (a.first <= b.first) { - append_merged(r.m_ranges, a.first, a.second); - ++i; - } else { - append_merged(r.m_ranges, b.first, b.second); - ++j; - } - } - while (i < n) { - auto a = m_ranges[i++]; - append_merged(r.m_ranges, a.first, a.second); - } - while (j < m) { - auto b = o.m_ranges[j++]; - append_merged(r.m_ranges, b.first, b.second); - } - SASSERT(r.well_formed()); - return r; - } - - range_predicate range_predicate::operator&(range_predicate const& o) const { - SASSERT(m_max_char == o.m_max_char); - range_predicate r(m_max_char); - unsigned i = 0, j = 0; - const unsigned n = m_ranges.size(); - const unsigned m = o.m_ranges.size(); - while (i < n && j < m) { - auto [a_lo, a_hi] = m_ranges[i]; - auto [b_lo, b_hi] = o.m_ranges[j]; - unsigned lo = std::max(a_lo, b_lo); - unsigned hi = std::min(a_hi, b_hi); - if (lo <= hi) - r.m_ranges.push_back({lo, hi}); - // Advance the range that ends first. - if (a_hi < b_hi) ++i; - else if (b_hi < a_hi) ++j; - else { ++i; ++j; } - } - SASSERT(r.well_formed()); - return r; - } - - range_predicate range_predicate::operator~() const { - range_predicate r(m_max_char); - unsigned cursor = 0; - for (auto [lo, hi] : m_ranges) { - if (cursor < lo) - r.m_ranges.push_back({cursor, lo - 1}); - // Step past hi without overflow: hi <= m_max_char and we - // only step when more characters remain. - if (hi >= m_max_char) { - cursor = m_max_char + 1; // sentinel: no more characters - break; - } - cursor = hi + 1; - } - if (cursor <= m_max_char) - r.m_ranges.push_back({cursor, m_max_char}); - SASSERT(r.well_formed()); - return r; - } - - range_predicate range_predicate::operator-(range_predicate const& o) const { - SASSERT(m_max_char == o.m_max_char); - // A - B by linear sweep: for each range of A, subtract overlapping - // ranges of B. Both inputs are sorted so we advance j monotonically. - range_predicate r(m_max_char); - unsigned j = 0; - const unsigned m = o.m_ranges.size(); - for (auto [a_lo, a_hi] : m_ranges) { - unsigned cursor = a_lo; - while (j < m && o.m_ranges[j].second < cursor) - ++j; - unsigned k = j; - while (k < m && o.m_ranges[k].first <= a_hi) { - auto [b_lo, b_hi] = o.m_ranges[k]; - if (cursor < b_lo) - r.m_ranges.push_back({cursor, std::min(a_hi, b_lo - 1)}); - if (b_hi >= a_hi) { - cursor = a_hi + 1; - break; - } - cursor = b_hi + 1; - ++k; - } - if (cursor <= a_hi) - r.m_ranges.push_back({cursor, a_hi}); - } - SASSERT(r.well_formed()); - return r; - } - - range_predicate range_predicate::operator^(range_predicate const& o) const { - SASSERT(m_max_char == o.m_max_char); - // (A | B) - (A & B), but implemented directly with one linear sweep - // over the union of breakpoints. - return (*this | o) - (*this & o); - } - - // ----------------------------------------------------------------------- - // Display - // ----------------------------------------------------------------------- - - std::ostream& range_predicate::display(std::ostream& out) const { - if (m_ranges.empty()) { - return out << "[]"; - } - out << "["; - bool first = true; - for (auto [lo, hi] : m_ranges) { - if (!first) out << ","; - first = false; - if (lo == hi) - out << lo; - else - out << lo << "-" << hi; - } - return out << "]"; - } - -} diff --git a/src/ast/rewriter/seq_range_predicate.h b/src/ast/rewriter/seq_range_predicate.h deleted file mode 100644 index 4fbf4938f5..0000000000 --- a/src/ast/rewriter/seq_range_predicate.h +++ /dev/null @@ -1,127 +0,0 @@ -/*++ -Copyright (c) 2026 Microsoft Corporation - -Module Name: - - seq_range_predicate.h - -Abstract: - - Specialized range-algebra over an unsigned character domain [0, max_char]. - - A range_predicate represents a subset of the character domain as a - sorted sequence of non-overlapping, non-adjacent, non-empty ranges: - - [(lo_0, hi_0), (lo_1, hi_1), ...] with hi_i + 1 < lo_{i+1}. - - The representation is canonical, so two range_predicates over the same - domain are extensionally equivalent iff their internal vectors are - elementwise equal. - - All Boolean operations (union, intersection, complement, difference) - are linear in the total number of ranges and produce the canonical - representation. - - Intended use: - * path conditions for symbolic derivative computation, - * OneStep predicates capturing length-1 acceptance, - * smart-constructor side conditions for regex rewrites such as - R & psi --> toregex(OneStep(R) & psi). - - The type is a pure value: no ast_manager allocation occurs in its - construction or its Boolean operations. Conversion to and from - expr* is the responsibility of a separate translator (see callers - in seq_derive / seq_rewriter). - -Authors: - - Margus Veanes (veanes) 2026 - ---*/ -#pragma once - -#include "util/vector.h" -#include -#include - -namespace seq { - - class range_predicate { - using range_t = std::pair; - using ranges_t = svector; - - // Sorted by first; ranges are disjoint and non-adjacent; - // every range satisfies lo <= hi <= m_max_char. - ranges_t m_ranges; - unsigned m_max_char { 0 }; - - // Invariant check used in debug builds. - bool well_formed() const; - - public: - range_predicate() = default; - explicit range_predicate(unsigned max_char) : m_max_char(max_char) {} - - // ---------------- Factory functions ---------------- - - static range_predicate empty(unsigned max_char); - static range_predicate top(unsigned max_char); - static range_predicate singleton(unsigned c, unsigned max_char); - static range_predicate range(unsigned lo, unsigned hi, unsigned max_char); - - // ---------------- Observers ---------------- - - unsigned max_char() const { return m_max_char; } - unsigned num_ranges() const { return m_ranges.size(); } - range_t operator[](unsigned i) const { return m_ranges[i]; } - ranges_t const& ranges() const { return m_ranges; } - - bool is_empty() const { return m_ranges.empty(); } - bool is_top() const { - return m_ranges.size() == 1 - && m_ranges[0].first == 0 - && m_ranges[0].second == m_max_char; - } - bool is_singleton(unsigned& c) const { - if (m_ranges.size() != 1) return false; - if (m_ranges[0].first != m_ranges[0].second) return false; - c = m_ranges[0].first; - return true; - } - bool contains(unsigned c) const; - - // Number of characters in the predicate (well-defined for any domain). - uint64_t cardinality() const; - - // ---------------- Equality, ordering, hashing ---------------- - - bool equals(range_predicate const& o) const; - bool operator==(range_predicate const& o) const { return equals(o); } - bool operator!=(range_predicate const& o) const { return !equals(o); } - - // Total order: lexicographic on the canonical range sequence, - // with shorter sequences ordered before longer prefixes. - // Predicates over different domains compare by max_char first. - bool operator<(range_predicate const& o) const; - bool less_than(range_predicate const& o) const { return *this < o; } - - unsigned hash() const; - - // ---------------- Boolean operations ---------------- - - range_predicate operator|(range_predicate const& o) const; // union - range_predicate operator&(range_predicate const& o) const; // intersection - range_predicate operator-(range_predicate const& o) const; // difference - range_predicate operator^(range_predicate const& o) const; // symmetric diff - range_predicate operator~() const; // complement - - // ---------------- Display ---------------- - - std::ostream& display(std::ostream& out) const; - }; - - inline std::ostream& operator<<(std::ostream& out, range_predicate const& p) { - return p.display(out); - } - -} diff --git a/src/ast/rewriter/seq_regex_bisim.cpp b/src/ast/rewriter/seq_regex_bisim.cpp index 68b2907a55..e296d5b3e0 100644 --- a/src/ast/rewriter/seq_regex_bisim.cpp +++ b/src/ast/rewriter/seq_regex_bisim.cpp @@ -85,6 +85,45 @@ namespace seq { return is_ground(r); } + /* + Collect the leaves of a t-regex der (an ITE-tree whose leaves are + regex expressions) into the output vector. Empty (re.empty) leaves + are dropped. + + Each leaf is treated as a single bisimulation state regardless of + its top-level shape (including re.union and re.antimirov_union): + descending into a union at the leaf would split one state into + several, which is semantically unsound for the bisimulation / + union-find merging that follows. + + Returns false if we encountered an unexpected node (e.g. a free + variable creeping in) — in that case the caller should bail out. + */ + bool regex_bisim::collect_leaves(expr* der, expr_ref_vector& leaves) { + ptr_vector work; + obj_hashtable seen; + work.push_back(der); + seen.insert(der); + while (!work.empty()) { + expr* e = work.back(); + work.pop_back(); + expr* c = nullptr, * t = nullptr, * f = nullptr; + if (m.is_ite(e, c, t, f)) { + if (seen.insert_if_not_there(t)) + work.push_back(t); + if (seen.insert_if_not_there(f)) + work.push_back(f); + continue; + } + if (m_util.re.is_empty(e)) + continue; + if (!m_util.is_re(e)) + return false; + leaves.push_back(e); + } + return true; + } + /* Fast inequivalence check based on the get_info().classical flag. @@ -193,19 +232,15 @@ namespace seq { m_worklist.pop_back(); // Compute the symbolic derivative wrt the canonical variable - // (:var 0) and enumerate its reachable leaves in fully - // ITE-hoisted normal form. Every if-then-else over the input - // character — even one that would otherwise be buried under a - // concat or union — is hoisted to the top and infeasible - // minterms are pruned, so each leaf is a ground regex free of - // (:var 0) whose nullability is always decidable. Unions are - // kept intact as single leaves (a union leaf denotes a single - // bisimulation state, never a split into separate states). - expr_ref_pair_vector cofs(m); - m_rw.brz_derivative_cofactors(r, cofs); + // (:var 0). The result is a transition regex (ITE tree) whose + // leaves are regex expressions. We use the classical Brzozowski + // entry point so the derivative stays as a single TRegex and + // does not lift unions to the top via antimirov nodes — this + // preserves the XOR-pair invariant the bisimulation relies on. + expr_ref d(m_rw.mk_brz_derivative(r), m); expr_ref_vector leaves(m); - for (auto const& p : cofs) - leaves.push_back(p.second); + if (!collect_leaves(d, leaves)) + return l_undef; // First pass: check for any nullable leaf (definitive // distinguishing empty-continuation word) or any classically diff --git a/src/ast/rewriter/seq_regex_bisim.h b/src/ast/rewriter/seq_regex_bisim.h index 7ec5c30a33..d158cc3793 100644 --- a/src/ast/rewriter/seq_regex_bisim.h +++ b/src/ast/rewriter/seq_regex_bisim.h @@ -74,6 +74,7 @@ namespace seq { unsigned node_of(expr* r); bool merge_leaf(expr* xor_pair); + bool collect_leaves(expr* der, expr_ref_vector& leaves); lbool nullability(expr* r); bool is_supported(expr* r); // Returns true if the leaf l proves that the original pair is diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 85adb94d17..3dd2d9a364 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -21,7 +21,6 @@ Authors: #include "util/uint_set.h" #include "ast/rewriter/seq_rewriter.h" #include "ast/rewriter/seq_regex_bisim.h" -#include "ast/rewriter/seq_range_collapse.h" #include "ast/arith_decl_plugin.h" #include "ast/array_decl_plugin.h" #include "ast/ast_pp.h" @@ -2727,7 +2726,7 @@ expr_ref seq_rewriter::is_nullable(expr* r) { << mk_pp(r, m()) << std::endl;); expr_ref result(m_op_cache.find(_OP_RE_IS_NULLABLE, r, nullptr, nullptr), m()); if (!result) { - result = m_derive.nullable(r); + result = is_nullable_rec(r); m_op_cache.insert(_OP_RE_IS_NULLABLE, r, nullptr, nullptr, result); } STRACE(seq_verbose, tout << "is_nullable result: " @@ -2735,6 +2734,117 @@ expr_ref seq_rewriter::is_nullable(expr* r) { return result; } +expr_ref seq_rewriter::is_nullable_rec(expr* r) { + SASSERT(m_util.is_re(r) || m_util.is_seq(r)); + expr* r1 = nullptr, *r2 = nullptr, *cond = nullptr; + sort* seq_sort = nullptr; + unsigned lo = 0, hi = 0; + zstring s1; + expr_ref result(m()); + if (re().is_concat(r, r1, r2) || + re().is_intersection(r, r1, r2)) { + m_br.mk_and(is_nullable(r1), is_nullable(r2), result); + } + else if (re().is_union(r, r1, r2) || re().is_antimirov_union(r, r1, r2)) { + m_br.mk_or(is_nullable(r1), is_nullable(r2), result); + } + else if (re().is_diff(r, r1, r2)) { + m_br.mk_not(is_nullable(r2), result); + m_br.mk_and(result, is_nullable(r1), result); + } + else if (re().is_xor(r, r1, r2)) { + // Null(r1 XOR r2) = Null(r1) XOR Null(r2) + expr_ref n1(is_nullable(r1), m()); + expr_ref n2(is_nullable(r2), m()); + // Simplify when either operand is a boolean literal so the + // bisimulation procedure can use the answer directly. + if (m().is_true(n1)) + result = mk_not(m(), n2); + else if (m().is_false(n1)) + result = n2; + else if (m().is_true(n2)) + result = mk_not(m(), n1); + else if (m().is_false(n2)) + result = n1; + else + result = m().mk_xor(n1, n2); + } + else if (re().is_star(r) || + re().is_opt(r) || + re().is_full_seq(r) || + re().is_epsilon(r) || + (re().is_loop(r, r1, lo) && lo == 0) || + (re().is_loop(r, r1, lo, hi) && lo == 0)) { + result = m().mk_true(); + } + else if (re().is_full_char(r) || + re().is_empty(r) || + re().is_of_pred(r) || + re().is_range(r)) { + result = m().mk_false(); + } + else if (re().is_plus(r, r1) || + (re().is_loop(r, r1, lo) && lo > 0) || + (re().is_loop(r, r1, lo, hi) && lo > 0) || + (re().is_reverse(r, r1))) { + result = is_nullable(r1); + } + else if (re().is_complement(r, r1)) { + m_br.mk_not(is_nullable(r1), result); + } + else if (re().is_to_re(r, r1)) { + result = is_nullable(r1); + } + else if (m().is_ite(r, cond, r1, r2)) { + m_br.mk_ite(cond, is_nullable(r1), is_nullable(r2), result); + } + else if (m_util.is_re(r, seq_sort)) { + result = is_nullable_symbolic_regex(r, seq_sort); + } + else if (str().is_concat(r, r1, r2)) { + m_br.mk_and(is_nullable(r1), is_nullable(r2), result); + } + else if (str().is_empty(r)) { + result = m().mk_true(); + } + else if (str().is_unit(r)) { + result = m().mk_false(); + } + else if (str().is_string(r, s1)) { + result = m().mk_bool_val(s1.length() == 0); + } + else { + SASSERT(m_util.is_seq(r)); + result = m().mk_eq(str().mk_empty(r->get_sort()), r); + } + return result; +} + +expr_ref seq_rewriter::is_nullable_symbolic_regex(expr* r, sort* seq_sort) { + SASSERT(m_util.is_re(r)); + expr* elem = nullptr, *r1 = r, * r2 = nullptr, * s = nullptr; + expr_ref elems(str().mk_empty(seq_sort), m()); + expr_ref result(m()); + while (re().is_derivative(r1, elem, r2)) { + if (str().is_empty(elems)) + elems = str().mk_unit(elem); + else + elems = str().mk_concat(str().mk_unit(elem), elems); + r1 = r2; + } + if (re().is_to_re(r1, s)) { + // r is nullable + // iff after taking the derivatives the remaining sequence is empty + // iff the inner sequence equals to the sequence of derivative elements in reverse + result = m().mk_eq(elems, s); + return result; + } + // the default case when either r is not a derivative + // or when the nested derivatives are not applied to a sequence + result = re().mk_in_re(str().mk_empty(seq_sort), r); + return result; +} + /* Push reverse inwards (whenever possible). */ @@ -2958,18 +3068,370 @@ bool seq_rewriter::check_deriv_normal_form(expr* r, int level) { #endif expr_ref seq_rewriter::mk_derivative(expr* r) { - auto result = m_derive(seq::derivative_kind::antimirov_t, r); - TRACE(seq, tout << "Derivative of " << mk_pp(r, m()) << "\nis\n" << result << std::endl;); - return result; + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + expr_ref v(m().mk_var(0, ele_sort), m()); + return mk_antimirov_deriv(v, r, m().mk_true()); +} + +expr_ref seq_rewriter::mk_brz_derivative(expr* r) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + expr_ref v(m().mk_var(0, ele_sort), m()); + return mk_derivative_rec(v, r); } expr_ref seq_rewriter::mk_derivative(expr* ele, expr* r) { - auto result = m_derive(seq::derivative_kind::antimirov_t, ele, r); - TRACE(seq, - tout << "Derivative of " << mk_pp(r, m()) << " w.r.t. " << mk_pp(ele, m()) << "\nis\n" << result << std::endl;); + return mk_antimirov_deriv(ele, r, m().mk_true()); +} + +expr_ref seq_rewriter::mk_antimirov_deriv(expr* e, expr* r, expr* path) { + // Ensure references are owned + expr_ref _e(e, m()), _path(path, m()), _r(r, m()); + expr_ref result(m_op_cache.find(OP_RE_DERIVATIVE, e, r, path), m()); + if (!result) { + mk_antimirov_deriv_rec(e, r, path, result); + m_op_cache.insert(OP_RE_DERIVATIVE, e, r, path, result); + STRACE(seq_regex, tout << "D(" << mk_pp(e, m()) << "," << mk_pp(r, m()) << "," << mk_pp(path, m()) << ")" << std::endl;); + STRACE(seq_regex, tout << "= " << mk_pp(result, m()) << std::endl;); + } return result; } +void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref& result) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + expr_ref _r(r, m()), _path(path, m()); + VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + SASSERT(ele_sort == e->get_sort()); + expr* r1 = nullptr, * r2 = nullptr, * c = nullptr; + expr_ref c1(m()); + expr_ref c2(m()); + auto nothing = [&]() { return expr_ref(re().mk_empty(r->get_sort()), m()); }; + auto epsilon = [&]() { return expr_ref(re().mk_epsilon(seq_sort), m()); }; + auto dotstar = [&]() { return expr_ref(re().mk_full_seq(r->get_sort()), m()); }; + unsigned lo = 0, hi = 0; + if (re().is_empty(r) || re().is_epsilon(r)) + // D(e,[]) = D(e,()) = [] + result = nothing(); + else if (re().is_full_seq(r) || re().is_dot_plus(r)) + // D(e,.*) = D(e,.+) = .* + result = dotstar(); + else if (re().is_full_char(r)) + // D(e,.) = () + result = epsilon(); + else if (re().is_to_re(r, r1)) { + expr_ref h(m()); + expr_ref t(m()); + // here r1 is a sequence + if (get_head_tail(r1, h, t)) { + if (eq_char(e, h)) + result = re().mk_to_re(t); + else if (neq_char(e, h)) + result = nothing(); + else + result = re().mk_ite_simplify(m().mk_eq(e, h), re().mk_to_re(t), nothing()); + } + else { + // observe that the precondition |r1|>0 is is implied by c1 for use of mk_seq_first + { + auto is_non_empty = m().mk_not(m().mk_eq(r1, str().mk_empty(seq_sort))); + auto eq_first = m().mk_eq(mk_seq_first(r1), e); + m_br.mk_and(is_non_empty, eq_first, c1); + } + m_br.mk_and(path, c1, c2); + if (m().is_false(c2)) + result = nothing(); + else + // observe that the precondition |r1|>0 is implied by c1 for use of mk_seq_rest + result = m().mk_ite(c1, re().mk_to_re(mk_seq_rest(r1)), nothing()); + } + } + else if (re().is_reverse(r, r2)) + if (re().is_to_re(r2, r1)) { + // here r1 is a sequence + // observe that the precondition |r1|>0 of mk_seq_last is implied by c1 + { + auto is_non_empty = m().mk_not(m().mk_eq(r1, str().mk_empty(seq_sort))); + auto eq_last = m().mk_eq(mk_seq_last(r1), e); + m_br.mk_and(is_non_empty, eq_last, c1); + } + m_br.mk_and(path, c1, c2); + if (m().is_false(c2)) + result = nothing(); + else + // observe that the precondition |r1|>0 of mk_seq_rest is implied by c1 + result = re().mk_ite_simplify(c1, re().mk_reverse(re().mk_to_re(mk_seq_butlast(r1))), nothing()); + } + else { + result = mk_regex_reverse(r2); + if (result.get() == r) + //r2 is an uninterpreted regex that is stuck + //for example if r = (re.reverse R) where R is a regex variable then + //here result.get() == r + result = re().mk_derivative(e, result); + else + result = mk_antimirov_deriv(e, result, path); + } + else if (re().is_concat(r, r1, r2)) { + expr_ref r1nullable(is_nullable(r1), m()); + c1 = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), r2); + expr_ref r1nullable_and_path(m()); + m_br.mk_and(r1nullable, path, r1nullable_and_path); + if (m().is_false(r1nullable_and_path)) + // D(e,r1)r2 + result = c1; + else + // D(e,r1)r2|(ite (r1nullable) (D(e,r2)) []) + // observe that (mk_ite_simplify(true, D(e,r2), []) = D(e,r2) + result = mk_antimirov_deriv_union(c1, re().mk_ite_simplify(r1nullable, mk_antimirov_deriv(e, r2, path), nothing())); + } + else if (m().is_ite(r, c, r1, r2)) { + { + auto cp = m().mk_and(c, path); + c1 = simplify_path(e, cp); + } + { + auto notc = m().mk_not(c); + auto np = m().mk_and(notc, path); + c2 = simplify_path(e, np); + } + if (m().is_false(c1)) + result = mk_antimirov_deriv(e, r2, c2); + else if (m().is_false(c2)) + result = mk_antimirov_deriv(e, r1, c1); + else + result = re().mk_ite_simplify(c, mk_antimirov_deriv(e, r1, c1), mk_antimirov_deriv(e, r2, c2)); + } + else if (re().is_range(r, r1, r2)) { + expr_ref range(m()); + expr_ref psi(m().mk_false(), m()); + if (str().is_unit_string(r1, c1) && str().is_unit_string(r2, c2)) { + // SASSERT(u().is_char(c1)); + // SASSERT(u().is_char(c2)); + // case: c1 <= e <= c2 + // deterministic evaluation for range bounds + auto a_le = u().mk_le(c1, e); + auto b_le = u().mk_le(e, c2); + auto rng_cond = m().mk_and(a_le, b_le); + range = simplify_path(e, rng_cond); + psi = simplify_path(e, m().mk_and(path, range)); + } + else if (!str().is_string(r1) && str().is_unit_string(r2, c2)) { + SASSERT(u().is_char(c2)); + // r1 nonground: |r1|=1 & r1[0] <= e <= c2 + expr_ref one(m_autil.mk_int(1), m()); + expr_ref zero(m_autil.mk_int(0), m()); + expr_ref r1_length_eq_one(m().mk_eq(str().mk_length(r1), one), m()); + expr_ref r1_0(str().mk_nth_i(r1, zero), m()); + range = simplify_path(e, m().mk_and(r1_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, c2)))); + psi = simplify_path(e, m().mk_and(path, range)); + } + else if (!str().is_string(r2) && str().is_unit_string(r1, c1)) { + SASSERT(u().is_char(c1)); + // r2 nonground: |r2|=1 & c1 <= e <= r2_0 + expr_ref one(m_autil.mk_int(1), m()); + expr_ref zero(m_autil.mk_int(0), m()); + expr_ref r2_length_eq_one(m().mk_eq(str().mk_length(r2), one), m()); + expr_ref r2_0(str().mk_nth_i(r2, zero), m()); + range = simplify_path(e, m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(c1, e), u().mk_le(e, r2_0)))); + psi = simplify_path(e, m().mk_and(path, range)); + } + else if (!str().is_string(r1) && !str().is_string(r2)) { + // both r1 and r2 nonground: |r1|=1 & |r2|=1 & r1[0] <= e <= r2[0] + expr_ref one(m_autil.mk_int(1), m()); + expr_ref zero(m_autil.mk_int(0), m()); + expr_ref r1_length_eq_one(m().mk_eq(str().mk_length(r1), one), m()); + expr_ref r1_0(str().mk_nth_i(r1, zero), m()); + expr_ref r2_length_eq_one(m().mk_eq(str().mk_length(r2), one), m()); + expr_ref r2_0(str().mk_nth_i(r2, zero), m()); + range = simplify_path(e, m().mk_and(r1_length_eq_one, m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, r2_0))))); + psi = simplify_path(e, m().mk_and(path, range)); + } + if (m().is_false(psi)) + result = nothing(); + else + result = re().mk_ite_simplify(range, epsilon(), nothing()); + } + else if (re().is_union(r, r1, r2)) + result = mk_antimirov_deriv_union(mk_antimirov_deriv(e, r1, path), mk_antimirov_deriv(e, r2, path)); + else if (re().is_intersection(r, r1, r2)) + result = mk_antimirov_deriv_intersection(e, + mk_antimirov_deriv(e, r1, path), + mk_antimirov_deriv(e, r2, path), m().mk_true()); + else if (re().is_star(r, r1) || re().is_plus(r, r1) || (re().is_loop(r, r1, lo) && 0 <= lo && lo <= 1)) + result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), re().mk_star(r1)); + else if (re().is_loop(r, r1, lo)) + result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), re().mk_loop(r1, lo - 1)); + else if (re().is_loop(r, r1, lo, hi)) { + if ((lo == 0 && hi == 0) || hi < lo) + result = nothing(); + else { + expr_ref t(re().mk_loop_proper(r1, (lo == 0 ? 0 : lo - 1), hi - 1), m()); + result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), t); + } + } + else if (re().is_opt(r, r1)) + result = mk_antimirov_deriv(e, r1, path); + else if (re().is_complement(r, r1)) + // D(e,~r1) = ~D(e,r1) + result = mk_antimirov_deriv_negate(e, mk_antimirov_deriv(e, r1, path)); + else if (re().is_diff(r, r1, r2)) + result = mk_antimirov_deriv_intersection(e, + mk_antimirov_deriv(e, r1, path), + mk_antimirov_deriv_negate(e, mk_antimirov_deriv(e, r2, path)), m().mk_true()); + else if (re().is_xor(r, r1, r2)) + // D(e, r1 XOR r2) = D(e, r1) XOR D(e, r2) + result = mk_der_xor(mk_antimirov_deriv(e, r1, path), + mk_antimirov_deriv(e, r2, path)); + else if (re().is_of_pred(r, r1)) { + array_util array(m()); + expr* args[2] = { r1, e }; + result = array.mk_select(2, args); + // Use mk_der_cond to normalize + result = mk_der_cond(result, e, seq_sort); + } + else + // stuck cases + result = re().mk_derivative(e, r); +} + +expr_ref seq_rewriter::mk_antimirov_deriv_intersection(expr* e, expr* d1, expr* d2, expr* path) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(d1, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + expr_ref result(m()); + expr* c, * a, * b; + if (re().is_empty(d1)) + result = d1; + else if (re().is_empty(d2)) + result = d2; + else if (m().is_ite(d1, c, a, b)) { + expr_ref path_and_c(simplify_path(e, m().mk_and(path, c)), m()); + expr_ref path_and_notc(simplify_path(e, m().mk_and(path, m().mk_not(c))), m()); + if (m().is_false(path_and_c)) + result = mk_antimirov_deriv_intersection(e, b, d2, path); + else if (m().is_false(path_and_notc)) + result = mk_antimirov_deriv_intersection(e, a, d2, path); + else + result = m().mk_ite(c, mk_antimirov_deriv_intersection(e, a, d2, path_and_c), + mk_antimirov_deriv_intersection(e, b, d2, path_and_notc)); + } + else if (m().is_ite(d2)) + // swap d1 and d2 + result = mk_antimirov_deriv_intersection(e, d2, d1, path); + else if (d1 == d2 || re().is_full_seq(d2)) + result = mk_antimirov_deriv_restrict(e, d1, path); + else if (re().is_full_seq(d1)) + result = mk_antimirov_deriv_restrict(e, d2, path); + else if (re().is_union(d1, a, b)) + // distribute intersection over the union in d1 + result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(e, a, d2, path), + mk_antimirov_deriv_intersection(e, b, d2, path)); + else if (re().is_union(d2, a, b)) + // distribute intersection over the union in d2 + result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(e, d1, a, path), + mk_antimirov_deriv_intersection(e, d1, b, path)); + else + result = mk_regex_inter_normalize(d1, d2); + return result; +} + +expr_ref seq_rewriter::mk_antimirov_deriv_concat(expr* d, expr* r) { + expr_ref result(m()); + expr_ref _r(r, m()), _d(d, m()); + expr* c, * t, * e; + if (m().is_ite(d, c, t, e)) { + auto r2 = mk_antimirov_deriv_concat(e, r); + auto r1 = mk_antimirov_deriv_concat(t, r); + result = m().mk_ite(c, r1, r2); + } + else if (re().is_union(d, t, e)) + result = mk_antimirov_deriv_union(mk_antimirov_deriv_concat(t, r), mk_antimirov_deriv_concat(e, r)); + else + result = mk_re_append(d, r); + SASSERT(result.get()); + return result; +} + +expr_ref seq_rewriter::mk_antimirov_deriv_negate(expr* elem, expr* d) { + sort* seq_sort = nullptr; + VERIFY(m_util.is_re(d, seq_sort)); + auto nothing = [&]() { return expr_ref(re().mk_empty(d->get_sort()), m()); }; + auto epsilon = [&]() { return expr_ref(re().mk_epsilon(seq_sort), m()); }; + auto dotstar = [&]() { return expr_ref(re().mk_full_seq(d->get_sort()), m()); }; + auto dotplus = [&]() { return expr_ref(re().mk_plus(re().mk_full_char(d->get_sort())), m()); }; + expr_ref result(m()); + expr* c, * t, * e; + if (re().is_empty(d)) + result = dotstar(); + else if (re().is_epsilon(d)) + result = dotplus(); + else if (re().is_full_seq(d)) + result = nothing(); + else if (re().is_dot_plus(d)) + result = epsilon(); + else if (m().is_ite(d, c, t, e)) + result = m().mk_ite(c, mk_antimirov_deriv_negate(elem, t), mk_antimirov_deriv_negate(elem, e)); + else if (re().is_union(d, t, e)) + result = mk_antimirov_deriv_intersection(elem, mk_antimirov_deriv_negate(elem, t), mk_antimirov_deriv_negate(elem, e), m().mk_true()); + else if (re().is_intersection(d, t, e)) + result = mk_antimirov_deriv_union(mk_antimirov_deriv_negate(elem, t), mk_antimirov_deriv_negate(elem, e)); + else if (re().is_complement(d, t)) + result = t; + else + result = re().mk_complement(d); + return result; +} + +expr_ref seq_rewriter::mk_antimirov_deriv_union(expr* d1, expr* d2) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(d1, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + expr_ref result(m()); + expr* c1, * t1, * e1, * c2, * t2, * e2; + if (m().is_ite(d1, c1, t1, e1) && m().is_ite(d2, c2, t2, e2) && c1 == c2) + // eliminate duplicate branching on exactly the same condition + result = m().mk_ite(c1, mk_antimirov_deriv_union(t1, t2), mk_antimirov_deriv_union(e1, e2)); + else + result = mk_regex_union_normalize(d1, d2); + return result; +} + +// restrict the guards of all conditionals id d and simplify the resulting derivative +// restrict(if(c, a, b), cond) = if(c, restrict(a, cond & c), restrict(b, cond & ~c)) +// restrict(a U b, cond) = restrict(a, cond) U restrict(b, cond) +// where {} U X = X, X U X = X +// restrict(R, cond) = R +// +// restrict(d, false) = [] +// +// it is already assumed that the restriction takes place within a branch +// so the condition is not added explicitly but propagated down in order to eliminate +// infeasible cases +expr_ref seq_rewriter::mk_antimirov_deriv_restrict(expr* e, expr* d, expr* cond) { + expr_ref result(d, m()); + expr_ref _cond(cond, m()); + expr* c, * a, * b; + if (m().is_false(cond)) + result = re().mk_empty(d->get_sort()); + else if (re().is_empty(d) || m().is_true(cond)) + result = d; + else if (m().is_ite(d, c, a, b)) { + expr_ref path_and_c(simplify_path(e, m().mk_and(cond, c)), m()); + expr_ref path_and_notc(simplify_path(e, m().mk_and(cond, m().mk_not(c))), m()); + result = re().mk_ite_simplify(c, mk_antimirov_deriv_restrict(e, a, path_and_c), + mk_antimirov_deriv_restrict(e, b, path_and_notc)); + } + else if (re().is_union(d, a, b)) { + expr_ref a1(mk_antimirov_deriv_restrict(e, a, cond), m()); + expr_ref b1(mk_antimirov_deriv_restrict(e, b, cond), m()); + result = mk_antimirov_deriv_union(a1, b1); + } + return result; +} expr_ref seq_rewriter::mk_regex_union_normalize(expr* r1, expr* r2) { expr_ref _r1(r1, m()), _r2(r2, m()); @@ -3162,6 +3624,126 @@ expr_ref seq_rewriter::merge_regex_sets(expr* r1, expr* r2, expr* unit, } } +expr_ref seq_rewriter::mk_regex_reverse(expr* r) { + expr* r1 = nullptr, * r2 = nullptr, * c = nullptr; + unsigned lo = 0, hi = 0; + expr_ref result(m()); + if (re().is_empty(r) || re().is_range(r) || re().is_epsilon(r) || re().is_full_seq(r) || + re().is_full_char(r) || re().is_dot_plus(r) || re().is_of_pred(r)) + result = r; + else if (re().is_to_re(r)) + result = re().mk_reverse(r); + else if (re().is_reverse(r, r1)) + result = r1; + else if (re().is_concat(r, r1, r2)) + result = mk_regex_concat(mk_regex_reverse(r2), mk_regex_reverse(r1)); + else if (m().is_ite(r, c, r1, r2)) + result = m().mk_ite(c, mk_regex_reverse(r1), mk_regex_reverse(r2)); + else if (re().is_union(r, r1, r2)) { + // enforce deterministic evaluation order + auto a1 = mk_regex_reverse(r1); + auto b1 = mk_regex_reverse(r2); + result = re().mk_union(a1, b1); + } + else if (re().is_intersection(r, r1, r2)) { + auto a1 = mk_regex_reverse(r1); + auto b1 = mk_regex_reverse(r2); + result = re().mk_inter(a1, b1); + } + else if (re().is_diff(r, r1, r2)) { + auto a1 = mk_regex_reverse(r1); + auto b1 = mk_regex_reverse(r2); + result = re().mk_diff(a1, b1); + } + else if (re().is_xor(r, r1, r2)) { + auto a1 = mk_regex_reverse(r1); + auto b1 = mk_regex_reverse(r2); + result = re().mk_xor(a1, b1); + } + else if (re().is_star(r, r1)) + result = re().mk_star(mk_regex_reverse(r1)); + else if (re().is_plus(r, r1)) + result = re().mk_plus(mk_regex_reverse(r1)); + else if (re().is_loop(r, r1, lo)) + result = re().mk_loop(mk_regex_reverse(r1), lo); + else if (re().is_loop(r, r1, lo, hi)) + result = re().mk_loop_proper(mk_regex_reverse(r1), lo, hi); + else if (re().is_opt(r, r1)) + result = re().mk_opt(mk_regex_reverse(r1)); + else if (re().is_complement(r, r1)) + result = re().mk_complement(mk_regex_reverse(r1)); + else + //stuck cases: such as r being a regex variable + //observe that re().mk_reverse(to_re(s)) is not a stuck case + result = re().mk_reverse(r); + return result; +} + +expr_ref seq_rewriter::mk_regex_concat(expr* r, expr* s) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(u().is_seq(seq_sort, ele_sort)); + SASSERT(r->get_sort() == s->get_sort()); + expr_ref result(m()); + expr* r1, * r2; + if (re().is_epsilon(r) || re().is_empty(s)) + result = s; + else if (re().is_epsilon(s) || re().is_empty(r)) + result = r; + else if (re().is_full_seq(r) && re().is_full_seq(s)) + result = r; + else if (re().is_full_char(r) && re().is_full_seq(s)) + // ..* = .+ + result = re().mk_plus(re().mk_full_char(ele_sort)); + else if (re().is_full_seq(r) && re().is_full_char(s)) + // .*. = .+ + result = re().mk_plus(re().mk_full_char(ele_sort)); + else if (re().is_concat(r, r1, r2)) + // create the resulting concatenation in right-associative form except for the following case + // TODO: maintain the following invariant for A ++ B{m,n} + C + // concat(concat(A, B{m,n}), C) (if A != () and C != ()) + // concat(B{m,n}, C) (if A == () and C != ()) + // where A, B, C are regexes + // Using & below for Intersection and | for Union + // In other words, do not make A ++ B{m,n} into right-assoc form, but keep B{m,n} at the top + // This will help to identify this situation in the merge routine: + // concat(concat(A, B{0,m}), C) | concat(concat(A, B{0,n}), C) + // simplifies to + // concat(concat(A, B{0,max(m,n)}), C) + // analogously: + // concat(concat(A, B{0,m}), C) & concat(concat(A, B{0,n}), C) + // simplifies to + // concat(concat(A, B{0,min(m,n)}), C) + result = mk_regex_concat(r1, mk_regex_concat(r2, s)); + else { + result = re().mk_concat(r, s); + } + return result; +} + +expr_ref seq_rewriter::mk_in_antimirov(expr* s, expr* d){ + expr_ref result(mk_in_antimirov_rec(s, d), m()); + return result; +} + +expr_ref seq_rewriter::mk_in_antimirov_rec(expr* s, expr* d) { + expr* c, * d1, * d2; + expr_ref result(m()); + if (re().is_full_seq(d) || (str().min_length(s) > 0 && re().is_dot_plus(d))) + // s in .* <==> true, also: s in .+ <==> true when |s|>0 + result = m().mk_true(); + else if (re().is_empty(d) || (str().min_length(s) > 0 && re().is_epsilon(d))) + // s in [] <==> false, also: s in () <==> false when |s|>0 + result = m().mk_false(); + else if (m().is_ite(d, c, d1, d2)) + result = re().mk_ite_simplify(c, mk_in_antimirov_rec(s, d1), mk_in_antimirov_rec(s, d2)); + else if (re().is_union(d, d1, d2)) + m_br.mk_or(mk_in_antimirov_rec(s, d1), mk_in_antimirov_rec(s, d2), result); + else + result = re().mk_in_re(s, d); + return result; +} + /* * calls elim_condition */ @@ -3222,10 +3804,6 @@ bool seq_rewriter::le_char(expr* ch1, expr* ch2) { Current cases handled: - a and b are char <= constraints, or negations of char <= constraints - - a and b are equalities (element = constant char), or their negations. - These arise from derivatives of single characters and must be pruned - when combining BDDs so that no unreachable branch such as - ite(x = 'a', ite(x = 'b', ...), ...) (with 'a' != 'b') is created. */ bool seq_rewriter::pred_implies(expr* a, expr* b) { STRACE(seq_verbose, tout << "pred_implies: " @@ -3233,26 +3811,6 @@ bool seq_rewriter::pred_implies(expr* a, expr* b) { << "," << mk_pp(b, m()) << std::endl;); expr *cha1 = nullptr, *cha2 = nullptr, *nota = nullptr, *chb1 = nullptr, *chb2 = nullptr, *notb = nullptr; - // (element = constant char), returning the element and char code. - auto is_char_eq = [&](expr* e, expr*& x, unsigned& v) { - expr* t1 = nullptr, *t2 = nullptr; - if (!m().is_eq(e, t1, t2)) - return false; - if (u().is_const_char(t2, v)) { x = t1; return true; } - if (u().is_const_char(t1, v)) { x = t2; return true; } - return false; - }; - expr *xa = nullptr, *xb = nullptr; - unsigned va = 0, vb = 0; - if (is_char_eq(a, xa, va)) { - // a is (xa = va) - if (is_char_eq(b, xb, vb) && xa == xb) - // (x = va) => (x = vb) iff va == vb - return va == vb; - if (m().is_not(b, notb) && is_char_eq(notb, xb, vb) && xa == xb) - // (x = va) => not (x = vb) iff va != vb - return va != vb; - } if (m().is_not(a, nota) && m().is_not(b, notb)) { return pred_implies(notb, nota); @@ -3450,33 +4008,26 @@ expr_ref seq_rewriter::mk_der_op(decl_kind k, expr* a, expr* b) { // transformations hide ite sub-terms, // Rewriting that changes associativity of // operators may hide ite sub-terms. - // - // When either operand is an ite (a derivative BDD), skip the - // pre-simplification: its blind ite-hoisting would bypass the - // pred_implies-based pruning in mk_der_op_rec and create unreachable - // branches such as ite(x = 'a', ite(x = 'b', ...), ...) with 'a' != 'b'. - bool has_ite = m().is_ite(a) || m().is_ite(b); - if (!has_ite) { - switch (k) { - case OP_RE_INTERSECT: - if (BR_FAILED != mk_re_inter0(a, b, result)) - return result; - break; - case OP_RE_UNION: - if (BR_FAILED != mk_re_union0(a, b, result)) - return result; - break; - case OP_RE_CONCAT: - if (BR_FAILED != mk_re_concat(a, b, result)) - return result; - break; - case OP_RE_XOR: - if (BR_FAILED != mk_re_xor0(a, b, result)) - return result; - break; - default: - break; - } + + switch (k) { + case OP_RE_INTERSECT: + if (BR_FAILED != mk_re_inter0(a, b, result)) + return result; + break; + case OP_RE_UNION: + if (BR_FAILED != mk_re_union0(a, b, result)) + return result; + break; + case OP_RE_CONCAT: + if (BR_FAILED != mk_re_concat(a, b, result)) + return result; + break; + case OP_RE_XOR: + if (BR_FAILED != mk_re_xor0(a, b, result)) + return result; + break; + default: + break; } result = m_op_cache.find(k, a, b, nullptr); if (!result) { @@ -3578,6 +4129,230 @@ expr_ref seq_rewriter::mk_der_cond(expr* cond, expr* ele, sort* seq_sort) { return result; } +/* + Classical Brzozowski derivative used by the regex_bisim equivalence + procedure. Unlike `mk_antimirov_deriv`, this variant never creates + _OP_RE_ANTIMIROV_UNION nodes — it stays in a classical (single regex + tree) form. The bisimulation algorithm relies on this so that each + leaf of D(p XOR q) is a coherent XOR pair (D_v p) XOR (D_v q). +*/ +expr_ref seq_rewriter::mk_derivative_rec(expr* ele, expr* r) { + expr_ref result(m()); + sort* seq_sort = nullptr, *ele_sort = nullptr; + VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); + SASSERT(ele_sort == ele->get_sort()); + expr* r1 = nullptr, *r2 = nullptr, *p = nullptr; + auto mk_empty = [&]() { return expr_ref(re().mk_empty(r->get_sort()), m()); }; + unsigned lo = 0, hi = 0; + if (re().is_concat(r, r1, r2)) { + expr_ref is_n = is_nullable(r1); + expr_ref dr1 = mk_derivative_rec(ele, r1); + result = mk_der_concat(dr1, r2); + if (m().is_false(is_n)) { + return result; + } + expr_ref dr2 = mk_derivative_rec(ele, r2); + is_n = re_predicate(is_n, seq_sort); + if (re().is_empty(dr2)) { + //do not concatenate [], it is a deade-end + return result; + } + else { + // Classical Brzozowski union: keep the derivative tree free of + // antimirov-union nodes so the bisimulation procedure sees a + // single regex tree whose leaves are XOR pairs. + return mk_der_union(result, mk_der_concat(is_n, dr2)); + } + } + else if (re().is_star(r, r1)) { + return mk_der_concat(mk_derivative_rec(ele, r1), r); + } + else if (re().is_plus(r, r1)) { + expr_ref star(re().mk_star(r1), m()); + return mk_derivative_rec(ele, star); + } + else if (re().is_union(r, r1, r2)) { + return mk_der_union(mk_derivative_rec(ele, r1), mk_derivative_rec(ele, r2)); + } + else if (re().is_intersection(r, r1, r2)) { + return mk_der_inter(mk_derivative_rec(ele, r1), mk_derivative_rec(ele, r2)); + } + else if (re().is_diff(r, r1, r2)) { + return mk_der_inter(mk_derivative_rec(ele, r1), mk_der_compl(mk_derivative_rec(ele, r2))); + } + else if (re().is_xor(r, r1, r2)) { + return mk_der_xor(mk_derivative_rec(ele, r1), mk_derivative_rec(ele, r2)); + } + else if (m().is_ite(r, p, r1, r2)) { + // there is no BDD normalization here + result = m().mk_ite(p, mk_derivative_rec(ele, r1), mk_derivative_rec(ele, r2)); + return result; + } + else if (re().is_opt(r, r1)) { + return mk_derivative_rec(ele, r1); + } + else if (re().is_complement(r, r1)) { + return mk_der_compl(mk_derivative_rec(ele, r1)); + } + else if (re().is_loop(r, r1, lo)) { + if (lo > 0) { + lo--; + } + result = mk_derivative_rec(ele, r1); + //do not concatenate with [] (emptyset) + if (re().is_empty(result)) { + return result; + } + else { + //do not create loop r1{0,}, instead create r1* + return mk_der_concat(result, (lo == 0 ? re().mk_star(r1) : re().mk_loop(r1, lo))); + } + } + else if (re().is_loop(r, r1, lo, hi)) { + if (hi == 0) { + return mk_empty(); + } + hi--; + if (lo > 0) { + lo--; + } + result = mk_derivative_rec(ele, r1); + //do not concatenate with [] (emptyset) or handle the rest of the loop if no more iterations remain + if (re().is_empty(result) || hi == 0) { + return result; + } + else { + return mk_der_concat(result, re().mk_loop_proper(r1, lo, hi)); + } + } + else if (re().is_full_seq(r) || + re().is_empty(r)) { + return expr_ref(r, m()); + } + else if (re().is_to_re(r, r1)) { + // r1 is a string here (not a regexp) + expr_ref hd(m()), tl(m()); + if (get_head_tail(r1, hd, tl)) { + // head must be equal; if so, derivative is tail + // Use mk_der_cond to normalize + STRACE(seq_verbose, tout << "deriv to_re" << std::endl;); + result = m().mk_eq(ele, hd); + result = mk_der_cond(result, ele, seq_sort); + expr_ref r1(re().mk_to_re(tl), m()); + result = mk_der_concat(result, r1); + return result; + } + else if (str().is_empty(r1)) { + //observe: str().is_empty(r1) checks that r = () = epsilon + //while mk_empty() = [], because deriv(epsilon) = [] = nothing + return mk_empty(); + } + else if (str().is_itos(r1)) { + // + // here r1 = (str.from_int r2) and r2 is non-ground + // or else the expression would have been simplified earlier + // so r1 must be nonempty and must consists of decimal digits + // '0' <= elem <= '9' + // if ((isdigit ele) and (ele = (hd r1))) then (to_re (tl r1)) else [] + // + hd = mk_seq_first(r1); + // isolate nested conjunction for deterministic evaluation + auto a0 = u().mk_le(m_util.mk_char('0'), ele); + auto a1 = u().mk_le(ele, m_util.mk_char('9')); + auto a2 = m().mk_not(m().mk_eq(r1, str().mk_empty(seq_sort))); + auto a3 = m().mk_eq(hd, ele); + auto inner = m().mk_and(a2, a3); + m_br.mk_and(a0, a1, inner, result); + tl = re().mk_to_re(mk_seq_rest(r1)); + return re_and(result, tl); + } + else { + // recall: [] denotes the empty language (nothing) regex, () denotes epsilon or empty sequence + // construct the term (if (r1 != () and (ele = (first r1)) then (to_re (rest r1)) else [])) + hd = mk_seq_first(r1); + m_br.mk_and(m().mk_not(m().mk_eq(r1, str().mk_empty(seq_sort))), m().mk_eq(hd, ele), result); + tl = re().mk_to_re(mk_seq_rest(r1)); + return re_and(result, tl); + } + } + else if (re().is_reverse(r, r1)) { + if (re().is_to_re(r1, r2)) { + // First try to extract hd and tl such that r = hd ++ tl and |tl|=1 + expr_ref hd(m()), tl(m()); + if (get_head_tail_reversed(r2, hd, tl)) { + // Use mk_der_cond to normalize + STRACE(seq_verbose, tout << "deriv reverse to_re" << std::endl;); + result = m().mk_eq(ele, tl); + result = mk_der_cond(result, ele, seq_sort); + result = mk_der_concat(result, re().mk_reverse(re().mk_to_re(hd))); + return result; + } + else if (str().is_empty(r2)) { + return mk_empty(); + } + else { + // construct the term (if (r2 != () and (ele = (last r2)) then reverse(to_re (butlast r2)) else [])) + // hd = first of reverse(r2) i.e. last of r2 + // tl = rest of reverse(r2) i.e. butlast of r2 + //hd = str().mk_nth_i(r2, m_autil.mk_sub(str().mk_length(r2), one())); + hd = mk_seq_last(r2); + // factor nested constructor calls to enforce deterministic argument evaluation order + auto a_non_empty = m().mk_not(m().mk_eq(r2, str().mk_empty(seq_sort))); + auto a_eq = m().mk_eq(hd, ele); + m_br.mk_and(a_non_empty, a_eq, result); + tl = re().mk_to_re(mk_seq_butlast(r2)); + return re_and(result, re().mk_reverse(tl)); + } + } + } + else if (re().is_range(r, r1, r2)) { + // r1, r2 are sequences. + zstring s1, s2; + if (str().is_string(r1, s1) && str().is_string(r2, s2)) { + if (s1.length() == 1 && s2.length() == 1) { + expr_ref ch1(m_util.mk_char(s1[0]), m()); + expr_ref ch2(m_util.mk_char(s2[0]), m()); + // Use mk_der_cond to normalize + STRACE(seq_verbose, tout << "deriv range zstring" << std::endl;); + expr_ref p1(u().mk_le(ch1, ele), m()); + p1 = mk_der_cond(p1, ele, seq_sort); + expr_ref p2(u().mk_le(ele, ch2), m()); + p2 = mk_der_cond(p2, ele, seq_sort); + result = mk_der_inter(p1, p2); + return result; + } + else { + return mk_empty(); + } + } + expr* e1 = nullptr, * e2 = nullptr; + if (str().is_unit(r1, e1) && str().is_unit(r2, e2)) { + SASSERT(u().is_char(e1)); + // Use mk_der_cond to normalize + STRACE(seq_verbose, tout << "deriv range str" << std::endl;); + expr_ref p1(u().mk_le(e1, ele), m()); + p1 = mk_der_cond(p1, ele, seq_sort); + expr_ref p2(u().mk_le(ele, e2), m()); + p2 = mk_der_cond(p2, ele, seq_sort); + result = mk_der_inter(p1, p2); + return result; + } + } + else if (re().is_full_char(r)) { + return expr_ref(re().mk_to_re(str().mk_empty(seq_sort)), m()); + } + else if (re().is_of_pred(r, p)) { + array_util array(m()); + expr* args[2] = { p, ele }; + result = array.mk_select(2, args); + // Use mk_der_cond to normalize + STRACE(seq_verbose, tout << "deriv of_pred" << std::endl;); + return mk_der_cond(result, ele, seq_sort); + } + // stuck cases: re.derivative, re variable, + return expr_ref(re().mk_derivative(ele, r), m()); +} /************************************************* ***** End Derivative Code ***** @@ -4005,17 +4780,6 @@ br_status seq_rewriter::mk_re_concat(expr* a, expr* b, expr_ref& result) { result = b; return BR_DONE; } - // Collapse adjacent full_seq factors regardless of concat grouping: - // (R ++ Σ*) ++ Σ* → R ++ Σ* (a ends with Σ*, b is Σ*) - // Σ* ++ (Σ* ++ R) → Σ* ++ R (a is Σ*, b starts with Σ*) - if (re().is_full_seq(b) && ends_with_full_seq(a)) { - result = a; - return BR_DONE; - } - if (re().is_full_seq(a) && starts_with_full_seq(b)) { - result = b; - return BR_DONE; - } expr* u1 = nullptr, *u2 = nullptr; if (re().is_full_seq(a) && re().is_union(b, u1, u2) && (starts_with_full_seq(u1) || starts_with_full_seq(u2))) { @@ -4057,7 +4821,7 @@ br_status seq_rewriter::mk_re_concat(expr* a, expr* b, expr_ref& result) { result = re().mk_to_re(str().mk_concat(a_str, b_str)); return BR_REWRITE2; } - expr *a1 = nullptr, *a2 = nullptr; + expr* a1 = nullptr; expr* b1 = nullptr; if (re().is_to_re(a, a1) && re().is_to_re(b, b1)) { result = re().mk_to_re(str().mk_concat(a1, b1)); @@ -4078,7 +4842,6 @@ br_status seq_rewriter::mk_re_concat(expr* a, expr* b, expr_ref& result) { } unsigned lo1, hi1, lo2, hi2; - if (re().is_loop(a, a1, lo1, hi1) && lo1 <= hi1 && re().is_loop(b, b1, lo2, hi2) && lo2 <= hi2 && a1 == b1) { result = re().mk_loop_proper(a1, lo1 + lo2, hi1 + hi2); return BR_DONE; @@ -4110,68 +4873,9 @@ br_status seq_rewriter::mk_re_concat(expr* a, expr* b, expr_ref& result) { } std::swap(a, b); } - // Hoist ite out of concat: concat(ite(c, r1, r2), b) → ite(c, concat(r1, b), concat(r2, b)) - expr* c = nullptr; - if (m().is_ite(a, c, a1, b1)) { - result = m().mk_ite(c, re().mk_concat(a1, b), re().mk_concat(b1, b)); - return BR_REWRITE3; - } - if (m().is_ite(b, c, a1, b1)) { - result = m().mk_ite(c, re().mk_concat(a, a1), re().mk_concat(a, b1)); - return BR_REWRITE3; - } - if (re().is_concat(a, a1, a2)) { - // Maintain right-associative normal form: re().mk_concat is a raw - // constructor, so re-simplify the result to recursively reassociate - // any concat nested in a2 (and re-apply concat simplifications). - result = re().mk_concat(a1, re().mk_concat(a2, b)); - return BR_DONE; - } return BR_FAILED; } -expr_ref seq_rewriter::mk_regex_concat(expr *r, expr *s) { - sort *seq_sort = nullptr, *ele_sort = nullptr; - VERIFY(m_util.is_re(r, seq_sort)); - VERIFY(u().is_seq(seq_sort, ele_sort)); - SASSERT(r->get_sort() == s->get_sort()); - expr_ref result(m()); - expr *r1, *r2; - if (re().is_epsilon(r) || re().is_empty(s)) - result = s; - else if (re().is_epsilon(s) || re().is_empty(r)) - result = r; - else if (re().is_full_seq(r) && re().is_full_seq(s)) - result = r; - else if (re().is_full_char(r) && re().is_full_seq(s)) - // ..* = .+ - result = re().mk_plus(re().mk_full_char(r->get_sort())); - else if (re().is_full_seq(r) && re().is_full_char(s)) - // .*. = .+ - result = re().mk_plus(re().mk_full_char(r->get_sort())); - else if (re().is_concat(r, r1, r2)) - // create the resulting concatenation in right-associative form except for the following case - // TODO: maintain the following invariant for A ++ B{m,n} + C - // concat(concat(A, B{m,n}), C) (if A != () and C != ()) - // concat(B{m,n}, C) (if A == () and C != ()) - // where A, B, C are regexes - // Using & below for Intersection and | for Union - // In other words, do not make A ++ B{m,n} into right-assoc form, but keep B{m,n} at the top - // This will help to identify this situation in the merge routine: - // concat(concat(A, B{0,m}), C) | concat(concat(A, B{0,n}), C) - // simplifies to - // concat(concat(A, B{0,max(m,n)}), C) - // analogously: - // concat(concat(A, B{0,m}), C) & concat(concat(A, B{0,n}), C) - // simplifies to - // concat(concat(A, B{0,min(m,n)}), C) - result = mk_regex_concat(r1, mk_regex_concat(r2, s)); - else { - result = re().mk_concat(r, s); - } - return result; -} - bool seq_rewriter::are_complements(expr* r1, expr* r2) const { expr* r = nullptr; if (re().is_complement(r1, r) && r == r2) @@ -4188,32 +4892,6 @@ bool seq_rewriter::is_subset(expr* r1, expr* r2) const { return m_subset.is_subset(r1, r2); } -bool seq_rewriter::try_collapse_re_union(expr* a, expr* b, expr_ref& result) { - sort* seq_sort = nullptr; - if (!u().is_re(a->get_sort(), seq_sort)) - return false; - seq::range_predicate pa(u().max_char()), pb(u().max_char()); - if (!seq::regex_to_range_predicate(u(), a, pa)) - return false; - if (!seq::regex_to_range_predicate(u(), b, pb)) - return false; - result = seq::range_predicate_to_regex(u(), pa | pb, seq_sort); - return true; -} - -bool seq_rewriter::try_collapse_re_inter(expr* a, expr* b, expr_ref& result) { - sort* seq_sort = nullptr; - if (!u().is_re(a->get_sort(), seq_sort)) - return false; - seq::range_predicate pa(u().max_char()), pb(u().max_char()); - if (!seq::regex_to_range_predicate(u(), a, pa)) - return false; - if (!seq::regex_to_range_predicate(u(), b, pb)) - return false; - result = seq::range_predicate_to_regex(u(), pa & pb, seq_sort); - return true; -} - br_status seq_rewriter::mk_re_union0(expr* a, expr* b, expr_ref& result) { if (a == b) { result = a; @@ -4243,30 +4921,11 @@ br_status seq_rewriter::mk_re_union0(expr* a, expr* b, expr_ref& result) { result = b; return BR_DONE; } - // r ∪ ~r → Σ* (complement absorption) - if (are_complements(a, b)) { - result = re().mk_full_seq(a->get_sort()); - return BR_DONE; - } - // Hoist ite out of union: union(ite(c, r1, r2), b) → ite(c, union(r1, b), union(r2, b)) - expr *c = nullptr, *r1 = nullptr, *r2 = nullptr; - if (m().is_ite(a, c, r1, r2)) { - result = m().mk_ite(c, re().mk_union(r1, b), re().mk_union(r2, b)); - return BR_REWRITE3; - } - if (m().is_ite(b, c, r1, r2)) { - result = m().mk_ite(c, re().mk_union(a, r1), re().mk_union(a, r2)); - return BR_REWRITE3; - } - if (try_collapse_re_union(a, b, result)) - return BR_DONE; return BR_FAILED; } /* Creates a normalized union. */ br_status seq_rewriter::mk_re_union(expr* a, expr* b, expr_ref& result) { - if (try_collapse_re_union(a, b, result)) - return BR_DONE; result = mk_regex_union_normalize(a, b); return BR_DONE; } @@ -4304,11 +4963,42 @@ br_status seq_rewriter::mk_re_complement(expr* a, expr_ref& result) { result = re().mk_plus(re().mk_full_char(a->get_sort())); return BR_DONE; } - // Hoist ite out of complement: ~(ite(c, r1, r2)) → ite(c, ~r1, ~r2) - expr* c = nullptr; - if (m().is_ite(a, c, e1, e2)) { - result = m().mk_ite(c, re().mk_complement(e1), re().mk_complement(e2)); - return BR_REWRITE3; + // Range complement: comp([a,b]) → [0,a-1] ∪ [b+1,max] (or one half when a=0 or b=max) + unsigned lo_v = 0, hi_v = 0; + if (re().is_range(a, lo_v, hi_v)) { + unsigned max_c = u().max_char(); + sort *srt = a->get_sort(), *seq_sort = nullptr; + VERIFY(m_util.is_re(a, seq_sort)); + bool has_left = (lo_v > 0); + bool has_right = (hi_v < max_c); + auto empty_re = [&]() { return re().mk_empty(srt); }; + auto len0_re = [&]() { return re().mk_to_re(str().mk_empty(seq_sort)); }; + auto full_re = [&]() { return re().mk_full_seq(srt); }; + auto len2_plus_re = [&]() { return re().mk_concat(re().mk_full_char(srt), re().mk_plus(re().mk_full_char(srt))); }; + if (!has_left && !has_right) { + // [0, max_c]: complement is empty + result = empty_re(); + return BR_DONE; + } + if (lo_v > hi_v) { + result = full_re(); + return BR_DONE; + } + if (!has_left) { + // [0, b]: complement is [b+1, max] + result = re().mk_union(len0_re(), re().mk_union(re().mk_range(srt, hi_v + 1, max_c), len2_plus_re())); + return BR_DONE; + } + if (!has_right) { + // [a, max]: complement is [0, a-1] + result = re().mk_union(len0_re(), re().mk_union(re().mk_range(srt, 0u, lo_v - 1), len2_plus_re())); + return BR_DONE; + } + // General: [a, b] → [0, a-1] ∪ [b+1, max] + auto left = re().mk_range(srt, 0u, lo_v - 1); + auto right = re().mk_range(srt, hi_v + 1, max_c); + result = re().mk_union(len0_re(), re().mk_union(left, re().mk_union(right, len2_plus_re()))); + return BR_DONE; } return BR_FAILED; } @@ -4335,43 +5025,16 @@ br_status seq_rewriter::mk_re_inter0(expr* a, expr* b, expr_ref& result) { result = a; return BR_DONE; } - // r ∩ ~r → ∅ (complement absorption) - if (are_complements(a, b)) { - result = re().mk_empty(a->get_sort()); - return BR_DONE; - } - // Hoist ite out of intersection: inter(ite(c, r1, r2), b) → ite(c, inter(r1, b), inter(r2, b)) - expr *c = nullptr, *r1 = nullptr, *r2 = nullptr; - if (m().is_ite(a, c, r1, r2)) { - result = m().mk_ite(c, re().mk_inter(r1, b), re().mk_inter(r2, b)); - return BR_REWRITE3; - } - if (m().is_ite(b, c, r1, r2)) { - result = m().mk_ite(c, re().mk_inter(a, r1), re().mk_inter(a, r2)); - return BR_REWRITE3; - } - if (try_collapse_re_inter(a, b, result)) - return BR_DONE; return BR_FAILED; } /* Creates a normalized intersection. */ br_status seq_rewriter::mk_re_inter(expr* a, expr* b, expr_ref& result) { - if (try_collapse_re_inter(a, b, result)) - return BR_DONE; result = mk_regex_inter_normalize(a, b); return BR_DONE; } br_status seq_rewriter::mk_re_diff(expr* a, expr* b, expr_ref& result) { - seq::range_predicate pa(u().max_char()), pb(u().max_char()); - sort* seq_sort = nullptr; - if (u().is_re(a->get_sort(), seq_sort) - && seq::regex_to_range_predicate(u(), a, pa) - && seq::regex_to_range_predicate(u(), b, pb)) { - result = seq::range_predicate_to_regex(u(), pa - pb, seq_sort); - return BR_DONE; - } result = mk_regex_inter_normalize(a, re().mk_complement(b)); return BR_REWRITE2; } @@ -4601,9 +5264,7 @@ br_status seq_rewriter::mk_re_star(expr* a, expr_ref& result) { result = re().mk_full_seq(b1->get_sort()); return BR_REWRITE2; } - // Hoist ite out of star: (ite c r1 r2)* → ite(c, r1*, r2*) - result = m().mk_ite(c, re().mk_star(b1), re().mk_star(c1)); - return BR_REWRITE3; + } return BR_FAILED; } @@ -5788,7 +6449,7 @@ void seq_rewriter::op_cache::cleanup() { lbool seq_rewriter::some_string_in_re(expr* r, zstring& s) { sort* rs; (void)rs; - // SASSERT(u().is_re(r, rs) && m_util.is_string(rs)); + // SASSERT(re().is_re(r, rs) && m_util.is_string(rs)); expr_mark visited; unsigned_vector str; diff --git a/src/ast/rewriter/seq_rewriter.h b/src/ast/rewriter/seq_rewriter.h index fc47f2c636..1b693ca3d6 100644 --- a/src/ast/rewriter/seq_rewriter.h +++ b/src/ast/rewriter/seq_rewriter.h @@ -19,7 +19,6 @@ Notes: #pragma once #include "ast/seq_decl_plugin.h" -#include "ast/rewriter/seq_derive.h" #include "ast/ast_pp.h" #include "ast/arith_decl_plugin.h" #include "ast/rewriter/rewriter_types.h" @@ -129,20 +128,15 @@ class seq_rewriter { void insert(decl_kind op, expr* a, expr* b, expr* c, expr* r); }; - friend class seq::derive; - seq_util m_util; seq_subset m_subset; arith_util m_autil; bool_rewriter m_br; - seq::derive m_derive; // re2automaton m_re2aut; op_cache m_op_cache; expr_ref_vector m_es, m_lhs, m_rhs; - bool m_coalesce_chars = false; - bool m_in_bisim { false }; - unsigned m_re_deriv_depth { 0 }; - static const unsigned m_max_re_deriv_depth = 512; + bool m_coalesce_chars; + bool m_in_bisim { false }; enum length_comparison { shorter_c, @@ -185,6 +179,8 @@ class seq_rewriter { expr_ref re_replace_char(expr *r, unsigned a_ch, unsigned b_ch, expr *a_str, expr *b_str); // Calculate derivative, memoized and enforcing a normal form + expr_ref is_nullable_rec(expr* r); + expr_ref mk_derivative_rec(expr* ele, expr* r); expr_ref mk_der_op(decl_kind k, expr* a, expr* b); expr_ref mk_der_op_rec(decl_kind k, expr* a, expr* b); expr_ref mk_der_concat(expr* a, expr* b); @@ -195,10 +191,26 @@ class seq_rewriter { expr_ref mk_der_cond(expr* cond, expr* ele, sort* seq_sort); expr_ref mk_der_antimirov_union(expr* r1, expr* r2); bool ite_bdds_compatible(expr* a, expr* b); + /* if r has the form deriv(en..deriv(e1,to_re(s))..) returns 's = [e1..en]' else returns '() in r'*/ + expr_ref is_nullable_symbolic_regex(expr* r, sort* seq_sort); #ifdef Z3DEBUG bool check_deriv_normal_form(expr* r, int level = 3); #endif + void mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref& result); + + expr_ref mk_antimirov_deriv(expr* e, expr* r, expr* path); + expr_ref mk_in_antimirov_rec(expr* s, expr* d); + expr_ref mk_in_antimirov(expr* s, expr* d); + + expr_ref mk_antimirov_deriv_intersection(expr* elem, expr* d1, expr* d2, expr* path); + expr_ref mk_antimirov_deriv_concat(expr* d, expr* r); + expr_ref mk_antimirov_deriv_negate(expr* elem, expr* d); + expr_ref mk_antimirov_deriv_union(expr* d1, expr* d2); + expr_ref mk_antimirov_deriv_restrict(expr* elem, expr* d1, expr* cond); + expr_ref mk_regex_reverse(expr* r); + expr_ref mk_regex_concat(expr* r1, expr* r2); + expr_ref merge_regex_sets(expr* r1, expr* r2, expr* unit, std::function& decompose, std::function& compose); // elem is (:var 0) and path a condition that may have (:var 0) as a free variable @@ -255,14 +267,6 @@ class seq_rewriter { br_status mk_re_union0(expr* a, expr* b, expr_ref& result); br_status mk_re_inter0(expr* a, expr* b, expr_ref& result); br_status mk_re_complement(expr* a, expr_ref& result); - // Range-set collapse helpers: if the operands form a boolean - // combination of character-class regexes, materialize the result as a - // canonical regex over a single range_predicate. See - // ast/rewriter/seq_range_collapse.h for the recognized fragment. - // NOTE: re.complement is intentionally not in this set because it - // operates at the sequence level, not the character-class level. - bool try_collapse_re_union(expr* a, expr* b, expr_ref& result); - bool try_collapse_re_inter(expr* a, expr* b, expr_ref& result); br_status mk_re_star(expr* a, expr_ref& result); br_status mk_re_diff(expr* a, expr* b, expr_ref& result); br_status mk_re_xor(expr* a, expr* b, expr_ref& result); @@ -347,9 +351,9 @@ class seq_rewriter { public: seq_rewriter(ast_manager & m, params_ref const & p = params_ref()): - m_util(m), m_subset(m_util.re), m_autil(m), m_br(m, p), m_derive(m, *this), + m_util(m), m_subset(m_util.re), m_autil(m), m_br(m, p), // m_re2aut(m), m_op_cache(m), m_es(m), - m_lhs(m), m_rhs(m) { + m_lhs(m), m_rhs(m), m_coalesce_chars(true) { } ast_manager & m() const { return m_util.get_manager(); } family_id get_fid() const { return m_util.get_family_id(); } @@ -360,7 +364,7 @@ public: static void get_param_descrs(param_descrs & r); - // bool coalesce_chars() const { return m_coalesce_chars; } + bool coalesce_chars() const { return m_coalesce_chars; } br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); @@ -376,34 +380,6 @@ public: return result; } - expr_ref mk_xor0(expr *a, expr *b) { - expr_ref result(m()); - if (mk_re_xor0(a, b, result) == BR_FAILED) - result = re().mk_xor(a, b); - return result; - } - - expr_ref mk_union(expr *a, expr *b) { - expr_ref result(m()); - if (mk_re_union(a, b, result) == BR_FAILED) - result = re().mk_union(a, b); - return result; - } - - expr_ref mk_inter(expr *a, expr *b) { - expr_ref result(m()); - if (mk_re_inter(a, b, result) == BR_FAILED) - result = re().mk_inter(a, b); - return result; - } - - expr_ref mk_complement(expr *a) { - expr_ref result(m()); - if (mk_re_complement(a, result) == BR_FAILED) - result = re().mk_complement(a); - return result; - } - /* * makes concat and simplifies */ @@ -464,31 +440,7 @@ public: procedure which relies on each leaf of D(p XOR q) being a coherent XOR pair (D_v p) XOR (D_v q). */ - expr_ref mk_brz_derivative(expr *r) { - return mk_derivative(r); - } - - /* - Enumerate the cofactors (min-terms) of a transition regex r taken with - respect to ele. Produces (path_condition, leaf_regex) pairs for every - feasible path through the ITE-tree, pruning infeasible character ranges. - Delegates to the derivative engine so the same path/interval context used - while hoisting ITEs is reused for the leaf simplification. - */ - void get_cofactors(expr* ele, expr* r, expr_ref_pair_vector& result) { - m_derive.get_cofactors(ele, r, result); - } - - /* - Compute the symbolic derivative of r and enumerate its reachable leaves - in fully ITE-hoisted normal form: a list of (path_condition, target) - pairs where every target is free of (:var 0) (so nullability is always - decidable) and unions are kept intact as single states. Used by - regex_bisim, which consumes the targets and ignores the path conditions. - */ - void brz_derivative_cofactors(expr* r, expr_ref_pair_vector& result) { - m_derive.derivative_cofactors(r, result); - } + expr_ref mk_brz_derivative(expr* r); // heuristic elimination of element from condition that comes form a derivative. // special case optimization for conjunctions of equalities, disequalities and ranges. @@ -499,8 +451,6 @@ public: /* Apply simplifications to the intersection to keep it normalized (r1 and r2 are not normalized)*/ expr_ref mk_regex_inter_normalize(expr* r1, expr* r2); - expr_ref mk_regex_concat(expr *r1, expr *r2); - /* * Extract some string that is a member of r. * Return true if a valid string was extracted. diff --git a/src/ast/rewriter/seq_subset.cpp b/src/ast/rewriter/seq_subset.cpp index 1af42d06d8..2fc4d1f715 100644 --- a/src/ast/rewriter/seq_subset.cpp +++ b/src/ast/rewriter/seq_subset.cpp @@ -19,7 +19,7 @@ Author: bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const { while (true) { - + if (a == b) return true; if (m_re.is_empty(a)) @@ -30,7 +30,7 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const { return true; if (depth >= m_max_depth) - return false; + return false; expr* a1 = nullptr, * a2 = nullptr, * b1 = nullptr, * b2 = nullptr; unsigned la, ua, lb, ub; @@ -39,12 +39,16 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const { if (m_re.is_dot_plus(b) && m_re.get_info(a).nullable == l_false) return true; + // a ⊆ a* + if (m_re.is_star(b, b1) && is_subset_rec(a, b1, depth)) + return true; + // e ⊆ a* if (m_re.is_epsilon(a) && m_re.is_star(b, b1)) return true; - // a ⊆ a*: if b = b1* and a ⊆ b1, then a ⊆ b1* - if (m_re.is_star(b, b1) && is_subset_rec(a, b1, depth)) + // R ⊆ R* + if (m_re.is_star(b, b1) && is_subset_rec(a, b1, depth + 1)) return true; // R1* ⊆ R2* if R1 ⊆ R2 @@ -108,12 +112,6 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const { if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b1) && is_subset_rec(a, b2, depth)) return true; - // prefix absorption: P·R' ⊆ Σ*·R' for any prefix P (since P ⊆ Σ*). - // Detect that a has R' (= b2) as a concatenation suffix, where b = Σ*·R'. - // Covers contains-patterns, e.g. Σ*·a·Σ*·b·Σ* ⊆ Σ*·b·Σ*. - if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b1) && ends_with(a, b2)) - return true; - // R ⊆ R'·Σ* if R ⊆ R' if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b2) && is_subset_rec(a, b1, depth)) return true; @@ -146,30 +144,3 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const { bool seq_subset::is_subset(expr* a, expr* b) const { return is_subset_rec(a, b, 0); } - -bool seq_subset::ends_with(expr* a, expr* suf) const { - if (a == suf) - return true; - // Flatten both regexes into their sequence of concatenation factors - // (independent of left/right associativity) and test list-suffix equality. - ptr_vector af, sf; - flatten_concat(a, af); - flatten_concat(suf, sf); - if (sf.size() > af.size()) - return false; - unsigned off = af.size() - sf.size(); - for (unsigned i = 0; i < sf.size(); ++i) - if (af[off + i] != sf[i]) - return false; - return true; -} - -void seq_subset::flatten_concat(expr* a, ptr_vector& out) const { - expr* a1 = nullptr, * a2 = nullptr; - if (m_re.is_concat(a, a1, a2)) { - flatten_concat(a1, out); - flatten_concat(a2, out); - } - else - out.push_back(a); -} diff --git a/src/ast/rewriter/seq_subset.h b/src/ast/rewriter/seq_subset.h index e62333dea3..7329c898e1 100644 --- a/src/ast/rewriter/seq_subset.h +++ b/src/ast/rewriter/seq_subset.h @@ -24,12 +24,6 @@ class seq_subset { bool is_subset_rec(expr* a, expr* b, unsigned depth) const; - // true if regex a, viewed as a flattened concatenation, has suf as a - // structural (concatenation) suffix. - bool ends_with(expr* a, expr* suf) const; - - void flatten_concat(expr* a, ptr_vector& out) const; - public: explicit seq_subset(seq_util::rex& re) : m_re(re) {} bool is_subset(expr* a, expr* b) const; diff --git a/src/smt/seq_regex.cpp b/src/smt/seq_regex.cpp index 5a801550cd..0e9a03b633 100644 --- a/src/smt/seq_regex.cpp +++ b/src/smt/seq_regex.cpp @@ -461,24 +461,6 @@ namespace smt { if (re().is_empty(r)) //trivially true return; - // When one side is re.none the equation is a pure emptiness check on - // the other regex (symmetric_diff already returned it as r). Decide - // it directly by antimirov NFA reachability instead of running the - // bisimulation/XOR closure, which would build large un-canonicalized - // product states for intersections of contains-patterns. - if ((re().is_empty(r1) || re().is_empty(r2)) && is_ground(r)) { - switch (re_is_empty(r)) { - case l_true: - STRACE(seq_regex_brief, tout << "empty:eq ";); - return; // languages equal (both empty): trivially true - case l_false: - STRACE(seq_regex_brief, tout << "empty:neq ";); - th.add_axiom(~th.mk_eq(r1, r2, false), false_literal); - return; - case l_undef: - break; - } - } // Try the bisimulation procedure on ground regexes first. If it // returns a definite answer, dispatch the corresponding axiom and // bypass the symbolic emptiness/derivative closure. @@ -580,7 +562,7 @@ namespace smt { lits.push_back(null_lit); expr_ref_pair_vector cofactors(m); - seq_rw().get_cofactors(hd, d, cofactors); + get_cofactors(d, cofactors); for (auto const& p : cofactors) { if (is_member(p.second, u)) continue; @@ -689,67 +671,6 @@ namespace smt { return result; } - /* - Decide emptiness of a ground regex r via antimirov-mode NFA - reachability. - - The symbolic derivative engine runs in antimirov mode, so the - derivative of an intersection distributes into a *set* of individual - product states inter(A_i, B_j) (each a small, ground regex) rather - than one giant union-of-intersections term. get_derivative_targets - enumerates these NFA successor states. - - We short-circuit to l_false (non-empty) as soon as a reachable state - is nullable (accepts the empty word) or classical (a regex built only - from to_re/all/union/concat/star/plus/opt/loop, hence non-empty). An - intersection itself is never classical, but once one operand reduces - to Σ* the intersection collapses (via the derivative's subset - simplification) to the other, classical, operand. - - If the worklist is exhausted with no such state, r is empty (l_true). - Returns l_undef if a step bound is hit, so callers can fall back to - the general procedure. - */ - lbool seq_regex::re_is_empty(expr* r) { - if (re().is_empty(r)) - return l_true; - expr_ref_vector pinned(m); - obj_hashtable visited; - ptr_vector work; - work.push_back(r); - visited.insert(r); - pinned.push_back(r); - unsigned const bound = 100000; - unsigned steps = 0; - while (!work.empty()) { - if (++steps > bound) - return l_undef; - expr* s = work.back(); - work.pop_back(); - auto info = re().get_info(s); - if (!info.is_known()) - return l_undef; - // ε ∈ L(s) or s is a non-empty classical regex ⇒ L(r) non-empty. - if (info.nullable == l_true || info.classical) - return l_false; - // Dead state: prune (min_length == UINT_MAX means no word is - // accepted from here). - if (info.min_length == UINT_MAX) - continue; - expr_ref_vector targets(m); - get_derivative_targets(s, targets); - for (expr* t : targets) { - if (visited.contains(t)) - continue; - visited.insert(t); - pinned.push_back(t); - work.push_back(t); - } - } - return l_true; - } - - /* Return a list of all target regexes in the derivative of a regex r, ignoring the conditions along each path. @@ -786,26 +707,53 @@ namespace smt { /* Return a list of all (cond, leaf) pairs in a given derivative - expression r, where elem is the character symbol the derivative was - taken with respect to. + expression r. - The transition regexes produced by the symbolic derivative engine are - ITE-trees over character predicates ci on elem (equalities such as - elem = 'A', and ranges such as 'a' <= elem <= 'z'). These predicates - are typically mutually exclusive, so the number of feasible truth - assignments to {c1,..,ck} ("minterms") is small. + Note: this implementation is inefficient: it simply collects all expressions under an if and + iterates over all combinations. - The enumeration is delegated to seq::derive (via seq_rw().get_cofactors) - so it reuses the very same path/interval context that the derivative - engine uses while hoisting ITEs: each feasible path through the ITE-tree - yields one (path_condition, leaf) cofactor, infeasible character-range - combinations are pruned, and the leaf is simplified with the path-aware - smart constructors. - - This is used by: + This method is still used by: propagate_is_empty propagate_is_non_empty */ + void seq_regex::get_cofactors(expr* r, expr_ref_pair_vector& result) { + obj_hashtable ifs; + expr* cond = nullptr, * r1 = nullptr, * r2 = nullptr; + for (expr* e : subterms::ground(expr_ref(r, m))) + if (m.is_ite(e, cond, r1, r2)) + ifs.insert(cond); + + expr_ref_vector rs(m); + vector conds; + conds.push_back(expr_ref_vector(m)); + rs.push_back(r); + for (expr* c : ifs) { + unsigned sz = conds.size(); + expr_safe_replace rep1(m); + expr_safe_replace rep2(m); + rep1.insert(c, m.mk_true()); + rep2.insert(c, m.mk_false()); + expr_ref r2(m); + for (unsigned i = 0; i < sz; ++i) { + expr_ref_vector cs = conds[i]; + cs.push_back(mk_not(m, c)); + conds.push_back(cs); + conds[i].push_back(c); + expr_ref r1(rs.get(i), m); + rep1(r1, r2); + rs[i] = r2; + rep2(r1, r2); + rs.push_back(r2); + } + } + for (unsigned i = 0; i < conds.size(); ++i) { + expr_ref conj = mk_and(conds[i]); + expr_ref r(rs.get(i), m); + ctx.get_rewriter()(r); + if (!m.is_false(conj) && !re().is_empty(r)) + result.push_back(conj, r); + } + } /* is_empty(r, u) => ~is_nullable(r) @@ -833,7 +781,7 @@ namespace smt { d = mk_derivative_wrapper(hd, r); literal_vector lits; expr_ref_pair_vector cofactors(m); - seq_rw().get_cofactors(hd, d, cofactors); + get_cofactors(d, cofactors); for (auto const& p : cofactors) { if (is_member(p.second, u)) continue; diff --git a/src/smt/seq_regex.h b/src/smt/seq_regex.h index dd1c474b31..5c3fddd252 100644 --- a/src/smt/seq_regex.h +++ b/src/smt/seq_regex.h @@ -164,12 +164,7 @@ namespace smt { // returned by derivative_wrapper expr_ref mk_deriv_accept(expr* s, unsigned i, expr* r); void get_derivative_targets(expr* r, expr_ref_vector& targets); - - // Decide emptiness of a ground regex by antimirov-mode NFA - // reachability: explore derivative target states, short-circuiting to - // "non-empty" on the first reachable nullable or classical state. - // Returns l_true (empty), l_false (non-empty), l_undef (gave up). - lbool re_is_empty(expr* r); + void get_cofactors(expr* r, expr_ref_pair_vector& result); /* Pretty print the regex of the state id to the out stream, diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index e26f17cf44..404cf45538 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -115,18 +115,15 @@ add_executable(test-z3 polynomial_factorization.cpp polynorm.cpp prime_generator.cpp - seq_regex_bisim.cpp proof_checker.cpp qe_arith.cpp mbp_qel.cpp quant_elim.cpp quant_solve.cpp random.cpp - range_predicate.cpp rational.cpp rcf.cpp region.cpp - regex_range_collapse.cpp sat_local_search.cpp sat_lookahead.cpp sat_user_scope.cpp diff --git a/src/test/main.cpp b/src/test/main.cpp index dc5854da7c..b78e387892 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -113,8 +113,6 @@ X(api_bug) \ X(api_special_relations) \ X(arith_rewriter) \ - X(range_predicate) \ - X(regex_range_collapse) \ X(seq_rewriter) \ X(check_assumptions) \ X(smt_context) \ @@ -197,7 +195,6 @@ X(finite_set) \ X(finite_set_rewriter) \ X(fpa) \ - X(seq_regex_bisim) \ X(term_enumeration) \ X(lcube) diff --git a/src/test/range_predicate.cpp b/src/test/range_predicate.cpp deleted file mode 100644 index e526a63302..0000000000 --- a/src/test/range_predicate.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/*++ -Copyright (c) 2026 Microsoft Corporation - -Module Name: - - test/range_predicate.cpp - -Abstract: - - Unit tests for the range-algebra value type seq::range_predicate. - - The tests exercise: - * factory constructors and canonical-form invariants, - * extensional equality and total ordering, - * Boolean operations (|, &, ~, -, ^) on hand-picked instances, - * exhaustive verification of de-Morgan and lattice laws on a - small character domain, by enumerating every subset. - -Author: - - Margus Veanes (veanes) 2026 - ---*/ - -#include "ast/rewriter/seq_range_predicate.h" -#include "util/debug.h" -#include -#include -#include - -using seq::range_predicate; - -namespace { - - // Build a range_predicate from a bitmask over [0, max_char] for testing. - range_predicate from_mask(uint64_t mask, unsigned max_char) { - range_predicate r = range_predicate::empty(max_char); - for (unsigned c = 0; c <= max_char; ++c) - if ((mask >> c) & 1u) - r = r | range_predicate::singleton(c, max_char); - return r; - } - - // Convert a range_predicate back to a bitmask for cross-checking. - uint64_t to_mask(range_predicate const& r) { - uint64_t mask = 0; - for (unsigned c = 0; c <= r.max_char(); ++c) - if (r.contains(c)) - mask |= (uint64_t(1) << c); - return mask; - } - - void test_factories() { - auto e = range_predicate::empty(255); - ENSURE(e.is_empty()); - ENSURE(!e.is_top()); - ENSURE(e.num_ranges() == 0); - ENSURE(e.cardinality() == 0); - - auto t = range_predicate::top(255); - ENSURE(!t.is_empty()); - ENSURE(t.is_top()); - ENSURE(t.num_ranges() == 1); - ENSURE(t.cardinality() == 256); - ENSURE(t.contains(0)); - ENSURE(t.contains(255)); - - auto s = range_predicate::singleton(42, 255); - ENSURE(s.num_ranges() == 1); - ENSURE(s.cardinality() == 1); - ENSURE(s.contains(42)); - ENSURE(!s.contains(41)); - unsigned c = 0; - ENSURE(s.is_singleton(c)); - ENSURE(c == 42); - - auto r = range_predicate::range(10, 20, 255); - ENSURE(r.num_ranges() == 1); - ENSURE(r.cardinality() == 11); - ENSURE(r.contains(10)); - ENSURE(r.contains(20)); - ENSURE(!r.contains(9)); - ENSURE(!r.contains(21)); - - // Reversed bounds produce empty. - auto bad = range_predicate::range(20, 10, 255); - ENSURE(bad.is_empty()); - - // Clipping at max_char. - auto clipped = range_predicate::range(200, 1000, 255); - ENSURE(clipped.num_ranges() == 1); - ENSURE(clipped[0] == std::make_pair(200u, 255u)); - } - - void test_equality_and_order() { - auto a = range_predicate::range(1, 5, 31); - auto b = range_predicate::range(1, 5, 31); - auto c = range_predicate::range(1, 6, 31); - ENSURE(a == b); - ENSURE(a != c); - ENSURE(a.hash() == b.hash()); - ENSURE(a < c || c < a); - ENSURE(!(a < a)); - - auto empty = range_predicate::empty(31); - ENSURE(empty < a); - - // Canonical merging of adjacent ranges. - auto d = range_predicate::range(0, 4, 31) | range_predicate::range(5, 10, 31); - auto e = range_predicate::range(0, 10, 31); - ENSURE(d == e); - } - - void test_union_intersection_hand() { - unsigned const M = 31; - auto a = range_predicate::range(0, 4, M) | range_predicate::range(10, 14, M); - auto b = range_predicate::range(3, 11, M); - - auto u = a | b; // [0,14] - ENSURE(u.num_ranges() == 1); - ENSURE(u[0] == std::make_pair(0u, 14u)); - - auto i = a & b; // [3,4] U [10,11] - ENSURE(i.num_ranges() == 2); - ENSURE(i[0] == std::make_pair(3u, 4u)); - ENSURE(i[1] == std::make_pair(10u, 11u)); - - auto d = a - b; // [0,2] U [12,14] - ENSURE(d.num_ranges() == 2); - ENSURE(d[0] == std::make_pair(0u, 2u)); - ENSURE(d[1] == std::make_pair(12u, 14u)); - - auto x = a ^ b; // [0,2] U [5,9] U [12,14] - ENSURE(x.num_ranges() == 3); - ENSURE(x[0] == std::make_pair(0u, 2u)); - ENSURE(x[1] == std::make_pair(5u, 9u)); - ENSURE(x[2] == std::make_pair(12u, 14u)); - } - - void test_complement_hand() { - unsigned const M = 10; - auto e = range_predicate::empty(M); - ENSURE((~e).is_top()); - auto t = range_predicate::top(M); - ENSURE((~t).is_empty()); - - // ~([2,3] U [7,8]) = [0,1] U [4,6] U [9,10] - auto a = range_predicate::range(2, 3, M) | range_predicate::range(7, 8, M); - auto na = ~a; - ENSURE(na.num_ranges() == 3); - ENSURE(na[0] == std::make_pair(0u, 1u)); - ENSURE(na[1] == std::make_pair(4u, 6u)); - ENSURE(na[2] == std::make_pair(9u, 10u)); - - // ~([0,4]) = [5,10] - auto b = range_predicate::range(0, 4, M); - auto nb = ~b; - ENSURE(nb.num_ranges() == 1); - ENSURE(nb[0] == std::make_pair(5u, 10u)); - - // ~([5,10]) = [0,4] - auto cnb = ~nb; - ENSURE(cnb == b); - } - - // Exhaustively verify the lattice / de-Morgan laws on a small domain - // by enumerating every possible subset (bitmask). - void test_exhaustive_laws() { - unsigned const M = 5; // 6 characters -> 64 subsets - unsigned const N = 1u << (M + 1); - for (unsigned i = 0; i < N; ++i) { - range_predicate A = from_mask(i, M); - ENSURE(to_mask(A) == i); - // ~ ~ A == A - ENSURE(~~A == A); - // A | ~A == top - ENSURE((A | ~A).is_top()); - // A & ~A == empty - ENSURE((A & ~A).is_empty()); - // cardinality matches popcount - unsigned pop = 0; - for (unsigned k = 0; k <= M; ++k) if ((i >> k) & 1u) ++pop; - ENSURE(A.cardinality() == pop); - } - for (unsigned i = 0; i < N; ++i) { - range_predicate A = from_mask(i, M); - for (unsigned j = 0; j < N; ++j) { - range_predicate B = from_mask(j, M); - // Bitmask reference semantics. - ENSURE(to_mask(A | B) == (i | j)); - ENSURE(to_mask(A & B) == (i & j)); - ENSURE(to_mask(A - B) == (i & ~j & ((1u << (M + 1)) - 1u))); - ENSURE(to_mask(A ^ B) == (i ^ j)); - // de-Morgan - ENSURE(~(A | B) == (~A & ~B)); - ENSURE(~(A & B) == (~A | ~B)); - // Commutativity - ENSURE((A | B) == (B | A)); - ENSURE((A & B) == (B & A)); - // (A - B) == A & ~B - ENSURE((A - B) == (A & ~B)); - // (A ^ B) == (A | B) - (A & B) - ENSURE((A ^ B) == ((A | B) - (A & B))); - // Extensional equality is reflexive on equal masks. - if (i == j) { - ENSURE(A == B); - ENSURE(A.hash() == B.hash()); - } - } - } - } - - void test_total_order_strict() { - unsigned const M = 5; - unsigned const N = 1u << (M + 1); - // Strict total order: for any distinct A, B exactly one of A - -namespace { - - using seq::range_predicate; - using seq::regex_to_range_predicate; - using seq::range_predicate_to_regex; - - static void check(bool ok, char const* what) { - if (!ok) { - std::cerr << "regex_range_collapse FAILED: " << what << "\n"; - ENSURE(false); - } - } - - static expr_ref mk_singleton_str(seq_util& u, unsigned c) { - return expr_ref(u.str.mk_string(zstring(c)), u.get_manager()); - } - - static bool extract_range_chars(seq_util& u, expr* e, unsigned& lo, unsigned& hi) { - expr* lo_e = nullptr; expr* hi_e = nullptr; - if (!u.re.is_range(e, lo_e, hi_e)) - return false; - // Accept either string-constant or (seq.unit (Char N)) bound form. - if (u.re.is_range(e, lo, hi)) - return true; - expr* lc = nullptr; expr* hc = nullptr; - if (u.str.is_unit(lo_e, lc) && u.is_const_char(lc, lo) && - u.str.is_unit(hi_e, hc) && u.is_const_char(hc, hi)) - return true; - return false; - } - - static void run() { - ast_manager m; - reg_decl_plugins(m); - seq_util u(m); - unsigned const M = u.max_char(); - - sort* str_sort = u.str.mk_string_sort(); - sort* re_sort = u.re.mk_re(str_sort); - - // primitives - { - range_predicate p(M); - check(regex_to_range_predicate(u, u.re.mk_empty(re_sort), p) && p.is_empty(), - "re.empty -> empty"); - check(regex_to_range_predicate(u, u.re.mk_full_char(re_sort), p) && p.is_top(), - "re.full_char -> top"); - } - // re.range "a" "z" - { - range_predicate p(M); - expr_ref a = mk_singleton_str(u, 'a'); - expr_ref z = mk_singleton_str(u, 'z'); - expr_ref r(u.re.mk_range(a, z), m); - check(regex_to_range_predicate(u, r, p) && p.num_ranges() == 1 && - p[0].first == 'a' && p[0].second == 'z', - "re.range a z -> [a,z]"); - } - // Disjoint union: (a..z) | (0..9) - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m); - expr_ref r2(u.re.mk_range(mk_singleton_str(u, '0'), mk_singleton_str(u, '9')), m); - expr_ref un(u.re.mk_union(r1, r2), m); - check(regex_to_range_predicate(u, un, p) && p.num_ranges() == 2, - "(a-z)|(0-9) -> 2 ranges"); - // canonical order: lower lo first - check(p[0].first == '0' && p[0].second == '9' && p[1].first == 'a' && p[1].second == 'z', - "(a-z)|(0-9) ranges in canonical order"); - } - // Overlapping union: (a..c) | (b..f) -> (a..f) - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'c')), m); - expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'b'), mk_singleton_str(u, 'f')), m); - expr_ref un(u.re.mk_union(r1, r2), m); - check(regex_to_range_predicate(u, un, p) && p.num_ranges() == 1 && - p[0].first == 'a' && p[0].second == 'f', - "(a-c)|(b-f) -> (a-f)"); - } - // Adjacent union: (a..c) | (d..f) -> (a..f) (canonical predicate merges adjacent) - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'c')), m); - expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'd'), mk_singleton_str(u, 'f')), m); - expr_ref un(u.re.mk_union(r1, r2), m); - check(regex_to_range_predicate(u, un, p) && p.num_ranges() == 1 && - p[0].first == 'a' && p[0].second == 'f', - "(a-c)|(d-f) -> (a-f) via adjacency"); - } - // Disjoint intersection: (a..z) & (0..9) -> empty - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m); - expr_ref r2(u.re.mk_range(mk_singleton_str(u, '0'), mk_singleton_str(u, '9')), m); - expr_ref ix(u.re.mk_inter(r1, r2), m); - check(regex_to_range_predicate(u, ix, p) && p.is_empty(), - "(a-z)&(0-9) -> empty"); - } - // Overlapping intersection: (a..f) & (c..z) -> (c..f) - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'f')), m); - expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'c'), mk_singleton_str(u, 'z')), m); - expr_ref ix(u.re.mk_inter(r1, r2), m); - check(regex_to_range_predicate(u, ix, p) && p.num_ranges() == 1 && - p[0].first == 'c' && p[0].second == 'f', - "(a-f)&(c-z) -> (c-f)"); - } - // Complement: re.complement is intentionally NOT a char-class op - // (it operates over Σ*), so it must NOT be translated. - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m); - expr_ref cmp(u.re.mk_complement(r1), m); - check(!regex_to_range_predicate(u, cmp, p), - "re.comp of range is NOT translatable (sequence-level complement)"); - } - // Diff: (a..f) \ (c..z) -> (a..b) - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'f')), m); - expr_ref r2(u.re.mk_range(mk_singleton_str(u, 'c'), mk_singleton_str(u, 'z')), m); - expr_ref df(u.re.mk_diff(r1, r2), m); - check(regex_to_range_predicate(u, df, p) && p.num_ranges() == 1 && - p[0].first == 'a' && p[0].second == 'b', - "(a-f) \\ (c-z) -> (a-b)"); - } - // Negative: re.* of a range is NOT a char class - { - range_predicate p(M); - expr_ref r1(u.re.mk_range(mk_singleton_str(u, 'a'), mk_singleton_str(u, 'z')), m); - expr_ref star(u.re.mk_star(r1), m); - check(!regex_to_range_predicate(u, star, p), - "re.* of range not translatable"); - } - - // Negative: a regex whose element type is NOT a sequence of - // characters (here (Seq Int)) must be rejected outright, even for - // shapes that structurally resemble char-class operators. - { - range_predicate p(M); - arith_util a(m); - sort* int_seq = u.str.mk_seq(a.mk_int()); - sort* int_re = u.re.mk_re(int_seq); - check(!regex_to_range_predicate(u, u.re.mk_empty(int_re), p), - "re.empty over (Seq Int) is NOT a char class"); - check(!regex_to_range_predicate(u, u.re.mk_full_char(int_re), p), - "re.full_char over (Seq Int) is NOT a char class"); - } - - // ---- materialization round-trip ---- - - // empty -> re.empty - { - range_predicate p = range_predicate::empty(M); - expr_ref e = range_predicate_to_regex(u, p, str_sort); - check(u.re.is_empty(e), "empty -> re.empty"); - } - // top -> re.full_char - { - range_predicate p = range_predicate::top(M); - expr_ref e = range_predicate_to_regex(u, p, str_sort); - check(u.re.is_full_char(e), "top -> re.full_char"); - } - // single range -> re.range - { - range_predicate p = range_predicate::range('a', 'z', M); - expr_ref e = range_predicate_to_regex(u, p, str_sort); - unsigned lo = 0, hi = 0; - check(extract_range_chars(u, e, lo, hi) && lo == 'a' && hi == 'z', - "[a-z] -> re.range a z"); - } - // singleton -> re.range c c - { - range_predicate p = range_predicate::singleton('A', M); - expr_ref e = range_predicate_to_regex(u, p, str_sort); - unsigned lo = 0, hi = 0; - check(extract_range_chars(u, e, lo, hi) && lo == 'A' && hi == 'A', - "{A} -> re.range A A"); - } - // 2 ranges -> re.union(range_0, range_1) in canonical order - { - range_predicate p = range_predicate::range('0', '9', M) - | range_predicate::range('a', 'z', M); - expr_ref e = range_predicate_to_regex(u, p, str_sort); - expr* a = nullptr; expr* b = nullptr; - check(u.re.is_union(e, a, b), "2-range -> union"); - unsigned lo0 = 0, hi0 = 0, lo1 = 0, hi1 = 0; - check(extract_range_chars(u, a, lo0, hi0) && lo0 == '0' && hi0 == '9', - "union arg0 = (0-9) (canonical: lower lo first)"); - check(extract_range_chars(u, b, lo1, hi1) && lo1 == 'a' && hi1 == 'z', - "union arg1 = (a-z)"); - } - // 3 ranges -> right-associated union - { - range_predicate p = range_predicate::range(0, 5, M) - | range_predicate::range(10, 15, M) - | range_predicate::range(20, 25, M); - expr_ref e = range_predicate_to_regex(u, p, str_sort); - expr* a = nullptr; expr* rest = nullptr; - check(u.re.is_union(e, a, rest), "3-range -> union(...)"); - unsigned lo = 0, hi = 0; - check(extract_range_chars(u, a, lo, hi) && lo == 0 && hi == 5, "first arg = (0-5)"); - expr* b = nullptr; expr* c = nullptr; - check(u.re.is_union(rest, b, c), "rest is union(...,...)"); - check(extract_range_chars(u, b, lo, hi) && lo == 10 && hi == 15, "second range"); - check(extract_range_chars(u, c, lo, hi) && lo == 20 && hi == 25, "third range"); - } - // Round-trip identity for an arbitrary range-set - { - range_predicate p_in = range_predicate::range('a', 'c', M) - | range_predicate::range('m', 'p', M) - | range_predicate::range('x', 'z', M); - expr_ref e = range_predicate_to_regex(u, p_in, str_sort); - range_predicate p_out(M); - check(regex_to_range_predicate(u, e, p_out), "round-trip translatable"); - check(p_in == p_out, "round-trip equal"); - } - - std::cerr << "regex_range_collapse tests passed\n"; - } -} - -void tst_regex_range_collapse() { - run(); -} diff --git a/src/test/seq_regex_bisim.cpp b/src/test/seq_regex_bisim.cpp deleted file mode 100644 index 83404439b5..0000000000 --- a/src/test/seq_regex_bisim.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// Regression test for the seq::derive::intersect_intervals bug. -// -// Background: derive uses a path-tracking interval set to compute symbolic -// derivatives. The intersect_intervals routine used to react to a single -// disjoint interval by dropping the entire kept suffix and skipping the rest -// of the list, which silently killed valid branches in derivatives such as -// D(a|b). That made the bisimulation procedure conclude bogus equalities -// like a* == (a|b)*. -// -// This file also covers the seq::derive top-level-cache poisoning bug. -// `m_top_cache` is keyed only by the regex; the routine used to populate it -// while `m_ele` was set to a *concrete* character, baking that character -// into the cached "symbolic" derivative. Subsequent calls with the same -// regex but a different ele then returned a stale concrete answer instead -// of the true symbolic derivative. The simplest victim is -// (str.in_re "aP" (re.++ (re.* "a") "P")) -// which used to return false because the derivative wrt 'a' was cached and -// re-used as the derivative wrt 'P'. -#include "ast/ast.h" -#include "ast/ast_pp.h" -#include "ast/reg_decl_plugins.h" -#include "ast/seq_decl_plugin.h" -#include "ast/rewriter/seq_rewriter.h" -#include "ast/rewriter/seq_regex_bisim.h" -#include "ast/rewriter/th_rewriter.h" -#include - -static void test_a_star_neq_ab_star() { - ast_manager m; - reg_decl_plugins(m); - seq_util u(m); - seq_rewriter rw(m); - - sort_ref str_sort(u.str.mk_string_sort(), m); - - zstring sa("a"), sb("b"); - expr_ref re_a(u.re.mk_to_re(u.str.mk_string(sa)), m); - expr_ref re_b(u.re.mk_to_re(u.str.mk_string(sb)), m); - expr_ref a_star(u.re.mk_star(re_a), m); - expr_ref ab(u.re.mk_union(re_a, re_b), m); - expr_ref ab_star(u.re.mk_star(ab), m); - - expr_ref d_ab = rw.mk_brz_derivative(ab); - std::cout << "D(a|b) = " << mk_pp(d_ab, m) << "\n"; - - // Both the 'a' branch and the 'b' branch of D(a|b) must reach epsilon. - // Collect the regex leaves of the symbolic derivative and require at - // least two distinct accepting leaves (one for 'a' and one for 'b'). - expr_ref_vector leaves(m); - auto collect = [&](expr* e, auto&& self) -> void { - expr* c, *t, *f; - if (m.is_ite(e, c, t, f) || u.re.is_union(e, t, f) || u.re.is_antimirov_union(e, t, f)) { - self(t, self); - self(f, self); - return; - } - if (u.re.is_empty(e)) return; - leaves.push_back(e); - }; - collect(d_ab, collect); - unsigned nullable_leaves = 0; - for (expr* l : leaves) { - expr_ref n = rw.is_nullable(l); - if (m.is_true(n)) ++nullable_leaves; - } - std::cout << "D(a|b) leaves=" << leaves.size() - << " nullable=" << nullable_leaves << "\n"; - ENSURE(nullable_leaves >= 2); - - // Bisim must report the two languages are not equivalent. - seq::regex_bisim bisim(rw); - lbool eq = bisim.are_equivalent(a_star, ab_star); - std::cout << "bisim(a*, (a|b)*) = " - << (eq == l_true ? "true" : eq == l_false ? "false" : "undef") << "\n"; - ENSURE(eq == l_false); -} - -// Regression for the derive top-level-cache poisoning bug. -// Take r = (re.* "a") ++ "P" and check str.in_re "aP" r. Before the fix -// the first per-char derivative call (wrt 'a') populated m_top_cache with -// 'a' baked into the symbolic ITE-tree, so the next call (wrt 'P') returned -// that stale cached value instead of computing D_P(r) = epsilon, making -// str.in_re wrongly return false. -static void test_derive_cache_per_ele() { - ast_manager m; - reg_decl_plugins(m); - seq_util u(m); - seq_rewriter rw(m); - - sort_ref str_sort(u.str.mk_string_sort(), m); - - zstring sa("a"), sP("P"), s_aP("aP"); - expr_ref re_a(u.re.mk_to_re(u.str.mk_string(sa)), m); - expr_ref re_P(u.re.mk_to_re(u.str.mk_string(sP)), m); - expr_ref a_star(u.re.mk_star(re_a), m); - expr_ref r(u.re.mk_concat(a_star, re_P), m); - expr_ref aP(u.str.mk_string(s_aP), m); - - // Compute D_'a'(a*P) and D_'P'(a*P) directly via mk_derivative. - // Before the fix, m_top_cache was populated while m_ele = ele (the - // concrete char), so the second call hit the stale cached answer from - // the first. After the fix the cache is keyed by a symbolic var, so - // each concrete-ele substitution produces the right answer. - expr_ref ch_a(u.mk_char('a'), m); - expr_ref ch_P(u.mk_char('P'), m); - expr_ref d_a = rw.mk_derivative(ch_a, r); - expr_ref d_P = rw.mk_derivative(ch_P, r); - std::cout << "D_a(a*P) = " << mk_pp(d_a, m) << "\n"; - std::cout << "D_P(a*P) = " << mk_pp(d_P, m) << "\n"; - - // D_P(a*P) must be nullable (it accepts the empty suffix), while - // D_a(a*P) must not be (it still needs a trailing 'P'). - expr_ref n_a = rw.is_nullable(d_a); - expr_ref n_P = rw.is_nullable(d_P); - th_rewriter trw(m); - trw(n_a); - trw(n_P); - std::cout << "nullable(D_a) = " << mk_pp(n_a, m) << "\n"; - std::cout << "nullable(D_P) = " << mk_pp(n_P, m) << "\n"; - ENSURE(m.is_false(n_a)); - ENSURE(m.is_true(n_P)); -} - -void tst_seq_regex_bisim() { - test_a_star_neq_ab_star(); - test_derive_cache_per_ele(); -}