use crate::units::Unit;
use decimal::d128;
use std::fmt::{self, Display};
use std::time::Instant;
pub mod evaluator;
#[rustfmt::skip]
pub mod lexer;
#[rustfmt::skip]
mod lookup;
pub mod parser;
#[rustfmt::skip]
pub mod units;
#[derive(Clone, Debug, PartialEq)]
pub struct Number {
pub value: d128,
pub unit: Unit,
}
impl Number {
pub const fn new(value: d128, unit: Unit) -> Number {
Number { value, unit }
}
pub fn get_simplified_value(&self) -> d128 {
let mut value = self.value.reduce();
value = value + d128!(0);
value
}
}
impl Display for Number {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = self.get_simplified_value();
let word = match self.value == d128!(1) {
true => self.unit.singular(),
false => self.unit.plural(),
};
let output = match word {
"" => format!("{value}"),
_ => format!("{value} {word}"),
};
write!(f, "{output}")
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Operator {
Plus,
Minus,
Multiply,
Divide,
Modulo,
Caret,
LeftParen, RightParen, }
#[derive(Clone, Debug, PartialEq)]
pub enum UnaryOperator {
Percent,
Factorial,
}
#[derive(Clone, Debug, PartialEq)]
pub enum TextOperator {
To,
Of,
}
#[derive(Clone, Debug, PartialEq)]
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)]
pub enum Constant {
Pi,
E,
}
#[derive(Clone, Debug, PartialEq)]
pub enum FunctionIdentifier {
Sqrt,
Cbrt,
Log,
Ln,
Exp,
Round,
Ceil,
Floor,
Abs,
Sin,
Cos,
Tan,
}
#[derive(Clone, Debug, PartialEq)]
pub enum LexerKeyword {
Per,
PercentChar,
In,
DoubleQuotes,
Mercury,
Hg,
PoundForce,
Force,
Revolution,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Operator(Operator),
UnaryOperator(UnaryOperator),
Number(d128),
FunctionIdentifier(FunctionIdentifier),
Constant(Constant),
Paren,
Per,
LexerKeyword(LexerKeyword),
TextOperator(TextOperator),
NamedNumber(NamedNumber),
Negative,
Unit(units::Unit),
}
#[macro_export]
macro_rules! numtok {
( $num:literal ) => {
Token::Number(d128!($num))
};
}
pub fn eval(
input: &str,
allow_trailing_operators: bool,
verbose: bool,
) -> Result<Number, String> {
let lex_start = Instant::now();
match lexer::lex(input, allow_trailing_operators) {
Ok(tokens) => {
let lex_time = Instant::now().duration_since(lex_start).as_nanos() as f32;
if verbose {
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 {
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 {
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
);
}
Ok(answer)
}
Err(e) => Err(format!("Eval error: {}", e)),
}
}
Err(e) => Err(format!("Parsing error: {}", e)),
}
}
Err(e) => Err(format!("Lexing error: {}", e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_eval(input: &str) -> Number {
eval(input, true, false).unwrap()
}
#[test]
fn test_simplify() {
assert_eq!(&default_eval("sin(pi)").to_string(), "0");
assert_eq!(&default_eval("0.2/0.01").to_string(), "20");
}
#[test]
fn test_evaluations() {
assert_eq!(default_eval("-2(-3)"), Number::new(d128!(6), Unit::NoUnit));
assert_eq!(default_eval("-2(3)"), Number::new(d128!(-6), Unit::NoUnit));
assert_eq!(default_eval("(3)-2"), Number::new(d128!(1), Unit::NoUnit));
assert_eq!(default_eval("-1km to m"), Number::new(d128!(-1000), Unit::Meter));
assert_eq!(default_eval("2*-3*0.5"), Number::new(d128!(-3), Unit::NoUnit));
assert_eq!(default_eval("-3^2"), Number::new(d128!(-9), Unit::NoUnit));
assert_eq!(default_eval("-1+2"), Number::new(d128!(1), Unit::NoUnit));
}
}