Skip to main content

axfive_matrix_dicebot/dice/
parser.rs

1use nom::{
2    alt, bytes::complete::tag, character::complete::digit1, complete, many0, named,
3    sequence::tuple, tag, IResult,
4};
5
6use crate::dice::{Dice, Element, ElementExpression, SignedElement};
7use crate::parser::eat_whitespace;
8
9#[derive(Debug, PartialEq, Eq, Clone, Copy)]
10enum Sign {
11    Plus,
12    Minus,
13}
14
15// Parse a dice expression.  Does not eat whitespace
16fn parse_dice(input: &str) -> IResult<&str, Dice> {
17    let (input, (count, _, sides)) = tuple((digit1, tag("d"), digit1))(input)?;
18    Ok((
19        input,
20        Dice::new(count.parse().unwrap(), sides.parse().unwrap()),
21    ))
22}
23
24// Parse a single digit expression.  Does not eat whitespace
25fn parse_bonus(input: &str) -> IResult<&str, u32> {
26    let (input, bonus) = digit1(input)?;
27    Ok((input, bonus.parse().unwrap()))
28}
29
30// Parse a sign expression.  Eats whitespace.
31fn parse_sign(input: &str) -> IResult<&str, Sign> {
32    let (input, _) = eat_whitespace(input)?;
33    named!(sign(&str) -> Sign, alt!(
34            complete!(tag!("+")) => { |_| Sign::Plus } |
35            complete!(tag!("-")) => { |_| Sign::Minus }
36    ));
37
38    let (input, sign) = sign(input)?;
39    Ok((input, sign))
40}
41
42// Parse an element expression.  Eats whitespace.
43fn parse_element(input: &str) -> IResult<&str, Element> {
44    let (input, _) = eat_whitespace(input)?;
45    named!(element(&str) -> Element, alt!(
46            parse_dice => { |d| Element::Dice(d) } |
47            parse_bonus => { |b| Element::Bonus(b) }
48    ));
49
50    let (input, element) = element(input)?;
51    Ok((input, element))
52}
53
54// Parse a signed element expression.  Eats whitespace.
55fn parse_signed_element(input: &str) -> IResult<&str, SignedElement> {
56    let (input, _) = eat_whitespace(input)?;
57    let (input, sign) = parse_sign(input)?;
58    let (input, _) = eat_whitespace(input)?;
59
60    let (input, element) = parse_element(input)?;
61    let element = match sign {
62        Sign::Plus => SignedElement::Positive(element),
63        Sign::Minus => SignedElement::Negative(element),
64    };
65    Ok((input, element))
66}
67
68// Parse a full element expression.  Eats whitespace.
69pub fn parse_element_expression(input: &str) -> IResult<&str, ElementExpression> {
70    named!(first_element(&str) -> SignedElement, alt!(
71            parse_signed_element => { |e| e } |
72            parse_element => { |e| SignedElement::Positive(e) }
73    ));
74    let (input, first) = first_element(input)?;
75    let (input, rest) = if input.trim().is_empty() {
76        (input, vec![first])
77    } else {
78        named!(rest_elements(&str) -> Vec<SignedElement>, many0!(parse_signed_element));
79        let (input, mut rest) = rest_elements(input)?;
80        rest.insert(0, first);
81        (input, rest)
82    };
83
84    Ok((input, ElementExpression(rest)))
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    #[test]
91    fn dice_test() {
92        assert_eq!(parse_dice("2d4"), Ok(("", Dice::new(2, 4))));
93        assert_eq!(parse_dice("20d40"), Ok(("", Dice::new(20, 40))));
94        assert_eq!(parse_dice("8d7"), Ok(("", Dice::new(8, 7))));
95    }
96
97    #[test]
98    fn element_test() {
99        assert_eq!(
100            parse_element("  \t\n\r\n 8d7 \n"),
101            Ok((" \n", Element::Dice(Dice::new(8, 7))))
102        );
103        assert_eq!(
104            parse_element("  \t\n\r\n 8 \n"),
105            Ok((" \n", Element::Bonus(8)))
106        );
107    }
108
109    #[test]
110    fn signed_element_test() {
111        assert_eq!(
112            parse_signed_element("+ 7"),
113            Ok(("", SignedElement::Positive(Element::Bonus(7))))
114        );
115        assert_eq!(
116            parse_signed_element("  \t\n\r\n- 8 \n"),
117            Ok((" \n", SignedElement::Negative(Element::Bonus(8))))
118        );
119        assert_eq!(
120            parse_signed_element("  \t\n\r\n- 8d4 \n"),
121            Ok((
122                " \n",
123                SignedElement::Negative(Element::Dice(Dice::new(8, 4)))
124            ))
125        );
126        assert_eq!(
127            parse_signed_element("  \t\n\r\n+ 8d4 \n"),
128            Ok((
129                " \n",
130                SignedElement::Positive(Element::Dice(Dice::new(8, 4)))
131            ))
132        );
133    }
134
135    #[test]
136    fn element_expression_test() {
137        assert_eq!(
138            parse_element_expression("8d4"),
139            Ok((
140                "",
141                ElementExpression(vec![SignedElement::Positive(Element::Dice(Dice::new(
142                    8, 4
143                )))])
144            ))
145        );
146        assert_eq!(
147            parse_element_expression(" -  8d4 \n "),
148            Ok((
149                " \n ",
150                ElementExpression(vec![SignedElement::Negative(Element::Dice(Dice::new(
151                    8, 4
152                )))])
153            ))
154        );
155        assert_eq!(
156            parse_element_expression("\t3d4 + 7 - 5 - 6d12 + 1d1 + 53 1d5 "),
157            Ok((
158                " 1d5 ",
159                ElementExpression(vec![
160                    SignedElement::Positive(Element::Dice(Dice::new(3, 4))),
161                    SignedElement::Positive(Element::Bonus(7)),
162                    SignedElement::Negative(Element::Bonus(5)),
163                    SignedElement::Negative(Element::Dice(Dice::new(6, 12))),
164                    SignedElement::Positive(Element::Dice(Dice::new(1, 1))),
165                    SignedElement::Positive(Element::Bonus(53)),
166                ])
167            ))
168        );
169    }
170}