2020-08-21 21:49:22 +00:00
|
|
|
use nom::{
|
|
|
|
alt, bytes::complete::tag, character::complete::digit1, complete, many0, named,
|
|
|
|
sequence::tuple, tag, IResult,
|
|
|
|
};
|
|
|
|
|
2020-08-27 23:56:19 +00:00
|
|
|
use crate::cofd::dice::{DicePool, DicePoolQuality};
|
2020-08-21 21:49:22 +00:00
|
|
|
use crate::parser::eat_whitespace;
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
|
|
pub enum DicePoolElement {
|
|
|
|
NumberOfDice(u32),
|
|
|
|
SuccessesForExceptional(u32),
|
|
|
|
DicePoolQuality(DicePoolQuality),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse a single digit expression. Does not eat whitespace
|
|
|
|
fn parse_digit(input: &str) -> IResult<&str, u32> {
|
|
|
|
let (input, num) = digit1(input)?;
|
|
|
|
Ok((input, num.parse().unwrap()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_quality(input: &str) -> IResult<&str, DicePoolQuality> {
|
|
|
|
let (input, _) = eat_whitespace(input)?;
|
|
|
|
named!(quality(&str) -> DicePoolQuality, alt!(
|
|
|
|
complete!(tag!("n")) => { |_| DicePoolQuality::NineAgain } |
|
|
|
|
complete!(tag!("e")) => { |_| DicePoolQuality::EightAgain } |
|
2020-08-28 21:47:09 +00:00
|
|
|
complete!(tag!("r")) => { |_| DicePoolQuality::Rote } |
|
|
|
|
complete!(tag!("x")) => { |_| DicePoolQuality::NoExplode }
|
2020-08-21 21:49:22 +00:00
|
|
|
));
|
|
|
|
|
|
|
|
let (input, dice_pool_quality) = quality(input)?;
|
|
|
|
Ok((input, dice_pool_quality))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_exceptional_requirement(input: &str) -> IResult<&str, u32> {
|
|
|
|
let (input, _) = eat_whitespace(input)?;
|
|
|
|
let (input, (_, successes)) = tuple((tag("s"), digit1))(input)?;
|
|
|
|
Ok((input, successes.parse().unwrap()))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse a dice pool element expression. Eats whitespace.
|
|
|
|
fn parse_dice_pool_element(input: &str) -> IResult<&str, DicePoolElement> {
|
|
|
|
let (input, _) = eat_whitespace(input)?;
|
|
|
|
named!(element(&str) -> DicePoolElement, alt!(
|
|
|
|
parse_digit => { |num| DicePoolElement::NumberOfDice(num) } |
|
|
|
|
parse_quality => { |qual| DicePoolElement::DicePoolQuality(qual) } |
|
|
|
|
parse_exceptional_requirement => { |succ| DicePoolElement::SuccessesForExceptional(succ) }
|
|
|
|
));
|
|
|
|
|
|
|
|
let (input, element) = element(input)?;
|
|
|
|
Ok((input, element))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_elements(elements: Vec<DicePoolElement>) -> (Option<u32>, DicePoolQuality, u32) {
|
|
|
|
let mut found_quality: Option<DicePoolQuality> = None;
|
|
|
|
let mut found_count: Option<u32> = None;
|
|
|
|
let mut found_successes_required: Option<u32> = None;
|
|
|
|
|
|
|
|
for element in elements {
|
|
|
|
if found_quality.is_some() && found_count.is_some() && found_successes_required.is_some() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
match element {
|
|
|
|
DicePoolElement::NumberOfDice(found) => {
|
|
|
|
if found_count.is_none() {
|
|
|
|
found_count = Some(found);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DicePoolElement::DicePoolQuality(found) => {
|
|
|
|
if found_quality.is_none() {
|
|
|
|
found_quality = Some(found);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DicePoolElement::SuccessesForExceptional(found) => {
|
|
|
|
if found_successes_required.is_none() {
|
|
|
|
found_successes_required = Some(found);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let quality: DicePoolQuality = found_quality.unwrap_or(DicePoolQuality::TenAgain);
|
|
|
|
let successes_for_exceptional: u32 = found_successes_required.unwrap_or(5);
|
|
|
|
(found_count, quality, successes_for_exceptional)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn convert_to_dice_pool(input: &str, elements: Vec<DicePoolElement>) -> IResult<&str, DicePool> {
|
|
|
|
let (count, quality, successes_for_exceptional) = find_elements(elements);
|
|
|
|
|
|
|
|
if count.is_some() {
|
|
|
|
Ok((
|
|
|
|
input,
|
|
|
|
DicePool::new(count.unwrap(), successes_for_exceptional, quality),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
use nom::error::ErrorKind;
|
|
|
|
use nom::Err;
|
|
|
|
Err(Err::Error((input, ErrorKind::Alt)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_dice_pool(input: &str) -> IResult<&str, DicePool> {
|
|
|
|
named!(first_element(&str) -> DicePoolElement, alt!(
|
|
|
|
parse_dice_pool_element => { |e| e }
|
|
|
|
));
|
|
|
|
let (input, first) = first_element(input)?;
|
|
|
|
let (input, elements) = if input.trim().is_empty() {
|
|
|
|
(input, vec![first])
|
|
|
|
} else {
|
|
|
|
named!(rest_elements(&str) -> Vec<DicePoolElement>, many0!(parse_dice_pool_element));
|
|
|
|
let (input, mut rest) = rest_elements(input)?;
|
|
|
|
rest.insert(0, first);
|
|
|
|
(input, rest)
|
|
|
|
};
|
|
|
|
|
|
|
|
convert_to_dice_pool(input, elements)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn create_chance_die() -> IResult<&'static str, DicePool> {
|
|
|
|
Ok(("", DicePool::chance_die()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_digit_test() {
|
|
|
|
use nom::error::ErrorKind;
|
|
|
|
use nom::Err;
|
|
|
|
assert_eq!(parse_digit("1"), Ok(("", 1)));
|
|
|
|
assert_eq!(parse_digit("10"), Ok(("", 10)));
|
|
|
|
assert_eq!(
|
|
|
|
parse_digit("adsf"),
|
|
|
|
Err(Err::Error(("adsf", ErrorKind::Digit)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn quality_test() {
|
|
|
|
use nom::error::ErrorKind;
|
|
|
|
use nom::Err;
|
|
|
|
assert_eq!(parse_quality("n"), Ok(("", DicePoolQuality::NineAgain)));
|
|
|
|
assert_eq!(parse_quality("e"), Ok(("", DicePoolQuality::EightAgain)));
|
|
|
|
assert_eq!(parse_quality("r"), Ok(("", DicePoolQuality::Rote)));
|
2020-08-28 21:47:09 +00:00
|
|
|
assert_eq!(parse_quality("x"), Ok(("", DicePoolQuality::NoExplode)));
|
2020-08-21 21:49:22 +00:00
|
|
|
assert_eq!(parse_quality("b"), Err(Err::Error(("b", ErrorKind::Alt))));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn multiple_quality_test() {
|
|
|
|
assert_eq!(parse_quality("ner"), Ok(("er", DicePoolQuality::NineAgain)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn exceptional_success_test() {
|
|
|
|
use nom::error::ErrorKind;
|
|
|
|
use nom::Err;
|
|
|
|
assert_eq!(parse_exceptional_requirement("s3"), Ok(("", 3)));
|
|
|
|
assert_eq!(parse_exceptional_requirement("s10"), Ok(("", 10)));
|
|
|
|
assert_eq!(parse_exceptional_requirement("s20b"), Ok(("b", 20)));
|
|
|
|
assert_eq!(
|
|
|
|
parse_exceptional_requirement("sab10"),
|
|
|
|
Err(Err::Error(("ab10", ErrorKind::Digit)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn dice_pool_element_expression_test() {
|
|
|
|
use nom::error::ErrorKind;
|
|
|
|
use nom::Err;
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool_element("8"),
|
|
|
|
Ok(("", DicePoolElement::NumberOfDice(8)))
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool_element("n"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
DicePoolElement::DicePoolQuality(DicePoolQuality::NineAgain)
|
|
|
|
))
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool_element("s3"),
|
|
|
|
Ok(("", DicePoolElement::SuccessesForExceptional(3)))
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool_element("8ns3"),
|
|
|
|
Ok(("ns3", DicePoolElement::NumberOfDice(8)))
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool_element("totallynotvalid"),
|
|
|
|
Err(Err::Error(("totallynotvalid", ErrorKind::Alt)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn dice_pool_number_only_test() {
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool("8"),
|
|
|
|
Ok(("", DicePool::new(8, 5, DicePoolQuality::TenAgain)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn dice_pool_number_with_quality() {
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool("8n"),
|
|
|
|
Ok(("", DicePool::new(8, 5, DicePoolQuality::NineAgain)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn dice_pool_number_with_success_change() {
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool("8s3"),
|
|
|
|
Ok(("", DicePool::new(8, 3, DicePoolQuality::TenAgain)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn dice_pool_with_quality_and_success_change() {
|
|
|
|
assert_eq!(
|
|
|
|
parse_dice_pool("8rs3"),
|
|
|
|
Ok(("", DicePool::new(8, 3, DicePoolQuality::Rote)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|