Compare commits
3 Commits
8939d6debd
...
c4e0393d99
Author | SHA1 | Date |
---|---|---|
projectmoon | c4e0393d99 | |
projectmoon | d67328ac6b | |
projectmoon | ec66bfa3d6 |
|
@ -1,14 +1,14 @@
|
||||||
use crate::context::Context;
|
|
||||||
use crate::dice::calculate_dice_amount;
|
|
||||||
use crate::error::{BotError, DiceRollingError};
|
use crate::error::{BotError, DiceRollingError};
|
||||||
use crate::parser::Amount;
|
use crate::parser::{Amount, Element};
|
||||||
|
use crate::{context::Context, db::variables::UserAndRoom};
|
||||||
|
use crate::{dice::calculate_single_die_amount, parser::DiceParsingError};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// A planned dice roll.
|
/// A planned dice roll.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct DiceRoll {
|
pub struct DiceRoll {
|
||||||
pub amounts: Vec<Amount>,
|
pub amount: Amount,
|
||||||
pub modifier: DiceRollModifier,
|
pub modifier: DiceRollModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ impl fmt::Display for RolledDice {
|
||||||
pub struct AdvancementRoll {
|
pub struct AdvancementRoll {
|
||||||
/// The amount (0 to 100) of the existing skill. We must beat this
|
/// The amount (0 to 100) of the existing skill. We must beat this
|
||||||
/// target number to advance the skill, or roll above a 95.
|
/// target number to advance the skill, or roll above a 95.
|
||||||
pub existing_skill: Vec<Amount>,
|
pub existing_skill: Amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for AdvancementRoll {
|
impl fmt::Display for AdvancementRoll {
|
||||||
|
@ -251,10 +251,25 @@ impl fmt::Display for RolledAdvancement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is a trait so we can inject controlled dice rolls in unit
|
||||||
|
/// tests.
|
||||||
trait DieRoller {
|
trait DieRoller {
|
||||||
fn roll(&mut self) -> u32;
|
fn roll(&mut self) -> u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Macro to determine if an Amount is a variable.
|
||||||
|
macro_rules! is_variable {
|
||||||
|
($existing_skill:ident) => {
|
||||||
|
matches!(
|
||||||
|
$existing_skill,
|
||||||
|
Amount {
|
||||||
|
element: Element::Variable(_),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
///A version of DieRoller that uses a rand::Rng to roll numbers.
|
///A version of DieRoller that uses a rand::Rng to roll numbers.
|
||||||
struct RngDieRoller<R: rand::Rng>(R);
|
struct RngDieRoller<R: rand::Rng>(R);
|
||||||
|
|
||||||
|
@ -343,23 +358,39 @@ fn roll_advancement_dice<R: DieRoller>(target: u32, roller: &mut R) -> RolledAdv
|
||||||
pub async fn regular_roll(
|
pub async fn regular_roll(
|
||||||
roll_with_ctx: &DiceRollWithContext<'_>,
|
roll_with_ctx: &DiceRollWithContext<'_>,
|
||||||
) -> Result<ExecutedDiceRoll, BotError> {
|
) -> Result<ExecutedDiceRoll, BotError> {
|
||||||
let target = calculate_dice_amount(&roll_with_ctx.0.amounts, roll_with_ctx.1).await?;
|
let target = calculate_single_die_amount(&roll_with_ctx.0.amount, roll_with_ctx.1).await?;
|
||||||
let target = u32::try_from(target).map_err(|_| DiceRollingError::InvalidAmount)?;
|
let target = u32::try_from(target).map_err(|_| DiceRollingError::InvalidAmount)?;
|
||||||
|
|
||||||
let mut roller = RngDieRoller(rand::thread_rng());
|
let mut roller = RngDieRoller(rand::thread_rng());
|
||||||
let rolled_dice = roll_regular_dice(&roll_with_ctx.0.modifier, target, &mut roller);
|
let rolled_dice = roll_regular_dice(&roll_with_ctx.0.modifier, target, &mut roller);
|
||||||
|
|
||||||
Ok(ExecutedDiceRoll {
|
Ok(ExecutedDiceRoll {
|
||||||
target: target,
|
target,
|
||||||
modifier: roll_with_ctx.0.modifier,
|
modifier: roll_with_ctx.0.modifier,
|
||||||
roll: rolled_dice,
|
roll: rolled_dice,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_skill(ctx: &Context, variable: &str, value: u32) -> Result<(), BotError> {
|
||||||
|
use std::convert::TryInto;
|
||||||
|
let value: i32 = value.try_into()?;
|
||||||
|
let key = UserAndRoom(ctx.username, ctx.room_id().as_str());
|
||||||
|
ctx.db.variables.set_user_variable(&key, variable, value)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_variable(amount: &Amount) -> Result<&str, DiceParsingError> {
|
||||||
|
match amount.element {
|
||||||
|
Element::Variable(ref varname) => Ok(&varname[..]),
|
||||||
|
_ => Err(DiceParsingError::WrongElementType),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub async fn advancement_roll(
|
pub async fn advancement_roll(
|
||||||
roll_with_ctx: &AdvancementRollWithContext<'_>,
|
roll_with_ctx: &AdvancementRollWithContext<'_>,
|
||||||
) -> Result<ExecutedAdvancementRoll, BotError> {
|
) -> Result<ExecutedAdvancementRoll, BotError> {
|
||||||
let target = calculate_dice_amount(&roll_with_ctx.0.existing_skill, roll_with_ctx.1).await?;
|
let existing_skill = &roll_with_ctx.0.existing_skill;
|
||||||
|
let target = calculate_single_die_amount(existing_skill, roll_with_ctx.1).await?;
|
||||||
|
|
||||||
let target = u32::try_from(target).map_err(|_| DiceRollingError::InvalidAmount)?;
|
let target = u32::try_from(target).map_err(|_| DiceRollingError::InvalidAmount)?;
|
||||||
|
|
||||||
if target > 100 {
|
if target > 100 {
|
||||||
|
@ -368,6 +399,12 @@ pub async fn advancement_roll(
|
||||||
|
|
||||||
let mut roller = RngDieRoller(rand::thread_rng());
|
let mut roller = RngDieRoller(rand::thread_rng());
|
||||||
let roll = roll_advancement_dice(target, &mut roller);
|
let roll = roll_advancement_dice(target, &mut roller);
|
||||||
|
|
||||||
|
if roll.successful && is_variable!(existing_skill) {
|
||||||
|
let variable_name: &str = extract_variable(existing_skill)?;
|
||||||
|
update_skill(roll_with_ctx.1, variable_name, roll.new_skill_amount())?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ExecutedAdvancementRoll { target, roll })
|
Ok(ExecutedAdvancementRoll { target, roll })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,13 +450,37 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_variable_gets_variable_name() {
|
||||||
|
let amount = Amount {
|
||||||
|
operator: Operator::Plus,
|
||||||
|
element: Element::Variable("abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = extract_variable(&amount);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), "abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_variable_fails_on_number() {
|
||||||
|
let result = extract_variable(&Amount {
|
||||||
|
operator: Operator::Plus,
|
||||||
|
element: Element::Number(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result, Err(DiceParsingError::WrongElementType)));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn regular_roll_rejects_negative_numbers() {
|
async fn regular_roll_rejects_negative_numbers() {
|
||||||
let roll = DiceRoll {
|
let roll = DiceRoll {
|
||||||
amounts: vec![Amount {
|
amount: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(-10),
|
element: Element::Number(-10),
|
||||||
}],
|
},
|
||||||
modifier: DiceRollModifier::Normal,
|
modifier: DiceRollModifier::Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -445,10 +506,10 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn advancement_roll_rejects_negative_numbers() {
|
async fn advancement_roll_rejects_negative_numbers() {
|
||||||
let roll = AdvancementRoll {
|
let roll = AdvancementRoll {
|
||||||
existing_skill: vec![Amount {
|
existing_skill: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(-10),
|
element: Element::Number(-10),
|
||||||
}],
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = Database::new_temp().unwrap();
|
let db = Database::new_temp().unwrap();
|
||||||
|
@ -473,10 +534,10 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn advancement_roll_rejects_big_numbers() {
|
async fn advancement_roll_rejects_big_numbers() {
|
||||||
let roll = AdvancementRoll {
|
let roll = AdvancementRoll {
|
||||||
existing_skill: vec![Amount {
|
existing_skill: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(3000),
|
element: Element::Number(3000),
|
||||||
}],
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = Database::new_temp().unwrap();
|
let db = Database::new_temp().unwrap();
|
||||||
|
@ -498,6 +559,26 @@ mod tests {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_variable_macro_succeds_on_variable() {
|
||||||
|
let amount = Amount {
|
||||||
|
operator: Operator::Plus,
|
||||||
|
element: Element::Variable("abc".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(is_variable!(amount), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_variable_macro_fails_on_number() {
|
||||||
|
let amount = Amount {
|
||||||
|
operator: Operator::Plus,
|
||||||
|
element: Element::Number(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(is_variable!(amount), false);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regular_roll_succeeds_when_below_target() {
|
fn regular_roll_succeeds_when_below_target() {
|
||||||
//Roll 30, succeeding.
|
//Roll 30, succeeding.
|
||||||
|
|
|
@ -20,7 +20,6 @@ fn parse_modifier(input: &str) -> Result<DiceRollModifier, DiceParsingError> {
|
||||||
//Make diceroll take a vec of Amounts
|
//Make diceroll take a vec of Amounts
|
||||||
//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 input: Vec<&str> = input.trim().split(":").collect();
|
let input: Vec<&str> = input.trim().split(":").collect();
|
||||||
|
|
||||||
|
@ -31,14 +30,13 @@ pub fn parse_regular_roll(input: &str) -> Result<DiceRoll, DiceParsingError> {
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let modifier = parse_modifier(modifiers_str)?;
|
let modifier = parse_modifier(modifiers_str)?;
|
||||||
let amounts = crate::parser::parse_amounts(amounts_str)?;
|
let amount = crate::parser::parse_single_amount(amounts_str)?;
|
||||||
|
Ok(DiceRoll { modifier, amount })
|
||||||
Ok(DiceRoll { modifier, amounts })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = crate::parser::parse_amounts(input)?;
|
let amounts = crate::parser::parse_single_amount(input)?;
|
||||||
|
|
||||||
Ok(AdvancementRoll {
|
Ok(AdvancementRoll {
|
||||||
existing_skill: amounts,
|
existing_skill: amounts,
|
||||||
|
@ -57,26 +55,32 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DiceRoll {
|
DiceRoll {
|
||||||
amounts: vec![Amount {
|
amount: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(60)
|
element: Element::Number(60)
|
||||||
}],
|
},
|
||||||
modifier: DiceRollModifier::Normal
|
modifier: DiceRollModifier::Normal
|
||||||
},
|
},
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regular_roll_rejects_complex_expressions() {
|
||||||
|
let result = parse_regular_roll("3 + abc + bob - 4");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regular_roll_accepts_two_bonus() {
|
fn regular_roll_accepts_two_bonus() {
|
||||||
let result = parse_regular_roll("bb:60");
|
let result = parse_regular_roll("bb:60");
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DiceRoll {
|
DiceRoll {
|
||||||
amounts: vec![Amount {
|
amount: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(60)
|
element: Element::Number(60)
|
||||||
}],
|
},
|
||||||
modifier: DiceRollModifier::TwoBonus
|
modifier: DiceRollModifier::TwoBonus
|
||||||
},
|
},
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
|
@ -89,10 +93,10 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DiceRoll {
|
DiceRoll {
|
||||||
amounts: vec![Amount {
|
amount: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(60)
|
element: Element::Number(60)
|
||||||
}],
|
},
|
||||||
modifier: DiceRollModifier::OneBonus
|
modifier: DiceRollModifier::OneBonus
|
||||||
},
|
},
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
|
@ -105,10 +109,10 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DiceRoll {
|
DiceRoll {
|
||||||
amounts: vec![Amount {
|
amount: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(60)
|
element: Element::Number(60)
|
||||||
}],
|
},
|
||||||
modifier: DiceRollModifier::TwoPenalty
|
modifier: DiceRollModifier::TwoPenalty
|
||||||
},
|
},
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
|
@ -121,10 +125,10 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DiceRoll {
|
DiceRoll {
|
||||||
amounts: vec![Amount {
|
amount: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(60)
|
element: Element::Number(60)
|
||||||
}],
|
},
|
||||||
modifier: DiceRollModifier::OnePenalty
|
modifier: DiceRollModifier::OnePenalty
|
||||||
},
|
},
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
|
@ -132,7 +136,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regular_roll_accepts_whitespacen() {
|
fn regular_roll_accepts_whitespace() {
|
||||||
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(" 60 ").is_ok());
|
assert!(parse_regular_roll(" 60 ").is_ok());
|
||||||
|
@ -167,10 +171,10 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AdvancementRoll {
|
AdvancementRoll {
|
||||||
existing_skill: vec![Amount {
|
existing_skill: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Number(60)
|
element: Element::Number(60)
|
||||||
}]
|
}
|
||||||
},
|
},
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
);
|
);
|
||||||
|
@ -187,41 +191,18 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AdvancementRoll {
|
AdvancementRoll {
|
||||||
existing_skill: vec![Amount {
|
existing_skill: Amount {
|
||||||
operator: Operator::Plus,
|
operator: Operator::Plus,
|
||||||
element: Element::Variable(String::from("abc"))
|
element: Element::Variable(String::from("abc"))
|
||||||
}]
|
}
|
||||||
},
|
},
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn advancement_roll_allows_complex_expressions() {
|
fn advancement_roll_rejects_complex_expressions() {
|
||||||
let result = parse_advancement_roll("3 + abc + bob - 4");
|
let result = parse_advancement_roll("3 + abc + bob - 4");
|
||||||
assert!(result.is_ok());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
|
||||||
AdvancementRoll {
|
|
||||||
existing_skill: vec![
|
|
||||||
Amount {
|
|
||||||
operator: Operator::Plus,
|
|
||||||
element: Element::Number(3)
|
|
||||||
},
|
|
||||||
Amount {
|
|
||||||
operator: Operator::Plus,
|
|
||||||
element: Element::Variable(String::from("abc"))
|
|
||||||
},
|
|
||||||
Amount {
|
|
||||||
operator: Operator::Plus,
|
|
||||||
element: Element::Variable(String::from("bob"))
|
|
||||||
},
|
|
||||||
Amount {
|
|
||||||
operator: Operator::Minus,
|
|
||||||
element: Element::Number(4)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
result.unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/dice.rs
11
src/dice.rs
|
@ -5,6 +5,17 @@ use crate::error::DiceRollingError;
|
||||||
use crate::parser::Amount;
|
use crate::parser::Amount;
|
||||||
use crate::parser::Element as NewElement;
|
use crate::parser::Element as NewElement;
|
||||||
use futures::stream::{self, StreamExt, TryStreamExt};
|
use futures::stream::{self, StreamExt, TryStreamExt};
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
/// Calculate the amount of dice to roll by consulting the database
|
||||||
|
/// and replacing variables with corresponding the amount. Errors out
|
||||||
|
/// if it cannot find a variable defined, or if the database errors.
|
||||||
|
pub async fn calculate_single_die_amount(
|
||||||
|
amount: &Amount,
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
) -> Result<i32, BotError> {
|
||||||
|
calculate_dice_amount(slice::from_ref(amount), ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate the amount of dice to roll by consulting the database
|
/// Calculate the amount of dice to roll by consulting the database
|
||||||
/// and replacing variables with corresponding amounts. Errors out if
|
/// and replacing variables with corresponding amounts. Errors out if
|
||||||
|
|
|
@ -75,6 +75,9 @@ pub enum BotError {
|
||||||
|
|
||||||
#[error("too many commands or message was too large")]
|
#[error("too many commands or message was too large")]
|
||||||
MessageTooLarge,
|
MessageTooLarge,
|
||||||
|
|
||||||
|
#[error("could not convert to proper integer type")]
|
||||||
|
TryFromIntError(#[from] std::num::TryFromIntError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
|
@ -21,6 +21,9 @@ pub enum DiceParsingError {
|
||||||
|
|
||||||
#[error("number parsing error (too large?)")]
|
#[error("number parsing error (too large?)")]
|
||||||
ConversionError,
|
ConversionError,
|
||||||
|
|
||||||
|
#[error("unexpected element in expression")]
|
||||||
|
WrongElementType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::num::ParseIntError> for DiceParsingError {
|
impl From<std::num::ParseIntError> for DiceParsingError {
|
||||||
|
@ -33,7 +36,7 @@ type ParseResult<T> = Result<T, DiceParsingError>;
|
||||||
|
|
||||||
/// A parsed operator for a number. Whether to add or remove it from
|
/// A parsed operator for a number. Whether to add or remove it from
|
||||||
/// the total amount of dice rolled.
|
/// the total amount of dice rolled.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum Operator {
|
pub enum Operator {
|
||||||
Plus,
|
Plus,
|
||||||
Minus,
|
Minus,
|
||||||
|
@ -148,10 +151,7 @@ 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. Parsers
|
/// deal with XdY rolls. Support for that will be added later.
|
||||||
/// utilzing this function should layer their own checks on top of
|
|
||||||
/// this; perhaps they do not want more than one expression, or some
|
|
||||||
/// other rules.
|
|
||||||
pub fn parse_amounts(input: &str) -> ParseResult<Vec<Amount>> {
|
pub fn parse_amounts(input: &str) -> ParseResult<Vec<Amount>> {
|
||||||
let input = input.trim();
|
let input = input.trim();
|
||||||
|
|
||||||
|
@ -177,9 +177,84 @@ pub fn parse_amounts(input: &str) -> ParseResult<Vec<Amount>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
/// Parse an expression that expects a single number or variable. No
|
||||||
mod tests {
|
/// operators are allowed. This function is common to systems that
|
||||||
|
/// don't deal with XdY rolls. Currently. this function does not
|
||||||
|
/// support parsing negative numbers.
|
||||||
|
pub fn parse_single_amount(input: &str) -> ParseResult<Amount> {
|
||||||
|
// TODO add support for negative numbers, as technically they
|
||||||
|
// should be allowed.
|
||||||
|
let input = input.trim();
|
||||||
|
let mut parser = first_amount_parser().map(|amount: ParseResult<Amount>| amount);
|
||||||
|
|
||||||
|
let (result, rest) = parser.parse(input)?;
|
||||||
|
|
||||||
|
if rest.len() == 0 {
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
Err(DiceParsingError::UnconsumedInput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod parse_single_amount_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_single_variable_test() {
|
||||||
|
let result = parse_single_amount("abc");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
Amount {
|
||||||
|
operator: Operator::Plus,
|
||||||
|
element: Element::Variable("abc".to_string())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add support for negative numbers in parse_single_amount
|
||||||
|
// #[test]
|
||||||
|
// fn parse_single_negative_number_test() {
|
||||||
|
// let result = parse_single_amount("-1");
|
||||||
|
// assert!(result.is_ok());
|
||||||
|
// assert_eq!(
|
||||||
|
// result.unwrap(),
|
||||||
|
// Amount {
|
||||||
|
// operator: Operator::Minus,
|
||||||
|
// element: Element::Number(1)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_single_number_test() {
|
||||||
|
let result = parse_single_amount("1");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
Amount {
|
||||||
|
operator: Operator::Plus,
|
||||||
|
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)]
|
||||||
|
mod parse_many_amounts_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue