From 015aeeac61d372c941fb8efa61db491adaa9f7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85dne=20E=2E=20Moldes=C3=A6ter?= Date: Tue, 23 Jun 2020 21:37:45 +0200 Subject: [PATCH] Added segment and filter enums and constructors and added keep and lowest/highest options for keep/drop --- src/lib.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++--- src/lib_test.rs | 44 +++++++++++++++++------------- 2 files changed, 95 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0b3d5af..8eb3dcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,74 @@ use regex::Regex; +use thiserror::Error; +use anyhow::Result; #[cfg(test)] mod lib_test; -fn parse_dice_segments<'a>(cmd: &'a str) -> Vec> { +#[derive(Debug, PartialEq)] +enum DiceFilter { + DropLowest(i32), DropHighest(i32), KeepLowest(i32), KeepHighest(i32), +} + +#[derive(Debug, PartialEq)] +enum Segment { + DiceRoll{ + op: char, + count: i32, + size: i32, + filter: Option, + }, + Modifier{ + op: char, + amount: i32, + }, +} + +#[derive(Error, Debug, PartialEq)] +enum SegmentError { + #[error("Invalid segment")] + InvalidSegment, + #[error("No dice size was given")] + SizeIsMissing, + #[error("Invalid filter operator: {op}")] + InvalidFilterOperator{op: String}, + #[error("Incomplete filter")] + IncompleteFilter, +} + +fn construct_dice_filter(op: &str, amount: i32) -> Result { + match op.to_lowercase().as_str() { + "d" | "dl" => Ok(DiceFilter::DropLowest(amount)), + "dh" => Ok(DiceFilter::DropHighest(amount)), + "k" | "kh" => Ok(DiceFilter::KeepHighest(amount)), + "kl" => Ok(DiceFilter::KeepLowest(amount)), + _ => Err(SegmentError::InvalidFilterOperator{op: op.to_owned()})?, + } +} + +fn construct_dice_segment(cap: regex::Captures) -> Result { + let op = cap.name("op").and_then(|i| i.as_str().chars().next()).unwrap_or('+'); + let modifier = cap.name("mod").map(|i| i.as_str().parse()).transpose()?; + + if let Some(amount) = modifier { + return Ok(Segment::Modifier{ op, amount }); + } + + let count:i32 = cap.name("count").map(|i| i.as_str().parse()).transpose()?.unwrap_or(1); + let size:i32 = cap.name("size").map(|i| i.as_str().parse()).transpose()?.ok_or(SegmentError::SizeIsMissing)?; + let filter_amount = cap.name("filter").map(|i| i.as_str().parse::()).transpose()?; + let filter_op = cap.name("filter_op").map(|op| op.as_str()); + dbg!(filter_amount); + dbg!(filter_op); + if filter_amount.is_some() != filter_op.is_some() { + Err(SegmentError::IncompleteFilter)?; + } + let filter = filter_amount.and_then(|amount| (filter_op.map(|op| construct_dice_filter(op, amount)))).transpose()?; + + Ok(Segment::DiceRoll{op, count, size, filter}) +} + +fn parse_dice_segments(cmd: &str) -> Result> { let regex = Regex::new(r#"(?x) (?P[+\-/*])? \s* @@ -12,12 +77,13 @@ fn parse_dice_segments<'a>(cmd: &'a str) -> Vec> { (?P\d+)? # count (optional) d (?P\d+) # dice size - (?:d(?P\d+))? + (?P[dk][hl]?)? + (?P\d+)? ) | (?: (?P\d+) ) ) "#).expect("Failed to compile regex"); - regex.captures_iter(cmd).collect() + regex.captures_iter(cmd).map(construct_dice_segment).collect() } diff --git a/src/lib_test.rs b/src/lib_test.rs index 9c51ff1..326f2a2 100644 --- a/src/lib_test.rs +++ b/src/lib_test.rs @@ -2,27 +2,35 @@ use super::*; #[test] fn test_roll_dice() { - let mut results = parse_dice_segments("2d6"); + let mut results = parse_dice_segments("2d6").expect("Failed to unpack results"); - assert_eq!(results.len(), 1); - assert_eq!(results[0]["count"], *"2"); - assert_eq!(results[0]["size"], *"6"); + assert_eq!(results, vec![ Segment::DiceRoll{ op: '+', count: 2, size: 6, filter: None } ]); - results = parse_dice_segments("2d6 + 1d20"); + results = parse_dice_segments("2d6 + 1d20").expect("Failed to unpack results"); - assert_eq!(results.len(), 2); - assert_eq!(results[1].name("op").map(|i| i.as_str()), Some("+")); - assert_eq!(results[1].name("count").map(|i| i.as_str()), Some("1")); - assert_eq!(results[1]["size"], *"20"); + assert_eq!(results, vec![ + Segment::DiceRoll{ op: '+', count: 2, size: 6, filter: None }, + Segment::DiceRoll{ op: '+', count: 1, size: 20, filter: None }, + ]); + + results = parse_dice_segments("4d6d1").expect("Failed to unpack results"); + + assert_eq!(results, vec![ + Segment::DiceRoll{ op: '+', count: 4, size: 6, filter: Some(DiceFilter::DropLowest(1)) }, + ]); + + results = parse_dice_segments("3d8+8").expect("Failed to unpack results"); - results = parse_dice_segments("4d6d1"); + assert_eq!(results, vec![ + Segment::DiceRoll{ op: '+', count: 3, size: 8, filter: None }, + Segment::Modifier{ op: '+', amount: 8 }, + ]); - assert_eq!(results.len(), 1); - assert_eq!(results[0].name("drop").map(|i| i.as_str()), Some("1")); - - results = parse_dice_segments("3d8+8"); - - assert_eq!(results.len(), 2); - assert_eq!(results[1].name("op").map(|i| i.as_str()), Some("+")); - assert_eq!(results[1].name("mod").map(|i| i.as_str()), Some("8")); + results = parse_dice_segments("3d8kl3 - 2d1dh1 / 2").expect("Failed to unpack results"); + + assert_eq!(results, vec![ + Segment::DiceRoll{ op: '+', count: 3, size: 8, filter: Some(DiceFilter::KeepLowest(3)) }, + Segment::DiceRoll{ op: '-', count: 2, size: 1, filter: Some(DiceFilter::DropHighest(1)) }, + Segment::Modifier{ op: '/', amount: 2 }, + ]); }