[英]Boost phoenix variadic function parse
I have a parser code as below for a function "TakeOne". 我有一个函数“ TakeOne”的解析器代码,如下所示。 TakeOne function works like it returns the first parameter which is not equal to '%null%' Ex:
TakeOne函数的工作方式类似于返回不等于'%null%'的第一个参数,例如:
TakeOne( %null% , '3', 'defaultVal'); --> result = 3
TakeOne( 5 , 'asd', 'defaultVal'); -> result = 5
Now I want to modify this function to 现在我想将此功能修改为
TakeOne(parm1, parm2, ... , defaultValue);
Is it possible to do this without using C++11 features? 不使用C ++ 11功能就可以做到这一点吗? Thanks
谢谢
#include <string>
#include <fstream>
#include <algorithm>
#include "sstream"
#include <locale.h>
#include <iomanip>
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/functional/hash.hpp>
#include <boost/variant.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/tokenizer.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/phoenix/function/adapt_function.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/special_functions/round.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/algorithm/string.hpp>
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
typedef double NumValue;
typedef boost::variant<double, std::wstring> GenericValue;
const std::wstring ParserNullChar = L"%null%";
const double NumValueDoubleNull = std::numeric_limits<double>::infinity();
//Convert string to numeric values
struct AsNumValue : boost::static_visitor<double>
{
double operator()(double d) const { return d; }
double operator()(std::wstring const& s) const
{
if(boost::iequals(s, ParserNullChar))
{
return NumValueDoubleNull;
}
try { return boost::lexical_cast<double>(s); }
catch(...)
{
throw;
}
}
};
double Num(GenericValue const& val)
{
return boost::apply_visitor(AsNumValue(), val);
}
bool CheckIfNumValueIsNull(double num)
{
if(num == NumValueDoubleNull)
return true;
else
return false;
}
bool CheckIfGenericValIsNull(const GenericValue& val)
{
std::wostringstream woss;
woss << val;
if(boost::iequals(woss.str(), ParserNullChar))
{
return true;
}
else if(val.which() != 1 && CheckIfNumValueIsNull(Num(val)))
{
return true;
}
else
return false;
}
GenericValue TakeOne(GenericValue val1, GenericValue val2, GenericValue def)
{
if(!CheckIfGenericValIsNull(val1))
{
return val1;
}
else if(!CheckIfGenericValIsNull(val2))
{
return val2;
}
else
return def;
}
BOOST_PHOENIX_ADAPT_FUNCTION(GenericValue, TakeOne_, TakeOne, 3)
template <typename It, typename Skipper = qi::space_type >
struct MapFunctionParser : qi::grammar<It, GenericValue(), Skipper>
{
MapFunctionParser() : MapFunctionParser::base_type(expr_)
{
using namespace qi;
function_call_ =
(no_case[L"TakeOne"] > '(' > expr_ > ',' > expr_ > ',' > expr_ > ')')
[_val = TakeOne_(_1, _2, _3) ];
string_ = (L'"' > *~char_('"') > L'"')
| (L"'" > *~char_("'") > L"'");
factor_ =
(no_case[ParserNullChar]) [_val = NumValueDoubleNull]
| double_ [ _val = _1]
| string_ [ _val = _1]
| function_call_ [ _val = _1]
;
expr_ = factor_;
on_error<fail> ( expr_, std::cout
<< phx::val("Error! Expecting ") << _4 << phx::val(" here: \"")
<< phx::construct<std::string>(_3, _2) << phx::val("\"\n"));
#ifdef _DEBUG
BOOST_SPIRIT_DEBUG_NODE(function_call_);
BOOST_SPIRIT_DEBUG_NODE(expr_);
BOOST_SPIRIT_DEBUG_NODE(string_);
BOOST_SPIRIT_DEBUG_NODE(factor_);
#endif
}
private:
qi::rule<It, std::wstring()> string_;
qi::rule<It, GenericValue(), Skipper> function_call_, expr_, factor_;
};
int main()
{
std::wstringstream wss;
typedef std::wstring::const_iterator AttIter;
MapFunctionParser<AttIter , boost::spirit::qi::space_type> mapFunctionParser;
bool ret;
GenericValue result;
std::wstring functionStr = L"TakeOne(%null%, 5, 'default')";
std::wstring::const_iterator beginExpression(functionStr.begin());
std::wstring::const_iterator endExpression(functionStr.end());
ret = boost::spirit::qi::phrase_parse(beginExpression,endExpression,mapFunctionParser,boost::spirit::qi::space,result);
std::wcout << result << std::endl;
return 0;
}
Here's a second answer to address (some of the) the XY-problem(s) you are probably trying to solve. 这是第二个答案,用于解决您可能试图解决的XY问题。
As I noted in a comment there are a number of code smells [1] in your example. 正如我在评论中指出的那样,您的示例中有许多代码气味 [1] 。 Let me explain what I mean.
让我解释一下我的意思。
Let's consider what the goal of the program is: You're doing input parsing . 让我们考虑一下程序的目标是什么 : 您正在执行输入解析 。
The result of parsing should be data, preferrably in C++ datatypes with strong type information, so you can avoid working with quirky (possibly invalid) variable text representations and focus on business logic. 解析的结果应该是数据,最好是具有强类型信息的C ++数据类型,因此可以避免使用古怪的(可能无效的)可变文本表示形式,而将精力放在业务逻辑上。
Now on to the smells: 现在来闻一下:
You define "abstract datatypes" (like NumValue
) but then you fail to use them consistently: 您定义了“抽象数据类型”(如
NumValue
),但随后却无法一致地使用它们:
typedef double NumValue; typedef boost::variant<double, std::wstring> GenericValue; // ^--- should be NumValue
Be more consistent, and make your code reflect the design : 更加一致,并使您的代码反映设计 :
namespace ast { typedef double Number; typedef std::wstring String; typedef boost::variant<Number, String> Value; }
You use a parser generator for the parsing, yet you are also invoking 您使用解析器生成器进行解析, 但是您也正在调用
boost::lexical_cast<double>
on ... strings boost::lexical_cast<double>
wostringstream
, from which you (forgetting std::ios::skipws
...) extract "a" string wostringstream
,从中(忘记std::ios::skipws
...)提取“ a”字符串 boost::iequals
to compare strings, that should already have been parsed into their strongly-typed AST types, independent of letter-case. boost::iequals
比较字符串,该字符串应该已经被解析为其强类型的AST类型,而与字母大小写无关。 You have a static_visitor
to act on variant types, yet you rely on stringification (using wostringstream
). 您有一个
static_visitor
可以处理变量类型, 但是您依赖于字符串化(使用wostringstream
)。 In fact, you only ever call the visitor on that variant iff you already know that it's a number: 事实上,你永远只能调用该变种访问者当且仅当你已经知道,这是一个数字:
else if(val.which() != 1 && CheckIfNumValueIsNull(Num(val)))
That's a bit funny, because in this case you could just have used boost::get<NumValue>(val)
to get the known-type value out. 这有点有趣,因为在这种情况下,您可以使用
boost::get<NumValue>(val)
来获取已知类型的值。
Pro Tip: Using "lowlevel" parsing/streaming operations while using a high-level parser-generator, is a code smell
专家提示:在使用高级解析器生成器的同时使用“低级”解析/流操作是代码的味道
Your generic value variant suggests that your grammar supports two kind of values. 您的通用值变体建议您的语法支持两种值。 Yet, your grammar definition clearly shows that you have a third type of value:
%null%
. 但是,您的语法定义清楚地表明您具有第三种类型的值:
%null%
。
There's evidence that you got somewhat confused by this yourself, as we can see the parser 有证据表明您自己对此有些困惑,因为我们可以看到解析器
%null%
(or %NULL%
etc) into ... some kind of magic number. %null%
(或%NULL%
等)解析为...某种幻数。 %null%
was parsed, it will always be a NumValue
in your AST %null%
进行了解析时,它始终是AST中的NumValue
wstring
subtype GenericValue
wstring
子类型GenericValue
GenericValue
as potentially null ? GenericValue
视为可能为null ? All in all it leads to a rather surprising... 总而言之,这导致了令人惊讶的...
Summary : You have
AsNumValue
which you (ironically) appear to be using to find out whether aString
might actually beNull
摘要 :您具有(讽刺地)似乎正在使用
AsNumValue
来查找String
是否实际上为Null
Hint: a String
could never represent the %null%
to begin with, it makes no sense to transform random strings into numbers, and random 'magic numeric values' should not have been used to represent Null
in the first place. 提示:
String
永远不能代表%null%
,将随机字符串转换为数字没有任何意义,并且首先不应该使用随机的“魔术数值”来表示Null
。
Your grammar makes unbalanced use of semantic actions : 您的语法不平衡地使用了语义动作 :
factor_ = (no_case[ParserNullChar]) [_val = NumValueDoubleNull] | double_ [ _val = _1] | string_ [ _val = _1] | function_call_ [ _val = _1] ;
We notice that you are simultaneously 我们注意到您同时
[_val = _1]
) [_val = _1]
) Null
AST datatype) Null
AST数据类型的地方) In my suggested solution below, the rule becomes: 在下面的建议解决方案中,规则变为:
factor_ = null_ | string_ | double_ | function_call_;
That's it. 而已。
Pro Tip: Using Semantic Actions sparingly (see also Boost Spirit: "Semantic actions are evil"? )
专家提示:谨慎使用语义动作(另请参阅Boost Spirit:“语义动作是邪恶的”? )
All in all, plenty of room to simplify and cleanup. 总而言之,有足够的空间来简化和清理。 In the AST department,
在AST部门,
Null
subtype Null
子类型扩展Value变量 AsNumValue
function which has no purpose. AsNumValue
函数。 Instead, have an IsNull
visitor that just reports true
for Null
values. IsNull
访问者,它仅对Null
值报告为true
。 namespace ast {
typedef double Number;
typedef std::wstring String;
struct Null {};
typedef boost::variant<Null, Number, String> Value;
struct IsNull
{
typedef bool result_type;
template <typename... T>
constexpr result_type operator()(T const&...) const { return false; }
constexpr result_type operator()(Null const&) const { return true; }
};
}
In the Grammar department, 在语法部门
Factor the grammar into rules that match the AST nodes 将语法分解为与AST节点匹配的规则
qi::rule<It, ast::String()> string_; qi::rule<It, ast::Number()> number_; qi::rule<It, ast::Null()> null_; qi::rule<It, ast::Value(), Skipper> factor_, expr_, function_call_;
This makes your grammar simple to maintain and to reason about: 这使您的语法易于维护和推理:
string_ = (L'"' > *~char_('"') > L'"') | (L"'" > *~char_("'") > L"'") ; number_ = double_; null_ = no_case["%null%"] > attr(ast::Null()); factor_ = null_ | string_ | double_ | function_call_; expr_ = factor_; BOOST_SPIRIT_DEBUG_NODES((expr_)(factor_)(null_)(string_)(number_)(function_call_))
Note it makes debug output more informative too 注意,它也使调试输出更加有用
I've taken the liberty to rename TakeOne
to Coalesce
[2] : 我冒昧地将
TakeOne
重命名为Coalesce
[2] :
function_call_ = no_case[L"Coalesce"] > ('(' > expr_ % ',' > ')') [ _val = Coalesce_(_1) ];
This is still using the approach like I showed in the other answer, only, the implementation has become much simpler because there is no longer so much confusion about what could be Null 这仍然使用了我在其他答案中所示的方法,只是,实现变得更加简单,因为不再对可能为Null的内容感到困惑
Take Away: Values that are Null are just... Null!
拿走: 空值就是...空值!
Removing the now unused header includes, and adding a load of test inputs: 删除现在未使用的标头包括,并添加大量测试输入:
int main()
{
typedef std::wstring::const_iterator It;
MapFunctionParser<It, boost::spirit::qi::space_type> parser;
for (std::wstring input : {
L"Coalesce()",
L"Coalesce('simple')",
L"CoALesce(99)",
L"CoalESCe(%null%, 'default')",
L"coalesce(%null%, -inf)",
L"COALESCE(%null%, 3e-1)",
L"Coalesce(%null%, \"3e-1\")",
L"COALESCE(%null%, 5, 'default')",
L"COALESCE(%NULL%, %null%, %nuLl%, %NULL%, %null%, %null%, %nUll%, %null%, %NULL%, %nUll%, %null%, \n"
L"%NULL%, %NULL%, %null%, %null%, %nUll%, %null%, %nUll%, %nULl%, %null%, %null%, %null%, \n"
L"%null%, %nUll%, %NULL%, %null%, %null%, %null%, %null%, %null%, %null%, %nUll%, %nulL%, \n"
L"%null%, %null%, %nUll%, %NULL%, 'this is the first nonnull', %nUll%, %null%, %Null%, \n"
L"%NULL%, %null%, %null%, %null%, %NULL%, %null%, %null%, %null%, %NULL%, %NULL%, %nuLl%, \n"
L"%null%, %null%, %nUll%, %nuLl%, 'this is the default')",
})
{
It begin(input.begin()), end(input.end());
ast::Value result;
bool ret = phrase_parse(begin, end, parser, qi::space, result);
std::wcout << std::boolalpha << ret << ":\t" << result << std::endl;
}
}
We can now test the parsing, evaluation and error-handling: 现在,我们可以测试解析,评估和错误处理:
Error! Expecting <list><expr_>"," here: ")"
false: %null%
true: simple
true: 99
true: default
true: -inf
true: 0.3
true: 3e-1
true: 5
true: this is the first nonnull
See it Live On Coliru 在Coliru上实时观看
Without the extra test cases it takes about 77 lines of code , less than half of your original code. 如果没有额外的测试用例,则大约需要77行代码 ,不到原始代码的一半 。
For future reference 备查
//#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/phoenix/function/adapt_function.hpp>
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
namespace ast {
typedef double Number;
typedef std::wstring String;
struct Null {
friend std::wostream& operator<<(std::wostream& os, Null) { return os << L"%null%"; }
friend std:: ostream& operator<<(std:: ostream& os, Null) { return os << "%null%"; }
};
typedef boost::variant<Null, Number, String> Value;
struct IsNull
{
typedef bool result_type;
template <typename... T>
constexpr result_type operator()(T const&...) const { return false; }
constexpr result_type operator()(Null const&) const { return true; }
};
Value Coalesce(std::vector<Value> const& arglist) {
for (auto& v : arglist)
if (!boost::apply_visitor(IsNull(), v))
return v;
//
if (arglist.empty())
return Value(Null());
else
return arglist.back(); // last is the default, even if Null
}
}
BOOST_PHOENIX_ADAPT_FUNCTION(ast::Value, Coalesce_, ast::Coalesce, 1)
template <typename It, typename Skipper = qi::space_type >
struct MapFunctionParser : qi::grammar<It, ast::Value(), Skipper>
{
MapFunctionParser() : MapFunctionParser::base_type(expr_)
{
using namespace qi;
function_call_ = no_case[L"Coalesce"]
> ('(' > expr_ % ',' > ')') [ _val = Coalesce_(_1) ];
string_ =
(L'"' > *~char_('"') > L'"')
| (L"'" > *~char_("'") > L"'");
number_ = double_;
null_ = no_case["%null%"] > attr(ast::Null());
factor_ = null_ | string_ | double_ | function_call_;
expr_ = factor_;
on_error<fail> (expr_, std::cout
<< phx::val("Error! Expecting ") << _4 << phx::val(" here: \"")
<< phx::construct<std::string>(_3, _2) << phx::val("\"\n"));
BOOST_SPIRIT_DEBUG_NODES((expr_)(factor_)(null_)(string_)(number_)(function_call_))
}
private:
qi::rule<It, ast::String()> string_;
qi::rule<It, ast::Number()> number_;
qi::rule<It, ast::Null()> null_;
qi::rule<It, ast::Value(), Skipper> factor_, expr_, function_call_;
};
int main()
{
typedef std::wstring::const_iterator It;
MapFunctionParser<It, boost::spirit::qi::space_type> parser;
for (std::wstring input : {
L"Coalesce()",
L"Coalesce('simple')",
L"CoALesce(99)",
L"CoalESCe(%null%, 'default')",
L"coalesce(%null%, -inf)",
L"COALESCE(%null%, 3e-1)",
L"Coalesce(%null%, \"3e-1\")",
L"COALESCE(%null%, 5, 'default')",
L"COALESCE(%NULL%, %null%, %nuLl%, %NULL%, %null%, %null%, %nUll%, %null%, %NULL%, %nUll%, %null%, \n"
L"%NULL%, %NULL%, %null%, %null%, %nUll%, %null%, %nUll%, %nULl%, %null%, %null%, %null%, \n"
L"%null%, %nUll%, %NULL%, %null%, %null%, %null%, %null%, %null%, %null%, %nUll%, %nulL%, \n"
L"%null%, %null%, %nUll%, %NULL%, 'this is the first nonnull', %nUll%, %null%, %Null%, \n"
L"%NULL%, %null%, %null%, %null%, %NULL%, %null%, %null%, %null%, %NULL%, %NULL%, %nuLl%, \n"
L"%null%, %null%, %nUll%, %nuLl%, 'this is the default')",
})
{
It begin(input.begin()), end(input.end());
ast::Value result;
bool ret = phrase_parse(begin, end, parser, qi::space, result);
std::wcout << std::boolalpha << ret << ":\t" << result << std::endl;
}
}
[1] Origin? [1]起源? http://c2.com/cgi/wiki?CodeSmell (maybe it was Kent Beck?)
http://c2.com/cgi/wiki?CodeSmell (也许是肯特·贝克?)
[2] Coalesce
referring to corresponding functions in some programming languages [2]在某些编程语言中
Coalesce
引用相应的功能
Update : I just addressed some other concerns in a second answer .
更新 :我只是在第二个回答中解决了其他一些问题。 Code went down to 77 lines while getting simpler and more robust (and answering your question too).
代码减少到77行,同时变得更加简单和强大(并且也回答了您的问题)。
The direct answer to your Phoenix question would be yes: 您的Phoenix问题的直接答案是:
struct TakeOne
{
template <typename...> struct result { typedef GenericValue type; };
template <typename... Args>
GenericValue operator()(Args const&... args) const {
return first(args...);
}
private:
GenericValue first(GenericValue const& def) const {
return def;
}
template <typename... Args> GenericValue first(GenericValue const& def, GenericValue const& head, Args const&... tail) const {
if (CheckIfGenericValIsNull(head))
return first(def, tail...);
else
return head;
}
};
static const boost::phoenix::function<TakeOne> TakeOne_;
And it would behave largely the same (although you'd need to pass the default as the first argument): 而且它的行为大致相同(尽管您需要将默认值作为第一个参数传递):
function_call_ = no_case[L"TakeOne"] > (
('(' > expr_ > ',' > expr_ > ')' ) [_val=TakeOne_(_2,_1)]
| ('(' > expr_ > ',' > expr_ > ',' > expr_ > ')' ) [_val=TakeOne_(_3,_1,_2)]
| ('(' > expr_ > ',' > expr_ > ',' > expr_ > expr_ > ')') [_val=TakeOne_(_4,_1,_2,_3)]
// ... etc
);
However, as you can see, it's not so flexible! 但是,如您所见,它并不那么灵活! Variadics are likely not what you wanted, because variadics imply statically known numbers of arguments.
可变参数可能不是您想要的,因为可变参数隐含着静态已知的参数数量。 Something that depends on runtime input (being parsed) can never fit into the 'statically known' category.
某些依赖于运行时输入(已解析)的内容永远无法放入“静态已知”类别。 So I'd suggest this:
所以我建议这样做:
function_call_ =
no_case[L"TakeOne"] >
('(' > expr_ % ',' > ')') [_val=TakeOne_(_1)];
So you're passing a std::vector<GenericValue>
instead. 因此,您传递的是
std::vector<GenericValue>
。 Now, TakeOne
becomes a breeze: 现在,
TakeOne
变得轻而易举:
GenericValue TakeOne(std::vector<GenericValue> const& arglist) {
assert(!arglist.empty());
for (auto& v : arglist)
if (!CheckIfGenericValIsNull(v))
return v;
return arglist.back(); // last is the default
}
BOOST_PHOENIX_ADAPT_FUNCTION(GenericValue, TakeOne_, TakeOne, 1)
To simplify, here's the visitor re-imagined: 为简化起见,以下是访客的重新构想:
struct IsNull : boost::static_visitor<bool> {
bool operator()(double num) const {
return (num == NumValueDoubleNull);
}
bool operator()(std::wstring const& s) const {
return boost::iequals(s, ParserNullChar);
}
};
GenericValue TakeOne(std::vector<GenericValue> const& arglist) {
assert(!arglist.empty());
for (auto& v : arglist)
if (!boost::apply_visitor(IsNull(), v))
return v;
return arglist.back(); // last is the default
}
BOOST_PHOENIX_ADAPT_FUNCTION(GenericValue, TakeOne_, TakeOne, 1)
That "fix" alone saves you ~51 LoC (112 vs. 163) . 仅此一个“修复”即可为您节省〜51 LoC(112对163) 。 See it Live on Coliru
在Coliru上实时观看
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.