簡體   English   中英

Boost phoenix可變參數函數解析

[英]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] ; 

    我們注意到您同時

    • 使用SA手動執行應該執行的自動屬性傳播( [_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

在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引用相應的功能

更新 :我只是在第二個回答中解決了其他一些問題。 代碼減少到77行,同時變得更加簡單和強大(並且也回答了您的問題)。

您的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM