implement builtin function math.hypot

This commit is contained in:
Connor Skees 2020-07-31 14:05:00 -04:00
parent 123ed80e9a
commit 8bd14e0e86
3 changed files with 125 additions and 1 deletions

View File

@ -102,7 +102,74 @@ fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
}
fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
todo!()
args.min_args(1)?;
let span = args.span();
let mut numbers = args.get_variadic()?.into_iter().map(|v| -> SassResult<_> {
match v.node {
Value::Dimension(n, u, ..) => Ok((n, u)),
v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()),
}
});
let first: (Number, Unit) = match numbers.next().unwrap()? {
(Some(n), u) => (n.clone() * n, u),
(None, u) => return Ok(Value::Dimension(None, u, true)),
};
let rest = numbers
.enumerate()
.map(|(idx, val)| -> SassResult<Option<Number>> {
let (number, unit) = val?;
if first.1 == Unit::None {
if unit == Unit::None {
Ok(number.map(|n| n.clone() * n))
} else {
Err((
format!(
"Argument 1 is unitless but argument {} has unit {}. \
Arguments must all have units or all be unitless.",
idx + 2,
unit
),
span,
)
.into())
}
} else if unit == Unit::None {
Err((
format!(
"Argument 1 has unit {} but argument {} is unitless. \
Arguments must all have units or all be unitless.",
first.1,
idx + 2,
),
span,
)
.into())
} else if first.1.comparable(&unit) {
Ok(number
.map(|n| n.convert(&unit, &first.1))
.map(|n| n.clone() * n))
} else {
Err((
format!("Incompatible units {} and {}.", first.1, unit),
span,
)
.into())
}
})
.collect::<SassResult<Option<Vec<Number>>>>()?;
let rest = match rest {
Some(v) => v,
None => return Ok(Value::Dimension(None, first.1, true)),
};
let sum = first.0 + rest.into_iter().fold(Number::zero(), |a, b| a + b);
Ok(Value::Dimension(sum.sqrt(), first.1, true))
}
fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
@ -421,6 +488,7 @@ pub(crate) fn declare(f: &mut Module) {
f.insert_builtin("atan", atan);
f.insert_builtin("log", log);
f.insert_builtin("pow", pow);
f.insert_builtin("hypot", hypot);
#[cfg(feature = "random")]
f.insert_builtin("random", random);

View File

@ -12,6 +12,8 @@ use num_traits::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Num, One, Signed, ToPrimitive, Zero,
};
use crate::unit::{Unit, UNIT_CONVERSION_TABLE};
use integer::Integer;
mod integer;
@ -134,6 +136,11 @@ impl Number {
self.as_float()?.powf(exponent.as_float()?),
)?)))
}
/// Invariants: `from.comparable(&to)` must be true
pub fn convert(self, from: &Unit, to: &Unit) -> Self {
self * UNIT_CONVERSION_TABLE[to.to_string().as_str()][from.to_string().as_str()].clone()
}
}
macro_rules! trig_fn(

View File

@ -446,3 +446,52 @@ test!(
"@use 'sass:math';\na {\n color: math.pow(2, 0);\n}\n",
"a {\n color: 1;\n}\n"
);
test!(
hypot_all_same_unit,
"@use 'sass:math';\na {\n color: math.hypot(1px, 2px, 3px, 4px, 5px);\n}\n",
"a {\n color: 7.4161984871px;\n}\n"
);
test!(
hypot_negative,
"@use 'sass:math';\na {\n color: math.hypot(1px, 2px, 3px, 4px, 5px, -20px);\n}\n",
"a {\n color: 21.3307290077px;\n}\n"
);
test!(
hypot_all_different_but_comparable_unit,
"@use 'sass:math';\na {\n color: math.hypot(1in, 2cm, 3mm, 4pt, 5pc);\n}\n",
"a {\n color: 1.5269191636in;\n}\n"
);
test!(
hypot_all_no_unit,
"@use 'sass:math';\na {\n color: math.hypot(1, 2, 3);\n}\n",
"a {\n color: 3.7416573868;\n}\n"
);
test!(
hypot_nan_has_comparable_unit,
"@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, math.acos(2));\n}\n",
"a {\n color: NaNdeg;\n}\n"
);
error!(
hypot_no_args,
"@use 'sass:math';\na {\n color: math.hypot();\n}\n",
"Error: At least one argument must be passed."
);
error!(
hypot_first_has_no_unit_third_has_unit,
"@use 'sass:math';\na {\n color: math.hypot(1, 2, 3px);\n}\n",
"Error: Argument 1 is unitless but argument 3 has unit px. Arguments must all have units or all be unitless."
);
error!(
hypot_non_numeric_argument,
"@use 'sass:math';\na {\n color: math.hypot(1, red, 3);\n}\n", "Error: red is not a number."
);
error!(
hypot_units_not_comparable,
"@use 'sass:math';\na {\n color: math.hypot(1px, 2in, 3rem);\n}\n",
"Error: Incompatible units px and rem."
);
error!(
hypot_nan_has_no_unit_but_first_has_unit,
"@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, (0 / 0));\n}\n",
"Error: Argument 1 has unit deg but argument 3 is unitless. Arguments must all have units or all be unitless."
);