3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-08-02 12:13:25 +00:00
z3/src/ast/rewriter/seq_range_collapse.cpp
davedets 7502e3c409
Remove new instances of unused local vars and functions (#10309)
A few seem to have crept in.  Verified with both dbg and release builds.
2026-07-30 13:41:20 -07:00

284 lines
10 KiB
C++

/*++
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 {
// Cofactor guard `guard` (a Boolean over the character variable v0 = (:var 0)) ->
// the canonical range_predicate (union of ranges) of the characters satisfying it.
// Returns false on a construct outside {true,false,and,or,not,=,char.<=} over v0.
bool guard_to_range_predicate(seq_util& u, expr* v0, expr* guard, range_predicate& out) {
ast_manager& m = u.get_manager();
unsigned maxc = u.max_char();
expr* a = nullptr, * b = nullptr;
unsigned c = 0;
if (m.is_true(guard)) {
out = range_predicate::top(maxc);
return true;
}
if (m.is_false(guard)) {
out = range_predicate::empty(maxc);
return true;
}
if (m.is_eq(guard, a, b)) {
if (a == v0 && u.is_const_char(b, c)) {
out = range_predicate::singleton(c, maxc);
return true;
}
if (b == v0 && u.is_const_char(a, c)) {
out = range_predicate::singleton(c, maxc);
return true;
}
return false;
}
if (u.is_char_le(guard, a, b)) {
if (b == v0 && u.is_const_char(a, c)) {
out = range_predicate::range(c, maxc, maxc);
return true;
}
if (a == v0 && u.is_const_char(b, c)) {
out = range_predicate::range(0, c, maxc);
return true;
}
return false;
}
if (m.is_not(guard, a)) {
range_predicate s(maxc);
if (!guard_to_range_predicate(u, v0, a, s))
return false;
out = ~s;
return true;
}
if (m.is_and(guard)) {
out = range_predicate::top(maxc);
for (expr *arg : *to_app(guard)) {
range_predicate s(maxc);
if (!guard_to_range_predicate(u, v0, arg, s))
return false;
out = out & s;
}
return true;
}
if (m.is_or(guard)) {
out = range_predicate::empty(maxc);
for (expr *arg : *to_app(guard)) {
range_predicate s(maxc);
if (!guard_to_range_predicate(u, v0, arg, s))
return false;
out = out | s;
}
return true;
}
return false;
}
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;
auto &m = u.get_manager();
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;
}
if (re.is_of_pred(r, a) && is_lambda(a)) {
auto q = to_quantifier(a);
if (q->get_num_decls() != 1)
return false;
auto body = q->get_expr();
sort *char_sort = q->get_decl_sort(0);
expr_ref var(m.mk_var(0, char_sort), m);
if (u.get_char_plugin().get_family_id() == char_sort->get_family_id() && guard_to_range_predicate(u, var, body, out))
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_single_range_regex(seq_util& u, unsigned lo, unsigned hi, sort* re_sort) {
ast_manager& m = u.get_manager();
return expr_ref(u.re.mk_range(re_sort, lo, hi), 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);
sort *char_sort = nullptr;
VERIFY(u.is_seq(seq_sort, char_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;
#if 0
expr_ref bound(m.mk_var(0, char_sort), m);
symbol char_sym("ch");
auto &ch = u.get_char_plugin();
for (unsigned i = 0; i < n; ++i) {
auto [lo, hi] = p[i];
{
auto _seq251_0 = ch.mk_le(ch.mk_char(lo), bound);
auto _seq251_1 = ch.mk_le(bound, ch.mk_char(hi));
ranges.push_back(m.mk_and(_seq251_0, _seq251_1));
}
}
expr_ref body(m.mk_or(ranges), m);
auto lam = m.mk_lambda(1, &char_sort, &char_sym, body);
return expr_ref(u.re.mk_of_pred(lam), m);
#endif
}
expr_ref unfold_fold(seq_rewriter &rw, expr *r) {
auto &m = rw.m();
auto& u = rw.u();
expr_ref_pair_vector cofactors(m);
rw.brz_derivative_cofactors(r, cofactors);
if (cofactors.empty())
return expr_ref(u.re.mk_empty(r->get_sort()), m);
sort *seq_sort = nullptr, *char_sort = nullptr;
VERIFY(u.is_re(r, seq_sort));
VERIFY(u.is_seq(seq_sort, char_sort));
expr_ref var(m.mk_var(0, char_sort), m);
expr_ref result(m);
symbol ch("ch");
for (auto const &[c, cof] : cofactors) {
auto prefix = u.re.mk_of_pred(m.mk_lambda(1, &char_sort, &ch, c));
auto term = u.re.mk_concat(prefix, cof);
if (result)
result = u.re.mk_union(term, result);
else
result = term;
}
return result;
}
}