only evaluate division in certain cases

This commit is contained in:
Connor Skees 2020-07-09 23:52:35 -04:00
parent 0639a6ba2b
commit 830d56bd77
21 changed files with 336 additions and 226 deletions

View File

@ -35,7 +35,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
} }
let lightness = match channels.pop() { let lightness = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100), Some(Value::Dimension(n, ..)) => n / Number::from(100),
Some(v) => { Some(v) => {
return Err(( return Err((
format!("$lightness: {} is not a number.", v.inspect(args.span())?), format!("$lightness: {} is not a number.", v.inspect(args.span())?),
@ -47,7 +47,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
}; };
let saturation = match channels.pop() { let saturation = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100), Some(Value::Dimension(n, ..)) => n / Number::from(100),
Some(v) => { Some(v) => {
return Err(( return Err((
format!("$saturation: {} is not a number.", v.inspect(args.span())?), format!("$saturation: {} is not a number.", v.inspect(args.span())?),
@ -59,7 +59,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
}; };
let hue = match channels.pop() { let hue = match channels.pop() {
Some(Value::Dimension(n, _)) => n, Some(Value::Dimension(n, ..)) => n,
Some(v) => { Some(v) => {
return Err(( return Err((
format!("$hue: {} is not a number.", v.inspect(args.span())?), format!("$hue: {} is not a number.", v.inspect(args.span())?),
@ -78,7 +78,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
)))) ))))
} else { } else {
let hue = match parser.arg(&mut args, 0, "hue")? { let hue = match parser.arg(&mut args, 0, "hue")? {
Value::Dimension(n, _) => n, Value::Dimension(n, ..) => n,
v if v.is_special_function() => { v if v.is_special_function() => {
let saturation = parser.arg(&mut args, 1, "saturation")?; let saturation = parser.arg(&mut args, 1, "saturation")?;
let lightness = parser.arg(&mut args, 2, "lightness")?; let lightness = parser.arg(&mut args, 2, "lightness")?;
@ -109,7 +109,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
} }
}; };
let saturation = match parser.arg(&mut args, 1, "saturation")? { let saturation = match parser.arg(&mut args, 1, "saturation")? {
Value::Dimension(n, _) => n / Number::from(100), Value::Dimension(n, ..) => n / Number::from(100),
v if v.is_special_function() => { v if v.is_special_function() => {
let lightness = parser.arg(&mut args, 2, "lightness")?; let lightness = parser.arg(&mut args, 2, "lightness")?;
let mut string = format!( let mut string = format!(
@ -142,7 +142,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
} }
}; };
let lightness = match parser.arg(&mut args, 2, "lightness")? { let lightness = match parser.arg(&mut args, 2, "lightness")? {
Value::Dimension(n, _) => n / Number::from(100), Value::Dimension(n, ..) => n / Number::from(100),
v if v.is_special_function() => { v if v.is_special_function() => {
let mut string = format!( let mut string = format!(
"{}({}, {}, {}", "{}({}, {}, {}",
@ -177,10 +177,10 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
&mut args, &mut args,
3, 3,
"alpha", "alpha",
Value::Dimension(Number::one(), Unit::None), Value::Dimension(Number::one(), Unit::None, true),
)? { )? {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None, _) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100), Value::Dimension(n, Unit::Percent, _) => n / Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -229,7 +229,7 @@ fn hsla(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg)), Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg, true)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?), format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(), args.span(),
@ -241,7 +241,7 @@ fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent)), Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent, true)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?), format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(), args.span(),
@ -253,7 +253,7 @@ fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent)), Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent, true)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?), format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(), args.span(),
@ -275,7 +275,7 @@ fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
} }
}; };
let degrees = match parser.arg(&mut args, 1, "degrees")? { let degrees = match parser.arg(&mut args, 1, "degrees")? {
Value::Dimension(n, _) => n, Value::Dimension(n, ..) => n,
v => { v => {
return Err(( return Err((
format!( format!(
@ -303,7 +303,7 @@ fn lighten(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
}; };
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
format!( format!(
@ -331,7 +331,7 @@ fn darken(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
}; };
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
format!( format!(
@ -361,7 +361,7 @@ fn saturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
format!( format!(
@ -375,7 +375,7 @@ fn saturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
}; };
let color = match parser.arg(&mut args, 0, "color")? { let color = match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => c, Value::Color(c) => c,
Value::Dimension(n, u) => { Value::Dimension(n, u, _) => {
return Ok(Value::String( return Ok(Value::String(
format!("saturate({}{})", n, u), format!("saturate({}{})", n, u),
QuoteKind::None, QuoteKind::None,
@ -405,7 +405,7 @@ fn desaturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
} }
}; };
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
format!( format!(
@ -424,7 +424,7 @@ fn grayscale(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let color = match parser.arg(&mut args, 0, "color")? { let color = match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => c, Value::Color(c) => c,
Value::Dimension(n, u) => { Value::Dimension(n, u, _) => {
return Ok(Value::String( return Ok(Value::String(
format!("grayscale({}{})", n, u), format!("grayscale({}{})", n, u),
QuoteKind::None, QuoteKind::None,
@ -462,9 +462,9 @@ fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
&mut args, &mut args,
1, 1,
"weight", "weight",
Value::Dimension(Number::from(100), Unit::Percent), Value::Dimension(Number::from(100), Unit::Percent, true),
)? { )? {
Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u, _) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
format!( format!(
@ -478,7 +478,7 @@ fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
}; };
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))), Value::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))),
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent, _) => {
Ok(Value::String(format!("invert({}%)", n), QuoteKind::None)) Ok(Value::String(format!("invert({}%)", n), QuoteKind::None))
} }
Value::Dimension(..) => Err(( Value::Dimension(..) => Err((

View File

@ -8,7 +8,7 @@ use crate::{
fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None, true)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?), format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(), args.span(),
@ -20,8 +20,8 @@ fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn opacity(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn opacity(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None, true)),
Value::Dimension(num, unit) => Ok(Value::String( Value::Dimension(num, unit, _) => Ok(Value::String(
format!("opacity({}{})", num, unit), format!("opacity({}{})", num, unit),
QuoteKind::None, QuoteKind::None,
)), )),
@ -46,7 +46,7 @@ fn opacify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
}; };
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( return Err((
format!("$amount: {} is not a number.", v.inspect(args.span())?), format!("$amount: {} is not a number.", v.inspect(args.span())?),
@ -71,7 +71,7 @@ fn fade_in(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
}; };
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( return Err((
format!("$amount: {} is not a number.", v.inspect(args.span())?), format!("$amount: {} is not a number.", v.inspect(args.span())?),
@ -96,7 +96,7 @@ fn transparentize(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Val
} }
}; };
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( return Err((
format!("$amount: {} is not a number.", v.inspect(args.span())?), format!("$amount: {} is not a number.", v.inspect(args.span())?),
@ -121,7 +121,7 @@ fn fade_out(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
}; };
let amount = match parser.arg(&mut args, 1, "amount")? { let amount = match parser.arg(&mut args, 1, "amount")? {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1), Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( return Err((
format!("$amount: {} is not a number.", v.inspect(args.span())?), format!("$amount: {} is not a number.", v.inspect(args.span())?),

View File

@ -15,7 +15,7 @@ use crate::{
macro_rules! opt_rgba { macro_rules! opt_rgba {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? { let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
Value::Dimension(n, u) => Some(bound!($args, $arg, n, u, $low, $high)), Value::Dimension(n, u, _) => Some(bound!($args, $arg, n, u, $low, $high)),
Value::Null => None, Value::Null => None,
v => { v => {
return Err(( return Err((
@ -31,7 +31,7 @@ macro_rules! opt_rgba {
macro_rules! opt_hsl { macro_rules! opt_hsl {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? { let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
Value::Dimension(n, u) => { Value::Dimension(n, u, _) => {
Some(bound!($args, $arg, n, u, $low, $high) / Number::from(100)) Some(bound!($args, $arg, n, u, $low, $high) / Number::from(100))
} }
Value::Null => None, Value::Null => None,
@ -81,7 +81,7 @@ fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value
} }
let hue = match parser.default_named_arg(&mut args, "hue", Value::Null)? { let hue = match parser.default_named_arg(&mut args, "hue", Value::Null)? {
Value::Dimension(n, _) => Some(n), Value::Dimension(n, ..) => Some(n),
Value::Null => None, Value::Null => None,
v => { v => {
return Err(( return Err((
@ -140,7 +140,7 @@ fn adjust_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value
} }
let hue = match parser.default_named_arg(&mut args, "hue", Value::Null)? { let hue = match parser.default_named_arg(&mut args, "hue", Value::Null)? {
Value::Dimension(n, _) => Some(n), Value::Dimension(n, ..) => Some(n),
Value::Null => None, Value::Null => None,
v => { v => {
return Err(( return Err((
@ -198,7 +198,7 @@ fn scale_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
macro_rules! opt_scale_arg { macro_rules! opt_scale_arg {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? { let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent, _) => {
Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100)) Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100))
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {

View File

@ -38,8 +38,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
} }
let blue = match channels.pop() { let blue = match channels.pop() {
Some(Value::Dimension(n, Unit::None)) => n, Some(Value::Dimension(n, Unit::None, _)) => n,
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255), Some(Value::Dimension(n, Unit::Percent, _)) => {
(n / Number::from(100)) * Number::from(255)
}
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
let green = channels.pop().unwrap(); let green = channels.pop().unwrap();
let red = channels.pop().unwrap(); let red = channels.pop().unwrap();
@ -65,8 +67,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
}; };
let green = match channels.pop() { let green = match channels.pop() {
Some(Value::Dimension(n, Unit::None)) => n, Some(Value::Dimension(n, Unit::None, _)) => n,
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255), Some(Value::Dimension(n, Unit::Percent, _)) => {
(n / Number::from(100)) * Number::from(255)
}
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
let string = match channels.pop() { let string = match channels.pop() {
Some(red) => format!( Some(red) => format!(
@ -91,8 +95,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
}; };
let red = match channels.pop() { let red = match channels.pop() {
Some(Value::Dimension(n, Unit::None)) => n, Some(Value::Dimension(n, Unit::None, _)) => n,
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255), Some(Value::Dimension(n, Unit::Percent, _)) => {
(n / Number::from(100)) * Number::from(255)
}
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
return Ok(Value::String( return Ok(Value::String(
format!( format!(
@ -142,8 +148,8 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
} }
}; };
let alpha = match parser.arg(&mut args, 1, "alpha")? { let alpha = match parser.arg(&mut args, 1, "alpha")? {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None, _) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100), Value::Dimension(n, Unit::Percent, _) => n / Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -178,8 +184,8 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
Ok(Value::Color(Box::new(color.with_alpha(alpha)))) Ok(Value::Color(Box::new(color.with_alpha(alpha))))
} else { } else {
let red = match parser.arg(&mut args, 0, "red")? { let red = match parser.arg(&mut args, 0, "red")? {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None, _) => n,
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -220,8 +226,8 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
} }
}; };
let green = match parser.arg(&mut args, 1, "green")? { let green = match parser.arg(&mut args, 1, "green")? {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None, _) => n,
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -261,8 +267,8 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
} }
}; };
let blue = match parser.arg(&mut args, 2, "blue")? { let blue = match parser.arg(&mut args, 2, "blue")? {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None, _) => n,
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -304,10 +310,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
&mut args, &mut args,
3, 3,
"alpha", "alpha",
Value::Dimension(Number::one(), Unit::None), Value::Dimension(Number::one(), Unit::None, true),
)? { )? {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None, _) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100), Value::Dimension(n, Unit::Percent, _) => n / Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -354,7 +360,7 @@ fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None, true)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?), format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(), args.span(),
@ -366,7 +372,7 @@ fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None, true)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?), format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(), args.span(),
@ -378,7 +384,7 @@ fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "color")? { match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None, true)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?), format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(), args.span(),
@ -415,9 +421,9 @@ fn mix(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
&mut args, &mut args,
2, 2,
"weight", "weight",
Value::Dimension(Number::from(50), Unit::None), Value::Dimension(Number::from(50), Unit::None, true),
)? { )? {
Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u, _) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
format!( format!(

View File

@ -16,6 +16,7 @@ fn length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
Ok(Value::Dimension( Ok(Value::Dimension(
Number::from(parser.arg(&mut args, 0, "list")?.as_list().len()), Number::from(parser.arg(&mut args, 0, "list")?.as_list().len()),
Unit::None, Unit::None,
true,
)) ))
} }
@ -23,7 +24,7 @@ fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let mut list = parser.arg(&mut args, 0, "list")?.as_list(); let mut list = parser.arg(&mut args, 0, "list")?.as_list();
let n = match parser.arg(&mut args, 1, "n")? { let n = match parser.arg(&mut args, 1, "n")? {
Value::Dimension(num, _) => num, Value::Dimension(num, ..) => num,
v => { v => {
return Err(( return Err((
format!("$n: {} is not a number.", v.inspect(args.span())?), format!("$n: {} is not a number.", v.inspect(args.span())?),
@ -80,7 +81,7 @@ fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
v => (vec![v], ListSeparator::Space, Brackets::None), v => (vec![v], ListSeparator::Space, Brackets::None),
}; };
let n = match parser.arg(&mut args, 1, "n")? { let n = match parser.arg(&mut args, 1, "n")? {
Value::Dimension(num, _) => num, Value::Dimension(num, ..) => num,
v => { v => {
return Err(( return Err((
format!("$n: {} is not a number.", v.inspect(args.span())?), format!("$n: {} is not a number.", v.inspect(args.span())?),
@ -248,7 +249,7 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
Some(v) => Number::from(v + 1), Some(v) => Number::from(v + 1),
None => return Ok(Value::Null), None => return Ok(Value::Null),
}; };
Ok(Value::Dimension(index, Unit::None)) Ok(Value::Dimension(index, Unit::None, true))
} }
fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {

View File

@ -16,7 +16,7 @@ use crate::{
fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let num = match parser.arg(&mut args, 0, "number")? { let num = match parser.arg(&mut args, 0, "number")? {
Value::Dimension(n, Unit::None) => n * Number::from(100), Value::Dimension(n, Unit::None, _) => n * Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(
@ -35,13 +35,13 @@ fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
.into()) .into())
} }
}; };
Ok(Value::Dimension(num, Unit::Percent)) Ok(Value::Dimension(num, Unit::Percent, true))
} }
fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "number")? { match parser.arg(&mut args, 0, "number")? {
Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.round(), u, true)),
v => Err(( v => Err((
format!("$number: {} is not a number.", v.inspect(args.span())?), format!("$number: {} is not a number.", v.inspect(args.span())?),
args.span(), args.span(),
@ -53,7 +53,7 @@ fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "number")? { match parser.arg(&mut args, 0, "number")? {
Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.ceil(), u, true)),
v => Err(( v => Err((
format!("$number: {} is not a number.", v.inspect(args.span())?), format!("$number: {} is not a number.", v.inspect(args.span())?),
args.span(), args.span(),
@ -65,7 +65,7 @@ fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "number")? { match parser.arg(&mut args, 0, "number")? {
Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.floor(), u, true)),
v => Err(( v => Err((
format!("$number: {} is not a number.", v.inspect(args.span())?), format!("$number: {} is not a number.", v.inspect(args.span())?),
args.span(), args.span(),
@ -77,7 +77,7 @@ fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "number")? { match parser.arg(&mut args, 0, "number")? {
Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.abs(), u, true)),
v => Err(( v => Err((
format!("$number: {} is not a number.", v.inspect(args.span())?), format!("$number: {} is not a number.", v.inspect(args.span())?),
args.span(), args.span(),
@ -89,7 +89,7 @@ fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let unit1 = match parser.arg(&mut args, 0, "number1")? { let unit1 = match parser.arg(&mut args, 0, "number1")? {
Value::Dimension(_, u) => u, Value::Dimension(_, u, _) => u,
v => { v => {
return Err(( return Err((
format!("$number1: {} is not a number.", v.inspect(args.span())?), format!("$number1: {} is not a number.", v.inspect(args.span())?),
@ -99,7 +99,7 @@ fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
} }
}; };
let unit2 = match parser.arg(&mut args, 1, "number2")? { let unit2 = match parser.arg(&mut args, 1, "number2")? {
Value::Dimension(_, u) => u, Value::Dimension(_, u, _) => u,
v => { v => {
return Err(( return Err((
format!("$number2: {} is not a number.", v.inspect(args.span())?), format!("$number2: {} is not a number.", v.inspect(args.span())?),
@ -117,12 +117,13 @@ fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let limit = match parser.default_arg(&mut args, 0, "limit", Value::Null)? { let limit = match parser.default_arg(&mut args, 0, "limit", Value::Null)? {
Value::Dimension(n, _) => n, Value::Dimension(n, ..) => n,
Value::Null => { Value::Null => {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
return Ok(Value::Dimension( return Ok(Value::Dimension(
Number::from(rng.gen_range(0.0, 1.0)), Number::from(rng.gen_range(0.0, 1.0)),
Unit::None, Unit::None,
true,
)); ));
} }
v => { v => {
@ -135,7 +136,7 @@ fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
}; };
if limit.is_one() { if limit.is_one() {
return Ok(Value::Dimension(Number::one(), Unit::None)); return Ok(Value::Dimension(Number::one(), Unit::None, true));
} }
if limit.is_decimal() { if limit.is_decimal() {
@ -165,6 +166,7 @@ fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
Ok(Value::Dimension( Ok(Value::Dimension(
Number::from(rng.gen_range(0, limit) + 1), Number::from(rng.gen_range(0, limit) + 1),
Unit::None, Unit::None,
true,
)) ))
} }
@ -175,7 +177,7 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
.variadic_args(args)? .variadic_args(args)?
.into_iter() .into_iter()
.map(|val| match val.node { .map(|val| match val.node {
Value::Dimension(number, unit) => Ok((number, unit)), Value::Dimension(number, unit, _) => Ok((number, unit)),
v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()), v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()),
}) })
.collect::<SassResult<Vec<(Number, Unit)>>>()? .collect::<SassResult<Vec<(Number, Unit)>>>()?
@ -187,15 +189,23 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
for num in nums { for num in nums {
if ValueVisitor::new(parser, span) if ValueVisitor::new(parser, span)
.less_than( .less_than(
HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())), HigherIntermediateValue::Literal(Value::Dimension(
HigherIntermediateValue::Literal(Value::Dimension(min.0.clone(), min.1.clone())), num.0.clone(),
num.1.clone(),
true,
)),
HigherIntermediateValue::Literal(Value::Dimension(
min.0.clone(),
min.1.clone(),
true,
)),
)? )?
.is_true() .is_true()
{ {
min = num; min = num;
} }
} }
Ok(Value::Dimension(min.0, min.1)) Ok(Value::Dimension(min.0, min.1, true))
} }
fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
@ -205,7 +215,7 @@ fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
.variadic_args(args)? .variadic_args(args)?
.into_iter() .into_iter()
.map(|val| match val.node { .map(|val| match val.node {
Value::Dimension(number, unit) => Ok((number, unit)), Value::Dimension(number, unit, _) => Ok((number, unit)),
v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()), v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()),
}) })
.collect::<SassResult<Vec<(Number, Unit)>>>()? .collect::<SassResult<Vec<(Number, Unit)>>>()?
@ -217,15 +227,23 @@ fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
for num in nums { for num in nums {
if ValueVisitor::new(parser, span) if ValueVisitor::new(parser, span)
.greater_than( .greater_than(
HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())), HigherIntermediateValue::Literal(Value::Dimension(
HigherIntermediateValue::Literal(Value::Dimension(max.0.clone(), max.1.clone())), num.0.clone(),
num.1.clone(),
true,
)),
HigherIntermediateValue::Literal(Value::Dimension(
max.0.clone(),
max.1.clone(),
true,
)),
)? )?
.is_true() .is_true()
{ {
max = num; max = num;
} }
} }
Ok(Value::Dimension(max.0, max.1)) Ok(Value::Dimension(max.0, max.1, true))
} }
pub(crate) fn declare(f: &mut GlobalFunctionMap) { pub(crate) fn declare(f: &mut GlobalFunctionMap) {

View File

@ -53,7 +53,7 @@ fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Val
fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let unit = match parser.arg(&mut args, 0, "number")? { let unit = match parser.arg(&mut args, 0, "number")? {
Value::Dimension(_, u) => u.to_string(), Value::Dimension(_, u, _) => u.to_string(),
v => { v => {
return Err(( return Err((
format!("$number: {} is not a number.", v.inspect(args.span())?), format!("$number: {} is not a number.", v.inspect(args.span())?),
@ -75,8 +75,8 @@ fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
#[allow(clippy::match_same_arms)] #[allow(clippy::match_same_arms)]
Ok(match parser.arg(&mut args, 0, "number")? { Ok(match parser.arg(&mut args, 0, "number")? {
Value::Dimension(_, Unit::None) => Value::True, Value::Dimension(_, Unit::None, _) => Value::True,
Value::Dimension(_, _) => Value::False, Value::Dimension(..) => Value::False,
_ => Value::True, _ => Value::True,
}) })
} }

View File

@ -51,6 +51,7 @@ fn str_length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
Value::String(i, _) => Ok(Value::Dimension( Value::String(i, _) => Ok(Value::Dimension(
Number::from(i.chars().count()), Number::from(i.chars().count()),
Unit::None, Unit::None,
true,
)), )),
v => Err(( v => Err((
format!("$string: {} is not a string.", v.inspect(args.span())?), format!("$string: {} is not a string.", v.inspect(args.span())?),
@ -98,15 +99,15 @@ fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
}; };
let str_len = string.chars().count(); let str_len = string.chars().count();
let start = match parser.arg(&mut args, 1, "start-at")? { let start = match parser.arg(&mut args, 1, "start-at")? {
Value::Dimension(n, Unit::None) if n.is_decimal() => { Value::Dimension(n, Unit::None, _) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into()) return Err((format!("{} is not an int.", n), args.span()).into())
} }
Value::Dimension(n, Unit::None) if n.is_positive() => { Value::Dimension(n, Unit::None, _) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1) 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.is_zero() => 1_usize,
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 1_usize, Value::Dimension(n, Unit::None, _) if n < -Number::from(str_len) => 1_usize,
Value::Dimension(n, Unit::None) => (n.to_integer() + BigInt::from(str_len + 1)) Value::Dimension(n, Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1))
.to_usize() .to_usize()
.unwrap(), .unwrap(),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -128,15 +129,15 @@ fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
}; };
let mut end = match parser.default_arg(&mut args, 2, "end-at", Value::Null)? { let mut end = match parser.default_arg(&mut args, 2, "end-at", Value::Null)? {
Value::Dimension(n, Unit::None) if n.is_decimal() => { Value::Dimension(n, Unit::None, _) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into()) return Err((format!("{} is not an int.", n), args.span()).into())
} }
Value::Dimension(n, Unit::None) if n.is_positive() => { Value::Dimension(n, Unit::None, _) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1) 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.is_zero() => 0_usize,
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 0_usize, Value::Dimension(n, Unit::None, _) if n < -Number::from(str_len) => 0_usize,
Value::Dimension(n, Unit::None) => (n.to_integer() + BigInt::from(str_len + 1)) Value::Dimension(n, Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1))
.to_usize() .to_usize()
.unwrap_or(str_len + 1), .unwrap_or(str_len + 1),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -202,7 +203,7 @@ fn str_index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
}; };
Ok(match s1.find(&substr) { Ok(match s1.find(&substr) {
Some(v) => Value::Dimension(Number::from(v + 1), Unit::None), Some(v) => Value::Dimension(Number::from(v + 1), Unit::None, true),
None => Value::Null, None => Value::Null,
}) })
} }
@ -232,10 +233,10 @@ fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
}; };
let index = match parser.arg(&mut args, 2, "index")? { let index = match parser.arg(&mut args, 2, "index")? {
Value::Dimension(n, Unit::None) if n.is_decimal() => { Value::Dimension(n, Unit::None, _) if n.is_decimal() => {
return Err((format!("$index: {} is not an int.", n), args.span()).into()) return Err((format!("$index: {} is not an int.", n), args.span()).into())
} }
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None, _) => n,
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
format!( format!(

View File

@ -178,7 +178,7 @@ impl<'a> Parser<'a> {
} else { } else {
CallArg::Named(mem::take(&mut name).into()) CallArg::Named(mem::take(&mut name).into())
}, },
self.parse_value_from_vec(val), self.parse_value_from_vec(val, true),
); );
span = span.merge(tok.pos()); span = span.merge(tok.pos());
return Ok(CallArgs(args, span)); return Ok(CallArgs(args, span));
@ -218,7 +218,7 @@ impl<'a> Parser<'a> {
} }
if is_splat { if is_splat {
let val = self.parse_value_from_vec(mem::take(&mut val))?; let val = self.parse_value_from_vec(mem::take(&mut val), true)?;
match val.node { match val.node {
Value::ArgList(v) => { Value::ArgList(v) => {
for arg in v { for arg in v {
@ -263,7 +263,7 @@ impl<'a> Parser<'a> {
} else { } else {
CallArg::Named(mem::take(&mut name).into()) CallArg::Named(mem::take(&mut name).into())
}, },
self.parse_value_from_vec(mem::take(&mut val)), self.parse_value_from_vec(mem::take(&mut val), true),
); );
} }
@ -374,7 +374,7 @@ impl<'a> Parser<'a> {
let val = match args.get(idx, arg.name) { let val = match args.get(idx, arg.name) {
Some(v) => v, Some(v) => v,
None => match arg.default.as_mut() { None => match arg.default.as_mut() {
Some(v) => self.parse_value_from_vec(mem::take(v)), Some(v) => self.parse_value_from_vec(mem::take(v), true),
None => { None => {
return Err( return Err(
(format!("Missing argument ${}.", &arg.name), args.span()).into() (format!("Missing argument ${}.", &arg.name), args.span()).into()

View File

@ -22,7 +22,7 @@ impl<'a> Parser<'a> {
let mut found_true = false; let mut found_true = false;
let mut body = Vec::new(); let mut body = Vec::new();
let init_cond = self.parse_value()?.node; let init_cond = self.parse_value(true)?.node;
// consume the open curly brace // consume the open curly brace
let span_before = match self.toks.next() { let span_before = match self.toks.next() {
@ -85,7 +85,7 @@ impl<'a> Parser<'a> {
self.throw_away_until_open_curly_brace()?; self.throw_away_until_open_curly_brace()?;
false false
} else { } else {
let v = self.parse_value()?.node.is_true(); let v = self.parse_value(true)?.node.is_true();
match self.toks.next() { match self.toks.next() {
Some(Token { kind: '{', .. }) => {} Some(Token { kind: '{', .. }) => {}
Some(..) | None => { Some(..) | None => {
@ -237,9 +237,9 @@ impl<'a> Parser<'a> {
} }
} }
self.whitespace(); self.whitespace();
let from_val = self.parse_value_from_vec(from_toks)?; let from_val = self.parse_value_from_vec(from_toks, true)?;
let from = match from_val.node { let from = match from_val.node {
Value::Dimension(n, _) => match n.to_integer().to_isize() { Value::Dimension(n, ..) => match n.to_integer().to_isize() {
Some(v) => v, Some(v) => v,
None => return Err((format!("{} is not a int.", n), from_val.span).into()), None => return Err((format!("{} is not a int.", n), from_val.span).into()),
}, },
@ -252,9 +252,9 @@ impl<'a> Parser<'a> {
} }
}; };
let to_val = self.parse_value()?; let to_val = self.parse_value(true)?;
let to = match to_val.node { let to = match to_val.node {
Value::Dimension(n, _) => match n.to_integer().to_isize() { Value::Dimension(n, ..) => match n.to_integer().to_isize() {
Some(v) => v, Some(v) => v,
None => return Err((format!("{} is not a int.", n), to_val.span).into()), None => return Err((format!("{} is not a int.", n), to_val.span).into()),
}, },
@ -297,7 +297,7 @@ impl<'a> Parser<'a> {
self.scopes.insert_var_last( self.scopes.insert_var_last(
var.node, var.node,
Spanned { Spanned {
node: Value::Dimension(Number::from(i), Unit::None), node: Value::Dimension(Number::from(i), Unit::None, true),
span: var.span, span: var.span,
}, },
); );
@ -368,7 +368,7 @@ impl<'a> Parser<'a> {
self.whitespace(); self.whitespace();
let mut stmts = Vec::new(); let mut stmts = Vec::new();
let mut val = self.parse_value_from_vec(cond.clone())?; let mut val = self.parse_value_from_vec(cond.clone(), true)?;
self.scopes.enter_new_scope(); self.scopes.enter_new_scope();
while val.node.is_true() { while val.node.is_true() {
if self.flags.in_function() { if self.flags.in_function() {
@ -411,7 +411,7 @@ impl<'a> Parser<'a> {
.parse()?, .parse()?,
); );
} }
val = self.parse_value_from_vec(cond.clone())?; val = self.parse_value_from_vec(cond.clone(), true)?;
} }
self.scopes.exit_scope(); self.scopes.exit_scope();
@ -452,7 +452,10 @@ impl<'a> Parser<'a> {
} }
self.whitespace(); self.whitespace();
let iter_val_toks = read_until_open_curly_brace(self.toks)?; let iter_val_toks = read_until_open_curly_brace(self.toks)?;
let iter = self.parse_value_from_vec(iter_val_toks)?.node.as_list(); let iter = self
.parse_value_from_vec(iter_val_toks, true)?
.node
.as_list();
self.toks.next(); self.toks.next();
self.whitespace(); self.whitespace();
let mut body = read_until_closing_curly_brace(self.toks)?; let mut body = read_until_closing_curly_brace(self.toks)?;

View File

@ -63,7 +63,7 @@ impl<'a> Parser<'a> {
pub(super) fn parse_return(&mut self) -> SassResult<Box<Value>> { pub(super) fn parse_return(&mut self) -> SassResult<Box<Value>> {
let toks = read_until_semicolon_or_closing_curly_brace(self.toks)?; let toks = read_until_semicolon_or_closing_curly_brace(self.toks)?;
let v = self.parse_value_from_vec(toks)?; let v = self.parse_value_from_vec(toks, true)?;
if let Some(Token { kind: ';', .. }) = self.toks.peek() { if let Some(Token { kind: ';', .. }) = self.toks.peek() {
self.toks.next(); self.toks.next();
} }

View File

@ -22,7 +22,7 @@ impl<'a> Parser<'a> {
let Spanned { let Spanned {
node: file_name_as_value, node: file_name_as_value,
span, span,
} = self.parse_value()?; } = self.parse_value(true)?;
let file_name = match file_name_as_value { let file_name = match file_name_as_value {
Value::String(s, QuoteKind::Quoted) => { Value::String(s, QuoteKind::Quoted) => {
if s.ends_with(".css") || s.starts_with("http://") || s.starts_with("https://") { if s.ends_with(".css") || s.starts_with("http://") || s.starts_with("https://") {

View File

@ -139,7 +139,7 @@ impl<'a> Parser<'a> {
let Spanned { let Spanned {
node: message, node: message,
span, span,
} = self.parse_value()?; } = self.parse_value(false)?;
return Err(( return Err((
message.inspect(span)?.to_string(), message.inspect(span)?.to_string(),
@ -151,7 +151,7 @@ impl<'a> Parser<'a> {
let Spanned { let Spanned {
node: message, node: message,
span, span,
} = self.parse_value()?; } = self.parse_value(false)?;
span.merge(kind_string.span); span.merge(kind_string.span);
if let Some(Token { kind: ';', pos }) = self.toks.peek() { if let Some(Token { kind: ';', pos }) = self.toks.peek() {
kind_string.span.merge(*pos); kind_string.span.merge(*pos);
@ -166,7 +166,7 @@ impl<'a> Parser<'a> {
let Spanned { let Spanned {
node: message, node: message,
span, span,
} = self.parse_value()?; } = self.parse_value(false)?;
span.merge(kind_string.span); span.merge(kind_string.span);
if let Some(Token { kind: ';', pos }) = self.toks.peek() { if let Some(Token { kind: ';', pos }) = self.toks.peek() {
kind_string.span.merge(*pos); kind_string.span.merge(*pos);
@ -416,7 +416,7 @@ impl<'a> Parser<'a> {
} }
pub fn parse_interpolation(&mut self) -> SassResult<Spanned<Value>> { pub fn parse_interpolation(&mut self) -> SassResult<Spanned<Value>> {
let val = self.parse_value()?; let val = self.parse_value(true)?;
match self.toks.next() { match self.toks.next() {
Some(Token { kind: '}', .. }) => {} Some(Token { kind: '}', .. }) => {}
Some(..) | None => return Err(("expected \"}\".", val.span).into()), Some(..) | None => return Err(("expected \"}\".", val.span).into()),
@ -437,7 +437,7 @@ impl<'a> Parser<'a> {
toks: Vec<Token>, toks: Vec<Token>,
quoted: bool, quoted: bool,
) -> SassResult<Cow<'static, str>> { ) -> SassResult<Cow<'static, str>> {
let value = self.parse_value_from_vec(toks)?; let value = self.parse_value_from_vec(toks, false)?;
if quoted { if quoted {
value.node.to_css_string(value.span) value.node.to_css_string(value.span)
} else { } else {

View File

@ -125,7 +125,7 @@ impl<'a> Parser<'a> {
c if is_name(*c) => { c if is_name(*c) => {
if let Some(toks) = self.parse_style_value_when_no_space_after_semicolon() { if let Some(toks) = self.parse_style_value_when_no_space_after_semicolon() {
let len = toks.len(); let len = toks.len();
if let Ok(val) = self.parse_value_from_vec(toks) { if let Ok(val) = self.parse_value_from_vec(toks, false) {
self.toks.take(len).for_each(drop); self.toks.take(len).for_each(drop);
return Ok(SelectorOrStyle::Style( return Ok(SelectorOrStyle::Style(
InternedString::get_or_intern(property), InternedString::get_or_intern(property),
@ -173,7 +173,7 @@ impl<'a> Parser<'a> {
} }
fn parse_style_value(&mut self) -> SassResult<Spanned<Value>> { fn parse_style_value(&mut self) -> SassResult<Spanned<Value>> {
self.parse_value() self.parse_value(false)
} }
pub(super) fn parse_style_group( pub(super) fn parse_style_group(

View File

@ -339,7 +339,7 @@ impl<'a> Parser<'a> {
fn peek_interpolation(&mut self) -> SassResult<Spanned<Value>> { fn peek_interpolation(&mut self) -> SassResult<Spanned<Value>> {
let vec = peek_until_closing_curly_brace(self.toks)?; let vec = peek_until_closing_curly_brace(self.toks)?;
self.toks.advance_cursor(); self.toks.advance_cursor();
let val = self.parse_value_from_vec(vec)?; let val = self.parse_value_from_vec(vec, false)?;
Ok(Spanned { Ok(Spanned {
node: val.node.unquote(), node: val.node.unquote(),
span: val.span, span: val.span,

View File

@ -46,12 +46,15 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
Self { parser, span } Self { parser, span }
} }
pub fn eval(&mut self, value: HigherIntermediateValue) -> SassResult<Value> { pub fn eval(&mut self, value: HigherIntermediateValue, in_parens: bool) -> SassResult<Value> {
match value { match value {
HigherIntermediateValue::Literal(Value::Dimension(n, u, _)) if in_parens => {
Ok(Value::Dimension(n, u, true))
}
HigherIntermediateValue::Literal(v) => Ok(v), HigherIntermediateValue::Literal(v) => Ok(v),
HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2), HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2, in_parens),
HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val), HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val, in_parens),
HigherIntermediateValue::Paren(val) => self.eval(*val), HigherIntermediateValue::Paren(val) => self.eval(*val, true),
HigherIntermediateValue::Function(function, args) => { HigherIntermediateValue::Function(function, args) => {
self.parser.call_function(function, args) self.parser.call_function(function, args)
} }
@ -63,16 +66,21 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
val1: HigherIntermediateValue, val1: HigherIntermediateValue,
op: Op, op: Op,
val2: HigherIntermediateValue, val2: HigherIntermediateValue,
in_parens: bool,
) -> SassResult<Value> { ) -> SassResult<Value> {
let mut val1 = self.paren_or_unary(val1)?; let mut val1 = self.paren_or_unary(val1, in_parens)?;
let val2 = self.paren_or_unary(val2)?; let val2 = self.paren_or_unary(val2, in_parens)?;
if let HigherIntermediateValue::BinaryOp(val1_1, op2, val1_2) = val1 { if let HigherIntermediateValue::BinaryOp(val1_1, op2, val1_2) = val1 {
let in_parens = op != Op::Div || op2 != Op::Div;
if op2.precedence() >= op.precedence() { if op2.precedence() >= op.precedence() {
val1 = HigherIntermediateValue::Literal(self.bin_op(*val1_1, op2, *val1_2)?); val1 = HigherIntermediateValue::Literal(
self.bin_op(*val1_1, op2, *val1_2, in_parens)?,
);
} else { } else {
let val2 = HigherIntermediateValue::Literal(self.bin_op(*val1_2, op, val2)?); let val2 =
return self.bin_op(*val1_1, op2, val2); HigherIntermediateValue::Literal(self.bin_op(*val1_2, op, val2, in_parens)?);
return self.bin_op(*val1_1, op2, val2, in_parens);
} }
} }
@ -80,7 +88,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
Op::Plus => self.add(val1, val2)?, Op::Plus => self.add(val1, val2)?,
Op::Minus => self.sub(val1, val2)?, Op::Minus => self.sub(val1, val2)?,
Op::Mul => self.mul(val1, val2)?, Op::Mul => self.mul(val1, val2)?,
Op::Div => self.div(val1, val2)?, Op::Div => self.div(val1, val2, in_parens)?,
Op::Rem => self.rem(val1, val2)?, Op::Rem => self.rem(val1, val2)?,
Op::And => Self::and(val1, val2)?, Op::And => Self::and(val1, val2)?,
Op::Or => Self::or(val1, val2)?, Op::Or => Self::or(val1, val2)?,
@ -94,8 +102,13 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
}) })
} }
fn unary_op(&mut self, op: Op, val: HigherIntermediateValue) -> SassResult<Value> { fn unary_op(
let val = self.eval(val)?; &mut self,
op: Op,
val: HigherIntermediateValue,
in_parens: bool,
) -> SassResult<Value> {
let val = self.eval(val, in_parens)?;
match op { match op {
Op::Minus => self.unary_minus(val), Op::Minus => self.unary_minus(val),
Op::Not => Self::unary_not(&val), Op::Not => Self::unary_not(&val),
@ -106,7 +119,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
fn unary_minus(&self, val: Value) -> SassResult<Value> { fn unary_minus(&self, val: Value) -> SassResult<Value> {
Ok(match val { Ok(match val {
Value::Dimension(n, u) => Value::Dimension(-n, u), Value::Dimension(n, u, should_divide) => Value::Dimension(-n, u, should_divide),
v => Value::String(format!("-{}", v.to_css_string(self.span)?), QuoteKind::None), v => Value::String(format!("-{}", v.to_css_string(self.span)?), QuoteKind::None),
}) })
} }
@ -124,7 +137,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
fn paren(&mut self, val: HigherIntermediateValue) -> SassResult<HigherIntermediateValue> { fn paren(&mut self, val: HigherIntermediateValue) -> SassResult<HigherIntermediateValue> {
Ok(if let HigherIntermediateValue::Paren(v) = val { Ok(if let HigherIntermediateValue::Paren(v) = val {
HigherIntermediateValue::Literal(self.eval(*v)?) HigherIntermediateValue::Literal(self.eval(*v, true)?)
} else { } else {
val val
}) })
@ -133,11 +146,12 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
fn paren_or_unary( fn paren_or_unary(
&mut self, &mut self,
val: HigherIntermediateValue, val: HigherIntermediateValue,
in_parens: bool,
) -> SassResult<HigherIntermediateValue> { ) -> SassResult<HigherIntermediateValue> {
let val = self.paren(val)?; let val = self.paren(val)?;
Ok(match val { Ok(match val {
HigherIntermediateValue::UnaryOp(op, val) => { HigherIntermediateValue::UnaryOp(op, val) => {
HigherIntermediateValue::Literal(self.unary_op(op, *val)?) HigherIntermediateValue::Literal(self.unary_op(op, *val, in_parens)?)
} }
HigherIntermediateValue::Function(function, args) => { HigherIntermediateValue::Function(function, args) => {
HigherIntermediateValue::Literal(self.parser.call_function(function, args)?) HigherIntermediateValue::Literal(self.parser.call_function(function, args)?)
@ -191,8 +205,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
QuoteKind::None, QuoteKind::None,
), ),
}, },
Value::Dimension(num, unit) => match right { Value::Dimension(num, unit, _) => match right {
Value::Dimension(num2, unit2) => { Value::Dimension(num2, unit2, _) => {
if !unit.comparable(&unit2) { if !unit.comparable(&unit2) {
return Err(( return Err((
format!("Incompatible units {} and {}.", unit2, unit), format!("Incompatible units {} and {}.", unit2, unit),
@ -201,11 +215,11 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
.into()); .into());
} }
if unit == unit2 { if unit == unit2 {
Value::Dimension(num + num2, unit) Value::Dimension(num + num2, unit, true)
} else if unit == Unit::None { } else if unit == Unit::None {
Value::Dimension(num + num2, unit2) Value::Dimension(num + num2, unit2, true)
} else if unit2 == Unit::None { } else if unit2 == Unit::None {
Value::Dimension(num + num2, unit) Value::Dimension(num + num2, unit, true)
} else { } else {
Value::Dimension( Value::Dimension(
num + num2 num + num2
@ -213,6 +227,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
[unit2.to_string().as_str()] [unit2.to_string().as_str()]
.clone(), .clone(),
unit, unit,
true,
) )
} }
} }
@ -299,8 +314,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
format!("-{}", right.to_css_string(self.span)?), format!("-{}", right.to_css_string(self.span)?),
QuoteKind::None, QuoteKind::None,
), ),
Value::Dimension(num, unit) => match right { Value::Dimension(num, unit, _) => match right {
Value::Dimension(num2, unit2) => { Value::Dimension(num2, unit2, _) => {
if !unit.comparable(&unit2) { if !unit.comparable(&unit2) {
return Err(( return Err((
format!("Incompatible units {} and {}.", unit2, unit), format!("Incompatible units {} and {}.", unit2, unit),
@ -309,11 +324,11 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
.into()); .into());
} }
if unit == unit2 { if unit == unit2 {
Value::Dimension(num - num2, unit) Value::Dimension(num - num2, unit, true)
} else if unit == Unit::None { } else if unit == Unit::None {
Value::Dimension(num - num2, unit2) Value::Dimension(num - num2, unit2, true)
} else if unit2 == Unit::None { } else if unit2 == Unit::None {
Value::Dimension(num - num2, unit) Value::Dimension(num - num2, unit, true)
} else { } else {
Value::Dimension( Value::Dimension(
num - num2 num - num2
@ -321,6 +336,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
[unit2.to_string().as_str()] [unit2.to_string().as_str()]
.clone(), .clone(),
unit, unit,
true,
) )
} }
} }
@ -418,14 +434,14 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
v => panic!("{:?}", v), v => panic!("{:?}", v),
}; };
Ok(match left { Ok(match left {
Value::Dimension(num, unit) => match right { Value::Dimension(num, unit, _) => match right {
Value::Dimension(num2, unit2) => { Value::Dimension(num2, unit2, _) => {
if unit == Unit::None { if unit == Unit::None {
Value::Dimension(num * num2, unit2) Value::Dimension(num * num2, unit2, true)
} else if unit2 == Unit::None { } else if unit2 == Unit::None {
Value::Dimension(num * num2, unit) Value::Dimension(num * num2, unit, true)
} else { } else {
Value::Dimension(num * num2, unit * unit2) Value::Dimension(num * num2, unit * unit2, true)
} }
} }
_ => { _ => {
@ -459,6 +475,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
&self, &self,
left: HigherIntermediateValue, left: HigherIntermediateValue,
right: HigherIntermediateValue, right: HigherIntermediateValue,
in_parens: bool,
) -> SassResult<Value> { ) -> SassResult<Value> {
let left = match left { let left = match left {
HigherIntermediateValue::Literal(v) => v, HigherIntermediateValue::Literal(v) => v,
@ -473,35 +490,43 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
format!("/{}", right.to_css_string(self.span)?), format!("/{}", right.to_css_string(self.span)?),
QuoteKind::None, QuoteKind::None,
), ),
Value::Dimension(num, unit) => match right { Value::Dimension(num, unit, should_divide1) => match right {
Value::Dimension(num2, unit2) => { Value::Dimension(num2, unit2, should_divide2) => {
// `unit(1em / 1em)` => `""` if should_divide1 || should_divide2 || in_parens {
if unit == unit2 { // `unit(1em / 1em)` => `""`
Value::Dimension(num / num2, Unit::None) if unit == unit2 {
Value::Dimension(num / num2, Unit::None, true)
// `unit(1 / 1em)` => `"em^-1"` // `unit(1 / 1em)` => `"em^-1"`
} else if unit == Unit::None { } else if unit == Unit::None {
Value::Dimension(num / num2, Unit::None / unit2) Value::Dimension(num / num2, Unit::None / unit2, true)
// `unit(1em / 1)` => `"em"` // `unit(1em / 1)` => `"em"`
} else if unit2 == Unit::None { } else if unit2 == Unit::None {
Value::Dimension(num / num2, unit) Value::Dimension(num / num2, unit, true)
// `unit(1in / 1px)` => `""` // `unit(1in / 1px)` => `""`
} else if unit.comparable(&unit2) { } else if unit.comparable(&unit2) {
Value::Dimension( Value::Dimension(
num / (num2 num / (num2
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()] * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
[unit2.to_string().as_str()] [unit2.to_string().as_str()]
.clone()), .clone()),
Unit::None, Unit::None,
) true,
// `unit(1em / 1px)` => `"em/px"` )
// todo: this should probably be its own variant // `unit(1em / 1px)` => `"em/px"`
// within the `Value` enum // todo: this should probably be its own variant
// within the `Value` enum
} else {
// todo: remember to account for `Mul` and `Div`
todo!("non-comparable inverse units")
}
} else { } else {
// todo: remember to account for `Mul` and `Div` Value::String(
todo!("non-comparable inverse units") format!("{}{}/{}{}", num, unit, num2, unit2),
QuoteKind::None,
)
} }
} }
Value::String(s, q) => { Value::String(s, q) => {
@ -605,28 +630,28 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
v => panic!("{:?}", v), v => panic!("{:?}", v),
}; };
Ok(match left { Ok(match left {
Value::Dimension(n, u) => match right { Value::Dimension(n, u, _) => match right {
Value::Dimension(n2, u2) => { Value::Dimension(n2, u2, _) => {
if !u.comparable(&u2) { if !u.comparable(&u2) {
return Err( return Err(
(format!("Incompatible units {} and {}.", u2, u), self.span).into() (format!("Incompatible units {} and {}.", u2, u), self.span).into()
); );
} }
if u == u2 { if u == u2 {
Value::Dimension(n % n2, u) Value::Dimension(n % n2, u, true)
} else if u == Unit::None { } else if u == Unit::None {
Value::Dimension(n % n2, u2) Value::Dimension(n % n2, u2, true)
} else if u2 == Unit::None { } else if u2 == Unit::None {
Value::Dimension(n % n2, u) Value::Dimension(n % n2, u, true)
} else { } else {
Value::Dimension(n, u) Value::Dimension(n, u, true)
} }
} }
_ => { _ => {
return Err(( return Err((
format!( format!(
"Undefined operation \"{} % {}\".", "Undefined operation \"{} % {}\".",
Value::Dimension(n, u).inspect(self.span)?, Value::Dimension(n, u, true).inspect(self.span)?,
right.inspect(self.span)? right.inspect(self.span)?
), ),
self.span, self.span,
@ -711,8 +736,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
v => panic!("{:?}", v), v => panic!("{:?}", v),
}; };
let ordering = match left { let ordering = match left {
Value::Dimension(num, unit) => match &right { Value::Dimension(num, unit, _) => match &right {
Value::Dimension(num2, unit2) => { Value::Dimension(num2, unit2, _) => {
if !unit.comparable(unit2) { if !unit.comparable(unit2) {
return Err(( return Err((
format!("Incompatible units {} and {}.", unit2, unit), format!("Incompatible units {} and {}.", unit2, unit),

View File

@ -52,7 +52,7 @@ impl IsWhitespace for IntermediateValue {
} }
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
pub(crate) fn parse_value(&mut self) -> SassResult<Spanned<Value>> { pub(crate) fn parse_value(&mut self, in_paren: bool) -> SassResult<Spanned<Value>> {
self.whitespace(); self.whitespace();
let span = match self.toks.peek() { let span = match self.toks.peek() {
Some(Token { kind: '}', .. }) Some(Token { kind: '}', .. })
@ -80,6 +80,7 @@ impl<'a> Parser<'a> {
}, },
&mut space_separated, &mut space_separated,
last_was_whitespace, last_was_whitespace,
in_paren,
)?; )?;
} }
IntermediateValue::Whitespace => { IntermediateValue::Whitespace => {
@ -104,7 +105,7 @@ impl<'a> Parser<'a> {
span = span.merge(a.span); span = span.merge(a.span);
a.node a.node
}) })
.map(|a| ValueVisitor::new(iter.parser, span).eval(a)) .map(|a| ValueVisitor::new(iter.parser, span).eval(a, in_paren))
.collect::<SassResult<Vec<Value>>>()?, .collect::<SassResult<Vec<Value>>>()?,
ListSeparator::Space, ListSeparator::Space,
Brackets::None, Brackets::None,
@ -117,7 +118,7 @@ impl<'a> Parser<'a> {
last_was_whitespace = false; last_was_whitespace = false;
space_separated.push( space_separated.push(
HigherIntermediateValue::Literal( HigherIntermediateValue::Literal(
match iter.parser.parse_value_from_vec(t)?.node { match iter.parser.parse_value_from_vec(t, in_paren)?.node {
Value::List(v, sep, Brackets::None) => { Value::List(v, sep, Brackets::None) => {
Value::List(v, sep, Brackets::Bracketed) Value::List(v, sep, Brackets::Bracketed)
} }
@ -147,7 +148,7 @@ impl<'a> Parser<'a> {
HigherIntermediateValue::Literal(Value::List( HigherIntermediateValue::Literal(Value::List(
space_separated space_separated
.into_iter() .into_iter()
.map(|a| ValueVisitor::new(self, span).eval(a.node)) .map(|a| ValueVisitor::new(self, span).eval(a.node, in_paren))
.collect::<SassResult<Vec<Value>>>()?, .collect::<SassResult<Vec<Value>>>()?,
ListSeparator::Space, ListSeparator::Space,
Brackets::None, Brackets::None,
@ -158,7 +159,7 @@ impl<'a> Parser<'a> {
Value::List( Value::List(
comma_separated comma_separated
.into_iter() .into_iter()
.map(|a| ValueVisitor::new(self, span).eval(a.node)) .map(|a| ValueVisitor::new(self, span).eval(a.node, in_paren))
.collect::<SassResult<Vec<Value>>>()?, .collect::<SassResult<Vec<Value>>>()?,
ListSeparator::Comma, ListSeparator::Comma,
Brackets::None, Brackets::None,
@ -166,13 +167,13 @@ impl<'a> Parser<'a> {
.span(span) .span(span)
} else if space_separated.len() == 1 { } else if space_separated.len() == 1 {
ValueVisitor::new(self, span) ValueVisitor::new(self, span)
.eval(space_separated.pop().unwrap().node)? .eval(space_separated.pop().unwrap().node, in_paren)?
.span(span) .span(span)
} else { } else {
Value::List( Value::List(
space_separated space_separated
.into_iter() .into_iter()
.map(|a| ValueVisitor::new(self, span).eval(a.node)) .map(|a| ValueVisitor::new(self, span).eval(a.node, in_paren))
.collect::<SassResult<Vec<Value>>>()?, .collect::<SassResult<Vec<Value>>>()?,
ListSeparator::Space, ListSeparator::Space,
Brackets::None, Brackets::None,
@ -181,7 +182,11 @@ impl<'a> Parser<'a> {
}) })
} }
pub(crate) fn parse_value_from_vec(&mut self, toks: Vec<Token>) -> SassResult<Spanned<Value>> { pub(crate) fn parse_value_from_vec(
&mut self,
toks: Vec<Token>,
in_paren: bool,
) -> SassResult<Spanned<Value>> {
Parser { Parser {
toks: &mut toks.into_iter().peekmore(), toks: &mut toks.into_iter().peekmore(),
map: self.map, map: self.map,
@ -197,7 +202,7 @@ impl<'a> Parser<'a> {
extender: self.extender, extender: self.extender,
content_scopes: self.content_scopes, content_scopes: self.content_scopes,
} }
.parse_value() .parse_value(in_paren)
} }
fn parse_ident_value(&mut self) -> SassResult<Spanned<IntermediateValue>> { fn parse_ident_value(&mut self) -> SassResult<Spanned<IntermediateValue>> {
@ -391,6 +396,7 @@ impl<'a> Parser<'a> {
HigherIntermediateValue::Literal(Value::Dimension( HigherIntermediateValue::Literal(Value::Dimension(
Number::new_small(n), Number::new_small(n),
unit, unit,
false,
)), )),
) )
.span(span))); .span(span)));
@ -403,6 +409,7 @@ impl<'a> Parser<'a> {
HigherIntermediateValue::Literal(Value::Dimension( HigherIntermediateValue::Literal(Value::Dimension(
Number::new_small(n), Number::new_small(n),
unit, unit,
false,
)), )),
) )
.span(span))); .span(span)));
@ -415,6 +422,7 @@ impl<'a> Parser<'a> {
HigherIntermediateValue::Literal(Value::Dimension( HigherIntermediateValue::Literal(Value::Dimension(
Number::new_big(n), Number::new_big(n),
unit, unit,
false,
)), )),
) )
.span(span))); .span(span)));
@ -443,6 +451,7 @@ impl<'a> Parser<'a> {
IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Dimension( IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Dimension(
Number::new_big(n * times_ten), Number::new_big(n * times_ten),
unit, unit,
false,
))) )))
.span(span) .span(span)
} }
@ -747,11 +756,12 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
op: Spanned<Op>, op: Spanned<Op>,
space_separated: &mut Vec<Spanned<HigherIntermediateValue>>, space_separated: &mut Vec<Spanned<HigherIntermediateValue>>,
last_was_whitespace: bool, last_was_whitespace: bool,
in_paren: bool,
) -> SassResult<()> { ) -> SassResult<()> {
match op.node { match op.node {
Op::Not => { Op::Not => {
self.whitespace(); self.whitespace();
let right = self.single_value()?; let right = self.single_value(in_paren)?;
space_separated.push(Spanned { space_separated.push(Spanned {
node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)), node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)),
span: right.span, span: right.span,
@ -759,7 +769,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
} }
Op::Div => { Op::Div => {
self.whitespace(); self.whitespace();
let right = self.single_value()?; let right = self.single_value(in_paren)?;
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
space_separated.push(Spanned { space_separated.push(Spanned {
node: HigherIntermediateValue::BinaryOp( node: HigherIntermediateValue::BinaryOp(
@ -776,7 +786,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
format!( format!(
"/{}", "/{}",
ValueVisitor::new(self.parser, right.span) ValueVisitor::new(self.parser, right.span)
.eval(right.node)? .eval(right.node, false)?
.to_css_string(right.span)? .to_css_string(right.span)?
), ),
QuoteKind::None, QuoteKind::None,
@ -788,7 +798,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
Op::Plus => { Op::Plus => {
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
self.whitespace(); self.whitespace();
let right = self.single_value()?; let right = self.single_value(in_paren)?;
space_separated.push(Spanned { space_separated.push(Spanned {
node: HigherIntermediateValue::BinaryOp( node: HigherIntermediateValue::BinaryOp(
Box::new(left.node), Box::new(left.node),
@ -799,7 +809,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
}); });
} else { } else {
self.whitespace(); self.whitespace();
let right = self.single_value()?; let right = self.single_value(in_paren)?;
space_separated.push(Spanned { space_separated.push(Spanned {
node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)), node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)),
span: right.span, span: right.span,
@ -808,7 +818,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
} }
Op::Minus => { Op::Minus => {
if self.whitespace() || !last_was_whitespace { if self.whitespace() || !last_was_whitespace {
let right = self.single_value()?; let right = self.single_value(in_paren)?;
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
space_separated.push(Spanned { space_separated.push(Spanned {
node: HigherIntermediateValue::BinaryOp( node: HigherIntermediateValue::BinaryOp(
@ -826,7 +836,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
); );
} }
} else { } else {
let right = self.single_value()?; let right = self.single_value(in_paren)?;
space_separated.push( space_separated.push(
right.map_node(|n| HigherIntermediateValue::UnaryOp(op.node, Box::new(n))), right.map_node(|n| HigherIntermediateValue::UnaryOp(op.node, Box::new(n))),
); );
@ -846,10 +856,10 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
} else if let Some(left) = space_separated.pop() { } else if let Some(left) = space_separated.pop() {
self.whitespace(); self.whitespace();
if ValueVisitor::new(self.parser, left.span) if ValueVisitor::new(self.parser, left.span)
.eval(left.node.clone())? .eval(left.node.clone(), false)?
.is_true() .is_true()
{ {
let right = self.single_value()?; let right = self.single_value(in_paren)?;
space_separated.push( space_separated.push(
HigherIntermediateValue::BinaryOp( HigherIntermediateValue::BinaryOp(
Box::new(left.node), Box::new(left.node),
@ -890,7 +900,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
} else if let Some(left) = space_separated.pop() { } else if let Some(left) = space_separated.pop() {
self.whitespace(); self.whitespace();
if ValueVisitor::new(self.parser, left.span) if ValueVisitor::new(self.parser, left.span)
.eval(left.node.clone())? .eval(left.node.clone(), false)?
.is_true() .is_true()
{ {
// we explicitly ignore errors here as a workaround for short circuiting // we explicitly ignore errors here as a workaround for short circuiting
@ -912,7 +922,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
} }
space_separated.push(left); space_separated.push(left);
} else { } else {
let right = self.single_value()?; let right = self.single_value(in_paren)?;
space_separated.push( space_separated.push(
HigherIntermediateValue::BinaryOp( HigherIntermediateValue::BinaryOp(
Box::new(left.node), Box::new(left.node),
@ -929,7 +939,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
_ => { _ => {
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
self.whitespace(); self.whitespace();
let right = self.single_value()?; let right = self.single_value(in_paren)?;
space_separated.push( space_separated.push(
HigherIntermediateValue::BinaryOp( HigherIntermediateValue::BinaryOp(
Box::new(left.node), Box::new(left.node),
@ -946,7 +956,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
Ok(()) Ok(())
} }
fn single_value(&mut self) -> SassResult<Spanned<HigherIntermediateValue>> { fn single_value(&mut self, in_paren: bool) -> SassResult<Spanned<HigherIntermediateValue>> {
let next = self let next = self
.next() .next()
.ok_or(("Expected expression.", self.parser.span_before))??; .ok_or(("Expected expression.", self.parser.span_before))??;
@ -955,7 +965,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
IntermediateValue::Op(op) => match op { IntermediateValue::Op(op) => match op {
Op::Minus => { Op::Minus => {
self.whitespace(); self.whitespace();
let val = self.single_value()?; let val = self.single_value(in_paren)?;
Spanned { Spanned {
node: HigherIntermediateValue::UnaryOp(Op::Minus, Box::new(val.node)), node: HigherIntermediateValue::UnaryOp(Op::Minus, Box::new(val.node)),
span: next.span.merge(val.span), span: next.span.merge(val.span),
@ -963,7 +973,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
} }
Op::Not => { Op::Not => {
self.whitespace(); self.whitespace();
let val = self.single_value()?; let val = self.single_value(in_paren)?;
Spanned { Spanned {
node: HigherIntermediateValue::UnaryOp(Op::Not, Box::new(val.node)), node: HigherIntermediateValue::UnaryOp(Op::Not, Box::new(val.node)),
span: next.span.merge(val.span), span: next.span.merge(val.span),
@ -971,17 +981,17 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
} }
Op::Plus => { Op::Plus => {
self.whitespace(); self.whitespace();
self.single_value()? self.single_value(in_paren)?
} }
Op::Div => { Op::Div => {
self.whitespace(); self.whitespace();
let val = self.single_value()?; let val = self.single_value(in_paren)?;
Spanned { Spanned {
node: HigherIntermediateValue::Literal(Value::String( node: HigherIntermediateValue::Literal(Value::String(
format!( format!(
"/{}", "/{}",
ValueVisitor::new(self.parser, val.span) ValueVisitor::new(self.parser, val.span)
.eval(val.node)? .eval(val.node, false)?
.to_css_string(val.span)? .to_css_string(val.span)?
), ),
QuoteKind::None, QuoteKind::None,
@ -1012,7 +1022,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
return Err(("Expected expression.", self.parser.span_before).into()) return Err(("Expected expression.", self.parser.span_before).into())
} }
IntermediateValue::Bracketed(t) => { IntermediateValue::Bracketed(t) => {
let v = self.parser.parse_value_from_vec(t)?; let v = self.parser.parse_value_from_vec(t, in_paren)?;
HigherIntermediateValue::Literal(match v.node { HigherIntermediateValue::Literal(match v.node {
Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed), Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed),
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed), v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed),
@ -1050,7 +1060,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
let mut map = SassMap::new(); let mut map = SassMap::new();
let key = self let key = self
.parser .parser
.parse_value_from_vec(read_until_char(paren_toks, ':')?)?; .parse_value_from_vec(read_until_char(paren_toks, ':')?, true)?;
if paren_toks.peek().is_none() { if paren_toks.peek().is_none() {
return Ok(Spanned { return Ok(Spanned {
@ -1063,7 +1073,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
let val = self let val = self
.parser .parser
.parse_value_from_vec(read_until_char(paren_toks, ',')?)?; .parse_value_from_vec(read_until_char(paren_toks, ',')?, true)?;
map.insert(key.node, val.node); map.insert(key.node, val.node);
@ -1081,11 +1091,11 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
loop { loop {
let key = self let key = self
.parser .parser
.parse_value_from_vec(read_until_char(paren_toks, ':')?)?; .parse_value_from_vec(read_until_char(paren_toks, ':')?, true)?;
devour_whitespace(paren_toks); devour_whitespace(paren_toks);
let val = self let val = self
.parser .parser
.parse_value_from_vec(read_until_char(paren_toks, ',')?)?; .parse_value_from_vec(read_until_char(paren_toks, ',')?, true)?;
span = span.merge(val.span); span = span.merge(val.span);
devour_whitespace(paren_toks); devour_whitespace(paren_toks);
if map.insert(key.node, val.node) { if map.insert(key.node, val.node) {

View File

@ -169,7 +169,7 @@ impl<'a> Parser<'a> {
_ => val_toks.push(self.toks.next().unwrap()), _ => val_toks.push(self.toks.next().unwrap()),
} }
} }
let val = self.parse_value_from_vec(val_toks)?; let val = self.parse_value_from_vec(val_toks, true)?;
Ok(VariableValue::new(val, global, default)) Ok(VariableValue::new(val, global, default))
} }
} }

View File

@ -30,7 +30,7 @@ pub(crate) enum Value {
True, True,
False, False,
Null, Null,
Dimension(Number, Unit), Dimension(Number, Unit, bool),
List(Vec<Value>, ListSeparator, Brackets), List(Vec<Value>, ListSeparator, Brackets),
Color(Box<Color>), Color(Box<Color>),
String(String, QuoteKind), String(String, QuoteKind),
@ -122,7 +122,7 @@ impl Value {
pub fn to_css_string(&self, span: Span) -> SassResult<Cow<'static, str>> { pub fn to_css_string(&self, span: Span) -> SassResult<Cow<'static, str>> {
Ok(match self { Ok(match self {
Value::Important => Cow::const_str("!important"), Value::Important => Cow::const_str("!important"),
Value::Dimension(num, unit) => match unit { Value::Dimension(num, unit, _) => match unit {
Unit::Mul(..) | Unit::Div(..) => { Unit::Mul(..) | Unit::Div(..) => {
return Err((format!("{}{} isn't a valid CSS value.", num, unit), span).into()); return Err((format!("{}{} isn't a valid CSS value.", num, unit), span).into());
} }
@ -252,8 +252,8 @@ impl Value {
Value::String(s2, ..) => s1 == s2, Value::String(s2, ..) => s1 == s2,
_ => false, _ => false,
}, },
Value::Dimension(n, unit) => match other { Value::Dimension(n, unit, _) => match other {
Value::Dimension(n2, unit2) => { Value::Dimension(n2, unit2, _) => {
if !unit.comparable(unit2) { if !unit.comparable(unit2) {
false false
} else if unit == unit2 { } else if unit == unit2 {
@ -294,8 +294,8 @@ impl Value {
Value::String(s2, ..) => s1 != s2, Value::String(s2, ..) => s1 != s2,
_ => true, _ => true,
}, },
Value::Dimension(n, unit) => match other { Value::Dimension(n, unit, _) => match other {
Value::Dimension(n2, unit2) => { Value::Dimension(n2, unit2, _) => {
if !unit.comparable(unit2) { if !unit.comparable(unit2) {
true true
} else if unit == unit2 { } else if unit == unit2 {
@ -371,7 +371,7 @@ impl Value {
.collect::<SassResult<Vec<String>>>()? .collect::<SassResult<Vec<String>>>()?
.join(", ") .join(", ")
)), )),
Value::Dimension(num, unit) => Cow::owned(format!("{}{}", num, unit)), Value::Dimension(num, unit, _) => Cow::owned(format!("{}{}", num, unit)),
Value::ArgList(args) if args.is_empty() => Cow::const_str("()"), Value::ArgList(args) if args.is_empty() => Cow::const_str("()"),
Value::ArgList(args) if args.len() == 1 => Cow::owned(format!( Value::ArgList(args) if args.len() == 1 => Cow::owned(format!(
"({},)", "({},)",

View File

@ -118,3 +118,54 @@ error!(
"a {\n color: 1 / get-function(lighten);\n}\n", "a {\n color: 1 / get-function(lighten);\n}\n",
"Error: get-function(\"lighten\") isn't a valid CSS value." "Error: get-function(\"lighten\") isn't a valid CSS value."
); );
test!(
does_not_eval_plain,
"a {\n color: 1 / 2;\n}\n",
"a {\n color: 1/2;\n}\n"
);
test!(
does_eval_inside_parens,
"a {\n color: (1/2);\n}\n",
"a {\n color: 0.5;\n}\n"
);
test!(
does_eval_when_one_is_calculated,
"a {\n color: (1*1) / 2;\n}\n",
"a {\n color: 0.5;\n}\n"
);
test!(
does_not_eval_from_unary_minus,
"a {\n color: -1 / 2;\n}\n",
"a {\n color: -1/2;\n}\n"
);
test!(
does_eval_from_variable,
"$a: 1;\na {\n color: $a / 2;\n}\n",
"a {\n color: 0.5;\n}\n"
);
test!(
does_eval_single_number_in_parens,
"a {\n color: (1) / 2;\n}\n",
"a {\n color: 0.5;\n}\n"
);
test!(
does_eval_function_call,
"@function foo() {
@return 1;
}
a {
color: foo() / 2;
}",
"a {\n color: 0.5;\n}\n"
);
test!(
does_not_eval_chained_binop_division,
"a {\n color: 1 / 3 / 4;\n}\n",
"a {\n color: 1/3/4;\n}\n"
);
test!(
does_not_eval_chained_binop_one_not_division,
"a {\n color: 1 + 3 / 4;\n}\n",
"a {\n color: 1.75;\n}\n"
);

View File

@ -50,11 +50,6 @@ test!(
); );
test!(positive_float_leading_zero, "a {\n color: 0.1;\n}\n"); test!(positive_float_leading_zero, "a {\n color: 0.1;\n}\n");
test!(negative_float_leading_zero, "a {\n color: -0.1;\n}\n"); test!(negative_float_leading_zero, "a {\n color: -0.1;\n}\n");
test!(
num_plus_div,
"a {\n color: 1 + 3/4;\n}\n",
"a {\n color: 1.75;\n}\n"
);
test!( test!(
negative_near_zero_no_sign, negative_near_zero_no_sign,
"a {\n color: -0.000000000001;\n}\n", "a {\n color: -0.000000000001;\n}\n",