implement builtin function math.atan2
This commit is contained in:
parent
28701b2707
commit
a9e4d5cba5
@ -464,7 +464,125 @@ fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
|
|
||||||
fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
args.max_args(2)?;
|
args.max_args(2)?;
|
||||||
todo!()
|
let (y_num, y_unit) = match args.get_err(0, "y")? {
|
||||||
|
Value::Dimension(n, u, ..) => (n, u),
|
||||||
|
v => {
|
||||||
|
return Err((
|
||||||
|
format!("$y: {} is not a number.", v.inspect(args.span())?),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (x_num, x_unit) = match args.get_err(1, "x")? {
|
||||||
|
Value::Dimension(n, u, ..) => (n, u),
|
||||||
|
v => {
|
||||||
|
return Err((
|
||||||
|
format!("$x: {} is not a number.", v.inspect(args.span())?),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (x_num, y_num) = if x_unit == Unit::None && y_unit == Unit::None {
|
||||||
|
let x = match x_num {
|
||||||
|
Some(n) => n,
|
||||||
|
None => return Ok(Value::Dimension(None, Unit::Deg, true)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let y = match y_num {
|
||||||
|
Some(n) => n,
|
||||||
|
None => return Ok(Value::Dimension(None, Unit::Deg, true)),
|
||||||
|
};
|
||||||
|
|
||||||
|
(x, y)
|
||||||
|
} else if y_unit == Unit::None {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"$y is unitless but $x has unit {}. \
|
||||||
|
Arguments must all have units or all be unitless.",
|
||||||
|
x_unit
|
||||||
|
),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
} else if x_unit == Unit::None {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"$y has unit {} but $x is unitless. \
|
||||||
|
Arguments must all have units or all be unitless.",
|
||||||
|
y_unit
|
||||||
|
),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
} else if x_unit.comparable(&y_unit) {
|
||||||
|
let x = match x_num {
|
||||||
|
Some(n) => n,
|
||||||
|
None => return Ok(Value::Dimension(None, Unit::Deg, true)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let y = match y_num {
|
||||||
|
Some(n) => n,
|
||||||
|
None => return Ok(Value::Dimension(None, Unit::Deg, true)),
|
||||||
|
};
|
||||||
|
|
||||||
|
(x, y.convert(&y_unit, &x_unit))
|
||||||
|
} else {
|
||||||
|
return Err((
|
||||||
|
format!("Incompatible units {} and {}.", y_unit, x_unit),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
match (
|
||||||
|
NumberState::from_number(&x_num),
|
||||||
|
NumberState::from_number(&y_num),
|
||||||
|
) {
|
||||||
|
(NumberState::Zero, NumberState::FiniteNegative) => {
|
||||||
|
Value::Dimension(Some(Number::from(-90)), Unit::Deg, true)
|
||||||
|
}
|
||||||
|
(NumberState::Zero, NumberState::Zero) | (NumberState::Finite, NumberState::Zero) => {
|
||||||
|
Value::Dimension(Some(Number::zero()), Unit::Deg, true)
|
||||||
|
}
|
||||||
|
(NumberState::Zero, NumberState::Finite) => {
|
||||||
|
Value::Dimension(Some(Number::from(90)), Unit::Deg, true)
|
||||||
|
}
|
||||||
|
(NumberState::Finite, NumberState::Finite)
|
||||||
|
| (NumberState::FiniteNegative, NumberState::Finite)
|
||||||
|
| (NumberState::Finite, NumberState::FiniteNegative)
|
||||||
|
| (NumberState::FiniteNegative, NumberState::FiniteNegative) => Value::Dimension(
|
||||||
|
y_num
|
||||||
|
.atan2(x_num)
|
||||||
|
.map(|n| (n * Number::from(180)) / Number::pi()),
|
||||||
|
Unit::Deg,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(NumberState::FiniteNegative, NumberState::Zero) => {
|
||||||
|
Value::Dimension(Some(Number::from(180)), Unit::Deg, true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NumberState {
|
||||||
|
Zero,
|
||||||
|
Finite,
|
||||||
|
FiniteNegative,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NumberState {
|
||||||
|
fn from_number(num: &Number) -> Self {
|
||||||
|
match (num.is_zero(), num.is_positive()) {
|
||||||
|
(true, _) => NumberState::Zero,
|
||||||
|
(false, true) => NumberState::Finite,
|
||||||
|
(false, false) => NumberState::FiniteNegative,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn declare(f: &mut Module) {
|
pub(crate) fn declare(f: &mut Module) {
|
||||||
@ -489,6 +607,7 @@ pub(crate) fn declare(f: &mut Module) {
|
|||||||
f.insert_builtin("log", log);
|
f.insert_builtin("log", log);
|
||||||
f.insert_builtin("pow", pow);
|
f.insert_builtin("pow", pow);
|
||||||
f.insert_builtin("hypot", hypot);
|
f.insert_builtin("hypot", hypot);
|
||||||
|
f.insert_builtin("atan2", atan2);
|
||||||
#[cfg(feature = "random")]
|
#[cfg(feature = "random")]
|
||||||
f.insert_builtin("random", random);
|
f.insert_builtin("random", random);
|
||||||
|
|
||||||
|
@ -137,6 +137,16 @@ impl Number {
|
|||||||
)?)))
|
)?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pi() -> Self {
|
||||||
|
Number::from(std::f64::consts::PI)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn atan2(self, other: Self) -> Option<Self> {
|
||||||
|
Some(Number::Big(Box::new(BigRational::from_float(
|
||||||
|
self.as_float()?.atan2(other.as_float()?),
|
||||||
|
)?)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Invariants: `from.comparable(&to)` must be true
|
/// Invariants: `from.comparable(&to)` must be true
|
||||||
pub fn convert(self, from: &Unit, to: &Unit) -> Self {
|
pub fn convert(self, from: &Unit, to: &Unit) -> Self {
|
||||||
self * UNIT_CONVERSION_TABLE[to][from].clone()
|
self * UNIT_CONVERSION_TABLE[to][from].clone()
|
||||||
|
@ -495,3 +495,98 @@ error!(
|
|||||||
"@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, (0 / 0));\n}\n",
|
"@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."
|
"Error: Argument 1 has unit deg but argument 3 is unitless. Arguments must all have units or all be unitless."
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
atan2_both_positive,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3, 4);\n}\n",
|
||||||
|
"a {\n color: 36.8698976458deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_first_negative,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(-3, 4);\n}\n",
|
||||||
|
"a {\n color: -36.8698976458deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_second_negative,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3, -4);\n}\n",
|
||||||
|
"a {\n color: 143.1301023542deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_both_negative,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(-3, -4);\n}\n",
|
||||||
|
"a {\n color: -143.1301023542deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_first_positive_second_zero,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3, 0);\n}\n",
|
||||||
|
"a {\n color: 90deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_first_negative_second_zero,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(-3, 0);\n}\n",
|
||||||
|
"a {\n color: -90deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_first_zero_second_positive,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(0, 4);\n}\n",
|
||||||
|
"a {\n color: 0deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_first_zero_second_negative,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(0, -4);\n}\n",
|
||||||
|
"a {\n color: 180deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_both_zero,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(0, 0);\n}\n",
|
||||||
|
"a {\n color: 0deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_both_same_unit,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3px, 4px);\n}\n",
|
||||||
|
"a {\n color: 36.8698976458deg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_both_different_but_comparable_unit,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3px, 4in);\n}\n",
|
||||||
|
"a {\n color: 0.4476141709deg;\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
atan2_first_unitless_second_unit,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3, 4rem);\n}\n",
|
||||||
|
"Error: $y is unitless but $x has unit rem. Arguments must all have units or all be unitless."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
atan2_first_unit_second_unitless,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3px, 4);\n}\n",
|
||||||
|
"Error: $y has unit px but $x is unitless. Arguments must all have units or all be unitless."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
atan2_incompatible_units,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(3px, 4rem);\n}\n",
|
||||||
|
"Error: Incompatible units px and rem."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
atan2_nan_incompatible_units,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(math.acos(2), 3);\n}\n",
|
||||||
|
"Error: $y has unit deg but $x is unitless. Arguments must all have units or all be unitless."
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_first_nan,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2((0/0), 0);\n}\n",
|
||||||
|
"a {\n color: NaNdeg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_second_nan,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(0, (0/0));\n}\n",
|
||||||
|
"a {\n color: NaNdeg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_both_nan,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2((0/0), (0/0));\n}\n",
|
||||||
|
"a {\n color: NaNdeg;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
atan2_nan_with_same_units,
|
||||||
|
"@use 'sass:math';\na {\n color: math.atan2(math.acos(2), 3deg);\n}\n",
|
||||||
|
"a {\n color: NaNdeg;\n}\n"
|
||||||
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user