use super::{ParseError, Value};
use std::str::FromStr;
use std::u32;
pub fn apply(name: &str, args: &[Value]) -> Result<Value, ParseError> {
match name {
"add" => add(args),
"subtract" => subtract(args),
"divide" => divide(args),
"multiply" => multiply(args),
"power" => power(args),
"intdiv" => intdiv(args),
"modulus" => modulus(args),
"concat" => concat(args),
"join" => join(args),
_ => Err(error("No such function")),
}
}
pub fn add(args: &[Value]) -> Result<Value, ParseError> {
convert::<i64>(args)
.map(|(lhs, rhs)| (lhs + rhs).to_string())
.or_else(|_| convert::<f64>(args).map(|(lhs, rhs)| (lhs + rhs).to_string()))
.map_err(|_| error("Addition requires two numeric arguments"))
.map(Value::String)
}
pub fn subtract(args: &[Value]) -> Result<Value, ParseError> {
convert::<i64>(args)
.map(|(lhs, rhs)| (lhs - rhs).to_string())
.or_else(|_| convert::<f64>(args).map(|(lhs, rhs)| (lhs - rhs).to_string()))
.map_err(|_| error("Subtraction requires two numeric arguments"))
.map(Value::String)
}
pub fn multiply(args: &[Value]) -> Result<Value, ParseError> {
convert::<i64>(args)
.map(|(lhs, rhs)| (lhs * rhs).to_string())
.or_else(|_| convert::<f64>(args).map(|(lhs, rhs)| (lhs * rhs).to_string()))
.map_err(|_| error("Multiplication requires two numeric arguments"))
.map(Value::String)
}
pub fn divide(args: &[Value]) -> Result<Value, ParseError> {
convert::<f64>(args)
.map(|(lhs, rhs)| (lhs / rhs).to_string())
.map_err(|_| error("Division requires two numeric arguments"))
.map(Value::String)
}
pub fn power(args: &[Value]) -> Result<Value, ParseError> {
convert::<i64>(args)
.and_then(|(lhs, rhs)| {
if rhs > 0 && rhs <= i64::from(u32::MAX) {
Ok(lhs.pow(rhs as u32).to_string())
} else {
Err(())
}
}).or_else(|_| convert::<f64>(args).map(|(lhs, rhs)| lhs.powf(rhs).to_string()))
.map_err(|_| error("Power requires two numeric arguments"))
.map(Value::String)
}
pub fn intdiv(args: &[Value]) -> Result<Value, ParseError> {
convert::<i64>(args)
.map(|(lhs, rhs)| (lhs / rhs).to_string())
.map_err(|_| error("Integer division requires two integer arguments"))
.map(Value::String)
}
pub fn modulus(args: &[Value]) -> Result<Value, ParseError> {
convert::<i64>(args)
.map(|(lhs, rhs)| (lhs % rhs).to_string())
.map_err(|_| error("Modulus requires two integer arguments"))
.map(Value::String)
}
fn concat(args: &[Value]) -> Result<Value, ParseError> {
args.iter()
.try_fold(String::new(), |mut s, a| {
a.as_str().map(|a| {
s.push_str(a);
s
})
}).map(Value::String)
.ok_or_else(|| error("Concat only takes string arguments"))
}
fn join(args: &[Value]) -> Result<Value, ParseError> {
args.iter()
.try_fold(Vec::new(), |mut v, a| {
a.as_str().map(|a| {
v.push(a);
v
})
}).ok_or_else(|| error("Join only takes string arguments"))
.and_then(|strs| {
if strs.is_empty() {
Err(error("Not enough arguments to join"))
} else {
Ok(Value::String(strs[1..].join(strs[0])))
}
})
}
fn convert<T: FromStr>(args: &[Value]) -> Result<(T, T), ()> {
args.get(2)
.map_or_else(|| Ok(()), |_| Err(()))
.and_then(|_| {
args.get(0)
.and_then(|lhs| args.get(1).map(|rhs| (lhs, rhs)))
.ok_or(())
}).and_then(|(lhs, rhs)| {
lhs.as_str()
.and_then(|lhs| rhs.as_str().map(|rhs| (lhs, rhs)))
.ok_or(())
}).and_then(|(lhs, rhs)| {
lhs.parse::<T>()
.and_then(|lhs| rhs.parse::<T>().map(|rhs| (lhs, rhs)))
.map_err(|_| ())
})
}
fn error(reason: &'static str) -> ParseError {
ParseError {
reason,
positions: Vec::new(),
}
}