use std::{fmt::Display, rc::Rc};
use crate::{
coin::{self, CoinResult},
dice::{Roll, RollParseError, RollResult},
interval::{Interval, IntervalParseError, IntervalSample},
Error, Pcg,
};
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SharedEntry(Rc<EntryData>);
#[derive(Debug, Clone, PartialEq)]
enum EntryData {
Text(Rc<str>),
Expr(Expr),
}
#[derive(Debug, Clone, PartialEq)]
enum Expr {
Coin,
Dice(Roll),
Interval(Interval),
}
impl SharedEntry {
pub fn new(text: Rc<str>, eval_expr: bool) -> Result<Self, Error> {
if eval_expr {
Self::expr(text)
} else {
Ok(Self::text(text))
}
}
pub fn text(text: Rc<str>) -> Self {
Self(Rc::new(EntryData::Text(text)))
}
pub fn expr(text: Rc<str>) -> Result<Self, Error> {
let data = if let Some(expr) = parse_expr(&text)? {
EntryData::Expr(expr)
} else {
EntryData::Text(text)
};
Ok(Self(Rc::new(data)))
}
pub fn eval(&self, rng: &mut Pcg) -> Entry {
match self.0.as_ref() {
EntryData::Text(t) => Entry::Text(Rc::clone(t)),
EntryData::Expr(e) => e.eval(rng),
}
}
}
impl Expr {
fn eval(&self, rng: &mut Pcg) -> Entry {
match self {
Expr::Coin => Entry::Coin(coin::toss_coin(rng)),
Expr::Dice(r) => Entry::Dice(r.eval(rng)),
Expr::Interval(i) => Entry::Interval(i.eval(rng)),
}
}
}
fn parse_expr(expr: &str) -> Result<Option<Expr>, Error> {
if expr == "coin" {
return Ok(Some(Expr::Coin));
}
match expr.parse::<Roll>() {
Err(RollParseError::NoMatch) => {}
Ok(r) => return Ok(Some(Expr::Dice(r))),
Err(e) => return Err(Error::Expr(e.to_string())),
}
match expr.parse::<Interval>() {
Err(IntervalParseError::NoMatch) => {}
Ok(i) => return Ok(Some(Expr::Interval(i))),
Err(e) => return Err(Error::Expr(e.to_string())),
}
Ok(None)
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum Entry {
Text(Rc<str>),
Coin(CoinResult),
Dice(RollResult),
Interval(IntervalSample),
}
impl Entry {
pub(crate) fn into_raw(self) -> Rc<str> {
match self {
Entry::Text(t) => t,
_ => Rc::from(format!("{:#}", self).as_str()),
}
}
}
impl Display for Entry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Entry::Text(t) => t.fmt(f),
Entry::Coin(r) => r.fmt(f),
Entry::Dice(r) => r.fmt(f),
Entry::Interval(i) => i.fmt(f),
}
}
}