/* * Canonical Expressions * * AKA, simplified, normalized algebraic expressions. * */ use std::fmt; use crate::sexpr::SExpr; /* pub enum NumericConstant { Pi, // 3.141592... E, // 2.718281... } */ #[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), Symbol(String), Sum(Option, Vec), Product(Option, Vec), Exponent(Box, Box), Factorial(Box), UnaryFunction(String, Box), } impl CExpr { /* fn sort_type_val(&self) -> u8 { use CExpr::*; match self { Number(Integer(_)) => 1, Number(Rational(_)) => 2, Number(Float(_)) => 3, Symbol(_) => 3, Sum(Some(_), _) => 4, Sum(None, _) => 4, Product(Some(_), _) => 4, Product(None, _) => 4, Exponent(_, _) => 4, Factorial(_) => 4, UnaryFunction(_, _) => 4, } } */ 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) => 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) => { if let (SExpr::SInteger(numer), SExpr::SInteger(denom)) = (&rest[0], &rest[1]) { match (numer, denom) { (_, 0) => Err("division by zero".to_string()), (0, _) => Ok(CExpr::Number(CNumber::Integer(0))), (a, 1) => Ok(CExpr::Number(CNumber::Integer(*a))), (a, b) if a == b => Ok(CExpr::Number(CNumber::Integer(1))), (a, b) if a % b == 0 => Ok(CExpr::Number(CNumber::Integer(a/b))), _ => Ok(CExpr::Number(CNumber::Rational(*numer, *denom))), } } else { Err("only integers in fractions supported".to_string()) } }, // 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>>()?), // TODO: subtraction, division ("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::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), } } }