Previously, we would get LFSC proof checking failures if we were using an identifier that was valid in SMT-LIB but not LFSC.
Co-authored-by: Alex Ozdemir <aozdemir@hmc.edu>
#include "proof/lfsc/lfsc_node_converter.h"
+#include <iomanip>
#include <sstream>
#include "expr/array_store_all.h"
}
else if (n.isVar())
{
- std::stringstream ss;
- ss << n;
- Node nn = mkInternalSymbol(getNameForUserName(ss.str()), tn);
- return nn;
+ return mkInternalSymbol(getNameForUserNameOf(n), tn);
}
else if (k == APPLY_UF)
{
std::string LfscNodeConverter::getNameForUserName(const std::string& name)
{
- std::stringstream ss;
- ss << "cvc." << name;
- return ss.str();
+ // For user name X, we do cvc.Y, where Y contains an escaped version of X.
+ // Specifically, since LFSC does not allow these characters in identifier
+ // bodies: "() \t\n\f;", each is replaced with an escape sequence "\xXX"
+ // where XX is the zero-padded hex representation of the code point. "\\" is
+ // also escaped.
+ //
+ // See also: https://github.com/cvc5/LFSC/blob/master/src/lexer.flex#L24
+ //
+ // The "cvc." prefix ensures we do not clash with LFSC definitions.
+ //
+ // The escaping ensures that all names are valid LFSC identifiers.
+ std::string sanitized("cvc.");
+ size_t found = sanitized.size();
+ sanitized += name;
+ // The following sanitizes symbols that are not allowed in LFSC identifiers
+ // here, e.g.
+ // |a b|
+ // is converted to:
+ // cvc.a\x20b
+ // where "20" is the hex unicode value of " ".
+ do
+ {
+ found = sanitized.find_first_of("() \t\n\f\\;", found);
+ if (found != std::string::npos)
+ {
+ // Emit hex sequence
+ std::stringstream seq;
+ seq << "\\x" << std::setbase(16) << std::setfill('0') << std::setw(2)
+ << static_cast<size_t>(sanitized[found]);
+ sanitized.replace(found, 1, seq.str());
+ // increment found over the escape
+ found += 3;
+ }
+ } while (found != std::string::npos);
+ return sanitized;
+}
+
+std::string LfscNodeConverter::getNameForUserNameOf(Node v)
+{
+ std::string name;
+ v.getAttribute(expr::VarNameAttr(), name);
+ return getNameForUserName(name);
}
bool LfscNodeConverter::shouldTraverse(Node n)
{
Assert(indices.size() == 1);
// must convert to user name
- std::stringstream sss;
- sss << indices[0];
TypeNode intType = nm->integerType();
indices[0] =
- getSymbolInternal(k, intType, getNameForUserName(sss.str()));
+ getSymbolInternal(k, intType, getNameForUserNameOf(indices[0]));
}
}
else if (op.getType().isFunction())
{
unsigned index = DType::indexOf(op);
const DType& dt = DType::datatypeOf(op);
- std::stringstream ssc;
- ssc << dt[index].getConstructor();
- opName << getNameForUserName(ssc.str());
+ // get its variable name
+ opName << getNameForUserNameOf(dt[index].getConstructor());
}
else if (k == APPLY_SELECTOR)
{
/** get name for user name */
static std::string getNameForUserName(const std::string& name);
+ /** get name for the name of node v, where v should be a variable */
+ static std::string getNameForUserNameOf(Node v);
private:
/** Should we traverse n? */
add_subdirectory(options)
add_subdirectory(parser)
add_subdirectory(printer)
+ add_subdirectory(proof)
add_subdirectory(prop)
add_subdirectory(theory)
add_subdirectory(preprocessing)
--- /dev/null
+cvc5_add_unit_test_black(lfsc_node_converter_black proof)
--- /dev/null
+/******************************************************************************
+ * Top contributors (to current version):
+ * Alex Ozdemir
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 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.
+ * ****************************************************************************
+ *
+ * Black box testing of LFSC proof conversion
+ */
+
+#include <string>
+
+#include "proof/lfsc/lfsc_node_converter.h"
+#include "test.h"
+
+namespace cvc5 {
+using namespace cvc5::proof;
+
+namespace test {
+
+class TestLfscNodeConverterBlack : public TestInternal
+{
+};
+
+TEST_F(TestLfscNodeConverterBlack, ident_sanitize)
+{
+ // LFSC does not allow these characters in identifier bodies: "() \t\n\f;"
+ //
+ // See also: https://github.com/cvc5/LFSC/blob/master/src/lexer.flex#L24
+ //
+ // We use hex escapes that are valid in Python:
+ // https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there are spaces"),
+ R"x(cvc.there\x20are\x20spaces)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName(" "), R"x(cvc.\x20\x20)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there\\are\\slashes"),
+ R"x(cvc.there\x5care\x5cslashes)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there\nare\nnewlines"),
+ R"x(cvc.there\x0aare\x0anewlines)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there\rare\rCRs"),
+ "cvc.there\rare\rCRs");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there\tare\ttabs"),
+ R"x(cvc.there\x09are\x09tabs)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there(are(parens"),
+ R"x(cvc.there\x28are\x28parens)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there)are)parens"),
+ R"x(cvc.there\x29are\x29parens)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName("there;are;semis"),
+ R"x(cvc.there\x3bare\x3bsemis)x");
+ ASSERT_EQ(LfscNodeConverter::getNameForUserName(""), R"x(cvc.)x");
+}
+
+} // namespace test
+
+} // namespace cvc5