/*++ Copyright (c) 2026 Microsoft Corporation Module Name: euf_sgraph.cpp Abstract: Self-contained unit tests for the sgraph string graph layer. Tests snode classification, metadata computation, push/pop backtracking, associativity-respecting hash table, compound node construction, and snode navigation. --*/ #include "util/util.h" #include "ast/euf/euf_sgraph.h" #include "ast/reg_decl_plugins.h" #include "ast/ast_pp.h" #include "ast/arith_decl_plugin.h" #include // test classification and metadata for basic string nodes: // variables, empty strings, characters, units, and concats static void test_sgraph_classify() { std::cout << "test_sgraph_classify\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); // string variable expr_ref x(m.mk_const("x", str_sort), m); euf::snode* sx = sg.mk(x); SASSERT(sx && sx->is_var()); SASSERT(!sx->is_ground()); SASSERT(sx->is_regex_free()); SASSERT(sx->level() == 1); SASSERT(sx->length() == 1); SASSERT(sx->is_token()); // empty string expr_ref empty(seq.str.mk_empty(str_sort), m); euf::snode* se = sg.mk(empty); SASSERT(se && se->is_empty()); SASSERT(se->is_ground()); SASSERT(se->level() == 0); SASSERT(se->length() == 0); SASSERT(!se->is_token()); // character unit with literal char expr_ref ch(seq.str.mk_char('A'), m); expr_ref unit_a(seq.str.mk_unit(ch), m); euf::snode* sca = sg.mk(unit_a); SASSERT(sca && sca->is_char()); SASSERT(sca->is_ground()); SASSERT(sca->level() == 1); SASSERT(sca->length() == 1); SASSERT(sca->is_token()); // concat of two variables expr_ref y(m.mk_const("y", str_sort), m); expr_ref xy(seq.str.mk_concat(x, y), m); euf::snode* sxy = sg.mk(xy); SASSERT(sxy && sxy->is_concat()); SASSERT(!sxy->is_ground()); SASSERT(sxy->is_regex_free()); SASSERT(sxy->level() == 2); SASSERT(sxy->length() == 2); SASSERT(sxy->num_args() == 2); SASSERT(!sxy->is_token()); sg.display(std::cout); } // test classification for regex nodes: // star, union, intersection, complement, full_seq, full_char, fail, to_re, in_re static void test_sgraph_regex() { std::cout << "test_sgraph_regex\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); // to_re expr_ref to_re_x(seq.re.mk_to_re(x), m); euf::snode* str = sg.mk(to_re_x); SASSERT(str && str->is_to_re()); SASSERT(!str->is_regex_free()); SASSERT(str->num_args() == 1); // star expr_ref star_x(seq.re.mk_star(to_re_x), m); euf::snode* ss = sg.mk(star_x); SASSERT(ss && ss->is_star()); SASSERT(!ss->is_regex_free()); SASSERT(ss->num_args() == 1); // full_seq (.*) expr_ref full_seq(seq.re.mk_full_seq(str_sort), m); euf::snode* sfs = sg.mk(full_seq); SASSERT(sfs && sfs->is_full_seq()); SASSERT(sfs->is_ground()); // full_char (.) expr_ref full_char(seq.re.mk_full_char(str_sort), m); euf::snode* sfc = sg.mk(full_char); SASSERT(sfc && sfc->is_full_char()); SASSERT(sfc->is_ground()); // empty set, fail sort_ref re_sort(seq.re.mk_re(str_sort), m); expr_ref empty_set(seq.re.mk_empty(re_sort), m); euf::snode* sfail = sg.mk(empty_set); SASSERT(sfail && sfail->is_fail()); // union: to_re(x) | star(to_re(x)), nullable because star is expr_ref re_union(seq.re.mk_union(to_re_x, star_x), m); euf::snode* su = sg.mk(re_union); SASSERT(su && su->is_union()); // intersection: to_re(x) & star(to_re(x)), nullable only if both are expr_ref re_inter(seq.re.mk_inter(to_re_x, star_x), m); euf::snode* si = sg.mk(re_inter); SASSERT(si && si->is_intersect()); // complement of to_re(x): nullable because to_re(x) is not nullable expr_ref re_comp(seq.re.mk_complement(to_re_x), m); euf::snode* sc = sg.mk(re_comp); SASSERT(sc && sc->is_complement()); // in_re expr_ref in_re(seq.re.mk_in_re(x, star_x), m); euf::snode* sir = sg.mk(in_re); SASSERT(sir && sir->is_in_re()); SASSERT(!sir->is_regex_free()); sg.display(std::cout); } // test power node classification and metadata static void test_sgraph_power() { std::cout << "test_sgraph_power\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); arith_util arith(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); expr_ref n(arith.mk_int(3), m); expr_ref xn(seq.str.mk_power(x, n), m); euf::snode* sp = sg.mk(xn); SASSERT(sp && sp->is_power()); SASSERT(!sp->is_ground()); // base x is not ground SASSERT(sp->is_regex_free()); SASSERT(sp->num_args() >= 1); sg.display(std::cout); } // test push/pop backtracking: nodes created inside a scope // are removed on pop, nodes before persist static void test_sgraph_push_pop() { std::cout << "test_sgraph_push_pop\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); expr_ref y(m.mk_const("y", str_sort), m); expr_ref z(m.mk_const("z", str_sort), m); // create x before any scope sg.mk(x); unsigned before = sg.num_nodes(); SASSERT(sg.find(x)); sg.push(); // create y and concat(x,y) inside scope expr_ref xy(seq.str.mk_concat(x, y), m); sg.mk(xy); SASSERT(sg.num_nodes() > before); SASSERT(sg.find(y)); SASSERT(sg.find(xy)); sg.pop(1); // x persists, y and xy removed SASSERT(sg.find(x)); SASSERT(!sg.find(y)); SASSERT(!sg.find(xy)); SASSERT(sg.num_nodes() == before); } // test nested push/pop with multiple scopes static void test_sgraph_nested_scopes() { std::cout << "test_sgraph_nested_scopes\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref a(m.mk_const("a", str_sort), m); expr_ref b(m.mk_const("b", str_sort), m); expr_ref c(m.mk_const("c", str_sort), m); sg.mk(a); unsigned n0 = sg.num_nodes(); sg.push(); sg.mk(b); unsigned n1 = sg.num_nodes(); sg.push(); sg.mk(c); unsigned n2 = sg.num_nodes(); SASSERT(n2 > n1 && n1 > n0); // pop inner scope, c goes away sg.pop(1); SASSERT(sg.num_nodes() == n1); SASSERT(sg.find(a)); SASSERT(sg.find(b)); SASSERT(!sg.find(c)); // pop outer scope, b goes away sg.pop(1); SASSERT(sg.num_nodes() == n0); SASSERT(sg.find(a)); SASSERT(!sg.find(b)); } // test that find returns the same snode for the same expression static void test_sgraph_find_idempotent() { std::cout << "test_sgraph_find_idempotent\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); euf::snode* s1 = sg.mk(x); euf::snode* s2 = sg.mk(x); // calling mk again returns same node SASSERT(s1 == s2); SASSERT(s1 == sg.find(x)); } // test mk_concat: empty absorption, node construction via mk(concat_expr) static void test_sgraph_mk_concat() { std::cout << "test_sgraph_mk_concat\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); expr_ref y(m.mk_const("y", str_sort), m); expr_ref empty(seq.str.mk_empty(str_sort), m); euf::snode* sx = sg.mk(x); euf::snode* sy = sg.mk(y); euf::snode* se = sg.mk(empty); // concat with empty yields the non-empty side at sgraph level // (empty absorption is a property of the expression, checked via mk) SASSERT(se && se->is_empty()); // normal concat via expression expr_ref xy(seq.str.mk_concat(x, y), m); euf::snode* sxy = sg.mk(xy); SASSERT(sxy && sxy->is_concat()); SASSERT(sxy->num_args() == 2); SASSERT(sxy->arg(0) == sx); SASSERT(sxy->arg(1) == sy); // calling mk again with same expr returns same node euf::snode* sxy2 = sg.mk(xy); SASSERT(sxy == sxy2); } // test power node construction via mk(power_expr) static void test_sgraph_mk_power() { std::cout << "test_sgraph_mk_power\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); arith_util arith(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); expr_ref n(arith.mk_int(5), m); expr_ref xn(seq.str.mk_power(x, n), m); euf::snode* sx = sg.mk(x); euf::snode* sp = sg.mk(xn); SASSERT(sp && sp->is_power()); SASSERT(sp->num_args() == 2); SASSERT(sp->arg(0) == sx); // calling mk again returns same node euf::snode* sp2 = sg.mk(xn); SASSERT(sp == sp2); } // test snode first/last navigation on concat trees static void test_sgraph_first_last() { std::cout << "test_sgraph_first_last\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref a(m.mk_const("a", str_sort), m); expr_ref b(m.mk_const("b", str_sort), m); expr_ref c(m.mk_const("c", str_sort), m); euf::snode* sa = sg.mk(a); euf::snode* sb = sg.mk(b); euf::snode* sc = sg.mk(c); // concat(concat(a,b),c): first=a, last=c expr_ref ab(seq.str.mk_concat(a, b), m); expr_ref ab_c(seq.str.mk_concat(ab, c), m); euf::snode* sab_c = sg.mk(ab_c); SASSERT(sab_c->first() == sa); SASSERT(sab_c->last() == sc); // concat(a,concat(b,c)): first=a, last=c expr_ref bc(seq.str.mk_concat(b, c), m); expr_ref a_bc(seq.str.mk_concat(a, bc), m); euf::snode* sa_bc = sg.mk(a_bc); SASSERT(sa_bc->first() == sa); SASSERT(sa_bc->last() == sc); // single node: first and last are self SASSERT(sa->first() == sa); SASSERT(sa->last() == sa); } // test concat metadata propagation: // ground, regex_free, nullable, level, length static void test_sgraph_concat_metadata() { std::cout << "test_sgraph_concat_metadata\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); expr_ref empty(seq.str.mk_empty(str_sort), m); expr_ref ch(seq.str.mk_char('Z'), m); expr_ref unit_z(seq.str.mk_unit(ch), m); euf::snode* sx = sg.mk(x); euf::snode* se = sg.mk(empty); euf::snode* sz = sg.mk(unit_z); // concat(x, unit('Z')): not ground (x is variable), regex_free, not nullable expr_ref xz(seq.str.mk_concat(x, unit_z), m); euf::snode* sxz = sg.mk(xz); SASSERT(!sxz->is_ground()); SASSERT(sxz->is_regex_free()); SASSERT(sxz->length() == 2); SASSERT(sxz->level() == 2); // concat(empty, empty): nullable (both empty) expr_ref empty2(seq.str.mk_concat(empty, empty), m); euf::snode* see = sg.mk(empty2); SASSERT(see->is_ground()); SASSERT(see->length() == 0); // deep chain: concat(concat(x,x),concat(x,x)) has level 3, length 4 expr_ref xx(seq.str.mk_concat(x, x), m); expr_ref xxxx(seq.str.mk_concat(xx, xx), m); euf::snode* sxxxx = sg.mk(xxxx); SASSERT(sxxxx->level() == 3); SASSERT(sxxxx->length() == 4); } // test display does not crash static void test_sgraph_display() { std::cout << "test_sgraph_display\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); expr_ref x(m.mk_const("x", str_sort), m); expr_ref y(m.mk_const("y", str_sort), m); expr_ref xy(seq.str.mk_concat(x, y), m); sg.mk(xy); std::ostringstream oss; sg.display(oss); std::string out = oss.str(); SASSERT(out.find("var") != std::string::npos); SASSERT(out.find("concat") != std::string::npos); std::cout << out; } // test sgraph factory methods: mk_var, mk_char, mk_empty, mk_concat static void test_sgraph_factory() { std::cout << "test_sgraph_factory\n"; ast_manager m; 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"), sg.get_str_sort()); SASSERT(x && x->is_var()); SASSERT(!x->is_ground()); SASSERT(x->length() == 1); // mk_char euf::snode* a = sg.mk_char('A'); SASSERT(a && a->is_char()); SASSERT(a->is_ground()); SASSERT(a->length() == 1); // mk_empty euf::snode* e = sg.mk_empty_seq(seq.str.mk_string_sort()); SASSERT(e && e->is_empty()); SASSERT(e->length() == 0); // mk_concat with empty absorption euf::snode* xe = sg.mk_concat(x, e); SASSERT(xe == x); euf::snode* ex = sg.mk_concat(e, x); SASSERT(ex == x); // mk_concat of two variables euf::snode* y = sg.mk_var(symbol("y"), sg.get_str_sort()); euf::snode* xy = sg.mk_concat(x, y); SASSERT(xy && xy->is_concat()); SASSERT(xy->length() == 2); SASSERT(xy->arg(0) == x); SASSERT(xy->arg(1) == y); // mk_concat of multiple characters euf::snode* b = sg.mk_char('B'); euf::snode* c = sg.mk_char('C'); euf::snode* abc = sg.mk_concat(sg.mk_concat(a, b), c); SASSERT(abc->length() == 3); SASSERT(abc->is_ground()); SASSERT(abc->first() == a); SASSERT(abc->last() == c); } // test snode::at() and snode::collect_tokens() static void test_sgraph_indexing() { std::cout << "test_sgraph_indexing\n"; ast_manager m; 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'); euf::snode* c = sg.mk_char('C'); euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort()); // build concat(concat(a, b), concat(c, x)) => [A, B, C, x] euf::snode* ab = sg.mk_concat(a, b); euf::snode* cx = sg.mk_concat(c, x); euf::snode* abcx = sg.mk_concat(ab, cx); SASSERT(abcx->length() == 4); // test at() SASSERT(abcx->at(0) == a); SASSERT(abcx->at(1) == b); SASSERT(abcx->at(2) == c); SASSERT(abcx->at(3) == x); SASSERT(abcx->at(4) == nullptr); // out of bounds // test collect_tokens() euf::snode_vector tokens; abcx->collect_tokens(tokens); SASSERT(tokens.size() == 4); SASSERT(tokens[0] == a); SASSERT(tokens[1] == b); SASSERT(tokens[2] == c); SASSERT(tokens[3] == x); // single token: at(0) is self SASSERT(a->at(0) == a); SASSERT(a->at(1) == nullptr); // empty: at(0) is nullptr 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); SASSERT(empty_tokens.empty()); } // test sgraph drop operations static void test_sgraph_drop() { std::cout << "test_sgraph_drop\n"; ast_manager m; 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'); euf::snode* c = sg.mk_char('C'); euf::snode* d = sg.mk_char('D'); // build concat(concat(a, b), concat(c, d)) => [A, B, C, D] euf::snode* ab = sg.mk_concat(a, b); euf::snode* cd = sg.mk_concat(c, d); euf::snode* abcd = sg.mk_concat(ab, cd); SASSERT(abcd->length() == 4); // drop_first: [A, B, C, D] => [B, C, D] euf::snode* bcd = sg.drop_first(abcd); SASSERT(bcd->length() == 3); SASSERT(bcd->first() == b); SASSERT(bcd->last() == d); // drop_last: [A, B, C, D] => [A, B, C] euf::snode* abc = sg.drop_last(abcd); SASSERT(abc->length() == 3); SASSERT(abc->first() == a); SASSERT(abc->last() == c); // drop_left(2): [A, B, C, D] => [C, D] euf::snode* cd2 = sg.drop_left(abcd, 2); SASSERT(cd2->length() == 2); SASSERT(cd2->first() == c); // drop_right(2): [A, B, C, D] => [A, B] euf::snode* ab2 = sg.drop_right(abcd, 2); SASSERT(ab2->length() == 2); SASSERT(ab2->last() == b); // drop all: [A, B, C, D] => empty euf::snode* empty = sg.drop_left(abcd, 4); SASSERT(empty->is_empty()); // drop from single token: [A] => empty euf::snode* e = sg.drop_first(a); SASSERT(e->is_empty()); // drop from empty: no change euf::snode* ee = sg.drop_first(sg.mk_empty_seq(seq.str.mk_string_sort())); SASSERT(ee->is_empty()); } // test sgraph substitution static void test_sgraph_subst() { std::cout << "test_sgraph_subst\n"; ast_manager m; 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"), sg.get_str_sort()); euf::snode* y = sg.mk_var(symbol("y"), sg.get_str_sort()); euf::snode* a = sg.mk_char('A'); euf::snode* b = sg.mk_char('B'); // concat(x, concat(a, x)) with x -> b gives concat(b, concat(a, b)) euf::snode* ax = sg.mk_concat(a, x); euf::snode* xax = sg.mk_concat(x, ax); SASSERT(xax->length() == 3); euf::snode* result = sg.subst(xax, x, b); SASSERT(result->length() == 3); SASSERT(result->first() == b); SASSERT(result->last() == b); SASSERT(result->at(1) == a); // middle is still 'A' // substitution of non-occurring variable is identity euf::snode* same = sg.subst(xax, y, b); SASSERT(same == xax); // substitution of variable with 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); } // test complex concatenation creation, merging and simplification static void test_sgraph_complex_concat() { std::cout << "test_sgraph_complex_concat\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); // build a string "HELLO" = concat(H, concat(E, concat(L, concat(L, O)))) euf::snode* h = sg.mk_char('H'); euf::snode* e = sg.mk_char('E'); euf::snode* l = sg.mk_char('L'); euf::snode* o = sg.mk_char('O'); euf::snode* lo = sg.mk_concat(l, o); euf::snode* llo = sg.mk_concat(l, lo); euf::snode* ello = sg.mk_concat(e, llo); euf::snode* hello = sg.mk_concat(h, ello); SASSERT(hello->length() == 5); SASSERT(hello->is_ground()); SASSERT(hello->first() == h); SASSERT(hello->last() == o); // index into "HELLO" SASSERT(hello->at(0) == h); SASSERT(hello->at(1) == e); SASSERT(hello->at(2) == l); SASSERT(hello->at(3) == l); SASSERT(hello->at(4) == o); // drop first 2 from "HELLO" => "LLO" euf::snode* llo2 = sg.drop_left(hello, 2); SASSERT(llo2->length() == 3); SASSERT(llo2->first() == l); // drop last 3 from "HELLO" => "HE" euf::snode* he = sg.drop_right(hello, 3); SASSERT(he->length() == 2); SASSERT(he->first() == h); SASSERT(he->last() == e); // mixed variables and characters: concat(x, "AB", y) euf::snode* x = sg.mk_var(symbol("x"), sg.get_str_sort()); euf::snode* y = sg.mk_var(symbol("y"), sg.get_str_sort()); euf::snode* a = sg.mk_char('A'); euf::snode* b = sg.mk_char('B'); euf::snode* ab = sg.mk_concat(a, b); euf::snode* xab = sg.mk_concat(x, ab); euf::snode* xaby = sg.mk_concat(xab, y); SASSERT(xaby->length() == 4); SASSERT(!xaby->is_ground()); SASSERT(xaby->at(0) == x); SASSERT(xaby->at(1) == a); SASSERT(xaby->at(2) == b); SASSERT(xaby->at(3) == y); } // test Brzozowski derivative computation static void test_sgraph_brzozowski() { std::cout << "test_sgraph_brzozowski\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); // derivative of re.star(to_re("a")) w.r.t. 'a' // d/da (a*) = a* expr_ref ch_a(seq.str.mk_char('a'), m); expr_ref unit_a(seq.str.mk_unit(ch_a), m); expr_ref to_re_a(seq.re.mk_to_re(unit_a), m); expr_ref star_a(seq.re.mk_star(to_re_a), m); euf::snode* s_star_a = sg.mk(star_a); euf::snode* s_unit_a = sg.mk(unit_a); euf::snode* deriv = sg.brzozowski_deriv(s_star_a, s_unit_a); SASSERT(deriv != nullptr); std::cout << " d/da(a*) kind: " << (int)deriv->kind() << "\n"; // derivative of re.empty w.r.t. 'a' should be re.empty sort_ref re_sort(seq.re.mk_re(str_sort), m); expr_ref re_empty(seq.re.mk_empty(re_sort), m); euf::snode* s_empty = sg.mk(re_empty); euf::snode* deriv_empty = sg.brzozowski_deriv(s_empty, s_unit_a); SASSERT(deriv_empty != nullptr); SASSERT(deriv_empty->is_fail()); // derivative of empty set is empty set std::cout << " d/da(empty) kind: " << (int)deriv_empty->kind() << "\n"; sg.display(std::cout); } // test minterm computation static void test_sgraph_minterms() { std::cout << "test_sgraph_minterms\n"; ast_manager m; reg_decl_plugins(m); euf::egraph eg(m); euf::sgraph sg(m, eg); seq_util seq(m); sort_ref str_sort(seq.str.mk_string_sort(), m); // simple regex with no character predicates: re.all (.*) expr_ref re_all(seq.re.mk_full_seq(str_sort), m); euf::snode* s_re_all = sg.mk(re_all); euf::snode_vector minterms; sg.compute_minterms(s_re_all, minterms); // no predicates => single minterm (full_char) SASSERT(minterms.size() == 1); std::cout << " re.all minterms: " << minterms.size() << "\n"; // test union of strings: "evil" and "/evil" expr_ref evil(seq.re.mk_to_re(seq.str.mk_string(zstring("evil"))), m); expr_ref slash_evil(seq.re.mk_to_re(seq.str.mk_string(zstring("/evil"))), m); expr_ref union_re(seq.re.mk_union(evil, slash_evil), m); euf::snode* s_union_re = sg.mk(union_re); euf::snode_vector union_minterms; sg.compute_minterms(s_union_re, union_minterms); std::cout << " union minterms: " << union_minterms.size() << "\n"; // should collect 'e' and '/', yielding 3 disjoint subset partitions (e), (/), and the rest SASSERT(union_minterms.size() == 3); } void tst_euf_sgraph() { test_sgraph_classify(); test_sgraph_regex(); test_sgraph_power(); test_sgraph_push_pop(); test_sgraph_nested_scopes(); test_sgraph_find_idempotent(); test_sgraph_mk_concat(); test_sgraph_mk_power(); test_sgraph_first_last(); test_sgraph_concat_metadata(); test_sgraph_display(); test_sgraph_factory(); test_sgraph_indexing(); test_sgraph_drop(); test_sgraph_subst(); test_sgraph_complex_concat(); test_sgraph_brzozowski(); test_sgraph_minterms(); }