1use alloc::{
40 boxed::Box,
41 fmt, format,
42 string::{String, ToString},
43 vec::Vec,
44};
45use core::str;
46
47use chumsky::prelude::*;
48
49use crate::{
50 dice::{
51 modifier::{Condition, Modifier},
52 Dice,
53 },
54 expr::Expr,
55};
56
57#[must_use]
59pub fn dice_part<'src>() -> impl Parser<'src, &'src str, Dice, extra::Err<Rich<'src, char>>> + Copy {
60 text::int(10)
62 .labelled("dice count")
63 .or_not()
64 .then_ignore(just('d'))
65 .then(text::int(10).labelled("dice sides"))
66 .then(modifier_list_part())
67 .try_map(|((count, sides), modifiers), span| {
68 let count = count
69 .unwrap_or("1")
70 .parse()
71 .map_err(|err| Rich::custom(span, format!("Dice count: {err}")))?;
72 let sides = sides
73 .parse()
74 .map_err(|err| Rich::custom(span, format!("Dice sides: {err}")))?;
75
76 Ok(Dice {
77 count,
78 sides,
79 modifiers,
80 })
81 })
82 .labelled("dice set")
83}
84
85#[must_use]
88pub fn dice<'src>() -> impl Parser<'src, &'src str, Dice, extra::Err<Rich<'src, char>>> + Copy {
89 dice_part().then_ignore(end())
90}
91
92#[must_use]
94pub fn modifier_part<'src>() -> impl Parser<'src, &'src str, Modifier, extra::Err<Rich<'src, char>>> + Copy {
95 let condition = condition_part();
97
98 choice((
100 just('r')
102 .ignored()
103 .then(just('r').ignored().or_not().map(|r| r.is_some()))
104 .then(condition)
105 .map(|(((), recurse), cond)| Modifier::Reroll { cond, recurse }),
106 just('x')
108 .ignored()
109 .then(just('o').ignored().or_not().map(|o| o.is_none()))
110 .then(condition.or_not())
111 .map(|(((), recurse), cond)| Modifier::Explode { cond, recurse }),
112 just("kl")
114 .ignored()
115 .then(text::int(10).labelled("keep lowest count").or_not())
116 .try_map(|((), count), span| {
117 let count = count
118 .unwrap_or("1")
119 .parse()
120 .map_err(|err| Rich::custom(span, format!("Keep lowest count: {err}")))?;
121 Ok(Modifier::KeepLow(count))
122 }),
123 just('k')
125 .ignored()
126 .then_ignore(just('h').or_not())
127 .then(text::int(10).labelled("keep highest count").or_not())
128 .try_map(|((), count), span| {
129 let count = count
130 .unwrap_or("1")
131 .parse()
132 .map_err(|err| Rich::custom(span, format!("Keep highest count: {err}")))?;
133 Ok(Modifier::KeepHigh(count))
134 }),
135 just("min")
137 .ignored()
138 .then(text::int(10).labelled("min roll value"))
139 .try_map(|((), min): ((), &str), span| {
140 let min = min
141 .parse()
142 .map_err(|err| Rich::custom(span, format!("Minimum: {err}")))?;
143 Ok(Modifier::Min(min))
144 }),
145 just("max")
147 .ignored()
148 .then(text::int(10).labelled("max roll value"))
149 .try_map(|((), max): ((), &str), span| {
150 let max = max
151 .parse()
152 .map_err(|err| Rich::custom(span, format!("Maximum: {err}")))?;
153 Ok(Modifier::Max(max))
154 }),
155 ))
156 .labelled("dice modifier")
157}
158
159#[must_use]
162pub fn modifier<'src>() -> impl Parser<'src, &'src str, Modifier, extra::Err<Rich<'src, char>>> + Copy {
163 modifier_part().then_ignore(end())
164}
165
166#[must_use]
168pub fn modifier_list_part<'src>() -> impl Parser<'src, &'src str, Vec<Modifier>, extra::Err<Rich<'src, char>>> + Copy {
169 modifier_part().repeated().collect()
170}
171
172#[must_use]
175pub fn modifier_list<'src>() -> impl Parser<'src, &'src str, Vec<Modifier>, extra::Err<Rich<'src, char>>> + Copy {
176 modifier_list_part().then_ignore(end())
177}
178
179#[must_use]
181pub fn condition_part<'src>() -> impl Parser<'src, &'src str, Condition, extra::Err<Rich<'src, char>>> + Copy {
182 choice((
183 just(">=").to(Condition::Gte as fn(u8) -> _),
184 just("<=").to(Condition::Lte as fn(u8) -> _),
185 just('>').to(Condition::Gt as fn(u8) -> _),
186 just('<').to(Condition::Lt as fn(u8) -> _),
187 just('=').to(Condition::Eq as fn(u8) -> _),
188 ))
189 .labelled("condition symbol")
190 .or_not()
191 .then(text::int::<&'src str, _, _>(10).labelled("condition number"))
192 .try_map(|(condfn, val), span| {
193 let val = val
194 .parse()
195 .map_err(|err| Rich::custom(span, format!("Modifier condition: {err}")))?;
196 Ok(condfn.map_or_else(|| Condition::Eq(val), |condfn| condfn(val)))
197 })
198 .labelled("modifier condition")
199}
200
201#[must_use]
204pub fn condition<'src>() -> impl Parser<'src, &'src str, Condition, extra::Err<Rich<'src, char>>> + Copy {
205 condition_part().then_ignore(end())
206}
207
208#[must_use]
211pub fn expr_part<'src>() -> impl Parser<'src, &'src str, Expr, extra::Err<Rich<'src, char>>> + Clone {
212 let op = |c| just(c).padded();
214
215 recursive(|expr| {
216 let int = text::int(10)
218 .try_map(|s: &str, span| {
219 s.parse()
220 .map(Expr::Num)
221 .map_err(|err| Rich::custom(span, format!("{err}")))
222 })
223 .labelled("number");
224
225 let dice = dice_part().map(Expr::Dice);
227
228 let atom = dice
230 .or(int)
231 .or(expr.delimited_by(just('('), just(')')).labelled("group"))
232 .padded();
233
234 let unary = op('-').repeated().foldr(atom, |_op, rhs| Expr::Neg(Box::new(rhs)));
236
237 let product = unary.clone().foldl(
239 choice((
240 op('*').to(Expr::Mul as fn(_, _) -> _),
241 op('/').to(Expr::DivDown as fn(_, _) -> _),
242 op('\\').to(Expr::DivUp as fn(_, _) -> _),
243 ))
244 .then(unary)
245 .repeated(),
246 |lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)),
247 );
248
249 product.clone().foldl(
251 choice((
252 op('+').to(Expr::Add as fn(_, _) -> _),
253 op('-').to(Expr::Sub as fn(_, _) -> _),
254 ))
255 .then(product)
256 .repeated(),
257 |lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)),
258 )
259 })
260}
261
262#[must_use]
265pub fn expr<'src>() -> impl Parser<'src, &'src str, Expr, extra::Err<Rich<'src, char>>> + Clone {
266 expr_part().then_ignore(end())
267}
268
269#[derive(Debug, Clone)]
271#[non_exhaustive]
272pub struct Error {
273 pub details: String,
275}
276
277#[allow(clippy::absolute_paths)]
278impl core::error::Error for Error {
279 fn description(&self) -> &str {
280 &self.details
281 }
282}
283
284impl fmt::Display for Error {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 write!(f, "{}", self.details)
287 }
288}
289
290impl str::FromStr for Dice {
291 type Err = Error;
292
293 fn from_str(s: &str) -> Result<Self, Self::Err> {
294 let lc = s.to_lowercase();
295 let result = dice().parse(&lc).into_result().map_err(|errs| Error {
296 details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
297 });
298 result
299 }
300}
301
302impl str::FromStr for Modifier {
303 type Err = Error;
304
305 fn from_str(s: &str) -> Result<Self, Self::Err> {
306 let lc = s.to_lowercase();
307 let result = modifier().parse(&lc).into_result().map_err(|errs| Error {
308 details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
309 });
310 result
311 }
312}
313
314impl str::FromStr for Condition {
315 type Err = Error;
316
317 fn from_str(s: &str) -> Result<Self, Self::Err> {
318 condition().parse(s).into_result().map_err(|errs| Error {
319 details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
320 })
321 }
322}
323
324impl str::FromStr for Expr {
325 type Err = Error;
326
327 fn from_str(s: &str) -> Result<Self, Self::Err> {
328 let lc = s.to_lowercase();
329 let result = expr().parse(&lc).into_result().map_err(|errs| Error {
330 details: errs.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "),
331 });
332 result
333 }
334}
335
336pub trait GenParser<T> {
338 #[must_use]
340 fn parser<'src>() -> impl Parser<'src, &'src str, T, extra::Err<Rich<'src, char>>> + Clone;
341
342 #[must_use]
345 fn part_parser<'src>() -> impl Parser<'src, &'src str, T, extra::Err<Rich<'src, char>>> + Clone;
346}
347
348impl GenParser<Dice> for Dice {
349 #[inline]
350 #[allow(refining_impl_trait)]
351 fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
352 dice()
353 }
354
355 #[inline]
356 #[allow(refining_impl_trait)]
357 fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
358 dice_part()
359 }
360}
361
362impl GenParser<Modifier> for Modifier {
363 #[inline]
364 #[allow(refining_impl_trait)]
365 fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
366 modifier()
367 }
368
369 #[inline]
370 #[allow(refining_impl_trait)]
371 fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
372 modifier_part()
373 }
374}
375
376impl GenParser<Condition> for Condition {
377 #[inline]
378 #[allow(refining_impl_trait)]
379 fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
380 condition()
381 }
382
383 #[inline]
384 #[allow(refining_impl_trait)]
385 fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Copy {
386 condition_part()
387 }
388}
389
390impl GenParser<Expr> for Expr {
391 #[inline]
392 fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Clone {
393 expr()
394 }
395
396 #[inline]
397 fn part_parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<Rich<'src, char>>> + Clone {
398 expr_part()
399 }
400}