3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-04-18 01:50:17 +00:00
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2026-03-13 18:19:25 -07:00
parent 8a48caf742
commit 27f5541b0b
11 changed files with 2176 additions and 80 deletions

View file

@ -449,6 +449,7 @@ static void test_sgraph_factory() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
// mk_var
euf::snode* x = sg.mk_var(symbol("x"));
@ -463,7 +464,7 @@ static void test_sgraph_factory() {
SASSERT(a->length() == 1);
// mk_empty
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
SASSERT(e && e->is_empty());
SASSERT(e->is_nullable());
SASSERT(e->length() == 0);
@ -499,6 +500,7 @@ static void test_sgraph_indexing() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
euf::snode* a = sg.mk_char('A');
euf::snode* b = sg.mk_char('B');
@ -533,7 +535,7 @@ static void test_sgraph_indexing() {
SASSERT(a->at(1) == nullptr);
// empty: at(0) is nullptr
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
SASSERT(e->at(0) == nullptr);
euf::snode_vector empty_tokens;
e->collect_tokens(empty_tokens);
@ -547,6 +549,7 @@ static void test_sgraph_drop() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
euf::snode* a = sg.mk_char('A');
euf::snode* b = sg.mk_char('B');
@ -591,7 +594,7 @@ static void test_sgraph_drop() {
SASSERT(e->is_empty());
// drop from empty: no change
euf::snode* ee = sg.drop_first(sg.mk_empty());
euf::snode* ee = sg.drop_first(sg.mk_empty_seq(seq.str.mk_string_sort()));
SASSERT(ee->is_empty());
}
@ -602,6 +605,7 @@ static void test_sgraph_subst() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
@ -624,7 +628,7 @@ static void test_sgraph_subst() {
SASSERT(same == xax);
// substitution of variable with empty
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
euf::snode* collapsed = sg.subst(xax, x, e);
SASSERT(collapsed->length() == 1); // just 'a' remains
SASSERT(collapsed == a);

View file

@ -91,14 +91,15 @@ static void test_nseq_simplification() {
std::cout << "test_nseq_simplification\n";
ast_manager m;
reg_decl_plugins(m);
seq_util su(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
nseq_basic_dummy_solver solver;
seq::nielsen_graph ng(sg, solver);
// Add a trivial equality: empty = empty
euf::snode* empty1 = sg.mk_empty();
euf::snode* empty2 = sg.mk_empty();
euf::snode* empty1 = sg.mk_empty_seq(su.str.mk_string_sort());
euf::snode* empty2 = sg.mk_empty_seq(su.str.mk_string_sort());
ng.add_str_eq(empty1, empty2);
@ -113,6 +114,7 @@ static void test_nseq_node_satisfied() {
std::cout << "test_nseq_node_satisfied\n";
ast_manager m;
reg_decl_plugins(m);
seq_util su(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
nseq_basic_dummy_solver solver;
@ -123,7 +125,7 @@ static void test_nseq_node_satisfied() {
SASSERT(node->is_satisfied());
// add a trivial equality
euf::snode* empty = sg.mk_empty();
euf::snode* empty = sg.mk_empty_seq(su.str.mk_string_sort());
seq::dep_tracker dep;
seq::str_eq eq(empty, empty, dep);
node->add_str_eq(eq);

View file

@ -16,6 +16,9 @@ Abstract:
#include "ast/euf/euf_egraph.h"
#include "ast/euf/euf_sgraph.h"
#include "smt/nseq_regex.h"
#include "smt/seq/seq_nielsen.h"
#include "util/lbool.h"
#include "util/zstring.h"
#include <iostream>
// Test 1: nseq_regex instantiation
@ -66,9 +69,643 @@ static void test_nseq_regex_is_full() {
std::cout << " ok: re.all not recognized as empty\n";
}
// Test 4: strengthened_stabilizer — null safety
static void test_strengthened_stabilizer_null() {
std::cout << "test_strengthened_stabilizer_null\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
SASSERT(nr.strengthened_stabilizer(nullptr, nullptr) == nullptr);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
euf::snode* full_re = sg.mk(full_e);
SASSERT(nr.strengthened_stabilizer(full_re, nullptr) == nullptr);
SASSERT(nr.strengthened_stabilizer(nullptr, full_re) == nullptr);
std::cout << " ok\n";
}
// Test 5: strengthened_stabilizer — single char cycle on a*
// Regex a*, history = 'a'. D('a', a*) = a* (sub-cycle back to start).
// Stabilizer body should be to_re("a").
static void test_strengthened_stabilizer_single_char() {
std::cout << "test_strengthened_stabilizer_single_char\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// Build a*
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
euf::snode* re_star_a = sg.mk(star_a);
// Build history = char 'a' (single token, no concat needed)
euf::snode* tok_a = sg.mk_char('a');
euf::snode* history = tok_a;
euf::snode* result = nr.strengthened_stabilizer(re_star_a, history);
// Should produce a non-null stabilizer body (to_re("a"))
SASSERT(result != nullptr);
std::cout << " ok: a* with history 'a' -> non-null stabilizer\n";
}
// Test 6: strengthened_stabilizer — two-char cycle with sub-cycle
// Regex (ab)*, history = 'a', 'b'. D('a', (ab)*) = b(ab)*, D('b', b(ab)*) = (ab)*
// This should detect a sub-cycle and build a stabilizer body.
static void test_strengthened_stabilizer_two_char() {
std::cout << "test_strengthened_stabilizer_two_char\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// Build (ab)*
expr_ref ab(su.re.mk_to_re(su.str.mk_string("ab")), m);
expr_ref star_ab(su.re.mk_star(ab), m);
euf::snode* re_star_ab = sg.mk(star_ab);
// Build history: concat(char_a, char_b) using string concat
euf::snode* tok_a = sg.mk_char('a');
euf::snode* tok_b = sg.mk_char('b');
euf::snode* history = sg.mk_concat(tok_a, tok_b);
euf::snode* result = nr.strengthened_stabilizer(re_star_ab, history);
// Should produce a non-null stabilizer body
SASSERT(result != nullptr);
std::cout << " ok: (ab)* with history 'ab' -> non-null stabilizer\n";
}
// Test 7: get_filtered_stabilizer_star — no stabilizers registered
static void test_filtered_stabilizer_star_empty() {
std::cout << "test_filtered_stabilizer_star_empty\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
euf::snode* full_re = sg.mk(full_e);
euf::snode* tok_a = sg.mk_char('a');
euf::snode* result = nr.get_filtered_stabilizer_star(full_re, tok_a);
SASSERT(result == nullptr);
std::cout << " ok: no stabilizers -> nullptr\n";
}
// Test 8: get_filtered_stabilizer_star — with registered stabilizer that passes filter
static void test_filtered_stabilizer_star_with_stab() {
std::cout << "test_filtered_stabilizer_star_with_stab\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// Build a* as the regex state
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
euf::snode* re_star_a = sg.mk(star_a);
// Register a stabilizer: to_re("b") — only accepts "b"
expr_ref stab_b(su.re.mk_to_re(su.str.mk_string("b")), m);
euf::snode* stab_b_sn = sg.mk(stab_b);
nr.add_stabilizer(re_star_a, stab_b_sn);
// Exclude char 'a': D('a', to_re("b")) should be fail
euf::snode* tok_a = sg.mk_char('a');
euf::snode* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
// to_re("b") should pass the filter → result is star(to_re("b"))
SASSERT(result != nullptr);
SASSERT(result->is_star());
std::cout << " ok: filter keeps to_re('b') when excluding 'a'\n";
}
// Test 9: get_filtered_stabilizer_star — stabilizer filtered out
static void test_filtered_stabilizer_star_filtered() {
std::cout << "test_filtered_stabilizer_star_filtered\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// Build a* as the regex state
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
euf::snode* re_star_a = sg.mk(star_a);
// Register a stabilizer: to_re("a") — accepts "a"
expr_ref stab_a(su.re.mk_to_re(su.str.mk_string("a")), m);
euf::snode* stab_a_sn = sg.mk(stab_a);
nr.add_stabilizer(re_star_a, stab_a_sn);
// Exclude char 'a': D('a', to_re("a")) is NOT fail → filtered out
euf::snode* tok_a = sg.mk_char('a');
euf::snode* result = nr.get_filtered_stabilizer_star(re_star_a, tok_a);
SASSERT(result == nullptr);
std::cout << " ok: filter removes to_re('a') when excluding 'a'\n";
}
// Test 10: extract_cycle_history — basic extraction
static void test_extract_cycle_history_basic() {
std::cout << "test_extract_cycle_history_basic\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
euf::snode* full_re = sg.mk(full_e);
euf::snode* tok_a = sg.mk_char('a');
euf::snode* tok_b = sg.mk_char('b');
euf::snode* tok_c = sg.mk_char('c');
// Ancestor history: just 'a' (length 1)
euf::snode* anc_hist = tok_a;
// Current history: concat(concat(a, b), c) = a,b,c (length 3)
euf::snode* cur_hist = sg.mk_concat(sg.mk_concat(tok_a, tok_b), tok_c);
euf::snode* empty_str = sg.mk_empty_seq(str_sort);
seq::dep_tracker empty_dep;
seq::str_mem ancestor(empty_str, full_re, anc_hist, 0, empty_dep);
seq::str_mem current(empty_str, full_re, cur_hist, 0, empty_dep);
euf::snode* cycle = nr.extract_cycle_history(current, ancestor);
// Should return the last 2 tokens (b, c)
SASSERT(cycle != nullptr);
SASSERT(cycle->length() == 2);
std::cout << " ok: extracted cycle of length 2\n";
}
// Test 11: extract_cycle_history — null ancestor history
static void test_extract_cycle_history_null_ancestor() {
std::cout << "test_extract_cycle_history_null_ancestor\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
euf::snode* full_re = sg.mk(full_e);
euf::snode* tok_a = sg.mk_char('a');
euf::snode* tok_b = sg.mk_char('b');
euf::snode* cur_hist = sg.mk_concat(tok_a, tok_b);
euf::snode* empty_str = sg.mk_empty_seq(str_sort);
seq::dep_tracker empty_dep;
// Ancestor has no history (nullptr)
seq::str_mem ancestor(empty_str, full_re, nullptr, 0, empty_dep);
seq::str_mem current(empty_str, full_re, cur_hist, 0, empty_dep);
euf::snode* cycle = nr.extract_cycle_history(current, ancestor);
// With null ancestor history, entire current history is the cycle
SASSERT(cycle != nullptr);
SASSERT(cycle->length() == 2);
std::cout << " ok: null ancestor -> full history as cycle\n";
}
// Test 12: BFS emptiness — re.none (empty language) is empty
static void test_bfs_empty_none() {
std::cout << "test_bfs_empty_none\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref none_e(su.re.mk_empty(re_sort), m);
euf::snode* none_re = sg.mk(none_e);
lbool result = nr.is_empty_bfs(none_re);
SASSERT(result == l_true);
std::cout << " ok: re.none -> l_true (empty)\n";
}
// Test 13: BFS emptiness — full_seq (Sigma*) is NOT empty
static void test_bfs_nonempty_full() {
std::cout << "test_bfs_nonempty_full\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref full_e(su.re.mk_full_seq(re_sort), m);
euf::snode* full_re = sg.mk(full_e);
lbool result = nr.is_empty_bfs(full_re);
SASSERT(result == l_false);
std::cout << " ok: full_seq -> l_false (non-empty)\n";
}
// Test 14: BFS emptiness — to_re("abc") is NOT empty
static void test_bfs_nonempty_to_re() {
std::cout << "test_bfs_nonempty_to_re\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
expr_ref to_re_abc(su.re.mk_to_re(su.str.mk_string("abc")), m);
euf::snode* re_abc = sg.mk(to_re_abc);
lbool result = nr.is_empty_bfs(re_abc);
SASSERT(result == l_false);
std::cout << " ok: to_re(\"abc\") -> l_false (non-empty)\n";
}
// Test 15: BFS emptiness — a* is NOT empty (accepts epsilon)
static void test_bfs_nonempty_star() {
std::cout << "test_bfs_nonempty_star\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
expr_ref star_a(su.re.mk_star(su.re.mk_to_re(su.str.mk_string("a"))), m);
euf::snode* re_star_a = sg.mk(star_a);
lbool result = nr.is_empty_bfs(re_star_a);
SASSERT(result == l_false);
std::cout << " ok: a* -> l_false (non-empty, accepts epsilon)\n";
}
// Test 16: BFS emptiness — union(none, none) is empty
static void test_bfs_empty_union_of_empties() {
std::cout << "test_bfs_empty_union_of_empties\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref none1(su.re.mk_empty(re_sort), m);
expr_ref none2(su.re.mk_empty(re_sort), m);
expr_ref union_e(su.re.mk_union(none1, none2), m);
euf::snode* re_union = sg.mk(union_e);
lbool result = nr.is_empty_bfs(re_union);
SASSERT(result == l_true);
std::cout << " ok: union(none, none) -> l_true (empty)\n";
}
// Test 17: BFS emptiness — re.range('a','z') is NOT empty
static void test_bfs_nonempty_range() {
std::cout << "test_bfs_nonempty_range\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
expr_ref lo(su.mk_char('a'), m);
expr_ref hi(su.mk_char('z'), m);
expr_ref range_e(su.re.mk_range(su.str.mk_unit(lo), su.str.mk_unit(hi)), m);
euf::snode* re_range = sg.mk(range_e);
lbool result = nr.is_empty_bfs(re_range);
SASSERT(result == l_false);
std::cout << " ok: range('a','z') -> l_false (non-empty)\n";
}
// Test 18: BFS emptiness — complement(full_seq) = empty
static void test_bfs_empty_complement_full() {
std::cout << "test_bfs_empty_complement_full\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
sort* re_sort = su.re.mk_re(str_sort);
expr_ref comp_full(su.re.mk_complement(su.re.mk_full_seq(re_sort)), m);
euf::snode* re_comp = sg.mk(comp_full);
lbool result = nr.is_empty_bfs(re_comp);
SASSERT(result == l_true);
std::cout << " ok: ~full_seq -> l_true (empty)\n";
}
// Test 19: BFS emptiness — nullptr returns l_undef
static void test_bfs_null_safety() {
std::cout << "test_bfs_null_safety\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
lbool result = nr.is_empty_bfs(nullptr);
SASSERT(result == l_undef);
std::cout << " ok: nullptr -> l_undef\n";
}
// Test 20: BFS emptiness — max_states bound respected
static void test_bfs_bounded() {
std::cout << "test_bfs_bounded\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// (a|b)+ requires at least one char; with max_states=1 should bail
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
expr_ref ab_plus(su.re.mk_plus(ab_union), m);
euf::snode* re_plus = sg.mk(ab_plus);
lbool result = nr.is_empty_bfs(re_plus, 1);
SASSERT(result == l_undef);
std::cout << " ok: (a|b)+ with max_states=1 -> l_undef (bounded)\n";
}
// -----------------------------------------------------------------------
// New tests for regex membership completion (Phase 1-4)
// -----------------------------------------------------------------------
// Test: char_set::is_subset
static void test_char_set_is_subset() {
std::cout << "test_char_set_is_subset\n";
// {a} ⊆ {a,b,c} = [97,100)
char_set cs1(char_range('a', 'b')); // {a}
char_set cs2(char_range('a', 'd')); // {a,b,c}
SASSERT(cs1.is_subset(cs2));
SASSERT(!cs2.is_subset(cs1));
// empty ⊆ anything
char_set empty;
SASSERT(empty.is_subset(cs1));
SASSERT(empty.is_subset(cs2));
// self ⊆ self
SASSERT(cs1.is_subset(cs1));
SASSERT(cs2.is_subset(cs2));
// disjoint: {x} not ⊆ {a}
char_set cs3(char_range('x', 'y'));
SASSERT(!cs3.is_subset(cs1));
std::cout << " ok\n";
}
// Test: stabilizer store basic operations
static void test_stabilizer_store_basic() {
std::cout << "test_stabilizer_store_basic\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
euf::snode* a_sn = sg.mk(a_re);
euf::snode* b_sn = sg.mk(b_re);
SASSERT(!nr.has_stabilizers(a_sn));
nr.add_stabilizer(a_sn, b_sn);
SASSERT(nr.has_stabilizers(a_sn));
SASSERT(nr.get_stabilizer_union(a_sn) == b_sn);
// dedup: adding same stabilizer again
nr.add_stabilizer(a_sn, b_sn);
auto* stabs = nr.get_stabilizers(a_sn);
SASSERT(stabs && stabs->size() == 1);
// reset
nr.reset_stabilizers();
SASSERT(!nr.has_stabilizers(a_sn));
std::cout << " ok\n";
}
// Test: self-stabilizing flag
static void test_self_stabilizing() {
std::cout << "test_self_stabilizing\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
euf::snode* a_sn = sg.mk(a_re);
SASSERT(!nr.is_self_stabilizing(a_sn));
nr.set_self_stabilizing(a_sn);
SASSERT(nr.is_self_stabilizing(a_sn));
// star should be detected as self-stabilizing
expr_ref star_a(su.re.mk_star(a_re), m);
euf::snode* star_sn = sg.mk(star_a);
SASSERT(nr.compute_self_stabilizing(star_sn));
std::cout << " ok\n";
}
// Test: check_intersection_emptiness — SAT case
static void test_check_intersection_sat() {
std::cout << "test_check_intersection_sat\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// a* ∩ (a|b)* should be non-empty (both accept "a")
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
expr_ref star_a(su.re.mk_star(a_re), m);
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
expr_ref star_ab(su.re.mk_star(ab_union), m);
euf::snode* s1 = sg.mk(star_a);
euf::snode* s2 = sg.mk(star_ab);
ptr_vector<euf::snode> regexes;
regexes.push_back(s1);
regexes.push_back(s2);
lbool result = nr.check_intersection_emptiness(regexes);
SASSERT(result == l_false); // non-empty
std::cout << " ok: a* ∩ (a|b)* is non-empty\n";
}
// Test: check_intersection_emptiness — UNSAT case
static void test_check_intersection_unsat() {
std::cout << "test_check_intersection_unsat\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
// to_re("a") ∩ to_re("b") should be empty
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
euf::snode* s1 = sg.mk(a_re);
euf::snode* s2 = sg.mk(b_re);
ptr_vector<euf::snode> regexes;
regexes.push_back(s1);
regexes.push_back(s2);
lbool result = nr.check_intersection_emptiness(regexes);
SASSERT(result == l_true); // empty
std::cout << " ok: to_re(a) ∩ to_re(b) is empty\n";
}
// Test: is_language_subset — true case
static void test_is_language_subset_true() {
std::cout << "test_is_language_subset_true\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// a* ⊆ (a|b)* should be true
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
expr_ref star_a(su.re.mk_star(a_re), m);
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
expr_ref star_ab(su.re.mk_star(ab_union), m);
euf::snode* subset = sg.mk(star_a);
euf::snode* superset = sg.mk(star_ab);
lbool result = nr.is_language_subset(subset, superset);
SASSERT(result == l_true);
std::cout << " ok: a* ⊆ (a|b)*\n";
}
// Test: is_language_subset — false case
static void test_is_language_subset_false() {
std::cout << "test_is_language_subset_false\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
// (a|b)* ⊄ a* should be false (b ∈ (a|b)* but b ∉ a*)
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
expr_ref star_a(su.re.mk_star(a_re), m);
expr_ref b_re(su.re.mk_to_re(su.str.mk_string("b")), m);
expr_ref ab_union(su.re.mk_union(a_re, b_re), m);
expr_ref star_ab(su.re.mk_star(ab_union), m);
euf::snode* subset = sg.mk(star_ab);
euf::snode* superset = sg.mk(star_a);
lbool result = nr.is_language_subset(subset, superset);
SASSERT(result == l_false);
std::cout << " ok: (a|b)* ⊄ a*\n";
}
// Test: is_language_subset — trivial cases
static void test_is_language_subset_trivial() {
std::cout << "test_is_language_subset_trivial\n";
ast_manager m;
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
smt::nseq_regex nr(sg);
seq_util su(m);
sort* str_sort = su.str.mk_string_sort();
// ∅ ⊆ anything = true
expr_ref none(su.re.mk_empty(su.re.mk_re(str_sort)), m);
expr_ref a_re(su.re.mk_to_re(su.str.mk_string("a")), m);
euf::snode* empty_sn = sg.mk(none);
euf::snode* a_sn = sg.mk(a_re);
SASSERT(nr.is_language_subset(empty_sn, a_sn) == l_true);
// anything ⊆ Σ* = true
expr_ref full(su.re.mk_full_seq(su.re.mk_re(str_sort)), m);
euf::snode* full_sn = sg.mk(full);
SASSERT(nr.is_language_subset(a_sn, full_sn) == l_true);
// L ⊆ L = true (same pointer)
SASSERT(nr.is_language_subset(a_sn, a_sn) == l_true);
std::cout << " ok\n";
}
void tst_nseq_regex() {
test_nseq_regex_instantiation();
test_nseq_regex_is_empty();
test_nseq_regex_is_full();
test_strengthened_stabilizer_null();
test_strengthened_stabilizer_single_char();
test_strengthened_stabilizer_two_char();
test_filtered_stabilizer_star_empty();
test_filtered_stabilizer_star_with_stab();
test_filtered_stabilizer_star_filtered();
test_extract_cycle_history_basic();
test_extract_cycle_history_null_ancestor();
test_bfs_empty_none();
test_bfs_nonempty_full();
test_bfs_nonempty_to_re();
test_bfs_nonempty_star();
test_bfs_empty_union_of_empties();
test_bfs_nonempty_range();
test_bfs_empty_complement_full();
test_bfs_null_safety();
test_bfs_bounded();
// New tests for regex membership completion
test_char_set_is_subset();
test_stabilizer_store_basic();
test_self_stabilizing();
test_check_intersection_sat();
test_check_intersection_unsat();
test_is_language_subset_true();
test_is_language_subset_false();
test_is_language_subset_trivial();
std::cout << "nseq_regex: all tests passed\n";
}

View file

@ -57,9 +57,10 @@ public:
// -----------------------------------------------------------------------
struct str_builder {
euf::sgraph& sg;
seq_util& su;
euf::snode* vars[26] = {}; // vars['A'-'A'] .. vars['Z'-'A']
explicit str_builder(euf::sgraph& sg) : sg(sg) {}
str_builder(euf::sgraph& sg, seq_util& su) : sg(sg), su(su) {}
euf::snode* var(char c) {
int idx = c - 'A';
@ -76,7 +77,7 @@ struct str_builder {
: sg.mk_char((unsigned)(unsigned char)*p);
result = result ? sg.mk_concat(result, tok) : tok;
}
return result ? result : sg.mk_empty();
return result ? result : sg.mk_empty_seq(su.str.mk_string_sort());
}
};
@ -190,7 +191,7 @@ struct nseq_fixture {
static ast_manager& init(ast_manager& m) { reg_decl_plugins(m); return m; }
nseq_fixture()
: eg(init(m)), sg(m, eg), ng(sg, dummy_solver), su(m), sb(sg), rb(m, su, sg)
: eg(init(m)), sg(m, eg), ng(sg, dummy_solver), su(m), sb(sg, su), rb(m, su, sg)
{}
euf::snode* S(const char* s) { return sb.parse(s); }

View file

@ -74,11 +74,12 @@ static void test_str_eq() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
seq::dep_tracker dep; dep.insert(0);
@ -121,7 +122,7 @@ static void test_str_mem() {
sort_ref str_sort(seq.str.mk_string_sort(), m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
// create a regex: re.all (.*)
expr_ref star_fc(seq.re.mk_full_seq(str_sort), m);
@ -149,11 +150,12 @@ static void test_nielsen_subst() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
seq::dep_tracker dep;
@ -208,7 +210,7 @@ static void test_nielsen_node() {
// regex membership
expr_ref re_all(seq.re.mk_full_seq(str_sort), m);
euf::snode* regex = sg.mk(re_all);
euf::snode* empty = sg.mk_empty();
euf::snode* empty = sg.mk_empty_seq(seq.str.mk_string_sort());
root->add_str_mem(seq::str_mem(x, regex, empty, 0, dep));
SASSERT(root->str_mems().size() == 1);
@ -311,7 +313,7 @@ static void test_nielsen_subst_apply() {
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* a = sg.mk_char('A');
euf::snode* b = sg.mk_char('B');
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
// create node with constraint: concat(x, A) = concat(B, y)
seq::nielsen_node* node = ng.mk_node();
@ -362,6 +364,7 @@ static void test_nielsen_expansion() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
dummy_simple_solver solver;
seq::nielsen_graph ng(sg, solver);
@ -379,7 +382,7 @@ static void test_nielsen_expansion() {
seq::dep_tracker dep;
// branch 1: x -> eps (eliminating, progress)
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
seq::nielsen_node* child1 = ng.mk_child(root);
seq::nielsen_subst s1(x, e, dep);
child1->apply_subst(sg, s1);
@ -1720,9 +1723,10 @@ static void test_simplify_empty_propagation() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* xy = sg.mk_concat(x, y);
@ -1743,9 +1747,10 @@ static void test_simplify_empty_vs_char() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
euf::snode* a = sg.mk_char('A');
// ε = A → rhs has non-variable token → conflict
@ -1791,11 +1796,12 @@ static void test_simplify_trivial_removal() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
seq::nielsen_node* node = ng.mk_node();
seq::dep_tracker dep; dep.insert(0);
@ -1814,10 +1820,11 @@ static void test_simplify_all_trivial_satisfied() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
seq::nielsen_node* node = ng.mk_node();
seq::dep_tracker dep; dep.insert(0);
@ -1838,7 +1845,7 @@ static void test_simplify_regex_infeasible() {
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
expr_ref ch_a(seq.str.mk_char('A'), m);
expr_ref unit_a(seq.str.mk_unit(ch_a), m);
@ -1865,7 +1872,7 @@ static void test_simplify_nullable_removal() {
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
expr_ref ch_a(seq.str.mk_char('A'), m);
expr_ref unit_a(seq.str.mk_unit(ch_a), m);
@ -1894,7 +1901,7 @@ static void test_simplify_brzozowski_sat() {
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* a = sg.mk_char('A');
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
expr_ref ch_a(seq.str.mk_char('A'), m);
expr_ref unit_a(seq.str.mk_unit(ch_a), m);
@ -1923,7 +1930,7 @@ static void test_simplify_brzozowski_rtl_suffix() {
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* a = sg.mk_char('A');
euf::snode* xa = sg.mk_concat(x, a);
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
expr_ref ch_b(seq.str.mk_char('B'), m);
expr_ref unit_b(seq.str.mk_unit(ch_b), m);
@ -1955,13 +1962,14 @@ static void test_simplify_multiple_eqs() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* a = sg.mk_char('A');
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* y = sg.mk_var(symbol("y"));
euf::snode* z = sg.mk_var(symbol("z"));
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
seq::nielsen_node* node = ng.mk_node();
seq::dep_tracker dep; dep.insert(0);
@ -2083,7 +2091,7 @@ static void test_explain_conflict_mem_only() {
seq_util seq(m);
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
expr_ref ch_a(seq.str.mk_char('A'), m);
expr_ref unit_a(seq.str.mk_unit(ch_a), m);
@ -2115,7 +2123,7 @@ static void test_explain_conflict_mixed_eq_mem() {
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);
euf::snode* a = sg.mk_char('A');
euf::snode* b = sg.mk_char('B');
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
// eq[0]: A = B (conflict)
ng.add_str_eq(a, b);
@ -2418,9 +2426,10 @@ static void test_length_constraints_empty_side() {
reg_decl_plugins(m);
euf::egraph eg(m);
euf::sgraph sg(m, eg);
seq_util seq(m);
euf::snode* x = sg.mk_var(symbol("x"));
euf::snode* e = sg.mk_empty();
euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort());
// x = ε
dummy_simple_solver solver; seq::nielsen_graph ng(sg, solver);