What is the C ++ way of processing the framework for calling functions of different types from a script?

I tried different approaches and asked some specific questions regarding the sub-problems of my requirement here. But my solution does not work as expected, so I take a step back and ask here from a more general point of view. Please keep in mind that I am not a C ++ pro. Not a newbie, but I'm still learning the language.

So, I have the following requirement. I need to read text files containing conditions, such as "Big" or "Equal", all as functions that return boolean values. Text files also include options for these conditions. Please note that these parameters can be of different types (integer, decimal, etc.), and each such condition can take a different number of parameters (for example, "Equal" takes 2 parameters, and "Between" takes 3 parameters). Thus, the file might look something like this:

Greater, 2, 3 Greater, 2.4, 1.0 Equals, true, true Between, 20, 10, 30 

The logic for reading this file and disassembling it has already been completed. Now I need to "concatenate" all these logical functions with their parameters and check if they are all true.

So, I thought that I would create functions or a class with static methods to represent these logical test functions, and then create a map of function pointers to these functions displayed by their name. At runtime, I then read in the file, called the corresponding function pointer, and passed parameters. This seemed easy to me, but in fact, I am afraid mainly because these logical functions can take a different number of parameters and that they can be of different types.

Can you recommend a way to solve this requirement in C ++? I am not asking for a complete solution, but for a suitable C ++ approach or a guide that I could follow. Thanks in advance!

+5
source share
3 answers

Here is a quick and grammar grammar for the input shown.

UPDATE

Now the call and implementation of predicate functions ( GreaterImpl and EqualsImpl ) is EqualsImpl .

I tried to be smart, allowing you to compare mixed arithmetic types (but not, for example, Greater(bool,string) ). If you compare incompatible types, you will get a std::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"; } } 

Print

 --- 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; 
+6
source

boost option 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 
+3
source

Your main problem is that C ++ is a statically typed language. General-purpose programming languages, as a rule, fall into two crude categories: statically and dynamically typed. In a dynamically typed language such as Perl, the type of an object is determined at runtime. In a statically typed language, such as C ++, the type of an object is specified at compile time.

This does not mean that this is not possible in C ++ in a safe manner. It is, but it does require some work.

The usual approach is to encapsulate all types into classes that are derived from some base class that defines virtual methods, with subclasses that implement them. Use only int and float s.

 // Forward declarations class FloatNumber; class IntNumber; class Number { // virtual methods to be defined later. }; class FloatNumber : public Number { float value; // Implements the virtual methods for float values. }; class IntNumber : public Number { int value; // Implements the virtual methods for int values. }; 

Now you can perform basic operations. In the base Number class, you define conversion methods:

 virtual FloatNumber asFloat() const = 0; virtual IntNumber asInt() const = 0; 

In each subclass, you implement them in an obvious way, returning either *this if it is the same type, or create another subclass and return the newly constructed class.

Now you can perform basic operations. Let's say equal to:

 virtual bool equals(const Number &other) const =0; 

Now you implement this virtual method in each subclass. For example, in FloatNumber::equals() you call other.asFloat() and compare its val with your val . Same thing for IntNumber::equals() . If both Number , which are compared, are of the same type, this leads to a direct comparison of the two values; otherwise, automatic type conversion occurs.

Now this is not an ideal approach, since if the first number is IntNumber , FloatNumber ends with the conversion down to int , and you really want the conversion to go the other way. There are classic ways to solve this problem in a safe type. But first you must first implement this basic approach, and then worry about working with various corner cabinets.

Thus, you can continue and create a class hierarchy that implements the general operation on numbers. This is most likely more work than you expected, but it is how to do things like this in C ++ correctly, in a completely safe way. Modern C ++ compilers are quite efficient, and the end result will be quite small and compact.

The last step for you is to read the file, analyze the values ​​and have a simple lookup table that maps the "Equals" to the "equals" method, etc ...

+1
source

Source: https://habr.com/ru/post/1261397/


All Articles