I have tried different approaches and asked several specific questions regarding sub-problems of my requirement here. But my solution doesn't really work as expected, so I am taking a step back and ask here from a more general perspective. Please bear in mind that I am not a C++ pro. Not a beginner either, but I am still learning the language.
So, I have the following requirement. I need to read-in text files, which contain conditions, like "Greater" or "Equals", all like functions that return boolean values. The text files also include parameters for these conditions. Note that these parameters can be of different types (integer, decimal, etc.), and each such condition can take different number of parameters (e.g. "Equals" takes 2 paramters, while "Between" would take 3 parameters). Thus the file could look something like this:
Greater, 2, 3
Greater, 2.4, 1.0
Equals, true, true
Between, 20, 10, 30
The logic to read-in that file and parsing it is already done. Now I need to "concatenate" all these boolean functions with their parameters and check if all of them are true.
So I thought I would create functions or a class with static methods to represent these boolean test functions, then create a map of function pointers to these functions, mapped by their name. At runtime I would then read in the file, call the respective function pointer and pass-in the paramteres. That seemed easy to me, but in reality I am struggling mostly with the fact that these boolean functions can take a different number of parameters, and that these can be of different types.
Can you recommend a way to tackle that requirement in C++? I am not asking for a complete solution, but for a suitable C++ approach, or a guideline I could follow. Thanks in advance!
Here's a quick&dirty Spirit grammar for the input shown.
UPDATE
Now added invocation and implementation of the predicate functions (
GreaterImpl
andEqualsImpl
).I tried to be smart allowing comparisons between mixed arithmetic types (but not e.g.
Greater(bool,string)
. If you compare incompatible types you will get astd::runtime_error
exception that provides type feedback to the caller.
Live On Coliru
#include <deque>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/struct.hpp>
namespace qi = boost::spirit::qi;
namespace Ast {
using Value = boost::variant<int, double, bool, std::string>;
using BinaryPred = std::function<bool(Value, Value)>;
using TernaryPred = std::function<bool(Value, Value, Value)>;
using Pred = boost::variant<BinaryPred, TernaryPred>;
using Values = std::vector<Value>;
struct Invocation { Pred pred; Values args; };
using Invocations = std::vector<Invocation>;
}
BOOST_FUSION_ADAPT_STRUCT(Ast::Invocation, pred, args)
namespace Predicates {
using Ast::Value;
struct Greater : boost::static_visitor<bool> {
bool operator()(Value const& a, Value const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T> bool operator()(T const& a, T const& b) const { return std::greater<T>{}(a, b); }
template <typename T, typename U>
typename std::enable_if<std::is_arithmetic<T>() && std::is_arithmetic<U>(), bool>::type
operator()(T const& a, U const& b) const { return a > b; }
template <typename T, typename U>
typename std::enable_if<not (std::is_arithmetic<T>() && std::is_arithmetic<U>()), bool>::type
operator()(T const&, U const&) const { throw std::runtime_error("Type Mismatch"); }
};
struct Equals : boost::static_visitor<bool> {
bool operator()(Value const& a, Value const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T> bool operator()(T const& a, T const& b) const { return std::equal_to<T>{}(a, b); }
template <typename T, typename U, typename enable = typename std::enable_if<std::is_arithmetic<T>() && std::is_arithmetic<U>()>::type >
bool operator()(T const& a, U const& b) const { return a == b; }
template <typename T, typename U>
typename std::enable_if<not (std::is_arithmetic<T>() && std::is_arithmetic<U>()), bool>::type
operator()(T const&, U const&) const { throw std::runtime_error("Type Mismatch"); }
};
struct Between {
bool operator()(Value const& v, Value const& lower, Value const& upper) const {
return Greater{}(v,lower) && Greater{}(upper,v);
}
};
}
static inline bool evaluate(Ast::Invocation const& i) {
struct Invoker {
using result_type = bool;
Ast::Values const& args;
result_type operator()(Ast::BinaryPred const& p) const {
if (args.size() != 2) throw std::runtime_error("Arity Mismatch");
return p(args.at(0), args.at(1));
}
result_type operator()(Ast::TernaryPred const& p) const {
if (args.size() != 3) throw std::runtime_error("Arity Mismatch");
return p(args.at(0), args.at(1), args.at(2));
}
};
return boost::apply_visitor(Invoker{i.args}, i.pred);
}
template <typename It>
struct Grammar : qi::grammar<It, Ast::Invocations()> {
Grammar() : Grammar::base_type(start) {
using namespace qi;
start = skip(blank) [ invocation % eol ];
invocation = pred >> -("," >> args);
args = arg % ",";
arg = my_double_ | qi::int_ | qi::bool_ | lexeme['"' > *~char_('"') > '"'];
}
private:
struct pred_t : qi::symbols<char, Ast::Pred> {
pred_t() {
this->add
("Greater", Predicates::Greater{})
("Equals", Predicates::Equals{})
("Between", Predicates::Between{})
;
}
} const pred;
qi::rule<It, Ast::Invocations()> start;
qi::rule<It, Ast::Invocation(), qi::blank_type> invocation;
qi::rule<It, Ast::Values(), qi::blank_type> args;
qi::rule<It, Ast::Value(), qi::blank_type> arg;
qi::real_parser<double, qi::strict_real_policies<double> > my_double_;
};
#include <sstream>
int main() {
using It = boost::spirit::istream_iterator;
std::deque<std::string> testcases {
// one multiline case:
"Between, 20, 10, 30\n"
"Between, NaN, NaN, NaN\n"
"Between, \"q\", \"a\", \"z\""
};
// many single line cases for easy test reporting
for (std::string op : {"Greater","Equals"})
for (auto rhs : { "42", "0.0", "true", "\"hello\"" })
for (auto lhs : { "41", "-0.0", "false", "\"bye\"" }) {
testcases.push_front(op + ", " + lhs + ", " + rhs);
}
for (auto testcase : testcases) {
std::cout << "--- Testcase '" << testcase << "' -> ";
std::istringstream iss(testcase);
It f(iss >> std::noskipws), l;
Ast::Invocations parsed;
if (qi::parse(f, l, Grammar<It>(), parsed)) {
for (auto& invocation : parsed) {
try {
std::cout << std::boolalpha << evaluate(invocation) << "; ";
} catch(std::exception const& e) {
std::cout << e.what() << "; ";
}
}
std::cout << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f != l)
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
}
Prints:
--- Testcase 'Equals, "bye", "hello"' -> false;
--- Testcase 'Equals, false, "hello"' -> Type Mismatch;
--- Testcase 'Equals, -0.0, "hello"' -> Type Mismatch;
--- Testcase 'Equals, 41, "hello"' -> Type Mismatch;
--- Testcase 'Equals, "bye", true' -> Type Mismatch;
--- Testcase 'Equals, false, true' -> false;
--- Testcase 'Equals, -0.0, true' -> false;
--- Testcase 'Equals, 41, true' -> false;
--- Testcase 'Equals, "bye", 0.0' -> Type Mismatch;
--- Testcase 'Equals, false, 0.0' -> true;
--- Testcase 'Equals, -0.0, 0.0' -> true;
--- Testcase 'Equals, 41, 0.0' -> false;
--- Testcase 'Equals, "bye", 42' -> Type Mismatch;
--- Testcase 'Equals, false, 42' -> false;
--- Testcase 'Equals, -0.0, 42' -> false;
--- Testcase 'Equals, 41, 42' -> false;
--- Testcase 'Greater, "bye", "hello"' -> false;
--- Testcase 'Greater, false, "hello"' -> Type Mismatch;
--- Testcase 'Greater, -0.0, "hello"' -> Type Mismatch;
--- Testcase 'Greater, 41, "hello"' -> Type Mismatch;
--- Testcase 'Greater, "bye", true' -> Type Mismatch;
--- Testcase 'Greater, false, true' -> false;
--- Testcase 'Greater, -0.0, true' -> false;
--- Testcase 'Greater, 41, true' -> true;
--- Testcase 'Greater, "bye", 0.0' -> Type Mismatch;
--- Testcase 'Greater, false, 0.0' -> false;
--- Testcase 'Greater, -0.0, 0.0' -> false;
--- Testcase 'Greater, 41, 0.0' -> true;
--- Testcase 'Greater, "bye", 42' -> Type Mismatch;
--- Testcase 'Greater, false, 42' -> false;
--- Testcase 'Greater, -0.0, 42' -> false;
--- Testcase 'Greater, 41, 42' -> false;
--- Testcase 'Between, 20, 10, 30
Between, NaN, NaN, NaN
Between, "q", "a", "z"' -> true; false; true;
boost variant is the easiest way IMHO:
#include <boost/variant.hpp>
#include <boost/operators.hpp>
#include <string>
#include <iostream>
#include <iomanip>
// define the concept of equality in my scripting language
struct is_equal : boost::static_visitor<bool>
{
// x == x is easy
template<class T>
bool operator()(const T& l, const T& r) const {
return l == r;
}
// define the concept of comparing strings to integers
bool operator()(const std::string& l, const int& r) const {
return l == std::to_string(r);
}
// and integers to strings
bool operator()(const int& l, const std::string& r) const {
return (*this)(r, l);
}
};
struct is_less : boost::static_visitor<bool>
{
// x == x is easy
template<class T>
bool operator()(const T& l, const T& r) const {
return l < r;
}
// define the concept of comparing strings to integers
bool operator()(const std::string& l, const int& r) const {
return std::stoi(l) < r;
}
// and integers to strings
bool operator()(const int& l, const std::string& r) const {
return l < std::stoi(r);
}
};
struct emit : boost::static_visitor<std::ostream&>
{
emit(std::ostream& os) : os_(os) {}
// x == x is easy
template<class T>
std::ostream& operator()(const T& l) const {
return os_ << l;
}
std::ostream& operator()(const std::string& s) const {
return os_ << std::quoted(s);
}
std::ostream& os_;
};
struct scriptable_value
: boost::less_than_comparable<scriptable_value>
, boost::equality_comparable<scriptable_value>
{
using variant_type = boost::variant<std::string, int>;
scriptable_value(std::string v) : variant_(std::move(v)) {}
scriptable_value(int v) : variant_(v) {}
variant_type const& as_variant() const {
return variant_;
}
private:
variant_type variant_;
};
bool operator==(scriptable_value const& l, scriptable_value const& r)
{
return boost::apply_visitor(is_equal(), l.as_variant(), r.as_variant());
}
bool operator<(scriptable_value const& l, scriptable_value const& r)
{
return boost::apply_visitor(is_less(), l.as_variant(), r.as_variant());
}
std::ostream& operator<<(std::ostream& os, scriptable_value const& r)
{
return boost::apply_visitor(emit(os), r.as_variant());
}
int main()
{
auto x = scriptable_value(10);
auto y = scriptable_value("10");
auto x2 = scriptable_value(9);
auto y2 = scriptable_value("9");
std::cout << x << " == " << y << " : " << std::boolalpha << (x == y) << std::endl;
std::cout << x << " != " << y << " : " << std::boolalpha << (x != y) << std::endl;
std::cout << x << " == " << y2 << " : " << std::boolalpha << (x == y2) << std::endl;
std::cout << x << " != " << y2 << " : " << std::boolalpha << (x != y2) << std::endl;
std::cout << x << " < " << y << " : " << std::boolalpha << (x < y) << std::endl;
std::cout << x << " >= " << y << " : " << std::boolalpha << (x >= y) << std::endl;
std::cout << x << " < " << y2 << " : " << std::boolalpha << (x < y2) << std::endl;
std::cout << x << " >= " << y2 << " : " << std::boolalpha << (x >= y2) << std::endl;
std::cout << x << " == " << x2 << " : " << std::boolalpha << (x == x2) << std::endl;
std::cout << x << " != " << x2 << " : " << std::boolalpha << (x != x2) << std::endl;
}
expected output:
10 == "10" : true
10 != "10" : false
10 == "9" : false
10 != "9" : true
10 < "10" : false
10 >= "10" : true
10 < "9" : false
10 >= "9" : true
10 == 9 : false
10 != 9 : true
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With