[我正在尝试编写一个有可能像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;
}
[语义动作是“延迟的参与者”。含义:它们是describe函数调用的函数对象,在规则定义期间不会调用它们。
因此您可以使用
使用凤凰绑定,因为它与您的代码最接近:
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)
,则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; }
该代码已大大简化,我添加了必要的管道以获取调试输出:
现在,我们从概念上看一下语法。实际上,<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
。因此,如果我们假设它的优先级与unary的正负号相同,则可以简化规则,同时使语法更加通用:
3d7
哇!不再有expression = term [_val = _1] >> *( ('+' >> term [_val += _1]) | ('-' >> term [_val -= _1]) | ('d' >> term [_val = px::bind(::roll_dice, _val, _1)]) ) ;
规则。同样,以下内容突然变成有效输入:
roll
完整演示Live On Coliru
1*3d(5+2) (3+9*3)d8 0*0d5 (3d5)d15 1d(15d3) (1d1d1d1) * 42
打印
¹你不爱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"; } } }