use std::fmt;
use std::fmt::{Display, Formatter};
use std::num::ParseIntError;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub enum Expr {
Number(u32),
Add(Box<Expr>, Box<Expr>),
Subtract(Box<Expr>, Box<Expr>),
Multiply(Box<Expr>, Box<Expr>),
Negate(Box<Expr>),
DiceTotal(Box<DiceExpr>),
}
impl Expr {
#[mutants::skip] fn fmt_recursive(&self, f: &mut Formatter<'_>, indent: usize) -> fmt::Result {
let indent_str = " ".repeat(indent);
let indent_size = 2;
let new_indent = indent + indent_size;
match self {
Expr::Number(n) => write!(f, "{}Number({})", indent_str, n),
Expr::Add(left, right) => {
writeln!(f, "{}Add", indent_str)?;
left.fmt_recursive(f, new_indent)?;
right.fmt_recursive(f, new_indent)
}
Expr::Subtract(left, right) => {
writeln!(f, "{}Subtract", indent_str)?;
left.fmt_recursive(f, new_indent)?;
right.fmt_recursive(f, new_indent)
}
Expr::Multiply(left, right) => {
writeln!(f, "{}Multiply", indent_str)?;
left.fmt_recursive(f, new_indent)?;
right.fmt_recursive(f, new_indent)
}
Expr::Negate(expr) => {
writeln!(f, "{}Negate", indent_str)?;
expr.fmt_recursive(f, new_indent)
}
Expr::DiceTotal(expr) => {
writeln!(f, "{}DiceTotal", indent_str)?;
expr.fmt_recursive(f, new_indent)
}
}
}
}
#[mutants::skip] impl Display for Expr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Expr::Number(nb) => {
write!(f, "{}", nb)
}
Expr::Add(expr, expr2) => {
write!(f, "{}+{}", expr, expr2)
}
Expr::Subtract(expr, expr2) => {
write!(f, "{}-{}", expr, expr2)
}
Expr::Multiply(expr, expr2) => {
write!(f, "{}*{}", expr, expr2)
}
Expr::Negate(expr) => {
write!(f, "-{}", expr)
}
Expr::DiceTotal(expr) => {
write!(f, "{}", expr)
}
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub enum DiceExpr {
Dice(u32, u32),
ModKeepHighest(Box<DiceExpr>, u32),
ModKeepLowest(Box<DiceExpr>, u32),
ModDropHighest(Box<DiceExpr>, u32),
ModDropLowest(Box<DiceExpr>, u32),
ModReroll(Box<DiceExpr>, u32),
ModRerollOnce(Box<DiceExpr>, u32),
ModMininum(Box<DiceExpr>, u32),
ModMaximum(Box<DiceExpr>, u32),
ModCountGreaterOrEqual(Box<DiceExpr>, u32),
ModCountLowerOrEqual(Box<DiceExpr>, u32),
}
impl Display for DiceExpr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
DiceExpr::Dice(nb, faces) => {
write!(f, "{}d{}", nb, faces)
}
DiceExpr::ModKeepHighest(expr, highest_to_keep) => {
write!(f, "{}k{}", expr, highest_to_keep)
}
DiceExpr::ModKeepLowest(expr, lowest_to_keep) => {
write!(f, "{}kl{}", expr, lowest_to_keep)
}
DiceExpr::ModDropHighest(expr, highest_to_drop) => {
write!(f, "{}dh{}", expr, highest_to_drop)
}
DiceExpr::ModDropLowest(expr, lowest_to_drop) => {
write!(f, "{}d{}", expr, lowest_to_drop)
}
DiceExpr::ModReroll(expr, to_reroll) => {
write!(f, "{}r{}", expr, to_reroll)
}
DiceExpr::ModRerollOnce(expr, to_reroll) => {
write!(f, "{}ro{}", expr, to_reroll)
}
DiceExpr::ModMininum(expr, minimum) => {
write!(f, "{}min{}", expr, minimum)
}
DiceExpr::ModMaximum(expr, max) => {
write!(f, "{}max{}", expr, max)
}
DiceExpr::ModCountGreaterOrEqual(expr, to_compare) => {
write!(f, "{}>{}", expr, to_compare)
}
DiceExpr::ModCountLowerOrEqual(expr, to_compare) => {
write!(f, "{}<{}", expr, to_compare)
}
}
}
}
impl DiceExpr {
#[mutants::skip] fn fmt_recursive(&self, f: &mut Formatter<'_>, indent: usize) -> fmt::Result {
let indent_str = " ".repeat(indent);
let indent_size = 2;
let new_indent = indent + indent_size;
match self {
DiceExpr::Dice(count, sides) => write!(f, "{}Dice({}d{})", indent_str, count, sides),
DiceExpr::ModKeepHighest(expr, to_keep) => {
writeln!(f, "{}ModKeepHighest({})", indent_str, to_keep)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModKeepLowest(expr, to_keep) => {
writeln!(f, "{}ModKeepLowest({})", indent_str, to_keep)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModReroll(expr, reroll_below) => {
writeln!(f, "{}ModReroll({})", indent_str, reroll_below)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModRerollOnce(expr, reroll_below) => {
writeln!(f, "{}ModRerollOnce({})", indent_str, reroll_below)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModMininum(expr, minimum) => {
writeln!(f, "{}ModMinimum({})", indent_str, minimum)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModMaximum(expr, maximum) => {
writeln!(f, "{}ModMaximum({})", indent_str, maximum)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModCountGreaterOrEqual(expr, pivot) => {
writeln!(f, "{}ModGreaterOrEqual({})", indent_str, pivot)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModCountLowerOrEqual(expr, pivot) => {
writeln!(f, "{}ModLowerOrEqual({})", indent_str, pivot)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModDropHighest(expr, to_drop) => {
writeln!(f, "{}ModDropHighest({})", indent_str, to_drop)?;
expr.fmt_recursive(f, new_indent)
}
DiceExpr::ModDropLowest(expr, to_drop) => {
writeln!(f, "{}ModDropLowest({})", indent_str, to_drop)?;
expr.fmt_recursive(f, new_indent)
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Number(u32),
DiceOp,
Plus,
Minus,
Multiply,
LeftParen,
RightParen,
DiceModKeepHighest,
DiceModKeepLowest,
DiceModDropHighest,
DiceModReroll,
DiceModRerollOnce,
Highest,
Lowest,
DiceModMinimum,
DiceModMaximum,
GreaterThan,
LowerThan,
EndOfFile,
}
#[mutants::skip] impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Token::Number(n) => {
write!(f, "Number({})", n)
}
Token::DiceOp => {
write!(f, "DiceOp")
}
Token::Plus => {
write!(f, "Plus")
}
Token::Minus => {
write!(f, "Minus")
}
Token::Multiply => {
write!(f, "Multiply")
}
Token::LeftParen => {
write!(f, "LeftParen")
}
Token::RightParen => {
write!(f, "RightParen")
}
Token::EndOfFile => {
write!(f, "EOF")
}
Token::DiceModKeepHighest => {
write!(f, "ModKeepHighest")
}
Token::DiceModKeepLowest => {
write!(f, "ModKeepLowest")
}
Token::DiceModDropHighest => {
write!(f, "ModDropHighest")
}
Token::Highest => {
write!(f, "Highest")
}
Token::Lowest => {
write!(f, "Lowest")
}
Token::DiceModReroll => {
write!(f, "ModReroll")
}
Token::DiceModRerollOnce => {
write!(f, "ModRerollOnce")
}
Token::DiceModMinimum => {
write!(f, "ModMinimum")
}
Token::DiceModMaximum => {
write!(f, "ModMaximum")
}
Token::GreaterThan => {
write!(f, "GreaterThan")
}
Token::LowerThan => {
write!(f, "LowerThan")
}
}
}
}
#[derive(Debug, Error)]
pub enum LexerError {
#[error("Unexpected character: {0}")]
UnexpectedCharacter(char),
#[error("Could not parse float number: {0}")]
ParseIntError(#[from] ParseIntError),
}
struct Lexer {
input: Vec<char>,
position: usize,
}
impl Lexer {
fn new(input: &str) -> Self {
Lexer {
input: input.chars().collect(),
position: 0,
}
}
fn peek(&self) -> Option<char> {
if self.position < self.input.len() {
Some(self.input[self.position])
} else {
None
}
}
fn advance(&mut self) {
self.position += 1;
}
fn next_token(&mut self) -> Result<Token, LexerError> {
while let Some(c) = self.peek() {
if !c.is_whitespace() {
break;
}
self.advance();
}
match self.peek() {
None => Ok(Token::EndOfFile),
Some(c) => match c {
'0'..='9' => {
let mut number = String::new();
while let Some(c) = self.peek() {
if c.is_ascii_digit() || c == '.' {
number.push(c);
self.advance();
} else {
break;
}
}
Ok(Token::Number(number.parse()?))
}
'd' | 'D' => {
self.advance();
if let Some(c) = self.peek() {
if c == 'h' {
self.advance();
Ok(Token::DiceModDropHighest)
} else {
Ok(Token::DiceOp)
}
} else {
Ok(Token::DiceOp)
}
}
'+' => {
self.advance();
Ok(Token::Plus)
}
'-' => {
self.advance();
Ok(Token::Minus)
}
'*' => {
self.advance();
Ok(Token::Multiply)
}
'(' => {
self.advance();
Ok(Token::LeftParen)
}
')' => {
self.advance();
Ok(Token::RightParen)
}
'k' => {
self.advance();
if let Some(c) = self.peek() {
if c == 'l' {
self.advance();
Ok(Token::DiceModKeepLowest)
} else {
Ok(Token::DiceModKeepHighest)
}
} else {
Ok(Token::DiceModKeepHighest)
}
}
'r' => {
self.advance();
if let Some(c) = self.peek() {
if c == 'o' {
self.advance();
Ok(Token::DiceModRerollOnce)
} else {
Ok(Token::DiceModReroll)
}
} else {
Ok(Token::DiceModReroll)
}
}
'm' => {
self.advance();
if let Some(c) = self.peek() {
if c == 'i' {
self.advance();
if let Some(c) = self.peek() {
if c == 'n' {
self.advance();
}
Ok(Token::DiceModMinimum)
} else {
Err(LexerError::UnexpectedCharacter(c))
}
} else if c == 'a' {
self.advance();
if let Some(c) = self.peek() {
if c == 'x' {
self.advance();
}
Ok(Token::DiceModMaximum)
} else {
Err(LexerError::UnexpectedCharacter(c))
}
} else {
Err(LexerError::UnexpectedCharacter(c))
}
} else {
Err(LexerError::UnexpectedCharacter(c))
}
}
'>' => {
self.advance();
Ok(Token::GreaterThan)
}
'<' => {
self.advance();
Ok(Token::LowerThan)
}
'H' => {
self.advance();
Ok(Token::Highest)
}
'L' => {
self.advance();
Ok(Token::Lowest)
}
_ => Err(LexerError::UnexpectedCharacter(c)),
},
}
}
}
#[derive(Debug, Error)]
pub enum ParserError {
#[error(transparent)]
LexerError(#[from] LexerError),
#[error("Unexpected token: '{current}', expected: '{expected}'")]
UnexpectedToken { current: Token, expected: String },
#[error("Unexpected factor token: '{0}'")]
UnexpectedFactorToken(Token),
#[error("Unexpected dice token: '{0}'")]
UnexpectedDiceToken(Token),
}
pub struct Parser {
lexer: Lexer,
current_token: Token,
}
impl Parser {
pub fn parse(input: &str) -> Result<Expr, ParserError> {
let mut lexer = Lexer::new(input);
let current_token = lexer.next_token()?;
let mut parser = Parser {
lexer,
current_token,
};
parser.expression()
}
fn eat(&mut self, token_type: Token) -> Result<(), ParserError> {
if self.current_token == token_type {
self.current_token = self.lexer.next_token()?;
} else {
return Err(ParserError::UnexpectedToken {
current: self.current_token.clone(),
expected: token_type.to_string(),
});
}
Ok(())
}
fn expression(&mut self) -> Result<Expr, ParserError> {
let mut node = self.term()?;
loop {
match self.current_token {
Token::Plus => {
self.eat(Token::Plus)?;
node = Expr::Add(Box::new(node), Box::new(self.term()?));
}
Token::Minus => {
self.eat(Token::Minus)?;
node = Expr::Subtract(Box::new(node), Box::new(self.term()?));
}
_ => break,
}
}
Ok(node)
}
fn term(&mut self) -> Result<Expr, ParserError> {
let mut node = self.factor()?;
while let Token::Multiply = self.current_token {
self.eat(Token::Multiply)?;
node = Expr::Multiply(Box::new(node), Box::new(self.factor()?));
}
Ok(node)
}
fn factor(&mut self) -> Result<Expr, ParserError> {
match &self.current_token {
Token::Number(value) => {
let val = *value;
self.eat(Token::Number(val))?;
if self.current_token == Token::DiceOp {
self.dice_op(val)
} else {
Ok(Expr::Number(val))
}
}
Token::DiceOp => self.dice_op(1),
Token::LeftParen => {
self.eat(Token::LeftParen)?;
let node = self.expression()?;
self.eat(Token::RightParen)?;
Ok(node)
}
Token::Plus => {
self.eat(Token::Plus)?;
self.factor() }
Token::Minus => {
self.eat(Token::Minus)?;
let expr = self.factor()?;
Ok(Expr::Negate(Box::new(expr)))
}
token => Err(ParserError::UnexpectedFactorToken(token.clone())),
}
}
fn dice_op(&mut self, number_of_dices: u32) -> Result<Expr, ParserError> {
if self.current_token == Token::DiceOp {
self.eat(Token::DiceOp)?;
if let Token::Number(sides) = self.current_token {
let sides_val = sides;
self.eat(Token::Number(sides))?;
let dice = DiceExpr::Dice(number_of_dices, sides_val);
let dice = self.modifiers_op(dice)?;
Ok(Expr::DiceTotal(Box::new(dice)))
} else {
Err(ParserError::UnexpectedToken {
current: self.current_token.clone(),
expected: "Number".to_string(),
})
}
} else {
Err(ParserError::UnexpectedDiceToken(self.current_token.clone()))
}
}
fn modifiers_op(&mut self, dice_op: DiceExpr) -> Result<DiceExpr, ParserError> {
match self.current_token {
Token::DiceModKeepHighest => {
self.eat(Token::DiceModKeepHighest)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModKeepHighest(Box::new(dice_op), n);
self.modifiers_op(expr)
}
Token::Highest => {
self.eat(Token::Highest)?;
let expr = DiceExpr::ModKeepHighest(Box::new(dice_op), 1);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::DiceModKeepLowest => {
self.eat(Token::DiceModKeepLowest)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModKeepLowest(Box::new(dice_op), n);
self.modifiers_op(expr)
}
Token::Lowest => {
self.eat(Token::Lowest)?;
let expr = DiceExpr::ModKeepLowest(Box::new(dice_op), 1);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::DiceModReroll => {
self.eat(Token::DiceModReroll)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModReroll(Box::new(dice_op), n);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::DiceModRerollOnce => {
self.eat(Token::DiceModRerollOnce)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModRerollOnce(Box::new(dice_op), n);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::DiceOp => {
self.eat(Token::DiceOp)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModDropLowest(Box::new(dice_op), n);
self.modifiers_op(expr)
}
Token::Lowest => {
self.eat(Token::Lowest)?;
let expr = DiceExpr::ModDropLowest(Box::new(dice_op), 1);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::DiceModDropHighest => {
self.eat(Token::DiceModDropHighest)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModDropHighest(Box::new(dice_op), n);
self.modifiers_op(expr)
}
Token::Highest => {
self.eat(Token::Highest)?;
let expr = DiceExpr::ModDropHighest(Box::new(dice_op), 1);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::DiceModMaximum => {
self.eat(Token::DiceModMaximum)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModMaximum(Box::new(dice_op), n);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::DiceModMinimum => {
self.eat(Token::DiceModMinimum)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModMininum(Box::new(dice_op), n);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::GreaterThan => {
self.eat(Token::GreaterThan)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModCountGreaterOrEqual(Box::new(dice_op), n);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
Token::LowerThan => {
self.eat(Token::LowerThan)?;
match self.current_token {
Token::Number(n) => {
self.eat(Token::Number(n))?;
let expr = DiceExpr::ModCountLowerOrEqual(Box::new(dice_op), n);
self.modifiers_op(expr)
}
_ => Err(ParserError::UnexpectedDiceToken(self.current_token.clone())),
}
}
_ => Ok(dice_op),
}
}
}
#[cfg(test)]
mod tests {
use crate::ast::{DiceExpr, Expr, Parser};
#[test]
fn parse_single_value() {
let expr = Parser::parse("5").unwrap();
assert_eq!(expr, Expr::Number(5))
}
#[test]
fn parse_single_dice() {
let expr = Parser::parse("d6").unwrap();
assert_eq!(expr, Expr::DiceTotal(Box::new(DiceExpr::Dice(1, 6))))
}
#[test]
fn parse_multiple_dice() {
let expr = Parser::parse("2d20").unwrap();
assert_eq!(expr, Expr::DiceTotal(Box::new(DiceExpr::Dice(2, 20))))
}
#[test]
fn parse_multiple_dice_negative() {
let expr = Parser::parse("-2d20").unwrap();
assert_eq!(
expr,
Expr::Negate(Box::new(Expr::DiceTotal(Box::new(DiceExpr::Dice(2, 20)))))
)
}
#[test]
fn parse_multiple_dice_keep_highest_modifier_number() {
let expr = Parser::parse("2d20k2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModKeepHighest(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_keep_highest_modifier_highest() {
let expr = Parser::parse("2d20kH").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModKeepHighest(
Box::new(DiceExpr::Dice(2, 20)),
1
)))
)
}
#[test]
fn parse_multiple_dice_keep_highest_modifier_lowest() {
let expr = Parser::parse("2d20kL");
assert!(expr.is_err())
}
#[test]
fn parse_multiple_dice_keep_lowest_modifier_number() {
let expr = Parser::parse("2d20kl2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModKeepLowest(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_keep_lowest_modifier_lowest() {
let expr = Parser::parse("2d20klL").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModKeepLowest(
Box::new(DiceExpr::Dice(2, 20)),
1
)))
)
}
#[test]
fn parse_multiple_dice_keep_lowest_modifier_highest() {
let expr = Parser::parse("2d20klH");
assert!(expr.is_err())
}
#[test]
fn parse_multiple_dice_drop_highest_modifier_number() {
let expr = Parser::parse("2d20dh2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModDropHighest(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_drop_highest_modifier_highest() {
let expr = Parser::parse("2d20dhH").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModDropHighest(
Box::new(DiceExpr::Dice(2, 20)),
1
)))
)
}
#[test]
fn parse_multiple_dice_drop_highest_modifier_lowest() {
let expr = Parser::parse("2d20dhL");
assert!(expr.is_err())
}
#[test]
fn parse_multiple_dice_drop_lowest_modifier_number() {
let expr = Parser::parse("2d20d2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModDropLowest(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_drop_lowest_modifier_lowest() {
let expr = Parser::parse("2d20dL").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModDropLowest(
Box::new(DiceExpr::Dice(2, 20)),
1
)))
)
}
#[test]
fn parse_multiple_dice_drop_lowest_modifier_highest() {
let expr = Parser::parse("2d20dH");
assert!(expr.is_err())
}
#[test]
fn parse_multiple_dice_reroll_modifier() {
let expr = Parser::parse("2d20r3").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModReroll(
Box::new(DiceExpr::Dice(2, 20)),
3
)))
)
}
#[test]
fn parse_multiple_dice_reroll_once_modifier() {
let expr = Parser::parse("2d20ro2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModRerollOnce(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_minimum_modifier() {
let expr = Parser::parse("2d20mi2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModMininum(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_minimum_long_modifier() {
let expr = Parser::parse("2d20min2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModMininum(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_maximum_modifier() {
let expr = Parser::parse("2d20ma2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModMaximum(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_maximum_long_modifier() {
let expr = Parser::parse("2d20max2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModMaximum(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_count_greater_modifier() {
let expr = Parser::parse("2d20>2").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModCountGreaterOrEqual(
Box::new(DiceExpr::Dice(2, 20)),
2
)))
)
}
#[test]
fn parse_multiple_dice_count_lower_modifier() {
let expr = Parser::parse("2d20<5").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModCountLowerOrEqual(
Box::new(DiceExpr::Dice(2, 20)),
5
)))
)
}
#[test]
fn parse_multiple_dice_combined_modifier() {
let expr = Parser::parse("2d20k5dhHmax6<5").unwrap();
assert_eq!(
expr,
Expr::DiceTotal(Box::new(DiceExpr::ModCountLowerOrEqual(
Box::new(DiceExpr::ModMaximum(
Box::new(DiceExpr::ModDropHighest(
Box::new(DiceExpr::ModKeepHighest(Box::new(DiceExpr::Dice(2, 20)), 5)),
1
)),
6
)),
5
)))
)
}
#[test]
fn parse_multiple_dice_add_value() {
let expr = Parser::parse("2d20+6").unwrap();
assert_eq!(
expr,
Expr::Add(
Box::new(Expr::DiceTotal(Box::new(DiceExpr::Dice(2, 20)))),
Box::new(Expr::Number(6))
)
)
}
#[test]
fn parse_multiple_dice_add_value_reverse() {
let expr = Parser::parse("6 + 2d20").unwrap();
assert_eq!(
expr,
Expr::Add(
Box::new(Expr::Number(6)),
Box::new(Expr::DiceTotal(Box::new(DiceExpr::Dice(2, 20))))
)
)
}
#[test]
fn parse_multiple_dice_subtract_value() {
let expr = Parser::parse("3d100-45").unwrap();
assert_eq!(
expr,
Expr::Subtract(
Box::new(Expr::DiceTotal(Box::new(DiceExpr::Dice(3, 100)))),
Box::new(Expr::Number(45))
)
)
}
#[test]
fn parse_multiple_dice_multiply_value() {
let expr = Parser::parse("3d100*45").unwrap();
assert_eq!(
expr,
Expr::Multiply(
Box::new(Expr::DiceTotal(Box::new(DiceExpr::Dice(3, 100)))),
Box::new(Expr::Number(45))
)
)
}
#[test]
fn parse_complex() {
let expr = Parser::parse("((2d6+1) + (-10-1d3)) * 3").unwrap();
assert_eq!(
expr,
Expr::Multiply(
Box::new(Expr::Add(
Box::new(Expr::Add(
Box::new(Expr::DiceTotal(Box::new(DiceExpr::Dice(2, 6)))),
Box::new(Expr::Number(1))
)),
Box::new(Expr::Subtract(
Box::new(Expr::Negate(Box::new(Expr::Number(10)))),
Box::new(Expr::DiceTotal(Box::new(DiceExpr::Dice(1, 3))))
))
)),
Box::new(Expr::Number(3))
)
)
}
}