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 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}