aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Perry <np@nathanperry.dev>2024-08-07 07:46:17 -0400
committerNathan Perry <np@nathanperry.dev>2024-08-07 07:46:17 -0400
commitba827ea23b170365e86564912bf7c213af1b0c2f (patch)
tree1832fbf99ae19ea4deaddf393501af2727b6466c
parentb58b03c22d637fe8f7200edb6953325bf359544d (diff)
calc: refactor to freestanding functions
-rw-r--r--calc/src/lib.rs252
1 files changed, 127 insertions, 125 deletions
diff --git a/calc/src/lib.rs b/calc/src/lib.rs
index 05b83a9..c3ca3fd 100644
--- a/calc/src/lib.rs
+++ b/calc/src/lib.rs
@@ -1,14 +1,20 @@
-use lazy_static::lazy_static;
-use pest::iterators::Pair;
-use rand::Rng;
+use pest::{
+ iterators::{
+ Pair,
+ Pairs,
+ },
+ pratt_parser::PrattParser,
+ Parser,
+};
+use rand::Rng as _;
#[derive(pest_derive::Parser)]
#[grammar = "calc.pest"]
pub struct Calc;
#[derive(Copy, Clone, thiserror::Error, Debug, PartialEq, Eq, Hash)]
-pub enum CalcError {
- #[error("pest was unable to parse the input")]
+pub enum Error {
+ #[error("unable to parse input")]
Pest,
#[error("invalid number format")]
@@ -18,151 +24,147 @@ pub enum CalcError {
ArgCount,
}
-impl Calc {
- pub fn eval<S: AsRef<str>>(s: S) -> Result<f64, CalcError> {
- use pest::{
- iterators::Pairs,
- pratt_parser::PrattParser,
- Parser,
+lazy_static::lazy_static! {
+ static ref PRATT_PARSER: PrattParser<Rule> = {
+ use pest::pratt_parser::{
+ Assoc::*,
+ Op,
};
+ use Rule::*;
- use self::Rule::*;
-
- lazy_static! {
- static ref CLIMBER: PrattParser<Rule> = {
- use pest::pratt_parser::{
- Assoc::*,
- Op,
- };
+ PrattParser::new()
+ .op(Op::infix(add, Left) | Op::infix(sub, Left) | Op::infix(modulo, Left))
+ .op(Op::infix(mul, Left) | Op::infix(div, Left))
+ .op(Op::infix(dice, Left))
+ .op(Op::infix(pow, Right))
+ .op(Op::postfix(EOI)) // discarded below
+ };
+}
- PrattParser::new()
- .op(Op::infix(add, Left) | Op::infix(sub, Left) | Op::infix(modulo, Left))
- .op(Op::infix(mul, Left) | Op::infix(div, Left))
- .op(Op::infix(dice, Left))
- .op(Op::infix(pow, Right))
- .op(Op::postfix(EOI)) // discarded below
- };
- }
+impl Calc {
+ #[inline]
+ pub fn eval<S: AsRef<str>>(s: S) -> Result<f64, Error> {
+ let result = Calc::parse(Rule::calc, s.as_ref()).map_err(|_| Error::Pest)?;
+ eval_expr(result)
+ }
+}
- let result = Calc::parse(calc, s.as_ref()).map_err(|_| CalcError::Pest)?;
+fn eval_single_pair(pair: Pair<Rule>) -> Result<f64, Error> {
+ use Rule::*;
- fn eval_single_pair(pair: Pair<Rule>) -> Result<f64, CalcError> {
- let result = match pair.as_rule() {
- oct | hex | binary => {
- let base = match pair.as_rule() {
- hex => 16,
- oct => 8,
- binary => 2,
- _ => unreachable!(),
- };
+ let result = match pair.as_rule() {
+ oct | hex | binary => {
+ let base = match pair.as_rule() {
+ hex => 16,
+ oct => 8,
+ binary => 2,
+ _ => unreachable!(),
+ };
- u64::from_str_radix(&pair.as_str()[2..], base)
- .map_err(|_| CalcError::NumberFormat)? as f64
- },
- float => pair.as_str().parse::<f64>().map_err(|_| CalcError::NumberFormat)?,
- expr | num => eval_expr(pair.into_inner())?,
- unary_expr => {
- let mut p = pair.into_inner();
+ u64::from_str_radix(&pair.as_str()[2..], base).map_err(|_| Error::NumberFormat)? as f64
+ },
+ float => pair.as_str().parse::<f64>().map_err(|_| Error::NumberFormat)?,
+ expr | num => eval_expr(pair.into_inner())?,
+ unary_expr => {
+ let mut p = pair.into_inner();
- let op = p.next().ok_or(CalcError::ArgCount)?;
- let arg = eval_expr(p)?;
+ let op = p.next().ok_or(Error::ArgCount)?;
+ let arg = eval_expr(p)?;
- match op.as_rule() {
- log => arg.ln(),
- sqrt => arg.sqrt(),
- sgn => arg.signum(),
+ match op.as_rule() {
+ log => arg.ln(),
+ sqrt => arg.sqrt(),
+ sgn => arg.signum(),
- sin => arg.sin(),
- cos => arg.cos(),
- tan => arg.tan(),
- asin => arg.asin(),
- acos => arg.acos(),
- atan => arg.atan(),
+ sin => arg.sin(),
+ cos => arg.cos(),
+ tan => arg.tan(),
+ asin => arg.asin(),
+ acos => arg.acos(),
+ atan => arg.atan(),
- sinh => arg.sinh(),
- cosh => arg.cosh(),
- tanh => arg.tanh(),
- asinh => arg.asinh(),
- acosh => arg.acosh(),
- atanh => arg.atanh(),
+ sinh => arg.sinh(),
+ cosh => arg.cosh(),
+ tanh => arg.tanh(),
+ asinh => arg.asinh(),
+ acosh => arg.acosh(),
+ atanh => arg.atanh(),
- exp => arg.exp(),
- abs => arg.abs(),
- ceil => arg.ceil(),
- floor => arg.floor(),
- round => arg.round(),
- _ => unreachable!(),
- }
- },
- binary_expr => {
- let mut p = pair.into_inner();
+ exp => arg.exp(),
+ abs => arg.abs(),
+ ceil => arg.ceil(),
+ floor => arg.floor(),
+ round => arg.round(),
+ _ => unreachable!(),
+ }
+ },
+ binary_expr => {
+ let mut p = pair.into_inner();
- let op = p.next().ok_or(CalcError::ArgCount)?;
+ let op = p.next().ok_or(Error::ArgCount)?;
- let arg1 = eval_single_pair(p.next().ok_or(CalcError::ArgCount)?)?;
- let arg2 = eval_single_pair(p.next().ok_or(CalcError::ArgCount)?)?;
+ let arg1 = eval_single_pair(p.next().ok_or(Error::ArgCount)?)?;
+ let arg2 = eval_single_pair(p.next().ok_or(Error::ArgCount)?)?;
- assert!(p.next().is_none());
+ assert!(p.next().is_none());
- match op.as_rule() {
- min => arg1.min(arg2),
- max => arg1.max(arg2),
- atan2 => arg1.atan2(arg2),
- _ => unreachable!(),
- }
- },
- suffix_expr => {
- let mut p = pair.into_inner();
+ match op.as_rule() {
+ min => arg1.min(arg2),
+ max => arg1.max(arg2),
+ atan2 => arg1.atan2(arg2),
+ _ => unreachable!(),
+ }
+ },
+ suffix_expr => {
+ let mut p = pair.into_inner();
- let arg = eval_expr(p.next().ok_or(CalcError::ArgCount)?.into_inner())?;
- let op = p.next().ok_or(CalcError::ArgCount)?;
+ let arg = eval_expr(p.next().ok_or(Error::ArgCount)?.into_inner())?;
+ let op = p.next().ok_or(Error::ArgCount)?;
- assert!(p.next().is_none());
+ assert!(p.next().is_none());
- match op.as_rule() {
- factorial => statrs::function::gamma::gamma(arg + 1.),
- _ => unreachable!(),
- }
- },
+ match op.as_rule() {
+ factorial => statrs::function::gamma::gamma(arg + 1.),
_ => unreachable!(),
- };
+ }
+ },
+ _ => unreachable!(),
+ };
- Ok(result)
- }
+ Ok(result)
+}
- pub fn eval_expr(p: Pairs<Rule>) -> Result<f64, CalcError> {
- CLIMBER
- .map_primary(eval_single_pair)
- .map_infix(|lhs, op, rhs| {
- let lhs = lhs?;
- let rhs = rhs?;
+fn eval_expr(p: Pairs<Rule>) -> Result<f64, Error> {
+ use Rule::*;
- let result = match op.as_rule() {
- add => lhs + rhs,
- sub => lhs - rhs,
- mul => lhs * rhs,
- div => lhs / rhs,
- pow => lhs.powf(rhs),
- dice => {
- let dice_count = lhs as usize;
- let dice_faces = rhs as usize;
+ PRATT_PARSER
+ .map_primary(eval_single_pair)
+ .map_infix(|lhs, op, rhs| {
+ let lhs = lhs?;
+ let rhs = rhs?;
- let mut rng = rand::thread_rng();
- (0..dice_count)
- .map(|_| rng.gen_range(1..(dice_faces + 1)))
- .sum::<usize>() as f64
- },
- _ => unreachable!(),
- };
+ let result = match op.as_rule() {
+ add => lhs + rhs,
+ sub => lhs - rhs,
+ mul => lhs * rhs,
+ div => lhs / rhs,
+ pow => lhs.powf(rhs),
+ dice => {
+ let dice_count = lhs as usize;
+ let dice_faces = rhs as usize;
- Ok(result)
- })
- .map_postfix(|arg, _post| arg) // discard EOI
- .parse(p)
- }
+ let mut rng = rand::thread_rng();
+ (0..dice_count)
+ .map(|_| rng.gen_range(1..(dice_faces + 1)))
+ .sum::<usize>() as f64
+ },
+ _ => unreachable!(),
+ };
- eval_expr(result)
- }
+ Ok(result)
+ })
+ .map_postfix(|arg, _post| arg) // discard EOI
+ .parse(p)
}
#[cfg(test)]