use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{One, ToPrimitive, Zero};
use std::{
fmt::Display,
ops::{Add, Div, Mul, Sub},
};
#[derive(Debug, PartialEq, Clone)]
pub enum Number {
NaturalNumber(BigInt),
DecimalNumber(BigRational),
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Operator {
Add,
Sub,
Mul,
Div,
Pow,
Une,
Fac,
Eql,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Associate {
LeftAssociative,
RightAssociative,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Bracket {
Open,
Close,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Token<'a> {
Operand(Number),
Operator(Operator),
Bracket(Bracket),
Function(MathFunction),
Comma,
Variable(&'a str),
SemiColon,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum MathFunction {
Sin,
Cos,
Tan,
ASin,
ACos,
ATan,
Ln,
Log,
Abs,
Sqrt,
Max,
Min,
Floor,
Ceil,
Round,
Exp,
Pdf,
Cdf,
None,
}
impl Token<'_> {
const fn from_operator(c: char) -> Option<Token<'static>> {
match c {
'+' => Some(Token::Operator(Operator::Add)),
'-' => Some(Token::Operator(Operator::Sub)),
'*' | '×' => Some(Token::Operator(Operator::Mul)),
'/' | '÷' => Some(Token::Operator(Operator::Div)),
'^' => Some(Token::Operator(Operator::Pow)),
'#' => Some(Token::Operator(Operator::Une)),
'!' => Some(Token::Operator(Operator::Fac)),
'=' => Some(Token::Operator(Operator::Eql)),
_ => None,
}
}
const fn from_bracket(c: char) -> Option<Token<'static>> {
match c {
'(' | '[' => Some(Token::Bracket(Bracket::Open)),
')' | ']' => Some(Token::Bracket(Bracket::Close)),
_ => None,
}
}
fn get_some(fun: &str) -> Option<MathFunction> {
match fun.to_lowercase().as_str() {
"sin" => Some(MathFunction::Sin),
"cos" => Some(MathFunction::Cos),
"tan" => Some(MathFunction::Tan),
"asin" => Some(MathFunction::ASin),
"acos" => Some(MathFunction::ACos),
"atan" => Some(MathFunction::ATan),
"ln" => Some(MathFunction::Ln),
"log" | "log10" => Some(MathFunction::Log),
"abs" => Some(MathFunction::Abs),
"sqrt" => Some(MathFunction::Sqrt),
"max" => Some(MathFunction::Max),
"min" => Some(MathFunction::Min),
"floor" => Some(MathFunction::Floor),
"ceil" => Some(MathFunction::Ceil),
"round" => Some(MathFunction::Round),
"exp" => Some(MathFunction::Exp),
"pdf" => Some(MathFunction::Pdf),
"cdf" => Some(MathFunction::Cdf),
&_ => None,
}
}
#[must_use]
pub fn tokenize(t: &str) -> Option<Token<'_>> {
match t.chars().next() {
Some(s) => match s {
c @ ('+' | '-' | '*' | '/' | '^' | '!' | '=' | '×' | '÷') => {
return Some(Token::from_operator(c).unwrap())
}
b @ ('(' | ')' | '[' | ']') => return Some(Token::from_bracket(b).unwrap()),
',' => return Some(Token::Comma),
';' => return Some(Token::SemiColon),
_ => (), },
None => return None,
}
if let Ok(v) = t.parse::<BigInt>() {
return Some(Token::Operand(Number::NaturalNumber(v)));
}
if let Some(v) = parse_decimal_literal(t) {
return Some(Token::Operand(Number::DecimalNumber(v)));
}
if let Some(fun) = Token::get_some(t) {
return Some(Token::Function(fun));
}
Some(Token::Variable(t))
}
fn operator_priority(o: Token) -> (u8, Associate) {
match o {
Token::Operator(Operator::Add | Operator::Sub) => (1, Associate::LeftAssociative),
Token::Operator(Operator::Mul | Operator::Div) => (2, Associate::LeftAssociative),
Token::Operator(Operator::Pow) => (3, Associate::RightAssociative),
Token::Operator(Operator::Une) => (4, Associate::RightAssociative),
Token::Operator(Operator::Fac) => (5, Associate::LeftAssociative),
Token::Operator(Operator::Eql) => (0, Associate::RightAssociative),
_ => panic!("Operator '{o}' not recognised. This must not happen!"),
}
}
#[must_use]
pub fn compare_operator_priority(op1: Token, op2: Token) -> bool {
let v_op1: (u8, Associate) = self::Token::operator_priority(op1);
let v_op2: (u8, Associate) = self::Token::operator_priority(op2);
v_op1.1 == Associate::LeftAssociative && v_op1.0 <= v_op2.0
|| v_op1.1 == Associate::RightAssociative && v_op1.0 < v_op2.0
}
}
impl Display for Number {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Number::NaturalNumber(v) => write!(f, "{v}"),
Number::DecimalNumber(v) => {
if v.denom().is_one() {
write!(f, "{}", v.to_integer())
} else if let Some(fl) = v.to_f64() {
write!(f, "{fl}")
} else {
write!(f, "{}/{}", v.numer(), v.denom())
}
}
}
}
}
fn apply_functional_token_operation<NF, DF>(ln: Number, rn: Number, nf: NF, df: DF) -> Number
where
NF: Fn(BigInt, BigInt) -> BigInt,
DF: Fn(BigRational, BigRational) -> BigRational,
{
match (ln, rn.clone()) {
(Number::NaturalNumber(v1), Number::NaturalNumber(v2)) => Number::NaturalNumber(nf(v1, v2)),
(Number::NaturalNumber(v1), Number::DecimalNumber(v2)) => {
Number::DecimalNumber(df(BigRational::from(v1), v2))
}
(Number::DecimalNumber(v1), Number::NaturalNumber(v2)) => {
Number::DecimalNumber(df(v1, BigRational::from(v2)))
}
(Number::DecimalNumber(v1), Number::DecimalNumber(v2)) => Number::DecimalNumber(df(v1, v2)),
}
}
impl Add for Number {
type Output = Number;
fn add(self, rhs: Self) -> Self::Output {
apply_functional_token_operation(self, rhs, |a, b| a + b, |a, b| a + b)
}
}
impl Sub for Number {
type Output = Number;
fn sub(self, rhs: Self) -> Self::Output {
apply_functional_token_operation(self, rhs, |a, b| a - b, |a, b| a - b)
}
}
impl Mul for Number {
type Output = Number;
fn mul(self, rhs: Self) -> Self::Output {
apply_functional_token_operation(self, rhs, |a, b| a * b, |a, b| a * b)
}
}
impl Div for Number {
type Output = Number;
fn div(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Number::NaturalNumber(v1), Number::NaturalNumber(v2)) => {
Number::DecimalNumber(BigRational::new(v1, v2))
}
(Number::NaturalNumber(v1), Number::DecimalNumber(v2)) => {
Number::DecimalNumber(BigRational::from(v1) / v2)
}
(Number::DecimalNumber(v1), Number::NaturalNumber(v2)) => {
Number::DecimalNumber(v1 / BigRational::from(v2))
}
(Number::DecimalNumber(v1), Number::DecimalNumber(v2)) => {
Number::DecimalNumber(v1 / v2)
}
}
}
}
impl PartialOrd for Number {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Number::NaturalNumber(v1), Number::NaturalNumber(v2)) => v1.partial_cmp(&v2),
(Number::NaturalNumber(v1), Number::DecimalNumber(v2)) => {
BigRational::from(v1.clone()).partial_cmp(v2)
}
(Number::DecimalNumber(v1), Number::NaturalNumber(v2)) => {
v1.partial_cmp(&BigRational::from(v2.clone()))
}
(Number::DecimalNumber(v1), Number::DecimalNumber(v2)) => v1.partial_cmp(&v2),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ConversionError {
#[error("value '{value}' is out of range for target type {target}")]
OutOfRange {
value: String,
target: &'static str,
},
}
impl From<Number> for BigInt {
fn from(n: Number) -> BigInt {
match n {
Number::NaturalNumber(v) => v,
Number::DecimalNumber(v) => v.to_integer(),
}
}
}
impl TryFrom<Number> for f64 {
type Error = ConversionError;
fn try_from(n: Number) -> Result<Self, Self::Error> {
let value = match &n {
Number::NaturalNumber(v) => v.to_f64(),
Number::DecimalNumber(v) => v.to_f64(),
};
value
.filter(|f| f.is_finite())
.ok_or_else(|| ConversionError::OutOfRange {
value: n.to_string(),
target: "f64",
})
}
}
impl TryFrom<Number> for i32 {
type Error = ConversionError;
fn try_from(n: Number) -> Result<Self, Self::Error> {
let value: BigInt = n.into();
value.to_i32().ok_or_else(|| ConversionError::OutOfRange {
value: value.to_string(),
target: "i32",
})
}
}
impl TryFrom<Number> for i64 {
type Error = ConversionError;
fn try_from(n: Number) -> Result<Self, Self::Error> {
let value: BigInt = n.into();
value.to_i64().ok_or_else(|| ConversionError::OutOfRange {
value: value.to_string(),
target: "i64",
})
}
}
impl TryFrom<Number> for i128 {
type Error = ConversionError;
fn try_from(n: Number) -> Result<Self, Self::Error> {
let value: BigInt = n.into();
value.to_i128().ok_or_else(|| ConversionError::OutOfRange {
value: value.to_string(),
target: "i128",
})
}
}
impl Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Operator::Add => write!(f, "+"),
Operator::Sub => write!(f, "-"),
Operator::Mul => write!(f, "*"),
Operator::Div => write!(f, "/"),
Operator::Pow => write!(f, "^"),
Operator::Une => write!(f, "#"),
Operator::Fac => write!(f, "!"),
Operator::Eql => write!(f, "="),
}
}
}
impl Display for Bracket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::Open => write!(f, "("),
Self::Close => write!(f, ")"),
}
}
}
impl Display for MathFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", *self)
}
}
impl Display for Token<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::Operand(v) => write!(f, "({v})"),
Token::Operator(v) => write!(f, "({v})"),
Token::Bracket(v) => write!(f, "({v})"),
Token::Function(v) => write!(f, "({v})"),
Token::Variable(v) => write!(f, "({v})"),
Token::Comma => write!(f, "(,)"),
Token::SemiColon => write!(f, "(;)"),
}
}
}
fn parse_decimal_literal(literal: &str) -> Option<BigRational> {
let (whole, fractional) = literal.split_once('.')?;
let whole = if whole.is_empty() {
BigInt::zero()
} else {
whole.parse::<BigInt>().ok()?
};
let fractional = if fractional.is_empty() {
BigInt::zero()
} else {
fractional.parse::<BigInt>().ok()?
};
let fractional_digits = literal
.split_once('.')
.map_or(0, |(_, digits)| digits.len());
let mut exact_scale = BigInt::one();
for _ in 0..fractional_digits {
exact_scale *= 10_u8;
}
Some(BigRational::new(
whole * exact_scale.clone() + fractional,
exact_scale,
))
}
#[cfg(test)]
mod tests {
use num::One;
use super::*;
#[test]
fn test_tokenise_operators() {
let v = vec!["1", "+", "2.1"];
assert_eq!(Token::tokenize(v[1]), Some(Token::Operator(Operator::Add)));
assert_eq!(
Token::tokenize(v[0]),
Some(Token::Operand(Number::NaturalNumber(One::one())))
);
assert_eq!(
Token::tokenize(v[2]),
Some(Token::Operand(Number::DecimalNumber(BigRational::new(
BigInt::from(21),
BigInt::from(10)
))))
);
}
#[test]
fn test_from_operator_valid() {
assert_eq!(
Token::from_operator('+'),
Some(Token::Operator(Operator::Add))
);
assert_eq!(
Token::from_operator('-'),
Some(Token::Operator(Operator::Sub))
);
assert_eq!(
Token::from_operator('*'),
Some(Token::Operator(Operator::Mul))
);
assert_eq!(
Token::from_operator('×'),
Some(Token::Operator(Operator::Mul))
);
assert_eq!(
Token::from_operator('/'),
Some(Token::Operator(Operator::Div))
);
assert_eq!(
Token::from_operator('÷'),
Some(Token::Operator(Operator::Div))
);
assert_eq!(
Token::from_operator('!'),
Some(Token::Operator(Operator::Fac))
);
}
#[test]
fn test_from_operator_invalid() {
assert_eq!(Token::from_operator('a'), None);
assert_eq!(Token::from_operator('1'), None);
assert_eq!(Token::from_operator('~'), None);
}
#[test]
fn test_tokenize_valid() {
assert_eq!(Token::tokenize("+"), Some(Token::Operator(Operator::Add)));
assert_eq!(
Token::tokenize("100"),
Some(Token::Operand(Number::NaturalNumber(BigInt::from(100))))
);
assert_eq!(
Token::tokenize("3.14"),
Some(Token::Operand(Number::DecimalNumber(BigRational::new(
BigInt::from(157),
BigInt::from(50)
))))
);
assert_eq!(Token::tokenize("("), Some(Token::Bracket(Bracket::Open)));
}
#[test]
fn test_tokenize_vec_valid() {
assert_eq!(Token::tokenize("+"), Some(Token::Operator(Operator::Add)));
assert_eq!(
Token::tokenize("100"),
Some(Token::Operand(Number::NaturalNumber(BigInt::from(100))))
);
assert_eq!(
Token::tokenize("3.14"),
Some(Token::Operand(Number::DecimalNumber(BigRational::new(
BigInt::from(157),
BigInt::from(50)
))))
);
assert_eq!(Token::tokenize("("), Some(Token::Bracket(Bracket::Open)));
}
#[test]
fn test_tryfrom_i32_out_of_range_is_err_not_panic() {
let big = Number::NaturalNumber(BigInt::from(2).pow(100));
assert!(i32::try_from(big).is_err());
}
#[test]
fn test_tryfrom_i64_in_range_ok() {
let n = Number::NaturalNumber(BigInt::from(3_265_920));
assert_eq!(i64::try_from(n).unwrap(), 3_265_920_i64);
}
#[test]
fn test_decimal_to_bigint_is_exact_for_large_values() {
let big = BigInt::from(10).pow(30) + BigInt::from(1);
let n = Number::DecimalNumber(BigRational::from_integer(big.clone()));
assert_eq!(BigInt::from(n), big);
}
#[test]
fn test_decimal_to_bigint_truncates_toward_zero() {
let pos = Number::DecimalNumber(BigRational::new(BigInt::from(7), BigInt::from(2)));
assert_eq!(BigInt::from(pos), BigInt::from(3));
let neg = Number::DecimalNumber(BigRational::new(BigInt::from(-7), BigInt::from(2)));
assert_eq!(BigInt::from(neg), BigInt::from(-3));
}
#[test]
fn test_tryfrom_f64_ok_and_overflow_is_err() {
let half = Number::DecimalNumber(BigRational::new(BigInt::from(1), BigInt::from(2)));
assert!((f64::try_from(half).unwrap() - 0.5_f64).abs() < f64::EPSILON);
let huge = Number::NaturalNumber(BigInt::from(10).pow(400));
assert!(f64::try_from(huge).is_err());
}
#[test]
fn test_operator_priority() {
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Add)),
(1, Associate::LeftAssociative)
);
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Sub)),
(1, Associate::LeftAssociative)
);
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Mul)),
(2, Associate::LeftAssociative)
);
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Div)),
(2, Associate::LeftAssociative)
);
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Pow)),
(3, Associate::RightAssociative)
);
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Une)),
(4, Associate::RightAssociative)
);
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Fac)),
(5, Associate::LeftAssociative)
);
}
#[test]
fn test_operator_priority_for_assignment() {
assert_eq!(
Token::operator_priority(Token::Operator(Operator::Eql)),
(0, Associate::RightAssociative)
);
}
#[test]
fn test_tokenize_edge_cases() {
assert_eq!(Token::tokenize(""), None);
assert_eq!(Token::tokenize("["), Some(Token::Bracket(Bracket::Open)));
assert_eq!(Token::tokenize("]"), Some(Token::Bracket(Bracket::Close)));
assert_eq!(Token::tokenize(";"), Some(Token::SemiColon));
assert_eq!(Token::tokenize(","), Some(Token::Comma));
assert_eq!(Token::tokenize("×"), Some(Token::Operator(Operator::Mul)));
assert_eq!(Token::tokenize("÷"), Some(Token::Operator(Operator::Div)));
assert_eq!(Token::tokenize("foo"), Some(Token::Variable("foo")));
}
#[test]
fn test_tokenize_functions_are_case_insensitive() {
assert_eq!(
Token::tokenize("SIN"),
Some(Token::Function(MathFunction::Sin))
);
assert_eq!(
Token::tokenize("Cos"),
Some(Token::Function(MathFunction::Cos))
);
assert_eq!(
Token::tokenize("log10"),
Some(Token::Function(MathFunction::Log))
);
}
#[test]
fn test_parse_decimal_literal_variants() {
assert_eq!(
parse_decimal_literal(".5"),
Some(BigRational::new(BigInt::from(1), BigInt::from(2)))
);
assert_eq!(
parse_decimal_literal("1."),
Some(BigRational::from_integer(BigInt::from(1)))
);
assert_eq!(
parse_decimal_literal("3.14"),
Some(BigRational::new(BigInt::from(157), BigInt::from(50)))
);
assert_eq!(
parse_decimal_literal("0.001"),
Some(BigRational::new(BigInt::from(1), BigInt::from(1000)))
);
assert_eq!(parse_decimal_literal("42"), None);
}
#[test]
fn test_number_display() {
assert_eq!(Number::NaturalNumber(BigInt::from(5)).to_string(), "5");
assert_eq!(
Number::DecimalNumber(BigRational::new(BigInt::from(4), BigInt::from(2))).to_string(),
"2"
);
assert_eq!(
Number::DecimalNumber(BigRational::new(BigInt::from(1), BigInt::from(2))).to_string(),
"0.5"
);
let third = Number::DecimalNumber(BigRational::new(BigInt::from(1), BigInt::from(3)));
assert_eq!(third.to_string(), format!("{}", 1.0_f64 / 3.0));
}
#[test]
fn test_conversion_error_reports_target_type() {
let big = Number::NaturalNumber(BigInt::from(2).pow(100));
let msg = i32::try_from(big).unwrap_err().to_string();
assert!(msg.contains("i32"), "message was: {msg}");
assert!(msg.contains("out of range"), "message was: {msg}");
}
#[test]
fn test_tryfrom_ok_paths() {
assert_eq!(
i32::try_from(Number::NaturalNumber(BigInt::from(42))).unwrap(),
42_i32
);
assert_eq!(
i32::try_from(Number::DecimalNumber(BigRational::new(
BigInt::from(7),
BigInt::from(2)
)))
.unwrap(),
3_i32
);
assert_eq!(
i128::try_from(Number::NaturalNumber(BigInt::from(2).pow(70))).unwrap(),
1_180_591_620_717_411_303_424_i128
);
assert!(
(f64::try_from(Number::NaturalNumber(BigInt::from(10))).unwrap() - 10.0).abs()
< f64::EPSILON
);
}
}