implement builtin function math.hypot
This commit is contained in:
parent
123ed80e9a
commit
8bd14e0e86
@ -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);
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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."
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user