diff --git a/src/builtin/color/hsl.rs b/src/builtin/color/hsl.rs index ac31b7b..4792524 100644 --- a/src/builtin/color/hsl.rs +++ b/src/builtin/color/hsl.rs @@ -35,7 +35,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } let lightness = match channels.pop() { - Some(Value::Dimension(n, _)) => n / Number::from(100), + Some(Value::Dimension(n, ..)) => n / Number::from(100), Some(v) => { return Err(( 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() { - Some(Value::Dimension(n, _)) => n / Number::from(100), + Some(Value::Dimension(n, ..)) => n / Number::from(100), Some(v) => { return Err(( 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() { - Some(Value::Dimension(n, _)) => n, + Some(Value::Dimension(n, ..)) => n, Some(v) => { return Err(( 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 { let hue = match parser.arg(&mut args, 0, "hue")? { - Value::Dimension(n, _) => n, + Value::Dimension(n, ..) => n, v if v.is_special_function() => { let saturation = parser.arg(&mut args, 1, "saturation")?; 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")? { - Value::Dimension(n, _) => n / Number::from(100), + Value::Dimension(n, ..) => n / Number::from(100), v if v.is_special_function() => { let lightness = parser.arg(&mut args, 2, "lightness")?; 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")? { - Value::Dimension(n, _) => n / Number::from(100), + Value::Dimension(n, ..) => n / Number::from(100), v if v.is_special_function() => { let mut string = format!( "{}({}, {}, {}", @@ -177,10 +177,10 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> &mut args, 3, "alpha", - Value::Dimension(Number::one(), Unit::None), + Value::Dimension(Number::one(), Unit::None, true), )? { - Value::Dimension(n, Unit::None) => n, - Value::Dimension(n, Unit::Percent) => n / Number::from(100), + Value::Dimension(n, Unit::None, _) => n, + Value::Dimension(n, Unit::Percent, _) => n / Number::from(100), v @ Value::Dimension(..) => { return Err(( format!( @@ -229,7 +229,7 @@ fn hsla(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -241,7 +241,7 @@ fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -253,7 +253,7 @@ fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -275,7 +275,7 @@ fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult } }; let degrees = match parser.arg(&mut args, 1, "degrees")? { - Value::Dimension(n, _) => n, + Value::Dimension(n, ..) => n, v => { return Err(( format!( @@ -303,7 +303,7 @@ fn lighten(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; 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 => { return Err(( format!( @@ -331,7 +331,7 @@ fn darken(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; 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 => { return Err(( format!( @@ -361,7 +361,7 @@ fn saturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } 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 => { return Err(( format!( @@ -375,7 +375,7 @@ fn saturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }; let color = match parser.arg(&mut args, 0, "color")? { Value::Color(c) => c, - Value::Dimension(n, u) => { + Value::Dimension(n, u, _) => { return Ok(Value::String( format!("saturate({}{})", n, u), QuoteKind::None, @@ -405,7 +405,7 @@ fn desaturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult } }; 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 => { return Err(( format!( @@ -424,7 +424,7 @@ fn grayscale(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let color = match parser.arg(&mut args, 0, "color")? { Value::Color(c) => c, - Value::Dimension(n, u) => { + Value::Dimension(n, u, _) => { return Ok(Value::String( format!("grayscale({}{})", n, u), QuoteKind::None, @@ -462,9 +462,9 @@ fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { &mut args, 1, "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 => { return Err(( format!( @@ -478,7 +478,7 @@ fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }; match parser.arg(&mut args, 0, "color")? { 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)) } Value::Dimension(..) => Err(( diff --git a/src/builtin/color/opacity.rs b/src/builtin/color/opacity.rs index 648a5e7..46e84c6 100644 --- a/src/builtin/color/opacity.rs +++ b/src/builtin/color/opacity.rs @@ -8,7 +8,7 @@ use crate::{ fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -20,8 +20,8 @@ fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn opacity(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match parser.arg(&mut args, 0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)), - Value::Dimension(num, unit) => Ok(Value::String( + Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None, true)), + Value::Dimension(num, unit, _) => Ok(Value::String( format!("opacity({}{})", num, unit), QuoteKind::None, )), @@ -46,7 +46,7 @@ fn opacify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; 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 => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), @@ -71,7 +71,7 @@ fn fade_in(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; 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 => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), @@ -96,7 +96,7 @@ fn transparentize(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult bound!(args, "amount", n, u, 0, 1), + Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1), v => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), @@ -121,7 +121,7 @@ fn fade_out(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; 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 => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), diff --git a/src/builtin/color/other.rs b/src/builtin/color/other.rs index 3d87e7d..00bc92f 100644 --- a/src/builtin/color/other.rs +++ b/src/builtin/color/other.rs @@ -15,7 +15,7 @@ use crate::{ macro_rules! opt_rgba { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => { 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, v => { return Err(( @@ -31,7 +31,7 @@ macro_rules! opt_rgba { macro_rules! opt_hsl { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => { 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)) } Value::Null => None, @@ -81,7 +81,7 @@ fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Some(n), + Value::Dimension(n, ..) => Some(n), Value::Null => None, v => { return Err(( @@ -140,7 +140,7 @@ fn adjust_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Some(n), + Value::Dimension(n, ..) => Some(n), Value::Null => None, v => { return Err(( @@ -198,7 +198,7 @@ fn scale_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult macro_rules! opt_scale_arg { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => { 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)) } v @ Value::Dimension(..) => { diff --git a/src/builtin/color/rgb.rs b/src/builtin/color/rgb.rs index 3513eea..86dfeb1 100644 --- a/src/builtin/color/rgb.rs +++ b/src/builtin/color/rgb.rs @@ -38,8 +38,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } let blue = match channels.pop() { - 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::None, _)) => n, + Some(Value::Dimension(n, Unit::Percent, _)) => { + (n / Number::from(100)) * Number::from(255) + } Some(v) if v.is_special_function() => { let green = 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() { - 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::None, _)) => n, + Some(Value::Dimension(n, Unit::Percent, _)) => { + (n / Number::from(100)) * Number::from(255) + } Some(v) if v.is_special_function() => { let string = match channels.pop() { Some(red) => format!( @@ -91,8 +95,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> }; let red = match channels.pop() { - 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::None, _)) => n, + Some(Value::Dimension(n, Unit::Percent, _)) => { + (n / Number::from(100)) * Number::from(255) + } Some(v) if v.is_special_function() => { return Ok(Value::String( 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")? { - Value::Dimension(n, Unit::None) => n, - Value::Dimension(n, Unit::Percent) => n / Number::from(100), + Value::Dimension(n, Unit::None, _) => n, + Value::Dimension(n, Unit::Percent, _) => n / Number::from(100), v @ Value::Dimension(..) => { return Err(( 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)))) } else { let red = match parser.arg(&mut args, 0, "red")? { - Value::Dimension(n, Unit::None) => n, - Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + Value::Dimension(n, Unit::None, _) => n, + Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255), v @ Value::Dimension(..) => { return Err(( 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")? { - Value::Dimension(n, Unit::None) => n, - Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + Value::Dimension(n, Unit::None, _) => n, + Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255), v @ Value::Dimension(..) => { return Err(( 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")? { - Value::Dimension(n, Unit::None) => n, - Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + Value::Dimension(n, Unit::None, _) => n, + Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255), v @ Value::Dimension(..) => { return Err(( format!( @@ -304,10 +310,10 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> &mut args, 3, "alpha", - Value::Dimension(Number::one(), Unit::None), + Value::Dimension(Number::one(), Unit::None, true), )? { - Value::Dimension(n, Unit::None) => n, - Value::Dimension(n, Unit::Percent) => n / Number::from(100), + Value::Dimension(n, Unit::None, _) => n, + Value::Dimension(n, Unit::Percent, _) => n / Number::from(100), v @ Value::Dimension(..) => { return Err(( format!( @@ -354,7 +360,7 @@ fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -366,7 +372,7 @@ fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -378,7 +384,7 @@ fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -415,9 +421,9 @@ fn mix(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { &mut args, 2, "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 => { return Err(( format!( diff --git a/src/builtin/list.rs b/src/builtin/list.rs index fd10048..e3edbc7 100644 --- a/src/builtin/list.rs +++ b/src/builtin/list.rs @@ -16,6 +16,7 @@ fn length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Dimension( Number::from(parser.arg(&mut args, 0, "list")?.as_list().len()), Unit::None, + true, )) } @@ -23,7 +24,7 @@ fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let mut list = parser.arg(&mut args, 0, "list")?.as_list(); let n = match parser.arg(&mut args, 1, "n")? { - Value::Dimension(num, _) => num, + Value::Dimension(num, ..) => num, v => { return Err(( format!("$n: {} is not a number.", v.inspect(args.span())?), @@ -80,7 +81,7 @@ fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { v => (vec![v], ListSeparator::Space, Brackets::None), }; let n = match parser.arg(&mut args, 1, "n")? { - Value::Dimension(num, _) => num, + Value::Dimension(num, ..) => num, v => { return Err(( format!("$n: {} is not a number.", v.inspect(args.span())?), @@ -248,7 +249,7 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Some(v) => Number::from(v + 1), 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 { diff --git a/src/builtin/math.rs b/src/builtin/math.rs index bbd597f..0c83db8 100644 --- a/src/builtin/math.rs +++ b/src/builtin/math.rs @@ -16,7 +16,7 @@ use crate::{ fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(..) => { return Err(( format!( @@ -35,13 +35,13 @@ fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult .into()) } }; - Ok(Value::Dimension(num, Unit::Percent)) + Ok(Value::Dimension(num, Unit::Percent, true)) } fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -53,7 +53,7 @@ fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -65,7 +65,7 @@ fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -77,7 +77,7 @@ fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; 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(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -89,7 +89,7 @@ fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let unit1 = match parser.arg(&mut args, 0, "number1")? { - Value::Dimension(_, u) => u, + Value::Dimension(_, u, _) => u, v => { return Err(( format!("$number1: {} is not a number.", v.inspect(args.span())?), @@ -99,7 +99,7 @@ fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult } }; let unit2 = match parser.arg(&mut args, 1, "number2")? { - Value::Dimension(_, u) => u, + Value::Dimension(_, u, _) => u, v => { return Err(( format!("$number2: {} is not a number.", v.inspect(args.span())?), @@ -117,12 +117,13 @@ fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let limit = match parser.default_arg(&mut args, 0, "limit", Value::Null)? { - Value::Dimension(n, _) => n, + 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, + true, )); } v => { @@ -135,7 +136,7 @@ fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }; 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() { @@ -165,6 +166,7 @@ fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Dimension( Number::from(rng.gen_range(0, limit) + 1), Unit::None, + true, )) } @@ -175,7 +177,7 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { .variadic_args(args)? .into_iter() .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()), }) .collect::>>()? @@ -187,15 +189,23 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { for num in nums { if ValueVisitor::new(parser, span) .less_than( - HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())), - HigherIntermediateValue::Literal(Value::Dimension(min.0.clone(), min.1.clone())), + HigherIntermediateValue::Literal(Value::Dimension( + num.0.clone(), + num.1.clone(), + true, + )), + HigherIntermediateValue::Literal(Value::Dimension( + min.0.clone(), + min.1.clone(), + true, + )), )? .is_true() { 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 { @@ -205,7 +215,7 @@ fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { .variadic_args(args)? .into_iter() .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()), }) .collect::>>()? @@ -217,15 +227,23 @@ fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { for num in nums { if ValueVisitor::new(parser, span) .greater_than( - HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())), - HigherIntermediateValue::Literal(Value::Dimension(max.0.clone(), max.1.clone())), + HigherIntermediateValue::Literal(Value::Dimension( + num.0.clone(), + num.1.clone(), + true, + )), + HigherIntermediateValue::Literal(Value::Dimension( + max.0.clone(), + max.1.clone(), + true, + )), )? .is_true() { max = num; } } - Ok(Value::Dimension(max.0, max.1)) + Ok(Value::Dimension(max.0, max.1, true)) } pub(crate) fn declare(f: &mut GlobalFunctionMap) { diff --git a/src/builtin/meta.rs b/src/builtin/meta.rs index 957391e..73f0215 100644 --- a/src/builtin/meta.rs +++ b/src/builtin/meta.rs @@ -53,7 +53,7 @@ fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; let unit = match parser.arg(&mut args, 0, "number")? { - Value::Dimension(_, u) => u.to_string(), + Value::Dimension(_, u, _) => u.to_string(), v => { return Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), @@ -75,8 +75,8 @@ fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; #[allow(clippy::match_same_arms)] Ok(match parser.arg(&mut args, 0, "number")? { - Value::Dimension(_, Unit::None) => Value::True, - Value::Dimension(_, _) => Value::False, + Value::Dimension(_, Unit::None, _) => Value::True, + Value::Dimension(..) => Value::False, _ => Value::True, }) } diff --git a/src/builtin/string.rs b/src/builtin/string.rs index 5357e8a..917c1d0 100644 --- a/src/builtin/string.rs +++ b/src/builtin/string.rs @@ -51,6 +51,7 @@ fn str_length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Value::String(i, _) => Ok(Value::Dimension( Number::from(i.chars().count()), Unit::None, + true, )), v => Err(( format!("$string: {} is not a string.", v.inspect(args.span())?), @@ -98,15 +99,15 @@ fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }; let str_len = string.chars().count(); 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()) } - 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) } - 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) => (n.to_integer() + BigInt::from(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, _) => (n.to_integer() + BigInt::from(str_len + 1)) .to_usize() .unwrap(), v @ Value::Dimension(..) => { @@ -128,15 +129,15 @@ fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; 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()) } - 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) } - 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) => (n.to_integer() + BigInt::from(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, _) => (n.to_integer() + BigInt::from(str_len + 1)) .to_usize() .unwrap_or(str_len + 1), v @ Value::Dimension(..) => { @@ -202,7 +203,7 @@ fn str_index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }; 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, }) } @@ -232,10 +233,10 @@ fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult }; 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()) } - Value::Dimension(n, Unit::None) => n, + Value::Dimension(n, Unit::None, _) => n, v @ Value::Dimension(..) => { return Err(( format!( diff --git a/src/parse/args.rs b/src/parse/args.rs index 1da193c..c4b00d7 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -178,7 +178,7 @@ impl<'a> Parser<'a> { } else { 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()); return Ok(CallArgs(args, span)); @@ -218,7 +218,7 @@ impl<'a> Parser<'a> { } 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 { Value::ArgList(v) => { for arg in v { @@ -263,7 +263,7 @@ impl<'a> Parser<'a> { } else { 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) { Some(v) => v, 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 => { return Err( (format!("Missing argument ${}.", &arg.name), args.span()).into() diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 646896d..a37fa72 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -22,7 +22,7 @@ impl<'a> Parser<'a> { let mut found_true = false; 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 let span_before = match self.toks.next() { @@ -85,7 +85,7 @@ impl<'a> Parser<'a> { self.throw_away_until_open_curly_brace()?; false } else { - let v = self.parse_value()?.node.is_true(); + let v = self.parse_value(true)?.node.is_true(); match self.toks.next() { Some(Token { kind: '{', .. }) => {} Some(..) | None => { @@ -237,9 +237,9 @@ impl<'a> Parser<'a> { } } 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 { - Value::Dimension(n, _) => match n.to_integer().to_isize() { + Value::Dimension(n, ..) => match n.to_integer().to_isize() { Some(v) => v, 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 { - Value::Dimension(n, _) => match n.to_integer().to_isize() { + Value::Dimension(n, ..) => match n.to_integer().to_isize() { Some(v) => v, 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( var.node, Spanned { - node: Value::Dimension(Number::from(i), Unit::None), + node: Value::Dimension(Number::from(i), Unit::None, true), span: var.span, }, ); @@ -368,7 +368,7 @@ impl<'a> Parser<'a> { self.whitespace(); 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(); while val.node.is_true() { if self.flags.in_function() { @@ -411,7 +411,7 @@ impl<'a> Parser<'a> { .parse()?, ); } - val = self.parse_value_from_vec(cond.clone())?; + val = self.parse_value_from_vec(cond.clone(), true)?; } self.scopes.exit_scope(); @@ -452,7 +452,10 @@ impl<'a> Parser<'a> { } self.whitespace(); 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.whitespace(); let mut body = read_until_closing_curly_brace(self.toks)?; diff --git a/src/parse/function.rs b/src/parse/function.rs index 2dc5296..4172dd0 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -63,7 +63,7 @@ impl<'a> Parser<'a> { pub(super) fn parse_return(&mut self) -> SassResult> { 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() { self.toks.next(); } diff --git a/src/parse/import.rs b/src/parse/import.rs index eb89660..64fef73 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -22,7 +22,7 @@ impl<'a> Parser<'a> { let Spanned { node: file_name_as_value, span, - } = self.parse_value()?; + } = self.parse_value(true)?; let file_name = match file_name_as_value { Value::String(s, QuoteKind::Quoted) => { if s.ends_with(".css") || s.starts_with("http://") || s.starts_with("https://") { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 825baca..001183f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -139,7 +139,7 @@ impl<'a> Parser<'a> { let Spanned { node: message, span, - } = self.parse_value()?; + } = self.parse_value(false)?; return Err(( message.inspect(span)?.to_string(), @@ -151,7 +151,7 @@ impl<'a> Parser<'a> { let Spanned { node: message, span, - } = self.parse_value()?; + } = self.parse_value(false)?; span.merge(kind_string.span); if let Some(Token { kind: ';', pos }) = self.toks.peek() { kind_string.span.merge(*pos); @@ -166,7 +166,7 @@ impl<'a> Parser<'a> { let Spanned { node: message, span, - } = self.parse_value()?; + } = self.parse_value(false)?; span.merge(kind_string.span); if let Some(Token { kind: ';', pos }) = self.toks.peek() { kind_string.span.merge(*pos); @@ -416,7 +416,7 @@ impl<'a> Parser<'a> { } pub fn parse_interpolation(&mut self) -> SassResult> { - let val = self.parse_value()?; + let val = self.parse_value(true)?; match self.toks.next() { Some(Token { kind: '}', .. }) => {} Some(..) | None => return Err(("expected \"}\".", val.span).into()), @@ -437,7 +437,7 @@ impl<'a> Parser<'a> { toks: Vec, quoted: bool, ) -> SassResult> { - let value = self.parse_value_from_vec(toks)?; + let value = self.parse_value_from_vec(toks, false)?; if quoted { value.node.to_css_string(value.span) } else { diff --git a/src/parse/style.rs b/src/parse/style.rs index 2ee336c..d4052b1 100644 --- a/src/parse/style.rs +++ b/src/parse/style.rs @@ -125,7 +125,7 @@ impl<'a> Parser<'a> { c if is_name(*c) => { if let Some(toks) = self.parse_style_value_when_no_space_after_semicolon() { 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); return Ok(SelectorOrStyle::Style( InternedString::get_or_intern(property), @@ -173,7 +173,7 @@ impl<'a> Parser<'a> { } fn parse_style_value(&mut self) -> SassResult> { - self.parse_value() + self.parse_value(false) } pub(super) fn parse_style_group( diff --git a/src/parse/value/css_function.rs b/src/parse/value/css_function.rs index 36a720a..e3f2674 100644 --- a/src/parse/value/css_function.rs +++ b/src/parse/value/css_function.rs @@ -339,7 +339,7 @@ impl<'a> Parser<'a> { fn peek_interpolation(&mut self) -> SassResult> { let vec = peek_until_closing_curly_brace(self.toks)?; self.toks.advance_cursor(); - let val = self.parse_value_from_vec(vec)?; + let val = self.parse_value_from_vec(vec, false)?; Ok(Spanned { node: val.node.unquote(), span: val.span, diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs index 994bfd9..781e228 100644 --- a/src/parse/value/eval.rs +++ b/src/parse/value/eval.rs @@ -46,12 +46,15 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { Self { parser, span } } - pub fn eval(&mut self, value: HigherIntermediateValue) -> SassResult { + pub fn eval(&mut self, value: HigherIntermediateValue, in_parens: bool) -> SassResult { match value { + HigherIntermediateValue::Literal(Value::Dimension(n, u, _)) if in_parens => { + Ok(Value::Dimension(n, u, true)) + } HigherIntermediateValue::Literal(v) => Ok(v), - HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2), - HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val), - HigherIntermediateValue::Paren(val) => self.eval(*val), + HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2, in_parens), + HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val, in_parens), + HigherIntermediateValue::Paren(val) => self.eval(*val, true), HigherIntermediateValue::Function(function, args) => { self.parser.call_function(function, args) } @@ -63,16 +66,21 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { val1: HigherIntermediateValue, op: Op, val2: HigherIntermediateValue, + in_parens: bool, ) -> SassResult { - let mut val1 = self.paren_or_unary(val1)?; - let val2 = self.paren_or_unary(val2)?; + let mut val1 = self.paren_or_unary(val1, in_parens)?; + let val2 = self.paren_or_unary(val2, in_parens)?; if let HigherIntermediateValue::BinaryOp(val1_1, op2, val1_2) = val1 { + let in_parens = op != Op::Div || op2 != Op::Div; 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 { - let val2 = HigherIntermediateValue::Literal(self.bin_op(*val1_2, op, val2)?); - return self.bin_op(*val1_1, op2, val2); + let 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::Minus => self.sub(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::And => Self::and(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 { - let val = self.eval(val)?; + fn unary_op( + &mut self, + op: Op, + val: HigherIntermediateValue, + in_parens: bool, + ) -> SassResult { + let val = self.eval(val, in_parens)?; match op { Op::Minus => self.unary_minus(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 { 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), }) } @@ -124,7 +137,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { fn paren(&mut self, val: HigherIntermediateValue) -> SassResult { Ok(if let HigherIntermediateValue::Paren(v) = val { - HigherIntermediateValue::Literal(self.eval(*v)?) + HigherIntermediateValue::Literal(self.eval(*v, true)?) } else { val }) @@ -133,11 +146,12 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { fn paren_or_unary( &mut self, val: HigherIntermediateValue, + in_parens: bool, ) -> SassResult { let val = self.paren(val)?; Ok(match 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::Literal(self.parser.call_function(function, args)?) @@ -191,8 +205,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { QuoteKind::None, ), }, - Value::Dimension(num, unit) => match right { - Value::Dimension(num2, unit2) => { + Value::Dimension(num, unit, _) => match right { + Value::Dimension(num2, unit2, _) => { if !unit.comparable(&unit2) { return Err(( format!("Incompatible units {} and {}.", unit2, unit), @@ -201,11 +215,11 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { .into()); } if unit == unit2 { - Value::Dimension(num + num2, unit) + Value::Dimension(num + num2, unit, true) } else if unit == Unit::None { - Value::Dimension(num + num2, unit2) + Value::Dimension(num + num2, unit2, true) } else if unit2 == Unit::None { - Value::Dimension(num + num2, unit) + Value::Dimension(num + num2, unit, true) } else { Value::Dimension( num + num2 @@ -213,6 +227,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { [unit2.to_string().as_str()] .clone(), unit, + true, ) } } @@ -299,8 +314,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { format!("-{}", right.to_css_string(self.span)?), QuoteKind::None, ), - Value::Dimension(num, unit) => match right { - Value::Dimension(num2, unit2) => { + Value::Dimension(num, unit, _) => match right { + Value::Dimension(num2, unit2, _) => { if !unit.comparable(&unit2) { return Err(( format!("Incompatible units {} and {}.", unit2, unit), @@ -309,11 +324,11 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { .into()); } if unit == unit2 { - Value::Dimension(num - num2, unit) + Value::Dimension(num - num2, unit, true) } else if unit == Unit::None { - Value::Dimension(num - num2, unit2) + Value::Dimension(num - num2, unit2, true) } else if unit2 == Unit::None { - Value::Dimension(num - num2, unit) + Value::Dimension(num - num2, unit, true) } else { Value::Dimension( num - num2 @@ -321,6 +336,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { [unit2.to_string().as_str()] .clone(), unit, + true, ) } } @@ -418,14 +434,14 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { v => panic!("{:?}", v), }; Ok(match left { - Value::Dimension(num, unit) => match right { - Value::Dimension(num2, unit2) => { + Value::Dimension(num, unit, _) => match right { + Value::Dimension(num2, unit2, _) => { if unit == Unit::None { - Value::Dimension(num * num2, unit2) + Value::Dimension(num * num2, unit2, true) } else if unit2 == Unit::None { - Value::Dimension(num * num2, unit) + Value::Dimension(num * num2, unit, true) } 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, left: HigherIntermediateValue, right: HigherIntermediateValue, + in_parens: bool, ) -> SassResult { let left = match left { HigherIntermediateValue::Literal(v) => v, @@ -473,35 +490,43 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { format!("/{}", right.to_css_string(self.span)?), QuoteKind::None, ), - Value::Dimension(num, unit) => match right { - Value::Dimension(num2, unit2) => { - // `unit(1em / 1em)` => `""` - if unit == unit2 { - Value::Dimension(num / num2, Unit::None) + Value::Dimension(num, unit, should_divide1) => match right { + Value::Dimension(num2, unit2, should_divide2) => { + if should_divide1 || should_divide2 || in_parens { + // `unit(1em / 1em)` => `""` + if unit == unit2 { + Value::Dimension(num / num2, Unit::None, true) - // `unit(1 / 1em)` => `"em^-1"` - } else if unit == Unit::None { - Value::Dimension(num / num2, Unit::None / unit2) + // `unit(1 / 1em)` => `"em^-1"` + } else if unit == Unit::None { + Value::Dimension(num / num2, Unit::None / unit2, true) - // `unit(1em / 1)` => `"em"` - } else if unit2 == Unit::None { - Value::Dimension(num / num2, unit) + // `unit(1em / 1)` => `"em"` + } else if unit2 == Unit::None { + Value::Dimension(num / num2, unit, true) - // `unit(1in / 1px)` => `""` - } else if unit.comparable(&unit2) { - Value::Dimension( - num / (num2 - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone()), - Unit::None, - ) - // `unit(1em / 1px)` => `"em/px"` - // todo: this should probably be its own variant - // within the `Value` enum + // `unit(1in / 1px)` => `""` + } else if unit.comparable(&unit2) { + Value::Dimension( + num / (num2 + * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] + [unit2.to_string().as_str()] + .clone()), + Unit::None, + true, + ) + // `unit(1em / 1px)` => `"em/px"` + // 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 { - // todo: remember to account for `Mul` and `Div` - todo!("non-comparable inverse units") + Value::String( + format!("{}{}/{}{}", num, unit, num2, unit2), + QuoteKind::None, + ) } } Value::String(s, q) => { @@ -605,28 +630,28 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { v => panic!("{:?}", v), }; Ok(match left { - Value::Dimension(n, u) => match right { - Value::Dimension(n2, u2) => { + Value::Dimension(n, u, _) => match right { + Value::Dimension(n2, u2, _) => { if !u.comparable(&u2) { return Err( (format!("Incompatible units {} and {}.", u2, u), self.span).into() ); } if u == u2 { - Value::Dimension(n % n2, u) + Value::Dimension(n % n2, u, true) } else if u == Unit::None { - Value::Dimension(n % n2, u2) + Value::Dimension(n % n2, u2, true) } else if u2 == Unit::None { - Value::Dimension(n % n2, u) + Value::Dimension(n % n2, u, true) } else { - Value::Dimension(n, u) + Value::Dimension(n, u, true) } } _ => { return Err(( format!( "Undefined operation \"{} % {}\".", - Value::Dimension(n, u).inspect(self.span)?, + Value::Dimension(n, u, true).inspect(self.span)?, right.inspect(self.span)? ), self.span, @@ -711,8 +736,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { v => panic!("{:?}", v), }; let ordering = match left { - Value::Dimension(num, unit) => match &right { - Value::Dimension(num2, unit2) => { + Value::Dimension(num, unit, _) => match &right { + Value::Dimension(num2, unit2, _) => { if !unit.comparable(unit2) { return Err(( format!("Incompatible units {} and {}.", unit2, unit), diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 4efdee9..4651911 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -52,7 +52,7 @@ impl IsWhitespace for IntermediateValue { } impl<'a> Parser<'a> { - pub(crate) fn parse_value(&mut self) -> SassResult> { + pub(crate) fn parse_value(&mut self, in_paren: bool) -> SassResult> { self.whitespace(); let span = match self.toks.peek() { Some(Token { kind: '}', .. }) @@ -80,6 +80,7 @@ impl<'a> Parser<'a> { }, &mut space_separated, last_was_whitespace, + in_paren, )?; } IntermediateValue::Whitespace => { @@ -104,7 +105,7 @@ impl<'a> Parser<'a> { span = span.merge(a.span); a.node }) - .map(|a| ValueVisitor::new(iter.parser, span).eval(a)) + .map(|a| ValueVisitor::new(iter.parser, span).eval(a, in_paren)) .collect::>>()?, ListSeparator::Space, Brackets::None, @@ -117,7 +118,7 @@ impl<'a> Parser<'a> { last_was_whitespace = false; space_separated.push( 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::Bracketed) } @@ -147,7 +148,7 @@ impl<'a> Parser<'a> { HigherIntermediateValue::Literal(Value::List( space_separated .into_iter() - .map(|a| ValueVisitor::new(self, span).eval(a.node)) + .map(|a| ValueVisitor::new(self, span).eval(a.node, in_paren)) .collect::>>()?, ListSeparator::Space, Brackets::None, @@ -158,7 +159,7 @@ impl<'a> Parser<'a> { Value::List( comma_separated .into_iter() - .map(|a| ValueVisitor::new(self, span).eval(a.node)) + .map(|a| ValueVisitor::new(self, span).eval(a.node, in_paren)) .collect::>>()?, ListSeparator::Comma, Brackets::None, @@ -166,13 +167,13 @@ impl<'a> Parser<'a> { .span(span) } else if space_separated.len() == 1 { ValueVisitor::new(self, span) - .eval(space_separated.pop().unwrap().node)? + .eval(space_separated.pop().unwrap().node, in_paren)? .span(span) } else { Value::List( space_separated .into_iter() - .map(|a| ValueVisitor::new(self, span).eval(a.node)) + .map(|a| ValueVisitor::new(self, span).eval(a.node, in_paren)) .collect::>>()?, ListSeparator::Space, Brackets::None, @@ -181,7 +182,11 @@ impl<'a> Parser<'a> { }) } - pub(crate) fn parse_value_from_vec(&mut self, toks: Vec) -> SassResult> { + pub(crate) fn parse_value_from_vec( + &mut self, + toks: Vec, + in_paren: bool, + ) -> SassResult> { Parser { toks: &mut toks.into_iter().peekmore(), map: self.map, @@ -197,7 +202,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, } - .parse_value() + .parse_value(in_paren) } fn parse_ident_value(&mut self) -> SassResult> { @@ -391,6 +396,7 @@ impl<'a> Parser<'a> { HigherIntermediateValue::Literal(Value::Dimension( Number::new_small(n), unit, + false, )), ) .span(span))); @@ -403,6 +409,7 @@ impl<'a> Parser<'a> { HigherIntermediateValue::Literal(Value::Dimension( Number::new_small(n), unit, + false, )), ) .span(span))); @@ -415,6 +422,7 @@ impl<'a> Parser<'a> { HigherIntermediateValue::Literal(Value::Dimension( Number::new_big(n), unit, + false, )), ) .span(span))); @@ -443,6 +451,7 @@ impl<'a> Parser<'a> { IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Dimension( Number::new_big(n * times_ten), unit, + false, ))) .span(span) } @@ -747,11 +756,12 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { op: Spanned, space_separated: &mut Vec>, last_was_whitespace: bool, + in_paren: bool, ) -> SassResult<()> { match op.node { Op::Not => { self.whitespace(); - let right = self.single_value()?; + let right = self.single_value(in_paren)?; space_separated.push(Spanned { node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)), span: right.span, @@ -759,7 +769,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { } Op::Div => { self.whitespace(); - let right = self.single_value()?; + let right = self.single_value(in_paren)?; if let Some(left) = space_separated.pop() { space_separated.push(Spanned { node: HigherIntermediateValue::BinaryOp( @@ -776,7 +786,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { format!( "/{}", ValueVisitor::new(self.parser, right.span) - .eval(right.node)? + .eval(right.node, false)? .to_css_string(right.span)? ), QuoteKind::None, @@ -788,7 +798,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { Op::Plus => { if let Some(left) = space_separated.pop() { self.whitespace(); - let right = self.single_value()?; + let right = self.single_value(in_paren)?; space_separated.push(Spanned { node: HigherIntermediateValue::BinaryOp( Box::new(left.node), @@ -799,7 +809,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { }); } else { self.whitespace(); - let right = self.single_value()?; + let right = self.single_value(in_paren)?; space_separated.push(Spanned { node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)), span: right.span, @@ -808,7 +818,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { } Op::Minus => { 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() { space_separated.push(Spanned { node: HigherIntermediateValue::BinaryOp( @@ -826,7 +836,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { ); } } else { - let right = self.single_value()?; + let right = self.single_value(in_paren)?; space_separated.push( 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() { self.whitespace(); if ValueVisitor::new(self.parser, left.span) - .eval(left.node.clone())? + .eval(left.node.clone(), false)? .is_true() { - let right = self.single_value()?; + let right = self.single_value(in_paren)?; space_separated.push( HigherIntermediateValue::BinaryOp( Box::new(left.node), @@ -890,7 +900,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { } else if let Some(left) = space_separated.pop() { self.whitespace(); if ValueVisitor::new(self.parser, left.span) - .eval(left.node.clone())? + .eval(left.node.clone(), false)? .is_true() { // 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); } else { - let right = self.single_value()?; + let right = self.single_value(in_paren)?; space_separated.push( HigherIntermediateValue::BinaryOp( Box::new(left.node), @@ -929,7 +939,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { _ => { if let Some(left) = space_separated.pop() { self.whitespace(); - let right = self.single_value()?; + let right = self.single_value(in_paren)?; space_separated.push( HigherIntermediateValue::BinaryOp( Box::new(left.node), @@ -946,7 +956,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { Ok(()) } - fn single_value(&mut self) -> SassResult> { + fn single_value(&mut self, in_paren: bool) -> SassResult> { let next = self .next() .ok_or(("Expected expression.", self.parser.span_before))??; @@ -955,7 +965,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { IntermediateValue::Op(op) => match op { Op::Minus => { self.whitespace(); - let val = self.single_value()?; + let val = self.single_value(in_paren)?; Spanned { node: HigherIntermediateValue::UnaryOp(Op::Minus, Box::new(val.node)), span: next.span.merge(val.span), @@ -963,7 +973,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { } Op::Not => { self.whitespace(); - let val = self.single_value()?; + let val = self.single_value(in_paren)?; Spanned { node: HigherIntermediateValue::UnaryOp(Op::Not, Box::new(val.node)), span: next.span.merge(val.span), @@ -971,17 +981,17 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { } Op::Plus => { self.whitespace(); - self.single_value()? + self.single_value(in_paren)? } Op::Div => { self.whitespace(); - let val = self.single_value()?; + let val = self.single_value(in_paren)?; Spanned { node: HigherIntermediateValue::Literal(Value::String( format!( "/{}", ValueVisitor::new(self.parser, val.span) - .eval(val.node)? + .eval(val.node, false)? .to_css_string(val.span)? ), QuoteKind::None, @@ -1012,7 +1022,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { return Err(("Expected expression.", self.parser.span_before).into()) } 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 { Value::List(v, sep, Brackets::None) => Value::List(v, sep, 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 key = self .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() { return Ok(Spanned { @@ -1063,7 +1073,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { let val = self .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); @@ -1081,11 +1091,11 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> { loop { let key = self .parser - .parse_value_from_vec(read_until_char(paren_toks, ':')?)?; + .parse_value_from_vec(read_until_char(paren_toks, ':')?, true)?; devour_whitespace(paren_toks); let val = self .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); devour_whitespace(paren_toks); if map.insert(key.node, val.node) { diff --git a/src/parse/variable.rs b/src/parse/variable.rs index caad769..40428f4 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -169,7 +169,7 @@ impl<'a> Parser<'a> { _ => 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)) } } diff --git a/src/value/mod.rs b/src/value/mod.rs index 4e5e77b..1e5f123 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -30,7 +30,7 @@ pub(crate) enum Value { True, False, Null, - Dimension(Number, Unit), + Dimension(Number, Unit, bool), List(Vec, ListSeparator, Brackets), Color(Box), String(String, QuoteKind), @@ -122,7 +122,7 @@ impl Value { pub fn to_css_string(&self, span: Span) -> SassResult> { Ok(match self { Value::Important => Cow::const_str("!important"), - Value::Dimension(num, unit) => match unit { + Value::Dimension(num, unit, _) => match unit { Unit::Mul(..) | Unit::Div(..) => { return Err((format!("{}{} isn't a valid CSS value.", num, unit), span).into()); } @@ -252,8 +252,8 @@ impl Value { Value::String(s2, ..) => s1 == s2, _ => false, }, - Value::Dimension(n, unit) => match other { - Value::Dimension(n2, unit2) => { + Value::Dimension(n, unit, _) => match other { + Value::Dimension(n2, unit2, _) => { if !unit.comparable(unit2) { false } else if unit == unit2 { @@ -294,8 +294,8 @@ impl Value { Value::String(s2, ..) => s1 != s2, _ => true, }, - Value::Dimension(n, unit) => match other { - Value::Dimension(n2, unit2) => { + Value::Dimension(n, unit, _) => match other { + Value::Dimension(n2, unit2, _) => { if !unit.comparable(unit2) { true } else if unit == unit2 { @@ -371,7 +371,7 @@ impl Value { .collect::>>()? .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.len() == 1 => Cow::owned(format!( "({},)", diff --git a/tests/division.rs b/tests/division.rs index 87fd6af..1c02dce 100644 --- a/tests/division.rs +++ b/tests/division.rs @@ -118,3 +118,54 @@ error!( "a {\n color: 1 / get-function(lighten);\n}\n", "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" +); diff --git a/tests/number.rs b/tests/number.rs index d98419d..032976c 100644 --- a/tests/number.rs +++ b/tests/number.rs @@ -50,11 +50,6 @@ test!( ); 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!( - num_plus_div, - "a {\n color: 1 + 3/4;\n}\n", - "a {\n color: 1.75;\n}\n" -); test!( negative_near_zero_no_sign, "a {\n color: -0.000000000001;\n}\n",