Semanti c Действия - «отложенные актеры». Значение: это объекты функций, которые описывают вызов функции, они не вызываются во время определения правила.
Таким образом, вы можете использовать
Соблюдайте привязку феникса, так как она ближе всего к вашему code:
roll = (qi::int_ >> 'd' >> qi::int_)
[ _val = px::bind(::roll, _1, _2) ] ;
- Обратите внимание, как я убрал использование локальных переменных. Это были бы UB , потому что они не существуют после завершения конструктора!
- Примечание также , которое мне нужно для устранения неоднозначности
::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 ++?
¹ разве ты не любишь c ++?