From 21c8f4aae01880dba291cc94accd741a394cb3c6 Mon Sep 17 00:00:00 2001
From: Murphy Berzish <murphy.berzish@gmail.com>
Date: Fri, 5 May 2017 19:26:15 -0400
Subject: [PATCH 1/2] formatting theory_str.cpp

---
 src/smt/theory_str.cpp | 20032 +++++++++++++++++++--------------------
 1 file changed, 10015 insertions(+), 10017 deletions(-)

diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp
index e7c99da69..4a6a6da5b 100644
--- a/src/smt/theory_str.cpp
+++ b/src/smt/theory_str.cpp
@@ -1,19 +1,19 @@
 /*++
-Module Name:
+  Module Name:
 
-    theory_str.cpp
+  theory_str.cpp
 
-Abstract:
+  Abstract:
 
-    String Theory Plugin
+  String Theory Plugin
 
-Author:
+  Author:
 
-    Murphy Berzish and Yunhui Zheng
+  Murphy Berzish and Yunhui Zheng
 
-Revision History:
+  Revision History:
 
---*/
+  --*/
 #include"ast_smt2_pp.h"
 #include"smt_context.h"
 #include"theory_str.h"
@@ -24,13 +24,11 @@ Revision History:
 #include<vector>
 #include<algorithm>
 #include"theory_seq_empty.h"
-
-#include "../ast/ast.h"
 #include"theory_arith.h"
 
 namespace smt {
-
-theory_str::theory_str(ast_manager & m, theory_str_params const & params):
+    
+    theory_str::theory_str(ast_manager & m, theory_str_params const & params):
         theory(m.mk_family_id("seq")),
         m_params(params),
         /* Options */
@@ -64,3864 +62,3041 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params):
         cacheMissCount(0),
         m_find(*this),
         m_trail_stack(*this)
-{
-    initialize_charset();
-}
+    {
+        initialize_charset();
+    }
 
-theory_str::~theory_str() {
-    m_trail_stack.reset();
-}
+    theory_str::~theory_str() {
+        m_trail_stack.reset();
+    }
 
-expr * theory_str::mk_string(zstring const& str) {
-    if (m_params.m_StringConstantCache) {
-        ++totalCacheAccessCount;
-        expr * val;
-        if (stringConstantCache.find(str, val)) {
-            return val;
+    expr * theory_str::mk_string(zstring const& str) {
+        if (m_params.m_StringConstantCache) {
+            ++totalCacheAccessCount;
+            expr * val;
+            if (stringConstantCache.find(str, val)) {
+                return val;
+            } else {
+                val = u.str.mk_string(str);
+                m_trail.push_back(val);
+                stringConstantCache.insert(str, val);
+                return val;
+            }
         } else {
-            val = u.str.mk_string(str);
-            m_trail.push_back(val);
-            stringConstantCache.insert(str, val);
-            return val;
+            return u.str.mk_string(str);
         }
-    } else {
-        return u.str.mk_string(str);
-    }
-}
-
-expr * theory_str::mk_string(const char * str) {
-    symbol sym(str);
-    return u.str.mk_string(sym);
-}
-
-void theory_str::initialize_charset() {
-	bool defaultCharset = true;
-	if (defaultCharset) {
-		// valid C strings can't contain the null byte ('\0')
-		charSetSize = 255;
-		char_set = alloc_svect(char, charSetSize);
-		int idx = 0;
-		// small letters
-		for (int i = 97; i < 123; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// caps
-		for (int i = 65; i < 91; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// numbers
-		for (int i = 48; i < 58; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// printable marks - 1
-		for (int i = 32; i < 48; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// printable marks - 2
-		for (int i = 58; i < 65; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// printable marks - 3
-		for (int i = 91; i < 97; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// printable marks - 4
-		for (int i = 123; i < 127; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// non-printable - 1
-		for (int i = 1; i < 32; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-		// non-printable - 2
-		for (int i = 127; i < 256; i++) {
-			char_set[idx] = (char) i;
-			charSetLookupTable[char_set[idx]] = idx;
-			idx++;
-		}
-	} else {
-		const char setset[] = { 'a', 'b', 'c' };
-		int fSize = sizeof(setset) / sizeof(char);
-
-		char_set = alloc_svect(char, fSize);
-		charSetSize = fSize;
-		for (int i = 0; i < charSetSize; i++) {
-			char_set[i] = setset[i];
-			charSetLookupTable[setset[i]] = i;
-		}
-	}
-}
-
-void theory_str::assert_axiom(expr * e) {
-    if (opt_VerifyFinalCheckProgress) {
-        finalCheckProgressIndicator = true;
     }
 
-    if (get_manager().is_true(e)) return;
-    TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;);
-    context & ctx = get_context();
-    if (!ctx.b_internalized(e)) {
-        ctx.internalize(e, false);
-    }
-    literal lit(ctx.get_literal(e));
-    ctx.mark_as_relevant(lit);
-    ctx.mk_th_axiom(get_id(), 1, &lit);
-
-    // crash/error avoidance: add all axioms to the trail
-    m_trail.push_back(e);
-
-    //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;);
-}
-
-expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) {
-    ast_manager & m = get_manager();
-    return m.mk_or(m.mk_not(premise), conclusion);
-}
-
-void theory_str::assert_implication(expr * premise, expr * conclusion) {
-    ast_manager & m = get_manager();
-    TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;);
-    expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m);
-    assert_axiom(axiom);
-}
-
-bool theory_str::internalize_atom(app * atom, bool gate_ctx) {
-    return internalize_term(atom);
-}
-
-bool theory_str::internalize_term(app * term) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-    SASSERT(term->get_family_id() == get_family_id());
-
-    TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;);
-
-    // emulation of user_smt_theory::internalize_term()
-
-    unsigned num_args = term->get_num_args();
-    for (unsigned i = 0; i < num_args; ++i) {
-        ctx.internalize(term->get_arg(i), false);
-    }
-    if (ctx.e_internalized(term)) {
-        enode * e = ctx.get_enode(term);
-        mk_var(e);
-        return true;
-    }
-    // m_parents.push_back(term);
-    enode * e = ctx.mk_enode(term, false, m.is_bool(term), true);
-    if (m.is_bool(term)) {
-        bool_var bv = ctx.mk_bool_var(term);
-        ctx.set_var_theory(bv, get_id());
-        ctx.set_enode_flag(bv, true);
-    }
-    // make sure every argument is attached to a theory variable
-    for (unsigned i = 0; i < num_args; ++i) {
-        enode * arg = e->get_arg(i);
-        theory_var v_arg = mk_var(arg);
-        TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;);
+    expr * theory_str::mk_string(const char * str) {
+        symbol sym(str);
+        return u.str.mk_string(sym);
     }
 
-    theory_var v = mk_var(e);
-    TRACE("str", tout << "term has theory var #" << v << std::endl;);
+    void theory_str::initialize_charset() {
+        bool defaultCharset = true;
+        if (defaultCharset) {
+            // valid C strings can't contain the null byte ('\0')
+            charSetSize = 255;
+            char_set = alloc_svect(char, charSetSize);
+            int idx = 0;
+            // small letters
+            for (int i = 97; i < 123; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // caps
+            for (int i = 65; i < 91; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // numbers
+            for (int i = 48; i < 58; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // printable marks - 1
+            for (int i = 32; i < 48; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // printable marks - 2
+            for (int i = 58; i < 65; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // printable marks - 3
+            for (int i = 91; i < 97; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // printable marks - 4
+            for (int i = 123; i < 127; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // non-printable - 1
+            for (int i = 1; i < 32; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+            // non-printable - 2
+            for (int i = 127; i < 256; i++) {
+                char_set[idx] = (char) i;
+                charSetLookupTable[char_set[idx]] = idx;
+                idx++;
+            }
+        } else {
+            const char setset[] = { 'a', 'b', 'c' };
+            int fSize = sizeof(setset) / sizeof(char);
 
-    if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) {
-        TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;);
-        m_basicstr_axiom_todo.insert(e);
-    }
-    return true;
-}
-
-enode* theory_str::ensure_enode(expr* e) {
-    context& ctx = get_context();
-    if (!ctx.e_internalized(e)) {
-        ctx.internalize(e, false);
-    }
-    enode* n = ctx.get_enode(e);
-    ctx.mark_as_relevant(n);
-    return n;
-}
-
-void theory_str::refresh_theory_var(expr * e) {
-    enode * en = ensure_enode(e);
-    theory_var v = mk_var(en);
-    TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;);
-    m_basicstr_axiom_todo.push_back(en);
-}
-
-theory_var theory_str::mk_var(enode* n) {
-    TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;);
-    ast_manager & m = get_manager();
-    if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) {
-        return null_theory_var;
-    }
-    if (is_attached_to_var(n)) {
-        TRACE("str", tout << "already attached to theory var" << std::endl;);
-        return n->get_th_var(get_id());
-    } else {
-        theory_var v = theory::mk_var(n);
-        m_find.mk_var();
-        TRACE("str", tout << "new theory var v#" << v << std::endl;);
-        get_context().attach_th_var(n, this, v);
-        get_context().mark_as_relevant(n);
-        return v;
-    }
-}
-
-static void cut_vars_map_copy(std::map<expr*, int> & dest, std::map<expr*, int> & src) {
-    std::map<expr*, int>::iterator itor = src.begin();
-    for (; itor != src.end(); itor++) {
-        dest[itor->first] = 1;
-    }
-}
-
-bool theory_str::has_self_cut(expr * n1, expr * n2) {
-	if (!cut_var_map.contains(n1)) {
-        return false;
-    }
-    if (!cut_var_map.contains(n2)) {
-        return false;
-    }
-    if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) {
-        return false;
+            char_set = alloc_svect(char, fSize);
+            charSetSize = fSize;
+            for (int i = 0; i < charSetSize; i++) {
+                char_set[i] = setset[i];
+                charSetLookupTable[setset[i]] = i;
+            }
+        }
     }
 
-    std::map<expr*, int>::iterator itor = cut_var_map[n1].top()->vars.begin();
-    for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) {
-        if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) {
+    void theory_str::assert_axiom(expr * e) {
+        if (opt_VerifyFinalCheckProgress) {
+            finalCheckProgressIndicator = true;
+        }
+
+        if (get_manager().is_true(e)) return;
+        TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;);
+        context & ctx = get_context();
+        if (!ctx.b_internalized(e)) {
+            ctx.internalize(e, false);
+        }
+        literal lit(ctx.get_literal(e));
+        ctx.mark_as_relevant(lit);
+        ctx.mk_th_axiom(get_id(), 1, &lit);
+
+        // crash/error avoidance: add all axioms to the trail
+        m_trail.push_back(e);
+
+        //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;);
+    }
+
+    expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) {
+        ast_manager & m = get_manager();
+        return m.mk_or(m.mk_not(premise), conclusion);
+    }
+
+    void theory_str::assert_implication(expr * premise, expr * conclusion) {
+        ast_manager & m = get_manager();
+        TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;);
+        expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m);
+        assert_axiom(axiom);
+    }
+
+    bool theory_str::internalize_atom(app * atom, bool gate_ctx) {
+        return internalize_term(atom);
+    }
+
+    bool theory_str::internalize_term(app * term) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        SASSERT(term->get_family_id() == get_family_id());
+
+        TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;);
+
+        // emulation of user_smt_theory::internalize_term()
+
+        unsigned num_args = term->get_num_args();
+        for (unsigned i = 0; i < num_args; ++i) {
+            ctx.internalize(term->get_arg(i), false);
+        }
+        if (ctx.e_internalized(term)) {
+            enode * e = ctx.get_enode(term);
+            mk_var(e);
             return true;
         }
-    }
-    return false;
-}
+        // m_parents.push_back(term);
+        enode * e = ctx.mk_enode(term, false, m.is_bool(term), true);
+        if (m.is_bool(term)) {
+            bool_var bv = ctx.mk_bool_var(term);
+            ctx.set_var_theory(bv, get_id());
+            ctx.set_enode_flag(bv, true);
+        }
+        // make sure every argument is attached to a theory variable
+        for (unsigned i = 0; i < num_args; ++i) {
+            enode * arg = e->get_arg(i);
+            theory_var v_arg = mk_var(arg);
+            TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;);
+        }
 
-void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) {
-    // crash avoidance?
-    m_trail.push_back(baseNode);
-    m_trail.push_back(node);
-    if (!cut_var_map.contains(baseNode)) {
-        T_cut * varInfo = alloc(T_cut);
-        varInfo->level = slevel;
-        varInfo->vars[node] = 1;
-        cut_var_map.insert(baseNode, std::stack<T_cut*>());
-        cut_var_map[baseNode].push(varInfo);
-        TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;);
-    } else {
-        if (cut_var_map[baseNode].empty()) {
+        theory_var v = mk_var(e);
+        TRACE("str", tout << "term has theory var #" << v << std::endl;);
+
+        if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) {
+            TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;);
+            m_basicstr_axiom_todo.insert(e);
+        }
+        return true;
+    }
+
+    enode* theory_str::ensure_enode(expr* e) {
+        context& ctx = get_context();
+        if (!ctx.e_internalized(e)) {
+            ctx.internalize(e, false);
+        }
+        enode* n = ctx.get_enode(e);
+        ctx.mark_as_relevant(n);
+        return n;
+    }
+
+    void theory_str::refresh_theory_var(expr * e) {
+        enode * en = ensure_enode(e);
+        theory_var v = mk_var(en);
+        TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;);
+        m_basicstr_axiom_todo.push_back(en);
+    }
+
+    theory_var theory_str::mk_var(enode* n) {
+        TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;);
+        ast_manager & m = get_manager();
+        if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) {
+            return null_theory_var;
+        }
+        if (is_attached_to_var(n)) {
+            TRACE("str", tout << "already attached to theory var" << std::endl;);
+            return n->get_th_var(get_id());
+        } else {
+            theory_var v = theory::mk_var(n);
+            m_find.mk_var();
+            TRACE("str", tout << "new theory var v#" << v << std::endl;);
+            get_context().attach_th_var(n, this, v);
+            get_context().mark_as_relevant(n);
+            return v;
+        }
+    }
+
+    static void cut_vars_map_copy(std::map<expr*, int> & dest, std::map<expr*, int> & src) {
+        std::map<expr*, int>::iterator itor = src.begin();
+        for (; itor != src.end(); itor++) {
+            dest[itor->first] = 1;
+        }
+    }
+
+    bool theory_str::has_self_cut(expr * n1, expr * n2) {
+        if (!cut_var_map.contains(n1)) {
+            return false;
+        }
+        if (!cut_var_map.contains(n2)) {
+            return false;
+        }
+        if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) {
+            return false;
+        }
+
+        std::map<expr*, int>::iterator itor = cut_var_map[n1].top()->vars.begin();
+        for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) {
+            if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) {
+        // crash avoidance?
+        m_trail.push_back(baseNode);
+        m_trail.push_back(node);
+        if (!cut_var_map.contains(baseNode)) {
             T_cut * varInfo = alloc(T_cut);
             varInfo->level = slevel;
             varInfo->vars[node] = 1;
+            cut_var_map.insert(baseNode, std::stack<T_cut*>());
             cut_var_map[baseNode].push(varInfo);
             TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;);
         } else {
-            if (cut_var_map[baseNode].top()->level < slevel) {
+            if (cut_var_map[baseNode].empty()) {
                 T_cut * varInfo = alloc(T_cut);
                 varInfo->level = slevel;
-                cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars);
                 varInfo->vars[node] = 1;
                 cut_var_map[baseNode].push(varInfo);
                 TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;);
-            } else if (cut_var_map[baseNode].top()->level == slevel) {
-                cut_var_map[baseNode].top()->vars[node] = 1;
-                TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;);
             } else {
-                get_manager().raise_exception("entered illegal state during add_cut_info_one_node()");
+                if (cut_var_map[baseNode].top()->level < slevel) {
+                    T_cut * varInfo = alloc(T_cut);
+                    varInfo->level = slevel;
+                    cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars);
+                    varInfo->vars[node] = 1;
+                    cut_var_map[baseNode].push(varInfo);
+                    TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;);
+                } else if (cut_var_map[baseNode].top()->level == slevel) {
+                    cut_var_map[baseNode].top()->vars[node] = 1;
+                    TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;);
+                } else {
+                    get_manager().raise_exception("entered illegal state during add_cut_info_one_node()");
+                }
             }
         }
     }
-}
 
-void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) {
-    // crash avoidance?
-    m_trail.push_back(destNode);
-    m_trail.push_back(srcNode);
-    if (!cut_var_map.contains(srcNode)) {
-        get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode");
-    }
+    void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) {
+        // crash avoidance?
+        m_trail.push_back(destNode);
+        m_trail.push_back(srcNode);
+        if (!cut_var_map.contains(srcNode)) {
+            get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode");
+        }
 
-    if (cut_var_map[srcNode].empty()) {
-        get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty");
-    }
+        if (cut_var_map[srcNode].empty()) {
+            get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty");
+        }
 
-    if (!cut_var_map.contains(destNode)) {
-        T_cut * varInfo = alloc(T_cut);
-        varInfo->level = slevel;
-        cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars);
-        cut_var_map.insert(destNode, std::stack<T_cut*>());
-        cut_var_map[destNode].push(varInfo);
-        TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;);
-    } else {
-        if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) {
+        if (!cut_var_map.contains(destNode)) {
             T_cut * varInfo = alloc(T_cut);
             varInfo->level = slevel;
-            cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars);
             cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars);
+            cut_var_map.insert(destNode, std::stack<T_cut*>());
             cut_var_map[destNode].push(varInfo);
             TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;);
-        } else if (cut_var_map[destNode].top()->level == slevel) {
-            cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars);
-            TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;);
         } else {
-            get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels");
-        }
-    }
-}
-
-void theory_str::check_and_init_cut_var(expr * node) {
-    if (cut_var_map.contains(node)) {
-        return;
-    } else if (!u.str.is_string(node)) {
-        add_cut_info_one_node(node, -1, node);
-    }
-}
-
-literal theory_str::mk_literal(expr* _e) {
-    ast_manager & m = get_manager();
-    expr_ref e(_e, m);
-    context& ctx = get_context();
-    ensure_enode(e);
-    return ctx.get_literal(e);
-}
-
-app * theory_str::mk_int(int n) {
-    return m_autil.mk_numeral(rational(n), true);
-}
-
-app * theory_str::mk_int(rational & q) {
-    return m_autil.mk_numeral(q, true);
-}
-
-expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) {
-	ast_manager & m = get_manager();
-
-	std::stringstream ss;
-	ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount;
-	tmpLenTestVarCount += 1;
-	std::string name = ss.str();
-	app * var = mk_str_var(name);
-	internal_lenTest_vars.insert(var);
-	m_trail.push_back(var);
-	return var;
-}
-
-expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) {
-	ast_manager & m = get_manager();
-	std::stringstream ss;
-	ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount;
-	tmpValTestVarCount += 1;
-	std::string name = ss.str();
-	app * var = mk_str_var(name);
-	internal_valTest_vars.insert(var);
-	m_trail.push_back(var);
-	return var;
-}
-
-void theory_str::track_variable_scope(expr * var) {
-    if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) {
-        internal_variable_scope_levels[sLevel] = std::set<expr*>();
-    }
-    internal_variable_scope_levels[sLevel].insert(var);
-}
-
-app * theory_str::mk_internal_xor_var() {
-    return mk_int_var("$$_xor");
-}
-
-app * theory_str::mk_int_var(std::string name) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;);
-
-    sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT);
-    app * a = m.mk_fresh_const(name.c_str(), int_sort);
-
-    ctx.internalize(a, false);
-    SASSERT(ctx.get_enode(a) != NULL);
-    SASSERT(ctx.e_internalized(a));
-    ctx.mark_as_relevant(a);
-    // I'm assuming that this combination will do the correct thing in the integer theory.
-
-    //mk_var(ctx.get_enode(a));
-    m_trail.push_back(a);
-    //variable_set.insert(a);
-    //internal_variable_set.insert(a);
-    //track_variable_scope(a);
-
-    return a;
-}
-
-app * theory_str::mk_unroll_bound_var() {
-	return mk_int_var("unroll");
-}
-
-app * theory_str::mk_unroll_test_var() {
-	app * v = mk_str_var("unrollTest"); // was uRt
-	internal_unrollTest_vars.insert(v);
-	track_variable_scope(v);
-	return v;
-}
-
-app * theory_str::mk_str_var(std::string name) {
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;);
-
-	sort * string_sort = u.str.mk_string_sort();
-	app * a = m.mk_fresh_const(name.c_str(), string_sort);
-
-	TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl
-	        << "this->get_family_id() = " << this->get_family_id() << std::endl;);
-
-	// I have a hunch that this may not get internalized for free...
-	ctx.internalize(a, false);
-	SASSERT(ctx.get_enode(a) != NULL);
-	SASSERT(ctx.e_internalized(a));
-	// this might help??
-	mk_var(ctx.get_enode(a));
-	m_basicstr_axiom_todo.push_back(ctx.get_enode(a));
-	TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;);
-
-	m_trail.push_back(a);
-	variable_set.insert(a);
-	internal_variable_set.insert(a);
-	track_variable_scope(a);
-
-	return a;
-}
-
-app * theory_str::mk_regex_rep_var() {
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	sort * string_sort = u.str.mk_string_sort();
-	app * a = m.mk_fresh_const("regex", string_sort);
-
-	ctx.internalize(a, false);
-	SASSERT(ctx.get_enode(a) != NULL);
-	SASSERT(ctx.e_internalized(a));
-	mk_var(ctx.get_enode(a));
-	m_basicstr_axiom_todo.push_back(ctx.get_enode(a));
-	TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;);
-
-	m_trail.push_back(a);
-	variable_set.insert(a);
-	//internal_variable_set.insert(a);
-	regex_variable_set.insert(a);
-	track_variable_scope(a);
-
-	return a;
-}
-
-void theory_str::add_nonempty_constraint(expr * s) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m);
-    assert_axiom(ax1);
-
-    {
-        // build LHS
-        expr_ref len_str(mk_strlen(s), m);
-        SASSERT(len_str);
-        // build RHS
-        expr_ref zero(m_autil.mk_numeral(rational(0), true), m);
-        SASSERT(zero);
-        // build LHS > RHS and assert
-        // we have to build !(LHS <= RHS) instead
-        expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m);
-        SASSERT(lhs_gt_rhs);
-        assert_axiom(lhs_gt_rhs);
-    }
-}
-
-app * theory_str::mk_nonempty_str_var() {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    std::stringstream ss;
-    ss << tmpStringVarCount;
-    tmpStringVarCount++;
-    std::string name = "$$_str" + ss.str();
-
-    TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;);
-
-    sort * string_sort = u.str.mk_string_sort();
-    app * a = m.mk_fresh_const(name.c_str(), string_sort);
-
-    ctx.internalize(a, false);
-    SASSERT(ctx.get_enode(a) != NULL);
-    // this might help??
-    mk_var(ctx.get_enode(a));
-
-    // assert a variation of the basic string axioms that ensures this string is nonempty
-    {
-        // build LHS
-        expr_ref len_str(mk_strlen(a), m);
-        SASSERT(len_str);
-        // build RHS
-        expr_ref zero(m_autil.mk_numeral(rational(0), true), m);
-        SASSERT(zero);
-        // build LHS > RHS and assert
-        // we have to build !(LHS <= RHS) instead
-        expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m);
-        SASSERT(lhs_gt_rhs);
-        assert_axiom(lhs_gt_rhs);
-    }
-
-    // add 'a' to variable sets, so we can keep track of it
-    m_trail.push_back(a);
-    variable_set.insert(a);
-    internal_variable_set.insert(a);
-    track_variable_scope(a);
-
-    return a;
-}
-
-app * theory_str::mk_unroll(expr * n, expr * bound) {
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	expr * args[2] = {n, bound};
-	app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args);
-	m_trail.push_back(unrollFunc);
-
-	expr_ref_vector items(m);
-	items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string(""))));
-	items.push_back(m_autil.mk_ge(bound, mk_int(0)));
-	items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0)));
-
-	expr_ref finalAxiom(mk_and(items), m);
-	SASSERT(finalAxiom);
-	assert_axiom(finalAxiom);
-	return unrollFunc;
-}
-
-app * theory_str::mk_contains(expr * haystack, expr * needle) {
-    app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order
-    m_trail.push_back(contains);
-    // immediately force internalization so that axiom setup does not fail
-    get_context().internalize(contains, false);
-    set_up_axioms(contains);
-    return contains;
-}
-
-app * theory_str::mk_indexof(expr * haystack, expr * needle) {
-    // TODO check meaning of the third argument here
-    app * indexof = u.str.mk_index(haystack, needle, mk_int(0));
-    m_trail.push_back(indexof);
-    // immediately force internalization so that axiom setup does not fail
-    get_context().internalize(indexof, false);
-    set_up_axioms(indexof);
-    return indexof;
-}
-
-app * theory_str::mk_strlen(expr * e) {
-    /*if (m_strutil.is_string(e)) {*/ if (false) {
-        zstring strval;
-        u.str.is_string(e, strval);
-        unsigned int len = strval.length();
-        return m_autil.mk_numeral(rational(len), true);
-    } else {
-        if (false) {
-            // use cache
-            app * lenTerm = NULL;
-            if (!length_ast_map.find(e, lenTerm)) {
-                lenTerm = u.str.mk_length(e);
-                length_ast_map.insert(e, lenTerm);
-                m_trail.push_back(lenTerm);
+            if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) {
+                T_cut * varInfo = alloc(T_cut);
+                varInfo->level = slevel;
+                cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars);
+                cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars);
+                cut_var_map[destNode].push(varInfo);
+                TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;);
+            } else if (cut_var_map[destNode].top()->level == slevel) {
+                cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars);
+                TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;);
+            } else {
+                get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels");
             }
-            return lenTerm;
-        } else {
-            // always regen
-            return u.str.mk_length(e);
         }
     }
-}
 
-/*
- * Returns the simplified concatenation of two expressions,
- * where either both expressions are constant strings
- * or one expression is the empty string.
- * If this precondition does not hold, the function returns NULL.
- * (note: this function was strTheory::Concat())
- */
-expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) {
-    bool n1HasEqcValue = false;
-    bool n2HasEqcValue = false;
-    expr * v1 = get_eqc_value(n1, n1HasEqcValue);
-    expr * v2 = get_eqc_value(n2, n2HasEqcValue);
-    if (n1HasEqcValue && n2HasEqcValue) {
-        zstring n1_str;
-        u.str.is_string(v1, n1_str);
-        zstring n2_str;
-        u.str.is_string(v2, n2_str);
-        zstring result = n1_str + n2_str;
-        return mk_string(result);
-    } else if (n1HasEqcValue && !n2HasEqcValue) {
-        zstring n1_str;
-        u.str.is_string(v1, n1_str);
-        if (n1_str.empty()) {
-            return n2;
-        }
-    } else if (!n1HasEqcValue && n2HasEqcValue) {
-        zstring n2_str;
-        u.str.is_string(v2, n2_str);
-        if (n2_str.empty()) {
-            return n1;
+    void theory_str::check_and_init_cut_var(expr * node) {
+        if (cut_var_map.contains(node)) {
+            return;
+        } else if (!u.str.is_string(node)) {
+            add_cut_info_one_node(node, -1, node);
         }
     }
-    return NULL;
-}
 
-expr * theory_str::mk_concat(expr * n1, expr * n2) {
-    context & ctx = get_context();
-	ast_manager & m = get_manager();
-	ENSURE(n1 != NULL);
-	ENSURE(n2 != NULL);
-	bool n1HasEqcValue = false;
-	bool n2HasEqcValue = false;
-	n1 = get_eqc_value(n1, n1HasEqcValue);
-	n2 = get_eqc_value(n2, n2HasEqcValue);
-	if (n1HasEqcValue && n2HasEqcValue) {
-	    return mk_concat_const_str(n1, n2);
-	} else if (n1HasEqcValue && !n2HasEqcValue) {
-	    bool n2_isConcatFunc = u.str.is_concat(to_app(n2));
-	    zstring n1_str;
-	    u.str.is_string(n1, n1_str);
-	    if (n1_str.empty()) {
-	        return n2;
-	    }
-	    if (n2_isConcatFunc) {
-	        expr * n2_arg0 = to_app(n2)->get_arg(0);
-	        expr * n2_arg1 = to_app(n2)->get_arg(1);
-	        if (u.str.is_string(n2_arg0)) {
-	            n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant
-	            n2 = n2_arg1;
-	        }
-	    }
-	} else if (!n1HasEqcValue && n2HasEqcValue) {
-	    zstring n2_str;
-	    u.str.is_string(n2, n2_str);
-	    if (n2_str.empty()) {
-	        return n1;
-	    }
-
-	    if (u.str.is_concat(to_app(n1))) {
-	        expr * n1_arg0 = to_app(n1)->get_arg(0);
-	        expr * n1_arg1 = to_app(n1)->get_arg(1);
-	        if (u.str.is_string(n1_arg1)) {
-	            n1 = n1_arg0;
-	            n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant
-	        }
-	    }
-	} else {
-	    if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) {
-	        expr * n1_arg0 = to_app(n1)->get_arg(0);
-	        expr * n1_arg1 = to_app(n1)->get_arg(1);
-	        expr * n2_arg0 = to_app(n2)->get_arg(0);
-	        expr * n2_arg1 = to_app(n2)->get_arg(1);
-	        if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) {
-	            expr * tmpN1 = n1_arg0;
-	            expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0);
-	            n1 = mk_concat(tmpN1, tmpN2);
-	            n2 = n2_arg1;
-	        }
-	    }
-	}
-
-	//------------------------------------------------------
-	// * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2);
-	// * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2);
-	// Z3 treats (ast1) and (ast2) as two different nodes.
-	//-------------------------------------------------------
-
-	expr * concatAst = NULL;
-
-	if (!concat_astNode_map.find(n1, n2, concatAst)) {
-	    concatAst = u.str.mk_concat(n1, n2);
-	    m_trail.push_back(concatAst);
-	    concat_astNode_map.insert(n1, n2, concatAst);
-
-	    expr_ref concat_length(mk_strlen(concatAst), m);
-
-	    ptr_vector<expr> childrenVector;
-	    get_nodes_in_concat(concatAst, childrenVector);
-	    expr_ref_vector items(m);
-	    for (unsigned int i = 0; i < childrenVector.size(); i++) {
-	        items.push_back(mk_strlen(childrenVector.get(i)));
-	    }
-	    expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m);
-	    assert_axiom(lenAssert);
-	}
-	return concatAst;
-}
-
-bool theory_str::can_propagate() {
-    return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty()
-            || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty()
-            || !m_library_aware_axiom_todo.empty()
-			|| !m_delayed_axiom_setup_terms.empty();
-            ;
-}
-
-void theory_str::propagate() {
-    context & ctx = get_context();
-    while (can_propagate()) {
-        TRACE("str", tout << "propagating..." << std::endl;);
-        for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) {
-            instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]);
-        }
-        m_basicstr_axiom_todo.reset();
-        TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;);
-
-        for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) {
-            std::pair<enode*,enode*> pair = m_str_eq_todo[i];
-            enode * lhs = pair.first;
-            enode * rhs = pair.second;
-            handle_equality(lhs->get_owner(), rhs->get_owner());
-        }
-        m_str_eq_todo.reset();
-
-        for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) {
-            instantiate_concat_axiom(m_concat_axiom_todo[i]);
-        }
-        m_concat_axiom_todo.reset();
-
-        for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) {
-            try_eval_concat(m_concat_eval_todo[i]);
-        }
-        m_concat_eval_todo.reset();
-
-        for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) {
-        	enode * e = m_library_aware_axiom_todo[i];
-        	app * a = e->get_owner();
-        	if (u.str.is_stoi(a)) {
-        		instantiate_axiom_str_to_int(e);
-        	} else if (u.str.is_itos(a)) {
-        	    instantiate_axiom_int_to_str(e);
-        	} else if (u.str.is_at(a)) {
-        	    instantiate_axiom_CharAt(e);
-        	} else if (u.str.is_prefix(a)) {
-        	    instantiate_axiom_prefixof(e);
-        	} else if (u.str.is_suffix(a)) {
-        	    instantiate_axiom_suffixof(e);
-        	} else if (u.str.is_contains(a)) {
-        	    instantiate_axiom_Contains(e);
-        	} else if (u.str.is_index(a)) {
-        	    instantiate_axiom_Indexof(e);
-        	    /* TODO NEXT: Indexof2/Lastindexof rewrite?
-        	} else if (is_Indexof2(e)) {
-        	    instantiate_axiom_Indexof2(e);
-        	} else if (is_LastIndexof(e)) {
-        	    instantiate_axiom_LastIndexof(e);
-        	    */
-        	} else if (u.str.is_extract(a)) {
-        	    // TODO check semantics of substr vs. extract
-        	    instantiate_axiom_Substr(e);
-        	} else if (u.str.is_replace(a)) {
-        	    instantiate_axiom_Replace(e);
-        	} else if (u.str.is_in_re(a)) {
-        	    instantiate_axiom_RegexIn(e);
-        	} else {
-        		TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;);
-        		NOT_IMPLEMENTED_YET();
-        	}
-        }
-        m_library_aware_axiom_todo.reset();
-
-        for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) {
-            // I think this is okay
-            ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false);
-            set_up_axioms(m_delayed_axiom_setup_terms[i].get());
-        }
-        m_delayed_axiom_setup_terms.reset();
-    }
-}
-
-/*
- * Attempt to evaluate a concat over constant strings,
- * and if this is possible, assert equality between the
- * flattened string and the original term.
- */
-
-void theory_str::try_eval_concat(enode * cat) {
-    app * a_cat = cat->get_owner();
-    SASSERT(u.str.is_concat(a_cat));
-
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;);
-
-    std::stack<app*> worklist;
-    zstring flattenedString("");
-    bool constOK = true;
-
-    {
-        app * arg0 = to_app(a_cat->get_arg(0));
-        app * arg1 = to_app(a_cat->get_arg(1));
-
-        worklist.push(arg1);
-        worklist.push(arg0);
+    literal theory_str::mk_literal(expr* _e) {
+        ast_manager & m = get_manager();
+        expr_ref e(_e, m);
+        context& ctx = get_context();
+        ensure_enode(e);
+        return ctx.get_literal(e);
     }
 
-    while (constOK && !worklist.empty()) {
-        app * evalArg = worklist.top(); worklist.pop();
-        zstring nextStr;
-        if (u.str.is_string(evalArg, nextStr)) {
-            flattenedString = flattenedString + nextStr;
-        } else if (u.str.is_concat(evalArg)) {
-            app * arg0 = to_app(evalArg->get_arg(0));
-            app * arg1 = to_app(evalArg->get_arg(1));
+    app * theory_str::mk_int(int n) {
+        return m_autil.mk_numeral(rational(n), true);
+    }
 
-            worklist.push(arg1);
-            worklist.push(arg0);
-        } else {
-            TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;);
-            constOK = false;
-            break;
+    app * theory_str::mk_int(rational & q) {
+        return m_autil.mk_numeral(q, true);
+    }
+
+    expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) {
+        ast_manager & m = get_manager();
+
+        std::stringstream ss;
+        ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount;
+        tmpLenTestVarCount += 1;
+        std::string name = ss.str();
+        app * var = mk_str_var(name);
+        internal_lenTest_vars.insert(var);
+        m_trail.push_back(var);
+        return var;
+    }
+
+    expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) {
+        ast_manager & m = get_manager();
+        std::stringstream ss;
+        ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount;
+        tmpValTestVarCount += 1;
+        std::string name = ss.str();
+        app * var = mk_str_var(name);
+        internal_valTest_vars.insert(var);
+        m_trail.push_back(var);
+        return var;
+    }
+
+    void theory_str::track_variable_scope(expr * var) {
+        if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) {
+            internal_variable_scope_levels[sLevel] = std::set<expr*>();
         }
-    }
-    if (constOK) {
-        TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;);
-        expr_ref constStr(mk_string(flattenedString), m);
-        expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m);
-        assert_axiom(axiom);
-    }
-}
-
-/*
- * Instantiate an axiom of the following form:
- * Length(Concat(x, y)) = Length(x) + Length(y)
- */
-void theory_str::instantiate_concat_axiom(enode * cat) {
-    app * a_cat = cat->get_owner();
-    SASSERT(u.str.is_concat(a_cat));
-
-    ast_manager & m = get_manager();
-
-    TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;);
-
-    // build LHS
-    expr_ref len_xy(m);
-    len_xy = mk_strlen(a_cat);
-    SASSERT(len_xy);
-
-    // build RHS: start by extracting x and y from Concat(x, y)
-    unsigned nArgs = a_cat->get_num_args();
-    SASSERT(nArgs == 2);
-    app * a_x = to_app(a_cat->get_arg(0));
-    app * a_y = to_app(a_cat->get_arg(1));
-
-    expr_ref len_x(m);
-    len_x = mk_strlen(a_x);
-    SASSERT(len_x);
-
-    expr_ref len_y(m);
-    len_y = mk_strlen(a_y);
-    SASSERT(len_y);
-
-    // now build len_x + len_y
-    expr_ref len_x_plus_len_y(m);
-    len_x_plus_len_y = m_autil.mk_add(len_x, len_y);
-    SASSERT(len_x_plus_len_y);
-
-    // finally assert equality between the two subexpressions
-    app * eq = m.mk_eq(len_xy, len_x_plus_len_y);
-    SASSERT(eq);
-    assert_axiom(eq);
-}
-
-/*
- * Add axioms that are true for any string variable:
- * 1. Length(x) >= 0
- * 2. Length(x) == 0 <=> x == ""
- * If the term is a string constant, we can assert something stronger:
- *    Length(x) == strlen(x)
- */
-void theory_str::instantiate_basic_string_axioms(enode * str) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;);
-
-    // TESTING: attempt to avoid a crash here when a variable goes out of scope
-    if (str->get_iscope_lvl() > ctx.get_scope_level()) {
-        TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;);
-        return;
+        internal_variable_scope_levels[sLevel].insert(var);
     }
 
-    // generate a stronger axiom for constant strings
-    app * a_str = str->get_owner();
-    if (u.str.is_string(a_str)) {
-        expr_ref len_str(m);
-        len_str = mk_strlen(a_str);
-        SASSERT(len_str);
+    app * theory_str::mk_internal_xor_var() {
+        return mk_int_var("$$_xor");
+    }
 
-        zstring strconst;
-        u.str.is_string(str->get_owner(), strconst);
-        TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;);
-        unsigned int l = strconst.length();
-        expr_ref len(m_autil.mk_numeral(rational(l), true), m);
+    app * theory_str::mk_int_var(std::string name) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;);
+
+        sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT);
+        app * a = m.mk_fresh_const(name.c_str(), int_sort);
+
+        ctx.internalize(a, false);
+        SASSERT(ctx.get_enode(a) != NULL);
+        SASSERT(ctx.e_internalized(a));
+        ctx.mark_as_relevant(a);
+        // I'm assuming that this combination will do the correct thing in the integer theory.
+
+        //mk_var(ctx.get_enode(a));
+        m_trail.push_back(a);
+        //variable_set.insert(a);
+        //internal_variable_set.insert(a);
+        //track_variable_scope(a);
+
+        return a;
+    }
+
+    app * theory_str::mk_unroll_bound_var() {
+        return mk_int_var("unroll");
+    }
+
+    app * theory_str::mk_unroll_test_var() {
+        app * v = mk_str_var("unrollTest"); // was uRt
+        internal_unrollTest_vars.insert(v);
+        track_variable_scope(v);
+        return v;
+    }
+
+    app * theory_str::mk_str_var(std::string name) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;);
+
+        sort * string_sort = u.str.mk_string_sort();
+        app * a = m.mk_fresh_const(name.c_str(), string_sort);
+
+        TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl
+              << "this->get_family_id() = " << this->get_family_id() << std::endl;);
+
+        // I have a hunch that this may not get internalized for free...
+        ctx.internalize(a, false);
+        SASSERT(ctx.get_enode(a) != NULL);
+        SASSERT(ctx.e_internalized(a));
+        // this might help??
+        mk_var(ctx.get_enode(a));
+        m_basicstr_axiom_todo.push_back(ctx.get_enode(a));
+        TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;);
+
+        m_trail.push_back(a);
+        variable_set.insert(a);
+        internal_variable_set.insert(a);
+        track_variable_scope(a);
+
+        return a;
+    }
+
+    app * theory_str::mk_regex_rep_var() {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        sort * string_sort = u.str.mk_string_sort();
+        app * a = m.mk_fresh_const("regex", string_sort);
+
+        ctx.internalize(a, false);
+        SASSERT(ctx.get_enode(a) != NULL);
+        SASSERT(ctx.e_internalized(a));
+        mk_var(ctx.get_enode(a));
+        m_basicstr_axiom_todo.push_back(ctx.get_enode(a));
+        TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;);
+
+        m_trail.push_back(a);
+        variable_set.insert(a);
+        //internal_variable_set.insert(a);
+        regex_variable_set.insert(a);
+        track_variable_scope(a);
+
+        return a;
+    }
+
+    void theory_str::add_nonempty_constraint(expr * s) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m);
+        assert_axiom(ax1);
 
-        literal lit(mk_eq(len_str, len, false));
-        ctx.mark_as_relevant(lit);
-        ctx.mk_th_axiom(get_id(), 1, &lit);
-    } else {
-        // build axiom 1: Length(a_str) >= 0
         {
             // build LHS
-            expr_ref len_str(m);
-            len_str = mk_strlen(a_str);
+            expr_ref len_str(mk_strlen(s), m);
             SASSERT(len_str);
             // build RHS
-            expr_ref zero(m);
-            zero = m_autil.mk_numeral(rational(0), true);
+            expr_ref zero(m_autil.mk_numeral(rational(0), true), m);
             SASSERT(zero);
-            // build LHS >= RHS and assert
-            app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero);
-            SASSERT(lhs_ge_rhs);
-            TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;);
-            assert_axiom(lhs_ge_rhs);
+            // build LHS > RHS and assert
+            // we have to build !(LHS <= RHS) instead
+            expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m);
+            SASSERT(lhs_gt_rhs);
+            assert_axiom(lhs_gt_rhs);
         }
+    }
 
-        // build axiom 2: Length(a_str) == 0 <=> a_str == ""
+    app * theory_str::mk_nonempty_str_var() {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        std::stringstream ss;
+        ss << tmpStringVarCount;
+        tmpStringVarCount++;
+        std::string name = "$$_str" + ss.str();
+
+        TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;);
+
+        sort * string_sort = u.str.mk_string_sort();
+        app * a = m.mk_fresh_const(name.c_str(), string_sort);
+
+        ctx.internalize(a, false);
+        SASSERT(ctx.get_enode(a) != NULL);
+        // this might help??
+        mk_var(ctx.get_enode(a));
+
+        // assert a variation of the basic string axioms that ensures this string is nonempty
         {
-            // build LHS of iff
-            expr_ref len_str(m);
-            len_str = mk_strlen(a_str);
+            // build LHS
+            expr_ref len_str(mk_strlen(a), m);
             SASSERT(len_str);
-            expr_ref zero(m);
-            zero = m_autil.mk_numeral(rational(0), true);
+            // build RHS
+            expr_ref zero(m_autil.mk_numeral(rational(0), true), m);
             SASSERT(zero);
-            expr_ref lhs(m);
-            lhs = ctx.mk_eq_atom(len_str, zero);
-            SASSERT(lhs);
-            // build RHS of iff
-            expr_ref empty_str(m);
-            empty_str = mk_string("");
-            SASSERT(empty_str);
-            expr_ref rhs(m);
-            rhs = ctx.mk_eq_atom(a_str, empty_str);
-            SASSERT(rhs);
-            // build LHS <=> RHS and assert
-            TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;);
-            literal l(mk_eq(lhs, rhs, true));
-            ctx.mark_as_relevant(l);
-            ctx.mk_th_axiom(get_id(), 1, &l);
+            // build LHS > RHS and assert
+            // we have to build !(LHS <= RHS) instead
+            expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m);
+            SASSERT(lhs_gt_rhs);
+            assert_axiom(lhs_gt_rhs);
         }
 
-    }
-}
+        // add 'a' to variable sets, so we can keep track of it
+        m_trail.push_back(a);
+        variable_set.insert(a);
+        internal_variable_set.insert(a);
+        track_variable_scope(a);
 
-/*
- * Add an axiom of the form:
- * (lhs == rhs) -> ( Length(lhs) == Length(rhs) )
- */
-void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * a_lhs = lhs->get_owner();
-    app * a_rhs = rhs->get_owner();
-
-    // build premise: (lhs == rhs)
-    expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m);
-
-    // build conclusion: ( Length(lhs) == Length(rhs) )
-    expr_ref len_lhs(mk_strlen(a_lhs), m);
-    SASSERT(len_lhs);
-    expr_ref len_rhs(mk_strlen(a_rhs), m);
-    SASSERT(len_rhs);
-    expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m);
-
-    TRACE("str", tout << "string-eq length-eq axiom: "
-            << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;);
-    assert_implication(premise, conclusion);
-}
-
-void theory_str::instantiate_axiom_CharAt(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;);
-
-    expr_ref ts0(mk_str_var("ts0"), m);
-    expr_ref ts1(mk_str_var("ts1"), m);
-    expr_ref ts2(mk_str_var("ts2"), m);
-
-    expr_ref cond(m.mk_and(
-            m_autil.mk_ge(expr->get_arg(1), mk_int(0)),
-            // REWRITE for arithmetic theory:
-            // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0)))
-            m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0)))
-            ), m);
-
-    expr_ref_vector and_item(m);
-    and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2))));
-    and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0)));
-    and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1)));
-
-    expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m);
-    expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m);
-
-    expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m);
-    expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m);
-
-    SASSERT(axiom);
-    SASSERT(reductionVar);
-
-    expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m);
-    SASSERT(finalAxiom);
-    assert_axiom(finalAxiom);
-}
-
-void theory_str::instantiate_axiom_prefixof(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;);
-
-    expr_ref ts0(mk_str_var("ts0"), m);
-    expr_ref ts1(mk_str_var("ts1"), m);
-
-    expr_ref_vector innerItems(m);
-    innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1)));
-    innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0))));
-    innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr)));
-    expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m);
-    SASSERT(then1);
-
-    // the top-level condition is Length(arg0) >= Length(arg1)
-    expr_ref topLevelCond(
-            m_autil.mk_ge(
-                    m_autil.mk_add(
-                            mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))),
-                            mk_int(0))
-                            , m);
-    SASSERT(topLevelCond);
-
-    expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m);
-    SASSERT(finalAxiom);
-    assert_axiom(finalAxiom);
-}
-
-void theory_str::instantiate_axiom_suffixof(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;);
-
-    expr_ref ts0(mk_str_var("ts0"), m);
-    expr_ref ts1(mk_str_var("ts1"), m);
-
-    expr_ref_vector innerItems(m);
-    innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1)));
-    innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0))));
-    innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr)));
-    expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m);
-    SASSERT(then1);
-
-    // the top-level condition is Length(arg0) >= Length(arg1)
-    expr_ref topLevelCond(
-            m_autil.mk_ge(
-                    m_autil.mk_add(
-                            mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))),
-                            mk_int(0))
-                            , m);
-    SASSERT(topLevelCond);
-
-    expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m);
-    SASSERT(finalAxiom);
-    assert_axiom(finalAxiom);
-}
-
-void theory_str::instantiate_axiom_Contains(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * ex = e->get_owner();
-    if (axiomatized_terms.contains(ex)) {
-        TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(ex);
-
-    // quick path, because this is necessary due to rewriter behaviour
-    // at minimum it should fix z3str/concat-006.smt2
-    zstring haystackStr, needleStr;
-    if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) {
-        TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;);
-        if (haystackStr.contains(needleStr)) {
-            assert_axiom(ex);
-        } else {
-            assert_axiom(m.mk_not(ex));
-        }
-        return;
+        return a;
     }
 
-    { // register Contains()
-        expr * str = ex->get_arg(0);
-        expr * substr = ex->get_arg(1);
-        contains_map.push_back(ex);
-        std::pair<expr*, expr*> key = std::pair<expr*, expr*>(str, substr);
-        contain_pair_bool_map.insert(str, substr, ex);
-        contain_pair_idx_map[str].insert(key);
-        contain_pair_idx_map[substr].insert(key);
-    }
+    app * theory_str::mk_unroll(expr * n, expr * bound) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-    TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;);
+        expr * args[2] = {n, bound};
+        app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args);
+        m_trail.push_back(unrollFunc);
 
-    expr_ref ts0(mk_str_var("ts0"), m);
-    expr_ref ts1(mk_str_var("ts1"), m);
+        expr_ref_vector items(m);
+        items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string(""))));
+        items.push_back(m_autil.mk_ge(bound, mk_int(0)));
+        items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0)));
 
-    expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m);
-    SASSERT(breakdownAssert);
-    assert_axiom(breakdownAssert);
-}
-
-void theory_str::instantiate_axiom_Indexof(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;);
-
-    expr_ref x1(mk_str_var("x1"), m);
-    expr_ref x2(mk_str_var("x2"), m);
-    expr_ref indexAst(mk_int_var("index"), m);
-
-    expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m);
-    SASSERT(condAst);
-
-    // -----------------------
-    // true branch
-    expr_ref_vector thenItems(m);
-    //  args[0] = x1 . args[1] . x2
-    thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))));
-    //  indexAst = |x1|
-    thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1)));
-    //     args[0]  = x3 . x4
-    //  /\ |x3| = |x1| + |args[1]| - 1
-    //  /\ ! contains(x3, args[1])
-    expr_ref x3(mk_str_var("x3"), m);
-    expr_ref x4(mk_str_var("x4"), m);
-    expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m);
-    SASSERT(tmpLen);
-    thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4)));
-    thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen));
-    thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1))));
-    expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m);
-    SASSERT(thenBranch);
-
-    // -----------------------
-    // false branch
-    expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m);
-    SASSERT(elseBranch);
-
-    expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m);
-    SASSERT(breakdownAssert);
-
-    expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m);
-    SASSERT(reduceToIndex);
-
-    expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m);
-    SASSERT(finalAxiom);
-    assert_axiom(finalAxiom);
-}
-
-void theory_str::instantiate_axiom_Indexof2(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;);
-
-    // -------------------------------------------------------------------------------
-    //   if (arg[2] >= length(arg[0]))                          // ite2
-    //     resAst = -1
-    //   else
-    //     args[0] = prefix . suffix
-    //     /\ indexAst = indexof(suffix, arg[1])
-    //     /\ args[2] = len(prefix)
-    //     /\ if (indexAst == -1)  resAst = indexAst            // ite3
-    //        else   resAst = args[2] + indexAst
-    // -------------------------------------------------------------------------------
-
-    expr_ref resAst(mk_int_var("res"), m);
-    expr_ref indexAst(mk_int_var("index"), m);
-    expr_ref prefix(mk_str_var("prefix"), m);
-    expr_ref suffix(mk_str_var("suffix"), m);
-    expr_ref prefixLen(mk_strlen(prefix), m);
-    expr_ref zeroAst(mk_int(0), m);
-    expr_ref negOneAst(mk_int(-1), m);
-
-    expr_ref ite3(m.mk_ite(
-            ctx.mk_eq_atom(indexAst, negOneAst),
-            ctx.mk_eq_atom(resAst, negOneAst),
-            ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst))
-            ),m);
-
-    expr_ref_vector ite2ElseItems(m);
-    ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix)));
-    ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1))));
-    ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen));
-    ite2ElseItems.push_back(ite3);
-    expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m);
-    SASSERT(ite2Else);
-
-    expr_ref ite2(m.mk_ite(
-            //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))),
-            m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst),
-            ctx.mk_eq_atom(resAst, negOneAst),
-            ite2Else
-            ), m);
-    SASSERT(ite2);
-
-    expr_ref ite1(m.mk_ite(
-            //m_autil.mk_lt(expr->get_arg(2), zeroAst),
-            m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)),
-            ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))),
-            ite2
-            ), m);
-    SASSERT(ite1);
-    assert_axiom(ite1);
-
-    expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m);
-    SASSERT(reduceTerm);
-    assert_axiom(reduceTerm);
-}
-
-void theory_str::instantiate_axiom_LastIndexof(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;);
-
-    expr_ref x1(mk_str_var("x1"), m);
-    expr_ref x2(mk_str_var("x2"), m);
-    expr_ref indexAst(mk_int_var("index"), m);
-    expr_ref_vector items(m);
-
-    // args[0] = x1 . args[1] . x2
-    expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m);
-    expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m);  // arg0HasArg1 = Contains(args[0], args[1])
-    items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1));
-
-
-    expr_ref condAst(arg0HasArg1, m);
-    //----------------------------
-    // true branch
-    expr_ref_vector thenItems(m);
-    thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0)));
-    //  args[0] = x1 . args[1] . x2
-    //  x1 doesn't contain args[1]
-    thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1))));
-    thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1)));
-
-    bool canSkip = false;
-    zstring arg1Str;
-    if (u.str.is_string(expr->get_arg(1), arg1Str)) {
-        if (arg1Str.length() == 1) {
-            canSkip = true;
-        }
-    }
-
-    if (!canSkip) {
-        // args[0]  = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1])
-        expr_ref x3(mk_str_var("x3"), m);
-        expr_ref x4(mk_str_var("x4"), m);
-        expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m);
-        thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4)));
-        thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen));
-        thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1))));
-    }
-    //----------------------------
-    // else branch
-    expr_ref_vector elseItems(m);
-    elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1)));
-
-    items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr())));
-
-    expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m);
-    SASSERT(breakdownAssert);
-
-    expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m);
-    SASSERT(reduceToIndex);
-
-    expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m);
-    SASSERT(finalAxiom);
-    assert_axiom(finalAxiom);
-}
-
-void theory_str::instantiate_axiom_Substr(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;);
-
-    expr_ref substrBase(expr->get_arg(0), m);
-    expr_ref substrPos(expr->get_arg(1), m);
-    expr_ref substrLen(expr->get_arg(2), m);
-    SASSERT(substrBase);
-    SASSERT(substrPos);
-    SASSERT(substrLen);
-
-    expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m);
-    expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m);
-    SASSERT(zero);
-    SASSERT(minusOne);
-
-    expr_ref_vector argumentsValid_terms(m);
-    // pos >= 0
-    argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero));
-    // pos < strlen(base)
-    // --> pos + -1*strlen(base) < 0
-    argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge(
-            m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)),
-            zero)));
-    // len >= 0
-    argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero));
-
-    expr_ref argumentsValid(mk_and(argumentsValid_terms), m);
-    SASSERT(argumentsValid);
-    ctx.internalize(argumentsValid, false);
-
-    // (pos+len) >= strlen(base)
-    // --> pos + len + -1*strlen(base) >= 0
-    expr_ref lenOutOfBounds(m_autil.mk_ge(
-            m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))),
-            zero), m);
-    SASSERT(lenOutOfBounds);
-    ctx.internalize(argumentsValid, false);
-
-    // Case 1: pos < 0 or pos >= strlen(base) or len < 0
-    // ==> (Substr ...) = ""
-    expr_ref case1_premise(m.mk_not(argumentsValid), m);
-    SASSERT(case1_premise);
-    ctx.internalize(case1_premise, false);
-    expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m);
-    SASSERT(case1_conclusion);
-    ctx.internalize(case1_conclusion, false);
-    expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m);
-    SASSERT(case1);
-
-    // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base)
-    // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1
-    expr_ref t0(mk_str_var("t0"), m);
-    expr_ref t1(mk_str_var("t1"), m);
-    expr_ref case2_conclusion(m.mk_and(
-            ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)),
-            ctx.mk_eq_atom(mk_strlen(t0), substrPos),
-            ctx.mk_eq_atom(expr, t1)), m);
-    expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m);
-    SASSERT(case2);
-
-    // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base)
-    // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3
-    expr_ref t2(mk_str_var("t2"), m);
-    expr_ref t3(mk_str_var("t3"), m);
-    expr_ref t4(mk_str_var("t4"), m);
-    expr_ref_vector case3_conclusion_terms(m);
-    case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4))));
-    case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos));
-    case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen));
-    case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3));
-    expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m);
-    expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m);
-    SASSERT(case3);
-
-    ctx.internalize(case1, false);
-    ctx.internalize(case2, false);
-    ctx.internalize(case3, false);
-
-    expr_ref finalAxiom(m.mk_and(case1, case2, case3), m);
-    SASSERT(finalAxiom);
-    assert_axiom(finalAxiom);
-}
-
-void theory_str::instantiate_axiom_Replace(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * expr = e->get_owner();
-    if (axiomatized_terms.contains(expr)) {
-        TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(expr);
-
-    TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;);
-
-    expr_ref x1(mk_str_var("x1"), m);
-    expr_ref x2(mk_str_var("x2"), m);
-    expr_ref i1(mk_int_var("i1"), m);
-    expr_ref result(mk_str_var("result"), m);
-
-    // condAst = Contains(args[0], args[1])
-    expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m);
-    // -----------------------
-    // true branch
-    expr_ref_vector thenItems(m);
-    //  args[0] = x1 . args[1] . x2
-    thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))));
-    //  i1 = |x1|
-    thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1)));
-    //  args[0]  = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1])
-    expr_ref x3(mk_str_var("x3"), m);
-    expr_ref x4(mk_str_var("x4"), m);
-    expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m);
-    thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4)));
-    thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen));
-    thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1))));
-    thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2))));
-    // -----------------------
-    // false branch
-    expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m);
-
-    expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m);
-    SASSERT(breakdownAssert);
-
-    expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m);
-    SASSERT(reduceToResult);
-
-    expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m);
-    SASSERT(finalAxiom);
-    assert_axiom(finalAxiom);
-}
-
-void theory_str::instantiate_axiom_str_to_int(enode * e) {
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	app * ex = e->get_owner();
-	if (axiomatized_terms.contains(ex)) {
-		TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;);
-		return;
-	}
-	axiomatized_terms.insert(ex);
-
-	TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;);
-
-	// let expr = (str.to-int S)
-	// axiom 1: expr >= -1
-	// axiom 2: expr = 0 <==> S = "0"
-	// axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0"
-
-	expr * S = ex->get_arg(0);
-	{
-		expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m);
-		SASSERT(axiom1);
-		assert_axiom(axiom1);
-	}
-
-	{
-		expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m);
-		expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m);
-		expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m);
-		SASSERT(axiom2);
-		assert_axiom(axiom2);
-	}
-
-	{
-		expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m);
-		expr_ref hd(mk_str_var("hd"), m);
-		expr_ref tl(mk_str_var("tl"), m);
-		expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m);
-		expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m);
-		expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m);
-		expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m);
-		SASSERT(premise);
-		SASSERT(conclusion);
-		assert_implication(premise, conclusion);
-	}
-}
-
-void theory_str::instantiate_axiom_int_to_str(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * ex = e->get_owner();
-    if (axiomatized_terms.contains(ex)) {
-        TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(ex);
-
-    TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;);
-
-    // axiom 1: N < 0 <==> (str.from-int N) = ""
-    expr * N = ex->get_arg(0);
-    {
-        expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m);
-        expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m);
-        expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m);
-        SASSERT(axiom1);
-        assert_axiom(axiom1);
-    }
-}
-
-expr * theory_str::mk_RegexIn(expr * str, expr * regexp) {
-    app * regexIn = u.re.mk_in_re(str, regexp);
-    // immediately force internalization so that axiom setup does not fail
-    get_context().internalize(regexIn, false);
-    set_up_axioms(regexIn);
-    return regexIn;
-}
-
-static zstring str2RegexStr(zstring str) {
-    zstring res("");
-    int len = str.length();
-    for (int i = 0; i < len; i++) {
-      char nc = str[i];
-      // 12 special chars
-      if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?'
-          || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') {
-        res = res + zstring("\\");
-      }
-      char tmp[2] = {(char)str[i], '\0'};
-      res = res + zstring(tmp);
-    }
-    return res;
-}
-
-zstring theory_str::get_std_regex_str(expr * regex) {
-    app * a_regex = to_app(regex);
-    if (u.re.is_to_re(a_regex)) {
-        expr * regAst = a_regex->get_arg(0);
-        zstring regAstVal;
-        u.str.is_string(regAst, regAstVal);
-        zstring regStr = str2RegexStr(regAstVal);
-        return regStr;
-    } else if (u.re.is_concat(a_regex)) {
-        expr * reg1Ast = a_regex->get_arg(0);
-        expr * reg2Ast = a_regex->get_arg(1);
-        zstring reg1Str = get_std_regex_str(reg1Ast);
-        zstring reg2Str = get_std_regex_str(reg2Ast);
-        return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")");
-    } else if (u.re.is_union(a_regex)) {
-        expr * reg1Ast = a_regex->get_arg(0);
-        expr * reg2Ast = a_regex->get_arg(1);
-        zstring reg1Str = get_std_regex_str(reg1Ast);
-        zstring reg2Str = get_std_regex_str(reg2Ast);
-        return  zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")");
-    } else if (u.re.is_star(a_regex)) {
-        expr * reg1Ast = a_regex->get_arg(0);
-        zstring reg1Str = get_std_regex_str(reg1Ast);
-        return  zstring("(") + reg1Str + zstring(")*");
-    } else if (u.re.is_range(a_regex)) {
-        expr * range1 = a_regex->get_arg(0);
-        expr * range2 = a_regex->get_arg(1);
-        zstring range1val, range2val;
-        u.str.is_string(range1, range1val);
-        u.str.is_string(range2, range2val);
-        return zstring("[") + range1val + zstring("-") + range2val + zstring("]");
-    } else {
-        TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;);
-        UNREACHABLE(); return zstring("");
-    }
-}
-
-void theory_str::instantiate_axiom_RegexIn(enode * e) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    app * ex = e->get_owner();
-    if (axiomatized_terms.contains(ex)) {
-        TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;);
-        return;
-    }
-    axiomatized_terms.insert(ex);
-
-    TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;);
-
-    {
-        zstring regexStr = get_std_regex_str(ex->get_arg(1));
-        std::pair<expr*, zstring> key1(ex->get_arg(0), regexStr);
-        // skip Z3str's map check, because we already check if we set up axioms on this term
-        regex_in_bool_map[key1] = ex;
-        regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr);
-    }
-
-    expr_ref str(ex->get_arg(0), m);
-    app * regex = to_app(ex->get_arg(1));
-
-    if (u.re.is_to_re(regex)) {
-    	expr_ref rxStr(regex->get_arg(0), m);
-    	// want to assert 'expr IFF (str == rxStr)'
-    	expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m);
-    	expr_ref finalAxiom(m.mk_iff(ex, rhs), m);
-    	SASSERT(finalAxiom);
-    	assert_axiom(finalAxiom);
-    	TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;);
-    } else if (u.re.is_concat(regex)) {
-    	expr_ref var1(mk_regex_rep_var(), m);
-    	expr_ref var2(mk_regex_rep_var(), m);
-    	expr_ref rhs(mk_concat(var1, var2), m);
-    	expr_ref rx1(regex->get_arg(0), m);
-    	expr_ref rx2(regex->get_arg(1), m);
-    	expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m);
-    	expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m);
-
-    	expr_ref_vector items(m);
-    	items.push_back(var1InRegex1);
-    	items.push_back(var2InRegex2);
-    	items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs)));
-
-    	expr_ref finalAxiom(mk_and(items), m);
-    	SASSERT(finalAxiom);
-    	assert_axiom(finalAxiom);
-    } else if (u.re.is_union(regex)) {
-    	expr_ref var1(mk_regex_rep_var(), m);
-    	expr_ref var2(mk_regex_rep_var(), m);
-    	expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m);
-    	expr_ref regex1(regex->get_arg(0), m);
-    	expr_ref regex2(regex->get_arg(1), m);
-    	expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m);
-    	expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m);
-    	expr_ref_vector items(m);
-    	items.push_back(var1InRegex1);
-    	items.push_back(var2InRegex2);
-    	items.push_back(ctx.mk_eq_atom(ex, orVar));
-    	assert_axiom(mk_and(items));
-    } else if (u.re.is_star(regex)) {
-    	// slightly more complex due to the unrolling step.
-    	expr_ref regex1(regex->get_arg(0), m);
-    	expr_ref unrollCount(mk_unroll_bound_var(), m);
-    	expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m);
-    	expr_ref_vector items(m);
-    	items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc)));
-    	items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string(""))));
-    	expr_ref finalAxiom(mk_and(items), m);
-    	SASSERT(finalAxiom);
-    	assert_axiom(finalAxiom);
-    } else if (u.re.is_range(regex)) {
-        // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z");
-        // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z")
-        expr_ref lo(regex->get_arg(0), m);
-        expr_ref hi(regex->get_arg(1), m);
-        zstring str_lo, str_hi;
-        SASSERT(u.str.is_string(lo));
-        SASSERT(u.str.is_string(hi));
-        u.str.is_string(lo, str_lo);
-        u.str.is_string(hi, str_hi);
-        SASSERT(str_lo.length() == 1);
-        SASSERT(str_hi.length() == 1);
-        unsigned int c1 = str_lo[0];
-        unsigned int c2 = str_hi[0];
-        if (c1 > c2) {
-            // exchange
-            unsigned int tmp = c1;
-            c1 = c2;
-            c2 = tmp;
-        }
-        expr_ref_vector range_cases(m);
-        for (unsigned int ch = c1; ch <= c2; ++ch) {
-            zstring s_ch(ch);
-            expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m);
-            range_cases.push_back(rhs);
-        }
-        expr_ref rhs(mk_or(range_cases), m);
-        expr_ref finalAxiom(m.mk_iff(ex, rhs), m);
+        expr_ref finalAxiom(mk_and(items), m);
         SASSERT(finalAxiom);
         assert_axiom(finalAxiom);
-    } else {
-    	TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;);
-    	NOT_IMPLEMENTED_YET();
-    }
-}
-
-void theory_str::attach_new_th_var(enode * n) {
-    context & ctx = get_context();
-    theory_var v = mk_var(n);
-    ctx.attach_th_var(n, this, v);
-    TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;);
-}
-
-void theory_str::reset_eh() {
-    TRACE("str", tout << "resetting" << std::endl;);
-    m_trail_stack.reset();
-
-    m_basicstr_axiom_todo.reset();
-    m_str_eq_todo.reset();
-    m_concat_axiom_todo.reset();
-    pop_scope_eh(get_context().get_scope_level());
-}
-
-/*
- * Check equality among equivalence class members of LHS and RHS
- * to discover an incorrect LHS == RHS.
- * For example, if we have y2 == "str3"
- * and the equivalence classes are
- * { y2, (Concat ce m2) }
- * { "str3", (Concat abc x2) }
- * then y2 can't be equal to "str3".
- * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3")
- */
-bool theory_str::new_eq_check(expr * lhs, expr * rhs) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    // skip this check if we defer consistency checking, as we can do it for every EQC in final check
-    if (!opt_DeferEQCConsistencyCheck) {
-        check_concat_len_in_eqc(lhs);
-        check_concat_len_in_eqc(rhs);
+        return unrollFunc;
     }
 
-    // Now we iterate over all pairs of terms across both EQCs
-    // and check whether we can show that any pair of distinct terms
-    // cannot possibly be equal.
-    // If that's the case, we assert an axiom to that effect and stop.
+    app * theory_str::mk_contains(expr * haystack, expr * needle) {
+        app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order
+        m_trail.push_back(contains);
+        // immediately force internalization so that axiom setup does not fail
+        get_context().internalize(contains, false);
+        set_up_axioms(contains);
+        return contains;
+    }
 
-    expr * eqc_nn1 = lhs;
-    do {
-        expr * eqc_nn2 = rhs;
-        do {
-            TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;);
-            // inconsistency check: value
-            if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) {
-                TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;);
-                expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m);
-                assert_axiom(to_assert);
-                // this shouldn't use the integer theory at all, so we don't allow the option of quick-return
-                return false;
-            }
-            if (!check_length_consistency(eqc_nn1, eqc_nn2)) {
-                TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;);
-                if (opt_NoQuickReturn_IntegerTheory){
-                    TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;);
-                } else {
-                    return false;
-                }
-            }
-            eqc_nn2 = get_eqc_next(eqc_nn2);
-        } while (eqc_nn2 != rhs);
-        eqc_nn1 = get_eqc_next(eqc_nn1);
-    } while (eqc_nn1 != lhs);
-
-    if (!contains_map.empty()) {
-        check_contain_in_new_eq(lhs, rhs);
-    }
-
-    if (!regex_in_bool_map.empty()) {
-        TRACE("str", tout << "checking regex consistency" << std::endl;);
-        check_regex_in(lhs, rhs);
-    }
-
-    // okay, all checks here passed
-    return true;
-}
-
-// support for user_smt_theory-style EQC handling
-
-app * theory_str::get_ast(theory_var i) {
-    return get_enode(i)->get_owner();
-}
-
-theory_var theory_str::get_var(expr * n) const {
-    if (!is_app(n)) {
-        return null_theory_var;
-    }
-    context & ctx = get_context();
-    if (ctx.e_internalized(to_app(n))) {
-        enode * e = ctx.get_enode(to_app(n));
-        return e->get_th_var(get_id());
-    }
-    return null_theory_var;
-}
-
-// simulate Z3_theory_get_eqc_next()
-expr * theory_str::get_eqc_next(expr * n) {
-    theory_var v = get_var(n);
-    if (v != null_theory_var) {
-        theory_var r = m_find.next(v);
-        return get_ast(r);
-    }
-    return n;
-}
-
-void theory_str::group_terms_by_eqc(expr * n, std::set<expr*> & concats, std::set<expr*> & vars, std::set<expr*> & consts) {
-    context & ctx = get_context();
-    expr * eqcNode = n;
-    do {
-        app * ast = to_app(eqcNode);
-        if (u.str.is_concat(ast)) {
-            expr * simConcat = simplify_concat(ast);
-            if (simConcat != ast) {
-                if (u.str.is_concat(to_app(simConcat))) {
-                    concats.insert(simConcat);
-                } else {
-                    if (u.str.is_string(simConcat)) {
-                        consts.insert(simConcat);
-                    } else {
-                        vars.insert(simConcat);
-                    }
+    app * theory_str::mk_indexof(expr * haystack, expr * needle) {
+        // TODO check meaning of the third argument here
+        app * indexof = u.str.mk_index(haystack, needle, mk_int(0));
+        m_trail.push_back(indexof);
+        // immediately force internalization so that axiom setup does not fail
+        get_context().internalize(indexof, false);
+        set_up_axioms(indexof);
+        return indexof;
+    }
+
+    app * theory_str::mk_strlen(expr * e) {
+        /*if (m_strutil.is_string(e)) {*/ if (false) {
+            zstring strval;
+            u.str.is_string(e, strval);
+            unsigned int len = strval.length();
+            return m_autil.mk_numeral(rational(len), true);
+        } else {
+            if (false) {
+                // use cache
+                app * lenTerm = NULL;
+                if (!length_ast_map.find(e, lenTerm)) {
+                    lenTerm = u.str.mk_length(e);
+                    length_ast_map.insert(e, lenTerm);
+                    m_trail.push_back(lenTerm);
                 }
+                return lenTerm;
             } else {
-                concats.insert(simConcat);
+                // always regen
+                return u.str.mk_length(e);
             }
-        } else if (u.str.is_string(ast)) {
-            consts.insert(ast);
-        } else {
-            vars.insert(ast);
-        }
-        eqcNode = get_eqc_next(eqcNode);
-    } while (eqcNode != n);
-}
-
-void theory_str::get_nodes_in_concat(expr * node, ptr_vector<expr> & nodeList) {
-    app * a_node = to_app(node);
-    if (!u.str.is_concat(a_node)) {
-        nodeList.push_back(node);
-        return;
-    } else {
-        SASSERT(a_node->get_num_args() == 2);
-        expr * leftArg = a_node->get_arg(0);
-        expr * rightArg = a_node->get_arg(1);
-        get_nodes_in_concat(leftArg, nodeList);
-        get_nodes_in_concat(rightArg, nodeList);
-    }
-}
-
-// previously Concat() in strTheory.cpp
-// Evaluates the concatenation (n1 . n2) with respect to
-// the current equivalence classes of n1 and n2.
-// Returns a constant string expression representing this concatenation
-// if one can be determined, or NULL if this is not possible.
-expr * theory_str::eval_concat(expr * n1, expr * n2) {
-    bool n1HasEqcValue = false;
-    bool n2HasEqcValue = false;
-    expr * v1 = get_eqc_value(n1, n1HasEqcValue);
-    expr * v2 = get_eqc_value(n2, n2HasEqcValue);
-    if (n1HasEqcValue && n2HasEqcValue) {
-        zstring n1_str, n2_str;
-        u.str.is_string(v1, n1_str);
-        u.str.is_string(v2, n2_str);
-        zstring result = n1_str + n2_str;
-        return mk_string(result);
-    } else if (n1HasEqcValue && !n2HasEqcValue) {
-        zstring v1_str;
-        u.str.is_string(v1, v1_str);
-        if (v1_str.empty()) {
-            return n2;
-        }
-    } else if (n2HasEqcValue && !n1HasEqcValue) {
-        zstring v2_str;
-        u.str.is_string(v2, v2_str);
-        if (v2_str.empty()) {
-            return n1;
-        }
-    }
-    // give up
-    return NULL;
-}
-
-static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) {
-    if (x_exists) {
-        return x.to_string();
-    } else {
-        return "?";
-    }
-}
-
-/*
- * The inputs:
- *    ~ nn: non const node
- *    ~ eq_str: the equivalent constant string of nn
- *  Iterate the parent of all eqc nodes of nn, looking for:
- *    ~ concat node
- *  to see whether some concat nodes can be simplified.
- */
-void theory_str::simplify_parent(expr * nn, expr * eq_str) {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-
-    TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m)
-            << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;);
-
-    ctx.internalize(nn, false);
-
-    zstring eq_strValue;
-    u.str.is_string(eq_str, eq_strValue);
-    expr * n_eqNode = nn;
-    do {
-        enode * n_eq_enode = ctx.get_enode(n_eqNode);
-        TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl
-                << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;);
-
-        // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop.
-        // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it,
-        // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over.
-
-        enode_vector current_parents;
-        for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) {
-            current_parents.insert(*parent_it);
-        }
-
-        for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) {
-            enode * e_parent = *parent_it;
-            SASSERT(e_parent != NULL);
-
-            app * a_parent = e_parent->get_owner();
-            TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;);
-
-            if (u.str.is_concat(a_parent)) {
-                expr * arg0 = a_parent->get_arg(0);
-                expr * arg1 = a_parent->get_arg(1);
-
-                rational parentLen;
-                bool parentLen_exists = get_len_value(a_parent, parentLen);
-
-                if (arg0 == n_eq_enode->get_owner()) {
-                    rational arg0Len, arg1Len;
-                    bool arg0Len_exists = get_len_value(eq_str, arg0Len);
-                    bool arg1Len_exists = get_len_value(arg1, arg1Len);
-
-                    TRACE("str",
-                            tout << "simplify_parent #1:" << std::endl
-                            << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl
-                            << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl
-                            << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl
-                            << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl;
-                    );
-
-                    if (parentLen_exists && !arg1Len_exists) {
-                        TRACE("str", tout << "make up len for arg1" << std::endl;);
-                        expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)),
-                                ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m);
-                        rational makeUpLenArg1 = parentLen - arg0Len;
-                        if (makeUpLenArg1.is_nonneg()) {
-                            expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m);
-                            assert_implication(implyL11, implyR11);
-                        } else {
-                            expr_ref neg(m.mk_not(implyL11), m);
-                            assert_axiom(neg);
-                        }
-                    }
-
-                    // (Concat n_eqNode arg1) /\ arg1 has eq const
-
-                    expr * concatResult = eval_concat(eq_str, arg1);
-                    if (concatResult != NULL) {
-                        bool arg1HasEqcValue = false;
-                        expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue);
-                        expr_ref implyL(m);
-                        if (arg1 != arg1Value) {
-                            expr_ref eq_ast1(m);
-                            eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str);
-                            SASSERT(eq_ast1);
-
-                            expr_ref eq_ast2(m);
-                            eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value);
-                            SASSERT(eq_ast2);
-                            implyL = m.mk_and(eq_ast1, eq_ast2);
-                        } else {
-                            implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                        }
-
-
-                        if (!in_same_eqc(a_parent, concatResult)) {
-                            expr_ref implyR(m);
-                            implyR = ctx.mk_eq_atom(a_parent, concatResult);
-                            SASSERT(implyR);
-
-                            assert_implication(implyL, implyR);
-                        }
-                    } else if (u.str.is_concat(to_app(n_eqNode))) {
-                        expr_ref simpleConcat(m);
-                        simpleConcat = mk_concat(eq_str, arg1);
-                        if (!in_same_eqc(a_parent, simpleConcat)) {
-                            expr_ref implyL(m);
-                            implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                            SASSERT(implyL);
-
-                            expr_ref implyR(m);
-                            implyR = ctx.mk_eq_atom(a_parent, simpleConcat);
-                            SASSERT(implyR);
-                            assert_implication(implyL, implyR);
-                        }
-                    }
-                } // if (arg0 == n_eq_enode->get_owner())
-
-                if (arg1 == n_eq_enode->get_owner()) {
-                    rational arg0Len, arg1Len;
-                    bool arg0Len_exists = get_len_value(arg0, arg0Len);
-                    bool arg1Len_exists = get_len_value(eq_str, arg1Len);
-
-                    TRACE("str",
-                            tout << "simplify_parent #2:" << std::endl
-                            << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl
-                            << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl
-                            << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl
-                            << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl;
-                    );
-                    if (parentLen_exists && !arg0Len_exists) {
-                        TRACE("str", tout << "make up len for arg0" << std::endl;);
-                        expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)),
-                                ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m);
-                        rational makeUpLenArg0 = parentLen - arg1Len;
-                        if (makeUpLenArg0.is_nonneg()) {
-                            expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m);
-                            assert_implication(implyL11, implyR11);
-                        } else {
-                            expr_ref neg(m.mk_not(implyL11), m);
-                            assert_axiom(neg);
-                        }
-                    }
-
-                    // (Concat arg0 n_eqNode) /\ arg0 has eq const
-
-                    expr * concatResult = eval_concat(arg0, eq_str);
-                    if (concatResult != NULL) {
-                        bool arg0HasEqcValue = false;
-                        expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue);
-                        expr_ref implyL(m);
-                        if (arg0 != arg0Value) {
-                            expr_ref eq_ast1(m);
-                            eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str);
-                            SASSERT(eq_ast1);
-                            expr_ref eq_ast2(m);
-                            eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value);
-                            SASSERT(eq_ast2);
-
-                            implyL = m.mk_and(eq_ast1, eq_ast2);
-                        } else {
-                            implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                        }
-
-                        if (!in_same_eqc(a_parent, concatResult)) {
-                            expr_ref implyR(m);
-                            implyR = ctx.mk_eq_atom(a_parent, concatResult);
-                            SASSERT(implyR);
-
-                            assert_implication(implyL, implyR);
-                        }
-                    } else if (u.str.is_concat(to_app(n_eqNode))) {
-                        expr_ref simpleConcat(m);
-                        simpleConcat = mk_concat(arg0, eq_str);
-                        if (!in_same_eqc(a_parent, simpleConcat)) {
-                            expr_ref implyL(m);
-                            implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                            SASSERT(implyL);
-
-                            expr_ref implyR(m);
-                            implyR = ctx.mk_eq_atom(a_parent, simpleConcat);
-                            SASSERT(implyR);
-                            assert_implication(implyL, implyR);
-                        }
-                    }
-                } // if (arg1 == n_eq_enode->get_owner
-
-
-                //---------------------------------------------------------
-                // Case (2-1) begin: (Concat n_eqNode (Concat str var))
-                if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) {
-                    app * a_arg1 = to_app(arg1);
-                    TRACE("str", tout << "simplify_parent #3" << std::endl;);
-                    expr * r_concat_arg0 = a_arg1->get_arg(0);
-                    if (u.str.is_string(r_concat_arg0)) {
-                        expr * combined_str = eval_concat(eq_str, r_concat_arg0);
-                        SASSERT(combined_str);
-                        expr * r_concat_arg1 = a_arg1->get_arg(1);
-                        expr_ref implyL(m);
-                        implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                        expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1);
-                        if (!in_same_eqc(a_parent, simplifiedAst)) {
-                            expr_ref implyR(m);
-                            implyR = ctx.mk_eq_atom(a_parent, simplifiedAst);
-                            assert_implication(implyL, implyR);
-                        }
-                    }
-                }
-                // Case (2-1) end: (Concat n_eqNode (Concat str var))
-                //---------------------------------------------------------
-
-
-                //---------------------------------------------------------
-                // Case (2-2) begin: (Concat (Concat var str) n_eqNode)
-                if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) {
-                    app * a_arg0 = to_app(arg0);
-                    TRACE("str", tout << "simplify_parent #4" << std::endl;);
-                    expr * l_concat_arg1 = a_arg0->get_arg(1);
-                    if (u.str.is_string(l_concat_arg1)) {
-                        expr * combined_str = eval_concat(l_concat_arg1, eq_str);
-                        SASSERT(combined_str);
-                        expr * l_concat_arg0 = a_arg0->get_arg(0);
-                        expr_ref implyL(m);
-                        implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                        expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str);
-                        if (!in_same_eqc(a_parent, simplifiedAst)) {
-                            expr_ref implyR(m);
-                            implyR = ctx.mk_eq_atom(a_parent, simplifiedAst);
-                            assert_implication(implyL, implyR);
-                        }
-                    }
-                }
-                // Case (2-2) end: (Concat (Concat var str) n_eqNode)
-                //---------------------------------------------------------
-
-                // Have to look up one more layer: if the parent of the concat is another concat
-                //-------------------------------------------------
-                // Case (3-1) begin: (Concat (Concat var n_eqNode) str )
-                if (arg1 == n_eqNode) {
-                    for (enode_vector::iterator concat_parent_it = e_parent->begin_parents();
-                            concat_parent_it != e_parent->end_parents(); concat_parent_it++) {
-                        enode * e_concat_parent = *concat_parent_it;
-                        app * concat_parent = e_concat_parent->get_owner();
-                        if (u.str.is_concat(concat_parent)) {
-                            expr * concat_parent_arg0 = concat_parent->get_arg(0);
-                            expr * concat_parent_arg1 = concat_parent->get_arg(1);
-                            if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) {
-                                TRACE("str", tout << "simplify_parent #5" << std::endl;);
-                                expr * combinedStr = eval_concat(eq_str, concat_parent_arg1);
-                                SASSERT(combinedStr);
-                                expr_ref implyL(m);
-                                implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                                expr * simplifiedAst = mk_concat(arg0, combinedStr);
-                                if (!in_same_eqc(concat_parent, simplifiedAst)) {
-                                    expr_ref implyR(m);
-                                    implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst);
-                                    assert_implication(implyL, implyR);
-                                }
-                            }
-                        }
-                    }
-                }
-                // Case (3-1) end: (Concat (Concat var n_eqNode) str )
-                // Case (3-2) begin: (Concat str (Concat n_eqNode var) )
-                if (arg0 == n_eqNode) {
-                    for (enode_vector::iterator concat_parent_it = e_parent->begin_parents();
-                            concat_parent_it != e_parent->end_parents(); concat_parent_it++) {
-                        enode * e_concat_parent = *concat_parent_it;
-                        app * concat_parent = e_concat_parent->get_owner();
-                        if (u.str.is_concat(concat_parent)) {
-                            expr * concat_parent_arg0 = concat_parent->get_arg(0);
-                            expr * concat_parent_arg1 = concat_parent->get_arg(1);
-                            if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) {
-                                TRACE("str", tout << "simplify_parent #6" << std::endl;);
-                                expr * combinedStr = eval_concat(concat_parent_arg0, eq_str);
-                                SASSERT(combinedStr);
-                                expr_ref implyL(m);
-                                implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
-                                expr * simplifiedAst = mk_concat(combinedStr, arg1);
-                                if (!in_same_eqc(concat_parent, simplifiedAst)) {
-                                    expr_ref implyR(m);
-                                    implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst);
-                                    assert_implication(implyL, implyR);
-                                }
-                            }
-                        }
-                    }
-                }
-                // Case (3-2) end: (Concat str (Concat n_eqNode var) )
-            } // if is_concat(a_parent)
-        } // for parent_it : n_eq_enode->begin_parents()
-
-
-        // check next EQC member
-        n_eqNode = get_eqc_next(n_eqNode);
-    } while (n_eqNode != nn);
-}
-
-expr * theory_str::simplify_concat(expr * node) {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-    std::map<expr*, expr*> resolvedMap;
-    ptr_vector<expr> argVec;
-    get_nodes_in_concat(node, argVec);
-
-    for (unsigned i = 0; i < argVec.size(); ++i) {
-        bool vArgHasEqcValue = false;
-        expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue);
-        if (vArg != argVec[i]) {
-            resolvedMap[argVec[i]] = vArg;
-        }
-    }
-
-    if (resolvedMap.size() == 0) {
-        // no simplification possible
-        return node;
-    } else {
-        expr * resultAst = mk_string("");
-        for (unsigned i = 0; i < argVec.size(); ++i) {
-            bool vArgHasEqcValue = false;
-            expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue);
-            resultAst = mk_concat(resultAst, vArg);
-        }
-        TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;);
-
-        if (in_same_eqc(node, resultAst)) {
-            TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;);
-        } else {
-            expr_ref_vector items(m);
-            int pos = 0;
-            std::map<expr*, expr*>::iterator itor = resolvedMap.begin();
-            for (; itor != resolvedMap.end(); ++itor) {
-                items.push_back(ctx.mk_eq_atom(itor->first, itor->second));
-                pos += 1;
-            }
-            expr_ref premise(mk_and(items), m);
-            expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m);
-            assert_implication(premise, conclusion);
-        }
-        return resultAst;
-    }
-
-}
-
-// Modified signature of Z3str2's inferLenConcat().
-// Returns true iff nLen can be inferred by this method
-// (i.e. the equivalent of a len_exists flag in get_len_value()).
-
-bool theory_str::infer_len_concat(expr * n, rational & nLen) {
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-	expr * arg0 = to_app(n)->get_arg(0);
-	expr * arg1 = to_app(n)->get_arg(1);
-
-	rational arg0_len, arg1_len;
-	bool arg0_len_exists = get_len_value(arg0, arg0_len);
-	bool arg1_len_exists = get_len_value(arg1, arg1_len);
-	rational tmp_len;
-	bool nLen_exists = get_len_value(n, tmp_len);
-
-	if (arg0_len_exists && arg1_len_exists && !nLen_exists) {
-		expr_ref_vector l_items(m);
-		// if (mk_strlen(arg0) != mk_int(arg0_len)) {
-		{
-			l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len)));
-		}
-
-		// if (mk_strlen(arg1) != mk_int(arg1_len)) {
-		{
-			l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len)));
-		}
-
-		expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
-		rational nnLen = arg0_len + arg1_len;
-		expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m);
-		TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;);
-		assert_implication(axl, axr);
-		nLen = nnLen;
-		return true;
-	} else {
-		return false;
-	}
-}
-
-void theory_str::infer_len_concat_arg(expr * n, rational len) {
-	if (len.is_neg()) {
-		return;
-	}
-
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	expr * arg0 = to_app(n)->get_arg(0);
-	expr * arg1 = to_app(n)->get_arg(1);
-	rational arg0_len, arg1_len;
-	bool arg0_len_exists = get_len_value(arg0, arg0_len);
-	bool arg1_len_exists = get_len_value(arg1, arg1_len);
-
-	expr_ref_vector l_items(m);
-	expr_ref axr(m);
-	axr.reset();
-
-	// if (mk_length(t, n) != mk_int(ctx, len)) {
-	{
-		l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len)));
-	}
-
-	if (!arg0_len_exists && arg1_len_exists) {
-		//if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) {
-		{
-			l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len)));
-		}
-		rational arg0Len = len - arg1_len;
-		if (arg0Len.is_nonneg()) {
-			axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len));
-		} else {
-			// could negate
-		}
-	} else if (arg0_len_exists && !arg1_len_exists) {
-		//if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) {
-		{
-			l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len)));
-		}
-		rational arg1Len = len - arg0_len;
-		if (arg1Len.is_nonneg()) {
-			axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len));
-		} else {
-			// could negate
-		}
-	} else {
-
-	}
-
-	if (axr) {
-		expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
-		assert_implication(axl, axr);
-	}
-}
-
-void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) {
-    rational nnLen;
-    bool nnLen_exists = get_len_value(nn1, nnLen);
-    if (!nnLen_exists) {
-        nnLen_exists = get_len_value(nn2, nnLen);
-    }
-
-    // case 1:
-    //    Known: a1_arg0 and a1_arg1
-    //    Unknown: nn1
-
-    if (u.str.is_concat(to_app(nn1))) {
-        rational nn1ConcatLen;
-        bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen);
-        if (nnLen_exists && nn1ConcatLen_exists) {
-            nnLen = nn1ConcatLen;
-        }
-    }
-
-    // case 2:
-    //    Known: a1_arg0 and a1_arg1
-    //    Unknown: nn1
-
-    if (u.str.is_concat(to_app(nn2))) {
-        rational nn2ConcatLen;
-        bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen);
-        if (nnLen_exists && nn2ConcatLen_exists) {
-            nnLen = nn2ConcatLen;
-        }
-    }
-
-    if (nnLen_exists) {
-        if (u.str.is_concat(to_app(nn1))) {
-            infer_len_concat_arg(nn1, nnLen);
-        }
-        if (u.str.is_concat(to_app(nn2))) {
-            infer_len_concat_arg(nn2, nnLen);
         }
     }
 
     /*
-    if (isConcatFunc(t, nn2)) {
-        int nn2ConcatLen = inferLenConcat(t, nn2);
-        if (nnLen == -1 && nn2ConcatLen != -1)
-            nnLen = nn2ConcatLen;
-    }
-
-    if (nnLen != -1) {
-        if (isConcatFunc(t, nn1)) {
-            inferLenConcatArg(t, nn1, nnLen);
-        }
-        if (isConcatFunc(t, nn2)) {
-            inferLenConcatArg(t, nn2, nnLen);
-        }
-    }
-    */
-}
-
-void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) {
-    context & ctx = get_context();
-    ctx.internalize(term, false);
-    bool_var v = ctx.get_bool_var(term);
-    ctx.add_theory_aware_branching_info(v, priority, phase);
-}
-
-void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) {
-    context & ctx = get_context();
-    // pull each literal out of the arrangement disjunction
-    literal_vector ls;
-    for (unsigned i = 0; i < terms.size(); ++i) {
-        expr * e = terms.get(i);
-        literal l = ctx.get_literal(e);
-        ls.push_back(l);
-    }
-    ctx.mk_th_case_split(ls.size(), ls.c_ptr());
-}
-
-void theory_str::print_cut_var(expr * node, std::ofstream & xout) {
-    ast_manager & m = get_manager();
-    xout << "Cut info of " << mk_pp(node, m) << std::endl;
-    if (cut_var_map.contains(node)) {
-        if (!cut_var_map[node].empty()) {
-            xout << "[" << cut_var_map[node].top()->level << "] ";
-            std::map<expr*, int>::iterator itor = cut_var_map[node].top()->vars.begin();
-            for (; itor != cut_var_map[node].top()->vars.end(); ++itor) {
-                xout << mk_pp(itor->first, m) << ", ";
+     * Returns the simplified concatenation of two expressions,
+     * where either both expressions are constant strings
+     * or one expression is the empty string.
+     * If this precondition does not hold, the function returns NULL.
+     * (note: this function was strTheory::Concat())
+     */
+    expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) {
+        bool n1HasEqcValue = false;
+        bool n2HasEqcValue = false;
+        expr * v1 = get_eqc_value(n1, n1HasEqcValue);
+        expr * v2 = get_eqc_value(n2, n2HasEqcValue);
+        if (n1HasEqcValue && n2HasEqcValue) {
+            zstring n1_str;
+            u.str.is_string(v1, n1_str);
+            zstring n2_str;
+            u.str.is_string(v2, n2_str);
+            zstring result = n1_str + n2_str;
+            return mk_string(result);
+        } else if (n1HasEqcValue && !n2HasEqcValue) {
+            zstring n1_str;
+            u.str.is_string(v1, n1_str);
+            if (n1_str.empty()) {
+                return n2;
+            }
+        } else if (!n1HasEqcValue && n2HasEqcValue) {
+            zstring n2_str;
+            u.str.is_string(v2, n2_str);
+            if (n2_str.empty()) {
+                return n1;
             }
-            xout << std::endl;
         }
-    }
-}
-
-/*
- * Handle two equivalent Concats.
- */
-void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-
-    app * a_nn1 = to_app(nn1);
-    SASSERT(a_nn1->get_num_args() == 2);
-    app * a_nn2 = to_app(nn2);
-    SASSERT(a_nn2->get_num_args() == 2);
-
-    expr * a1_arg0 = a_nn1->get_arg(0);
-    expr * a1_arg1 = a_nn1->get_arg(1);
-    expr * a2_arg0 = a_nn2->get_arg(0);
-    expr * a2_arg1 = a_nn2->get_arg(1);
-
-    rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len;
-
-    bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len);
-    bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len);
-    bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len);
-    bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len);
-
-    TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl
-            << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;);
-
-    TRACE("str", tout
-            << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl
-            << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl
-            << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl
-            << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl
-            << std::endl;);
-
-    infer_len_concat_equality(nn1, nn2);
-
-    if (a1_arg0 == a2_arg0) {
-        if (!in_same_eqc(a1_arg1, a2_arg1)) {
-            expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m);
-            expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m);
-            expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m);
-            expr_ref conclusion(m.mk_and(eq1, eq2), m);
-            assert_implication(premise, conclusion);
-        }
-        TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;);
-        return;
+        return NULL;
     }
 
-    if (a1_arg1 == a2_arg1) {
-        if (!in_same_eqc(a1_arg0, a2_arg0)) {
-            expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m);
-            expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m);
-            expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m);
-            expr_ref conclusion(m.mk_and(eq1, eq2), m);
-            assert_implication(premise, conclusion);
-        }
-        TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;);
-        return;
-    }
+    expr * theory_str::mk_concat(expr * n1, expr * n2) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        ENSURE(n1 != NULL);
+        ENSURE(n2 != NULL);
+        bool n1HasEqcValue = false;
+        bool n2HasEqcValue = false;
+        n1 = get_eqc_value(n1, n1HasEqcValue);
+        n2 = get_eqc_value(n2, n2HasEqcValue);
+        if (n1HasEqcValue && n2HasEqcValue) {
+            return mk_concat_const_str(n1, n2);
+        } else if (n1HasEqcValue && !n2HasEqcValue) {
+            bool n2_isConcatFunc = u.str.is_concat(to_app(n2));
+            zstring n1_str;
+            u.str.is_string(n1, n1_str);
+            if (n1_str.empty()) {
+                return n2;
+            }
+            if (n2_isConcatFunc) {
+                expr * n2_arg0 = to_app(n2)->get_arg(0);
+                expr * n2_arg1 = to_app(n2)->get_arg(1);
+                if (u.str.is_string(n2_arg0)) {
+                    n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant
+                    n2 = n2_arg1;
+                }
+            }
+        } else if (!n1HasEqcValue && n2HasEqcValue) {
+            zstring n2_str;
+            u.str.is_string(n2, n2_str);
+            if (n2_str.empty()) {
+                return n1;
+            }
 
-    // quick path
-
-    if (in_same_eqc(a1_arg0, a2_arg0)) {
-        if (in_same_eqc(a1_arg1, a2_arg1)) {
-            TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;);
-            return;
+            if (u.str.is_concat(to_app(n1))) {
+                expr * n1_arg0 = to_app(n1)->get_arg(0);
+                expr * n1_arg1 = to_app(n1)->get_arg(1);
+                if (u.str.is_string(n1_arg1)) {
+                    n1 = n1_arg0;
+                    n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant
+                }
+            }
         } else {
-            TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;);
-            expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m);
-            expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m);
-            assert_implication(premise, conclusion);
-            return;
-        }
-    } else {
-        if (in_same_eqc(a1_arg1, a2_arg1)) {
-            TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;);
-            expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m);
-            expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m);
-            assert_implication(premise, conclusion);
-            return;
-        }
-    }
-
-    // quick path 2-1
-    if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) {
-        if (!in_same_eqc(a1_arg0, a2_arg0)) {
-            TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;);
-            expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m);
-            expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m);
-            expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m);
-            expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m);
-
-            expr_ref premise(m.mk_and(ax_l1, ax_l2), m);
-            expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m);
-
-            assert_implication(premise, conclusion);
-
-            if (opt_NoQuickReturn_IntegerTheory) {
-                TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;);
-            } else {
-                return;
+            if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) {
+                expr * n1_arg0 = to_app(n1)->get_arg(0);
+                expr * n1_arg1 = to_app(n1)->get_arg(1);
+                expr * n2_arg0 = to_app(n2)->get_arg(0);
+                expr * n2_arg1 = to_app(n2)->get_arg(1);
+                if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) {
+                    expr * tmpN1 = n1_arg0;
+                    expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0);
+                    n1 = mk_concat(tmpN1, tmpN2);
+                    n2 = n2_arg1;
+                }
             }
         }
+
+        //------------------------------------------------------
+        // * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2);
+        // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2);
+        // Z3 treats (ast1) and (ast2) as two different nodes.
+        //-------------------------------------------------------
+
+        expr * concatAst = NULL;
+
+        if (!concat_astNode_map.find(n1, n2, concatAst)) {
+            concatAst = u.str.mk_concat(n1, n2);
+            m_trail.push_back(concatAst);
+            concat_astNode_map.insert(n1, n2, concatAst);
+
+            expr_ref concat_length(mk_strlen(concatAst), m);
+
+            ptr_vector<expr> childrenVector;
+            get_nodes_in_concat(concatAst, childrenVector);
+            expr_ref_vector items(m);
+            for (unsigned int i = 0; i < childrenVector.size(); i++) {
+                items.push_back(mk_strlen(childrenVector.get(i)));
+            }
+            expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m);
+            assert_axiom(lenAssert);
+        }
+        return concatAst;
     }
 
-    if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) {
-        if (!in_same_eqc(a1_arg1, a2_arg1)) {
-            TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;);
-            expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m);
-            expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m);
-            expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m);
-            expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m);
+    bool theory_str::can_propagate() {
+        return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty()
+            || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty()
+            || !m_library_aware_axiom_todo.empty()
+            || !m_delayed_axiom_setup_terms.empty();
+        ;
+    }
 
-            expr_ref premise(m.mk_and(ax_l1, ax_l2), m);
-            expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m);
+    void theory_str::propagate() {
+        context & ctx = get_context();
+        while (can_propagate()) {
+            TRACE("str", tout << "propagating..." << std::endl;);
+            for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) {
+                instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]);
+            }
+            m_basicstr_axiom_todo.reset();
+            TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;);
 
-            assert_implication(premise, conclusion);
-            if (opt_NoQuickReturn_IntegerTheory) {
-                TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;);
+            for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) {
+                std::pair<enode*,enode*> pair = m_str_eq_todo[i];
+                enode * lhs = pair.first;
+                enode * rhs = pair.second;
+                handle_equality(lhs->get_owner(), rhs->get_owner());
+            }
+            m_str_eq_todo.reset();
+
+            for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) {
+                instantiate_concat_axiom(m_concat_axiom_todo[i]);
+            }
+            m_concat_axiom_todo.reset();
+
+            for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) {
+                try_eval_concat(m_concat_eval_todo[i]);
+            }
+            m_concat_eval_todo.reset();
+
+            for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) {
+                enode * e = m_library_aware_axiom_todo[i];
+                app * a = e->get_owner();
+                if (u.str.is_stoi(a)) {
+                    instantiate_axiom_str_to_int(e);
+                } else if (u.str.is_itos(a)) {
+                    instantiate_axiom_int_to_str(e);
+                } else if (u.str.is_at(a)) {
+                    instantiate_axiom_CharAt(e);
+                } else if (u.str.is_prefix(a)) {
+                    instantiate_axiom_prefixof(e);
+                } else if (u.str.is_suffix(a)) {
+                    instantiate_axiom_suffixof(e);
+                } else if (u.str.is_contains(a)) {
+                    instantiate_axiom_Contains(e);
+                } else if (u.str.is_index(a)) {
+                    instantiate_axiom_Indexof(e);
+                    /* TODO NEXT: Indexof2/Lastindexof rewrite?
+                       } else if (is_Indexof2(e)) {
+                       instantiate_axiom_Indexof2(e);
+                       } else if (is_LastIndexof(e)) {
+                       instantiate_axiom_LastIndexof(e);
+                    */
+                } else if (u.str.is_extract(a)) {
+                    // TODO check semantics of substr vs. extract
+                    instantiate_axiom_Substr(e);
+                } else if (u.str.is_replace(a)) {
+                    instantiate_axiom_Replace(e);
+                } else if (u.str.is_in_re(a)) {
+                    instantiate_axiom_RegexIn(e);
+                } else {
+                    TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;);
+                    NOT_IMPLEMENTED_YET();
+                }
+            }
+            m_library_aware_axiom_todo.reset();
+
+            for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) {
+                // I think this is okay
+                ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false);
+                set_up_axioms(m_delayed_axiom_setup_terms[i].get());
+            }
+            m_delayed_axiom_setup_terms.reset();
+        }
+    }
+
+    /*
+     * Attempt to evaluate a concat over constant strings,
+     * and if this is possible, assert equality between the
+     * flattened string and the original term.
+     */
+
+    void theory_str::try_eval_concat(enode * cat) {
+        app * a_cat = cat->get_owner();
+        SASSERT(u.str.is_concat(a_cat));
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;);
+
+        std::stack<app*> worklist;
+        zstring flattenedString("");
+        bool constOK = true;
+
+        {
+            app * arg0 = to_app(a_cat->get_arg(0));
+            app * arg1 = to_app(a_cat->get_arg(1));
+
+            worklist.push(arg1);
+            worklist.push(arg0);
+        }
+
+        while (constOK && !worklist.empty()) {
+            app * evalArg = worklist.top(); worklist.pop();
+            zstring nextStr;
+            if (u.str.is_string(evalArg, nextStr)) {
+                flattenedString = flattenedString + nextStr;
+            } else if (u.str.is_concat(evalArg)) {
+                app * arg0 = to_app(evalArg->get_arg(0));
+                app * arg1 = to_app(evalArg->get_arg(1));
+
+                worklist.push(arg1);
+                worklist.push(arg0);
             } else {
-                return;
+                TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;);
+                constOK = false;
+                break;
             }
         }
+        if (constOK) {
+            TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;);
+            expr_ref constStr(mk_string(flattenedString), m);
+            expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m);
+            assert_axiom(axiom);
+        }
     }
 
-    expr_ref new_nn1(simplify_concat(nn1), m);
-    expr_ref new_nn2(simplify_concat(nn2), m);
-    app * a_new_nn1 = to_app(new_nn1);
-    app * a_new_nn2 = to_app(new_nn2);
+    /*
+     * Instantiate an axiom of the following form:
+     * Length(Concat(x, y)) = Length(x) + Length(y)
+     */
+    void theory_str::instantiate_concat_axiom(enode * cat) {
+        app * a_cat = cat->get_owner();
+        SASSERT(u.str.is_concat(a_cat));
 
-    TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl
-            << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;);
+        ast_manager & m = get_manager();
 
-    if (new_nn1 == new_nn2) {
-        TRACE("str", tout << "equal concats, return" << std::endl;);
-        return;
+        TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;);
+
+        // build LHS
+        expr_ref len_xy(m);
+        len_xy = mk_strlen(a_cat);
+        SASSERT(len_xy);
+
+        // build RHS: start by extracting x and y from Concat(x, y)
+        unsigned nArgs = a_cat->get_num_args();
+        SASSERT(nArgs == 2);
+        app * a_x = to_app(a_cat->get_arg(0));
+        app * a_y = to_app(a_cat->get_arg(1));
+
+        expr_ref len_x(m);
+        len_x = mk_strlen(a_x);
+        SASSERT(len_x);
+
+        expr_ref len_y(m);
+        len_y = mk_strlen(a_y);
+        SASSERT(len_y);
+
+        // now build len_x + len_y
+        expr_ref len_x_plus_len_y(m);
+        len_x_plus_len_y = m_autil.mk_add(len_x, len_y);
+        SASSERT(len_x_plus_len_y);
+
+        // finally assert equality between the two subexpressions
+        app * eq = m.mk_eq(len_xy, len_x_plus_len_y);
+        SASSERT(eq);
+        assert_axiom(eq);
     }
 
-    if (!can_two_nodes_eq(new_nn1, new_nn2)) {
-        expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m);
-        TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;);
-        assert_axiom(detected);
-        return;
+    /*
+     * Add axioms that are true for any string variable:
+     * 1. Length(x) >= 0
+     * 2. Length(x) == 0 <=> x == ""
+     * If the term is a string constant, we can assert something stronger:
+     *    Length(x) == strlen(x)
+     */
+    void theory_str::instantiate_basic_string_axioms(enode * str) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;);
+
+        // TESTING: attempt to avoid a crash here when a variable goes out of scope
+        if (str->get_iscope_lvl() > ctx.get_scope_level()) {
+            TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;);
+            return;
+        }
+
+        // generate a stronger axiom for constant strings
+        app * a_str = str->get_owner();
+        if (u.str.is_string(a_str)) {
+            expr_ref len_str(m);
+            len_str = mk_strlen(a_str);
+            SASSERT(len_str);
+
+            zstring strconst;
+            u.str.is_string(str->get_owner(), strconst);
+            TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;);
+            unsigned int l = strconst.length();
+            expr_ref len(m_autil.mk_numeral(rational(l), true), m);
+
+            literal lit(mk_eq(len_str, len, false));
+            ctx.mark_as_relevant(lit);
+            ctx.mk_th_axiom(get_id(), 1, &lit);
+        } else {
+            // build axiom 1: Length(a_str) >= 0
+            {
+                // build LHS
+                expr_ref len_str(m);
+                len_str = mk_strlen(a_str);
+                SASSERT(len_str);
+                // build RHS
+                expr_ref zero(m);
+                zero = m_autil.mk_numeral(rational(0), true);
+                SASSERT(zero);
+                // build LHS >= RHS and assert
+                app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero);
+                SASSERT(lhs_ge_rhs);
+                TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;);
+                assert_axiom(lhs_ge_rhs);
+            }
+
+            // build axiom 2: Length(a_str) == 0 <=> a_str == ""
+            {
+                // build LHS of iff
+                expr_ref len_str(m);
+                len_str = mk_strlen(a_str);
+                SASSERT(len_str);
+                expr_ref zero(m);
+                zero = m_autil.mk_numeral(rational(0), true);
+                SASSERT(zero);
+                expr_ref lhs(m);
+                lhs = ctx.mk_eq_atom(len_str, zero);
+                SASSERT(lhs);
+                // build RHS of iff
+                expr_ref empty_str(m);
+                empty_str = mk_string("");
+                SASSERT(empty_str);
+                expr_ref rhs(m);
+                rhs = ctx.mk_eq_atom(a_str, empty_str);
+                SASSERT(rhs);
+                // build LHS <=> RHS and assert
+                TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;);
+                literal l(mk_eq(lhs, rhs, true));
+                ctx.mark_as_relevant(l);
+                ctx.mk_th_axiom(get_id(), 1, &l);
+            }
+
+        }
     }
 
-    // check whether new_nn1 and new_nn2 are still concats
+    /*
+     * Add an axiom of the form:
+     * (lhs == rhs) -> ( Length(lhs) == Length(rhs) )
+     */
+    void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-    bool n1IsConcat = u.str.is_concat(a_new_nn1);
-    bool n2IsConcat = u.str.is_concat(a_new_nn2);
-    if (!n1IsConcat && n2IsConcat) {
-        TRACE("str", tout << "nn1_new is not a concat" << std::endl;);
-        if (u.str.is_string(a_new_nn1)) {
-            simplify_parent(new_nn2, new_nn1);
-        }
-        return;
-    } else if (n1IsConcat && !n2IsConcat) {
-        TRACE("str", tout << "nn2_new is not a concat" << std::endl;);
-        if (u.str.is_string(a_new_nn2)) {
-            simplify_parent(new_nn1, new_nn2);
-        }
-        return;
-    } else if (!n1IsConcat && !n2IsConcat) {
-    	// normally this should never happen, because group_terms_by_eqc() should have pre-simplified
-    	// as much as possible. however, we make a defensive check here just in case
-    	TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;);
-    	return;
-    }
+        app * a_lhs = lhs->get_owner();
+        app * a_rhs = rhs->get_owner();
 
-    expr * v1_arg0 = a_new_nn1->get_arg(0);
-    expr * v1_arg1 = a_new_nn1->get_arg(1);
-    expr * v2_arg0 = a_new_nn2->get_arg(0);
-    expr * v2_arg1 = a_new_nn2->get_arg(1);
+        // build premise: (lhs == rhs)
+        expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m);
 
-    if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) {
-        int ii4 = 0;
-        expr* item[3];
-        if (nn1 != new_nn1) {
-            item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1);
-        }
-        if (nn2 != new_nn2) {
-            item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2);
-        }
-        item[ii4++] = ctx.mk_eq_atom(nn1, nn2);
-        expr_ref premise(m.mk_and(ii4, item), m);
-        expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m);
+        // build conclusion: ( Length(lhs) == Length(rhs) )
+        expr_ref len_lhs(mk_strlen(a_lhs), m);
+        SASSERT(len_lhs);
+        expr_ref len_rhs(mk_strlen(a_rhs), m);
+        SASSERT(len_rhs);
+        expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m);
+
+        TRACE("str", tout << "string-eq length-eq axiom: "
+              << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;);
         assert_implication(premise, conclusion);
     }
 
-    // start to split both concats
-    check_and_init_cut_var(v1_arg0);
-    check_and_init_cut_var(v1_arg1);
-    check_and_init_cut_var(v2_arg0);
-    check_and_init_cut_var(v2_arg1);
+    void theory_str::instantiate_axiom_CharAt(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-    //*************************************************************
-    // case 1: concat(x, y) = concat(m, n)
-    //*************************************************************
-    if (is_concat_eq_type1(new_nn1, new_nn2)) {
-        process_concat_eq_type1(new_nn1, new_nn2);
-        return;
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
+
+        TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;);
+
+        expr_ref ts0(mk_str_var("ts0"), m);
+        expr_ref ts1(mk_str_var("ts1"), m);
+        expr_ref ts2(mk_str_var("ts2"), m);
+
+        expr_ref cond(m.mk_and(
+                          m_autil.mk_ge(expr->get_arg(1), mk_int(0)),
+                          // REWRITE for arithmetic theory:
+                          // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0)))
+                          m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0)))
+                               ), m);
+
+        expr_ref_vector and_item(m);
+        and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2))));
+        and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0)));
+        and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1)));
+
+        expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m);
+        expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m);
+
+        expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m);
+        expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m);
+
+        SASSERT(axiom);
+        SASSERT(reductionVar);
+
+        expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m);
+        SASSERT(finalAxiom);
+        assert_axiom(finalAxiom);
     }
 
-    //*************************************************************
-    // case 2: concat(x, y) = concat(m, "str")
-    //*************************************************************
-    if (is_concat_eq_type2(new_nn1, new_nn2)) {
-        process_concat_eq_type2(new_nn1, new_nn2);
-        return;
+    void theory_str::instantiate_axiom_prefixof(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
+
+        TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;);
+
+        expr_ref ts0(mk_str_var("ts0"), m);
+        expr_ref ts1(mk_str_var("ts1"), m);
+
+        expr_ref_vector innerItems(m);
+        innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1)));
+        innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0))));
+        innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr)));
+        expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m);
+        SASSERT(then1);
+
+        // the top-level condition is Length(arg0) >= Length(arg1)
+        expr_ref topLevelCond(
+            m_autil.mk_ge(
+                m_autil.mk_add(
+                    mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))),
+                mk_int(0))
+            , m);
+        SASSERT(topLevelCond);
+
+        expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m);
+        SASSERT(finalAxiom);
+        assert_axiom(finalAxiom);
     }
 
-    //*************************************************************
-    // case 3: concat(x, y) = concat("str", n)
-    //*************************************************************
-    if (is_concat_eq_type3(new_nn1, new_nn2)) {
-        process_concat_eq_type3(new_nn1, new_nn2);
-        return;
+    void theory_str::instantiate_axiom_suffixof(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
+
+        TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;);
+
+        expr_ref ts0(mk_str_var("ts0"), m);
+        expr_ref ts1(mk_str_var("ts1"), m);
+
+        expr_ref_vector innerItems(m);
+        innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1)));
+        innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0))));
+        innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr)));
+        expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m);
+        SASSERT(then1);
+
+        // the top-level condition is Length(arg0) >= Length(arg1)
+        expr_ref topLevelCond(
+            m_autil.mk_ge(
+                m_autil.mk_add(
+                    mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))),
+                mk_int(0))
+            , m);
+        SASSERT(topLevelCond);
+
+        expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m);
+        SASSERT(finalAxiom);
+        assert_axiom(finalAxiom);
     }
 
-    //*************************************************************
-    //  case 4: concat("str1", y) = concat("str2", n)
-    //*************************************************************
-    if (is_concat_eq_type4(new_nn1, new_nn2)) {
-        process_concat_eq_type4(new_nn1, new_nn2);
-        return;
+    void theory_str::instantiate_axiom_Contains(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        app * ex = e->get_owner();
+        if (axiomatized_terms.contains(ex)) {
+            TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(ex);
+
+        // quick path, because this is necessary due to rewriter behaviour
+        // at minimum it should fix z3str/concat-006.smt2
+        zstring haystackStr, needleStr;
+        if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) {
+            TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;);
+            if (haystackStr.contains(needleStr)) {
+                assert_axiom(ex);
+            } else {
+                assert_axiom(m.mk_not(ex));
+            }
+            return;
+        }
+
+        { // register Contains()
+            expr * str = ex->get_arg(0);
+            expr * substr = ex->get_arg(1);
+            contains_map.push_back(ex);
+            std::pair<expr*, expr*> key = std::pair<expr*, expr*>(str, substr);
+            contain_pair_bool_map.insert(str, substr, ex);
+            contain_pair_idx_map[str].insert(key);
+            contain_pair_idx_map[substr].insert(key);
+        }
+
+        TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;);
+
+        expr_ref ts0(mk_str_var("ts0"), m);
+        expr_ref ts1(mk_str_var("ts1"), m);
+
+        expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m);
+        SASSERT(breakdownAssert);
+        assert_axiom(breakdownAssert);
     }
 
-    //*************************************************************
-    //  case 5: concat(x, "str1") = concat(m, "str2")
-    //*************************************************************
-    if (is_concat_eq_type5(new_nn1, new_nn2)) {
-        process_concat_eq_type5(new_nn1, new_nn2);
-        return;
-    }
-    //*************************************************************
-    //  case 6: concat("str1", y) = concat(m, "str2")
-    //*************************************************************
-    if (is_concat_eq_type6(new_nn1, new_nn2)) {
-        process_concat_eq_type6(new_nn1, new_nn2);
-        return;
+    void theory_str::instantiate_axiom_Indexof(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
+
+        TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;);
+
+        expr_ref x1(mk_str_var("x1"), m);
+        expr_ref x2(mk_str_var("x2"), m);
+        expr_ref indexAst(mk_int_var("index"), m);
+
+        expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m);
+        SASSERT(condAst);
+
+        // -----------------------
+        // true branch
+        expr_ref_vector thenItems(m);
+        //  args[0] = x1 . args[1] . x2
+        thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))));
+        //  indexAst = |x1|
+        thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1)));
+        //     args[0]  = x3 . x4
+        //  /\ |x3| = |x1| + |args[1]| - 1
+        //  /\ ! contains(x3, args[1])
+        expr_ref x3(mk_str_var("x3"), m);
+        expr_ref x4(mk_str_var("x4"), m);
+        expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m);
+        SASSERT(tmpLen);
+        thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4)));
+        thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen));
+        thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1))));
+        expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m);
+        SASSERT(thenBranch);
+
+        // -----------------------
+        // false branch
+        expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m);
+        SASSERT(elseBranch);
+
+        expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m);
+        SASSERT(breakdownAssert);
+
+        expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m);
+        SASSERT(reduceToIndex);
+
+        expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m);
+        SASSERT(finalAxiom);
+        assert_axiom(finalAxiom);
     }
 
-}
+    void theory_str::instantiate_axiom_Indexof2(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-/*
- * Returns true if attempting to process a concat equality between lhs and rhs
- * will result in overlapping variables (false otherwise).
- */
-bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) {
-    ast_manager & m = get_manager();
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
 
-    expr_ref new_nn1(simplify_concat(lhs), m);
-    expr_ref new_nn2(simplify_concat(rhs), m);
-    app * a_new_nn1 = to_app(new_nn1);
-    app * a_new_nn2 = to_app(new_nn2);
+        TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;);
 
-    bool n1IsConcat = u.str.is_concat(a_new_nn1);
-    bool n2IsConcat = u.str.is_concat(a_new_nn2);
-    if (!n1IsConcat && !n2IsConcat) {
-        // we simplified both sides to non-concat expressions...
-        return false;
+        // -------------------------------------------------------------------------------
+        //   if (arg[2] >= length(arg[0]))                          // ite2
+        //     resAst = -1
+        //   else
+        //     args[0] = prefix . suffix
+        //     /\ indexAst = indexof(suffix, arg[1])
+        //     /\ args[2] = len(prefix)
+        //     /\ if (indexAst == -1)  resAst = indexAst            // ite3
+        //        else   resAst = args[2] + indexAst
+        // -------------------------------------------------------------------------------
+
+        expr_ref resAst(mk_int_var("res"), m);
+        expr_ref indexAst(mk_int_var("index"), m);
+        expr_ref prefix(mk_str_var("prefix"), m);
+        expr_ref suffix(mk_str_var("suffix"), m);
+        expr_ref prefixLen(mk_strlen(prefix), m);
+        expr_ref zeroAst(mk_int(0), m);
+        expr_ref negOneAst(mk_int(-1), m);
+
+        expr_ref ite3(m.mk_ite(
+                          ctx.mk_eq_atom(indexAst, negOneAst),
+                          ctx.mk_eq_atom(resAst, negOneAst),
+                          ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst))
+                               ),m);
+
+        expr_ref_vector ite2ElseItems(m);
+        ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix)));
+        ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1))));
+        ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen));
+        ite2ElseItems.push_back(ite3);
+        expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m);
+        SASSERT(ite2Else);
+
+        expr_ref ite2(m.mk_ite(
+                          //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))),
+                          m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst),
+                          ctx.mk_eq_atom(resAst, negOneAst),
+                          ite2Else
+                               ), m);
+        SASSERT(ite2);
+
+        expr_ref ite1(m.mk_ite(
+                          //m_autil.mk_lt(expr->get_arg(2), zeroAst),
+                          m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)),
+                          ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))),
+                          ite2
+                               ), m);
+        SASSERT(ite1);
+        assert_axiom(ite1);
+
+        expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m);
+        SASSERT(reduceTerm);
+        assert_axiom(reduceTerm);
     }
 
-    expr * v1_arg0 = a_new_nn1->get_arg(0);
-    expr * v1_arg1 = a_new_nn1->get_arg(1);
-    expr * v2_arg0 = a_new_nn2->get_arg(0);
-    expr * v2_arg1 = a_new_nn2->get_arg(1);
+    void theory_str::instantiate_axiom_LastIndexof(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-    TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;);
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
 
-    check_and_init_cut_var(v1_arg0);
-    check_and_init_cut_var(v1_arg1);
-    check_and_init_cut_var(v2_arg0);
-    check_and_init_cut_var(v2_arg1);
+        TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;);
 
-    //*************************************************************
-    // case 1: concat(x, y) = concat(m, n)
-    //*************************************************************
-    if (is_concat_eq_type1(new_nn1, new_nn2)) {
-        TRACE("str", tout << "Type 1 check." << std::endl;);
-        expr * x = to_app(new_nn1)->get_arg(0);
-        expr * y = to_app(new_nn1)->get_arg(1);
-        expr * m = to_app(new_nn2)->get_arg(0);
-        expr * n = to_app(new_nn2)->get_arg(1);
+        expr_ref x1(mk_str_var("x1"), m);
+        expr_ref x2(mk_str_var("x2"), m);
+        expr_ref indexAst(mk_int_var("index"), m);
+        expr_ref_vector items(m);
 
-        if (has_self_cut(m, y)) {
-            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout););
-            return true;
-        } else if (has_self_cut(x, n)) {
-            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout););
-            return true;
-        } else {
-            return false;
+        // args[0] = x1 . args[1] . x2
+        expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m);
+        expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m);  // arg0HasArg1 = Contains(args[0], args[1])
+        items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1));
+
+
+        expr_ref condAst(arg0HasArg1, m);
+        //----------------------------
+        // true branch
+        expr_ref_vector thenItems(m);
+        thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0)));
+        //  args[0] = x1 . args[1] . x2
+        //  x1 doesn't contain args[1]
+        thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1))));
+        thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1)));
+
+        bool canSkip = false;
+        zstring arg1Str;
+        if (u.str.is_string(expr->get_arg(1), arg1Str)) {
+            if (arg1Str.length() == 1) {
+                canSkip = true;
+            }
+        }
+
+        if (!canSkip) {
+            // args[0]  = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1])
+            expr_ref x3(mk_str_var("x3"), m);
+            expr_ref x4(mk_str_var("x4"), m);
+            expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m);
+            thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4)));
+            thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen));
+            thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1))));
+        }
+        //----------------------------
+        // else branch
+        expr_ref_vector elseItems(m);
+        elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1)));
+
+        items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr())));
+
+        expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m);
+        SASSERT(breakdownAssert);
+
+        expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m);
+        SASSERT(reduceToIndex);
+
+        expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m);
+        SASSERT(finalAxiom);
+        assert_axiom(finalAxiom);
+    }
+
+    void theory_str::instantiate_axiom_Substr(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
+
+        TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;);
+
+        expr_ref substrBase(expr->get_arg(0), m);
+        expr_ref substrPos(expr->get_arg(1), m);
+        expr_ref substrLen(expr->get_arg(2), m);
+        SASSERT(substrBase);
+        SASSERT(substrPos);
+        SASSERT(substrLen);
+
+        expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m);
+        expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m);
+        SASSERT(zero);
+        SASSERT(minusOne);
+
+        expr_ref_vector argumentsValid_terms(m);
+        // pos >= 0
+        argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero));
+        // pos < strlen(base)
+        // --> pos + -1*strlen(base) < 0
+        argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge(
+                                                    m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)),
+                                                    zero)));
+        // len >= 0
+        argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero));
+
+        expr_ref argumentsValid(mk_and(argumentsValid_terms), m);
+        SASSERT(argumentsValid);
+        ctx.internalize(argumentsValid, false);
+
+        // (pos+len) >= strlen(base)
+        // --> pos + len + -1*strlen(base) >= 0
+        expr_ref lenOutOfBounds(m_autil.mk_ge(
+                                    m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))),
+                                    zero), m);
+        SASSERT(lenOutOfBounds);
+        ctx.internalize(argumentsValid, false);
+
+        // Case 1: pos < 0 or pos >= strlen(base) or len < 0
+        // ==> (Substr ...) = ""
+        expr_ref case1_premise(m.mk_not(argumentsValid), m);
+        SASSERT(case1_premise);
+        ctx.internalize(case1_premise, false);
+        expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m);
+        SASSERT(case1_conclusion);
+        ctx.internalize(case1_conclusion, false);
+        expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m);
+        SASSERT(case1);
+
+        // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base)
+        // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1
+        expr_ref t0(mk_str_var("t0"), m);
+        expr_ref t1(mk_str_var("t1"), m);
+        expr_ref case2_conclusion(m.mk_and(
+                                      ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)),
+                                      ctx.mk_eq_atom(mk_strlen(t0), substrPos),
+                                      ctx.mk_eq_atom(expr, t1)), m);
+        expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m);
+        SASSERT(case2);
+
+        // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base)
+        // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3
+        expr_ref t2(mk_str_var("t2"), m);
+        expr_ref t3(mk_str_var("t3"), m);
+        expr_ref t4(mk_str_var("t4"), m);
+        expr_ref_vector case3_conclusion_terms(m);
+        case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4))));
+        case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos));
+        case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen));
+        case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3));
+        expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m);
+        expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m);
+        SASSERT(case3);
+
+        ctx.internalize(case1, false);
+        ctx.internalize(case2, false);
+        ctx.internalize(case3, false);
+
+        expr_ref finalAxiom(m.mk_and(case1, case2, case3), m);
+        SASSERT(finalAxiom);
+        assert_axiom(finalAxiom);
+    }
+
+    void theory_str::instantiate_axiom_Replace(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        app * expr = e->get_owner();
+        if (axiomatized_terms.contains(expr)) {
+            TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(expr);
+
+        TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;);
+
+        expr_ref x1(mk_str_var("x1"), m);
+        expr_ref x2(mk_str_var("x2"), m);
+        expr_ref i1(mk_int_var("i1"), m);
+        expr_ref result(mk_str_var("result"), m);
+
+        // condAst = Contains(args[0], args[1])
+        expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m);
+        // -----------------------
+        // true branch
+        expr_ref_vector thenItems(m);
+        //  args[0] = x1 . args[1] . x2
+        thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))));
+        //  i1 = |x1|
+        thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1)));
+        //  args[0]  = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1])
+        expr_ref x3(mk_str_var("x3"), m);
+        expr_ref x4(mk_str_var("x4"), m);
+        expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m);
+        thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4)));
+        thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen));
+        thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1))));
+        thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2))));
+        // -----------------------
+        // false branch
+        expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m);
+
+        expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m);
+        SASSERT(breakdownAssert);
+
+        expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m);
+        SASSERT(reduceToResult);
+
+        expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m);
+        SASSERT(finalAxiom);
+        assert_axiom(finalAxiom);
+    }
+
+    void theory_str::instantiate_axiom_str_to_int(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        app * ex = e->get_owner();
+        if (axiomatized_terms.contains(ex)) {
+            TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;);
+            return;
+        }
+        axiomatized_terms.insert(ex);
+
+        TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;);
+
+        // let expr = (str.to-int S)
+        // axiom 1: expr >= -1
+        // axiom 2: expr = 0 <==> S = "0"
+        // axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0"
+
+        expr * S = ex->get_arg(0);
+        {
+            expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m);
+            SASSERT(axiom1);
+            assert_axiom(axiom1);
+        }
+
+        {
+            expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m);
+            expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m);
+            expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m);
+            SASSERT(axiom2);
+            assert_axiom(axiom2);
+        }
+
+        {
+            expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m);
+            expr_ref hd(mk_str_var("hd"), m);
+            expr_ref tl(mk_str_var("tl"), m);
+            expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m);
+            expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m);
+            expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m);
+            expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m);
+            SASSERT(premise);
+            SASSERT(conclusion);
+            assert_implication(premise, conclusion);
         }
     }
 
-    //*************************************************************
-    // case 2: concat(x, y) = concat(m, "str")
-    //*************************************************************
-    if (is_concat_eq_type2(new_nn1, new_nn2)) {
+    void theory_str::instantiate_axiom_int_to_str(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-        expr * y = NULL;
-        expr * m = NULL;
-        expr * v1_arg0 = to_app(new_nn1)->get_arg(0);
-        expr * v1_arg1 = to_app(new_nn1)->get_arg(1);
-        expr * v2_arg0 = to_app(new_nn2)->get_arg(0);
-        expr * v2_arg1 = to_app(new_nn2)->get_arg(1);
-
-        if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) {
-            m = v1_arg0;
-            y = v2_arg1;
-        } else {
-            m = v2_arg0;
-            y = v1_arg1;
+        app * ex = e->get_owner();
+        if (axiomatized_terms.contains(ex)) {
+            TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;);
+            return;
         }
+        axiomatized_terms.insert(ex);
 
-        if (has_self_cut(m, y)) {
-            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout););
-            return true;
-        } else {
-            return false;
+        TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;);
+
+        // axiom 1: N < 0 <==> (str.from-int N) = ""
+        expr * N = ex->get_arg(0);
+        {
+            expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m);
+            expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m);
+            expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m);
+            SASSERT(axiom1);
+            assert_axiom(axiom1);
         }
     }
 
-    //*************************************************************
-    // case 3: concat(x, y) = concat("str", n)
-    //*************************************************************
-    if (is_concat_eq_type3(new_nn1, new_nn2)) {
-        expr * v1_arg0 = to_app(new_nn1)->get_arg(0);
-        expr * v1_arg1 = to_app(new_nn1)->get_arg(1);
-        expr * v2_arg0 = to_app(new_nn2)->get_arg(0);
-        expr * v2_arg1 = to_app(new_nn2)->get_arg(1);
+    expr * theory_str::mk_RegexIn(expr * str, expr * regexp) {
+        app * regexIn = u.re.mk_in_re(str, regexp);
+        // immediately force internalization so that axiom setup does not fail
+        get_context().internalize(regexIn, false);
+        set_up_axioms(regexIn);
+        return regexIn;
+    }
 
-        expr * x = NULL;
-        expr * n = NULL;
-
-        if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) {
-            n = v1_arg1;
-            x = v2_arg0;
-        } else {
-            n = v2_arg1;
-            x = v1_arg0;
+    static zstring str2RegexStr(zstring str) {
+        zstring res("");
+        int len = str.length();
+        for (int i = 0; i < len; i++) {
+            char nc = str[i];
+            // 12 special chars
+            if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?'
+                || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') {
+                res = res + zstring("\\");
+            }
+            char tmp[2] = {(char)str[i], '\0'};
+            res = res + zstring(tmp);
         }
-        if (has_self_cut(x, n)) {
-            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout););
-            return true;
+        return res;
+    }
+
+    zstring theory_str::get_std_regex_str(expr * regex) {
+        app * a_regex = to_app(regex);
+        if (u.re.is_to_re(a_regex)) {
+            expr * regAst = a_regex->get_arg(0);
+            zstring regAstVal;
+            u.str.is_string(regAst, regAstVal);
+            zstring regStr = str2RegexStr(regAstVal);
+            return regStr;
+        } else if (u.re.is_concat(a_regex)) {
+            expr * reg1Ast = a_regex->get_arg(0);
+            expr * reg2Ast = a_regex->get_arg(1);
+            zstring reg1Str = get_std_regex_str(reg1Ast);
+            zstring reg2Str = get_std_regex_str(reg2Ast);
+            return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")");
+        } else if (u.re.is_union(a_regex)) {
+            expr * reg1Ast = a_regex->get_arg(0);
+            expr * reg2Ast = a_regex->get_arg(1);
+            zstring reg1Str = get_std_regex_str(reg1Ast);
+            zstring reg2Str = get_std_regex_str(reg2Ast);
+            return  zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")");
+        } else if (u.re.is_star(a_regex)) {
+            expr * reg1Ast = a_regex->get_arg(0);
+            zstring reg1Str = get_std_regex_str(reg1Ast);
+            return  zstring("(") + reg1Str + zstring(")*");
+        } else if (u.re.is_range(a_regex)) {
+            expr * range1 = a_regex->get_arg(0);
+            expr * range2 = a_regex->get_arg(1);
+            zstring range1val, range2val;
+            u.str.is_string(range1, range1val);
+            u.str.is_string(range2, range2val);
+            return zstring("[") + range1val + zstring("-") + range2val + zstring("]");
         } else {
-            return false;
+            TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;);
+            UNREACHABLE(); return zstring("");
         }
     }
 
-    //*************************************************************
-    //  case 4: concat("str1", y) = concat("str2", n)
-    //*************************************************************
-    if (is_concat_eq_type4(new_nn1, new_nn2)) {
-        // This case can never result in an overlap.
-        return false;
-    }
+    void theory_str::instantiate_axiom_RegexIn(enode * e) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-    //*************************************************************
-    //  case 5: concat(x, "str1") = concat(m, "str2")
-    //*************************************************************
-    if (is_concat_eq_type5(new_nn1, new_nn2)) {
-        // This case can never result in an overlap.
-        return false;
-    }
-    //*************************************************************
-    //  case 6: concat("str1", y) = concat(m, "str2")
-    //*************************************************************
-    if (is_concat_eq_type6(new_nn1, new_nn2)) {
-        expr * v1_arg0 = to_app(new_nn1)->get_arg(0);
-        expr * v1_arg1 = to_app(new_nn1)->get_arg(1);
-        expr * v2_arg0 = to_app(new_nn2)->get_arg(0);
-        expr * v2_arg1 = to_app(new_nn2)->get_arg(1);
-
-        expr * y = NULL;
-        expr * m = NULL;
-
-        if (u.str.is_string(v1_arg0)) {
-            y = v1_arg1;
-            m = v2_arg0;
-        } else {
-            y = v2_arg1;
-            m = v1_arg0;
+        app * ex = e->get_owner();
+        if (axiomatized_terms.contains(ex)) {
+            TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;);
+            return;
         }
-        if (has_self_cut(m, y)) {
-            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout););
-            return true;
+        axiomatized_terms.insert(ex);
+
+        TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;);
+
+        {
+            zstring regexStr = get_std_regex_str(ex->get_arg(1));
+            std::pair<expr*, zstring> key1(ex->get_arg(0), regexStr);
+            // skip Z3str's map check, because we already check if we set up axioms on this term
+            regex_in_bool_map[key1] = ex;
+            regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr);
+        }
+
+        expr_ref str(ex->get_arg(0), m);
+        app * regex = to_app(ex->get_arg(1));
+
+        if (u.re.is_to_re(regex)) {
+            expr_ref rxStr(regex->get_arg(0), m);
+            // want to assert 'expr IFF (str == rxStr)'
+            expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m);
+            expr_ref finalAxiom(m.mk_iff(ex, rhs), m);
+            SASSERT(finalAxiom);
+            assert_axiom(finalAxiom);
+            TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;);
+        } else if (u.re.is_concat(regex)) {
+            expr_ref var1(mk_regex_rep_var(), m);
+            expr_ref var2(mk_regex_rep_var(), m);
+            expr_ref rhs(mk_concat(var1, var2), m);
+            expr_ref rx1(regex->get_arg(0), m);
+            expr_ref rx2(regex->get_arg(1), m);
+            expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m);
+            expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m);
+
+            expr_ref_vector items(m);
+            items.push_back(var1InRegex1);
+            items.push_back(var2InRegex2);
+            items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs)));
+
+            expr_ref finalAxiom(mk_and(items), m);
+            SASSERT(finalAxiom);
+            assert_axiom(finalAxiom);
+        } else if (u.re.is_union(regex)) {
+            expr_ref var1(mk_regex_rep_var(), m);
+            expr_ref var2(mk_regex_rep_var(), m);
+            expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m);
+            expr_ref regex1(regex->get_arg(0), m);
+            expr_ref regex2(regex->get_arg(1), m);
+            expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m);
+            expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m);
+            expr_ref_vector items(m);
+            items.push_back(var1InRegex1);
+            items.push_back(var2InRegex2);
+            items.push_back(ctx.mk_eq_atom(ex, orVar));
+            assert_axiom(mk_and(items));
+        } else if (u.re.is_star(regex)) {
+            // slightly more complex due to the unrolling step.
+            expr_ref regex1(regex->get_arg(0), m);
+            expr_ref unrollCount(mk_unroll_bound_var(), m);
+            expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m);
+            expr_ref_vector items(m);
+            items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc)));
+            items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string(""))));
+            expr_ref finalAxiom(mk_and(items), m);
+            SASSERT(finalAxiom);
+            assert_axiom(finalAxiom);
+        } else if (u.re.is_range(regex)) {
+            // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z");
+            // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z")
+            expr_ref lo(regex->get_arg(0), m);
+            expr_ref hi(regex->get_arg(1), m);
+            zstring str_lo, str_hi;
+            SASSERT(u.str.is_string(lo));
+            SASSERT(u.str.is_string(hi));
+            u.str.is_string(lo, str_lo);
+            u.str.is_string(hi, str_hi);
+            SASSERT(str_lo.length() == 1);
+            SASSERT(str_hi.length() == 1);
+            unsigned int c1 = str_lo[0];
+            unsigned int c2 = str_hi[0];
+            if (c1 > c2) {
+                // exchange
+                unsigned int tmp = c1;
+                c1 = c2;
+                c2 = tmp;
+            }
+            expr_ref_vector range_cases(m);
+            for (unsigned int ch = c1; ch <= c2; ++ch) {
+                zstring s_ch(ch);
+                expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m);
+                range_cases.push_back(rhs);
+            }
+            expr_ref rhs(mk_or(range_cases), m);
+            expr_ref finalAxiom(m.mk_iff(ex, rhs), m);
+            SASSERT(finalAxiom);
+            assert_axiom(finalAxiom);
         } else {
-            return false;
+            TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;);
+            NOT_IMPLEMENTED_YET();
         }
     }
 
-    TRACE("str", tout << "warning: unrecognized concat case" << std::endl;);
-    return false;
-}
+    void theory_str::attach_new_th_var(enode * n) {
+        context & ctx = get_context();
+        theory_var v = mk_var(n);
+        ctx.attach_th_var(n, this, v);
+        TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;);
+    }
 
-/*************************************************************
- * Type 1: concat(x, y) = concat(m, n)
- *         x, y, m and n all variables
- *************************************************************/
-bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) {
-    expr * x = to_app(concatAst1)->get_arg(0);
-    expr * y = to_app(concatAst1)->get_arg(1);
-    expr * m = to_app(concatAst2)->get_arg(0);
-    expr * n = to_app(concatAst2)->get_arg(1);
+    void theory_str::reset_eh() {
+        TRACE("str", tout << "resetting" << std::endl;);
+        m_trail_stack.reset();
 
-    if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) {
+        m_basicstr_axiom_todo.reset();
+        m_str_eq_todo.reset();
+        m_concat_axiom_todo.reset();
+        pop_scope_eh(get_context().get_scope_level());
+    }
+
+    /*
+     * Check equality among equivalence class members of LHS and RHS
+     * to discover an incorrect LHS == RHS.
+     * For example, if we have y2 == "str3"
+     * and the equivalence classes are
+     * { y2, (Concat ce m2) }
+     * { "str3", (Concat abc x2) }
+     * then y2 can't be equal to "str3".
+     * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3")
+     */
+    bool theory_str::new_eq_check(expr * lhs, expr * rhs) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        // skip this check if we defer consistency checking, as we can do it for every EQC in final check
+        if (!opt_DeferEQCConsistencyCheck) {
+            check_concat_len_in_eqc(lhs);
+            check_concat_len_in_eqc(rhs);
+        }
+
+        // Now we iterate over all pairs of terms across both EQCs
+        // and check whether we can show that any pair of distinct terms
+        // cannot possibly be equal.
+        // If that's the case, we assert an axiom to that effect and stop.
+
+        expr * eqc_nn1 = lhs;
+        do {
+            expr * eqc_nn2 = rhs;
+            do {
+                TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;);
+                // inconsistency check: value
+                if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) {
+                    TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;);
+                    expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m);
+                    assert_axiom(to_assert);
+                    // this shouldn't use the integer theory at all, so we don't allow the option of quick-return
+                    return false;
+                }
+                if (!check_length_consistency(eqc_nn1, eqc_nn2)) {
+                    TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;);
+                    if (opt_NoQuickReturn_IntegerTheory){
+                        TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;);
+                    } else {
+                        return false;
+                    }
+                }
+                eqc_nn2 = get_eqc_next(eqc_nn2);
+            } while (eqc_nn2 != rhs);
+            eqc_nn1 = get_eqc_next(eqc_nn1);
+        } while (eqc_nn1 != lhs);
+
+        if (!contains_map.empty()) {
+            check_contain_in_new_eq(lhs, rhs);
+        }
+
+        if (!regex_in_bool_map.empty()) {
+            TRACE("str", tout << "checking regex consistency" << std::endl;);
+            check_regex_in(lhs, rhs);
+        }
+
+        // okay, all checks here passed
         return true;
-    } else {
+    }
+
+    // support for user_smt_theory-style EQC handling
+
+    app * theory_str::get_ast(theory_var i) {
+        return get_enode(i)->get_owner();
+    }
+
+    theory_var theory_str::get_var(expr * n) const {
+        if (!is_app(n)) {
+            return null_theory_var;
+        }
+        context & ctx = get_context();
+        if (ctx.e_internalized(to_app(n))) {
+            enode * e = ctx.get_enode(to_app(n));
+            return e->get_th_var(get_id());
+        }
+        return null_theory_var;
+    }
+
+    // simulate Z3_theory_get_eqc_next()
+    expr * theory_str::get_eqc_next(expr * n) {
+        theory_var v = get_var(n);
+        if (v != null_theory_var) {
+            theory_var r = m_find.next(v);
+            return get_ast(r);
+        }
+        return n;
+    }
+
+    void theory_str::group_terms_by_eqc(expr * n, std::set<expr*> & concats, std::set<expr*> & vars, std::set<expr*> & consts) {
+        context & ctx = get_context();
+        expr * eqcNode = n;
+        do {
+            app * ast = to_app(eqcNode);
+            if (u.str.is_concat(ast)) {
+                expr * simConcat = simplify_concat(ast);
+                if (simConcat != ast) {
+                    if (u.str.is_concat(to_app(simConcat))) {
+                        concats.insert(simConcat);
+                    } else {
+                        if (u.str.is_string(simConcat)) {
+                            consts.insert(simConcat);
+                        } else {
+                            vars.insert(simConcat);
+                        }
+                    }
+                } else {
+                    concats.insert(simConcat);
+                }
+            } else if (u.str.is_string(ast)) {
+                consts.insert(ast);
+            } else {
+                vars.insert(ast);
+            }
+            eqcNode = get_eqc_next(eqcNode);
+        } while (eqcNode != n);
+    }
+
+    void theory_str::get_nodes_in_concat(expr * node, ptr_vector<expr> & nodeList) {
+        app * a_node = to_app(node);
+        if (!u.str.is_concat(a_node)) {
+            nodeList.push_back(node);
+            return;
+        } else {
+            SASSERT(a_node->get_num_args() == 2);
+            expr * leftArg = a_node->get_arg(0);
+            expr * rightArg = a_node->get_arg(1);
+            get_nodes_in_concat(leftArg, nodeList);
+            get_nodes_in_concat(rightArg, nodeList);
+        }
+    }
+
+    // previously Concat() in strTheory.cpp
+    // Evaluates the concatenation (n1 . n2) with respect to
+    // the current equivalence classes of n1 and n2.
+    // Returns a constant string expression representing this concatenation
+    // if one can be determined, or NULL if this is not possible.
+    expr * theory_str::eval_concat(expr * n1, expr * n2) {
+        bool n1HasEqcValue = false;
+        bool n2HasEqcValue = false;
+        expr * v1 = get_eqc_value(n1, n1HasEqcValue);
+        expr * v2 = get_eqc_value(n2, n2HasEqcValue);
+        if (n1HasEqcValue && n2HasEqcValue) {
+            zstring n1_str, n2_str;
+            u.str.is_string(v1, n1_str);
+            u.str.is_string(v2, n2_str);
+            zstring result = n1_str + n2_str;
+            return mk_string(result);
+        } else if (n1HasEqcValue && !n2HasEqcValue) {
+            zstring v1_str;
+            u.str.is_string(v1, v1_str);
+            if (v1_str.empty()) {
+                return n2;
+            }
+        } else if (n2HasEqcValue && !n1HasEqcValue) {
+            zstring v2_str;
+            u.str.is_string(v2, v2_str);
+            if (v2_str.empty()) {
+                return n1;
+            }
+        }
+        // give up
+        return NULL;
+    }
+
+    static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) {
+        if (x_exists) {
+            return x.to_string();
+        } else {
+            return "?";
+        }
+    }
+
+    /*
+     * The inputs:
+     *    ~ nn: non const node
+     *    ~ eq_str: the equivalent constant string of nn
+     *  Iterate the parent of all eqc nodes of nn, looking for:
+     *    ~ concat node
+     *  to see whether some concat nodes can be simplified.
+     */
+    void theory_str::simplify_parent(expr * nn, expr * eq_str) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m)
+              << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;);
+
+        ctx.internalize(nn, false);
+
+        zstring eq_strValue;
+        u.str.is_string(eq_str, eq_strValue);
+        expr * n_eqNode = nn;
+        do {
+            enode * n_eq_enode = ctx.get_enode(n_eqNode);
+            TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl
+                  << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;);
+
+            // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop.
+            // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it,
+            // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over.
+
+            enode_vector current_parents;
+            for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) {
+                current_parents.insert(*parent_it);
+            }
+
+            for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) {
+                enode * e_parent = *parent_it;
+                SASSERT(e_parent != NULL);
+
+                app * a_parent = e_parent->get_owner();
+                TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;);
+
+                if (u.str.is_concat(a_parent)) {
+                    expr * arg0 = a_parent->get_arg(0);
+                    expr * arg1 = a_parent->get_arg(1);
+
+                    rational parentLen;
+                    bool parentLen_exists = get_len_value(a_parent, parentLen);
+
+                    if (arg0 == n_eq_enode->get_owner()) {
+                        rational arg0Len, arg1Len;
+                        bool arg0Len_exists = get_len_value(eq_str, arg0Len);
+                        bool arg1Len_exists = get_len_value(arg1, arg1Len);
+
+                        TRACE("str",
+                              tout << "simplify_parent #1:" << std::endl
+                              << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl
+                              << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl
+                              << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl
+                              << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl;
+                              );
+
+                        if (parentLen_exists && !arg1Len_exists) {
+                            TRACE("str", tout << "make up len for arg1" << std::endl;);
+                            expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)),
+                                                       ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m);
+                            rational makeUpLenArg1 = parentLen - arg0Len;
+                            if (makeUpLenArg1.is_nonneg()) {
+                                expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m);
+                                assert_implication(implyL11, implyR11);
+                            } else {
+                                expr_ref neg(m.mk_not(implyL11), m);
+                                assert_axiom(neg);
+                            }
+                        }
+
+                        // (Concat n_eqNode arg1) /\ arg1 has eq const
+
+                        expr * concatResult = eval_concat(eq_str, arg1);
+                        if (concatResult != NULL) {
+                            bool arg1HasEqcValue = false;
+                            expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue);
+                            expr_ref implyL(m);
+                            if (arg1 != arg1Value) {
+                                expr_ref eq_ast1(m);
+                                eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str);
+                                SASSERT(eq_ast1);
+
+                                expr_ref eq_ast2(m);
+                                eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value);
+                                SASSERT(eq_ast2);
+                                implyL = m.mk_and(eq_ast1, eq_ast2);
+                            } else {
+                                implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                            }
+
+
+                            if (!in_same_eqc(a_parent, concatResult)) {
+                                expr_ref implyR(m);
+                                implyR = ctx.mk_eq_atom(a_parent, concatResult);
+                                SASSERT(implyR);
+
+                                assert_implication(implyL, implyR);
+                            }
+                        } else if (u.str.is_concat(to_app(n_eqNode))) {
+                            expr_ref simpleConcat(m);
+                            simpleConcat = mk_concat(eq_str, arg1);
+                            if (!in_same_eqc(a_parent, simpleConcat)) {
+                                expr_ref implyL(m);
+                                implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                                SASSERT(implyL);
+
+                                expr_ref implyR(m);
+                                implyR = ctx.mk_eq_atom(a_parent, simpleConcat);
+                                SASSERT(implyR);
+                                assert_implication(implyL, implyR);
+                            }
+                        }
+                    } // if (arg0 == n_eq_enode->get_owner())
+
+                    if (arg1 == n_eq_enode->get_owner()) {
+                        rational arg0Len, arg1Len;
+                        bool arg0Len_exists = get_len_value(arg0, arg0Len);
+                        bool arg1Len_exists = get_len_value(eq_str, arg1Len);
+
+                        TRACE("str",
+                              tout << "simplify_parent #2:" << std::endl
+                              << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl
+                              << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl
+                              << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl
+                              << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl;
+                              );
+                        if (parentLen_exists && !arg0Len_exists) {
+                            TRACE("str", tout << "make up len for arg0" << std::endl;);
+                            expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)),
+                                                       ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m);
+                            rational makeUpLenArg0 = parentLen - arg1Len;
+                            if (makeUpLenArg0.is_nonneg()) {
+                                expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m);
+                                assert_implication(implyL11, implyR11);
+                            } else {
+                                expr_ref neg(m.mk_not(implyL11), m);
+                                assert_axiom(neg);
+                            }
+                        }
+
+                        // (Concat arg0 n_eqNode) /\ arg0 has eq const
+
+                        expr * concatResult = eval_concat(arg0, eq_str);
+                        if (concatResult != NULL) {
+                            bool arg0HasEqcValue = false;
+                            expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue);
+                            expr_ref implyL(m);
+                            if (arg0 != arg0Value) {
+                                expr_ref eq_ast1(m);
+                                eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str);
+                                SASSERT(eq_ast1);
+                                expr_ref eq_ast2(m);
+                                eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value);
+                                SASSERT(eq_ast2);
+
+                                implyL = m.mk_and(eq_ast1, eq_ast2);
+                            } else {
+                                implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                            }
+
+                            if (!in_same_eqc(a_parent, concatResult)) {
+                                expr_ref implyR(m);
+                                implyR = ctx.mk_eq_atom(a_parent, concatResult);
+                                SASSERT(implyR);
+
+                                assert_implication(implyL, implyR);
+                            }
+                        } else if (u.str.is_concat(to_app(n_eqNode))) {
+                            expr_ref simpleConcat(m);
+                            simpleConcat = mk_concat(arg0, eq_str);
+                            if (!in_same_eqc(a_parent, simpleConcat)) {
+                                expr_ref implyL(m);
+                                implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                                SASSERT(implyL);
+
+                                expr_ref implyR(m);
+                                implyR = ctx.mk_eq_atom(a_parent, simpleConcat);
+                                SASSERT(implyR);
+                                assert_implication(implyL, implyR);
+                            }
+                        }
+                    } // if (arg1 == n_eq_enode->get_owner
+
+
+                    //---------------------------------------------------------
+                    // Case (2-1) begin: (Concat n_eqNode (Concat str var))
+                    if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) {
+                        app * a_arg1 = to_app(arg1);
+                        TRACE("str", tout << "simplify_parent #3" << std::endl;);
+                        expr * r_concat_arg0 = a_arg1->get_arg(0);
+                        if (u.str.is_string(r_concat_arg0)) {
+                            expr * combined_str = eval_concat(eq_str, r_concat_arg0);
+                            SASSERT(combined_str);
+                            expr * r_concat_arg1 = a_arg1->get_arg(1);
+                            expr_ref implyL(m);
+                            implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                            expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1);
+                            if (!in_same_eqc(a_parent, simplifiedAst)) {
+                                expr_ref implyR(m);
+                                implyR = ctx.mk_eq_atom(a_parent, simplifiedAst);
+                                assert_implication(implyL, implyR);
+                            }
+                        }
+                    }
+                    // Case (2-1) end: (Concat n_eqNode (Concat str var))
+                    //---------------------------------------------------------
+
+
+                    //---------------------------------------------------------
+                    // Case (2-2) begin: (Concat (Concat var str) n_eqNode)
+                    if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) {
+                        app * a_arg0 = to_app(arg0);
+                        TRACE("str", tout << "simplify_parent #4" << std::endl;);
+                        expr * l_concat_arg1 = a_arg0->get_arg(1);
+                        if (u.str.is_string(l_concat_arg1)) {
+                            expr * combined_str = eval_concat(l_concat_arg1, eq_str);
+                            SASSERT(combined_str);
+                            expr * l_concat_arg0 = a_arg0->get_arg(0);
+                            expr_ref implyL(m);
+                            implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                            expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str);
+                            if (!in_same_eqc(a_parent, simplifiedAst)) {
+                                expr_ref implyR(m);
+                                implyR = ctx.mk_eq_atom(a_parent, simplifiedAst);
+                                assert_implication(implyL, implyR);
+                            }
+                        }
+                    }
+                    // Case (2-2) end: (Concat (Concat var str) n_eqNode)
+                    //---------------------------------------------------------
+
+                    // Have to look up one more layer: if the parent of the concat is another concat
+                    //-------------------------------------------------
+                    // Case (3-1) begin: (Concat (Concat var n_eqNode) str )
+                    if (arg1 == n_eqNode) {
+                        for (enode_vector::iterator concat_parent_it = e_parent->begin_parents();
+                             concat_parent_it != e_parent->end_parents(); concat_parent_it++) {
+                            enode * e_concat_parent = *concat_parent_it;
+                            app * concat_parent = e_concat_parent->get_owner();
+                            if (u.str.is_concat(concat_parent)) {
+                                expr * concat_parent_arg0 = concat_parent->get_arg(0);
+                                expr * concat_parent_arg1 = concat_parent->get_arg(1);
+                                if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) {
+                                    TRACE("str", tout << "simplify_parent #5" << std::endl;);
+                                    expr * combinedStr = eval_concat(eq_str, concat_parent_arg1);
+                                    SASSERT(combinedStr);
+                                    expr_ref implyL(m);
+                                    implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                                    expr * simplifiedAst = mk_concat(arg0, combinedStr);
+                                    if (!in_same_eqc(concat_parent, simplifiedAst)) {
+                                        expr_ref implyR(m);
+                                        implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst);
+                                        assert_implication(implyL, implyR);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    // Case (3-1) end: (Concat (Concat var n_eqNode) str )
+                    // Case (3-2) begin: (Concat str (Concat n_eqNode var) )
+                    if (arg0 == n_eqNode) {
+                        for (enode_vector::iterator concat_parent_it = e_parent->begin_parents();
+                             concat_parent_it != e_parent->end_parents(); concat_parent_it++) {
+                            enode * e_concat_parent = *concat_parent_it;
+                            app * concat_parent = e_concat_parent->get_owner();
+                            if (u.str.is_concat(concat_parent)) {
+                                expr * concat_parent_arg0 = concat_parent->get_arg(0);
+                                expr * concat_parent_arg1 = concat_parent->get_arg(1);
+                                if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) {
+                                    TRACE("str", tout << "simplify_parent #6" << std::endl;);
+                                    expr * combinedStr = eval_concat(concat_parent_arg0, eq_str);
+                                    SASSERT(combinedStr);
+                                    expr_ref implyL(m);
+                                    implyL = ctx.mk_eq_atom(n_eqNode, eq_str);
+                                    expr * simplifiedAst = mk_concat(combinedStr, arg1);
+                                    if (!in_same_eqc(concat_parent, simplifiedAst)) {
+                                        expr_ref implyR(m);
+                                        implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst);
+                                        assert_implication(implyL, implyR);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    // Case (3-2) end: (Concat str (Concat n_eqNode var) )
+                } // if is_concat(a_parent)
+            } // for parent_it : n_eq_enode->begin_parents()
+
+
+            // check next EQC member
+            n_eqNode = get_eqc_next(n_eqNode);
+        } while (n_eqNode != nn);
+    }
+
+    expr * theory_str::simplify_concat(expr * node) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+        std::map<expr*, expr*> resolvedMap;
+        ptr_vector<expr> argVec;
+        get_nodes_in_concat(node, argVec);
+
+        for (unsigned i = 0; i < argVec.size(); ++i) {
+            bool vArgHasEqcValue = false;
+            expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue);
+            if (vArg != argVec[i]) {
+                resolvedMap[argVec[i]] = vArg;
+            }
+        }
+
+        if (resolvedMap.size() == 0) {
+            // no simplification possible
+            return node;
+        } else {
+            expr * resultAst = mk_string("");
+            for (unsigned i = 0; i < argVec.size(); ++i) {
+                bool vArgHasEqcValue = false;
+                expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue);
+                resultAst = mk_concat(resultAst, vArg);
+            }
+            TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;);
+
+            if (in_same_eqc(node, resultAst)) {
+                TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;);
+            } else {
+                expr_ref_vector items(m);
+                int pos = 0;
+                std::map<expr*, expr*>::iterator itor = resolvedMap.begin();
+                for (; itor != resolvedMap.end(); ++itor) {
+                    items.push_back(ctx.mk_eq_atom(itor->first, itor->second));
+                    pos += 1;
+                }
+                expr_ref premise(mk_and(items), m);
+                expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m);
+                assert_implication(premise, conclusion);
+            }
+            return resultAst;
+        }
+
+    }
+
+    // Modified signature of Z3str2's inferLenConcat().
+    // Returns true iff nLen can be inferred by this method
+    // (i.e. the equivalent of a len_exists flag in get_len_value()).
+
+    bool theory_str::infer_len_concat(expr * n, rational & nLen) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        expr * arg0 = to_app(n)->get_arg(0);
+        expr * arg1 = to_app(n)->get_arg(1);
+
+        rational arg0_len, arg1_len;
+        bool arg0_len_exists = get_len_value(arg0, arg0_len);
+        bool arg1_len_exists = get_len_value(arg1, arg1_len);
+        rational tmp_len;
+        bool nLen_exists = get_len_value(n, tmp_len);
+
+        if (arg0_len_exists && arg1_len_exists && !nLen_exists) {
+            expr_ref_vector l_items(m);
+            // if (mk_strlen(arg0) != mk_int(arg0_len)) {
+            {
+                l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len)));
+            }
+
+            // if (mk_strlen(arg1) != mk_int(arg1_len)) {
+            {
+                l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len)));
+            }
+
+            expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
+            rational nnLen = arg0_len + arg1_len;
+            expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m);
+            TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;);
+            assert_implication(axl, axr);
+            nLen = nnLen;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void theory_str::infer_len_concat_arg(expr * n, rational len) {
+        if (len.is_neg()) {
+            return;
+        }
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr * arg0 = to_app(n)->get_arg(0);
+        expr * arg1 = to_app(n)->get_arg(1);
+        rational arg0_len, arg1_len;
+        bool arg0_len_exists = get_len_value(arg0, arg0_len);
+        bool arg1_len_exists = get_len_value(arg1, arg1_len);
+
+        expr_ref_vector l_items(m);
+        expr_ref axr(m);
+        axr.reset();
+
+        // if (mk_length(t, n) != mk_int(ctx, len)) {
+        {
+            l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len)));
+        }
+
+        if (!arg0_len_exists && arg1_len_exists) {
+            //if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) {
+            {
+                l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len)));
+            }
+            rational arg0Len = len - arg1_len;
+            if (arg0Len.is_nonneg()) {
+                axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len));
+            } else {
+                // could negate
+            }
+        } else if (arg0_len_exists && !arg1_len_exists) {
+            //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) {
+            {
+                l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len)));
+            }
+            rational arg1Len = len - arg0_len;
+            if (arg1Len.is_nonneg()) {
+                axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len));
+            } else {
+                // could negate
+            }
+        } else {
+
+        }
+
+        if (axr) {
+            expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
+            assert_implication(axl, axr);
+        }
+    }
+
+    void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) {
+        rational nnLen;
+        bool nnLen_exists = get_len_value(nn1, nnLen);
+        if (!nnLen_exists) {
+            nnLen_exists = get_len_value(nn2, nnLen);
+        }
+
+        // case 1:
+        //    Known: a1_arg0 and a1_arg1
+        //    Unknown: nn1
+
+        if (u.str.is_concat(to_app(nn1))) {
+            rational nn1ConcatLen;
+            bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen);
+            if (nnLen_exists && nn1ConcatLen_exists) {
+                nnLen = nn1ConcatLen;
+            }
+        }
+
+        // case 2:
+        //    Known: a1_arg0 and a1_arg1
+        //    Unknown: nn1
+
+        if (u.str.is_concat(to_app(nn2))) {
+            rational nn2ConcatLen;
+            bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen);
+            if (nnLen_exists && nn2ConcatLen_exists) {
+                nnLen = nn2ConcatLen;
+            }
+        }
+
+        if (nnLen_exists) {
+            if (u.str.is_concat(to_app(nn1))) {
+                infer_len_concat_arg(nn1, nnLen);
+            }
+            if (u.str.is_concat(to_app(nn2))) {
+                infer_len_concat_arg(nn2, nnLen);
+            }
+        }
+
+        /*
+          if (isConcatFunc(t, nn2)) {
+          int nn2ConcatLen = inferLenConcat(t, nn2);
+          if (nnLen == -1 && nn2ConcatLen != -1)
+          nnLen = nn2ConcatLen;
+          }
+
+          if (nnLen != -1) {
+          if (isConcatFunc(t, nn1)) {
+          inferLenConcatArg(t, nn1, nnLen);
+          }
+          if (isConcatFunc(t, nn2)) {
+          inferLenConcatArg(t, nn2, nnLen);
+          }
+          }
+        */
+    }
+
+    void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) {
+        context & ctx = get_context();
+        ctx.internalize(term, false);
+        bool_var v = ctx.get_bool_var(term);
+        ctx.add_theory_aware_branching_info(v, priority, phase);
+    }
+
+    void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) {
+        context & ctx = get_context();
+        // pull each literal out of the arrangement disjunction
+        literal_vector ls;
+        for (unsigned i = 0; i < terms.size(); ++i) {
+            expr * e = terms.get(i);
+            literal l = ctx.get_literal(e);
+            ls.push_back(l);
+        }
+        ctx.mk_th_case_split(ls.size(), ls.c_ptr());
+    }
+
+    void theory_str::print_cut_var(expr * node, std::ofstream & xout) {
+        ast_manager & m = get_manager();
+        xout << "Cut info of " << mk_pp(node, m) << std::endl;
+        if (cut_var_map.contains(node)) {
+            if (!cut_var_map[node].empty()) {
+                xout << "[" << cut_var_map[node].top()->level << "] ";
+                std::map<expr*, int>::iterator itor = cut_var_map[node].top()->vars.begin();
+                for (; itor != cut_var_map[node].top()->vars.end(); ++itor) {
+                    xout << mk_pp(itor->first, m) << ", ";
+                }
+                xout << std::endl;
+            }
+        }
+    }
+
+    /*
+     * Handle two equivalent Concats.
+     */
+    void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        app * a_nn1 = to_app(nn1);
+        SASSERT(a_nn1->get_num_args() == 2);
+        app * a_nn2 = to_app(nn2);
+        SASSERT(a_nn2->get_num_args() == 2);
+
+        expr * a1_arg0 = a_nn1->get_arg(0);
+        expr * a1_arg1 = a_nn1->get_arg(1);
+        expr * a2_arg0 = a_nn2->get_arg(0);
+        expr * a2_arg1 = a_nn2->get_arg(1);
+
+        rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len;
+
+        bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len);
+        bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len);
+        bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len);
+        bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len);
+
+        TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl
+              << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;);
+
+        TRACE("str", tout
+              << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl
+              << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl
+              << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl
+              << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl
+              << std::endl;);
+
+        infer_len_concat_equality(nn1, nn2);
+
+        if (a1_arg0 == a2_arg0) {
+            if (!in_same_eqc(a1_arg1, a2_arg1)) {
+                expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m);
+                expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m);
+                expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m);
+                expr_ref conclusion(m.mk_and(eq1, eq2), m);
+                assert_implication(premise, conclusion);
+            }
+            TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;);
+            return;
+        }
+
+        if (a1_arg1 == a2_arg1) {
+            if (!in_same_eqc(a1_arg0, a2_arg0)) {
+                expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m);
+                expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m);
+                expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m);
+                expr_ref conclusion(m.mk_and(eq1, eq2), m);
+                assert_implication(premise, conclusion);
+            }
+            TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;);
+            return;
+        }
+
+        // quick path
+
+        if (in_same_eqc(a1_arg0, a2_arg0)) {
+            if (in_same_eqc(a1_arg1, a2_arg1)) {
+                TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;);
+                return;
+            } else {
+                TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;);
+                expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m);
+                expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m);
+                assert_implication(premise, conclusion);
+                return;
+            }
+        } else {
+            if (in_same_eqc(a1_arg1, a2_arg1)) {
+                TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;);
+                expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m);
+                expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m);
+                assert_implication(premise, conclusion);
+                return;
+            }
+        }
+
+        // quick path 2-1
+        if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) {
+            if (!in_same_eqc(a1_arg0, a2_arg0)) {
+                TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;);
+                expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m);
+                expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m);
+                expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m);
+                expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m);
+
+                expr_ref premise(m.mk_and(ax_l1, ax_l2), m);
+                expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m);
+
+                assert_implication(premise, conclusion);
+
+                if (opt_NoQuickReturn_IntegerTheory) {
+                    TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;);
+                } else {
+                    return;
+                }
+            }
+        }
+
+        if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) {
+            if (!in_same_eqc(a1_arg1, a2_arg1)) {
+                TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;);
+                expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m);
+                expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m);
+                expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m);
+                expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m);
+
+                expr_ref premise(m.mk_and(ax_l1, ax_l2), m);
+                expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m);
+
+                assert_implication(premise, conclusion);
+                if (opt_NoQuickReturn_IntegerTheory) {
+                    TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;);
+                } else {
+                    return;
+                }
+            }
+        }
+
+        expr_ref new_nn1(simplify_concat(nn1), m);
+        expr_ref new_nn2(simplify_concat(nn2), m);
+        app * a_new_nn1 = to_app(new_nn1);
+        app * a_new_nn2 = to_app(new_nn2);
+
+        TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl
+              << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;);
+
+        if (new_nn1 == new_nn2) {
+            TRACE("str", tout << "equal concats, return" << std::endl;);
+            return;
+        }
+
+        if (!can_two_nodes_eq(new_nn1, new_nn2)) {
+            expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m);
+            TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;);
+            assert_axiom(detected);
+            return;
+        }
+
+        // check whether new_nn1 and new_nn2 are still concats
+
+        bool n1IsConcat = u.str.is_concat(a_new_nn1);
+        bool n2IsConcat = u.str.is_concat(a_new_nn2);
+        if (!n1IsConcat && n2IsConcat) {
+            TRACE("str", tout << "nn1_new is not a concat" << std::endl;);
+            if (u.str.is_string(a_new_nn1)) {
+                simplify_parent(new_nn2, new_nn1);
+            }
+            return;
+        } else if (n1IsConcat && !n2IsConcat) {
+            TRACE("str", tout << "nn2_new is not a concat" << std::endl;);
+            if (u.str.is_string(a_new_nn2)) {
+                simplify_parent(new_nn1, new_nn2);
+            }
+            return;
+        } else if (!n1IsConcat && !n2IsConcat) {
+            // normally this should never happen, because group_terms_by_eqc() should have pre-simplified
+            // as much as possible. however, we make a defensive check here just in case
+            TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;);
+            return;
+        }
+
+        expr * v1_arg0 = a_new_nn1->get_arg(0);
+        expr * v1_arg1 = a_new_nn1->get_arg(1);
+        expr * v2_arg0 = a_new_nn2->get_arg(0);
+        expr * v2_arg1 = a_new_nn2->get_arg(1);
+
+        if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) {
+            int ii4 = 0;
+            expr* item[3];
+            if (nn1 != new_nn1) {
+                item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1);
+            }
+            if (nn2 != new_nn2) {
+                item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2);
+            }
+            item[ii4++] = ctx.mk_eq_atom(nn1, nn2);
+            expr_ref premise(m.mk_and(ii4, item), m);
+            expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m);
+            assert_implication(premise, conclusion);
+        }
+
+        // start to split both concats
+        check_and_init_cut_var(v1_arg0);
+        check_and_init_cut_var(v1_arg1);
+        check_and_init_cut_var(v2_arg0);
+        check_and_init_cut_var(v2_arg1);
+
+        //*************************************************************
+              // case 1: concat(x, y) = concat(m, n)
+              //*************************************************************
+                    if (is_concat_eq_type1(new_nn1, new_nn2)) {
+                        process_concat_eq_type1(new_nn1, new_nn2);
+                        return;
+                    }
+
+        //*************************************************************
+              // case 2: concat(x, y) = concat(m, "str")
+              //*************************************************************
+                    if (is_concat_eq_type2(new_nn1, new_nn2)) {
+                        process_concat_eq_type2(new_nn1, new_nn2);
+                        return;
+                    }
+
+        //*************************************************************
+              // case 3: concat(x, y) = concat("str", n)
+              //*************************************************************
+                    if (is_concat_eq_type3(new_nn1, new_nn2)) {
+                        process_concat_eq_type3(new_nn1, new_nn2);
+                        return;
+                    }
+
+        //*************************************************************
+              //  case 4: concat("str1", y) = concat("str2", n)
+              //*************************************************************
+                    if (is_concat_eq_type4(new_nn1, new_nn2)) {
+                        process_concat_eq_type4(new_nn1, new_nn2);
+                        return;
+                    }
+
+        //*************************************************************
+              //  case 5: concat(x, "str1") = concat(m, "str2")
+              //*************************************************************
+                    if (is_concat_eq_type5(new_nn1, new_nn2)) {
+                        process_concat_eq_type5(new_nn1, new_nn2);
+                        return;
+                    }
+        //*************************************************************
+              //  case 6: concat("str1", y) = concat(m, "str2")
+              //*************************************************************
+                    if (is_concat_eq_type6(new_nn1, new_nn2)) {
+                        process_concat_eq_type6(new_nn1, new_nn2);
+                        return;
+                    }
+
+    }
+
+    /*
+     * Returns true if attempting to process a concat equality between lhs and rhs
+     * will result in overlapping variables (false otherwise).
+     */
+    bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) {
+        ast_manager & m = get_manager();
+
+        expr_ref new_nn1(simplify_concat(lhs), m);
+        expr_ref new_nn2(simplify_concat(rhs), m);
+        app * a_new_nn1 = to_app(new_nn1);
+        app * a_new_nn2 = to_app(new_nn2);
+
+        bool n1IsConcat = u.str.is_concat(a_new_nn1);
+        bool n2IsConcat = u.str.is_concat(a_new_nn2);
+        if (!n1IsConcat && !n2IsConcat) {
+            // we simplified both sides to non-concat expressions...
+            return false;
+        }
+
+        expr * v1_arg0 = a_new_nn1->get_arg(0);
+        expr * v1_arg1 = a_new_nn1->get_arg(1);
+        expr * v2_arg0 = a_new_nn2->get_arg(0);
+        expr * v2_arg1 = a_new_nn2->get_arg(1);
+
+        TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;);
+
+        check_and_init_cut_var(v1_arg0);
+        check_and_init_cut_var(v1_arg1);
+        check_and_init_cut_var(v2_arg0);
+        check_and_init_cut_var(v2_arg1);
+
+        //*************************************************************
+              // case 1: concat(x, y) = concat(m, n)
+              //*************************************************************
+                    if (is_concat_eq_type1(new_nn1, new_nn2)) {
+                        TRACE("str", tout << "Type 1 check." << std::endl;);
+                        expr * x = to_app(new_nn1)->get_arg(0);
+                        expr * y = to_app(new_nn1)->get_arg(1);
+                        expr * m = to_app(new_nn2)->get_arg(0);
+                        expr * n = to_app(new_nn2)->get_arg(1);
+
+                        if (has_self_cut(m, y)) {
+                            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout););
+                            return true;
+                        } else if (has_self_cut(x, n)) {
+                            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout););
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+
+        //*************************************************************
+              // case 2: concat(x, y) = concat(m, "str")
+              //*************************************************************
+                    if (is_concat_eq_type2(new_nn1, new_nn2)) {
+
+                        expr * y = NULL;
+                        expr * m = NULL;
+                        expr * v1_arg0 = to_app(new_nn1)->get_arg(0);
+                        expr * v1_arg1 = to_app(new_nn1)->get_arg(1);
+                        expr * v2_arg0 = to_app(new_nn2)->get_arg(0);
+                        expr * v2_arg1 = to_app(new_nn2)->get_arg(1);
+
+                        if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) {
+                            m = v1_arg0;
+                            y = v2_arg1;
+                        } else {
+                            m = v2_arg0;
+                            y = v1_arg1;
+                        }
+
+                        if (has_self_cut(m, y)) {
+                            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout););
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+
+        //*************************************************************
+              // case 3: concat(x, y) = concat("str", n)
+              //*************************************************************
+                    if (is_concat_eq_type3(new_nn1, new_nn2)) {
+                        expr * v1_arg0 = to_app(new_nn1)->get_arg(0);
+                        expr * v1_arg1 = to_app(new_nn1)->get_arg(1);
+                        expr * v2_arg0 = to_app(new_nn2)->get_arg(0);
+                        expr * v2_arg1 = to_app(new_nn2)->get_arg(1);
+
+                        expr * x = NULL;
+                        expr * n = NULL;
+
+                        if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) {
+                            n = v1_arg1;
+                            x = v2_arg0;
+                        } else {
+                            n = v2_arg1;
+                            x = v1_arg0;
+                        }
+                        if (has_self_cut(x, n)) {
+                            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout););
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+
+        //*************************************************************
+              //  case 4: concat("str1", y) = concat("str2", n)
+              //*************************************************************
+                    if (is_concat_eq_type4(new_nn1, new_nn2)) {
+                        // This case can never result in an overlap.
+                        return false;
+                    }
+
+        //*************************************************************
+              //  case 5: concat(x, "str1") = concat(m, "str2")
+              //*************************************************************
+                    if (is_concat_eq_type5(new_nn1, new_nn2)) {
+                        // This case can never result in an overlap.
+                        return false;
+                    }
+        //*************************************************************
+              //  case 6: concat("str1", y) = concat(m, "str2")
+              //*************************************************************
+                    if (is_concat_eq_type6(new_nn1, new_nn2)) {
+                        expr * v1_arg0 = to_app(new_nn1)->get_arg(0);
+                        expr * v1_arg1 = to_app(new_nn1)->get_arg(1);
+                        expr * v2_arg0 = to_app(new_nn2)->get_arg(0);
+                        expr * v2_arg1 = to_app(new_nn2)->get_arg(1);
+
+                        expr * y = NULL;
+                        expr * m = NULL;
+
+                        if (u.str.is_string(v1_arg0)) {
+                            y = v1_arg1;
+                            m = v2_arg0;
+                        } else {
+                            y = v2_arg1;
+                            m = v1_arg0;
+                        }
+                        if (has_self_cut(m, y)) {
+                            TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout););
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+
+        TRACE("str", tout << "warning: unrecognized concat case" << std::endl;);
         return false;
     }
-}
 
-void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) {
-    ast_manager & mgr = get_manager();
-    context & ctx = get_context();
+    /*************************************************************
+     * Type 1: concat(x, y) = concat(m, n)
+     *         x, y, m and n all variables
+     *************************************************************/
+    bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) {
+        expr * x = to_app(concatAst1)->get_arg(0);
+        expr * y = to_app(concatAst1)->get_arg(1);
+        expr * m = to_app(concatAst2)->get_arg(0);
+        expr * n = to_app(concatAst2)->get_arg(1);
 
-    bool overlapAssumptionUsed = false;
-
-    TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl
-            << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
-            << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
-    );
-
-    if (!u.str.is_concat(to_app(concatAst1))) {
-        TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
-        return;
-    }
-    if (!u.str.is_concat(to_app(concatAst2))) {
-        TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
-        return;
-    }
-    expr * x = to_app(concatAst1)->get_arg(0);
-    expr * y = to_app(concatAst1)->get_arg(1);
-    expr * m = to_app(concatAst2)->get_arg(0);
-    expr * n = to_app(concatAst2)->get_arg(1);
-
-    rational x_len, y_len, m_len, n_len;
-    bool x_len_exists = get_len_value(x, x_len);
-    bool y_len_exists = get_len_value(y, y_len);
-    bool m_len_exists = get_len_value(m, m_len);
-    bool n_len_exists = get_len_value(n, n_len);
-
-    int splitType = -1;
-    if (x_len_exists && m_len_exists) {
-        TRACE("str", tout << "length values found: x/m" << std::endl;);
-        if (x_len < m_len) {
-            splitType = 0;
-        } else if (x_len == m_len) {
-            splitType = 1;
+        if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) {
+            return true;
         } else {
-            splitType = 2;
+            return false;
         }
     }
 
-    if (splitType == -1 && y_len_exists && n_len_exists) {
-        TRACE("str", tout << "length values found: y/n" << std::endl;);
-        if (y_len > n_len) {
-            splitType = 0;
-        } else if (y_len == n_len) {
-            splitType = 1;
-        } else {
-            splitType = 2;
+    void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) {
+        ast_manager & mgr = get_manager();
+        context & ctx = get_context();
+
+        bool overlapAssumptionUsed = false;
+
+        TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl
+              << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
+              << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
+              );
+
+        if (!u.str.is_concat(to_app(concatAst1))) {
+            TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
+            return;
         }
-    }
+        if (!u.str.is_concat(to_app(concatAst2))) {
+            TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
+            return;
+        }
+        expr * x = to_app(concatAst1)->get_arg(0);
+        expr * y = to_app(concatAst1)->get_arg(1);
+        expr * m = to_app(concatAst2)->get_arg(0);
+        expr * n = to_app(concatAst2)->get_arg(1);
 
-    TRACE("str", tout
-    		<< "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl
-    		<< "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl
-			<< "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl
-			<< "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl
-    		<< "split type " << splitType << std::endl;
-    );
+        rational x_len, y_len, m_len, n_len;
+        bool x_len_exists = get_len_value(x, x_len);
+        bool y_len_exists = get_len_value(y, y_len);
+        bool m_len_exists = get_len_value(m, m_len);
+        bool n_len_exists = get_len_value(n, n_len);
 
-    expr * t1 = NULL;
-    expr * t2 = NULL;
-    expr * xorFlag = NULL;
+        int splitType = -1;
+        if (x_len_exists && m_len_exists) {
+            TRACE("str", tout << "length values found: x/m" << std::endl;);
+            if (x_len < m_len) {
+                splitType = 0;
+            } else if (x_len == m_len) {
+                splitType = 1;
+            } else {
+                splitType = 2;
+            }
+        }
 
-    std::pair<expr*, expr*> key1(concatAst1, concatAst2);
-    std::pair<expr*, expr*> key2(concatAst2, concatAst1);
+        if (splitType == -1 && y_len_exists && n_len_exists) {
+            TRACE("str", tout << "length values found: y/n" << std::endl;);
+            if (y_len > n_len) {
+                splitType = 0;
+            } else if (y_len == n_len) {
+                splitType = 1;
+            } else {
+                splitType = 2;
+            }
+        }
 
-    // check the entries in this map to make sure they're still in scope
-    // before we use them.
+        TRACE("str", tout
+              << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl
+              << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl
+              << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl
+              << "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl
+              << "split type " << splitType << std::endl;
+              );
 
-    std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
-    std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
+        expr * t1 = NULL;
+        expr * t2 = NULL;
+        expr * xorFlag = NULL;
 
-    bool entry1InScope;
-    if (entry1 == varForBreakConcat.end()) {
-        entry1InScope = false;
-    } else {
-        if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
-                || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()
-                /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) {
+        std::pair<expr*, expr*> key1(concatAst1, concatAst2);
+        std::pair<expr*, expr*> key2(concatAst2, concatAst1);
+
+        // check the entries in this map to make sure they're still in scope
+        // before we use them.
+
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
+
+        bool entry1InScope;
+        if (entry1 == varForBreakConcat.end()) {
             entry1InScope = false;
         } else {
-            entry1InScope = true;
+            if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
+                || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()
+                /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) {
+                entry1InScope = false;
+            } else {
+                entry1InScope = true;
+            }
         }
-    }
 
-    bool entry2InScope;
-    if (entry2 == varForBreakConcat.end()) {
-        entry2InScope = false;
-    } else {
-        if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
-                || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()
-                /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) {
+        bool entry2InScope;
+        if (entry2 == varForBreakConcat.end()) {
             entry2InScope = false;
         } else {
-            entry2InScope = true;
-        }
-    }
-
-    TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
-            << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
-
-    if (!entry1InScope && !entry2InScope) {
-        t1 = mk_nonempty_str_var();
-        t2 = mk_nonempty_str_var();
-        xorFlag = mk_internal_xor_var();
-        check_and_init_cut_var(t1);
-        check_and_init_cut_var(t2);
-        varForBreakConcat[key1][0] = t1;
-        varForBreakConcat[key1][1] = t2;
-        varForBreakConcat[key1][2] = xorFlag;
-    } else {
-        // match found
-        if (entry1InScope) {
-            t1 = varForBreakConcat[key1][0];
-            t2 = varForBreakConcat[key1][1];
-            xorFlag = varForBreakConcat[key1][2];
-        } else {
-            t1 = varForBreakConcat[key2][0];
-            t2 = varForBreakConcat[key2][1];
-            xorFlag = varForBreakConcat[key2][2];
-        }
-        refresh_theory_var(t1);
-        add_nonempty_constraint(t1);
-        refresh_theory_var(t2);
-        add_nonempty_constraint(t2);
-    }
-
-    // For split types 0 through 2, we can get away with providing
-    // fewer split options since more length information is available.
-    if (splitType == 0) {
-        //--------------------------------------
-        // Type 0: M cuts Y.
-        //   len(x) < len(m) || len(y) > len(n)
-        //--------------------------------------
-        expr_ref_vector ax_l_items(mgr);
-        expr_ref_vector ax_r_items(mgr);
-
-        ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
-
-        expr_ref x_t1(mk_concat(x, t1), mgr);
-        expr_ref t1_n(mk_concat(t1, n), mgr);
-
-        ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1));
-        ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n));
-
-        if (m_len_exists && x_len_exists) {
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
-            rational m_sub_x = m_len - x_len;
-            ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x)));
-        } else {
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
-            rational y_sub_n = y_len - n_len;
-            ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n)));
-        }
-
-        expr_ref ax_l(mk_and(ax_l_items), mgr);
-        expr_ref ax_r(mk_and(ax_r_items), mgr);
-
-        if (!has_self_cut(m, y)) {
-            // Cut Info
-            add_cut_info_merge(t1, sLevel, m);
-            add_cut_info_merge(t1, sLevel, y);
-
-            if (m_params.m_StrongArrangements) {
-                expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
-                assert_axiom(ax_strong);
+            if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
+                || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()
+                /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) {
+                entry2InScope = false;
             } else {
-                assert_implication(ax_l, ax_r);
-            }
-        } else {
-            loopDetected = true;
-            if (m_params.m_FiniteOverlapModels) {
-                expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
-                assert_implication(ax_l, tester);
-                add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
-            } else {
-                TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
-                TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
-
-                if (!overlapAssumptionUsed) {
-                	overlapAssumptionUsed = true;
-                	assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
-                }
+                entry2InScope = true;
             }
         }
-    } else if (splitType == 1) {
-        // Type 1:
-        //   len(x) = len(m) || len(y) = len(n)
-        expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
-        expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr);
-        expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr);
-        expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr);
-        assert_implication(ax_l, ax_r);
-    } else if (splitType == 2) {
-        // Type 2: X cuts N.
-        //   len(x) > len(m) || len(y) < len(n)
-        expr_ref m_t2(mk_concat(m, t2), mgr);
-        expr_ref t2_y(mk_concat(t2, y), mgr);
 
-        expr_ref_vector ax_l_items(mgr);
-        ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
+        TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
+              << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
 
-        expr_ref_vector ax_r_items(mgr);
-        ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2));
-        ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n));
-
-        if (m_len_exists && x_len_exists) {
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
-            rational x_sub_m = x_len - m_len;
-            ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m)));
+        if (!entry1InScope && !entry2InScope) {
+            t1 = mk_nonempty_str_var();
+            t2 = mk_nonempty_str_var();
+            xorFlag = mk_internal_xor_var();
+            check_and_init_cut_var(t1);
+            check_and_init_cut_var(t2);
+            varForBreakConcat[key1][0] = t1;
+            varForBreakConcat[key1][1] = t2;
+            varForBreakConcat[key1][2] = xorFlag;
         } else {
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
-            ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
-            rational n_sub_y = n_len - y_len;
-            ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y)));
+            // match found
+            if (entry1InScope) {
+                t1 = varForBreakConcat[key1][0];
+                t2 = varForBreakConcat[key1][1];
+                xorFlag = varForBreakConcat[key1][2];
+            } else {
+                t1 = varForBreakConcat[key2][0];
+                t2 = varForBreakConcat[key2][1];
+                xorFlag = varForBreakConcat[key2][2];
+            }
+            refresh_theory_var(t1);
+            add_nonempty_constraint(t1);
+            refresh_theory_var(t2);
+            add_nonempty_constraint(t2);
         }
 
-        expr_ref ax_l(mk_and(ax_l_items), mgr);
-        expr_ref ax_r(mk_and(ax_r_items), mgr);
+        // For split types 0 through 2, we can get away with providing
+        // fewer split options since more length information is available.
+        if (splitType == 0) {
+            //--------------------------------------
+            // Type 0: M cuts Y.
+            //   len(x) < len(m) || len(y) > len(n)
+            //--------------------------------------
+            expr_ref_vector ax_l_items(mgr);
+            expr_ref_vector ax_r_items(mgr);
 
-        if (!has_self_cut(x, n)) {
-            // Cut Info
-            add_cut_info_merge(t2, sLevel, x);
-            add_cut_info_merge(t2, sLevel, n);
+            ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
 
-            if (m_params.m_StrongArrangements) {
-                expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
-                assert_axiom(ax_strong);
-            } else {
-                assert_implication(ax_l, ax_r);
-            }
-        } else {
-            loopDetected = true;
-            if (m_params.m_FiniteOverlapModels) {
-                expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
-                assert_implication(ax_l, tester);
-                add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
-            } else {
-                TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
-                TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
-
-                if (!overlapAssumptionUsed) {
-                	overlapAssumptionUsed = true;
-                	assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
-                }
-            }
-        }
-    } else if (splitType == -1) {
-        // Here we don't really have a choice. We have no length information at all...
-
-        // This vector will eventually contain one term for each possible arrangement we explore.
-        expr_ref_vector arrangement_disjunction(mgr);
-
-        // break option 1: m cuts y
-        // len(x) < len(m) || len(y) > len(n)
-        if (!avoidLoopCut || !has_self_cut(m, y)) {
-            expr_ref_vector and_item(mgr);
-            // break down option 1-1
             expr_ref x_t1(mk_concat(x, t1), mgr);
             expr_ref t1_n(mk_concat(t1, n), mgr);
 
-            and_item.push_back(ctx.mk_eq_atom(m, x_t1));
-            and_item.push_back(ctx.mk_eq_atom(y, t1_n));
+            ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1));
+            ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n));
 
-            expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr);
-            and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1));
-            // These were crashing the solver because the integer theory
-            // expects a constant on the right-hand side.
-            // The things we want to assert here are len(m) > len(x) and len(y) > len(n).
-            // We rewrite A > B as A-B > 0 and then as not(A-B <= 0),
-            // and then, *because we aren't allowed to use subtraction*,
-            // as not(A + -1*B <= 0)
-            and_item.push_back(
-                    mgr.mk_not(m_autil.mk_le(
-                    		m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))),
-							mk_int(0))) );
-            and_item.push_back(
-                    mgr.mk_not(m_autil.mk_le(
-                    		m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))),
-							mk_int(0))) );
-
-            expr_ref option1(mk_and(and_item), mgr);
-            arrangement_disjunction.push_back(option1);
-            add_theory_aware_branching_info(option1, 0.1, l_true);
-
-            add_cut_info_merge(t1, ctx.get_scope_level(), m);
-            add_cut_info_merge(t1, ctx.get_scope_level(), y);
-        } else {
-            loopDetected = true;
-            if (m_params.m_FiniteOverlapModels) {
-                expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
-                arrangement_disjunction.push_back(tester);
-                add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+            if (m_len_exists && x_len_exists) {
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
+                rational m_sub_x = m_len - x_len;
+                ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x)));
             } else {
-                TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
-                TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
-
-                if (!overlapAssumptionUsed) {
-                	overlapAssumptionUsed = true;
-                	arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
-                }
-            }
-        }
-
-        // break option 2:
-        // x = m . t2
-        // n = t2 . y
-        if (!avoidLoopCut || !has_self_cut(x, n)) {
-            expr_ref_vector and_item(mgr);
-            // break down option 1-2
-            expr_ref m_t2(mk_concat(m, t2), mgr);
-            expr_ref t2_y(mk_concat(t2, y), mgr);
-
-            and_item.push_back(ctx.mk_eq_atom(x, m_t2));
-            and_item.push_back(ctx.mk_eq_atom(n, t2_y));
-
-
-            expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr);
-
-            and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2));
-            // want len(x) > len(m) and len(n) > len(y)
-            and_item.push_back(
-            		mgr.mk_not(m_autil.mk_le(
-            				m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))),
-							mk_int(0))) );
-            and_item.push_back(
-            		mgr.mk_not(m_autil.mk_le(
-            				m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))),
-							mk_int(0))) );
-
-            expr_ref option2(mk_and(and_item), mgr);
-            arrangement_disjunction.push_back(option2);
-            add_theory_aware_branching_info(option2, 0.1, l_true);
-
-            add_cut_info_merge(t2, ctx.get_scope_level(), x);
-            add_cut_info_merge(t2, ctx.get_scope_level(), n);
-        } else {
-            loopDetected = true;
-            if (m_params.m_FiniteOverlapModels) {
-                expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
-                arrangement_disjunction.push_back(tester);
-                add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
-            } else {
-                TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
-                TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);});
-
-                if (!overlapAssumptionUsed) {
-                	overlapAssumptionUsed = true;
-                	arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
-                }
-            }
-        }
-
-        // option 3:
-        // x = m, y = n
-        if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) {
-            expr_ref_vector and_item(mgr);
-
-            and_item.push_back(ctx.mk_eq_atom(x, m));
-            and_item.push_back(ctx.mk_eq_atom(y, n));
-            and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)));
-            and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n)));
-
-            expr_ref option3(mk_and(and_item), mgr);
-            arrangement_disjunction.push_back(option3);
-            // prioritize this case, it is easier
-            add_theory_aware_branching_info(option3, 0.5, l_true);
-        }
-
-        if (!arrangement_disjunction.empty()) {
-            expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
-            expr_ref conclusion(mk_or(arrangement_disjunction), mgr);
-            if (m_params.m_StrongArrangements) {
-                expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr);
-                assert_axiom(ax_strong);
-            } else {
-                assert_implication(premise, conclusion);
-            }
-            // assert mutual exclusion between each branch of the arrangement
-            generate_mutual_exclusion(arrangement_disjunction);
-        } else {
-            TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;);
-        }
-    } // (splitType == -1)
-}
-
-/*************************************************************
- * Type 2: concat(x, y) = concat(m, "str")
- *************************************************************/
-bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) {
-	expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-	expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-	expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-	expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
-
-	if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)
-			&& (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) {
-		return true;
-	} else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)
-			&& (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) {
-		return true;
-	} else {
-		return false;
-	}
-}
-
-void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) {
-	ast_manager & mgr = get_manager();
-	context & ctx = get_context();
-
-	bool overlapAssumptionUsed = false;
-
-	TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl
-			<< "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
-			<< "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
-	);
-
-	if (!u.str.is_concat(to_app(concatAst1))) {
-		TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
-		return;
-	}
-	if (!u.str.is_concat(to_app(concatAst2))) {
-		TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
-		return;
-	}
-
-	expr * x = NULL;
-	expr * y = NULL;
-	expr * strAst = NULL;
-	expr * m = NULL;
-
-	expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-	expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-	expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-	expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
-
-	if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) {
-		m = v1_arg0;
-		strAst = v1_arg1;
-		x = v2_arg0;
-		y = v2_arg1;
-	} else {
-		m = v2_arg0;
-		strAst = v2_arg1;
-		x = v1_arg0;
-		y = v1_arg1;
-	}
-
-	zstring strValue;
-	u.str.is_string(strAst, strValue);
-
-	rational x_len, y_len, m_len, str_len;
-	bool x_len_exists = get_len_value(x, x_len);
-	bool y_len_exists = get_len_value(y, y_len);
-	bool m_len_exists = get_len_value(m, m_len);
-	bool str_len_exists = true;
-	str_len = rational(strValue.length());
-
-	// setup
-
-	expr * xorFlag = NULL;
-	expr * temp1 = NULL;
-	std::pair<expr*, expr*> key1(concatAst1, concatAst2);
-	std::pair<expr*, expr*> key2(concatAst2, concatAst1);
-
-	// check the entries in this map to make sure they're still in scope
-	// before we use them.
-
-	std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
-	std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
-
-    // prevent checking scope for the XOR term, as it's always in the same scope as the split var
-
-	bool entry1InScope;
-	if (entry1 == varForBreakConcat.end()) {
-	    entry1InScope = false;
-	} else {
-	    if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
-	            /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/
-	            ) {
-	        entry1InScope = false;
-	    } else {
-	        entry1InScope = true;
-	    }
-	}
-
-	bool entry2InScope;
-	if (entry2 == varForBreakConcat.end()) {
-	    entry2InScope = false;
-	} else {
-	    if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
-	            /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/
-	            ) {
-	        entry2InScope = false;
-	    } else {
-	        entry2InScope = true;
-	    }
-	}
-
-	TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
-	        << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
-
-
-	if (!entry1InScope && !entry2InScope) {
-		temp1 = mk_nonempty_str_var();
-		xorFlag = mk_internal_xor_var();
-		varForBreakConcat[key1][0] = temp1;
-		varForBreakConcat[key1][1] = xorFlag;
-	} else {
-		if (entry1InScope) {
-			temp1 = varForBreakConcat[key1][0];
-			xorFlag = varForBreakConcat[key1][1];
-		} else if (entry2InScope) {
-			temp1 = varForBreakConcat[key2][0];
-			xorFlag = varForBreakConcat[key2][1];
-		}
-		refresh_theory_var(temp1);
-		add_nonempty_constraint(temp1);
-	}
-
-	int splitType = -1;
-	if (x_len_exists && m_len_exists) {
-		if (x_len < m_len)
-			splitType = 0;
-		else if (x_len == m_len)
-			splitType = 1;
-		else
-			splitType = 2;
-	}
-	if (splitType == -1 && y_len_exists && str_len_exists) {
-		if (y_len > str_len)
-			splitType = 0;
-		else if (y_len == str_len)
-			splitType = 1;
-		else
-			splitType = 2;
-	}
-
-	TRACE("str", tout << "Split type " << splitType << std::endl;);
-
-	// Provide fewer split options when length information is available.
-
-	if (splitType == 0) {
-	    // M cuts Y
-	    //   |  x  |      y     |
-        //   |    m   |   str   |
-	    expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr);
-	    if (can_two_nodes_eq(y, temp1_strAst)) {
-            expr_ref_vector l_items(mgr);
-            l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
-
-            expr_ref_vector r_items(mgr);
-            expr_ref x_temp1(mk_concat(x, temp1), mgr);
-            r_items.push_back(ctx.mk_eq_atom(m, x_temp1));
-            r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst));
-
-            if (x_len_exists && m_len_exists) {
-                l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
-                l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
-                rational m_sub_x = (m_len - x_len);
-                r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x)));
-            } else {
-                l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
-                l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len)));
-                rational y_sub_str = (y_len - str_len);
-                r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str)));
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
+                rational y_sub_n = y_len - n_len;
+                ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n)));
             }
 
-            expr_ref ax_l(mk_and(l_items), mgr);
-            expr_ref ax_r(mk_and(r_items), mgr);
+            expr_ref ax_l(mk_and(ax_l_items), mgr);
+            expr_ref ax_r(mk_and(ax_r_items), mgr);
 
-	        if (!avoidLoopCut || !(has_self_cut(m, y))) {
-	            // break down option 2-1
-	            add_cut_info_merge(temp1, sLevel, y);
-	            add_cut_info_merge(temp1, sLevel, m);
-
-	            if (m_params.m_StrongArrangements) {
-	                expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
-	                assert_axiom(ax_strong);
-	            } else {
-	                assert_implication(ax_l, ax_r);
-	            }
-	        } else {
-	            loopDetected = true;
-
-	            if (m_params.m_FiniteOverlapModels) {
-	                expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
-                    assert_implication(ax_l, tester);
-                    add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
-	            } else {
-	                TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;);
-	                TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
-
-	                if (!overlapAssumptionUsed) {
-	                	overlapAssumptionUsed = true;
-	                	assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
-	                }
-	            }
-	        }
-	    }
-	} else if (splitType == 1) {
-	    //   |   x   |    y    |
-	    //   |   m   |   str   |
-	    expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
-	    expr_ref ax_l2(mgr.mk_or(
-	            ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)),
-	            ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr);
-	    expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr);
-	    expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr);
-	    assert_implication(ax_l, ax_r);
-	} else if (splitType == 2) {
-	    // m cut y,
-	    //    |   x   |  y  |
-	    //    | m |   str   |
-	    rational lenDelta;
-	    expr_ref_vector l_items(mgr);
-	    l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
-	    if (x_len_exists && m_len_exists) {
-	        l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
-	        l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
-	        lenDelta = x_len - m_len;
-	    } else {
-	        l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
-	        lenDelta = str_len - y_len;
-	    }
-	    TRACE("str",
-	            tout
-	                << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl
-	                << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl
-	                << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl
-	                << "xLen = " << x_len.to_string() << std::endl
-	                << "yLen = " << y_len.to_string() << std::endl
-	                << "mLen = " << m_len.to_string() << std::endl
-	                << "strLen = " << str_len.to_string() << std::endl
-	                << "lenDelta = " << lenDelta.to_string() << std::endl
-	                << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n"
-	                 ;
-	            );
-
-	    zstring part1Str = strValue.extract(0, lenDelta.get_unsigned());
-	    zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned());
-
-	    expr_ref prefixStr(mk_string(part1Str), mgr);
-	    expr_ref x_concat(mk_concat(m, prefixStr), mgr);
-	    expr_ref cropStr(mk_string(part2Str), mgr);
-
-	    if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) {
-	        expr_ref_vector r_items(mgr);
-	        r_items.push_back(ctx.mk_eq_atom(x, x_concat));
-	        r_items.push_back(ctx.mk_eq_atom(y, cropStr));
-	        expr_ref ax_l(mk_and(l_items), mgr);
-	        expr_ref ax_r(mk_and(r_items), mgr);
-
-	        if (m_params.m_StrongArrangements) {
-	            expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
-	            assert_axiom(ax_strong);
-	        } else {
-	            assert_implication(ax_l, ax_r);
-	        }
-	    } else {
-	        // negate! It's impossible to split str with these lengths
-	        TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;);
-	        expr_ref ax_l(mk_and(l_items), mgr);
-	        assert_axiom(mgr.mk_not(ax_l));
-	    }
-	} else {
-		// Split type -1: no idea about the length...
-		expr_ref_vector arrangement_disjunction(mgr);
-
-		expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr);
-
-		// m cuts y
-		if (can_two_nodes_eq(y, temp1_strAst)) {
-			if (!avoidLoopCut || !has_self_cut(m, y)) {
-				// break down option 2-1
-			    expr_ref_vector and_item(mgr);
-
-				expr_ref x_temp1(mk_concat(x, temp1), mgr);
-				and_item.push_back(ctx.mk_eq_atom(m, x_temp1));
-				and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst));
-
-				and_item.push_back(ctx.mk_eq_atom(mk_strlen(m),
-						m_autil.mk_add(mk_strlen(x), mk_strlen(temp1))));
-
-				expr_ref option1(mk_and(and_item), mgr);
-				arrangement_disjunction.push_back(option1);
-				add_theory_aware_branching_info(option1, 0.1, l_true);
-				add_cut_info_merge(temp1, ctx.get_scope_level(), y);
-				add_cut_info_merge(temp1, ctx.get_scope_level(), m);
-			} else {
-				loopDetected = true;
-				if (m_params.m_FiniteOverlapModels) {
-				    expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
-                    arrangement_disjunction.push_back(tester);
-                    add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
-				} else {
-				    TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
-				    TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
-
-				    if (!overlapAssumptionUsed) {
-				    	overlapAssumptionUsed = true;
-				    	arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
-				    }
-				}
-			}
-		}
-
-		for (unsigned int i = 0; i <= strValue.length(); ++i) {
-			zstring part1Str = strValue.extract(0, i);
-			zstring part2Str = strValue.extract(i, strValue.length() - i);
-			expr_ref prefixStr(mk_string(part1Str), mgr);
-			expr_ref x_concat(mk_concat(m, prefixStr), mgr);
-			expr_ref cropStr(mk_string(part2Str), mgr);
-			if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) {
-				// break down option 2-2
-			    expr_ref_vector and_item(mgr);
-				and_item.push_back(ctx.mk_eq_atom(x, x_concat));
-				and_item.push_back(ctx.mk_eq_atom(y, cropStr));
-				and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length())));
-				expr_ref option2(mk_and(and_item), mgr);
-				arrangement_disjunction.push_back(option2);
-				double priority;
-				// prioritize the option where y is equal to the original string
-				if (i == 0) {
-				    priority = 0.5;
-				} else {
-				    priority = 0.1;
-				}
-				add_theory_aware_branching_info(option2, priority, l_true);
-			}
-		}
-
-		if (!arrangement_disjunction.empty()) {
-			expr_ref implyR(mk_or(arrangement_disjunction), mgr);
-
-			if (m_params.m_StrongArrangements) {
-			    expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
-			    expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr);
-			    assert_axiom(ax_strong);
-			} else {
-			    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-			}
-			generate_mutual_exclusion(arrangement_disjunction);
-		} else {
-			TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;);
-		}
-	} // (splitType == -1)
-}
-
-/*************************************************************
- * Type 3: concat(x, y) = concat("str", n)
- *************************************************************/
-bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) {
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
-
-    if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1))
-            && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) {
-        return true;
-    } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))
-            && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) {
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) {
-    ast_manager & mgr = get_manager();
-    context & ctx = get_context();
-
-    bool overlapAssumptionUsed = false;
-
-    TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl
-            << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
-            << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
-    );
-
-    if (!u.str.is_concat(to_app(concatAst1))) {
-        TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
-        return;
-    }
-    if (!u.str.is_concat(to_app(concatAst2))) {
-        TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
-        return;
-    }
-
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
-
-    expr * x = NULL;
-    expr * y = NULL;
-    expr * strAst = NULL;
-    expr * n = NULL;
-
-    if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) {
-        strAst = v1_arg0;
-        n = v1_arg1;
-        x = v2_arg0;
-        y = v2_arg1;
-    } else {
-        strAst = v2_arg0;
-        n = v2_arg1;
-        x = v1_arg0;
-        y = v1_arg1;
-    }
-
-    zstring strValue;
-    u.str.is_string(strAst, strValue);
-
-    rational x_len, y_len, str_len, n_len;
-    bool x_len_exists = get_len_value(x, x_len);
-    bool y_len_exists = get_len_value(y, y_len);
-    str_len = rational((unsigned)(strValue.length()));
-    bool n_len_exists = get_len_value(n, n_len);
-
-    expr_ref xorFlag(mgr);
-    expr_ref temp1(mgr);
-    std::pair<expr*, expr*> key1(concatAst1, concatAst2);
-    std::pair<expr*, expr*> key2(concatAst2, concatAst1);
-
-    // check the entries in this map to make sure they're still in scope
-    // before we use them.
-
-    std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
-    std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
-
-    bool entry1InScope;
-    if (entry1 == varForBreakConcat.end()) {
-        entry1InScope = false;
-    } else {
-        if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
-                /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) {
-            entry1InScope = false;
-        } else {
-            entry1InScope = true;
-        }
-    }
-
-    bool entry2InScope;
-    if (entry2 == varForBreakConcat.end()) {
-        entry2InScope = false;
-    } else {
-        if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
-                /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) {
-            entry2InScope = false;
-        } else {
-            entry2InScope = true;
-        }
-    }
-
-    TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
-            << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
-
-
-    if (!entry1InScope && !entry2InScope) {
-        temp1 = mk_nonempty_str_var();
-        xorFlag = mk_internal_xor_var();
-
-        varForBreakConcat[key1][0] = temp1;
-        varForBreakConcat[key1][1] = xorFlag;
-    } else {
-        if (entry1InScope) {
-            temp1 = varForBreakConcat[key1][0];
-            xorFlag = varForBreakConcat[key1][1];
-        } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) {
-            temp1 = varForBreakConcat[key2][0];
-            xorFlag = varForBreakConcat[key2][1];
-        }
-        refresh_theory_var(temp1);
-        add_nonempty_constraint(temp1);
-    }
-
-
-
-    int splitType = -1;
-    if (x_len_exists) {
-        if (x_len < str_len)
-            splitType = 0;
-        else if (x_len == str_len)
-            splitType = 1;
-        else
-            splitType = 2;
-    }
-    if (splitType == -1 && y_len_exists && n_len_exists) {
-        if (y_len > n_len)
-            splitType = 0;
-        else if (y_len == n_len)
-            splitType = 1;
-        else
-            splitType = 2;
-    }
-
-    TRACE("str", tout << "Split type " << splitType << std::endl;);
-
-    // Provide fewer split options when length information is available.
-    if (splitType == 0) {
-        //   |   x   |    y     |
-        //   |  str     |   n   |
-        expr_ref_vector litems(mgr);
-        litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
-        rational prefixLen;
-        if (!x_len_exists) {
-            prefixLen = str_len - (y_len - n_len);
-            litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
-            litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
-        } else {
-            prefixLen = x_len;
-            litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
-        }
-        zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned());
-        rational str_sub_prefix = str_len - prefixLen;
-        zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned());
-        expr_ref prefixAst(mk_string(prefixStr), mgr);
-        expr_ref suffixAst(mk_string(suffixStr), mgr);
-        expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr);
-
-        expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr);
-        if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) {
-            expr_ref_vector r_items(mgr);
-            r_items.push_back(ctx.mk_eq_atom(x, prefixAst));
-            r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat));
-
-            if (m_params.m_StrongArrangements) {
-                expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr);
-                assert_axiom(ax_strong);
-            } else {
-                assert_implication(ax_l, mk_and(r_items));
-            }
-        } else {
-            // negate! It's impossible to split str with these lengths
-            TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;);
-            assert_axiom(mgr.mk_not(ax_l));
-        }
-    }
-    else if (splitType == 1) {
-        expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
-        expr_ref ax_l2(mgr.mk_or(
-                ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)),
-                ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr);
-        expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr);
-        expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr);
-
-        if (m_params.m_StrongArrangements) {
-            expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
-            assert_axiom(ax_strong);
-        } else {
-            assert_implication(ax_l, ax_r);
-        }
-    }
-    else if (splitType == 2) {
-        //   |   x        |    y     |
-        //   |  str   |       n      |
-        expr_ref_vector litems(mgr);
-        litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
-        rational tmpLen;
-        if (!x_len_exists) {
-            tmpLen = n_len - y_len;
-            litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
-            litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
-        } else {
-            tmpLen = x_len - str_len;
-            litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
-        }
-        expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr);
-
-        expr_ref str_temp1(mk_concat(strAst, temp1), mgr);
-        expr_ref temp1_y(mk_concat(temp1, y), mgr);
-
-        if (can_two_nodes_eq(x, str_temp1)) {
-            if (!avoidLoopCut || !(has_self_cut(x, n))) {
-                expr_ref_vector r_items(mgr);
-                r_items.push_back(ctx.mk_eq_atom(x, str_temp1));
-                r_items.push_back(ctx.mk_eq_atom(n, temp1_y));
-                r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen)));
-                expr_ref ax_r(mk_and(r_items), mgr);
-
-                //Cut Info
-                add_cut_info_merge(temp1, sLevel, x);
-                add_cut_info_merge(temp1, sLevel, n);
+            if (!has_self_cut(m, y)) {
+                // Cut Info
+                add_cut_info_merge(t1, sLevel, m);
+                add_cut_info_merge(t1, sLevel, y);
 
                 if (m_params.m_StrongArrangements) {
                     expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
@@ -3937,82 +3112,117 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) {
                     add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
                 } else {
                     TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
-                    TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);});
+                    TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
 
                     if (!overlapAssumptionUsed) {
-                    	overlapAssumptionUsed = true;
-                    	assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
+                        overlapAssumptionUsed = true;
+                        assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
                     }
                 }
             }
-        }
-        //    else {
-        //      // negate! It's impossible to split str with these lengths
-        //      __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__);
-        //      addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__);
-        //    }
-    }
-    else {
-        // Split type -1. We know nothing about the length...
+        } else if (splitType == 1) {
+            // Type 1:
+            //   len(x) = len(m) || len(y) = len(n)
+            expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
+            expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr);
+            expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr);
+            expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr);
+            assert_implication(ax_l, ax_r);
+        } else if (splitType == 2) {
+            // Type 2: X cuts N.
+            //   len(x) > len(m) || len(y) < len(n)
+            expr_ref m_t2(mk_concat(m, t2), mgr);
+            expr_ref t2_y(mk_concat(t2, y), mgr);
 
-        expr_ref_vector arrangement_disjunction(mgr);
+            expr_ref_vector ax_l_items(mgr);
+            ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
 
-        int pos = 1;
-        for (unsigned int i = 0; i <= strValue.length(); i++) {
-            zstring part1Str = strValue.extract(0, i);
-            zstring part2Str = strValue.extract(i, strValue.length() - i);
-            expr_ref cropStr(mk_string(part1Str), mgr);
-            expr_ref suffixStr(mk_string(part2Str), mgr);
-            expr_ref y_concat(mk_concat(suffixStr, n), mgr);
+            expr_ref_vector ax_r_items(mgr);
+            ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2));
+            ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n));
 
-            if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) {
+            if (m_len_exists && x_len_exists) {
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
+                rational x_sub_m = x_len - m_len;
+                ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m)));
+            } else {
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
+                ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
+                rational n_sub_y = n_len - y_len;
+                ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y)));
+            }
+
+            expr_ref ax_l(mk_and(ax_l_items), mgr);
+            expr_ref ax_r(mk_and(ax_r_items), mgr);
+
+            if (!has_self_cut(x, n)) {
+                // Cut Info
+                add_cut_info_merge(t2, sLevel, x);
+                add_cut_info_merge(t2, sLevel, n);
+
+                if (m_params.m_StrongArrangements) {
+                    expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
+                    assert_axiom(ax_strong);
+                } else {
+                    assert_implication(ax_l, ax_r);
+                }
+            } else {
+                loopDetected = true;
+                if (m_params.m_FiniteOverlapModels) {
+                    expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
+                    assert_implication(ax_l, tester);
+                    add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+                } else {
+                    TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
+                    TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
+
+                    if (!overlapAssumptionUsed) {
+                        overlapAssumptionUsed = true;
+                        assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
+                    }
+                }
+            }
+        } else if (splitType == -1) {
+            // Here we don't really have a choice. We have no length information at all...
+
+            // This vector will eventually contain one term for each possible arrangement we explore.
+            expr_ref_vector arrangement_disjunction(mgr);
+
+            // break option 1: m cuts y
+            // len(x) < len(m) || len(y) > len(n)
+            if (!avoidLoopCut || !has_self_cut(m, y)) {
                 expr_ref_vector and_item(mgr);
-                // break down option 3-1
-                expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr);
+                // break down option 1-1
+                expr_ref x_t1(mk_concat(x, t1), mgr);
+                expr_ref t1_n(mk_concat(t1, n), mgr);
 
-                and_item.push_back(x_eq_str); ++pos;
-                and_item.push_back(ctx.mk_eq_atom(y, y_concat));
-                and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos;
+                and_item.push_back(ctx.mk_eq_atom(m, x_t1));
+                and_item.push_back(ctx.mk_eq_atom(y, t1_n));
 
-                //        and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat)));
-                // adding length constraint for _ = constStr seems slowing things down.
+                expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr);
+                and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1));
+                // These were crashing the solver because the integer theory
+                // expects a constant on the right-hand side.
+                // The things we want to assert here are len(m) > len(x) and len(y) > len(n).
+                // We rewrite A > B as A-B > 0 and then as not(A-B <= 0),
+                // and then, *because we aren't allowed to use subtraction*,
+                // as not(A + -1*B <= 0)
+                and_item.push_back(
+                    mgr.mk_not(m_autil.mk_le(
+                                   m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))),
+                                   mk_int(0))) );
+                and_item.push_back(
+                    mgr.mk_not(m_autil.mk_le(
+                                   m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))),
+                                   mk_int(0))) );
 
                 expr_ref option1(mk_and(and_item), mgr);
                 arrangement_disjunction.push_back(option1);
-                double priority;
-                if (i == strValue.length()) {
-                    priority = 0.5;
-                } else {
-                    priority = 0.1;
-                }
-                add_theory_aware_branching_info(option1, priority, l_true);
-            }
-        }
+                add_theory_aware_branching_info(option1, 0.1, l_true);
 
-        expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr);
-
-
-        //--------------------------------------------------------
-        // x cut n
-        //--------------------------------------------------------
-        if (can_two_nodes_eq(x, strAst_temp1)) {
-            if (!avoidLoopCut || !(has_self_cut(x, n))) {
-                // break down option 3-2
-                expr_ref_vector and_item(mgr);
-
-                expr_ref temp1_y(mk_concat(temp1, y), mgr);
-                and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos;
-                and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos;
-
-                and_item.push_back(ctx.mk_eq_atom(mk_strlen(x),
-                        m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos;
-
-                expr_ref option2(mk_and(and_item), mgr);
-                arrangement_disjunction.push_back(option2);
-                add_theory_aware_branching_info(option2, 0.1, l_true);
-
-                add_cut_info_merge(temp1, sLevel, x);
-                add_cut_info_merge(temp1, sLevel, n);
+                add_cut_info_merge(t1, ctx.get_scope_level(), m);
+                add_cut_info_merge(t1, ctx.get_scope_level(), y);
             } else {
                 loopDetected = true;
                 if (m_params.m_FiniteOverlapModels) {
@@ -4020,6259 +3230,3742 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) {
                     arrangement_disjunction.push_back(tester);
                     add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
                 } else {
-                    TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;);
+                    TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
+                    TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
+
+                    if (!overlapAssumptionUsed) {
+                        overlapAssumptionUsed = true;
+                        arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
+                    }
+                }
+            }
+
+            // break option 2:
+            // x = m . t2
+            // n = t2 . y
+            if (!avoidLoopCut || !has_self_cut(x, n)) {
+                expr_ref_vector and_item(mgr);
+                // break down option 1-2
+                expr_ref m_t2(mk_concat(m, t2), mgr);
+                expr_ref t2_y(mk_concat(t2, y), mgr);
+
+                and_item.push_back(ctx.mk_eq_atom(x, m_t2));
+                and_item.push_back(ctx.mk_eq_atom(n, t2_y));
+
+
+                expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr);
+
+                and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2));
+                // want len(x) > len(m) and len(n) > len(y)
+                and_item.push_back(
+                    mgr.mk_not(m_autil.mk_le(
+                                   m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))),
+                                   mk_int(0))) );
+                and_item.push_back(
+                    mgr.mk_not(m_autil.mk_le(
+                                   m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))),
+                                   mk_int(0))) );
+
+                expr_ref option2(mk_and(and_item), mgr);
+                arrangement_disjunction.push_back(option2);
+                add_theory_aware_branching_info(option2, 0.1, l_true);
+
+                add_cut_info_merge(t2, ctx.get_scope_level(), x);
+                add_cut_info_merge(t2, ctx.get_scope_level(), n);
+            } else {
+                loopDetected = true;
+                if (m_params.m_FiniteOverlapModels) {
+                    expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
+                    arrangement_disjunction.push_back(tester);
+                    add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+                } else {
+                    TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
                     TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);});
 
                     if (!overlapAssumptionUsed) {
-                    	overlapAssumptionUsed = true;
-                    	arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
+                        overlapAssumptionUsed = true;
+                        arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
                     }
                 }
             }
-        }
 
+            // option 3:
+            // x = m, y = n
+            if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) {
+                expr_ref_vector and_item(mgr);
 
-        if (!arrangement_disjunction.empty()) {
-            expr_ref implyR(mk_or(arrangement_disjunction), mgr);
+                and_item.push_back(ctx.mk_eq_atom(x, m));
+                and_item.push_back(ctx.mk_eq_atom(y, n));
+                and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)));
+                and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n)));
 
-            if (m_params.m_StrongArrangements) {
-                expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
-                expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr);
-                assert_axiom(ax_strong);
+                expr_ref option3(mk_and(and_item), mgr);
+                arrangement_disjunction.push_back(option3);
+                // prioritize this case, it is easier
+                add_theory_aware_branching_info(option3, 0.5, l_true);
+            }
+
+            if (!arrangement_disjunction.empty()) {
+                expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
+                expr_ref conclusion(mk_or(arrangement_disjunction), mgr);
+                if (m_params.m_StrongArrangements) {
+                    expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr);
+                    assert_axiom(ax_strong);
+                } else {
+                    assert_implication(premise, conclusion);
+                }
+                // assert mutual exclusion between each branch of the arrangement
+                generate_mutual_exclusion(arrangement_disjunction);
             } else {
-                assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;);
             }
-            generate_mutual_exclusion(arrangement_disjunction);
+        } // (splitType == -1)
+    }
+
+    /*************************************************************
+     * Type 2: concat(x, y) = concat(m, "str")
+     *************************************************************/
+    bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) {
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)
+            && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) {
+            return true;
+        } else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)
+                   && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) {
+            return true;
         } else {
-            TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;);
+            return false;
         }
     }
 
-}
+    void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) {
+        ast_manager & mgr = get_manager();
+        context & ctx = get_context();
 
-/*************************************************************
- * Type 4: concat("str1", y) = concat("str2", n)
- *************************************************************/
-bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) {
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+        bool overlapAssumptionUsed = false;
 
-    if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1))
-            && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) {
-      return true;
-    } else {
-      return false;
-    }
-}
+        TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl
+              << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
+              << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
+              );
 
-void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) {
-    ast_manager & mgr = get_manager();
-    context & ctx = get_context();
-    TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl
-            << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
-            << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
-    );
-
-    if (!u.str.is_concat(to_app(concatAst1))) {
-        TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
-        return;
-    }
-    if (!u.str.is_concat(to_app(concatAst2))) {
-        TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
-        return;
-    }
-
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
-
-    expr * str1Ast = v1_arg0;
-    expr * y = v1_arg1;
-    expr * str2Ast = v2_arg0;
-    expr * n = v2_arg1;
-
-    zstring str1Value, str2Value;
-    u.str.is_string(str1Ast, str1Value);
-    u.str.is_string(str2Ast, str2Value);
-
-    unsigned int str1Len = str1Value.length();
-    unsigned int str2Len = str2Value.length();
-
-    int commonLen = (str1Len > str2Len) ? str2Len : str1Len;
-    if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) {
-        TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr)
-                << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;);
-        expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr);
-        assert_axiom(toNegate);
-        return;
-    } else {
-        if (str1Len > str2Len) {
-            zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len);
-            expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr);
-            if (!in_same_eqc(tmpAst, n)) {
-                // break down option 4-1
-                expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr);
-                if (m_params.m_StrongArrangements) {
-                    expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
-                    assert_axiom(ax_strong);
-                } else {
-                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-                }
-            }
-        } else if (str1Len == str2Len) {
-            if (!in_same_eqc(n, y)) {
-                //break down option 4-2
-                expr_ref implyR(ctx.mk_eq_atom(n, y), mgr);
-
-                if (m_params.m_StrongArrangements) {
-                    expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
-                    assert_axiom(ax_strong);
-                } else {
-                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-                }
-            }
-        } else {
-            zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len);
-            expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr);
-            if (!in_same_eqc(y, tmpAst)) {
-                //break down option 4-3
-                expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr);
-                if (m_params.m_StrongArrangements) {
-                    expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
-                    assert_axiom(ax_strong);
-                } else {
-                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-                }
-            }
+        if (!u.str.is_concat(to_app(concatAst1))) {
+            TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
+            return;
         }
-    }
-}
-
-/*************************************************************
- *  case 5: concat(x, "str1") = concat(m, "str2")
- *************************************************************/
-bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) {
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
-
-    if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)
-            && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) {
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) {
-    ast_manager & mgr = get_manager();
-    context & ctx = get_context();
-    TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl
-            << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
-            << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
-    );
-
-    if (!u.str.is_concat(to_app(concatAst1))) {
-        TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
-        return;
-    }
-    if (!u.str.is_concat(to_app(concatAst2))) {
-        TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
-        return;
-    }
-
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
-
-    expr * x = v1_arg0;
-    expr * str1Ast = v1_arg1;
-    expr * m = v2_arg0;
-    expr * str2Ast = v2_arg1;
-
-    zstring str1Value, str2Value;
-    u.str.is_string(str1Ast, str1Value);
-    u.str.is_string(str2Ast, str2Value);
-
-    unsigned int str1Len = str1Value.length();
-    unsigned int str2Len = str2Value.length();
-
-    int cLen = (str1Len > str2Len) ? str2Len : str1Len;
-    if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) {
-        TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr)
-                << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;);
-        expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr);
-        assert_axiom(toNegate);
-        return;
-    } else {
-        if (str1Len > str2Len) {
-            zstring deltaStr = str1Value.extract(0, str1Len - str2Len);
-            expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr);
-            if (!in_same_eqc(m, x_deltaStr)) {
-                expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr);
-                if (m_params.m_StrongArrangements) {
-                    expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
-                    assert_axiom(ax_strong);
-                } else {
-                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-                }
-            }
-        } else if (str1Len == str2Len) {
-            // test
-            if (!in_same_eqc(x, m)) {
-                expr_ref implyR(ctx.mk_eq_atom(x, m), mgr);
-                if (m_params.m_StrongArrangements) {
-                    expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
-                    assert_axiom(ax_strong);
-                } else {
-                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-                }
-            }
-        } else {
-            zstring deltaStr = str2Value.extract(0, str2Len - str1Len);
-            expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr);
-            if (!in_same_eqc(x, m_deltaStr)) {
-                expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr);
-                if (m_params.m_StrongArrangements) {
-                    expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
-                    assert_axiom(ax_strong);
-                } else {
-                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-                }
-            }
+        if (!u.str.is_concat(to_app(concatAst2))) {
+            TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
+            return;
         }
-    }
-}
 
-/*************************************************************
- *  case 6: concat("str1", y) = concat(m, "str2")
- *************************************************************/
-bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) {
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+        expr * x = NULL;
+        expr * y = NULL;
+        expr * strAst = NULL;
+        expr * m = NULL;
 
-    if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1))
-            && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) {
-        return true;
-    } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))
-            && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) {
-        return true;
-    } else {
-        return false;
-    }
-}
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
 
-void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) {
-    ast_manager & mgr = get_manager();
-    context & ctx = get_context();
-    TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl
-            << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
-            << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
-    );
+        if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) {
+            m = v1_arg0;
+            strAst = v1_arg1;
+            x = v2_arg0;
+            y = v2_arg1;
+        } else {
+            m = v2_arg0;
+            strAst = v2_arg1;
+            x = v1_arg0;
+            y = v1_arg1;
+        }
 
-    if (!u.str.is_concat(to_app(concatAst1))) {
-        TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
-        return;
-    }
-    if (!u.str.is_concat(to_app(concatAst2))) {
-        TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
-        return;
-    }
+        zstring strValue;
+        u.str.is_string(strAst, strValue);
 
-    expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
-    expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
-    expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
-    expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+        rational x_len, y_len, m_len, str_len;
+        bool x_len_exists = get_len_value(x, x_len);
+        bool y_len_exists = get_len_value(y, y_len);
+        bool m_len_exists = get_len_value(m, m_len);
+        bool str_len_exists = true;
+        str_len = rational(strValue.length());
 
+        // setup
 
-    expr * str1Ast = NULL;
-    expr * y = NULL;
-    expr * m = NULL;
-    expr * str2Ast = NULL;
+        expr * xorFlag = NULL;
+        expr * temp1 = NULL;
+        std::pair<expr*, expr*> key1(concatAst1, concatAst2);
+        std::pair<expr*, expr*> key2(concatAst2, concatAst1);
 
-    if (u.str.is_string(v1_arg0)) {
-        str1Ast = v1_arg0;
-        y = v1_arg1;
-        m = v2_arg0;
-        str2Ast = v2_arg1;
-    } else {
-        str1Ast = v2_arg0;
-        y = v2_arg1;
-        m = v1_arg0;
-        str2Ast = v1_arg1;
-    }
+        // check the entries in this map to make sure they're still in scope
+        // before we use them.
 
-    zstring str1Value, str2Value;
-    u.str.is_string(str1Ast, str1Value);
-    u.str.is_string(str2Ast, str2Value);
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
 
-    unsigned int str1Len = str1Value.length();
-    unsigned int str2Len = str2Value.length();
+        // prevent checking scope for the XOR term, as it's always in the same scope as the split var
 
-    //----------------------------------------
-    //(a)  |---str1---|----y----|
-    //     |--m--|-----str2-----|
-    //
-    //(b)  |---str1---|----y----|
-    //     |-----m----|--str2---|
-    //
-    //(c)  |---str1---|----y----|
-    //     |------m------|-str2-|
-    //----------------------------------------
-
-    std::list<unsigned int> overlapLen;
-    overlapLen.push_back(0);
-
-    for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) {
-        if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i))
-            overlapLen.push_back(i);
-    }
-
-    //----------------------------------------------------------------
-    expr * commonVar = NULL;
-    expr * xorFlag = NULL;
-    std::pair<expr*, expr*> key1(concatAst1, concatAst2);
-    std::pair<expr*, expr*> key2(concatAst2, concatAst1);
-
-    // check the entries in this map to make sure they're still in scope
-    // before we use them.
-
-    std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
-    std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
-
-    bool entry1InScope;
-    if (entry1 == varForBreakConcat.end()) {
-        entry1InScope = false;
-    } else {
-        if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
-                /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) {
+        bool entry1InScope;
+        if (entry1 == varForBreakConcat.end()) {
             entry1InScope = false;
         } else {
-            entry1InScope = true;
+            if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
+                /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/
+                ) {
+                entry1InScope = false;
+            } else {
+                entry1InScope = true;
+            }
         }
-    }
 
-    bool entry2InScope;
-    if (entry2 == varForBreakConcat.end()) {
-        entry2InScope = false;
-    } else {
-        if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
-                /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) {
+        bool entry2InScope;
+        if (entry2 == varForBreakConcat.end()) {
             entry2InScope = false;
         } else {
-            entry2InScope = true;
+            if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
+                /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/
+                ) {
+                entry2InScope = false;
+            } else {
+                entry2InScope = true;
+            }
         }
-    }
 
-    TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
-            << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
+        TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
+              << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
 
-    if (!entry1InScope && !entry2InScope) {
-        commonVar = mk_nonempty_str_var();
-        xorFlag = mk_internal_xor_var();
-        varForBreakConcat[key1][0] = commonVar;
-        varForBreakConcat[key1][1] = xorFlag;
-    } else {
-        if (entry1InScope) {
-            commonVar = (entry1->second)[0];
-            xorFlag = (entry1->second)[1];
+
+        if (!entry1InScope && !entry2InScope) {
+            temp1 = mk_nonempty_str_var();
+            xorFlag = mk_internal_xor_var();
+            varForBreakConcat[key1][0] = temp1;
+            varForBreakConcat[key1][1] = xorFlag;
         } else {
-            commonVar = (entry2->second)[0];
-            xorFlag = (entry2->second)[1];
-        }
-        refresh_theory_var(commonVar);
-        add_nonempty_constraint(commonVar);
-    }
-
-    bool overlapAssumptionUsed = false;
-
-    expr_ref_vector arrangement_disjunction(mgr);
-    int pos = 1;
-
-    if (!avoidLoopCut || !has_self_cut(m, y)) {
-        expr_ref_vector and_item(mgr);
-
-        expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr);
-        and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar));
-        pos += 1;
-
-        expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr);
-        and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2));
-        pos += 1;
-
-        and_item.push_back(ctx.mk_eq_atom(mk_strlen(m),
-                m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) ));
-        pos += 1;
-
-        //    addItems[0] = mk_length(t, commonVar);
-        //    addItems[1] = mk_length(t, str2Ast);
-        //    and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems)));
-
-        expr_ref option1(mk_and(and_item), mgr);
-        arrangement_disjunction.push_back(option1);
-        add_theory_aware_branching_info(option1, 0.1, l_true);
-    } else {
-        loopDetected = true;
-
-        if (m_params.m_FiniteOverlapModels) {
-            expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
-            arrangement_disjunction.push_back(tester);
-            add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
-        } else {
-            TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;);
-            TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout););
-
-            // only add the overlap assumption one time
-            if (!overlapAssumptionUsed) {
-                arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
-                overlapAssumptionUsed = true;
+            if (entry1InScope) {
+                temp1 = varForBreakConcat[key1][0];
+                xorFlag = varForBreakConcat[key1][1];
+            } else if (entry2InScope) {
+                temp1 = varForBreakConcat[key2][0];
+                xorFlag = varForBreakConcat[key2][1];
             }
+            refresh_theory_var(temp1);
+            add_nonempty_constraint(temp1);
         }
-    }
 
-    for (std::list<unsigned int>::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) {
-        unsigned int overLen = *itor;
-        zstring prefix = str1Value.extract(0, str1Len - overLen);
-        zstring suffix = str2Value.extract(overLen, str2Len - overLen);
-
-        expr_ref_vector and_item(mgr);
-
-        expr_ref prefixAst(mk_string(prefix), mgr);
-        expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr);
-        and_item.push_back(x_eq_prefix);
-        pos += 1;
-
-        and_item.push_back(
-                ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst)));
-        pos += 1;
-
-        // adding length constraint for _ = constStr seems slowing things down.
-
-        expr_ref suffixAst(mk_string(suffix), mgr);
-        expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr);
-        and_item.push_back(y_eq_suffix);
-        pos += 1;
-
-        and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst)));
-        pos += 1;
-
-        expr_ref option2(mk_and(and_item), mgr);
-        arrangement_disjunction.push_back(option2);
-        double priority;
-        // prefer the option "str1" = x
-        if (prefix == str1Value) {
-            priority = 0.5;
-        } else {
-            priority = 0.1;
+        int splitType = -1;
+        if (x_len_exists && m_len_exists) {
+            if (x_len < m_len)
+                splitType = 0;
+            else if (x_len == m_len)
+                splitType = 1;
+            else
+                splitType = 2;
         }
-        add_theory_aware_branching_info(option2, priority, l_true);
-    }
-
-    //  case 6: concat("str1", y) = concat(m, "str2")
-
-    expr_ref implyR(mk_or(arrangement_disjunction), mgr);
-
-    if (m_params.m_StrongArrangements) {
-        expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
-        assert_axiom(ax_strong);
-    } else {
-        assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
-    }
-    generate_mutual_exclusion(arrangement_disjunction);
-}
-
-void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) {
-	ast_manager & m = get_manager();
-
-	if (!u.re.is_unroll(to_app(unrollFunc))) {
-		return;
-	}
-	if (!u.str.is_string(constStr)) {
-		return;
-	}
-
-	expr * funcInUnroll = to_app(unrollFunc)->get_arg(0);
-	zstring strValue;
-	u.str.is_string(constStr, strValue);
-
-	TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl
-			<< "constStr: " << mk_pp(constStr, m) << std::endl;);
-
-	if (strValue == "") {
-		return;
-	}
-
-	if (u.re.is_to_re(to_app(funcInUnroll))) {
-		unroll_str2reg_constStr(unrollFunc, constStr);
-		return;
-	}
-}
-
-void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) {
-	context & ctx = get_context();
-	ast_manager & mgr = get_manager();
-
-	TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;);
-
-	std::pair<expr*, expr*> key = std::make_pair(concat, unroll);
-	expr_ref toAssert(mgr);
-
-	if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) {
-		expr_ref arg1(to_app(concat)->get_arg(0), mgr);
-		expr_ref arg2(to_app(concat)->get_arg(1), mgr);
-		expr_ref r1(to_app(unroll)->get_arg(0), mgr);
-		expr_ref t1(to_app(unroll)->get_arg(1), mgr);
-
-		expr_ref v1(mk_regex_rep_var(), mgr);
-		expr_ref v2(mk_regex_rep_var(), mgr);
-		expr_ref v3(mk_regex_rep_var(), mgr);
-		expr_ref v4(mk_regex_rep_var(), mgr);
-		expr_ref v5(mk_regex_rep_var(), mgr);
-
-		expr_ref t2(mk_unroll_bound_var(), mgr);
-		expr_ref t3(mk_unroll_bound_var(), mgr);
-		expr_ref emptyStr(mk_string(""), mgr);
-
-		expr_ref unroll1(mk_unroll(r1, t2), mgr);
-		expr_ref unroll2(mk_unroll(r1, t3), mgr);
-
-		expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr);
-		expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr);
-
-		expr_ref_vector op1Items(mgr);
-		expr_ref_vector op2Items(mgr);
-
-		op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr));
-		op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr));
-		op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0)));
-		op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0)));
-		expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr);
-
-		expr_ref v1v2(mk_concat(v1, v2), mgr);
-		op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2));
-		op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2))));
-		expr_ref v3v4(mk_concat(v3, v4), mgr);
-		op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4));
-		op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4))));
-
-		op2Items.push_back(ctx.mk_eq_atom(v1, unroll1));
-		op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1)));
-		op2Items.push_back(ctx.mk_eq_atom(v4, unroll2));
-		op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2)));
-		expr_ref v2v3(mk_concat(v2, v3), mgr);
-		op2Items.push_back(ctx.mk_eq_atom(v5, v2v3));
-		reduce_virtual_regex_in(v5, r1, op2Items);
-		op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3))));
-		op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1))));
-		expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr);
-
-		toAssert = mgr.mk_and(opAnd1, opAnd2);
-		m_trail.push_back(toAssert);
-		concat_eq_unroll_ast_map[key] = toAssert;
-	} else {
-		toAssert = concat_eq_unroll_ast_map[key];
-	}
-
-	assert_axiom(toAssert);
-}
-
-void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) {
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	expr * str2RegFunc = to_app(unrollFunc)->get_arg(0);
-	expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0);
-	expr * oriCnt = to_app(unrollFunc)->get_arg(1);
-
-	zstring strValue;
-	u.str.is_string(eqConstStr, strValue);
-	zstring regStrValue;
-	u.str.is_string(strInStr2RegFunc, regStrValue);
-	unsigned int strLen = strValue.length();
-	unsigned int regStrLen = regStrValue.length();
-	SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere
-	unsigned int cnt = strLen / regStrLen;
-
-	expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m);
-	expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m);
-	expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m);
-	expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m);
-	SASSERT(implyL);
-	SASSERT(axiomRHS);
-	assert_implication(implyL, axiomRHS);
-}
-
-/*
- * Look through the equivalence class of n to find a string constant.
- * Return that constant if it is found, and set hasEqcValue to true.
- * Otherwise, return n, and set hasEqcValue to false.
- */
-
-expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) {
-    return z3str2_get_eqc_value(n, hasEqcValue);
-}
-
-
-// Simulate the behaviour of get_eqc_value() from Z3str2.
-// We only check m_find for a string constant.
-
-expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) {
-    expr * curr = n;
-    do {
-        if (u.str.is_string(curr)) {
-            hasEqcValue = true;
-            return curr;
+        if (splitType == -1 && y_len_exists && str_len_exists) {
+            if (y_len > str_len)
+                splitType = 0;
+            else if (y_len == str_len)
+                splitType = 1;
+            else
+                splitType = 2;
         }
-        curr = get_eqc_next(curr);
-    } while (curr != n);
-    hasEqcValue = false;
-    return n;
-}
 
-// from Z3: theory_seq.cpp
+        TRACE("str", tout << "Split type " << splitType << std::endl;);
 
-static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) {
-    theory* th = ctx.get_theory(afid);
-    if (th && ctx.e_internalized(e)) {
-        return dynamic_cast<theory_mi_arith*>(th);
-    }
-    else {
-        return 0;
-    }
-}
+        // Provide fewer split options when length information is available.
 
-bool theory_str::get_value(expr* e, rational& val) const {
-    if (opt_DisableIntegerTheoryIntegration) {
-        TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
-        return false;
-    }
+        if (splitType == 0) {
+            // M cuts Y
+            //   |  x  |      y     |
+            //   |    m   |   str   |
+            expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr);
+            if (can_two_nodes_eq(y, temp1_strAst)) {
+                expr_ref_vector l_items(mgr);
+                l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
 
-    context& ctx = get_context();
-    ast_manager & m = get_manager();
-    theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e);
-    if (!tha) {
-        return false;
-    }
-    TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;);
-    expr_ref _val(m);
-    enode * en_e = ctx.get_enode(e);
-    enode * it = en_e;
-    do {
-        if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) {
-            // found an arithmetic term
-            TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )"
-                    << std::endl;);
-            return true;
-        } else {
-            TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;);
-        }
-        it = it->get_next();
-    } while (it != en_e);
-    TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;);
-    return false;
-}
+                expr_ref_vector r_items(mgr);
+                expr_ref x_temp1(mk_concat(x, temp1), mgr);
+                r_items.push_back(ctx.mk_eq_atom(m, x_temp1));
+                r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst));
 
-bool theory_str::lower_bound(expr* _e, rational& lo) {
-    if (opt_DisableIntegerTheoryIntegration) {
-        TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
-        return false;
-    }
-
-    context& ctx = get_context();
-    ast_manager & m = get_manager();
-    theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e);
-    expr_ref _lo(m);
-    if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false;
-    return m_autil.is_numeral(_lo, lo) && lo.is_int();
-}
-
-bool theory_str::upper_bound(expr* _e, rational& hi) {
-    if (opt_DisableIntegerTheoryIntegration) {
-        TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
-        return false;
-    }
-
-    context& ctx = get_context();
-    ast_manager & m = get_manager();
-    theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e);
-    expr_ref _hi(m);
-    if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false;
-    return m_autil.is_numeral(_hi, hi) && hi.is_int();
-}
-
-bool theory_str::get_len_value(expr* e, rational& val) {
-    if (opt_DisableIntegerTheoryIntegration) {
-        TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
-        return false;
-    }
-
-    context& ctx = get_context();
-    ast_manager & m = get_manager();
-
-    theory* th = ctx.get_theory(m_autil.get_family_id());
-    if (!th) {
-        TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;);
-        return false;
-    }
-    theory_mi_arith* tha = dynamic_cast<theory_mi_arith*>(th);
-    if (!tha) {
-        TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;);
-        return false;
-    }
-
-    TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;);
-
-    rational val1;
-    expr_ref len(m), len_val(m);
-    expr* e1, *e2;
-    ptr_vector<expr> todo;
-    todo.push_back(e);
-    val.reset();
-    while (!todo.empty()) {
-        expr* c = todo.back();
-        todo.pop_back();
-        if (u.str.is_concat(to_app(c))) {
-            e1 = to_app(c)->get_arg(0);
-            e2 = to_app(c)->get_arg(1);
-            todo.push_back(e1);
-            todo.push_back(e2);
-        }
-        else if (u.str.is_string(to_app(c))) {
-            zstring tmp;
-            u.str.is_string(to_app(c), tmp);
-            unsigned int sl = tmp.length();
-            val += rational(sl);
-        }
-        else {
-            len = mk_strlen(c);
-
-            // debugging
-            TRACE("str", {
-               tout << mk_pp(len, m) << ":" << std::endl
-               << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl
-               << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl
-               ;
-               if (ctx.e_internalized(len)) {
-                   enode * e_len = ctx.get_enode(len);
-                   tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl;
-
-                   // eqc debugging
-                   {
-                       tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl;
-                       enode * nNode = ctx.get_enode(len);
-                       enode * eqcNode = nNode;
-                       do {
-                           app * ast = eqcNode->get_owner();
-                           tout << mk_pp(ast, get_manager()) << std::endl;
-                           eqcNode = eqcNode->get_next();
-                       } while (eqcNode != nNode);
-                   }
-               }
-            });
-
-            if (ctx.e_internalized(len) && get_value(len, val1)) {
-                val += val1;
-                TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;);
-            }
-            else {
-                TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;);
-                return false;
-            }
-        }
-    }
-
-    TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;);
-    return val.is_int();
-}
-
-/*
- * Decide whether n1 and n2 are already in the same equivalence class.
- * This only checks whether the core considers them to be equal;
- * they may not actually be equal.
- */
-bool theory_str::in_same_eqc(expr * n1, expr * n2) {
-    if (n1 == n2) return true;
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    // similar to get_eqc_value(), make absolutely sure
-    // that we've set this up properly for the context
-
-    if (!ctx.e_internalized(n1)) {
-        TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;);
-        ctx.internalize(n1, false);
-    }
-    if (!ctx.e_internalized(n2)) {
-        TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;);
-        ctx.internalize(n2, false);
-    }
-
-    expr * curr = get_eqc_next(n1);
-    while (curr != n1) {
-        if (curr == n2)
-            return true;
-        curr = get_eqc_next(curr);
-    }
-    return false;
-}
-
-expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) {
-    context & ctx = get_context();
-    expr * constStrNode = NULL;
-
-    expr * ex = n;
-    do {
-        if (u.str.is_string(to_app(ex))) {
-            constStrNode = ex;
-        }
-        eqcSet.push_back(ex);
-
-        ex = get_eqc_next(ex);
-    } while (ex != n);
-    return constStrNode;
-}
-
-/*
- * Collect constant strings (from left to right) in an AST node.
- */
-void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) {
-    ast_manager & m = get_manager();
-    if (u.str.is_string(node)) {
-        astList.push_back(node);
-    //} else if (getNodeType(t, node) == my_Z3_Func) {
-    } else if (is_app(node)) {
-        app * func_app = to_app(node);
-        unsigned int argCount = func_app->get_num_args();
-        for (unsigned int i = 0; i < argCount; i++) {
-            expr * argAst = func_app->get_arg(i);
-            get_const_str_asts_in_node(argAst, astList);
-        }
-    }
-}
-
-void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;);
-
-    expr_ref_vector litems(m);
-
-    if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) {
-        std::set<std::pair<expr*, expr*> >::iterator itor1 = contain_pair_idx_map[varNode].begin();
-        for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) {
-            expr * strAst = itor1->first;
-            expr * substrAst = itor1->second;
-
-            expr * boolVar;
-            if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) {
-                TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;);
-            }
-            // boolVar is actually a Contains term
-            app * containsApp = to_app(boolVar);
-
-            // we only want to inspect the Contains terms where either of strAst or substrAst
-            // are equal to varNode.
-
-            TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;);
-
-            if (varNode != strAst && varNode != substrAst) {
-                TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;);
-                continue;
-            }
-            TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;);
-
-            // varEqcNode is str
-            if (strAst == varNode) {
-                expr_ref implyR(m);
-                litems.reset();
-
-                if (strAst != constNode) {
-                    litems.push_back(ctx.mk_eq_atom(strAst, constNode));
-                }
-                zstring strConst;
-                u.str.is_string(constNode, strConst);
-                bool subStrHasEqcValue = false;
-                expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue);
-                if (substrValue != substrAst) {
-                    litems.push_back(ctx.mk_eq_atom(substrAst, substrValue));
+                if (x_len_exists && m_len_exists) {
+                    l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
+                    l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
+                    rational m_sub_x = (m_len - x_len);
+                    r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x)));
+                } else {
+                    l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
+                    l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len)));
+                    rational y_sub_str = (y_len - str_len);
+                    r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str)));
                 }
 
-                if (subStrHasEqcValue) {
-                    // subStr has an eqc constant value
-                    zstring subStrConst;
-                    u.str.is_string(substrValue, subStrConst);
+                expr_ref ax_l(mk_and(l_items), mgr);
+                expr_ref ax_r(mk_and(r_items), mgr);
 
-                    TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";);
+                if (!avoidLoopCut || !(has_self_cut(m, y))) {
+                    // break down option 2-1
+                    add_cut_info_merge(temp1, sLevel, y);
+                    add_cut_info_merge(temp1, sLevel, m);
 
-                    if (strConst.contains(subStrConst)) {
-                        //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx));
-                        implyR = boolVar;
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
+                        assert_axiom(ax_strong);
                     } else {
-                        //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx));
-                        implyR = m.mk_not(boolVar);
+                        assert_implication(ax_l, ax_r);
                     }
                 } else {
-                    // ------------------------------------------------------------------------------------------------
-                    // subStr doesn't have an eqc contant value
-                    // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n)
-                    // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false
-                    // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet
-                    // ------------------------------------------------------------------------------------------------
-                    // collect eqc concat
-                    std::set<expr*> eqcConcats;
-                    get_concats_in_eqc(substrAst, eqcConcats);
-                    for (std::set<expr*>::iterator concatItor = eqcConcats.begin();
-                            concatItor != eqcConcats.end(); concatItor++) {
-                        expr_ref_vector constList(m);
-                        bool counterEgFound = false;
-                        // get constant strings in concat
-                        expr * aConcat = *concatItor;
-                        get_const_str_asts_in_node(aConcat, constList);
-                        for (expr_ref_vector::iterator cstItor = constList.begin();
-                                cstItor != constList.end(); cstItor++) {
-                            zstring pieceStr;
-                            u.str.is_string(*cstItor, pieceStr);
-                            if (!strConst.contains(pieceStr)) {
-                                counterEgFound = true;
-                                if (aConcat != substrAst) {
-                                    litems.push_back(ctx.mk_eq_atom(substrAst, aConcat));
-                                }
-                                //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx));
-                                implyR = m.mk_not(boolVar);
-                                break;
+                    loopDetected = true;
+
+                    if (m_params.m_FiniteOverlapModels) {
+                        expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
+                        assert_implication(ax_l, tester);
+                        add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+                    } else {
+                        TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;);
+                        TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
+
+                        if (!overlapAssumptionUsed) {
+                            overlapAssumptionUsed = true;
+                            assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
+                        }
+                    }
+                }
+            }
+        } else if (splitType == 1) {
+            //   |   x   |    y    |
+            //   |   m   |   str   |
+            expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
+            expr_ref ax_l2(mgr.mk_or(
+                               ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)),
+                               ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr);
+            expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr);
+            expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr);
+            assert_implication(ax_l, ax_r);
+        } else if (splitType == 2) {
+            // m cut y,
+            //    |   x   |  y  |
+            //    | m |   str   |
+            rational lenDelta;
+            expr_ref_vector l_items(mgr);
+            l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
+            if (x_len_exists && m_len_exists) {
+                l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
+                l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)));
+                lenDelta = x_len - m_len;
+            } else {
+                l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
+                lenDelta = str_len - y_len;
+            }
+            TRACE("str",
+                  tout
+                  << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl
+                  << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl
+                  << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl
+                  << "xLen = " << x_len.to_string() << std::endl
+                  << "yLen = " << y_len.to_string() << std::endl
+                  << "mLen = " << m_len.to_string() << std::endl
+                  << "strLen = " << str_len.to_string() << std::endl
+                  << "lenDelta = " << lenDelta.to_string() << std::endl
+                  << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n"
+                  ;
+                  );
+
+            zstring part1Str = strValue.extract(0, lenDelta.get_unsigned());
+            zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned());
+
+            expr_ref prefixStr(mk_string(part1Str), mgr);
+            expr_ref x_concat(mk_concat(m, prefixStr), mgr);
+            expr_ref cropStr(mk_string(part2Str), mgr);
+
+            if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) {
+                expr_ref_vector r_items(mgr);
+                r_items.push_back(ctx.mk_eq_atom(x, x_concat));
+                r_items.push_back(ctx.mk_eq_atom(y, cropStr));
+                expr_ref ax_l(mk_and(l_items), mgr);
+                expr_ref ax_r(mk_and(r_items), mgr);
+
+                if (m_params.m_StrongArrangements) {
+                    expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
+                    assert_axiom(ax_strong);
+                } else {
+                    assert_implication(ax_l, ax_r);
+                }
+            } else {
+                // negate! It's impossible to split str with these lengths
+                TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;);
+                expr_ref ax_l(mk_and(l_items), mgr);
+                assert_axiom(mgr.mk_not(ax_l));
+            }
+        } else {
+            // Split type -1: no idea about the length...
+            expr_ref_vector arrangement_disjunction(mgr);
+
+            expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr);
+
+            // m cuts y
+            if (can_two_nodes_eq(y, temp1_strAst)) {
+                if (!avoidLoopCut || !has_self_cut(m, y)) {
+                    // break down option 2-1
+                    expr_ref_vector and_item(mgr);
+
+                    expr_ref x_temp1(mk_concat(x, temp1), mgr);
+                    and_item.push_back(ctx.mk_eq_atom(m, x_temp1));
+                    and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst));
+
+                    and_item.push_back(ctx.mk_eq_atom(mk_strlen(m),
+                                                      m_autil.mk_add(mk_strlen(x), mk_strlen(temp1))));
+
+                    expr_ref option1(mk_and(and_item), mgr);
+                    arrangement_disjunction.push_back(option1);
+                    add_theory_aware_branching_info(option1, 0.1, l_true);
+                    add_cut_info_merge(temp1, ctx.get_scope_level(), y);
+                    add_cut_info_merge(temp1, ctx.get_scope_level(), m);
+                } else {
+                    loopDetected = true;
+                    if (m_params.m_FiniteOverlapModels) {
+                        expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
+                        arrangement_disjunction.push_back(tester);
+                        add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+                    } else {
+                        TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
+                        TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);});
+
+                        if (!overlapAssumptionUsed) {
+                            overlapAssumptionUsed = true;
+                            arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
+                        }
+                    }
+                }
+            }
+
+            for (unsigned int i = 0; i <= strValue.length(); ++i) {
+                zstring part1Str = strValue.extract(0, i);
+                zstring part2Str = strValue.extract(i, strValue.length() - i);
+                expr_ref prefixStr(mk_string(part1Str), mgr);
+                expr_ref x_concat(mk_concat(m, prefixStr), mgr);
+                expr_ref cropStr(mk_string(part2Str), mgr);
+                if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) {
+                    // break down option 2-2
+                    expr_ref_vector and_item(mgr);
+                    and_item.push_back(ctx.mk_eq_atom(x, x_concat));
+                    and_item.push_back(ctx.mk_eq_atom(y, cropStr));
+                    and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length())));
+                    expr_ref option2(mk_and(and_item), mgr);
+                    arrangement_disjunction.push_back(option2);
+                    double priority;
+                    // prioritize the option where y is equal to the original string
+                    if (i == 0) {
+                        priority = 0.5;
+                    } else {
+                        priority = 0.1;
+                    }
+                    add_theory_aware_branching_info(option2, priority, l_true);
+                }
+            }
+
+            if (!arrangement_disjunction.empty()) {
+                expr_ref implyR(mk_or(arrangement_disjunction), mgr);
+
+                if (m_params.m_StrongArrangements) {
+                    expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
+                    expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr);
+                    assert_axiom(ax_strong);
+                } else {
+                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                }
+                generate_mutual_exclusion(arrangement_disjunction);
+            } else {
+                TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;);
+            }
+        } // (splitType == -1)
+    }
+
+    /*************************************************************
+     * Type 3: concat(x, y) = concat("str", n)
+     *************************************************************/
+    bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) {
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1))
+            && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) {
+            return true;
+        } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))
+                   && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) {
+        ast_manager & mgr = get_manager();
+        context & ctx = get_context();
+
+        bool overlapAssumptionUsed = false;
+
+        TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl
+              << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
+              << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
+              );
+
+        if (!u.str.is_concat(to_app(concatAst1))) {
+            TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
+            return;
+        }
+        if (!u.str.is_concat(to_app(concatAst2))) {
+            TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
+            return;
+        }
+
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        expr * x = NULL;
+        expr * y = NULL;
+        expr * strAst = NULL;
+        expr * n = NULL;
+
+        if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) {
+            strAst = v1_arg0;
+            n = v1_arg1;
+            x = v2_arg0;
+            y = v2_arg1;
+        } else {
+            strAst = v2_arg0;
+            n = v2_arg1;
+            x = v1_arg0;
+            y = v1_arg1;
+        }
+
+        zstring strValue;
+        u.str.is_string(strAst, strValue);
+
+        rational x_len, y_len, str_len, n_len;
+        bool x_len_exists = get_len_value(x, x_len);
+        bool y_len_exists = get_len_value(y, y_len);
+        str_len = rational((unsigned)(strValue.length()));
+        bool n_len_exists = get_len_value(n, n_len);
+
+        expr_ref xorFlag(mgr);
+        expr_ref temp1(mgr);
+        std::pair<expr*, expr*> key1(concatAst1, concatAst2);
+        std::pair<expr*, expr*> key2(concatAst2, concatAst1);
+
+        // check the entries in this map to make sure they're still in scope
+        // before we use them.
+
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
+
+        bool entry1InScope;
+        if (entry1 == varForBreakConcat.end()) {
+            entry1InScope = false;
+        } else {
+            if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
+                /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) {
+                entry1InScope = false;
+            } else {
+                entry1InScope = true;
+            }
+        }
+
+        bool entry2InScope;
+        if (entry2 == varForBreakConcat.end()) {
+            entry2InScope = false;
+        } else {
+            if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
+                /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) {
+                entry2InScope = false;
+            } else {
+                entry2InScope = true;
+            }
+        }
+
+        TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
+              << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
+
+
+        if (!entry1InScope && !entry2InScope) {
+            temp1 = mk_nonempty_str_var();
+            xorFlag = mk_internal_xor_var();
+
+            varForBreakConcat[key1][0] = temp1;
+            varForBreakConcat[key1][1] = xorFlag;
+        } else {
+            if (entry1InScope) {
+                temp1 = varForBreakConcat[key1][0];
+                xorFlag = varForBreakConcat[key1][1];
+            } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) {
+                temp1 = varForBreakConcat[key2][0];
+                xorFlag = varForBreakConcat[key2][1];
+            }
+            refresh_theory_var(temp1);
+            add_nonempty_constraint(temp1);
+        }
+
+
+
+        int splitType = -1;
+        if (x_len_exists) {
+            if (x_len < str_len)
+                splitType = 0;
+            else if (x_len == str_len)
+                splitType = 1;
+            else
+                splitType = 2;
+        }
+        if (splitType == -1 && y_len_exists && n_len_exists) {
+            if (y_len > n_len)
+                splitType = 0;
+            else if (y_len == n_len)
+                splitType = 1;
+            else
+                splitType = 2;
+        }
+
+        TRACE("str", tout << "Split type " << splitType << std::endl;);
+
+        // Provide fewer split options when length information is available.
+        if (splitType == 0) {
+            //   |   x   |    y     |
+            //   |  str     |   n   |
+            expr_ref_vector litems(mgr);
+            litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
+            rational prefixLen;
+            if (!x_len_exists) {
+                prefixLen = str_len - (y_len - n_len);
+                litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
+                litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
+            } else {
+                prefixLen = x_len;
+                litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
+            }
+            zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned());
+            rational str_sub_prefix = str_len - prefixLen;
+            zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned());
+            expr_ref prefixAst(mk_string(prefixStr), mgr);
+            expr_ref suffixAst(mk_string(suffixStr), mgr);
+            expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr);
+
+            expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr);
+            if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) {
+                expr_ref_vector r_items(mgr);
+                r_items.push_back(ctx.mk_eq_atom(x, prefixAst));
+                r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat));
+
+                if (m_params.m_StrongArrangements) {
+                    expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr);
+                    assert_axiom(ax_strong);
+                } else {
+                    assert_implication(ax_l, mk_and(r_items));
+                }
+            } else {
+                // negate! It's impossible to split str with these lengths
+                TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;);
+                assert_axiom(mgr.mk_not(ax_l));
+            }
+        }
+        else if (splitType == 1) {
+            expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
+            expr_ref ax_l2(mgr.mk_or(
+                               ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)),
+                               ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr);
+            expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr);
+            expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr);
+
+            if (m_params.m_StrongArrangements) {
+                expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
+                assert_axiom(ax_strong);
+            } else {
+                assert_implication(ax_l, ax_r);
+            }
+        }
+        else if (splitType == 2) {
+            //   |   x        |    y     |
+            //   |  str   |       n      |
+            expr_ref_vector litems(mgr);
+            litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2));
+            rational tmpLen;
+            if (!x_len_exists) {
+                tmpLen = n_len - y_len;
+                litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)));
+                litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)));
+            } else {
+                tmpLen = x_len - str_len;
+                litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)));
+            }
+            expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr);
+
+            expr_ref str_temp1(mk_concat(strAst, temp1), mgr);
+            expr_ref temp1_y(mk_concat(temp1, y), mgr);
+
+            if (can_two_nodes_eq(x, str_temp1)) {
+                if (!avoidLoopCut || !(has_self_cut(x, n))) {
+                    expr_ref_vector r_items(mgr);
+                    r_items.push_back(ctx.mk_eq_atom(x, str_temp1));
+                    r_items.push_back(ctx.mk_eq_atom(n, temp1_y));
+                    r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen)));
+                    expr_ref ax_r(mk_and(r_items), mgr);
+
+                    //Cut Info
+                    add_cut_info_merge(temp1, sLevel, x);
+                    add_cut_info_merge(temp1, sLevel, n);
+
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr);
+                        assert_axiom(ax_strong);
+                    } else {
+                        assert_implication(ax_l, ax_r);
+                    }
+                } else {
+                    loopDetected = true;
+                    if (m_params.m_FiniteOverlapModels) {
+                        expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
+                        assert_implication(ax_l, tester);
+                        add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+                    } else {
+                        TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;);
+                        TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);});
+
+                        if (!overlapAssumptionUsed) {
+                            overlapAssumptionUsed = true;
+                            assert_implication(ax_l, m_theoryStrOverlapAssumption_term);
+                        }
+                    }
+                }
+            }
+            //    else {
+            //      // negate! It's impossible to split str with these lengths
+            //      __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__);
+            //      addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__);
+            //    }
+        }
+        else {
+            // Split type -1. We know nothing about the length...
+
+            expr_ref_vector arrangement_disjunction(mgr);
+
+            int pos = 1;
+            for (unsigned int i = 0; i <= strValue.length(); i++) {
+                zstring part1Str = strValue.extract(0, i);
+                zstring part2Str = strValue.extract(i, strValue.length() - i);
+                expr_ref cropStr(mk_string(part1Str), mgr);
+                expr_ref suffixStr(mk_string(part2Str), mgr);
+                expr_ref y_concat(mk_concat(suffixStr, n), mgr);
+
+                if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) {
+                    expr_ref_vector and_item(mgr);
+                    // break down option 3-1
+                    expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr);
+
+                    and_item.push_back(x_eq_str); ++pos;
+                    and_item.push_back(ctx.mk_eq_atom(y, y_concat));
+                    and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos;
+
+                    //        and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat)));
+                    // adding length constraint for _ = constStr seems slowing things down.
+
+                    expr_ref option1(mk_and(and_item), mgr);
+                    arrangement_disjunction.push_back(option1);
+                    double priority;
+                    if (i == strValue.length()) {
+                        priority = 0.5;
+                    } else {
+                        priority = 0.1;
+                    }
+                    add_theory_aware_branching_info(option1, priority, l_true);
+                }
+            }
+
+            expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr);
+
+
+            //--------------------------------------------------------
+            // x cut n
+            //--------------------------------------------------------
+            if (can_two_nodes_eq(x, strAst_temp1)) {
+                if (!avoidLoopCut || !(has_self_cut(x, n))) {
+                    // break down option 3-2
+                    expr_ref_vector and_item(mgr);
+
+                    expr_ref temp1_y(mk_concat(temp1, y), mgr);
+                    and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos;
+                    and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos;
+
+                    and_item.push_back(ctx.mk_eq_atom(mk_strlen(x),
+                                                      m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos;
+
+                    expr_ref option2(mk_and(and_item), mgr);
+                    arrangement_disjunction.push_back(option2);
+                    add_theory_aware_branching_info(option2, 0.1, l_true);
+
+                    add_cut_info_merge(temp1, sLevel, x);
+                    add_cut_info_merge(temp1, sLevel, n);
+                } else {
+                    loopDetected = true;
+                    if (m_params.m_FiniteOverlapModels) {
+                        expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
+                        arrangement_disjunction.push_back(tester);
+                        add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+                    } else {
+                        TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;);
+                        TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);});
+
+                        if (!overlapAssumptionUsed) {
+                            overlapAssumptionUsed = true;
+                            arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
+                        }
+                    }
+                }
+            }
+
+
+            if (!arrangement_disjunction.empty()) {
+                expr_ref implyR(mk_or(arrangement_disjunction), mgr);
+
+                if (m_params.m_StrongArrangements) {
+                    expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr);
+                    expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr);
+                    assert_axiom(ax_strong);
+                } else {
+                    assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                }
+                generate_mutual_exclusion(arrangement_disjunction);
+            } else {
+                TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;);
+            }
+        }
+
+    }
+
+    /*************************************************************
+     * Type 4: concat("str1", y) = concat("str2", n)
+     *************************************************************/
+    bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) {
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1))
+            && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) {
+        ast_manager & mgr = get_manager();
+        context & ctx = get_context();
+        TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl
+              << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
+              << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
+              );
+
+        if (!u.str.is_concat(to_app(concatAst1))) {
+            TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
+            return;
+        }
+        if (!u.str.is_concat(to_app(concatAst2))) {
+            TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
+            return;
+        }
+
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        expr * str1Ast = v1_arg0;
+        expr * y = v1_arg1;
+        expr * str2Ast = v2_arg0;
+        expr * n = v2_arg1;
+
+        zstring str1Value, str2Value;
+        u.str.is_string(str1Ast, str1Value);
+        u.str.is_string(str2Ast, str2Value);
+
+        unsigned int str1Len = str1Value.length();
+        unsigned int str2Len = str2Value.length();
+
+        int commonLen = (str1Len > str2Len) ? str2Len : str1Len;
+        if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) {
+            TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr)
+                  << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;);
+            expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr);
+            assert_axiom(toNegate);
+            return;
+        } else {
+            if (str1Len > str2Len) {
+                zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len);
+                expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr);
+                if (!in_same_eqc(tmpAst, n)) {
+                    // break down option 4-1
+                    expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr);
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
+                        assert_axiom(ax_strong);
+                    } else {
+                        assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                    }
+                }
+            } else if (str1Len == str2Len) {
+                if (!in_same_eqc(n, y)) {
+                    //break down option 4-2
+                    expr_ref implyR(ctx.mk_eq_atom(n, y), mgr);
+
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
+                        assert_axiom(ax_strong);
+                    } else {
+                        assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                    }
+                }
+            } else {
+                zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len);
+                expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr);
+                if (!in_same_eqc(y, tmpAst)) {
+                    //break down option 4-3
+                    expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr);
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
+                        assert_axiom(ax_strong);
+                    } else {
+                        assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                    }
+                }
+            }
+        }
+    }
+
+    /*************************************************************
+     *  case 5: concat(x, "str1") = concat(m, "str2")
+     *************************************************************/
+    bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) {
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)
+            && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) {
+        ast_manager & mgr = get_manager();
+        context & ctx = get_context();
+        TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl
+              << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
+              << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
+              );
+
+        if (!u.str.is_concat(to_app(concatAst1))) {
+            TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
+            return;
+        }
+        if (!u.str.is_concat(to_app(concatAst2))) {
+            TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
+            return;
+        }
+
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        expr * x = v1_arg0;
+        expr * str1Ast = v1_arg1;
+        expr * m = v2_arg0;
+        expr * str2Ast = v2_arg1;
+
+        zstring str1Value, str2Value;
+        u.str.is_string(str1Ast, str1Value);
+        u.str.is_string(str2Ast, str2Value);
+
+        unsigned int str1Len = str1Value.length();
+        unsigned int str2Len = str2Value.length();
+
+        int cLen = (str1Len > str2Len) ? str2Len : str1Len;
+        if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) {
+            TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr)
+                  << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;);
+            expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr);
+            assert_axiom(toNegate);
+            return;
+        } else {
+            if (str1Len > str2Len) {
+                zstring deltaStr = str1Value.extract(0, str1Len - str2Len);
+                expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr);
+                if (!in_same_eqc(m, x_deltaStr)) {
+                    expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr);
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
+                        assert_axiom(ax_strong);
+                    } else {
+                        assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                    }
+                }
+            } else if (str1Len == str2Len) {
+                // test
+                if (!in_same_eqc(x, m)) {
+                    expr_ref implyR(ctx.mk_eq_atom(x, m), mgr);
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
+                        assert_axiom(ax_strong);
+                    } else {
+                        assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                    }
+                }
+            } else {
+                zstring deltaStr = str2Value.extract(0, str2Len - str1Len);
+                expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr);
+                if (!in_same_eqc(x, m_deltaStr)) {
+                    expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr);
+                    if (m_params.m_StrongArrangements) {
+                        expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
+                        assert_axiom(ax_strong);
+                    } else {
+                        assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+                    }
+                }
+            }
+        }
+    }
+
+    /*************************************************************
+     *  case 6: concat("str1", y) = concat(m, "str2")
+     *************************************************************/
+    bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) {
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+        if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1))
+            && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) {
+            return true;
+        } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))
+                   && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) {
+        ast_manager & mgr = get_manager();
+        context & ctx = get_context();
+        TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl
+              << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl
+              << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl;
+              );
+
+        if (!u.str.is_concat(to_app(concatAst1))) {
+            TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;);
+            return;
+        }
+        if (!u.str.is_concat(to_app(concatAst2))) {
+            TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;);
+            return;
+        }
+
+        expr * v1_arg0 = to_app(concatAst1)->get_arg(0);
+        expr * v1_arg1 = to_app(concatAst1)->get_arg(1);
+        expr * v2_arg0 = to_app(concatAst2)->get_arg(0);
+        expr * v2_arg1 = to_app(concatAst2)->get_arg(1);
+
+
+        expr * str1Ast = NULL;
+        expr * y = NULL;
+        expr * m = NULL;
+        expr * str2Ast = NULL;
+
+        if (u.str.is_string(v1_arg0)) {
+            str1Ast = v1_arg0;
+            y = v1_arg1;
+            m = v2_arg0;
+            str2Ast = v2_arg1;
+        } else {
+            str1Ast = v2_arg0;
+            y = v2_arg1;
+            m = v1_arg0;
+            str2Ast = v1_arg1;
+        }
+
+        zstring str1Value, str2Value;
+        u.str.is_string(str1Ast, str1Value);
+        u.str.is_string(str2Ast, str2Value);
+
+        unsigned int str1Len = str1Value.length();
+        unsigned int str2Len = str2Value.length();
+
+        //----------------------------------------
+        //(a)  |---str1---|----y----|
+        //     |--m--|-----str2-----|
+        //
+        //(b)  |---str1---|----y----|
+        //     |-----m----|--str2---|
+        //
+        //(c)  |---str1---|----y----|
+        //     |------m------|-str2-|
+        //----------------------------------------
+
+        std::list<unsigned int> overlapLen;
+        overlapLen.push_back(0);
+
+        for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) {
+            if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i))
+                overlapLen.push_back(i);
+        }
+
+        //----------------------------------------------------------------
+        expr * commonVar = NULL;
+        expr * xorFlag = NULL;
+        std::pair<expr*, expr*> key1(concatAst1, concatAst2);
+        std::pair<expr*, expr*> key2(concatAst2, concatAst1);
+
+        // check the entries in this map to make sure they're still in scope
+        // before we use them.
+
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
+        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
+
+        bool entry1InScope;
+        if (entry1 == varForBreakConcat.end()) {
+            entry1InScope = false;
+        } else {
+            if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()
+                /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) {
+                entry1InScope = false;
+            } else {
+                entry1InScope = true;
+            }
+        }
+
+        bool entry2InScope;
+        if (entry2 == varForBreakConcat.end()) {
+            entry2InScope = false;
+        } else {
+            if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()
+                /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) {
+                entry2InScope = false;
+            } else {
+                entry2InScope = true;
+            }
+        }
+
+        TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
+              << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
+
+        if (!entry1InScope && !entry2InScope) {
+            commonVar = mk_nonempty_str_var();
+            xorFlag = mk_internal_xor_var();
+            varForBreakConcat[key1][0] = commonVar;
+            varForBreakConcat[key1][1] = xorFlag;
+        } else {
+            if (entry1InScope) {
+                commonVar = (entry1->second)[0];
+                xorFlag = (entry1->second)[1];
+            } else {
+                commonVar = (entry2->second)[0];
+                xorFlag = (entry2->second)[1];
+            }
+            refresh_theory_var(commonVar);
+            add_nonempty_constraint(commonVar);
+        }
+
+        bool overlapAssumptionUsed = false;
+
+        expr_ref_vector arrangement_disjunction(mgr);
+        int pos = 1;
+
+        if (!avoidLoopCut || !has_self_cut(m, y)) {
+            expr_ref_vector and_item(mgr);
+
+            expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr);
+            and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar));
+            pos += 1;
+
+            expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr);
+            and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2));
+            pos += 1;
+
+            and_item.push_back(ctx.mk_eq_atom(mk_strlen(m),
+                                              m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) ));
+            pos += 1;
+
+            //    addItems[0] = mk_length(t, commonVar);
+            //    addItems[1] = mk_length(t, str2Ast);
+            //    and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems)));
+
+            expr_ref option1(mk_and(and_item), mgr);
+            arrangement_disjunction.push_back(option1);
+            add_theory_aware_branching_info(option1, 0.1, l_true);
+        } else {
+            loopDetected = true;
+
+            if (m_params.m_FiniteOverlapModels) {
+                expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2);
+                arrangement_disjunction.push_back(tester);
+                add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true);
+            } else {
+                TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;);
+                TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout););
+
+                // only add the overlap assumption one time
+                if (!overlapAssumptionUsed) {
+                    arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term);
+                    overlapAssumptionUsed = true;
+                }
+            }
+        }
+
+        for (std::list<unsigned int>::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) {
+            unsigned int overLen = *itor;
+            zstring prefix = str1Value.extract(0, str1Len - overLen);
+            zstring suffix = str2Value.extract(overLen, str2Len - overLen);
+
+            expr_ref_vector and_item(mgr);
+
+            expr_ref prefixAst(mk_string(prefix), mgr);
+            expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr);
+            and_item.push_back(x_eq_prefix);
+            pos += 1;
+
+            and_item.push_back(
+                ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst)));
+            pos += 1;
+
+            // adding length constraint for _ = constStr seems slowing things down.
+
+            expr_ref suffixAst(mk_string(suffix), mgr);
+            expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr);
+            and_item.push_back(y_eq_suffix);
+            pos += 1;
+
+            and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst)));
+            pos += 1;
+
+            expr_ref option2(mk_and(and_item), mgr);
+            arrangement_disjunction.push_back(option2);
+            double priority;
+            // prefer the option "str1" = x
+            if (prefix == str1Value) {
+                priority = 0.5;
+            } else {
+                priority = 0.1;
+            }
+            add_theory_aware_branching_info(option2, priority, l_true);
+        }
+
+        //  case 6: concat("str1", y) = concat(m, "str2")
+
+        expr_ref implyR(mk_or(arrangement_disjunction), mgr);
+
+        if (m_params.m_StrongArrangements) {
+            expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr);
+            assert_axiom(ax_strong);
+        } else {
+            assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR);
+        }
+        generate_mutual_exclusion(arrangement_disjunction);
+    }
+
+    void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) {
+        ast_manager & m = get_manager();
+
+        if (!u.re.is_unroll(to_app(unrollFunc))) {
+            return;
+        }
+        if (!u.str.is_string(constStr)) {
+            return;
+        }
+
+        expr * funcInUnroll = to_app(unrollFunc)->get_arg(0);
+        zstring strValue;
+        u.str.is_string(constStr, strValue);
+
+        TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl
+              << "constStr: " << mk_pp(constStr, m) << std::endl;);
+
+        if (strValue == "") {
+            return;
+        }
+
+        if (u.re.is_to_re(to_app(funcInUnroll))) {
+            unroll_str2reg_constStr(unrollFunc, constStr);
+            return;
+        }
+    }
+
+    void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;);
+
+        std::pair<expr*, expr*> key = std::make_pair(concat, unroll);
+        expr_ref toAssert(mgr);
+
+        if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) {
+            expr_ref arg1(to_app(concat)->get_arg(0), mgr);
+            expr_ref arg2(to_app(concat)->get_arg(1), mgr);
+            expr_ref r1(to_app(unroll)->get_arg(0), mgr);
+            expr_ref t1(to_app(unroll)->get_arg(1), mgr);
+
+            expr_ref v1(mk_regex_rep_var(), mgr);
+            expr_ref v2(mk_regex_rep_var(), mgr);
+            expr_ref v3(mk_regex_rep_var(), mgr);
+            expr_ref v4(mk_regex_rep_var(), mgr);
+            expr_ref v5(mk_regex_rep_var(), mgr);
+
+            expr_ref t2(mk_unroll_bound_var(), mgr);
+            expr_ref t3(mk_unroll_bound_var(), mgr);
+            expr_ref emptyStr(mk_string(""), mgr);
+
+            expr_ref unroll1(mk_unroll(r1, t2), mgr);
+            expr_ref unroll2(mk_unroll(r1, t3), mgr);
+
+            expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr);
+            expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr);
+
+            expr_ref_vector op1Items(mgr);
+            expr_ref_vector op2Items(mgr);
+
+            op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr));
+            op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr));
+            op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0)));
+            op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0)));
+            expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr);
+
+            expr_ref v1v2(mk_concat(v1, v2), mgr);
+            op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2));
+            op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2))));
+            expr_ref v3v4(mk_concat(v3, v4), mgr);
+            op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4));
+            op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4))));
+
+            op2Items.push_back(ctx.mk_eq_atom(v1, unroll1));
+            op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1)));
+            op2Items.push_back(ctx.mk_eq_atom(v4, unroll2));
+            op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2)));
+            expr_ref v2v3(mk_concat(v2, v3), mgr);
+            op2Items.push_back(ctx.mk_eq_atom(v5, v2v3));
+            reduce_virtual_regex_in(v5, r1, op2Items);
+            op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3))));
+            op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1))));
+            expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr);
+
+            toAssert = mgr.mk_and(opAnd1, opAnd2);
+            m_trail.push_back(toAssert);
+            concat_eq_unroll_ast_map[key] = toAssert;
+        } else {
+            toAssert = concat_eq_unroll_ast_map[key];
+        }
+
+        assert_axiom(toAssert);
+    }
+
+    void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr * str2RegFunc = to_app(unrollFunc)->get_arg(0);
+        expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0);
+        expr * oriCnt = to_app(unrollFunc)->get_arg(1);
+
+        zstring strValue;
+        u.str.is_string(eqConstStr, strValue);
+        zstring regStrValue;
+        u.str.is_string(strInStr2RegFunc, regStrValue);
+        unsigned int strLen = strValue.length();
+        unsigned int regStrLen = regStrValue.length();
+        SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere
+        unsigned int cnt = strLen / regStrLen;
+
+        expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m);
+        expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m);
+        expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m);
+        expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m);
+        SASSERT(implyL);
+        SASSERT(axiomRHS);
+        assert_implication(implyL, axiomRHS);
+    }
+
+    /*
+     * Look through the equivalence class of n to find a string constant.
+     * Return that constant if it is found, and set hasEqcValue to true.
+     * Otherwise, return n, and set hasEqcValue to false.
+     */
+
+    expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) {
+        return z3str2_get_eqc_value(n, hasEqcValue);
+    }
+
+
+    // Simulate the behaviour of get_eqc_value() from Z3str2.
+    // We only check m_find for a string constant.
+
+    expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) {
+        expr * curr = n;
+        do {
+            if (u.str.is_string(curr)) {
+                hasEqcValue = true;
+                return curr;
+            }
+            curr = get_eqc_next(curr);
+        } while (curr != n);
+        hasEqcValue = false;
+        return n;
+    }
+
+    // from Z3: theory_seq.cpp
+
+    static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) {
+        theory* th = ctx.get_theory(afid);
+        if (th && ctx.e_internalized(e)) {
+            return dynamic_cast<theory_mi_arith*>(th);
+        }
+        else {
+            return 0;
+        }
+    }
+
+    bool theory_str::get_value(expr* e, rational& val) const {
+        if (opt_DisableIntegerTheoryIntegration) {
+            TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
+            return false;
+        }
+
+        context& ctx = get_context();
+        ast_manager & m = get_manager();
+        theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e);
+        if (!tha) {
+            return false;
+        }
+        TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;);
+        expr_ref _val(m);
+        enode * en_e = ctx.get_enode(e);
+        enode * it = en_e;
+        do {
+            if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) {
+                // found an arithmetic term
+                TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )"
+                      << std::endl;);
+                return true;
+            } else {
+                TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;);
+            }
+            it = it->get_next();
+        } while (it != en_e);
+        TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;);
+        return false;
+    }
+
+    bool theory_str::lower_bound(expr* _e, rational& lo) {
+        if (opt_DisableIntegerTheoryIntegration) {
+            TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
+            return false;
+        }
+
+        context& ctx = get_context();
+        ast_manager & m = get_manager();
+        theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e);
+        expr_ref _lo(m);
+        if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false;
+        return m_autil.is_numeral(_lo, lo) && lo.is_int();
+    }
+
+    bool theory_str::upper_bound(expr* _e, rational& hi) {
+        if (opt_DisableIntegerTheoryIntegration) {
+            TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
+            return false;
+        }
+
+        context& ctx = get_context();
+        ast_manager & m = get_manager();
+        theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e);
+        expr_ref _hi(m);
+        if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false;
+        return m_autil.is_numeral(_hi, hi) && hi.is_int();
+    }
+
+    bool theory_str::get_len_value(expr* e, rational& val) {
+        if (opt_DisableIntegerTheoryIntegration) {
+            TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;);
+            return false;
+        }
+
+        context& ctx = get_context();
+        ast_manager & m = get_manager();
+
+        theory* th = ctx.get_theory(m_autil.get_family_id());
+        if (!th) {
+            TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;);
+            return false;
+        }
+        theory_mi_arith* tha = dynamic_cast<theory_mi_arith*>(th);
+        if (!tha) {
+            TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;);
+            return false;
+        }
+
+        TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;);
+
+        rational val1;
+        expr_ref len(m), len_val(m);
+        expr* e1, *e2;
+        ptr_vector<expr> todo;
+        todo.push_back(e);
+        val.reset();
+        while (!todo.empty()) {
+            expr* c = todo.back();
+            todo.pop_back();
+            if (u.str.is_concat(to_app(c))) {
+                e1 = to_app(c)->get_arg(0);
+                e2 = to_app(c)->get_arg(1);
+                todo.push_back(e1);
+                todo.push_back(e2);
+            }
+            else if (u.str.is_string(to_app(c))) {
+                zstring tmp;
+                u.str.is_string(to_app(c), tmp);
+                unsigned int sl = tmp.length();
+                val += rational(sl);
+            }
+            else {
+                len = mk_strlen(c);
+
+                // debugging
+                TRACE("str", {
+                        tout << mk_pp(len, m) << ":" << std::endl
+                             << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl
+                             << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl
+                            ;
+                        if (ctx.e_internalized(len)) {
+                            enode * e_len = ctx.get_enode(len);
+                            tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl;
+
+                            // eqc debugging
+                            {
+                                tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl;
+                                enode * nNode = ctx.get_enode(len);
+                                enode * eqcNode = nNode;
+                                do {
+                                    app * ast = eqcNode->get_owner();
+                                    tout << mk_pp(ast, get_manager()) << std::endl;
+                                    eqcNode = eqcNode->get_next();
+                                } while (eqcNode != nNode);
                             }
                         }
-                        if (counterEgFound) {
-                            TRACE("str", tout << "Inconsistency found!" << std::endl;);
-                            break;
-                        }
-                    }
+                    });
+
+                if (ctx.e_internalized(len) && get_value(len, val1)) {
+                    val += val1;
+                    TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;);
                 }
-                // add assertion
-                if (implyR) {
-                    expr_ref implyLHS(mk_and(litems), m);
-                    assert_implication(implyLHS, implyR);
+                else {
+                    TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;);
+                    return false;
                 }
             }
-            // varEqcNode is subStr
-            else if (substrAst == varNode) {
-                expr_ref implyR(m);
-                litems.reset();
+        }
 
-                if (substrAst != constNode) {
-                    litems.push_back(ctx.mk_eq_atom(substrAst, constNode));
-                }
-                bool strHasEqcValue = false;
-                expr * strValue = get_eqc_value(strAst, strHasEqcValue);
-                if (strValue != strAst) {
-                    litems.push_back(ctx.mk_eq_atom(strAst, strValue));
-                }
+        TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;);
+        return val.is_int();
+    }
 
-                if (strHasEqcValue) {
-                    zstring strConst, subStrConst;
-                    u.str.is_string(strValue, strConst);
-                    u.str.is_string(constNode, subStrConst);
-                    if (strConst.contains(subStrConst)) {
-                        //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx));
-                        implyR = boolVar;
-                    } else {
-                        // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx));
-                        implyR = m.mk_not(boolVar);
-                    }
-                }
+    /*
+     * Decide whether n1 and n2 are already in the same equivalence class.
+     * This only checks whether the core considers them to be equal;
+     * they may not actually be equal.
+     */
+    bool theory_str::in_same_eqc(expr * n1, expr * n2) {
+        if (n1 == n2) return true;
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-                // add assertion
-                if (implyR) {
-                    expr_ref implyLHS(mk_and(litems), m);
-                    assert_implication(implyLHS, implyR);
-                }
+        // similar to get_eqc_value(), make absolutely sure
+        // that we've set this up properly for the context
+
+        if (!ctx.e_internalized(n1)) {
+            TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;);
+            ctx.internalize(n1, false);
+        }
+        if (!ctx.e_internalized(n2)) {
+            TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;);
+            ctx.internalize(n2, false);
+        }
+
+        expr * curr = get_eqc_next(n1);
+        while (curr != n1) {
+            if (curr == n2)
+                return true;
+            curr = get_eqc_next(curr);
+        }
+        return false;
+    }
+
+    expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) {
+        context & ctx = get_context();
+        expr * constStrNode = NULL;
+
+        expr * ex = n;
+        do {
+            if (u.str.is_string(to_app(ex))) {
+                constStrNode = ex;
             }
-        } // for (itor1 : contains_map)
-    } // if varNode in contain_pair_idx_map
-}
+            eqcSet.push_back(ex);
 
-void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-    expr_ref_vector litems(m);
+            ex = get_eqc_next(ex);
+        } while (ex != n);
+        return constStrNode;
+    }
 
-    if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) {
-        std::set<std::pair<expr*, expr*> >::iterator itor1 = contain_pair_idx_map[varNode].begin();
-        for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) {
-            expr * strAst = itor1->first;
-            expr * substrAst = itor1->second;
-
-            expr * boolVar;
-            if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) {
-                TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;);
+    /*
+     * Collect constant strings (from left to right) in an AST node.
+     */
+    void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) {
+        ast_manager & m = get_manager();
+        if (u.str.is_string(node)) {
+            astList.push_back(node);
+            //} else if (getNodeType(t, node) == my_Z3_Func) {
+        } else if (is_app(node)) {
+            app * func_app = to_app(node);
+            unsigned int argCount = func_app->get_num_args();
+            for (unsigned int i = 0; i < argCount; i++) {
+                expr * argAst = func_app->get_arg(i);
+                get_const_str_asts_in_node(argAst, astList);
             }
-            // boolVar is actually a Contains term
-            app * containsApp = to_app(boolVar);
+        }
+    }
 
-            // we only want to inspect the Contains terms where either of strAst or substrAst
-            // are equal to varNode.
+    void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-            TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;);
+        TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;);
 
-            if (varNode != strAst && varNode != substrAst) {
-                TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;);
-                continue;
-            }
-            TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;);
+        expr_ref_vector litems(m);
 
-            if (substrAst == varNode) {
-                bool strAstHasVal = false;
-                expr * strValue = get_eqc_value(strAst, strAstHasVal);
-                if (strAstHasVal) {
-                    TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;);
-                    if (strValue != strAst) {
-                        litems.push_back(ctx.mk_eq_atom(strAst, strValue));
+        if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) {
+            std::set<std::pair<expr*, expr*> >::iterator itor1 = contain_pair_idx_map[varNode].begin();
+            for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) {
+                expr * strAst = itor1->first;
+                expr * substrAst = itor1->second;
+
+                expr * boolVar;
+                if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) {
+                    TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;);
+                }
+                // boolVar is actually a Contains term
+                app * containsApp = to_app(boolVar);
+
+                // we only want to inspect the Contains terms where either of strAst or substrAst
+                // are equal to varNode.
+
+                TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;);
+
+                if (varNode != strAst && varNode != substrAst) {
+                    TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;);
+                    continue;
+                }
+                TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;);
+
+                // varEqcNode is str
+                if (strAst == varNode) {
+                    expr_ref implyR(m);
+                    litems.reset();
+
+                    if (strAst != constNode) {
+                        litems.push_back(ctx.mk_eq_atom(strAst, constNode));
                     }
                     zstring strConst;
-                    u.str.is_string(strValue, strConst);
-                    // iterate eqc (also eqc-to-be) of substr
-                    for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) {
-                        bool counterEgFound = false;
-                        if (u.str.is_concat(to_app(*itAst))) {
+                    u.str.is_string(constNode, strConst);
+                    bool subStrHasEqcValue = false;
+                    expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue);
+                    if (substrValue != substrAst) {
+                        litems.push_back(ctx.mk_eq_atom(substrAst, substrValue));
+                    }
+
+                    if (subStrHasEqcValue) {
+                        // subStr has an eqc constant value
+                        zstring subStrConst;
+                        u.str.is_string(substrValue, subStrConst);
+
+                        TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";);
+
+                        if (strConst.contains(subStrConst)) {
+                            //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx));
+                            implyR = boolVar;
+                        } else {
+                            //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx));
+                            implyR = m.mk_not(boolVar);
+                        }
+                    } else {
+                        // ------------------------------------------------------------------------------------------------
+                        // subStr doesn't have an eqc contant value
+                        // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n)
+                        // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false
+                        // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet
+                        // ------------------------------------------------------------------------------------------------
+                        // collect eqc concat
+                        std::set<expr*> eqcConcats;
+                        get_concats_in_eqc(substrAst, eqcConcats);
+                        for (std::set<expr*>::iterator concatItor = eqcConcats.begin();
+                             concatItor != eqcConcats.end(); concatItor++) {
                             expr_ref_vector constList(m);
+                            bool counterEgFound = false;
                             // get constant strings in concat
-                            app * aConcat = to_app(*itAst);
+                            expr * aConcat = *concatItor;
                             get_const_str_asts_in_node(aConcat, constList);
                             for (expr_ref_vector::iterator cstItor = constList.begin();
-                                    cstItor != constList.end(); cstItor++) {
+                                 cstItor != constList.end(); cstItor++) {
                                 zstring pieceStr;
                                 u.str.is_string(*cstItor, pieceStr);
                                 if (!strConst.contains(pieceStr)) {
-                                    TRACE("str", tout << "Inconsistency found!" << std::endl;);
                                     counterEgFound = true;
                                     if (aConcat != substrAst) {
                                         litems.push_back(ctx.mk_eq_atom(substrAst, aConcat));
                                     }
-                                    expr_ref implyLHS(mk_and(litems), m);
-                                    expr_ref implyR(m.mk_not(boolVar), m);
-                                    assert_implication(implyLHS, implyR);
+                                    //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx));
+                                    implyR = m.mk_not(boolVar);
                                     break;
                                 }
                             }
-                        }
-                        if (counterEgFound) {
-                            break;
+                            if (counterEgFound) {
+                                TRACE("str", tout << "Inconsistency found!" << std::endl;);
+                                break;
+                            }
                         }
                     }
+                    // add assertion
+                    if (implyR) {
+                        expr_ref implyLHS(mk_and(litems), m);
+                        assert_implication(implyLHS, implyR);
+                    }
                 }
-            }
-        }
-    } // varNode in contain_pair_idx_map
-}
+                // varEqcNode is subStr
+                else if (substrAst == varNode) {
+                    expr_ref implyR(m);
+                    litems.reset();
 
-bool theory_str::in_contain_idx_map(expr * n) {
-    return contain_pair_idx_map.find(n) != contain_pair_idx_map.end();
-}
+                    if (substrAst != constNode) {
+                        litems.push_back(ctx.mk_eq_atom(substrAst, constNode));
+                    }
+                    bool strHasEqcValue = false;
+                    expr * strValue = get_eqc_value(strAst, strHasEqcValue);
+                    if (strValue != strAst) {
+                        litems.push_back(ctx.mk_eq_atom(strAst, strValue));
+                    }
 
-void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
+                    if (strHasEqcValue) {
+                        zstring strConst, subStrConst;
+                        u.str.is_string(strValue, strConst);
+                        u.str.is_string(constNode, subStrConst);
+                        if (strConst.contains(subStrConst)) {
+                            //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx));
+                            implyR = boolVar;
+                        } else {
+                            // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx));
+                            implyR = m.mk_not(boolVar);
+                        }
+                    }
 
-    if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) {
-        std::set<std::pair<expr*, expr*> >::iterator keysItor1 = contain_pair_idx_map[n1].begin();
-        std::set<std::pair<expr*, expr*> >::iterator keysItor2;
-
-        for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) {
-            // keysItor1 is on set {<.., n1>, ..., <n1, ...>, ...}
-            std::pair<expr*, expr*> key1 = *keysItor1;
-            if (key1.first == n1 && key1.second == n2) {
-                expr_ref implyL(m);
-                expr_ref implyR(contain_pair_bool_map[key1], m);
-                if (n1 != n2) {
-                    implyL = ctx.mk_eq_atom(n1, n2);
-                    assert_implication(implyL, implyR);
-                } else {
-                    assert_axiom(implyR);
+                    // add assertion
+                    if (implyR) {
+                        expr_ref implyLHS(mk_and(litems), m);
+                        assert_implication(implyLHS, implyR);
+                    }
                 }
-            }
+            } // for (itor1 : contains_map)
+        } // if varNode in contain_pair_idx_map
+    }
 
-            for (keysItor2 = contain_pair_idx_map[n2].begin();
-                    keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) {
-                // keysItor2 is on set {<.., n2>, ..., <n2, ...>, ...}
-                std::pair<expr*, expr*> key2 = *keysItor2;
-                // skip if the pair is eq
-                if (key1 == key2) {
+    void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        expr_ref_vector litems(m);
+
+        if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) {
+            std::set<std::pair<expr*, expr*> >::iterator itor1 = contain_pair_idx_map[varNode].begin();
+            for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) {
+                expr * strAst = itor1->first;
+                expr * substrAst = itor1->second;
+
+                expr * boolVar;
+                if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) {
+                    TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;);
+                }
+                // boolVar is actually a Contains term
+                app * containsApp = to_app(boolVar);
+
+                // we only want to inspect the Contains terms where either of strAst or substrAst
+                // are equal to varNode.
+
+                TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;);
+
+                if (varNode != strAst && varNode != substrAst) {
+                    TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;);
                     continue;
                 }
+                TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;);
 
-                // ***************************
-                // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n
-                // ***************************
-                if (key1.first == n1 && key2.first == n2) {
-                    expr * subAst1 = key1.second;
-                    expr * subAst2 = key2.second;
-                    bool subAst1HasValue = false;
-                    bool subAst2HasValue = false;
-                    expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue);
-                    expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue);
-
-                    TRACE("str",
-                            tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl;
-                            tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl;
-                            if (subAst1 != subValue1) {
-                                tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl;
+                if (substrAst == varNode) {
+                    bool strAstHasVal = false;
+                    expr * strValue = get_eqc_value(strAst, strAstHasVal);
+                    if (strAstHasVal) {
+                        TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;);
+                        if (strValue != strAst) {
+                            litems.push_back(ctx.mk_eq_atom(strAst, strValue));
+                        }
+                        zstring strConst;
+                        u.str.is_string(strValue, strConst);
+                        // iterate eqc (also eqc-to-be) of substr
+                        for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) {
+                            bool counterEgFound = false;
+                            if (u.str.is_concat(to_app(*itAst))) {
+                                expr_ref_vector constList(m);
+                                // get constant strings in concat
+                                app * aConcat = to_app(*itAst);
+                                get_const_str_asts_in_node(aConcat, constList);
+                                for (expr_ref_vector::iterator cstItor = constList.begin();
+                                     cstItor != constList.end(); cstItor++) {
+                                    zstring pieceStr;
+                                    u.str.is_string(*cstItor, pieceStr);
+                                    if (!strConst.contains(pieceStr)) {
+                                        TRACE("str", tout << "Inconsistency found!" << std::endl;);
+                                        counterEgFound = true;
+                                        if (aConcat != substrAst) {
+                                            litems.push_back(ctx.mk_eq_atom(substrAst, aConcat));
+                                        }
+                                        expr_ref implyLHS(mk_and(litems), m);
+                                        expr_ref implyR(m.mk_not(boolVar), m);
+                                        assert_implication(implyLHS, implyR);
+                                        break;
+                                    }
+                                }
                             }
-                            if (subAst2 != subValue2) {
-                                tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl;
-                            }
-                            );
-
-                    if (subAst1HasValue && subAst2HasValue) {
-                        expr_ref_vector litems1(m);
-                        if (n1 != n2) {
-                            litems1.push_back(ctx.mk_eq_atom(n1, n2));
-                        }
-                        if (subValue1 != subAst1) {
-                            litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1));
-                        }
-                        if (subValue2 != subAst2) {
-                            litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2));
-                        }
-
-                        zstring subConst1, subConst2;
-                        u.str.is_string(subValue1, subConst1);
-                        u.str.is_string(subValue2, subConst2);
-                        expr_ref implyR(m);
-                        if (subConst1 == subConst2) {
-                            // key1.first = key2.first /\ key1.second = key2.second
-                            // ==> (containPairBoolMap[key1] = containPairBoolMap[key2])
-                            implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
-                        } else if (subConst1.contains(subConst2)) {
-                            // key1.first = key2.first /\ Contains(key1.second, key2.second)
-                            // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2])
-                            implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
-                        } else if (subConst2.contains(subConst1)) {
-                            // key1.first = key2.first /\ Contains(key2.second, key1.second)
-                            // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1])
-                            implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]);
-                        }
-
-                        if (implyR) {
-                            if (litems1.empty()) {
-                                assert_axiom(implyR);
-                            } else {
-                                assert_implication(mk_and(litems1), implyR);
+                            if (counterEgFound) {
+                                break;
                             }
                         }
+                    }
+                }
+            }
+        } // varNode in contain_pair_idx_map
+    }
+
+    bool theory_str::in_contain_idx_map(expr * n) {
+        return contain_pair_idx_map.find(n) != contain_pair_idx_map.end();
+    }
+
+    void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) {
+            std::set<std::pair<expr*, expr*> >::iterator keysItor1 = contain_pair_idx_map[n1].begin();
+            std::set<std::pair<expr*, expr*> >::iterator keysItor2;
+
+            for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) {
+                // keysItor1 is on set {<.., n1>, ..., <n1, ...>, ...}
+                std::pair<expr*, expr*> key1 = *keysItor1;
+                if (key1.first == n1 && key1.second == n2) {
+                    expr_ref implyL(m);
+                    expr_ref implyR(contain_pair_bool_map[key1], m);
+                    if (n1 != n2) {
+                        implyL = ctx.mk_eq_atom(n1, n2);
+                        assert_implication(implyL, implyR);
                     } else {
-                        expr_ref_vector subAst1Eqc(m);
-                        expr_ref_vector subAst2Eqc(m);
-                        collect_eq_nodes(subAst1, subAst1Eqc);
-                        collect_eq_nodes(subAst2, subAst2Eqc);
+                        assert_axiom(implyR);
+                    }
+                }
 
-                        if (subAst1Eqc.contains(subAst2)) {
-                            // -----------------------------------------------------------
-                            // * key1.first = key2.first /\ key1.second = key2.second
-                            //   -->  containPairBoolMap[key1] = containPairBoolMap[key2]
-                            // -----------------------------------------------------------
-                            expr_ref_vector litems2(m);
+                for (keysItor2 = contain_pair_idx_map[n2].begin();
+                     keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) {
+                    // keysItor2 is on set {<.., n2>, ..., <n2, ...>, ...}
+                    std::pair<expr*, expr*> key2 = *keysItor2;
+                    // skip if the pair is eq
+                    if (key1 == key2) {
+                        continue;
+                    }
+
+                    // ***************************
+                    // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n
+                    // ***************************
+                    if (key1.first == n1 && key2.first == n2) {
+                        expr * subAst1 = key1.second;
+                        expr * subAst2 = key2.second;
+                        bool subAst1HasValue = false;
+                        bool subAst2HasValue = false;
+                        expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue);
+                        expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue);
+
+                        TRACE("str",
+                              tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl;
+                              tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl;
+                              if (subAst1 != subValue1) {
+                                  tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl;
+                              }
+                              if (subAst2 != subValue2) {
+                                  tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl;
+                              }
+                              );
+
+                        if (subAst1HasValue && subAst2HasValue) {
+                            expr_ref_vector litems1(m);
                             if (n1 != n2) {
-                                litems2.push_back(ctx.mk_eq_atom(n1, n2));
+                                litems1.push_back(ctx.mk_eq_atom(n1, n2));
                             }
-                            if (subAst1 != subAst2) {
-                                litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2));
+                            if (subValue1 != subAst1) {
+                                litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1));
                             }
-                            expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
-                            if (litems2.empty()) {
-                                assert_axiom(implyR);
-                            } else {
-                                assert_implication(mk_and(litems2), implyR);
+                            if (subValue2 != subAst2) {
+                                litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2));
+                            }
+
+                            zstring subConst1, subConst2;
+                            u.str.is_string(subValue1, subConst1);
+                            u.str.is_string(subValue2, subConst2);
+                            expr_ref implyR(m);
+                            if (subConst1 == subConst2) {
+                                // key1.first = key2.first /\ key1.second = key2.second
+                                // ==> (containPairBoolMap[key1] = containPairBoolMap[key2])
+                                implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
+                            } else if (subConst1.contains(subConst2)) {
+                                // key1.first = key2.first /\ Contains(key1.second, key2.second)
+                                // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2])
+                                implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
+                            } else if (subConst2.contains(subConst1)) {
+                                // key1.first = key2.first /\ Contains(key2.second, key1.second)
+                                // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1])
+                                implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]);
+                            }
+
+                            if (implyR) {
+                                if (litems1.empty()) {
+                                    assert_axiom(implyR);
+                                } else {
+                                    assert_implication(mk_and(litems1), implyR);
+                                }
                             }
                         } else {
-                            // -----------------------------------------------------------
-                            // * key1.first = key2.first
-                            //   check eqc(key1.second) and eqc(key2.second)
-                            // -----------------------------------------------------------
-                            expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin();
-                            for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) {
-                                expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin();
-                                for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) {
-                                    // ------------
-                                    // key1.first = key2.first /\ containPairBoolMap[<eqc(key1.second), eqc(key2.second)>]
-                                    // ==>  (containPairBoolMap[key1] --> containPairBoolMap[key2])
-                                    // ------------
-                                    {
-                                        expr_ref_vector litems3(m);
-                                        if (n1 != n2) {
-                                            litems3.push_back(ctx.mk_eq_atom(n1, n2));
+                            expr_ref_vector subAst1Eqc(m);
+                            expr_ref_vector subAst2Eqc(m);
+                            collect_eq_nodes(subAst1, subAst1Eqc);
+                            collect_eq_nodes(subAst2, subAst2Eqc);
+
+                            if (subAst1Eqc.contains(subAst2)) {
+                                // -----------------------------------------------------------
+                                // * key1.first = key2.first /\ key1.second = key2.second
+                                //   -->  containPairBoolMap[key1] = containPairBoolMap[key2]
+                                // -----------------------------------------------------------
+                                expr_ref_vector litems2(m);
+                                if (n1 != n2) {
+                                    litems2.push_back(ctx.mk_eq_atom(n1, n2));
+                                }
+                                if (subAst1 != subAst2) {
+                                    litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2));
+                                }
+                                expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
+                                if (litems2.empty()) {
+                                    assert_axiom(implyR);
+                                } else {
+                                    assert_implication(mk_and(litems2), implyR);
+                                }
+                            } else {
+                                // -----------------------------------------------------------
+                                // * key1.first = key2.first
+                                //   check eqc(key1.second) and eqc(key2.second)
+                                // -----------------------------------------------------------
+                                expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin();
+                                for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) {
+                                    expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin();
+                                    for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) {
+                                        // ------------
+                                        // key1.first = key2.first /\ containPairBoolMap[<eqc(key1.second), eqc(key2.second)>]
+                                        // ==>  (containPairBoolMap[key1] --> containPairBoolMap[key2])
+                                        // ------------
+                                        {
+                                            expr_ref_vector litems3(m);
+                                            if (n1 != n2) {
+                                                litems3.push_back(ctx.mk_eq_atom(n1, n2));
+                                            }
+                                            expr * eqSubVar1 = *eqItorSub1;
+                                            if (eqSubVar1 != subAst1) {
+                                                litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1));
+                                            }
+                                            expr * eqSubVar2 = *eqItorSub2;
+                                            if (eqSubVar2 != subAst2) {
+                                                litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2));
+                                            }
+                                            std::pair<expr*, expr*> tryKey1 = std::make_pair(eqSubVar1, eqSubVar2);
+                                            if (contain_pair_bool_map.contains(tryKey1)) {
+                                                TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;);
+                                                litems3.push_back(contain_pair_bool_map[tryKey1]);
+                                                expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
+                                                assert_implication(mk_and(litems3), implR);
+                                            }
                                         }
-                                        expr * eqSubVar1 = *eqItorSub1;
-                                        if (eqSubVar1 != subAst1) {
-                                            litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1));
-                                        }
-                                        expr * eqSubVar2 = *eqItorSub2;
-                                        if (eqSubVar2 != subAst2) {
-                                            litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2));
-                                        }
-                                        std::pair<expr*, expr*> tryKey1 = std::make_pair(eqSubVar1, eqSubVar2);
-                                        if (contain_pair_bool_map.contains(tryKey1)) {
-                                            TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;);
-                                            litems3.push_back(contain_pair_bool_map[tryKey1]);
-                                            expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
-                                            assert_implication(mk_and(litems3), implR);
-                                        }
-                                    }
-                                    // ------------
-                                    // key1.first = key2.first /\ containPairBoolMap[<eqc(key2.second), eqc(key1.second)>]
-                                    // ==>  (containPairBoolMap[key2] --> containPairBoolMap[key1])
-                                    // ------------
-                                    {
-                                        expr_ref_vector litems4(m);
-                                        if (n1 != n2) {
-                                            litems4.push_back(ctx.mk_eq_atom(n1, n2));
-                                        }
-                                        expr * eqSubVar1 = *eqItorSub1;
-                                        if (eqSubVar1 != subAst1) {
-                                            litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1));
-                                        }
-                                        expr * eqSubVar2 = *eqItorSub2;
-                                        if (eqSubVar2 != subAst2) {
-                                            litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2));
-                                        }
-                                        std::pair<expr*, expr*> tryKey2 = std::make_pair(eqSubVar2, eqSubVar1);
-                                        if (contain_pair_bool_map.contains(tryKey2)) {
-                                            TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;);
-                                            litems4.push_back(contain_pair_bool_map[tryKey2]);
-                                            expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m);
-                                            assert_implication(mk_and(litems4), implR);
+                                        // ------------
+                                        // key1.first = key2.first /\ containPairBoolMap[<eqc(key2.second), eqc(key1.second)>]
+                                        // ==>  (containPairBoolMap[key2] --> containPairBoolMap[key1])
+                                        // ------------
+                                        {
+                                            expr_ref_vector litems4(m);
+                                            if (n1 != n2) {
+                                                litems4.push_back(ctx.mk_eq_atom(n1, n2));
+                                            }
+                                            expr * eqSubVar1 = *eqItorSub1;
+                                            if (eqSubVar1 != subAst1) {
+                                                litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1));
+                                            }
+                                            expr * eqSubVar2 = *eqItorSub2;
+                                            if (eqSubVar2 != subAst2) {
+                                                litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2));
+                                            }
+                                            std::pair<expr*, expr*> tryKey2 = std::make_pair(eqSubVar2, eqSubVar1);
+                                            if (contain_pair_bool_map.contains(tryKey2)) {
+                                                TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;);
+                                                litems4.push_back(contain_pair_bool_map[tryKey2]);
+                                                expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m);
+                                                assert_implication(mk_and(litems4), implR);
+                                            }
                                         }
                                     }
                                 }
                             }
                         }
                     }
-                }
-                // ***************************
-                // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n
-                // ***************************
-                else if (key1.second == n1 && key2.second == n2) {
-                    expr * str1 = key1.first;
-                    expr * str2 = key2.first;
-                    bool str1HasValue = false;
-                    bool str2HasValue = false;
-                    expr * strVal1 = get_eqc_value(str1, str1HasValue);
-                    expr * strVal2 = get_eqc_value(str2, str2HasValue);
+                    // ***************************
+                    // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n
+                    // ***************************
+                    else if (key1.second == n1 && key2.second == n2) {
+                        expr * str1 = key1.first;
+                        expr * str2 = key2.first;
+                        bool str1HasValue = false;
+                        bool str2HasValue = false;
+                        expr * strVal1 = get_eqc_value(str1, str1HasValue);
+                        expr * strVal2 = get_eqc_value(str2, str2HasValue);
 
-                    TRACE("str",
-                            tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl;
-                            tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl;
-                            if (str1 != strVal1) {
-                                tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl;
-                            }
-                            if (str2 != strVal2) {
-                                tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl;
-                            }
-                            );
+                        TRACE("str",
+                              tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl;
+                              tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl;
+                              if (str1 != strVal1) {
+                                  tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl;
+                              }
+                              if (str2 != strVal2) {
+                                  tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl;
+                              }
+                              );
 
-                    if (str1HasValue && str2HasValue) {
-                        expr_ref_vector litems1(m);
-                        if (n1 != n2) {
-                            litems1.push_back(ctx.mk_eq_atom(n1, n2));
-                        }
-                        if (strVal1 != str1) {
-                            litems1.push_back(ctx.mk_eq_atom(str1, strVal1));
-                        }
-                        if (strVal2 != str2) {
-                            litems1.push_back(ctx.mk_eq_atom(str2, strVal2));
-                        }
-
-                        zstring const1, const2;
-                        u.str.is_string(strVal1, const1);
-                        u.str.is_string(strVal2, const2);
-                        expr_ref implyR(m);
-
-                        if (const1 == const2) {
-                            // key1.second = key2.second /\ key1.first = key2.first
-                            // ==> (containPairBoolMap[key1] = containPairBoolMap[key2])
-                            implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
-                        } else if (const1.contains(const2)) {
-                            // key1.second = key2.second /\ Contains(key1.first, key2.first)
-                            // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1])
-                            implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]);
-                        } else if (const2.contains(const1)) {
-                            // key1.first = key2.first /\ Contains(key2.first, key1.first)
-                            // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2])
-                            implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
-                        }
-
-                        if (implyR) {
-                            if (litems1.size() == 0) {
-                                assert_axiom(implyR);
-                            } else {
-                                assert_implication(mk_and(litems1), implyR);
-                            }
-                        }
-                    }
-
-                    else {
-                        expr_ref_vector str1Eqc(m);
-                        expr_ref_vector str2Eqc(m);
-                        collect_eq_nodes(str1, str1Eqc);
-                        collect_eq_nodes(str2, str2Eqc);
-
-                        if (str1Eqc.contains(str2)) {
-                            // -----------------------------------------------------------
-                            // * key1.first = key2.first /\ key1.second = key2.second
-                            //   -->  containPairBoolMap[key1] = containPairBoolMap[key2]
-                            // -----------------------------------------------------------
-                            expr_ref_vector litems2(m);
+                        if (str1HasValue && str2HasValue) {
+                            expr_ref_vector litems1(m);
                             if (n1 != n2) {
-                                litems2.push_back(ctx.mk_eq_atom(n1, n2));
+                                litems1.push_back(ctx.mk_eq_atom(n1, n2));
                             }
-                            if (str1 != str2) {
-                                litems2.push_back(ctx.mk_eq_atom(str1, str2));
+                            if (strVal1 != str1) {
+                                litems1.push_back(ctx.mk_eq_atom(str1, strVal1));
                             }
-                            expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
-                            if (litems2.empty()) {
-                                assert_axiom(implyR);
+                            if (strVal2 != str2) {
+                                litems1.push_back(ctx.mk_eq_atom(str2, strVal2));
+                            }
+
+                            zstring const1, const2;
+                            u.str.is_string(strVal1, const1);
+                            u.str.is_string(strVal2, const2);
+                            expr_ref implyR(m);
+
+                            if (const1 == const2) {
+                                // key1.second = key2.second /\ key1.first = key2.first
+                                // ==> (containPairBoolMap[key1] = containPairBoolMap[key2])
+                                implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
+                            } else if (const1.contains(const2)) {
+                                // key1.second = key2.second /\ Contains(key1.first, key2.first)
+                                // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1])
+                                implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]);
+                            } else if (const2.contains(const1)) {
+                                // key1.first = key2.first /\ Contains(key2.first, key1.first)
+                                // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2])
+                                implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]);
+                            }
+
+                            if (implyR) {
+                                if (litems1.size() == 0) {
+                                    assert_axiom(implyR);
+                                } else {
+                                    assert_implication(mk_and(litems1), implyR);
+                                }
+                            }
+                        }
+
+                        else {
+                            expr_ref_vector str1Eqc(m);
+                            expr_ref_vector str2Eqc(m);
+                            collect_eq_nodes(str1, str1Eqc);
+                            collect_eq_nodes(str2, str2Eqc);
+
+                            if (str1Eqc.contains(str2)) {
+                                // -----------------------------------------------------------
+                                // * key1.first = key2.first /\ key1.second = key2.second
+                                //   -->  containPairBoolMap[key1] = containPairBoolMap[key2]
+                                // -----------------------------------------------------------
+                                expr_ref_vector litems2(m);
+                                if (n1 != n2) {
+                                    litems2.push_back(ctx.mk_eq_atom(n1, n2));
+                                }
+                                if (str1 != str2) {
+                                    litems2.push_back(ctx.mk_eq_atom(str1, str2));
+                                }
+                                expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
+                                if (litems2.empty()) {
+                                    assert_axiom(implyR);
+                                } else {
+                                    assert_implication(mk_and(litems2), implyR);
+                                }
                             } else {
-                                assert_implication(mk_and(litems2), implyR);
-                            }
-                        } else {
-                            // -----------------------------------------------------------
-                            // * key1.second = key2.second
-                            //   check eqc(key1.first) and eqc(key2.first)
-                            // -----------------------------------------------------------
-                            expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin();
-                            for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) {
-                                expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin();
-                                for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) {
-                                    {
-                                        expr_ref_vector litems3(m);
-                                        if (n1 != n2) {
-                                            litems3.push_back(ctx.mk_eq_atom(n1, n2));
-                                        }
-                                        expr * eqStrVar1 = *eqItorStr1;
-                                        if (eqStrVar1 != str1) {
-                                            litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1));
-                                        }
-                                        expr * eqStrVar2 = *eqItorStr2;
-                                        if (eqStrVar2 != str2) {
-                                            litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2));
-                                        }
-                                        std::pair<expr*, expr*> tryKey1 = std::make_pair(eqStrVar1, eqStrVar2);
-                                        if (contain_pair_bool_map.contains(tryKey1)) {
-                                            TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;);
-                                            litems3.push_back(contain_pair_bool_map[tryKey1]);
+                                // -----------------------------------------------------------
+                                // * key1.second = key2.second
+                                //   check eqc(key1.first) and eqc(key2.first)
+                                // -----------------------------------------------------------
+                                expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin();
+                                for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) {
+                                    expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin();
+                                    for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) {
+                                        {
+                                            expr_ref_vector litems3(m);
+                                            if (n1 != n2) {
+                                                litems3.push_back(ctx.mk_eq_atom(n1, n2));
+                                            }
+                                            expr * eqStrVar1 = *eqItorStr1;
+                                            if (eqStrVar1 != str1) {
+                                                litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1));
+                                            }
+                                            expr * eqStrVar2 = *eqItorStr2;
+                                            if (eqStrVar2 != str2) {
+                                                litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2));
+                                            }
+                                            std::pair<expr*, expr*> tryKey1 = std::make_pair(eqStrVar1, eqStrVar2);
+                                            if (contain_pair_bool_map.contains(tryKey1)) {
+                                                TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;);
+                                                litems3.push_back(contain_pair_bool_map[tryKey1]);
 
-                                            // ------------
-                                            // key1.second = key2.second /\ containPairBoolMap[<eqc(key1.first), eqc(key2.first)>]
-                                            // ==>  (containPairBoolMap[key2] --> containPairBoolMap[key1])
-                                            // ------------
-                                            expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m);
-                                            assert_implication(mk_and(litems3), implR);
+                                                // ------------
+                                                // key1.second = key2.second /\ containPairBoolMap[<eqc(key1.first), eqc(key2.first)>]
+                                                // ==>  (containPairBoolMap[key2] --> containPairBoolMap[key1])
+                                                // ------------
+                                                expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m);
+                                                assert_implication(mk_and(litems3), implR);
+                                            }
                                         }
-                                    }
 
-                                    {
-                                        expr_ref_vector litems4(m);
-                                        if (n1 != n2) {
-                                            litems4.push_back(ctx.mk_eq_atom(n1, n2));
-                                        }
-                                        expr * eqStrVar1 = *eqItorStr1;
-                                        if (eqStrVar1 != str1) {
-                                            litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1));
-                                        }
-                                        expr *eqStrVar2 = *eqItorStr2;
-                                        if (eqStrVar2 != str2) {
-                                            litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2));
-                                        }
-                                        std::pair<expr*, expr*> tryKey2 = std::make_pair(eqStrVar2, eqStrVar1);
+                                        {
+                                            expr_ref_vector litems4(m);
+                                            if (n1 != n2) {
+                                                litems4.push_back(ctx.mk_eq_atom(n1, n2));
+                                            }
+                                            expr * eqStrVar1 = *eqItorStr1;
+                                            if (eqStrVar1 != str1) {
+                                                litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1));
+                                            }
+                                            expr *eqStrVar2 = *eqItorStr2;
+                                            if (eqStrVar2 != str2) {
+                                                litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2));
+                                            }
+                                            std::pair<expr*, expr*> tryKey2 = std::make_pair(eqStrVar2, eqStrVar1);
 
-                                        if (contain_pair_bool_map.contains(tryKey2)) {
-                                            TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;);
-                                            litems4.push_back(contain_pair_bool_map[tryKey2]);
-                                            // ------------
-                                            // key1.first = key2.first /\ containPairBoolMap[<eqc(key2.second), eqc(key1.second)>]
-                                            // ==>  (containPairBoolMap[key1] --> containPairBoolMap[key2])
-                                            // ------------
-                                            expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
-                                            assert_implication(mk_and(litems4), implR);
+                                            if (contain_pair_bool_map.contains(tryKey2)) {
+                                                TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;);
+                                                litems4.push_back(contain_pair_bool_map[tryKey2]);
+                                                // ------------
+                                                // key1.first = key2.first /\ containPairBoolMap[<eqc(key2.second), eqc(key1.second)>]
+                                                // ==>  (containPairBoolMap[key1] --> containPairBoolMap[key2])
+                                                // ------------
+                                                expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m);
+                                                assert_implication(mk_and(litems4), implR);
+                                            }
                                         }
                                     }
                                 }
                             }
                         }
-                    }
 
-                }
-            }
-
-            if (n1 == n2) {
-                break;
-            }
-        }
-    } // (in_contain_idx_map(n1) && in_contain_idx_map(n2))
-}
-
-void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) {
-    if (contains_map.empty()) {
-        return;
-    }
-
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-    TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;);
-
-    expr_ref_vector willEqClass(m);
-    expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass);
-    expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass);
-    expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2;
-
-    TRACE("str", tout << "eqc of n1 is {";
-            for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) {
-                expr * el = *it;
-                tout << " " << mk_pp(el, m);
-            }
-            tout << std::endl;
-            if (constStrAst == NULL) {
-                tout << "constStrAst = NULL" << std::endl;
-            } else {
-                tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl;
-            }
-            );
-
-    // step 1: we may have constant values for Contains checks now
-    if (constStrAst != NULL) {
-        expr_ref_vector::iterator itAst = willEqClass.begin();
-        for (; itAst != willEqClass.end(); itAst++) {
-            if (*itAst == constStrAst) {
-                continue;
-            }
-            check_contain_by_eqc_val(*itAst, constStrAst);
-        }
-    } else {
-        // no concrete value to be put in eqc, solely based on context
-        // Check here is used to detected the facts as follows:
-        //   * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M
-        //   * new fact: M = concat(..., "jio", ...)
-        // Note that in this branch, either M or concat(..., "jio", ...) has a constant value
-        // So, only need to check
-        //   * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and
-        //   * If strAst registered has an eqc constant in the context
-        // -------------------------------------------------------------
-        expr_ref_vector::iterator itAst = willEqClass.begin();
-        for (; itAst != willEqClass.end(); ++itAst) {
-            check_contain_by_substr(*itAst, willEqClass);
-        }
-    }
-
-    // ------------------------------------------
-    // step 2: check for b1 = contains(x, m), b2 = contains(y, n)
-    //         (1) x = y /\ m = n  ==>  b1 = b2
-    //         (2) x = y /\ Contains(const(m), const(n))  ==>  (b1 -> b2)
-    //         (3) x = y /\ Contains(const(n), const(m))  ==>  (b2 -> b1)
-    //         (4) x = y /\ containPairBoolMap[<eqc(m), eqc(n)>]  ==>  (b1 -> b2)
-    //         (5) x = y /\ containPairBoolMap[<eqc(n), eqc(m)>]  ==>  (b2 -> b1)
-    //         (6) Contains(const(x), const(y)) /\ m = n  ==>  (b2 -> b1)
-    //         (7) Contains(const(y), const(x)) /\ m = n  ==>  (b1 -> b2)
-    //         (8) containPairBoolMap[<eqc(x), eqc(y)>] /\ m = n  ==>  (b2 -> b1)
-    //         (9) containPairBoolMap[<eqc(y), eqc(x)>] /\ m = n  ==>  (b1 -> b2)
-    // ------------------------------------------
-
-    expr_ref_vector::iterator varItor1 = willEqClass.begin();
-    for (; varItor1 != willEqClass.end(); ++varItor1) {
-        expr * varAst1 = *varItor1;
-        expr_ref_vector::iterator varItor2 = varItor1;
-        for (; varItor2 != willEqClass.end(); ++varItor2) {
-            expr * varAst2 = *varItor2;
-            check_contain_by_eq_nodes(varAst1, varAst2);
-        }
-    }
-}
-
-expr * theory_str::dealias_node(expr * node, std::map<expr*, expr*> & varAliasMap, std::map<expr*, expr*> & concatAliasMap) {
-    if (variable_set.find(node) != variable_set.end()) {
-        return get_alias_index_ast(varAliasMap, node);
-    } else if (u.str.is_concat(to_app(node))) {
-        return get_alias_index_ast(concatAliasMap, node);
-    }
-    return node;
-}
-
-void theory_str::get_grounded_concats(expr* node, std::map<expr*, expr*> & varAliasMap,
-        std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr*> & varConstMap,
-        std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap,
-        std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap) {
-    if (u.re.is_unroll(to_app(node))) {
-        return;
-    }
-    // **************************************************
-    // first deAlias the node if it is a var or concat
-    // **************************************************
-    node = dealias_node(node, varAliasMap, concatAliasMap);
-
-    if (groundedMap.find(node) != groundedMap.end()) {
-        return;
-    }
-
-    // haven't computed grounded concats for "node" (de-aliased)
-    // ---------------------------------------------------------
-
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    // const strings: node is de-aliased
-    if (u.str.is_string(node)) {
-        std::vector<expr*> concatNodes;
-        concatNodes.push_back(node);
-        groundedMap[node][concatNodes].clear();   // no condition
-    }
-    // Concat functions
-    else if (u.str.is_concat(to_app(node))) {
-        // if "node" equals to a constant string, thenjust push the constant into the concat vector
-        // Again "node" has been de-aliased at the very beginning
-        if (concatConstMap.find(node) != concatConstMap.end()) {
-            std::vector<expr*> concatNodes;
-            concatNodes.push_back(concatConstMap[node]);
-            groundedMap[node][concatNodes].clear();
-            groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node]));
-        }
-        // node doesn't have eq constant value. Process its children.
-        else {
-            // merge arg0 and arg1
-            expr * arg0 = to_app(node)->get_arg(0);
-            expr * arg1 = to_app(node)->get_arg(1);
-            expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap);
-            expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap);
-            get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
-            get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
-
-            std::map<std::vector<expr*>, std::set<expr*> >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin();
-            std::map<std::vector<expr*>, std::set<expr*> >::iterator arg1_grdItor;
-            for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) {
-                arg1_grdItor = groundedMap[arg1DeAlias].begin();
-                for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) {
-                    std::vector<expr*> ndVec;
-                    ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end());
-                    int arg0VecSize = arg0_grdItor->first.size();
-                    int arg1VecSize = arg1_grdItor->first.size();
-                    if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) {
-                        ndVec.pop_back();
-                        ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0]));
-                        for (int i = 1; i < arg1VecSize; i++) {
-                            ndVec.push_back(arg1_grdItor->first[i]);
-                        }
-                    } else {
-                        ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end());
-                    }
-                    // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough
-                    if (groundedMap[node].find(ndVec) == groundedMap[node].end()) {
-                        groundedMap[node][ndVec];
-                        if (arg0 != arg0DeAlias) {
-                            groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias));
-                        }
-                        groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end());
-
-                        if (arg1 != arg1DeAlias) {
-                            groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias));
-                        }
-                        groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end());
                     }
                 }
+
+                if (n1 == n2) {
+                    break;
+                }
+            }
+        } // (in_contain_idx_map(n1) && in_contain_idx_map(n2))
+    }
+
+    void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) {
+        if (contains_map.empty()) {
+            return;
+        }
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;);
+
+        expr_ref_vector willEqClass(m);
+        expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass);
+        expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass);
+        expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2;
+
+        TRACE("str", tout << "eqc of n1 is {";
+              for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) {
+                  expr * el = *it;
+                  tout << " " << mk_pp(el, m);
+              }
+              tout << std::endl;
+              if (constStrAst == NULL) {
+                  tout << "constStrAst = NULL" << std::endl;
+              } else {
+                  tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl;
+              }
+              );
+
+        // step 1: we may have constant values for Contains checks now
+        if (constStrAst != NULL) {
+            expr_ref_vector::iterator itAst = willEqClass.begin();
+            for (; itAst != willEqClass.end(); itAst++) {
+                if (*itAst == constStrAst) {
+                    continue;
+                }
+                check_contain_by_eqc_val(*itAst, constStrAst);
+            }
+        } else {
+            // no concrete value to be put in eqc, solely based on context
+            // Check here is used to detected the facts as follows:
+            //   * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M
+            //   * new fact: M = concat(..., "jio", ...)
+            // Note that in this branch, either M or concat(..., "jio", ...) has a constant value
+            // So, only need to check
+            //   * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and
+            //   * If strAst registered has an eqc constant in the context
+            // -------------------------------------------------------------
+            expr_ref_vector::iterator itAst = willEqClass.begin();
+            for (; itAst != willEqClass.end(); ++itAst) {
+                check_contain_by_substr(*itAst, willEqClass);
+            }
+        }
+
+        // ------------------------------------------
+        // step 2: check for b1 = contains(x, m), b2 = contains(y, n)
+        //         (1) x = y /\ m = n  ==>  b1 = b2
+        //         (2) x = y /\ Contains(const(m), const(n))  ==>  (b1 -> b2)
+        //         (3) x = y /\ Contains(const(n), const(m))  ==>  (b2 -> b1)
+        //         (4) x = y /\ containPairBoolMap[<eqc(m), eqc(n)>]  ==>  (b1 -> b2)
+        //         (5) x = y /\ containPairBoolMap[<eqc(n), eqc(m)>]  ==>  (b2 -> b1)
+        //         (6) Contains(const(x), const(y)) /\ m = n  ==>  (b2 -> b1)
+        //         (7) Contains(const(y), const(x)) /\ m = n  ==>  (b1 -> b2)
+        //         (8) containPairBoolMap[<eqc(x), eqc(y)>] /\ m = n  ==>  (b2 -> b1)
+        //         (9) containPairBoolMap[<eqc(y), eqc(x)>] /\ m = n  ==>  (b1 -> b2)
+        // ------------------------------------------
+
+        expr_ref_vector::iterator varItor1 = willEqClass.begin();
+        for (; varItor1 != willEqClass.end(); ++varItor1) {
+            expr * varAst1 = *varItor1;
+            expr_ref_vector::iterator varItor2 = varItor1;
+            for (; varItor2 != willEqClass.end(); ++varItor2) {
+                expr * varAst2 = *varItor2;
+                check_contain_by_eq_nodes(varAst1, varAst2);
             }
         }
     }
-    // string variables
-    else if (variable_set.find(node) != variable_set.end()) {
-        // deAliasedVar = Constant
-        if (varConstMap.find(node) != varConstMap.end()) {
-            std::vector<expr*> concatNodes;
-            concatNodes.push_back(varConstMap[node]);
-            groundedMap[node][concatNodes].clear();
-            groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node]));
-        }
-        // deAliasedVar = someConcat
-        else if (varEqConcatMap.find(node) != varEqConcatMap.end()) {
-            expr * eqConcat = varEqConcatMap[node].begin()->first;
-            expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap);
-            get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
 
-            std::map<std::vector<expr*>, std::set<expr*> >::iterator grdItor = groundedMap[deAliasedEqConcat].begin();
-            for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) {
-                std::vector<expr*> ndVec;
-                ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end());
-                // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough
-                if (groundedMap[node].find(ndVec) == groundedMap[node].end()) {
-                    // condition: node = deAliasedEqConcat
-                    groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat));
-                    // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)"
-                    groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end());
-                }
-            }
+    expr * theory_str::dealias_node(expr * node, std::map<expr*, expr*> & varAliasMap, std::map<expr*, expr*> & concatAliasMap) {
+        if (variable_set.find(node) != variable_set.end()) {
+            return get_alias_index_ast(varAliasMap, node);
+        } else if (u.str.is_concat(to_app(node))) {
+            return get_alias_index_ast(concatAliasMap, node);
         }
-        // node (has been de-aliased) != constant && node (has been de-aliased) != any concat
-        // just push in the deAliasedVar
-        else {
+        return node;
+    }
+
+    void theory_str::get_grounded_concats(expr* node, std::map<expr*, expr*> & varAliasMap,
+                                          std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr*> & varConstMap,
+                                          std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap,
+                                          std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap) {
+        if (u.re.is_unroll(to_app(node))) {
+            return;
+        }
+        // **************************************************
+        // first deAlias the node if it is a var or concat
+        // **************************************************
+        node = dealias_node(node, varAliasMap, concatAliasMap);
+
+        if (groundedMap.find(node) != groundedMap.end()) {
+            return;
+        }
+
+        // haven't computed grounded concats for "node" (de-aliased)
+        // ---------------------------------------------------------
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        // const strings: node is de-aliased
+        if (u.str.is_string(node)) {
             std::vector<expr*> concatNodes;
             concatNodes.push_back(node);
-            groundedMap[node][concatNodes];
+            groundedMap[node][concatNodes].clear();   // no condition
+        }
+        // Concat functions
+        else if (u.str.is_concat(to_app(node))) {
+            // if "node" equals to a constant string, thenjust push the constant into the concat vector
+            // Again "node" has been de-aliased at the very beginning
+            if (concatConstMap.find(node) != concatConstMap.end()) {
+                std::vector<expr*> concatNodes;
+                concatNodes.push_back(concatConstMap[node]);
+                groundedMap[node][concatNodes].clear();
+                groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node]));
+            }
+            // node doesn't have eq constant value. Process its children.
+            else {
+                // merge arg0 and arg1
+                expr * arg0 = to_app(node)->get_arg(0);
+                expr * arg1 = to_app(node)->get_arg(1);
+                expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap);
+                expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap);
+                get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
+                get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
+
+                std::map<std::vector<expr*>, std::set<expr*> >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin();
+                std::map<std::vector<expr*>, std::set<expr*> >::iterator arg1_grdItor;
+                for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) {
+                    arg1_grdItor = groundedMap[arg1DeAlias].begin();
+                    for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) {
+                        std::vector<expr*> ndVec;
+                        ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end());
+                        int arg0VecSize = arg0_grdItor->first.size();
+                        int arg1VecSize = arg1_grdItor->first.size();
+                        if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) {
+                            ndVec.pop_back();
+                            ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0]));
+                            for (int i = 1; i < arg1VecSize; i++) {
+                                ndVec.push_back(arg1_grdItor->first[i]);
+                            }
+                        } else {
+                            ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end());
+                        }
+                        // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough
+                        if (groundedMap[node].find(ndVec) == groundedMap[node].end()) {
+                            groundedMap[node][ndVec];
+                            if (arg0 != arg0DeAlias) {
+                                groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias));
+                            }
+                            groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end());
+
+                            if (arg1 != arg1DeAlias) {
+                                groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias));
+                            }
+                            groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end());
+                        }
+                    }
+                }
+            }
+        }
+        // string variables
+        else if (variable_set.find(node) != variable_set.end()) {
+            // deAliasedVar = Constant
+            if (varConstMap.find(node) != varConstMap.end()) {
+                std::vector<expr*> concatNodes;
+                concatNodes.push_back(varConstMap[node]);
+                groundedMap[node][concatNodes].clear();
+                groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node]));
+            }
+            // deAliasedVar = someConcat
+            else if (varEqConcatMap.find(node) != varEqConcatMap.end()) {
+                expr * eqConcat = varEqConcatMap[node].begin()->first;
+                expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap);
+                get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
+
+                std::map<std::vector<expr*>, std::set<expr*> >::iterator grdItor = groundedMap[deAliasedEqConcat].begin();
+                for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) {
+                    std::vector<expr*> ndVec;
+                    ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end());
+                    // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough
+                    if (groundedMap[node].find(ndVec) == groundedMap[node].end()) {
+                        // condition: node = deAliasedEqConcat
+                        groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat));
+                        // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)"
+                        groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end());
+                    }
+                }
+            }
+            // node (has been de-aliased) != constant && node (has been de-aliased) != any concat
+            // just push in the deAliasedVar
+            else {
+                std::vector<expr*> concatNodes;
+                concatNodes.push_back(node);
+                groundedMap[node][concatNodes];
+            }
         }
     }
-}
 
-void theory_str::print_grounded_concat(expr * node, std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap) {
-    ast_manager & m = get_manager();
-    TRACE("str", tout << mk_pp(node, m) << std::endl;);
-    if (groundedMap.find(node) != groundedMap.end()) {
-        std::map<std::vector<expr*>, std::set<expr*> >::iterator itor = groundedMap[node].begin();
-        for (; itor != groundedMap[node].end(); ++itor) {
-            TRACE("str",
-                tout << "\t[grounded] ";
-                std::vector<expr*>::const_iterator vIt = itor->first.begin();
-                for (; vIt != itor->first.end(); ++vIt) {
-                    tout << mk_pp(*vIt, m) << ", ";
-                }
-                tout << std::endl;
-                tout << "\t[condition] ";
-                std::set<expr*>::iterator sIt = itor->second.begin();
-                for (; sIt != itor->second.end(); sIt++) {
-                    tout << mk_pp(*sIt, m) << ", ";
-                }
-                tout << std::endl;
-            );
+    void theory_str::print_grounded_concat(expr * node, std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap) {
+        ast_manager & m = get_manager();
+        TRACE("str", tout << mk_pp(node, m) << std::endl;);
+        if (groundedMap.find(node) != groundedMap.end()) {
+            std::map<std::vector<expr*>, std::set<expr*> >::iterator itor = groundedMap[node].begin();
+            for (; itor != groundedMap[node].end(); ++itor) {
+                TRACE("str",
+                      tout << "\t[grounded] ";
+                      std::vector<expr*>::const_iterator vIt = itor->first.begin();
+                      for (; vIt != itor->first.end(); ++vIt) {
+                          tout << mk_pp(*vIt, m) << ", ";
+                      }
+                      tout << std::endl;
+                      tout << "\t[condition] ";
+                      std::set<expr*>::iterator sIt = itor->second.begin();
+                      for (; sIt != itor->second.end(); sIt++) {
+                          tout << mk_pp(*sIt, m) << ", ";
+                      }
+                      tout << std::endl;
+                      );
+            }
+        } else {
+            TRACE("str", tout << "not found" << std::endl;);
         }
-    } else {
-        TRACE("str", tout << "not found" << std::endl;);
-    }
-}
-
-bool theory_str::is_partial_in_grounded_concat(const std::vector<expr*> & strVec, const std::vector<expr*> & subStrVec) {
-    int strCnt = strVec.size();
-    int subStrCnt = subStrVec.size();
-
-    if (strCnt == 0 || subStrCnt == 0) {
-        return false;
     }
 
-    // The assumption is that all consecutive constant strings are merged into one node
-    if (strCnt < subStrCnt) {
-        return false;
-    }
+    bool theory_str::is_partial_in_grounded_concat(const std::vector<expr*> & strVec, const std::vector<expr*> & subStrVec) {
+        int strCnt = strVec.size();
+        int subStrCnt = subStrVec.size();
 
-    if (subStrCnt == 1) {
-        zstring subStrVal;
-        if (u.str.is_string(subStrVec[0], subStrVal)) {
-            for (int i = 0; i < strCnt; i++) {
-                zstring strVal;
-                if (u.str.is_string(strVec[i], strVal)) {
-                    if (strVal.contains(subStrVal)) {
+        if (strCnt == 0 || subStrCnt == 0) {
+            return false;
+        }
+
+        // The assumption is that all consecutive constant strings are merged into one node
+        if (strCnt < subStrCnt) {
+            return false;
+        }
+
+        if (subStrCnt == 1) {
+            zstring subStrVal;
+            if (u.str.is_string(subStrVec[0], subStrVal)) {
+                for (int i = 0; i < strCnt; i++) {
+                    zstring strVal;
+                    if (u.str.is_string(strVec[i], strVal)) {
+                        if (strVal.contains(subStrVal)) {
+                            return true;
+                        }
+                    }
+                }
+            } else {
+                for (int i = 0; i < strCnt; i++) {
+                    if (strVec[i] == subStrVec[0]) {
                         return true;
                     }
                 }
             }
+            return false;
         } else {
-            for (int i = 0; i < strCnt; i++) {
-                if (strVec[i] == subStrVec[0]) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    } else {
-        for (int i = 0; i <= (strCnt - subStrCnt); i++) {
-            // The first node in subStrVect should be
-            //   * constant: a suffix of a note in strVec[i]
-            //   * variable:
-            bool firstNodesOK = true;
-            zstring subStrHeadVal;
-            if (u.str.is_string(subStrVec[0], subStrHeadVal)) {
-                zstring strHeadVal;
-                if (u.str.is_string(strVec[i], strHeadVal)) {
-                    if (strHeadVal.length() >= subStrHeadVal.length()) {
-                        zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length());
-                        if (suffix != subStrHeadVal) {
+            for (int i = 0; i <= (strCnt - subStrCnt); i++) {
+                // The first node in subStrVect should be
+                //   * constant: a suffix of a note in strVec[i]
+                //   * variable:
+                bool firstNodesOK = true;
+                zstring subStrHeadVal;
+                if (u.str.is_string(subStrVec[0], subStrHeadVal)) {
+                    zstring strHeadVal;
+                    if (u.str.is_string(strVec[i], strHeadVal)) {
+                        if (strHeadVal.length() >= subStrHeadVal.length()) {
+                            zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length());
+                            if (suffix != subStrHeadVal) {
+                                firstNodesOK = false;
+                            }
+                        } else {
                             firstNodesOK = false;
                         }
                     } else {
-                        firstNodesOK = false;
-                    }
-                } else {
-                    if (subStrVec[0] != strVec[i]) {
-                        firstNodesOK = false;
+                        if (subStrVec[0] != strVec[i]) {
+                            firstNodesOK = false;
+                        }
                     }
                 }
-            }
-            if (!firstNodesOK) {
-                continue;
-            }
-
-            // middle nodes
-            bool midNodesOK = true;
-            for (int j = 1; j < subStrCnt - 1; j++) {
-                if (subStrVec[j] != strVec[i + j]) {
-                    midNodesOK = false;
-                    break;
+                if (!firstNodesOK) {
+                    continue;
                 }
-            }
-            if (!midNodesOK) {
-                continue;
-            }
 
-            // tail nodes
-            int tailIdx = i + subStrCnt - 1;
-            zstring subStrTailVal;
-            if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) {
-                zstring strTailVal;
-                if (u.str.is_string(strVec[tailIdx], strTailVal)) {
-                    if (strTailVal.length() >= subStrTailVal.length()) {
-                        zstring prefix = strTailVal.extract(0, subStrTailVal.length());
-                        if (prefix == subStrTailVal) {
-                            return true;
+                // middle nodes
+                bool midNodesOK = true;
+                for (int j = 1; j < subStrCnt - 1; j++) {
+                    if (subStrVec[j] != strVec[i + j]) {
+                        midNodesOK = false;
+                        break;
+                    }
+                }
+                if (!midNodesOK) {
+                    continue;
+                }
+
+                // tail nodes
+                int tailIdx = i + subStrCnt - 1;
+                zstring subStrTailVal;
+                if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) {
+                    zstring strTailVal;
+                    if (u.str.is_string(strVec[tailIdx], strTailVal)) {
+                        if (strTailVal.length() >= subStrTailVal.length()) {
+                            zstring prefix = strTailVal.extract(0, subStrTailVal.length());
+                            if (prefix == subStrTailVal) {
+                                return true;
+                            } else {
+                                continue;
+                            }
                         } else {
                             continue;
                         }
+                    }
+                } else {
+                    if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) {
+                        return true;
                     } else {
                         continue;
                     }
                 }
-            } else {
-                if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) {
-                    return true;
-                } else {
-                    continue;
-                }
             }
-        }
-        return false;
-    }
-}
-
-void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar,
-    std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap) {
-
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-    std::map<std::vector<expr*>, std::set<expr*> >::iterator itorStr = groundedMap[strDeAlias].begin();
-    std::map<std::vector<expr*>, std::set<expr*> >::iterator itorSubStr;
-    for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) {
-        itorSubStr = groundedMap[subStrDeAlias].begin();
-        for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) {
-            bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first);
-            if (contain) {
-                expr_ref_vector litems(m);
-                if (str != strDeAlias) {
-                    litems.push_back(ctx.mk_eq_atom(str, strDeAlias));
-                }
-                if (subStr != subStrDeAlias) {
-                    litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias));
-                }
-
-                //litems.insert(itorStr->second.begin(), itorStr->second.end());
-                //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end());
-                for (std::set<expr*>::const_iterator i1 = itorStr->second.begin();
-                        i1 != itorStr->second.end(); ++i1) {
-                    litems.push_back(*i1);
-                }
-                for (std::set<expr*>::const_iterator i1 = itorSubStr->second.begin();
-                        i1 != itorSubStr->second.end(); ++i1) {
-                    litems.push_back(*i1);
-                }
-
-                expr_ref implyR(boolVar, m);
-
-                if (litems.empty()) {
-                    assert_axiom(implyR);
-                } else {
-                    expr_ref implyL(mk_and(litems), m);
-                    assert_implication(implyL, implyR);
-                }
-
-            }
-        }
-    }
-}
-
-void theory_str::compute_contains(std::map<expr*, expr*> & varAliasMap,
-                std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr*> & varConstMap,
-                std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap) {
-    std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > groundedMap;
-    theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin();
-    for (; containItor != contain_pair_bool_map.end(); containItor++) {
-        expr* containBoolVar = containItor->get_value();
-        expr* str = containItor->get_key1();
-        expr* subStr = containItor->get_key2();
-
-        expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap);
-        expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap);
-
-        get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
-        get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
-
-        // debugging
-        print_grounded_concat(strDeAlias, groundedMap);
-        print_grounded_concat(subStrDeAlias, groundedMap);
-
-        check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap);
-    }
-}
-
-bool theory_str::can_concat_eq_str(expr * concat, zstring& str) {
-	unsigned int strLen = str.length();
-	if (u.str.is_concat(to_app(concat))) {
-		ptr_vector<expr> args;
-		get_nodes_in_concat(concat, args);
-		expr * ml_node = args[0];
-		expr * mr_node = args[args.size() - 1];
-
-		zstring ml_str;
-		if (u.str.is_string(ml_node, ml_str)) {
-			unsigned int ml_len = ml_str.length();
-			if (ml_len > strLen) {
-				return false;
-			}
-			unsigned int cLen = ml_len;
-			if (ml_str != str.extract(0, cLen)) {
-				return false;
-			}
-		}
-
-		zstring mr_str;
-		if (u.str.is_string(mr_node, mr_str)) {
-			unsigned int mr_len = mr_str.length();
-			if (mr_len > strLen) {
-				return false;
-			}
-			unsigned int cLen = mr_len;
-			if (mr_str != str.extract(strLen - cLen, cLen)) {
-				return false;
-			}
-		}
-
-		unsigned int sumLen = 0;
-		for (unsigned int i = 0 ; i < args.size() ; i++) {
-			expr * oneArg = args[i];
-			zstring arg_str;
-			if (u.str.is_string(oneArg, arg_str)) {
-				if (!str.contains(arg_str)) {
-					return false;
-				}
-				sumLen += arg_str.length();
-			}
-		}
-
-		if (sumLen > strLen) {
-			return false;
-		}
-	}
-	return true;
-}
-
-bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) {
-	if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) {
-		{
-			// Suppose concat1 = (Concat X Y) and concat2 = (Concat M N).
-			expr * concat1_mostL = getMostLeftNodeInConcat(concat1);
-			expr * concat2_mostL = getMostLeftNodeInConcat(concat2);
-			// if both X and M are constant strings, check whether they have the same prefix
-			zstring concat1_mostL_str, concat2_mostL_str;
-			if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) {
-				unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length());
-				if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) {
-					return false;
-				}
-			}
-		}
-
-		{
-			// Similarly, if both Y and N are constant strings, check whether they have the same suffix
-			expr * concat1_mostR = getMostRightNodeInConcat(concat1);
-			expr * concat2_mostR = getMostRightNodeInConcat(concat2);
-			zstring concat1_mostR_str, concat2_mostR_str;
-			if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) {
-				unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length());
-				if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) !=
-						concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) {
-					return false;
-				}
-			}
-		}
-	}
-	return true;
-}
-
-/*
- * Check whether n1 and n2 could be equal.
- * Returns true if n1 could equal n2 (maybe),
- * and false if n1 is definitely not equal to n2 (no).
- */
-bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) {
-    app * n1_curr = to_app(n1);
-    app * n2_curr = to_app(n2);
-
-    // case 0: n1_curr is const string, n2_curr is const string
-    if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) {
-      if (n1_curr != n2_curr) {
-        return false;
-      }
-    }
-    // case 1: n1_curr is concat, n2_curr is const string
-    else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) {
-        zstring n2_curr_str;
-        u.str.is_string(n2_curr, n2_curr_str);
-        if (!can_concat_eq_str(n1_curr, n2_curr_str)) {
             return false;
         }
     }
-    // case 2: n2_curr is concat, n1_curr is const string
-    else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) {
-        zstring n1_curr_str;
-        u.str.is_string(n1_curr, n1_curr_str);
-        if (!can_concat_eq_str(n2_curr, n1_curr_str)) {
-            return false;
+
+    void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar,
+                                       std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap) {
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        std::map<std::vector<expr*>, std::set<expr*> >::iterator itorStr = groundedMap[strDeAlias].begin();
+        std::map<std::vector<expr*>, std::set<expr*> >::iterator itorSubStr;
+        for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) {
+            itorSubStr = groundedMap[subStrDeAlias].begin();
+            for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) {
+                bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first);
+                if (contain) {
+                    expr_ref_vector litems(m);
+                    if (str != strDeAlias) {
+                        litems.push_back(ctx.mk_eq_atom(str, strDeAlias));
+                    }
+                    if (subStr != subStrDeAlias) {
+                        litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias));
+                    }
+
+                    //litems.insert(itorStr->second.begin(), itorStr->second.end());
+                    //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end());
+                    for (std::set<expr*>::const_iterator i1 = itorStr->second.begin();
+                         i1 != itorStr->second.end(); ++i1) {
+                        litems.push_back(*i1);
+                    }
+                    for (std::set<expr*>::const_iterator i1 = itorSubStr->second.begin();
+                         i1 != itorSubStr->second.end(); ++i1) {
+                        litems.push_back(*i1);
+                    }
+
+                    expr_ref implyR(boolVar, m);
+
+                    if (litems.empty()) {
+                        assert_axiom(implyR);
+                    } else {
+                        expr_ref implyL(mk_and(litems), m);
+                        assert_implication(implyL, implyR);
+                    }
+
+                }
+            }
         }
     }
-    // case 3: both are concats
-    else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) {
-      if (!can_concat_eq_concat(n1_curr, n2_curr)) {
-        return false;
-      }
+
+    void theory_str::compute_contains(std::map<expr*, expr*> & varAliasMap,
+                                      std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr*> & varConstMap,
+                                      std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap) {
+        std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > groundedMap;
+        theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin();
+        for (; containItor != contain_pair_bool_map.end(); containItor++) {
+            expr* containBoolVar = containItor->get_value();
+            expr* str = containItor->get_key1();
+            expr* subStr = containItor->get_key2();
+
+            expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap);
+            expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap);
+
+            get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
+            get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap);
+
+            // debugging
+            print_grounded_concat(strDeAlias, groundedMap);
+            print_grounded_concat(subStrDeAlias, groundedMap);
+
+            check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap);
+        }
     }
 
-    return true;
-}
+    bool theory_str::can_concat_eq_str(expr * concat, zstring& str) {
+        unsigned int strLen = str.length();
+        if (u.str.is_concat(to_app(concat))) {
+            ptr_vector<expr> args;
+            get_nodes_in_concat(concat, args);
+            expr * ml_node = args[0];
+            expr * mr_node = args[args.size() - 1];
 
-// was checkLength2ConstStr() in Z3str2
-// returns true if everything is OK, or false if inconsistency detected
-// - note that these are different from the semantics in Z3str2
-bool theory_str::check_length_const_string(expr * n1, expr * constStr) {
-    ast_manager & mgr = get_manager();
-    context & ctx = get_context();
-
-    zstring tmp;
-    u.str.is_string(constStr, tmp);
-    rational strLen(tmp.length());
-
-    if (u.str.is_concat(to_app(n1))) {
-        ptr_vector<expr> args;
-        expr_ref_vector items(mgr);
-
-        get_nodes_in_concat(n1, args);
-
-        rational sumLen(0);
-        for (unsigned int i = 0; i < args.size(); ++i) {
-            rational argLen;
-            bool argLen_exists = get_len_value(args[i], argLen);
-            if (argLen_exists) {
-                if (!u.str.is_string(args[i])) {
-                    items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen)));
+            zstring ml_str;
+            if (u.str.is_string(ml_node, ml_str)) {
+                unsigned int ml_len = ml_str.length();
+                if (ml_len > strLen) {
+                    return false;
                 }
-                TRACE("str", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;);
-                sumLen += argLen;
-                if (sumLen > strLen) {
-                    items.push_back(ctx.mk_eq_atom(n1, constStr));
-                    expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
-                    TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;);
-                    assert_axiom(toAssert);
+                unsigned int cLen = ml_len;
+                if (ml_str != str.extract(0, cLen)) {
                     return false;
                 }
             }
+
+            zstring mr_str;
+            if (u.str.is_string(mr_node, mr_str)) {
+                unsigned int mr_len = mr_str.length();
+                if (mr_len > strLen) {
+                    return false;
+                }
+                unsigned int cLen = mr_len;
+                if (mr_str != str.extract(strLen - cLen, cLen)) {
+                    return false;
+                }
+            }
+
+            unsigned int sumLen = 0;
+            for (unsigned int i = 0 ; i < args.size() ; i++) {
+                expr * oneArg = args[i];
+                zstring arg_str;
+                if (u.str.is_string(oneArg, arg_str)) {
+                    if (!str.contains(arg_str)) {
+                        return false;
+                    }
+                    sumLen += arg_str.length();
+                }
+            }
+
+            if (sumLen > strLen) {
+                return false;
+            }
         }
-    } else { // !is_concat(n1)
-        rational oLen;
-        bool oLen_exists = get_len_value(n1, oLen);
-        if (oLen_exists && oLen != strLen) {
-            TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;);
+        return true;
+    }
+
+    bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) {
+        if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) {
+            {
+                // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N).
+                expr * concat1_mostL = getMostLeftNodeInConcat(concat1);
+                expr * concat2_mostL = getMostLeftNodeInConcat(concat2);
+                // if both X and M are constant strings, check whether they have the same prefix
+                zstring concat1_mostL_str, concat2_mostL_str;
+                if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) {
+                    unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length());
+                    if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) {
+                        return false;
+                    }
+                }
+            }
+
+            {
+                // Similarly, if both Y and N are constant strings, check whether they have the same suffix
+                expr * concat1_mostR = getMostRightNodeInConcat(concat1);
+                expr * concat2_mostR = getMostRightNodeInConcat(concat2);
+                zstring concat1_mostR_str, concat2_mostR_str;
+                if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) {
+                    unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length());
+                    if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) !=
+                        concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /*
+     * Check whether n1 and n2 could be equal.
+     * Returns true if n1 could equal n2 (maybe),
+     * and false if n1 is definitely not equal to n2 (no).
+     */
+    bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) {
+        app * n1_curr = to_app(n1);
+        app * n2_curr = to_app(n2);
+
+        // case 0: n1_curr is const string, n2_curr is const string
+        if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) {
+            if (n1_curr != n2_curr) {
+                return false;
+            }
+        }
+        // case 1: n1_curr is concat, n2_curr is const string
+        else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) {
+            zstring n2_curr_str;
+            u.str.is_string(n2_curr, n2_curr_str);
+            if (!can_concat_eq_str(n1_curr, n2_curr_str)) {
+                return false;
+            }
+        }
+        // case 2: n2_curr is concat, n1_curr is const string
+        else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) {
+            zstring n1_curr_str;
+            u.str.is_string(n1_curr, n1_curr_str);
+            if (!can_concat_eq_str(n2_curr, n1_curr_str)) {
+                return false;
+            }
+        }
+        // case 3: both are concats
+        else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) {
+            if (!can_concat_eq_concat(n1_curr, n2_curr)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    // was checkLength2ConstStr() in Z3str2
+    // returns true if everything is OK, or false if inconsistency detected
+    // - note that these are different from the semantics in Z3str2
+    bool theory_str::check_length_const_string(expr * n1, expr * constStr) {
+        ast_manager & mgr = get_manager();
+        context & ctx = get_context();
+
+        zstring tmp;
+        u.str.is_string(constStr, tmp);
+        rational strLen(tmp.length());
+
+        if (u.str.is_concat(to_app(n1))) {
+            ptr_vector<expr> args;
+            expr_ref_vector items(mgr);
+
+            get_nodes_in_concat(n1, args);
+
+            rational sumLen(0);
+            for (unsigned int i = 0; i < args.size(); ++i) {
+                rational argLen;
+                bool argLen_exists = get_len_value(args[i], argLen);
+                if (argLen_exists) {
+                    if (!u.str.is_string(args[i])) {
+                        items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen)));
+                    }
+                    TRACE("str", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;);
+                    sumLen += argLen;
+                    if (sumLen > strLen) {
+                        items.push_back(ctx.mk_eq_atom(n1, constStr));
+                        expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
+                        TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;);
+                        assert_axiom(toAssert);
+                        return false;
+                    }
+                }
+            }
+        } else { // !is_concat(n1)
+            rational oLen;
+            bool oLen_exists = get_len_value(n1, oLen);
+            if (oLen_exists && oLen != strLen) {
+                TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;);
+                expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr);
+                expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr);
+                assert_implication(l, r);
+                return false;
+            }
+        }
+        rational unused;
+        if (get_len_value(n1, unused) == false) {
             expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr);
             expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr);
             assert_implication(l, r);
-            return false;
         }
-    }
-    rational unused;
-    if (get_len_value(n1, unused) == false) {
-        expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr);
-        expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr);
-        assert_implication(l, r);
-    }
-    return true;
-}
-
-bool theory_str::check_length_concat_concat(expr * n1, expr * n2) {
-    context & ctx = get_context();
-    ast_manager & mgr = get_manager();
-
-    ptr_vector<expr> concat1Args;
-    ptr_vector<expr> concat2Args;
-    get_nodes_in_concat(n1, concat1Args);
-    get_nodes_in_concat(n2, concat2Args);
-
-    bool concat1LenFixed = true;
-    bool concat2LenFixed = true;
-
-    expr_ref_vector items(mgr);
-
-    rational sum1(0), sum2(0);
-
-    for (unsigned int i = 0; i < concat1Args.size(); ++i) {
-        expr * oneArg = concat1Args[i];
-        rational argLen;
-        bool argLen_exists = get_len_value(oneArg, argLen);
-        if (argLen_exists) {
-            sum1 += argLen;
-            if (!u.str.is_string(oneArg)) {
-                items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen)));
-            }
-        } else {
-            concat1LenFixed = false;
-        }
-    }
-
-    for (unsigned int i = 0; i < concat2Args.size(); ++i) {
-        expr * oneArg = concat2Args[i];
-        rational argLen;
-        bool argLen_exists = get_len_value(oneArg, argLen);
-        if (argLen_exists) {
-            sum2 += argLen;
-            if (!u.str.is_string(oneArg)) {
-                items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen)));
-            }
-        } else {
-            concat2LenFixed = false;
-        }
-    }
-
-    items.push_back(ctx.mk_eq_atom(n1, n2));
-
-    bool conflict = false;
-
-    if (concat1LenFixed && concat2LenFixed) {
-        if (sum1 != sum2) {
-            conflict = true;
-        }
-    } else if (!concat1LenFixed && concat2LenFixed) {
-        if (sum1 > sum2) {
-            conflict = true;
-        }
-    } else if (concat1LenFixed && !concat2LenFixed) {
-        if (sum1 < sum2) {
-            conflict = true;
-        }
-    }
-
-    if (conflict) {
-        TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;);
-        expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
-        assert_axiom(toAssert);
-        return false;
-    }
-    return true;
-}
-
-bool theory_str::check_length_concat_var(expr * concat, expr * var) {
-    context & ctx = get_context();
-    ast_manager & mgr = get_manager();
-
-    rational varLen;
-    bool varLen_exists = get_len_value(var, varLen);
-    if (!varLen_exists) {
         return true;
-    } else {
-        rational sumLen(0);
-        ptr_vector<expr> args;
+    }
+
+    bool theory_str::check_length_concat_concat(expr * n1, expr * n2) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        ptr_vector<expr> concat1Args;
+        ptr_vector<expr> concat2Args;
+        get_nodes_in_concat(n1, concat1Args);
+        get_nodes_in_concat(n2, concat2Args);
+
+        bool concat1LenFixed = true;
+        bool concat2LenFixed = true;
+
         expr_ref_vector items(mgr);
-        get_nodes_in_concat(concat, args);
-        for (unsigned int i = 0; i < args.size(); ++i) {
-            expr * oneArg = args[i];
+
+        rational sum1(0), sum2(0);
+
+        for (unsigned int i = 0; i < concat1Args.size(); ++i) {
+            expr * oneArg = concat1Args[i];
             rational argLen;
             bool argLen_exists = get_len_value(oneArg, argLen);
             if (argLen_exists) {
-                if (!u.str.is_string(oneArg) && !argLen.is_zero()) {
+                sum1 += argLen;
+                if (!u.str.is_string(oneArg)) {
                     items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen)));
                 }
-                sumLen += argLen;
-                if (sumLen > varLen) {
-                    TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;);
-                    items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen)));
-                    items.push_back(ctx.mk_eq_atom(concat, var));
-                    expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
-                    assert_axiom(toAssert);
-                    return false;
-                }
+            } else {
+                concat1LenFixed = false;
             }
         }
+
+        for (unsigned int i = 0; i < concat2Args.size(); ++i) {
+            expr * oneArg = concat2Args[i];
+            rational argLen;
+            bool argLen_exists = get_len_value(oneArg, argLen);
+            if (argLen_exists) {
+                sum2 += argLen;
+                if (!u.str.is_string(oneArg)) {
+                    items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen)));
+                }
+            } else {
+                concat2LenFixed = false;
+            }
+        }
+
+        items.push_back(ctx.mk_eq_atom(n1, n2));
+
+        bool conflict = false;
+
+        if (concat1LenFixed && concat2LenFixed) {
+            if (sum1 != sum2) {
+                conflict = true;
+            }
+        } else if (!concat1LenFixed && concat2LenFixed) {
+            if (sum1 > sum2) {
+                conflict = true;
+            }
+        } else if (concat1LenFixed && !concat2LenFixed) {
+            if (sum1 < sum2) {
+                conflict = true;
+            }
+        }
+
+        if (conflict) {
+            TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;);
+            expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
+            assert_axiom(toAssert);
+            return false;
+        }
         return true;
     }
-}
 
-bool theory_str::check_length_var_var(expr * var1, expr * var2) {
-    context & ctx = get_context();
-    ast_manager & mgr = get_manager();
+    bool theory_str::check_length_concat_var(expr * concat, expr * var) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
 
-    rational var1Len, var2Len;
-    bool var1Len_exists = get_len_value(var1, var1Len);
-    bool var2Len_exists = get_len_value(var2, var2Len);
-
-    if (var1Len_exists && var2Len_exists && var1Len != var2Len) {
-        TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;);
-        expr_ref_vector items(mgr);
-        items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len)));
-        items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len)));
-        items.push_back(ctx.mk_eq_atom(var1, var2));
-        expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
-        assert_axiom(toAssert);
-        return false;
-    }
-    return true;
-}
-
-// returns true if everything is OK, or false if inconsistency detected
-// - note that these are different from the semantics in Z3str2
-bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) {
-    // n1 and n2 are not const string: either variable or concat
-    bool n1Concat = u.str.is_concat(to_app(n1));
-    bool n2Concat = u.str.is_concat(to_app(n2));
-    if (n1Concat && n2Concat) {
-        return check_length_concat_concat(n1, n2);
-    }
-    // n1 is concat, n2 is variable
-    else if (n1Concat && (!n2Concat)) {
-        return check_length_concat_var(n1, n2);
-    }
-    // n1 is variable, n2 is concat
-    else if ((!n1Concat) && n2Concat) {
-        return check_length_concat_var(n2, n1);
-    }
-    // n1 and n2 are both variables
-    else {
-        return check_length_var_var(n1, n2);
-    }
-    return true;
-}
-
-// returns false if an inconsistency is detected, or true if no inconsistencies were found
-// - note that these are different from the semantics of checkLengConsistency() in Z3str2
-bool theory_str::check_length_consistency(expr * n1, expr * n2) {
-	if (u.str.is_string(n1) && u.str.is_string(n2)) {
-		// consistency has already been checked in can_two_nodes_eq().
-		return true;
-	} else if (u.str.is_string(n1) && (!u.str.is_string(n2))) {
-		return check_length_const_string(n2, n1);
-	} else if (u.str.is_string(n2) && (!u.str.is_string(n1))) {
-		return check_length_const_string(n1, n2);
-	} else {
-		// n1 and n2 are vars or concats
-		return check_length_eq_var_concat(n1, n2);
-	}
-	return true;
-}
-
-// Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted.
-// (This is used for deferred consistency checking)
-bool theory_str::check_concat_len_in_eqc(expr * concat) {
-    context & ctx = get_context();
-
-    bool no_assertions = true;
-
-    expr * eqc_n = concat;
-    do {
-        if (u.str.is_concat(to_app(eqc_n))) {
-            rational unused;
-            bool status = infer_len_concat(eqc_n, unused);
-            if (status) {
-                no_assertions = false;
-            }
-        }
-        eqc_n = get_eqc_next(eqc_n);
-    } while (eqc_n != concat);
-
-    return no_assertions;
-}
-
-// Convert a regular expression to an e-NFA using Thompson's construction
-void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) {
-    start = next_id();
-    end = next_id();
-    if (u.re.is_to_re(e)) {
-        app * a = to_app(e);
-        expr * arg_str = a->get_arg(0);
-        zstring str;
-        if (u.str.is_string(arg_str, str)) {
-            TRACE("str", tout << "build NFA for '" << str << "'" << "\n";);
-            /*
-             * For an n-character string, we make (n-1) intermediate states,
-             * labelled i_(0) through i_(n-2).
-             * Then we construct the following transitions:
-             * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final
-             */
-            unsigned last = start;
-            for (int i = 0; i <= ((int)str.length()) - 2; ++i) {
-                unsigned i_state = next_id();
-                make_transition(last, str[i], i_state);
-                TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";);
-                last = i_state;
-            }
-            make_transition(last, str[(str.length() - 1)], end);
-            TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";);
+        rational varLen;
+        bool varLen_exists = get_len_value(var, varLen);
+        if (!varLen_exists) {
+            return true;
         } else {
-            TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;);
+            rational sumLen(0);
+            ptr_vector<expr> args;
+            expr_ref_vector items(mgr);
+            get_nodes_in_concat(concat, args);
+            for (unsigned int i = 0; i < args.size(); ++i) {
+                expr * oneArg = args[i];
+                rational argLen;
+                bool argLen_exists = get_len_value(oneArg, argLen);
+                if (argLen_exists) {
+                    if (!u.str.is_string(oneArg) && !argLen.is_zero()) {
+                        items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen)));
+                    }
+                    sumLen += argLen;
+                    if (sumLen > varLen) {
+                        TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;);
+                        items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen)));
+                        items.push_back(ctx.mk_eq_atom(concat, var));
+                        expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
+                        assert_axiom(toAssert);
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    bool theory_str::check_length_var_var(expr * var1, expr * var2) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        rational var1Len, var2Len;
+        bool var1Len_exists = get_len_value(var1, var1Len);
+        bool var2Len_exists = get_len_value(var2, var2Len);
+
+        if (var1Len_exists && var2Len_exists && var1Len != var2Len) {
+            TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;);
+            expr_ref_vector items(mgr);
+            items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len)));
+            items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len)));
+            items.push_back(ctx.mk_eq_atom(var1, var2));
+            expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr);
+            assert_axiom(toAssert);
+            return false;
+        }
+        return true;
+    }
+
+    // returns true if everything is OK, or false if inconsistency detected
+    // - note that these are different from the semantics in Z3str2
+    bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) {
+        // n1 and n2 are not const string: either variable or concat
+        bool n1Concat = u.str.is_concat(to_app(n1));
+        bool n2Concat = u.str.is_concat(to_app(n2));
+        if (n1Concat && n2Concat) {
+            return check_length_concat_concat(n1, n2);
+        }
+        // n1 is concat, n2 is variable
+        else if (n1Concat && (!n2Concat)) {
+            return check_length_concat_var(n1, n2);
+        }
+        // n1 is variable, n2 is concat
+        else if ((!n1Concat) && n2Concat) {
+            return check_length_concat_var(n2, n1);
+        }
+        // n1 and n2 are both variables
+        else {
+            return check_length_var_var(n1, n2);
+        }
+        return true;
+    }
+
+    // returns false if an inconsistency is detected, or true if no inconsistencies were found
+    // - note that these are different from the semantics of checkLengConsistency() in Z3str2
+    bool theory_str::check_length_consistency(expr * n1, expr * n2) {
+        if (u.str.is_string(n1) && u.str.is_string(n2)) {
+            // consistency has already been checked in can_two_nodes_eq().
+            return true;
+        } else if (u.str.is_string(n1) && (!u.str.is_string(n2))) {
+            return check_length_const_string(n2, n1);
+        } else if (u.str.is_string(n2) && (!u.str.is_string(n1))) {
+            return check_length_const_string(n1, n2);
+        } else {
+            // n1 and n2 are vars or concats
+            return check_length_eq_var_concat(n1, n2);
+        }
+        return true;
+    }
+
+    // Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted.
+    // (This is used for deferred consistency checking)
+    bool theory_str::check_concat_len_in_eqc(expr * concat) {
+        context & ctx = get_context();
+
+        bool no_assertions = true;
+
+        expr * eqc_n = concat;
+        do {
+            if (u.str.is_concat(to_app(eqc_n))) {
+                rational unused;
+                bool status = infer_len_concat(eqc_n, unused);
+                if (status) {
+                    no_assertions = false;
+                }
+            }
+            eqc_n = get_eqc_next(eqc_n);
+        } while (eqc_n != concat);
+
+        return no_assertions;
+    }
+
+    // Convert a regular expression to an e-NFA using Thompson's construction
+    void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) {
+        start = next_id();
+        end = next_id();
+        if (u.re.is_to_re(e)) {
+            app * a = to_app(e);
+            expr * arg_str = a->get_arg(0);
+            zstring str;
+            if (u.str.is_string(arg_str, str)) {
+                TRACE("str", tout << "build NFA for '" << str << "'" << "\n";);
+                /*
+                 * For an n-character string, we make (n-1) intermediate states,
+                 * labelled i_(0) through i_(n-2).
+                 * Then we construct the following transitions:
+                 * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final
+                 */
+                unsigned last = start;
+                for (int i = 0; i <= ((int)str.length()) - 2; ++i) {
+                    unsigned i_state = next_id();
+                    make_transition(last, str[i], i_state);
+                    TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";);
+                    last = i_state;
+                }
+                make_transition(last, str[(str.length() - 1)], end);
+                TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";);
+            } else {
+                TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;);
+                m_valid = false;
+                return;
+            }
+        } else if (u.re.is_concat(e)){
+            app * a = to_app(e);
+            expr * re1 = a->get_arg(0);
+            expr * re2 = a->get_arg(1);
+            unsigned start1, end1;
+            convert_re(re1, start1, end1, u);
+            unsigned start2, end2;
+            convert_re(re2, start2, end2, u);
+            // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end
+            make_epsilon_move(start, start1);
+            make_epsilon_move(end1, start2);
+            make_epsilon_move(end2, end);
+            TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;);
+        } else if (u.re.is_union(e)) {
+            app * a = to_app(e);
+            expr * re1 = a->get_arg(0);
+            expr * re2 = a->get_arg(1);
+            unsigned start1, end1;
+            convert_re(re1, start1, end1, u);
+            unsigned start2, end2;
+            convert_re(re2, start2, end2, u);
+
+            // start --e--> start1 ; start --e--> start2
+            // end1 --e--> end ; end2 --e--> end
+            make_epsilon_move(start, start1);
+            make_epsilon_move(start, start2);
+            make_epsilon_move(end1, end);
+            make_epsilon_move(end2, end);
+            TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;);
+        } else if (u.re.is_star(e)) {
+            app * a = to_app(e);
+            expr * subex = a->get_arg(0);
+            unsigned start_subex, end_subex;
+            convert_re(subex, start_subex, end_subex, u);
+            // start --e--> start_subex, start --e--> end
+            // end_subex --e--> start_subex, end_subex --e--> end
+            make_epsilon_move(start, start_subex);
+            make_epsilon_move(start, end);
+            make_epsilon_move(end_subex, start_subex);
+            make_epsilon_move(end_subex, end);
+            TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;);
+        } else if (u.re.is_range(e)) {
+            // range('a', 'z')
+            // start --'a'--> end
+            // start --'b'--> end
+            // ...
+            // start --'z'--> end
+            app * a = to_app(e);
+            expr * c1 = a->get_arg(0);
+            expr * c2 = a->get_arg(1);
+            zstring s_c1, s_c2;
+            u.str.is_string(c1, s_c1);
+            u.str.is_string(c2, s_c2);
+
+            unsigned int id1 = s_c1[0];
+            unsigned int id2 = s_c2[0];
+            if (id1 > id2) {
+                unsigned int tmp = id1;
+                id1 = id2;
+                id2 = tmp;
+            }
+
+            for (unsigned int i = id1; i <= id2; ++i) {
+                char ch = (char)i;
+                make_transition(start, ch, end);
+            }
+
+            TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;);
+        } else {
+            TRACE("str", tout << "invalid regular expression" << std::endl;);
             m_valid = false;
             return;
         }
-    } else if (u.re.is_concat(e)){
-        app * a = to_app(e);
-        expr * re1 = a->get_arg(0);
-        expr * re2 = a->get_arg(1);
-        unsigned start1, end1;
-        convert_re(re1, start1, end1, u);
-        unsigned start2, end2;
-        convert_re(re2, start2, end2, u);
-        // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end
-        make_epsilon_move(start, start1);
-        make_epsilon_move(end1, start2);
-        make_epsilon_move(end2, end);
-        TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;);
-    } else if (u.re.is_union(e)) {
-        app * a = to_app(e);
-        expr * re1 = a->get_arg(0);
-        expr * re2 = a->get_arg(1);
-        unsigned start1, end1;
-        convert_re(re1, start1, end1, u);
-        unsigned start2, end2;
-        convert_re(re2, start2, end2, u);
-
-        // start --e--> start1 ; start --e--> start2
-        // end1 --e--> end ; end2 --e--> end
-        make_epsilon_move(start, start1);
-        make_epsilon_move(start, start2);
-        make_epsilon_move(end1, end);
-        make_epsilon_move(end2, end);
-        TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;);
-    } else if (u.re.is_star(e)) {
-        app * a = to_app(e);
-        expr * subex = a->get_arg(0);
-        unsigned start_subex, end_subex;
-        convert_re(subex, start_subex, end_subex, u);
-        // start --e--> start_subex, start --e--> end
-        // end_subex --e--> start_subex, end_subex --e--> end
-        make_epsilon_move(start, start_subex);
-        make_epsilon_move(start, end);
-        make_epsilon_move(end_subex, start_subex);
-        make_epsilon_move(end_subex, end);
-        TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;);
-    } else if (u.re.is_range(e)) {
-        // range('a', 'z')
-        // start --'a'--> end
-        // start --'b'--> end
-        // ...
-        // start --'z'--> end
-        app * a = to_app(e);
-        expr * c1 = a->get_arg(0);
-        expr * c2 = a->get_arg(1);
-        zstring s_c1, s_c2;
-        u.str.is_string(c1, s_c1);
-        u.str.is_string(c2, s_c2);
-
-        unsigned int id1 = s_c1[0];
-        unsigned int id2 = s_c2[0];
-        if (id1 > id2) {
-            unsigned int tmp = id1;
-            id1 = id2;
-            id2 = tmp;
-        }
-
-        for (unsigned int i = id1; i <= id2; ++i) {
-            char ch = (char)i;
-            make_transition(start, ch, end);
-        }
-
-        TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;);
-    } else {
-        TRACE("str", tout << "invalid regular expression" << std::endl;);
-        m_valid = false;
-        return;
     }
-}
 
-void nfa::epsilon_closure(unsigned start, std::set<unsigned> & closure) {
-    std::deque<unsigned> worklist;
-    closure.insert(start);
-    worklist.push_back(start);
+    void nfa::epsilon_closure(unsigned start, std::set<unsigned> & closure) {
+        std::deque<unsigned> worklist;
+        closure.insert(start);
+        worklist.push_back(start);
 
-    while(!worklist.empty()) {
-        unsigned state = worklist.front();
-        worklist.pop_front();
-        if (epsilon_map.find(state) != epsilon_map.end()) {
-            for (std::set<unsigned>::iterator it = epsilon_map[state].begin();
-                    it != epsilon_map[state].end(); ++it) {
-                unsigned new_state = *it;
-                if (closure.find(new_state) == closure.end()) {
-                    closure.insert(new_state);
-                    worklist.push_back(new_state);
+        while(!worklist.empty()) {
+            unsigned state = worklist.front();
+            worklist.pop_front();
+            if (epsilon_map.find(state) != epsilon_map.end()) {
+                for (std::set<unsigned>::iterator it = epsilon_map[state].begin();
+                     it != epsilon_map[state].end(); ++it) {
+                    unsigned new_state = *it;
+                    if (closure.find(new_state) == closure.end()) {
+                        closure.insert(new_state);
+                        worklist.push_back(new_state);
+                    }
+                }
+            }
+        }
+    }
+
+    bool nfa::matches(zstring input) {
+        /*
+         * Keep a set of all states the NFA can currently be in.
+         * Initially this is the e-closure of m_start_state
+         * For each character A in the input string,
+         * the set of next states contains
+         * all states in transition_map[S][A] for each S in current_states,
+         * and all states in epsilon_map[S] for each S in current_states.
+         * After consuming the entire input string,
+         * the match is successful iff current_states contains m_end_state.
+         */
+        std::set<unsigned> current_states;
+        epsilon_closure(m_start_state, current_states);
+        for (unsigned i = 0; i < input.length(); ++i) {
+            char A = (char)input[i];
+            std::set<unsigned> next_states;
+            for (std::set<unsigned>::iterator it = current_states.begin();
+                 it != current_states.end(); ++it) {
+                unsigned S = *it;
+                // check transition_map
+                if (transition_map[S].find(A) != transition_map[S].end()) {
+                    next_states.insert(transition_map[S][A]);
+                }
+            }
+
+            // take e-closure over next_states to compute the actual next_states
+            std::set<unsigned> epsilon_next_states;
+            for (std::set<unsigned>::iterator it = next_states.begin(); it != next_states.end(); ++it) {
+                unsigned S = *it;
+                std::set<unsigned> closure;
+                epsilon_closure(S, closure);
+                epsilon_next_states.insert(closure.begin(), closure.end());
+            }
+            current_states = epsilon_next_states;
+        }
+        if (current_states.find(m_end_state) != current_states.end()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void theory_str::check_regex_in(expr * nn1, expr * nn2) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr_ref_vector eqNodeSet(m);
+
+        expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet);
+        expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet);
+        expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2;
+
+        if (constStr == NULL) {
+            return;
+        } else {
+            expr_ref_vector::iterator itor = eqNodeSet.begin();
+            for (; itor != eqNodeSet.end(); itor++) {
+                if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) {
+                    std::set<zstring>::iterator strItor = regex_in_var_reg_str_map[*itor].begin();
+                    for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) {
+                        zstring regStr = *strItor;
+                        zstring constStrValue;
+                        u.str.is_string(constStr, constStrValue);
+                        std::pair<expr*, zstring> key1 = std::make_pair(*itor, regStr);
+                        if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) {
+                            expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term
+                            app * a_regexIn = to_app(boolVar);
+                            expr * regexTerm = a_regexIn->get_arg(1);
+
+                            // TODO figure out regex NFA stuff
+                            if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) {
+                                TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;);
+                                regex_nfa_cache[regexTerm] = nfa(u, regexTerm);
+                            } else {
+                                TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;);
+                            }
+
+                            nfa regexNFA = regex_nfa_cache[regexTerm];
+                            ENSURE(regexNFA.is_valid());
+                            bool matchRes = regexNFA.matches(constStrValue);
+
+                            TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;);
+
+                            expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m);
+                            if (matchRes) {
+                                assert_implication(implyL, boolVar);
+                            } else {
+                                assert_implication(implyL, m.mk_not(boolVar));
+                            }
+                        }
+                    }
                 }
             }
         }
     }
-}
 
-bool nfa::matches(zstring input) {
     /*
-     * Keep a set of all states the NFA can currently be in.
-     * Initially this is the e-closure of m_start_state
-     * For each character A in the input string,
-     * the set of next states contains
-     * all states in transition_map[S][A] for each S in current_states,
-     * and all states in epsilon_map[S] for each S in current_states.
-     * After consuming the entire input string,
-     * the match is successful iff current_states contains m_end_state.
+     * strArgmt::solve_concat_eq_str()
+     * Solve concatenations of the form:
+     *   const == Concat(const, X)
+     *   const == Concat(X, const)
      */
-    std::set<unsigned> current_states;
-    epsilon_closure(m_start_state, current_states);
-    for (unsigned i = 0; i < input.length(); ++i) {
-        char A = (char)input[i];
-        std::set<unsigned> next_states;
-        for (std::set<unsigned>::iterator it = current_states.begin();
-                it != current_states.end(); ++it) {
-            unsigned S = *it;
-            // check transition_map
-            if (transition_map[S].find(A) != transition_map[S].end()) {
-                next_states.insert(transition_map[S][A]);
-            }
-        }
-
-        // take e-closure over next_states to compute the actual next_states
-        std::set<unsigned> epsilon_next_states;
-        for (std::set<unsigned>::iterator it = next_states.begin(); it != next_states.end(); ++it) {
-            unsigned S = *it;
-            std::set<unsigned> closure;
-            epsilon_closure(S, closure);
-            epsilon_next_states.insert(closure.begin(), closure.end());
-        }
-        current_states = epsilon_next_states;
-    }
-    if (current_states.find(m_end_state) != current_states.end()) {
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void theory_str::check_regex_in(expr * nn1, expr * nn2) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    expr_ref_vector eqNodeSet(m);
-
-    expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet);
-    expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet);
-    expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2;
-
-    if (constStr == NULL) {
-        return;
-    } else {
-        expr_ref_vector::iterator itor = eqNodeSet.begin();
-        for (; itor != eqNodeSet.end(); itor++) {
-            if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) {
-                std::set<zstring>::iterator strItor = regex_in_var_reg_str_map[*itor].begin();
-                for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) {
-                    zstring regStr = *strItor;
-                    zstring constStrValue;
-                    u.str.is_string(constStr, constStrValue);
-                    std::pair<expr*, zstring> key1 = std::make_pair(*itor, regStr);
-                    if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) {
-                        expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term
-                        app * a_regexIn = to_app(boolVar);
-                        expr * regexTerm = a_regexIn->get_arg(1);
-
-                        // TODO figure out regex NFA stuff
-                        if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) {
-                            TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;);
-                            regex_nfa_cache[regexTerm] = nfa(u, regexTerm);
-                        } else {
-                            TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;);
-                        }
-
-                        nfa regexNFA = regex_nfa_cache[regexTerm];
-                        ENSURE(regexNFA.is_valid());
-                        bool matchRes = regexNFA.matches(constStrValue);
-
-                        TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;);
-
-                        expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m);
-                        if (matchRes) {
-                            assert_implication(implyL, boolVar);
-                        } else {
-                            assert_implication(implyL, m.mk_not(boolVar));
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-/*
- * strArgmt::solve_concat_eq_str()
- * Solve concatenations of the form:
- *   const == Concat(const, X)
- *   const == Concat(X, const)
- */
-void theory_str::solve_concat_eq_str(expr * concat, expr * str) {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-
-    TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;);
-
-    zstring const_str;
-    if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) {
-        app * a_concat = to_app(concat);
-        SASSERT(a_concat->get_num_args() == 2);
-        expr * a1 = a_concat->get_arg(0);
-        expr * a2 = a_concat->get_arg(1);
-
-        if (const_str.empty()) {
-            TRACE("str", tout << "quick path: concat == \"\"" << std::endl;);
-            // assert the following axiom:
-            // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") )
-
-
-            expr_ref premise(ctx.mk_eq_atom(concat, str), m);
-            expr_ref c1(ctx.mk_eq_atom(a1, str), m);
-            expr_ref c2(ctx.mk_eq_atom(a2, str), m);
-            expr_ref conclusion(m.mk_and(c1, c2), m);
-            assert_implication(premise, conclusion);
-
-            return;
-        }
-        bool arg1_has_eqc_value = false;
-        bool arg2_has_eqc_value = false;
-        expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value);
-        expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value);
-        expr_ref newConcat(m);
-        if (arg1 != a1 || arg2 != a2) {
-        	TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;);
-        	int iPos = 0;
-        	expr_ref_vector item1(m);
-        	if (a1 != arg1) {
-        		item1.push_back(ctx.mk_eq_atom(a1, arg1));
-        		iPos += 1;
-        	}
-        	if (a2 != arg2) {
-        		item1.push_back(ctx.mk_eq_atom(a2, arg2));
-        		iPos += 1;
-        	}
-        	expr_ref implyL1(mk_and(item1), m);
-        	newConcat = mk_concat(arg1, arg2);
-        	if (newConcat != str) {
-        		expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m);
-        		assert_implication(implyL1, implyR1);
-        	}
-        } else {
-        	newConcat = concat;
-        }
-        if (newConcat == str) {
-        	return;
-        }
-        if (!u.str.is_concat(to_app(newConcat))) {
-        	return;
-        }
-        if (arg1_has_eqc_value && arg2_has_eqc_value) {
-        	// Case 1: Concat(const, const) == const
-        	TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;);
-        	zstring arg1_str, arg2_str;
-        	u.str.is_string(arg1, arg1_str);
-        	u.str.is_string(arg2, arg2_str);
-
-        	zstring result_str = arg1_str + arg2_str;
-        	if (result_str != const_str) {
-        		// Inconsistency
-        		TRACE("str", tout << "inconsistency detected: \""
-        				<< arg1_str << "\" + \"" << arg2_str <<
-						"\" != \"" << const_str << "\"" << "\n";);
-        		expr_ref equality(ctx.mk_eq_atom(concat, str), m);
-        		expr_ref diseq(m.mk_not(equality), m);
-        		assert_axiom(diseq);
-        		return;
-        	}
-        } else if (!arg1_has_eqc_value && arg2_has_eqc_value) {
-        	// Case 2: Concat(var, const) == const
-        	TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;);
-        	zstring arg2_str;
-			u.str.is_string(arg2, arg2_str);
-			unsigned int resultStrLen = const_str.length();
-			unsigned int arg2StrLen = arg2_str.length();
-			if (resultStrLen < arg2StrLen) {
-				// Inconsistency
-				TRACE("str", tout << "inconsistency detected: \""
-						 << arg2_str <<
-						"\" is longer than \"" << const_str << "\","
-						<< " so cannot be concatenated with anything to form it" << "\n";);
-				expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
-				expr_ref diseq(m.mk_not(equality), m);
-				assert_axiom(diseq);
-				return;
-			} else {
-				int varStrLen = resultStrLen - arg2StrLen;
-				zstring firstPart = const_str.extract(0, varStrLen);
-				zstring secondPart = const_str.extract(varStrLen, arg2StrLen);
-				if (arg2_str != secondPart) {
-					// Inconsistency
-					TRACE("str", tout << "inconsistency detected: "
-							<< "suffix of concatenation result expected \"" << secondPart << "\", "
-							<< "actually \"" << arg2_str << "\""
-							<< "\n";);
-					expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
-					expr_ref diseq(m.mk_not(equality), m);
-					assert_axiom(diseq);
-					return;
-				} else {
-					expr_ref tmpStrConst(mk_string(firstPart), m);
-					expr_ref premise(ctx.mk_eq_atom(newConcat, str), m);
-					expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m);
-					assert_implication(premise, conclusion);
-					return;
-				}
-			}
-        } else if (arg1_has_eqc_value && !arg2_has_eqc_value) {
-        	// Case 3: Concat(const, var) == const
-        	TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;);
-        	zstring arg1_str;
-			u.str.is_string(arg1, arg1_str);
-			unsigned int resultStrLen = const_str.length();
-			unsigned int arg1StrLen = arg1_str.length();
-			if (resultStrLen < arg1StrLen) {
-				// Inconsistency
-				TRACE("str", tout << "inconsistency detected: \""
-						 << arg1_str <<
-						"\" is longer than \"" << const_str << "\","
-						<< " so cannot be concatenated with anything to form it" << "\n";);
-				expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
-				expr_ref diseq(m.mk_not(equality), m);
-				assert_axiom(diseq);
-				return;
-			} else {
-				int varStrLen = resultStrLen - arg1StrLen;
-				zstring firstPart = const_str.extract(0, arg1StrLen);
-				zstring secondPart = const_str.extract(arg1StrLen, varStrLen);
-				if (arg1_str != firstPart) {
-					// Inconsistency
-					TRACE("str", tout << "inconsistency detected: "
-							<< "prefix of concatenation result expected \"" << secondPart << "\", "
-							<< "actually \"" << arg1_str << "\""
-							<< "\n";);
-					expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
-					expr_ref diseq(m.mk_not(equality), m);
-					assert_axiom(diseq);
-					return;
-				} else {
-					expr_ref tmpStrConst(mk_string(secondPart), m);
-					expr_ref premise(ctx.mk_eq_atom(newConcat, str), m);
-					expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m);
-					assert_implication(premise, conclusion);
-					return;
-				}
-			}
-        } else {
-        	// Case 4: Concat(var, var) == const
-        	TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;);
-        	if (eval_concat(arg1, arg2) == NULL) {
-        	    rational arg1Len, arg2Len;
-        	    bool arg1Len_exists = get_len_value(arg1, arg1Len);
-        	    bool arg2Len_exists = get_len_value(arg2, arg2Len);
-        	    rational concatStrLen((unsigned)const_str.length());
-        		if (arg1Len_exists || arg2Len_exists) {
-        		    expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m);
-        		    expr_ref ax_l2(m);
-        		    zstring prefixStr, suffixStr;
-        		    if (arg1Len_exists) {
-        		        if (arg1Len.is_neg()) {
-        		            TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;);
-        		            expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m);
-        		            assert_axiom(toAssert);
-        		            return;
-        		        } else if (arg1Len > concatStrLen) {
-        		            TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;);
-        		            expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m);
-        		            assert_implication(ax_l1, ax_r1);
-        		            return;
-        		        }
-
-        		        prefixStr = const_str.extract(0, arg1Len.get_unsigned());
-        		        rational concat_minus_arg1 = concatStrLen - arg1Len;
-        		        suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned());
-        		        ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len));
-        		    } else {
-        		        // arg2's length is available
-        		        if (arg2Len.is_neg()) {
-        		            TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;);
-        		            expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m);
-        		            assert_axiom(toAssert);
-        		            return;
-        		        } else if (arg2Len > concatStrLen) {
-        		            TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;);
-        		            expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m);
-        		            assert_implication(ax_l1, ax_r1);
-        		            return;
-        		        }
-
-        		        rational concat_minus_arg2 = concatStrLen - arg2Len;
-        		        prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned());
-        		        suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned());
-        		        ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len));
-        		    }
-        		    // consistency check
-        		    if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) {
-        		        expr_ref ax_r(m.mk_not(ax_l2), m);
-        		        assert_implication(ax_l1, ax_r);
-        		        return;
-        		    }
-        		    if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) {
-        		        expr_ref ax_r(m.mk_not(ax_l2), m);
-        		        assert_implication(ax_l1, ax_r);
-        		        return;
-        		    }
-        		    expr_ref_vector r_items(m);
-        		    r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr)));
-        		    r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr)));
-        		    if (!arg1Len_exists) {
-        		        r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length())));
-        		    }
-        		    if (!arg2Len_exists) {
-        		        r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length())));
-        		    }
-        		    expr_ref lhs(m.mk_and(ax_l1, ax_l2), m);
-        		    expr_ref rhs(mk_and(r_items), m);
-        		    assert_implication(lhs, rhs);
-        		} else { /* ! (arg1Len != 1 || arg2Len != 1) */
-        			expr_ref xorFlag(m);
-        			std::pair<expr*, expr*> key1(arg1, arg2);
-        			std::pair<expr*, expr*> key2(arg2, arg1);
-
-        			// check the entries in this map to make sure they're still in scope
-        			// before we use them.
-
-        			std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
-        			std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
-
-        			bool entry1InScope;
-        			if (entry1 == varForBreakConcat.end()) {
-        			    TRACE("str", tout << "key1 no entry" << std::endl;);
-        			    entry1InScope = false;
-        			} else {
-        			    // OVERRIDE.
-        			    entry1InScope = true;
-        			    TRACE("str", tout << "key1 entry" << std::endl;);
-        			    /*
-        			    if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) {
-        			        TRACE("str", tout << "key1 entry not in scope" << std::endl;);
-        			        entry1InScope = false;
-        			    } else {
-        			        TRACE("str", tout << "key1 entry in scope" << std::endl;);
-        			        entry1InScope = true;
-        			    }
-        			    */
-        			}
-
-        			bool entry2InScope;
-        			if (entry2 == varForBreakConcat.end()) {
-        			    TRACE("str", tout << "key2 no entry" << std::endl;);
-        			    entry2InScope = false;
-        			} else {
-        			    // OVERRIDE.
-        			    entry2InScope = true;
-        			    TRACE("str", tout << "key2 entry" << std::endl;);
-        			    /*
-        			    if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) {
-        			        TRACE("str", tout << "key2 entry not in scope" << std::endl;);
-        			        entry2InScope = false;
-        			    } else {
-        			        TRACE("str", tout << "key2 entry in scope" << std::endl;);
-        			        entry2InScope = true;
-        			    }
-        			    */
-        			}
-
-        			TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
-        			        << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
-
-        			if (!entry1InScope && !entry2InScope) {
-        				xorFlag = mk_internal_xor_var();
-        				varForBreakConcat[key1][0] = xorFlag;
-        			} else if (entry1InScope) {
-        				xorFlag = varForBreakConcat[key1][0];
-        			} else { // entry2InScope
-        				xorFlag = varForBreakConcat[key2][0];
-        			}
-
-        			int concatStrLen = const_str.length();
-        			int and_count = 1;
-
-        			expr_ref_vector arrangement_disjunction(m);
-
-        			for (int i = 0; i < concatStrLen + 1; ++i) {
-        			    expr_ref_vector and_items(m);
-        				zstring prefixStr = const_str.extract(0, i);
-        				zstring suffixStr = const_str.extract(i, concatStrLen - i);
-        				// skip invalid options
-        				if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) {
-        				    continue;
-        				}
-        				if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) {
-        				    continue;
-        				}
-
-        				expr_ref prefixAst(mk_string(prefixStr), m);
-        				expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m);
-        				and_items.push_back(arg1_eq);
-        				and_count += 1;
-
-        				expr_ref suffixAst(mk_string(suffixStr), m);
-        				expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m);
-        				and_items.push_back(arg2_eq);
-        				and_count += 1;
-
-        				arrangement_disjunction.push_back(mk_and(and_items));
-        			}
-
-        			expr_ref implyL(ctx.mk_eq_atom(concat, str), m);
-        			expr_ref implyR1(m);
-        			if (arrangement_disjunction.empty()) {
-        				// negate
-        				expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m);
-        				expr_ref negate_ast(m.mk_not(concat_eq_str), m);
-        				assert_axiom(negate_ast);
-        			} else {
-        			    implyR1 = mk_or(arrangement_disjunction);
-        			    if (m_params.m_StrongArrangements) {
-        			        expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m);
-        			        assert_axiom(ax_strong);
-        			    } else {
-        			        assert_implication(implyL, implyR1);
-        			    }
-        			    generate_mutual_exclusion(arrangement_disjunction);
-        			}
-        		} /* (arg1Len != 1 || arg2Len != 1) */
-        	} /* if (Concat(arg1, arg2) == NULL) */
-        }
-    }
-}
-
-expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    TRACE("str", tout << "activating finite model testing for overlapping concats "
-            << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;);
-    std::map<expr*, int> concatMap;
-    std::map<expr*, int> unrollMap;
-    std::map<expr*, int> varMap;
-    classify_ast_by_type(lhs, varMap, concatMap, unrollMap);
-    classify_ast_by_type(rhs, varMap, concatMap, unrollMap);
-    TRACE("str", tout << "found vars:";
-    for (std::map<expr*,int>::iterator it = varMap.begin(); it != varMap.end(); ++it) {
-        tout << " " << mk_pp(it->first, m);
-    }
-    tout << std::endl;
-    );
-
-    expr_ref testvar(mk_str_var("finiteModelTest"), m);
-    m_trail.push_back(testvar);
-    ptr_vector<expr> varlist;
-
-    for (std::map<expr*, int>::iterator it = varMap.begin(); it != varMap.end(); ++it) {
-        expr * v = it->first;
-        varlist.push_back(v);
-    }
-
-    // make things easy for the core wrt. testvar
-    expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m);
-    expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m);
-    expr_ref testvaraxiom(m.mk_or(t1, t_yes), m);
-    assert_axiom(testvaraxiom);
-
-    finite_model_test_varlists.insert(testvar, varlist);
-    m_trail_stack.push(insert_obj_map<theory_str, expr, ptr_vector<expr> >(finite_model_test_varlists, testvar) );
-    return t_yes;
-}
-
-void theory_str::finite_model_test(expr * testvar, expr * str) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    zstring s;
-    if (!u.str.is_string(str, s)) return;
-    if (s == "yes") {
-        TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;);
-        ptr_vector<expr> & vars = finite_model_test_varlists[testvar];
-        for (ptr_vector<expr>::iterator it = vars.begin(); it != vars.end(); ++it) {
-            expr * v = *it;
-            bool v_has_eqc = false;
-            get_eqc_value(v, v_has_eqc);
-            if (v_has_eqc) {
-                TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;);
-                continue;
-            }
-            // check for any sort of existing length tester we might interfere with
-            if (m_params.m_UseBinarySearch) {
-                if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) {
-                    TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;);
-                    continue;
-                } else {
-                    // start binary search as normal
-                    expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m);
-                    expr_ref implRhs(binary_search_length_test(v, NULL, ""), m);
-                    assert_implication(implLhs, implRhs);
-                }
-            } else {
-                bool map_effectively_empty = false;
-                if (!fvar_len_count_map.contains(v)) {
-                    map_effectively_empty = true;
-                }
-
-                if (!map_effectively_empty) {
-                    map_effectively_empty = true;
-                    ptr_vector<expr> indicator_set = fvar_lenTester_map[v];
-                    for (ptr_vector<expr>::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) {
-                        expr * indicator = *it;
-                        if (internal_variable_set.find(indicator) != internal_variable_set.end()) {
-                            map_effectively_empty = false;
-                            break;
-                        }
-                    }
-                }
-
-                if (map_effectively_empty) {
-                    TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;);
-                    rational v_len;
-                    rational v_lower_bound;
-                    rational v_upper_bound;
-                    expr_ref vLengthExpr(mk_strlen(v), m);
-                    if (get_len_value(v, v_len)) {
-                        TRACE("str", tout << "length = " << v_len.to_string() << std::endl;);
-                        v_lower_bound = v_len;
-                        v_upper_bound = v_len;
-                    } else {
-                        bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound);
-                        bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound);
-                        TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?")
-                                << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;);
-
-                        // make sure the bounds are non-negative
-                        if (lower_bound_exists && v_lower_bound.is_neg()) {
-                            v_lower_bound = rational::zero();
-                        }
-                        if (upper_bound_exists && v_upper_bound.is_neg()) {
-                            v_upper_bound = rational::zero();
-                        }
-
-                        if (lower_bound_exists && upper_bound_exists) {
-                            // easiest case. we will search within these bounds
-                        } else if (upper_bound_exists && !lower_bound_exists) {
-                            // search between 0 and the upper bound
-                            v_lower_bound == rational::zero();
-                        } else if (lower_bound_exists && !upper_bound_exists) {
-                            // check some finite portion of the search space
-                            v_upper_bound = v_lower_bound + rational(10);
-                        } else {
-                            // no bounds information
-                            v_lower_bound = rational::zero();
-                            v_upper_bound = v_lower_bound + rational(10);
-                        }
-                    }
-                    // now create a fake length tester over this finite disjunction of lengths
-
-                    fvar_len_count_map[v] = 1;
-                    unsigned int testNum = fvar_len_count_map[v];
-
-                    expr_ref indicator(mk_internal_lenTest_var(v, testNum), m);
-                    SASSERT(indicator);
-                    m_trail.push_back(indicator);
-
-                    fvar_lenTester_map[v].shrink(0);
-                    fvar_lenTester_map[v].push_back(indicator);
-                    lenTester_fvar_map[indicator] = v;
-
-                    expr_ref_vector orList(m);
-                    expr_ref_vector andList(m);
-
-                    for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) {
-                        zstring lStr = zstring(l.to_string().c_str());
-                        expr_ref str_indicator(mk_string(lStr), m);
-                        expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m);
-                        orList.push_back(or_expr);
-                        expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m);
-                        andList.push_back(and_expr);
-                    }
-                    andList.push_back(mk_or(orList));
-                    expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m);
-                    expr_ref implRhs(mk_and(andList), m);
-                    assert_implication(implLhs, implRhs);
-                } else {
-                    TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;);
-                    continue;
-                }
-            }
-        } // foreach (v in vars)
-    } // (s == "yes")
-}
-
-void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) {
-    ast_manager & m = get_manager();
-    if (lenTester_fvar_map.contains(lenTester)) {
-        expr * fVar = lenTester_fvar_map[lenTester];
-        expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue);
-        TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;);
-        if (toAssert != NULL) {
-            assert_axiom(toAssert);
-        }
-    }
-}
-
-void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) {
-    ast_manager & m = get_manager();
-
-    expr * fVar = valueTester_fvar_map[valTester];
-    if (m_params.m_UseBinarySearch) {
-        if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) {
-            TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;);
-            NOT_IMPLEMENTED_YET();
-        }
-        expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back();
-        bool hasEqcValue;
-        expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue);
-        if (!hasEqcValue) {
-            TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;);
-        } else {
-            // safety check
-            zstring effectiveLenIndiStr;
-            u.str.is_string(len_indicator_value, effectiveLenIndiStr);
-            if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") {
-                TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;);
-                UNREACHABLE();
-            }
-            expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue);
-            TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;);
-            if (valueAssert != NULL) {
-                assert_axiom(valueAssert);
-            }
-        }
-    } else {
-        int lenTesterCount = fvar_lenTester_map[fVar].size();
-
-        expr * effectiveLenInd = NULL;
-        zstring effectiveLenIndiStr = "";
-        for (int i = 0; i < lenTesterCount; ++i) {
-            expr * len_indicator_pre = fvar_lenTester_map[fVar][i];
-            bool indicatorHasEqcValue = false;
-            expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue);
-            if (indicatorHasEqcValue) {
-                zstring len_pIndiStr;
-                u.str.is_string(len_indicator_value, len_pIndiStr);
-                if (len_pIndiStr != "more") {
-                    effectiveLenInd = len_indicator_pre;
-                    effectiveLenIndiStr = len_pIndiStr;
-                    break;
-                }
-            }
-        }
-        expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue);
-        TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;);
-        if (valueAssert != NULL) {
-            assert_axiom(valueAssert);
-        }
-    }
-}
-
-bool theory_str::free_var_attempt(expr * nn1, expr * nn2) {
-    ast_manager & m = get_manager();
-    zstring nn2_str;
-    if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) {
-        TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m)
-                << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;);
-        more_len_tests(nn1, nn2_str);
-        return true;
-    } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) {
-        if (nn2_str == "more") {
-            TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m)
-                            << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;);
-            more_value_tests(nn1, nn2_str);
-        }
-        return true;
-    } else if (internal_unrollTest_vars.contains(nn1)) {
-    	return true;
-    } else {
-        return false;
-    }
-}
-
-void theory_str::handle_equality(expr * lhs, expr * rhs) {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-    // both terms must be of sort String
-    sort * lhs_sort = m.get_sort(lhs);
-    sort * rhs_sort = m.get_sort(rhs);
-    sort * str_sort = u.str.mk_string_sort();
-
-    if (lhs_sort != str_sort || rhs_sort != str_sort) {
-        TRACE("str", tout << "skip equality: not String sort" << std::endl;);
-        return;
-    }
-
-    /* // temporarily disabled, we are borrowing these testers for something else
-    if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) {
-        if (finite_model_test_varlists.contains(lhs)) {
-            finite_model_test(lhs, rhs); return;
-        } else if (finite_model_test_varlists.contains(rhs)) {
-            finite_model_test(rhs, lhs); return;
-        }
-    }
-    */
-
-    if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) {
-        return;
-    }
-
-    if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) {
-        bool nn1HasEqcValue = false;
-        bool nn2HasEqcValue = false;
-        expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue);
-        expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue);
-        if (nn1HasEqcValue && !nn2HasEqcValue) {
-            simplify_parent(rhs, nn1_value);
-        }
-        if (!nn1HasEqcValue && nn2HasEqcValue) {
-            simplify_parent(lhs, nn2_value);
-        }
-
-        expr * nn1_arg0 = to_app(lhs)->get_arg(0);
-        expr * nn1_arg1 = to_app(lhs)->get_arg(1);
-        expr * nn2_arg0 = to_app(rhs)->get_arg(0);
-        expr * nn2_arg1 = to_app(rhs)->get_arg(1);
-        if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) {
-            TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;);
-            return;
-        }
-
-        if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) {
-            TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;);
-            return;
-        }
-    }
-
-    if (opt_DeferEQCConsistencyCheck) {
-        TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;);
-    } else {
-        // newEqCheck() -- check consistency wrt. existing equivalence classes
-        if (!new_eq_check(lhs, rhs)) {
-            return;
-        }
-    }
-
-    // BEGIN new_eq_handler() in strTheory
-
-    {
-        rational nn1Len, nn2Len;
-        bool nn1Len_exists = get_len_value(lhs, nn1Len);
-        bool nn2Len_exists = get_len_value(rhs, nn2Len);
-        expr * emptyStr = mk_string("");
-
-        if (nn1Len_exists && nn1Len.is_zero()) {
-            if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) {
-                expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m);
-                expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m);
-                expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m);
-                assert_axiom(toAssert);
-            }
-        }
-
-        if (nn2Len_exists && nn2Len.is_zero()) {
-            if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) {
-                expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m);
-                expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m);
-                expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m);
-                assert_axiom(toAssert);
-            }
-        }
-    }
-
-    instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs));
-
-    // group terms by equivalence class (groupNodeInEqc())
-
-    std::set<expr*> eqc_concat_lhs;
-    std::set<expr*> eqc_var_lhs;
-    std::set<expr*> eqc_const_lhs;
-    group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs);
-
-    std::set<expr*> eqc_concat_rhs;
-    std::set<expr*> eqc_var_rhs;
-    std::set<expr*> eqc_const_rhs;
-    group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs);
-
-    TRACE("str",
-        tout << "lhs eqc:" << std::endl;
-        tout << "Concats:" << std::endl;
-        for (std::set<expr*>::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) {
-            expr * ex = *it;
-            tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
-        }
-        tout << "Variables:" << std::endl;
-        for (std::set<expr*>::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) {
-            expr * ex = *it;
-            tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
-        }
-        tout << "Constants:" << std::endl;
-        for (std::set<expr*>::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) {
-            expr * ex = *it;
-            tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
-        }
-
-        tout << "rhs eqc:" << std::endl;
-        tout << "Concats:" << std::endl;
-        for (std::set<expr*>::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) {
-            expr * ex = *it;
-            tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
-        }
-        tout << "Variables:" << std::endl;
-        for (std::set<expr*>::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) {
-            expr * ex = *it;
-            tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
-        }
-        tout << "Constants:" << std::endl;
-        for (std::set<expr*>::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) {
-            expr * ex = *it;
-            tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
-        }
-        );
-
-    // step 1: Concat == Concat
-    int hasCommon = 0;
-    if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) {
-        std::set<expr*>::iterator itor1 = eqc_concat_lhs.begin();
-        std::set<expr*>::iterator itor2 = eqc_concat_rhs.begin();
-        for (; itor1 != eqc_concat_lhs.end(); itor1++) {
-            if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) {
-                hasCommon = 1;
-                break;
-            }
-        }
-        for (; itor2 != eqc_concat_rhs.end(); itor2++) {
-            if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) {
-                hasCommon = 1;
-                break;
-            }
-        }
-        if (hasCommon == 0) {
-            if (opt_ConcatOverlapAvoid) {
-                bool found = false;
-                // check each pair and take the first ones that won't immediately overlap
-                for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) {
-                    expr * concat_lhs = *itor1;
-                    for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) {
-                        expr * concat_rhs = *itor2;
-                        if (will_result_in_overlap(concat_lhs, concat_rhs)) {
-                            TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and "
-                                    << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;);
-                        } else {
-                            TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and "
-                                    << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;);
-                            simplify_concat_equality(concat_lhs, concat_rhs);
-                            found = true;
-                            break;
-                        }
-                    }
-                }
-                if (!found) {
-                    TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;);
-                    simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin()));
-                }
-            } else {
-                // default behaviour
-                simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin()));
-            }
-        }
-    }
-
-    // step 2: Concat == Constant
-
-    if (eqc_const_lhs.size() != 0) {
-        expr * conStr = *(eqc_const_lhs.begin());
-        std::set<expr*>::iterator itor2 = eqc_concat_rhs.begin();
-        for (; itor2 != eqc_concat_rhs.end(); itor2++) {
-            solve_concat_eq_str(*itor2, conStr);
-        }
-    } else if (eqc_const_rhs.size() != 0) {
-        expr* conStr = *(eqc_const_rhs.begin());
-        std::set<expr*>::iterator itor1 = eqc_concat_lhs.begin();
-        for (; itor1 != eqc_concat_lhs.end(); itor1++) {
-            solve_concat_eq_str(*itor1, conStr);
-        }
-    }
-
-    // simplify parents wrt. the equivalence class of both sides
-    bool nn1HasEqcValue = false;
-    bool nn2HasEqcValue = false;
-    // we want the Z3str2 eqc check here...
-    expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue);
-    expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue);
-    if (nn1HasEqcValue && !nn2HasEqcValue) {
-        simplify_parent(rhs, nn1_value);
-    }
-
-    if (!nn1HasEqcValue && nn2HasEqcValue) {
-        simplify_parent(lhs, nn2_value);
-    }
-
-    expr * nn1EqConst = NULL;
-    std::set<expr*> nn1EqUnrollFuncs;
-    get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs);
-    expr * nn2EqConst = NULL;
-    std::set<expr*> nn2EqUnrollFuncs;
-    get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs);
-
-    if (nn2EqConst != NULL) {
-    	for (std::set<expr*>::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) {
-    		process_unroll_eq_const_str(*itor1, nn2EqConst);
-    	}
-    }
-
-    if (nn1EqConst != NULL) {
-    	for (std::set<expr*>::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) {
-    		process_unroll_eq_const_str(*itor2, nn1EqConst);
-    	}
-    }
-
-}
-
-void theory_str::set_up_axioms(expr * ex) {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-
-    sort * ex_sort = m.get_sort(ex);
-    sort * str_sort = u.str.mk_string_sort();
-    sort * bool_sort = m.mk_bool_sort();
-
-    family_id m_arith_fid = m.mk_family_id("arith");
-    sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT);
-
-    if (ex_sort == str_sort) {
-        TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
-                ": expr is of sort String" << std::endl;);
-        // set up basic string axioms
-        enode * n = ctx.get_enode(ex);
-        SASSERT(n);
-        m_basicstr_axiom_todo.push_back(n);
-        TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;);
-
-
-        if (is_app(ex)) {
-            app * ap = to_app(ex);
-            if (u.str.is_concat(ap)) {
-                // if ex is a concat, set up concat axioms later
-                m_concat_axiom_todo.push_back(n);
-                // we also want to check whether we can eval this concat,
-                // in case the rewriter did not totally finish with this term
-                m_concat_eval_todo.push_back(n);
-            } else if (u.str.is_length(ap)) {
-            	// if the argument is a variable,
-            	// keep track of this for later, we'll need it during model gen
-            	expr * var = ap->get_arg(0);
-            	app * aVar = to_app(var);
-            	if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) {
-            		input_var_in_len.insert(var);
-            	}
-            } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) {
-                m_library_aware_axiom_todo.push_back(n);
-            } else if (u.str.is_itos(ap)) {
-                TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;);
-                string_int_conversion_terms.push_back(ap);
-                m_library_aware_axiom_todo.push_back(n);
-            } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) {
-                // if ex is a variable, add it to our list of variables
-                TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;);
-                variable_set.insert(ex);
-                ctx.mark_as_relevant(ex);
-                // this might help??
-                theory_var v = mk_var(n);
-                TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;);
-            }
-        }
-    } else if (ex_sort == bool_sort) {
-        TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
-                ": expr is of sort Bool" << std::endl;);
-        // set up axioms for boolean terms
-
-        ensure_enode(ex);
-        if (ctx.e_internalized(ex)) {
-            enode * n = ctx.get_enode(ex);
-            SASSERT(n);
-
-            if (is_app(ex)) {
-                app * ap = to_app(ex);
-                if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) {
-                    m_library_aware_axiom_todo.push_back(n);
-                }
-            }
-        } else {
-            TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;);
-            ENSURE(!search_started); // infinite loop prevention
-            m_delayed_axiom_setup_terms.push_back(ex);
-            return;
-        }
-    } else if (ex_sort == int_sort) {
-        TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
-                ": expr is of sort Int" << std::endl;);
-        // set up axioms for integer terms
-        enode * n = ensure_enode(ex);
-        SASSERT(n);
-
-        if (is_app(ex)) {
-            app * ap = to_app(ex);
-            // TODO indexof2/lastindexof
-            if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) {
-                m_library_aware_axiom_todo.push_back(n);
-            } else if (u.str.is_stoi(ap)) {
-                TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;);
-            	string_int_conversion_terms.push_back(ap);
-            	m_library_aware_axiom_todo.push_back(n);
-            }
-        }
-    } else {
-        TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
-                ": expr is of wrong sort, ignoring" << std::endl;);
-    }
-
-    // if expr is an application, recursively inspect all arguments
-    if (is_app(ex)) {
-        app * term = (app*)ex;
-        unsigned num_args = term->get_num_args();
-        for (unsigned i = 0; i < num_args; i++) {
-            set_up_axioms(term->get_arg(i));
-        }
-    }
-}
-
-void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) {
-    TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;);
-    symbol strOverlap("!!TheoryStrOverlapAssumption!!");
-    seq_util m_sequtil(get_manager());
-    sort * s = get_manager().mk_bool_sort();
-    m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager());
-    assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term));
-}
-
-lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) {
-    bool assumptionFound = false;
-
-    app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term));
-    get_context().internalize(target_term, false);
-    for (unsigned i = 0; i < unsat_core.size(); ++i) {
-        app * core_term = to_app(unsat_core.get(i));
-        // not sure if this is the correct way to compare terms in this context
-        enode * e1;
-        enode * e2;
-        e1 = get_context().get_enode(target_term);
-        e2 = get_context().get_enode(core_term);
-        if (e1 == e2) {
-            TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;);
-            assumptionFound = true;
-            return l_undef;
-        }
-    }
-
-    return l_false;
-}
-
-void theory_str::init_search_eh() {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-
-    TRACE("str",
-        tout << "dumping all asserted formulas:" << std::endl;
-        unsigned nFormulas = ctx.get_num_asserted_formulas();
-        for (unsigned i = 0; i < nFormulas; ++i) {
-            expr * ex = ctx.get_asserted_formula(i);
-            tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl;
-        }
-    );
-    /*
-     * Recursive descent through all asserted formulas to set up axioms.
-     * Note that this is just the input structure and not necessarily things
-     * that we know to be true or false. We're just doing this to see
-     * which terms are explicitly mentioned.
-     */
-    unsigned nFormulas = ctx.get_num_asserted_formulas();
-    for (unsigned i = 0; i < nFormulas; ++i) {
-        expr * ex = ctx.get_asserted_formula(i);
-        set_up_axioms(ex);
-    }
-
-    /*
-     * Similar recursive descent, except over all initially assigned terms.
-     * This is done to find equalities between terms, etc. that we otherwise
-     * might not get a chance to see.
-     */
-
-    /*
-    expr_ref_vector assignments(m);
-    ctx.get_assignments(assignments);
-    for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) {
-        expr * ex = *i;
-        if (m.is_eq(ex)) {
-            TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) <<
-                    ": expr is equality" << std::endl;);
-            app * eq = (app*)ex;
-            SASSERT(eq->get_num_args() == 2);
-            expr * lhs = eq->get_arg(0);
-            expr * rhs = eq->get_arg(1);
-
-            enode * e_lhs = ctx.get_enode(lhs);
-            enode * e_rhs = ctx.get_enode(rhs);
-            std::pair<enode*,enode*> eq_pair(e_lhs, e_rhs);
-            m_str_eq_todo.push_back(eq_pair);
-        } else {
-            TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m)
-                    << ": expr ignored" << std::endl;);
-        }
-    }
-    */
-
-    // this might be cheating but we need to make sure that certain maps are populated
-    // before the first call to new_eq_eh()
-    propagate();
-
-    TRACE("str", tout << "search started" << std::endl;);
-    search_started = true;
-}
-
-void theory_str::new_eq_eh(theory_var x, theory_var y) {
-    //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;);
-    TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " <<
-                                  mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;);
-
-    /*
-    if (m_find.find(x) == m_find.find(y)) {
-        return;
-    }
-    */
-    handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner());
-
-    // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality
-    m_find.merge(x, y);
-}
-
-void theory_str::new_diseq_eh(theory_var x, theory_var y) {
-    //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;);
-    TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " <<
-                                  mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;);
-}
-
-void theory_str::relevant_eh(app * n) {
-    TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;);
-}
-
-void theory_str::assign_eh(bool_var v, bool is_true) {
-    context & ctx = get_context();
-    TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;);
-}
-
-void theory_str::push_scope_eh() {
-    theory::push_scope_eh();
-    m_trail_stack.push_scope();
-
-    sLevel += 1;
-    TRACE("str", tout << "push to " << sLevel << std::endl;);
-    TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); });
-}
-
-void theory_str::recursive_check_variable_scope(expr * ex) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    if (is_app(ex)) {
-        app * a = to_app(ex);
-        if (a->get_num_args() == 0) {
-            // we only care about string variables
-            sort * s = m.get_sort(ex);
-            sort * string_sort = u.str.mk_string_sort();
-            if (s != string_sort) {
-                return;
-            }
-            // base case: string constant / var
-            if (u.str.is_string(a)) {
-                return;
-            } else {
-                // assume var
-                if (variable_set.find(ex) == variable_set.end()
-                        && internal_variable_set.find(ex) == internal_variable_set.end()) {
-                    TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;);
-                }
-            }
-        } else {
-            for (unsigned i = 0; i < a->get_num_args(); ++i) {
-                recursive_check_variable_scope(a->get_arg(i));
-            }
-        }
-    }
-}
-
-void theory_str::check_variable_scope() {
-    if (!opt_CheckVariableScope) {
-        return;
-    }
-
-    if (!is_trace_enabled("t_str_detail")) {
-    	return;
-    }
-
-    TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;);
-
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    expr_ref_vector assignments(m);
-    ctx.get_assignments(assignments);
-    for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) {
-        expr * ex = *i;
-        recursive_check_variable_scope(ex);
-    }
-}
-
-void theory_str::pop_scope_eh(unsigned num_scopes) {
-    sLevel -= num_scopes;
-    TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;);
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); });
-
-    // list of expr* to remove from cut_var_map
-    ptr_vector<expr> cutvarmap_removes;
-
-    obj_map<expr, std::stack<T_cut *> >::iterator varItor = cut_var_map.begin();
-    while (varItor != cut_var_map.end()) {
-        expr * e = varItor->m_key;
-    	std::stack<T_cut*> & val = cut_var_map[varItor->m_key];
-        while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) {
-            TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout););
-            T_cut * aCut = val.top();
-            val.pop();
-            // dealloc(aCut);
-        }
-        if (val.size() == 0) {
-        	cutvarmap_removes.insert(varItor->m_key);
-        }
-        varItor++;
-    }
-
-    if (!cutvarmap_removes.empty()) {
-    	ptr_vector<expr>::iterator it = cutvarmap_removes.begin();
-    	for (; it != cutvarmap_removes.end(); ++it) {
-    		expr * ex = *it;
-    		cut_var_map.remove(ex);
-    	}
-    }
-
-    ptr_vector<enode> new_m_basicstr;
-    for (ptr_vector<enode>::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) {
-        enode * e = *it;
-        app * a = e->get_owner();
-        TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager())
-                << ", enode scope level is " << e->get_iscope_lvl()
-                << std::endl;);
-        if (e->get_iscope_lvl() <= (unsigned)sLevel) {
-            new_m_basicstr.push_back(e);
-        }
-    }
-    m_basicstr_axiom_todo.reset();
-    m_basicstr_axiom_todo = new_m_basicstr;
-
-    m_trail_stack.pop_scope(num_scopes);
-    theory::pop_scope_eh(num_scopes);
-
-    //check_variable_scope();
-}
-
-void theory_str::dump_assignments() {
-    TRACE_CODE(
+    void theory_str::solve_concat_eq_str(expr * concat, expr * str) {
         ast_manager & m = get_manager();
         context & ctx = get_context();
-        tout << "dumping all assignments:" << std::endl;
-        expr_ref_vector assignments(m);
-        ctx.get_assignments(assignments);
-        for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) {
-            expr * ex = *i;
-            tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl;
-        }
-	);
-}
 
-void theory_str::classify_ast_by_type(expr * node, std::map<expr*, int> & varMap,
-		std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap) {
+        TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;);
 
-	// check whether the node is a string variable;
-	// testing set membership here bypasses several expensive checks.
-    // note that internal variables don't count if they're only length tester / value tester vars.
-	if (variable_set.find(node) != variable_set.end()
-			&& internal_lenTest_vars.find(node) == internal_lenTest_vars.end()
-			&& internal_valTest_vars.find(node) == internal_valTest_vars.end()
-	        && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) {
-	    if (varMap[node] != 1) {
-	        TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;);
-	    }
-		varMap[node] = 1;
-	}
-	// check whether the node is a function that we want to inspect
-	else if (is_app(node)) {
-		app * aNode = to_app(node);
-		if (u.str.is_length(aNode)) {
-			// Length
-			return;
-		} else if (u.str.is_concat(aNode)) {
-			expr * arg0 = aNode->get_arg(0);
-			expr * arg1 = aNode->get_arg(1);
-			bool arg0HasEq = false;
-			bool arg1HasEq = false;
-			expr * arg0Val = get_eqc_value(arg0, arg0HasEq);
-			expr * arg1Val = get_eqc_value(arg1, arg1HasEq);
+        zstring const_str;
+        if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) {
+            app * a_concat = to_app(concat);
+            SASSERT(a_concat->get_num_args() == 2);
+            expr * a1 = a_concat->get_arg(0);
+            expr * a2 = a_concat->get_arg(1);
 
-			int canskip = 0;
-			zstring tmp;
-			u.str.is_string(arg0Val, tmp);
-			if (arg0HasEq && tmp.empty()) {
-				canskip = 1;
-			}
-			u.str.is_string(arg1Val, tmp);
-			if (canskip == 0 && arg1HasEq && tmp.empty()) {
-				canskip = 1;
-			}
-			if (canskip == 0 && concatMap.find(node) == concatMap.end()) {
-				concatMap[node] = 1;
-			}
-		} else if (u.re.is_unroll(aNode)) {
-			// Unroll
-			if (unrollMap.find(node) == unrollMap.end()) {
-				unrollMap[node] = 1;
-			}
-		}
-		// recursively visit all arguments
-		for (unsigned i = 0; i < aNode->get_num_args(); ++i) {
-			expr * arg = aNode->get_arg(i);
-			classify_ast_by_type(arg, varMap, concatMap, unrollMap);
-		}
-	}
-}
+            if (const_str.empty()) {
+                TRACE("str", tout << "quick path: concat == \"\"" << std::endl;);
+                // assert the following axiom:
+                // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") )
 
-// NOTE: this function used to take an argument `Z3_ast node`;
-// it was not used and so was removed from the signature
-void theory_str::classify_ast_by_type_in_positive_context(std::map<expr*, int> & varMap,
-		std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap) {
 
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-	expr_ref_vector assignments(m);
-	ctx.get_assignments(assignments);
+                expr_ref premise(ctx.mk_eq_atom(concat, str), m);
+                expr_ref c1(ctx.mk_eq_atom(a1, str), m);
+                expr_ref c2(ctx.mk_eq_atom(a2, str), m);
+                expr_ref conclusion(m.mk_and(c1, c2), m);
+                assert_implication(premise, conclusion);
 
-	for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) {
-		expr * argAst = *it;
-		// the original code jumped through some hoops to check whether the AST node
-		// is a function, then checked whether that function is "interesting".
-		// however, the only thing that's considered "interesting" is an equality predicate.
-		// so we bypass a huge amount of work by doing the following...
-
-		if (m.is_eq(argAst)) {
-		    TRACE("str", tout
-		            << "eq ast " << mk_pp(argAst, m) << " is between args of sort "
-		            << m.get_sort(to_app(argAst)->get_arg(0))->get_name()
-		            << std::endl;);
-			classify_ast_by_type(argAst, varMap, concatMap, unrollMap);
-		}
-	}
-}
-
-inline expr * theory_str::get_alias_index_ast(std::map<expr*, expr*> & aliasIndexMap, expr * node) {
-  if (aliasIndexMap.find(node) != aliasIndexMap.end())
-    return aliasIndexMap[node];
-  else
-    return node;
-}
-
-inline expr * theory_str::getMostLeftNodeInConcat(expr * node) {
-	app * aNode = to_app(node);
-	if (!u.str.is_concat(aNode)) {
-		return node;
-	} else {
-		expr * concatArgL = aNode->get_arg(0);
-		return getMostLeftNodeInConcat(concatArgL);
-	}
-}
-
-inline expr * theory_str::getMostRightNodeInConcat(expr * node) {
-	app * aNode = to_app(node);
-	if (!u.str.is_concat(aNode)) {
-		return node;
-	} else {
-		expr * concatArgR = aNode->get_arg(1);
-		return getMostRightNodeInConcat(concatArgR);
-	}
-}
-
-void theory_str::trace_ctx_dep(std::ofstream & tout,
-        std::map<expr*, expr*> & aliasIndexMap,
-        std::map<expr*, expr*> & var_eq_constStr_map,
-        std::map<expr*, std::map<expr*, int> > & var_eq_concat_map,
-		std::map<expr*, std::map<expr*, int> > & var_eq_unroll_map,
-        std::map<expr*, expr*> & concat_eq_constStr_map,
-        std::map<expr*, std::map<expr*, int> > & concat_eq_concat_map,
-		std::map<expr*, std::set<expr*> > & unrollGroupMap) {
-#ifdef _TRACE
-	context & ctx = get_context();
-    ast_manager & mgr = get_manager();
-    {
-        tout << "(0) alias: variables" << std::endl;
-        std::map<expr*, std::map<expr*, int> > aliasSumMap;
-        std::map<expr*, expr*>::iterator itor0 = aliasIndexMap.begin();
-        for (; itor0 != aliasIndexMap.end(); itor0++) {
-            aliasSumMap[itor0->second][itor0->first] = 1;
-        }
-        std::map<expr*, std::map<expr*, int> >::iterator keyItor = aliasSumMap.begin();
-        for (; keyItor != aliasSumMap.end(); keyItor++) {
-            tout << "    * ";
-            tout << mk_pp(keyItor->first, mgr);
-            tout << " : ";
-            std::map<expr*, int>::iterator innerItor = keyItor->second.begin();
-            for (; innerItor != keyItor->second.end(); innerItor++) {
-                tout << mk_pp(innerItor->first, mgr);
-                tout << ", ";
+                return;
             }
-            tout << std::endl;
-        }
-        tout << std::endl;
-    }
-
-    {
-        tout << "(1) var = constStr:" << std::endl;
-        std::map<expr*, expr*>::iterator itor1 = var_eq_constStr_map.begin();
-        for (; itor1 != var_eq_constStr_map.end(); itor1++) {
-            tout << "    * ";
-            tout << mk_pp(itor1->first, mgr);
-            tout << " = ";
-            tout << mk_pp(itor1->second, mgr);
-            if (!in_same_eqc(itor1->first, itor1->second)) {
-                tout << "   (not true in ctx)";
-            }
-            tout << std::endl;
-        }
-        tout << std::endl;
-    }
-
-    {
-        tout << "(2) var = concat:" << std::endl;
-        std::map<expr*, std::map<expr*, int> >::iterator itor2 = var_eq_concat_map.begin();
-        for (; itor2 != var_eq_concat_map.end(); itor2++) {
-            tout << "    * ";
-            tout << mk_pp(itor2->first, mgr);
-            tout << " = { ";
-            std::map<expr*, int>::iterator i_itor = itor2->second.begin();
-            for (; i_itor != itor2->second.end(); i_itor++) {
-                tout << mk_pp(i_itor->first, mgr);
-                tout << ", ";
-            }
-            tout << std::endl;
-        }
-        tout << std::endl;
-    }
-
-    {
-        tout << "(3) var = unrollFunc:" << std::endl;
-        std::map<expr*, std::map<expr*, int> >::iterator itor2 = var_eq_unroll_map.begin();
-        for (; itor2 != var_eq_unroll_map.end(); itor2++) {
-            tout << "    * " << mk_pp(itor2->first, mgr) << " = { ";
-            std::map<expr*, int>::iterator i_itor = itor2->second.begin();
-            for (; i_itor != itor2->second.end(); i_itor++) {
-            	tout << mk_pp(i_itor->first, mgr) << ", ";
-            }
-            tout << " }" << std::endl;
-        }
-        tout << std::endl;
-    }
-
-    {
-        tout << "(4) concat = constStr:" << std::endl;
-        std::map<expr*, expr*>::iterator itor3 = concat_eq_constStr_map.begin();
-        for (; itor3 != concat_eq_constStr_map.end(); itor3++) {
-            tout << "    * ";
-            tout << mk_pp(itor3->first, mgr);
-            tout << " = ";
-            tout << mk_pp(itor3->second, mgr);
-            tout << std::endl;
-
-        }
-        tout << std::endl;
-    }
-
-    {
-        tout << "(5) eq concats:" << std::endl;
-        std::map<expr*, std::map<expr*, int> >::iterator itor4 = concat_eq_concat_map.begin();
-        for (; itor4 != concat_eq_concat_map.end(); itor4++) {
-            if (itor4->second.size() > 1) {
-                std::map<expr*, int>::iterator i_itor = itor4->second.begin();
-                tout << "    * ";
-                for (; i_itor != itor4->second.end(); i_itor++) {
-                    tout << mk_pp(i_itor->first, mgr);
-                    tout << " , ";
+            bool arg1_has_eqc_value = false;
+            bool arg2_has_eqc_value = false;
+            expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value);
+            expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value);
+            expr_ref newConcat(m);
+            if (arg1 != a1 || arg2 != a2) {
+                TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;);
+                int iPos = 0;
+                expr_ref_vector item1(m);
+                if (a1 != arg1) {
+                    item1.push_back(ctx.mk_eq_atom(a1, arg1));
+                    iPos += 1;
                 }
-                tout << std::endl;
-            }
-        }
-        tout << std::endl;
-    }
-
-    {
-        tout << "(6) eq unrolls:" << std::endl;
-        std::map<expr*, std::set<expr*> >::iterator itor5 = unrollGroupMap.begin();
-        for (; itor5 != unrollGroupMap.end(); itor5++) {
-            tout << "    * ";
-            std::set<expr*>::iterator i_itor = itor5->second.begin();
-            for (; i_itor != itor5->second.end(); i_itor++) {
-            	tout << mk_pp(*i_itor, mgr) << ",  ";
-            }
-            tout << std::endl;
-        }
-        tout << std::endl;
-    }
-
-    {
-        tout << "(7) unroll = concats:" << std::endl;
-        std::map<expr*, std::set<expr*> >::iterator itor5 = unrollGroupMap.begin();
-        for (; itor5 != unrollGroupMap.end(); itor5++) {
-            tout << "    * ";
-            expr * unroll = itor5->first;
-            tout << mk_pp(unroll, mgr) << std::endl;
-            enode * e_curr = ctx.get_enode(unroll);
-            enode * e_curr_end = e_curr;
-            do {
-            	app * curr = e_curr->get_owner();
-                if (u.str.is_concat(curr)) {
-                    tout << "      >>> " << mk_pp(curr, mgr) << std::endl;
+                if (a2 != arg2) {
+                    item1.push_back(ctx.mk_eq_atom(a2, arg2));
+                    iPos += 1;
                 }
-                e_curr = e_curr->get_next();
-            } while (e_curr != e_curr_end);
-            tout << std::endl;
-        }
-        tout << std::endl;
-    }
-#else
-    return;
-#endif // _TRACE
-}
-
-
-/*
- * Dependence analysis from current context assignment
- * - "freeVarMap" contains a set of variables that doesn't constrained by Concats.
- *    But it's possible that it's bounded by unrolls
- *    For the case of
- *    (1) var1 = unroll(r1, t1)
- *        var1 is in the freeVarMap
- *        > should unroll r1 for var1
- *    (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3)
- *        var2, var3 are all in freeVar
- *        > should split the unroll function so that var2 and var3 are bounded by new unrolls
- */
-int theory_str::ctx_dep_analysis(std::map<expr*, int> & strVarMap, std::map<expr*, int> & freeVarMap,
-		std::map<expr*, std::set<expr*> > & unrollGroupMap, std::map<expr*, std::map<expr*, int> > & var_eq_concat_map) {
-	std::map<expr*, int> concatMap;
-	std::map<expr*, int> unrollMap;
-	std::map<expr*, expr*> aliasIndexMap;
-	std::map<expr*, expr*> var_eq_constStr_map;
-	std::map<expr*, expr*> concat_eq_constStr_map;
-	std::map<expr*, std::map<expr*, int> > var_eq_unroll_map;
-	std::map<expr*, std::map<expr*, int> > concat_eq_concat_map;
-	std::map<expr*, std::map<expr*, int> > depMap;
-
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	// note that the old API concatenated these assignments into
-	// a massive conjunction; we may have the opportunity to avoid that here
-	expr_ref_vector assignments(m);
-	ctx.get_assignments(assignments);
-
-	// Step 1: get variables / concat AST appearing in the context
-	// the thing we iterate over should just be variable_set - internal_variable_set
-	// so we avoid computing the set difference (but this might be slower)
-	for(obj_hashtable<expr>::iterator it = variable_set.begin(); it != variable_set.end(); ++it) {
-		expr* var = *it;
-		if (internal_variable_set.find(var) == internal_variable_set.end()) {
-		    TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;);
-		    strVarMap[*it] = 1;
-		}
-	}
-	classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap);
-
-	std::map<expr*, expr*> aliasUnrollSet;
-	std::map<expr*, int>::iterator unrollItor = unrollMap.begin();
-	for (; unrollItor != unrollMap.end(); ++unrollItor) {
-		if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) {
-			continue;
-		}
-		expr * aRoot = NULL;
-		enode * e_currEqc = ctx.get_enode(unrollItor->first);
-		enode * e_curr = e_currEqc;
-		do {
-			app * curr = e_currEqc->get_owner();
-			if (u.re.is_unroll(curr)) {
-				if (aRoot == NULL) {
-					aRoot = curr;
-				}
-				aliasUnrollSet[curr] = aRoot;
-			}
-			e_currEqc = e_currEqc->get_next();
-		} while (e_currEqc != e_curr);
-	}
-
-	for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) {
-	    expr * unrFunc = unrollItor->first;
-	    expr * urKey = aliasUnrollSet[unrFunc];
-	    unrollGroupMap[urKey].insert(unrFunc);
-	}
-
-	// Step 2: collect alias relation
-	// e.g. suppose we have the equivalence class {x, y, z};
-	// then we set aliasIndexMap[y] = x
-	// and aliasIndexMap[z] = x
-
-	std::map<expr*, int>::iterator varItor = strVarMap.begin();
-	for (; varItor != strVarMap.end(); ++varItor) {
-	    if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) {
-	        continue;
-	    }
-	    expr * aRoot = NULL;
-	    expr * curr = varItor->first;
-	    do {
-	        if (variable_set.find(curr) != variable_set.end()) {
-	            if (aRoot == NULL) {
-	                aRoot = curr;
-	            } else {
-	                aliasIndexMap[curr] = aRoot;
-	            }
-	        }
-	        curr = get_eqc_next(curr);
-	    } while (curr != varItor->first);
-	}
-
-	// Step 3: Collect interested cases
-
-	varItor = strVarMap.begin();
-	for (; varItor != strVarMap.end(); ++varItor) {
-	    expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first);
-	    // Case 1: variable = string constant
-	    // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1"
-
-	    if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) {
-	        bool nodeHasEqcValue = false;
-	        expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue);
-	        if (nodeHasEqcValue) {
-	            var_eq_constStr_map[deAliasNode] = nodeValue;
-	        }
-	    }
-
-	    // Case 2: var_eq_concat
-	    // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1
-	    // var_eq_unroll
-	    // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1
-
-	    if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) {
-	        expr * curr = get_eqc_next(deAliasNode);
-	        while (curr != deAliasNode) {
-	            app * aCurr = to_app(curr);
-	            // collect concat
-	            if (u.str.is_concat(aCurr)) {
-	                expr * arg0 = aCurr->get_arg(0);
-	                expr * arg1 = aCurr->get_arg(1);
-	                bool arg0HasEqcValue = false;
-	                bool arg1HasEqcValue = false;
-	                expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue);
-	                expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue);
-
-	                bool is_arg0_emptyStr = false;
-	                if (arg0HasEqcValue) {
-	                    zstring strval;
-	                    u.str.is_string(arg0_value, strval);
-	                    if (strval.empty()) {
-	                        is_arg0_emptyStr = true;
-	                    }
-	                }
-
-	                bool is_arg1_emptyStr = false;
-	                if (arg1HasEqcValue) {
-	                    zstring strval;
-	                    u.str.is_string(arg1_value, strval);
-	                    if (strval.empty()) {
-	                        is_arg1_emptyStr = true;
-	                    }
-	                }
-
-	                if (!is_arg0_emptyStr && !is_arg1_emptyStr) {
-	                    var_eq_concat_map[deAliasNode][curr] = 1;
-	                }
-	            } else if (u.re.is_unroll(to_app(curr))) {
-	                var_eq_unroll_map[deAliasNode][curr] = 1;
-	            }
-
-	            curr = get_eqc_next(curr);
-	        }
-	    }
-
-	} // for(varItor in strVarMap)
-
-	// --------------------------------------------------
-	// * collect aliasing relation among eq concats
-	//   e.g EQC={concat1, concat2, concat3}
-	//       concats_eq_Index_map[concat2] = concat1
-	//       concats_eq_Index_map[concat3] = concat1
-	// --------------------------------------------------
-
-	std::map<expr*, expr*> concats_eq_index_map;
-	std::map<expr*, int>::iterator concatItor = concatMap.begin();
-	for(; concatItor != concatMap.end(); ++concatItor) {
-		if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) {
-			continue;
-		}
-		expr * aRoot = NULL;
-		expr * curr = concatItor->first;
-		do {
-			if (u.str.is_concat(to_app(curr))) {
-				if (aRoot == NULL) {
-					aRoot = curr;
-				} else {
-					concats_eq_index_map[curr] = aRoot;
-				}
-			}
-			curr = get_eqc_next(curr);
-		} while (curr != concatItor->first);
-	}
-
-	concatItor = concatMap.begin();
-	for(; concatItor != concatMap.end(); ++concatItor) {
-		expr * deAliasConcat = NULL;
-		if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) {
-			deAliasConcat = concats_eq_index_map[concatItor->first];
-		} else {
-			deAliasConcat = concatItor->first;
-		}
-
-		// (3) concat_eq_conststr, e.g. concat(a,b) = "str1"
-		if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) {
-			bool nodeHasEqcValue = false;
-			expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue);
-			if (nodeHasEqcValue) {
-				concat_eq_constStr_map[deAliasConcat] = nodeValue;
-			}
-		}
-
-		// (4) concat_eq_concat, e.g.
-		// concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f)
-		if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) {
-			expr * curr = deAliasConcat;
-			do {
-				if (u.str.is_concat(to_app(curr))) {
-					// curr cannot be reduced
-					if (concatMap.find(curr) != concatMap.end()) {
-						concat_eq_concat_map[deAliasConcat][curr] = 1;
-					}
-				}
-				curr = get_eqc_next(curr);
-			} while (curr != deAliasConcat);
-		}
-	}
-
-	// print some debugging info
-	TRACE("str", trace_ctx_dep(tout, aliasIndexMap, var_eq_constStr_map,
-	        var_eq_concat_map, var_eq_unroll_map,
-			concat_eq_constStr_map, concat_eq_concat_map, unrollGroupMap););
-
-	if (!contain_pair_bool_map.empty()) {
-		compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map);
-	}
-
-	// step 4: dependence analysis
-
-	// (1) var = string constant
-	for (std::map<expr*, expr*>::iterator itor = var_eq_constStr_map.begin();
-			itor != var_eq_constStr_map.end(); ++itor) {
-		expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
-		expr * strAst = itor->second;
-		depMap[var][strAst] = 1;
-	}
-
-	// (2) var = concat
-	for (std::map<expr*, std::map<expr*, int> >::iterator itor = var_eq_concat_map.begin();
-			itor != var_eq_concat_map.end(); ++itor) {
-		expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
-		for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) {
-			expr * concat = itor1->first;
-			std::map<expr*, int> inVarMap;
-			std::map<expr*, int> inConcatMap;
-			std::map<expr*, int> inUnrollMap;
-			classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap);
-			for (std::map<expr*, int>::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) {
-				expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first);
-				if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) {
-					depMap[var][varInConcat] = 2;
-				}
-			}
-		}
-	}
-
-	for (std::map<expr*, std::map<expr*, int> >::iterator itor = var_eq_unroll_map.begin();
-		itor != var_eq_unroll_map.end(); itor++) {
-		expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
-		for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
-			expr * unrollFunc = itor1->first;
-			std::map<expr*, int> inVarMap;
-			std::map<expr*, int> inConcatMap;
-			std::map<expr*, int> inUnrollMap;
-			classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap);
-			for (std::map<expr*, int>::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) {
-				expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first);
-
-				TRACE("str", tout << "var in unroll = " <<
-						mk_ismt2_pp(itor2->first, m) << std::endl
-						<< "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;);
-
-				// it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0),
-				// while $$_regVar_0 = "abcd"
-				// have to exclude such cases
-				bool varHasValue = false;
-				get_eqc_value(varInFunc, varHasValue);
-				if (varHasValue)
-					continue;
-
-				if (depMap[var].find(varInFunc) == depMap[var].end()) {
-					depMap[var][varInFunc] = 6;
-				}
-			}
-		}
-	}
-
-	// (3) concat = string constant
-	for (std::map<expr*, expr*>::iterator itor = concat_eq_constStr_map.begin();
-			itor != concat_eq_constStr_map.end(); itor++) {
-		expr * concatAst = itor->first;
-		expr * constStr = itor->second;
-		std::map<expr*, int> inVarMap;
-		std::map<expr*, int> inConcatMap;
-		std::map<expr*, int> inUnrollMap;
-		classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap);
-		for (std::map<expr*, int>::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) {
-			expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first);
-			if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1))
-				depMap[varInConcat][constStr] = 3;
-		}
-	}
-
-	// (4) equivalent concats
-	//     - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6)
-	//         ==> v2, v5 are constrained by "str"
-	//     - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5)
-	//         ==> v2, v4 are constrained by "str"
-	//--------------------------------------------------------------
-
-	std::map<expr*, expr*> mostLeftNodes;
-	std::map<expr*, expr*> mostRightNodes;
-
-	std::map<expr*, int> mLIdxMap;
-	std::map<int, std::set<expr*> > mLMap;
-	std::map<expr*, int> mRIdxMap;
-	std::map<int, std::set<expr*> > mRMap;
-	std::set<expr*> nSet;
-
-	for (std::map<expr*, std::map<expr*, int> >::iterator itor = concat_eq_concat_map.begin();
-			itor != concat_eq_concat_map.end(); itor++) {
-		mostLeftNodes.clear();
-		mostRightNodes.clear();
-
-		expr * mLConst = NULL;
-		expr * mRConst = NULL;
-
-		for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
-			expr * concatNode = itor1->first;
-			expr * mLNode = getMostLeftNodeInConcat(concatNode);
-			zstring strval;
-			if (u.str.is_string(to_app(mLNode), strval)) {
-				if (mLConst == NULL && strval.empty()) {
-					mLConst = mLNode;
-				}
-			} else {
-				mostLeftNodes[mLNode] = concatNode;
-			}
-
-			expr * mRNode = getMostRightNodeInConcat(concatNode);
-			if (u.str.is_string(to_app(mRNode), strval)) {
-				if (mRConst == NULL && strval.empty()) {
-					mRConst = mRNode;
-				}
-			} else {
-				mostRightNodes[mRNode] = concatNode;
-			}
-		}
-
-		if (mLConst != NULL) {
-			// -------------------------------------------------------------------------------------
-			// The left most variable in a concat is constrained by a constant string in eqc concat
-			// -------------------------------------------------------------------------------------
-			// e.g. Concat(x, ...) = Concat("abc", ...)
-			// -------------------------------------------------------------------------------------
-			for (std::map<expr*, expr*>::iterator itor1 = mostLeftNodes.begin();
-					itor1 != mostLeftNodes.end(); itor1++) {
-				expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first);
-				if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) {
-					depMap[deVar][mLConst] = 4;
-				}
-			}
-		}
-
-		{
-			// -------------------------------------------------------------------------------------
-			// The left most variables in eqc concats are constrained by each other
-			// -------------------------------------------------------------------------------------
-			// e.g. concat(x, ...) = concat(u, ...) = ...
-			//      x and u are constrained by each other
-			// -------------------------------------------------------------------------------------
-			nSet.clear();
-			std::map<expr*, expr*>::iterator itl = mostLeftNodes.begin();
-			for (; itl != mostLeftNodes.end(); itl++) {
-				bool lfHasEqcValue = false;
-				get_eqc_value(itl->first, lfHasEqcValue);
-				if (lfHasEqcValue)
-					continue;
-				expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first);
-				nSet.insert(deVar);
-			}
-
-			if (nSet.size() > 1) {
-				int lId = -1;
-				for (std::set<expr*>::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) {
-					if (mLIdxMap.find(*itor2) != mLIdxMap.end()) {
-						lId = mLIdxMap[*itor2];
-						break;
-					}
-				}
-				if (lId == -1)
-					lId = mLMap.size();
-				for (std::set<expr*>::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) {
-					bool itorHasEqcValue = false;
-					get_eqc_value(*itor2, itorHasEqcValue);
-					if (itorHasEqcValue)
-						continue;
-					mLIdxMap[*itor2] = lId;
-					mLMap[lId].insert(*itor2);
-				}
-			}
-		}
-
-		if (mRConst != NULL) {
-			for (std::map<expr*, expr*>::iterator itor1 = mostRightNodes.begin();
-					itor1 != mostRightNodes.end(); itor1++) {
-				expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first);
-				if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) {
-					depMap[deVar][mRConst] = 5;
-				}
-			}
-		}
-
-		{
-			nSet.clear();
-			std::map<expr*, expr*>::iterator itr = mostRightNodes.begin();
-			for (; itr != mostRightNodes.end(); itr++) {
-				expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first);
-				nSet.insert(deVar);
-			}
-			if (nSet.size() > 1) {
-				int rId = -1;
-				std::set<expr*>::iterator itor2 = nSet.begin();
-				for (; itor2 != nSet.end(); itor2++) {
-					if (mRIdxMap.find(*itor2) != mRIdxMap.end()) {
-						rId = mRIdxMap[*itor2];
-						break;
-					}
-				}
-				if (rId == -1)
-					rId = mRMap.size();
-				for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) {
-					bool rHasEqcValue = false;
-					get_eqc_value(*itor2, rHasEqcValue);
-					if (rHasEqcValue)
-						continue;
-					mRIdxMap[*itor2] = rId;
-					mRMap[rId].insert(*itor2);
-				}
-			}
-		}
-	}
-
-	// print the dependence map
-	TRACE("str",
-    tout << "Dependence Map" << std::endl;
-	for(std::map<expr*, std::map<expr*, int> >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) {
-	    tout << mk_pp(itor->first, m);
-	    rational nnLen;
-	    bool nnLen_exists = get_len_value(itor->first, nnLen);
-	    tout << "  [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t";
-	    for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
-	        tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), ";
-	    }
-	    tout << std::endl;
-	}
-	        );
-
-	// step, errr, 5: compute free variables based on the dependence map
-
-	// the case dependence map is empty, every var in VarMap is free
-	//---------------------------------------------------------------
-	// remove L/R most var in eq concat since they are constrained with each other
-	std::map<expr*, std::map<expr*, int> > lrConstrainedMap;
-	for (std::map<int, std::set<expr*> >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) {
-		for (std::set<expr*>::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) {
-			std::set<expr*>::iterator it2 = it1;
-			it2++;
-			for (; it2 != itor->second.end(); it2++) {
-				expr * n1 = *it1;
-				expr * n2 = *it2;
-				lrConstrainedMap[n1][n2] = 1;
-				lrConstrainedMap[n2][n1] = 1;
-			}
-		}
-	}
-	for (std::map<int, std::set<expr*> >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) {
-		for (std::set<expr*>::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) {
-			std::set<expr*>::iterator it2 = it1;
-			it2++;
-			for (; it2 != itor->second.end(); it2++) {
-				expr * n1 = *it1;
-				expr * n2 = *it2;
-				lrConstrainedMap[n1][n2] = 1;
-				lrConstrainedMap[n2][n1] = 1;
-			}
-		}
-	}
-
-	if (depMap.size() == 0) {
-		std::map<expr*, int>::iterator itor = strVarMap.begin();
-		for (; itor != strVarMap.end(); itor++) {
-			expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
-			if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
-				freeVarMap[var] = 1;
-			} else {
-				int lrConstainted = 0;
-				std::map<expr*, int>::iterator lrit = freeVarMap.begin();
-				for (; lrit != freeVarMap.end(); lrit++) {
-					if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
-						lrConstainted = 1;
-						break;
-					}
-				}
-				if (lrConstainted == 0) {
-					freeVarMap[var] = 1;
-				}
-			}
-		}
-	} else {
-		// if the keys in aliasIndexMap are not contained in keys in depMap, they are free
-		// e.g.,  x= y /\ x = z /\ t = "abc"
-		//        aliasIndexMap[y]= x, aliasIndexMap[z] = x
-		//        depMap        t ~ "abc"(1)
-		//        x should be free
-		std::map<expr*, int>::iterator itor2 = strVarMap.begin();
-		for (; itor2 != strVarMap.end(); itor2++) {
-			if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) {
-				expr * var = aliasIndexMap[itor2->first];
-				if (depMap.find(var) == depMap.end()) {
-					if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
-						freeVarMap[var] = 1;
-					} else {
-						int lrConstainted = 0;
-						std::map<expr*, int>::iterator lrit = freeVarMap.begin();
-						for (; lrit != freeVarMap.end(); lrit++) {
-							if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
-								lrConstainted = 1;
-								break;
-							}
-						}
-						if (lrConstainted == 0) {
-							freeVarMap[var] = 1;
-						}
-					}
-				}
-			} else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) {
-				// if a variable is not in aliasIndexMap and not in depMap, it's free
-				if (depMap.find(itor2->first) == depMap.end()) {
-					expr * var = itor2->first;
-					if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
-						freeVarMap[var] = 1;
-					} else {
-						int lrConstainted = 0;
-						std::map<expr*, int>::iterator lrit = freeVarMap.begin();
-						for (; lrit != freeVarMap.end(); lrit++) {
-							if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
-								lrConstainted = 1;
-								break;
-							}
-						}
-						if (lrConstainted == 0) {
-							freeVarMap[var] = 1;
-						}
-					}
-				}
-			}
-		}
-
-		std::map<expr*, std::map<expr*, int> >::iterator itor = depMap.begin();
-		for (; itor != depMap.end(); itor++) {
-			for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
-				if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var
-					expr * var = get_alias_index_ast(aliasIndexMap, itor1->first);
-					// if a var is dep on itself and all dependence are type 2, it's a free variable
-					// e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free
-					{
-						if (depMap.find(var) == depMap.end()) {
-							if (freeVarMap.find(var) == freeVarMap.end()) {
-								if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
-									freeVarMap[var] = 1;
-								} else {
-									int lrConstainted = 0;
-									std::map<expr*, int>::iterator lrit = freeVarMap.begin();
-									for (; lrit != freeVarMap.end(); lrit++) {
-										if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
-											lrConstainted = 1;
-											break;
-										}
-									}
-									if (lrConstainted == 0) {
-										freeVarMap[var] = 1;
-									}
-								}
-
-							} else {
-								freeVarMap[var] = freeVarMap[var] + 1;
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-
-	return 0;
-}
-
-// Check agreement between integer and string theories for the term a = (str.to-int S).
-// Returns true if axioms were added, and false otherwise.
-bool theory_str::finalcheck_str2int(app * a) {
-	bool axiomAdd = false;
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	expr * S = a->get_arg(0);
-
-	// check integer theory
-	rational Ival;
-	bool Ival_exists = get_value(a, Ival);
-	if (Ival_exists) {
-		TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;);
-		// if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival"
-		if (!Ival.is_minus_one()) {
-			zstring Ival_str(Ival.to_string().c_str());
-			expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m);
-			expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m);
-			expr_ref axiom(rewrite_implication(premise, conclusion), m);
-			if (!string_int_axioms.contains(axiom)) {
-				string_int_axioms.insert(axiom);
-				assert_axiom(axiom);
-				m_trail_stack.push(insert_obj_trail<theory_str, expr>(string_int_axioms, axiom));
-				axiomAdd = true;
-			}
-		}
-	} else {
-		TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;);
-		NOT_IMPLEMENTED_YET();
-	}
-
-	return axiomAdd;
-}
-
-bool theory_str::finalcheck_int2str(app * a) {
-    bool axiomAdd = false;
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    expr * N = a->get_arg(0);
-
-    // check string theory
-    bool Sval_expr_exists;
-    expr * Sval_expr = get_eqc_value(a, Sval_expr_exists);
-    if (Sval_expr_exists) {
-        zstring Sval;
-        u.str.is_string(Sval_expr, Sval);
-        TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";);
-        // empty string --> integer value < 0
-        if (Sval.empty()) {
-            // ignore this. we should already assert the axiom for what happens when the string is ""
-        } else {
-            // nonempty string --> convert to correct integer value, or disallow it
-            rational convertedRepresentation(0);
-            rational ten(10);
-            bool conversionOK = true;
-            for (unsigned i = 0; i < Sval.length(); ++i) {
-                char digit = (int)Sval[i];
-                if (isdigit((int)digit)) {
-                    std::string sDigit(1, digit);
-                    int val = atoi(sDigit.c_str());
-                    convertedRepresentation = (ten * convertedRepresentation) + rational(val);
-                } else {
-                    // not a digit, invalid
-                    TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;);
-                    conversionOK = false;
-                    break;
-                }
-            }
-            if (conversionOK) {
-                expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m);
-                expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m);
-                expr_ref axiom(rewrite_implication(premise, conclusion), m);
-                if (!string_int_axioms.contains(axiom)) {
-                    string_int_axioms.insert(axiom);
-                    assert_axiom(axiom);
-                    m_trail_stack.push(insert_obj_trail<theory_str, expr>(string_int_axioms, axiom));
-                    axiomAdd = true;
+                expr_ref implyL1(mk_and(item1), m);
+                newConcat = mk_concat(arg1, arg2);
+                if (newConcat != str) {
+                    expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m);
+                    assert_implication(implyL1, implyR1);
                 }
             } else {
-                expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m);
-                // always assert this axiom because this is a conflict clause
-                assert_axiom(axiom);
-                axiomAdd = true;
+                newConcat = concat;
             }
-        }
-    } else {
-        TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;);
-        NOT_IMPLEMENTED_YET();
-    }
-    return axiomAdd;
-}
-
-void theory_str::collect_var_concat(expr * node, std::set<expr*> & varSet, std::set<expr*> & concatSet) {
-    if (variable_set.find(node) != variable_set.end()) {
-        if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) {
-            varSet.insert(node);
-        }
-    }
-    else if (is_app(node)) {
-        app * aNode = to_app(node);
-        if (u.str.is_length(aNode)) {
-            // Length
-            return;
-        }
-        if (u.str.is_concat(aNode)) {
-            expr * arg0 = aNode->get_arg(0);
-            expr * arg1 = aNode->get_arg(1);
-            if (concatSet.find(node) == concatSet.end()) {
-                concatSet.insert(node);
+            if (newConcat == str) {
+                return;
             }
-        }
-        // recursively visit all arguments
-        for (unsigned i = 0; i < aNode->get_num_args(); ++i) {
-            expr * arg = aNode->get_arg(i);
-            collect_var_concat(arg, varSet, concatSet);
-        }
-    }
-}
-
-bool theory_str::propagate_length_within_eqc(expr * var) {
-    bool res = false;
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-
-    TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;);
-
-    enode * n_eq_enode = ctx.get_enode(var);
-    rational varLen;
-    if (! get_len_value(var, varLen)) {
-        bool hasLen = false;
-        expr * nodeWithLen= var;
-        do {
-            if (get_len_value(nodeWithLen, varLen)) {
-                hasLen = true;
-                break;
+            if (!u.str.is_concat(to_app(newConcat))) {
+                return;
             }
-            nodeWithLen = get_eqc_next(nodeWithLen);
-        } while (nodeWithLen != var);
+            if (arg1_has_eqc_value && arg2_has_eqc_value) {
+                // Case 1: Concat(const, const) == const
+                TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;);
+                zstring arg1_str, arg2_str;
+                u.str.is_string(arg1, arg1_str);
+                u.str.is_string(arg2, arg2_str);
 
-        if (hasLen) {
-            // var = nodeWithLen --> |var| = |nodeWithLen|
-            expr_ref_vector l_items(m);
-            expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m);
-            l_items.push_back(varEqNode);
-
-            expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m);
-            expr_ref varLenExpr (mk_int(varLen), m);
-            expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m);
-            l_items.push_back(lenEqNum);
-
-            expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
-            expr_ref varLen(mk_strlen(var), m);
-            expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m);
-            assert_implication(axl, axr);
-            TRACE("str", tout <<  mk_ismt2_pp(axl, m) << std::endl << "  --->  " << std::endl <<  mk_ismt2_pp(axr, m););
-            res = true;
-        }
-    }
-    return res;
-}
-
-bool theory_str::propagate_length(std::set<expr*> & varSet, std::set<expr*> & concatSet, std::map<expr*, int> & exprLenMap) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-    expr_ref_vector assignments(m);
-    ctx.get_assignments(assignments);
-    bool axiomAdded = false;
-    // collect all concats in context
-    for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) {
-        if (! ctx.is_relevant(*it)) {
-            continue;
-        }
-        if (m.is_eq(*it)) {
-            collect_var_concat(*it, varSet, concatSet);
-        }
-    }
-    // iterate each concat
-    // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved
-    for (std::set<expr*>::iterator it = concatSet.begin(); it != concatSet.end(); it++) {
-        expr * concat = *it;
-        rational lenValue;
-        expr_ref concatlenExpr (mk_strlen(concat), m) ;
-        bool allLeafResolved = true;
-        if (! get_value(concatlenExpr, lenValue)) {
-            // the length fo concat is unresolved yet
-            if (get_len_value(concat, lenValue)) {
-                // but all leaf nodes have length information
-                TRACE("str", tout << "* length pop-up: " <<  mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;);
-                std::set<expr*> leafNodes;
-                get_unique_non_concat_nodes(concat, leafNodes);
-                expr_ref_vector l_items(m);
-                for (std::set<expr*>::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) {
-                    rational leafLenValue;
-                    if (get_len_value(*leafIt, leafLenValue)) {
-                        expr_ref leafItLenExpr (mk_strlen(*leafIt), m);
-                        expr_ref leafLenValueExpr (mk_int(leafLenValue), m);
-                        expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m);
-                        l_items.push_back(lcExpr);
+                zstring result_str = arg1_str + arg2_str;
+                if (result_str != const_str) {
+                    // Inconsistency
+                    TRACE("str", tout << "inconsistency detected: \""
+                          << arg1_str << "\" + \"" << arg2_str <<
+                          "\" != \"" << const_str << "\"" << "\n";);
+                    expr_ref equality(ctx.mk_eq_atom(concat, str), m);
+                    expr_ref diseq(m.mk_not(equality), m);
+                    assert_axiom(diseq);
+                    return;
+                }
+            } else if (!arg1_has_eqc_value && arg2_has_eqc_value) {
+                // Case 2: Concat(var, const) == const
+                TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;);
+                zstring arg2_str;
+                u.str.is_string(arg2, arg2_str);
+                unsigned int resultStrLen = const_str.length();
+                unsigned int arg2StrLen = arg2_str.length();
+                if (resultStrLen < arg2StrLen) {
+                    // Inconsistency
+                    TRACE("str", tout << "inconsistency detected: \""
+                          << arg2_str <<
+                          "\" is longer than \"" << const_str << "\","
+                          << " so cannot be concatenated with anything to form it" << "\n";);
+                    expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
+                    expr_ref diseq(m.mk_not(equality), m);
+                    assert_axiom(diseq);
+                    return;
+                } else {
+                    int varStrLen = resultStrLen - arg2StrLen;
+                    zstring firstPart = const_str.extract(0, varStrLen);
+                    zstring secondPart = const_str.extract(varStrLen, arg2StrLen);
+                    if (arg2_str != secondPart) {
+                        // Inconsistency
+                        TRACE("str", tout << "inconsistency detected: "
+                              << "suffix of concatenation result expected \"" << secondPart << "\", "
+                              << "actually \"" << arg2_str << "\""
+                              << "\n";);
+                        expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
+                        expr_ref diseq(m.mk_not(equality), m);
+                        assert_axiom(diseq);
+                        return;
                     } else {
-                        allLeafResolved = false;
-                        break;
+                        expr_ref tmpStrConst(mk_string(firstPart), m);
+                        expr_ref premise(ctx.mk_eq_atom(newConcat, str), m);
+                        expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m);
+                        assert_implication(premise, conclusion);
+                        return;
                     }
                 }
-                if (allLeafResolved) {
-                    expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
-                    expr_ref lenValueExpr (mk_int(lenValue), m);
-                    expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m);
-                    assert_implication(axl, axr);
-                    TRACE("str", tout <<  mk_ismt2_pp(axl, m) << std::endl << "  --->  " << std::endl <<  mk_ismt2_pp(axr, m)<< std::endl;);
-                    axiomAdded = true;
+            } else if (arg1_has_eqc_value && !arg2_has_eqc_value) {
+                // Case 3: Concat(const, var) == const
+                TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;);
+                zstring arg1_str;
+                u.str.is_string(arg1, arg1_str);
+                unsigned int resultStrLen = const_str.length();
+                unsigned int arg1StrLen = arg1_str.length();
+                if (resultStrLen < arg1StrLen) {
+                    // Inconsistency
+                    TRACE("str", tout << "inconsistency detected: \""
+                          << arg1_str <<
+                          "\" is longer than \"" << const_str << "\","
+                          << " so cannot be concatenated with anything to form it" << "\n";);
+                    expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
+                    expr_ref diseq(m.mk_not(equality), m);
+                    assert_axiom(diseq);
+                    return;
+                } else {
+                    int varStrLen = resultStrLen - arg1StrLen;
+                    zstring firstPart = const_str.extract(0, arg1StrLen);
+                    zstring secondPart = const_str.extract(arg1StrLen, varStrLen);
+                    if (arg1_str != firstPart) {
+                        // Inconsistency
+                        TRACE("str", tout << "inconsistency detected: "
+                              << "prefix of concatenation result expected \"" << secondPart << "\", "
+                              << "actually \"" << arg1_str << "\""
+                              << "\n";);
+                        expr_ref equality(ctx.mk_eq_atom(newConcat, str), m);
+                        expr_ref diseq(m.mk_not(equality), m);
+                        assert_axiom(diseq);
+                        return;
+                    } else {
+                        expr_ref tmpStrConst(mk_string(secondPart), m);
+                        expr_ref premise(ctx.mk_eq_atom(newConcat, str), m);
+                        expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m);
+                        assert_implication(premise, conclusion);
+                        return;
+                    }
                 }
-            }
-        }
-    }
-    // if no concat length is propagated, check the length of variables.
-    if (! axiomAdded) {
-        for (std::set<expr*>::iterator it = varSet.begin(); it != varSet.end(); it++) {
-            expr * var = *it;
-            rational lenValue;
-            expr_ref varlen (mk_strlen(var), m) ;
-            bool allLeafResolved = true;
-            if (! get_value(varlen, lenValue)) {
-                if (propagate_length_within_eqc(var)) {
-                    axiomAdded = true;
-                }
-            }
-        }
-
-    }
-    return axiomAdded;
-}
-
-void theory_str::get_unique_non_concat_nodes(expr * node, std::set<expr*> & argSet) {
-    app * a_node = to_app(node);
-    if (!u.str.is_concat(a_node)) {
-        argSet.insert(node);
-        return;
-    } else {
-        SASSERT(a_node->get_num_args() == 2);
-        expr * leftArg = a_node->get_arg(0);
-        expr * rightArg = a_node->get_arg(1);
-        get_unique_non_concat_nodes(leftArg, argSet);
-        get_unique_non_concat_nodes(rightArg, argSet);
-    }
-}
-
-final_check_status theory_str::final_check_eh() {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-
-    expr_ref_vector assignments(m);
-    ctx.get_assignments(assignments);
-
-    if (opt_VerifyFinalCheckProgress) {
-        finalCheckProgressIndicator = false;
-    }
-
-    TRACE("str", tout << "final check" << std::endl;);
-    TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); });
-    check_variable_scope();
-
-    if (opt_DeferEQCConsistencyCheck) {
-        TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;);
-        std::set<enode*> eqc_roots;
-        for (ptr_vector<enode>::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) {
-            enode * e = *it;
-            enode * root = e->get_root();
-            eqc_roots.insert(root);
-        }
-
-        bool found_inconsistency = false;
-
-        for (std::set<enode*>::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) {
-            enode * e = *it;
-            app * a = e->get_owner();
-            if (!(m.get_sort(a) == u.str.mk_string_sort())) {
-                TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;);
             } else {
-                TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;);
-                // first call check_concat_len_in_eqc() on each member of the eqc
-                enode * e_it = e;
-                enode * e_root = e_it;
-                do {
-                    bool status = check_concat_len_in_eqc(e_it->get_owner());
-                    if (!status) {
-                        TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;);
-                        found_inconsistency = true;
-                    }
-                    e_it = e_it->get_next();
-                } while (e_it != e_root);
+                // Case 4: Concat(var, var) == const
+                TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;);
+                if (eval_concat(arg1, arg2) == NULL) {
+                    rational arg1Len, arg2Len;
+                    bool arg1Len_exists = get_len_value(arg1, arg1Len);
+                    bool arg2Len_exists = get_len_value(arg2, arg2Len);
+                    rational concatStrLen((unsigned)const_str.length());
+                    if (arg1Len_exists || arg2Len_exists) {
+                        expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m);
+                        expr_ref ax_l2(m);
+                        zstring prefixStr, suffixStr;
+                        if (arg1Len_exists) {
+                            if (arg1Len.is_neg()) {
+                                TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;);
+                                expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m);
+                                assert_axiom(toAssert);
+                                return;
+                            } else if (arg1Len > concatStrLen) {
+                                TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;);
+                                expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m);
+                                assert_implication(ax_l1, ax_r1);
+                                return;
+                            }
 
-                // now grab any two distinct elements from the EQC and call new_eq_check() on them
-                enode * e1 = e;
-                enode * e2 = e1->get_next();
-                if (e1 != e2) {
-                    TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;);
-                    bool result = new_eq_check(e1->get_owner(), e2->get_owner());
-                    if (!result) {
-                        TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;);
-                        found_inconsistency = true;
-                    }
-                }
+                            prefixStr = const_str.extract(0, arg1Len.get_unsigned());
+                            rational concat_minus_arg1 = concatStrLen - arg1Len;
+                            suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned());
+                            ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len));
+                        } else {
+                            // arg2's length is available
+                            if (arg2Len.is_neg()) {
+                                TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;);
+                                expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m);
+                                assert_axiom(toAssert);
+                                return;
+                            } else if (arg2Len > concatStrLen) {
+                                TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;);
+                                expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m);
+                                assert_implication(ax_l1, ax_r1);
+                                return;
+                            }
+
+                            rational concat_minus_arg2 = concatStrLen - arg2Len;
+                            prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned());
+                            suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned());
+                            ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len));
+                        }
+                        // consistency check
+                        if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) {
+                            expr_ref ax_r(m.mk_not(ax_l2), m);
+                            assert_implication(ax_l1, ax_r);
+                            return;
+                        }
+                        if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) {
+                            expr_ref ax_r(m.mk_not(ax_l2), m);
+                            assert_implication(ax_l1, ax_r);
+                            return;
+                        }
+                        expr_ref_vector r_items(m);
+                        r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr)));
+                        r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr)));
+                        if (!arg1Len_exists) {
+                            r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length())));
+                        }
+                        if (!arg2Len_exists) {
+                            r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length())));
+                        }
+                        expr_ref lhs(m.mk_and(ax_l1, ax_l2), m);
+                        expr_ref rhs(mk_and(r_items), m);
+                        assert_implication(lhs, rhs);
+                    } else { /* ! (arg1Len != 1 || arg2Len != 1) */
+                        expr_ref xorFlag(m);
+                        std::pair<expr*, expr*> key1(arg1, arg2);
+                        std::pair<expr*, expr*> key2(arg2, arg1);
+
+                        // check the entries in this map to make sure they're still in scope
+                        // before we use them.
+
+                        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry1 = varForBreakConcat.find(key1);
+                        std::map<std::pair<expr*,expr*>, std::map<int, expr*> >::iterator entry2 = varForBreakConcat.find(key2);
+
+                        bool entry1InScope;
+                        if (entry1 == varForBreakConcat.end()) {
+                            TRACE("str", tout << "key1 no entry" << std::endl;);
+                            entry1InScope = false;
+                        } else {
+                            // OVERRIDE.
+                            entry1InScope = true;
+                            TRACE("str", tout << "key1 entry" << std::endl;);
+                            /*
+                              if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) {
+                              TRACE("str", tout << "key1 entry not in scope" << std::endl;);
+                              entry1InScope = false;
+                              } else {
+                              TRACE("str", tout << "key1 entry in scope" << std::endl;);
+                              entry1InScope = true;
+                              }
+                            */
+                        }
+
+                        bool entry2InScope;
+                        if (entry2 == varForBreakConcat.end()) {
+                            TRACE("str", tout << "key2 no entry" << std::endl;);
+                            entry2InScope = false;
+                        } else {
+                            // OVERRIDE.
+                            entry2InScope = true;
+                            TRACE("str", tout << "key2 entry" << std::endl;);
+                            /*
+                              if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) {
+                              TRACE("str", tout << "key2 entry not in scope" << std::endl;);
+                              entry2InScope = false;
+                              } else {
+                              TRACE("str", tout << "key2 entry in scope" << std::endl;);
+                              entry2InScope = true;
+                              }
+                            */
+                        }
+
+                        TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl
+                              << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;);
+
+                        if (!entry1InScope && !entry2InScope) {
+                            xorFlag = mk_internal_xor_var();
+                            varForBreakConcat[key1][0] = xorFlag;
+                        } else if (entry1InScope) {
+                            xorFlag = varForBreakConcat[key1][0];
+                        } else { // entry2InScope
+                            xorFlag = varForBreakConcat[key2][0];
+                        }
+
+                        int concatStrLen = const_str.length();
+                        int and_count = 1;
+
+                        expr_ref_vector arrangement_disjunction(m);
+
+                        for (int i = 0; i < concatStrLen + 1; ++i) {
+                            expr_ref_vector and_items(m);
+                            zstring prefixStr = const_str.extract(0, i);
+                            zstring suffixStr = const_str.extract(i, concatStrLen - i);
+                            // skip invalid options
+                            if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) {
+                                continue;
+                            }
+                            if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) {
+                                continue;
+                            }
+
+                            expr_ref prefixAst(mk_string(prefixStr), m);
+                            expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m);
+                            and_items.push_back(arg1_eq);
+                            and_count += 1;
+
+                            expr_ref suffixAst(mk_string(suffixStr), m);
+                            expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m);
+                            and_items.push_back(arg2_eq);
+                            and_count += 1;
+
+                            arrangement_disjunction.push_back(mk_and(and_items));
+                        }
+
+                        expr_ref implyL(ctx.mk_eq_atom(concat, str), m);
+                        expr_ref implyR1(m);
+                        if (arrangement_disjunction.empty()) {
+                            // negate
+                            expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m);
+                            expr_ref negate_ast(m.mk_not(concat_eq_str), m);
+                            assert_axiom(negate_ast);
+                        } else {
+                            implyR1 = mk_or(arrangement_disjunction);
+                            if (m_params.m_StrongArrangements) {
+                                expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m);
+                                assert_axiom(ax_strong);
+                            } else {
+                                assert_implication(implyL, implyR1);
+                            }
+                            generate_mutual_exclusion(arrangement_disjunction);
+                        }
+                    } /* (arg1Len != 1 || arg2Len != 1) */
+                } /* if (Concat(arg1, arg2) == NULL) */
             }
         }
-
-        if (found_inconsistency) {
-            TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;);
-            return FC_CONTINUE;
-        } else {
-            TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;);
-        }
     }
 
-    // run dependence analysis to find free string variables
-    std::map<expr*, int> varAppearInAssign;
-    std::map<expr*, int> freeVar_map;
-    std::map<expr*, std::set<expr*> > unrollGroup_map;
-    std::map<expr*, std::map<expr*, int> > var_eq_concat_map;
-    int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map);
-    if (conflictInDep == -1) {
-    	// return Z3_TRUE;
-    	return FC_DONE;
-    }
-
-    // enhancement: improved backpropagation of string constants into var=concat terms
-    bool backpropagation_occurred = false;
-    for (std::map<expr*, std::map<expr*, int> >::iterator veqc_map_it = var_eq_concat_map.begin();
-    		veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) {
-    	expr * var = veqc_map_it->first;
-    	for (std::map<expr*, int>::iterator concat_map_it = veqc_map_it->second.begin();
-    			concat_map_it != veqc_map_it->second.end(); ++concat_map_it) {
-    		app * concat = to_app(concat_map_it->first);
-    		expr * concat_lhs = concat->get_arg(0);
-    		expr * concat_rhs = concat->get_arg(1);
-    		// If the concat LHS and RHS both have a string constant in their EQC,
-    		// but the var does not, then we assert an axiom of the form
-    		// (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs"
-    		bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc;
-    		expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc);
-    		expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc);
-    		expr * var_str = get_eqc_value(var, var_haseqc);
-    		if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) {
-    			TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl
-    					<< "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;);
-    			zstring lhsString, rhsString;
-    			u.str.is_string(concat_lhs_str, lhsString);
-    			u.str.is_string(concat_rhs_str, rhsString);
-    			zstring concatString = lhsString + rhsString;
-    			expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m);
-    			expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m);
-    			expr_ref lhs(m.mk_and(lhs1, lhs2), m);
-    			expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m);
-    			assert_implication(lhs, rhs);
-    			backpropagation_occurred = true;
-    		}
-    	}
-    }
-
-    if (backpropagation_occurred) {
-    	TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;);
-    	return FC_CONTINUE;
-    }
-
-    // enhancement: improved backpropagation of length information
-    {
-        std::set<expr*> varSet;
-        std::set<expr*> concatSet;
-        std::map<expr*, int> exprLenMap;
-
-        bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap);
-        if (length_propagation_occurred) {
-            TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;);
-            return FC_CONTINUE;
-        }
-    }
-
-    bool needToAssignFreeVars = false;
-    std::set<expr*> free_variables;
-    std::set<expr*> unused_internal_variables;
-    { // Z3str2 free variables check
-    	std::map<expr*, int>::iterator itor = varAppearInAssign.begin();
-    	for (; itor != varAppearInAssign.end(); ++itor) {
-    		/*
-    		std::string vName = std::string(Z3_ast_to_string(ctx, itor->first));
-    		if (vName.length() >= 3 && vName.substr(0, 3) == "$$_")
-    			continue;
-    			*/
-    		if (internal_variable_set.find(itor->first) != internal_variable_set.end()
-    				|| regex_variable_set.find(itor->first) != regex_variable_set.end()) {
-    			// this can be ignored, I think
-    			TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;);
-    			continue;
-    		}
-    		bool hasEqcValue = false;
-    		expr * eqcString = get_eqc_value(itor->first, hasEqcValue);
-    		if (!hasEqcValue) {
-    			TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;);
-    			needToAssignFreeVars = true;
-    			free_variables.insert(itor->first);
-    			// break;
-    		} else {
-    			// debug
-    			TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;);
-    		}
-    	}
-    }
-
-    if (!needToAssignFreeVars) {
-
-    	// check string-int terms
-    	bool addedStrIntAxioms = false;
-    	for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) {
-    		app * ex = to_app(string_int_conversion_terms[i].get());
-    		if (u.str.is_stoi(ex)) {
-    			bool axiomAdd = finalcheck_str2int(ex);
-    			if (axiomAdd) {
-    				addedStrIntAxioms = true;
-    			}
-    		} else if (u.str.is_itos(ex)) {
-    		    bool axiomAdd = finalcheck_int2str(ex);
-    		    if (axiomAdd) {
-    		        addedStrIntAxioms = true;
-    		    }
-    		} else {
-    			UNREACHABLE();
-    		}
-    	}
-    	if (addedStrIntAxioms) {
-    		TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;);
-    		return FC_CONTINUE;
-    	}
-
-        if (unused_internal_variables.empty()) {
-            TRACE("str", tout << "All variables are assigned. Done!" << std::endl;);
-            return FC_DONE;
-        } else {
-            TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;);
-            for (std::set<expr*>::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) {
-                expr * var = *it;
-                expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m);
-                assert_axiom(assignment);
-            }
-            return FC_CONTINUE;
-        }
-    }
-
-    CTRACE("str", needToAssignFreeVars,
-        tout << "Need to assign values to the following free variables:" << std::endl;
-        for (std::set<expr*>::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) {
-            tout << mk_ismt2_pp(*itx, m) << std::endl;
-        }
-        tout << "freeVar_map has the following entries:" << std::endl;
-        for (std::map<expr*, int>::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) {
-            expr * var = fvIt->first;
-            tout << mk_ismt2_pp(var, m) << std::endl;
-        }
-    );
-
-    // -----------------------------------------------------------
-    // variables in freeVar are those not bounded by Concats
-    // classify variables in freeVarMap:
-    // (1) freeVar = unroll(r1, t1)
-    // (2) vars are not bounded by either concat or unroll
-    // -----------------------------------------------------------
-    std::map<expr*, std::set<expr*> > fv_unrolls_map;
-    std::set<expr*> tmpSet;
-    expr * constValue = NULL;
-    for (std::map<expr*, int>::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) {
-    	expr * var = fvIt2->first;
-    	tmpSet.clear();
-    	get_eqc_allUnroll(var, constValue, tmpSet);
-    	if (tmpSet.size() > 0) {
-    		fv_unrolls_map[var] = tmpSet;
-    	}
-    }
-    // erase var bounded by an unroll function from freeVar_map
-    for (std::map<expr*, std::set<expr*> >::iterator fvIt3 = fv_unrolls_map.begin();
-    		fvIt3 != fv_unrolls_map.end(); fvIt3++) {
-    	expr * var = fvIt3->first;
-    	TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;);
-    	freeVar_map.erase(var);
-    }
-
-    // collect the case:
-    //   * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2)
-    //     concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)}
-
-    std::map<expr*, std::set<expr*> > concatEqUnrollsMap;
-    for (std::map<expr*, std::set<expr*> >::iterator urItor = unrollGroup_map.begin();
-    		urItor != unrollGroup_map.end(); urItor++) {
-    	expr * unroll = urItor->first;
-    	expr * curr = unroll;
-    	do {
-    		if (u.str.is_concat(to_app(curr))) {
-    			concatEqUnrollsMap[curr].insert(unroll);
-    			concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end());
-    		}
-    		enode * e_curr = ctx.get_enode(curr);
-    		curr = e_curr->get_next()->get_owner();
-    		// curr = get_eqc_next(curr);
-    	} while (curr != unroll);
-    }
-
-    std::map<expr*, std::set<expr*> > concatFreeArgsEqUnrollsMap;
-    std::set<expr*> fvUnrollSet;
-    for (std::map<expr*, std::set<expr*> >::iterator concatItor = concatEqUnrollsMap.begin();
-    		concatItor != concatEqUnrollsMap.end(); concatItor++) {
-    	expr * concat = concatItor->first;
-    	expr * concatArg1 = to_app(concat)->get_arg(0);
-    	expr * concatArg2 = to_app(concat)->get_arg(1);
-    	bool arg1Bounded = false;
-    	bool arg2Bounded = false;
-    	// arg1
-		if (variable_set.find(concatArg1) != variable_set.end()) {
-			if (freeVar_map.find(concatArg1) == freeVar_map.end()) {
-				arg1Bounded = true;
-			} else {
-				fvUnrollSet.insert(concatArg1);
-			}
-		} else if (u.str.is_concat(to_app(concatArg1))) {
-			if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) {
-				arg1Bounded = true;
-			}
-		}
-		// arg2
-		if (variable_set.find(concatArg2) != variable_set.end()) {
-			if (freeVar_map.find(concatArg2) == freeVar_map.end()) {
-				arg2Bounded = true;
-			} else {
-				fvUnrollSet.insert(concatArg2);
-			}
-		} else if (u.str.is_concat(to_app(concatArg2))) {
-			if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) {
-				arg2Bounded = true;
-			}
-		}
-		if (!arg1Bounded && !arg2Bounded) {
-			concatFreeArgsEqUnrollsMap[concat].insert(
-					concatEqUnrollsMap[concat].begin(),
-					concatEqUnrollsMap[concat].end());
-		}
-    }
-    for (std::set<expr*>::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) {
-    	TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;);
-    	freeVar_map.erase(*vItor);
-    }
-
-    // Assign free variables
-    std::set<expr*> fSimpUnroll;
-
-    constValue = NULL;
-
-    {
-        TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl;
-        for (std::map<expr*, int>::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) {
-            expr * freeVar = freeVarItor1->first;
-            rational lenValue;
-            bool lenValue_exists = get_len_value(freeVar, lenValue);
-            tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = "
-                    << (lenValue_exists ? lenValue.to_string() : "?")
-                    << "]" << std::endl;
-        }
-        );
-    }
-
-    for (std::map<expr*, std::set<expr*> >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin();
-    		fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) {
-    	expr * concat = fvIt2->first;
-    	for (std::set<expr*>::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) {
-    		expr * unroll = *urItor;
-    		process_concat_eq_unroll(concat, unroll);
-    	}
-    }
-
-    // --------
-    // experimental free variable assignment - begin
-    //   * special handling for variables that are not used in concat
-    // --------
-    bool testAssign = true;
-    if (!testAssign) {
-    	for (std::map<expr*, int>::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) {
-    		expr * freeVar = fvIt->first;
-    		/*
-    		std::string vName = std::string(Z3_ast_to_string(ctx, freeVar));
-    		if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") {
-    			continue;
-    		}
-    		*/
-    		expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, "");
-    		if (toAssert != NULL) {
-    			assert_axiom(toAssert);
-    		}
-    	}
-    } else {
-    	process_free_var(freeVar_map);
-    }
-    // experimental free variable assignment - end
-
-    // now deal with removed free variables that are bounded by an unroll
-    TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;);
-    for (std::map<expr*, std::set<expr*> >::iterator fvIt1 = fv_unrolls_map.begin();
-    		fvIt1 != fv_unrolls_map.end(); fvIt1++) {
-    	expr * var = fvIt1->first;
-    	fSimpUnroll.clear();
-    	get_eqc_simpleUnroll(var, constValue, fSimpUnroll);
-    	if (fSimpUnroll.size() == 0) {
-    		gen_assign_unroll_reg(fv_unrolls_map[var]);
-    	} else {
-    		expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll);
-    		if (toAssert != NULL) {
-    			assert_axiom(toAssert);
-    		}
-    	}
-    }
-
-    if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) {
-        TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;);
-        m.raise_exception("no progress in theory_str final check");
-    }
-
-    return FC_CONTINUE; // since by this point we've added axioms
-}
-
-inline zstring int_to_string(int i) {
-	std::stringstream ss;
-	ss << i;
-	std::string str = ss.str();
-	return zstring(str.c_str());
-}
-
-inline std::string longlong_to_string(long long i) {
-  std::stringstream ss;
-  ss << i;
-  return ss.str();
-}
-
-void theory_str::print_value_tester_list(svector<std::pair<int, expr*> > & testerList) {
-	ast_manager & m = get_manager();
-	TRACE("str",
-		int ss = testerList.size();
-		tout << "valueTesterList = {";
-		for (int i = 0; i < ss; ++i) {
-			if (i % 4 == 0) {
-				tout << std::endl;
-			}
-			tout << "(" << testerList[i].first << ", ";
-			tout << mk_ismt2_pp(testerList[i].second, m);
-			tout << "), ";
-		}
-		tout << std::endl << "}" << std::endl;
-	);
-}
-
-zstring theory_str::gen_val_string(int len, int_vector & encoding) {
-    SASSERT(charSetSize > 0);
-    SASSERT(char_set != NULL);
-
-    std::string re(len, char_set[0]);
-    for (int i = 0; i < (int) encoding.size() - 1; i++) {
-        int idx = encoding[i];
-        re[len - 1 - i] = char_set[idx];
-    }
-    return zstring(re.c_str());
-}
-
-/*
- * The return value indicates whether we covered the search space.
- *   - If the next encoding is valid, return false
- *   - Otherwise, return true
- */
-bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) {
-	SASSERT(charSetSize > 0);
-
-	TRACE("str", tout << "base vector: [ ";
-	    for (unsigned i = 0; i < base.size(); ++i) {
-	        tout << base[i] << " ";
-	    }
-	    tout << "]" << std::endl;
-	);
-
-    int s = 0;
-    int carry = 0;
-    next.reset();
-
-    for (int i = 0; i < (int) base.size(); i++) {
-        if (i == 0) {
-            s = base[i] + 1;
-            carry = s / charSetSize;
-            s = s % charSetSize;
-            next.push_back(s);
-        } else {
-            s = base[i] + carry;
-            carry = s / charSetSize;
-            s = s % charSetSize;
-            next.push_back(s);
-        }
-    }
-    if (next[next.size() - 1] > 0) {
-        next.reset();
-        return true;
-    } else {
-        return false;
-    }
-}
-
-expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator,
-		zstring lenStr, int tries) {
-	ast_manager & m = get_manager();
-	context & ctx = get_context();
-
-	int distance = 32;
-
-	// ----------------------------------------------------------------------------------------
-	// generate value options encoding
-	// encoding is a vector of size (len + 1)
-	// e.g, len = 2,
-	//      encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]"
-	//      the last item in the encoding indicates whether the whole space is covered
-	//      for example, if the charSet = {a, b}. All valid encodings are
-	//        {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}
-	//      if add 1 to the last one, we get
-	//        {0, 0, 1}
-	//      the last item "1" shows this is not a valid encoding, and we have covered all space
-	// ----------------------------------------------------------------------------------------
-	int len = atoi(lenStr.encode().c_str());
-	bool coverAll = false;
-	svector<int_vector> options;
-	int_vector base;
-
-	TRACE("str", tout
-			<< "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl
-			<< "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl
-			<< "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl
-			<< "lenstr = " << lenStr << "\n"
-			<< "tries = " << tries << "\n";
-            if (m_params.m_AggressiveValueTesting) {
-                tout << "note: aggressive value testing is enabled" << std::endl;
-            }
-	);
-
-	if (tries == 0) {
-		base = int_vector(len + 1, 0);
-		coverAll = false;
-	} else {
-		expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second;
-		TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;);
-		coverAll = get_next_val_encode(val_range_map[lastestValIndi], base);
-	}
-
-	long long l = (tries) * distance;
-	long long h = l;
-	for (int i = 0; i < distance; i++) {
-		if (coverAll)
-			break;
-		options.push_back(base);
-		h++;
-		coverAll = get_next_val_encode(options[options.size() - 1], base);
-	}
-	val_range_map[val_indicator] = options[options.size() - 1];
-
-	TRACE("str",
-			tout << "value tester encoding " << "{" << std::endl;
-		    int_vector vec = val_range_map[val_indicator];
-
-		    for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) {
-		    	tout << *it << std::endl;
-		    }
-			tout << "}" << std::endl;
-	);
-
-	// ----------------------------------------------------------------------------------------
-
-	ptr_vector<expr> orList;
-	ptr_vector<expr> andList;
-
-	for (long long i = l; i < h; i++) {
-		orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) ));
-		if (m_params.m_AggressiveValueTesting) {
-		    literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false);
-		    ctx.mark_as_relevant(l);
-		    ctx.force_phase(l);
-		}
-
-		zstring aStr = gen_val_string(len, options[i - l]);
-		expr * strAst;
-		if (m_params.m_UseFastValueTesterCache) {
-			if (!valueTesterCache.find(aStr, strAst)) {
-				strAst = mk_string(aStr);
-				valueTesterCache.insert(aStr, strAst);
-				m_trail.push_back(strAst);
-			}
-		} else {
-			strAst = mk_string(aStr);
-		}
-		andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst)));
-	}
-	if (!coverAll) {
-		orList.push_back(m.mk_eq(val_indicator, mk_string("more")));
-		if (m_params.m_AggressiveValueTesting) {
-		    literal l = mk_eq(val_indicator, mk_string("more"), false);
-		    ctx.mark_as_relevant(l);
-		    ctx.force_phase(~l);
-		}
-	}
-
-	expr ** or_items = alloc_svect(expr*, orList.size());
-	expr ** and_items = alloc_svect(expr*, andList.size() + 1);
-
-	for (int i = 0; i < (int) orList.size(); i++) {
-		or_items[i] = orList[i];
-	}
-	if (orList.size() > 1)
-		and_items[0] = m.mk_or(orList.size(), or_items);
-	else
-		and_items[0] = or_items[0];
-
-	for (int i = 0; i < (int) andList.size(); i++) {
-		and_items[i + 1] = andList[i];
-	}
-	expr * valTestAssert = m.mk_and(andList.size() + 1, and_items);
-
-	// ---------------------------------------
-	// If the new value tester is $$_val_x_16_i
-	// Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more")
-	// ---------------------------------------
-	andList.reset();
-	andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr)));
-	for (int i = 0; i < tries; i++) {
-		expr * vTester = fvar_valueTester_map[freeVar][len][i].second;
-		if (vTester != val_indicator)
-			andList.push_back(m.mk_eq(vTester, mk_string("more")));
-	}
-	expr * assertL = NULL;
-	if (andList.size() == 1) {
-		assertL = andList[0];
-	} else {
-		expr ** and_items = alloc_svect(expr*, andList.size());
-		for (int i = 0; i < (int) andList.size(); i++) {
-			and_items[i] = andList[i];
-		}
-		assertL = m.mk_and(andList.size(), and_items);
-	}
-
-	// (assertL => valTestAssert) <=> (!assertL OR valTestAssert)
-	valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert);
-	return valTestAssert;
-}
-
-expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator,
-		zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) {
-	ast_manager & m = get_manager();
-
-	int len = atoi(len_valueStr.encode().c_str());
-
-	// check whether any value tester is actually in scope
-	TRACE("str", tout << "checking scope of previous value testers" << std::endl;);
-	bool map_effectively_empty = true;
-	if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) {
-	    // there's *something* in the map, but check its scope
-	    svector<std::pair<int, expr*> > entries = fvar_valueTester_map[freeVar][len];
-	    for (svector<std::pair<int,expr*> >::iterator it = entries.begin(); it != entries.end(); ++it) {
-	        std::pair<int,expr*> entry = *it;
-	        expr * aTester = entry.second;
-	        if (internal_variable_set.find(aTester) == internal_variable_set.end()) {
-	            TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;);
-	        } else {
-	            TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;);
-	            map_effectively_empty = false;
-	            break;
-	        }
-	    }
-	}
-
-	if (map_effectively_empty) {
-		TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;);
-		int tries = 0;
-		expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries);
-		valueTester_fvar_map[val_indicator] = freeVar;
-		fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator));
-		print_value_tester_list(fvar_valueTester_map[freeVar][len]);
-		return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries);
-	} else {
-		TRACE("str", tout << "checking previous value testers" << std::endl;);
-		print_value_tester_list(fvar_valueTester_map[freeVar][len]);
-
-		// go through all previous value testers
-		// If some doesn't have an eqc value, add its assertion again.
-		int testerTotal = fvar_valueTester_map[freeVar][len].size();
-		int i = 0;
-		for (; i < testerTotal; i++) {
-			expr * aTester = fvar_valueTester_map[freeVar][len][i].second;
-
-			// it's probably worth checking scope here, actually
-			if (internal_variable_set.find(aTester) == internal_variable_set.end()) {
-			    TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;);
-			    continue;
-			}
-
-			if (aTester == valTesterInCbEq) {
-				break;
-			}
-
-			bool anEqcHasValue = false;
-			// Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue);
-			expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue);
-			if (!anEqcHasValue) {
-				TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m)
-						<< " doesn't have an equivalence class value." << std::endl;);
-				refresh_theory_var(aTester);
-
-				expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i);
-
-				TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl
-						<< mk_ismt2_pp(makeupAssert, m) << std::endl;);
-				assert_axiom(makeupAssert);
-			} else {
-			    TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m)
-			            << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;);
-			}
-		}
-
-		if (valTesterValueStr == "more") {
-			expr * valTester = NULL;
-			if (i + 1 < testerTotal) {
-				valTester = fvar_valueTester_map[freeVar][len][i + 1].second;
-				refresh_theory_var(valTester);
-			} else {
-				valTester = mk_internal_valTest_var(freeVar, len, i + 1);
-				valueTester_fvar_map[valTester] = freeVar;
-				fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester));
-				print_value_tester_list(fvar_valueTester_map[freeVar][len]);
-			}
-			expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1);
-			return nextAssert;
-		}
-
-		return NULL;
-	}
-}
-
-void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) {
-	context & ctx = get_context();
-	ast_manager & mgr = get_manager();
-
-	TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;);
-
-	app * regexFuncDecl = to_app(regex);
-	if (u.re.is_to_re(regexFuncDecl)) {
-		// ---------------------------------------------------------
-		// var \in Str2Reg(s1)
-		//   ==>
-		// var = s1 /\ length(var) = length(s1)
-		// ---------------------------------------------------------
-		expr * strInside = to_app(regex)->get_arg(0);
-		items.push_back(ctx.mk_eq_atom(var, strInside));
-		items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside)));
-		return;
-	}
-	// RegexUnion
-	else if (u.re.is_union(regexFuncDecl)) {
-		// ---------------------------------------------------------
-		// var \in RegexUnion(r1, r2)
-		//   ==>
-		// (var = newVar1 \/ var = newVar2)
-		// (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2))
-		//  /\ (newVar1 \in r1) /\  (newVar2 \in r2)
-		// ---------------------------------------------------------
-		expr_ref newVar1(mk_regex_rep_var(), mgr);
-		expr_ref newVar2(mk_regex_rep_var(), mgr);
-		items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2)));
-		items.push_back(mgr.mk_or(
-				mgr.mk_not(ctx.mk_eq_atom(var, newVar1)),
-				ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1))));
-		items.push_back(mgr.mk_or(
-				mgr.mk_not(ctx.mk_eq_atom(var, newVar2)),
-				ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2))));
-
-		expr * regArg1 = to_app(regex)->get_arg(0);
-		reduce_virtual_regex_in(newVar1, regArg1, items);
-
-		expr * regArg2 = to_app(regex)->get_arg(1);
-		reduce_virtual_regex_in(newVar2, regArg2, items);
-
-		return;
-	}
-	// RegexConcat
-	else if (u.re.is_concat(regexFuncDecl)) {
-		// ---------------------------------------------------------
-		// var \in RegexConcat(r1, r2)
-		//   ==>
-		//    (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) )
-		// /\ (newVar1 \in r1) /\  (newVar2 \in r2)
-		// ---------------------------------------------------------
-		expr_ref newVar1(mk_regex_rep_var(), mgr);
-		expr_ref newVar2(mk_regex_rep_var(), mgr);
-		expr_ref concatAst(mk_concat(newVar1, newVar2), mgr);
-		items.push_back(ctx.mk_eq_atom(var, concatAst));
-		items.push_back(ctx.mk_eq_atom(mk_strlen(var),
-				m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2))));
-
-		expr * regArg1 = to_app(regex)->get_arg(0);
-		reduce_virtual_regex_in(newVar1, regArg1, items);
-		expr * regArg2 = to_app(regex)->get_arg(1);
-		reduce_virtual_regex_in(newVar2, regArg2, items);
-		return;
-	}
-	// Unroll
-	else if (u.re.is_star(regexFuncDecl)) {
-		// ---------------------------------------------------------
-		// var \in Star(r1)
-		//   ==>
-		// var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)|
-		// ---------------------------------------------------------
-		expr * regArg = to_app(regex)->get_arg(0);
-		expr_ref unrollCnt(mk_unroll_bound_var(), mgr);
-		expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr);
-		items.push_back(ctx.mk_eq_atom(var, unrollFunc));
-		items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc)));
-		return;
-	}
-	// re.range
-	else if (u.re.is_range(regexFuncDecl)) {
-	    // var in range("a", "z")
-	    // ==>
-	    // (var = "a" or var = "b" or ... or var = "z")
-	    expr_ref lo(regexFuncDecl->get_arg(0), mgr);
-	    expr_ref hi(regexFuncDecl->get_arg(1), mgr);
-	    zstring str_lo, str_hi;
-	    SASSERT(u.str.is_string(lo));
-	    SASSERT(u.str.is_string(hi));
-	    u.str.is_string(lo, str_lo);
-	    u.str.is_string(hi, str_hi);
-	    SASSERT(str_lo.length() == 1);
-	    SASSERT(str_hi.length() == 1);
-	    unsigned int c1 = str_lo[0];
-	    unsigned int c2 = str_hi[0];
-	    if (c1 > c2) {
-	        // exchange
-	        unsigned int tmp = c1;
-	        c1 = c2;
-	        c2 = tmp;
-	    }
-	    expr_ref_vector range_cases(mgr);
-	    for (unsigned int ch = c1; ch <= c2; ++ch) {
-	        zstring s_ch(ch);
-	        expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr);
-	        range_cases.push_back(rhs);
-	    }
-	    expr_ref rhs(mk_or(range_cases), mgr);
-	    SASSERT(rhs);
-	    assert_axiom(rhs);
-	    return;
-	} else {
-		get_manager().raise_exception("unrecognized regex operator");
-		UNREACHABLE();
-	}
-}
-
-void theory_str::gen_assign_unroll_reg(std::set<expr*> & unrolls) {
-	context & ctx = get_context();
-	ast_manager & mgr = get_manager();
-
-	expr_ref_vector items(mgr);
-	for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
-		expr * unrFunc = *itor;
-		TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;);
-
-		expr * regexInUnr = to_app(unrFunc)->get_arg(0);
-		expr * cntInUnr = to_app(unrFunc)->get_arg(1);
-		items.reset();
-
-		rational low, high;
-		bool low_exists = lower_bound(cntInUnr, low);
-		bool high_exists = upper_bound(cntInUnr, high);
-
-		TRACE("str",
-				tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl;
-				rational unrLenValue;
-				bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue);
-				tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl;
-				rational cntInUnrValue;
-				bool cntHasValue = get_value(cntInUnr, cntInUnrValue);
-				tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?")
-						<< " low = "
-						<< (low_exists ? low.to_string() : "?")
-						<< " high = "
-						<< (high_exists ? high.to_string() : "?")
-						<< std::endl;
-			);
-
-		expr_ref toAssert(mgr);
-		if (low.is_neg()) {
-			toAssert = m_autil.mk_ge(cntInUnr, mk_int(0));
-		} else {
-			if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) {
-
-				expr_ref newVar1(mk_regex_rep_var(), mgr);
-				expr_ref newVar2(mk_regex_rep_var(), mgr);
-				expr_ref concatAst(mk_concat(newVar1, newVar2), mgr);
-				expr_ref newCnt(mk_unroll_bound_var(), mgr);
-				expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr);
-
-				// unroll(r1, t1) = newVar1 . newVar2
-				items.push_back(ctx.mk_eq_atom(unrFunc, concatAst));
-				items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2))));
-				// mk_strlen(unrFunc) >= mk_strlen(newVar{1,2})
-				items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0)));
-				items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0)));
-				// newVar1 \in r1
-				reduce_virtual_regex_in(newVar1, regexInUnr, items);
-				items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1))));
-				items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc));
-				items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc)));
-				toAssert = ctx.mk_eq_atom(
-						m_autil.mk_ge(cntInUnr, mk_int(1)),
-						mk_and(items));
-
-				// option 0
-				expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr);
-				expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr);
-				expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr);
-				expr_ref and1(mgr.mk_and(ast1, ast2), mgr);
-
-				// put together
-				toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert);
-
-				unroll_var_map[unrFunc] = toAssert;
-			} else {
-				toAssert = unroll_var_map[unrFunc];
-			}
-		}
-		m_trail.push_back(toAssert);
-		assert_axiom(toAssert);
-	}
-}
-
-static int computeGCD(int x, int y) {
-	if (x == 0) {
-		return y;
-	}
-	while (y != 0) {
-		if (x > y) {
-			x = x - y;
-		} else {
-			y = y - x;
-		}
-	}
-	return x;
-}
-
-static int computeLCM(int a, int b) {
-	int temp = computeGCD(a, b);
-	return temp ? (a / temp * b) : 0;
-}
-
-static zstring get_unrolled_string(zstring core, int count) {
-	zstring res("");
-	for (int i = 0; i < count; i++) {
-		res = res + core;
-	}
-	return res;
-}
-
-expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set<expr*> & unrolls) {
-	context & ctx = get_context();
-	ast_manager & mgr = get_manager();
-
-	int lcm = 1;
-	int coreValueCount = 0;
-	expr * oneUnroll = NULL;
-	zstring oneCoreStr("");
-	for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
-		expr * str2RegFunc = to_app(*itor)->get_arg(0);
-		expr * coreVal = to_app(str2RegFunc)->get_arg(0);
-		zstring coreStr;
-		u.str.is_string(coreVal, coreStr);
-		if (oneUnroll == NULL) {
-			oneUnroll = *itor;
-			oneCoreStr = coreStr;
-		}
-		coreValueCount++;
-		int core1Len = coreStr.length();
-		lcm = computeLCM(lcm, core1Len);
-	}
-	//
-	bool canHaveNonEmptyAssign = true;
-	expr_ref_vector litems(mgr);
-	zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length()));
-	for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
-		expr * str2RegFunc = to_app(*itor)->get_arg(0);
-		expr * coreVal = to_app(str2RegFunc)->get_arg(0);
-		zstring coreStr;
-		u.str.is_string(coreVal, coreStr);
-		unsigned int core1Len = coreStr.length();
-		zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len));
-		if (uStr != lcmStr) {
-			canHaveNonEmptyAssign = false;
-		}
-		litems.push_back(ctx.mk_eq_atom(n, *itor));
-	}
-
-	if (canHaveNonEmptyAssign) {
-		return gen_unroll_conditional_options(n, unrolls, lcmStr);
-	} else {
-		expr_ref implyL(mk_and(litems), mgr);
-		expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr);
-		// want to return (implyL -> implyR)
-		expr * final_axiom = rewrite_implication(implyL, implyR);
-		return final_axiom;
-	}
-}
-
-expr * theory_str::gen_unroll_conditional_options(expr * var, std::set<expr*> & unrolls, zstring lcmStr) {
-	context & ctx = get_context();
-	ast_manager & mgr = get_manager();
-
-	int dist = opt_LCMUnrollStep;
-	expr_ref_vector litems(mgr);
-	expr_ref moreAst(mk_string("more"), mgr);
-	for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
-		expr_ref item(ctx.mk_eq_atom(var, *itor), mgr);
-		TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;);
-		litems.push_back(item);
-	}
-
-	// handle out-of-scope entries in unroll_tries_map
-
-	ptr_vector<expr> outOfScopeTesters;
-
-	for (ptr_vector<expr>::iterator it = unroll_tries_map[var][unrolls].begin();
-	        it != unroll_tries_map[var][unrolls].end(); ++it) {
-	    expr * tester = *it;
-	    bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end());
-	    TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr)
-	            << (inScope ? " in scope" : " out of scope")
-	            << std::endl;);
-	    if (!inScope) {
-	        outOfScopeTesters.push_back(tester);
-	    }
-	}
-
-	for (ptr_vector<expr>::iterator it = outOfScopeTesters.begin();
-	        it != outOfScopeTesters.end(); ++it) {
-	    unroll_tries_map[var][unrolls].erase(*it);
-	}
-
-
-	if (unroll_tries_map[var][unrolls].size() == 0) {
-		unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var());
-	}
-
-	int tries = unroll_tries_map[var][unrolls].size();
-	for (int i = 0; i < tries; i++) {
-		expr * tester = unroll_tries_map[var][unrolls][i];
-		// TESTING
-		refresh_theory_var(tester);
-		bool testerHasValue = false;
-		expr * testerVal = get_eqc_value(tester, testerHasValue);
-		if (!testerHasValue) {
-			// generate make-up assertion
-			int l = i * dist;
-			int h = (i + 1) * dist;
-			expr_ref lImp(mk_and(litems), mgr);
-			expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr);
-
-			SASSERT(lImp);
-			TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;);
-			SASSERT(rImp);
-			TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;);
-
-			expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr);
-			SASSERT(toAssert);
-			TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;);
-			m_trail.push_back(toAssert);
-			return toAssert;
-
-			// note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions.
-			// the return is in the same place
-
-			// insert [tester = "more"] to litems so that the implyL for next tester is correct
-			litems.push_back(ctx.mk_eq_atom(tester, moreAst));
-		} else {
-		    zstring testerStr;
-		    u.str.is_string(testerVal, testerStr);
-			TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";);
-			if (testerStr == "more") {
-				litems.push_back(ctx.mk_eq_atom(tester, moreAst));
-			}
-		}
-	}
-	expr * tester = mk_unroll_test_var();
-	unroll_tries_map[var][unrolls].push_back(tester);
-	int l = tries * dist;
-	int h = (tries + 1) * dist;
-	expr_ref lImp(mk_and(litems), mgr);
-	expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr);
-	SASSERT(lImp);
-	SASSERT(rImp);
-	expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr);
-	SASSERT(toAssert);
-	TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;);
-	m_trail.push_back(toAssert);
-	return toAssert;
-}
-
-expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) {
-	context & ctx = get_context();
-	ast_manager & mgr = get_manager();
-
-	TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr
-			<< ", l = " << l << ", h = " << h << "\n";);
-
-	if (m_params.m_AggressiveUnrollTesting) {
-	    TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;);
-	}
-
-	expr_ref_vector orItems(mgr);
-	expr_ref_vector andItems(mgr);
-
-	for (int i = l; i < h; i++) {
-		zstring iStr = int_to_string(i);
-		expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr);
-		TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;);
-		if (m_params.m_AggressiveUnrollTesting) {
-		    literal l = mk_eq(testerVar, mk_string(iStr), false);
-		    ctx.mark_as_relevant(l);
-		    ctx.force_phase(l);
-		}
-
-		orItems.push_back(testerEqAst);
-		zstring unrollStrInstance = get_unrolled_string(lcmStr, i);
-
-		expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr);
-		TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;);
-		andItems.push_back(x1);
-
-		expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr);
-		TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;);
-		andItems.push_back(x2);
-	}
-	expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr);
-	TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;);
-	if (m_params.m_AggressiveUnrollTesting) {
-	    literal l = mk_eq(testerVar, mk_string("more"), false);
-	    ctx.mark_as_relevant(l);
-	    ctx.force_phase(~l);
-	}
-
-	orItems.push_back(testerEqMore);
-	int nextLowerLenBound = h * lcmStr.length();
-	expr_ref more2(ctx.mk_eq_atom(testerEqMore,
-			//Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound))
-			m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0))
-			), mgr);
-	TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;);
-	andItems.push_back(more2);
-
-	expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr);
-	TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;);
-	andItems.push_back(mk_or(orItems));
-
-	expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr);
-	TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;);
-
-	// doing the following avoids a segmentation fault
-	m_trail.push_back(finalAND);
-	return finalAND;
-}
-
-expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) {
-	ast_manager & m = get_manager();
-	context & ctx = get_context();
-
-	expr_ref freeVarLen(mk_strlen(freeVar), m);
-	SASSERT(freeVarLen);
-
-	expr_ref_vector orList(m);
-	expr_ref_vector andList(m);
-
-	int distance = 3;
-	int l = (tries - 1) * distance;
-	int h = tries * distance;
-
-	TRACE("str",
-	        tout << "building andList and orList" << std::endl;
-	        if (m_params.m_AggressiveLengthTesting) {
-	            tout << "note: aggressive length testing is active" << std::endl;
-	        }
-	);
-
-	// experimental theory-aware case split support
-	literal_vector case_split_literals;
-
-	for (int i = l; i < h; ++i) {
-	    expr_ref str_indicator(m);
-	    if (m_params.m_UseFastLengthTesterCache) {
-	        rational ri(i);
-	        expr * lookup_val;
-	        if(lengthTesterCache.find(ri, lookup_val)) {
-	            str_indicator = expr_ref(lookup_val, m);
-	        } else {
-	            // no match; create and insert
-	            zstring i_str = int_to_string(i);
-                expr_ref new_val(mk_string(i_str), m);
-                lengthTesterCache.insert(ri, new_val);
-                m_trail.push_back(new_val);
-                str_indicator = expr_ref(new_val, m);
-	        }
-	    } else {
-	        zstring i_str = int_to_string(i);
-	        str_indicator = expr_ref(mk_string(i_str), m);
-	    }
-		expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m);
-		orList.push_back(or_expr);
-
-		double priority;
-		// give high priority to small lengths if this is available
-		if (i <= 5) {
-		    priority = 0.3;
-		} else {
-		    // prioritize over "more"
-		    priority = 0.2;
-		}
-		add_theory_aware_branching_info(or_expr, priority, l_true);
-
-		if (m_params.m_AggressiveLengthTesting) {
-		    literal l = mk_eq(indicator, str_indicator, false);
-		    ctx.mark_as_relevant(l);
-		    ctx.force_phase(l);
-		}
-
-		case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false));
-
-		expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m);
-		andList.push_back(and_expr);
-	}
-
-	expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m);
-	orList.push_back(more_option);
-	// decrease priority of this option
-	add_theory_aware_branching_info(more_option, -0.1, l_true);
-	if (m_params.m_AggressiveLengthTesting) {
-	    literal l = mk_eq(indicator, mk_string("more"), false);
-	    ctx.mark_as_relevant(l);
-	    ctx.force_phase(~l);
-	}
-
-	andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h))));
-
-	/*
-	{ // more experimental theory case split support
-	    expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m);
-	    ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false);
-        case_split_literals.push_back(ctx.get_literal(tmp));
-        ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr());
-	}
-	*/
-
-	expr_ref_vector or_items(m);
-	expr_ref_vector and_items(m);
-
-	for (unsigned i = 0; i < orList.size(); ++i) {
-		or_items.push_back(orList.get(i));
-	}
-
-	and_items.push_back(mk_or(or_items));
-	for(unsigned i = 0; i < andList.size(); ++i) {
-		and_items.push_back(andList.get(i));
-	}
-
-	TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;);
-
-	expr_ref lenTestAssert = mk_and(and_items);
-	SASSERT(lenTestAssert);
-	TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;);
-
-	int testerCount = tries - 1;
-	if (testerCount > 0) {
-	    expr_ref_vector and_items_LHS(m);
-		expr_ref moreAst(mk_string("more"), m);
-		for (int i = 0; i < testerCount; ++i) {
-		    expr * indicator = fvar_lenTester_map[freeVar][i];
-		    if (internal_variable_set.find(indicator) == internal_variable_set.end()) {
-		        TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;);
-		        continue;
-		    } else {
-		        TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;);
-		        and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst));
-		    }
-		}
-		expr_ref assertL(mk_and(and_items_LHS), m);
-		SASSERT(assertL);
-		expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get());
-		SASSERT(finalAxiom != NULL);
-		TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;);
-		return finalAxiom;
-	} else {
-	    TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;);
-	    m_trail.push_back(lenTestAssert.get());
-	    return lenTestAssert.get();
-	}
-}
-
-// Return an expression of the form
-// (tester = "less" | tester = "N" | tester = "more") &
-//   (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N))
-expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) {
-    context & ctx = get_context();
-    ast_manager & m = get_manager();
-    rational N = bounds.midPoint;
-    rational N_minus_one = N - rational::one();
-    rational N_plus_one = N + rational::one();
-    expr_ref lenFreeVar(mk_strlen(freeVar), m);
-
-    TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m)
-            << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;);
-
-    expr_ref_vector combinedCaseSplit(m);
-    expr_ref_vector testerCases(m);
-
-    expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m);
-    testerCases.push_back(caseLess);
-    combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) )));
-
-    expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m);
-    testerCases.push_back(caseMore);
-    combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) )));
-
-    expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m);
-    testerCases.push_back(caseEq);
-    combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true))));
-
-    combinedCaseSplit.push_back(mk_or(testerCases));
-
-    // force internalization on all terms in testerCases so we can extract literals
-    for (unsigned i = 0; i < testerCases.size(); ++i) {
-        expr * testerCase = testerCases.get(i);
-        if (!ctx.b_internalized(testerCase)) {
-            ctx.internalize(testerCase, false);
-        }
-        literal l = ctx.get_literal(testerCase);
-        case_split.push_back(l);
-    }
-
-    expr_ref final_term(mk_and(combinedCaseSplit), m);
-    SASSERT(final_term);
-    TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;);
-    return final_term;
-}
-
-expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) {
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-
-    if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) {
-        TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl;
-            for (ptr_vector<expr>::const_iterator it = binary_search_len_tester_stack[freeVar].begin();
-                    it != binary_search_len_tester_stack[freeVar].end(); ++it) {
-                expr * tester = *it;
-                tout << mk_pp(tester, m) << ": ";
-                if (binary_search_len_tester_info.contains(tester)) {
-                    binary_search_info & bounds = binary_search_len_tester_info[tester];
-                    tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize;
-                } else {
-                    tout << "[WARNING: no bounds info available]";
-                }
-                bool hasEqcValue;
-                expr * testerEqcValue = get_eqc_value(tester, hasEqcValue);
-                if (hasEqcValue) {
-                    tout << " = " << mk_pp(testerEqcValue, m);
-                } else {
-                    tout << " [no eqc value]";
-                }
-                tout << std::endl;
-            }
-        );
-        expr * lastTester = binary_search_len_tester_stack[freeVar].back();
-        bool lastTesterHasEqcValue;
-        expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue);
-        zstring lastTesterConstant;
-        if (!lastTesterHasEqcValue) {
-            TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;);
-            // check previousLenTester
-            if (previousLenTester == lastTester) {
-                lastTesterConstant = previousLenTesterValue;
-                TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;);
-            } else {
-                TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;);
-                UNREACHABLE(); return NULL;
-            }
-        } else {
-            u.str.is_string(lastTesterValue, lastTesterConstant);
-        }
-        TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";);
-        if (lastTesterConstant == "more" || lastTesterConstant == "less") {
-            // use the previous bounds info to generate a new midpoint
-            binary_search_info lastBounds;
-            if (!binary_search_len_tester_info.find(lastTester, lastBounds)) {
-                // unexpected
-                TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;);
-                UNREACHABLE();
-            }
-            TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;);
-            binary_search_info newBounds;
-            expr * newTester;
-            if (lastTesterConstant == "more") {
-                // special case: if the midpoint, upper bound, and window size are all equal,
-                // we double the window size and adjust the bounds
-                if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) {
-                    TRACE("str", tout << "search hit window size; expanding" << std::endl;);
-                    newBounds.lowerBound = lastBounds.windowSize + rational::one();
-                    newBounds.windowSize = lastBounds.windowSize * rational(2);
-                    newBounds.upperBound = newBounds.windowSize;
-                    newBounds.calculate_midpoint();
-                } else if (false) {
-                    // handle the case where the midpoint can't be increased further
-                    // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50")
-                } else {
-                    // general case
-                    newBounds.lowerBound = lastBounds.midPoint + rational::one();
-                    newBounds.windowSize = lastBounds.windowSize;
-                    newBounds.upperBound = lastBounds.upperBound;
-                    newBounds.calculate_midpoint();
-                }
-                if (!binary_search_next_var_high.find(lastTester, newTester)) {
-                    newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32());
-                    binary_search_next_var_high.insert(lastTester, newTester);
-                }
-                refresh_theory_var(newTester);
-            } else if (lastTesterConstant == "less") {
-                if (false) {
-                    // handle the case where the midpoint can't be decreased further
-                    // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0"
-                } else {
-                    // general case
-                    newBounds.upperBound = lastBounds.midPoint - rational::one();
-                    newBounds.windowSize = lastBounds.windowSize;
-                    newBounds.lowerBound = lastBounds.lowerBound;
-                    newBounds.calculate_midpoint();
-                }
-                if (!binary_search_next_var_low.find(lastTester, newTester)) {
-                    newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32());
-                    binary_search_next_var_low.insert(lastTester, newTester);
-                }
-                refresh_theory_var(newTester);
-            }
-            TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;);
-            binary_search_len_tester_stack[freeVar].push_back(newTester);
-            m_trail_stack.push(binary_search_trail<theory_str>(binary_search_len_tester_stack, freeVar));
-            binary_search_len_tester_info.insert(newTester, newBounds);
-            m_trail_stack.push(insert_obj_map<theory_str, expr, binary_search_info>(binary_search_len_tester_info, newTester));
-
-            literal_vector case_split_literals;
-            expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals));
-            m_trail.push_back(next_case_split);
-	    // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr());
-            return next_case_split;
-        } else { // lastTesterConstant is a concrete value
-            TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;);
-            // defensive check that this length did not converge on a negative value.
-            binary_search_info lastBounds;
-            if (!binary_search_len_tester_info.find(lastTester, lastBounds)) {
-                // unexpected
-                TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;);
-                UNREACHABLE();
-            }
-            if (lastBounds.midPoint.is_neg()) {
-                TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;);
-                expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m);
-                return axiom;
-            }
-            // length is fixed
-            expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring(""));
-            return valueAssert;
-        }
-    } else {
-        // no length testers yet
-        TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;);
-        binary_search_len_tester_stack.insert(freeVar, ptr_vector<expr>());
-
-        expr * firstTester;
-        rational lowerBound(0);
-        rational upperBound(m_params.m_BinarySearchInitialUpperBound);
-        rational windowSize(upperBound);
-        rational midPoint(floor(upperBound / rational(2)));
-        if (!binary_search_starting_len_tester.find(freeVar, firstTester)) {
-            firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32());
-            binary_search_starting_len_tester.insert(freeVar, firstTester);
-        }
-        refresh_theory_var(firstTester);
-
-        binary_search_len_tester_stack[freeVar].push_back(firstTester);
-        m_trail_stack.push(binary_search_trail<theory_str>(binary_search_len_tester_stack, freeVar));
-        binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize);
-        binary_search_len_tester_info.insert(firstTester, new_info);
-        m_trail_stack.push(insert_obj_map<theory_str, expr, binary_search_info>(binary_search_len_tester_info, firstTester));
-
-        literal_vector case_split_literals;
-        expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals));
-        m_trail.push_back(initial_case_split);
-        // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr());
-        return initial_case_split;
-    }
-}
-
-// -----------------------------------------------------------------------------------------------------
-// True branch will be taken in final_check:
-//   - When we discover a variable is "free" for the first time
-//     lenTesterInCbEq = NULL
-//     lenTesterValue = ""
-// False branch will be taken when invoked by new_eq_eh().
-//   - After we set up length tester for a "free" var in final_check,
-//     when the tester is assigned to some value (e.g. "more" or "4"),
-//     lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue
-// The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc
-// -----------------------------------------------------------------------------------------------------
-expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) {
-
-	ast_manager & m = get_manager();
-
-	TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;);
-
-	if (m_params.m_UseBinarySearch) {
-	    TRACE("str", tout << "using binary search heuristic" << std::endl;);
-	    return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue);
-	} else {
-        bool map_effectively_empty = false;
-        if (!fvar_len_count_map.contains(freeVar)) {
-            TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;);
-            map_effectively_empty = true;
+    expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        TRACE("str", tout << "activating finite model testing for overlapping concats "
+              << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;);
+        std::map<expr*, int> concatMap;
+        std::map<expr*, int> unrollMap;
+        std::map<expr*, int> varMap;
+        classify_ast_by_type(lhs, varMap, concatMap, unrollMap);
+        classify_ast_by_type(rhs, varMap, concatMap, unrollMap);
+        TRACE("str", tout << "found vars:";
+              for (std::map<expr*,int>::iterator it = varMap.begin(); it != varMap.end(); ++it) {
+                  tout << " " << mk_pp(it->first, m);
+              }
+              tout << std::endl;
+              );
+
+        expr_ref testvar(mk_str_var("finiteModelTest"), m);
+        m_trail.push_back(testvar);
+        ptr_vector<expr> varlist;
+
+        for (std::map<expr*, int>::iterator it = varMap.begin(); it != varMap.end(); ++it) {
+            expr * v = it->first;
+            varlist.push_back(v);
         }
 
-        if (!map_effectively_empty) {
-            // check whether any entries correspond to variables that went out of scope;
-            // if every entry is out of scope then the map counts as being empty
+        // make things easy for the core wrt. testvar
+        expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m);
+        expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m);
+        expr_ref testvaraxiom(m.mk_or(t1, t_yes), m);
+        assert_axiom(testvaraxiom);
 
-            // assume empty and find a counterexample
-            map_effectively_empty = true;
-            ptr_vector<expr> indicator_set = fvar_lenTester_map[freeVar];
-            for (ptr_vector<expr>::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) {
-                expr * indicator = *it;
-                if (internal_variable_set.find(indicator) != internal_variable_set.end()) {
-                    TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m)
-                            << " in fvar_lenTester_map[freeVar]" << std::endl;);
-                    map_effectively_empty = false;
-                    break;
-                }
-            }
-            CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;);
-        }
+        finite_model_test_varlists.insert(testvar, varlist);
+        m_trail_stack.push(insert_obj_map<theory_str, expr, ptr_vector<expr> >(finite_model_test_varlists, testvar) );
+        return t_yes;
+    }
 
-        if (map_effectively_empty) {
-            // no length assertions for this free variable have ever been added.
-            TRACE("str", tout << "no length assertions yet" << std::endl;);
+    void theory_str::finite_model_test(expr * testvar, expr * str) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-            fvar_len_count_map.insert(freeVar, 1);
-            unsigned int testNum = fvar_len_count_map[freeVar];
-
-            expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m);
-            SASSERT(indicator);
-
-            // since the map is "effectively empty", we can remove those variables that have left scope...
-            fvar_lenTester_map[freeVar].shrink(0);
-            fvar_lenTester_map[freeVar].push_back(indicator);
-            lenTester_fvar_map.insert(indicator, freeVar);
-
-            expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum);
-            SASSERT(lenTestAssert != NULL);
-            return lenTestAssert;
-        } else {
-            TRACE("str", tout << "found previous in-scope length assertions" << std::endl;);
-
-            expr * effectiveLenInd = NULL;
-            zstring effectiveLenIndiStr("");
-            int lenTesterCount = (int) fvar_lenTester_map[freeVar].size();
-
-            TRACE("str",
-                    tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl;
-                    for (int i = 0; i < lenTesterCount; ++i) {
-                        expr * len_indicator = fvar_lenTester_map[freeVar][i];
-                        tout << mk_pp(len_indicator, m) << ": ";
-                        bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end());
-                        tout << (effectiveInScope ? "in scope" : "NOT in scope");
-                        tout << std::endl;
-                    }
-                    );
-
-            int i = 0;
-            for (; i < lenTesterCount; ++i) {
-                expr * len_indicator_pre = fvar_lenTester_map[freeVar][i];
-                // check whether this is in scope as well
-                if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) {
-                    TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;);
+        zstring s;
+        if (!u.str.is_string(str, s)) return;
+        if (s == "yes") {
+            TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;);
+            ptr_vector<expr> & vars = finite_model_test_varlists[testvar];
+            for (ptr_vector<expr>::iterator it = vars.begin(); it != vars.end(); ++it) {
+                expr * v = *it;
+                bool v_has_eqc = false;
+                get_eqc_value(v, v_has_eqc);
+                if (v_has_eqc) {
+                    TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;);
                     continue;
                 }
+                // check for any sort of existing length tester we might interfere with
+                if (m_params.m_UseBinarySearch) {
+                    if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) {
+                        TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;);
+                        continue;
+                    } else {
+                        // start binary search as normal
+                        expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m);
+                        expr_ref implRhs(binary_search_length_test(v, NULL, ""), m);
+                        assert_implication(implLhs, implRhs);
+                    }
+                } else {
+                    bool map_effectively_empty = false;
+                    if (!fvar_len_count_map.contains(v)) {
+                        map_effectively_empty = true;
+                    }
 
+                    if (!map_effectively_empty) {
+                        map_effectively_empty = true;
+                        ptr_vector<expr> indicator_set = fvar_lenTester_map[v];
+                        for (ptr_vector<expr>::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) {
+                            expr * indicator = *it;
+                            if (internal_variable_set.find(indicator) != internal_variable_set.end()) {
+                                map_effectively_empty = false;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (map_effectively_empty) {
+                        TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;);
+                        rational v_len;
+                        rational v_lower_bound;
+                        rational v_upper_bound;
+                        expr_ref vLengthExpr(mk_strlen(v), m);
+                        if (get_len_value(v, v_len)) {
+                            TRACE("str", tout << "length = " << v_len.to_string() << std::endl;);
+                            v_lower_bound = v_len;
+                            v_upper_bound = v_len;
+                        } else {
+                            bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound);
+                            bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound);
+                            TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?")
+                                  << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;);
+
+                            // make sure the bounds are non-negative
+                            if (lower_bound_exists && v_lower_bound.is_neg()) {
+                                v_lower_bound = rational::zero();
+                            }
+                            if (upper_bound_exists && v_upper_bound.is_neg()) {
+                                v_upper_bound = rational::zero();
+                            }
+
+                            if (lower_bound_exists && upper_bound_exists) {
+                                // easiest case. we will search within these bounds
+                            } else if (upper_bound_exists && !lower_bound_exists) {
+                                // search between 0 and the upper bound
+                                v_lower_bound == rational::zero();
+                            } else if (lower_bound_exists && !upper_bound_exists) {
+                                // check some finite portion of the search space
+                                v_upper_bound = v_lower_bound + rational(10);
+                            } else {
+                                // no bounds information
+                                v_lower_bound = rational::zero();
+                                v_upper_bound = v_lower_bound + rational(10);
+                            }
+                        }
+                        // now create a fake length tester over this finite disjunction of lengths
+
+                        fvar_len_count_map[v] = 1;
+                        unsigned int testNum = fvar_len_count_map[v];
+
+                        expr_ref indicator(mk_internal_lenTest_var(v, testNum), m);
+                        SASSERT(indicator);
+                        m_trail.push_back(indicator);
+
+                        fvar_lenTester_map[v].shrink(0);
+                        fvar_lenTester_map[v].push_back(indicator);
+                        lenTester_fvar_map[indicator] = v;
+
+                        expr_ref_vector orList(m);
+                        expr_ref_vector andList(m);
+
+                        for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) {
+                            zstring lStr = zstring(l.to_string().c_str());
+                            expr_ref str_indicator(mk_string(lStr), m);
+                            expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m);
+                            orList.push_back(or_expr);
+                            expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m);
+                            andList.push_back(and_expr);
+                        }
+                        andList.push_back(mk_or(orList));
+                        expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m);
+                        expr_ref implRhs(mk_and(andList), m);
+                        assert_implication(implLhs, implRhs);
+                    } else {
+                        TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;);
+                        continue;
+                    }
+                }
+            } // foreach (v in vars)
+        } // (s == "yes")
+    }
+
+    void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) {
+        ast_manager & m = get_manager();
+        if (lenTester_fvar_map.contains(lenTester)) {
+            expr * fVar = lenTester_fvar_map[lenTester];
+            expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue);
+            TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;);
+            if (toAssert != NULL) {
+                assert_axiom(toAssert);
+            }
+        }
+    }
+
+    void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) {
+        ast_manager & m = get_manager();
+
+        expr * fVar = valueTester_fvar_map[valTester];
+        if (m_params.m_UseBinarySearch) {
+            if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) {
+                TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;);
+                NOT_IMPLEMENTED_YET();
+            }
+            expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back();
+            bool hasEqcValue;
+            expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue);
+            if (!hasEqcValue) {
+                TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;);
+            } else {
+                // safety check
+                zstring effectiveLenIndiStr;
+                u.str.is_string(len_indicator_value, effectiveLenIndiStr);
+                if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") {
+                    TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;);
+                    UNREACHABLE();
+                }
+                expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue);
+                TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;);
+                if (valueAssert != NULL) {
+                    assert_axiom(valueAssert);
+                }
+            }
+        } else {
+            int lenTesterCount = fvar_lenTester_map[fVar].size();
+
+            expr * effectiveLenInd = NULL;
+            zstring effectiveLenIndiStr = "";
+            for (int i = 0; i < lenTesterCount; ++i) {
+                expr * len_indicator_pre = fvar_lenTester_map[fVar][i];
                 bool indicatorHasEqcValue = false;
                 expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue);
-                TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) <<
-                        " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;);
                 if (indicatorHasEqcValue) {
                     zstring len_pIndiStr;
                     u.str.is_string(len_indicator_value, len_pIndiStr);
@@ -10281,318 +6974,3623 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe
                         effectiveLenIndiStr = len_pIndiStr;
                         break;
                     }
-                } else {
-                    if (lenTesterInCbEq != len_indicator_pre) {
-                        TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m)
-                                << " does not have an equivalence class value."
-                                << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;);
-                        if (i > 0) {
-                            effectiveLenInd = fvar_lenTester_map[freeVar][i - 1];
-                            bool effectiveHasEqcValue;
-                            expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue);
-                            bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end());
-                            TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": "
-                                    << (effectiveInScope ? "in scope" : "NOT in scope") << ", ";
-                                    if (effectiveHasEqcValue) {
-                                        tout << "~= " << mk_pp(effective_eqc_value, m);
-                                    } else {
-                                        tout << "no eqc string constant";
-                                    }
-                                    tout << std::endl;);
-                            if (effectiveLenInd == lenTesterInCbEq) {
-                                effectiveLenIndiStr = lenTesterValue;
+                }
+            }
+            expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue);
+            TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;);
+            if (valueAssert != NULL) {
+                assert_axiom(valueAssert);
+            }
+        }
+    }
+
+    bool theory_str::free_var_attempt(expr * nn1, expr * nn2) {
+        ast_manager & m = get_manager();
+        zstring nn2_str;
+        if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) {
+            TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m)
+                  << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;);
+            more_len_tests(nn1, nn2_str);
+            return true;
+        } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) {
+            if (nn2_str == "more") {
+                TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m)
+                      << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;);
+                more_value_tests(nn1, nn2_str);
+            }
+            return true;
+        } else if (internal_unrollTest_vars.contains(nn1)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void theory_str::handle_equality(expr * lhs, expr * rhs) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+        // both terms must be of sort String
+        sort * lhs_sort = m.get_sort(lhs);
+        sort * rhs_sort = m.get_sort(rhs);
+        sort * str_sort = u.str.mk_string_sort();
+
+        if (lhs_sort != str_sort || rhs_sort != str_sort) {
+            TRACE("str", tout << "skip equality: not String sort" << std::endl;);
+            return;
+        }
+
+        /* // temporarily disabled, we are borrowing these testers for something else
+           if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) {
+           if (finite_model_test_varlists.contains(lhs)) {
+           finite_model_test(lhs, rhs); return;
+           } else if (finite_model_test_varlists.contains(rhs)) {
+           finite_model_test(rhs, lhs); return;
+           }
+           }
+        */
+
+        if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) {
+            return;
+        }
+
+        if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) {
+            bool nn1HasEqcValue = false;
+            bool nn2HasEqcValue = false;
+            expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue);
+            expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue);
+            if (nn1HasEqcValue && !nn2HasEqcValue) {
+                simplify_parent(rhs, nn1_value);
+            }
+            if (!nn1HasEqcValue && nn2HasEqcValue) {
+                simplify_parent(lhs, nn2_value);
+            }
+
+            expr * nn1_arg0 = to_app(lhs)->get_arg(0);
+            expr * nn1_arg1 = to_app(lhs)->get_arg(1);
+            expr * nn2_arg0 = to_app(rhs)->get_arg(0);
+            expr * nn2_arg1 = to_app(rhs)->get_arg(1);
+            if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) {
+                TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;);
+                return;
+            }
+
+            if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) {
+                TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;);
+                return;
+            }
+        }
+
+        if (opt_DeferEQCConsistencyCheck) {
+            TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;);
+        } else {
+            // newEqCheck() -- check consistency wrt. existing equivalence classes
+            if (!new_eq_check(lhs, rhs)) {
+                return;
+            }
+        }
+
+        // BEGIN new_eq_handler() in strTheory
+
+        {
+            rational nn1Len, nn2Len;
+            bool nn1Len_exists = get_len_value(lhs, nn1Len);
+            bool nn2Len_exists = get_len_value(rhs, nn2Len);
+            expr * emptyStr = mk_string("");
+
+            if (nn1Len_exists && nn1Len.is_zero()) {
+                if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) {
+                    expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m);
+                    expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m);
+                    expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m);
+                    assert_axiom(toAssert);
+                }
+            }
+
+            if (nn2Len_exists && nn2Len.is_zero()) {
+                if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) {
+                    expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m);
+                    expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m);
+                    expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m);
+                    assert_axiom(toAssert);
+                }
+            }
+        }
+
+        instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs));
+
+        // group terms by equivalence class (groupNodeInEqc())
+
+        std::set<expr*> eqc_concat_lhs;
+        std::set<expr*> eqc_var_lhs;
+        std::set<expr*> eqc_const_lhs;
+        group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs);
+
+        std::set<expr*> eqc_concat_rhs;
+        std::set<expr*> eqc_var_rhs;
+        std::set<expr*> eqc_const_rhs;
+        group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs);
+
+        TRACE("str",
+              tout << "lhs eqc:" << std::endl;
+              tout << "Concats:" << std::endl;
+              for (std::set<expr*>::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) {
+                  expr * ex = *it;
+                  tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
+              }
+              tout << "Variables:" << std::endl;
+              for (std::set<expr*>::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) {
+                  expr * ex = *it;
+                  tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
+              }
+              tout << "Constants:" << std::endl;
+              for (std::set<expr*>::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) {
+                  expr * ex = *it;
+                  tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
+              }
+
+              tout << "rhs eqc:" << std::endl;
+              tout << "Concats:" << std::endl;
+              for (std::set<expr*>::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) {
+                  expr * ex = *it;
+                  tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
+              }
+              tout << "Variables:" << std::endl;
+              for (std::set<expr*>::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) {
+                  expr * ex = *it;
+                  tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
+              }
+              tout << "Constants:" << std::endl;
+              for (std::set<expr*>::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) {
+                  expr * ex = *it;
+                  tout << mk_ismt2_pp(ex, get_manager()) << std::endl;
+              }
+              );
+
+        // step 1: Concat == Concat
+        int hasCommon = 0;
+        if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) {
+            std::set<expr*>::iterator itor1 = eqc_concat_lhs.begin();
+            std::set<expr*>::iterator itor2 = eqc_concat_rhs.begin();
+            for (; itor1 != eqc_concat_lhs.end(); itor1++) {
+                if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) {
+                    hasCommon = 1;
+                    break;
+                }
+            }
+            for (; itor2 != eqc_concat_rhs.end(); itor2++) {
+                if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) {
+                    hasCommon = 1;
+                    break;
+                }
+            }
+            if (hasCommon == 0) {
+                if (opt_ConcatOverlapAvoid) {
+                    bool found = false;
+                    // check each pair and take the first ones that won't immediately overlap
+                    for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) {
+                        expr * concat_lhs = *itor1;
+                        for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) {
+                            expr * concat_rhs = *itor2;
+                            if (will_result_in_overlap(concat_lhs, concat_rhs)) {
+                                TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and "
+                                      << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;);
                             } else {
-                                if (effectiveHasEqcValue) {
-                                    u.str.is_string(effective_eqc_value, effectiveLenIndiStr);
-                                } else {
-                                    NOT_IMPLEMENTED_YET();
-                                }
+                                TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and "
+                                      << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;);
+                                simplify_concat_equality(concat_lhs, concat_rhs);
+                                found = true;
+                                break;
                             }
                         }
-                        break;
                     }
-                    // lenTesterInCbEq == len_indicator_pre
-                    else {
-                        if (lenTesterValue != "more") {
-                            effectiveLenInd = len_indicator_pre;
-                            effectiveLenIndiStr = lenTesterValue;
+                    if (!found) {
+                        TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;);
+                        simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin()));
+                    }
+                } else {
+                    // default behaviour
+                    simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin()));
+                }
+            }
+        }
+
+        // step 2: Concat == Constant
+
+        if (eqc_const_lhs.size() != 0) {
+            expr * conStr = *(eqc_const_lhs.begin());
+            std::set<expr*>::iterator itor2 = eqc_concat_rhs.begin();
+            for (; itor2 != eqc_concat_rhs.end(); itor2++) {
+                solve_concat_eq_str(*itor2, conStr);
+            }
+        } else if (eqc_const_rhs.size() != 0) {
+            expr* conStr = *(eqc_const_rhs.begin());
+            std::set<expr*>::iterator itor1 = eqc_concat_lhs.begin();
+            for (; itor1 != eqc_concat_lhs.end(); itor1++) {
+                solve_concat_eq_str(*itor1, conStr);
+            }
+        }
+
+        // simplify parents wrt. the equivalence class of both sides
+        bool nn1HasEqcValue = false;
+        bool nn2HasEqcValue = false;
+        // we want the Z3str2 eqc check here...
+        expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue);
+        expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue);
+        if (nn1HasEqcValue && !nn2HasEqcValue) {
+            simplify_parent(rhs, nn1_value);
+        }
+
+        if (!nn1HasEqcValue && nn2HasEqcValue) {
+            simplify_parent(lhs, nn2_value);
+        }
+
+        expr * nn1EqConst = NULL;
+        std::set<expr*> nn1EqUnrollFuncs;
+        get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs);
+        expr * nn2EqConst = NULL;
+        std::set<expr*> nn2EqUnrollFuncs;
+        get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs);
+
+        if (nn2EqConst != NULL) {
+            for (std::set<expr*>::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) {
+                process_unroll_eq_const_str(*itor1, nn2EqConst);
+            }
+        }
+
+        if (nn1EqConst != NULL) {
+            for (std::set<expr*>::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) {
+                process_unroll_eq_const_str(*itor2, nn1EqConst);
+            }
+        }
+
+    }
+
+    void theory_str::set_up_axioms(expr * ex) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        sort * ex_sort = m.get_sort(ex);
+        sort * str_sort = u.str.mk_string_sort();
+        sort * bool_sort = m.mk_bool_sort();
+
+        family_id m_arith_fid = m.mk_family_id("arith");
+        sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT);
+
+        if (ex_sort == str_sort) {
+            TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
+                  ": expr is of sort String" << std::endl;);
+            // set up basic string axioms
+            enode * n = ctx.get_enode(ex);
+            SASSERT(n);
+            m_basicstr_axiom_todo.push_back(n);
+            TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;);
+
+
+            if (is_app(ex)) {
+                app * ap = to_app(ex);
+                if (u.str.is_concat(ap)) {
+                    // if ex is a concat, set up concat axioms later
+                    m_concat_axiom_todo.push_back(n);
+                    // we also want to check whether we can eval this concat,
+                    // in case the rewriter did not totally finish with this term
+                    m_concat_eval_todo.push_back(n);
+                } else if (u.str.is_length(ap)) {
+                    // if the argument is a variable,
+                    // keep track of this for later, we'll need it during model gen
+                    expr * var = ap->get_arg(0);
+                    app * aVar = to_app(var);
+                    if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) {
+                        input_var_in_len.insert(var);
+                    }
+                } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) {
+                    m_library_aware_axiom_todo.push_back(n);
+                } else if (u.str.is_itos(ap)) {
+                    TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;);
+                    string_int_conversion_terms.push_back(ap);
+                    m_library_aware_axiom_todo.push_back(n);
+                } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) {
+                    // if ex is a variable, add it to our list of variables
+                    TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;);
+                    variable_set.insert(ex);
+                    ctx.mark_as_relevant(ex);
+                    // this might help??
+                    theory_var v = mk_var(n);
+                    TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;);
+                }
+            }
+        } else if (ex_sort == bool_sort) {
+            TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
+                  ": expr is of sort Bool" << std::endl;);
+            // set up axioms for boolean terms
+
+            ensure_enode(ex);
+            if (ctx.e_internalized(ex)) {
+                enode * n = ctx.get_enode(ex);
+                SASSERT(n);
+
+                if (is_app(ex)) {
+                    app * ap = to_app(ex);
+                    if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) {
+                        m_library_aware_axiom_todo.push_back(n);
+                    }
+                }
+            } else {
+                TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;);
+                ENSURE(!search_started); // infinite loop prevention
+                m_delayed_axiom_setup_terms.push_back(ex);
+                return;
+            }
+        } else if (ex_sort == int_sort) {
+            TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
+                  ": expr is of sort Int" << std::endl;);
+            // set up axioms for integer terms
+            enode * n = ensure_enode(ex);
+            SASSERT(n);
+
+            if (is_app(ex)) {
+                app * ap = to_app(ex);
+                // TODO indexof2/lastindexof
+                if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) {
+                    m_library_aware_axiom_todo.push_back(n);
+                } else if (u.str.is_stoi(ap)) {
+                    TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;);
+                    string_int_conversion_terms.push_back(ap);
+                    m_library_aware_axiom_todo.push_back(n);
+                }
+            }
+        } else {
+            TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) <<
+                  ": expr is of wrong sort, ignoring" << std::endl;);
+        }
+
+        // if expr is an application, recursively inspect all arguments
+        if (is_app(ex)) {
+            app * term = (app*)ex;
+            unsigned num_args = term->get_num_args();
+            for (unsigned i = 0; i < num_args; i++) {
+                set_up_axioms(term->get_arg(i));
+            }
+        }
+    }
+
+    void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) {
+        TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;);
+        symbol strOverlap("!!TheoryStrOverlapAssumption!!");
+        seq_util m_sequtil(get_manager());
+        sort * s = get_manager().mk_bool_sort();
+        m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager());
+        assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term));
+    }
+
+    lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) {
+        bool assumptionFound = false;
+
+        app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term));
+        get_context().internalize(target_term, false);
+        for (unsigned i = 0; i < unsat_core.size(); ++i) {
+            app * core_term = to_app(unsat_core.get(i));
+            // not sure if this is the correct way to compare terms in this context
+            enode * e1;
+            enode * e2;
+            e1 = get_context().get_enode(target_term);
+            e2 = get_context().get_enode(core_term);
+            if (e1 == e2) {
+                TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;);
+                assumptionFound = true;
+                return l_undef;
+            }
+        }
+
+        return l_false;
+    }
+
+    void theory_str::init_search_eh() {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        TRACE("str",
+              tout << "dumping all asserted formulas:" << std::endl;
+              unsigned nFormulas = ctx.get_num_asserted_formulas();
+              for (unsigned i = 0; i < nFormulas; ++i) {
+                  expr * ex = ctx.get_asserted_formula(i);
+                  tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl;
+              }
+              );
+        /*
+         * Recursive descent through all asserted formulas to set up axioms.
+         * Note that this is just the input structure and not necessarily things
+         * that we know to be true or false. We're just doing this to see
+         * which terms are explicitly mentioned.
+         */
+        unsigned nFormulas = ctx.get_num_asserted_formulas();
+        for (unsigned i = 0; i < nFormulas; ++i) {
+            expr * ex = ctx.get_asserted_formula(i);
+            set_up_axioms(ex);
+        }
+
+        /*
+         * Similar recursive descent, except over all initially assigned terms.
+         * This is done to find equalities between terms, etc. that we otherwise
+         * might not get a chance to see.
+         */
+
+        /*
+          expr_ref_vector assignments(m);
+          ctx.get_assignments(assignments);
+          for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) {
+          expr * ex = *i;
+          if (m.is_eq(ex)) {
+          TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) <<
+          ": expr is equality" << std::endl;);
+          app * eq = (app*)ex;
+          SASSERT(eq->get_num_args() == 2);
+          expr * lhs = eq->get_arg(0);
+          expr * rhs = eq->get_arg(1);
+
+          enode * e_lhs = ctx.get_enode(lhs);
+          enode * e_rhs = ctx.get_enode(rhs);
+          std::pair<enode*,enode*> eq_pair(e_lhs, e_rhs);
+          m_str_eq_todo.push_back(eq_pair);
+          } else {
+          TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m)
+          << ": expr ignored" << std::endl;);
+          }
+          }
+        */
+
+        // this might be cheating but we need to make sure that certain maps are populated
+        // before the first call to new_eq_eh()
+        propagate();
+
+        TRACE("str", tout << "search started" << std::endl;);
+        search_started = true;
+    }
+
+    void theory_str::new_eq_eh(theory_var x, theory_var y) {
+        //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;);
+        TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " <<
+              mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;);
+
+        /*
+          if (m_find.find(x) == m_find.find(y)) {
+          return;
+          }
+        */
+        handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner());
+
+        // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality
+        m_find.merge(x, y);
+    }
+
+    void theory_str::new_diseq_eh(theory_var x, theory_var y) {
+        //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;);
+        TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " <<
+              mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;);
+    }
+
+    void theory_str::relevant_eh(app * n) {
+        TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;);
+    }
+
+    void theory_str::assign_eh(bool_var v, bool is_true) {
+        context & ctx = get_context();
+        TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;);
+    }
+
+    void theory_str::push_scope_eh() {
+        theory::push_scope_eh();
+        m_trail_stack.push_scope();
+
+        sLevel += 1;
+        TRACE("str", tout << "push to " << sLevel << std::endl;);
+        TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); });
+    }
+
+    void theory_str::recursive_check_variable_scope(expr * ex) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        if (is_app(ex)) {
+            app * a = to_app(ex);
+            if (a->get_num_args() == 0) {
+                // we only care about string variables
+                sort * s = m.get_sort(ex);
+                sort * string_sort = u.str.mk_string_sort();
+                if (s != string_sort) {
+                    return;
+                }
+                // base case: string constant / var
+                if (u.str.is_string(a)) {
+                    return;
+                } else {
+                    // assume var
+                    if (variable_set.find(ex) == variable_set.end()
+                        && internal_variable_set.find(ex) == internal_variable_set.end()) {
+                        TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;);
+                    }
+                }
+            } else {
+                for (unsigned i = 0; i < a->get_num_args(); ++i) {
+                    recursive_check_variable_scope(a->get_arg(i));
+                }
+            }
+        }
+    }
+
+    void theory_str::check_variable_scope() {
+        if (!opt_CheckVariableScope) {
+            return;
+        }
+
+        if (!is_trace_enabled("t_str_detail")) {
+            return;
+        }
+
+        TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;);
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr_ref_vector assignments(m);
+        ctx.get_assignments(assignments);
+        for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) {
+            expr * ex = *i;
+            recursive_check_variable_scope(ex);
+        }
+    }
+
+    void theory_str::pop_scope_eh(unsigned num_scopes) {
+        sLevel -= num_scopes;
+        TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;);
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); });
+
+        // list of expr* to remove from cut_var_map
+        ptr_vector<expr> cutvarmap_removes;
+
+        obj_map<expr, std::stack<T_cut *> >::iterator varItor = cut_var_map.begin();
+        while (varItor != cut_var_map.end()) {
+            expr * e = varItor->m_key;
+            std::stack<T_cut*> & val = cut_var_map[varItor->m_key];
+            while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) {
+                TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout););
+                T_cut * aCut = val.top();
+                val.pop();
+                // dealloc(aCut);
+            }
+            if (val.size() == 0) {
+                cutvarmap_removes.insert(varItor->m_key);
+            }
+            varItor++;
+        }
+
+        if (!cutvarmap_removes.empty()) {
+            ptr_vector<expr>::iterator it = cutvarmap_removes.begin();
+            for (; it != cutvarmap_removes.end(); ++it) {
+                expr * ex = *it;
+                cut_var_map.remove(ex);
+            }
+        }
+
+        ptr_vector<enode> new_m_basicstr;
+        for (ptr_vector<enode>::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) {
+            enode * e = *it;
+            app * a = e->get_owner();
+            TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager())
+                  << ", enode scope level is " << e->get_iscope_lvl()
+                  << std::endl;);
+            if (e->get_iscope_lvl() <= (unsigned)sLevel) {
+                new_m_basicstr.push_back(e);
+            }
+        }
+        m_basicstr_axiom_todo.reset();
+        m_basicstr_axiom_todo = new_m_basicstr;
+
+        m_trail_stack.pop_scope(num_scopes);
+        theory::pop_scope_eh(num_scopes);
+
+        //check_variable_scope();
+    }
+
+    void theory_str::dump_assignments() {
+        TRACE_CODE(
+            ast_manager & m = get_manager();
+            context & ctx = get_context();
+            tout << "dumping all assignments:" << std::endl;
+            expr_ref_vector assignments(m);
+            ctx.get_assignments(assignments);
+            for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) {
+                expr * ex = *i;
+                tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl;
+            }
+                   );
+    }
+
+    void theory_str::classify_ast_by_type(expr * node, std::map<expr*, int> & varMap,
+                                          std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap) {
+
+        // check whether the node is a string variable;
+        // testing set membership here bypasses several expensive checks.
+        // note that internal variables don't count if they're only length tester / value tester vars.
+        if (variable_set.find(node) != variable_set.end()
+            && internal_lenTest_vars.find(node) == internal_lenTest_vars.end()
+            && internal_valTest_vars.find(node) == internal_valTest_vars.end()
+            && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) {
+            if (varMap[node] != 1) {
+                TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;);
+            }
+            varMap[node] = 1;
+        }
+        // check whether the node is a function that we want to inspect
+        else if (is_app(node)) {
+            app * aNode = to_app(node);
+            if (u.str.is_length(aNode)) {
+                // Length
+                return;
+            } else if (u.str.is_concat(aNode)) {
+                expr * arg0 = aNode->get_arg(0);
+                expr * arg1 = aNode->get_arg(1);
+                bool arg0HasEq = false;
+                bool arg1HasEq = false;
+                expr * arg0Val = get_eqc_value(arg0, arg0HasEq);
+                expr * arg1Val = get_eqc_value(arg1, arg1HasEq);
+
+                int canskip = 0;
+                zstring tmp;
+                u.str.is_string(arg0Val, tmp);
+                if (arg0HasEq && tmp.empty()) {
+                    canskip = 1;
+                }
+                u.str.is_string(arg1Val, tmp);
+                if (canskip == 0 && arg1HasEq && tmp.empty()) {
+                    canskip = 1;
+                }
+                if (canskip == 0 && concatMap.find(node) == concatMap.end()) {
+                    concatMap[node] = 1;
+                }
+            } else if (u.re.is_unroll(aNode)) {
+                // Unroll
+                if (unrollMap.find(node) == unrollMap.end()) {
+                    unrollMap[node] = 1;
+                }
+            }
+            // recursively visit all arguments
+            for (unsigned i = 0; i < aNode->get_num_args(); ++i) {
+                expr * arg = aNode->get_arg(i);
+                classify_ast_by_type(arg, varMap, concatMap, unrollMap);
+            }
+        }
+    }
+
+    // NOTE: this function used to take an argument `Z3_ast node`;
+    // it was not used and so was removed from the signature
+    void theory_str::classify_ast_by_type_in_positive_context(std::map<expr*, int> & varMap,
+                                                              std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap) {
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        expr_ref_vector assignments(m);
+        ctx.get_assignments(assignments);
+
+        for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) {
+            expr * argAst = *it;
+            // the original code jumped through some hoops to check whether the AST node
+            // is a function, then checked whether that function is "interesting".
+            // however, the only thing that's considered "interesting" is an equality predicate.
+            // so we bypass a huge amount of work by doing the following...
+
+            if (m.is_eq(argAst)) {
+                TRACE("str", tout
+                      << "eq ast " << mk_pp(argAst, m) << " is between args of sort "
+                      << m.get_sort(to_app(argAst)->get_arg(0))->get_name()
+                      << std::endl;);
+                classify_ast_by_type(argAst, varMap, concatMap, unrollMap);
+            }
+        }
+    }
+
+    inline expr * theory_str::get_alias_index_ast(std::map<expr*, expr*> & aliasIndexMap, expr * node) {
+        if (aliasIndexMap.find(node) != aliasIndexMap.end())
+            return aliasIndexMap[node];
+        else
+            return node;
+    }
+
+    inline expr * theory_str::getMostLeftNodeInConcat(expr * node) {
+        app * aNode = to_app(node);
+        if (!u.str.is_concat(aNode)) {
+            return node;
+        } else {
+            expr * concatArgL = aNode->get_arg(0);
+            return getMostLeftNodeInConcat(concatArgL);
+        }
+    }
+
+    inline expr * theory_str::getMostRightNodeInConcat(expr * node) {
+        app * aNode = to_app(node);
+        if (!u.str.is_concat(aNode)) {
+            return node;
+        } else {
+            expr * concatArgR = aNode->get_arg(1);
+            return getMostRightNodeInConcat(concatArgR);
+        }
+    }
+
+    void theory_str::trace_ctx_dep(std::ofstream & tout,
+                                   std::map<expr*, expr*> & aliasIndexMap,
+                                   std::map<expr*, expr*> & var_eq_constStr_map,
+                                   std::map<expr*, std::map<expr*, int> > & var_eq_concat_map,
+                                   std::map<expr*, std::map<expr*, int> > & var_eq_unroll_map,
+                                   std::map<expr*, expr*> & concat_eq_constStr_map,
+                                   std::map<expr*, std::map<expr*, int> > & concat_eq_concat_map,
+                                   std::map<expr*, std::set<expr*> > & unrollGroupMap) {
+#ifdef _TRACE
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+        {
+            tout << "(0) alias: variables" << std::endl;
+            std::map<expr*, std::map<expr*, int> > aliasSumMap;
+            std::map<expr*, expr*>::iterator itor0 = aliasIndexMap.begin();
+            for (; itor0 != aliasIndexMap.end(); itor0++) {
+                aliasSumMap[itor0->second][itor0->first] = 1;
+            }
+            std::map<expr*, std::map<expr*, int> >::iterator keyItor = aliasSumMap.begin();
+            for (; keyItor != aliasSumMap.end(); keyItor++) {
+                tout << "    * ";
+                tout << mk_pp(keyItor->first, mgr);
+                tout << " : ";
+                std::map<expr*, int>::iterator innerItor = keyItor->second.begin();
+                for (; innerItor != keyItor->second.end(); innerItor++) {
+                    tout << mk_pp(innerItor->first, mgr);
+                    tout << ", ";
+                }
+                tout << std::endl;
+            }
+            tout << std::endl;
+        }
+
+        {
+            tout << "(1) var = constStr:" << std::endl;
+            std::map<expr*, expr*>::iterator itor1 = var_eq_constStr_map.begin();
+            for (; itor1 != var_eq_constStr_map.end(); itor1++) {
+                tout << "    * ";
+                tout << mk_pp(itor1->first, mgr);
+                tout << " = ";
+                tout << mk_pp(itor1->second, mgr);
+                if (!in_same_eqc(itor1->first, itor1->second)) {
+                    tout << "   (not true in ctx)";
+                }
+                tout << std::endl;
+            }
+            tout << std::endl;
+        }
+
+        {
+            tout << "(2) var = concat:" << std::endl;
+            std::map<expr*, std::map<expr*, int> >::iterator itor2 = var_eq_concat_map.begin();
+            for (; itor2 != var_eq_concat_map.end(); itor2++) {
+                tout << "    * ";
+                tout << mk_pp(itor2->first, mgr);
+                tout << " = { ";
+                std::map<expr*, int>::iterator i_itor = itor2->second.begin();
+                for (; i_itor != itor2->second.end(); i_itor++) {
+                    tout << mk_pp(i_itor->first, mgr);
+                    tout << ", ";
+                }
+                tout << std::endl;
+            }
+            tout << std::endl;
+        }
+
+        {
+            tout << "(3) var = unrollFunc:" << std::endl;
+            std::map<expr*, std::map<expr*, int> >::iterator itor2 = var_eq_unroll_map.begin();
+            for (; itor2 != var_eq_unroll_map.end(); itor2++) {
+                tout << "    * " << mk_pp(itor2->first, mgr) << " = { ";
+                std::map<expr*, int>::iterator i_itor = itor2->second.begin();
+                for (; i_itor != itor2->second.end(); i_itor++) {
+                    tout << mk_pp(i_itor->first, mgr) << ", ";
+                }
+                tout << " }" << std::endl;
+            }
+            tout << std::endl;
+        }
+
+        {
+            tout << "(4) concat = constStr:" << std::endl;
+            std::map<expr*, expr*>::iterator itor3 = concat_eq_constStr_map.begin();
+            for (; itor3 != concat_eq_constStr_map.end(); itor3++) {
+                tout << "    * ";
+                tout << mk_pp(itor3->first, mgr);
+                tout << " = ";
+                tout << mk_pp(itor3->second, mgr);
+                tout << std::endl;
+
+            }
+            tout << std::endl;
+        }
+
+        {
+            tout << "(5) eq concats:" << std::endl;
+            std::map<expr*, std::map<expr*, int> >::iterator itor4 = concat_eq_concat_map.begin();
+            for (; itor4 != concat_eq_concat_map.end(); itor4++) {
+                if (itor4->second.size() > 1) {
+                    std::map<expr*, int>::iterator i_itor = itor4->second.begin();
+                    tout << "    * ";
+                    for (; i_itor != itor4->second.end(); i_itor++) {
+                        tout << mk_pp(i_itor->first, mgr);
+                        tout << " , ";
+                    }
+                    tout << std::endl;
+                }
+            }
+            tout << std::endl;
+        }
+
+        {
+            tout << "(6) eq unrolls:" << std::endl;
+            std::map<expr*, std::set<expr*> >::iterator itor5 = unrollGroupMap.begin();
+            for (; itor5 != unrollGroupMap.end(); itor5++) {
+                tout << "    * ";
+                std::set<expr*>::iterator i_itor = itor5->second.begin();
+                for (; i_itor != itor5->second.end(); i_itor++) {
+                    tout << mk_pp(*i_itor, mgr) << ",  ";
+                }
+                tout << std::endl;
+            }
+            tout << std::endl;
+        }
+
+        {
+            tout << "(7) unroll = concats:" << std::endl;
+            std::map<expr*, std::set<expr*> >::iterator itor5 = unrollGroupMap.begin();
+            for (; itor5 != unrollGroupMap.end(); itor5++) {
+                tout << "    * ";
+                expr * unroll = itor5->first;
+                tout << mk_pp(unroll, mgr) << std::endl;
+                enode * e_curr = ctx.get_enode(unroll);
+                enode * e_curr_end = e_curr;
+                do {
+                    app * curr = e_curr->get_owner();
+                    if (u.str.is_concat(curr)) {
+                        tout << "      >>> " << mk_pp(curr, mgr) << std::endl;
+                    }
+                    e_curr = e_curr->get_next();
+                } while (e_curr != e_curr_end);
+                tout << std::endl;
+            }
+            tout << std::endl;
+        }
+#else
+        return;
+#endif // _TRACE
+    }
+
+
+    /*
+     * Dependence analysis from current context assignment
+     * - "freeVarMap" contains a set of variables that doesn't constrained by Concats.
+     *    But it's possible that it's bounded by unrolls
+     *    For the case of
+     *    (1) var1 = unroll(r1, t1)
+     *        var1 is in the freeVarMap
+     *        > should unroll r1 for var1
+     *    (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3)
+     *        var2, var3 are all in freeVar
+     *        > should split the unroll function so that var2 and var3 are bounded by new unrolls
+     */
+    int theory_str::ctx_dep_analysis(std::map<expr*, int> & strVarMap, std::map<expr*, int> & freeVarMap,
+                                     std::map<expr*, std::set<expr*> > & unrollGroupMap, std::map<expr*, std::map<expr*, int> > & var_eq_concat_map) {
+        std::map<expr*, int> concatMap;
+        std::map<expr*, int> unrollMap;
+        std::map<expr*, expr*> aliasIndexMap;
+        std::map<expr*, expr*> var_eq_constStr_map;
+        std::map<expr*, expr*> concat_eq_constStr_map;
+        std::map<expr*, std::map<expr*, int> > var_eq_unroll_map;
+        std::map<expr*, std::map<expr*, int> > concat_eq_concat_map;
+        std::map<expr*, std::map<expr*, int> > depMap;
+
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        // note that the old API concatenated these assignments into
+        // a massive conjunction; we may have the opportunity to avoid that here
+        expr_ref_vector assignments(m);
+        ctx.get_assignments(assignments);
+
+        // Step 1: get variables / concat AST appearing in the context
+        // the thing we iterate over should just be variable_set - internal_variable_set
+        // so we avoid computing the set difference (but this might be slower)
+        for(obj_hashtable<expr>::iterator it = variable_set.begin(); it != variable_set.end(); ++it) {
+            expr* var = *it;
+            if (internal_variable_set.find(var) == internal_variable_set.end()) {
+                TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;);
+                strVarMap[*it] = 1;
+            }
+        }
+        classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap);
+
+        std::map<expr*, expr*> aliasUnrollSet;
+        std::map<expr*, int>::iterator unrollItor = unrollMap.begin();
+        for (; unrollItor != unrollMap.end(); ++unrollItor) {
+            if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) {
+                continue;
+            }
+            expr * aRoot = NULL;
+            enode * e_currEqc = ctx.get_enode(unrollItor->first);
+            enode * e_curr = e_currEqc;
+            do {
+                app * curr = e_currEqc->get_owner();
+                if (u.re.is_unroll(curr)) {
+                    if (aRoot == NULL) {
+                        aRoot = curr;
+                    }
+                    aliasUnrollSet[curr] = aRoot;
+                }
+                e_currEqc = e_currEqc->get_next();
+            } while (e_currEqc != e_curr);
+        }
+
+        for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) {
+            expr * unrFunc = unrollItor->first;
+            expr * urKey = aliasUnrollSet[unrFunc];
+            unrollGroupMap[urKey].insert(unrFunc);
+        }
+
+        // Step 2: collect alias relation
+        // e.g. suppose we have the equivalence class {x, y, z};
+        // then we set aliasIndexMap[y] = x
+        // and aliasIndexMap[z] = x
+
+        std::map<expr*, int>::iterator varItor = strVarMap.begin();
+        for (; varItor != strVarMap.end(); ++varItor) {
+            if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) {
+                continue;
+            }
+            expr * aRoot = NULL;
+            expr * curr = varItor->first;
+            do {
+                if (variable_set.find(curr) != variable_set.end()) {
+                    if (aRoot == NULL) {
+                        aRoot = curr;
+                    } else {
+                        aliasIndexMap[curr] = aRoot;
+                    }
+                }
+                curr = get_eqc_next(curr);
+            } while (curr != varItor->first);
+        }
+
+        // Step 3: Collect interested cases
+
+        varItor = strVarMap.begin();
+        for (; varItor != strVarMap.end(); ++varItor) {
+            expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first);
+            // Case 1: variable = string constant
+            // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1"
+
+            if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) {
+                bool nodeHasEqcValue = false;
+                expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue);
+                if (nodeHasEqcValue) {
+                    var_eq_constStr_map[deAliasNode] = nodeValue;
+                }
+            }
+
+            // Case 2: var_eq_concat
+            // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1
+            // var_eq_unroll
+            // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1
+
+            if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) {
+                expr * curr = get_eqc_next(deAliasNode);
+                while (curr != deAliasNode) {
+                    app * aCurr = to_app(curr);
+                    // collect concat
+                    if (u.str.is_concat(aCurr)) {
+                        expr * arg0 = aCurr->get_arg(0);
+                        expr * arg1 = aCurr->get_arg(1);
+                        bool arg0HasEqcValue = false;
+                        bool arg1HasEqcValue = false;
+                        expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue);
+                        expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue);
+
+                        bool is_arg0_emptyStr = false;
+                        if (arg0HasEqcValue) {
+                            zstring strval;
+                            u.str.is_string(arg0_value, strval);
+                            if (strval.empty()) {
+                                is_arg0_emptyStr = true;
+                            }
+                        }
+
+                        bool is_arg1_emptyStr = false;
+                        if (arg1HasEqcValue) {
+                            zstring strval;
+                            u.str.is_string(arg1_value, strval);
+                            if (strval.empty()) {
+                                is_arg1_emptyStr = true;
+                            }
+                        }
+
+                        if (!is_arg0_emptyStr && !is_arg1_emptyStr) {
+                            var_eq_concat_map[deAliasNode][curr] = 1;
+                        }
+                    } else if (u.re.is_unroll(to_app(curr))) {
+                        var_eq_unroll_map[deAliasNode][curr] = 1;
+                    }
+
+                    curr = get_eqc_next(curr);
+                }
+            }
+
+        } // for(varItor in strVarMap)
+
+        // --------------------------------------------------
+        // * collect aliasing relation among eq concats
+        //   e.g EQC={concat1, concat2, concat3}
+        //       concats_eq_Index_map[concat2] = concat1
+        //       concats_eq_Index_map[concat3] = concat1
+        // --------------------------------------------------
+
+        std::map<expr*, expr*> concats_eq_index_map;
+        std::map<expr*, int>::iterator concatItor = concatMap.begin();
+        for(; concatItor != concatMap.end(); ++concatItor) {
+            if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) {
+                continue;
+            }
+            expr * aRoot = NULL;
+            expr * curr = concatItor->first;
+            do {
+                if (u.str.is_concat(to_app(curr))) {
+                    if (aRoot == NULL) {
+                        aRoot = curr;
+                    } else {
+                        concats_eq_index_map[curr] = aRoot;
+                    }
+                }
+                curr = get_eqc_next(curr);
+            } while (curr != concatItor->first);
+        }
+
+        concatItor = concatMap.begin();
+        for(; concatItor != concatMap.end(); ++concatItor) {
+            expr * deAliasConcat = NULL;
+            if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) {
+                deAliasConcat = concats_eq_index_map[concatItor->first];
+            } else {
+                deAliasConcat = concatItor->first;
+            }
+
+            // (3) concat_eq_conststr, e.g. concat(a,b) = "str1"
+            if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) {
+                bool nodeHasEqcValue = false;
+                expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue);
+                if (nodeHasEqcValue) {
+                    concat_eq_constStr_map[deAliasConcat] = nodeValue;
+                }
+            }
+
+            // (4) concat_eq_concat, e.g.
+            // concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f)
+            if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) {
+                expr * curr = deAliasConcat;
+                do {
+                    if (u.str.is_concat(to_app(curr))) {
+                        // curr cannot be reduced
+                        if (concatMap.find(curr) != concatMap.end()) {
+                            concat_eq_concat_map[deAliasConcat][curr] = 1;
+                        }
+                    }
+                    curr = get_eqc_next(curr);
+                } while (curr != deAliasConcat);
+            }
+        }
+
+        // print some debugging info
+        TRACE("str", trace_ctx_dep(tout, aliasIndexMap, var_eq_constStr_map,
+                                   var_eq_concat_map, var_eq_unroll_map,
+                                   concat_eq_constStr_map, concat_eq_concat_map, unrollGroupMap););
+
+        if (!contain_pair_bool_map.empty()) {
+            compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map);
+        }
+
+        // step 4: dependence analysis
+
+        // (1) var = string constant
+        for (std::map<expr*, expr*>::iterator itor = var_eq_constStr_map.begin();
+             itor != var_eq_constStr_map.end(); ++itor) {
+            expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
+            expr * strAst = itor->second;
+            depMap[var][strAst] = 1;
+        }
+
+        // (2) var = concat
+        for (std::map<expr*, std::map<expr*, int> >::iterator itor = var_eq_concat_map.begin();
+             itor != var_eq_concat_map.end(); ++itor) {
+            expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
+            for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) {
+                expr * concat = itor1->first;
+                std::map<expr*, int> inVarMap;
+                std::map<expr*, int> inConcatMap;
+                std::map<expr*, int> inUnrollMap;
+                classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap);
+                for (std::map<expr*, int>::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) {
+                    expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first);
+                    if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) {
+                        depMap[var][varInConcat] = 2;
+                    }
+                }
+            }
+        }
+
+        for (std::map<expr*, std::map<expr*, int> >::iterator itor = var_eq_unroll_map.begin();
+             itor != var_eq_unroll_map.end(); itor++) {
+            expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
+            for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
+                expr * unrollFunc = itor1->first;
+                std::map<expr*, int> inVarMap;
+                std::map<expr*, int> inConcatMap;
+                std::map<expr*, int> inUnrollMap;
+                classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap);
+                for (std::map<expr*, int>::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) {
+                    expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first);
+
+                    TRACE("str", tout << "var in unroll = " <<
+                          mk_ismt2_pp(itor2->first, m) << std::endl
+                          << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;);
+
+                    // it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0),
+                    // while $$_regVar_0 = "abcd"
+                    // have to exclude such cases
+                    bool varHasValue = false;
+                    get_eqc_value(varInFunc, varHasValue);
+                    if (varHasValue)
+                        continue;
+
+                    if (depMap[var].find(varInFunc) == depMap[var].end()) {
+                        depMap[var][varInFunc] = 6;
+                    }
+                }
+            }
+        }
+
+        // (3) concat = string constant
+        for (std::map<expr*, expr*>::iterator itor = concat_eq_constStr_map.begin();
+             itor != concat_eq_constStr_map.end(); itor++) {
+            expr * concatAst = itor->first;
+            expr * constStr = itor->second;
+            std::map<expr*, int> inVarMap;
+            std::map<expr*, int> inConcatMap;
+            std::map<expr*, int> inUnrollMap;
+            classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap);
+            for (std::map<expr*, int>::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) {
+                expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first);
+                if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1))
+                    depMap[varInConcat][constStr] = 3;
+            }
+        }
+
+        // (4) equivalent concats
+        //     - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6)
+        //         ==> v2, v5 are constrained by "str"
+        //     - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5)
+        //         ==> v2, v4 are constrained by "str"
+        //--------------------------------------------------------------
+
+        std::map<expr*, expr*> mostLeftNodes;
+        std::map<expr*, expr*> mostRightNodes;
+
+        std::map<expr*, int> mLIdxMap;
+        std::map<int, std::set<expr*> > mLMap;
+        std::map<expr*, int> mRIdxMap;
+        std::map<int, std::set<expr*> > mRMap;
+        std::set<expr*> nSet;
+
+        for (std::map<expr*, std::map<expr*, int> >::iterator itor = concat_eq_concat_map.begin();
+             itor != concat_eq_concat_map.end(); itor++) {
+            mostLeftNodes.clear();
+            mostRightNodes.clear();
+
+            expr * mLConst = NULL;
+            expr * mRConst = NULL;
+
+            for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
+                expr * concatNode = itor1->first;
+                expr * mLNode = getMostLeftNodeInConcat(concatNode);
+                zstring strval;
+                if (u.str.is_string(to_app(mLNode), strval)) {
+                    if (mLConst == NULL && strval.empty()) {
+                        mLConst = mLNode;
+                    }
+                } else {
+                    mostLeftNodes[mLNode] = concatNode;
+                }
+
+                expr * mRNode = getMostRightNodeInConcat(concatNode);
+                if (u.str.is_string(to_app(mRNode), strval)) {
+                    if (mRConst == NULL && strval.empty()) {
+                        mRConst = mRNode;
+                    }
+                } else {
+                    mostRightNodes[mRNode] = concatNode;
+                }
+            }
+
+            if (mLConst != NULL) {
+                // -------------------------------------------------------------------------------------
+                // The left most variable in a concat is constrained by a constant string in eqc concat
+                // -------------------------------------------------------------------------------------
+                // e.g. Concat(x, ...) = Concat("abc", ...)
+                // -------------------------------------------------------------------------------------
+                for (std::map<expr*, expr*>::iterator itor1 = mostLeftNodes.begin();
+                     itor1 != mostLeftNodes.end(); itor1++) {
+                    expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first);
+                    if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) {
+                        depMap[deVar][mLConst] = 4;
+                    }
+                }
+            }
+
+            {
+                // -------------------------------------------------------------------------------------
+                // The left most variables in eqc concats are constrained by each other
+                // -------------------------------------------------------------------------------------
+                // e.g. concat(x, ...) = concat(u, ...) = ...
+                //      x and u are constrained by each other
+                // -------------------------------------------------------------------------------------
+                nSet.clear();
+                std::map<expr*, expr*>::iterator itl = mostLeftNodes.begin();
+                for (; itl != mostLeftNodes.end(); itl++) {
+                    bool lfHasEqcValue = false;
+                    get_eqc_value(itl->first, lfHasEqcValue);
+                    if (lfHasEqcValue)
+                        continue;
+                    expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first);
+                    nSet.insert(deVar);
+                }
+
+                if (nSet.size() > 1) {
+                    int lId = -1;
+                    for (std::set<expr*>::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) {
+                        if (mLIdxMap.find(*itor2) != mLIdxMap.end()) {
+                            lId = mLIdxMap[*itor2];
                             break;
                         }
                     }
-                } // !indicatorHasEqcValue
-            } // for (i : [0..lenTesterCount-1])
-            if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") {
-                TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;);
-                expr_ref indicator(m);
-                unsigned int testNum = 0;
-
-                TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr
-                        << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";);
-
-                if (i == lenTesterCount) {
-                    fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1;
-                    testNum = fvar_len_count_map[freeVar];
-                    indicator = mk_internal_lenTest_var(freeVar, testNum);
-                    fvar_lenTester_map[freeVar].push_back(indicator);
-                    lenTester_fvar_map.insert(indicator, freeVar);
-                } else {
-                    indicator = fvar_lenTester_map[freeVar][i];
-                    refresh_theory_var(indicator);
-                    testNum = i + 1;
+                    if (lId == -1)
+                        lId = mLMap.size();
+                    for (std::set<expr*>::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) {
+                        bool itorHasEqcValue = false;
+                        get_eqc_value(*itor2, itorHasEqcValue);
+                        if (itorHasEqcValue)
+                            continue;
+                        mLIdxMap[*itor2] = lId;
+                        mLMap[lId].insert(*itor2);
+                    }
                 }
+            }
+
+            if (mRConst != NULL) {
+                for (std::map<expr*, expr*>::iterator itor1 = mostRightNodes.begin();
+                     itor1 != mostRightNodes.end(); itor1++) {
+                    expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first);
+                    if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) {
+                        depMap[deVar][mRConst] = 5;
+                    }
+                }
+            }
+
+            {
+                nSet.clear();
+                std::map<expr*, expr*>::iterator itr = mostRightNodes.begin();
+                for (; itr != mostRightNodes.end(); itr++) {
+                    expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first);
+                    nSet.insert(deVar);
+                }
+                if (nSet.size() > 1) {
+                    int rId = -1;
+                    std::set<expr*>::iterator itor2 = nSet.begin();
+                    for (; itor2 != nSet.end(); itor2++) {
+                        if (mRIdxMap.find(*itor2) != mRIdxMap.end()) {
+                            rId = mRIdxMap[*itor2];
+                            break;
+                        }
+                    }
+                    if (rId == -1)
+                        rId = mRMap.size();
+                    for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) {
+                        bool rHasEqcValue = false;
+                        get_eqc_value(*itor2, rHasEqcValue);
+                        if (rHasEqcValue)
+                            continue;
+                        mRIdxMap[*itor2] = rId;
+                        mRMap[rId].insert(*itor2);
+                    }
+                }
+            }
+        }
+
+        // print the dependence map
+        TRACE("str",
+              tout << "Dependence Map" << std::endl;
+              for(std::map<expr*, std::map<expr*, int> >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) {
+                  tout << mk_pp(itor->first, m);
+                  rational nnLen;
+                  bool nnLen_exists = get_len_value(itor->first, nnLen);
+                  tout << "  [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t";
+                  for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
+                      tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), ";
+                  }
+                  tout << std::endl;
+              }
+              );
+
+        // step, errr, 5: compute free variables based on the dependence map
+
+        // the case dependence map is empty, every var in VarMap is free
+        //---------------------------------------------------------------
+        // remove L/R most var in eq concat since they are constrained with each other
+        std::map<expr*, std::map<expr*, int> > lrConstrainedMap;
+        for (std::map<int, std::set<expr*> >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) {
+            for (std::set<expr*>::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) {
+                std::set<expr*>::iterator it2 = it1;
+                it2++;
+                for (; it2 != itor->second.end(); it2++) {
+                    expr * n1 = *it1;
+                    expr * n2 = *it2;
+                    lrConstrainedMap[n1][n2] = 1;
+                    lrConstrainedMap[n2][n1] = 1;
+                }
+            }
+        }
+        for (std::map<int, std::set<expr*> >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) {
+            for (std::set<expr*>::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) {
+                std::set<expr*>::iterator it2 = it1;
+                it2++;
+                for (; it2 != itor->second.end(); it2++) {
+                    expr * n1 = *it1;
+                    expr * n2 = *it2;
+                    lrConstrainedMap[n1][n2] = 1;
+                    lrConstrainedMap[n2][n1] = 1;
+                }
+            }
+        }
+
+        if (depMap.size() == 0) {
+            std::map<expr*, int>::iterator itor = strVarMap.begin();
+            for (; itor != strVarMap.end(); itor++) {
+                expr * var = get_alias_index_ast(aliasIndexMap, itor->first);
+                if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
+                    freeVarMap[var] = 1;
+                } else {
+                    int lrConstainted = 0;
+                    std::map<expr*, int>::iterator lrit = freeVarMap.begin();
+                    for (; lrit != freeVarMap.end(); lrit++) {
+                        if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
+                            lrConstainted = 1;
+                            break;
+                        }
+                    }
+                    if (lrConstainted == 0) {
+                        freeVarMap[var] = 1;
+                    }
+                }
+            }
+        } else {
+            // if the keys in aliasIndexMap are not contained in keys in depMap, they are free
+            // e.g.,  x= y /\ x = z /\ t = "abc"
+            //        aliasIndexMap[y]= x, aliasIndexMap[z] = x
+            //        depMap        t ~ "abc"(1)
+            //        x should be free
+            std::map<expr*, int>::iterator itor2 = strVarMap.begin();
+            for (; itor2 != strVarMap.end(); itor2++) {
+                if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) {
+                    expr * var = aliasIndexMap[itor2->first];
+                    if (depMap.find(var) == depMap.end()) {
+                        if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
+                            freeVarMap[var] = 1;
+                        } else {
+                            int lrConstainted = 0;
+                            std::map<expr*, int>::iterator lrit = freeVarMap.begin();
+                            for (; lrit != freeVarMap.end(); lrit++) {
+                                if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
+                                    lrConstainted = 1;
+                                    break;
+                                }
+                            }
+                            if (lrConstainted == 0) {
+                                freeVarMap[var] = 1;
+                            }
+                        }
+                    }
+                } else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) {
+                    // if a variable is not in aliasIndexMap and not in depMap, it's free
+                    if (depMap.find(itor2->first) == depMap.end()) {
+                        expr * var = itor2->first;
+                        if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
+                            freeVarMap[var] = 1;
+                        } else {
+                            int lrConstainted = 0;
+                            std::map<expr*, int>::iterator lrit = freeVarMap.begin();
+                            for (; lrit != freeVarMap.end(); lrit++) {
+                                if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
+                                    lrConstainted = 1;
+                                    break;
+                                }
+                            }
+                            if (lrConstainted == 0) {
+                                freeVarMap[var] = 1;
+                            }
+                        }
+                    }
+                }
+            }
+
+            std::map<expr*, std::map<expr*, int> >::iterator itor = depMap.begin();
+            for (; itor != depMap.end(); itor++) {
+                for (std::map<expr*, int>::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) {
+                    if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var
+                        expr * var = get_alias_index_ast(aliasIndexMap, itor1->first);
+                        // if a var is dep on itself and all dependence are type 2, it's a free variable
+                        // e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free
+                        {
+                            if (depMap.find(var) == depMap.end()) {
+                                if (freeVarMap.find(var) == freeVarMap.end()) {
+                                    if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) {
+                                        freeVarMap[var] = 1;
+                                    } else {
+                                        int lrConstainted = 0;
+                                        std::map<expr*, int>::iterator lrit = freeVarMap.begin();
+                                        for (; lrit != freeVarMap.end(); lrit++) {
+                                            if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) {
+                                                lrConstainted = 1;
+                                                break;
+                                            }
+                                        }
+                                        if (lrConstainted == 0) {
+                                            freeVarMap[var] = 1;
+                                        }
+                                    }
+
+                                } else {
+                                    freeVarMap[var] = freeVarMap[var] + 1;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return 0;
+    }
+
+    // Check agreement between integer and string theories for the term a = (str.to-int S).
+    // Returns true if axioms were added, and false otherwise.
+    bool theory_str::finalcheck_str2int(app * a) {
+        bool axiomAdd = false;
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr * S = a->get_arg(0);
+
+        // check integer theory
+        rational Ival;
+        bool Ival_exists = get_value(a, Ival);
+        if (Ival_exists) {
+            TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;);
+            // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival"
+            if (!Ival.is_minus_one()) {
+                zstring Ival_str(Ival.to_string().c_str());
+                expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m);
+                expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m);
+                expr_ref axiom(rewrite_implication(premise, conclusion), m);
+                if (!string_int_axioms.contains(axiom)) {
+                    string_int_axioms.insert(axiom);
+                    assert_axiom(axiom);
+                    m_trail_stack.push(insert_obj_trail<theory_str, expr>(string_int_axioms, axiom));
+                    axiomAdd = true;
+                }
+            }
+        } else {
+            TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;);
+            NOT_IMPLEMENTED_YET();
+        }
+
+        return axiomAdd;
+    }
+
+    bool theory_str::finalcheck_int2str(app * a) {
+        bool axiomAdd = false;
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr * N = a->get_arg(0);
+
+        // check string theory
+        bool Sval_expr_exists;
+        expr * Sval_expr = get_eqc_value(a, Sval_expr_exists);
+        if (Sval_expr_exists) {
+            zstring Sval;
+            u.str.is_string(Sval_expr, Sval);
+            TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";);
+            // empty string --> integer value < 0
+            if (Sval.empty()) {
+                // ignore this. we should already assert the axiom for what happens when the string is ""
+            } else {
+                // nonempty string --> convert to correct integer value, or disallow it
+                rational convertedRepresentation(0);
+                rational ten(10);
+                bool conversionOK = true;
+                for (unsigned i = 0; i < Sval.length(); ++i) {
+                    char digit = (int)Sval[i];
+                    if (isdigit((int)digit)) {
+                        std::string sDigit(1, digit);
+                        int val = atoi(sDigit.c_str());
+                        convertedRepresentation = (ten * convertedRepresentation) + rational(val);
+                    } else {
+                        // not a digit, invalid
+                        TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;);
+                        conversionOK = false;
+                        break;
+                    }
+                }
+                if (conversionOK) {
+                    expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m);
+                    expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m);
+                    expr_ref axiom(rewrite_implication(premise, conclusion), m);
+                    if (!string_int_axioms.contains(axiom)) {
+                        string_int_axioms.insert(axiom);
+                        assert_axiom(axiom);
+                        m_trail_stack.push(insert_obj_trail<theory_str, expr>(string_int_axioms, axiom));
+                        axiomAdd = true;
+                    }
+                } else {
+                    expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m);
+                    // always assert this axiom because this is a conflict clause
+                    assert_axiom(axiom);
+                    axiomAdd = true;
+                }
+            }
+        } else {
+            TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;);
+            NOT_IMPLEMENTED_YET();
+        }
+        return axiomAdd;
+    }
+
+    void theory_str::collect_var_concat(expr * node, std::set<expr*> & varSet, std::set<expr*> & concatSet) {
+        if (variable_set.find(node) != variable_set.end()) {
+            if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) {
+                varSet.insert(node);
+            }
+        }
+        else if (is_app(node)) {
+            app * aNode = to_app(node);
+            if (u.str.is_length(aNode)) {
+                // Length
+                return;
+            }
+            if (u.str.is_concat(aNode)) {
+                expr * arg0 = aNode->get_arg(0);
+                expr * arg1 = aNode->get_arg(1);
+                if (concatSet.find(node) == concatSet.end()) {
+                    concatSet.insert(node);
+                }
+            }
+            // recursively visit all arguments
+            for (unsigned i = 0; i < aNode->get_num_args(); ++i) {
+                expr * arg = aNode->get_arg(i);
+                collect_var_concat(arg, varSet, concatSet);
+            }
+        }
+    }
+
+    bool theory_str::propagate_length_within_eqc(expr * var) {
+        bool res = false;
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;);
+
+        enode * n_eq_enode = ctx.get_enode(var);
+        rational varLen;
+        if (! get_len_value(var, varLen)) {
+            bool hasLen = false;
+            expr * nodeWithLen= var;
+            do {
+                if (get_len_value(nodeWithLen, varLen)) {
+                    hasLen = true;
+                    break;
+                }
+                nodeWithLen = get_eqc_next(nodeWithLen);
+            } while (nodeWithLen != var);
+
+            if (hasLen) {
+                // var = nodeWithLen --> |var| = |nodeWithLen|
+                expr_ref_vector l_items(m);
+                expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m);
+                l_items.push_back(varEqNode);
+
+                expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m);
+                expr_ref varLenExpr (mk_int(varLen), m);
+                expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m);
+                l_items.push_back(lenEqNum);
+
+                expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
+                expr_ref varLen(mk_strlen(var), m);
+                expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m);
+                assert_implication(axl, axr);
+                TRACE("str", tout <<  mk_ismt2_pp(axl, m) << std::endl << "  --->  " << std::endl <<  mk_ismt2_pp(axr, m););
+                res = true;
+            }
+        }
+        return res;
+    }
+
+    bool theory_str::propagate_length(std::set<expr*> & varSet, std::set<expr*> & concatSet, std::map<expr*, int> & exprLenMap) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        expr_ref_vector assignments(m);
+        ctx.get_assignments(assignments);
+        bool axiomAdded = false;
+        // collect all concats in context
+        for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) {
+            if (! ctx.is_relevant(*it)) {
+                continue;
+            }
+            if (m.is_eq(*it)) {
+                collect_var_concat(*it, varSet, concatSet);
+            }
+        }
+        // iterate each concat
+        // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved
+        for (std::set<expr*>::iterator it = concatSet.begin(); it != concatSet.end(); it++) {
+            expr * concat = *it;
+            rational lenValue;
+            expr_ref concatlenExpr (mk_strlen(concat), m) ;
+            bool allLeafResolved = true;
+            if (! get_value(concatlenExpr, lenValue)) {
+                // the length fo concat is unresolved yet
+                if (get_len_value(concat, lenValue)) {
+                    // but all leaf nodes have length information
+                    TRACE("str", tout << "* length pop-up: " <<  mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;);
+                    std::set<expr*> leafNodes;
+                    get_unique_non_concat_nodes(concat, leafNodes);
+                    expr_ref_vector l_items(m);
+                    for (std::set<expr*>::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) {
+                        rational leafLenValue;
+                        if (get_len_value(*leafIt, leafLenValue)) {
+                            expr_ref leafItLenExpr (mk_strlen(*leafIt), m);
+                            expr_ref leafLenValueExpr (mk_int(leafLenValue), m);
+                            expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m);
+                            l_items.push_back(lcExpr);
+                        } else {
+                            allLeafResolved = false;
+                            break;
+                        }
+                    }
+                    if (allLeafResolved) {
+                        expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m);
+                        expr_ref lenValueExpr (mk_int(lenValue), m);
+                        expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m);
+                        assert_implication(axl, axr);
+                        TRACE("str", tout <<  mk_ismt2_pp(axl, m) << std::endl << "  --->  " << std::endl <<  mk_ismt2_pp(axr, m)<< std::endl;);
+                        axiomAdded = true;
+                    }
+                }
+            }
+        }
+        // if no concat length is propagated, check the length of variables.
+        if (! axiomAdded) {
+            for (std::set<expr*>::iterator it = varSet.begin(); it != varSet.end(); it++) {
+                expr * var = *it;
+                rational lenValue;
+                expr_ref varlen (mk_strlen(var), m) ;
+                bool allLeafResolved = true;
+                if (! get_value(varlen, lenValue)) {
+                    if (propagate_length_within_eqc(var)) {
+                        axiomAdded = true;
+                    }
+                }
+            }
+
+        }
+        return axiomAdded;
+    }
+
+    void theory_str::get_unique_non_concat_nodes(expr * node, std::set<expr*> & argSet) {
+        app * a_node = to_app(node);
+        if (!u.str.is_concat(a_node)) {
+            argSet.insert(node);
+            return;
+        } else {
+            SASSERT(a_node->get_num_args() == 2);
+            expr * leftArg = a_node->get_arg(0);
+            expr * rightArg = a_node->get_arg(1);
+            get_unique_non_concat_nodes(leftArg, argSet);
+            get_unique_non_concat_nodes(rightArg, argSet);
+        }
+    }
+
+    final_check_status theory_str::final_check_eh() {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+
+        expr_ref_vector assignments(m);
+        ctx.get_assignments(assignments);
+
+        if (opt_VerifyFinalCheckProgress) {
+            finalCheckProgressIndicator = false;
+        }
+
+        TRACE("str", tout << "final check" << std::endl;);
+        TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); });
+        check_variable_scope();
+
+        if (opt_DeferEQCConsistencyCheck) {
+            TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;);
+            std::set<enode*> eqc_roots;
+            for (ptr_vector<enode>::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) {
+                enode * e = *it;
+                enode * root = e->get_root();
+                eqc_roots.insert(root);
+            }
+
+            bool found_inconsistency = false;
+
+            for (std::set<enode*>::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) {
+                enode * e = *it;
+                app * a = e->get_owner();
+                if (!(m.get_sort(a) == u.str.mk_string_sort())) {
+                    TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;);
+                } else {
+                    TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;);
+                    // first call check_concat_len_in_eqc() on each member of the eqc
+                    enode * e_it = e;
+                    enode * e_root = e_it;
+                    do {
+                        bool status = check_concat_len_in_eqc(e_it->get_owner());
+                        if (!status) {
+                            TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;);
+                            found_inconsistency = true;
+                        }
+                        e_it = e_it->get_next();
+                    } while (e_it != e_root);
+
+                    // now grab any two distinct elements from the EQC and call new_eq_check() on them
+                    enode * e1 = e;
+                    enode * e2 = e1->get_next();
+                    if (e1 != e2) {
+                        TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;);
+                        bool result = new_eq_check(e1->get_owner(), e2->get_owner());
+                        if (!result) {
+                            TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;);
+                            found_inconsistency = true;
+                        }
+                    }
+                }
+            }
+
+            if (found_inconsistency) {
+                TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;);
+                return FC_CONTINUE;
+            } else {
+                TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;);
+            }
+        }
+
+        // run dependence analysis to find free string variables
+        std::map<expr*, int> varAppearInAssign;
+        std::map<expr*, int> freeVar_map;
+        std::map<expr*, std::set<expr*> > unrollGroup_map;
+        std::map<expr*, std::map<expr*, int> > var_eq_concat_map;
+        int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map);
+        if (conflictInDep == -1) {
+            // return Z3_TRUE;
+            return FC_DONE;
+        }
+
+        // enhancement: improved backpropagation of string constants into var=concat terms
+        bool backpropagation_occurred = false;
+        for (std::map<expr*, std::map<expr*, int> >::iterator veqc_map_it = var_eq_concat_map.begin();
+             veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) {
+            expr * var = veqc_map_it->first;
+            for (std::map<expr*, int>::iterator concat_map_it = veqc_map_it->second.begin();
+                 concat_map_it != veqc_map_it->second.end(); ++concat_map_it) {
+                app * concat = to_app(concat_map_it->first);
+                expr * concat_lhs = concat->get_arg(0);
+                expr * concat_rhs = concat->get_arg(1);
+                // If the concat LHS and RHS both have a string constant in their EQC,
+                // but the var does not, then we assert an axiom of the form
+                // (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs"
+                bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc;
+                expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc);
+                expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc);
+                expr * var_str = get_eqc_value(var, var_haseqc);
+                if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) {
+                    TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl
+                          << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;);
+                    zstring lhsString, rhsString;
+                    u.str.is_string(concat_lhs_str, lhsString);
+                    u.str.is_string(concat_rhs_str, rhsString);
+                    zstring concatString = lhsString + rhsString;
+                    expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m);
+                    expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m);
+                    expr_ref lhs(m.mk_and(lhs1, lhs2), m);
+                    expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m);
+                    assert_implication(lhs, rhs);
+                    backpropagation_occurred = true;
+                }
+            }
+        }
+
+        if (backpropagation_occurred) {
+            TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;);
+            return FC_CONTINUE;
+        }
+
+        // enhancement: improved backpropagation of length information
+        {
+            std::set<expr*> varSet;
+            std::set<expr*> concatSet;
+            std::map<expr*, int> exprLenMap;
+
+            bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap);
+            if (length_propagation_occurred) {
+                TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;);
+                return FC_CONTINUE;
+            }
+        }
+
+        bool needToAssignFreeVars = false;
+        std::set<expr*> free_variables;
+        std::set<expr*> unused_internal_variables;
+        { // Z3str2 free variables check
+            std::map<expr*, int>::iterator itor = varAppearInAssign.begin();
+            for (; itor != varAppearInAssign.end(); ++itor) {
+                /*
+                  std::string vName = std::string(Z3_ast_to_string(ctx, itor->first));
+                  if (vName.length() >= 3 && vName.substr(0, 3) == "$$_")
+                  continue;
+                */
+                if (internal_variable_set.find(itor->first) != internal_variable_set.end()
+                    || regex_variable_set.find(itor->first) != regex_variable_set.end()) {
+                    // this can be ignored, I think
+                    TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;);
+                    continue;
+                }
+                bool hasEqcValue = false;
+                expr * eqcString = get_eqc_value(itor->first, hasEqcValue);
+                if (!hasEqcValue) {
+                    TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;);
+                    needToAssignFreeVars = true;
+                    free_variables.insert(itor->first);
+                    // break;
+                } else {
+                    // debug
+                    TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;);
+                }
+            }
+        }
+
+        if (!needToAssignFreeVars) {
+
+            // check string-int terms
+            bool addedStrIntAxioms = false;
+            for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) {
+                app * ex = to_app(string_int_conversion_terms[i].get());
+                if (u.str.is_stoi(ex)) {
+                    bool axiomAdd = finalcheck_str2int(ex);
+                    if (axiomAdd) {
+                        addedStrIntAxioms = true;
+                    }
+                } else if (u.str.is_itos(ex)) {
+                    bool axiomAdd = finalcheck_int2str(ex);
+                    if (axiomAdd) {
+                        addedStrIntAxioms = true;
+                    }
+                } else {
+                    UNREACHABLE();
+                }
+            }
+            if (addedStrIntAxioms) {
+                TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;);
+                return FC_CONTINUE;
+            }
+
+            if (unused_internal_variables.empty()) {
+                TRACE("str", tout << "All variables are assigned. Done!" << std::endl;);
+                return FC_DONE;
+            } else {
+                TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;);
+                for (std::set<expr*>::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) {
+                    expr * var = *it;
+                    expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m);
+                    assert_axiom(assignment);
+                }
+                return FC_CONTINUE;
+            }
+        }
+
+        CTRACE("str", needToAssignFreeVars,
+               tout << "Need to assign values to the following free variables:" << std::endl;
+               for (std::set<expr*>::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) {
+                   tout << mk_ismt2_pp(*itx, m) << std::endl;
+               }
+               tout << "freeVar_map has the following entries:" << std::endl;
+               for (std::map<expr*, int>::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) {
+                   expr * var = fvIt->first;
+                   tout << mk_ismt2_pp(var, m) << std::endl;
+               }
+               );
+
+        // -----------------------------------------------------------
+        // variables in freeVar are those not bounded by Concats
+        // classify variables in freeVarMap:
+        // (1) freeVar = unroll(r1, t1)
+        // (2) vars are not bounded by either concat or unroll
+        // -----------------------------------------------------------
+        std::map<expr*, std::set<expr*> > fv_unrolls_map;
+        std::set<expr*> tmpSet;
+        expr * constValue = NULL;
+        for (std::map<expr*, int>::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) {
+            expr * var = fvIt2->first;
+            tmpSet.clear();
+            get_eqc_allUnroll(var, constValue, tmpSet);
+            if (tmpSet.size() > 0) {
+                fv_unrolls_map[var] = tmpSet;
+            }
+        }
+        // erase var bounded by an unroll function from freeVar_map
+        for (std::map<expr*, std::set<expr*> >::iterator fvIt3 = fv_unrolls_map.begin();
+             fvIt3 != fv_unrolls_map.end(); fvIt3++) {
+            expr * var = fvIt3->first;
+            TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;);
+            freeVar_map.erase(var);
+        }
+
+        // collect the case:
+        //   * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2)
+        //     concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)}
+
+        std::map<expr*, std::set<expr*> > concatEqUnrollsMap;
+        for (std::map<expr*, std::set<expr*> >::iterator urItor = unrollGroup_map.begin();
+             urItor != unrollGroup_map.end(); urItor++) {
+            expr * unroll = urItor->first;
+            expr * curr = unroll;
+            do {
+                if (u.str.is_concat(to_app(curr))) {
+                    concatEqUnrollsMap[curr].insert(unroll);
+                    concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end());
+                }
+                enode * e_curr = ctx.get_enode(curr);
+                curr = e_curr->get_next()->get_owner();
+                // curr = get_eqc_next(curr);
+            } while (curr != unroll);
+        }
+
+        std::map<expr*, std::set<expr*> > concatFreeArgsEqUnrollsMap;
+        std::set<expr*> fvUnrollSet;
+        for (std::map<expr*, std::set<expr*> >::iterator concatItor = concatEqUnrollsMap.begin();
+             concatItor != concatEqUnrollsMap.end(); concatItor++) {
+            expr * concat = concatItor->first;
+            expr * concatArg1 = to_app(concat)->get_arg(0);
+            expr * concatArg2 = to_app(concat)->get_arg(1);
+            bool arg1Bounded = false;
+            bool arg2Bounded = false;
+            // arg1
+            if (variable_set.find(concatArg1) != variable_set.end()) {
+                if (freeVar_map.find(concatArg1) == freeVar_map.end()) {
+                    arg1Bounded = true;
+                } else {
+                    fvUnrollSet.insert(concatArg1);
+                }
+            } else if (u.str.is_concat(to_app(concatArg1))) {
+                if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) {
+                    arg1Bounded = true;
+                }
+            }
+            // arg2
+            if (variable_set.find(concatArg2) != variable_set.end()) {
+                if (freeVar_map.find(concatArg2) == freeVar_map.end()) {
+                    arg2Bounded = true;
+                } else {
+                    fvUnrollSet.insert(concatArg2);
+                }
+            } else if (u.str.is_concat(to_app(concatArg2))) {
+                if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) {
+                    arg2Bounded = true;
+                }
+            }
+            if (!arg1Bounded && !arg2Bounded) {
+                concatFreeArgsEqUnrollsMap[concat].insert(
+                    concatEqUnrollsMap[concat].begin(),
+                    concatEqUnrollsMap[concat].end());
+            }
+        }
+        for (std::set<expr*>::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) {
+            TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;);
+            freeVar_map.erase(*vItor);
+        }
+
+        // Assign free variables
+        std::set<expr*> fSimpUnroll;
+
+        constValue = NULL;
+
+        {
+            TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl;
+                  for (std::map<expr*, int>::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) {
+                      expr * freeVar = freeVarItor1->first;
+                      rational lenValue;
+                      bool lenValue_exists = get_len_value(freeVar, lenValue);
+                      tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = "
+                           << (lenValue_exists ? lenValue.to_string() : "?")
+                           << "]" << std::endl;
+                  }
+                  );
+        }
+
+        for (std::map<expr*, std::set<expr*> >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin();
+             fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) {
+            expr * concat = fvIt2->first;
+            for (std::set<expr*>::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) {
+                expr * unroll = *urItor;
+                process_concat_eq_unroll(concat, unroll);
+            }
+        }
+
+        // --------
+        // experimental free variable assignment - begin
+        //   * special handling for variables that are not used in concat
+        // --------
+        bool testAssign = true;
+        if (!testAssign) {
+            for (std::map<expr*, int>::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) {
+                expr * freeVar = fvIt->first;
+                /*
+                  std::string vName = std::string(Z3_ast_to_string(ctx, freeVar));
+                  if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") {
+                  continue;
+                  }
+                */
+                expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, "");
+                if (toAssert != NULL) {
+                    assert_axiom(toAssert);
+                }
+            }
+        } else {
+            process_free_var(freeVar_map);
+        }
+        // experimental free variable assignment - end
+
+        // now deal with removed free variables that are bounded by an unroll
+        TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;);
+        for (std::map<expr*, std::set<expr*> >::iterator fvIt1 = fv_unrolls_map.begin();
+             fvIt1 != fv_unrolls_map.end(); fvIt1++) {
+            expr * var = fvIt1->first;
+            fSimpUnroll.clear();
+            get_eqc_simpleUnroll(var, constValue, fSimpUnroll);
+            if (fSimpUnroll.size() == 0) {
+                gen_assign_unroll_reg(fv_unrolls_map[var]);
+            } else {
+                expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll);
+                if (toAssert != NULL) {
+                    assert_axiom(toAssert);
+                }
+            }
+        }
+
+        if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) {
+            TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;);
+            m.raise_exception("no progress in theory_str final check");
+        }
+
+        return FC_CONTINUE; // since by this point we've added axioms
+    }
+
+    inline zstring int_to_string(int i) {
+        std::stringstream ss;
+        ss << i;
+        std::string str = ss.str();
+        return zstring(str.c_str());
+    }
+
+    inline std::string longlong_to_string(long long i) {
+        std::stringstream ss;
+        ss << i;
+        return ss.str();
+    }
+
+    void theory_str::print_value_tester_list(svector<std::pair<int, expr*> > & testerList) {
+        ast_manager & m = get_manager();
+        TRACE("str",
+              int ss = testerList.size();
+              tout << "valueTesterList = {";
+              for (int i = 0; i < ss; ++i) {
+                  if (i % 4 == 0) {
+                      tout << std::endl;
+                  }
+                  tout << "(" << testerList[i].first << ", ";
+                  tout << mk_ismt2_pp(testerList[i].second, m);
+                  tout << "), ";
+              }
+              tout << std::endl << "}" << std::endl;
+              );
+    }
+
+    zstring theory_str::gen_val_string(int len, int_vector & encoding) {
+        SASSERT(charSetSize > 0);
+        SASSERT(char_set != NULL);
+
+        std::string re(len, char_set[0]);
+        for (int i = 0; i < (int) encoding.size() - 1; i++) {
+            int idx = encoding[i];
+            re[len - 1 - i] = char_set[idx];
+        }
+        return zstring(re.c_str());
+    }
+
+    /*
+     * The return value indicates whether we covered the search space.
+     *   - If the next encoding is valid, return false
+     *   - Otherwise, return true
+     */
+    bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) {
+        SASSERT(charSetSize > 0);
+
+        TRACE("str", tout << "base vector: [ ";
+              for (unsigned i = 0; i < base.size(); ++i) {
+                  tout << base[i] << " ";
+              }
+              tout << "]" << std::endl;
+              );
+
+        int s = 0;
+        int carry = 0;
+        next.reset();
+
+        for (int i = 0; i < (int) base.size(); i++) {
+            if (i == 0) {
+                s = base[i] + 1;
+                carry = s / charSetSize;
+                s = s % charSetSize;
+                next.push_back(s);
+            } else {
+                s = base[i] + carry;
+                carry = s / charSetSize;
+                s = s % charSetSize;
+                next.push_back(s);
+            }
+        }
+        if (next[next.size() - 1] > 0) {
+            next.reset();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator,
+                                       zstring lenStr, int tries) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        int distance = 32;
+
+        // ----------------------------------------------------------------------------------------
+        // generate value options encoding
+        // encoding is a vector of size (len + 1)
+        // e.g, len = 2,
+        //      encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]"
+        //      the last item in the encoding indicates whether the whole space is covered
+        //      for example, if the charSet = {a, b}. All valid encodings are
+        //        {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}
+        //      if add 1 to the last one, we get
+        //        {0, 0, 1}
+        //      the last item "1" shows this is not a valid encoding, and we have covered all space
+        // ----------------------------------------------------------------------------------------
+        int len = atoi(lenStr.encode().c_str());
+        bool coverAll = false;
+        svector<int_vector> options;
+        int_vector base;
+
+        TRACE("str", tout
+              << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl
+              << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl
+              << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl
+              << "lenstr = " << lenStr << "\n"
+              << "tries = " << tries << "\n";
+              if (m_params.m_AggressiveValueTesting) {
+                  tout << "note: aggressive value testing is enabled" << std::endl;
+              }
+              );
+
+        if (tries == 0) {
+            base = int_vector(len + 1, 0);
+            coverAll = false;
+        } else {
+            expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second;
+            TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;);
+            coverAll = get_next_val_encode(val_range_map[lastestValIndi], base);
+        }
+
+        long long l = (tries) * distance;
+        long long h = l;
+        for (int i = 0; i < distance; i++) {
+            if (coverAll)
+                break;
+            options.push_back(base);
+            h++;
+            coverAll = get_next_val_encode(options[options.size() - 1], base);
+        }
+        val_range_map[val_indicator] = options[options.size() - 1];
+
+        TRACE("str",
+              tout << "value tester encoding " << "{" << std::endl;
+              int_vector vec = val_range_map[val_indicator];
+
+              for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) {
+                  tout << *it << std::endl;
+              }
+              tout << "}" << std::endl;
+              );
+
+        // ----------------------------------------------------------------------------------------
+
+        ptr_vector<expr> orList;
+        ptr_vector<expr> andList;
+
+        for (long long i = l; i < h; i++) {
+            orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) ));
+            if (m_params.m_AggressiveValueTesting) {
+                literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false);
+                ctx.mark_as_relevant(l);
+                ctx.force_phase(l);
+            }
+
+            zstring aStr = gen_val_string(len, options[i - l]);
+            expr * strAst;
+            if (m_params.m_UseFastValueTesterCache) {
+                if (!valueTesterCache.find(aStr, strAst)) {
+                    strAst = mk_string(aStr);
+                    valueTesterCache.insert(aStr, strAst);
+                    m_trail.push_back(strAst);
+                }
+            } else {
+                strAst = mk_string(aStr);
+            }
+            andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst)));
+        }
+        if (!coverAll) {
+            orList.push_back(m.mk_eq(val_indicator, mk_string("more")));
+            if (m_params.m_AggressiveValueTesting) {
+                literal l = mk_eq(val_indicator, mk_string("more"), false);
+                ctx.mark_as_relevant(l);
+                ctx.force_phase(~l);
+            }
+        }
+
+        expr ** or_items = alloc_svect(expr*, orList.size());
+        expr ** and_items = alloc_svect(expr*, andList.size() + 1);
+
+        for (int i = 0; i < (int) orList.size(); i++) {
+            or_items[i] = orList[i];
+        }
+        if (orList.size() > 1)
+            and_items[0] = m.mk_or(orList.size(), or_items);
+        else
+            and_items[0] = or_items[0];
+
+        for (int i = 0; i < (int) andList.size(); i++) {
+            and_items[i + 1] = andList[i];
+        }
+        expr * valTestAssert = m.mk_and(andList.size() + 1, and_items);
+
+        // ---------------------------------------
+        // If the new value tester is $$_val_x_16_i
+        // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more")
+        // ---------------------------------------
+        andList.reset();
+        andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr)));
+        for (int i = 0; i < tries; i++) {
+            expr * vTester = fvar_valueTester_map[freeVar][len][i].second;
+            if (vTester != val_indicator)
+                andList.push_back(m.mk_eq(vTester, mk_string("more")));
+        }
+        expr * assertL = NULL;
+        if (andList.size() == 1) {
+            assertL = andList[0];
+        } else {
+            expr ** and_items = alloc_svect(expr*, andList.size());
+            for (int i = 0; i < (int) andList.size(); i++) {
+                and_items[i] = andList[i];
+            }
+            assertL = m.mk_and(andList.size(), and_items);
+        }
+
+        // (assertL => valTestAssert) <=> (!assertL OR valTestAssert)
+        valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert);
+        return valTestAssert;
+    }
+
+    expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator,
+                                            zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) {
+        ast_manager & m = get_manager();
+
+        int len = atoi(len_valueStr.encode().c_str());
+
+        // check whether any value tester is actually in scope
+        TRACE("str", tout << "checking scope of previous value testers" << std::endl;);
+        bool map_effectively_empty = true;
+        if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) {
+            // there's *something* in the map, but check its scope
+            svector<std::pair<int, expr*> > entries = fvar_valueTester_map[freeVar][len];
+            for (svector<std::pair<int,expr*> >::iterator it = entries.begin(); it != entries.end(); ++it) {
+                std::pair<int,expr*> entry = *it;
+                expr * aTester = entry.second;
+                if (internal_variable_set.find(aTester) == internal_variable_set.end()) {
+                    TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;);
+                } else {
+                    TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;);
+                    map_effectively_empty = false;
+                    break;
+                }
+            }
+        }
+
+        if (map_effectively_empty) {
+            TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;);
+            int tries = 0;
+            expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries);
+            valueTester_fvar_map[val_indicator] = freeVar;
+            fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator));
+            print_value_tester_list(fvar_valueTester_map[freeVar][len]);
+            return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries);
+        } else {
+            TRACE("str", tout << "checking previous value testers" << std::endl;);
+            print_value_tester_list(fvar_valueTester_map[freeVar][len]);
+
+            // go through all previous value testers
+            // If some doesn't have an eqc value, add its assertion again.
+            int testerTotal = fvar_valueTester_map[freeVar][len].size();
+            int i = 0;
+            for (; i < testerTotal; i++) {
+                expr * aTester = fvar_valueTester_map[freeVar][len][i].second;
+
+                // it's probably worth checking scope here, actually
+                if (internal_variable_set.find(aTester) == internal_variable_set.end()) {
+                    TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;);
+                    continue;
+                }
+
+                if (aTester == valTesterInCbEq) {
+                    break;
+                }
+
+                bool anEqcHasValue = false;
+                // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue);
+                expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue);
+                if (!anEqcHasValue) {
+                    TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m)
+                          << " doesn't have an equivalence class value." << std::endl;);
+                    refresh_theory_var(aTester);
+
+                    expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i);
+
+                    TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl
+                          << mk_ismt2_pp(makeupAssert, m) << std::endl;);
+                    assert_axiom(makeupAssert);
+                } else {
+                    TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m)
+                          << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;);
+                }
+            }
+
+            if (valTesterValueStr == "more") {
+                expr * valTester = NULL;
+                if (i + 1 < testerTotal) {
+                    valTester = fvar_valueTester_map[freeVar][len][i + 1].second;
+                    refresh_theory_var(valTester);
+                } else {
+                    valTester = mk_internal_valTest_var(freeVar, len, i + 1);
+                    valueTester_fvar_map[valTester] = freeVar;
+                    fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester));
+                    print_value_tester_list(fvar_valueTester_map[freeVar][len]);
+                }
+                expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1);
+                return nextAssert;
+            }
+
+            return NULL;
+        }
+    }
+
+    void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;);
+
+        app * regexFuncDecl = to_app(regex);
+        if (u.re.is_to_re(regexFuncDecl)) {
+            // ---------------------------------------------------------
+            // var \in Str2Reg(s1)
+            //   ==>
+            // var = s1 /\ length(var) = length(s1)
+            // ---------------------------------------------------------
+            expr * strInside = to_app(regex)->get_arg(0);
+            items.push_back(ctx.mk_eq_atom(var, strInside));
+            items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside)));
+            return;
+        }
+        // RegexUnion
+        else if (u.re.is_union(regexFuncDecl)) {
+            // ---------------------------------------------------------
+            // var \in RegexUnion(r1, r2)
+            //   ==>
+            // (var = newVar1 \/ var = newVar2)
+            // (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2))
+            //  /\ (newVar1 \in r1) /\  (newVar2 \in r2)
+            // ---------------------------------------------------------
+            expr_ref newVar1(mk_regex_rep_var(), mgr);
+            expr_ref newVar2(mk_regex_rep_var(), mgr);
+            items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2)));
+            items.push_back(mgr.mk_or(
+                                mgr.mk_not(ctx.mk_eq_atom(var, newVar1)),
+                                ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1))));
+            items.push_back(mgr.mk_or(
+                                mgr.mk_not(ctx.mk_eq_atom(var, newVar2)),
+                                ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2))));
+
+            expr * regArg1 = to_app(regex)->get_arg(0);
+            reduce_virtual_regex_in(newVar1, regArg1, items);
+
+            expr * regArg2 = to_app(regex)->get_arg(1);
+            reduce_virtual_regex_in(newVar2, regArg2, items);
+
+            return;
+        }
+        // RegexConcat
+        else if (u.re.is_concat(regexFuncDecl)) {
+            // ---------------------------------------------------------
+            // var \in RegexConcat(r1, r2)
+            //   ==>
+            //    (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) )
+            // /\ (newVar1 \in r1) /\  (newVar2 \in r2)
+            // ---------------------------------------------------------
+            expr_ref newVar1(mk_regex_rep_var(), mgr);
+            expr_ref newVar2(mk_regex_rep_var(), mgr);
+            expr_ref concatAst(mk_concat(newVar1, newVar2), mgr);
+            items.push_back(ctx.mk_eq_atom(var, concatAst));
+            items.push_back(ctx.mk_eq_atom(mk_strlen(var),
+                                           m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2))));
+
+            expr * regArg1 = to_app(regex)->get_arg(0);
+            reduce_virtual_regex_in(newVar1, regArg1, items);
+            expr * regArg2 = to_app(regex)->get_arg(1);
+            reduce_virtual_regex_in(newVar2, regArg2, items);
+            return;
+        }
+        // Unroll
+        else if (u.re.is_star(regexFuncDecl)) {
+            // ---------------------------------------------------------
+            // var \in Star(r1)
+            //   ==>
+            // var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)|
+            // ---------------------------------------------------------
+            expr * regArg = to_app(regex)->get_arg(0);
+            expr_ref unrollCnt(mk_unroll_bound_var(), mgr);
+            expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr);
+            items.push_back(ctx.mk_eq_atom(var, unrollFunc));
+            items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc)));
+            return;
+        }
+        // re.range
+        else if (u.re.is_range(regexFuncDecl)) {
+            // var in range("a", "z")
+            // ==>
+            // (var = "a" or var = "b" or ... or var = "z")
+            expr_ref lo(regexFuncDecl->get_arg(0), mgr);
+            expr_ref hi(regexFuncDecl->get_arg(1), mgr);
+            zstring str_lo, str_hi;
+            SASSERT(u.str.is_string(lo));
+            SASSERT(u.str.is_string(hi));
+            u.str.is_string(lo, str_lo);
+            u.str.is_string(hi, str_hi);
+            SASSERT(str_lo.length() == 1);
+            SASSERT(str_hi.length() == 1);
+            unsigned int c1 = str_lo[0];
+            unsigned int c2 = str_hi[0];
+            if (c1 > c2) {
+                // exchange
+                unsigned int tmp = c1;
+                c1 = c2;
+                c2 = tmp;
+            }
+            expr_ref_vector range_cases(mgr);
+            for (unsigned int ch = c1; ch <= c2; ++ch) {
+                zstring s_ch(ch);
+                expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr);
+                range_cases.push_back(rhs);
+            }
+            expr_ref rhs(mk_or(range_cases), mgr);
+            SASSERT(rhs);
+            assert_axiom(rhs);
+            return;
+        } else {
+            get_manager().raise_exception("unrecognized regex operator");
+            UNREACHABLE();
+        }
+    }
+
+    void theory_str::gen_assign_unroll_reg(std::set<expr*> & unrolls) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        expr_ref_vector items(mgr);
+        for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
+            expr * unrFunc = *itor;
+            TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;);
+
+            expr * regexInUnr = to_app(unrFunc)->get_arg(0);
+            expr * cntInUnr = to_app(unrFunc)->get_arg(1);
+            items.reset();
+
+            rational low, high;
+            bool low_exists = lower_bound(cntInUnr, low);
+            bool high_exists = upper_bound(cntInUnr, high);
+
+            TRACE("str",
+                  tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl;
+                  rational unrLenValue;
+                  bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue);
+                  tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl;
+                  rational cntInUnrValue;
+                  bool cntHasValue = get_value(cntInUnr, cntInUnrValue);
+                  tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?")
+                  << " low = "
+                  << (low_exists ? low.to_string() : "?")
+                  << " high = "
+                  << (high_exists ? high.to_string() : "?")
+                  << std::endl;
+                  );
+
+            expr_ref toAssert(mgr);
+            if (low.is_neg()) {
+                toAssert = m_autil.mk_ge(cntInUnr, mk_int(0));
+            } else {
+                if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) {
+
+                    expr_ref newVar1(mk_regex_rep_var(), mgr);
+                    expr_ref newVar2(mk_regex_rep_var(), mgr);
+                    expr_ref concatAst(mk_concat(newVar1, newVar2), mgr);
+                    expr_ref newCnt(mk_unroll_bound_var(), mgr);
+                    expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr);
+
+                    // unroll(r1, t1) = newVar1 . newVar2
+                    items.push_back(ctx.mk_eq_atom(unrFunc, concatAst));
+                    items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2))));
+                    // mk_strlen(unrFunc) >= mk_strlen(newVar{1,2})
+                    items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0)));
+                    items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0)));
+                    // newVar1 \in r1
+                    reduce_virtual_regex_in(newVar1, regexInUnr, items);
+                    items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1))));
+                    items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc));
+                    items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc)));
+                    toAssert = ctx.mk_eq_atom(
+                        m_autil.mk_ge(cntInUnr, mk_int(1)),
+                        mk_and(items));
+
+                    // option 0
+                    expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr);
+                    expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr);
+                    expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr);
+                    expr_ref and1(mgr.mk_and(ast1, ast2), mgr);
+
+                    // put together
+                    toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert);
+
+                    unroll_var_map[unrFunc] = toAssert;
+                } else {
+                    toAssert = unroll_var_map[unrFunc];
+                }
+            }
+            m_trail.push_back(toAssert);
+            assert_axiom(toAssert);
+        }
+    }
+
+    static int computeGCD(int x, int y) {
+        if (x == 0) {
+            return y;
+        }
+        while (y != 0) {
+            if (x > y) {
+                x = x - y;
+            } else {
+                y = y - x;
+            }
+        }
+        return x;
+    }
+
+    static int computeLCM(int a, int b) {
+        int temp = computeGCD(a, b);
+        return temp ? (a / temp * b) : 0;
+    }
+
+    static zstring get_unrolled_string(zstring core, int count) {
+        zstring res("");
+        for (int i = 0; i < count; i++) {
+            res = res + core;
+        }
+        return res;
+    }
+
+    expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set<expr*> & unrolls) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        int lcm = 1;
+        int coreValueCount = 0;
+        expr * oneUnroll = NULL;
+        zstring oneCoreStr("");
+        for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
+            expr * str2RegFunc = to_app(*itor)->get_arg(0);
+            expr * coreVal = to_app(str2RegFunc)->get_arg(0);
+            zstring coreStr;
+            u.str.is_string(coreVal, coreStr);
+            if (oneUnroll == NULL) {
+                oneUnroll = *itor;
+                oneCoreStr = coreStr;
+            }
+            coreValueCount++;
+            int core1Len = coreStr.length();
+            lcm = computeLCM(lcm, core1Len);
+        }
+        //
+        bool canHaveNonEmptyAssign = true;
+        expr_ref_vector litems(mgr);
+        zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length()));
+        for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
+            expr * str2RegFunc = to_app(*itor)->get_arg(0);
+            expr * coreVal = to_app(str2RegFunc)->get_arg(0);
+            zstring coreStr;
+            u.str.is_string(coreVal, coreStr);
+            unsigned int core1Len = coreStr.length();
+            zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len));
+            if (uStr != lcmStr) {
+                canHaveNonEmptyAssign = false;
+            }
+            litems.push_back(ctx.mk_eq_atom(n, *itor));
+        }
+
+        if (canHaveNonEmptyAssign) {
+            return gen_unroll_conditional_options(n, unrolls, lcmStr);
+        } else {
+            expr_ref implyL(mk_and(litems), mgr);
+            expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr);
+            // want to return (implyL -> implyR)
+            expr * final_axiom = rewrite_implication(implyL, implyR);
+            return final_axiom;
+        }
+    }
+
+    expr * theory_str::gen_unroll_conditional_options(expr * var, std::set<expr*> & unrolls, zstring lcmStr) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        int dist = opt_LCMUnrollStep;
+        expr_ref_vector litems(mgr);
+        expr_ref moreAst(mk_string("more"), mgr);
+        for (std::set<expr*>::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) {
+            expr_ref item(ctx.mk_eq_atom(var, *itor), mgr);
+            TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;);
+            litems.push_back(item);
+        }
+
+        // handle out-of-scope entries in unroll_tries_map
+
+        ptr_vector<expr> outOfScopeTesters;
+
+        for (ptr_vector<expr>::iterator it = unroll_tries_map[var][unrolls].begin();
+             it != unroll_tries_map[var][unrolls].end(); ++it) {
+            expr * tester = *it;
+            bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end());
+            TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr)
+                  << (inScope ? " in scope" : " out of scope")
+                  << std::endl;);
+            if (!inScope) {
+                outOfScopeTesters.push_back(tester);
+            }
+        }
+
+        for (ptr_vector<expr>::iterator it = outOfScopeTesters.begin();
+             it != outOfScopeTesters.end(); ++it) {
+            unroll_tries_map[var][unrolls].erase(*it);
+        }
+
+
+        if (unroll_tries_map[var][unrolls].size() == 0) {
+            unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var());
+        }
+
+        int tries = unroll_tries_map[var][unrolls].size();
+        for (int i = 0; i < tries; i++) {
+            expr * tester = unroll_tries_map[var][unrolls][i];
+            // TESTING
+            refresh_theory_var(tester);
+            bool testerHasValue = false;
+            expr * testerVal = get_eqc_value(tester, testerHasValue);
+            if (!testerHasValue) {
+                // generate make-up assertion
+                int l = i * dist;
+                int h = (i + 1) * dist;
+                expr_ref lImp(mk_and(litems), mgr);
+                expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr);
+
+                SASSERT(lImp);
+                TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;);
+                SASSERT(rImp);
+                TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;);
+
+                expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr);
+                SASSERT(toAssert);
+                TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;);
+                m_trail.push_back(toAssert);
+                return toAssert;
+
+                // note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions.
+                // the return is in the same place
+
+                // insert [tester = "more"] to litems so that the implyL for next tester is correct
+                litems.push_back(ctx.mk_eq_atom(tester, moreAst));
+            } else {
+                zstring testerStr;
+                u.str.is_string(testerVal, testerStr);
+                TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";);
+                if (testerStr == "more") {
+                    litems.push_back(ctx.mk_eq_atom(tester, moreAst));
+                }
+            }
+        }
+        expr * tester = mk_unroll_test_var();
+        unroll_tries_map[var][unrolls].push_back(tester);
+        int l = tries * dist;
+        int h = (tries + 1) * dist;
+        expr_ref lImp(mk_and(litems), mgr);
+        expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr);
+        SASSERT(lImp);
+        SASSERT(rImp);
+        expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr);
+        SASSERT(toAssert);
+        TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;);
+        m_trail.push_back(toAssert);
+        return toAssert;
+    }
+
+    expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) {
+        context & ctx = get_context();
+        ast_manager & mgr = get_manager();
+
+        TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr
+              << ", l = " << l << ", h = " << h << "\n";);
+
+        if (m_params.m_AggressiveUnrollTesting) {
+            TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;);
+        }
+
+        expr_ref_vector orItems(mgr);
+        expr_ref_vector andItems(mgr);
+
+        for (int i = l; i < h; i++) {
+            zstring iStr = int_to_string(i);
+            expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr);
+            TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;);
+            if (m_params.m_AggressiveUnrollTesting) {
+                literal l = mk_eq(testerVar, mk_string(iStr), false);
+                ctx.mark_as_relevant(l);
+                ctx.force_phase(l);
+            }
+
+            orItems.push_back(testerEqAst);
+            zstring unrollStrInstance = get_unrolled_string(lcmStr, i);
+
+            expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr);
+            TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;);
+            andItems.push_back(x1);
+
+            expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr);
+            TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;);
+            andItems.push_back(x2);
+        }
+        expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr);
+        TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;);
+        if (m_params.m_AggressiveUnrollTesting) {
+            literal l = mk_eq(testerVar, mk_string("more"), false);
+            ctx.mark_as_relevant(l);
+            ctx.force_phase(~l);
+        }
+
+        orItems.push_back(testerEqMore);
+        int nextLowerLenBound = h * lcmStr.length();
+        expr_ref more2(ctx.mk_eq_atom(testerEqMore,
+                                      //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound))
+                                      m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0))
+                                      ), mgr);
+        TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;);
+        andItems.push_back(more2);
+
+        expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr);
+        TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;);
+        andItems.push_back(mk_or(orItems));
+
+        expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr);
+        TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;);
+
+        // doing the following avoids a segmentation fault
+        m_trail.push_back(finalAND);
+        return finalAND;
+    }
+
+    expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        expr_ref freeVarLen(mk_strlen(freeVar), m);
+        SASSERT(freeVarLen);
+
+        expr_ref_vector orList(m);
+        expr_ref_vector andList(m);
+
+        int distance = 3;
+        int l = (tries - 1) * distance;
+        int h = tries * distance;
+
+        TRACE("str",
+              tout << "building andList and orList" << std::endl;
+              if (m_params.m_AggressiveLengthTesting) {
+                  tout << "note: aggressive length testing is active" << std::endl;
+              }
+              );
+
+        // experimental theory-aware case split support
+        literal_vector case_split_literals;
+
+        for (int i = l; i < h; ++i) {
+            expr_ref str_indicator(m);
+            if (m_params.m_UseFastLengthTesterCache) {
+                rational ri(i);
+                expr * lookup_val;
+                if(lengthTesterCache.find(ri, lookup_val)) {
+                    str_indicator = expr_ref(lookup_val, m);
+                } else {
+                    // no match; create and insert
+                    zstring i_str = int_to_string(i);
+                    expr_ref new_val(mk_string(i_str), m);
+                    lengthTesterCache.insert(ri, new_val);
+                    m_trail.push_back(new_val);
+                    str_indicator = expr_ref(new_val, m);
+                }
+            } else {
+                zstring i_str = int_to_string(i);
+                str_indicator = expr_ref(mk_string(i_str), m);
+            }
+            expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m);
+            orList.push_back(or_expr);
+
+            double priority;
+            // give high priority to small lengths if this is available
+            if (i <= 5) {
+                priority = 0.3;
+            } else {
+                // prioritize over "more"
+                priority = 0.2;
+            }
+            add_theory_aware_branching_info(or_expr, priority, l_true);
+
+            if (m_params.m_AggressiveLengthTesting) {
+                literal l = mk_eq(indicator, str_indicator, false);
+                ctx.mark_as_relevant(l);
+                ctx.force_phase(l);
+            }
+
+            case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false));
+
+            expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m);
+            andList.push_back(and_expr);
+        }
+
+        expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m);
+        orList.push_back(more_option);
+        // decrease priority of this option
+        add_theory_aware_branching_info(more_option, -0.1, l_true);
+        if (m_params.m_AggressiveLengthTesting) {
+            literal l = mk_eq(indicator, mk_string("more"), false);
+            ctx.mark_as_relevant(l);
+            ctx.force_phase(~l);
+        }
+
+        andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h))));
+
+        /*
+          { // more experimental theory case split support
+          expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m);
+          ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false);
+          case_split_literals.push_back(ctx.get_literal(tmp));
+          ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr());
+          }
+        */
+
+        expr_ref_vector or_items(m);
+        expr_ref_vector and_items(m);
+
+        for (unsigned i = 0; i < orList.size(); ++i) {
+            or_items.push_back(orList.get(i));
+        }
+
+        and_items.push_back(mk_or(or_items));
+        for(unsigned i = 0; i < andList.size(); ++i) {
+            and_items.push_back(andList.get(i));
+        }
+
+        TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;);
+
+        expr_ref lenTestAssert = mk_and(and_items);
+        SASSERT(lenTestAssert);
+        TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;);
+
+        int testerCount = tries - 1;
+        if (testerCount > 0) {
+            expr_ref_vector and_items_LHS(m);
+            expr_ref moreAst(mk_string("more"), m);
+            for (int i = 0; i < testerCount; ++i) {
+                expr * indicator = fvar_lenTester_map[freeVar][i];
+                if (internal_variable_set.find(indicator) == internal_variable_set.end()) {
+                    TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;);
+                    continue;
+                } else {
+                    TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;);
+                    and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst));
+                }
+            }
+            expr_ref assertL(mk_and(and_items_LHS), m);
+            SASSERT(assertL);
+            expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get());
+            SASSERT(finalAxiom != NULL);
+            TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;);
+            return finalAxiom;
+        } else {
+            TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;);
+            m_trail.push_back(lenTestAssert.get());
+            return lenTestAssert.get();
+        }
+    }
+
+    // Return an expression of the form
+    // (tester = "less" | tester = "N" | tester = "more") &
+    //   (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N))
+    expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
+        rational N = bounds.midPoint;
+        rational N_minus_one = N - rational::one();
+        rational N_plus_one = N + rational::one();
+        expr_ref lenFreeVar(mk_strlen(freeVar), m);
+
+        TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m)
+              << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;);
+
+        expr_ref_vector combinedCaseSplit(m);
+        expr_ref_vector testerCases(m);
+
+        expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m);
+        testerCases.push_back(caseLess);
+        combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) )));
+
+        expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m);
+        testerCases.push_back(caseMore);
+        combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) )));
+
+        expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m);
+        testerCases.push_back(caseEq);
+        combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true))));
+
+        combinedCaseSplit.push_back(mk_or(testerCases));
+
+        // force internalization on all terms in testerCases so we can extract literals
+        for (unsigned i = 0; i < testerCases.size(); ++i) {
+            expr * testerCase = testerCases.get(i);
+            if (!ctx.b_internalized(testerCase)) {
+                ctx.internalize(testerCase, false);
+            }
+            literal l = ctx.get_literal(testerCase);
+            case_split.push_back(l);
+        }
+
+        expr_ref final_term(mk_and(combinedCaseSplit), m);
+        SASSERT(final_term);
+        TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;);
+        return final_term;
+    }
+
+    expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) {
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+
+        if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) {
+            TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl;
+                  for (ptr_vector<expr>::const_iterator it = binary_search_len_tester_stack[freeVar].begin();
+                       it != binary_search_len_tester_stack[freeVar].end(); ++it) {
+                      expr * tester = *it;
+                      tout << mk_pp(tester, m) << ": ";
+                      if (binary_search_len_tester_info.contains(tester)) {
+                          binary_search_info & bounds = binary_search_len_tester_info[tester];
+                          tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize;
+                      } else {
+                          tout << "[WARNING: no bounds info available]";
+                      }
+                      bool hasEqcValue;
+                      expr * testerEqcValue = get_eqc_value(tester, hasEqcValue);
+                      if (hasEqcValue) {
+                          tout << " = " << mk_pp(testerEqcValue, m);
+                      } else {
+                          tout << " [no eqc value]";
+                      }
+                      tout << std::endl;
+                  }
+                  );
+            expr * lastTester = binary_search_len_tester_stack[freeVar].back();
+            bool lastTesterHasEqcValue;
+            expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue);
+            zstring lastTesterConstant;
+            if (!lastTesterHasEqcValue) {
+                TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;);
+                // check previousLenTester
+                if (previousLenTester == lastTester) {
+                    lastTesterConstant = previousLenTesterValue;
+                    TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;);
+                } else {
+                    TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;);
+                    UNREACHABLE(); return NULL;
+                }
+            } else {
+                u.str.is_string(lastTesterValue, lastTesterConstant);
+            }
+            TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";);
+            if (lastTesterConstant == "more" || lastTesterConstant == "less") {
+                // use the previous bounds info to generate a new midpoint
+                binary_search_info lastBounds;
+                if (!binary_search_len_tester_info.find(lastTester, lastBounds)) {
+                    // unexpected
+                    TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;);
+                    UNREACHABLE();
+                }
+                TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;);
+                binary_search_info newBounds;
+                expr * newTester;
+                if (lastTesterConstant == "more") {
+                    // special case: if the midpoint, upper bound, and window size are all equal,
+                    // we double the window size and adjust the bounds
+                    if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) {
+                        TRACE("str", tout << "search hit window size; expanding" << std::endl;);
+                        newBounds.lowerBound = lastBounds.windowSize + rational::one();
+                        newBounds.windowSize = lastBounds.windowSize * rational(2);
+                        newBounds.upperBound = newBounds.windowSize;
+                        newBounds.calculate_midpoint();
+                    } else if (false) {
+                        // handle the case where the midpoint can't be increased further
+                        // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50")
+                    } else {
+                        // general case
+                        newBounds.lowerBound = lastBounds.midPoint + rational::one();
+                        newBounds.windowSize = lastBounds.windowSize;
+                        newBounds.upperBound = lastBounds.upperBound;
+                        newBounds.calculate_midpoint();
+                    }
+                    if (!binary_search_next_var_high.find(lastTester, newTester)) {
+                        newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32());
+                        binary_search_next_var_high.insert(lastTester, newTester);
+                    }
+                    refresh_theory_var(newTester);
+                } else if (lastTesterConstant == "less") {
+                    if (false) {
+                        // handle the case where the midpoint can't be decreased further
+                        // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0"
+                    } else {
+                        // general case
+                        newBounds.upperBound = lastBounds.midPoint - rational::one();
+                        newBounds.windowSize = lastBounds.windowSize;
+                        newBounds.lowerBound = lastBounds.lowerBound;
+                        newBounds.calculate_midpoint();
+                    }
+                    if (!binary_search_next_var_low.find(lastTester, newTester)) {
+                        newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32());
+                        binary_search_next_var_low.insert(lastTester, newTester);
+                    }
+                    refresh_theory_var(newTester);
+                }
+                TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;);
+                binary_search_len_tester_stack[freeVar].push_back(newTester);
+                m_trail_stack.push(binary_search_trail<theory_str>(binary_search_len_tester_stack, freeVar));
+                binary_search_len_tester_info.insert(newTester, newBounds);
+                m_trail_stack.push(insert_obj_map<theory_str, expr, binary_search_info>(binary_search_len_tester_info, newTester));
+
+                literal_vector case_split_literals;
+                expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals));
+                m_trail.push_back(next_case_split);
+                // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr());
+                return next_case_split;
+            } else { // lastTesterConstant is a concrete value
+                TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;);
+                // defensive check that this length did not converge on a negative value.
+                binary_search_info lastBounds;
+                if (!binary_search_len_tester_info.find(lastTester, lastBounds)) {
+                    // unexpected
+                    TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;);
+                    UNREACHABLE();
+                }
+                if (lastBounds.midPoint.is_neg()) {
+                    TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;);
+                    expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m);
+                    return axiom;
+                }
+                // length is fixed
+                expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring(""));
+                return valueAssert;
+            }
+        } else {
+            // no length testers yet
+            TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;);
+            binary_search_len_tester_stack.insert(freeVar, ptr_vector<expr>());
+
+            expr * firstTester;
+            rational lowerBound(0);
+            rational upperBound(m_params.m_BinarySearchInitialUpperBound);
+            rational windowSize(upperBound);
+            rational midPoint(floor(upperBound / rational(2)));
+            if (!binary_search_starting_len_tester.find(freeVar, firstTester)) {
+                firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32());
+                binary_search_starting_len_tester.insert(freeVar, firstTester);
+            }
+            refresh_theory_var(firstTester);
+
+            binary_search_len_tester_stack[freeVar].push_back(firstTester);
+            m_trail_stack.push(binary_search_trail<theory_str>(binary_search_len_tester_stack, freeVar));
+            binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize);
+            binary_search_len_tester_info.insert(firstTester, new_info);
+            m_trail_stack.push(insert_obj_map<theory_str, expr, binary_search_info>(binary_search_len_tester_info, firstTester));
+
+            literal_vector case_split_literals;
+            expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals));
+            m_trail.push_back(initial_case_split);
+            // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr());
+            return initial_case_split;
+        }
+    }
+
+    // -----------------------------------------------------------------------------------------------------
+    // True branch will be taken in final_check:
+    //   - When we discover a variable is "free" for the first time
+    //     lenTesterInCbEq = NULL
+    //     lenTesterValue = ""
+    // False branch will be taken when invoked by new_eq_eh().
+    //   - After we set up length tester for a "free" var in final_check,
+    //     when the tester is assigned to some value (e.g. "more" or "4"),
+    //     lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue
+    // The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc
+    // -----------------------------------------------------------------------------------------------------
+    expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) {
+
+        ast_manager & m = get_manager();
+
+        TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;);
+
+        if (m_params.m_UseBinarySearch) {
+            TRACE("str", tout << "using binary search heuristic" << std::endl;);
+            return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue);
+        } else {
+            bool map_effectively_empty = false;
+            if (!fvar_len_count_map.contains(freeVar)) {
+                TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;);
+                map_effectively_empty = true;
+            }
+
+            if (!map_effectively_empty) {
+                // check whether any entries correspond to variables that went out of scope;
+                // if every entry is out of scope then the map counts as being empty
+
+                // assume empty and find a counterexample
+                map_effectively_empty = true;
+                ptr_vector<expr> indicator_set = fvar_lenTester_map[freeVar];
+                for (ptr_vector<expr>::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) {
+                    expr * indicator = *it;
+                    if (internal_variable_set.find(indicator) != internal_variable_set.end()) {
+                        TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m)
+                              << " in fvar_lenTester_map[freeVar]" << std::endl;);
+                        map_effectively_empty = false;
+                        break;
+                    }
+                }
+                CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;);
+            }
+
+            if (map_effectively_empty) {
+                // no length assertions for this free variable have ever been added.
+                TRACE("str", tout << "no length assertions yet" << std::endl;);
+
+                fvar_len_count_map.insert(freeVar, 1);
+                unsigned int testNum = fvar_len_count_map[freeVar];
+
+                expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m);
+                SASSERT(indicator);
+
+                // since the map is "effectively empty", we can remove those variables that have left scope...
+                fvar_lenTester_map[freeVar].shrink(0);
+                fvar_lenTester_map[freeVar].push_back(indicator);
+                lenTester_fvar_map.insert(indicator, freeVar);
+
                 expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum);
                 SASSERT(lenTestAssert != NULL);
                 return lenTestAssert;
             } else {
-                TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;);
-                // length is fixed
-                expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring(""));
-                return valueAssert;
+                TRACE("str", tout << "found previous in-scope length assertions" << std::endl;);
+
+                expr * effectiveLenInd = NULL;
+                zstring effectiveLenIndiStr("");
+                int lenTesterCount = (int) fvar_lenTester_map[freeVar].size();
+
+                TRACE("str",
+                      tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl;
+                      for (int i = 0; i < lenTesterCount; ++i) {
+                          expr * len_indicator = fvar_lenTester_map[freeVar][i];
+                          tout << mk_pp(len_indicator, m) << ": ";
+                          bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end());
+                          tout << (effectiveInScope ? "in scope" : "NOT in scope");
+                          tout << std::endl;
+                      }
+                      );
+
+                int i = 0;
+                for (; i < lenTesterCount; ++i) {
+                    expr * len_indicator_pre = fvar_lenTester_map[freeVar][i];
+                    // check whether this is in scope as well
+                    if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) {
+                        TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;);
+                        continue;
+                    }
+
+                    bool indicatorHasEqcValue = false;
+                    expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue);
+                    TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) <<
+                          " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;);
+                    if (indicatorHasEqcValue) {
+                        zstring len_pIndiStr;
+                        u.str.is_string(len_indicator_value, len_pIndiStr);
+                        if (len_pIndiStr != "more") {
+                            effectiveLenInd = len_indicator_pre;
+                            effectiveLenIndiStr = len_pIndiStr;
+                            break;
+                        }
+                    } else {
+                        if (lenTesterInCbEq != len_indicator_pre) {
+                            TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m)
+                                  << " does not have an equivalence class value."
+                                  << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;);
+                            if (i > 0) {
+                                effectiveLenInd = fvar_lenTester_map[freeVar][i - 1];
+                                bool effectiveHasEqcValue;
+                                expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue);
+                                bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end());
+                                TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": "
+                                      << (effectiveInScope ? "in scope" : "NOT in scope") << ", ";
+                                      if (effectiveHasEqcValue) {
+                                          tout << "~= " << mk_pp(effective_eqc_value, m);
+                                      } else {
+                                          tout << "no eqc string constant";
+                                      }
+                                      tout << std::endl;);
+                                if (effectiveLenInd == lenTesterInCbEq) {
+                                    effectiveLenIndiStr = lenTesterValue;
+                                } else {
+                                    if (effectiveHasEqcValue) {
+                                        u.str.is_string(effective_eqc_value, effectiveLenIndiStr);
+                                    } else {
+                                        NOT_IMPLEMENTED_YET();
+                                    }
+                                }
+                            }
+                            break;
+                        }
+                        // lenTesterInCbEq == len_indicator_pre
+                        else {
+                            if (lenTesterValue != "more") {
+                                effectiveLenInd = len_indicator_pre;
+                                effectiveLenIndiStr = lenTesterValue;
+                                break;
+                            }
+                        }
+                    } // !indicatorHasEqcValue
+                } // for (i : [0..lenTesterCount-1])
+                if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") {
+                    TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;);
+                    expr_ref indicator(m);
+                    unsigned int testNum = 0;
+
+                    TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr
+                          << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";);
+
+                    if (i == lenTesterCount) {
+                        fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1;
+                        testNum = fvar_len_count_map[freeVar];
+                        indicator = mk_internal_lenTest_var(freeVar, testNum);
+                        fvar_lenTester_map[freeVar].push_back(indicator);
+                        lenTester_fvar_map.insert(indicator, freeVar);
+                    } else {
+                        indicator = fvar_lenTester_map[freeVar][i];
+                        refresh_theory_var(indicator);
+                        testNum = i + 1;
+                    }
+                    expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum);
+                    SASSERT(lenTestAssert != NULL);
+                    return lenTestAssert;
+                } else {
+                    TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;);
+                    // length is fixed
+                    expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring(""));
+                    return valueAssert;
+                }
+            } // fVarLenCountMap.find(...)
+
+        } // !UseBinarySearch
+    }
+
+    void theory_str::get_concats_in_eqc(expr * n, std::set<expr*> & concats) {
+        context & ctx = get_context();
+
+        expr * eqcNode = n;
+        do {
+            if (u.str.is_concat(to_app(eqcNode))) {
+                concats.insert(eqcNode);
             }
-        } // fVarLenCountMap.find(...)
+            eqcNode = get_eqc_next(eqcNode);
+        } while (eqcNode != n);
+    }
 
-	} // !UseBinarySearch
-}
+    void theory_str::get_var_in_eqc(expr * n, std::set<expr*> & varSet) {
+        context & ctx = get_context();
 
-void theory_str::get_concats_in_eqc(expr * n, std::set<expr*> & concats) {
-    context & ctx = get_context();
+        expr * eqcNode = n;
+        do {
+            if (variable_set.find(eqcNode) != variable_set.end()) {
+                varSet.insert(eqcNode);
+            }
+            eqcNode = get_eqc_next(eqcNode);
+        } while (eqcNode != n);
+    }
 
-    expr * eqcNode = n;
-    do {
-        if (u.str.is_concat(to_app(eqcNode))) {
-            concats.insert(eqcNode);
-        }
-        eqcNode = get_eqc_next(eqcNode);
-    } while (eqcNode != n);
-}
+    bool cmpvarnames(expr * lhs, expr * rhs) {
+        symbol lhs_name = to_app(lhs)->get_decl()->get_name();
+        symbol rhs_name = to_app(rhs)->get_decl()->get_name();
+        return lhs_name.str() < rhs_name.str();
+    }
 
-void theory_str::get_var_in_eqc(expr * n, std::set<expr*> & varSet) {
-	context & ctx = get_context();
+    void theory_str::process_free_var(std::map<expr*, int> & freeVar_map) {
+        context & ctx = get_context();
+        ast_manager & m = get_manager();
 
-	expr * eqcNode = n;
-	do {
-		if (variable_set.find(eqcNode) != variable_set.end()) {
-			varSet.insert(eqcNode);
-		}
-		eqcNode = get_eqc_next(eqcNode);
-	} while (eqcNode != n);
-}
+        std::set<expr*> eqcRepSet;
+        std::set<expr*> leafVarSet;
+        std::map<int, std::set<expr*> > aloneVars;
 
-bool cmpvarnames(expr * lhs, expr * rhs) {
-    symbol lhs_name = to_app(lhs)->get_decl()->get_name();
-    symbol rhs_name = to_app(rhs)->get_decl()->get_name();
-    return lhs_name.str() < rhs_name.str();
-}
+        for (std::map<expr*, int>::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) {
+            expr * freeVar = fvIt->first;
+            // skip all regular expression vars
+            if (regex_variable_set.find(freeVar) != regex_variable_set.end()) {
+                continue;
+            }
 
-void theory_str::process_free_var(std::map<expr*, int> & freeVar_map) {
-	context & ctx = get_context();
-	ast_manager & m = get_manager();
-
-	std::set<expr*> eqcRepSet;
-	std::set<expr*> leafVarSet;
-	std::map<int, std::set<expr*> > aloneVars;
-
-	for (std::map<expr*, int>::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) {
-		expr * freeVar = fvIt->first;
-		// skip all regular expression vars
-		if (regex_variable_set.find(freeVar) != regex_variable_set.end()) {
-			continue;
-		}
-
-		// Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet.
-		// If found, have to filter it out
-		std::set<expr*> eqVarSet;
-		get_var_in_eqc(freeVar, eqVarSet);
-		bool duplicated = false;
-		expr * dupVar = NULL;
-		for (std::set<expr*>::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) {
-			if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) {
-				duplicated = true;
-				dupVar = *itorEqv;
-				break;
-			}
-		}
-		if (duplicated && dupVar != NULL) {
-			TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m)
-					<< " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;);
-			continue;
-		} else {
-			eqcRepSet.insert(freeVar);
-		}
-	}
-
-	for (std::set<expr*>::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) {
-		bool standAlone = true;
-		expr * freeVar = *fvIt;
-		// has length constraint initially
-		if (input_var_in_len.find(freeVar) != input_var_in_len.end()) {
-			standAlone = false;
-		}
-		// iterate parents
-		if (standAlone) {
-			// I hope this works!
-			enode * e_freeVar = ctx.get_enode(freeVar);
-			enode_vector::iterator it = e_freeVar->begin_parents();
-			for (; it != e_freeVar->end_parents(); ++it) {
-				expr * parentAst = (*it)->get_owner();
-				if (u.str.is_concat(to_app(parentAst))) {
-					standAlone = false;
-					break;
-				}
-			}
-		}
-
-		if (standAlone) {
-		    rational len_value;
-		    bool len_value_exists = get_len_value(freeVar, len_value);
-			if (len_value_exists) {
-				leafVarSet.insert(freeVar);
-			} else {
-				aloneVars[-1].insert(freeVar);
-			}
-		} else {
-			leafVarSet.insert(freeVar);
-		}
-	}
-
-	for(std::set<expr*>::iterator itor1 = leafVarSet.begin();
-	        itor1 != leafVarSet.end(); ++itor1) {
-	    expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, "");
-	    // gen_len_val_options_for_free_var() can legally return NULL,
-	    // as methods that it calls may assert their own axioms instead.
-	    if (toAssert != NULL) {
-	        assert_axiom(toAssert);
-	    }
-	}
-
-	for (std::map<int, std::set<expr*> >::iterator mItor = aloneVars.begin();
-			mItor != aloneVars.end(); ++mItor) {
-		std::set<expr*>::iterator itor2 = mItor->second.begin();
-		for(; itor2 != mItor->second.end(); ++itor2) {
-			expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, "");
-			// same deal with returning a NULL axiom here
-			if(toAssert != NULL) {
-			    assert_axiom(toAssert);
-			}
-		}
-	}
-}
-
-/*
- * Collect all unroll functions
- * and constant string in eqc of node n
- */
-void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet) {
-    constStr = NULL;
-    unrollFuncSet.clear();
-    context & ctx = get_context();
-
-    expr * curr = n;
-    do {
-        if (u.str.is_string(to_app(curr))) {
-            constStr = curr;
-        } else if (u.re.is_unroll(to_app(curr))) {
-            if (unrollFuncSet.find(curr) == unrollFuncSet.end()) {
-                unrollFuncSet.insert(curr);
+            // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet.
+            // If found, have to filter it out
+            std::set<expr*> eqVarSet;
+            get_var_in_eqc(freeVar, eqVarSet);
+            bool duplicated = false;
+            expr * dupVar = NULL;
+            for (std::set<expr*>::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) {
+                if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) {
+                    duplicated = true;
+                    dupVar = *itorEqv;
+                    break;
+                }
+            }
+            if (duplicated && dupVar != NULL) {
+                TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m)
+                      << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;);
+                continue;
+            } else {
+                eqcRepSet.insert(freeVar);
             }
         }
-        curr = get_eqc_next(curr);
-    } while (curr != n);
-}
 
-// Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n.
-void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet) {
-	constStr = NULL;
-	unrollFuncSet.clear();
-	context & ctx = get_context();
+        for (std::set<expr*>::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) {
+            bool standAlone = true;
+            expr * freeVar = *fvIt;
+            // has length constraint initially
+            if (input_var_in_len.find(freeVar) != input_var_in_len.end()) {
+                standAlone = false;
+            }
+            // iterate parents
+            if (standAlone) {
+                // I hope this works!
+                enode * e_freeVar = ctx.get_enode(freeVar);
+                enode_vector::iterator it = e_freeVar->begin_parents();
+                for (; it != e_freeVar->end_parents(); ++it) {
+                    expr * parentAst = (*it)->get_owner();
+                    if (u.str.is_concat(to_app(parentAst))) {
+                        standAlone = false;
+                        break;
+                    }
+                }
+            }
 
-	expr * curr = n;
-	do {
-		if (u.str.is_string(to_app(curr))) {
-			constStr = curr;
-		} else if (u.re.is_unroll(to_app(curr))) {
-			expr * core = to_app(curr)->get_arg(0);
-			if (u.re.is_to_re(to_app(core))) {
-				if (unrollFuncSet.find(curr) == unrollFuncSet.end()) {
-					unrollFuncSet.insert(curr);
-				}
-			}
-		}
-		curr = get_eqc_next(curr);
-	} while (curr != n);
-}
+            if (standAlone) {
+                rational len_value;
+                bool len_value_exists = get_len_value(freeVar, len_value);
+                if (len_value_exists) {
+                    leafVarSet.insert(freeVar);
+                } else {
+                    aloneVars[-1].insert(freeVar);
+                }
+            } else {
+                leafVarSet.insert(freeVar);
+            }
+        }
 
-void theory_str::init_model(model_generator & mg) {
-    //TRACE("str", tout << "initializing model" << std::endl; display(tout););
-    m_factory = alloc(str_value_factory, get_manager(), get_family_id());
-    mg.register_factory(m_factory);
-}
+        for(std::set<expr*>::iterator itor1 = leafVarSet.begin();
+            itor1 != leafVarSet.end(); ++itor1) {
+            expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, "");
+            // gen_len_val_options_for_free_var() can legally return NULL,
+            // as methods that it calls may assert their own axioms instead.
+            if (toAssert != NULL) {
+                assert_axiom(toAssert);
+            }
+        }
 
-/*
- * Helper function for mk_value().
- * Attempts to resolve the expression 'n' to a string constant.
- * Stronger than get_eqc_value() in that it will perform recursive descent
- * through every subexpression and attempt to resolve those to concrete values as well.
- * Returns the concrete value obtained from this process,
- * guaranteed to satisfy m_strutil.is_string(),
- * if one could be obtained,
- * or else returns NULL if no concrete value was derived.
- */
-app * theory_str::mk_value_helper(app * n) {
-    if (u.str.is_string(n)) {
-        return n;
-    } else if (u.str.is_concat(n)) {
-        // recursively call this function on each argument
-        SASSERT(n->get_num_args() == 2);
-        expr * a0 = n->get_arg(0);
-        expr * a1 = n->get_arg(1);
-
-        app * a0_conststr = mk_value_helper(to_app(a0));
-        app * a1_conststr = mk_value_helper(to_app(a1));
-
-        if (a0_conststr != NULL && a1_conststr != NULL) {
-            zstring a0_s, a1_s;
-            u.str.is_string(a0_conststr, a0_s);
-            u.str.is_string(a1_conststr, a1_s);
-            zstring result = a0_s + a1_s;
-            return to_app(mk_string(result));
+        for (std::map<int, std::set<expr*> >::iterator mItor = aloneVars.begin();
+             mItor != aloneVars.end(); ++mItor) {
+            std::set<expr*>::iterator itor2 = mItor->second.begin();
+            for(; itor2 != mItor->second.end(); ++itor2) {
+                expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, "");
+                // same deal with returning a NULL axiom here
+                if(toAssert != NULL) {
+                    assert_axiom(toAssert);
+                }
+            }
         }
     }
-    // fallback path
-    // try to find some constant string, anything, in the equivalence class of n
-    bool hasEqc = false;
-    expr * n_eqc = get_eqc_value(n, hasEqc);
-    if (hasEqc) {
-        return to_app(n_eqc);
-    } else {
-        return NULL;
+
+    /*
+     * Collect all unroll functions
+     * and constant string in eqc of node n
+     */
+    void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet) {
+        constStr = NULL;
+        unrollFuncSet.clear();
+        context & ctx = get_context();
+
+        expr * curr = n;
+        do {
+            if (u.str.is_string(to_app(curr))) {
+                constStr = curr;
+            } else if (u.re.is_unroll(to_app(curr))) {
+                if (unrollFuncSet.find(curr) == unrollFuncSet.end()) {
+                    unrollFuncSet.insert(curr);
+                }
+            }
+            curr = get_eqc_next(curr);
+        } while (curr != n);
     }
-}
 
-model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) {
-    TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) <<
-                                " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;);
-    ast_manager & m = get_manager();
-    context & ctx = get_context();
-    app_ref owner(m);
-    owner = n->get_owner();
+    // Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n.
+    void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet) {
+        constStr = NULL;
+        unrollFuncSet.clear();
+        context & ctx = get_context();
 
-    // If the owner is not internalized, it doesn't have an enode associated.
-    SASSERT(ctx.e_internalized(owner));
-
-    app * val = mk_value_helper(owner);
-    if (val != NULL) {
-        return alloc(expr_wrapper_proc, val);
-    } else {
-        TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;);
-        return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**")));
+        expr * curr = n;
+        do {
+            if (u.str.is_string(to_app(curr))) {
+                constStr = curr;
+            } else if (u.re.is_unroll(to_app(curr))) {
+                expr * core = to_app(curr)->get_arg(0);
+                if (u.re.is_to_re(to_app(core))) {
+                    if (unrollFuncSet.find(curr) == unrollFuncSet.end()) {
+                        unrollFuncSet.insert(curr);
+                    }
+                }
+            }
+            curr = get_eqc_next(curr);
+        } while (curr != n);
     }
-}
 
-void theory_str::finalize_model(model_generator & mg) {}
+    void theory_str::init_model(model_generator & mg) {
+        //TRACE("str", tout << "initializing model" << std::endl; display(tout););
+        m_factory = alloc(str_value_factory, get_manager(), get_family_id());
+        mg.register_factory(m_factory);
+    }
 
-void theory_str::display(std::ostream & out) const {
-    out << "TODO: theory_str display" << std::endl;
-}
+    /*
+     * Helper function for mk_value().
+     * Attempts to resolve the expression 'n' to a string constant.
+     * Stronger than get_eqc_value() in that it will perform recursive descent
+     * through every subexpression and attempt to resolve those to concrete values as well.
+     * Returns the concrete value obtained from this process,
+     * guaranteed to satisfy m_strutil.is_string(),
+     * if one could be obtained,
+     * or else returns NULL if no concrete value was derived.
+     */
+    app * theory_str::mk_value_helper(app * n) {
+        if (u.str.is_string(n)) {
+            return n;
+        } else if (u.str.is_concat(n)) {
+            // recursively call this function on each argument
+            SASSERT(n->get_num_args() == 2);
+            expr * a0 = n->get_arg(0);
+            expr * a1 = n->get_arg(1);
+
+            app * a0_conststr = mk_value_helper(to_app(a0));
+            app * a1_conststr = mk_value_helper(to_app(a1));
+
+            if (a0_conststr != NULL && a1_conststr != NULL) {
+                zstring a0_s, a1_s;
+                u.str.is_string(a0_conststr, a0_s);
+                u.str.is_string(a1_conststr, a1_s);
+                zstring result = a0_s + a1_s;
+                return to_app(mk_string(result));
+            }
+        }
+        // fallback path
+        // try to find some constant string, anything, in the equivalence class of n
+        bool hasEqc = false;
+        expr * n_eqc = get_eqc_value(n, hasEqc);
+        if (hasEqc) {
+            return to_app(n_eqc);
+        } else {
+            return NULL;
+        }
+    }
+
+    model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) {
+        TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) <<
+              " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;);
+        ast_manager & m = get_manager();
+        context & ctx = get_context();
+        app_ref owner(m);
+        owner = n->get_owner();
+
+        // If the owner is not internalized, it doesn't have an enode associated.
+        SASSERT(ctx.e_internalized(owner));
+
+        app * val = mk_value_helper(owner);
+        if (val != NULL) {
+            return alloc(expr_wrapper_proc, val);
+        } else {
+            TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;);
+            return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**")));
+        }
+    }
+
+    void theory_str::finalize_model(model_generator & mg) {}
+
+    void theory_str::display(std::ostream & out) const {
+        out << "TODO: theory_str display" << std::endl;
+    }
 
 }; /* namespace smt */

From f904b033ad8f6db0eefe37ebfe40f1bd60b310e5 Mon Sep 17 00:00:00 2001
From: Murphy Berzish <murphy.berzish@gmail.com>
Date: Fri, 5 May 2017 19:29:53 -0400
Subject: [PATCH 2/2] formatting theory_str.h

---
 src/smt/theory_str.h | 1110 +++++++++++++++++++++---------------------
 1 file changed, 555 insertions(+), 555 deletions(-)

diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h
index 7c2df9e12..2e6d96fa7 100644
--- a/src/smt/theory_str.h
+++ b/src/smt/theory_str.h
@@ -1,19 +1,19 @@
 /*++
-Module Name:
+  Module Name:
 
-    theory_str.h
+  theory_str.h
 
-Abstract:
+  Abstract:
 
-    String Theory Plugin
+  String Theory Plugin
 
-Author:
+  Author:
 
-    Murphy Berzish and Yunhui Zheng
+  Murphy Berzish and Yunhui Zheng
 
-Revision History:
+  Revision History:
 
---*/
+  --*/
 #ifndef _THEORY_STR_H_
 #define _THEORY_STR_H_
 
@@ -33,619 +33,619 @@ Revision History:
 
 namespace smt {
 
-    typedef hashtable<symbol, symbol_hash_proc, symbol_eq_proc> symbol_set;
+typedef hashtable<symbol, symbol_hash_proc, symbol_eq_proc> symbol_set;
 
-    class str_value_factory : public value_factory {
-        seq_util u;
-        symbol_set m_strings;
-        std::string delim;
-        unsigned m_next;
-    public:
-        str_value_factory(ast_manager & m, family_id fid) :
-            value_factory(m, fid),
-            u(m), delim("!"), m_next(0) {}
-        virtual ~str_value_factory() {}
-        virtual expr * get_some_value(sort * s) {
-            return u.str.mk_string(symbol("some value"));
-        }
-        virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) {
-            v1 = u.str.mk_string(symbol("value 1"));
-            v2 = u.str.mk_string(symbol("value 2"));
-            return true;
-        }
-        virtual expr * get_fresh_value(sort * s) {
-            if (u.is_string(s)) {
-                while (true) {
-                    std::ostringstream strm;
-                    strm << delim << std::hex << (m_next++) << std::dec << delim;
-                    symbol sym(strm.str().c_str());
-                    if (m_strings.contains(sym)) continue;
-                    m_strings.insert(sym);
-                    return u.str.mk_string(sym);
-                }
+class str_value_factory : public value_factory {
+    seq_util u;
+    symbol_set m_strings;
+    std::string delim;
+    unsigned m_next;
+public:
+    str_value_factory(ast_manager & m, family_id fid) :
+        value_factory(m, fid),
+        u(m), delim("!"), m_next(0) {}
+    virtual ~str_value_factory() {}
+    virtual expr * get_some_value(sort * s) {
+        return u.str.mk_string(symbol("some value"));
+    }
+    virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) {
+        v1 = u.str.mk_string(symbol("value 1"));
+        v2 = u.str.mk_string(symbol("value 2"));
+        return true;
+    }
+    virtual expr * get_fresh_value(sort * s) {
+        if (u.is_string(s)) {
+            while (true) {
+                std::ostringstream strm;
+                strm << delim << std::hex << (m_next++) << std::dec << delim;
+                symbol sym(strm.str().c_str());
+                if (m_strings.contains(sym)) continue;
+                m_strings.insert(sym);
+                return u.str.mk_string(sym);
             }
-            sort* seq = 0;
-            if (u.is_re(s, seq)) {
-                expr* v0 = get_fresh_value(seq);
-                return u.re.mk_to_re(v0);
-            }
-            TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;);
-            UNREACHABLE(); return NULL;
         }
-        virtual void register_value(expr * n) { /* Ignore */ }
-    };
+        sort* seq = 0;
+        if (u.is_re(s, seq)) {
+            expr* v0 = get_fresh_value(seq);
+            return u.re.mk_to_re(v0);
+        }
+        TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;);
+        UNREACHABLE(); return NULL;
+    }
+    virtual void register_value(expr * n) { /* Ignore */ }
+};
 
-    // rather than modify obj_pair_map I inherit from it and add my own helper methods
-    class theory_str_contain_pair_bool_map_t : public obj_pair_map<expr, expr, expr*> {
-    public:
-        expr * operator[](std::pair<expr*, expr*> key) const {
-            expr * value;
-            bool found = this->find(key.first, key.second, value);
-            if (found) {
-                return value;
+// rather than modify obj_pair_map I inherit from it and add my own helper methods
+class theory_str_contain_pair_bool_map_t : public obj_pair_map<expr, expr, expr*> {
+public:
+    expr * operator[](std::pair<expr*, expr*> key) const {
+        expr * value;
+        bool found = this->find(key.first, key.second, value);
+        if (found) {
+            return value;
+        } else {
+            TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;);
+            return NULL;
+        }
+    }
+
+    bool contains(std::pair<expr*, expr*> key) const {
+        expr * unused;
+        return this->find(key.first, key.second, unused);
+    }
+};
+
+template<typename Ctx>
+class binary_search_trail : public trail<Ctx> {
+    obj_map<expr, ptr_vector<expr> > & target;
+    expr * entry;
+public:
+    binary_search_trail(obj_map<expr, ptr_vector<expr> > & target, expr * entry) :
+        target(target), entry(entry) {}
+    virtual ~binary_search_trail() {}
+    virtual void undo(Ctx & ctx) {
+        TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;);
+        if (target.contains(entry)) {
+            if (!target[entry].empty()) {
+                target[entry].pop_back();
             } else {
-                TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;);
-                return NULL;
+                TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;);
             }
+        } else {
+            TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;);
         }
+    }
+};
 
-        bool contains(std::pair<expr*, expr*> key) const {
-            expr * unused;
-            return this->find(key.first, key.second, unused);
+
+class nfa {
+protected:
+    bool m_valid;
+    unsigned m_next_id;
+
+    unsigned next_id() {
+        unsigned retval = m_next_id;
+        ++m_next_id;
+        return retval;
+    }
+
+    unsigned m_start_state;
+    unsigned m_end_state;
+
+    std::map<unsigned, std::map<char, unsigned> > transition_map;
+    std::map<unsigned, std::set<unsigned> > epsilon_map;
+
+    void make_transition(unsigned start, char symbol, unsigned end) {
+        transition_map[start][symbol] = end;
+    }
+
+    void make_epsilon_move(unsigned start, unsigned end) {
+        epsilon_map[start].insert(end);
+    }
+
+    // Convert a regular expression to an e-NFA using Thompson's construction
+    void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u);
+
+public:
+    nfa(seq_util & u, expr * e)
+: m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) {
+        convert_re(e, m_start_state, m_end_state, u);
+    }
+
+    nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {}
+
+    bool is_valid() const {
+        return m_valid;
+    }
+
+    void epsilon_closure(unsigned start, std::set<unsigned> & closure);
+
+    bool matches(zstring input);
+};
+
+class theory_str : public theory {
+    struct T_cut
+    {
+        int level;
+        std::map<expr*, int> vars;
+
+        T_cut() {
+            level = -100;
         }
     };
 
-    template<typename Ctx>
-    class binary_search_trail : public trail<Ctx> {
-        obj_map<expr, ptr_vector<expr> > & target;
-        expr * entry;
-    public:
-        binary_search_trail(obj_map<expr, ptr_vector<expr> > & target, expr * entry) :
-            target(target), entry(entry) {}
-        virtual ~binary_search_trail() {}
-        virtual void undo(Ctx & ctx) {
-            TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;);
-            if (target.contains(entry)) {
-                if (!target[entry].empty()) {
-                    target[entry].pop_back();
-                } else {
-                    TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;);
-                }
-            } else {
-                TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;);
-            }
+    typedef trail_stack<theory_str> th_trail_stack;
+    typedef union_find<theory_str> th_union_find;
+
+    typedef map<rational, expr*, obj_hash<rational>, default_eq<rational> > rational_map;
+    struct zstring_hash_proc {
+        unsigned operator()(zstring const & s) const {
+            return string_hash(s.encode().c_str(), static_cast<unsigned>(s.length()), 17);
         }
     };
+    typedef map<zstring, expr*, zstring_hash_proc, default_eq<zstring> > string_map;
 
+protected:
+    theory_str_params const & m_params;
 
-    class nfa {
-    protected:
-        bool m_valid;
-        unsigned m_next_id;
+    /*
+     * Setting EagerStringConstantLengthAssertions to true allows some methods,
+     * in particular internalize_term(), to add
+     * length assertions about relevant string constants.
+     * Note that currently this should always be set to 'true', or else *no* length assertions
+     * will be made about string constants.
+     */
+    bool opt_EagerStringConstantLengthAssertions;
 
-        unsigned next_id() {
-            unsigned retval = m_next_id;
-            ++m_next_id;
-            return retval;
+    /*
+     * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked
+     * without asserting any new axioms is considered a bug and will throw an exception.
+     */
+    bool opt_VerifyFinalCheckProgress;
+
+    /*
+     * This constant controls how eagerly we expand unrolls in unbounded regex membership tests.
+     */
+    int opt_LCMUnrollStep;
+
+    /*
+     * If NoQuickReturn_IntegerTheory is set to true,
+     * integer theory integration checks that assert axioms
+     * will not return from the function after asserting their axioms.
+     * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect.
+     */
+    bool opt_NoQuickReturn_IntegerTheory;
+
+    /*
+     * If DisableIntegerTheoryIntegration is set to true,
+     * ALL calls to the integer theory integration methods
+     * (get_value, get_len_value, lower_bound, upper_bound)
+     * will ignore what the arithmetic solver believes about length terms,
+     * and will return no information.
+     *
+     * This reduces performance significantly, but can be useful to enable
+     * if it is suspected that string-integer integration, or the arithmetic solver itself,
+     * might have a bug.
+     *
+     * The default behaviour of Z3str2 is to set this to 'false'.
+     */
+    bool opt_DisableIntegerTheoryIntegration;
+
+    /*
+     * If DeferEQCConsistencyCheck is set to true,
+     * expensive calls to new_eq_check() will be deferred until final check,
+     * at which time the consistency of *all* string equivalence classes will be validated.
+     */
+    bool opt_DeferEQCConsistencyCheck;
+
+    /*
+     * If CheckVariableScope is set to true,
+     * pop_scope_eh() and final_check_eh() will run extra checks
+     * to determine whether the current assignment
+     * contains references to any internal variables that are no longer in scope.
+     */
+    bool opt_CheckVariableScope;
+
+    /*
+     * If ConcatOverlapAvoid is set to true,
+     * the check to simplify Concat = Concat in handle_equality() will
+     * avoid simplifying wrt. pairs of Concat terms that will immediately
+     * result in an overlap. (false = Z3str2 behaviour)
+     */
+    bool opt_ConcatOverlapAvoid;
+
+    bool search_started;
+    arith_util m_autil;
+    seq_util u;
+    int sLevel;
+
+    bool finalCheckProgressIndicator;
+
+    expr_ref_vector m_trail; // trail for generated terms
+
+    str_value_factory * m_factory;
+
+    // terms we couldn't go through set_up_axioms() with because they weren't internalized
+    expr_ref_vector m_delayed_axiom_setup_terms;
+
+    ptr_vector<enode> m_basicstr_axiom_todo;
+    svector<std::pair<enode*,enode*> > m_str_eq_todo;
+    ptr_vector<enode> m_concat_axiom_todo;
+    ptr_vector<enode> m_string_constant_length_todo;
+    ptr_vector<enode> m_concat_eval_todo;
+
+    // enode lists for library-aware/high-level string terms (e.g. substr, contains)
+    ptr_vector<enode> m_library_aware_axiom_todo;
+
+    // hashtable of all exprs for which we've already set up term-specific axioms --
+    // this prevents infinite recursive descent with respect to axioms that
+    // include an occurrence of the term for which axioms are being generated
+    obj_hashtable<expr> axiomatized_terms;
+
+    int tmpStringVarCount;
+    int tmpXorVarCount;
+    int tmpLenTestVarCount;
+    int tmpValTestVarCount;
+    std::map<std::pair<expr*, expr*>, std::map<int, expr*> > varForBreakConcat;
+
+    bool avoidLoopCut;
+    bool loopDetected;
+    obj_map<expr, std::stack<T_cut*> > cut_var_map;
+    expr_ref m_theoryStrOverlapAssumption_term;
+
+    obj_hashtable<expr> variable_set;
+    obj_hashtable<expr> internal_variable_set;
+    obj_hashtable<expr> regex_variable_set;
+    std::map<int, std::set<expr*> > internal_variable_scope_levels;
+
+    obj_hashtable<expr> internal_lenTest_vars;
+    obj_hashtable<expr> internal_valTest_vars;
+    obj_hashtable<expr> internal_unrollTest_vars;
+
+    obj_hashtable<expr> input_var_in_len;
+
+    obj_map<expr, unsigned int> fvar_len_count_map;
+    std::map<expr*, ptr_vector<expr> > fvar_lenTester_map;
+    obj_map<expr, expr*> lenTester_fvar_map;
+
+    std::map<expr*, std::map<int, svector<std::pair<int, expr*> > > > fvar_valueTester_map;
+    std::map<expr*, expr*> valueTester_fvar_map;
+
+    std::map<expr*, int_vector> val_range_map;
+
+    // This can't be an expr_ref_vector because the constructor is wrong,
+    // we would need to modify the allocator so we pass in ast_manager
+    std::map<expr*, std::map<std::set<expr*>, ptr_vector<expr> > > unroll_tries_map;
+    std::map<expr*, expr*> unroll_var_map;
+    std::map<std::pair<expr*, expr*>, expr*> concat_eq_unroll_ast_map;
+
+    expr_ref_vector contains_map;
+
+    theory_str_contain_pair_bool_map_t contain_pair_bool_map;
+    //obj_map<expr, obj_pair_set<expr, expr> > contain_pair_idx_map;
+    std::map<expr*, std::set<std::pair<expr*, expr*> > > contain_pair_idx_map;
+
+    std::map<std::pair<expr*, zstring>, expr*> regex_in_bool_map;
+    std::map<expr*, std::set<zstring> > regex_in_var_reg_str_map;
+
+    std::map<expr*, nfa> regex_nfa_cache; // Regex term --> NFA
+
+    char * char_set;
+    std::map<char, int> charSetLookupTable;
+    int charSetSize;
+
+    obj_pair_map<expr, expr, expr*> concat_astNode_map;
+
+    // all (str.to-int) and (int.to-str) terms
+    expr_ref_vector string_int_conversion_terms;
+    obj_hashtable<expr> string_int_axioms;
+
+    // used when opt_FastLengthTesterCache is true
+    rational_map lengthTesterCache;
+    // used when opt_FastValueTesterCache is true
+    string_map valueTesterCache;
+
+    string_map stringConstantCache;
+    unsigned long totalCacheAccessCount;
+    unsigned long cacheHitCount;
+    unsigned long cacheMissCount;
+
+    // cache mapping each string S to Length(S)
+    obj_map<expr, app*> length_ast_map;
+
+    th_union_find m_find;
+    th_trail_stack m_trail_stack;
+    theory_var get_var(expr * n) const;
+    expr * get_eqc_next(expr * n);
+    app * get_ast(theory_var i);
+
+    // binary search heuristic data
+    struct binary_search_info {
+        rational lowerBound;
+        rational midPoint;
+        rational upperBound;
+        rational windowSize;
+
+        binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()),
+                upperBound(rational::zero()), windowSize(rational::zero()) {}
+        binary_search_info(rational lower, rational mid, rational upper, rational windowSize) :
+            lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {}
+
+        void calculate_midpoint() {
+            midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) );
         }
-
-        unsigned m_start_state;
-        unsigned m_end_state;
-
-        std::map<unsigned, std::map<char, unsigned> > transition_map;
-        std::map<unsigned, std::set<unsigned> > epsilon_map;
-
-        void make_transition(unsigned start, char symbol, unsigned end) {
-            transition_map[start][symbol] = end;
-        }
-
-        void make_epsilon_move(unsigned start, unsigned end) {
-            epsilon_map[start].insert(end);
-        }
-
-        // Convert a regular expression to an e-NFA using Thompson's construction
-        void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u);
-
-    public:
-        nfa(seq_util & u, expr * e)
-    : m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) {
-            convert_re(e, m_start_state, m_end_state, u);
-        }
-
-        nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {}
-
-        bool is_valid() const {
-            return m_valid;
-        }
-
-        void epsilon_closure(unsigned start, std::set<unsigned> & closure);
-
-        bool matches(zstring input);
     };
+    // maps a free string var to a stack of active length testers.
+    // can use binary_search_trail to record changes to this object
+    obj_map<expr, ptr_vector<expr> > binary_search_len_tester_stack;
+    // maps a length tester var to the *active* search window
+    obj_map<expr, binary_search_info> binary_search_len_tester_info;
+    // maps a free string var to the first length tester to be (re)used
+    obj_map<expr, expr*> binary_search_starting_len_tester;
+    // maps a length tester to the next length tester to be (re)used if the split is "low"
+    obj_map<expr, expr*> binary_search_next_var_low;
+    // maps a length tester to the next length tester to be (re)used if the split is "high"
+    obj_map<expr, expr*> binary_search_next_var_high;
 
-    class theory_str : public theory {
-        struct T_cut
-        {
-            int level;
-            std::map<expr*, int> vars;
+    // finite model finding data
+    // maps a finite model tester var to a list of variables that will be tested
+    obj_map<expr, ptr_vector<expr> > finite_model_test_varlists;
+protected:
+    void assert_axiom(expr * e);
+    void assert_implication(expr * premise, expr * conclusion);
+    expr * rewrite_implication(expr * premise, expr * conclusion);
 
-            T_cut() {
-              level = -100;
-            }
-        };
+    expr * mk_string(zstring const& str);
+    expr * mk_string(const char * str);
 
-        typedef trail_stack<theory_str> th_trail_stack;
-        typedef union_find<theory_str> th_union_find;
+    app * mk_strlen(expr * e);
+    expr * mk_concat(expr * n1, expr * n2);
+    expr * mk_concat_const_str(expr * n1, expr * n2);
+    app * mk_contains(expr * haystack, expr * needle);
+    app * mk_indexof(expr * haystack, expr * needle);
 
-        typedef map<rational, expr*, obj_hash<rational>, default_eq<rational> > rational_map;
-        struct zstring_hash_proc {
-            unsigned operator()(zstring const & s) const {
-            	return string_hash(s.encode().c_str(), static_cast<unsigned>(s.length()), 17);
-            }
-        };
-        typedef map<zstring, expr*, zstring_hash_proc, default_eq<zstring> > string_map;
+    literal mk_literal(expr* _e);
+    app * mk_int(int n);
+    app * mk_int(rational & q);
 
-    protected:
-        theory_str_params const & m_params;
+    void check_and_init_cut_var(expr * node);
+    void add_cut_info_one_node(expr * baseNode, int slevel, expr * node);
+    void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode);
+    bool has_self_cut(expr * n1, expr * n2);
 
-        /*
-         * Setting EagerStringConstantLengthAssertions to true allows some methods,
-         * in particular internalize_term(), to add
-         * length assertions about relevant string constants.
-         * Note that currently this should always be set to 'true', or else *no* length assertions
-         * will be made about string constants.
-         */
-        bool opt_EagerStringConstantLengthAssertions;
+    // for ConcatOverlapAvoid
+    bool will_result_in_overlap(expr * lhs, expr * rhs);
 
-        /*
-         * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked
-         * without asserting any new axioms is considered a bug and will throw an exception.
-         */
-        bool opt_VerifyFinalCheckProgress;
+    void track_variable_scope(expr * var);
+    app * mk_str_var(std::string name);
+    app * mk_int_var(std::string name);
+    app * mk_nonempty_str_var();
+    app * mk_internal_xor_var();
+    expr * mk_internal_valTest_var(expr * node, int len, int vTries);
+    app * mk_regex_rep_var();
+    app * mk_unroll_bound_var();
+    app * mk_unroll_test_var();
+    void add_nonempty_constraint(expr * s);
 
-        /*
-         * This constant controls how eagerly we expand unrolls in unbounded regex membership tests.
-         */
-        int opt_LCMUnrollStep;
+    void instantiate_concat_axiom(enode * cat);
+    void try_eval_concat(enode * cat);
+    void instantiate_basic_string_axioms(enode * str);
+    void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs);
 
-        /*
-         * If NoQuickReturn_IntegerTheory is set to true,
-         * integer theory integration checks that assert axioms
-         * will not return from the function after asserting their axioms.
-         * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect.
-         */
-        bool opt_NoQuickReturn_IntegerTheory;
+    void instantiate_axiom_CharAt(enode * e);
+    void instantiate_axiom_prefixof(enode * e);
+    void instantiate_axiom_suffixof(enode * e);
+    void instantiate_axiom_Contains(enode * e);
+    void instantiate_axiom_Indexof(enode * e);
+    void instantiate_axiom_Indexof2(enode * e);
+    void instantiate_axiom_LastIndexof(enode * e);
+    void instantiate_axiom_Substr(enode * e);
+    void instantiate_axiom_Replace(enode * e);
+    void instantiate_axiom_str_to_int(enode * e);
+    void instantiate_axiom_int_to_str(enode * e);
 
-        /*
-         * If DisableIntegerTheoryIntegration is set to true,
-         * ALL calls to the integer theory integration methods
-         * (get_value, get_len_value, lower_bound, upper_bound)
-         * will ignore what the arithmetic solver believes about length terms,
-         * and will return no information.
-         *
-         * This reduces performance significantly, but can be useful to enable
-         * if it is suspected that string-integer integration, or the arithmetic solver itself,
-         * might have a bug.
-         *
-         * The default behaviour of Z3str2 is to set this to 'false'.
-         */
-        bool opt_DisableIntegerTheoryIntegration;
+    expr * mk_RegexIn(expr * str, expr * regexp);
+    void instantiate_axiom_RegexIn(enode * e);
+    app * mk_unroll(expr * n, expr * bound);
 
-        /*
-         * If DeferEQCConsistencyCheck is set to true,
-         * expensive calls to new_eq_check() will be deferred until final check,
-         * at which time the consistency of *all* string equivalence classes will be validated.
-         */
-        bool opt_DeferEQCConsistencyCheck;
+    void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr);
+    void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr);
+    void process_concat_eq_unroll(expr * concat, expr * unroll);
 
-        /*
-         * If CheckVariableScope is set to true,
-         * pop_scope_eh() and final_check_eh() will run extra checks
-         * to determine whether the current assignment
-         * contains references to any internal variables that are no longer in scope.
-         */
-        bool opt_CheckVariableScope;
+    void set_up_axioms(expr * ex);
+    void handle_equality(expr * lhs, expr * rhs);
 
-        /*
-         * If ConcatOverlapAvoid is set to true,
-         * the check to simplify Concat = Concat in handle_equality() will
-         * avoid simplifying wrt. pairs of Concat terms that will immediately
-         * result in an overlap. (false = Z3str2 behaviour)
-         */
-        bool opt_ConcatOverlapAvoid;
+    app * mk_value_helper(app * n);
+    expr * get_eqc_value(expr * n, bool & hasEqcValue);
+    expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue);
+    bool in_same_eqc(expr * n1, expr * n2);
+    expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet);
 
-        bool search_started;
-        arith_util m_autil;
-        seq_util u;
-        int sLevel;
+    bool get_value(expr* e, rational& val) const;
+    bool get_len_value(expr* e, rational& val);
+    bool lower_bound(expr* _e, rational& lo);
+    bool upper_bound(expr* _e, rational& hi);
 
-        bool finalCheckProgressIndicator;
-
-        expr_ref_vector m_trail; // trail for generated terms
-
-        str_value_factory * m_factory;
-
-        // terms we couldn't go through set_up_axioms() with because they weren't internalized
-        expr_ref_vector m_delayed_axiom_setup_terms;
-
-        ptr_vector<enode> m_basicstr_axiom_todo;
-        svector<std::pair<enode*,enode*> > m_str_eq_todo;
-        ptr_vector<enode> m_concat_axiom_todo;
-        ptr_vector<enode> m_string_constant_length_todo;
-        ptr_vector<enode> m_concat_eval_todo;
-
-        // enode lists for library-aware/high-level string terms (e.g. substr, contains)
-        ptr_vector<enode> m_library_aware_axiom_todo;
-
-        // hashtable of all exprs for which we've already set up term-specific axioms --
-        // this prevents infinite recursive descent with respect to axioms that
-        // include an occurrence of the term for which axioms are being generated
-        obj_hashtable<expr> axiomatized_terms;
-
-        int tmpStringVarCount;
-        int tmpXorVarCount;
-        int tmpLenTestVarCount;
-        int tmpValTestVarCount;
-        std::map<std::pair<expr*, expr*>, std::map<int, expr*> > varForBreakConcat;
-
-        bool avoidLoopCut;
-        bool loopDetected;
-        obj_map<expr, std::stack<T_cut*> > cut_var_map;
-        expr_ref m_theoryStrOverlapAssumption_term;
-
-        obj_hashtable<expr> variable_set;
-        obj_hashtable<expr> internal_variable_set;
-        obj_hashtable<expr> regex_variable_set;
-        std::map<int, std::set<expr*> > internal_variable_scope_levels;
-
-        obj_hashtable<expr> internal_lenTest_vars;
-        obj_hashtable<expr> internal_valTest_vars;
-        obj_hashtable<expr> internal_unrollTest_vars;
-
-        obj_hashtable<expr> input_var_in_len;
-
-        obj_map<expr, unsigned int> fvar_len_count_map;
-        std::map<expr*, ptr_vector<expr> > fvar_lenTester_map;
-        obj_map<expr, expr*> lenTester_fvar_map;
-
-        std::map<expr*, std::map<int, svector<std::pair<int, expr*> > > > fvar_valueTester_map;
-        std::map<expr*, expr*> valueTester_fvar_map;
-
-        std::map<expr*, int_vector> val_range_map;
-
-        // This can't be an expr_ref_vector because the constructor is wrong,
-        // we would need to modify the allocator so we pass in ast_manager
-        std::map<expr*, std::map<std::set<expr*>, ptr_vector<expr> > > unroll_tries_map;
-        std::map<expr*, expr*> unroll_var_map;
-        std::map<std::pair<expr*, expr*>, expr*> concat_eq_unroll_ast_map;
-
-        expr_ref_vector contains_map;
-
-        theory_str_contain_pair_bool_map_t contain_pair_bool_map;
-        //obj_map<expr, obj_pair_set<expr, expr> > contain_pair_idx_map;
-        std::map<expr*, std::set<std::pair<expr*, expr*> > > contain_pair_idx_map;
-
-        std::map<std::pair<expr*, zstring>, expr*> regex_in_bool_map;
-        std::map<expr*, std::set<zstring> > regex_in_var_reg_str_map;
-
-        std::map<expr*, nfa> regex_nfa_cache; // Regex term --> NFA
-
-        char * char_set;
-        std::map<char, int> charSetLookupTable;
-        int charSetSize;
-
-        obj_pair_map<expr, expr, expr*> concat_astNode_map;
-
-        // all (str.to-int) and (int.to-str) terms
-        expr_ref_vector string_int_conversion_terms;
-        obj_hashtable<expr> string_int_axioms;
-
-        // used when opt_FastLengthTesterCache is true
-        rational_map lengthTesterCache;
-        // used when opt_FastValueTesterCache is true
-        string_map valueTesterCache;
-
-        string_map stringConstantCache;
-        unsigned long totalCacheAccessCount;
-        unsigned long cacheHitCount;
-        unsigned long cacheMissCount;
-
-        // cache mapping each string S to Length(S)
-        obj_map<expr, app*> length_ast_map;
-
-        th_union_find m_find;
-        th_trail_stack m_trail_stack;
-        theory_var get_var(expr * n) const;
-        expr * get_eqc_next(expr * n);
-        app * get_ast(theory_var i);
-
-        // binary search heuristic data
-        struct binary_search_info {
-            rational lowerBound;
-            rational midPoint;
-            rational upperBound;
-            rational windowSize;
-
-            binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()),
-                    upperBound(rational::zero()), windowSize(rational::zero()) {}
-            binary_search_info(rational lower, rational mid, rational upper, rational windowSize) :
-                lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {}
-
-            void calculate_midpoint() {
-                midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) );
-            }
-        };
-        // maps a free string var to a stack of active length testers.
-        // can use binary_search_trail to record changes to this object
-        obj_map<expr, ptr_vector<expr> > binary_search_len_tester_stack;
-        // maps a length tester var to the *active* search window
-        obj_map<expr, binary_search_info> binary_search_len_tester_info;
-        // maps a free string var to the first length tester to be (re)used
-        obj_map<expr, expr*> binary_search_starting_len_tester;
-        // maps a length tester to the next length tester to be (re)used if the split is "low"
-        obj_map<expr, expr*> binary_search_next_var_low;
-        // maps a length tester to the next length tester to be (re)used if the split is "high"
-        obj_map<expr, expr*> binary_search_next_var_high;
-
-        // finite model finding data
-        // maps a finite model tester var to a list of variables that will be tested
-        obj_map<expr, ptr_vector<expr> > finite_model_test_varlists;
-    protected:
-        void assert_axiom(expr * e);
-        void assert_implication(expr * premise, expr * conclusion);
-        expr * rewrite_implication(expr * premise, expr * conclusion);
-
-        expr * mk_string(zstring const& str);
-        expr * mk_string(const char * str);
-
-        app * mk_strlen(expr * e);
-        expr * mk_concat(expr * n1, expr * n2);
-        expr * mk_concat_const_str(expr * n1, expr * n2);
-        app * mk_contains(expr * haystack, expr * needle);
-        app * mk_indexof(expr * haystack, expr * needle);
-
-        literal mk_literal(expr* _e);
-        app * mk_int(int n);
-        app * mk_int(rational & q);
-
-        void check_and_init_cut_var(expr * node);
-        void add_cut_info_one_node(expr * baseNode, int slevel, expr * node);
-        void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode);
-        bool has_self_cut(expr * n1, expr * n2);
-
-        // for ConcatOverlapAvoid
-        bool will_result_in_overlap(expr * lhs, expr * rhs);
-
-        void track_variable_scope(expr * var);
-        app * mk_str_var(std::string name);
-        app * mk_int_var(std::string name);
-        app * mk_nonempty_str_var();
-        app * mk_internal_xor_var();
-        expr * mk_internal_valTest_var(expr * node, int len, int vTries);
-        app * mk_regex_rep_var();
-        app * mk_unroll_bound_var();
-        app * mk_unroll_test_var();
-        void add_nonempty_constraint(expr * s);
-
-        void instantiate_concat_axiom(enode * cat);
-        void try_eval_concat(enode * cat);
-        void instantiate_basic_string_axioms(enode * str);
-        void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs);
-
-        void instantiate_axiom_CharAt(enode * e);
-        void instantiate_axiom_prefixof(enode * e);
-        void instantiate_axiom_suffixof(enode * e);
-        void instantiate_axiom_Contains(enode * e);
-        void instantiate_axiom_Indexof(enode * e);
-        void instantiate_axiom_Indexof2(enode * e);
-        void instantiate_axiom_LastIndexof(enode * e);
-        void instantiate_axiom_Substr(enode * e);
-        void instantiate_axiom_Replace(enode * e);
-        void instantiate_axiom_str_to_int(enode * e);
-        void instantiate_axiom_int_to_str(enode * e);
-
-        expr * mk_RegexIn(expr * str, expr * regexp);
-        void instantiate_axiom_RegexIn(enode * e);
-        app * mk_unroll(expr * n, expr * bound);
-
-        void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr);
-        void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr);
-        void process_concat_eq_unroll(expr * concat, expr * unroll);
-
-        void set_up_axioms(expr * ex);
-        void handle_equality(expr * lhs, expr * rhs);
-
-        app * mk_value_helper(app * n);
-        expr * get_eqc_value(expr * n, bool & hasEqcValue);
-        expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue);
-        bool in_same_eqc(expr * n1, expr * n2);
-        expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet);
-
-        bool get_value(expr* e, rational& val) const;
-        bool get_len_value(expr* e, rational& val);
-        bool lower_bound(expr* _e, rational& lo);
-        bool upper_bound(expr* _e, rational& hi);
-
-        bool can_two_nodes_eq(expr * n1, expr * n2);
-        bool can_concat_eq_str(expr * concat, zstring& str);
-        bool can_concat_eq_concat(expr * concat1, expr * concat2);
-        bool check_concat_len_in_eqc(expr * concat);
-        bool check_length_consistency(expr * n1, expr * n2);
-        bool check_length_const_string(expr * n1, expr * constStr);
-        bool check_length_eq_var_concat(expr * n1, expr * n2);
-        bool check_length_concat_concat(expr * n1, expr * n2);
-        bool check_length_concat_var(expr * concat, expr * var);
-        bool check_length_var_var(expr * var1, expr * var2);
-        void check_contain_in_new_eq(expr * n1, expr * n2);
-        void check_contain_by_eqc_val(expr * varNode, expr * constNode);
-        void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass);
-        void check_contain_by_eq_nodes(expr * n1, expr * n2);
-        bool in_contain_idx_map(expr * n);
-        void compute_contains(std::map<expr*, expr*> & varAliasMap,
-                std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr *> & varConstMap,
-                std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap);
-        expr * dealias_node(expr * node, std::map<expr*, expr*> & varAliasMap, std::map<expr*, expr*> & concatAliasMap);
-        void get_grounded_concats(expr* node, std::map<expr*, expr*> & varAliasMap,
-                std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr*> & varConstMap,
-                std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap,
-                std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap);
-        void print_grounded_concat(expr * node, std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap);
-        void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar,
+    bool can_two_nodes_eq(expr * n1, expr * n2);
+    bool can_concat_eq_str(expr * concat, zstring& str);
+    bool can_concat_eq_concat(expr * concat1, expr * concat2);
+    bool check_concat_len_in_eqc(expr * concat);
+    bool check_length_consistency(expr * n1, expr * n2);
+    bool check_length_const_string(expr * n1, expr * constStr);
+    bool check_length_eq_var_concat(expr * n1, expr * n2);
+    bool check_length_concat_concat(expr * n1, expr * n2);
+    bool check_length_concat_var(expr * concat, expr * var);
+    bool check_length_var_var(expr * var1, expr * var2);
+    void check_contain_in_new_eq(expr * n1, expr * n2);
+    void check_contain_by_eqc_val(expr * varNode, expr * constNode);
+    void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass);
+    void check_contain_by_eq_nodes(expr * n1, expr * n2);
+    bool in_contain_idx_map(expr * n);
+    void compute_contains(std::map<expr*, expr*> & varAliasMap,
+            std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr *> & varConstMap,
+            std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap);
+    expr * dealias_node(expr * node, std::map<expr*, expr*> & varAliasMap, std::map<expr*, expr*> & concatAliasMap);
+    void get_grounded_concats(expr* node, std::map<expr*, expr*> & varAliasMap,
+            std::map<expr*, expr*> & concatAliasMap, std::map<expr*, expr*> & varConstMap,
+            std::map<expr*, expr*> & concatConstMap, std::map<expr*, std::map<expr*, int> > & varEqConcatMap,
             std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap);
-        bool is_partial_in_grounded_concat(const std::vector<expr*> & strVec, const std::vector<expr*> & subStrVec);
+    void print_grounded_concat(expr * node, std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap);
+    void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar,
+            std::map<expr*, std::map<std::vector<expr*>, std::set<expr*> > > & groundedMap);
+    bool is_partial_in_grounded_concat(const std::vector<expr*> & strVec, const std::vector<expr*> & subStrVec);
 
-        void get_nodes_in_concat(expr * node, ptr_vector<expr> & nodeList);
-        expr * simplify_concat(expr * node);
+    void get_nodes_in_concat(expr * node, ptr_vector<expr> & nodeList);
+    expr * simplify_concat(expr * node);
 
-        void simplify_parent(expr * nn, expr * eq_str);
+    void simplify_parent(expr * nn, expr * eq_str);
 
-        void simplify_concat_equality(expr * lhs, expr * rhs);
-        void solve_concat_eq_str(expr * concat, expr * str);
+    void simplify_concat_equality(expr * lhs, expr * rhs);
+    void solve_concat_eq_str(expr * concat, expr * str);
 
-        void infer_len_concat_equality(expr * nn1, expr * nn2);
-        bool infer_len_concat(expr * n, rational & nLen);
-        void infer_len_concat_arg(expr * n, rational len);
+    void infer_len_concat_equality(expr * nn1, expr * nn2);
+    bool infer_len_concat(expr * n, rational & nLen);
+    void infer_len_concat_arg(expr * n, rational len);
 
-        bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2);
-        bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2);
-        bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2);
-        bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2);
-        bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2);
-        bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2);
+    bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2);
+    bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2);
+    bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2);
+    bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2);
+    bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2);
+    bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2);
 
-        void process_concat_eq_type1(expr * concatAst1, expr * concatAst2);
-        void process_concat_eq_type2(expr * concatAst1, expr * concatAst2);
-        void process_concat_eq_type3(expr * concatAst1, expr * concatAst2);
-        void process_concat_eq_type4(expr * concatAst1, expr * concatAst2);
-        void process_concat_eq_type5(expr * concatAst1, expr * concatAst2);
-        void process_concat_eq_type6(expr * concatAst1, expr * concatAst2);
+    void process_concat_eq_type1(expr * concatAst1, expr * concatAst2);
+    void process_concat_eq_type2(expr * concatAst1, expr * concatAst2);
+    void process_concat_eq_type3(expr * concatAst1, expr * concatAst2);
+    void process_concat_eq_type4(expr * concatAst1, expr * concatAst2);
+    void process_concat_eq_type5(expr * concatAst1, expr * concatAst2);
+    void process_concat_eq_type6(expr * concatAst1, expr * concatAst2);
 
-        void print_cut_var(expr * node, std::ofstream & xout);
+    void print_cut_var(expr * node, std::ofstream & xout);
 
-        void generate_mutual_exclusion(expr_ref_vector & exprs);
-        void add_theory_aware_branching_info(expr * term, double priority, lbool phase);
+    void generate_mutual_exclusion(expr_ref_vector & exprs);
+    void add_theory_aware_branching_info(expr * term, double priority, lbool phase);
 
-        bool new_eq_check(expr * lhs, expr * rhs);
-        void group_terms_by_eqc(expr * n, std::set<expr*> & concats, std::set<expr*> & vars, std::set<expr*> & consts);
+    bool new_eq_check(expr * lhs, expr * rhs);
+    void group_terms_by_eqc(expr * n, std::set<expr*> & concats, std::set<expr*> & vars, std::set<expr*> & consts);
 
-        int ctx_dep_analysis(std::map<expr*, int> & strVarMap, std::map<expr*, int> & freeVarMap,
-        		std::map<expr*, std::set<expr*> > & unrollGroupMap, std::map<expr*, std::map<expr*, int> > & var_eq_concat_map);
-        void trace_ctx_dep(std::ofstream & tout,
-                std::map<expr*, expr*> & aliasIndexMap,
-                std::map<expr*, expr*> & var_eq_constStr_map,
-                std::map<expr*, std::map<expr*, int> > & var_eq_concat_map,
-				std::map<expr*, std::map<expr*, int> > & var_eq_unroll_map,
-                std::map<expr*, expr*> & concat_eq_constStr_map,
-                std::map<expr*, std::map<expr*, int> > & concat_eq_concat_map,
-				std::map<expr*, std::set<expr*> > & unrollGroupMap);
+    int ctx_dep_analysis(std::map<expr*, int> & strVarMap, std::map<expr*, int> & freeVarMap,
+            std::map<expr*, std::set<expr*> > & unrollGroupMap, std::map<expr*, std::map<expr*, int> > & var_eq_concat_map);
+    void trace_ctx_dep(std::ofstream & tout,
+            std::map<expr*, expr*> & aliasIndexMap,
+            std::map<expr*, expr*> & var_eq_constStr_map,
+            std::map<expr*, std::map<expr*, int> > & var_eq_concat_map,
+            std::map<expr*, std::map<expr*, int> > & var_eq_unroll_map,
+            std::map<expr*, expr*> & concat_eq_constStr_map,
+            std::map<expr*, std::map<expr*, int> > & concat_eq_concat_map,
+            std::map<expr*, std::set<expr*> > & unrollGroupMap);
 
-        void classify_ast_by_type(expr * node, std::map<expr*, int> & varMap,
-        		std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap);
-        void classify_ast_by_type_in_positive_context(std::map<expr*, int> & varMap,
-        		std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap);
+    void classify_ast_by_type(expr * node, std::map<expr*, int> & varMap,
+            std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap);
+    void classify_ast_by_type_in_positive_context(std::map<expr*, int> & varMap,
+            std::map<expr*, int> & concatMap, std::map<expr*, int> & unrollMap);
 
-        expr * mk_internal_lenTest_var(expr * node, int lTries);
-        expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue);
-        void process_free_var(std::map<expr*, int> & freeVar_map);
-        expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries);
-        expr * gen_free_var_options(expr * freeVar, expr * len_indicator,
-        		zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr);
-        expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator,
-        		zstring lenStr, int tries);
-        void print_value_tester_list(svector<std::pair<int, expr*> > & testerList);
-        bool get_next_val_encode(int_vector & base, int_vector & next);
-        zstring gen_val_string(int len, int_vector & encoding);
+    expr * mk_internal_lenTest_var(expr * node, int lTries);
+    expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue);
+    void process_free_var(std::map<expr*, int> & freeVar_map);
+    expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries);
+    expr * gen_free_var_options(expr * freeVar, expr * len_indicator,
+            zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr);
+    expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator,
+            zstring lenStr, int tries);
+    void print_value_tester_list(svector<std::pair<int, expr*> > & testerList);
+    bool get_next_val_encode(int_vector & base, int_vector & next);
+    zstring gen_val_string(int len, int_vector & encoding);
 
-        // binary search heuristic
-        expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue);
-        expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits);
+    // binary search heuristic
+    expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue);
+    expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits);
 
-        bool free_var_attempt(expr * nn1, expr * nn2);
-        void more_len_tests(expr * lenTester, zstring lenTesterValue);
-        void more_value_tests(expr * valTester, zstring valTesterValue);
+    bool free_var_attempt(expr * nn1, expr * nn2);
+    void more_len_tests(expr * lenTester, zstring lenTesterValue);
+    void more_value_tests(expr * valTester, zstring valTesterValue);
 
-        expr * get_alias_index_ast(std::map<expr*, expr*> & aliasIndexMap, expr * node);
-        expr * getMostLeftNodeInConcat(expr * node);
-        expr * getMostRightNodeInConcat(expr * node);
-        void get_var_in_eqc(expr * n, std::set<expr*> & varSet);
-        void get_concats_in_eqc(expr * n, std::set<expr*> & concats);
-        void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList);
-        expr * eval_concat(expr * n1, expr * n2);
+    expr * get_alias_index_ast(std::map<expr*, expr*> & aliasIndexMap, expr * node);
+    expr * getMostLeftNodeInConcat(expr * node);
+    expr * getMostRightNodeInConcat(expr * node);
+    void get_var_in_eqc(expr * n, std::set<expr*> & varSet);
+    void get_concats_in_eqc(expr * n, std::set<expr*> & concats);
+    void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList);
+    expr * eval_concat(expr * n1, expr * n2);
 
-        bool finalcheck_str2int(app * a);
-        bool finalcheck_int2str(app * a);
+    bool finalcheck_str2int(app * a);
+    bool finalcheck_int2str(app * a);
 
-        // strRegex
+    // strRegex
 
-        void get_eqc_allUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet);
-        void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet);
-        void gen_assign_unroll_reg(std::set<expr*> & unrolls);
-        expr * gen_assign_unroll_Str2Reg(expr * n, std::set<expr*> & unrolls);
-        expr * gen_unroll_conditional_options(expr * var, std::set<expr*> & unrolls, zstring lcmStr);
-        expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h);
-        void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items);
-        void check_regex_in(expr * nn1, expr * nn2);
-        zstring get_std_regex_str(expr * r);
+    void get_eqc_allUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet);
+    void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set<expr*> & unrollFuncSet);
+    void gen_assign_unroll_reg(std::set<expr*> & unrolls);
+    expr * gen_assign_unroll_Str2Reg(expr * n, std::set<expr*> & unrolls);
+    expr * gen_unroll_conditional_options(expr * var, std::set<expr*> & unrolls, zstring lcmStr);
+    expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h);
+    void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items);
+    void check_regex_in(expr * nn1, expr * nn2);
+    zstring get_std_regex_str(expr * r);
 
-        void dump_assignments();
-        void initialize_charset();
+    void dump_assignments();
+    void initialize_charset();
 
-        void check_variable_scope();
-        void recursive_check_variable_scope(expr * ex);
+    void check_variable_scope();
+    void recursive_check_variable_scope(expr * ex);
 
-        void collect_var_concat(expr * node, std::set<expr*> & varSet, std::set<expr*> & concatSet);
-        bool propagate_length(std::set<expr*> & varSet, std::set<expr*> & concatSet, std::map<expr*, int> & exprLenMap);
-        void get_unique_non_concat_nodes(expr * node, std::set<expr*> & argSet);
-        bool propagate_length_within_eqc(expr * var);
+    void collect_var_concat(expr * node, std::set<expr*> & varSet, std::set<expr*> & concatSet);
+    bool propagate_length(std::set<expr*> & varSet, std::set<expr*> & concatSet, std::map<expr*, int> & exprLenMap);
+    void get_unique_non_concat_nodes(expr * node, std::set<expr*> & argSet);
+    bool propagate_length_within_eqc(expr * var);
 
-        // TESTING
-        void refresh_theory_var(expr * e);
+    // TESTING
+    void refresh_theory_var(expr * e);
 
-        expr_ref set_up_finite_model_test(expr * lhs, expr * rhs);
-        void finite_model_test(expr * v, expr * c);
+    expr_ref set_up_finite_model_test(expr * lhs, expr * rhs);
+    void finite_model_test(expr * v, expr * c);
 
-    public:
-        theory_str(ast_manager & m, theory_str_params const & params);
-        virtual ~theory_str();
+public:
+    theory_str(ast_manager & m, theory_str_params const & params);
+    virtual ~theory_str();
 
-        virtual char const * get_name() const { return "seq"; }
-        virtual void display(std::ostream & out) const;
+    virtual char const * get_name() const { return "seq"; }
+    virtual void display(std::ostream & out) const;
 
-        bool overlapping_variables_detected() const { return loopDetected; }
+    bool overlapping_variables_detected() const { return loopDetected; }
 
-        th_trail_stack& get_trail_stack() { return m_trail_stack; }
-        void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2) {}
-        void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { }
-        void unmerge_eh(theory_var v1, theory_var v2) {}
-    protected:
-        virtual bool internalize_atom(app * atom, bool gate_ctx);
-        virtual bool internalize_term(app * term);
-        virtual enode* ensure_enode(expr* e);
-        virtual theory_var mk_var(enode * n);
+    th_trail_stack& get_trail_stack() { return m_trail_stack; }
+    void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2) {}
+    void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { }
+    void unmerge_eh(theory_var v1, theory_var v2) {}
+protected:
+    virtual bool internalize_atom(app * atom, bool gate_ctx);
+    virtual bool internalize_term(app * term);
+    virtual enode* ensure_enode(expr* e);
+    virtual theory_var mk_var(enode * n);
 
-        virtual void new_eq_eh(theory_var, theory_var);
-        virtual void new_diseq_eh(theory_var, theory_var);
+    virtual void new_eq_eh(theory_var, theory_var);
+    virtual void new_diseq_eh(theory_var, theory_var);
 
-        virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); }
-        virtual void init_search_eh();
-        virtual void add_theory_assumptions(expr_ref_vector & assumptions);
-        virtual lbool validate_unsat_core(expr_ref_vector & unsat_core);
-        virtual void relevant_eh(app * n);
-        virtual void assign_eh(bool_var v, bool is_true);
-        virtual void push_scope_eh();
-        virtual void pop_scope_eh(unsigned num_scopes);
-        virtual void reset_eh();
+    virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); }
+    virtual void init_search_eh();
+    virtual void add_theory_assumptions(expr_ref_vector & assumptions);
+    virtual lbool validate_unsat_core(expr_ref_vector & unsat_core);
+    virtual void relevant_eh(app * n);
+    virtual void assign_eh(bool_var v, bool is_true);
+    virtual void push_scope_eh();
+    virtual void pop_scope_eh(unsigned num_scopes);
+    virtual void reset_eh();
 
-        virtual bool can_propagate();
-        virtual void propagate();
+    virtual bool can_propagate();
+    virtual void propagate();
 
-        virtual final_check_status final_check_eh();
-        virtual void attach_new_th_var(enode * n);
+    virtual final_check_status final_check_eh();
+    virtual void attach_new_th_var(enode * n);
 
-        virtual void init_model(model_generator & m);
-        virtual model_value_proc * mk_value(enode * n, model_generator & mg);
-        virtual void finalize_model(model_generator & mg);
-    };
+    virtual void init_model(model_generator & m);
+    virtual model_value_proc * mk_value(enode * n, model_generator & mg);
+    virtual void finalize_model(model_generator & mg);
+};
 
 };