1#![warn(clippy::all, clippy::pedantic)]
2#![allow(clippy::pedantic::module_name_repetitions)]
3#![warn(missing_docs)]
4#![warn(missing_doc_code_examples)]
5
6use nom::{branch, bytes, character, combinator, sequence, Err};
10
11pub mod dice_roll;
13pub mod error;
15
16pub mod dice_roll_with_op;
18
19mod parse;
20
21use crate::dice_roll::{DiceRoll, Operation, RollType};
22use crate::dice_roll_with_op::DiceRollWithOp;
23use crate::error::ParserError;
24use crate::parse::terminated_spare;
25
26fn parse_end_of_input_or_modifier(i: &str) -> nom::IResult<&str, &str> {
28 branch::alt((
29 bytes::complete::tag("+"),
30 bytes::complete::tag("-"),
31 bytes::complete::tag(","),
32 combinator::eof,
33 ))(i)
34}
35
36fn parse_operator_as_value(i: &str) -> nom::IResult<&str, Operation> {
38 branch::alt((
39 combinator::value(Operation::Addition, character::complete::char('+')),
40 combinator::value(Operation::Subtraction, character::complete::char('-')),
41 ))(i)
42}
43
44fn parse_roll_type(i: &str) -> nom::IResult<&str, RollType> {
46 let result = combinator::opt(branch::alt((
47 combinator::value(
48 RollType::KeepHighest,
49 terminated_spare(
51 branch::alt((
52 bytes::complete::tag_no_case("kh"),
53 bytes::complete::tag_no_case("adv"),
54 bytes::complete::tag_no_case("a"),
55 bytes::complete::tag_no_case("bane"),
56 )),
57 parse_end_of_input_or_modifier,
58 ),
59 ),
60 combinator::value(
61 RollType::KeepLowest,
62 terminated_spare(
64 branch::alt((
65 bytes::complete::tag_no_case("kl"),
66 bytes::complete::tag_no_case("dadv"),
67 bytes::complete::tag_no_case("d"),
68 bytes::complete::tag_no_case("boon"),
69 )),
70 parse_end_of_input_or_modifier,
71 ),
72 ),
73 )))(i);
74 match result {
75 Ok((i, None)) => Ok((i, RollType::Regular)),
76 Ok((i, Some(roll_type))) => Ok((i, roll_type)),
77 Err(e) => Err(e),
78 }
79}
80
81fn parse_dice_parts(i: &str) -> nom::IResult<&str, (Option<&str>, &str, &str)> {
83 sequence::tuple((
84 combinator::opt(character::complete::digit1),
85 bytes::complete::tag_no_case("d"),
86 character::complete::digit1,
87 ))(i)
88}
89
90fn parse_roll_as_value(i: &str) -> nom::IResult<&str, DiceRoll> {
92 branch::alt((parse_dice_with_operator, parse_dice_without_operator))(i)
94}
95
96fn parse_dice_with_operator(i: &str) -> nom::IResult<&str, DiceRoll> {
98 let result = sequence::tuple((
99 parse_dice_parts,
100 parse_operator_as_value,
101 terminated_spare(
102 character::complete::digit1,
103 combinator::not(sequence::tuple((
104 bytes::complete::tag_no_case("d"),
105 character::complete::digit1,
106 ))),
107 ),
108 parse_roll_type,
109 ))(i);
110 match result {
111 Ok((remaining, ((number_of_dice, _, dice_sides), operation, modifier, roll_type))) => Ok((
112 remaining,
113 dice_roll_from_parsed_items(
114 number_of_dice,
115 dice_sides,
116 Some(operation),
117 Some(modifier),
118 roll_type,
119 ),
120 )),
121 Err(e) => Err(e),
122 }
123}
124
125fn parse_dice_without_operator(i: &str) -> nom::IResult<&str, DiceRoll> {
127 let result = sequence::tuple((parse_dice_parts, parse_roll_type))(i);
128 match result {
129 Ok((remaining, ((number_of_dice, _, dice_sides), roll_type))) => Ok((
130 remaining,
131 dice_roll_from_parsed_items(number_of_dice, dice_sides, None, None, roll_type),
132 )),
133 Err(e) => Err(e),
134 }
135}
136
137fn parse_statement_with_leading_op(i: &str) -> nom::IResult<&str, Vec<DiceRollWithOp>> {
138 let (remaining, (operation, roll, later_rolls)) = sequence::tuple((
139 parse_operator_as_value,
140 parse_roll_as_value,
141 branch::alt((
142 parse_statement_with_leading_op, combinator::value(Vec::new(), character::complete::space0), )),
145 ))(i)?;
146 let mut rolls = Vec::with_capacity(later_rolls.len() + 1);
147 rolls.push(DiceRollWithOp::new(roll, operation));
148 for roll in later_rolls {
149 rolls.push(roll);
150 }
151 Ok((remaining, rolls))
152}
153
154fn parse_initial_statement(i: &str) -> nom::IResult<&str, DiceRollWithOp> {
156 let (remaining, (operator, roll)) = sequence::tuple((
157 combinator::opt(parse_operator_as_value),
158 parse_roll_as_value,
159 ))(i)?;
160 Ok((
161 remaining,
162 DiceRollWithOp::new(roll, operator.unwrap_or(Operation::Addition)),
163 ))
164}
165
166fn parse_statements(i: &str) -> nom::IResult<&str, Vec<DiceRollWithOp>> {
167 let (remaining, (operation, roll, later_rolls)) = sequence::tuple((
169 parse_operator_as_value,
170 parse_roll_as_value,
171 branch::alt((
172 parse_statement_with_leading_op, combinator::value(Vec::new(), character::complete::space0), )),
176 ))(i)?;
177 let mut rolls = Vec::with_capacity(later_rolls.len() + 1);
178 rolls.push(DiceRollWithOp::new(roll, operation));
179 for roll in later_rolls {
180 rolls.push(roll);
181 }
182 Ok((remaining, rolls))
183}
184
185fn parse_group(i: &str) -> nom::IResult<&str, Vec<DiceRollWithOp>> {
186 let (remaining, (initial_roll, additional_rolls)) = sequence::tuple((
187 parse_initial_statement,
188 branch::alt((
190 parse_statements,
191 combinator::value(Vec::new(), character::complete::space0),
193 )),
194 ))(i)?;
195
196 let mut rolls = Vec::with_capacity(additional_rolls.len() + 1);
197 rolls.push(initial_roll);
198 for roll in additional_rolls {
199 rolls.push(roll);
200 }
201 Ok((remaining, rolls))
202}
203
204fn parse_groups(i: &str) -> nom::IResult<&str, Vec<Vec<DiceRollWithOp>>> {
205 let (remaining, (group_rolls, other_groups)) = sequence::tuple((
206 parse_group,
207 combinator::opt(sequence::tuple((
208 character::complete::char(','),
209 parse_groups,
210 ))),
211 ))(i)?;
212
213 let other_groups_size = match &other_groups {
214 Some((_, rolls)) => rolls.len(),
215 None => 0,
216 };
217
218 let mut rolls: Vec<Vec<DiceRollWithOp>> = Vec::with_capacity(other_groups_size + 1);
219 rolls.push(group_rolls);
220 if other_groups.is_some() {
221 let (_, other_groups_rolls) = other_groups.unwrap();
222 rolls.extend(other_groups_rolls);
223 }
224 Ok((remaining, rolls))
225}
226
227fn dice_roll_from_parsed_items(
228 number_of_dice: Option<&str>,
229 dice_sides: &str,
230 modifier_operation: Option<Operation>,
231 modifier_value: Option<&str>,
232 roll_type: RollType,
233) -> DiceRoll {
234 let number_of_dice: u32 = number_of_dice.map_or(Ok(1), str::parse).unwrap();
235 let dice_sides: u32 = dice_sides.parse().unwrap();
236 if modifier_operation.is_some() && modifier_value.is_some() {
237 let modifier_operation = modifier_operation.unwrap();
238 let modifier_value: i32 = modifier_value.unwrap().parse().unwrap();
239 match modifier_operation {
240 Operation::Addition => {
241 return DiceRoll::new(dice_sides, Some(modifier_value), number_of_dice, roll_type)
242 }
243 Operation::Subtraction => {
244 let modifier = Some(-modifier_value);
245 return DiceRoll::new(dice_sides, modifier, number_of_dice, roll_type);
246 }
247 }
248 }
249 DiceRoll::new(dice_sides, None, number_of_dice, roll_type)
250}
251
252pub fn parse_line(i: &str) -> Result<Vec<Vec<DiceRollWithOp>>, ParserError> {
275 let whitespaceless: String = i.replace(" ", "");
276
277 match parse_groups(&whitespaceless) {
278 Ok((remaining, dice_rolls)) => {
279 if !remaining.trim().is_empty() {
280 return Err(ParserError::ParseError(format!(
281 "Expected remaining input to be empty, found: {0}",
282 remaining
283 )));
284 }
285 return Ok(dice_rolls);
286 }
287 Err(Err::Error(e)) | Err(Err::Failure(e)) => {
288 return Err(ParserError::ParseError(format!("{0}", e)));
289 }
290 Err(Err::Incomplete(_)) => {
291 return Err(ParserError::Unknown);
292 }
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn test_parse_dice_parts() {
302 assert_eq!(
303 parse_dice_parts("2d6 + 2"),
304 Ok((" + 2", (Some("2"), "d", "6")))
305 );
306 assert_eq!(parse_dice_parts("d8 + 3"), Ok((" + 3", (None, "d", "8"))));
307 assert_eq!(parse_dice_parts("d6 + 2"), Ok((" + 2", (None, "d", "6"))));
308 assert_eq!(parse_dice_parts("d6+2+"), Ok(("+2+", (None, "d", "6"))));
309 assert_eq!(parse_dice_parts("d1"), Ok(("", (None, "d", "1"))));
310 assert!(parse_dice_parts("6 + 2").is_err());
311 }
312
313 #[test]
314 fn test_parse_operator_as_value() {
315 assert_eq!(
316 parse_operator_as_value("+2"),
317 Ok(("2", Operation::Addition))
318 );
319 assert_eq!(
320 parse_operator_as_value("-1"),
321 Ok(("1", Operation::Subtraction))
322 );
323
324 assert_eq!(parse_operator_as_value("*1").is_err(), true);
325 }
326
327 #[test]
328 fn test_roll_type_parser() {
329 assert_eq!(parse_roll_type("a"), Ok(("", RollType::KeepHighest)));
330
331 assert_eq!(parse_roll_type("d"), Ok(("", RollType::KeepLowest)));
332
333 assert_eq!(parse_roll_type(""), Ok(("", RollType::Regular)));
334
335 assert_eq!(parse_roll_type("e"), Ok(("e", RollType::Regular)));
336 assert_eq!(parse_roll_type("+"), Ok(("+", RollType::Regular)));
337 assert_eq!(parse_roll_type("d+"), Ok(("+", RollType::KeepLowest)));
338 }
339
340 #[test]
341 fn test_parse_roll_as_value() {
342 assert_eq!(
343 parse_roll_as_value("d6+2"),
344 Ok(("", DiceRoll::new_regular_roll(6, Some(2), 1)))
345 );
346
347 assert_eq!(
348 parse_roll_as_value("3d6-2"),
349 Ok(("", DiceRoll::new_regular_roll(6, Some(-2), 3)))
350 );
351
352 assert_eq!(
353 parse_roll_as_value("2d20-2a"),
354 Ok(("", DiceRoll::new(20, Some(-2), 2, RollType::KeepHighest)))
355 );
356
357 assert_eq!(
358 parse_roll_as_value("2d20-2kh"),
359 Ok(("", DiceRoll::new(20, Some(-2), 2, RollType::KeepHighest)))
360 );
361
362 assert_eq!(
363 parse_roll_as_value("2d20-2adv"),
364 Ok(("", DiceRoll::new(20, Some(-2), 2, RollType::KeepHighest)))
365 );
366
367 assert_eq!(
368 parse_roll_as_value("2d20-2bane"),
369 Ok(("", DiceRoll::new(20, Some(-2), 2, RollType::KeepHighest)))
370 );
371
372 assert_eq!(
373 parse_roll_as_value("2d20-2a+d4"),
374 Ok(("+d4", DiceRoll::new(20, Some(-2), 2, RollType::KeepHighest)))
375 );
376
377 assert_eq!(
378 parse_roll_as_value("2d20-2+d4"),
379 Ok(("+d4", DiceRoll::new(20, Some(-2), 2, RollType::Regular)))
380 );
381
382 assert_eq!(
383 parse_roll_as_value("2d6+d4"),
384 Ok(("+d4", DiceRoll::new(6, None, 2, RollType::Regular)))
385 );
386 assert_eq!(
387 parse_roll_as_value("2d6+2d4"),
388 Ok(("+2d4", DiceRoll::new(6, None, 2, RollType::Regular)))
389 );
390
391 assert_eq!(
392 parse_roll_as_value("3d4+1d"),
393 Ok(("", DiceRoll::new(4, Some(1), 3, RollType::KeepLowest)))
394 );
395
396 assert_eq!(
397 parse_roll_as_value("3d4+1d"),
398 Ok(("", DiceRoll::new(4, Some(1), 3, RollType::KeepLowest)))
399 );
400
401 assert_eq!(
402 parse_roll_as_value("3d4+1dadv"),
403 Ok(("", DiceRoll::new(4, Some(1), 3, RollType::KeepLowest)))
404 );
405
406 assert_eq!(
407 parse_roll_as_value("3d4+1boon"),
408 Ok(("", DiceRoll::new(4, Some(1), 3, RollType::KeepLowest)))
409 );
410
411 assert_eq!(
412 parse_roll_as_value("3d4+1kl"),
413 Ok(("", DiceRoll::new(4, Some(1), 3, RollType::KeepLowest)))
414 );
415
416 assert_eq!(
417 parse_roll_as_value("d1d"),
418 Ok(("", DiceRoll::new(1, None, 1, RollType::KeepLowest)))
419 );
420 assert_eq!(
421 parse_roll_as_value("d20d,d4"),
422 Ok((",d4", DiceRoll::new(20, None, 1, RollType::KeepLowest)))
423 );
424 }
425
426 #[test]
427 fn test_parse_initial() {
428 assert_eq!(
429 parse_initial_statement("d6"),
430 Ok((
431 "",
432 DiceRollWithOp::new(
433 DiceRoll::new(6, None, 1, RollType::Regular,),
434 Operation::Addition
435 )
436 ))
437 );
438
439 assert_eq!(
440 parse_initial_statement("4d6+10a"),
441 Ok((
442 "",
443 DiceRollWithOp::new(
444 DiceRoll::new(6, Some(10), 4, RollType::KeepHighest),
445 Operation::Addition
446 )
447 ))
448 );
449
450 assert_eq!(
451 parse_initial_statement("d6+d4"),
452 Ok((
453 "+d4",
454 DiceRollWithOp::new(
455 DiceRoll::new(6, None, 1, RollType::Regular,),
456 Operation::Addition
457 )
458 ))
459 );
460
461 assert_eq!(
462 parse_initial_statement("4d6+10a-d4"),
463 Ok((
464 "-d4",
465 DiceRollWithOp::new(
466 DiceRoll::new(6, Some(10), 4, RollType::KeepHighest),
467 Operation::Addition
468 )
469 ))
470 );
471
472 assert_eq!(
473 parse_initial_statement("-d1d-4d4d"),
474 Ok((
475 "-4d4d",
476 DiceRollWithOp::new(
477 DiceRoll::new(1, None, 1, RollType::KeepLowest),
478 Operation::Subtraction
479 )
480 ))
481 );
482
483 assert_eq!(
484 parse_initial_statement("d20d,d4"),
485 Ok((
486 ",d4",
487 DiceRollWithOp::new(
488 DiceRoll::new(20, None, 1, RollType::KeepLowest),
489 Operation::Addition
490 )
491 ))
492 );
493 }
494
495 #[test]
496 fn test_parse_line() {
497 assert_eq!(
498 parse_line("d6"),
499 Ok(vec![vec![DiceRollWithOp::new(
500 DiceRoll::new(6, None, 1, RollType::Regular,),
501 Operation::Addition
502 )]])
503 );
504 assert_eq!(
505 parse_line("d20 + 5"),
506 Ok(vec![vec![DiceRollWithOp::new(
507 DiceRoll::new(20, Some(5), 1, RollType::Regular,),
508 Operation::Addition,
509 )]])
510 );
511 assert_eq!(
512 parse_line("2d10 - 5"),
513 Ok(vec![vec![DiceRollWithOp::new(
514 DiceRoll::new(10, Some(-5), 2, RollType::Regular,),
515 Operation::Addition
516 )]])
517 );
518 assert_eq!(
519 parse_line("3d6"),
520 Ok(vec![vec![DiceRollWithOp::new(
521 DiceRoll::new(6, None, 3, RollType::Regular,),
522 Operation::Addition,
523 )]])
524 );
525 assert_eq!(
526 parse_line("5d20 + 5"),
527 Ok(vec![vec![DiceRollWithOp::new(
528 DiceRoll::new(20, Some(5), 5, RollType::Regular,),
529 Operation::Addition
530 )]])
531 );
532
533 assert_eq!(
535 parse_line("d0 - 5"),
536 Ok(vec![vec![DiceRollWithOp::new(
537 DiceRoll::new(0, Some(-5), 1, RollType::Regular,),
538 Operation::Addition
539 )]])
540 );
541
542 assert_eq!(
543 parse_line("d200a"),
544 Ok(vec![vec![DiceRollWithOp::new(
545 DiceRoll::new(200, None, 1, RollType::KeepHighest),
546 Operation::Addition
547 )]])
548 );
549
550 assert_eq!(
551 parse_line("d200 A"),
552 Ok(vec![vec![DiceRollWithOp::new(
553 DiceRoll::new(200, None, 1, RollType::KeepHighest),
554 Operation::Addition
555 )]])
556 );
557 assert_eq!(
558 parse_line("d200 d"),
559 Ok(vec![vec![DiceRollWithOp::new(
560 DiceRoll::new(200, None, 1, RollType::KeepLowest),
561 Operation::Addition
562 )]])
563 );
564
565 assert_eq!(
566 parse_line("d100 + d4"),
567 Ok(vec![vec![
568 DiceRollWithOp::new(
569 DiceRoll::new(100, None, 1, RollType::Regular),
570 Operation::Addition
571 ),
572 DiceRollWithOp::new(
573 DiceRoll::new(4, None, 1, RollType::Regular),
574 Operation::Addition
575 )
576 ]])
577 );
578
579 assert_eq!(
580 parse_line("d100 - d6"),
581 Ok(vec![vec![
582 DiceRollWithOp::new(
583 DiceRoll::new(100, None, 1, RollType::Regular),
584 Operation::Addition
585 ),
586 DiceRollWithOp::new(
587 DiceRoll::new(6, None, 1, RollType::Regular),
588 Operation::Subtraction
589 )
590 ]])
591 );
592
593 assert!(parse_line("cd20").is_err());
594
595 assert_eq!(
596 parse_line("2d6 + 2d4"),
597 Ok(vec![vec![
598 DiceRollWithOp::new(
599 DiceRoll::new(6, None, 2, RollType::Regular),
600 Operation::Addition
601 ),
602 DiceRollWithOp::new(
603 DiceRoll::new(4, None, 2, RollType::Regular),
604 Operation::Addition
605 )
606 ]])
607 );
608
609 assert_eq!(
610 parse_line("d20 + 2 + d4"),
611 Ok(vec![vec![
612 DiceRollWithOp::new(
613 DiceRoll::new(20, Some(2), 1, RollType::Regular,),
614 Operation::Addition
615 ),
616 DiceRollWithOp::new(
617 DiceRoll::new(4, None, 1, RollType::Regular,),
618 Operation::Addition
619 )
620 ]])
621 );
622
623 assert_eq!(
624 parse_line("d20 + d4 - 2d6"),
625 Ok(vec![vec![
626 DiceRollWithOp::new(
627 DiceRoll::new(20, None, 1, RollType::Regular),
628 Operation::Addition
629 ),
630 DiceRollWithOp::new(
631 DiceRoll::new(4, None, 1, RollType::Regular,),
632 Operation::Addition
633 ),
634 DiceRollWithOp::new(
635 DiceRoll::new(6, None, 2, RollType::Regular,),
636 Operation::Subtraction
637 ),
638 ]])
639 );
640
641 assert_eq!(
642 parse_line("d20 + 2 + d4 - 2d6"),
643 Ok(vec![vec![
644 DiceRollWithOp::new(
645 DiceRoll::new(20, Some(2), 1, RollType::Regular,),
646 Operation::Addition
647 ),
648 DiceRollWithOp::new(
649 DiceRoll::new(4, None, 1, RollType::Regular,),
650 Operation::Addition
651 ),
652 DiceRollWithOp::new(
653 DiceRoll::new(6, None, 2, RollType::Regular,),
654 Operation::Subtraction
655 ),
656 ]])
657 );
658
659 assert_eq!(
660 parse_line("d20 - 6 + d4 - 2d6"),
661 Ok(vec![vec![
662 DiceRollWithOp::new(
663 DiceRoll::new(20, Some(-6), 1, RollType::Regular,),
664 Operation::Addition
665 ),
666 DiceRollWithOp::new(
667 DiceRoll::new(4, None, 1, RollType::Regular,),
668 Operation::Addition
669 ),
670 DiceRollWithOp::new(
671 DiceRoll::new(6, None, 2, RollType::Regular,),
672 Operation::Subtraction
673 ),
674 ]])
675 );
676
677 assert_eq!(
678 parse_line("6d20 - 3d4+1kl"),
679 Ok(vec![vec![
680 DiceRollWithOp::new(
681 DiceRoll::new(20, None, 6, RollType::Regular),
682 Operation::Addition
683 ),
684 DiceRollWithOp::new(
685 DiceRoll::new(4, Some(1), 3, RollType::KeepLowest),
686 Operation::Subtraction
687 )
688 ]])
689 );
690
691 assert_eq!(
692 parse_line("-d1kl - 4d4kl"),
693 Ok(vec![vec![
694 DiceRollWithOp::new(
695 DiceRoll::new(1, None, 1, RollType::KeepLowest),
696 Operation::Subtraction
697 ),
698 DiceRollWithOp::new(
699 DiceRoll::new(4, None, 4, RollType::KeepLowest),
700 Operation::Subtraction
701 )
702 ]])
703 );
704 assert_eq!(
705 parse_line("d20, d4"),
706 Ok(vec![
707 vec![DiceRollWithOp::new(
708 DiceRoll::new(20, None, 1, RollType::Regular),
709 Operation::Addition
710 )],
711 vec![DiceRollWithOp::new(
712 DiceRoll::new(4, None, 1, RollType::Regular),
713 Operation::Addition
714 )]
715 ])
716 );
717 assert_eq!(
718 parse_line("d20, d4, d6, d100, 3d100"),
719 Ok(vec![
720 vec![DiceRollWithOp::new(
721 DiceRoll::new(20, None, 1, RollType::Regular),
722 Operation::Addition
723 )],
724 vec![DiceRollWithOp::new(
725 DiceRoll::new(4, None, 1, RollType::Regular),
726 Operation::Addition
727 )],
728 vec![DiceRollWithOp::new(
729 DiceRoll::new(6, None, 1, RollType::Regular),
730 Operation::Addition
731 )],
732 vec![DiceRollWithOp::new(
733 DiceRoll::new(100, None, 1, RollType::Regular),
734 Operation::Addition
735 )],
736 vec![DiceRollWithOp::new(
737 DiceRoll::new(100, None, 3, RollType::Regular),
738 Operation::Addition
739 )]
740 ])
741 );
742 assert_eq!(
743 parse_line("d20, -d4, -d6, -d100+2, -3d100-6kl"),
744 Ok(vec![
745 vec![DiceRollWithOp::new(
746 DiceRoll::new(20, None, 1, RollType::Regular),
747 Operation::Addition
748 )],
749 vec![DiceRollWithOp::new(
750 DiceRoll::new(4, None, 1, RollType::Regular),
751 Operation::Subtraction
752 )],
753 vec![DiceRollWithOp::new(
754 DiceRoll::new(6, None, 1, RollType::Regular),
755 Operation::Subtraction
756 )],
757 vec![DiceRollWithOp::new(
758 DiceRoll::new(100, Some(2), 1, RollType::Regular),
759 Operation::Subtraction
760 )],
761 vec![DiceRollWithOp::new(
762 DiceRoll::new(100, Some(-6), 3, RollType::KeepLowest),
763 Operation::Subtraction
764 )]
765 ])
766 );
767 assert_eq!(
768 parse_line("d20kl, d4"),
769 Ok(vec![
770 vec![DiceRollWithOp::new(
771 DiceRoll::new(20, None, 1, RollType::KeepLowest),
772 Operation::Addition
773 )],
774 vec![DiceRollWithOp::new(
775 DiceRoll::new(4, None, 1, RollType::Regular),
776 Operation::Addition
777 )]
778 ])
779 );
780 }
781
782 #[test]
783 fn test_statement_parser() {
784 assert_eq!(
785 parse_statement_with_leading_op("+2d4"),
786 Ok((
787 "",
788 vec![DiceRollWithOp::new(
789 DiceRoll::new(4, None, 2, RollType::Regular),
790 Operation::Addition
791 )]
792 ))
793 );
794
795 assert_eq!(
796 parse_statement_with_leading_op("-3d12-4a"),
797 Ok((
798 "",
799 vec![DiceRollWithOp::new(
800 DiceRoll::new(12, Some(-4), 3, RollType::KeepHighest),
801 Operation::Subtraction
802 )]
803 ))
804 );
805 }
806}