#![deny(clippy::all, clippy::pedantic, clippy::nursery)]
#![deny(missing_debug_implementations, rust_2018_idioms)]
#![deny(missing_docs)]
#![deny(warnings)]
#![allow(
clippy::module_name_repetitions,
clippy::cast_possible_truncation,
clippy::non_ascii_literal
)]
pub mod checker;
mod config;
#[cfg(feature = "detail")]
pub mod detail;
pub mod error;
pub mod expr;
mod parser;
pub mod roll;
mod tree;
use config::Limit;
use pest::Parser;
use crate::{
checker::Checker,
error::CompileError,
expr::AstTreeNode,
parser::{GurgleCommandParser, Rule},
roll::GurgleRoll,
};
pub use {config::Config, expr::Dice};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Gurgle {
expr: AstTreeNode,
checker: Option<Checker>,
}
impl Gurgle {
#[allow(clippy::missing_panics_doc)] pub fn compile_with_config(s: &str, config: &Config) -> Result<Self, CompileError> {
let mut limit = Limit::new(config);
let pairs = GurgleCommandParser::parse(Rule::command, s)?;
let mut expr = None;
let mut checker = None;
for pair in pairs {
match pair.as_rule() {
Rule::expr => {
expr.replace(AstTreeNode::from_pair(pair, &mut limit)?);
}
Rule::checker => {
checker.replace(Checker::from_pair(pair, &limit)?);
}
Rule::EOI => {}
_ => unreachable!(),
}
}
Ok(Self {
expr: expr.unwrap(),
checker,
})
}
pub fn compile(s: &str) -> Result<Self, CompileError> {
Self::compile_with_config(s, &config::DEFAULT_CONFIG)
}
#[must_use]
pub const fn expr(&self) -> &AstTreeNode {
&self.expr
}
#[must_use]
pub const fn checker(&self) -> Option<&Checker> {
self.checker.as_ref()
}
#[must_use]
pub fn roll(&self) -> GurgleRoll<'_> {
GurgleRoll::new(self.expr.roll(), self.checker())
}
}
pub fn roll(s: &str) -> Result<i64, CompileError> {
Gurgle::compile(s).map(|x| x.roll().value())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser_correct() {
assert!(Gurgle::compile("1d6+1").is_ok());
assert!(Gurgle::compile("3d6+2d10+1").is_ok());
assert!(Gurgle::compile("3d6max+2d10min+1").is_ok());
assert!(Gurgle::compile("3d6max+2d10min+1>=10").is_ok());
assert!(Gurgle::compile("3d6max+2d10min+1>=-10").is_ok());
assert!(Gurgle::compile("100d1000+-1").is_ok());
assert!(Gurgle::compile("100d1000*5").is_ok());
assert!(Gurgle::compile("10d1000x1d10").is_ok());
assert!(Gurgle::compile("(10d1000)+(1)").is_ok());
assert!(Gurgle::compile("3d6 + (2d4 + 1) * 2 + 1>20").is_ok());
assert!(Gurgle::compile("3d6+(2d4+1)*2+1 >20").is_ok());
assert!(Gurgle::compile("3d6+(2d4+1)*2+1> 20").is_ok());
assert!(Gurgle::compile("3d6+(2d4+1)*2+1 > 20").is_ok());
}
#[test]
fn test_parser_invalid() {
assert!(std::matches!(
Gurgle::compile("+").unwrap_err(),
CompileError::InvalidSyntax(_)
));
assert!(std::matches!(
Gurgle::compile("good").unwrap_err(),
CompileError::InvalidSyntax(_)
));
assert!(std::matches!(
Gurgle::compile("3d6+2p10+1").unwrap_err(),
CompileError::InvalidSyntax(_)
));
assert!(std::matches!(
Gurgle::compile("3d6max+2d10min+1avg").unwrap_err(),
CompileError::InvalidSyntax(_)
));
assert!(std::matches!(
Gurgle::compile("3d6+(1").unwrap_err(),
CompileError::InvalidSyntax(_),
));
assert!(std::matches!(
Gurgle::compile("3d6 max+2d10min+1avg").unwrap_err(),
CompileError::InvalidSyntax(_)
));
assert!(std::matches!(
Gurgle::compile("3d6+100000000000000000000000000").unwrap_err(),
CompileError::ParseNumberError(_),
));
}
#[test]
fn test_compile_error() {
assert_eq!(
Gurgle::compile("10d-10").unwrap_err(),
CompileError::DiceRollOrSidedNegative,
);
assert_eq!(
Gurgle::compile("-10d10").unwrap_err(),
CompileError::DiceRollOrSidedNegative,
);
assert_eq!(
Gurgle::compile(
"3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+3d6+1"
)
.unwrap_err(),
CompileError::ItemCountLimitExceeded,
);
assert_eq!(
Gurgle::compile("10d1001").unwrap_err(),
CompileError::DiceSidedCountLimitExceeded,
);
assert_eq!(
Gurgle::compile("1001d10").unwrap_err(),
CompileError::DiceRollTimesLimitExceeded,
);
assert_eq!(
Gurgle::compile("1000d10+1d10").unwrap_err(),
CompileError::DiceRollTimesLimitExceeded,
);
assert_eq!(
Gurgle::compile("65537").unwrap_err(),
CompileError::NumberItemOutOfRange,
);
assert_eq!(
Gurgle::compile("-65537").unwrap_err(),
CompileError::NumberItemOutOfRange,
);
}
#[test]
fn test_roll() {
let attack = Gurgle::compile("3d6min+3d6avg+3d6max+3d6+(2d4+1)*2+1>15").unwrap();
let result = attack.roll();
#[cfg(feature = "detail")]
println!("attack rolling result is: {}", result);
println!("attack = {}", result.value());
assert!(result.value() >= 13);
assert_eq!(result.success().unwrap(), result.value() > 15);
}
}