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 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<Value>
.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<Value> {
@ -232,24 +217,14 @@ fn saturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value
));
}
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())
}
};
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<Value
.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> {
@ -279,25 +254,16 @@ fn desaturate(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Val
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, 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<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> {
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::<SassResult<_>, _>(|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)),

View File

@ -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<Value> {
args.max_args(1)?;

View File

@ -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<Value>),
}
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
hsl::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> {
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<Value>
.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<Value> {
@ -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) {

View File

@ -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<Value>),
}
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<Value> {
@ -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<Value> {
@ -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<Value> {
@ -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 {

View File

@ -2,11 +2,10 @@ use crate::builtin::builtin_imports::*;
pub(crate) fn length(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
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<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 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<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> {
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<Value> {
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<Value> {
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<Value> {
@ -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<Value> {

View File

@ -33,51 +33,40 @@ fn if_(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)?;
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<Value> {
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<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> {
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<Value> {
@ -116,14 +97,14 @@ pub(crate) fn variable_exists(
visitor: &mut Visitor,
) -> SassResult<Value> {
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<Value> {
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<Value> {
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),

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> {
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<Value> {
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<Value> {
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<Value> {
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<Value> {
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<Value> {
@ -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<Value> {

View File

@ -206,8 +206,8 @@ fn log(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
}
};
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<Value> {
} else {
number.ln()
},
unit: Unit::None,
as_slash: None,
}))
)))
}
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)?;
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<Value> {
@ -255,11 +251,9 @@ fn sqrt(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
.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)),
);
}

View File

@ -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<Value> {
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<Value> {
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)

View File

@ -30,6 +30,14 @@ pub(crate) fn conversion_factor(from: &Unit, to: &Unit) -> Option<f64> {
}
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 {
self.unit.comparable(other_unit)
}