Как получить результат функции в действии Boost.Spirit semanti c - PullRequest
1 голос
/ 16 июня 2020

Пытаюсь написать калькулятор с возможностью бросать кости, как в DnD, Munchkin и др. c. Поэтому мне нужно вычислить выражения вроде 2*(2d5+3d7), где 2d5 означает результат броска 2 кубиков с 5 гранями. Я взял за основу сырой калькулятор и он работает. Теперь я пытаюсь добавить правило для броска с помощью действий semanti c. Я хочу вызывать функцию roll каждый раз, когда появляется выражение XdY, и добавлять его результат к текущему значению. Но, похоже, я не могу просто сделать _val+=roll(dice_number, dice_value) в действии semanti c. Итак, как это сделать? Полный код здесь:

#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;
}

1 Ответ

1 голос
/ 16 июня 2020

Semanti c Действия - «отложенные актеры». Значение: это объекты функций, которые описывают вызов функции, они не вызываются во время определения правила.

Таким образом, вы можете использовать

Соблюдайте привязку феникса, так как она ближе всего к вашему code:

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) приводит к UB ¹ , если b<a.

Аналогично, когда кто-то набирает -7d5.

Вам нужно добавить проверки:

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;
}

Защитное программирование обязательно в любом домене / языке. В C ++ он защищает от назальных демонов

Generalize!

Этот код был значительно упрощен, и я добавил необходимую сантехнику для получения отладочных данных:

<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

Теперь давайте рассмотрим грамматику концептуально. На самом деле d - это просто бинарный инфиксный оператор, например 3+7 или 3d7. Итак, если мы предположим, что он имеет тот же приоритет, что и унарный плюс / минус, мы можем упростить правила, сделав грамматику более общей:

factor = (qi::uint_         [_val = _1]
    |   '(' >> expression  [_val = _1] >> ')'
    |   ('-' >> factor     [_val = -_1])
    |   ('+' >> factor     [_val = _1])
    ) >> *(
      'd' >> factor [_val = px::bind(::roll_dice, _val, _1)]
    )
    ;

Wh oop ! Нет больше правила roll. Кроме того, неожиданно следующие данные становятся действительными:

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

Полная демонстрация

Live On Coliru

//#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("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;
}

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";
        }
    }
}

Печать


¹ разве ты не любишь c ++?

enter image description here

¹ разве ты не любишь c ++?

...