use super::{
css_fn_arg, deg_value, diff_units_msg, expected_to, known_dim,
known_dim_spec, num2radians, CallError, CheckedArg, FunctionMap,
NumOrSpecial, ResolvedArgs,
};
use crate::css::{self, BinOp, Value};
use crate::sass::{ArgsError, Name};
use crate::value::{Numeric, Quotes, UnitSet};
use std::f64::consts::E;
use std::ops::Rem;
type Result<T, E = CallError> = std::result::Result<T, E>;
pub fn global(global: &mut FunctionMap) {
def_va!(global, exp(number), |s| {
Ok(match one_number_or_special(s, unitless)? {
NumOrSpecial::Num(v) => Value::scalar(v.exp()),
NumOrSpecial::Special(v) => Value::call("exp", [v]),
})
});
def_va!(global, log(number), |s| {
let mut args = args_iter(s)?;
let num = required_arg(args.next())??;
let base = args
.next()
.transpose()?
.unwrap_or(NumOrSpecial::Num(Numeric::scalar(E)));
check_excess_args(2, args.count())?;
match (num, base) {
(NumOrSpecial::Num(n), NumOrSpecial::Num(b)) => {
let n = unitless(n).map_err(CallError::msg)?;
let b = unitless(b).map_err(CallError::msg)?;
Ok(Value::scalar(n.log(b)))
}
(n, b) => Ok(Value::call("log", [n, b])),
}
});
def_va!(global, pow(number), |s| {
match two_num_or_special(s)? {
(NumOrSpecial::Num(base), NumOrSpecial::Num(exponent)) => {
Ok(Value::scalar(
unitless(base)
.and_then(|b| unitless(exponent).map(|e| (b.powf(e))))
.map_err(CallError::msg)?,
))
}
(base, exp) => Ok(Value::call("pow", [base, exp])),
}
});
def_va!(global, sqrt(number), |s| {
Ok(match one_number_or_special(s, unitless)? {
NumOrSpecial::Num(v) => Value::scalar(v.sqrt()),
NumOrSpecial::Special(v) => Value::call("sqrt", [v]),
})
});
fn num_rad(v: Numeric) -> Result<f64> {
num2radians(v).named(name!(number))
}
def_va!(global, sin(number), |s| {
Ok(match one_number_or_special(s, Ok)? {
NumOrSpecial::Num(v) => Value::scalar(num_rad(v)?.sin()),
NumOrSpecial::Special(v) => Value::call("sin", [v]),
})
});
def_va!(global, cos(number), |s| {
Ok(match one_number_or_special(s, Ok)? {
NumOrSpecial::Num(v) => Value::scalar(num_rad(v)?.cos()),
NumOrSpecial::Special(v) => Value::call("cos", [v]),
})
});
def_va!(global, tan(number), |s| {
Ok(match one_number_or_special(s, Ok)? {
NumOrSpecial::Num(v) => Value::scalar(num_rad(v)?.tan()),
NumOrSpecial::Special(v) => Value::call("tan", [v]),
})
});
def_va!(global, asin(number), |s| {
Ok(match one_number_or_special(s, unitless)? {
NumOrSpecial::Num(v) => deg_value(v.asin()),
NumOrSpecial::Special(v) => Value::call("asin", [v]),
})
});
def_va!(global, acos(number), |s| {
Ok(match one_number_or_special(s, unitless)? {
NumOrSpecial::Num(v) => deg_value(v.acos()),
NumOrSpecial::Special(v) => Value::call("acos", [v]),
})
});
def_va!(global, atan(number), |s| {
Ok(match one_number_or_special(s, unitless)? {
NumOrSpecial::Num(v) => deg_value(v.atan()),
NumOrSpecial::Special(v) => Value::call("atan", [v]),
})
});
def_va!(global, atan2(number), |s| {
match two_num_or_special(s)? {
(NumOrSpecial::Num(x), NumOrSpecial::Num(y)) => {
if x.unit.is_percent() || y.unit.is_percent() {
Ok(Value::call("atan2", [x, y]))
} else if let Some(x) = x.as_unitset(&y.unit) {
Ok(deg_value(f64::from(x).atan2(f64::from(y.value))))
} else if !x.unit.is_known() || !y.unit.is_known() {
Ok(Value::call("atan2", [x, y]))
} else {
Err(CallError::msg(diff_units_msg(&x, &y, name!(y))))
}
}
(x, y) => Ok(Value::call("atan2", [x, y])),
}
});
def_va!(global, sign(number), |s| {
Ok(match one_number_or_special(s, Ok)? {
NumOrSpecial::Num(v) => {
Numeric::new(v.value.signum(), v.unit).into()
}
NumOrSpecial::Special(v) => Value::call("sign", [v]),
})
});
def_va!(global, mod(number), |s| {
match two_num_or_special(s)? {
(NumOrSpecial::Num(y), NumOrSpecial::Num(x)) => {
fn allow(u: &UnitSet) -> bool {
u.is_percent() || !u.is_known()
}
if let Some(x) = x.as_unitset(&y.unit) {
let unit = y.unit;
let m = f64::from(x);
let mut y = f64::from(y.value).rem(m);
if (y * m.signum()).is_sign_negative() {
if m.is_finite() {
y += m;
y = y.rem(m);
} else {
y = f64::NAN;
}
}
Ok(Numeric::new(y.abs() * m.signum(), unit).into())
} else if allow(&y.unit) || allow(&x.unit) {
Ok(Value::call("mod", [y, x]))
} else {
Err(CallError::incompatible_values(y, x))
}
},
(x, y) => Ok(Value::call("mod", [x, y])),
}
});
def_va!(global, rem(number), |s| {
match two_num_or_special(s)? {
(NumOrSpecial::Num(y), NumOrSpecial::Num(x)) => {
fn allow(u: &UnitSet) -> bool {
u.is_percent() || !u.is_known()
}
if let Some(x) = x.as_unitset(&y.unit) {
let r = f64::from(y.value).rem(f64::from(x));
Ok(Numeric::new(r, y.unit).into())
} else if allow(&y.unit) || allow(&x.unit) {
Ok(Value::call("rem", [y, x]))
} else {
Err(CallError::incompatible_values(y, x))
}
}
(x, y) => Ok(Value::call("rem", [x, y])),
}
});
def_va!(global, clamp(number), |s| {
let mut args = args_iter(s)?;
let min = required_arg(args.next())??;
let number = args.next().transpose()?;
let max = args.next().transpose()?;
check_excess_args(3, args.count())?;
match (min, number, max) {
(NumOrSpecial::Num(_min), None, _) => Err(missing_arg_nm(3, 1)),
(NumOrSpecial::Num(_), Some(NumOrSpecial::Num(_)), None) => {
Err(missing_arg_nm(3, 2))
}
(
NumOrSpecial::Num(min),
Some(NumOrSpecial::Num(mut number)),
Some(NumOrSpecial::Num(max)),
) => {
let min_d = known_dim(&min);
let num_d = known_dim(&number);
let max_d = known_dim(&max);
if min_d.is_some() && num_d.is_some() && min_d != num_d {
Err(CallError::incompatible_values(min, number))
} else if min_d.is_some() && max_d.is_some() && min_d != max_d
{
Err(CallError::incompatible_values(min, max))
} else if known_dim_spec(&min) == known_dim_spec(&number)
&& known_dim_spec(&number) == known_dim_spec(&max)
{
if number >= max {
number = max;
}
if number <= min {
number = min;
}
Ok(number.into())
} else {
Ok(Value::call("clamp", [min, number, max]))
}
}
(min, Some(number), Some(max)) => {
Ok(Value::call("clamp", [min, number, max]))
}
(min, Some(number), None) => {
Ok(Value::call("clamp", [min, number]))
}
(arg, None, _) => Ok(Value::call("clamp", [arg])),
}
});
def_va!(global, calc(number), |s| {
fn pre_calc(v: &Value) -> bool {
match v {
Value::Numeric(..) => true,
Value::Call(ref name, _) => css::is_calc_name(name),
Value::Literal(s) => s.is_css_calc(),
Value::Paren(s) => pre_calc(s),
_ => false,
}
}
fn do_eval(v: Value) -> Result<Value> {
match v {
Value::Literal(s) if s.quotes() == Quotes::None => {
let s = s.value();
if s.eq_ignore_ascii_case("e") {
Ok(Value::scalar(std::f64::consts::E))
} else if s.eq_ignore_ascii_case("pi") {
Ok(Value::scalar(std::f64::consts::PI))
} else if s.eq_ignore_ascii_case("infinity") {
Ok(Value::scalar(f64::INFINITY))
} else if s.eq_ignore_ascii_case("-infinity") {
Ok(Value::scalar(-f64::INFINITY))
} else if s.eq_ignore_ascii_case("NaN") {
Ok(Value::scalar(f64::NAN))
} else if let Some(arg) = s
.strip_prefix("calc(")
.and_then(|s| s.strip_suffix(')'))
{
Ok(Value::Paren(Box::new(arg.into())))
} else {
Ok(s.into())
}
}
Value::Call(name, args) => {
if name == "calc" {
let arg = args.get_single().unwrap();
match do_eval(arg.clone())? {
Value::Literal(s) if s.is_name() => Ok(s.into()),
arg => Ok(Value::Paren(Box::new(arg))),
}
} else {
Ok(Value::Call(name, args))
}
}
Value::BinOp(op) => {
let a = do_eval(op.a().clone())?;
let b = do_eval(op.b().clone())?;
let op = op.op();
if let Ok(Some(result)) = op.eval(a.clone(), b.clone()) {
return Ok(result);
}
Ok(BinOp::new(a, true, op, true, b).into())
}
Value::Paren(v) => match v.as_ref() {
l @ Value::Paren(_) => Ok(l.clone()),
l @ Value::BinOp(..) => Ok(l.clone()),
_ => Ok(Value::Paren(v)),
},
Value::List(v, sep, bra) => {
fn seems_numeric(v: &Value) -> bool {
match v {
Value::Numeric(..) => true,
Value::Call(n, _) if n == "calc" => true,
Value::Literal(s) => {
s.quotes() == Quotes::None
&& !s.value().starts_with('-')
&& !s.value().starts_with('+')
&& !s.value().ends_with('-')
&& !s.value().ends_with('+')
}
_ => false,
}
}
if v.windows(2).all(|p| p.iter().all(seems_numeric)) {
Err(CallError::msg("Missing math operator."))
} else {
Ok(Value::List(v, sep, bra))
}
}
v => NumOrSpecial::in_calc(v)
.map_err(CallError::msg)
.map(Value::from),
}
}
let mut args = raw_args_iter(s)?;
let v = required_arg(args.next())?;
check_excess_args(1, args.count())?;
let v = do_eval(v)?;
if pre_calc(&v) {
match v {
Value::Paren(v) => Ok(*v),
v => Ok(v),
}
} else {
Ok(Value::call("calc", [css_fn_arg(v)?]))
}
});
}
fn one_number_or_special<F, T>(
s: &ResolvedArgs,
f: F,
) -> Result<NumOrSpecial<T>>
where
F: Fn(Numeric) -> Result<T, String>,
{
let mut args = args_iter(s)?;
let arg = required_arg(args.next())??;
check_excess_args(1, args.count())?;
arg.try_map(f).map_err(CallError::msg)
}
fn raw_args_iter(s: &ResolvedArgs) -> Result<impl Iterator<Item = Value>> {
Ok(s.get_va::<Value>(name!(number))?.into_iter())
}
fn args_iter(
s: &ResolvedArgs,
) -> Result<impl Iterator<Item = Result<NumOrSpecial>>> {
Ok(raw_args_iter(s)?.map(|v| v.try_into().map_err(CallError::msg)))
}
fn two_num_or_special(
s: &ResolvedArgs,
) -> Result<(NumOrSpecial, NumOrSpecial)> {
let mut args = args_iter(s)?;
let arg1 = required_arg(args.next())??;
let arg2 = args.next().ok_or_else(|| missing_arg_nm(2, 1))??;
check_excess_args(2, args.count())?;
Ok((arg1, arg2))
}
pub(super) fn required_arg<T>(arg: Option<T>) -> Result<T> {
arg.ok_or_else(|| bad_argument("Missing argument."))
}
fn missing_arg_nm(n: usize, m: usize) -> CallError {
bad_argument(format!(
"{n} arguments required, but only {m} {} passed.",
if m == 1 { "was" } else { "were" },
))
}
pub(super) fn check_excess_args(prev: usize, remain: usize) -> Result<()> {
if remain > 0 {
Err(bad_argument(ArgsError::TooMany(prev, prev + remain)))
} else {
Ok(())
}
}
fn bad_argument<S: ToString>(msg: S) -> CallError {
CallError::BadArgument(Name::from_static(""), msg.to_string())
}
fn unitless(val: Numeric) -> Result<f64, String> {
if val.is_no_unit() {
Ok(val.value.into())
} else {
Err(expected_to(val, "have no units"))
}
}