roll_rs/
lib.rs

1mod filtermodifier;
2mod interpreter;
3mod options;
4mod parser;
5mod roll;
6
7use crate::interpreter::Ast;
8pub use crate::parser::*;
9pub use crate::roll::*;
10use core::fmt;
11pub use rand_core;
12use std::collections::HashMap;
13
14pub struct RollResult {
15    pub string_result: String,
16    pub dice_total: crate::interpreter::Value,
17}
18
19impl fmt::Display for RollResult {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(f, "{}", self.string_result)
22    }
23}
24
25const STAT_ROLL: &str = "4d6l";
26pub fn roll_stats() -> String {
27    let mut res = String::new();
28    fn roll_stat() -> Roll {
29        let mut rolls = Vec::new();
30        Parser::new(STAT_ROLL)
31            .parse()
32            .unwrap()
33            .interp(&mut rolls)
34            .unwrap();
35        rolls.remove(0).1
36    }
37
38    for _ in 0..6 {
39        let roll = roll_stat();
40        res.push_str(&format!("{:2}: {:?}\n", roll.total, roll.vals))
41    }
42    res
43}
44
45pub fn roll_inline(s: &str, advanced: bool) -> Result<RollResult, String> {
46    let mut p = Parser::new(s);
47    p.advanced = advanced;
48
49    let ast = p.parse().map_err(|e| e.to_string())?;
50
51    let copy = ast.clone();
52
53    let mut rolls = Vec::new();
54    let total = ast.interp(&mut rolls).unwrap();
55
56    let mut map = HashMap::new();
57    for (pos, roll) in rolls {
58        map.insert(pos, roll);
59    }
60
61    let res = replace_rolls(copy, &map, |roll| format!("{:?}", roll.vals));
62    let result: RollResult = RollResult {
63        string_result: format!("{} = {} = {}", s, res, total),
64        dice_total: total,
65    };
66    Ok(result)
67}
68
69fn replace_rolls(ast: Ast, lookup: &HashMap<u64, Roll>, func: fn(&Roll) -> String) -> Ast {
70    return match ast {
71        Ast::Add(l, r) => Ast::Add(
72            Box::from(replace_rolls(*l, lookup, func)),
73            Box::from(replace_rolls(*r, lookup, func)),
74        ),
75        Ast::Sub(l, r) => Ast::Sub(
76            Box::from(replace_rolls(*l, lookup, func)),
77            Box::from(replace_rolls(*r, lookup, func)),
78        ),
79        Ast::Mul(l, r) => Ast::Mul(
80            Box::from(replace_rolls(*l, lookup, func)),
81            Box::from(replace_rolls(*r, lookup, func)),
82        ),
83        Ast::Div(l, r) => Ast::Div(
84            Box::from(replace_rolls(*l, lookup, func)),
85            Box::from(replace_rolls(*r, lookup, func)),
86        ),
87        Ast::Mod(l, r) => Ast::Mod(
88            Box::from(replace_rolls(*l, lookup, func)),
89            Box::from(replace_rolls(*r, lookup, func)),
90        ),
91        Ast::IDiv(l, r) => Ast::IDiv(
92            Box::from(replace_rolls(*l, lookup, func)),
93            Box::from(replace_rolls(*r, lookup, func)),
94        ),
95        Ast::Power(l, r) => Ast::Power(
96            Box::from(replace_rolls(*l, lookup, func)),
97            Box::from(replace_rolls(*r, lookup, func)),
98        ),
99        Ast::Minus(l) => Ast::Minus(Box::from(replace_rolls(*l, lookup, func))),
100        Ast::Dice(_, _, _, pos) => {
101            // Safety: we exhaustively add all positions to this hashmap so it must contain everything
102            // we look up.
103            let roll = lookup.get(&pos).unwrap();
104            Ast::Const(func(roll))
105        }
106        x @ Ast::Const(_) => x,
107    };
108}
109
110#[cfg(test)]
111mod test {
112    use super::*;
113    use crate::parser::Parser;
114    use bnf::Grammar;
115
116    const GRAMMAR: &str = include_str!("../../grammar.bnf");
117
118    fn generate_sentence(g: &Grammar) -> String {
119        loop {
120            let res = g.generate();
121            match res {
122                Ok(i) => break i,
123                Err(bnf::Error::RecursionLimit(_)) => continue,
124                _ => panic!("aaaaa"),
125            }
126        }
127    }
128
129    #[test]
130    fn fuzz() {
131        let grammar: Grammar = GRAMMAR.parse().unwrap();
132
133        for _ in 0..500 {
134            let sentence = generate_sentence(&grammar);
135            if let Err(e) = Parser::new(&sentence).advanced().parse() {
136                println!("failed with sentence \"{}\" and error: {:?}", sentence, e);
137                break;
138            }
139        }
140    }
141
142    #[test]
143    fn test_inplace() {
144        println!("{}", roll_inline("4d8 + 2d8", false).unwrap());
145    }
146}