cvc5-cvc5-1.2.0.src.proof.lfsc.lfsc_printer.cpp Maven / Gradle / Ivy
The newest version!
/******************************************************************************
* Top contributors (to current version):
* Andrew Reynolds, Hans-Jörg Schurr, Abdalrhman Mohamed
*
* This file is part of the cvc5 project.
*
* Copyright (c) 2009-2024 by the authors listed in the file AUTHORS
* in the top-level source directory and their institutional affiliations.
* All rights reserved. See the file COPYING in the top-level source
* directory for licensing information.
* ****************************************************************************
*
* The printer for LFSC proofs
*/
#include "proof/lfsc/lfsc_printer.h"
#include
#include "expr/dtype.h"
#include "expr/dtype_cons.h"
#include "expr/dtype_selector.h"
#include "expr/node_algorithm.h"
#include "expr/skolem_manager.h"
#include "options/proof_options.h"
#include "proof/lfsc/lfsc_list_sc_node_converter.h"
#include "proof/lfsc/lfsc_print_channel.h"
using namespace cvc5::internal::kind;
using namespace cvc5::internal::rewriter;
namespace cvc5::internal {
namespace proof {
LfscPrinter::LfscPrinter(Env& env,
LfscNodeConverter& ltp,
rewriter::RewriteDb* rdb)
: EnvObj(env),
d_tproc(ltp),
d_assumpCounter(0),
d_trustChildPletCounter(0),
d_termLetPrefix("t"),
d_assumpPrefix("a"),
d_pletPrefix("p"),
d_pletTrustChildPrefix("q"),
d_rdb(rdb)
{
NodeManager* nm = nodeManager();
d_boolType = nm->booleanType();
// used for the `flag` type in LFSC
d_tt = d_tproc.mkInternalSymbol("tt", d_boolType);
d_ff = d_tproc.mkInternalSymbol("ff", d_boolType);
}
void LfscPrinter::print(std::ostream& out, const ProofNode* pn)
{
Trace("lfsc-print-debug") << "; ORIGINAL PROOF: " << *pn << std::endl;
Assert (!pn->getChildren().empty());
// closing parentheses
std::stringstream cparen;
const std::vector& definitions = pn->getArguments();
std::unordered_set definedSymbols;
for (const Node& n : definitions)
{
definedSymbols.insert(n[0]);
// Note that we don't have to convert it via the term processor (for the
// sake of inferring declared symbols), since this is already done in the
// lfsc post processor update method for the outermost SCOPE.
}
const std::vector& assertions = pn->getChildren()[0]->getArguments();
const ProofNode* pnBody = pn->getChildren()[0]->getChildren()[0].get();
// clear the rules we have warned about
d_trustWarned.clear();
// [1] convert assertions to internal and set up assumption map
Trace("lfsc-print-debug") << "; print declarations" << std::endl;
std::vector iasserts;
std::map passumeMap;
for (size_t i = 0, nasserts = assertions.size(); i < nasserts; i++)
{
Node a = assertions[i];
iasserts.push_back(d_tproc.convert(a));
// remember the assumption name
passumeMap[a] = i;
}
d_assumpCounter = assertions.size();
// [2] compute the proof letification
Trace("lfsc-print-debug") << "; compute proof letification" << std::endl;
std::vector pletList;
std::map pletMap;
computeProofLetification(pnBody, pletList, pletMap);
// [3] compute the global term letification and declared symbols and types
Trace("lfsc-print-debug")
<< "; compute global term letification and declared symbols" << std::endl;
LetBinding lbind(d_termLetPrefix);
for (const Node& ia : iasserts)
{
lbind.process(ia);
}
// We do a "dry-run" of proof printing here, using the LetBinding print
// channel. This pass traverses the proof but does not print it, but instead
// updates the let binding data structure for all nodes that appear anywhere
// in the proof. It is also important for the term processor for collecting
// symbols and types that are used in the proof.
LfscPrintChannelPre lpcp(lbind);
LetBinding emptyLetBind(d_termLetPrefix);
std::map::iterator itp;
for (const ProofNode* p : pletList)
{
itp = pletMap.find(p);
Assert(itp != pletMap.end());
size_t pid = itp->second;
pletMap.erase(p);
printProofInternal(&lpcp, p, emptyLetBind, pletMap, passumeMap);
pletMap[p] = pid;
Node resType = p->getResult();
lbind.process(d_tproc.convert(resType));
}
// Print the body of the outermost scope
printProofInternal(&lpcp, pnBody, emptyLetBind, pletMap, passumeMap);
// [4] print declared sorts and symbols
// [4a] user declare function symbols
// Note that this is buffered into an output stream preambleSymDecl and then
// printed after types. We require printing the declared symbols here so that
// the set of collected declared types is complete at [4b].
Trace("lfsc-print-debug") << "; print user symbols" << std::endl;
std::stringstream preambleSymDecl;
const std::unordered_set& syms = d_tproc.getDeclaredSymbols();
for (const Node& s : syms)
{
TypeNode st = s.getType();
if (st.isDatatypeConstructor() || st.isDatatypeSelector()
|| st.isDatatypeTester() || st.isDatatypeUpdater()
|| definedSymbols.find(s) != definedSymbols.cend())
{
// Constructors, selector, testers, updaters are defined by the datatype.
// Some definitions depend on declarations and other definitions. So, we
// print them in order after declarations.
continue;
}
Node si = d_tproc.convert(s);
preambleSymDecl << "(define " << si << " (var "
<< d_tproc.getOrAssignIndexForFVar(s) << " ";
printType(preambleSymDecl, st);
preambleSymDecl << "))" << std::endl;
}
// Note that definitions always use their own internal letification, since
// their bodies are not part of the main proof. It is possible to share term
// letification via global definitions, however, this requires further
// analysis to ensure symbols are printed in the correct order. This is
// not done for simplicity.
for (const Node& def : definitions)
{
Node si = d_tproc.convert(def[0]);
preambleSymDecl << "(define " << si << ' ';
print(preambleSymDecl, def[1]);
preambleSymDecl << ')' << std::endl;
}
// [4b] user declared sorts
Trace("lfsc-print-debug") << "; print user sorts" << std::endl;
std::stringstream preamble;
std::unordered_set sts;
std::unordered_set tupleArity;
// get the types from the term processor, which has seen all terms occurring
// in the proof at this point
// The for loop below may add elements to the set of declared types, so we
// copy the set to ensure that the for loop iterators do not become outdated.
const std::unordered_set types = d_tproc.getDeclaredTypes();
for (const TypeNode& st : types)
{
// note that we must get all "component types" of a type, so that
// e.g. U is printed as a sort declaration when we have type (Array U Int).
ensureTypeDefinitionPrinted(preamble, st, sts, tupleArity);
}
// print datatype definitions for the above sorts
for (const TypeNode& stc : sts)
{
if (!stc.isDatatype() || stc.getKind() == Kind::PARAMETRIC_DATATYPE)
{
// skip the instance of a parametric datatype
continue;
}
const DType& dt = stc.getDType();
preamble << "; DATATYPE " << dt.getName() << std::endl;
NodeManager* nm = nodeManager();
for (size_t i = 0, ncons = dt.getNumConstructors(); i < ncons; i++)
{
const DTypeConstructor& cons = dt[i];
std::string cname = d_tproc.getNameForUserNameOf(cons.getConstructor());
Node cc = nm->mkRawSymbol(cname, stc);
// print constructor/tester
preamble << "(declare " << cc << " term)" << std::endl;
for (size_t j = 0, nargs = cons.getNumArgs(); j < nargs; j++)
{
const DTypeSelector& arg = cons[j];
// print selector
std::string sname = d_tproc.getNameForUserNameOf(arg.getSelector());
Node sc = nm->mkRawSymbol(sname, stc);
preamble << "(declare " << sc << " term)" << std::endl;
}
}
// testers and updaters are instances of parametric symbols
// shared selectors are instance of parametric symbol "sel"
preamble << "; END DATATYPE " << std::endl;
}
// [4c] user declared function symbols
preamble << preambleSymDecl.str();
// [5] print warnings
for (ProofRule r : d_trustWarned)
{
out << "; WARNING: adding trust step for " << r << std::endl;
}
// [6] print the DSL rewrite rule declarations
const std::unordered_set& dslrs = lpcp.getDslRewrites();
for (ProofRewriteRule dslr : dslrs)
{
// also computes the format for the rule
printDslRule(out, dslr, d_dslFormat[dslr]);
}
// [7] print the check command and term lets
out << preamble.str();
if (options().proof.lfscFlatten)
{
// print term lets as definitions
std::stringstream cparenTmp;
printLetList(out, cparenTmp, lbind, true);
}
else
{
// the outer check statement for the main proof
out << "(check" << std::endl;
cparen << ")";
// print the term let list wrapped around the body of the final proof
printLetList(out, cparen, lbind, false);
}
Trace("lfsc-print-debug") << "; print asserts" << std::endl;
// [8] print the assertions, with letification
// the assumption identifier mapping
for (size_t i = 0, nasserts = iasserts.size(); i < nasserts; i++)
{
Node ia = iasserts[i];
if (options().proof.lfscFlatten)
{
out << "(declare ";
LfscPrintChannelOut::printId(out, i, d_assumpPrefix);
out << " (holds ";
printInternal(out, ia, lbind);
out << "))" << std::endl;
}
else
{
out << "(# ";
LfscPrintChannelOut::printId(out, i, d_assumpPrefix);
out << " (holds ";
printInternal(out, ia, lbind);
out << ")" << std::endl;
cparen << ")";
}
}
Trace("lfsc-print-debug") << "; print annotation" << std::endl;
// [9] print the annotation
if (!options().proof.lfscFlatten)
{
out << "(: (holds false)" << std::endl;
cparen << ")";
}
Trace("lfsc-print-debug") << "; print proof body" << std::endl;
// [10] print the proof body
Assert(pn->getRule() == ProofRule::SCOPE);
// the outermost scope can be ignored (it is the scope of the assertions,
// which are already printed above).
LfscPrintChannelOut lout(out);
if (options().proof.lfscFlatten)
{
// print the proof letification as separate check statements, followed
// by the main proof.
for (size_t i = 0; i <= pletList.size(); i++)
{
bool isFinal = (i == pletList.size());
const ProofNode* p = isFinal ? pnBody : pletList[i];
Node res = p->getResult();
std::stringstream resType;
printInternal(resType, d_tproc.convert(res), lbind);
out << "(check (: (holds " << resType.str() << ")" << std::endl;
// print the letified proof
if (isFinal)
{
printProofInternal(&lout, p, lbind, pletMap, passumeMap);
out << "))" << std::endl;
}
else
{
itp = pletMap.find(p);
Assert(itp != pletMap.end());
size_t pid = itp->second;
pletMap.erase(p);
printProofInternal(&lout, p, lbind, pletMap, passumeMap);
pletMap[p] = pid;
out << "))" << std::endl;
out << "(declare ";
LfscPrintChannelOut::printId(out, pid, d_pletPrefix);
out << " (holds " << resType.str() << "))" << std::endl;
}
}
}
else
{
printProofLetify(&lout, pnBody, lbind, pletList, pletMap, passumeMap);
}
// [11] print closing parantheses
out << cparen.str() << std::endl;
}
void LfscPrinter::ensureTypeDefinitionPrinted(
std::ostream& os,
TypeNode tn,
std::unordered_set& processed,
std::unordered_set& tupleArityProcessed)
{
// note that we must get all "component types" of a type, so that
// e.g. U is printed as a sort declaration when we have type (Array U Int).
std::unordered_set ctypes;
expr::getComponentTypes(tn, ctypes);
for (const TypeNode& stc : ctypes)
{
printTypeDefinition(os, stc, processed, tupleArityProcessed);
}
}
void LfscPrinter::printTypeDefinition(
std::ostream& os,
TypeNode tn,
std::unordered_set& processed,
std::unordered_set& tupleArityProcessed)
{
if (processed.find(tn) != processed.end())
{
return;
}
processed.insert(tn);
// print uninterpreted sorts and uninterpreted sort constructors here
if (tn.getKind() == Kind::SORT_TYPE)
{
os << "(declare ";
printType(os, tn);
uint64_t arity = 0;
if (tn.isUninterpretedSortConstructor())
{
arity = tn.getUninterpretedSortConstructorArity();
}
std::stringstream tcparen;
for (uint64_t i = 0; i < arity; i++)
{
os << " (! s" << i << " sort";
tcparen << ")";
}
os << " sort" << tcparen.str() << ")" << std::endl;
}
else if (tn.isDatatype())
{
const DType& dt = tn.getDType();
if (tn.getKind() == Kind::PARAMETRIC_DATATYPE || dt.isNullable())
{
// skip the instance of a parametric datatype
// nullables don't need printing
return;
}
if (dt.isTuple())
{
const DTypeConstructor& cons = dt[0];
size_t arity = cons.getNumArgs();
if (tupleArityProcessed.find(arity) == tupleArityProcessed.end())
{
tupleArityProcessed.insert(arity);
if (arity>0)
{
os << "(declare Tuple";
os << "_" << arity;
}
else
{
os << "(declare UnitTuple";
}
os << " ";
std::stringstream tcparen;
for (size_t j = 0, nargs = cons.getNumArgs(); j < nargs; j++)
{
os << "(! s" << j << " sort ";
tcparen << ")";
}
os << "sort" << tcparen.str() << ")";
}
os << std::endl;
}
else
{
os << "(declare ";
printType(os, tn);
std::stringstream cdttparens;
if (dt.isParametric())
{
std::vector params = dt.getParameters();
for (const TypeNode& p : params)
{
os << " (! " << p << " sort";
cdttparens << ")";
}
}
os << " sort)" << cdttparens.str() << std::endl;
}
// must also ensure the subfield types of the datatype are printed
std::unordered_set sftypes = dt.getSubfieldTypes();
for (const TypeNode& sft : sftypes)
{
ensureTypeDefinitionPrinted(os, sft, processed, tupleArityProcessed);
}
}
// all other sorts are builtin into the LFSC signature
}
void LfscPrinter::printProofLetify(
LfscPrintChannel* out,
const ProofNode* pn,
const LetBinding& lbind,
const std::vector& pletList,
std::map& pletMap,
std::map& passumeMap)
{
// closing parentheses
size_t cparen = 0;
// define the let proofs
if (!pletList.empty())
{
std::map::iterator itp;
for (const ProofNode* p : pletList)
{
itp = pletMap.find(p);
Assert(itp != pletMap.end());
size_t pid = itp->second;
pletMap.erase(p);
printPLet(out, p, pid, d_pletPrefix, lbind, pletMap, passumeMap);
pletMap[p] = pid;
// printPLet opens two parentheses
cparen = cparen + 2;
}
out->printEndLine();
}
// [2] print the proof body
printProofInternal(out, pn, lbind, pletMap, passumeMap);
// print the closing parenthesis
out->printCloseRule(cparen);
}
void LfscPrinter::printPLet(LfscPrintChannel* out,
const ProofNode* p,
size_t pid,
const std::string& prefix,
const LetBinding& lbind,
const std::map& pletMap,
std::map& passumeMap)
{
// print (plet _ _
out->printOpenLfscRule(LfscRule::PLET);
out->printHole();
out->printHole();
out->printEndLine();
// print the letified proof
printProofInternal(out, p, lbind, pletMap, passumeMap);
// print the lambda (\ __pX
out->printOpenLfscRule(LfscRule::LAMBDA);
out->printId(pid, prefix);
out->printEndLine();
}
void LfscPrinter::printProofInternal(
LfscPrintChannel* out,
const ProofNode* pn,
const LetBinding& lbind,
const std::map& pletMap,
std::map& passumeMap)
{
// the stack
std::vector visit;
// whether we have to process children
std::unordered_set processingChildren;
// helper iterators
std::unordered_set::iterator pit;
std::map::const_iterator pletIt;
std::map::iterator passumeIt;
Node curn;
TypeNode curtn;
const ProofNode* cur;
visit.push_back(PExpr(pn));
do
{
curn = visit.back().d_node;
curtn = visit.back().d_typeNode;
cur = visit.back().d_pnode;
visit.pop_back();
// case 1: printing a proof
if (cur != nullptr)
{
ProofRule r = cur->getRule();
// maybe it is letified
pletIt = pletMap.find(cur);
if (pletIt != pletMap.end())
{
// a letified proof
out->printId(pletIt->second, d_pletPrefix);
continue;
}
pit = processingChildren.find(cur);
if (pit == processingChildren.end())
{
bool isLambda = false;
if (r == ProofRule::LFSC_RULE)
{
Assert(!cur->getArguments().empty());
LfscRule lr = getLfscRule(cur->getArguments()[0]);
isLambda = (lr == LfscRule::LAMBDA);
}
if (r == ProofRule::ASSUME)
{
// an assumption, must have a name
passumeIt = passumeMap.find(cur->getResult());
Assert(passumeIt != passumeMap.end());
out->printId(passumeIt->second, d_assumpPrefix);
}
else if (r == ProofRule::ENCODE_EQ_INTRO)
{
// just add child
visit.push_back(PExpr(cur->getChildren()[0].get()));
}
else if (isLambda)
{
Assert(cur->getArguments().size() == 3);
// lambdas are handled specially. We print in a self contained way
// here.
// allocate an assumption, if necessary
size_t pid;
Node assumption = cur->getArguments()[2];
passumeIt = passumeMap.find(assumption);
if (passumeIt == passumeMap.end())
{
pid = d_assumpCounter;
d_assumpCounter++;
passumeMap[assumption] = pid;
}
else
{
pid = passumeIt->second;
}
// make the node whose name is the assumption id, where notice that
// the type of this node does not matter
std::stringstream pidNodeName;
LfscPrintChannelOut::printId(pidNodeName, pid, d_assumpPrefix);
// must be an internal symbol so that it is not turned into (bvar ...)
Node pidNode =
d_tproc.mkInternalSymbol(pidNodeName.str(), d_boolType);
// print "(\ "
out->printOpenRule(cur);
// print the identifier
out->printNode(pidNode);
// Print the body of the proof with a fresh proof letification. We can
// keep the assumption map and the let binding (for terms).
std::vector pletListNested;
std::map pletMapNested;
const ProofNode* curBody = cur->getChildren()[0].get();
computeProofLetification(curBody, pletListNested, pletMapNested);
printProofLetify(
out, curBody, lbind, pletListNested, pletMapNested, passumeMap);
// print ")"
out->printCloseRule();
}
else
{
// assert that we should traverse cur when letifying
Assert(d_lpltc.shouldTraverse(cur));
// a normal rule application, compute the proof arguments, which
// notice in the case of PI also may modify our passumeMap.
std::vector args;
if (computeProofArgs(cur, args))
{
processingChildren.insert(cur);
// will revisit this proof node to close parentheses
visit.push_back(PExpr(cur));
std::reverse(args.begin(), args.end());
visit.insert(visit.end(), args.begin(), args.end());
// print the rule name
out->printOpenRule(cur);
}
else
{
// Could not print the rule, trust for now.
// If we are expanding trusted steps, its children are printed as
// plet applications that wrap this term, so that all subproofs are
// recorded in the proof.
size_t cparenTrustChild = 0;
if (options().proof.lfscExpandTrust)
{
const std::vector>& children =
cur->getChildren();
for (const std::shared_ptr& c : children)
{
size_t pid = d_trustChildPletCounter;
d_trustChildPletCounter++;
printPLet(out,
c.get(),
pid,
d_pletTrustChildPrefix,
lbind,
pletMap,
passumeMap);
cparenTrustChild = cparenTrustChild + 2;
}
}
Node res = d_tproc.convert(cur->getResult());
res = lbind.convert(res, true);
out->printTrust(res, r);
d_trustWarned.insert(r);
out->printCloseRule(cparenTrustChild);
}
}
}
else
{
processingChildren.erase(cur);
out->printCloseRule();
}
}
// case 2: printing a node
else if (!curn.isNull())
{
// it has already been converted to internal form, we letify it here
Node curni = lbind.convert(curn, true);
out->printNode(curni);
}
// case 3: printing a type node
else if (!curtn.isNull())
{
out->printTypeNode(curtn);
}
// case 4: a hole
else
{
out->printHole();
}
} while (!visit.empty());
}
bool LfscPrinter::computeProofArgs(const ProofNode* pn,
std::vector& pargs)
{
const std::vector>& children = pn->getChildren();
std::vector cs;
for (const std::shared_ptr& c : children)
{
cs.push_back(c.get());
}
ProofRule r = pn->getRule();
const std::vector& args = pn->getArguments();
std::vector as;
for (const Node& a : args)
{
Node ac = d_tproc.convert(a);
Assert(!ac.isNull());
as.push_back(ac);
}
// The proof expression stream, which packs the next expressions (proofs,
// terms, sorts, LFSC datatypes) into a print-expression vector pargs. This
// stream can be used via "pf << e" which appends an expression to the
// vector maintained by this stream.
PExprStream pf(pargs, d_tt, d_ff);
// hole
PExpr h;
Trace("lfsc-print-debug2")
<< "Compute proof args " << r << " #children= " << cs.size()
<< " #args=" << as.size() << std::endl;
switch (r)
{
// SAT
case ProofRule::RESOLUTION:
pf << h << h << h << cs[0] << cs[1] << args[0].getConst() << as[1];
break;
case ProofRule::REORDERING: pf << h << as[0] << cs[0]; break;
case ProofRule::FACTORING: pf << h << h << cs[0]; break;
// Boolean
case ProofRule::SPLIT: pf << as[0]; break;
case ProofRule::NOT_NOT_ELIM: pf << h << cs[0]; break;
case ProofRule::CONTRA: pf << h << cs[0] << cs[1]; break;
case ProofRule::MODUS_PONENS:
case ProofRule::EQ_RESOLVE: pf << h << h << cs[0] << cs[1]; break;
case ProofRule::NOT_AND: pf << h << h << cs[0]; break;
case ProofRule::NOT_OR_ELIM:
case ProofRule::AND_ELIM: pf << h << h << args[0] << cs[0]; break;
case ProofRule::IMPLIES_ELIM:
case ProofRule::NOT_IMPLIES_ELIM1:
case ProofRule::NOT_IMPLIES_ELIM2:
case ProofRule::EQUIV_ELIM1:
case ProofRule::EQUIV_ELIM2:
case ProofRule::NOT_EQUIV_ELIM1:
case ProofRule::NOT_EQUIV_ELIM2:
case ProofRule::XOR_ELIM1:
case ProofRule::XOR_ELIM2:
case ProofRule::NOT_XOR_ELIM1:
case ProofRule::NOT_XOR_ELIM2: pf << h << h << cs[0]; break;
case ProofRule::ITE_ELIM1:
case ProofRule::ITE_ELIM2:
case ProofRule::NOT_ITE_ELIM1:
case ProofRule::NOT_ITE_ELIM2: pf << h << h << h << cs[0]; break;
// CNF
case ProofRule::CNF_AND_POS:
case ProofRule::CNF_OR_NEG:
// print second argument as a raw integer (mpz)
pf << h << as[0] << args[1];
break;
case ProofRule::CNF_AND_NEG: pf << h << as[0]; break;
case ProofRule::CNF_OR_POS:
pf << as[0];
break;
break;
case ProofRule::CNF_IMPLIES_POS:
case ProofRule::CNF_IMPLIES_NEG1:
case ProofRule::CNF_IMPLIES_NEG2:
case ProofRule::CNF_EQUIV_POS1:
case ProofRule::CNF_EQUIV_POS2:
case ProofRule::CNF_EQUIV_NEG1:
case ProofRule::CNF_EQUIV_NEG2:
case ProofRule::CNF_XOR_POS1:
case ProofRule::CNF_XOR_POS2:
case ProofRule::CNF_XOR_NEG1:
case ProofRule::CNF_XOR_NEG2: pf << as[0][0] << as[0][1]; break;
case ProofRule::CNF_ITE_POS1:
case ProofRule::CNF_ITE_POS2:
case ProofRule::CNF_ITE_POS3:
case ProofRule::CNF_ITE_NEG1:
case ProofRule::CNF_ITE_NEG2:
case ProofRule::CNF_ITE_NEG3: pf << as[0][0] << as[0][1] << as[0][2]; break;
// equality
case ProofRule::REFL: pf << as[0]; break;
case ProofRule::SYMM: pf << h << h << cs[0]; break;
case ProofRule::TRANS: pf << h << h << h << cs[0] << cs[1]; break;
case ProofRule::TRUE_INTRO:
case ProofRule::FALSE_INTRO:
case ProofRule::TRUE_ELIM:
case ProofRule::FALSE_ELIM: pf << h << cs[0]; break;
// arithmetic
case ProofRule::ARITH_MULT_POS:
case ProofRule::ARITH_MULT_NEG:
{
pf << h << as[0] << as[1] << d_tproc.convertType(as[0].getType());
}
break;
case ProofRule::ARITH_TRICHOTOMY:
{
// should be robust to different orderings
pf << h << h << h << cs[0] << cs[1];
}
break;
case ProofRule::INT_TIGHT_UB:
case ProofRule::INT_TIGHT_LB:
{
Node res = pn->getResult();
Assert(res.getNumChildren() == 2);
Assert(res[1].isConst());
pf << h << h << d_tproc.convert(res[1]) << cs[0];
}
break;
// strings
case ProofRule::STRING_LENGTH_POS:
pf << as[0] << d_tproc.convertType(as[0].getType()) << h;
break;
case ProofRule::STRING_LENGTH_NON_EMPTY: pf << h << h << cs[0]; break;
case ProofRule::RE_INTER: pf << h << h << h << cs[0] << cs[1]; break;
case ProofRule::CONCAT_EQ:
pf << h << h << h << args[0].getConst()
<< d_tproc.convertType(children[0]->getResult()[0].getType()) << cs[0];
break;
case ProofRule::CONCAT_UNIFY:
pf << h << h << h << h << args[0].getConst()
<< d_tproc.convertType(children[0]->getResult()[0].getType()) << cs[0]
<< cs[1];
break;
case ProofRule::CONCAT_CSPLIT:
pf << h << h << h << h << args[0].getConst()
<< d_tproc.convertType(children[0]->getResult()[0].getType()) << cs[0]
<< cs[1];
break;
case ProofRule::CONCAT_CONFLICT:
pf << h << h << args[0].getConst()
<< d_tproc.convertType(children[0]->getResult()[0].getType()) << cs[0];
break;
case ProofRule::RE_UNFOLD_POS:
if (children[0]->getResult()[1].getKind() != Kind::REGEXP_CONCAT)
{
return false;
}
pf << h << h << h << cs[0];
break;
case ProofRule::STRING_EAGER_REDUCTION:
{
Kind k = as[0].getKind();
if (k == Kind::STRING_TO_CODE || k == Kind::STRING_CONTAINS
|| k == Kind::STRING_INDEXOF)
{
pf << h << as[0] << as[0][0].getType();
}
else
{
// not yet defined for other kinds
return false;
}
}
break;
case ProofRule::STRING_REDUCTION:
{
Kind k = as[0].getKind();
if (k == Kind::STRING_SUBSTR || k == Kind::STRING_INDEXOF)
{
pf << h << as[0] << d_tproc.convertType(as[0][0].getType());
}
else
{
// not yet defined for other kinds
return false;
}
}
break;
// quantifiers
case ProofRule::SKOLEM_INTRO:
{
pf << d_tproc.convert(SkolemManager::getUnpurifiedForm(args[0]));
}
break;
// ---------- arguments of non-translated rules go here
case ProofRule::LFSC_RULE:
{
LfscRule lr = getLfscRule(args[0]);
// lambda should be processed elsewhere
Assert(lr != LfscRule::LAMBDA);
// Note that `args` has 2 builtin arguments, thus the first real argument
// begins at index 2
switch (lr)
{
case LfscRule::DEFINITION: pf << as[1][0]; break;
case LfscRule::SCOPE: pf << h << as[2] << cs[0]; break;
case LfscRule::NEG_SYMM: pf << h << h << cs[0]; break;
case LfscRule::CONG: pf << h << h << h << h << cs[0] << cs[1]; break;
case LfscRule::AND_INTRO1: pf << h << cs[0]; break;
case LfscRule::NOT_AND_REV: pf << h << h << cs[0]; break;
case LfscRule::PROCESS_SCOPE: pf << h << h << as[2] << cs[0]; break;
case LfscRule::AND_INTRO2: pf << h << h << cs[0] << cs[1]; break;
case LfscRule::ARITH_SUM_UB: pf << h << h << h << cs[0] << cs[1]; break;
case LfscRule::CONCAT_CONFLICT_DEQ:
pf << h << h << h << h << as[2].getConst()
<< d_tproc.convertType(children[0]->getResult()[0].getType())
<< cs[0] << cs[1];
break;
case LfscRule::INSTANTIATE:
pf << h << h << h << h << as[2] << cs[0];
break;
case LfscRule::BETA_REDUCE: pf << h << as[2]; break;
default: return false; break;
}
}
break;
case ProofRule::DSL_REWRITE:
{
ProofRewriteRule di = ProofRewriteRule::NONE;
if (!rewriter::getRewriteRule(args[0], di))
{
Assert(false);
}
Trace("lfsc-print-debug2") << "Printing dsl rule " << di << std::endl;
const rewriter::RewriteProofRule& rpr = d_rdb->getRule(di);
const std::vector& varList = rpr.getVarList();
Assert(as.size() == varList.size() + 1);
// print holes/terms based on whether variables are explicit
for (size_t i = 1, nargs = as.size(); i < nargs; i++)
{
Node v = varList[i - 1];
if (rpr.isExplicitVar(v))
{
// If the variable is a list variable, we must convert its value to
// the proper term. This is based on its context.
if (as[i].getKind() == Kind::SEXPR)
{
Assert(args[i].getKind() == Kind::SEXPR);
NodeManager* nm = nodeManager();
Kind k = rpr.getListContext(v);
// notice we use d_tproc.getNullTerminator and not
// expr::getNullTerminator here, which has subtle differences
// e.g. re.empty vs (str.to_re "").
Node null = d_tproc.getNullTerminator(k, v.getType());
Node t;
if (as[i].getNumChildren() == 1)
{
// Singleton list uses null terminator. We first construct an
// original term and convert it.
Node tt = nm->mkNode(k, args[i][0], null);
tt = d_tproc.convert(tt);
// Since conversion adds a null terminator, we have that
// tt is of the form (f t (f null null)). We reconstruct
// the proper term (f t null) below.
Assert(tt.getNumChildren() == 2);
Assert(tt[1].getNumChildren() == 2);
std::vector tchildren;
if (tt.getMetaKind() == metakind::PARAMETERIZED)
{
tchildren.push_back(tt.getOperator());
}
tchildren.push_back(tt[0]);
tchildren.push_back(tt[1][0]);
t = nm->mkNode(tt.getKind(), tchildren);
}
else
{
if (k == Kind::UNDEFINED_KIND)
{
Unhandled() << "Unknown context for list variable " << v
<< " in rule " << di;
}
if (as[i].getNumChildren() == 0)
{
t = null;
}
else
{
// re-convert it
std::vector vec(args[i].begin(), args[i].end());
t = nm->mkNode(k, vec);
}
t = d_tproc.convert(t);
}
pf << t;
}
else
{
pf << as[i];
}
}
else
{
pf << h;
}
}
// print child proofs, which is based on the format computed for the rule
size_t ccounter = 0;
std::map>::iterator itf =
d_dslFormat.find(di);
if (itf == d_dslFormat.end())
{
// We may not have computed the format yet, e.g. if we are printing
// via the pre print channel. In this case, just print all the children.
for (const ProofNode* c : cs)
{
pf << c;
}
}
else
{
for (const Node& f : itf->second)
{
if (f.isNull())
{
// this position is a hole
pf << h;
continue;
}
Assert(ccounter < cs.size());
pf << cs[ccounter];
ccounter++;
}
Assert(ccounter == cs.size());
}
}
break;
default:
{
return false;
break;
}
}
return true;
}
void LfscPrinter::computeProofLetification(
const ProofNode* pn,
std::vector& pletList,
std::map& pletMap)
{
// use callback to specify to stop at LAMBDA
ProofLetify::computeProofLet(pn, pletList, pletMap, 2, &d_lpltc);
}
void LfscPrinter::print(std::ostream& out, Node n)
{
Node ni = d_tproc.convert(n);
printLetify(out, ni);
}
void LfscPrinter::printLetify(std::ostream& out, Node n)
{
// closing parentheses
std::stringstream cparen;
LetBinding lbind(d_termLetPrefix);
lbind.process(n);
// [1] print the letification
printLetList(out, cparen, lbind);
// [2] print the body
printInternal(out, n, lbind);
out << cparen.str();
}
void LfscPrinter::printLetList(std::ostream& out,
std::ostream& cparen,
LetBinding& lbind,
bool asDefs)
{
std::vector letList;
lbind.letify(letList);
std::map::const_iterator it;
for (size_t i = 0, nlets = letList.size(); i < nlets; i++)
{
Node nl = letList[i];
size_t id = lbind.getId(nl);
Assert(id != 0);
if (asDefs)
{
out << "(define ";
LfscPrintChannelOut::printId(out, id, d_termLetPrefix);
out << " ";
// do not letify the top term
printInternal(out, nl, lbind, false);
out << ")" << std::endl;
}
else
{
out << "(@ ";
LfscPrintChannelOut::printId(out, id, d_termLetPrefix);
out << " ";
// do not letify the top term
printInternal(out, nl, lbind, false);
out << std::endl;
cparen << ")";
}
}
}
void LfscPrinter::printInternal(std::ostream& out, Node n)
{
LetBinding lbind(d_termLetPrefix);
printInternal(out, n, lbind);
}
void LfscPrinter::printInternal(std::ostream& out,
Node n,
LetBinding& lbind,
bool letTop)
{
Node nc = lbind.convert(n, letTop);
LfscPrintChannelOut::printNodeInternal(out, nc);
}
void LfscPrinter::printType(std::ostream& out, TypeNode tn)
{
TypeNode tni = d_tproc.convertType(tn);
LfscPrintChannelOut::printTypeNodeInternal(out, tni);
}
void LfscPrinter::printDslRule(std::ostream& out,
ProofRewriteRule id,
std::vector& format)
{
const rewriter::RewriteProofRule& rpr = d_rdb->getRule(id);
const std::vector& varList = rpr.getVarList();
const std::vector& uvarList = rpr.getUserVarList();
const std::vector& conds = rpr.getConditions();
Node conc = rpr.getConclusion();
std::stringstream oscs;
std::stringstream odecl;
std::stringstream rparen;
odecl << "(declare ";
LfscPrintChannelOut::printProofRewriteRule(odecl, id);
std::vector vlsubs;
// streams for printing the computation of term in side conditions or
// list semantics substitutions
std::stringstream argList;
std::stringstream argListTerms;
// the list variables
std::unordered_set listVars;
argList << "(";
// use the names from the user variable list (uvarList)
for (const Node& v : uvarList)
{
std::stringstream sss;
sss << v;
Node s = d_tproc.mkInternalSymbol(sss.str(), v.getType());
odecl << " (! " << sss.str() << " term";
argList << "(" << sss.str() << " term)";
argListTerms << " " << sss.str();
rparen << ")";
vlsubs.push_back(s);
// remember if v was a list variable, we must convert these in side
// condition printing below
if (expr::isListVar(v))
{
listVars.insert(s);
}
}
argList << ")";
// print conditions
size_t termCount = 0;
size_t scCount = 0;
// print conditions, then conclusion
for (size_t i = 0, nconds = conds.size(); i <= nconds; i++)
{
bool isConclusion = i == nconds;
Node term = isConclusion ? conc : conds[i];
Node sterm = term.substitute(
varList.begin(), varList.end(), vlsubs.begin(), vlsubs.end());
if (expr::hasListVar(term))
{
Assert(!listVars.empty());
scCount++;
std::stringstream scName;
scName << "dsl.sc." << scCount << "." << id;
// generate the side condition
oscs << "(function " << scName.str() << " " << argList.str() << " term"
<< std::endl;
// body must be converted to incorporate list semantics for substitutions
// first traversal applies nary_elim to required n-ary applications
LfscListScNodeConverter llsncp(nodeManager(), d_tproc, listVars, true);
Node tscp;
if (isConclusion)
{
Assert(sterm.getKind() == Kind::EQUAL);
// optimization: don't need nary_elim for heads
tscp = llsncp.convert(sterm[1]);
tscp = sterm[0].eqNode(tscp);
}
else
{
tscp = llsncp.convert(sterm);
}
// second traversal converts to LFSC form
Node t = d_tproc.convert(tscp);
// third traversal applies nary_concat where list variables are used
LfscListScNodeConverter llsnc(nodeManager(), d_tproc, listVars, false);
Node tsc = llsnc.convert(t);
oscs << " ";
print(oscs, tsc);
oscs << ")" << std::endl;
termCount++;
// introduce a term computed by side condition
odecl << " (! _t" << termCount << " term";
rparen << ")";
format.push_back(Node::null());
// side condition, which is an implicit argument
odecl << " (! _s" << scCount << " (^ (" << scName.str();
rparen << ")";
// arguments to side condition
odecl << argListTerms.str() << ") ";
// matches condition
odecl << "_t" << termCount << ")";
if (!isConclusion)
{
// the child proof
odecl << " (! _u" << i;
rparen << ")";
format.push_back(term);
}
odecl << " (holds _t" << termCount << ")";
continue;
}
// ordinary condition/conclusion, print the term directly
if (!isConclusion)
{
odecl << " (! _u" << i;
rparen << ")";
format.push_back(term);
}
odecl << " (holds ";
Node t = d_tproc.convert(sterm);
print(odecl, t);
odecl << ")";
}
odecl << rparen.str() << ")" << std::endl;
// print the side conditions
out << oscs.str();
// print the rule declaration
out << odecl.str();
}
} // namespace proof
} // namespace cvc5::internal