genesys_dice_command_parser/
lib.rs1#![warn(clippy::all, clippy::pedantic)]
2#![allow(clippy::pedantic::module_name_repetitions)]
3#![warn(missing_docs)]
4#![warn(missing_doc_code_examples)]
5
6use std::collections::HashMap;
11
12use nom::{branch, bytes, multi, character, combinator, sequence, Err};
13
14pub mod dice_roll;
16pub mod error;
18pub mod dice;
20
21
22use crate::dice_roll::{DiceRoll};
23use crate::dice::Dice;
24use crate::error::ParserError;
25
26
27fn parse_dice_as_value(i: &str) -> nom::IResult<&str, Dice> {
29 branch::alt((
30 combinator::value(Dice::Ability, branch::alt((bytes::complete::tag_no_case("green"), bytes::complete::tag_no_case("g"), bytes::complete::tag_no_case("ability"), bytes::complete::tag_no_case("abil")))),
31 combinator::value(Dice::Challenge, branch::alt((bytes::complete::tag_no_case("challenge"), bytes::complete::tag_no_case("cha"), bytes::complete::tag_no_case("red"), bytes::complete::tag_no_case("r"), ))),
32 combinator::value(Dice::Proficiency, branch::alt((bytes::complete::tag_no_case("proficiency"), bytes::complete::tag_no_case("prof"), bytes::complete::tag_no_case("yellow"), bytes::complete::tag_no_case("y"),))),
33 combinator::value(Dice::Difficulty, branch::alt((bytes::complete::tag_no_case("difficulty"), bytes::complete::tag_no_case("purple"), bytes::complete::tag_no_case("p"), bytes::complete::tag_no_case("diff"), bytes::complete::tag_no_case("dif")))),
34 combinator::value(Dice::Setback, branch::alt((bytes::complete::tag_no_case("black"), bytes::complete::tag_no_case("k"), bytes::complete::tag_no_case("setback"), bytes::complete::tag_no_case("s"), ))),
35 combinator::value(Dice::Force, branch::alt((bytes::complete::tag_no_case("force"), bytes::complete::tag_no_case("white"), bytes::complete::tag_no_case("w"), ))),
36 combinator::value(Dice::Boost, branch::alt((bytes::complete::tag_no_case("blue"), bytes::complete::tag_no_case("boost"), bytes::complete::tag_no_case("b")))),
37 ))(i)
38}
39
40fn parse_dice(i: &str) -> nom::IResult<&str, DiceRoll> {
42 let result = sequence::tuple((
43 combinator::opt(character::complete::digit1), parse_dice_as_value ))(i);
44 match result {
45 Ok((remaining, (number_of_dice, dice))) => Ok((
46 remaining,
47 DiceRoll::new(dice, number_of_dice.map_or(Ok(1), str::parse).unwrap()),
48 )),
49 Err(e) => Err(e),
50 }
51}
52
53fn parse_group(i: &str) -> nom::IResult<&str, Vec<DiceRoll>> {
54 let (remaining, rolls) = multi::many1(parse_dice)(i)?;
55
56 let mut dice_counts: HashMap<Dice, u32> = HashMap::new();
57
58 rolls.into_iter().for_each(|roll| {
59 let group = dice_counts.entry(roll.die).or_insert(0);
60 *group += roll.number_of_dice_to_roll;
61 });
62
63 let rolls = dice_counts.into_iter().map(|(key, value)| DiceRoll::new(key, value)).collect();
64 Ok((remaining, rolls))
65}
66
67fn parse_groups(i: &str) -> nom::IResult<&str, Vec<Vec<DiceRoll>>> {
68 let (remaining, (group_rolls, other_groups)) = sequence::tuple((
69 parse_group,
70 combinator::opt(sequence::tuple((
71 character::complete::char(','),
72 parse_groups,
73 ))),
74 ))(i)?;
75
76 let other_groups_size = match &other_groups {
77 Some((_, rolls)) => rolls.len(),
78 None => 0,
79 };
80
81 let mut rolls: Vec<Vec<DiceRoll>> = Vec::with_capacity(other_groups_size + 1);
82 rolls.push(group_rolls);
83 if other_groups.is_some() {
84 let (_, other_groups_rolls) = other_groups.unwrap();
85 rolls.extend(other_groups_rolls);
86 }
87 Ok((remaining, rolls))
88}
89
90pub fn parse_line(i: &str) -> Result<Vec<Vec<DiceRoll>>, ParserError> {
113 let whitespaceless: String = i.replace(" ", "");
114
115 match parse_groups(&whitespaceless) {
116 Ok((remaining, dice_rolls)) => {
117 if !remaining.trim().is_empty() {
118 return Err(ParserError::ParseError(format!(
119 "Expected remaining input to be empty, found: {0}",
120 remaining
121 )));
122 }
123 return Ok(dice_rolls);
124 }
125 Err(Err::Error(e)) | Err(Err::Failure(e)) => {
126 return Err(ParserError::ParseError(format!("{0}", e)));
127 }
128 Err(Err::Incomplete(_)) => {
129 return Err(ParserError::Unknown);
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::Dice;
138
139 #[test]
140 fn test_parse_dice_as_value() {
141 assert_eq!(
143 parse_dice_as_value("green"),
144 Ok(("", Dice::Ability))
145 );
146 assert_eq!(
147 parse_dice_as_value("g"),
148 Ok(("", Dice::Ability))
149 );
150 assert_eq!(
151 parse_dice_as_value("ability"),
152 Ok(("", Dice::Ability))
153 );
154 assert_eq!(
155 parse_dice_as_value("abil"),
156 Ok(("", Dice::Ability))
157 );
158
159 assert_eq!(
160 parse_dice_as_value("challenge"),
161 Ok(("", Dice::Challenge))
162 );
163 assert_eq!(
164 parse_dice_as_value("cha"),
165 Ok(("", Dice::Challenge))
166 );
167 assert_eq!(
168 parse_dice_as_value("red"),
169 Ok(("", Dice::Challenge))
170 );
171 assert_eq!(
172 parse_dice_as_value("r"),
173 Ok(("", Dice::Challenge))
174 );
175
176 assert_eq!(
177 parse_dice_as_value("Proficiency"),
178 Ok(("", Dice::Proficiency))
179 );
180 assert_eq!(
181 parse_dice_as_value("prof"),
182 Ok(("", Dice::Proficiency))
183 );
184 assert_eq!(
185 parse_dice_as_value("yellow"),
186 Ok(("", Dice::Proficiency))
187 );
188 assert_eq!(
189 parse_dice_as_value("y"),
190 Ok(("", Dice::Proficiency))
191 );
192
193 assert_eq!(
194 parse_dice_as_value("difficulty"),
195 Ok(("", Dice::Difficulty))
196 );
197 assert_eq!(
198 parse_dice_as_value("diff"),
199 Ok(("", Dice::Difficulty))
200 );
201 assert_eq!(
202 parse_dice_as_value("purple"),
203 Ok(("", Dice::Difficulty))
204 );
205 assert_eq!(
206 parse_dice_as_value("p"),
207 Ok(("", Dice::Difficulty))
208 );
209
210 assert_eq!(
211 parse_dice_as_value("black"),
212 Ok(("", Dice::Setback))
213 );
214 assert_eq!(
215 parse_dice_as_value("setback"),
216 Ok(("", Dice::Setback))
217 );
218 assert_eq!(
219 parse_dice_as_value("k"),
220 Ok(("", Dice::Setback))
221 );
222
223 assert_eq!(
224 parse_dice_as_value("force"),
225 Ok(("", Dice::Force))
226 );
227 assert_eq!(
228 parse_dice_as_value("white"),
229 Ok(("", Dice::Force))
230 );
231 assert_eq!(
232 parse_dice_as_value("w"),
233 Ok(("", Dice::Force))
234 );
235
236 assert_eq!(
237 parse_dice_as_value("blue"),
238 Ok(("", Dice::Boost))
239 );
240 assert_eq!(
241 parse_dice_as_value("b"),
242 Ok(("", Dice::Boost))
243 );
244 assert_eq!(
245 parse_dice_as_value("boost"),
246 Ok(("", Dice::Boost))
247 );
248 assert!(parse_dice_as_value("6 + 2").is_err());
249 }
250
251 #[test]
252 fn test_parse_dice() {
253 assert_eq!(
254 parse_dice("2p"),
255 Ok(("", DiceRoll::new(Dice::Difficulty, 2)))
256 );
257 assert_eq!(
258 parse_dice("6ryyy"),
259 Ok(("yyy", DiceRoll::new(Dice::Challenge, 6)))
260 );
261 assert_eq!(
262 parse_dice("ggbpp"),
263 Ok(("gbpp", DiceRoll::new(Dice::Ability, 1)))
264 );
265
266 assert!(parse_dice("*1").is_err());
267 }
268
269 #[test]
270 fn test_parse_group() {
271 assert_eq!(parse_group("6ryyy"), Ok(("", vec![DiceRoll::new(Dice::Challenge, 6), DiceRoll::new(Dice::Proficiency, 3)])));
272 assert_eq!(parse_group("d"), Ok(("", vec![DiceRoll::new(Dice::Difficulty, 1)])));
273 assert_eq!(parse_group("ddkb"), Ok(("", vec![DiceRoll::new(Dice::Difficulty, 2), DiceRoll::new(Dice::Setback, 1), DiceRoll::new(Dice::Boost, 1)])));
274 }
275}