处理框架以从脚本调用不同类型函数的 C++ 方法是什么?
What is the C++ way of handling a framework to call functions of different types from a script?
我尝试了不同的方法,并询问了几个关于我的要求的子问题的具体问题。但是我的解决方案并没有真正按预期工作,所以我退后一步,从更一般的角度在这里提问。请记住,我不是 C++ 专业人士。也不是初学者,但我还在学习这门语言。
所以,我有以下需求。我需要读入文本文件,其中包含 "Greater" 或 "Equals" 等条件,所有这些都类似于 return 布尔值的函数。文本文件还包括这些条件的参数。请注意,这些参数可以是不同类型(整数、小数等),并且每个这样的条件可以采用不同数量的参数(例如 "Equals" 需要 2 个参数,而 "Between" 需要 3 个参数) .因此该文件可能看起来像这样:
Greater, 2, 3
Greater, 2.4, 1.0
Equals, true, true
Between, 20, 10, 30
读入该文件并对其进行解析的逻辑已经完成。现在我需要 "concatenate" 所有这些布尔函数及其参数,并检查它们是否全部为真。
所以我想我会用静态方法创建函数或 class 来表示这些布尔测试函数,然后创建指向这些函数的函数指针映射,并按它们的名称进行映射。在运行时,我会读入文件,调用相应的函数指针并传入参数。这对我来说似乎很容易,但实际上我最纠结的是这些布尔函数可以采用不同数量的参数,并且这些参数可以是不同的类型。
您能推荐一种在 C++ 中解决该要求的方法吗?我不是要一个完整的解决方案,而是要一个合适的 C++ 方法,或者我可以遵循的指南。提前致谢!
你的根本问题是 C++ 是一种静态类型的语言。通用编程语言往往分为两大类:静态类型和动态类型。在动态类型语言(如 Perl)中,对象的类型是在运行时确定的。在静态类型语言中,如 C++,对象的类型在编译时指定。
这并不意味着这在 C++ 中无法以类型安全的方式实现。是的,但它需要一些工作。
通常的方法是将所有类型封装到 classes 中,这些 classes 派生自一些定义虚拟方法的基 class,子 classes 实现它们。让我们只使用 int
s 和 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.
};
现在,您可以执行基本操作了。在 Number
基础 class 中,您定义转换方法:
virtual FloatNumber asFloat() const = 0;
virtual IntNumber asInt() const = 0;
在每个子class中,你将以明显的方式实现这些,返回*this
,如果它是相同的类型,或者构造另一个子class并返回新的-构造class.
现在,您可以执行基本操作了。比如说,等于:
virtual bool equals(const Number &other) const =0;
现在,您可以在每个子class 中实现这个虚拟方法。例如,在 FloatNumber::equals()
中,您将调用 other.asFloat()
,并将其 val
与其自身的 val
进行比较。 IntNumber::equals()
同上。如果比较的两个 Number
都是同一类型,则直接比较两个值;否则会发生自动类型转换。
现在,这不是一个完美的方法,因为如果第一个数字是 IntNumber
,FloatNumber
最终会向下转换为 int
,而你真的希望转换以另一种方式进行。也有 class 以类型安全的方式解决这个问题的合理方法。但首先,您应该先实现这个基本方法,然后再考虑处理各种极端情况。
通过这种方式,您可以继续构建一个 class 层次结构,实现对数字的通用操作。这可能比您预期的要多,但这是在 C++ 中以完全类型安全的方式正确执行此类操作的方法。现代 C++ 编译器非常高效,最终结果将非常小且紧凑。
您的最后一步是读取文件、解析值并进行简单查找 table 将 "Equals" 映射到 "equals" 方法,等等。 ..
boost 变体是恕我直言的最简单方法:
#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;
}
预期输出:
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
这里是显示的输入的快速和粗略的 Spirit 语法。
UPDATE
Now added invocation and implementation of the predicate functions (GreaterImpl
and EqualsImpl
).
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 a std::runtime_error
exception that provides type feedback to the caller.
#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";
}
}
打印:
--- 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;
我尝试了不同的方法,并询问了几个关于我的要求的子问题的具体问题。但是我的解决方案并没有真正按预期工作,所以我退后一步,从更一般的角度在这里提问。请记住,我不是 C++ 专业人士。也不是初学者,但我还在学习这门语言。
所以,我有以下需求。我需要读入文本文件,其中包含 "Greater" 或 "Equals" 等条件,所有这些都类似于 return 布尔值的函数。文本文件还包括这些条件的参数。请注意,这些参数可以是不同类型(整数、小数等),并且每个这样的条件可以采用不同数量的参数(例如 "Equals" 需要 2 个参数,而 "Between" 需要 3 个参数) .因此该文件可能看起来像这样:
Greater, 2, 3
Greater, 2.4, 1.0
Equals, true, true
Between, 20, 10, 30
读入该文件并对其进行解析的逻辑已经完成。现在我需要 "concatenate" 所有这些布尔函数及其参数,并检查它们是否全部为真。
所以我想我会用静态方法创建函数或 class 来表示这些布尔测试函数,然后创建指向这些函数的函数指针映射,并按它们的名称进行映射。在运行时,我会读入文件,调用相应的函数指针并传入参数。这对我来说似乎很容易,但实际上我最纠结的是这些布尔函数可以采用不同数量的参数,并且这些参数可以是不同的类型。
您能推荐一种在 C++ 中解决该要求的方法吗?我不是要一个完整的解决方案,而是要一个合适的 C++ 方法,或者我可以遵循的指南。提前致谢!
你的根本问题是 C++ 是一种静态类型的语言。通用编程语言往往分为两大类:静态类型和动态类型。在动态类型语言(如 Perl)中,对象的类型是在运行时确定的。在静态类型语言中,如 C++,对象的类型在编译时指定。
这并不意味着这在 C++ 中无法以类型安全的方式实现。是的,但它需要一些工作。
通常的方法是将所有类型封装到 classes 中,这些 classes 派生自一些定义虚拟方法的基 class,子 classes 实现它们。让我们只使用 int
s 和 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.
};
现在,您可以执行基本操作了。在 Number
基础 class 中,您定义转换方法:
virtual FloatNumber asFloat() const = 0;
virtual IntNumber asInt() const = 0;
在每个子class中,你将以明显的方式实现这些,返回*this
,如果它是相同的类型,或者构造另一个子class并返回新的-构造class.
现在,您可以执行基本操作了。比如说,等于:
virtual bool equals(const Number &other) const =0;
现在,您可以在每个子class 中实现这个虚拟方法。例如,在 FloatNumber::equals()
中,您将调用 other.asFloat()
,并将其 val
与其自身的 val
进行比较。 IntNumber::equals()
同上。如果比较的两个 Number
都是同一类型,则直接比较两个值;否则会发生自动类型转换。
现在,这不是一个完美的方法,因为如果第一个数字是 IntNumber
,FloatNumber
最终会向下转换为 int
,而你真的希望转换以另一种方式进行。也有 class 以类型安全的方式解决这个问题的合理方法。但首先,您应该先实现这个基本方法,然后再考虑处理各种极端情况。
通过这种方式,您可以继续构建一个 class 层次结构,实现对数字的通用操作。这可能比您预期的要多,但这是在 C++ 中以完全类型安全的方式正确执行此类操作的方法。现代 C++ 编译器非常高效,最终结果将非常小且紧凑。
您的最后一步是读取文件、解析值并进行简单查找 table 将 "Equals" 映射到 "equals" 方法,等等。 ..
boost 变体是恕我直言的最简单方法:
#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;
}
预期输出:
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
这里是显示的输入的快速和粗略的 Spirit 语法。
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.
#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";
}
}
打印:
--- 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;