dice_roll/
parser.rs

1/*
2Copyright 2021 Robin Marchart
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17use crate::{
18    dice_types::{
19        Dice, DiceType, Expression, Filter, FilteredDice, Operation, SelectedDice, Selector, Term,
20    },
21    LabeledExpression,
22};
23
24use nom::{
25    branch::alt,
26    bytes::complete::{tag, tag_no_case},
27    character::complete::{digit1, multispace0, satisfy},
28    combinator::{map, map_res, opt, recognize, success, verify},
29    error::context,
30    multi::{many0, many1},
31    sequence::{delimited, pair, preceded, terminated, tuple},
32    IResult,
33};
34
35pub fn parse_dice_digit(input: &str) -> IResult<&str, &str> {
36    alt((tag_no_case("d"), tag_no_case("w")))(input)
37}
38
39pub fn parse_dice_type(input: &str) -> IResult<&str, DiceType> {
40    alt((
41        map(
42            terminated(parse_u32, terminated(multispace0, tag_no_case("x"))),
43            DiceType::Multiply,
44        ),
45        map(parse_u32, DiceType::Number),
46        map(tag_no_case("f"), |_| DiceType::Fudge),
47        map(tag("%"), |_| DiceType::Number(100)),
48    ))(input)
49}
50
51pub fn parse_u32(input: &str) -> IResult<&str, u32> {
52    context(
53        "Failed to parse integer between 1 and 4294967295 inclusive",
54        verify(
55            map_res(digit1, |s: &str| s.parse::<u32>()),
56            |value: &u32| value > &0,
57        ),
58    )(input)
59}
60
61pub fn parse_i64(input: &str) -> IResult<&str, i64> {
62    map_res(
63        recognize(pair(alt((tag("+"), tag("-"), success(""))), digit1)),
64        |s: &str| s.parse::<i64>(),
65    )(input)
66}
67
68pub fn parse_dice(input: &str) -> IResult<&str, Dice> {
69    map(
70        tuple((
71            terminated(alt((parse_u32, success(1))), multispace0),
72            preceded(parse_dice_digit, preceded(multispace0, parse_dice_type)),
73        )),
74        |dice_params| Dice {
75            throws: dice_params.0,
76            dice: dice_params.1,
77        },
78    )(input)
79}
80
81pub fn parse_filter(input: &str) -> IResult<&str, Filter> {
82    alt((
83        map(tag(">="), |_| Filter::BiggerEq),
84        map(tag(">"), |_| Filter::Bigger),
85        map(tag("<="), |_| Filter::SmallerEq),
86        map(tag("<"), |_| Filter::Smaller),
87        map(tag("!="), |_| Filter::NotEq),
88    ))(input)
89}
90
91pub fn parse_filtered_dice(input: &str) -> IResult<&str, FilteredDice> {
92    alt((
93        map(
94            tuple((
95                parse_dice,
96                delimited(multispace0, parse_filter, multispace0),
97                parse_u32,
98            )),
99            |res| FilteredDice::Filtered(res.0, res.1, res.2),
100        ),
101        map(parse_dice, FilteredDice::Simple),
102    ))(input)
103}
104
105pub fn parse_selector(input: &str) -> IResult<&str, Selector> {
106    alt((
107        map(alt((tag_no_case("h"), tag_no_case("k"))), |_| {
108            Selector::Higher
109        }),
110        map(tag_no_case("l"), |_| Selector::Lower),
111    ))(input)
112}
113
114pub fn parse_selected_dice(input: &str) -> IResult<&str, SelectedDice> {
115    alt((
116        map(
117            tuple((
118                parse_filtered_dice,
119                delimited(multispace0, parse_selector, multispace0),
120                parse_u32,
121            )),
122            |select| SelectedDice::Selected(select.0, select.1, select.2),
123        ),
124        map(parse_filtered_dice, SelectedDice::Unchanged),
125    ))(input)
126}
127
128pub fn parse_term(input: &str) -> IResult<&str, Term> {
129    alt((
130        parse_term_calculation,
131        parse_term_roll,
132        parse_term_constant,
133        parse_term_subterm,
134    ))(input)
135}
136
137pub fn parse_term_constant(input: &str) -> IResult<&str, Term> {
138    map(parse_i64, Term::Constant)(input)
139}
140
141pub fn parse_term_subterm(input: &str) -> IResult<&str, Term> {
142    map(
143        delimited(
144            tag("("),
145            delimited(multispace0, parse_term, multispace0),
146            tag(")"),
147        ),
148        |subterm| Term::SubTerm(Box::new(subterm)),
149    )(input)
150}
151
152pub fn parse_term_roll(input: &str) -> IResult<&str, Term> {
153    map(parse_selected_dice, Term::DiceThrow)(input)
154}
155
156pub fn parse_operator(input: &str) -> IResult<&str, Operation> {
157    alt((
158        map(tag("+"), |_| Operation::Add),
159        map(tag("-"), |_| Operation::Sub),
160        map(tag("*"), |_| Operation::Mul),
161        map(tag("/"), |_| Operation::Div),
162    ))(input)
163}
164
165pub fn parse_term_calculation(input: &str) -> IResult<&str, Term> {
166    map(
167        tuple((
168            alt((parse_term_roll, parse_term_constant, parse_term_subterm)),
169            delimited(multispace0, parse_operator, multispace0),
170            parse_term,
171        )),
172        |calc| Term::Calculation(Box::new(calc.0), calc.1, Box::new(calc.2)),
173    )(input)
174}
175
176fn rearange_term(root: Term) -> Term {
177    if let Term::Calculation(left_top, op_top, right_top) = root {
178        if op_top == Operation::Mul || op_top == Operation::Div {
179            if let Term::Calculation(left_child, op_child, right_child) = *right_top {
180                Term::Calculation(
181                    Box::new(Term::Calculation(left_top, op_top, left_child)),
182                    op_child,
183                    Box::new(rearange_term(*right_child)),
184                )
185            } else {
186                Term::Calculation(left_top, op_top, Box::new(rearange_term(*right_top)))
187            }
188        } else {
189            Term::Calculation(left_top, op_top, Box::new(rearange_term(*right_top)))
190        }
191    } else if let Term::SubTerm(term) = root {
192        Term::SubTerm(Box::new(rearange_term(*term)))
193    } else {
194        root
195    }
196}
197
198pub fn parse_rearanged_term(input: &str) -> IResult<&str, Term> {
199    map(parse_term, rearange_term)(input)
200}
201
202pub fn parse_expression(input: &str) -> IResult<&str, Expression> {
203    alt((
204        map(
205            pair(
206                parse_u32,
207                preceded(
208                    multispace0,
209                    delimited(
210                        tag("{"),
211                        delimited(multispace0, parse_rearanged_term, multispace0),
212                        tag("}"),
213                    ),
214                ),
215            ),
216            |list| Expression::List(list.0, list.1),
217        ),
218        map(parse_rearanged_term, Expression::Simple),
219    ))(input)
220}
221
222pub fn parse_labeled(input: &str) -> IResult<&str, LabeledExpression> {
223    map(
224        pair(
225            parse_expression,
226            opt(preceded(
227                pair(tag("#"), multispace0),
228                map(
229                    many0(terminated(
230                        recognize(many1(satisfy(|c| !(c.is_whitespace() || c == '\n')))),
231                        multispace0,
232                    )),
233                    |labels: Vec<&str>| {
234                        labels
235                            .iter()
236                            .map(|s| s.to_string())
237                            .reduce(|l1, l2| format!("{} {}", l1, l2))
238                            .unwrap_or_else(|| "".to_string())
239                    },
240                ),
241            )),
242        ),
243        |r| match r {
244            (e, Some(l)) => LabeledExpression::Labeled(e, l),
245            (e, None) => LabeledExpression::Unlabeled(e),
246        },
247    )(input)
248}
249
250#[cfg(test)]
251mod tests {
252
253    use super::*;
254
255    #[test]
256    fn test_parse_dice_digit() {
257        assert_eq!(parse_dice_digit("d"), Ok(("", "d")));
258        assert_eq!(parse_dice_digit("D"), Ok(("", "D")));
259        assert_eq!(parse_dice_digit("w"), Ok(("", "w")));
260        assert_eq!(parse_dice_digit("W"), Ok(("", "W")));
261        assert_eq!(parse_dice_digit("dd"), Ok(("d", "d")));
262        assert_eq!(parse_dice_digit("d%"), Ok(("%", "d")));
263        assert!(parse_dice_digit("l").is_err());
264        assert!(parse_dice_digit("%").is_err());
265        assert!(parse_dice_digit("").is_err());
266    }
267
268    #[test]
269    fn test_parse_u32() {
270        assert_eq!(parse_u32("1"), Ok(("", 1)));
271        assert_eq!(parse_u32("6969"), Ok(("", 6969)));
272        assert_eq!(parse_u32("4294967295"), Ok(("", 4294967295)));
273        assert!(parse_u32("4294967296").is_err());
274        assert!(parse_u32("-1").is_err());
275        assert!(parse_u32("").is_err());
276        assert!(parse_u32("0").is_err());
277    }
278
279    #[test]
280    fn test_parse_i64() {
281        assert_eq!(parse_i64("0"), Ok(("", 0)));
282        assert_eq!(parse_i64("1"), Ok(("", 1)));
283        assert_eq!(parse_i64("+1"), Ok(("", 1)));
284        assert_eq!(parse_i64("-1"), Ok(("", -1)));
285        assert_eq!(parse_i64("6969"), Ok(("", 6969)));
286        assert_eq!(parse_i64("+6969"), Ok(("", 6969)));
287        assert_eq!(parse_i64("-1337"), Ok(("", -1337)));
288        assert_eq!(
289            parse_i64("-9223372036854775808"),
290            Ok(("", -9223372036854775808))
291        );
292        assert_eq!(
293            parse_i64("9223372036854775807"),
294            Ok(("", 9223372036854775807))
295        );
296        assert_eq!(
297            parse_i64("+9223372036854775807"),
298            Ok(("", 9223372036854775807))
299        );
300        assert_eq!(parse_i64("0k"), Ok(("k", 0)));
301        assert!(parse_i64("k").is_err());
302        assert!(parse_i64("").is_err());
303    }
304
305    #[test]
306    fn test_parse_dice_type() {
307        assert_eq!(parse_dice_type("1"), Ok(("", DiceType::Number(1))));
308        assert_eq!(parse_dice_type("1337"), Ok(("", DiceType::Number(1337))));
309        assert_eq!(parse_dice_type("%"), Ok(("", DiceType::Number(100))));
310        assert_eq!(parse_dice_type("f"), Ok(("", DiceType::Fudge)));
311        assert_eq!(parse_dice_type("F"), Ok(("", DiceType::Fudge)));
312        assert_eq!(parse_dice_type("1x"), Ok(("", DiceType::Multiply(1))));
313        assert_eq!(parse_dice_type("6969X"), Ok(("", DiceType::Multiply(6969))));
314        assert_eq!(
315            parse_dice_type("1337 x"),
316            Ok(("", DiceType::Multiply(1337)))
317        );
318        assert!(parse_dice_type("x").is_err());
319        assert!(parse_dice_type("").is_err());
320    }
321
322    #[test]
323    fn test_parse_dice() {
324        assert_eq!(
325            parse_dice("d1"),
326            Ok((
327                "",
328                Dice {
329                    throws: 1,
330                    dice: DiceType::Number(1)
331                }
332            ))
333        );
334        assert_eq!(
335            parse_dice("1D %"),
336            Ok((
337                "",
338                Dice {
339                    throws: 1,
340                    dice: DiceType::Number(100)
341                }
342            ))
343        );
344        assert_eq!(
345            parse_dice("20w  \t3\tX"),
346            Ok((
347                "",
348                Dice {
349                    throws: 20,
350                    dice: DiceType::Multiply(3)
351                }
352            ))
353        );
354    }
355
356    #[test]
357    fn test_parse_filter() {
358        assert_eq!(parse_filter("<"), Ok(("", Filter::Smaller)));
359        assert_eq!(parse_filter("<="), Ok(("", Filter::SmallerEq)));
360        assert_eq!(parse_filter(">"), Ok(("", Filter::Bigger)));
361        assert_eq!(parse_filter(">="), Ok(("", Filter::BiggerEq)));
362        assert_eq!(parse_filter("!="), Ok(("", Filter::NotEq)));
363        assert_eq!(parse_filter("!=3"), Ok(("3", Filter::NotEq)));
364        assert!(parse_filter("==").is_err());
365        assert!(parse_filter("").is_err());
366    }
367
368    #[test]
369    fn test_parse_filtered_dice() {
370        assert_eq!(
371            parse_filtered_dice("d4"),
372            Ok((
373                "",
374                FilteredDice::Simple(Dice {
375                    throws: 1,
376                    dice: DiceType::Number(4)
377                })
378            ))
379        );
380        assert_eq!(
381            parse_filtered_dice("2d2!=2"),
382            Ok((
383                "",
384                FilteredDice::Filtered(
385                    Dice {
386                        throws: 2,
387                        dice: DiceType::Number(2)
388                    },
389                    Filter::NotEq,
390                    2
391                )
392            ))
393        );
394        assert_eq!(
395            parse_filtered_dice("10   w  10  \t x \t  < \t 75"),
396            Ok((
397                "",
398                FilteredDice::Filtered(
399                    Dice {
400                        throws: 10,
401                        dice: DiceType::Multiply(10)
402                    },
403                    Filter::Smaller,
404                    75
405                )
406            ))
407        );
408        assert_eq!(
409            parse_filtered_dice("69d69>"),
410            Ok((
411                ">",
412                FilteredDice::Simple(Dice {
413                    throws: 69,
414                    dice: DiceType::Number(69)
415                })
416            ))
417        );
418        assert!(parse_filtered_dice("").is_err());
419    }
420
421    #[test]
422    fn test_parse_selector() {
423        assert_eq!(parse_selector("h"), Ok(("", Selector::Higher)));
424        assert_eq!(parse_selector("H"), Ok(("", Selector::Higher)));
425        assert_eq!(parse_selector("k"), Ok(("", Selector::Higher)));
426        assert_eq!(parse_selector("K"), Ok(("", Selector::Higher)));
427        assert_eq!(parse_selector("l"), Ok(("", Selector::Lower)));
428        assert_eq!(parse_selector("L"), Ok(("", Selector::Lower)));
429        assert_eq!(parse_selector("hl"), Ok(("l", Selector::Higher)));
430        assert!(parse_selector("").is_err());
431    }
432
433    #[test]
434    fn test_parse_selected_dice() {
435        assert_eq!(
436            parse_selected_dice("d3"),
437            Ok((
438                "",
439                SelectedDice::Unchanged(FilteredDice::Simple(Dice {
440                    throws: 1,
441                    dice: DiceType::Number(3)
442                }))
443            ))
444        );
445        assert_eq!(
446            parse_selected_dice("4W10X>50k2"),
447            Ok((
448                "",
449                SelectedDice::Selected(
450                    FilteredDice::Filtered(
451                        Dice {
452                            throws: 4,
453                            dice: DiceType::Multiply(10)
454                        },
455                        Filter::Bigger,
456                        50
457                    ),
458                    Selector::Higher,
459                    2
460                )
461            ))
462        );
463        assert_eq!(
464            parse_selected_dice("4\t  W \t 10  \tX\t  >\t  50\t  k \t 2"),
465            Ok((
466                "",
467                SelectedDice::Selected(
468                    FilteredDice::Filtered(
469                        Dice {
470                            throws: 4,
471                            dice: DiceType::Multiply(10)
472                        },
473                        Filter::Bigger,
474                        50
475                    ),
476                    Selector::Higher,
477                    2
478                )
479            ))
480        );
481        assert!(parse_selected_dice("").is_err());
482    }
483
484    #[test]
485    fn test_parse_term() {
486        assert!(parse_term("d 3 + d f + d % + 1337 d 69 x * 4 d 100 / ( 3 w 10 - 2 )").is_ok());
487        assert_eq!(
488            parse_term("d 3 + 66DF * 4d3x - 1"),
489            Ok((
490                "",
491                Term::Calculation(
492                    Box::new(Term::DiceThrow(SelectedDice::Unchanged(
493                        FilteredDice::Simple(Dice {
494                            throws: 1,
495                            dice: DiceType::Number(3)
496                        })
497                    ))),
498                    Operation::Add,
499                    Box::new(Term::Calculation(
500                        Box::new(Term::DiceThrow(SelectedDice::Unchanged(
501                            FilteredDice::Simple(Dice {
502                                throws: 66,
503                                dice: DiceType::Fudge
504                            })
505                        ))),
506                        Operation::Mul,
507                        Box::new(Term::Calculation(
508                            Box::new(Term::DiceThrow(SelectedDice::Unchanged(
509                                FilteredDice::Simple(Dice {
510                                    throws: 4,
511                                    dice: DiceType::Multiply(3)
512                                })
513                            ))),
514                            Operation::Sub,
515                            Box::new(Term::Constant(1))
516                        ))
517                    ))
518                )
519            ))
520        );
521        assert!(parse_term("").is_err())
522    }
523
524    fn test_parse_expr() {}
525}