cpc 1.7.0

evaluates math expressions, with support for units and conversion between units
Documentation
//! calculation + conversion
//! 
//! cpc parses and evaluates strings of math, with support for units and conversion. 128-bit decimal floating points are used for high accuracy.
//! 
//! cpc lets you mix units, so for example 1 km - 1m results in Number { value: 999, unit: Meter }.
//! 
//! Check out the [list of supported units](units/enum.Unit.html)
//! 
//! # Example usage
//! ```rust
//! use cpc::eval;
//! use cpc::units::Unit;
//! 
//! match eval("3m + 1cm", true, Unit::Celsius, false) {
//!     Ok(answer) => {
//!         // answer: Number { value: 301, unit: Unit::Centimeter }
//!         println!("Evaluated value: {} {:?}", answer.value, answer.unit)
//!     },
//!     Err(e) => {
//!         println!("{}", e)
//!     }
//! }
//! ```

use std::fmt::{self, Display};
use std::time::Instant;
use decimal::d128;
use crate::units::Unit;

/// Units, and functions you can use with them
pub mod units;
/// Turns a string into [`Token`]s
pub mod lexer;
/// Turns [`Token`]s into an [`AstNode`](parser::AstNode)
pub mod parser;
/// Turns an [`AstNode`](parser::AstNode) into a [`Number`]
pub mod evaluator;
mod lookup;

#[derive(Clone, Debug)]
/// A number with a `Unit`.
/// 
/// Example:
/// ```rust
/// use cpc::{eval,Number};
/// use cpc::units::Unit;
/// use decimal::d128;
/// 
/// let x = Number {
///   value: d128!(100),
///   unit: Unit::Meter,
/// };
/// ```
pub struct Number {
  /// The number part of a [`Number`] struct
  pub value: d128,
  /// The unit of a [`Number`] struct. This can be [`NoType`](units::UnitType::NoType)
  pub unit: Unit,
}

impl Number {
  pub fn new(value: d128, unit: Unit) -> Number {
    Number { value, unit }
  }
}
impl Display for Number {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    // 0.2/0.01 results in 2E+1, but if we add zero it becomes 20
    let fixed_value = self.value + d128!(0);
    let output = match self.unit {
      Unit::NoUnit => format!("{}", fixed_value),
      unit => format!("{} {:?}", fixed_value, unit),
    };
    return write!(f, "{}", output);
  }
}

#[derive(Clone, Debug, PartialEq)]
/// Math operators like [`Multiply`](Operator::Multiply), parentheses, etc.
pub enum Operator {
  Plus,
  Minus,
  Multiply,
  Divide,
  Modulo,
  Caret,
  LeftParen, // lexer only
  RightParen, // lexer only
}

#[derive(Clone, Debug, PartialEq)]
/// Unary operators like [`Percent`](UnaryOperator::Percent) and [`Factorial`](UnaryOperator::Factorial).
pub enum UnaryOperator {
  Percent,
  Factorial,
}

#[derive(Clone, Debug, PartialEq)]
/// A Text operator like [`To`](TextOperator::To) or [`Of`](TextOperator::Of).
pub enum TextOperator {
  To,
  Of,
}

#[derive(Clone, Debug, PartialEq)]
/// A named number like [`Million`](NamedNumber::Million).
pub enum NamedNumber {
  Hundred,
  Thousand,
  Million,
  Billion,
  Trillion,
  Quadrillion,
  Quintillion,
  Sextillion,
  Septillion,
  Octillion,
  Nonillion,
  Decillion,
  Undecillion,
  Duodecillion,
  Tredecillion,
  Quattuordecillion,
  Quindecillion,
  Sexdecillion,
  Septendecillion,
  Octodecillion,
  Novemdecillion,
  Vigintillion,
  Centillion,
  Googol,
}

#[derive(Clone, Debug, PartialEq)]
/// A constant like [`Pi`](Constant::Pi) or [`E`](Constant::E).
pub enum Constant {
  Pi,
  E,
}

#[derive(Clone, Debug, PartialEq)]
/// Functions identifiers like [`Sqrt`](FunctionIdentifier::Sqrt), [`Sin`](FunctionIdentifier::Sin), [`Round`](FunctionIdentifier::Round), etc.
pub enum FunctionIdentifier {
  Sqrt,
  Cbrt,

  Log,
  Ln,
  Exp,
  
  Round,
  Ceil,
  Floor,
  Abs,

  Sin,
  Cos,
  Tan,
}

#[derive(Clone, Debug, PartialEq)]
/// A temporary enum used by the [`lexer`] to later determine what [`Token`] it is.
/// 
/// For example, when a symbol like `%` is found, the lexer turns it into a
/// the [`PercentChar`](LexerKeyword::PercentChar) variant
/// and then later it checks the surrounding [`Token`]s and,
/// dependingon them, turns it into a [`Percent`](UnaryOperator::Percent) or
/// [`Modulo`](Operator::Modulo) [`Token`].
pub enum LexerKeyword {
  Per,
  PercentChar,
  In,
  DoubleQuotes,
  Mercury,
  Hg,
  PoundForce,
  Force,
  Revolution,
}

#[derive(Clone, Debug, PartialEq)]
/// A token like a [`Number`](Token::Number), [`Operator`](Token::Operator), [`Unit`](Token::Unit) etc.
/// 
/// Strings can be divided up into these tokens by the [`lexer`], and then put into the [`parser`].
pub enum Token {
  Operator(Operator),
  UnaryOperator(UnaryOperator),
  Number(d128),
  FunctionIdentifier(FunctionIdentifier),
  Constant(Constant),
  /// Used by the parser only
  Paren,
  /// Used by the lexer only
  Per,
  /// Used by the parser only
  LexerKeyword(LexerKeyword),
  TextOperator(TextOperator),
  NamedNumber(NamedNumber),
  /// The `-` symbol, specifically when used as `-5` and not `5-5`. Used by the parser only
  Negative,
  Unit(units::Unit),
}

#[macro_export]
macro_rules! numtok {
  ( $num:literal ) => {
    Token::Number(d128!($num));
  }
}

/// Evaluates a string into a resulting [`Number`].
/// 
/// Example:
/// ```rust
/// use cpc::eval;
/// use cpc::units::Unit;
/// 
/// match eval("3m + 1cm", true, Unit::Celsius, false) {
///     Ok(answer) => {
///         // answer: Number { value: 301, unit: Unit::Centimeter }
///         println!("Evaluated value: {} {:?}", answer.value, answer.unit)
///     },
///     Err(e) => {
///         println!("{}", e)
///     }
/// }
/// ```
pub fn eval(input: &str, allow_trailing_operators: bool, default_degree: Unit, verbose: bool) -> Result<Number, String> {

  let lex_start = Instant::now();

  match lexer::lex(input, allow_trailing_operators, default_degree) {
    Ok(tokens) => {
      let lex_time = Instant::now().duration_since(lex_start).as_nanos() as f32;
      if verbose == true { println!("Lexed TokenVector: {:?}", tokens); }

      let parse_start = Instant::now();
      match parser::parse(&tokens) {
        Ok(ast) => {
          let parse_time = Instant::now().duration_since(parse_start).as_nanos() as f32;
          if verbose == true { println!("Parsed AstNode: {:#?}", ast); }

          let eval_start = Instant::now();
          match evaluator::evaluate(&ast) {
            Ok(answer) => {
              let eval_time = Instant::now().duration_since(eval_start).as_nanos() as f32;

              if verbose == true {
                println!("Evaluated value: {} {:?}", answer.value, answer.unit);
                println!("\u{23f1}  {:.3}ms lexing", lex_time/1000.0/1000.0);
                println!("\u{23f1}  {:.3}ms parsing", parse_time/1000.0/1000.0);
                println!("\u{23f1}  {:.3}ms evaluation", eval_time/1000.0/1000.0);
              }

              return Ok(answer)
            },
            Err(e) => Err(format!("Eval error: {}", e)),
          }
        },
        Err(e) => Err(format!("Parsing error: {}", e)),
      }
    },
    Err(e) => Err(format!("Lexing error: {}", e)),
  }
}