From 2265e7eb748b5a1aadb1c47859b2a3dd0bc1451e Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 21:22:10 -0400 Subject: [PATCH] implement builtin function `math.sqrt` --- src/builtin/modules/math.rs | 25 ++++++++++++++++++++++- src/lib.rs | 1 + src/value/number/mod.rs | 16 ++++++++++++++- tests/math-module.rs | 40 +++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index fd55a8a..643beaa 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -115,7 +115,29 @@ fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) => Value::Dimension(n.sqrt(), Unit::None, true), + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to have no units.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn cos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -165,6 +187,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("unit", unit); f.insert_builtin("percentage", percentage); f.insert_builtin("clamp", clamp); + f.insert_builtin("sqrt", sqrt); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/lib.rs b/src/lib.rs index 32f4c41..dbea119 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ grass input.scss clippy::unknown_clippy_lints, clippy::replace_consts, clippy::single_match, + clippy::float_arithmetic, // temporarily allowed while under heavy development. // eventually these allows should be refactored away diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 9cbb4ed..8e95bf9 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -8,7 +8,9 @@ use std::{ use num_bigint::BigInt; use num_rational::{BigRational, Rational64}; -use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Num, One, Signed, Zero}; +use num_traits::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Num, One, Signed, ToPrimitive, Zero, +}; use integer::Integer; @@ -106,6 +108,18 @@ impl Number { self } + + #[allow(clippy::cast_precision_loss)] + pub fn sqrt(self) -> Option { + Some(match self { + Number::Small(n) => Number::Big(Box::new(BigRational::from_float( + ((*n.numer() as f64) / (*n.denom() as f64)).sqrt(), + )?)), + Number::Big(n) => Number::Big(Box::new(BigRational::from_float( + ((n.numer().to_f64()?) / (n.denom().to_f64()?)).sqrt(), + )?)), + }) + } } impl Default for Number { diff --git a/tests/math-module.rs b/tests/math-module.rs index 50030df..f5beefe 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -38,3 +38,43 @@ error!( "@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2);\n}\n", "Error: $min has unit mm but $max is unitless. Arguments must all have units or all be unitless." ); +test!( + sqrt_zero, + "@use 'sass:math';\na {\n color: math.sqrt(0);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + sqrt_small_positive, + "@use 'sass:math';\na {\n color: math.sqrt(99);\n}\n", + "a {\n color: 9.9498743711;\n}\n" +); +test!( + sqrt_small_negative, + "@use 'sass:math';\na {\n color: math.sqrt(-99);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + sqrt_big_positive, + "@use 'sass:math';\na {\n color: math.sqrt(9999999999999999999999999999999999999999999999999);\n}\n", + "a {\n color: 3162277660168379038695424;\n}\n" +); +test!( + sqrt_big_negative, + "@use 'sass:math';\na {\n color: math.sqrt(-9999999999999999999999999999999999999999999999999);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + sqrt_irrational, + "@use 'sass:math';\na {\n color: math.sqrt(2);\n}\n", + "a {\n color: 1.4142135624;\n}\n" +); +test!( + sqrt_of_nan, + "@use 'sass:math';\na {\n color: math.sqrt((0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +error!( + sqrt_with_units, + "@use 'sass:math';\na {\n color: math.sqrt(1px);\n}\n", + "Error: $number: Expected 1px to have no units." +);