如何获得Boost.Spirit语义动作的函数结果

问题描述 投票:1回答:1

[我正在尝试编写一个有可能像DnD,Munchkin等一样掷骰子的计算器。因此,我需要计算类似2*(2d5+3d7)的表达式,在这里我应该2d5代表掷5个面掷2个骰子的结果。我以原始计算器为基础,并且可以正常工作。现在,我正在尝试使用语义操作为滚动添加规则。我想每次出现表达式XdY时都调用roll函数,并将其结果添加到当前值。但是似乎我不能仅在语义动作上执行_val+=roll(dice_number, dice_value)。那么,该怎么做呢?完整的代码在这里:

#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/bind.hpp>
#include <boost/phoenix/bind/bind_function.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <ctime>
#include <boost/random.hpp>


std::time_t now = std::time(0);
boost::random::mt19937 gen{static_cast<std::uint32_t>(now)};

int roll(int dice_number, int dice_value, int use_crits=false)
{
    int res=0;
    boost::random::uniform_int_distribution<> dist{1, value};
    for(int i=0; i<dice_number; i++)
    {
        res+=dist(gen);
    }
    return res;
}

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    using boost::phoenix::push_back;
    using boost::phoenix::ref;
    //calculator grammar
    template <typename Iterator>
    struct calculator : qi::grammar<Iterator, int(), ascii::space_type>
    {
        calculator() : calculator::base_type(expression)
        {
            qi::_val_type _val;
            qi::_1_type _1, _2;
            qi::uint_type uint_;
            qi::int_type int_;
            int dice_num, dice_value;

            roll = 
                (int_ [ref(dice_num)=_1]>> 'd' >> int_ [ref(dice_value)=_1]) [_val+=roll(dice_num, dice_value)] ;//The problem is here

            expression =
                term                            [_val = _1]
                >> *(   ('+' >> term            [_val += _1])
                    |   ('-' >> term            [_val -= _1])
                    )
                ;

            term =
                factor                          [_val = _1]
                >> *(   ('*' >> factor          [_val *= _1])
                    |   ('/' >> factor          [_val /= _1])
                    )
                ;

            factor = 
                roll [_val=_1]
                | uint_                           [_val = _1]
                |   '(' >> expression           [_val = _1] >> ')'
                |   ('-' >> factor              [_val = -_1])
                |   ('+' >> factor              [_val = _1])
                ;
        }

        qi::rule<Iterator, int(), ascii::space_type> roll, expression, term, factor;
    };
}

int
main()
{
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Expression parser...\n\n";
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Type an expression...or [q or Q] to quit\n\n";

    typedef std::string::const_iterator iterator_type;
    typedef client::calculator<iterator_type> calculator;

    boost::spirit::ascii::space_type space; // skipper
    calculator calc; // grammar

    std::string str;
    int result;
    while (std::getline(std::cin, str))
    {
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
            break;

        std::string::const_iterator iter = str.begin();
        std::string::const_iterator end = str.end();
        bool r = phrase_parse(iter, end, calc, space, result);

        if (r && iter == end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            std::cout << "result = " << result << std::endl;
            std::cout << "-------------------------\n";
        }
        else
        {
            std::string rest(iter, end);
            std::cout << "-------------------------\n";
            std::cout << "Parsing failed\n";
            std::cout << "stopped at: \" " << rest << "\"\n";
            std::cout << "-------------------------\n";
        }
    }

    std::cout << "Bye... :-) \n\n";
    return 0;
}
c++ boost boost-spirit boost-spirit-qi
1个回答
0
投票

[语义动作是“延迟的参与者”。含义:它们是describe函数调用的函数对象,在规则定义期间不会调用它们。

因此您可以使用

使用凤凰绑定,因为它与您的代码最接近:

roll = (qi::int_ >> 'd' >> qi::int_)
    [ _val = px::bind(::roll, _1, _2) ] ;
  1. 注意我如何删除局部变量。它们本来是UB,因为在构造函数完成后它们不存在!
  2. 请注意[[也我需要用全局名称空间限定词来消除::roll的歧义,因为roll规则成员将其遮蔽了。

Live Demo

//#define BOOST_SPIRIT_DEBUG #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> static int roll_dice(int num, int faces); namespace Parser { namespace qi = boost::spirit::qi; namespace px = boost::phoenix; //calculator grammar template <typename Iterator> struct calculator : qi::grammar<Iterator, int()> { calculator() : calculator::base_type(start) { using namespace qi::labels; start = qi::skip(qi::space) [ expression ]; roll = (qi::int_ >> 'd' >> qi::int_) [ _val = px::bind(::roll_dice, _1, _2) ] ; expression = term [_val = _1] >> *( ('+' >> term [_val += _1]) | ('-' >> term [_val -= _1]) ) ; term = factor [_val = _1] >> *( ('*' >> factor [_val *= _1]) | ('/' >> factor [_val /= _1]) ) ; factor = roll [_val = _1] | qi::uint_ [_val = _1] | '(' >> expression [_val = _1] >> ')' | ('-' >> factor [_val = -_1]) | ('+' >> factor [_val = _1]) ; BOOST_SPIRIT_DEBUG_NODES((start)(roll)(expression)(term)(factor)) } private: qi::rule<Iterator, int()> start; qi::rule<Iterator, int(), qi::space_type> roll, expression, term, factor; }; } #include <random> #include <iomanip> static int roll_dice(int num, int faces) { static std::mt19937 gen{std::random_device{}()}; int res=0; std::uniform_int_distribution<> dist{1, faces}; for(int i=0; i<num; i++) { res+=dist(gen); } return res; } int main() { using It = std::string::const_iterator; Parser::calculator<It> const calc; for (std::string const& str : { "42", "2*(2d5+3d7)", }) { auto f = str.begin(), l = str.end(); int result; if (parse(f, l, calc, result)) { std::cout << "result = " << result << std::endl; } else { std::cout << "Parsing failed\n"; } if (f != l) { std::cout << "Remaining input: " << std::quoted(std::string(f, l)) << "\n"; } } }
例如,打印

result = 42 result = 38

错误!

首先正确。您可能没有意识到,但是如果uniform_int_distribution<>(a,b),则uniform_int_distribution<>(a,b)会导致UB¹

类似于有人键入b<a的时间。

您需要添加支票:

-7d5

  在任何域/语言中,

防御性编程都是必须的。在C ++中,它可以防止static int roll_dice(int num, int faces) { if (num < 0) throw std::range_error("num"); if (faces < 1) throw std::range_error("faces"); int res = 0; static std::mt19937 gen{ std::random_device{}() }; std::uniform_int_distribution<> dist{ 1, faces }; for (int i = 0; i < num; i++) { res += dist(gen); } std::cerr << "roll_dice(" << num << ", " << faces << ") -> " << res << "\n"; return res; }

一般化!

该代码已大大简化,我添加了必要的管道以获取调试输出:

Nasal Demons

现在,我们从概念上看一下语法。实际上,<start>
  <try>2*(2d5+3d7)</try>
  <expression>
    <try>2*(2d5+3d7)</try>
    <term>
      <try>2*(2d5+3d7)</try>
      <factor>
        <try>2*(2d5+3d7)</try>
        <roll>
          <try>2*(2d5+3d7)</try>
          <fail/>
        </roll>
        <success>*(2d5+3d7)</success>
        <attributes>[2]</attributes>
      </factor>
      <factor>
        <try>(2d5+3d7)</try>
        <roll>
          <try>(2d5+3d7)</try>
          <fail/>
        </roll>
        <expression>
          <try>2d5+3d7)</try>
          <term>
            <try>2d5+3d7)</try>
            <factor>
              <try>2d5+3d7)</try>
              <roll>
                <try>2d5+3d7)</try>
                <success>+3d7)</success>
                <attributes>[9]</attributes>
              </roll>
              <success>+3d7)</success>
              <attributes>[9]</attributes>
            </factor>
            <success>+3d7)</success>
            <attributes>[9]</attributes>
          </term>
          <term>
            <try>3d7)</try>
            <factor>
              <try>3d7)</try>
              <roll>
                <try>3d7)</try>
                <success>)</success>
                <attributes>[10]</attributes>
              </roll>
              <success>)</success>
              <attributes>[10]</attributes>
            </factor>
            <success>)</success>
            <attributes>[10]</attributes>
          </term>
          <success>)</success>
          <attributes>[19]</attributes>
        </expression>
        <success></success>
        <attributes>[19]</attributes>
      </factor>
      <success></success>
      <attributes>[38]</attributes>
    </term>
    <success></success>
    <attributes>[38]</attributes>
  </expression>
  <success></success>
  <attributes>[38]</attributes>
</start>
result = 38
只是一个二进制中缀运算符,例如d3+7。因此,如果我们假设它的优先级与

unary的正负号相同,则可以简化规则,同时使语法更加通用:

3d7
哇!不再有expression =
    term                   [_val = _1]
    >> *(   ('+' >> term   [_val += _1])
        |   ('-' >> term   [_val -= _1])
        |   ('d' >> term   [_val = px::bind(::roll_dice, _val, _1)])
        )
    ;
规则。同样,以下内容突然变成有效输入:

roll

完整演示

1*3d(5+2) (3+9*3)d8 0*0d5 (3d5)d15 1d(15d3) (1d1d1d1) * 42

Live On Coliru
打印


¹你不爱c ++吗?

//#define BOOST_SPIRIT_DEBUG #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> static int roll_dice(int num, int faces); namespace Parser { namespace qi = boost::spirit::qi; namespace px = boost::phoenix; //calculator grammar template <typename Iterator> struct calculator : qi::grammar<Iterator, int()> { calculator() : calculator::base_type(start) { using namespace qi::labels; start = qi::skip(qi::space) [ expression ]; expression = term [_val = _1] >> *( ('+' >> term [_val += _1]) | ('-' >> term [_val -= _1]) ) ; term = factor [_val = _1] >> *( ('*' >> factor [_val *= _1]) | ('/' >> factor [_val /= _1]) ) ; factor = (qi::uint_ [_val = _1] | '(' >> expression [_val = _1] >> ')' | ('-' >> factor [_val = -_1]) | ('+' >> factor [_val = _1]) ) >> *( 'd' >> factor [_val = px::bind(::roll_dice, _val, _1)] ) ; BOOST_SPIRIT_DEBUG_NODES((start)(expression)(term)(factor)) } private: qi::rule<Iterator, int()> start; qi::rule<Iterator, int(), qi::space_type> expression, term, factor; }; } #include <random> #include <iomanip> static int roll_dice(int num, int faces) { if (num < 0) throw std::range_error("faces"); if (faces < 1) throw std::range_error("faces"); int res = 0; static std::mt19937 gen{ std::random_device{}() }; std::uniform_int_distribution<> dist{ 1, faces }; for (int i = 0; i < num; i++) { res += dist(gen); } std::cerr << "roll_dice(" << num << ", " << faces << ") -> " << res << "\n"; return res; } int main() { using It = std::string::const_iterator; Parser::calculator<It> const calc; for (std::string const& input : { "42", "2*(2d5+3d7)", // generalized "1*3d(5+2)", "(3+9*3)d8", "0*0d5", "(3d5)d15", "1d(15d3)", "(1d1d1d1) * 42", }) { std::cout << "\n==== Parsing " << std::quoted(input) << "\n"; auto f = input.begin(), l = input.end(); int result; if (parse(f, l, calc, result)) { std::cout << "Parse result = " << result << std::endl; } else { std::cout << "Parsing failed\n"; } if (f != l) { std::cout << "Remaining input: " << std::quoted(std::string(f, l)) << "\n"; } } }

© www.soinside.com 2019 - 2024. All rights reserved.