/* * Canonical Expressions * * AKA, simplified, normalized algebraic expressions. * */ use crate::sexpr::SExpr; use std::fmt; #[derive(Debug, Clone, PartialEq, PartialOrd)] pub enum NumericConstant { Pi, // 3.141592... E, // 2.718281... } impl NumericConstant { pub fn as_number(&self) -> CNumber { use CNumber::*; use NumericConstant::*; match self { Pi => Float(3.141592f64), E => Float(2.718281f64), } } pub fn as_string(&self) -> String { use NumericConstant::*; match self { Pi => "pi".to_string(), E => "e".to_string(), } } } #[derive(Clone, PartialEq, PartialOrd)] pub enum CNumber { // order here is important for sorting, etc Integer(i64), Rational(i64, i64), Float(f64), } impl CNumber { pub fn to_sexpr(&self) -> Result { match self { CNumber::Integer(v) => Ok(SExpr::SInteger(*v)), CNumber::Rational(a, b) => Ok(SExpr::SList(vec![ SExpr::SIdentifier("/".to_string()), SExpr::SInteger(*a), SExpr::SInteger(*b), ])), CNumber::Float(v) => Ok(SExpr::SFloat(*v)), } } } #[derive(Clone, PartialEq, PartialOrd)] pub enum CExpr { // order here is important for sorting, etc Number(CNumber), Constant(NumericConstant), Symbol(String), Sum(Option, Vec), Product(Option, Vec), Exponent(Box, Box), Factorial(Box), UnaryFunction(String, Box), } impl CExpr { pub fn from_sexpr(sexpr: &SExpr) -> Result { // not all cases are handled; some atoms are covered trivialy match sexpr { SExpr::SNull => Err("null not handled".to_string()), SExpr::SBoolean(_) => Err("booleans not handled".to_string()), SExpr::SInteger(v) => Ok(CExpr::Number(CNumber::Integer(*v))), SExpr::SFloat(v) => Ok(CExpr::Number(CNumber::Float(*v))), SExpr::SString(_) => Err("null not handled".to_string()), SExpr::SIdentifier(v) => match v.as_str() { "pi" => Ok(CExpr::Constant(NumericConstant::Pi)), "e" => Ok(CExpr::Constant(NumericConstant::E)), _ => Ok(CExpr::Symbol(v.to_string())), }, SExpr::SList(l) => CExpr::from_sexpr_list(l), } } pub fn from_sexpr_list(list: &Vec) -> Result { // https://adventures.michaelfbryan.com/posts/daily/slice-patterns/ if let [SExpr::SIdentifier(ident), rest @ ..] = list.as_slice() { match (ident.as_str(), rest.len()) { ("factorial", 1) => Ok(CExpr::Factorial(Box::new(CExpr::from_sexpr(&rest[0])?))), ("cos" | "sin" | "tan", 1) => Ok(CExpr::UnaryFunction( ident.to_string(), Box::new(CExpr::from_sexpr(&rest[0])?), )), ("^", 2) => { let base = CExpr::from_sexpr(&rest[0])?; let power = CExpr::from_sexpr(&rest[1])?; use CExpr::*; use CNumber::*; match (base, power) { (Number(Integer(0)), _) => Ok(Number(Integer(0))), (Number(Integer(1)), _) => Ok(Number(Integer(1))), (_, Number(Integer(0))) => Ok(Number(Integer(1))), (base, Number(Integer(1))) => Ok(base), (base, power) => Ok(Exponent(Box::new(base), Box::new(power))), } } ("/", 2) => { use CExpr::*; use CNumber::*; use SExpr::*; match (&rest[0], &rest[1]) { (_, SInteger(0)) => Err("division by zero".to_string()), (SInteger(0), _) => Ok(Number(Integer(0))), (se, SInteger(1)) => CExpr::from_sexpr(se), (SInteger(a), SInteger(b)) if a == b => Ok(Number(Integer(1))), (SInteger(a), SInteger(b)) if a % b == 0 => Ok(Number(Integer(a / b))), (SInteger(a), SInteger(b)) => Ok(Number(Rational(*a, *b))), (SInteger(1), b) => { let denom = CExpr::from_sexpr(b)?; Ok(Exponent(Box::new(denom), Box::new(Number(Integer(-1))))) } (a, b) => { let (numer, denom) = (CExpr::from_sexpr(a)?, CExpr::from_sexpr(b)?); CExpr::new_product(vec![ numer, Exponent(Box::new(denom), Box::new(Number(Integer(-1)))), ]) } } } // TODO: how to make range unbounded? or less bounded? ("+", 2..=5000) => { CExpr::new_sum(rest.iter().map(|v| CExpr::from_sexpr(v)).collect::, String, >>( )?) } ("*", 2..=5000) => { CExpr::new_product(rest.iter().map(|v| CExpr::from_sexpr(v)).collect::, String, >>( )?) } ("-", 2) => { let a = CExpr::from_sexpr(&rest[0])?; let b = CExpr::from_sexpr(&rest[1])?; use CExpr::*; use CNumber::*; match (a, b) { (expr, Number(Integer(0))) => Ok(expr), (Number(Integer(a)), Number(Integer(b))) => Ok(Number(Integer(a - b))), (a, b) => CExpr::new_sum(vec![ a, CExpr::new_product(vec![Number(Integer(-1)), b])?, ]), } } ("factorial" | "^" | "cos" | "sin" | "tan", count) => { Err(format!("wrong number of arguments to {}: {}", ident, count)) } _ => Err(format!("procedure not handled: {}", ident)), } } else { Err(format!("S-Expr pattern not handled: {:?}", list)) } } pub fn new_sum(mut list: Vec) -> Result { use CExpr::*; use CNumber::*; list.sort_by(|a, b| a.partial_cmp(b).unwrap()); match list.as_slice() { [Number(Integer(0)), e] => Ok(e.clone()), // XXX: remove clone() [Number(Integer(a)), Number(Integer(b))] => Ok(Number(Integer(a + b))), [Number(Integer(a)), Number(Rational(n, d))] => Ok(Number(Rational(n + a * d, *d))), _ => Ok(Sum(None, list)), } } pub fn new_product(mut list: Vec) -> Result { use CExpr::*; use CNumber::*; list.sort_by(|a, b| a.partial_cmp(b).unwrap()); match list.as_slice() { [Number(Integer(1)), e] => Ok(e.clone()), // XXX: remove clone() [Number(Integer(a)), Number(Integer(b))] => Ok(Number(Integer(a * b))), [Number(Integer(a)), Number(Rational(n, d))] => Ok(Number(Rational(a * n, *d))), _ => Ok(Product(None, list)), } } pub fn to_sexpr(&self) -> Result { match self { CExpr::Symbol(s) => Ok(SExpr::SIdentifier(s.to_string())), CExpr::Number(n) => n.to_sexpr(), CExpr::Constant(n) => Ok(SExpr::SIdentifier(n.as_string())), CExpr::Sum(n, l) => { let mut list = l .iter() .map(|v| v.to_sexpr()) .collect::, String>>()?; list.insert(0, SExpr::SIdentifier("+".to_string())); if let Some(num) = n { list.insert(1, num.to_sexpr()?); } Ok(SExpr::SList(list)) } CExpr::Product(n, l) => { let mut list = l .iter() .map(|v| v.to_sexpr()) .collect::, String>>()?; list.insert(0, SExpr::SIdentifier("*".to_string())); if let Some(num) = n { list.insert(1, num.to_sexpr()?); } Ok(SExpr::SList(list)) } CExpr::Exponent(a, b) => Ok(SExpr::SList(vec![ SExpr::SIdentifier("^".to_string()), a.to_sexpr()?, b.to_sexpr()?, ])), CExpr::Factorial(v) => Ok(SExpr::SList(vec![ SExpr::SIdentifier("factorial".to_string()), v.to_sexpr()?, ])), CExpr::UnaryFunction(s, v) => Ok(SExpr::SList(vec![ SExpr::SIdentifier(s.to_string()), v.to_sexpr()?, ])), } } pub fn from_str(raw: &str) -> Result { let ast = SExpr::from_str(raw)?; CExpr::from_sexpr(&ast) } } impl fmt::Display for CExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.to_sexpr() { Ok(ast) => ast.fmt(f), Err(_) => Err(std::fmt::Error), } } }