Compare commits

..

No commits in common. "c514b8551079a9d481b5a36cc8ce6bd62e800071" and "44b1e0f6495bc037dc63f72de8de55468a5b9910" have entirely different histories.

4 changed files with 117 additions and 140 deletions

View File

@ -45,13 +45,13 @@ pub fn parse_modifiers(input: &str) -> Result<DicePoolModifiers, DiceParsingErro
let (result, rest) = parser.parse(input)?; let (result, rest) = parser.parse(input)?;
if rest.len() == 0 { if rest.len() == 0 {
convert_to_modifiers(&result) convert_to_info(&result)
} else { } else {
Err(DiceParsingError::UnconsumedInput) Err(DiceParsingError::UnconsumedInput)
} }
} }
fn convert_to_modifiers(parsed: &Vec<ParsedInfo>) -> Result<DicePoolModifiers, DiceParsingError> { fn convert_to_info(parsed: &Vec<ParsedInfo>) -> Result<DicePoolModifiers, DiceParsingError> {
use ParsedInfo::*; use ParsedInfo::*;
if parsed.len() == 0 { if parsed.len() == 0 {
Ok(DicePoolModifiers::default()) Ok(DicePoolModifiers::default())
@ -79,8 +79,19 @@ fn convert_to_modifiers(parsed: &Vec<ParsedInfo>) -> Result<DicePoolModifiers, D
} }
pub fn parse_dice_pool(input: &str) -> Result<DicePool, BotError> { pub fn parse_dice_pool(input: &str) -> Result<DicePool, BotError> {
let (amounts, modifiers_str) = parse_amounts(input)?; //The "modifiers:" part is optional. Assume amounts if no modifier
//section found.
let split = input.split(":").collect::<Vec<_>>();
let (modifiers_str, amounts_str) = (match split[..] {
[amounts] => Ok(("", amounts)),
[modifiers, amounts] => Ok((modifiers, amounts)),
_ => Err(BotError::DiceParsingError(
DiceParsingError::UnconsumedInput,
)),
})?;
let modifiers = parse_modifiers(modifiers_str)?; let modifiers = parse_modifiers(modifiers_str)?;
let amounts = parse_amounts(&amounts_str)?;
Ok(DicePool::new(amounts, modifiers)) Ok(DicePool::new(amounts, modifiers))
} }
@ -164,7 +175,7 @@ mod tests {
#[test] #[test]
fn dice_pool_number_with_quality() { fn dice_pool_number_with_quality() {
let result = parse_dice_pool("8 n"); let result = parse_dice_pool("n:8");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
result.unwrap(), result.unwrap(),
@ -175,7 +186,7 @@ mod tests {
#[test] #[test]
fn dice_pool_number_with_success_change() { fn dice_pool_number_with_success_change() {
let modifiers = DicePoolModifiers::custom_exceptional_on(3); let modifiers = DicePoolModifiers::custom_exceptional_on(3);
let result = parse_dice_pool("8 s3"); let result = parse_dice_pool("s3:8");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), DicePool::easy_with_modifiers(8, modifiers)); assert_eq!(result.unwrap(), DicePool::easy_with_modifiers(8, modifiers));
} }
@ -183,7 +194,7 @@ mod tests {
#[test] #[test]
fn dice_pool_with_quality_and_success_change() { fn dice_pool_with_quality_and_success_change() {
let modifiers = DicePoolModifiers::custom(DicePoolQuality::Rote, 3); let modifiers = DicePoolModifiers::custom(DicePoolQuality::Rote, 3);
let result = parse_dice_pool("8 rs3"); let result = parse_dice_pool("rs3:8");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), DicePool::easy_with_modifiers(8, modifiers)); assert_eq!(result.unwrap(), DicePool::easy_with_modifiers(8, modifiers));
} }
@ -213,20 +224,20 @@ mod tests {
let expected = DicePool::new(amounts, modifiers); let expected = DicePool::new(amounts, modifiers);
let result = parse_dice_pool("8+10-2+varname rs3"); let result = parse_dice_pool("rs3:8+10-2+varname");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), expected); assert_eq!(result.unwrap(), expected);
let result = parse_dice_pool("8+10- 2 + varname rs3"); let result = parse_dice_pool("rs3:8+10- 2 + varname");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), expected); assert_eq!(result.unwrap(), expected);
let result = parse_dice_pool("8+ 10 -2 + varname rs3"); let result = parse_dice_pool("rs3 : 8+ 10 -2 + varname");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), expected); assert_eq!(result.unwrap(), expected);
//This one has tabs in it. //This one has tabs in it.
let result = parse_dice_pool(" 8 + 10 -2 + varname r s3"); let result = parse_dice_pool(" r s3 : 8 + 10 -2 + varname");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), expected); assert_eq!(result.unwrap(), expected);
} }

View File

@ -221,9 +221,9 @@ mod tests {
#[test] #[test]
fn pool_whitespace_test() { fn pool_whitespace_test() {
parse_command("!pool 8 ns3 ").expect("was error"); parse_command("!pool ns3:8 ").expect("was error");
parse_command(" !pool 8 ns3").expect("was error"); parse_command(" !pool ns3:8").expect("was error");
parse_command(" !pool 8 ns3 ").expect("was error"); parse_command(" !pool ns3:8 ").expect("was error");
} }
#[test] #[test]

View File

@ -4,13 +4,16 @@ use crate::parser::dice::DiceParsingError;
//TOOD convert these to use parse_amounts from the common dice code. //TOOD convert these to use parse_amounts from the common dice code.
fn parse_modifier(input: &str) -> Result<DiceRollModifier, DiceParsingError> { fn parse_modifier(input: &str) -> Result<DiceRollModifier, DiceParsingError> {
match input.trim() { if input.ends_with("bb") {
"bb" => Ok(DiceRollModifier::TwoBonus), Ok(DiceRollModifier::TwoBonus)
"b" => Ok(DiceRollModifier::OneBonus), } else if input.ends_with("b") {
"pp" => Ok(DiceRollModifier::TwoPenalty), Ok(DiceRollModifier::OneBonus)
"p" => Ok(DiceRollModifier::OnePenalty), } else if input.ends_with("pp") {
"" => Ok(DiceRollModifier::Normal), Ok(DiceRollModifier::TwoPenalty)
_ => Err(DiceParsingError::InvalidModifiers), } else if input.ends_with("p") {
Ok(DiceRollModifier::OnePenalty)
} else {
Ok(DiceRollModifier::Normal)
} }
} }
@ -18,70 +21,32 @@ fn parse_modifier(input: &str) -> Result<DiceRollModifier, DiceParsingError> {
//Split based on :, send first part to parse_modifier. //Split based on :, send first part to parse_modifier.
//Send second part to parse_amounts //Send second part to parse_amounts
pub fn parse_regular_roll(input: &str) -> Result<DiceRoll, DiceParsingError> { pub fn parse_regular_roll(input: &str) -> Result<DiceRoll, DiceParsingError> {
let (amount, modifiers_str) = crate::parser::dice::parse_single_amount(input)?; let input: Vec<&str> = input.trim().split(":").collect();
let (modifiers_str, amounts_str) = match input[..] {
[amounts] => Ok(("", amounts)),
[modifiers, amounts] => Ok((modifiers, amounts)),
_ => Err(DiceParsingError::UnconsumedInput),
}?;
let modifier = parse_modifier(modifiers_str)?; let modifier = parse_modifier(modifiers_str)?;
let amount = crate::parser::dice::parse_single_amount(amounts_str)?;
Ok(DiceRoll { modifier, amount }) Ok(DiceRoll { modifier, amount })
} }
pub fn parse_advancement_roll(input: &str) -> Result<AdvancementRoll, DiceParsingError> { pub fn parse_advancement_roll(input: &str) -> Result<AdvancementRoll, DiceParsingError> {
let input = input.trim(); let input = input.trim();
let (amounts, unconsumed_input) = crate::parser::dice::parse_single_amount(input)?; let amounts = crate::parser::dice::parse_single_amount(input)?;
if unconsumed_input.len() == 0 { Ok(AdvancementRoll {
Ok(AdvancementRoll { existing_skill: amounts,
existing_skill: amounts, })
})
} else {
Err(DiceParsingError::InvalidAmount)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::parser::dice::{Amount, DiceParsingError, Element, Operator}; use crate::parser::dice::{Amount, Element, Operator};
#[test]
fn parse_modifier_rejects_bad_value() {
let modifier = parse_modifier("qqq");
assert!(matches!(modifier, Err(DiceParsingError::InvalidModifiers)))
}
#[test]
fn parse_modifier_accepts_one_bonus() {
let modifier = parse_modifier("b");
assert!(matches!(modifier, Ok(DiceRollModifier::OneBonus)))
}
#[test]
fn parse_modifier_accepts_two_bonus() {
let modifier = parse_modifier("bb");
assert!(matches!(modifier, Ok(DiceRollModifier::TwoBonus)))
}
#[test]
fn parse_modifier_accepts_two_penalty() {
let modifier = parse_modifier("pp");
assert!(matches!(modifier, Ok(DiceRollModifier::TwoPenalty)))
}
#[test]
fn parse_modifier_accepts_one_penalty() {
let modifier = parse_modifier("p");
assert!(matches!(modifier, Ok(DiceRollModifier::OnePenalty)))
}
#[test]
fn parse_modifier_accepts_normal() {
let modifier = parse_modifier("");
assert!(matches!(modifier, Ok(DiceRollModifier::Normal)))
}
#[test]
fn parse_modifier_accepts_normal_unaffected_by_whitespace() {
let modifier = parse_modifier(" ");
assert!(matches!(modifier, Ok(DiceRollModifier::Normal)))
}
#[test] #[test]
fn regular_roll_accepts_single_number() { fn regular_roll_accepts_single_number() {
@ -107,7 +72,7 @@ mod tests {
#[test] #[test]
fn regular_roll_accepts_two_bonus() { fn regular_roll_accepts_two_bonus() {
let result = parse_regular_roll("60 bb"); let result = parse_regular_roll("bb:60");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
DiceRoll { DiceRoll {
@ -123,7 +88,7 @@ mod tests {
#[test] #[test]
fn regular_roll_accepts_one_bonus() { fn regular_roll_accepts_one_bonus() {
let result = parse_regular_roll("60 b"); let result = parse_regular_roll("b:60");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
DiceRoll { DiceRoll {
@ -139,7 +104,7 @@ mod tests {
#[test] #[test]
fn regular_roll_accepts_two_penalty() { fn regular_roll_accepts_two_penalty() {
let result = parse_regular_roll("60 pp"); let result = parse_regular_roll("pp:60");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
DiceRoll { DiceRoll {
@ -155,7 +120,7 @@ mod tests {
#[test] #[test]
fn regular_roll_accepts_one_penalty() { fn regular_roll_accepts_one_penalty() {
let result = parse_regular_roll("60 p"); let result = parse_regular_roll("p:60");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
DiceRoll { DiceRoll {
@ -175,21 +140,21 @@ mod tests {
assert!(parse_regular_roll(" 60").is_ok()); assert!(parse_regular_roll(" 60").is_ok());
assert!(parse_regular_roll(" 60 ").is_ok()); assert!(parse_regular_roll(" 60 ").is_ok());
assert!(parse_regular_roll("60bb ").is_ok()); assert!(parse_regular_roll("bb:60 ").is_ok());
assert!(parse_regular_roll(" 60 bb").is_ok()); assert!(parse_regular_roll(" bb:60").is_ok());
assert!(parse_regular_roll(" 60 bb ").is_ok()); assert!(parse_regular_roll(" bb:60 ").is_ok());
assert!(parse_regular_roll("60b ").is_ok()); assert!(parse_regular_roll("b:60 ").is_ok());
assert!(parse_regular_roll(" 60 b").is_ok()); assert!(parse_regular_roll(" b:60").is_ok());
assert!(parse_regular_roll(" 60 b ").is_ok()); assert!(parse_regular_roll(" b:60 ").is_ok());
assert!(parse_regular_roll("60pp ").is_ok()); assert!(parse_regular_roll("pp:60 ").is_ok());
assert!(parse_regular_roll(" 60 pp").is_ok()); assert!(parse_regular_roll(" pp:60").is_ok());
assert!(parse_regular_roll(" 60 pp ").is_ok()); assert!(parse_regular_roll(" pp:60 ").is_ok());
assert!(parse_regular_roll("60p ").is_ok()); assert!(parse_regular_roll("p:60 ").is_ok());
assert!(parse_regular_roll(" 60p ").is_ok()); assert!(parse_regular_roll(" p:60").is_ok());
assert!(parse_regular_roll(" 60 p ").is_ok()); assert!(parse_regular_roll(" p:60 ").is_ok());
} }
#[test] #[test]

View File

@ -151,9 +151,8 @@ where
/// should not have an operator, but every one after that should. /// should not have an operator, but every one after that should.
/// Accepts expressions like "8", "10 + variablename", "variablename - /// Accepts expressions like "8", "10 + variablename", "variablename -
/// 3", etc. This function is currently common to systems that don't /// 3", etc. This function is currently common to systems that don't
/// deal with XdY rolls. Support for that will be added later. Returns /// deal with XdY rolls. Support for that will be added later.
/// parsed amounts and unconsumed input (e.g. roll modifiers). pub fn parse_amounts(input: &str) -> ParseResult<Vec<Amount>> {
pub fn parse_amounts(input: &str) -> ParseResult<(Vec<Amount>, &str)> {
let input = input.trim(); let input = input.trim();
let remaining_amounts = many(amount_parser()).map(|amounts: Vec<ParseResult<Amount>>| amounts); let remaining_amounts = many(amount_parser()).map(|amounts: Vec<ParseResult<Amount>>| amounts);
@ -170,23 +169,31 @@ pub fn parse_amounts(input: &str) -> ParseResult<(Vec<Amount>, &str)> {
(amounts, results.1) (amounts, results.1)
})?; })?;
// Any ParseResult errors will short-circuit the collect. if rest.len() == 0 {
let results: Vec<Amount> = results.into_iter().collect::<ParseResult<_>>()?; // Any ParseResult errors will short-circuit the collect.
Ok((results, rest)) results.into_iter().collect()
} else {
Err(DiceParsingError::UnconsumedInput)
}
} }
/// Parse an expression that expects a single number or variable. No /// Parse an expression that expects a single number or variable. No
/// operators are allowed. This function is common to systems that /// operators are allowed. This function is common to systems that
/// don't deal with XdY rolls. Currently. this function does not /// don't deal with XdY rolls. Currently. this function does not
/// support parsing negative numbers. Returns the parsed amount and /// support parsing negative numbers.
/// any unconsumed input (useful for dice roll modifiers). pub fn parse_single_amount(input: &str) -> ParseResult<Amount> {
pub fn parse_single_amount(input: &str) -> ParseResult<(Amount, &str)> {
// TODO add support for negative numbers, as technically they // TODO add support for negative numbers, as technically they
// should be allowed. // should be allowed.
let input = input.trim(); let input = input.trim();
let mut parser = first_amount_parser().map(|amount: ParseResult<Amount>| amount); let mut parser = first_amount_parser().map(|amount: ParseResult<Amount>| amount);
let (result, rest) = parser.parse(input)?; let (result, rest) = parser.parse(input)?;
Ok((result?, rest))
if rest.len() == 0 {
result
} else {
Err(DiceParsingError::UnconsumedInput)
}
} }
#[cfg(test)] #[cfg(test)]
@ -199,13 +206,10 @@ mod parse_single_amount_tests {
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
result.unwrap(), result.unwrap(),
( Amount {
Amount { operator: Operator::Plus,
operator: Operator::Plus, element: Element::Variable("abc".to_string())
element: Element::Variable("abc".to_string()) }
},
""
)
) )
} }
@ -229,15 +233,24 @@ mod parse_single_amount_tests {
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
result.unwrap(), result.unwrap(),
( Amount {
Amount { operator: Operator::Plus,
operator: Operator::Plus, element: Element::Number(1)
element: Element::Number(1) }
},
""
)
) )
} }
#[test]
fn parse_multiple_elements_test() {
let result = parse_single_amount("1+abc");
assert!(result.is_err());
let result = parse_single_amount("abc+1");
assert!(result.is_err());
let result = parse_single_amount("-1-abc");
assert!(result.is_err());
}
} }
#[cfg(test)] #[cfg(test)]
@ -250,26 +263,20 @@ mod parse_many_amounts_tests {
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
result.unwrap(), result.unwrap(),
( vec![Amount {
vec![Amount { operator: Operator::Plus,
operator: Operator::Plus, element: Element::Number(1)
element: Element::Number(1) }]
}],
""
)
); );
let result = parse_amounts("10"); let result = parse_amounts("10");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
result.unwrap(), result.unwrap(),
( vec![Amount {
vec![Amount { operator: Operator::Plus,
operator: Operator::Plus, element: Element::Number(10)
element: Element::Number(10) }]
}],
""
)
); );
} }
@ -288,26 +295,20 @@ mod parse_many_amounts_tests {
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
result.unwrap(), result.unwrap(),
( vec![Amount {
vec![Amount { operator: Operator::Plus,
operator: Operator::Plus, element: Element::Variable("asdf".to_string())
element: Element::Variable("asdf".to_string()) }]
}],
""
)
); );
let result = parse_amounts("nosis"); let result = parse_amounts("nosis");
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!( assert_eq!(
result.unwrap(), result.unwrap(),
( vec![Amount {
vec![Amount { operator: Operator::Plus,
operator: Operator::Plus, element: Element::Variable("nosis".to_string())
element: Element::Variable("nosis".to_string()) }]
}],
""
)
); );
} }