c++ - 如何在 Boost.Spirit 语义动作中获得函数结果
问题描述
我正在尝试编写一个计算器,可以像在 DnD、Munchkin 等中一样掷骰子。所以我需要计算像这样的表达式2*(2d5+3d7)
,我应该 2d5 代表用 5 个面掷出 2 个骰子的结果。我以一个原始计算器为基础,它可以工作。现在我正在尝试使用语义操作添加滚动规则。我想roll
每次出现表达式 XdY 时调用函数并将其结果添加到当前值。但似乎我不能只_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;
}
解决方案
语义动作是“延迟演员”。含义:它们是描述函数调用的函数对象,在规则定义期间它们不会被调用。
所以你可以使用
- 凤凰::绑定
- 凤凰::函数
- 写一个语义动作函数
使用凤凰绑定,因为它最接近您的代码:
roll = (qi::int_ >> 'd' >> qi::int_)
[ _val = px::bind(::roll, _1, _2) ] ;
- 请注意我如何删除局部变量的使用。它们本来是UB,因为它们在构造函数完成后就不存在了!
- 另请注意,我需要消除
::roll
全局命名空间限定的歧义,因为roll
规则成员会隐藏它。
//#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)
意识到,但如果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++ 中,它可以防止鼻恶魔
概括!
该代码已大大简化,我添加了必要的管道以获得调试输出:
<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
or 3d7
。因此,如果我们假设它与一元加/减具有相同的优先级,我们可以简化规则,同时使语法更加通用:
factor = (qi::uint_ [_val = _1]
| '(' >> expression [_val = _1] >> ')'
| ('-' >> factor [_val = -_1])
| ('+' >> factor [_val = _1])
) >> *(
'd' >> factor [_val = px::bind(::roll_dice, _val, _1)]
)
;
哎呀!没有更多的roll
规则。此外,突然以下成为有效输入:
1*3d(5+2)
(3+9*3)d8
0*0d5
(3d5)d15
1d(15d3)
(1d1d1d1) * 42
完整演示
//#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++ 吗?
推荐阅读
- java - Telegram bot 无法删除 48 小时前发送的消息,尽管它具有权限
- java - 如何在列表中设置子项?
- python - 如何在 django 的视图中使用 python 的语法检查
- python - 我怎样才能得到 pexpect 的 sendline('ls -l') 打印?
- node.js - Mongoose 阻止下一个 then() 被执行
- php - 为什么数据库不反映 php INSERT 所做的更改?
- qt - 如何获取存储指向外部模型的指针的模型的 QModelIndex
- google-sheets - 如何在 Google 电子表格中获取历史 VIX 数据
- qt - 如何正确对齐我的 qml 组件?
- typescript - 在 TypeScript 中动态添加函数声明