move builtin fns to outer scope to reduce nesting

This commit is contained in:
ConnorSkees 2020-04-30 19:36:34 -04:00
parent 170759239a
commit 0aed492123
9 changed files with 2759 additions and 2861 deletions

File diff suppressed because it is too large Load Diff

View File

@ -10,151 +10,151 @@ use crate::unit::Unit;
use crate::value::Number; use crate::value::Number;
use crate::value::Value; use crate::value::Value;
fn alpha(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}
fn opacity(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
Value::Dimension(num, unit) => Ok(Value::Ident(
format!("opacity({}{})", num, unit),
QuoteKind::None,
)),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}
fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_in(amount))))
}
fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_in(amount))))
}
fn transparentize(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_out(amount))))
}
fn fade_out(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_out(amount))))
}
pub(crate) fn register(f: &mut GlobalFunctionMap) { pub(crate) fn register(f: &mut GlobalFunctionMap) {
fn alpha(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}
fn opacity(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
Value::Dimension(num, unit) => Ok(Value::Ident(
format!("opacity({}{})", num, unit),
QuoteKind::None,
)),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}
fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_in(amount))))
}
fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_in(amount))))
}
fn transparentize(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_out(amount))))
}
fn fade_out(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.fade_out(amount))))
}
f.insert("alpha", Builtin::new(alpha)); f.insert("alpha", Builtin::new(alpha));
f.insert("opacity", Builtin::new(opacity)); f.insert("opacity", Builtin::new(opacity));
f.insert("opacify", Builtin::new(opacify)); f.insert("opacify", Builtin::new(opacify));

View File

@ -54,316 +54,302 @@ macro_rules! opt_hsl {
}; };
} }
pub(crate) fn register(f: &mut GlobalFunctionMap) { fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
fn change_color( if args.get_positional(1, scope, super_selector).is_some() {
mut args: CallArgs, return Err((
scope: &Scope, "Only one positional argument is allowed. All other arguments must be passed by name.",
super_selector: &Selector, args.span(),
) -> SassResult<Value> { )
if args.get_positional(1, scope, super_selector).is_some() { .into());
return Err(
("Only one positional argument is allowed. All other arguments must be passed by name.", args.span()
).into());
}
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector);
opt_rgba!(args, red, "red", 0, 255, scope, super_selector);
opt_rgba!(args, green, "green", 0, 255, scope, super_selector);
opt_rgba!(args, blue, "blue", 0, 255, scope, super_selector);
if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba(
red.unwrap_or(color.red()),
green.unwrap_or(color.green()),
blue.unwrap_or(color.blue()),
alpha.unwrap_or(color.alpha()),
))));
}
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
Value::Dimension(n, _) => Some(n),
Value::Null => None,
v => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
opt_hsl!(
args,
saturation,
"saturation",
0,
100,
scope,
super_selector
);
opt_hsl!(args, luminance, "lightness", 0, 100, scope, super_selector);
if hue.is_some() || saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc.
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
return Ok(Value::Color(Box::new(Color::from_hsla(
hue.unwrap_or(this_hue),
saturation.unwrap_or(this_saturation),
luminance.unwrap_or(this_luminance),
alpha.unwrap_or(this_alpha),
))));
}
Ok(Value::Color(if let Some(a) = alpha {
Box::new(color.with_alpha(a))
} else {
color
}))
} }
fn adjust_color( let color = match arg!(args, scope, super_selector, 0, "color") {
mut args: CallArgs, Value::Color(c) => c,
scope: &Scope, v => {
super_selector: &Selector, return Err((
) -> SassResult<Value> { format!("$color: {} is not a color.", v.to_css_string(args.span())?),
let color = match arg!(args, scope, super_selector, 0, "color") { args.span(),
Value::Color(c) => c, )
v => { .into())
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector);
opt_rgba!(args, red, "red", -255, 255, scope, super_selector);
opt_rgba!(args, green, "green", -255, 255, scope, super_selector);
opt_rgba!(args, blue, "blue", -255, 255, scope, super_selector);
if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba(
color.red() + red.unwrap_or(Number::zero()),
color.green() + green.unwrap_or(Number::zero()),
color.blue() + blue.unwrap_or(Number::zero()),
color.alpha() + alpha.unwrap_or(Number::zero()),
))));
} }
};
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) { opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector);
Value::Dimension(n, _) => Some(n), opt_rgba!(args, red, "red", 0, 255, scope, super_selector);
Value::Null => None, opt_rgba!(args, green, "green", 0, 255, scope, super_selector);
v => { opt_rgba!(args, blue, "blue", 0, 255, scope, super_selector);
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
opt_hsl!( if red.is_some() || green.is_some() || blue.is_some() {
args, return Ok(Value::Color(Box::new(Color::from_rgba(
saturation, red.unwrap_or(color.red()),
"saturation", green.unwrap_or(color.green()),
-100, blue.unwrap_or(color.blue()),
100, alpha.unwrap_or(color.alpha()),
scope, ))));
super_selector
);
opt_hsl!(
args,
luminance,
"lightness",
-100,
100,
scope,
super_selector
);
if hue.is_some() || saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc.
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
return Ok(Value::Color(Box::new(Color::from_hsla(
this_hue + hue.unwrap_or(Number::zero()),
this_saturation + saturation.unwrap_or(Number::zero()),
this_luminance + luminance.unwrap_or(Number::zero()),
this_alpha + alpha.unwrap_or(Number::zero()),
))));
}
Ok(Value::Color(if let Some(a) = alpha {
let temp_alpha = color.alpha();
Box::new(color.with_alpha(temp_alpha + a))
} else {
color
}))
} }
fn scale_color( let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
mut args: CallArgs, Value::Dimension(n, _) => Some(n),
scope: &Scope, Value::Null => None,
super_selector: &Selector, v => {
) -> SassResult<Value> { return Err((
fn scale(val: Number, by: Number, max: Number) -> Number { format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
if by.is_zero() { args.span(),
return val; )
} .into())
val.clone() + (if by.is_positive() { max - val } else { val }) * by
} }
};
let span = args.span(); opt_hsl!(
let color = match arg!(args, scope, super_selector, 0, "color") { args,
Value::Color(c) => c, saturation,
v => { "saturation",
return Err(( 0,
format!("$color: {} is not a color.", v.to_css_string(span)?), 100,
span, scope,
) super_selector
.into()) );
} opt_hsl!(args, luminance, "lightness", 0, 100, scope, super_selector);
};
macro_rules! opt_scale_arg { if hue.is_some() || saturation.is_some() || luminance.is_some() {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => { // Color::as_hsla() returns more exact values than Color::hue(), etc.
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
Value::Dimension(n, Unit::Percent) => { return Ok(Value::Color(Box::new(Color::from_hsla(
Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100)) hue.unwrap_or(this_hue),
} saturation.unwrap_or(this_saturation),
v @ Value::Dimension(..) => { luminance.unwrap_or(this_luminance),
return Err(( alpha.unwrap_or(this_alpha),
format!( ))));
"${}: Expected {} to have unit \"%\".", }
$arg,
v.to_css_string($args.span())? Ok(Value::Color(if let Some(a) = alpha {
), Box::new(color.with_alpha(a))
$args.span(), } else {
) color
.into()) }))
} }
Value::Null => None,
v => { fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
return Err(( let color = match arg!(args, scope, super_selector, 0, "color") {
format!( Value::Color(c) => c,
"${}: {} is not a number.", v => {
$arg, return Err((
v.to_css_string($args.span())? format!("$color: {} is not a color.", v.to_css_string(args.span())?),
), args.span(),
$args.span(), )
) .into())
.into()) }
} };
};
opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector);
opt_rgba!(args, red, "red", -255, 255, scope, super_selector);
opt_rgba!(args, green, "green", -255, 255, scope, super_selector);
opt_rgba!(args, blue, "blue", -255, 255, scope, super_selector);
if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba(
color.red() + red.unwrap_or(Number::zero()),
color.green() + green.unwrap_or(Number::zero()),
color.blue() + blue.unwrap_or(Number::zero()),
color.alpha() + alpha.unwrap_or(Number::zero()),
))));
}
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
Value::Dimension(n, _) => Some(n),
Value::Null => None,
v => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
opt_hsl!(
args,
saturation,
"saturation",
-100,
100,
scope,
super_selector
);
opt_hsl!(
args,
luminance,
"lightness",
-100,
100,
scope,
super_selector
);
if hue.is_some() || saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc.
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
return Ok(Value::Color(Box::new(Color::from_hsla(
this_hue + hue.unwrap_or(Number::zero()),
this_saturation + saturation.unwrap_or(Number::zero()),
this_luminance + luminance.unwrap_or(Number::zero()),
this_alpha + alpha.unwrap_or(Number::zero()),
))));
}
Ok(Value::Color(if let Some(a) = alpha {
let temp_alpha = color.alpha();
Box::new(color.with_alpha(temp_alpha + a))
} else {
color
}))
}
fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
fn scale(val: Number, by: Number, max: Number) -> Number {
if by.is_zero() {
return val;
}
val.clone() + (if by.is_positive() { max - val } else { val }) * by
}
let span = args.span();
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(span)?),
span,
)
.into())
}
};
macro_rules! opt_scale_arg {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => {
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
Value::Dimension(n, Unit::Percent) => {
Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100))
}
v @ Value::Dimension(..) => {
return Err((
format!(
"${}: Expected {} to have unit \"%\".",
$arg,
v.to_css_string($args.span())?
),
$args.span(),
)
.into())
}
Value::Null => None,
v => {
return Err((
format!(
"${}: {} is not a number.",
$arg,
v.to_css_string($args.span())?
),
$args.span(),
)
.into())
}
}; };
}
opt_scale_arg!(args, alpha, "alpha", -100, 100, scope, super_selector);
opt_scale_arg!(args, red, "red", -100, 100, scope, super_selector);
opt_scale_arg!(args, green, "green", -100, 100, scope, super_selector);
opt_scale_arg!(args, blue, "blue", -100, 100, scope, super_selector);
if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba(
scale(
color.red(),
red.unwrap_or(Number::zero()),
Number::from(255),
),
scale(
color.green(),
green.unwrap_or(Number::zero()),
Number::from(255),
),
scale(
color.blue(),
blue.unwrap_or(Number::zero()),
Number::from(255),
),
scale(
color.alpha(),
alpha.unwrap_or(Number::zero()),
Number::one(),
),
))));
}
opt_scale_arg!(
args,
saturation,
"saturation",
-100,
100,
scope,
super_selector
);
opt_scale_arg!(
args,
luminance,
"lightness",
-100,
100,
scope,
super_selector
);
if saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc.
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
return Ok(Value::Color(Box::new(Color::from_hsla(
scale(this_hue, Number::zero(), Number::from(360)),
scale(
this_saturation,
saturation.unwrap_or(Number::zero()),
Number::one(),
),
scale(
this_luminance,
luminance.unwrap_or(Number::zero()),
Number::one(),
),
scale(this_alpha, alpha.unwrap_or(Number::zero()), Number::one()),
))));
}
Ok(Value::Color(if let Some(a) = alpha {
let temp_alpha = color.alpha();
Box::new(color.with_alpha(scale(temp_alpha, a, Number::one())))
} else {
color
}))
}
fn ie_hex_str(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Ident(color.to_ie_hex_str(), QuoteKind::None))
} }
opt_scale_arg!(args, alpha, "alpha", -100, 100, scope, super_selector);
opt_scale_arg!(args, red, "red", -100, 100, scope, super_selector);
opt_scale_arg!(args, green, "green", -100, 100, scope, super_selector);
opt_scale_arg!(args, blue, "blue", -100, 100, scope, super_selector);
if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba(
scale(
color.red(),
red.unwrap_or(Number::zero()),
Number::from(255),
),
scale(
color.green(),
green.unwrap_or(Number::zero()),
Number::from(255),
),
scale(
color.blue(),
blue.unwrap_or(Number::zero()),
Number::from(255),
),
scale(
color.alpha(),
alpha.unwrap_or(Number::zero()),
Number::one(),
),
))));
}
opt_scale_arg!(
args,
saturation,
"saturation",
-100,
100,
scope,
super_selector
);
opt_scale_arg!(
args,
luminance,
"lightness",
-100,
100,
scope,
super_selector
);
if saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc.
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
return Ok(Value::Color(Box::new(Color::from_hsla(
scale(this_hue, Number::zero(), Number::from(360)),
scale(
this_saturation,
saturation.unwrap_or(Number::zero()),
Number::one(),
),
scale(
this_luminance,
luminance.unwrap_or(Number::zero()),
Number::one(),
),
scale(this_alpha, alpha.unwrap_or(Number::zero()), Number::one()),
))));
}
Ok(Value::Color(if let Some(a) = alpha {
let temp_alpha = color.alpha();
Box::new(color.with_alpha(scale(temp_alpha, a, Number::one())))
} else {
color
}))
}
fn ie_hex_str(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Ident(color.to_ie_hex_str(), QuoteKind::None))
}
pub(crate) fn register(f: &mut GlobalFunctionMap) {
f.insert("change-color", Builtin::new(change_color)); f.insert("change-color", Builtin::new(change_color));
f.insert("adjust-color", Builtin::new(adjust_color)); f.insert("adjust-color", Builtin::new(adjust_color));
f.insert("scale-color", Builtin::new(scale_color)); f.insert("scale-color", Builtin::new(scale_color));

File diff suppressed because it is too large Load Diff

View File

@ -11,316 +11,312 @@ use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
use crate::value::{Number, Value}; use crate::value::{Number, Value};
pub(crate) fn register(f: &mut GlobalFunctionMap) { fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { args.max_args(1)?;
args.max_args(1)?; let len = match arg!(args, scope, super_selector, 0, "list") {
let len = match arg!(args, scope, super_selector, 0, "list") { Value::List(v, ..) => Number::from(v.len()),
Value::List(v, ..) => Number::from(v.len()), Value::Map(m) => Number::from(m.len()),
Value::Map(m) => Number::from(m.len()), _ => Number::one(),
_ => Number::one(), };
}; Ok(Value::Dimension(len, Unit::None))
Ok(Value::Dimension(len, Unit::None)) }
fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let list = match arg!(args, scope, super_selector, 0, "list") {
Value::List(v, ..) => v,
Value::Map(m) => m.entries(),
v => vec![v],
};
let n = match arg!(args, scope, super_selector, 1, "n") {
Value::Dimension(num, _) => num,
v => {
return Err((
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
if n.is_zero() {
return Err(("$n: List index may not be 0.", args.span()).into());
} }
fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { if n.abs() > Number::from(list.len()) {
args.max_args(2)?; return Err((
let list = match arg!(args, scope, super_selector, 0, "list") { format!(
Value::List(v, ..) => v, "$n: Invalid index {} for a list with {} elements.",
Value::Map(m) => m.entries(), n,
v => vec![v], list.len()
}; ),
let n = match arg!(args, scope, super_selector, 1, "n") { args.span(),
Value::Dimension(num, _) => num, )
v => { .into());
}
if n.is_decimal() {
return Err((format!("$n: {} is not an int.", n), args.span()).into());
}
if n.is_positive() {
Ok(list[n.to_integer().to_usize().unwrap() - 1].clone())
} else {
Ok(list[list.len() - n.abs().to_integer().to_usize().unwrap()].clone())
}
}
fn list_separator(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?;
Ok(Value::Ident(
match arg!(args, scope, super_selector, 0, "list") {
Value::List(_, sep, ..) => sep.name(),
_ => ListSeparator::Space.name(),
}
.to_owned(),
QuoteKind::None,
))
}
fn set_nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(3)?;
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
Value::List(v, sep, b) => (v, sep, b),
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
v => (vec![v], ListSeparator::Space, Brackets::None),
};
let n = match arg!(args, scope, super_selector, 1, "n") {
Value::Dimension(num, _) => num,
v => {
return Err((
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
if n.is_zero() {
return Err(("$n: List index may not be 0.", args.span()).into());
}
let len = list.len();
if n.abs() > Number::from(len) {
return Err((
format!("$n: Invalid index {} for a list with {} elements.", n, len),
args.span(),
)
.into());
}
if n.is_decimal() {
return Err((format!("$n: {} is not an int.", n), args.span()).into());
}
let val = arg!(args, scope, super_selector, 2, "value");
if n.is_positive() {
list[n.to_integer().to_usize().unwrap() - 1] = val;
} else {
list[len - n.abs().to_integer().to_usize().unwrap()] = val;
}
Ok(Value::List(list, sep, brackets))
}
fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(3)?;
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
Value::List(v, sep, b) => (v, sep, b),
v => (vec![v], ListSeparator::Space, Brackets::None),
};
let val = arg!(args, scope, super_selector, 1, "val");
let sep = match arg!(
args,
scope,
super_selector,
2,
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
) {
Value::Ident(s, ..) => match s.as_str() {
"auto" => sep,
"comma" => ListSeparator::Comma,
"space" => ListSeparator::Space,
_ => {
return Err(( return Err((
format!("$n: {} is not a number.", v.to_css_string(args.span())?), "$separator: Must be \"space\", \"comma\", or \"auto\".",
args.span(), args.span(),
) )
.into()) .into())
} }
}; },
v => {
if n.is_zero() {
return Err(("$n: List index may not be 0.", args.span()).into());
}
if n.abs() > Number::from(list.len()) {
return Err(( return Err((
format!( format!(
"$n: Invalid index {} for a list with {} elements.", "$separator: {} is not a string.",
n, v.to_css_string(args.span())?
list.len()
), ),
args.span(), args.span(),
) )
.into()); .into())
} }
};
if n.is_decimal() { list.push(val);
return Err((format!("$n: {} is not an int.", n), args.span()).into());
}
if n.is_positive() { Ok(Value::List(list, sep, brackets))
Ok(list[n.to_integer().to_usize().unwrap() - 1].clone()) }
} else {
Ok(list[list.len() - n.abs().to_integer().to_usize().unwrap()].clone())
}
}
fn list_separator( fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
mut args: CallArgs, args.max_args(4)?;
scope: &Scope, let (mut list1, sep1, brackets) = match arg!(args, scope, super_selector, 0, "list1") {
super_selector: &Selector, Value::List(v, sep, brackets) => (v, sep, brackets),
) -> SassResult<Value> { Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
args.max_args(1)?; v => (vec![v], ListSeparator::Space, Brackets::None),
Ok(Value::Ident( };
match arg!(args, scope, super_selector, 0, "list") { let (list2, sep2) = match arg!(args, scope, super_selector, 1, "list2") {
Value::List(_, sep, ..) => sep.name(), Value::List(v, sep, ..) => (v, sep),
_ => ListSeparator::Space.name(), Value::Map(m) => (m.entries(), ListSeparator::Comma),
v => (vec![v], ListSeparator::Space),
};
let sep = match arg!(
args,
scope,
super_selector,
2,
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
) {
Value::Ident(s, ..) => match s.as_str() {
"auto" => {
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
sep2
} else {
sep1
}
} }
.to_owned(), "comma" => ListSeparator::Comma,
QuoteKind::None, "space" => ListSeparator::Space,
)) _ => {
}
fn set_nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(3)?;
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
Value::List(v, sep, b) => (v, sep, b),
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
v => (vec![v], ListSeparator::Space, Brackets::None),
};
let n = match arg!(args, scope, super_selector, 1, "n") {
Value::Dimension(num, _) => num,
v => {
return Err(( return Err((
format!("$n: {} is not a number.", v.to_css_string(args.span())?), "$separator: Must be \"space\", \"comma\", or \"auto\".",
args.span(), args.span(),
) )
.into()) .into())
} }
}; },
v => {
if n.is_zero() {
return Err(("$n: List index may not be 0.", args.span()).into());
}
let len = list.len();
if n.abs() > Number::from(len) {
return Err(( return Err((
format!("$n: Invalid index {} for a list with {} elements.", n, len), format!(
"$separator: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(), args.span(),
) )
.into()); .into())
} }
};
if n.is_decimal() { let brackets = match arg!(
return Err((format!("$n: {} is not an int.", n), args.span()).into()); args,
scope,
super_selector,
3,
"bracketed" = Value::Ident("auto".to_owned(), QuoteKind::None)
) {
Value::Ident(s, ..) => match s.as_str() {
"auto" => brackets,
_ => Brackets::Bracketed,
},
v => {
if v.is_true(args.span())? {
Brackets::Bracketed
} else {
Brackets::None
}
} }
};
let val = arg!(args, scope, super_selector, 2, "value"); list1.extend(list2);
if n.is_positive() { Ok(Value::List(list1, sep, brackets))
list[n.to_integer().to_usize().unwrap() - 1] = val; }
} else {
list[len - n.abs().to_integer().to_usize().unwrap()] = val;
}
Ok(Value::List(list, sep, brackets)) fn is_bracketed(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
} args.max_args(1)?;
Ok(Value::bool(
fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { match arg!(args, scope, super_selector, 0, "list") {
args.max_args(3)?; Value::List(.., brackets) => match brackets {
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") { Brackets::Bracketed => true,
Value::List(v, sep, b) => (v, sep, b), Brackets::None => false,
v => (vec![v], ListSeparator::Space, Brackets::None),
};
let val = arg!(args, scope, super_selector, 1, "val");
let sep = match arg!(
args,
scope,
super_selector,
2,
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
) {
Value::Ident(s, ..) => match s.as_str() {
"auto" => sep,
"comma" => ListSeparator::Comma,
"space" => ListSeparator::Space,
_ => {
return Err((
"$separator: Must be \"space\", \"comma\", or \"auto\".",
args.span(),
)
.into())
}
}, },
v => { _ => false,
return Err(( },
format!( ))
"$separator: {} is not a string.", }
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
list.push(val); fn index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let list = match arg!(args, scope, super_selector, 0, "list") {
Value::List(v, ..) => v,
Value::Map(m) => m.entries(),
v => vec![v],
};
let value = arg!(args, scope, super_selector, 1, "value");
// TODO: find a way around this unwrap.
// It should be impossible to hit as the arg is
// evaluated prior to checking equality, but
// it is still dirty.
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
let index = match list
.into_iter()
.position(|v| v.equals(value.clone(), args.span()).unwrap())
{
Some(v) => Number::from(v + 1),
None => return Ok(Value::Null),
};
Ok(Value::Dimension(index, Unit::None))
}
Ok(Value::List(list, sep, brackets)) fn zip(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
} let span = args.span();
let lists = args
fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { .get_variadic(scope, super_selector)?
args.max_args(4)?; .into_iter()
let (mut list1, sep1, brackets) = match arg!(args, scope, super_selector, 0, "list1") { .map(|x| {
Value::List(v, sep, brackets) => (v, sep, brackets), Ok(match x.node.eval(span)?.node {
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None), Value::List(v, ..) => v,
v => (vec![v], ListSeparator::Space, Brackets::None), Value::Map(m) => m.entries(),
}; v => vec![v],
let (list2, sep2) = match arg!(args, scope, super_selector, 1, "list2") {
Value::List(v, sep, ..) => (v, sep),
Value::Map(m) => (m.entries(), ListSeparator::Comma),
v => (vec![v], ListSeparator::Space),
};
let sep = match arg!(
args,
scope,
super_selector,
2,
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
) {
Value::Ident(s, ..) => match s.as_str() {
"auto" => {
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
sep2
} else {
sep1
}
}
"comma" => ListSeparator::Comma,
"space" => ListSeparator::Space,
_ => {
return Err((
"$separator: Must be \"space\", \"comma\", or \"auto\".",
args.span(),
)
.into())
}
},
v => {
return Err((
format!(
"$separator: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let brackets = match arg!(
args,
scope,
super_selector,
3,
"bracketed" = Value::Ident("auto".to_owned(), QuoteKind::None)
) {
Value::Ident(s, ..) => match s.as_str() {
"auto" => brackets,
_ => Brackets::Bracketed,
},
v => {
if v.is_true(args.span())? {
Brackets::Bracketed
} else {
Brackets::None
}
}
};
list1.extend(list2);
Ok(Value::List(list1, sep, brackets))
}
fn is_bracketed(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?;
Ok(Value::bool(
match arg!(args, scope, super_selector, 0, "list") {
Value::List(.., brackets) => match brackets {
Brackets::Bracketed => true,
Brackets::None => false,
},
_ => false,
},
))
}
fn index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let list = match arg!(args, scope, super_selector, 0, "list") {
Value::List(v, ..) => v,
Value::Map(m) => m.entries(),
v => vec![v],
};
let value = arg!(args, scope, super_selector, 1, "value");
// TODO: find a way around this unwrap.
// It should be impossible to hit as the arg is
// evaluated prior to checking equality, but
// it is still dirty.
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
let index = match list
.into_iter()
.position(|v| v.equals(value.clone(), args.span()).unwrap())
{
Some(v) => Number::from(v + 1),
None => return Ok(Value::Null),
};
Ok(Value::Dimension(index, Unit::None))
}
fn zip(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
let span = args.span();
let lists = args
.get_variadic(scope, super_selector)?
.into_iter()
.map(|x| {
Ok(match x.node.eval(span)?.node {
Value::List(v, ..) => v,
Value::Map(m) => m.entries(),
v => vec![v],
})
}) })
.collect::<SassResult<Vec<Vec<Value>>>>()?; })
.collect::<SassResult<Vec<Vec<Value>>>>()?;
let len = lists.iter().map(Vec::len).min().unwrap_or(0); let len = lists.iter().map(Vec::len).min().unwrap_or(0);
if len == 0 { if len == 0 {
return Ok(Value::List( return Ok(Value::List(
Vec::new(), Vec::new(),
ListSeparator::Comma, ListSeparator::Comma,
Brackets::None, Brackets::None,
)); ));
}
let result = (0..len)
.map(|i| {
let items = lists.iter().map(|v| v[i].clone()).collect();
Value::List(items, ListSeparator::Space, Brackets::None)
})
.collect();
Ok(Value::List(result, ListSeparator::Comma, Brackets::None))
} }
let result = (0..len)
.map(|i| {
let items = lists.iter().map(|v| v[i].clone()).collect();
Value::List(items, ListSeparator::Space, Brackets::None)
})
.collect();
Ok(Value::List(result, ListSeparator::Comma, Brackets::None))
}
pub(crate) fn register(f: &mut GlobalFunctionMap) {
f.insert("length", Builtin::new(length)); f.insert("length", Builtin::new(length));
f.insert("nth", Builtin::new(nth)); f.insert("nth", Builtin::new(nth));
f.insert("list-separator", Builtin::new(list_separator)); f.insert("list-separator", Builtin::new(list_separator));

View File

@ -8,144 +8,128 @@ use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::value::{SassMap, Value}; use crate::value::{SassMap, Value};
pub(crate) fn register(f: &mut GlobalFunctionMap) { fn map_get(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
fn map_get(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { args.max_args(2)?;
args.max_args(2)?; let key = arg!(args, scope, super_selector, 1, "key");
let key = arg!(args, scope, super_selector, 1, "key"); let map = match arg!(args, scope, super_selector, 0, "map") {
let map = match arg!(args, scope, super_selector, 0, "map") { Value::Map(m) => m,
Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(),
Value::List(v, ..) if v.is_empty() => SassMap::new(), v => {
v => { return Err((
return Err(( format!("$map: {} is not a map.", v.to_css_string(args.span())?),
format!("$map: {} is not a map.", v.to_css_string(args.span())?), args.span(),
args.span(), )
) .into())
.into())
}
};
Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
}
fn map_has_key(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?;
let key = arg!(args, scope, super_selector, 1, "key");
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::bool(map.get(&key, args.span())?.is_some()))
}
fn map_keys(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::List(
map.keys(),
ListSeparator::Comma,
Brackets::None,
))
}
fn map_values(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?;
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::List(
map.values(),
ListSeparator::Comma,
Brackets::None,
))
}
fn map_merge(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?;
let mut map1 = match arg!(args, scope, super_selector, 0, "map1") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map1: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let map2 = match arg!(args, scope, super_selector, 1, "map2") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map2: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
map1.merge(map2);
Ok(Value::Map(map1))
}
fn map_remove(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
let mut map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let keys = args.get_variadic(scope, super_selector)?;
for key in keys {
map.remove(&key);
} }
Ok(Value::Map(map)) };
} Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
}
fn map_has_key(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let key = arg!(args, scope, super_selector, 1, "key");
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::bool(map.get(&key, args.span())?.is_some()))
}
fn map_keys(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::List(
map.keys(),
ListSeparator::Comma,
Brackets::None,
))
}
fn map_values(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::List(
map.values(),
ListSeparator::Comma,
Brackets::None,
))
}
fn map_merge(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let mut map1 = match arg!(args, scope, super_selector, 0, "map1") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map1: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let map2 = match arg!(args, scope, super_selector, 1, "map2") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map2: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
map1.merge(map2);
Ok(Value::Map(map1))
}
fn map_remove(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
let mut map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let keys = args.get_variadic(scope, super_selector)?;
for key in keys {
map.remove(&key);
}
Ok(Value::Map(map))
}
pub(crate) fn register(f: &mut GlobalFunctionMap) {
f.insert("map-get", Builtin::new(map_get)); f.insert("map-get", Builtin::new(map_get));
f.insert("map-has-key", Builtin::new(map_has_key)); f.insert("map-has-key", Builtin::new(map_has_key));
f.insert("map-keys", Builtin::new(map_keys)); f.insert("map-keys", Builtin::new(map_keys));

View File

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

View File

@ -12,233 +12,225 @@ use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
use crate::value::{SassFunction, Value}; use crate::value::{SassFunction, Value};
pub(crate) fn register(f: &mut GlobalFunctionMap) { fn if_(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
fn if_(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { args.max_args(3)?;
args.max_args(3)?; if arg!(args, scope, super_selector, 0, "condition").is_true(args.span())? {
if arg!(args, scope, super_selector, 0, "condition").is_true(args.span())? { Ok(arg!(args, scope, super_selector, 1, "if-true"))
Ok(arg!(args, scope, super_selector, 1, "if-true")) } else {
} else { Ok(arg!(args, scope, super_selector, 2, "if-false"))
Ok(arg!(args, scope, super_selector, 2, "if-false"))
}
} }
}
fn feature_exists( fn feature_exists(
mut args: CallArgs, mut args: CallArgs,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "feature") { match arg!(args, scope, super_selector, 0, "feature") {
Value::Ident(s, _) => Ok(match s.as_str() { Value::Ident(s, _) => Ok(match s.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,
// the @extend rule will affect selectors nested in pseudo-classes // the @extend rule will affect selectors nested in pseudo-classes
// like :not() // like :not()
"extend-selector-pseudoclass" => Value::False, "extend-selector-pseudoclass" => Value::False,
// Full support for unit arithmetic using units defined in the // Full support for unit arithmetic using units defined in the
// [Values and Units Level 3][] spec. // [Values and Units Level 3][] spec.
"units-level-3" => Value::True, "units-level-3" => Value::True,
// The Sass `@error` directive is supported. // The Sass `@error` directive is supported.
"at-error" => Value::True, "at-error" => Value::True,
// The "Custom Properties Level 1" spec is supported. This means // The "Custom Properties Level 1" spec is supported. This means
// that custom properties are parsed statically, with only // that custom properties are parsed statically, with only
// interpolation treated as SassScript. // interpolation treated as SassScript.
"custom-property" => Value::False, "custom-property" => Value::False,
_ => Value::False, _ => Value::False,
}), }),
v => Err(( v => Err((
format!(
"$feature: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
}
}
fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
let unit = match arg!(args, scope, super_selector, 0, "number") {
Value::Dimension(_, u) => u.to_string(),
v => {
return Err((
format!( format!(
"$feature: {} is not a string.", "$number: {} is not a number.",
v.to_css_string(args.span())? v.to_css_string(args.span())?
), ),
args.span(), args.span(),
) )
.into()), .into())
} }
} };
Ok(Value::Ident(unit, QuoteKind::Quoted))
}
fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn type_of(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let unit = match arg!(args, scope, super_selector, 0, "number") { let value = arg!(args, scope, super_selector, 0, "value");
Value::Dimension(_, u) => u.to_string(), Ok(Value::Ident(
v => { value.kind(args.span())?.to_owned(),
return Err(( QuoteKind::None,
format!( ))
"$number: {} is not a number.", }
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Ident(unit, QuoteKind::Quoted))
}
fn type_of(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn unitless(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let value = arg!(args, scope, super_selector, 0, "value"); match arg!(args, scope, super_selector, 0, "number") {
Ok(Value::Ident( Value::Dimension(_, Unit::None) => Ok(Value::True),
value.kind(args.span())?.to_owned(), Value::Dimension(_, _) => Ok(Value::False),
QuoteKind::None, _ => Ok(Value::True),
))
} }
fn unitless(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { }
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "number") { fn inspect(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
Value::Dimension(_, Unit::None) => Ok(Value::True), args.max_args(1)?;
Value::Dimension(_, _) => Ok(Value::False), Ok(Value::Ident(
_ => Ok(Value::True), arg!(args, scope, super_selector, 0, "value").inspect(args.span())?,
} QuoteKind::None,
))
}
fn variable_exists(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))),
v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}
fn inspect(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn global_variable_exists(
args.max_args(1)?; mut args: CallArgs,
Ok(Value::Ident( scope: &Scope,
arg!(args, scope, super_selector, 0, "value").inspect(args.span())?, super_selector: &Selector,
QuoteKind::None, ) -> SassResult<Value> {
)) args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))),
v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}
fn variable_exists( fn mixin_exists(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
mut args: CallArgs, args.max_args(2)?;
scope: &Scope, match arg!(args, scope, super_selector, 0, "name") {
super_selector: &Selector, Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))),
) -> SassResult<Value> { v => Err((
args.max_args(1)?; format!("$name: {} is not a string.", v.to_css_string(args.span())?),
match arg!(args, scope, super_selector, 0, "name") { args.span(),
Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))), )
v => Err(( .into()),
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
} }
}
fn global_variable_exists( fn function_exists(
mut args: CallArgs, mut args: CallArgs,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(2)?;
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))), Value::Ident(s, _) => Ok(Value::bool(
v => Err(( scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(s.as_str()),
format!("$name: {} is not a string.", v.to_css_string(args.span())?), )),
args.span(), v => Err((
) format!("$name: {} is not a string.", v.to_css_string(args.span())?),
.into()), args.span(),
} )
.into()),
} }
}
fn mixin_exists( fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
mut args: CallArgs, args.max_args(3)?;
scope: &Scope, let name = match arg!(args, scope, super_selector, 0, "name") {
super_selector: &Selector, Value::Ident(s, _) => s,
) -> SassResult<Value> { v => {
args.max_args(2)?;
match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))),
v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}
fn function_exists(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?;
match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(
scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(s.as_str()),
)),
v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}
fn get_function(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(3)?;
let name = match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => s,
v => {
return Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let css =
arg!(args, scope, super_selector, 1, "css" = Value::False).is_true(args.span())?;
let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) {
Value::Ident(s, ..) => Some(s),
Value::Null => None,
v => {
return Err((
format!(
"$module: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
if module.is_some() && css {
return Err(( return Err((
"$css and $module may not both be passed at once.", format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(), args.span(),
) )
.into()); .into())
} }
};
let css = arg!(args, scope, super_selector, 1, "css" = Value::False).is_true(args.span())?;
let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) {
Value::Ident(s, ..) => Some(s),
Value::Null => None,
v => {
return Err((
format!(
"$module: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let func = match scope.get_fn(Spanned { if module.is_some() && css {
node: name.clone(), return Err((
span: args.span(), "$css and $module may not both be passed at once.",
}) { args.span(),
Ok(f) => SassFunction::UserDefined(Box::new(f), name), )
Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) { .into());
Some(f) => SassFunction::Builtin(f.clone(), name),
None => return Err((format!("Function not found: {}", name), args.span()).into()),
},
};
Ok(Value::Function(func))
} }
fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { let func = match scope.get_fn(Spanned {
let func = match arg!(args, scope, super_selector, 0, "function") { node: name.clone(),
Value::Function(f) => f, span: args.span(),
v => { }) {
return Err(( Ok(f) => SassFunction::UserDefined(Box::new(f), name),
format!( Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) {
"$function: {} is not a function reference.", Some(f) => SassFunction::Builtin(f.clone(), name),
v.to_css_string(args.span())? None => return Err((format!("Function not found: {}", name), args.span()).into()),
), },
args.span(), };
)
.into())
}
};
func.call(args.decrement(), scope, super_selector)
}
Ok(Value::Function(func))
}
fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
let func = match arg!(args, scope, super_selector, 0, "function") {
Value::Function(f) => f,
v => {
return Err((
format!(
"$function: {} is not a function reference.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
func.call(args.decrement(), scope, super_selector)
}
pub(crate) fn register(f: &mut GlobalFunctionMap) {
f.insert("if", Builtin::new(if_)); f.insert("if", Builtin::new(if_));
f.insert("feature-exists", Builtin::new(feature_exists)); f.insert("feature-exists", Builtin::new(feature_exists));
f.insert("unit", Builtin::new(unit)); f.insert("unit", Builtin::new(unit));

View File

@ -15,365 +15,349 @@ use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
use crate::value::{Number, Value}; use crate::value::{Number, Value};
pub(crate) fn register(f: &mut GlobalFunctionMap) { fn to_upper_case(
fn to_upper_case( mut args: CallArgs,
mut args: CallArgs, scope: &Scope,
scope: &Scope, super_selector: &Selector,
super_selector: &Selector, ) -> SassResult<Value> {
) -> SassResult<Value> { args.max_args(1)?;
args.max_args(1)?; match arg!(args, scope, super_selector, 0, "string") {
match arg!(args, scope, super_selector, 0, "string") { Value::Ident(mut i, q) => {
Value::Ident(mut i, q) => { i.make_ascii_uppercase();
i.make_ascii_uppercase(); Ok(Value::Ident(i, q))
Ok(Value::Ident(i, q)) }
} v => Err((
v => Err(( format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
}
}
fn to_lower_case(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(mut i, q) => {
i.make_ascii_lowercase();
Ok(Value::Ident(i, q))
}
v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
}
}
fn str_length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => Ok(Value::Dimension(
Number::from(i.chars().count()),
Unit::None,
)),
v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
}
}
fn quote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => Ok(Value::Ident(i, QuoteKind::Quoted)),
v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
}
}
fn unquote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") {
i @ Value::Ident(..) => Ok(i.unquote()),
v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
}
}
fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(3)?;
let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(s, q) => (s, q),
v => {
return Err((
format!( format!(
"$string: {} is not a string.", "$string: {} is not a string.",
v.to_css_string(args.span())? v.to_css_string(args.span())?
), ),
args.span(), args.span(),
) )
.into()), .into())
} }
};
let str_len = string.chars().count();
let start = match arg!(args, scope, super_selector, 1, "start-at") {
Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into())
}
Value::Dimension(n, Unit::None) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1)
}
Value::Dimension(n, Unit::None) if n.is_zero() => 1_usize,
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 1_usize,
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
.to_usize()
.unwrap(),
v @ Value::Dimension(..) => {
return Err((
format!(
"$start: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v => {
return Err((
format!(
"$start-at: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let mut end = match arg!(args, scope, super_selector, 2, "end-at" = Value::Null) {
Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into())
}
Value::Dimension(n, Unit::None) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1)
}
Value::Dimension(n, Unit::None) if n.is_zero() => 0_usize,
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 0_usize,
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
.to_usize()
.unwrap_or(str_len + 1),
v @ Value::Dimension(..) => {
return Err((
format!(
"$end: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
Value::Null => str_len,
v => {
return Err((
format!(
"$end-at: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
if end > str_len {
end = str_len;
} }
fn to_lower_case( if start > end || start > str_len {
mut args: CallArgs, Ok(Value::Ident(String::new(), quotes))
scope: &Scope, } else {
super_selector: &Selector, Ok(Value::Ident(
) -> SassResult<Value> { string
args.max_args(1)?; .chars()
match arg!(args, scope, super_selector, 0, "string") { .skip(start - 1)
Value::Ident(mut i, q) => { .take(end - start + 1)
i.make_ascii_lowercase(); .collect(),
Ok(Value::Ident(i, q)) quotes,
} ))
v => Err(( }
}
fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?;
let s1 = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => i,
v => {
return Err((
format!( format!(
"$string: {} is not a string.", "$string: {} is not a string.",
v.to_css_string(args.span())? v.to_css_string(args.span())?
), ),
args.span(), args.span(),
) )
.into()), .into())
} }
} };
fn str_length( let substr = match arg!(args, scope, super_selector, 1, "substring") {
mut args: CallArgs, Value::Ident(i, _) => i,
scope: &Scope, v => {
super_selector: &Selector, return Err((
) -> SassResult<Value> { format!(
args.max_args(1)?; "$substring: {} is not a string.",
match arg!(args, scope, super_selector, 0, "string") { v.to_css_string(args.span())?
Value::Ident(i, _) => Ok(Value::Dimension( ),
Number::from(i.chars().count()), args.span(),
Unit::None, )
)), .into())
v => Err(( }
};
Ok(match s1.find(&substr) {
Some(v) => Value::Dimension(Number::from(v + 1), Unit::None),
None => Value::Null,
})
}
fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(3)?;
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, q) => (i, q),
v => {
return Err((
format!( format!(
"$string: {} is not a string.", "$string: {} is not a string.",
v.to_css_string(args.span())? v.to_css_string(args.span())?
), ),
args.span(), args.span(),
) )
.into()), .into())
} }
} };
fn quote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { let substr = match arg!(args, scope, super_selector, 1, "insert") {
args.max_args(1)?; Value::Ident(i, _) => i,
match arg!(args, scope, super_selector, 0, "string") { v => {
Value::Ident(i, _) => Ok(Value::Ident(i, QuoteKind::Quoted)), return Err((
v => Err((
format!( format!(
"$string: {} is not a string.", "$insert: {} is not a string.",
v.to_css_string(args.span())? v.to_css_string(args.span())?
), ),
args.span(), args.span(),
) )
.into()), .into())
} }
} };
fn unquote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { let index = match arg!(args, scope, super_selector, 2, "index") {
args.max_args(1)?; Value::Dimension(n, Unit::None) if n.is_decimal() => {
match arg!(args, scope, super_selector, 0, "string") { return Err((format!("$index: {} is not an int.", n), args.span()).into())
i @ Value::Ident(..) => Ok(i.unquote()), }
v => Err(( Value::Dimension(n, Unit::None) => n,
v @ Value::Dimension(..) => {
return Err((
format!( format!(
"$string: {} is not a string.", "$index: Expected {} to have no units.",
v.to_css_string(args.span())? v.to_css_string(args.span())?
), ),
args.span(), args.span(),
) )
.into()), .into())
} }
v => {
return Err((
format!("$index: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
if s1.is_empty() {
return Ok(Value::Ident(substr, quotes));
} }
fn str_slice( let len = s1.chars().count();
mut args: CallArgs,
scope: &Scope, // Insert substring at char position, rather than byte position
super_selector: &Selector, let insert = |idx, s1: String, s2| {
) -> SassResult<Value> { s1.chars()
args.max_args(3)?; .enumerate()
let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") { .map(|(i, c)| {
Value::Ident(s, q) => (s, q), if i + 1 == idx {
v => { c.to_string() + s2
return Err(( } else if idx == 0 && i == 0 {
format!( s2.to_string() + &c.to_string()
"$string: {} is not a string.", } else {
v.to_css_string(args.span())? c.to_string()
), }
args.span(), })
) .collect::<String>()
.into()) };
}
}; let string = if index.is_positive() {
let str_len = string.chars().count(); insert(
let start = match arg!(args, scope, super_selector, 1, "start-at") { index
Value::Dimension(n, Unit::None) if n.is_decimal() => { .to_integer()
return Err((format!("{} is not an int.", n), args.span()).into())
}
Value::Dimension(n, Unit::None) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1)
}
Value::Dimension(n, Unit::None) if n.is_zero() => 1_usize,
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 1_usize,
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
.to_usize() .to_usize()
.unwrap(), .unwrap_or(len + 1)
v @ Value::Dimension(..) => { .min(len + 1)
return Err(( - 1,
format!( s1,
"$start: Expected {} to have no units.", &substr,
v.to_css_string(args.span())? )
), } else if index.is_zero() {
args.span(), insert(0, s1, &substr)
) } else {
.into()) let idx = index.abs().to_integer().to_usize().unwrap_or(len + 1);
} if idx > len {
v => {
return Err((
format!(
"$start-at: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let mut end = match arg!(args, scope, super_selector, 2, "end-at" = Value::Null) {
Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into())
}
Value::Dimension(n, Unit::None) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1)
}
Value::Dimension(n, Unit::None) if n.is_zero() => 0_usize,
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 0_usize,
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
.to_usize()
.unwrap_or(str_len + 1),
v @ Value::Dimension(..) => {
return Err((
format!(
"$end: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
Value::Null => str_len,
v => {
return Err((
format!(
"$end-at: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
if end > str_len {
end = str_len;
}
if start > end || start > str_len {
Ok(Value::Ident(String::new(), quotes))
} else {
Ok(Value::Ident(
string
.chars()
.skip(start - 1)
.take(end - start + 1)
.collect(),
quotes,
))
}
}
fn str_index(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?;
let s1 = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => i,
v => {
return Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let substr = match arg!(args, scope, super_selector, 1, "substring") {
Value::Ident(i, _) => i,
v => {
return Err((
format!(
"$substring: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(match s1.find(&substr) {
Some(v) => Value::Dimension(Number::from(v + 1), Unit::None),
None => Value::Null,
})
}
fn str_insert(
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(3)?;
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, q) => (i, q),
v => {
return Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let substr = match arg!(args, scope, super_selector, 1, "insert") {
Value::Ident(i, _) => i,
v => {
return Err((
format!(
"$insert: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let index = match arg!(args, scope, super_selector, 2, "index") {
Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("$index: {} is not an int.", n), args.span()).into())
}
Value::Dimension(n, Unit::None) => n,
v @ Value::Dimension(..) => {
return Err((
format!(
"$index: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v => {
return Err((
format!("$index: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
if s1.is_empty() {
return Ok(Value::Ident(substr, quotes));
}
let len = s1.chars().count();
// Insert substring at char position, rather than byte position
let insert = |idx, s1: String, s2| {
s1.chars()
.enumerate()
.map(|(i, c)| {
if i + 1 == idx {
c.to_string() + s2
} else if idx == 0 && i == 0 {
s2.to_string() + &c.to_string()
} else {
c.to_string()
}
})
.collect::<String>()
};
let string = if index.is_positive() {
insert(
index
.to_integer()
.to_usize()
.unwrap_or(len + 1)
.min(len + 1)
- 1,
s1,
&substr,
)
} else if index.is_zero() {
insert(0, s1, &substr) insert(0, s1, &substr)
} else { } else {
let idx = index.abs().to_integer().to_usize().unwrap_or(len + 1); insert(len - idx + 1, s1, &substr)
if idx > len { }
insert(0, s1, &substr) };
} else {
insert(len - idx + 1, s1, &substr)
}
};
Ok(Value::Ident(string, quotes)) Ok(Value::Ident(string, quotes))
} }
#[cfg(feature = "random")] #[cfg(feature = "random")]
fn unique_id(args: CallArgs, _: &Scope, _: &Selector) -> SassResult<Value> { fn unique_id(args: CallArgs, _: &Scope, _: &Selector) -> SassResult<Value> {
args.max_args(0)?; args.max_args(0)?;
let mut rng = thread_rng(); let mut rng = thread_rng();
let string = std::iter::repeat(()) let string = std::iter::repeat(())
.map(|()| rng.sample(Alphanumeric)) .map(|()| rng.sample(Alphanumeric))
.take(7) .take(7)
.collect(); .collect();
Ok(Value::Ident(string, QuoteKind::None)) Ok(Value::Ident(string, QuoteKind::None))
} }
pub(crate) fn register(f: &mut GlobalFunctionMap) {
f.insert("to-upper-case", Builtin::new(to_upper_case)); f.insert("to-upper-case", Builtin::new(to_upper_case));
f.insert("to-lower-case", Builtin::new(to_lower_case)); f.insert("to-lower-case", Builtin::new(to_lower_case));
f.insert("str-length", Builtin::new(str_length)); f.insert("str-length", Builtin::new(str_length));