Skip to content

Commit

Permalink
Continue work on documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Gawdl3y committed Mar 18, 2024
1 parent 926464f commit 681258b
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 29 deletions.
13 changes: 7 additions & 6 deletions src/dice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ impl Dice {
}

impl Default for Dice {
/// Creates the default dice (1d20).
#[inline]
fn default() -> Self {
Self::new(1, 20)
Expand All @@ -113,8 +114,8 @@ impl fmt::Display for Dice {
}
}

/// A `Modifier` is a routine that can be applied to a set of [Dice] to automatically manipulate resulting
/// [Rolled] dice sets from them as a part of their rolling process.
/// Routines that can be applied to a set of [Dice] to automatically manipulate resulting [Rolled] dice sets from them
/// as part of their rolling process.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Modifier {
Expand Down Expand Up @@ -337,7 +338,7 @@ impl fmt::Display for Modifier {
}
}

/// A `Condition` is a test that die values can be checked against.
/// A test that die values can be checked against.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Condition {
/// Checks whether values are equal to its own value. Symbol: `=`
Expand Down Expand Up @@ -485,7 +486,7 @@ impl Ord for DieRoll<'_> {
}

impl fmt::Display for DieRoll<'_> {
/// Formats the value using the given formatter.
/// Formats the value using the given formatter. [Read more][core::fmt::Debug::fmt()]
///
/// The format of a die roll is simply the plain numeric value of the roll.
/// If the roll was dropped, it is appended with ` (d)`.
Expand Down Expand Up @@ -596,7 +597,7 @@ impl Describe for Rolled<'_> {
.iter()
.take(max_rolls)
.map(|r| r.to_string())
.collect::<Vec<String>>()
.collect::<Vec<_>>()
.join(", "),
if truncated_rolls > 0 {
format!(", {} more...", truncated_rolls)
Expand All @@ -608,7 +609,7 @@ impl Describe for Rolled<'_> {
}

impl fmt::Display for Rolled<'_> {
/// Formats the value using the given formatter.
/// Formats the value using the given formatter. [Read more][core::fmt::Debug::fmt()]
///
/// The output is equivalent to calling [`Rolled::describe(None)`].
///
Expand Down
39 changes: 39 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,41 @@
#![cfg(feature = "parse")]
#![allow(clippy::tabs_in_doc_comments)]

//! Parser generator functions and implementations of [`std::str::FromStr`] for all dice and term data structures.
//! Requires the `parse` feature (enabled by default).
//!
//! The parser generators generate parsers for parsing dice, dice modifiers, modifier conditions, and full mathematical
//! dice expressions (such as `4d8 + 2d6x - 3`) from strings. They're all made with [chumsky] and are almost entirely
//! zero-copy. A parser can be used directly by calling [`chumsky::Parser::parse()`] on it.
//!
//! # Examples
//!
//! ## Parsing Dice
//! ```
//! use dicey::dice::Dice;
//!
//! let dice: Dice = "6d8x".parse().expect("unable to parse dice");
//! assert_eq!(dice, Dice::builder().count(6).sides(8).explode(None, true).build());
//! ```
//!
//! ## Parsing Terms (mathematical dice expressions)
//! ```
//! use dicey::{dice::Dice, term::Term};
//!
//! let term: Term = "6d8x + 4d6 - 3".parse().expect("unable to parse term");
//! assert_eq!(
//! term,
//! Term::Sub(
//! Box::new(Term::Add(
//! Box::new(Term::Dice(
//! Dice::builder().count(6).sides(8).explode(None, true).build()
//! )),
//! Box::new(Term::Dice(Dice::builder().count(4).sides(6).build())),
//! )),
//! Box::new(Term::Num(3)),
//! )
//! );
//! ```

use chumsky::prelude::*;

Expand Down Expand Up @@ -182,8 +219,10 @@ pub fn term<'src>() -> impl Parser<'src, &'src str, Term, extra::Err<Rich<'src,
term_part().then_ignore(end())
}

/// Error that can occur while parsing a string into a dice or term-related structure via [`std::str::FromStr`].
#[derive(Debug, Clone)]
pub struct Error {
/// Details of the originating one or more [`chumsky::error::Rich`]s that occurred during parsing
pub details: String,
}

Expand Down
103 changes: 80 additions & 23 deletions src/term.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! AST-like data structures for evaluating full mathematical dice expressions and working with their results.

use crate::dice::{Dice, Error as DiceError, Rolled};

/// Generates an implementation of [TermType] and [DescribeBinaryTerm] for an enum type.
Expand All @@ -14,18 +16,22 @@ macro_rules! term_type_impl {
}
}

#[must_use]
fn is_value(&self) -> bool {
matches!(self, Self::Num(..) | Self::Dice(..))
}

#[must_use]
fn is_unary(&self) -> bool {
matches!(self, Self::Neg(..))
}

#[must_use]
fn is_additive(&self) -> bool {
matches!(self, Self::Add(..) | Self::Sub(..))
}

#[must_use]
fn is_multiplicative(&self) -> bool {
matches!(self, Self::Mul(..) | Self::DivDown(..) | Self::DivUp(..))
}
Expand All @@ -38,25 +44,37 @@ macro_rules! term_type_impl {
/// Individual terms usable in expressions
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Term {
// Values
/// Standalone integer
Num(i32),

/// Dice literal
Dice(Dice),

// Unary operators
/// Negation of a term (makes it negative)
Neg(Box<Self>),

// Binary operators
/// Sum of two terms
Add(Box<Self>, Box<Self>),

/// Difference of two terms
Sub(Box<Self>, Box<Self>),

/// Product of two terms
Mul(Box<Self>, Box<Self>),

/// Integer quotient of two terms (rounded down)
DivDown(Box<Self>, Box<Self>),

/// Integer quotient of two terms (rounded up)
DivUp(Box<Self>, Box<Self>),
}

term_type_impl!(Term);

impl Term {
/// Evaluates the term
/// Evaluates the term. For most types of terms, this will directly result in a 1:1 equivalent [`EvaledTerm`],
/// with the notable exception of [`Term::Dice`]. For dice terms, the dice they contain are rolled, resulting
/// in an [`EvaledTerm::Dice`] with the [`Rolled`] set of dice.
pub fn eval(&self) -> Result<EvaledTerm, Error> {
Ok(match self {
Self::Num(x) => EvaledTerm::Num(*x),
Expand All @@ -72,7 +90,10 @@ impl Term {
})
}

/// Checks whether the term is deterministic (will always yield the same value)
/// Checks whether the term is deterministic (will always yield the same value with every evaluation).
/// A [`Term::Num`] will always return `true`, a [`Term::Dice`] will always return `false` unless the dice they
/// contain only have one side, and all unary and binary terms forward the check to their children.
#[must_use]
pub fn is_deterministic(&self) -> bool {
match self {
Self::Num(..) => true,
Expand All @@ -86,6 +107,13 @@ impl Term {
}

impl Describe for Term {
/// Builds a full usable expression from the terms. Operations are grouped with parentheses whenever the order of
/// operations could be considered ambiguous, such as when mixing addition and multiplication together. All strings
/// output from this should result in the exact same Term layout when re-parsing them.
///
/// `max_rolls` does not affect the output of this implementation in any way since there are no possible lists of
/// elements included, so it is always safe to pass `None`.
#[inline]
fn describe(&self, _max_rolls: Option<usize>) -> String {
match self {
Self::Num(x) => x.to_string(),
Expand All @@ -106,33 +134,48 @@ impl Describe for Term {
}

impl std::fmt::Display for Term {
/// Formats the value using the given formatter. [Read more][core::fmt::Debug::fmt()]
///
/// The output of this implementation is equivalent to [`Term::describe(None)`].
///
/// [`Term::describe(None)`]: ./enum.Term.html#method.describe
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.describe(None))
}
}

/// Individual evaluated terms from expressions
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EvaledTerm<'a> {
// Values
pub enum EvaledTerm<'r> {
/// Standalone integer
Num(i32),
Dice(Rolled<'a>),

// Unary operators
/// Rolled dice
Dice(Rolled<'r>),

/// Negation of a term (makes it negative)
Neg(Box<Self>),

// Binary operators
/// Sum of two terms
Add(Box<Self>, Box<Self>),

/// Difference of two terms
Sub(Box<Self>, Box<Self>),

/// Product of two terms
Mul(Box<Self>, Box<Self>),

/// Integer quotient of two terms (rounded down)
DivDown(Box<Self>, Box<Self>),

/// Integer quotient of two terms (rounded up)
DivUp(Box<Self>, Box<Self>),
}

term_type_impl!(EvaledTerm<'_>);

impl EvaledTerm<'_> {
/// Calculates the total of the evaluated term
/// Calculates the total (sum) of the evaluated term and all of its children (if any).
pub fn calc(&self) -> Result<i32, Error> {
match self {
Self::Num(x) => Ok(*x),
Expand Down Expand Up @@ -185,61 +228,75 @@ impl std::fmt::Display for EvaledTerm<'_> {
}
}

/// An error resulting from a term operation
/// Error resulting from evaluating a [`Term`] or calculating an [`EvaledTerm`]
#[derive(thiserror::Error, Debug)]
pub enum Error {
/// Dice-related error (likely during rolling)
#[error("dice error: {0}")]
Dice(#[from] DiceError),

/// Integer overflow (likely during calculation of a sum or product)
#[error("integer overflow")]
Overflow,

/// Division-related error (likely division by zero)
#[error("division error")]
Division,
}

/// Operation type for an individual term
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TermType {
/// Single value, no operation
Value,

/// Unary operation
Unary,

/// Additive operation (sum or difference)
Additive,

/// Multiplicative operation (product or quotient)
Multiplicative,
}

/// Trait for working with [TermType]s
/// Trait that offers [`TermType`]-related information
pub trait HasTermType {
/// Gets the type of this term
/// Gets the type of this term.
fn term_type(&self) -> TermType;

/// Checks whether this term is a value
/// Checks whether this term is a single value.
fn is_value(&self) -> bool;

/// Checks whether this term is unary
/// Checks whether this term is a unary operation.
fn is_unary(&self) -> bool;

/// Checks whether this term is additive
/// Checks whether this term is an additive operation.
fn is_additive(&self) -> bool;

/// Checks whether this term is multiplicative
/// Checks whether this term is a multiplicative operation.
fn is_multiplicative(&self) -> bool;

/// Checks whether this term is binary (additive or multiplicative)
/// Checks whether this term is a binary (additive or multiplicative) operation.
fn is_binary(&self) -> bool {
self.is_additive() || self.is_multiplicative()
}
}

/// Trait to allow creation of expanded descriptions with an optional max number of rolls where applicable
/// Trait to allow creation of expanded descriptions with an optional max number of individual listed results where
/// applicable
pub trait Describe {
/// Produces a detailed expression string with additional information about non-deterministic elements
/// Builds a detailed expression string with additional information about non-deterministic elements.
/// Any elements of the expression that can have a different result between multiple evaluations or multiple results
/// should list all of the specific individual results that occurred (ideally, up to `max_rolls` of them).
fn describe(&self, max_rolls: Option<usize>) -> String;
}

/// Trait for describing binary expressions with influence from own type.
/// Used for, e.g. wrapping parentheses around parts of expressions based on [TermType] of self and the expression.
trait DescribeBinaryTerm: HasTermType + Describe {
/// Builds a detailed description for a binary expression with parentheses around parts of it if appropriate
/// Builds a detailed description for a binary expression with parentheses added to disambiguate mixed
/// additive/multiplicative operations.
fn describe_binary_term(
&self,
op: char,
Expand Down Expand Up @@ -270,7 +327,7 @@ trait DescribeBinaryTerm: HasTermType + Describe {
}
}

/// Wraps a string in parentheses
/// Wraps a string in parentheses.
fn paren_wrap(mut text: String) -> String {
text.insert(0, '(');
text.push(')');
Expand Down

0 comments on commit 681258b

Please sign in to comment.