| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- //
- // die.cxx
- // dice-roll
- //
- // Created by Sam Jaffe on 12/1/18.
- // Copyright © 2018 Sam Jaffe. All rights reserved.
- //
- #include "dice-roll/die.h"
- #include <iostream>
- #include <sstream>
- #include "dice-roll/exception.h"
- static void advance_over_whitespace(std::istream & in, char const * also = "") {
- if (strchr(also, in.peek())) { in.get(); }
- while (isspace(in.peek())) {
- in.get();
- }
- }
- namespace dice {
- int sgn(sign s) { return s == MINUS ? -1 : 1; }
- std::string str(sign s) {
- switch (s) {
- case PLUS:
- return "+";
- case MINUS:
- return "-";
- default:
- return "";
- }
- }
- 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;
- }
- }
|