diff --git a/crates/compiler/src/builtin/functions/color/hsl.rs b/crates/compiler/src/builtin/functions/color/hsl.rs index b6901b7..24f85f9 100644 --- a/crates/compiler/src/builtin/functions/color/hsl.rs +++ b/crates/compiler/src/builtin/functions/color/hsl.rs @@ -2,7 +2,10 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::{builtin::builtin_imports::*, serializer::serialize_number, value::SassNumber}; -use super::rgb::{function_string, parse_channels, percentage_or_unitless, ParsedChannels}; +use super::{ + rgb::{function_string, parse_channels, percentage_or_unitless}, + ParsedChannels, +}; fn hsl_3_args( name: &'static str, @@ -14,15 +17,7 @@ fn hsl_3_args( let hue = args.get_err(0, "hue")?; let saturation = args.get_err(1, "saturation")?; let lightness = args.get_err(2, "lightness")?; - let alpha = args.default_arg( - 3, - "alpha", - Value::Dimension(SassNumber { - num: (Number::one()), - unit: Unit::None, - as_slash: None, - }), - ); + let alpha = args.default_arg(3, "alpha", Value::Dimension(SassNumber::new_unitless(1.0))); if [&hue, &saturation, &lightness, &alpha] .iter() @@ -195,25 +190,15 @@ fn darken(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult .get_err(0, "color")? .assert_color_with_name("color", args.span())?; - let amount = match args.get_err(1, "amount")? { - Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => bound!(args, "amount", n, u, 0, 100) / Number(100.0), - v => { - return Err(( - format!( - "$amount: {} is not a number.", - v.to_css_string(args.span(), false)? - ), - args.span(), - ) - .into()) - } - }; - Ok(Value::Color(Arc::new(color.darken(amount)))) + let mut amount = args + .get_err(1, "amount")? + .assert_number_with_name("amount", args.span())?; + + amount.assert_bounds("amount", 0.0, 100.0, args.span())?; + + amount.num /= Number(100.0); + + Ok(Value::Color(Arc::new(color.darken(amount.num)))) } fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -232,24 +217,14 @@ fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult todo!(), - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => bound!(args, "amount", n, u, 0, 100) / Number(100.0), - v => { - return Err(( - format!( - "$amount: {} is not a number.", - v.to_css_string(args.span(), false)? - ), - args.span(), - ) - .into()) - } - }; + let mut amount = args + .get_err(1, "amount")? + .assert_number_with_name("amount", args.span())?; + + amount.assert_bounds("amount", 0.0, 100.0, args.span())?; + + amount.num /= Number(100.0); + let color = match args.get_err(0, "color")? { Value::Color(c) => c, Value::Dimension(SassNumber { @@ -271,7 +246,7 @@ fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult SassResult { @@ -279,25 +254,16 @@ fn desaturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult todo!(), - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => bound!(args, "amount", n, u, 0, 100) / Number(100.0), - v => { - return Err(( - format!( - "$amount: {} is not a number.", - v.to_css_string(args.span(), visitor.options.is_compressed())? - ), - args.span(), - ) - .into()) - } - }; - Ok(Value::Color(Arc::new(color.desaturate(amount)))) + + let mut amount = args + .get_err(1, "amount")? + .assert_number_with_name("amount", args.span())?; + + amount.assert_bounds("amount", 0.0, 100.0, args.span())?; + + amount.num /= Number(100.0); + + Ok(Value::Color(Arc::new(color.desaturate(amount.num)))) } pub(crate) fn grayscale(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -335,32 +301,20 @@ pub(crate) fn complement(mut args: ArgumentResult, visitor: &mut Visitor) -> Sas pub(crate) fn invert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(2)?; - let weight = match args.get(1, "weight") { - Some(Spanned { - node: Value::Dimension(SassNumber { num: n, .. }), - .. - }) if n.is_nan() => todo!(), - Some(Spanned { - node: - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }), - .. - }) => Some(bound!(args, "weight", n, u, 0, 100) / Number(100.0)), - None => None, - Some(v) => { - return Err(( - format!( - "$weight: {} is not a number.", - v.to_css_string(args.span(), visitor.options.is_compressed())? - ), - args.span(), - ) - .into()) - } - }; + let span = args.span(); + let weight = args + .get(1, "weight") + .map::, _>(|weight| { + let mut weight = weight.node.assert_number_with_name("weight", span)?; + + weight.assert_bounds("weight", 0.0, 100.0, span)?; + + weight.num /= Number(100.0); + + Ok(weight.num) + }) + .transpose()?; + match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Color(Arc::new( c.invert(weight.unwrap_or_else(Number::one)), diff --git a/crates/compiler/src/builtin/functions/color/hwb.rs b/crates/compiler/src/builtin/functions/color/hwb.rs index 3144c88..7269a91 100644 --- a/crates/compiler/src/builtin/functions/color/hwb.rs +++ b/crates/compiler/src/builtin/functions/color/hwb.rs @@ -1,6 +1,6 @@ use crate::builtin::builtin_imports::*; -use super::rgb::{parse_channels, ParsedChannels}; +use super::{rgb::parse_channels, ParsedChannels}; pub(crate) fn blackness(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; diff --git a/crates/compiler/src/builtin/functions/color/mod.rs b/crates/compiler/src/builtin/functions/color/mod.rs index 3c6005f..4158cdb 100644 --- a/crates/compiler/src/builtin/functions/color/mod.rs +++ b/crates/compiler/src/builtin/functions/color/mod.rs @@ -1,3 +1,5 @@ +use crate::value::Value; + use super::GlobalFunctionMap; pub mod hsl; @@ -6,6 +8,12 @@ pub mod opacity; pub mod other; pub mod rgb; +#[derive(Debug, Clone)] +pub(crate) enum ParsedChannels { + String(String), + List(Vec), +} + pub(crate) fn declare(f: &mut GlobalFunctionMap) { hsl::declare(f); opacity::declare(f); diff --git a/crates/compiler/src/builtin/functions/color/opacity.rs b/crates/compiler/src/builtin/functions/color/opacity.rs index f4e9723..504bd40 100644 --- a/crates/compiler/src/builtin/functions/color/opacity.rs +++ b/crates/compiler/src/builtin/functions/color/opacity.rs @@ -32,21 +32,17 @@ mod test { pub(crate) fn alpha(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { if args.len() <= 1 { - match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(SassNumber { - num: c.alpha(), - unit: Unit::None, - as_slash: None, - })), - Value::String(s, QuoteKind::None) if is_ms_filter(&s) => { - Ok(Value::String(format!("alpha({})", s), QuoteKind::None)) + let color = args.get_err(0, "color")?; + + if let Value::String(s, QuoteKind::None) = &color { + if is_ms_filter(s) { + return Ok(Value::String(format!("alpha({})", s), QuoteKind::None)); } - v => Err(( - format!("$color: {} is not a color.", v.inspect(args.span())?), - args.span(), - ) - .into()), } + + let color = color.assert_color_with_name("color", args.span())?; + + Ok(Value::Dimension(SassNumber::new_unitless(color.alpha()))) } else { let err = args.max_args(1); let args = args @@ -72,11 +68,7 @@ pub(crate) fn opacity(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRe args.max_args(1)?; match args.get_err(0, "color")? { Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), - Value::Color(c) => Ok(Value::Dimension(SassNumber { - num: c.alpha(), - unit: Unit::None, - as_slash: None, - })), + Value::Color(c) => Ok(Value::Dimension(SassNumber::new_unitless(c.alpha()))), Value::Dimension(SassNumber { num, unit, @@ -102,9 +94,9 @@ fn opacify(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult .get_err(1, "amount")? .assert_number_with_name("amount", args.span())?; - let amount = bound!(args, "amount", amount.num, amount.unit(), 0, 1); + amount.assert_bounds("amount", 0.0, 1.0, args.span())?; - Ok(Value::Color(Arc::new(color.fade_in(amount)))) + Ok(Value::Color(Arc::new(color.fade_in(amount.num)))) } fn transparentize(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -112,22 +104,14 @@ fn transparentize(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult let color = args .get_err(0, "color")? .assert_color_with_name("color", args.span())?; - let amount = match args.get_err(1, "amount")? { - Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => bound!(args, "amount", n, u, 0, 1), - v => { - return Err(( - format!("$amount: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; - Ok(Value::Color(Arc::new(color.fade_out(amount)))) + + let amount = args + .get_err(1, "amount")? + .assert_number_with_name("amount", args.span())?; + + amount.assert_bounds("amount", 0.0, 1.0, args.span())?; + + Ok(Value::Color(Arc::new(color.fade_out(amount.num)))) } pub(crate) fn declare(f: &mut GlobalFunctionMap) { diff --git a/crates/compiler/src/builtin/functions/color/rgb.rs b/crates/compiler/src/builtin/functions/color/rgb.rs index d7a8e76..a971734 100644 --- a/crates/compiler/src/builtin/functions/color/rgb.rs +++ b/crates/compiler/src/builtin/functions/color/rgb.rs @@ -1,5 +1,7 @@ use crate::{builtin::builtin_imports::*, serializer::inspect_number, value::fuzzy_round}; +use super::ParsedChannels; + pub(crate) fn function_string( name: &'static str, args: &[Value], @@ -173,12 +175,6 @@ pub(crate) fn percentage_or_unitless( Ok(value.clamp(0.0, max).0) } -#[derive(Debug, Clone)] -pub(crate) enum ParsedChannels { - String(String), - List(Vec), -} - fn is_var_slash(value: &Value) -> bool { match value { Value::String(text, QuoteKind::Quoted) => { @@ -313,7 +309,6 @@ pub(crate) fn parse_channels( } } -/// name: Either `rgb` or `rgba` depending on the caller fn inner_rgb( name: &'static str, mut args: ArgumentResult, @@ -363,11 +358,7 @@ pub(crate) fn red(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult .get_err(0, "color")? .assert_color_with_name("color", args.span())?; - Ok(Value::Dimension(SassNumber { - num: color.red(), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless(color.red()))) } pub(crate) fn green(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -376,11 +367,7 @@ pub(crate) fn green(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResu .get_err(0, "color")? .assert_color_with_name("color", args.span())?; - Ok(Value::Dimension(SassNumber { - num: color.green(), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless(color.green()))) } pub(crate) fn blue(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -389,11 +376,7 @@ pub(crate) fn blue(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResul .get_err(0, "color")? .assert_color_with_name("color", args.span())?; - Ok(Value::Dimension(SassNumber { - num: color.blue(), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless(color.blue()))) } pub(crate) fn mix(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -409,11 +392,7 @@ pub(crate) fn mix(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult let weight = match args.default_arg( 2, "weight", - Value::Dimension(SassNumber { - num: (Number(50.0)), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(50.0)), ) { Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), Value::Dimension(SassNumber { diff --git a/crates/compiler/src/builtin/functions/list.rs b/crates/compiler/src/builtin/functions/list.rs index 2753d00..2306b20 100644 --- a/crates/compiler/src/builtin/functions/list.rs +++ b/crates/compiler/src/builtin/functions/list.rs @@ -2,11 +2,10 @@ use crate::builtin::builtin_imports::*; pub(crate) fn length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - Ok(Value::Dimension(SassNumber { - num: (Number::from(args.get_err(0, "list")?.as_list().len())), - unit: Unit::None, - as_slash: None, - })) + + let len = args.get_err(0, "list")?.as_list().len(); + + Ok(Value::Dimension(SassNumber::new_unitless(len))) } pub(crate) fn nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -260,14 +259,10 @@ pub(crate) fn index(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResu let list = args.get_err(0, "list")?.as_list(); let value = args.get_err(1, "value")?; let index = match list.into_iter().position(|v| v == value) { - Some(v) => Number::from(v + 1), + Some(v) => v + 1, None => return Ok(Value::Null), }; - Ok(Value::Dimension(SassNumber { - num: (index), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless(index))) } pub(crate) fn zip(args: ArgumentResult, visitor: &mut Visitor) -> SassResult { diff --git a/crates/compiler/src/builtin/functions/math.rs b/crates/compiler/src/builtin/functions/math.rs index c16f242..44fd698 100644 --- a/crates/compiler/src/builtin/functions/math.rs +++ b/crates/compiler/src/builtin/functions/math.rs @@ -16,74 +16,47 @@ pub(crate) fn percentage(mut args: ArgumentResult, visitor: &mut Visitor) -> Sas pub(crate) fn round(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "number")? { - // todo: better error message, consider finities - Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => { - Err(("Infinity or NaN toInt", args.span()).into()) - } - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => Ok(Value::Dimension(SassNumber { - num: (n.round()), - unit: u, - as_slash: None, - })), - v => Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()), + let mut number = args + .get_err(0, "number")? + .assert_number_with_name("number", args.span())?; + + if !number.num.is_finite() { + return Err(("Infinity or NaN toInt", args.span()).into()); } + + number.num = number.num.round(); + + Ok(Value::Dimension(number)) } pub(crate) fn ceil(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "number")? { - // todo: better error message, consider finities - Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => { - Err(("Infinity or NaN toInt", args.span()).into()) - } - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => Ok(Value::Dimension(SassNumber { - num: (n.ceil()), - unit: u, - as_slash: None, - })), - v => Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()), + let mut number = args + .get_err(0, "number")? + .assert_number_with_name("number", args.span())?; + + if !number.num.is_finite() { + return Err(("Infinity or NaN toInt", args.span()).into()); } + + number.num = number.num.ceil(); + + Ok(Value::Dimension(number)) } pub(crate) fn floor(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "number")? { - // todo: better error message, consider finities - Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => { - Err(("Infinity or NaN toInt", args.span()).into()) - } - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => Ok(Value::Dimension(SassNumber { - num: (n.floor()), - unit: u, - as_slash: None, - })), - v => Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()), + let mut number = args + .get_err(0, "number")? + .assert_number_with_name("number", args.span())?; + + if !number.num.is_finite() { + return Err(("Infinity or NaN toInt", args.span()).into()); } + + number.num = number.num.floor(); + + Ok(Value::Dimension(number)) } pub(crate) fn abs(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -92,6 +65,7 @@ pub(crate) fn abs(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult .get_err(0, "number")? .assert_number_with_name("number", args.span())?; + // todo: test for nan+infinity num.num = num.num.abs(); Ok(Value::Dimension(num)) @@ -120,22 +94,16 @@ pub(crate) fn random(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes if matches!(limit, Value::Null) { let mut rng = rand::thread_rng(); - return Ok(Value::Dimension(SassNumber { - num: (Number::from(rng.gen_range(0.0..1.0))), - unit: Unit::None, - as_slash: None, - })); + return Ok(Value::Dimension(SassNumber::new_unitless( + rng.gen_range(0.0..1.0), + ))); } let limit = limit.assert_number_with_name("limit", args.span())?.num; let limit_int = limit.assert_int_with_name("limit", args.span())?; if limit.is_one() { - return Ok(Value::Dimension(SassNumber { - num: (Number::one()), - unit: Unit::None, - as_slash: None, - })); + return Ok(Value::Dimension(SassNumber::new_unitless(1.0))); } if limit.is_zero() || limit.is_negative() { @@ -147,11 +115,9 @@ pub(crate) fn random(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes } let mut rng = rand::thread_rng(); - Ok(Value::Dimension(SassNumber { - num: (Number::from(rng.gen_range(0..limit_int) + 1)), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless( + rng.gen_range(0..limit_int) + 1, + ))) } pub(crate) fn min(args: ArgumentResult, visitor: &mut Visitor) -> SassResult { diff --git a/crates/compiler/src/builtin/functions/meta.rs b/crates/compiler/src/builtin/functions/meta.rs index 4290907..5ebdb85 100644 --- a/crates/compiler/src/builtin/functions/meta.rs +++ b/crates/compiler/src/builtin/functions/meta.rs @@ -33,51 +33,40 @@ fn if_(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { pub(crate) fn feature_exists(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "feature")? { - #[allow(clippy::match_same_arms)] - Value::String(s, _) => Ok(match s.as_str() { - // A local variable will shadow a global variable unless - // `!global` is used. - "global-variable-shadowing" => Value::True, - // the @extend rule will affect selectors nested in pseudo-classes - // like :not() - "extend-selector-pseudoclass" => Value::True, - // Full support for unit arithmetic using units defined in the - // [Values and Units Level 3][] spec. - "units-level-3" => Value::True, - // The Sass `@error` directive is supported. - "at-error" => Value::True, - // The "Custom Properties Level 1" spec is supported. This means - // that custom properties are parsed statically, with only - // interpolation treated as SassScript. - "custom-property" => Value::True, - _ => Value::False, - }), - v => Err(( - format!("$feature: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + let feature = args + .get_err(0, "feature")? + .assert_string_with_name("feature", args.span())? + .0; + + #[allow(clippy::match_same_arms)] + Ok(match feature.as_str() { + // A local variable will shadow a global variable unless + // `!global` is used. + "global-variable-shadowing" => Value::True, + // the @extend rule will affect selectors nested in pseudo-classes + // like :not() + "extend-selector-pseudoclass" => Value::True, + // Full support for unit arithmetic using units defined in the + // [Values and Units Level 3][] spec. + "units-level-3" => Value::True, + // The Sass `@error` directive is supported. + "at-error" => Value::True, + // The "Custom Properties Level 1" spec is supported. This means + // that custom properties are parsed statically, with only + // interpolation treated as SassScript. + "custom-property" => Value::True, + _ => Value::False, + }) } pub(crate) fn unit(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - let unit = match args.get_err(0, "number")? { - Value::Dimension(SassNumber { - num: _, - unit: u, - as_slash: _, - }) => u.to_string(), - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; - Ok(Value::String(unit, QuoteKind::Quoted)) + + let number = args + .get_err(0, "number")? + .assert_number_with_name("number", args.span())?; + + Ok(Value::String(number.unit.to_string(), QuoteKind::Quoted)) } pub(crate) fn type_of(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -88,19 +77,11 @@ pub(crate) fn type_of(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRe pub(crate) fn unitless(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - Ok(match args.get_err(0, "number")? { - Value::Dimension(SassNumber { - unit: Unit::None, .. - }) => Value::True, - Value::Dimension(SassNumber { .. }) => Value::False, - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }) + let number = args + .get_err(0, "number")? + .assert_number_with_name("number", args.span())?; + + Ok(Value::bool(number.unit == Unit::None)) } pub(crate) fn inspect(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -116,14 +97,14 @@ pub(crate) fn variable_exists( visitor: &mut Visitor, ) -> SassResult { args.max_args(1)?; - match args.get_err(0, "name")? { - Value::String(s, _) => Ok(Value::bool(visitor.env.var_exists(s.into(), None)?)), - v => Err(( - format!("$name: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + + let name = Identifier::from( + args.get_err(0, "name")? + .assert_string_with_name("name", args.span())? + .0, + ); + + Ok(Value::bool(visitor.env.var_exists(name, None)?)) } pub(crate) fn global_variable_exists( @@ -163,16 +144,11 @@ pub(crate) fn global_variable_exists( pub(crate) fn mixin_exists(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(2)?; - let name: Identifier = match args.get_err(0, "name")? { - Value::String(s, _) => s.into(), - v => { - return Err(( - format!("$name: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; + let name = Identifier::from( + args.get_err(0, "name")? + .assert_string_with_name("name", args.span())? + .0, + ); let module = match args.default_arg(1, "module", Value::Null) { Value::String(s, _) => Some(s), @@ -203,16 +179,11 @@ pub(crate) fn function_exists( ) -> SassResult { args.max_args(2)?; - let name: Identifier = match args.get_err(0, "name")? { - Value::String(s, _) => s.into(), - v => { - return Err(( - format!("$name: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; + let name = Identifier::from( + args.get_err(0, "name")? + .assert_string_with_name("name", args.span())? + .0, + ); let module = match args.default_arg(1, "module", Value::Null) { Value::String(s, _) => Some(s), diff --git a/crates/compiler/src/builtin/functions/string.rs b/crates/compiler/src/builtin/functions/string.rs index 52798e8..b4a909e 100644 --- a/crates/compiler/src/builtin/functions/string.rs +++ b/crates/compiler/src/builtin/functions/string.rs @@ -2,72 +2,59 @@ use crate::builtin::builtin_imports::*; pub(crate) fn to_upper_case(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "string")? { - Value::String(mut i, q) => { - i.make_ascii_uppercase(); - Ok(Value::String(i, q)) - } - v => Err(( - format!("$string: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + let (mut s, q) = args + .get_err(0, "string")? + .assert_string_with_name("string", args.span())?; + + s.make_ascii_uppercase(); + + Ok(Value::String(s, q)) } pub(crate) fn to_lower_case(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "string")? { - Value::String(mut i, q) => { - i.make_ascii_lowercase(); - Ok(Value::String(i, q)) - } - v => Err(( - format!("$string: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + + let (mut s, q) = args + .get_err(0, "string")? + .assert_string_with_name("string", args.span())?; + + s.make_ascii_lowercase(); + + Ok(Value::String(s, q)) } pub(crate) fn str_length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "string")? { - Value::String(i, _) => Ok(Value::Dimension(SassNumber { - num: (Number::from(i.chars().count())), - unit: Unit::None, - as_slash: None, - })), - v => Err(( - format!("$string: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + let s = args + .get_err(0, "string")? + .assert_string_with_name("string", args.span())? + .0; + + Ok(Value::Dimension(SassNumber::new_unitless( + s.chars().count(), + ))) } pub(crate) fn quote(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "string")? { - Value::String(i, _) => Ok(Value::String(i, QuoteKind::Quoted)), - v => Err(( - format!("$string: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + + let s = args + .get_err(0, "string")? + .assert_string_with_name("string", args.span())? + .0; + + Ok(Value::String(s, QuoteKind::Quoted)) } pub(crate) fn unquote(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "string")? { - i @ Value::String(..) => Ok(i.unquote()), - v => Err(( - format!("$string: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + + let s = args + .get_err(0, "string")? + .assert_string_with_name("string", args.span())? + .0; + + Ok(Value::String(s, QuoteKind::None)) } pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { @@ -100,11 +87,7 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass .default_arg( 2, "end-at", - Value::Dimension(SassNumber { - num: Number(-1.0), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(-1.0)), ) .assert_number_with_name("end-at", span)?; @@ -149,11 +132,7 @@ pub(crate) fn str_index(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass None => return Ok(Value::Null), }; - Ok(Value::Dimension(SassNumber { - num: Number::from(char_position), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless(char_position))) } pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { diff --git a/crates/compiler/src/builtin/modules/math.rs b/crates/compiler/src/builtin/modules/math.rs index a3a32a7..cb22a20 100644 --- a/crates/compiler/src/builtin/modules/math.rs +++ b/crates/compiler/src/builtin/modules/math.rs @@ -206,8 +206,8 @@ fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { } }; - Ok(Value::Dimension(SassNumber { - num: if let Some(base) = base { + Ok(Value::Dimension(SassNumber::new_unitless( + if let Some(base) = base { if base.is_zero() { Number::zero() } else { @@ -221,9 +221,7 @@ fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { } else { number.ln() }, - unit: Unit::None, - as_slash: None, - })) + ))) } fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { @@ -241,11 +239,9 @@ fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { .assert_number_with_name("exponent", span)?; exponent.assert_no_units("exponent", span)?; - Ok(Value::Dimension(SassNumber { - num: base.num.pow(exponent.num), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless( + base.num.pow(exponent.num), + ))) } fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { @@ -255,11 +251,9 @@ fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult { .assert_number_with_name("number", args.span())?; number.assert_no_units("number", args.span())?; - Ok(Value::Dimension(SassNumber { - num: number.num.sqrt(), - unit: Unit::None, - as_slash: None, - })) + Ok(Value::Dimension(SassNumber::new_unitless( + number.num.sqrt(), + ))) } macro_rules! trig_fn { @@ -274,11 +268,9 @@ macro_rules! trig_fn { num, unit: unit @ (Unit::None | Unit::Rad | Unit::Deg | Unit::Grad | Unit::Turn), .. - }) => Value::Dimension(SassNumber { - num: Number(coerce_to_rad(num.0, unit).$name()), - unit: Unit::None, - as_slash: None, - }), + }) => { + Value::Dimension(SassNumber::new_unitless(coerce_to_rad(num.0, unit).$name())) + } v @ Value::Dimension(..) => { return Err(( format!( @@ -482,58 +474,30 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin_var( "e", - Value::Dimension(SassNumber { - num: Number(std::f64::consts::E), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(std::f64::consts::E)), ); f.insert_builtin_var( "pi", - Value::Dimension(SassNumber { - num: Number(std::f64::consts::PI), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(std::f64::consts::PI)), ); f.insert_builtin_var( "epsilon", - Value::Dimension(SassNumber { - num: Number(std::f64::EPSILON), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(std::f64::EPSILON)), ); f.insert_builtin_var( "max-safe-integer", - Value::Dimension(SassNumber { - num: Number(9007199254740991.0), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(9007199254740991.0)), ); f.insert_builtin_var( "min-safe-integer", - Value::Dimension(SassNumber { - num: Number(-9007199254740991.0), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(-9007199254740991.0)), ); f.insert_builtin_var( "max-number", - Value::Dimension(SassNumber { - num: Number(f64::MAX), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(f64::MAX)), ); f.insert_builtin_var( "min-number", - Value::Dimension(SassNumber { - num: Number(f64::MIN_POSITIVE), - unit: Unit::None, - as_slash: None, - }), + Value::Dimension(SassNumber::new_unitless(f64::MIN_POSITIVE)), ); } diff --git a/crates/compiler/src/builtin/modules/meta.rs b/crates/compiler/src/builtin/modules/meta.rs index 0264bb7..d30e255 100644 --- a/crates/compiler/src/builtin/modules/meta.rs +++ b/crates/compiler/src/builtin/modules/meta.rs @@ -19,16 +19,10 @@ fn load_css(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<()> { let span = args.span(); - let url = match args.get_err(0, "module")? { - Value::String(s, ..) => s, - v => { - return Err(( - format!("$module: {} is not a string.", v.inspect(span)?), - span, - ) - .into()) - } - }; + let url = args + .get_err(0, "module")? + .assert_string_with_name("module", args.span())? + .0; let with = match args.default_arg(1, "with", Value::Null) { Value::Map(map) => Some(map), @@ -45,16 +39,8 @@ fn load_css(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<()> { let mut values = BTreeMap::new(); for (key, value) in with { - let name = match key.node { - Value::String(s, ..) => Identifier::from(s), - v => { - return Err(( - format!("$with key: {} is not a string.", v.inspect(span)?), - span, - ) - .into()) - } - }; + let name = + Identifier::from(key.node.assert_string_with_name("with key", args.span())?.0); if values.contains_key(&name) { // todo: write test for this @@ -97,16 +83,11 @@ fn load_css(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<()> { fn module_functions(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - let module = match args.get_err(0, "module")? { - Value::String(s, ..) => s, - v => { - return Err(( - format!("$module: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; + let module = Identifier::from( + args.get_err(0, "module")? + .assert_string_with_name("module", args.span())? + .0, + ); Ok(Value::Map( (*(*visitor.env.modules) @@ -120,16 +101,11 @@ fn module_functions(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResu fn module_variables(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - let module = match args.get_err(0, "module")? { - Value::String(s, ..) => s, - v => { - return Err(( - format!("$module: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; + let module = Identifier::from( + args.get_err(0, "module")? + .assert_string_with_name("module", args.span())? + .0, + ); Ok(Value::Map( (*(*visitor.env.modules) diff --git a/crates/compiler/src/value/sass_number.rs b/crates/compiler/src/value/sass_number.rs index da8f4a2..3ba7473 100644 --- a/crates/compiler/src/value/sass_number.rs +++ b/crates/compiler/src/value/sass_number.rs @@ -30,6 +30,14 @@ pub(crate) fn conversion_factor(from: &Unit, to: &Unit) -> Option { } impl SassNumber { + pub fn new_unitless>(n: N) -> Self { + Self { + num: n.into(), + unit: Unit::None, + as_slash: None, + } + } + pub fn has_comparable_units(&self, other_unit: &Unit) -> bool { self.unit.comparable(other_unit) }