Boost phoenix可变参数函数解析

毒蛇

我有一个函数“ 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%解析为……某种幻数。
    • 因此,我们知道iff%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]
            ;
    

    我们注意到您同时

    • 使用SA手动执行自动属性传播应该执行的操作([_val = _1]
    • 为“魔术”目的使用单个分支(这是您需要NullAST数据类型的地方)

    在下面我建议的解决方案中,规则变为:

    factor_ = null_ | string_ | double_ | function_call_;
    

    而已。

    专家提示:谨慎使用语义动作(另请参阅Boost Spirit:“语义动作是邪恶的”?


解决方案

总而言之,有足够的空间来简化和清理。在AST部门,

  • 使用显Null式子类型扩展Value变体
  • 重命名类型并进入命名空间以提高可读性
  • 删除AsNumValue没有目的功能。取而代之的是,有一个IsNull客人,只是报告trueNull值。
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_))
    
  • 请注意,它也使调试输出更具信息性

  • 我已自由地将其重命名TakeOneCoalesce[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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章