simplify builtin fn argument handling

This commit is contained in:
connorskees 2023-01-16 06:46:56 +00:00
parent 95efc582b5
commit f5a654fe5b
12 changed files with 261 additions and 477 deletions

View File

@ -2,7 +2,10 @@ use std::collections::{BTreeMap, BTreeSet};
use crate::{builtin::builtin_imports::*, serializer::serialize_number, value::SassNumber}; 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( fn hsl_3_args(
name: &'static str, name: &'static str,
@ -14,15 +17,7 @@ fn hsl_3_args(
let hue = args.get_err(0, "hue")?; let hue = args.get_err(0, "hue")?;
let saturation = args.get_err(1, "saturation")?; let saturation = args.get_err(1, "saturation")?;
let lightness = args.get_err(2, "lightness")?; let lightness = args.get_err(2, "lightness")?;
let alpha = args.default_arg( let alpha = args.default_arg(3, "alpha", Value::Dimension(SassNumber::new_unitless(1.0)));
3,
"alpha",
Value::Dimension(SassNumber {
num: (Number::one()),
unit: Unit::None,
as_slash: None,
}),
);
if [&hue, &saturation, &lightness, &alpha] if [&hue, &saturation, &lightness, &alpha]
.iter() .iter()
@ -195,25 +190,15 @@ fn darken(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value>
.get_err(0, "color")? .get_err(0, "color")?
.assert_color_with_name("color", args.span())?; .assert_color_with_name("color", args.span())?;
let amount = match args.get_err(1, "amount")? { let mut amount = args
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), .get_err(1, "amount")?
Value::Dimension(SassNumber { .assert_number_with_name("amount", args.span())?;
num: n,
unit: u, amount.assert_bounds("amount", 0.0, 100.0, args.span())?;
as_slash: _,
}) => bound!(args, "amount", n, u, 0, 100) / Number(100.0), amount.num /= Number(100.0);
v => {
return Err(( Ok(Value::Color(Arc::new(color.darken(amount.num))))
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span(), false)?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Arc::new(color.darken(amount))))
} }
fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -232,24 +217,14 @@ fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value
)); ));
} }
let amount = match args.get_err(1, "amount")? { let mut amount = args
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), .get_err(1, "amount")?
Value::Dimension(SassNumber { .assert_number_with_name("amount", args.span())?;
num: n,
unit: u, amount.assert_bounds("amount", 0.0, 100.0, args.span())?;
as_slash: _,
}) => bound!(args, "amount", n, u, 0, 100) / Number(100.0), amount.num /= Number(100.0);
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span(), false)?
),
args.span(),
)
.into())
}
};
let color = match args.get_err(0, "color")? { let color = match args.get_err(0, "color")? {
Value::Color(c) => c, Value::Color(c) => c,
Value::Dimension(SassNumber { Value::Dimension(SassNumber {
@ -271,7 +246,7 @@ fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value
.into()) .into())
} }
}; };
Ok(Value::Color(Arc::new(color.saturate(amount)))) Ok(Value::Color(Arc::new(color.saturate(amount.num))))
} }
fn desaturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { fn desaturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -279,25 +254,16 @@ fn desaturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Val
let color = args let color = args
.get_err(0, "color")? .get_err(0, "color")?
.assert_color_with_name("color", args.span())?; .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!(), let mut amount = args
Value::Dimension(SassNumber { .get_err(1, "amount")?
num: n, .assert_number_with_name("amount", args.span())?;
unit: u,
as_slash: _, amount.assert_bounds("amount", 0.0, 100.0, args.span())?;
}) => bound!(args, "amount", n, u, 0, 100) / Number(100.0),
v => { amount.num /= Number(100.0);
return Err((
format!( Ok(Value::Color(Arc::new(color.desaturate(amount.num))))
"$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))))
} }
pub(crate) fn grayscale(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn grayscale(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -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<Value> { pub(crate) fn invert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let weight = match args.get(1, "weight") { let span = args.span();
Some(Spanned { let weight = args
node: Value::Dimension(SassNumber { num: n, .. }), .get(1, "weight")
.. .map::<SassResult<_>, _>(|weight| {
}) if n.is_nan() => todo!(), let mut weight = weight.node.assert_number_with_name("weight", span)?;
Some(Spanned {
node: weight.assert_bounds("weight", 0.0, 100.0, span)?;
Value::Dimension(SassNumber {
num: n, weight.num /= Number(100.0);
unit: u,
as_slash: _, Ok(weight.num)
}), })
.. .transpose()?;
}) => 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())
}
};
match args.get_err(0, "color")? { match args.get_err(0, "color")? {
Value::Color(c) => Ok(Value::Color(Arc::new( Value::Color(c) => Ok(Value::Color(Arc::new(
c.invert(weight.unwrap_or_else(Number::one)), c.invert(weight.unwrap_or_else(Number::one)),

View File

@ -1,6 +1,6 @@
use crate::builtin::builtin_imports::*; 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<Value> { pub(crate) fn blackness(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;

View File

@ -1,3 +1,5 @@
use crate::value::Value;
use super::GlobalFunctionMap; use super::GlobalFunctionMap;
pub mod hsl; pub mod hsl;
@ -6,6 +8,12 @@ pub mod opacity;
pub mod other; pub mod other;
pub mod rgb; pub mod rgb;
#[derive(Debug, Clone)]
pub(crate) enum ParsedChannels {
String(String),
List(Vec<Value>),
}
pub(crate) fn declare(f: &mut GlobalFunctionMap) { pub(crate) fn declare(f: &mut GlobalFunctionMap) {
hsl::declare(f); hsl::declare(f);
opacity::declare(f); opacity::declare(f);

View File

@ -32,21 +32,17 @@ mod test {
pub(crate) fn alpha(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn alpha(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
if args.len() <= 1 { if args.len() <= 1 {
match args.get_err(0, "color")? { let color = args.get_err(0, "color")?;
Value::Color(c) => Ok(Value::Dimension(SassNumber {
num: c.alpha(), if let Value::String(s, QuoteKind::None) = &color {
unit: Unit::None, if is_ms_filter(s) {
as_slash: None, return Ok(Value::String(format!("alpha({})", s), QuoteKind::None));
})),
Value::String(s, QuoteKind::None) if is_ms_filter(&s) => {
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 { } else {
let err = args.max_args(1); let err = args.max_args(1);
let args = args let args = args
@ -72,11 +68,7 @@ pub(crate) fn opacity(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRe
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "color")? { match args.get_err(0, "color")? {
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(),
Value::Color(c) => Ok(Value::Dimension(SassNumber { Value::Color(c) => Ok(Value::Dimension(SassNumber::new_unitless(c.alpha()))),
num: c.alpha(),
unit: Unit::None,
as_slash: None,
})),
Value::Dimension(SassNumber { Value::Dimension(SassNumber {
num, num,
unit, unit,
@ -102,9 +94,9 @@ fn opacify(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value>
.get_err(1, "amount")? .get_err(1, "amount")?
.assert_number_with_name("amount", args.span())?; .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<Value> { fn transparentize(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -112,22 +104,14 @@ fn transparentize(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
let color = args let color = args
.get_err(0, "color")? .get_err(0, "color")?
.assert_color_with_name("color", args.span())?; .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!(), let amount = args
Value::Dimension(SassNumber { .get_err(1, "amount")?
num: n, .assert_number_with_name("amount", args.span())?;
unit: u,
as_slash: _, amount.assert_bounds("amount", 0.0, 1.0, args.span())?;
}) => bound!(args, "amount", n, u, 0, 1),
v => { Ok(Value::Color(Arc::new(color.fade_out(amount.num))))
return Err((
format!("$amount: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Arc::new(color.fade_out(amount))))
} }
pub(crate) fn declare(f: &mut GlobalFunctionMap) { pub(crate) fn declare(f: &mut GlobalFunctionMap) {

View File

@ -1,5 +1,7 @@
use crate::{builtin::builtin_imports::*, serializer::inspect_number, value::fuzzy_round}; use crate::{builtin::builtin_imports::*, serializer::inspect_number, value::fuzzy_round};
use super::ParsedChannels;
pub(crate) fn function_string( pub(crate) fn function_string(
name: &'static str, name: &'static str,
args: &[Value], args: &[Value],
@ -173,12 +175,6 @@ pub(crate) fn percentage_or_unitless(
Ok(value.clamp(0.0, max).0) Ok(value.clamp(0.0, max).0)
} }
#[derive(Debug, Clone)]
pub(crate) enum ParsedChannels {
String(String),
List(Vec<Value>),
}
fn is_var_slash(value: &Value) -> bool { fn is_var_slash(value: &Value) -> bool {
match value { match value {
Value::String(text, QuoteKind::Quoted) => { 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( fn inner_rgb(
name: &'static str, name: &'static str,
mut args: ArgumentResult, mut args: ArgumentResult,
@ -363,11 +358,7 @@ pub(crate) fn red(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
.get_err(0, "color")? .get_err(0, "color")?
.assert_color_with_name("color", args.span())?; .assert_color_with_name("color", args.span())?;
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(color.red())))
num: color.red(),
unit: Unit::None,
as_slash: None,
}))
} }
pub(crate) fn green(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn green(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -376,11 +367,7 @@ pub(crate) fn green(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResu
.get_err(0, "color")? .get_err(0, "color")?
.assert_color_with_name("color", args.span())?; .assert_color_with_name("color", args.span())?;
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(color.green())))
num: color.green(),
unit: Unit::None,
as_slash: None,
}))
} }
pub(crate) fn blue(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn blue(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -389,11 +376,7 @@ pub(crate) fn blue(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResul
.get_err(0, "color")? .get_err(0, "color")?
.assert_color_with_name("color", args.span())?; .assert_color_with_name("color", args.span())?;
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(color.blue())))
num: color.blue(),
unit: Unit::None,
as_slash: None,
}))
} }
pub(crate) fn mix(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn mix(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -409,11 +392,7 @@ pub(crate) fn mix(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
let weight = match args.default_arg( let weight = match args.default_arg(
2, 2,
"weight", "weight",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(50.0)),
num: (Number(50.0)),
unit: Unit::None,
as_slash: None,
}),
) { ) {
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(), Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => todo!(),
Value::Dimension(SassNumber { Value::Dimension(SassNumber {

View File

@ -2,11 +2,10 @@ use crate::builtin::builtin_imports::*;
pub(crate) fn length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
Ok(Value::Dimension(SassNumber {
num: (Number::from(args.get_err(0, "list")?.as_list().len())), let len = args.get_err(0, "list")?.as_list().len();
unit: Unit::None,
as_slash: None, Ok(Value::Dimension(SassNumber::new_unitless(len)))
}))
} }
pub(crate) fn nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn nth(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -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 list = args.get_err(0, "list")?.as_list();
let value = args.get_err(1, "value")?; let value = args.get_err(1, "value")?;
let index = match list.into_iter().position(|v| v == 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), None => return Ok(Value::Null),
}; };
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(index)))
num: (index),
unit: Unit::None,
as_slash: None,
}))
} }
pub(crate) fn zip(args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn zip(args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {

View File

@ -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<Value> { pub(crate) fn round(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { let mut number = args
// todo: better error message, consider finities .get_err(0, "number")?
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => { .assert_number_with_name("number", args.span())?;
Err(("Infinity or NaN toInt", args.span()).into())
} if !number.num.is_finite() {
Value::Dimension(SassNumber { return Err(("Infinity or NaN toInt", args.span()).into());
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()),
} }
number.num = number.num.round();
Ok(Value::Dimension(number))
} }
pub(crate) fn ceil(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn ceil(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { let mut number = args
// todo: better error message, consider finities .get_err(0, "number")?
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => { .assert_number_with_name("number", args.span())?;
Err(("Infinity or NaN toInt", args.span()).into())
} if !number.num.is_finite() {
Value::Dimension(SassNumber { return Err(("Infinity or NaN toInt", args.span()).into());
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()),
} }
number.num = number.num.ceil();
Ok(Value::Dimension(number))
} }
pub(crate) fn floor(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn floor(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { let mut number = args
// todo: better error message, consider finities .get_err(0, "number")?
Value::Dimension(SassNumber { num: n, .. }) if n.is_nan() => { .assert_number_with_name("number", args.span())?;
Err(("Infinity or NaN toInt", args.span()).into())
} if !number.num.is_finite() {
Value::Dimension(SassNumber { return Err(("Infinity or NaN toInt", args.span()).into());
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()),
} }
number.num = number.num.floor();
Ok(Value::Dimension(number))
} }
pub(crate) fn abs(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn abs(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -92,6 +65,7 @@ pub(crate) fn abs(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult
.get_err(0, "number")? .get_err(0, "number")?
.assert_number_with_name("number", args.span())?; .assert_number_with_name("number", args.span())?;
// todo: test for nan+infinity
num.num = num.num.abs(); num.num = num.num.abs();
Ok(Value::Dimension(num)) Ok(Value::Dimension(num))
@ -120,22 +94,16 @@ pub(crate) fn random(mut args: ArgumentResult, visitor: &mut Visitor) -> SassRes
if matches!(limit, Value::Null) { if matches!(limit, Value::Null) {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
return Ok(Value::Dimension(SassNumber { return Ok(Value::Dimension(SassNumber::new_unitless(
num: (Number::from(rng.gen_range(0.0..1.0))), rng.gen_range(0.0..1.0),
unit: Unit::None, )));
as_slash: None,
}));
} }
let limit = limit.assert_number_with_name("limit", args.span())?.num; let limit = limit.assert_number_with_name("limit", args.span())?.num;
let limit_int = limit.assert_int_with_name("limit", args.span())?; let limit_int = limit.assert_int_with_name("limit", args.span())?;
if limit.is_one() { if limit.is_one() {
return Ok(Value::Dimension(SassNumber { return Ok(Value::Dimension(SassNumber::new_unitless(1.0)));
num: (Number::one()),
unit: Unit::None,
as_slash: None,
}));
} }
if limit.is_zero() || limit.is_negative() { 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(); let mut rng = rand::thread_rng();
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(
num: (Number::from(rng.gen_range(0..limit_int) + 1)), rng.gen_range(0..limit_int) + 1,
unit: Unit::None, )))
as_slash: None,
}))
} }
pub(crate) fn min(args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn min(args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {

View File

@ -33,9 +33,13 @@ fn if_(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
pub(crate) fn feature_exists(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn feature_exists(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "feature")? { let feature = args
.get_err(0, "feature")?
.assert_string_with_name("feature", args.span())?
.0;
#[allow(clippy::match_same_arms)] #[allow(clippy::match_same_arms)]
Value::String(s, _) => Ok(match s.as_str() { Ok(match feature.as_str() {
// A local variable will shadow a global variable unless // A local variable will shadow a global variable unless
// `!global` is used. // `!global` is used.
"global-variable-shadowing" => Value::True, "global-variable-shadowing" => Value::True,
@ -52,32 +56,17 @@ pub(crate) fn feature_exists(mut args: ArgumentResult, visitor: &mut Visitor) ->
// interpolation treated as SassScript. // interpolation treated as SassScript.
"custom-property" => Value::True, "custom-property" => Value::True,
_ => Value::False, _ => Value::False,
}), })
v => Err((
format!("$feature: {} is not a string.", v.inspect(args.span())?),
args.span(),
)
.into()),
}
} }
pub(crate) fn unit(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn unit(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let unit = match args.get_err(0, "number")? {
Value::Dimension(SassNumber { let number = args
num: _, .get_err(0, "number")?
unit: u, .assert_number_with_name("number", args.span())?;
as_slash: _,
}) => u.to_string(), Ok(Value::String(number.unit.to_string(), QuoteKind::Quoted))
v => {
return Err((
format!("$number: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::String(unit, QuoteKind::Quoted))
} }
pub(crate) fn type_of(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn type_of(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -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<Value> { pub(crate) fn unitless(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
Ok(match args.get_err(0, "number")? { let number = args
Value::Dimension(SassNumber { .get_err(0, "number")?
unit: Unit::None, .. .assert_number_with_name("number", args.span())?;
}) => Value::True,
Value::Dimension(SassNumber { .. }) => Value::False, Ok(Value::bool(number.unit == Unit::None))
v => {
return Err((
format!("$number: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
})
} }
pub(crate) fn inspect(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn inspect(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -116,14 +97,14 @@ pub(crate) fn variable_exists(
visitor: &mut Visitor, visitor: &mut Visitor,
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "name")? {
Value::String(s, _) => Ok(Value::bool(visitor.env.var_exists(s.into(), None)?)), let name = Identifier::from(
v => Err(( args.get_err(0, "name")?
format!("$name: {} is not a string.", v.inspect(args.span())?), .assert_string_with_name("name", args.span())?
args.span(), .0,
) );
.into()),
} Ok(Value::bool(visitor.env.var_exists(name, None)?))
} }
pub(crate) fn global_variable_exists( 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<Value> { pub(crate) fn mixin_exists(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let name: Identifier = match args.get_err(0, "name")? { let name = Identifier::from(
Value::String(s, _) => s.into(), args.get_err(0, "name")?
v => { .assert_string_with_name("name", args.span())?
return Err(( .0,
format!("$name: {} is not a string.", v.inspect(args.span())?), );
args.span(),
)
.into())
}
};
let module = match args.default_arg(1, "module", Value::Null) { let module = match args.default_arg(1, "module", Value::Null) {
Value::String(s, _) => Some(s), Value::String(s, _) => Some(s),
@ -203,16 +179,11 @@ pub(crate) fn function_exists(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let name: Identifier = match args.get_err(0, "name")? { let name = Identifier::from(
Value::String(s, _) => s.into(), args.get_err(0, "name")?
v => { .assert_string_with_name("name", args.span())?
return Err(( .0,
format!("$name: {} is not a string.", v.inspect(args.span())?), );
args.span(),
)
.into())
}
};
let module = match args.default_arg(1, "module", Value::Null) { let module = match args.default_arg(1, "module", Value::Null) {
Value::String(s, _) => Some(s), Value::String(s, _) => Some(s),

View File

@ -2,72 +2,59 @@ use crate::builtin::builtin_imports::*;
pub(crate) fn to_upper_case(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn to_upper_case(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "string")? { let (mut s, q) = args
Value::String(mut i, q) => { .get_err(0, "string")?
i.make_ascii_uppercase(); .assert_string_with_name("string", args.span())?;
Ok(Value::String(i, q))
} s.make_ascii_uppercase();
v => Err((
format!("$string: {} is not a string.", v.inspect(args.span())?), Ok(Value::String(s, q))
args.span(),
)
.into()),
}
} }
pub(crate) fn to_lower_case(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn to_lower_case(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "string")? {
Value::String(mut i, q) => { let (mut s, q) = args
i.make_ascii_lowercase(); .get_err(0, "string")?
Ok(Value::String(i, q)) .assert_string_with_name("string", args.span())?;
}
v => Err(( s.make_ascii_lowercase();
format!("$string: {} is not a string.", v.inspect(args.span())?),
args.span(), Ok(Value::String(s, q))
)
.into()),
}
} }
pub(crate) fn str_length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn str_length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "string")? { let s = args
Value::String(i, _) => Ok(Value::Dimension(SassNumber { .get_err(0, "string")?
num: (Number::from(i.chars().count())), .assert_string_with_name("string", args.span())?
unit: Unit::None, .0;
as_slash: None,
})), Ok(Value::Dimension(SassNumber::new_unitless(
v => Err(( s.chars().count(),
format!("$string: {} is not a string.", v.inspect(args.span())?), )))
args.span(),
)
.into()),
}
} }
pub(crate) fn quote(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn quote(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "string")? {
Value::String(i, _) => Ok(Value::String(i, QuoteKind::Quoted)), let s = args
v => Err(( .get_err(0, "string")?
format!("$string: {} is not a string.", v.inspect(args.span())?), .assert_string_with_name("string", args.span())?
args.span(), .0;
)
.into()), Ok(Value::String(s, QuoteKind::Quoted))
}
} }
pub(crate) fn unquote(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn unquote(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "string")? {
i @ Value::String(..) => Ok(i.unquote()), let s = args
v => Err(( .get_err(0, "string")?
format!("$string: {} is not a string.", v.inspect(args.span())?), .assert_string_with_name("string", args.span())?
args.span(), .0;
)
.into()), Ok(Value::String(s, QuoteKind::None))
}
} }
pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
@ -100,11 +87,7 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
.default_arg( .default_arg(
2, 2,
"end-at", "end-at",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(-1.0)),
num: Number(-1.0),
unit: Unit::None,
as_slash: None,
}),
) )
.assert_number_with_name("end-at", span)?; .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), None => return Ok(Value::Null),
}; };
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(char_position)))
num: Number::from(char_position),
unit: Unit::None,
as_slash: None,
}))
} }
pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {

View File

@ -206,8 +206,8 @@ fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
} }
}; };
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(
num: if let Some(base) = base { if let Some(base) = base {
if base.is_zero() { if base.is_zero() {
Number::zero() Number::zero()
} else { } else {
@ -221,9 +221,7 @@ fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
} else { } else {
number.ln() number.ln()
}, },
unit: Unit::None, )))
as_slash: None,
}))
} }
fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> { fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
@ -241,11 +239,9 @@ fn pow(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
.assert_number_with_name("exponent", span)?; .assert_number_with_name("exponent", span)?;
exponent.assert_no_units("exponent", span)?; exponent.assert_no_units("exponent", span)?;
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(
num: base.num.pow(exponent.num), base.num.pow(exponent.num),
unit: Unit::None, )))
as_slash: None,
}))
} }
fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> { fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
@ -255,11 +251,9 @@ fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
.assert_number_with_name("number", args.span())?; .assert_number_with_name("number", args.span())?;
number.assert_no_units("number", args.span())?; number.assert_no_units("number", args.span())?;
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber::new_unitless(
num: number.num.sqrt(), number.num.sqrt(),
unit: Unit::None, )))
as_slash: None,
}))
} }
macro_rules! trig_fn { macro_rules! trig_fn {
@ -274,11 +268,9 @@ macro_rules! trig_fn {
num, num,
unit: unit @ (Unit::None | Unit::Rad | Unit::Deg | Unit::Grad | Unit::Turn), unit: unit @ (Unit::None | Unit::Rad | Unit::Deg | Unit::Grad | Unit::Turn),
.. ..
}) => Value::Dimension(SassNumber { }) => {
num: Number(coerce_to_rad(num.0, unit).$name()), Value::Dimension(SassNumber::new_unitless(coerce_to_rad(num.0, unit).$name()))
unit: Unit::None, }
as_slash: None,
}),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -482,58 +474,30 @@ pub(crate) fn declare(f: &mut Module) {
f.insert_builtin_var( f.insert_builtin_var(
"e", "e",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(std::f64::consts::E)),
num: Number(std::f64::consts::E),
unit: Unit::None,
as_slash: None,
}),
); );
f.insert_builtin_var( f.insert_builtin_var(
"pi", "pi",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(std::f64::consts::PI)),
num: Number(std::f64::consts::PI),
unit: Unit::None,
as_slash: None,
}),
); );
f.insert_builtin_var( f.insert_builtin_var(
"epsilon", "epsilon",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(std::f64::EPSILON)),
num: Number(std::f64::EPSILON),
unit: Unit::None,
as_slash: None,
}),
); );
f.insert_builtin_var( f.insert_builtin_var(
"max-safe-integer", "max-safe-integer",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(9007199254740991.0)),
num: Number(9007199254740991.0),
unit: Unit::None,
as_slash: None,
}),
); );
f.insert_builtin_var( f.insert_builtin_var(
"min-safe-integer", "min-safe-integer",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(-9007199254740991.0)),
num: Number(-9007199254740991.0),
unit: Unit::None,
as_slash: None,
}),
); );
f.insert_builtin_var( f.insert_builtin_var(
"max-number", "max-number",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(f64::MAX)),
num: Number(f64::MAX),
unit: Unit::None,
as_slash: None,
}),
); );
f.insert_builtin_var( f.insert_builtin_var(
"min-number", "min-number",
Value::Dimension(SassNumber { Value::Dimension(SassNumber::new_unitless(f64::MIN_POSITIVE)),
num: Number(f64::MIN_POSITIVE),
unit: Unit::None,
as_slash: None,
}),
); );
} }

View File

@ -19,16 +19,10 @@ fn load_css(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<()> {
let span = args.span(); let span = args.span();
let url = match args.get_err(0, "module")? { let url = args
Value::String(s, ..) => s, .get_err(0, "module")?
v => { .assert_string_with_name("module", args.span())?
return Err(( .0;
format!("$module: {} is not a string.", v.inspect(span)?),
span,
)
.into())
}
};
let with = match args.default_arg(1, "with", Value::Null) { let with = match args.default_arg(1, "with", Value::Null) {
Value::Map(map) => Some(map), Value::Map(map) => Some(map),
@ -45,16 +39,8 @@ fn load_css(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<()> {
let mut values = BTreeMap::new(); let mut values = BTreeMap::new();
for (key, value) in with { for (key, value) in with {
let name = match key.node { let name =
Value::String(s, ..) => Identifier::from(s), Identifier::from(key.node.assert_string_with_name("with key", args.span())?.0);
v => {
return Err((
format!("$with key: {} is not a string.", v.inspect(span)?),
span,
)
.into())
}
};
if values.contains_key(&name) { if values.contains_key(&name) {
// todo: write test for this // 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<Value> { fn module_functions(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let module = match args.get_err(0, "module")? { let module = Identifier::from(
Value::String(s, ..) => s, args.get_err(0, "module")?
v => { .assert_string_with_name("module", args.span())?
return Err(( .0,
format!("$module: {} is not a string.", v.inspect(args.span())?), );
args.span(),
)
.into())
}
};
Ok(Value::Map( Ok(Value::Map(
(*(*visitor.env.modules) (*(*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<Value> { fn module_variables(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let module = match args.get_err(0, "module")? { let module = Identifier::from(
Value::String(s, ..) => s, args.get_err(0, "module")?
v => { .assert_string_with_name("module", args.span())?
return Err(( .0,
format!("$module: {} is not a string.", v.inspect(args.span())?), );
args.span(),
)
.into())
}
};
Ok(Value::Map( Ok(Value::Map(
(*(*visitor.env.modules) (*(*visitor.env.modules)

View File

@ -30,6 +30,14 @@ pub(crate) fn conversion_factor(from: &Unit, to: &Unit) -> Option<f64> {
} }
impl SassNumber { impl SassNumber {
pub fn new_unitless<N: Into<Number>>(n: N) -> Self {
Self {
num: n.into(),
unit: Unit::None,
as_slash: None,
}
}
pub fn has_comparable_units(&self, other_unit: &Unit) -> bool { pub fn has_comparable_units(&self, other_unit: &Unit) -> bool {
self.unit.comparable(other_unit) self.unit.comparable(other_unit)
} }