提高规则的精神排列

时间:2013-07-22 16:36:43

标签: c++ parsing boost-spirit boost-spirit-qi boost-phoenix

我有两种类型的表达式需要解析并计算结果。

  • Artimetic表达式:+, - ,*,/和sqrt()函数; 例如:“2 + 3 * sqrt(100 * 25)” - >应计算为152

  • 函数:GetSubString()和ConcatenateStrings() 例如:“GetSubString('100str1',0,3)” - >应计算为100

我有两个单独的语法来解析这些表达式类型。现在我想结合这两个语法,并可以将这些表达式一起定义。

例如:

  • “GetSubString('100str1',0,2 + 1)+ sqrt(9)” - >结果= 103
  • “2 + 3 * sqrt(GetSubString('100str1',0,2 + 1))” - >结果= 32

我尝试使用置换运算符组合下面的2个语法。但它没有编译。

    expr_    =
        ( *( (function_call_ ^ arithmeticexpression_)| string_ ));

这是一个合并我的function_call_和arithmeticexpression_规则的正确方法,或者我该怎么做?

typedef boost::variant<int, float, double, std::wstring> RetValue;


RetValue CTranslationFunctions::GetSubString(RetValue const& str, RetValue position, RetValue len)
{
    std::wstring strToCut;
    size_t posInt = 0;
    size_t lenInt = 0;

    try
    {
        strToCut = boost::get<std::wstring>(str);
        posInt = boost::get<int>(position);
        lenInt = boost::get<int>(len);
    }
    catch (const boost::bad_get&)
    {
        throw;
    }

    return strToCut.substr(posInt, lenInt);
}

RetValue CTranslationFunctions::ConcatenateStrings(RetValue const& a, RetValue const& b) 
{
    wostringstream woss;
    woss << a << b;
    return woss.str();
}

double CTranslationFunctions::Negate(double num)
{
    return -num;
}

double CTranslationFunctions::Add(double num1 , const double num2)
{
    return num1 + num2;
};

double CTranslationFunctions::Subtruct(double num1 , double num2)
{
    return num1 - num2;
};

double CTranslationFunctions::Multiply(double num1 , double num2)
{
    return num1 * num2;
};

double CTranslationFunctions::Divide(double num1 , double num2)
{
    return num1 / num2;
};

double CTranslationFunctions::Sqrt(double num)
{
    return sqrt(num);
}

class InvalidParamEx{};

double CTranslationFunctions::ConvertStringToDouble(RetValue val)
{
    wostringstream wss;
    double dNum;
    wss << val;
    std::wistringstream iss;
    iss.str(wss.str());
    try
    {
        iss >> dNum;
    }
    catch (...)
    {
        throw InvalidParamEx();
    }

    return dNum;
}


BOOST_PHOENIX_ADAPT_FUNCTION(RetValue, ConcatenateStrings_, ConcatenateStrings, 2)
BOOST_PHOENIX_ADAPT_FUNCTION(RetValue, GetContainerId_, GetContainerId, 2)

BOOST_PHOENIX_ADAPT_FUNCTION(double, Add_, Add, 2)
BOOST_PHOENIX_ADAPT_FUNCTION(double, Subtruct_, Subtruct, 2)
BOOST_PHOENIX_ADAPT_FUNCTION(double, Multiply_, Multiply, 2)
BOOST_PHOENIX_ADAPT_FUNCTION(double, Divide_, Divide, 2)
BOOST_PHOENIX_ADAPT_FUNCTION(double, Negate_, Negate, 1)
BOOST_PHOENIX_ADAPT_FUNCTION(double, Sqrt_, Sqrt, 1)
BOOST_PHOENIX_ADAPT_FUNCTION(double, ConvertStringToDouble_, ConvertStringToDouble, 1)

// Grammar to parse map functions
template <typename It, typename Skipper = qi::space_type >
struct MapFunctionParser : qi::grammar<It, RetValue(), Skipper, qi::locals<char>  >
{
    MapFunctionParser() : MapFunctionParser::base_type(expr_)
    {
        using namespace qi;

        function_call_ = 

        | (lit(L"GetSubString") > '(' > expr_ > ',' > expr_ > ',' > expr_ > ')')               
            [ _val = GetSubString_(_1, _2, _3) ]
        | (lit(L"ConcatenateStrings") > '(' > expr_ > lit(',') > expr_ > ')')               
            [ _val = ConcatenateStrings_(_1, _2) ];


        string_ = as_wstring[omit    [ char_("'\"") [_a =_1] ]        
        >> no_skip [ *(char_ - char_(_a))  ]
        >> lit(_a)];

        arithmeticexpression_ =
            term_                        [_val = _1]
        >>  *( ('+' >> term_           [_val = Add_(_val,_1)])
             |   ('-' >> term_           [_val = Subtruct_(_val, _1)])
            );

        term_ =
            factor_              [_val = _1]
        >> *( ('*' >> factor_  [_val = Multiply_(_val, _1)])
            |   ('/' >> factor_  [_val = Divide_(_val, _1)])
            );

        factor_ =
            double_                            [_val = _1]
          string_                            [_val = ConvertStringToDouble(_1)]
      |   ('-' >> factor_                  [_val = Negate_(_1)])
      |   ('+' >> factor_                  [_val = _1])
        |   (L"Sqrt" > '(' > double_ > ')'  )     [_val = Sqrt_(_1)]);


        expr_    =
            ( *( (function_call_ ^ arithmeticexpression_)| string_ ));


        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_NODE(function_call_);
        BOOST_SPIRIT_DEBUG_NODE(expr_);
        BOOST_SPIRIT_DEBUG_NODE(string_);
        BOOST_SPIRIT_DEBUG_NODE(funcparameter_);
        BOOST_SPIRIT_DEBUG_NODE(arithmeticexpression_);
        BOOST_SPIRIT_DEBUG_NODE(factor_);
        BOOST_SPIRIT_DEBUG_NODE(term_);
    }

private:
    qi::rule<It, RetValue(), Skipper, qi::locals<char> > function_call_, expr_, funcparameter_;
    qi::rule<It, wstring(), Skipper, qi::locals<char> > string_;
    qi::rule<It, double(), Skipper> arithmeticexpression_, factor_,  term_;
};

1 个答案:

答案 0 :(得分:6)

修改 将我的早期回复移至底部

BIG UPDATE

这需要一段时间。主要是因为显示的代码有奇怪的问题:

  • 有几条规则包含语法错误(function_callfactor_
  • GetContainerId的引用,而GetSubstring从未适应凤凰
  • CTranslationFunctions类型不存在,并且声明了成员函数
  • 但是ADAPT_FUNCTION宏仍然引用了成员函数名称​​,就好像它们应该在封闭的命名空间中一样(?!)

所以我基本上最终做的是重写。是的,我知道。我疯了。不过,让我引导您完成它,解释我改变的一些事情以及原因。

#define BOOST_SPIRIT_USE_PHOENIX_V3
// #define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/phoenix/function/adapt_function.hpp>
#include <boost/lexical_cast.hpp>

namespace qi    = boost::spirit::qi;
namespace phx   = boost::phoenix;

typedef boost::variant<int, double>               NumValue;
typedef boost::variant<int, double, std::wstring> GenericValue;

马上,我将 numeric 泛型值的概念分开。这是因为区别对于某些表达式(主要是算术表达式)很重要。我本可以在任何地方使用GenericValue,但我们稍后会看到NumValue如何使算术评估更简单。

struct InvalidParamEx : public virtual std::exception 
{
    const char* what() const noexcept { return "Invalid type of operand/parameter"; }
};

您的例外类型,显示了一些良好做法。我们在期望数值时抛出它,但GenericValue包含不兼容的东西。怎么样?我们来看看:

struct AsNumValue : boost::static_visitor<NumValue>
{
    int      operator()(int i)                 const { return i; }
    double   operator()(double d)              const { return d; }
    NumValue operator()(std::wstring const& s) const
    { 
        try { return boost::lexical_cast<int>(s); }    catch(...) {}
        try { return boost::lexical_cast<double>(s); } catch(...) {}
        throw InvalidParamEx();
    }
};

class CTranslationFunctions
{
    // helper
    NumValue static num(GenericValue const& v) { return boost::apply_visitor(AsNumValue(), v); }

有。我为你定义了缺少的类,并立即添加了转换GenericValue → NumValue的帮助器。正如您所看到的,我使用boost::lexical_cast,因为重新发明轮子是没有用的。请注意,您之前使用ConvertStringToDouble的方法存在几个大问题:

  1. 它总是会产生double值,而您的功能可能需要int
  2. 它会将'100str1'视为值100而不会发出警告
  3. 它发生在错误的时间:任何简单term的字符串都将被转换为double,即使它真的是一个字符串。 (为什么这是相关的,当您看到修改后的expr_term_规则时,这些内容会变得清晰。
  4. 让我们继续前进:

      public:
        static GenericValue GetSubString(GenericValue const& str, GenericValue position, GenericValue len);
        static GenericValue ConcatenateStrings(GenericValue const& a, GenericValue const& b);
    

    是的,我们稍后会对它们进行定义。现在,为算术运算函数做好准备:

    #define DEFUNOP(name, expr) private: struct do_##name : boost::static_visitor<NumValue> {    \
            template <typename T1> NumValue operator()(T1 const& a) const { return expr; }       \
        }; \
        public: static NumValue name(GenericValue const& a) { auto na=num(a); return boost::apply_visitor(do_##name(), na); }
    #define DEFBINOP(name, infix) struct do_##name : boost::static_visitor<NumValue> {           \
            template <typename T1, typename T2> NumValue operator()(T1 const&a, T2 const&b) const\
            { return a infix b; }                                                                \
        }; \
        public: static NumValue name(GenericValue const& a, GenericValue const& b) { auto na=num(a), nb=num(b); return boost::apply_visitor(do_##name(), na, nb); }
    
        // define the operators polymorphically, so `int` + `double` becomes `double`, but `int` * `int` stays `int`
        DEFBINOP(Add     , +);
        DEFBINOP(Subtruct, -);
        DEFBINOP(Multiply, *);
        DEFBINOP(Divide  , /);
        DEFUNOP (Negate  , -a);
        DEFUNOP (Sqrt    , sqrt(a));
    };
    

    Whoaaaaah 那里发生了什么?好吧,评论说明了一切:

    • 您需要区分int + intdouble + int等。这称为多态评估。示例:GetSubString('100str1', 0, 2+1)可能无法运行,因为2+1需要评估为int(3),但您的double Add(double,double)始终生成double
    • 我使用MACRO删除了为每个操作符创建多态函数对象的繁琐工作
    • 我允许decltype检测混合情况下的结果类型
    • 此处NumValue的优势在GenericValue以上:因为NumValue只能是intdouble,我们知道通用 operator()实施涵盖所有合法组合。
    • 为了确保所有参数都是NumValues,在调用函数对象之前,它们会通过asNumeric传递。

    这很好地解决了你的算术运算,还有另外一个好处:它消除了需要&#39;对于ConvertStringToDouble ,因为您需要转换为NumValue,即评估算术运算。 当我们修改你的语法以支持你想要的输入表达式时,这是一件很重要的事情。

    如果你来到这里,你已经看到了粗糙的部分。其余的一帆风顺

    GenericValue CTranslationFunctions::GetSubString(GenericValue const& str, GenericValue position, GenericValue len)
    {
        using boost::get;
        return get<std::wstring>(str).substr(get<int>(position), get<int>(len));
    }
    

    是的,我稍微缩短了一点。

    GenericValue CTranslationFunctions::ConcatenateStrings(GenericValue const& a, GenericValue const& b) 
    {
        std::wostringstream woss;
        woss << a << b;
        return woss.str();
    }
    
    BOOST_PHOENIX_ADAPT_FUNCTION(GenericValue, ConcatenateStrings_, CTranslationFunctions::ConcatenateStrings, 2)
    BOOST_PHOENIX_ADAPT_FUNCTION(GenericValue, GetSubString_      , CTranslationFunctions::GetSubString      , 3)
    
    BOOST_PHOENIX_ADAPT_FUNCTION(NumValue    , Add_               , CTranslationFunctions::Add               , 2)
    BOOST_PHOENIX_ADAPT_FUNCTION(NumValue    , Subtruct_          , CTranslationFunctions::Subtruct          , 2)
    BOOST_PHOENIX_ADAPT_FUNCTION(NumValue    , Multiply_          , CTranslationFunctions::Multiply          , 2)
    BOOST_PHOENIX_ADAPT_FUNCTION(NumValue    , Divide_            , CTranslationFunctions::Divide            , 2)
    BOOST_PHOENIX_ADAPT_FUNCTION(NumValue    , Negate_            , CTranslationFunctions::Negate            , 1)
    BOOST_PHOENIX_ADAPT_FUNCTION(NumValue    , Sqrt_              , CTranslationFunctions::Sqrt              , 1)
    

    打哈欠。我们知道如何适应凤凰城的功能; 让我们进入语法定义!

    // Grammar to parse map functions
    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["GetSubString"]       > '(' > expr_ > ',' > expr_ > ',' > expr_ > ')') [ _val = GetSubString_(_1, _2, _3)   ]
            | (no_case["ConcatenateStrings"] > '(' > expr_ > ',' > expr_ > ')')               [ _val = ConcatenateStrings_(_1, _2) ]
            | (no_case["Sqrt"]               > '(' > expr_ > ')')                             [ _val = Sqrt_(_1)                   ]
            ;
    
            string_ = // keep it simple, silly (KISS)
                (L'"' > *~char_('"') > L'"')
              | (L"'" > *~char_("'") > L"'");  
    
            arithmeticexpression_ =
                term_                  [ _val = _1                  ]
            >>  *( ('+' >> term_       [ _val = Add_(_val,_1)       ])
                 | ('-' >> term_       [ _val = Subtruct_(_val, _1) ])
                 );                      
    
            term_ =                      
                  factor_              [ _val = _1                  ]
                >> *( ('*' >> factor_  [ _val = Multiply_(_val, _1) ])
                    | ('/' >> factor_  [ _val = Divide_(_val, _1)   ])
                    );
    
            factor_ =
                    int_               [ _val = _1          ]
                |   double_            [ _val = _1          ]
                |   string_            [ _val = _1          ]
                |   ('-' >> factor_)   [ _val = Negate_(_1) ]
                |   ('+' >> factor_)   [ _val = _1          ]
                |   function_call_     [ _val = _1          ]
                ;
    
            expr_ = arithmeticexpression_;
    
            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((function_call_) (expr_) (string_) (funcparameter_) (arithmeticexpression_) (factor_) (term_))
        }
    
    private:
        qi::rule<It, std::wstring()> 
            string_; // NO SKIPPER (review)
        qi::rule<It, GenericValue(), Skipper> 
            function_call_, expr_, funcparameter_, // NO LOCALS (review)
            arithmeticexpression_, term_, factor_;
    };
    

    好。我们在这里有什么改变了什么?

    • 我删除了qi::locals,这只是string_规则中使用的" oops ",我重写了这一点,以兑现KISS principle
    • 我还修复了字符串中的空格问题(您的解析器将解析"oops"string_相同)。我是通过从qi::lexeme[]声明中删除船长来实现的。这与将整个规则包含在Sqrt
    • 中具有相同的效果
    • 我已将function_call_移至no_case[]规则,因为它是一个函数调用。
    • 我将函数名称调整为sqrt(9)不区分大小写,因为您的示例表明Sqrt应该正常工作
    • 注意 | (L"Sqrt" > '(' > double_ > ')') // Wait, whaaat? 现在需要任何表达,而旧情况

      sqrt(GetSubstring(....))

      是的,这绝不会解析你的第二个例子,真的:|

    现在真正的操作来了。为了让function_call_解析,我们必须让term_成为expr_的可能值。一旦发生这种情况,我们就不再需要expr_中的任何内容,因为factor_可能包含一个term_function_call_代表expr_ = ( *( (function_call_ ^ arithmeticexpression_)| string_ )); 已经expr_ = arithmeticexpression_; ,所以

    string_

    蒸发成

    term_

    那里ConvertStringToDouble发生了什么事?好吧,它仍然在NumValue,它在那里,但int main() { static const MapFunctionParser<std::wstring::const_iterator> p; std::wstring input; while (std::getline(std::wcin, input)) { std::wstring::const_iterator f(begin(input)), l(end(input)); GenericValue value; assert(qi::phrase_parse(f, l, p, qi::space, value)); if (f!=l) std::wcout << L"remaining unparsed: '" << std::wstring(f,l) << L"'\n"; std::wcout << input << " --> " << value << std::endl; } } 已被移除。字符串很乐意成为字符串,除非在需要GetSubString('100str1', 0, 2+1) + sqrt(9) --> 103 2 + 3 * sqrt(GetSubString('100str1', 0, 2+1)) --> 32 算术运算的上下文中需要它们。当他们强制成为一个数字,而不是更早的时候(如上所示)。

     boost::variant<
          fusion::vector2<optional<RetValue>, optional<double>>,
          std::wstring>
    

    当我从你的问题中提供这两个小测试程序时,它尽职尽责地制作了以下文字:

     expr_    = string_ | function_call_ | arithmeticexpression_;
    

    您可以看到完整代码 on Coliru (遗憾的是,编译需要很长时间)。


    最初这个答案从以下开始:

      

    问。我尝试使用置换运算符组合下面的2个语法。但它没有编译

    您期望排列运算符做什么? The documentation states

      

    置换运算符a ^ b按任意顺序匹配一个或多个操作数(a,b,...等)...

         

    enter image description here

    如您所见,它会产生一个属性

    boost::variant<RetValue, double, std::wstring>

    显然不兼容。现在,我假设你只想要或/或语义,所以

    RetValue

    应该做得很好,导致{{1}} 可分配给{{1}}。

    现在,经过十几次箍以使您的示例代码编译(为什么......),这里有一个修复: