Represent function using an enum

We only support a limited set of function, so we may as well put them in an
enum instead of passing around strings.  This allows us to simplify match
statements, eliminating the case of unsupported functions.
This commit is contained in:
Colin Benner 2018-11-22 17:21:15 +01:00 committed by Tiffany Bennett
parent 1ce838983d
commit f805f5d180
4 changed files with 114 additions and 43 deletions

View file

@ -44,10 +44,88 @@ pub enum Expr {
Equals(Box<Expr>, Box<Expr>),
Suffix(Degree, Box<Expr>),
Of(String, Box<Expr>),
Call(String, Vec<Expr>),
Call(Function, Vec<Expr>),
Error(String),
}
#[derive(Debug, Clone)]
pub enum Function {
Sqrt,
Exp,
Ln,
Log2,
Log10,
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
Sinh,
Cosh,
Tanh,
Asinh,
Acosh,
Atanh,
Log,
Hypot,
Atan2,
}
impl Function {
pub fn name(&self) -> &str {
match *self {
Function::Sqrt => "sqrt",
Function::Exp => "exp",
Function::Ln => "ln",
Function::Log2 => "log2",
Function::Log10 => "log10",
Function::Sin => "sin",
Function::Cos => "cos",
Function::Tan => "tan",
Function::Asin => "asin",
Function::Acos => "acos",
Function::Atan => "atan",
Function::Sinh => "sinh",
Function::Cosh => "cosh",
Function::Tanh => "tanh",
Function::Asinh => "asinh",
Function::Acosh => "acosh",
Function::Atanh => "atanh",
Function::Log => "log",
Function::Hypot => "hypot",
Function::Atan2 => "atan2",
}
}
pub fn from_name(s: &str) -> Option<Self> {
let func = match s {
"sqrt" => Function::Sqrt,
"exp" => Function::Exp,
"ln" => Function::Ln,
"log2" => Function::Log2,
"log10" => Function::Log10,
"sin" => Function::Sin,
"cos" => Function::Cos,
"tan" => Function::Tan,
"asin" => Function::Asin,
"acos" => Function::Acos,
"atan" => Function::Atan,
"sinh" => Function::Sinh,
"cosh" => Function::Cosh,
"tanh" => Function::Tanh,
"asinh" => Function::Asinh,
"acosh" => Function::Acosh,
"atanh" => Function::Atanh,
"log" => Function::Log,
"hypot" => Function::Hypot,
"atan2" => Function::Atan2,
_ => return None,
};
Some(func)
}
}
#[derive(Debug, Clone)]
pub enum Conversion {
None,
@ -219,8 +297,8 @@ impl fmt::Display for Expr {
}
Ok(())
},
Expr::Call(ref name, ref args) => {
try!(write!(fmt, "{}(", name));
Expr::Call(ref func, ref args) => {
try!(write!(fmt, "{}(", func.name()));
if let Some(first) = args.first() {
try!(recurse(first, fmt, Prec::Equals));
}
@ -320,6 +398,7 @@ impl fmt::Display for DateToken {
#[cfg(test)]
mod test {
use super::Expr::{self, *};
use super::Function;
fn check<T: ::std::fmt::Display>(e: T, expected: &str) {
assert_eq!(e.to_string(), expected);
@ -333,9 +412,9 @@ mod test {
#[test]
fn test_display_call() {
check(Call("f".into(), vec![]), "f()");
check(Call("f".into(), vec![1.into()]), "f(1)");
check(Call("f".into(), vec![1.into(), 2.into()]), "f(1, 2)");
check(Call("f".into(), vec![1.into(), 2.into(), 3.into()]), "f(1, 2, 3)");
check(Call(Function::Sin, vec![]), "sin()");
check(Call(Function::Sin, vec![1.into()]), "sin(1)");
check(Call(Function::Sin, vec![1.into(), 2.into()]), "sin(1, 2)");
check(Call(Function::Sin, vec![1.into(), 2.into(), 3.into()]), "sin(1, 2, 3)");
}
}

View file

@ -6,7 +6,7 @@ use std::collections::BTreeMap;
use number::{Number, Dim, NumberParts, pow};
use num::{Num, Int};
use date;
use ast::{Expr, Query, Conversion, Digits};
use ast::{Expr, Query, Conversion, Digits, Function};
use std::rc::Rc;
use factorize::{factorize, Factors};
use value::{Value, Show};
@ -137,7 +137,7 @@ impl Context {
}
})
},
Expr::Call(ref name, ref args) => {
Expr::Call(ref func, ref args) => {
let args = try!(
args.iter()
.map(|x| self.eval(x))
@ -187,23 +187,23 @@ impl Context {
}
}}
match &**name {
"sqrt" => func!(fn sqrt(num: Number) {
match func {
Function::Sqrt => func!(fn sqrt(num: Number) {
num.root(2).map(Value::Number)
}),
"exp" => func!(fn exp(num: Number) {
Function::Exp => func!(fn exp(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().exp()),
unit: num.unit.clone(),
}))
}),
"ln" => func!(fn ln(num: Number) {
Function::Ln => func!(fn ln(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().ln()),
unit: num.unit.clone(),
}))
}),
"log" => func!(fn log(num: Number, base: Number) {
Function::Log => func!(fn log(num: Number, base: Number) {
if !base.unit.is_empty() {
Err("Base must be dimensionless".to_string())
} else {
@ -214,19 +214,19 @@ impl Context {
}))
}
}),
"log2" => func!(fn log2(num: Number) {
Function::Log2 => func!(fn log2(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().log2()),
unit: num.unit.clone(),
}))
}),
"log10" => func!(fn ln(num: Number) {
Function::Log10 => func!(fn ln(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().log10()),
unit: num.unit.clone(),
}))
}),
"hypot" => func!(fn hypot(x: Number, y: Number) {
Function::Hypot => func!(fn hypot(x: Number, y: Number) {
if x.unit != y.unit {
Err("Arguments to hypot must have matching dimensionality".to_string())
} else {
@ -236,43 +236,43 @@ impl Context {
}))
}
}),
"sin" => func!(fn sin(num: Number) {
Function::Sin => func!(fn sin(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().sin()),
unit: num.unit.clone(),
}))
}),
"cos" => func!(fn cos(num: Number) {
Function::Cos => func!(fn cos(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().cos()),
unit: num.unit.clone(),
}))
}),
"tan" => func!(fn tan(num: Number) {
Function::Tan => func!(fn tan(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().tan()),
unit: num.unit.clone(),
}))
}),
"asin" => func!(fn asin(num: Number) {
Function::Asin => func!(fn asin(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().asin()),
unit: num.unit.clone(),
}))
}),
"acos" => func!(fn acos(num: Number) {
Function::Acos => func!(fn acos(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().acos()),
unit: num.unit.clone(),
}))
}),
"atan" => func!(fn atan(num: Number) {
Function::Atan => func!(fn atan(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().atan()),
unit: num.unit.clone(),
}))
}),
"atan2" => func!(fn atan2(x: Number, y: Number) {
Function::Atan2 => func!(fn atan2(x: Number, y: Number) {
if x.unit != y.unit {
Err("Arguments to atan2 must have matching dimensionality".to_string())
} else {
@ -283,45 +283,42 @@ impl Context {
}))
}
}),
"sinh" => func!(fn sinh(num: Number) {
Function::Sinh => func!(fn sinh(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().sinh()),
unit: num.unit.clone(),
}))
}),
"cosh" => func!(fn cosh(num: Number) {
Function::Cosh => func!(fn cosh(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().cosh()),
unit: num.unit.clone(),
}))
}),
"tanh" => func!(fn tanh(num: Number) {
Function::Tanh => func!(fn tanh(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().tanh()),
unit: num.unit.clone(),
}))
}),
"asinh" => func!(fn asinh(num: Number) {
Function::Asinh => func!(fn asinh(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().asinh()),
unit: num.unit.clone(),
}))
}),
"acosh" => func!(fn acosh(num: Number) {
Function::Acosh => func!(fn acosh(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().acosh()),
unit: num.unit.clone(),
}))
}),
"atanh" => func!(fn atanh(num: Number) {
Function::Atanh => func!(fn atanh(num: Number) {
Ok(Value::Number(Number {
value: Num::Float(num.value.to_f64().atanh()),
unit: num.unit.clone(),
}))
}),
_ => Err(QueryError::Generic(format!(
"Function not found: {}", name
)))
}
},
Expr::Error(ref e) => Err(QueryError::Generic(e.clone())),

View file

@ -203,8 +203,8 @@ impl ExprReply {
literal!(")");
}
},
Expr::Call(ref name, ref args) => {
literal!(format!("{}(", name));
Expr::Call(ref func, ref args) => {
literal!(format!("{}(", func.name()));
if let Some(first) = args.first() {
recurse(first, parts, Prec::Equals);
}

View file

@ -429,12 +429,7 @@ impl<'a> Iterator for TokenIterator<'a> {
pub type Iter<'a> = Peekable<TokenIterator<'a>>;
fn is_func(name: &str) -> bool {
match name {
"sqrt" | "exp" | "ln" | "log" | "log2" | "log10" | "hypot" | "sin" |
"cos" | "tan" | "asin" | "acos" | "atan" | "atan2" | "sinh" | "cosh" |
"tanh" | "asinh" | "acosh" | "atanh" => true,
_ => false
}
Function::from_name(name).is_some()
}
fn is_attr(name: &str) -> Option<&'static str> {
@ -478,9 +473,9 @@ fn parse_term(iter: &mut Iter) -> Expr {
describe(&x)))
}
}
Expr::Call(name.clone(), args)
Expr::Call(Function::from_name(name.as_ref()).unwrap(), args)
},
_ => Expr::Call(name.clone(), vec![parse_pow(iter)]),
_ => Expr::Call(Function::from_name(name.as_ref()).unwrap(), vec![parse_pow(iter)]),
}
},
Token::Ident(ref attr) if is_attr(attr).is_some() => {