[英]Boost phoenix variadic function parse
我有一個函數“ 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
視為可能為null ? 總而言之,這導致了令人驚訝的...
摘要 :您具有(諷刺地)似乎正在使用
AsNumValue
來查找String
是否實際上為Null
提示: String
永遠不能代表%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
訪問者,它僅對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; }
};
}
在語法部門
將語法分解為與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
如果沒有額外的測試用例,則大約需要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
引用相應的功能
您的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_;
而且它的行為大致相同(盡管您需要將默認值作為第一個參數傳遞):
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
);
但是,如您所見,它並不那么靈活! 可變參數可能不是您想要的,因為可變參數隱含着靜態已知的參數數量。 某些依賴於運行時輸入(已解析)的內容永遠無法放入“靜態已知”類別。 所以我建議這樣做:
function_call_ =
no_case[L"TakeOne"] >
('(' > expr_ % ',' > ')') [_val=TakeOne_(_1)];
因此,您傳遞的是std::vector<GenericValue>
。 現在, 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)
為簡化起見,以下是訪客的重新構想:
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)
僅此一個“修復”即可為您節省〜51 LoC(112對163) 。 在Coliru上實時觀看
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.