|
|
@@ -34,143 +34,4 @@ namespace dice {
|
|
|
}
|
|
|
|
|
|
mod::operator int() const { return sgn(sign) * value; }
|
|
|
-
|
|
|
- std::ostream & operator<<(std::ostream & out, dice const & d) {
|
|
|
- if (d.num != 1) out << d.num << '{';
|
|
|
- for (die const & di : d.of) {
|
|
|
- out << str(di.sgn) << di.num << 'd' << di.sides;
|
|
|
- }
|
|
|
- for (mod m : d.modifier) {
|
|
|
- out << str(m.sign) << m.value;
|
|
|
- }
|
|
|
- if (d.num != 1) out << '}';
|
|
|
- return out;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-namespace dice { namespace {
|
|
|
- struct parser {
|
|
|
- void parse(sign s);
|
|
|
- void parse_dN(sign s, int value);
|
|
|
- void parse_const(sign s, int value);
|
|
|
-
|
|
|
- std::istream & in;
|
|
|
- dice & d;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * @sideeffect This function advances the input stream over a single numeric
|
|
|
- * token. This token represents the number of sides in the die roll.
|
|
|
- * This function appends a new die into {@see d.of}, representing (+/-)NdM,
|
|
|
- * where N is the input parameter 'value', and M is retrieved internally.
|
|
|
- * @param s The arithmatic sign attached to this roll, denoting whether this
|
|
|
- * die increases or decreases the total result of the roll. For example, the
|
|
|
- * 5E spell Bane imposes a -1d4 modifier on attack rolls, an attack roll might
|
|
|
- * go from '1d20+2+3' (1d20 + Proficiency + Ability) to '1d20+2+3-1d4'.
|
|
|
- * @param value The number of dice to be rolled.
|
|
|
- * Domain: value >= 0
|
|
|
- * @throw dice::unexpected_token if we somehow call parse_dN while the first
|
|
|
- * non-whitespace token after the 'd' char is not a number.
|
|
|
- */
|
|
|
- void parser::parse_dN(sign s, int value) {
|
|
|
- advance_over_whitespace(in, "dD");
|
|
|
- // Disallow 0dM, as that is not a real thing...
|
|
|
- d.of.push_back({s, std::max(value, 1), 0});
|
|
|
- if (!isnumber(in.peek())) {
|
|
|
- throw unexpected_token("Expected a number of sides", in.tellg());
|
|
|
- }
|
|
|
- in >> d.of.back().sides;
|
|
|
- parse(ZERO);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * @param s The arithmatic sign attached to this numeric constant. Because
|
|
|
- * value is non-negative, this token contains the +/- effect.
|
|
|
- * @param value The value associated with this modifier term.
|
|
|
- * Domain: value >= 0
|
|
|
- */
|
|
|
- void parser::parse_const(sign s, int value) {
|
|
|
- if (value) { // Zero is not a modifier we care about
|
|
|
- d.modifier.push_back({s, std::abs(value)});
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Main dispatch function for parsing a dice roll.
|
|
|
- * @param s The current +/- sign attached to the parse sequence. s is ZERO
|
|
|
- * when parsing the first token, or after parsing a die. This means an
|
|
|
- * expression like '1d4+5+1d6+2d8' is evaluated as a sequence like so:
|
|
|
- * [1d4][+][5+][1d6][+][2d8]. This produces the following states of (SIGN,
|
|
|
- * input stream):
|
|
|
- * 1) ZERO, 1d4+5+1d6+2d8
|
|
|
- * 2) ZERO, +5+1d6+2d8
|
|
|
- * 3) PLUS, 5+1d6+2d8
|
|
|
- * 4) PLUS, 1d6+2d8
|
|
|
- * 5) ZERO, +2d8
|
|
|
- * 6) PLUS, 2d8
|
|
|
- */
|
|
|
- void parser::parse(sign s) {
|
|
|
- advance_over_whitespace(in);
|
|
|
- // By defaulting this to zero, we can write a more elegant handling of
|
|
|
- // expressions like 1d4+1d6+5+1
|
|
|
- int value = 0;
|
|
|
- if (isnumber(in.peek())) {
|
|
|
- in >> value;
|
|
|
- } else if (in.peek() == EOF && s != ZERO) {
|
|
|
- throw unexpected_token("Unexpected EOF while parsing", -1);
|
|
|
- }
|
|
|
- advance_over_whitespace(in);
|
|
|
- switch (in.peek()) {
|
|
|
- case 'd':
|
|
|
- case 'D':
|
|
|
- return parse_dN(s, value);
|
|
|
- case '+':
|
|
|
- case '-':
|
|
|
- // Handle 5+... cases
|
|
|
- parse_const(s, value);
|
|
|
- // Add another token
|
|
|
- parse((in.get() == '+') ? PLUS : MINUS);
|
|
|
- break;
|
|
|
- default:
|
|
|
- parse_const(s, value);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-}}
|
|
|
-
|
|
|
-namespace dice {
|
|
|
- std::istream & operator>>(std::istream & in, dice & d) {
|
|
|
- int value{1};
|
|
|
- advance_over_whitespace(in);
|
|
|
- if (isnumber(in.peek())) { in >> value; }
|
|
|
- advance_over_whitespace(in);
|
|
|
- switch (in.peek()) {
|
|
|
- case 'd':
|
|
|
- case 'D':
|
|
|
- parser{in, d}.parse(ZERO);
|
|
|
- d.of.front().num = value;
|
|
|
- break;
|
|
|
- case '{':
|
|
|
- in.get();
|
|
|
- d.num = value;
|
|
|
- parser{in, d}.parse(ZERO);
|
|
|
- if (in.get() != '}') {
|
|
|
- throw unexpected_token("Expected closing '}' in repeated roll",
|
|
|
- in.tellg());
|
|
|
- }
|
|
|
- break;
|
|
|
- case EOF:
|
|
|
- throw unexpected_token("No dice in expression", in.tellg());
|
|
|
- default:
|
|
|
- throw unexpected_token("Unexpected token", in.tellg());
|
|
|
- }
|
|
|
- return in;
|
|
|
- }
|
|
|
-
|
|
|
- dice from_string(std::string const & str) {
|
|
|
- std::stringstream ss(str);
|
|
|
- dice d;
|
|
|
- ss >> d;
|
|
|
- return d;
|
|
|
- }
|
|
|
}
|