diff --git a/src/builtin/math.rs b/src/builtin/math.rs index 8b4d925..b826f7f 100644 --- a/src/builtin/math.rs +++ b/src/builtin/math.rs @@ -6,198 +6,204 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use rand::Rng; use super::Builtin; +use crate::args::CallArgs; +use crate::error::SassResult; +use crate::scope::Scope; +use crate::selector::Selector; use crate::unit::Unit; use crate::value::{Number, Value}; pub(crate) fn register(f: &mut GlobalFunctionMap) { - f.insert( - "percentage", - Builtin::new(|mut args, scope, super_selector| { - args.max_args(1)?; - let num = match arg!(args, scope, super_selector, 0, "number") { - Value::Dimension(n, Unit::None) => n * Number::from(100), - v @ Value::Dimension(..) => { - return Err(( - format!( - "$number: Expected {} to have no units.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()) - } - v => { - return Err(( - format!( - "$number: {} is not a number.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()) - } - }; - Ok(Value::Dimension(num, Unit::Percent)) - }), - ); - f.insert( - "round", - Builtin::new(|mut args, scope, super_selector| { - args.max_args(1)?; - match arg!(args, scope, super_selector, 0, "number") { - Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)), - v => Err(( - format!( - "$number: {} is not a number.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()), - } - }), - ); - f.insert( - "ceil", - Builtin::new(|mut args, scope, super_selector| { - args.max_args(1)?; - match arg!(args, scope, super_selector, 0, "number") { - Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)), - v => Err(( - format!( - "$number: {} is not a number.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()), - } - }), - ); - f.insert( - "floor", - Builtin::new(|mut args, scope, super_selector| { - args.max_args(1)?; - match arg!(args, scope, super_selector, 0, "number") { - Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)), - v => Err(( - format!( - "$number: {} is not a number.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()), - } - }), - ); - f.insert( - "abs", - Builtin::new(|mut args, scope, super_selector| { - args.max_args(1)?; - match arg!(args, scope, super_selector, 0, "number") { - Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)), - v => Err(( - format!( - "$number: {} is not a number.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()), - } - }), - ); - f.insert( - "comparable", - Builtin::new(|mut args, scope, super_selector| { - args.max_args(2)?; - let unit1 = match arg!(args, scope, super_selector, 0, "number1") { - Value::Dimension(_, u) => u, - v => { - return Err(( - format!( - "$number1: {} is not a number.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()) - } - }; - let unit2 = match arg!(args, scope, super_selector, 1, "number2") { - Value::Dimension(_, u) => u, - v => { - return Err(( - format!( - "$number2: {} is not a number.", - v.to_css_string(args.span())? - ), - args.span(), - ) - .into()) - } - }; - - Ok(Value::bool(unit1.comparable(&unit2))) - }), - ); - // TODO: write tests for this. how? - #[cfg(feature = "random")] - f.insert( - "random", - Builtin::new(|mut args, scope, super_selector| { - args.max_args(1)?; - let limit = match arg!(args, scope, super_selector, 0, "limit" = Value::Null) { - Value::Dimension(n, _) => n, - Value::Null => { - let mut rng = rand::thread_rng(); - return Ok(Value::Dimension( - Number::from(rng.gen_range(0.0, 1.0)), - Unit::None, - )); - } - v => { - return Err(( - format!("$limit: {} is not a number.", v.to_css_string(args.span())?), - args.span(), - ) - .into()) - } - }; - - if limit.is_one() { - return Ok(Value::Dimension(Number::one(), Unit::None)); - } - - if limit.is_decimal() { - return Err((format!("$limit: {} is not an int.", limit), args.span()).into()); - } - - if limit.is_zero() || limit.is_negative() { + fn percentage( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, + ) -> SassResult { + args.max_args(1)?; + let num = match arg!(args, scope, super_selector, 0, "number") { + Value::Dimension(n, Unit::None) => n * Number::from(100), + v @ Value::Dimension(..) => { return Err(( - format!("$limit: Must be greater than 0, was {}.", limit), + format!( + "$number: Expected {} to have no units.", + v.to_css_string(args.span())? + ), args.span(), ) - .into()); + .into()) } + v => { + return Err(( + format!( + "$number: {} is not a number.", + v.to_css_string(args.span())? + ), + args.span(), + ) + .into()) + } + }; + Ok(Value::Dimension(num, Unit::Percent)) + } - let limit = match limit.to_integer().to_u32() { - Some(n) => n, - None => { - return Err(( - format!("max must be in range 0 < max \u{2264} 2^32, was {}", limit), - args.span(), - ) - .into()) - } - }; + fn round(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { + args.max_args(1)?; + match arg!(args, scope, super_selector, 0, "number") { + Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)), + v => Err(( + format!( + "$number: {} is not a number.", + v.to_css_string(args.span())? + ), + args.span(), + ) + .into()), + } + } - let mut rng = rand::thread_rng(); - Ok(Value::Dimension( - Number::from(rng.gen_range(0, limit) + 1), - Unit::None, - )) - }), - ); + fn ceil(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { + args.max_args(1)?; + match arg!(args, scope, super_selector, 0, "number") { + Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)), + v => Err(( + format!( + "$number: {} is not a number.", + v.to_css_string(args.span())? + ), + args.span(), + ) + .into()), + } + } + + fn floor(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { + args.max_args(1)?; + match arg!(args, scope, super_selector, 0, "number") { + Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)), + v => Err(( + format!( + "$number: {} is not a number.", + v.to_css_string(args.span())? + ), + args.span(), + ) + .into()), + } + } + + fn abs(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { + args.max_args(1)?; + match arg!(args, scope, super_selector, 0, "number") { + Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)), + v => Err(( + format!( + "$number: {} is not a number.", + v.to_css_string(args.span())? + ), + args.span(), + ) + .into()), + } + } + + fn comparable( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, + ) -> SassResult { + args.max_args(2)?; + let unit1 = match arg!(args, scope, super_selector, 0, "number1") { + Value::Dimension(_, u) => u, + v => { + return Err(( + format!( + "$number1: {} is not a number.", + v.to_css_string(args.span())? + ), + args.span(), + ) + .into()) + } + }; + let unit2 = match arg!(args, scope, super_selector, 1, "number2") { + Value::Dimension(_, u) => u, + v => { + return Err(( + format!( + "$number2: {} is not a number.", + v.to_css_string(args.span())? + ), + args.span(), + ) + .into()) + } + }; + + Ok(Value::bool(unit1.comparable(&unit2))) + } + + // TODO: write tests for this + #[cfg(feature = "random")] + fn random(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { + args.max_args(1)?; + let limit = match arg!(args, scope, super_selector, 0, "limit" = Value::Null) { + Value::Dimension(n, _) => n, + Value::Null => { + let mut rng = rand::thread_rng(); + return Ok(Value::Dimension( + Number::from(rng.gen_range(0.0, 1.0)), + Unit::None, + )); + } + v => { + return Err(( + format!("$limit: {} is not a number.", v.to_css_string(args.span())?), + args.span(), + ) + .into()) + } + }; + + if limit.is_one() { + return Ok(Value::Dimension(Number::one(), Unit::None)); + } + + if limit.is_decimal() { + return Err((format!("$limit: {} is not an int.", limit), args.span()).into()); + } + + if limit.is_zero() || limit.is_negative() { + return Err(( + format!("$limit: Must be greater than 0, was {}.", limit), + args.span(), + ) + .into()); + } + + let limit = match limit.to_integer().to_u32() { + Some(n) => n, + None => { + return Err(( + format!("max must be in range 0 < max \u{2264} 2^32, was {}", limit), + args.span(), + ) + .into()) + } + }; + + let mut rng = rand::thread_rng(); + Ok(Value::Dimension( + Number::from(rng.gen_range(0, limit) + 1), + Unit::None, + )) + } + + f.insert("percentage", Builtin::new(percentage)); + f.insert("round", Builtin::new(round)); + f.insert("ceil", Builtin::new(ceil)); + f.insert("floor", Builtin::new(floor)); + f.insert("abs", Builtin::new(abs)); + f.insert("comparable", Builtin::new(comparable)); + #[cfg(feature = "random")] + f.insert("random", Builtin::new(random)); }