bracket_random/
parsing.rs

1use regex::Regex;
2use std::error;
3use std::fmt;
4
5#[cfg(feature = "serde")]
6use serde_crate::{Deserialize, Serialize};
7
8// Describes a dice roll type
9#[cfg_attr(
10    feature = "serde",
11    derive(Serialize, Deserialize),
12    serde(crate = "serde_crate")
13)]
14#[derive(Copy, Clone, PartialEq, Eq, Debug)]
15pub struct DiceType {
16    pub n_dice: i32,
17    pub die_type: i32,
18    pub bonus: i32,
19}
20
21impl DiceType {
22    pub fn new(n_dice: i32, die_type: i32, bonus: i32) -> Self {
23        DiceType {
24            n_dice,
25            die_type,
26            bonus,
27        }
28    }
29}
30
31impl Default for DiceType {
32    fn default() -> DiceType {
33        DiceType {
34            n_dice: 1,
35            die_type: 4,
36            bonus: 0,
37        }
38    }
39}
40
41#[derive(Debug, Clone)]
42pub struct DiceParseError;
43
44impl std::fmt::Display for DiceParseError {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        write!(f, "Invalid dice string")
47    }
48}
49
50impl error::Error for DiceParseError {
51    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
52        // Generic error, underlying cause isn't tracked.
53        None
54    }
55}
56
57#[allow(dead_code)]
58// Parses a dice string, of the type "1d6+3", "3d8-4" or "1d20".
59#[cfg(feature = "parsing")]
60pub fn parse_dice_string(dice: &str) -> Result<DiceType, DiceParseError> {
61    let dice = &dice.split_whitespace().collect::<Vec<_>>().join("");
62    lazy_static! {
63        static ref DICE_RE: Regex = Regex::new(r"(\d+)d(\d+)([\+\-]\d+)?").unwrap();
64    }
65    let mut result: DiceType = DiceType::default();
66    let mut did_something = false;
67    for cap in DICE_RE.captures_iter(dice) {
68        did_something = true;
69        if let Some(group) = cap.get(1) {
70            match group.as_str().parse::<i32>() {
71                Ok(number) => result.n_dice = number,
72                Err(_) => return Err(DiceParseError {}),
73            }
74        } else {
75            return Err(DiceParseError {});
76        }
77        if let Some(group) = cap.get(2) {
78            match group.as_str().parse::<i32>() {
79                Ok(number) => result.die_type = number,
80                Err(_) => return Err(DiceParseError {}),
81            }
82        } else {
83            return Err(DiceParseError {});
84        }
85        if let Some(group) = cap.get(3) {
86            match group.as_str().parse::<i32>() {
87                Ok(number) => result.bonus = number,
88                Err(_) => return Err(DiceParseError {}),
89            }
90        }
91    }
92    if !did_something {
93        return Err(DiceParseError {});
94    }
95    Ok(result)
96}
97
98#[cfg(test)]
99mod tests {
100    use super::{parse_dice_string, DiceType};
101
102    #[test]
103    fn parse_1d6() {
104        assert_eq!(parse_dice_string("1d6").unwrap(), DiceType::new(1, 6, 0));
105    }
106
107    #[test]
108    fn parse_1d20plus4() {
109        assert_eq!(
110            parse_dice_string("1d20+4").unwrap(),
111            DiceType::new(1, 20, 4)
112        );
113    }
114
115    #[test]
116    fn parse_3d6minus2() {
117        assert_eq!(parse_dice_string("3d6-2").unwrap(), DiceType::new(3, 6, -2));
118    }
119
120    #[test]
121    fn parse_whitespace_test() {
122        assert_eq!(
123            parse_dice_string("3d6 - 2").unwrap(),
124            DiceType::new(3, 6, -2)
125        );
126        assert_eq!(
127            parse_dice_string(" 3d6- 2").unwrap(),
128            DiceType::new(3, 6, -2)
129        );
130        assert_eq!(
131            parse_dice_string("3 d 6- 2").unwrap(),
132            DiceType::new(3, 6, -2)
133        );
134    }
135
136    #[test]
137    fn fail_parsing() {
138        assert!(parse_dice_string("blah").is_err());
139    }
140
141    #[cfg(feature = "serde")]
142    #[test]
143    fn serialize_parsing() {
144        use serde_crate::{Deserialize, Serialize};
145        let d = parse_dice_string("3d6 - 2").unwrap();
146        let serialized = serde_json::to_string(&d).unwrap();
147        let deserialized: DiceType = serde_json::from_str(&serialized).unwrap();
148        assert!(d == deserialized);
149    }
150}