我有一个函数“ TakeOne”的解析器代码,如下所示。TakeOne函数的工作方式类似于返回不等于'%null%'的第一个参数,例如:
TakeOne( %null% , '3', 'defaultVal'); --> result = 3
TakeOne( 5 , 'asd', 'defaultVal'); -> result = 5
现在我想将此功能修改为
TakeOne(parm1, parm2, ... , defaultValue);
是否可以在不使用C ++ 11功能的情况下做到这一点?谢谢
#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;
}
这是第二个答案,用于解决您可能试图解决的XY问题。
正如我在评论中指出的那样,您的示例中有许多代码气味[1]。让我解释一下我的意思。
让我们考虑一下程序的目标是什么:您正在执行输入解析。
解析的结果应该是数据,最好是具有强类型信息的C ++数据类型,因此可以避免使用古怪的(可能无效的)可变文本表示形式,而将精力放在业务逻辑上。
现在来闻一下:
您定义了“抽象数据类型”(如NumValue
),但随后无法始终如一地使用它们:
typedef double NumValue;
typedef boost::variant<double, std::wstring> GenericValue;
// ^--- should be NumValue
更加一致,并使您的代码反映设计:
namespace ast {
typedef double Number;
typedef std::wstring String;
typedef boost::variant<Number, String> Value;
}
您使用解析器生成器进行解析,但是您也正在调用
boost::lexical_cast<double>
在...弦上wostringstream
,您(忘记了std::ios::skipws
...)从中提取“ a”字符串boost::iequals
比较字符串,这些字符串应该已经被解析为其强类型的AST类型,而与字母大小写无关。您必须static_visitor
对变体类型采取行动,但您要依赖字符串化(使用wostringstream
)。事实上,你永远只能调用该变种访问者当且仅当你已经知道,这是一个数字:
else if(val.which() != 1 && CheckIfNumValueIsNull(Num(val)))
这有点可笑,因为在这种情况下,您可能只是用来boost::get<NumValue>(val)
获取已知类型的值。
专家提示:在使用高级解析器生成器的同时使用“低级”解析/流操作是代码的味道
您的通用值变体建议您的语法支持两种值。但是,您的语法定义清楚地表明您具有第三种价值:%null%
。
有证据表明您自己对此有些困惑,因为我们可以看到解析器
%null%
(或其他%NULL%
)解析为……某种幻数。%null%
被解析后,它在您的AST中将始终是NumValue
wstring
子类型GenericValue
GenericValue
视为空值?总而言之,这导致了相当令人惊讶的...
摘要:您
AsNumValue
(讽刺地)似乎正在使用它来查找是否String
可能实际上是Null
提示:aString
永远无法代表%null%
,将随机字符串转换为数字是没有意义的,并且首先不应该使用随机的“魔术数值”来表示Null
。
您的语法不平衡地使用了语义动作:
factor_ =
(no_case[ParserNullChar]) [_val = NumValueDoubleNull]
| double_ [ _val = _1]
| string_ [ _val = _1]
| function_call_ [ _val = _1]
;
我们注意到您同时
[_val = _1]
)Null
AST数据类型的地方)在下面我建议的解决方案中,规则变为:
factor_ = null_ | string_ | double_ | function_call_;
而已。
专家提示:谨慎使用语义动作(另请参阅Boost Spirit:“语义动作是邪恶的”?)
总而言之,有足够的空间来简化和清理。在AST部门,
Null
式子类型扩展Value变体AsNumValue
没有目的的功能。取而代之的是,有一个IsNull
客人,只是报告true
的Null
值。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; }
};
}
在语法部门,
将语法分解为与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_;
这使您的语法易于维护和推理:
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_))
请注意,它也使调试输出更具信息性
我已自由地将其重命名TakeOne
为Coalesce
[2]:
function_call_ = no_case[L"Coalesce"]
> ('(' > expr_ % ',' > ')') [ _val = Coalesce_(_1) ];
这仍然沿用我在其他答案中所示的方法,只是实现变得更加简单,因为不再对可能为Null的内容感到困惑
拿走: 空值就是...空值!
删除现在未使用的标头包括,并添加测试输入负载:
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;
}
}
现在,我们可以测试解析,评估和错误处理:
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
看到它住在Coliru
如果没有额外的测试用例,则大约需要77行代码,不到原始代码的一半。
备查
//#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]起源?http://c2.com/cgi/wiki?CodeSmell(也许是肯特·贝克?)
[2] Coalesce
在某些编程语言中引用相应的功能
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句