From 458fcf0fd83d529dbf650b56c3dc76fe36b809c9 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 01:52:34 -0400 Subject: [PATCH] implement builtin function `math.log` --- src/builtin/modules/math.rs | 67 ++++++++++++++++++++++++++++++++-- src/value/number/mod.rs | 6 ++++ tests/math-module.rs | 72 +++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 2 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index fef1d91..61f4c3c 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use num_traits::{One, Zero}; +use num_traits::{One, Signed, Zero}; use crate::{ args::CallArgs, @@ -107,7 +107,69 @@ fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; - todo!() + + let number = match args.get_err(0, "number")? { + Value::Dimension(Some(n), Unit::None, ..) => n, + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be unitless.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + v @ Value::Dimension(None, ..) => return Ok(v), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let base = match args.default_arg(1, "base", Value::Null)? { + Value::Null => None, + Value::Dimension(Some(n), Unit::None, ..) => Some(n), + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be unitless.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + v @ Value::Dimension(None, ..) => return Ok(v), + v => { + return Err(( + format!("$base: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + Ok(Value::Dimension( + if let Some(base) = base { + if base.is_zero() { + Some(Number::zero()) + } else { + (|| Some(number.ln()? / base.ln()?))() + } + } else if number.is_negative() { + None + } else if number.is_zero() { + todo!() + } else { + number.ln() + }, + Unit::None, + true, + )) } fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -310,6 +372,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("acos", acos); f.insert_builtin("asin", asin); f.insert_builtin("atan", atan); + f.insert_builtin("log", log); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index e6b87c1..14eb52c 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -122,6 +122,12 @@ impl Number { self.as_float()?.sqrt(), )?))) } + + pub fn ln(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.ln(), + )?))) + } } macro_rules! trig_fn( diff --git a/tests/math-module.rs b/tests/math-module.rs index 1b51562..9a32c9a 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -318,3 +318,75 @@ test!( "@use 'sass:math';\na {\n color: math.atan((0 / 0));\n}\n", "a {\n color: NaNdeg;\n}\n" ); +test!( + log_above_one, + "@use 'sass:math';\na {\n color: math.log(2);\n}\n", + "a {\n color: 0.6931471806;\n}\n" +); +test!( + log_below_negative_one, + "@use 'sass:math';\na {\n color: math.log(-2);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_one, + "@use 'sass:math';\na {\n color: math.log(1);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + log_negative_one, + "@use 'sass:math';\na {\n color: math.log(-1);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + #[ignore = "we do not support Infinity"] + log_zero, + "@use 'sass:math';\na {\n color: math.log(0);\n}\n", + "a {\n color: -Infinity;\n}\n" +); +test!( + log_point_five, + "@use 'sass:math';\na {\n color: math.log(.5);\n}\n", + "a {\n color: -0.6931471806;\n}\n" +); +test!( + log_nan, + "@use 'sass:math';\na {\n color: math.log((0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_base_nan, + "@use 'sass:math';\na {\n color: math.log(1, (0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_base_above_one, + "@use 'sass:math';\na {\n color: math.log(2, 2);\n}\n", + "a {\n color: 1;\n}\n" +); +test!( + log_base_below_negative_one, + "@use 'sass:math';\na {\n color: math.log(2, -2);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + #[ignore = "we do not support Infinity"] + log_base_one, + "@use 'sass:math';\na {\n color: math.log(2, 1);\n}\n", + "a {\n color: Infinity;\n}\n" +); +test!( + log_base_negative_one, + "@use 'sass:math';\na {\n color: math.log(2, -1);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_base_zero, + "@use 'sass:math';\na {\n color: math.log(2, 0);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + log_base_point_five, + "@use 'sass:math';\na {\n color: math.log(2, .5);\n}\n", + "a {\n color: -1;\n}\n" +);