@@ -224,6 +224,12 @@ void BasicRewriteRCons::ensureProofForTheoryRewrite(CDProof* cdp,
224
224
handledMacro = true ;
225
225
}
226
226
break ;
227
+ case ProofRewriteRule::MACRO_STR_EQ_LEN_UNIFY_PREFIX:
228
+ if (ensureProofMacroStrEqLenUnifyPrefix (cdp, eq))
229
+ {
230
+ handledMacro = true ;
231
+ }
232
+ break ;
227
233
case ProofRewriteRule::MACRO_STR_EQ_LEN_UNIFY:
228
234
if (ensureProofMacroStrEqLenUnify (cdp, eq))
229
235
{
@@ -926,6 +932,229 @@ bool BasicRewriteRCons::ensureProofMacroSubstrStripSymLength(CDProof* cdp,
926
932
return true ;
927
933
}
928
934
935
+
936
+ bool BasicRewriteRCons::ensureProofMacroStrEqLenUnifyPrefix (CDProof* cdp,
937
+ const Node& eq)
938
+ {
939
+ Trace (" brc-macro" ) << " Expand macro str eq len unify prefix " << eq
940
+ << std::endl;
941
+ NodeManager* nm = nodeManager ();
942
+ theory::strings::ArithEntail ae (nullptr );
943
+ theory::strings::StringsEntail sent (nullptr , ae);
944
+
945
+ Assert (eq[1 ].getKind () == Kind::AND);
946
+ Node eq1p = eq[1 ];
947
+ // get the equations equal empty
948
+ // we group (and (= s t) (= t1 "") ... (= tn "")) to
949
+ // (and (= s t) (and (= t1 "") ... (= tn ""))) and store in eq1p.
950
+ std::vector<Node> empeqs;
951
+ if (eq[1 ].getNumChildren () > 2 )
952
+ {
953
+ empeqs.insert (empeqs.end (), eq[1 ].begin () + 1 , eq[1 ].end ());
954
+ Node eq1g = nm->mkAnd (empeqs);
955
+ eq1p = nm->mkNode (Kind::AND, eq[1 ][0 ], eq1g);
956
+ Node eqg = eq1p.eqNode (eq[1 ]);
957
+ cdp->addStep (eqg, ProofRule::ACI_NORM, {}, {eqg});
958
+ }
959
+ else
960
+ {
961
+ empeqs.push_back (eq[1 ][1 ]);
962
+ }
963
+
964
+ // prove eq[0] => eq1p in cdfwd
965
+ CDProof cdfwd (d_env);
966
+ Node eqsrc = eq[0 ];
967
+ Node ret = sent.inferEqsFromContains (eqsrc[0 ], eqsrc[1 ]);
968
+ Trace (" brc-macro" ) << " [1] setup forward implication" << std::endl;
969
+ bool eqFlipped = false ;
970
+ if (ret.isNull ())
971
+ {
972
+ Trace (" brc-macro" ) << " ...failed " << ret << " , try flip" << std::endl;
973
+ eqsrc = eq[0 ][1 ].eqNode (eq[0 ][0 ]);
974
+ ret = sent.inferEqsFromContains (eqsrc[0 ], eqsrc[1 ]);
975
+ if (ret.isNull ())
976
+ {
977
+ Trace (" brc-macro" ) << " ... failed to replicate " << ret << std::endl;
978
+ return false ;
979
+ }
980
+ eqFlipped = true ;
981
+ cdfwd.addStep (eqsrc, ProofRule::SYMM, {eq[0 ]}, {});
982
+ }
983
+ Node len1 = nm->mkNode (Kind::STRING_LENGTH, eqsrc[0 ]);
984
+ Node len2 = nm->mkNode (Kind::STRING_LENGTH, eqsrc[1 ]);
985
+ Node leneq = proveCong (&cdfwd, len1, {eqsrc});
986
+ Node li[2 ];
987
+ std::vector<Node> eqi;
988
+ eqi.resize (2 );
989
+ for (size_t i = 0 ; i < 2 ; i++)
990
+ {
991
+ Node l = i == 0 ? len1 : len2;
992
+ TConvProofGenerator tcpg (d_env, nullptr );
993
+ li[i] = ae.rewriteLengthIntro (l, &tcpg);
994
+ if (li[i] != l)
995
+ {
996
+ Node equiv = l.eqNode (li[i]);
997
+ std::shared_ptr<ProofNode> pfn = tcpg.getProofFor (equiv);
998
+ cdfwd.addProof (pfn);
999
+ eqi[i] = pfn->getResult ();
1000
+ }
1001
+ }
1002
+ Node leneqi = li[0 ].eqNode (li[1 ]);
1003
+ if (leneqi != leneq)
1004
+ {
1005
+ Node equiv = proveCong (&cdfwd, leneq, eqi);
1006
+ cdfwd.addStep (leneqi, ProofRule::EQ_RESOLVE, {leneq, equiv}, {});
1007
+ }
1008
+ Trace (" brc-macro" ) << " ...length: " << li[0 ] << " == " << li[1 ] << std::endl;
1009
+ // based on swapping above, we should have len1i >= len2i
1010
+ Node diff = nm->mkNode (Kind::SUB, li[1 ], li[0 ]);
1011
+ Node diffn = theory::arith::PolyNorm::getPolyNorm (diff);
1012
+ Trace (" brc-macro" ) << " ...norm diff " << diffn << std::endl;
1013
+ Node diffneqz = diffn.eqNode (nm->mkConstInt (Rational (0 )));
1014
+ Node equiv = leneqi.eqNode (diffneqz);
1015
+ if (!ensureProofArithPolyNormRel (&cdfwd, equiv))
1016
+ {
1017
+ Trace (" brc-macro" ) << " ... failed poly norm rel" << std::endl;
1018
+ return false ;
1019
+ }
1020
+ cdfwd.addStep (diffneqz, ProofRule::EQ_RESOLVE, {leneqi, equiv}, {});
1021
+ Trace (" brc-macro" ) << " ...have " << diffneqz << std::endl;
1022
+
1023
+ // get the concatenation term corresponding to the components equated to empty
1024
+ std::vector<Node> concat;
1025
+ std::map<Node, Node> empMap;
1026
+ Assert (eq1p.getKind () == Kind::AND && eq1p.getNumChildren () == 2 );
1027
+ for (const Node& ee : empeqs)
1028
+ {
1029
+ Assert (ee.getKind () == Kind::EQUAL && ee[0 ].getType ().isStringLike ());
1030
+ concat.push_back (ee[0 ]);
1031
+ empMap[ee[0 ]] = ee;
1032
+ }
1033
+ TypeNode stype = concat[0 ].getType ();
1034
+ Node cc = theory::strings::utils::mkConcat (concat, stype);
1035
+ Node lcc = nm->mkNode (Kind::STRING_LENGTH, cc);
1036
+ TConvProofGenerator tcpg (d_env, nullptr );
1037
+ Node lcci = ae.rewriteLengthIntro (lcc, &tcpg);
1038
+ Trace (" brc-macro" ) << " ...normalized concat length " << lcci << std::endl;
1039
+ Node lcceq = lcc.eqNode (lcci);
1040
+ std::shared_ptr<ProofNode> pfn = tcpg.getProofFor (lcceq);
1041
+ cdfwd.addProof (pfn);
1042
+
1043
+ // the length of the empty components should be equal to the difference
1044
+ // the length of the equality before and after removing empty components
1045
+ Node pnEq = lcci.eqNode (diffneqz[0 ]);
1046
+ if (!cdfwd.addStep (pnEq, ProofRule::ARITH_POLY_NORM, {}, {pnEq}))
1047
+ {
1048
+ Trace (" brc-macro" ) << " ...fail poly norm " << pnEq << std::endl;
1049
+ return false ;
1050
+ }
1051
+ Node pnEq2 = lcc.eqNode (diffneqz[1 ]);
1052
+ cdfwd.addStep (pnEq2, ProofRule::TRANS, {lcceq, pnEq, diffneqz}, {});
1053
+ // now have proven (str.len (str.++ t1 ... tn)) = 0,
1054
+ // need t1 = "" ^ ... ^ tn = ""
1055
+ Trace (" brc-macro" ) << " ...have " << pnEq2 << std::endl;
1056
+ Node eqconv = pnEq2.eqNode (eq1p[1 ]);
1057
+ Trace (" brc-macro" ) << " - subgoal " << eqconv << std::endl;
1058
+ // subgoal, which can be filled with RARE rules
1059
+ // str-len-eq-zero-concat-rec and str-len-eq-zero-base
1060
+ cdfwd.addTrustedStep (eqconv, TrustId::MACRO_THEORY_REWRITE_RCONS, {}, {});
1061
+ cdfwd.addStep (eq1p[1 ], ProofRule::EQ_RESOLVE, {pnEq2, eqconv}, {});
1062
+
1063
+ // proves (=> (and t="") (= (= (str.++ s t) r) (= s r)))
1064
+ CDProof cdmid (d_env);
1065
+ Node srcRew = eqsrc[1 ];
1066
+ Trace (" brc-macro" ) << " [2] source term to rewrite " << srcRew << std::endl;
1067
+ Assert (srcRew.getKind () == Kind::STRING_CONCAT);
1068
+ std::vector<Node> eqe;
1069
+ std::map<Node, Node>::iterator it;
1070
+ for (const Node& tc : srcRew)
1071
+ {
1072
+ it = empMap.find (tc);
1073
+ if (it != empMap.end ())
1074
+ {
1075
+ eqe.push_back (it->second );
1076
+ }
1077
+ else
1078
+ {
1079
+ eqe.push_back (Node::null ());
1080
+ }
1081
+ }
1082
+ Node cres = proveCong (&cdmid, srcRew, eqe);
1083
+ if (cres.isNull ())
1084
+ {
1085
+ Trace (" brc-macro" ) << " ...fail cong" << std::endl;
1086
+ return false ;
1087
+ }
1088
+ Trace (" brc-macro" ) << " ...cong to " << cres[1 ] << std::endl;
1089
+ Node tgtRew = eq1p[0 ][eqFlipped ? 0 : 1 ];
1090
+ Trace (" brc-macro" ) << " ...target is " << tgtRew << std::endl;
1091
+
1092
+ Node eqacin = cres[1 ].eqNode (tgtRew);
1093
+ if (!cdmid.addStep (eqacin, ProofRule::ACI_NORM, {}, {eqacin}))
1094
+ {
1095
+ Trace (" brc-macro" ) << " ...fail aci norm" << std::endl;
1096
+ return false ;
1097
+ }
1098
+ Trace (" brc-macro" ) << " ...proved subs empty " << eqacin << std::endl;
1099
+ Node teq = srcRew.eqNode (tgtRew);
1100
+ cdmid.addStep (teq, ProofRule::TRANS, {cres, eqacin}, {});
1101
+
1102
+ Node eqqeq = eq[0 ].eqNode (eq1p[0 ]);
1103
+ std::vector<Node> eqee;
1104
+ for (size_t i = 0 ; i < 2 ; i++)
1105
+ {
1106
+ Node eee = eq[0 ][i].eqNode (eq1p[0 ][i]);
1107
+ eqee.push_back (eee);
1108
+ if (eee[0 ] == eee[1 ])
1109
+ {
1110
+ Node refl = eee[0 ].eqNode (eee[0 ]);
1111
+ cdmid.addStep (refl, ProofRule::REFL, {}, {eee[0 ]});
1112
+ }
1113
+ }
1114
+ std::vector<Node> cargs;
1115
+ ProofRule cr = expr::getCongRule (eq[0 ], cargs);
1116
+ cdmid.addStep (eqqeq, cr, eqee, cargs);
1117
+ Node implMid = nm->mkNode (Kind::IMPLIES, eq1p[1 ], eqqeq);
1118
+ cdmid.addStep (implMid, ProofRule::SCOPE, {eqqeq}, empeqs);
1119
+ Trace (" brc-macro" ) << " ...intermediate result: " << implMid << std::endl;
1120
+ std::shared_ptr<ProofNode> pfmid = cdmid.getProofFor (implMid);
1121
+ Assert (implMid[1 ][0 ] == eq[0 ]);
1122
+ Assert (implMid[1 ][1 ] == eq1p[0 ]);
1123
+
1124
+ // finish the forward proof
1125
+ cdfwd.addProof (pfmid);
1126
+ cdfwd.addStep (implMid[1 ], ProofRule::MODUS_PONENS, {eq1p[1 ], implMid}, {});
1127
+ cdfwd.addStep (eq1p[0 ], ProofRule::EQ_RESOLVE, {eq[0 ], implMid[1 ]}, {});
1128
+ cdfwd.addStep (eq1p, ProofRule::AND_INTRO, {eq1p[0 ], eq1p[1 ]}, {});
1129
+ Node impl = nm->mkNode (Kind::IMPLIES, eq[0 ], eq1p);
1130
+ cdfwd.addStep (impl, ProofRule::SCOPE, {eq1p}, {eq[0 ]});
1131
+ cdp->addProof (cdfwd.getProofFor (impl));
1132
+
1133
+ // reverse proof is easy
1134
+ CDProof cdrev (d_env);
1135
+ cdrev.addProof (pfmid);
1136
+ cdrev.addStep (implMid[1 ], ProofRule::MODUS_PONENS, {eq1p[1 ], implMid}, {});
1137
+ Node equivs = implMid[1 ][1 ].eqNode (implMid[1 ][0 ]);
1138
+ cdrev.addStep (equivs, ProofRule::SYMM, {implMid[1 ]}, {});
1139
+ cdrev.addStep (
1140
+ implMid[1 ][0 ], ProofRule::EQ_RESOLVE, {implMid[1 ][1 ], equivs}, {});
1141
+ Node implrev = nm->mkNode (Kind::IMPLIES, eq1p, eq[0 ]);
1142
+ cdrev.addStep (implrev, ProofRule::SCOPE, {eq[0 ]}, {eq1p[0 ], eq1p[1 ]});
1143
+ cdp->addProof (cdrev.getProofFor (implrev));
1144
+
1145
+ // dual implication
1146
+ Node eqfinal = proveDualImplication (cdp, impl, implrev);
1147
+
1148
+ // if we grouped the empty equations, we close with a transitive step
1149
+ // which we added via ACI_NORM above
1150
+ if (eq1p != eq[1 ])
1151
+ {
1152
+ cdp->addStep (eq, ProofRule::TRANS, {eqfinal, eq1p.eqNode (eq[1 ])}, {});
1153
+ }
1154
+
1155
+ return true ;
1156
+ }
1157
+
929
1158
bool BasicRewriteRCons::ensureProofMacroStrEqLenUnify (CDProof* cdp,
930
1159
const Node& eq)
931
1160
{
0 commit comments