diff --git a/rsass/src/css/call_args.rs b/rsass/src/css/call_args.rs index baf554f3..513cf9b8 100644 --- a/rsass/src/css/call_args.rs +++ b/rsass/src/css/call_args.rs @@ -47,6 +47,14 @@ impl CallArgs { } } + pub(crate) fn from_iter(positional: I) -> Self + where + T: Into, + I: IntoIterator, + { + Self::from_list(positional.into_iter().map(Into::into).collect()) + } + pub(crate) fn add_from_value_map( &mut self, map: OrderMap, diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index 1ffe69b1..0f4fb6cc 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -26,4 +26,4 @@ pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; pub(crate) use self::selectors::{ CssSelectorSet, LogicalSelector, SelectorCtx, }; -pub(crate) use self::util::{is_calc_name, is_function_name, is_not}; +pub(crate) use self::util::{is_calc_name, is_function_name, is_not, IsNot}; diff --git a/rsass/src/css/value.rs b/rsass/src/css/value.rs index 758bf1bc..29ebe63f 100644 --- a/rsass/src/css/value.rs +++ b/rsass/src/css/value.rs @@ -57,6 +57,13 @@ impl Value { Value::Numeric(Numeric::scalar(v), true) } + pub(crate) fn call, I>(name: &str, args: I) -> Value + where + I: IntoIterator, + { + Value::Call(name.into(), CallArgs::from_iter(args)) + } + /// Check that the value is valid in css. pub fn valid_css(self) -> Result { match self { diff --git a/rsass/src/sass/functions/call_error.rs b/rsass/src/sass/functions/call_error.rs index ae4dc906..7ab259a7 100644 --- a/rsass/src/sass/functions/call_error.rs +++ b/rsass/src/sass/functions/call_error.rs @@ -28,11 +28,11 @@ impl CallError { } /// The values were expected to be compatible, but wasn't. - pub fn incompatible_values(a: &Value, b: &Value) -> Self { + pub fn incompatible_values>(a: T, b: T) -> Self { Self::msg(format!( "{} and {} are incompatible.", - a.format(Format::introspect()), - b.format(Format::introspect()), + a.into().format(Format::introspect()), + b.into().format(Format::introspect()), )) } @@ -46,7 +46,11 @@ impl CallError { Error::BadCall(format!("{err:?}"), call_pos.clone(), None) } CallError::BadArgument(name, problem) => Error::BadCall( - format!("${name}: {problem}"), + if name.as_ref().is_empty() { + problem + } else { + format!("${name}: {problem}") + }, call_pos.clone(), None, ), diff --git a/rsass/src/sass/functions/color/hsl.rs b/rsass/src/sass/functions/color/hsl.rs index 028b1163..dc16a84d 100644 --- a/rsass/src/sass/functions/color/hsl.rs +++ b/rsass/src/sass/functions/color/hsl.rs @@ -1,8 +1,8 @@ use super::channels::Channels; use super::{ - check_alpha, check_amount, check_hue, check_pct, eval_inner, is_not, - is_special, make_call, relative_color, CallError, CheckedArg, - FunctionMap, ResolvedArgs, + check_alpha, check_amount, check_hue, eval_inner, is_not, is_special, + relative_color, CallError, CheckedArg, FunctionMap, NumOrSpecial, + ResolvedArgs, }; use crate::css::{CallArgs, Value}; use crate::output::Format; @@ -34,7 +34,7 @@ pub fn register(f: &mut Scope) { Ok(Hsla::new(col.hue(), zero(), col.lum(), col.alpha(), false) .into()) } - v @ Value::Numeric(..) => Ok(make_call("grayscale", vec![v])), + v @ Value::Numeric(..) => Ok(Value::call("grayscale", [v])), v => Err(is_not(&v, "a color")).named(name!(color)), }); } @@ -77,9 +77,10 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { Ok(Hsla::new(col.hue(), zero(), col.lum(), col.alpha(), false) .into()) } - v @ Value::Numeric(..) => Ok(make_call("grayscale", vec![v])), - v if is_special(&v) => Ok(make_call("grayscale", vec![v])), - v => Err(is_not(&v, "a color")).named(name!(color)), + v => NumOrSpecial::try_from(v) + .map_err(|e| is_not(e.value(), "a color")) + .named(name!(color)) + .map(|v| Value::call("grayscale", [v])), }); def_va!(f, saturate(kwargs), |s| { let a1 = FormalArgs::new(vec![one_arg!(color), one_arg!(amount)]); @@ -95,15 +96,9 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { .into()) } Err(CallError::Args(ArgsError::Missing(_), _)) => { - let s = eval_inner(&name!(saturate), &a2, s, args)?; - let sat = s.get_map(name!(amount), |v| { - if is_special(&v) { - Ok(v) - } else { - check_pct(v.clone()).map(|_| v) // validate only - } - })?; - Ok(make_call("saturate", vec![sat])) + eval_inner(&name!(saturate), &a2, s, args)? + .get::(name!(amount)) + .map(|sat| Value::call("saturate", [sat])) } Err(ae) => Err(ae), } @@ -152,7 +147,7 @@ fn do_hsla(fn_name: &Name, s: &ResolvedArgs) -> Result { hsla_from_values(fn_name, h, s, l, a) } Channels::Special(channels) => { - Ok(make_call(fn_name.as_ref(), vec![channels])) + Ok(Value::call(fn_name.as_ref(), [channels])) } }), Err(err @ CallError::Args(ArgsError::Missing(_), _)) => Err(err), @@ -179,7 +174,10 @@ fn hsla_from_values( a: Value, ) -> Result { if is_special(&h) || is_special(&s) || is_special(&l) || is_special(&a) { - Ok(make_call(fn_name.as_ref(), vec![h, s, l, a])) + Ok(Value::call( + fn_name.as_ref(), + [h, s, l, a].into_iter().filter(|v| v != &Value::Null), + )) } else if l == Value::Null { Err(CallError::msg("Missing argument $lightness.")) } else { @@ -195,7 +193,7 @@ fn hsla_from_values( } pub fn percentage(v: Rational) -> Value { - Numeric::new(v * 100, Unit::Percent).into() + Numeric::percentage(v).into() } /// Gets a percentage as a fraction 0 .. 1. diff --git a/rsass/src/sass/functions/color/mod.rs b/rsass/src/sass/functions/color/mod.rs index 59050100..28854e99 100644 --- a/rsass/src/sass/functions/color/mod.rs +++ b/rsass/src/sass/functions/color/mod.rs @@ -1,5 +1,5 @@ use super::{ - expected_to, is_not, is_special, CallError, CheckedArg, FunctionMap, + expected_to, is_not, CallError, CheckedArg, FunctionMap, NumOrSpecial, ResolvedArgs, }; use crate::css::{CallArgs, CssString, Value}; @@ -114,7 +114,7 @@ fn check_hue(v: Value) -> Result { fn check_pct(v: Value) -> Result { let val = Numeric::try_from(v)?; match val.as_unit_def(Unit::Percent) { - Some(v) => Ok(v), + Some(v) => Ok(v / 100), None => { dep_warn!( "Passing a number without unit % ({}) is deprecated.\ @@ -123,7 +123,7 @@ fn check_pct(v: Value) -> Result { val.format(Format::introspect()), val.unit ); - Ok(val.value) + Ok(val.value / 100) } } } @@ -142,22 +142,22 @@ fn check_expl_pct(v: Value) -> Result { fn check_pct_range(v: Value) -> Result { let val = check_pct(v)?; - if val < zero() || val > 100.into() { + if val < zero() || val > one() { Err(expected_to( - Numeric::new(val, Unit::Percent), + Numeric::percentage(val), "be within 0% and 100%", )) } else { - Ok(val.as_ratio()? / 100) + Ok(val.as_ratio()?) } } fn check_amount(v: Value) -> Result { let val = check_pct(v)?; - if val < zero() || val > 100.into() { - Err(expected_to(Value::scalar(val), "be within 0 and 100")) + if val < zero() || val > one() { + Err(expected_to(Value::scalar(val * 100), "be within 0 and 100")) } else { - Ok(val.as_ratio()? / 100) + Ok(val.as_ratio()?) } } @@ -191,15 +191,6 @@ fn num2chan(v: &Numeric) -> Result { } } -fn make_call(name: &str, args: Vec) -> Value { - Value::Call( - name.into(), - CallArgs::from_list( - args.into_iter().filter(|v| v != &Value::Null).collect(), - ), - ) -} - pub(crate) fn eval_inner( name: &Name, decl: &FormalArgs, @@ -243,3 +234,10 @@ fn relative_color(args: &CallArgs) -> bool { } args.get_single().map(inner).unwrap_or(false) } + +fn is_special(v: &Value) -> bool { + matches!( + NumOrSpecial::try_from(v.clone()), + Ok(NumOrSpecial::Special(_)) + ) +} diff --git a/rsass/src/sass/functions/color/other.rs b/rsass/src/sass/functions/color/other.rs index e8d97dfe..1fb0ed63 100644 --- a/rsass/src/sass/functions/color/other.rs +++ b/rsass/src/sass/functions/color/other.rs @@ -1,6 +1,6 @@ use super::{ check_alpha_pm, check_alpha_range, check_channel_pm, check_channel_range, - check_expl_pct, check_hue, expected_to, make_call, CallError, CheckedArg, + check_expl_pct, check_hue, expected_to, CallError, CheckedArg, FunctionMap, Name, }; use crate::css::{CallArgs, Value}; @@ -136,12 +136,12 @@ pub fn register(f: &mut Scope) { def!(f, opacity(color), |s| match s.get(name!(color))? { Value::Color(ref col, _) => Ok(Value::scalar(col.get_alpha())), - v => Ok(make_call("opacity", vec![v])), + v => Ok(Value::call("opacity", [v])), }); def!(f, alpha(color), |s| { let v = s.get(name!(color))?; if ok_as_filterarg(&v) { - Ok(make_call("alpha", vec![v])) + Ok(Value::call("alpha", [v])) } else { let color = Color::try_from(v).named(name!(color))?; Ok(Value::scalar(color.get_alpha())) diff --git a/rsass/src/sass/functions/color/rgb.rs b/rsass/src/sass/functions/color/rgb.rs index 02f0e0a5..5d6e6c31 100644 --- a/rsass/src/sass/functions/color/rgb.rs +++ b/rsass/src/sass/functions/color/rgb.rs @@ -1,8 +1,8 @@ use super::channels::Channels; use super::{ check_alpha, check_channel, check_pct_range, eval_inner, is_not, - is_special, make_call, relative_color, CallError, CheckedArg, - FunctionMap, ResolvedArgs, + is_special, relative_color, CallError, CheckedArg, FunctionMap, + NumOrSpecial, ResolvedArgs, }; use crate::css::{CallArgs, Value}; use crate::sass::{ArgsError, FormalArgs, Name}; @@ -66,7 +66,7 @@ pub fn register(f: &mut Scope) { if w == one() { match col { v @ Value::Numeric(..) => { - Ok(make_call("invert", vec![v])) + Ok(Value::call("invert", [v])) } v => Err(is_not(&v, "a color")).named(name!(color)), } @@ -99,15 +99,10 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { col => { let w = s.get_map(name!(weight), check_pct_range)?; if w == one() { - match col { - v @ Value::Numeric(..) => { - Ok(make_call("invert", vec![v])) - } - v if is_special(&v) => { - Ok(make_call("invert", vec![v])) - } - v => Err(is_not(&v, "a color")).named(name!(color)), - } + NumOrSpecial::try_from(col) + .map_err(|e| is_not(e.value(), "a color")) + .named(name!(color)) + .map(|v| Value::call("invert", [v])) } else { Err(CallError::msg("Only one argument may be passed to the plain-CSS invert() function.")) } @@ -161,7 +156,7 @@ fn do_rgba(fn_name: &Name, s: &ResolvedArgs) -> Result { rgba_from_values(fn_name, h, s, l, a) } Channels::Special(channels) => { - Ok(make_call(fn_name.as_ref(), vec![channels])) + Ok(Value::call(fn_name.as_ref(), [channels])) } }), Err(err @ CallError::Args(ArgsError::Missing(_), _)) => Err(err), @@ -172,9 +167,9 @@ fn do_rgba(fn_name: &Name, s: &ResolvedArgs) -> Result { if is_special(&c) || is_special(&a) { if let Ok(c) = Color::try_from(c.clone()) { let c = c.to_rgba(); - Ok(make_call( + Ok(Value::call( fn_name.as_ref(), - vec![ + [ Value::scalar(c.red()), Value::scalar(c.green()), Value::scalar(c.blue()), @@ -182,7 +177,7 @@ fn do_rgba(fn_name: &Name, s: &ResolvedArgs) -> Result { ], )) } else { - Ok(make_call(fn_name.as_ref(), vec![c, a])) + Ok(Value::call(fn_name.as_ref(), [c, a])) } } else { let mut c = Color::try_from(c).named(name!(color))?; @@ -216,7 +211,10 @@ fn rgba_from_values( a: Value, ) -> Result { if is_special(&r) || is_special(&g) || is_special(&b) || is_special(&a) { - Ok(make_call(fn_name.as_ref(), vec![r, g, b, a])) + Ok(Value::call( + fn_name.as_ref(), + [r, g, b, a].into_iter().filter(|v| v != &Value::Null), + )) } else { Ok(Rgba::new( check_channel(r).named(name!(red))?, diff --git a/rsass/src/sass/functions/list.rs b/rsass/src/sass/functions/list.rs index d8b0fd01..c14390c0 100644 --- a/rsass/src/sass/functions/list.rs +++ b/rsass/src/sass/functions/list.rs @@ -88,7 +88,7 @@ pub fn create_module() -> Scope { Ok(sep.into()) }); def_va!(f, slash(elements), |s| { - let list = s.get_map(name!(elements), check::va_list)?; + let list = s.get_va(name!(elements))?; if list.len() < 2 { return Err(CallError::msg( "At least two elements are required.", @@ -140,7 +140,7 @@ pub fn create_module() -> Scope { }); def_va!(f, zip(lists), |s| { let lists = s - .get_map(name!(lists), check::va_list)? + .get_va(name!(lists))? .into_iter() .map(Value::iter_items) .collect::>(); diff --git a/rsass/src/sass/functions/math.rs b/rsass/src/sass/functions/math.rs index 48d71a77..562960da 100644 --- a/rsass/src/sass/functions/math.rs +++ b/rsass/src/sass/functions/math.rs @@ -1,17 +1,18 @@ +use super::num_or_special::NumOrSpecial; use super::{ - check, css_dim, expected_to, is_not, is_special, CallError, CheckedArg, - FunctionMap, ResolvedArgs, Scope, + check, expected_to, unnamed, CallError, CheckedArg, FunctionMap, + ResolvedArgs, Scope, }; use crate::css::{BinOp, CallArgs, CssString, InvalidCss, Value}; use crate::output::Format; -use crate::parser::input_span; -use crate::sass::functions::{css_fn_arg, known_dim}; use crate::sass::Name; -use crate::value::{Number, Numeric, Quotes, Rational, Unit, UnitSet}; +use crate::value::{ + CssDimensionSet, Numeric, Operator, Quotes, Rational, Unit, +}; use std::cmp::Ordering; use std::f64::consts::{E, PI}; -use std::ops::Rem; +mod css; mod distance; mod round; @@ -35,20 +36,44 @@ pub fn create_module() -> Scope { // - - - Boundig Functions - - - def!(f, ceil(number), |s| { let val: Numeric = s.get(name!(number))?; - Ok(number(val.value.ceil(), val.unit)) + Ok(Numeric::new(val.value.ceil(), val.unit).into()) + }); + def!(f, clamp(min, number, max), |s| { + let min_v = s.get::(name!(min))?; + let check_numeric_compat_unit = + |v: Value| -> Result { + let v = Numeric::try_from(v)?; + if (v.is_no_unit() != min_v.is_no_unit()) + || !v.unit.is_compatible(&min_v.unit) + { + return Err(diff_units_msg(&v, &min_v, name!(min))); + } + Ok(v) + }; + let mut num = s.get_map(name!(number), check_numeric_compat_unit)?; + let max_v = s.get_map(name!(max), check_numeric_compat_unit)?; + + if num >= max_v { + num = max_v; + } + if num <= min_v { + num = min_v; + } + Ok(Value::Numeric(num, true)) }); - def!(f, clamp(min, number, max), clamp_fn); def!(f, floor(number), |s| { let val: Numeric = s.get(name!(number))?; - Ok(number(val.value.floor(), val.unit)) + Ok(Numeric::new(val.value.floor(), val.unit).into()) }); def_va!(f, max(numbers), |s| { - let numbers = s.get_map(name!(numbers), check::va_list)?; - find_extreme(&numbers, Ordering::Greater) + let numbers = unnamed(s.get_va(name!(numbers)))?; + find_extreme(&numbers, Ordering::Greater)? + .map_or_else(|| Ok(Value::call("max", numbers)), |v| Ok(v.into())) }); def_va!(f, min(numbers), |s| { - let numbers = s.get_map(name!(numbers), check::va_list)?; - find_extreme(&numbers, Ordering::Less) + let numbers = unnamed(s.get_va(name!(numbers)))?; + find_extreme(&numbers, Ordering::Less)? + .map_or_else(|| Ok(Value::call("min", numbers)), |v| Ok(v.into())) }); def!(f, round(number), round::sass_round); @@ -57,109 +82,41 @@ pub fn create_module() -> Scope { // - - - Exponential Functions - - - def!(f, exp(number), |s| { - s.get_map(name!(number), radians) - .map(|v| Value::scalar(v.exp())) - .or_else(|e| fallback1(s, "exp", name!(number)).map_err(|_| e)) + Ok(Value::scalar(get_unitless(s, "number")?.exp())) }); def!(f, log(number, base = b"null"), |s| { - let num = get_unitless(s, "number"); - let base = s.get_opt_map(name!(base), check::unitless); - match (num, base) { - (Ok(num), Ok(base)) => { - let base = base.map_or(E, Into::into); - Ok(Value::scalar(num.log(base))) - } - (Err(e), _) | (_, Err(e)) => { - let num = s.get_map(name!(number), expression); - let base = s.get_map(name!(base), expression); - if let (Ok(num), Ok(base)) = (num, base) { - Ok(Value::Call( - "log".into(), - CallArgs::from_list(vec![num, base]), - )) - } else { - Err(e) - } - } - } + let num = get_unitless(s, "number")?; + let base = s.get_opt_map(name!(base), check::unitless)?; + Ok(Value::scalar(num.log(base.map_or(E, Into::into)))) }); def!(f, pow(base, exponent), |s| { - let base = get_unitless(s, "base"); - let exponent = get_unitless(s, "exponent"); - match (base, exponent) { - (Ok(base), Ok(exponent)) => { - Ok(Value::scalar(base.powf(exponent))) - } - (Err(e), _) | (_, Err(e)) => { - fallback2(s, "pow", name!(base), name!(exponent)) - .map_err(|_| e) - } - } + let base = get_unitless(s, "base")?; + let exponent = get_unitless(s, "exponent")?; + Ok(Value::scalar(base.powf(exponent))) }); def!(f, sqrt(number), |s| { - get_unitless(s, "number") - .map(|v| Value::scalar(v.sqrt())) - .or_else(|e| fallback1(s, "sqrt", name!(number)).map_err(|_| e)) + Ok(Value::scalar(get_unitless(s, "number")?.sqrt())) }); // - - - Trigonometric Functions - - - def!(f, cos(number), |s| { - s.get_map(name!(number), radians) - .map(|v| Value::scalar(v.cos())) - .or_else(|e| fallback1(s, "cos", name!(number)).map_err(|_| e)) + Ok(Value::scalar(s.get_map(name!(number), radians)?.cos())) }); def!(f, sin(number), |s| { - s.get_map(name!(number), radians) - .map(|v| Value::scalar(v.sin())) - .or_else(|e| fallback1(s, "sin", name!(number)).map_err(|_| e)) + Ok(Value::scalar(s.get_map(name!(number), radians)?.sin())) }); def!(f, tan(number), |s| { - s.get_map(name!(number), radians) - .map(|v| Value::scalar(v.tan())) - .or_else(|e| fallback1(s, "tan", name!(number)).map_err(|_| e)) + Ok(Value::scalar(s.get_map(name!(number), radians)?.tan())) }); def!(f, acos(number), |s| { - get_unitless(s, "number") - .map(|v| deg_value(v.acos())) - .or_else(|e| { - s.get_map(name!(number), expression) - .map(|expr| { - Value::Call( - "acos".into(), - CallArgs::from_single(expr), - ) - }) - .map_err(|_| e) - }) + Ok(deg_value(get_unitless(s, "number")?.acos())) }); def!(f, asin(number), |s| { - get_unitless(s, "number") - .map(|v| deg_value(v.asin())) - .or_else(|e| { - s.get_map(name!(number), expression) - .map(|expr| { - Value::Call( - "asin".into(), - CallArgs::from_single(expr), - ) - }) - .map_err(|_| e) - }) + Ok(deg_value(get_unitless(s, "number")?.asin())) }); def!(f, atan(number), |s| { - get_unitless(s, "number") - .map(|v| deg_value(v.atan())) - .or_else(|e| { - s.get_map(name!(number), expression) - .map(|expr| { - Value::Call( - "atan".into(), - CallArgs::from_single(expr), - ) - }) - .map_err(|_| e) - }) + Ok(deg_value(get_unitless(s, "number")?.atan())) }); def!(f, atan2(y, x), |s| { let y: Numeric = s.get(name!(y))?; @@ -189,7 +146,7 @@ pub fn create_module() -> Scope { // - - - Other Functions - - - def!(f, percentage(number), |s| { let val = s.get_map(name!(number), check::unitless)?; - Ok(Numeric::new(val * 100, Unit::Percent).into()) + Ok(Numeric::percentage(val).into()) }); def!(f, random(limit = b"null"), |s| { match s.get_opt_map(name!(limit), check::positive_int)? { @@ -241,18 +198,6 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { (name!(floor), name!(floor)), (name!(max), name!(max)), (name!(min), name!(min)), - // - - - Exponential functions - - - - (name!(exp), name!(exp)), - (name!(log), name!(log)), - (name!(pow), name!(pow)), - (name!(sqrt), name!(sqrt)), - // - - - Trigonometric functions - - - - (name!(asin), name!(asin)), - (name!(acos), name!(acos)), - (name!(atan), name!(atan)), - (name!(sin), name!(sin)), - (name!(cos), name!(cos)), - (name!(tan), name!(tan)), // - - - Unit Functions - - - (name!(comparable), name!(compatible)), (name!(unitless), name!(is_unitless)), @@ -265,271 +210,148 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { } // Functions behave somewhat differently in the global scope vs in the math module. + css::global(global); distance::global(global); - def!(global, clamp(min, number = b"null", max = b"null"), |s| { - clamp_fn(s).or_else(|_| { - let mut args = vec![s.get::(name!(min))?]; - if let Some(b) = s.get_opt(name!(number))? { - args.push(b); - } - if let Some(c) = s.get_opt(name!(max))? { - args.push(c); - } - if let Some((a, rest)) = args.split_first() { - if let Some(adim) = css_dim(a) { - for b in rest { - if let Some(bdim) = css_dim(b) { - if adim != bdim { - return Err(CallError::incompatible_values( - a, b, - )); - } - } - } - } - } - Ok(Value::Call("clamp".into(), CallArgs::from_list(args))) - }) - }); - def!(global, atan2(y, x), |s| { - fn real_atan2(s: &ResolvedArgs) -> Result { - let y: Numeric = s.get(name!(y))?; - if y.unit.is_percent() { - return Err(String::from("No percentage here")) - .named(name!(y)); - } - let x = s.get_map(name!(x), |v| { - let v = Numeric::try_from(v)?; - v.as_unitset(&y.unit) - .ok_or_else(|| diff_units_msg(&v, &y, name!(y))) - })?; - Ok(deg_value(f64::from(y.value).atan2(f64::from(x)))) - } - real_atan2(s).or_else(|e| { - fallback2a(s, "atan2", name!(y), name!(x)).map_err(|_| e) - }) - }); - def!(global, rem(y, x), |s| { - fn real_rem(s: &ResolvedArgs) -> Result { - let y: Numeric = s.get(name!(y))?; - let x = s.get_map(name!(x), |v| { - let v = Numeric::try_from(v)?; - v.as_unitset(&y.unit) - .ok_or_else(|| diff_units_msg(&v, &y, name!(y))) - })?; - Ok(number(f64::from(y.value).rem(f64::from(x)), y.unit)) - } - real_rem(s).or_else(|e| { - fallback2a(s, "rem", name!(y), name!(x)).map_err(|_| e) - }) - }); - def!(global, mod(y, x), |s| { - fn real_mod(s: &ResolvedArgs) -> Result { - let y: Numeric = s.get(name!(y))?; - let x: Numeric = s.get(name!(x))?; - let x = x.as_unitset(&y.unit) - .ok_or_else(|| CallError::msg(diff_units_msg2(&y, &x)))?; - let unit = y.unit; - let y = f64::from(y.value); - let m = f64::from(x); - let mut y = y.rem(m); - if (y * m.signum()).is_sign_negative() { - if m.is_finite() { - y += m; - y = y.rem(m); - } else { - y = f64::NAN; - } - } - let y = y.abs() * m.signum(); - Ok(number(y, unit)) - } - real_mod(s).or_else(|e| { - fallback2a(s, "mod", name!(y), name!(x)).map_err(|_| e) - }) - }); def_va!(global, round(kwargs), round::css_round); - def!(global, sign(v), |s| { - fn real_sign(s: &ResolvedArgs) -> Result { - let v: Numeric = s.get(name!(v))?; - Ok(number(v.value.signum(), v.unit)) - } - real_sign(s) - .or_else(|e| fallback1(s, "sign", name!(v)).map_err(|_| e)) - }); } -fn radians(v: Value) -> Result { - let v = Numeric::try_from(v)?; +fn num2radians(v: Numeric) -> Result { v.as_unit_def(Unit::Rad).map(Into::into).ok_or_else(|| { expected_to(v, "have an angle unit (deg, grad, rad, turn)") }) } -fn fallback1( - s: &ResolvedArgs, - name: &str, - a1: Name, -) -> Result { - s.get_map(a1, expression) - .map(|expr| Value::Call(name.into(), CallArgs::from_single(expr))) +fn radians(v: Value) -> Result { + num2radians(v.try_into()?) } -fn fallback2( - s: &ResolvedArgs, - name: &str, - a1: Name, - a2: Name, -) -> Result { - let (a1, a2) = match (get_expr(s, a1), get_expr(s, a2)) { - (Ok(a1), Ok(a2)) => (a1, a2), - (Ok(a1), Err(a2 @ Value::Numeric(..))) => (a1, a2), - (Err(a1 @ Value::Numeric(..)), Ok(a2)) => (a1, a2), - _ => return Err(()), - }; - Ok(Value::Call(name.into(), CallArgs::from_list(vec![a1, a2]))) -} -fn fallback2a( - s: &ResolvedArgs, - name: &str, - a1: Name, - a2: Name, -) -> Result { - let (a1, a2) = match (get_expr_a(s, a1), get_expr_a(s, a2)) { - (Ok(a1), Ok(a2)) => { - let dim1 = css_dim(&a1); - let dim2 = css_dim(&a2); - if (dim1 == dim2) || dim1.is_none() || dim2.is_none() { - (a1, a2) - } else { - return Err(()); - } - } - _ => return Err(()), - }; - Ok(Value::Call(name.into(), CallArgs::from_list(vec![a1, a2]))) +fn get_unitless(s: &ResolvedArgs, name: &str) -> Result { + s.get_map(name.into(), check::unitless).map(Into::into) } -fn get_expr(s: &ResolvedArgs, name: Name) -> Result { - match s.get(name) { - Ok(v @ (Value::BinOp(_) | Value::Call(..))) => Ok(v), - Ok(v) => Err(v), - Err(_) => Err(Value::Null), - } -} -fn get_expr_a(s: &ResolvedArgs, name: Name) -> Result { - match s.get(name) { - Ok(v @ (Value::BinOp(_) | Value::Call(..) | Value::Numeric(..))) => { - Ok(v) - } - Ok(v) => Err(v), - Err(_) => Err(Value::Null), - } +/// convert f64 in radians (used by rust) to numeric Value in degrees +/// (used by sass). +fn deg_value(rad: f64) -> Value { + Numeric::new(rad.to_degrees(), Unit::Deg).into() } -fn expression(v: Value) -> Result { - match v { - v @ (Value::BinOp(_) | Value::Call(..)) => Ok(v), - _ => Err("Expected expression".into()), +fn find_extreme( + v: &[NumOrSpecial], + pref: Ordering, +) -> Result, ExtremeError> { + let mut v = v.iter(); + let found = v.next().ok_or(ExtremeError::OneRequired)?; + let NumOrSpecial::Num(ref found) = found else { + return Ok(None); + }; + let mut found = found; + for v in v { + let NumOrSpecial::Num(v) = v else { + return Ok(None); + }; + if let Some(o) = cmp2(found, v) { + found = if o == pref { found } else { v }; + } else if may_cmp_css(found, v) { + return Ok(None); + } else { + return Err(ExtremeError::Incompatible(found.clone(), v.clone())); + } } + Ok(Some(found.clone())) } -fn get_unitless(s: &ResolvedArgs, name: &str) -> Result { - s.get_map(name.into(), check::unitless).map(Into::into) +fn cmp2(a: &Numeric, b: &Numeric) -> Option { + a.partial_cmp(b).or_else(|| { + if a.is_no_unit() || b.is_no_unit() { + a.value.partial_cmp(&b.value) + } else { + None + } + }) } -fn number(v: impl Into, unit: impl Into) -> Value { - Numeric::new(v.into(), unit).into() +fn may_cmp_css(a: &Numeric, b: &Numeric) -> bool { + let a_dim = a.unit.css_dimension(); + let b_dim = b.unit.css_dimension(); + a_dim.is_empty() || b_dim.is_empty() || a_dim == b_dim } -/// convert f64 in radians (used by rust) to numeric Value in degrees -/// (used by sass). -fn deg_value(rad: f64) -> Value { - number(rad.to_degrees(), Unit::Deg) +#[derive(Debug)] +enum ExtremeError { + OneRequired, + Incompatible(Numeric, Numeric), } -fn find_extreme(v: &[Value], pref: Ordering) -> Result { - let as_call = || { - Value::Call( - if pref == Ordering::Greater { - "max" - } else { - "min" - } - .into(), - CallArgs::from_list(v.to_vec()), - ) - }; - if v.iter().any(is_special) { - return Ok(as_call()); - } - match find_extreme_inner(v, pref) { - Ok(Some(v)) => Ok(v.into()), - Ok(None) => { - Err(CallError::msg("At least one argument must be passed.")) - } - Err(ExtremeError::NonNumeric(v)) => { - if let Value::Literal(s) = &v { - if s.quotes().is_none() - && crate::parser::value::number( - input_span(s.value()).borrow(), - ) - .is_ok() - { - return Ok(as_call()); - } - } - if v.type_name() == "unknown" { - Ok(as_call()) - } else { - Err(CallError::msg(is_not(&v, "a number"))) +impl From for CallError { + fn from(value: ExtremeError) -> Self { + match value { + ExtremeError::OneRequired => { + CallError::msg("At least one argument must be passed.") } - } - Err(ExtremeError::Incompatible(a, b)) => { - let a_dim = a.unit.css_dimension(); - let b_dim = b.unit.css_dimension(); - if a_dim.is_empty() || b_dim.is_empty() || a_dim == b_dim { - Ok(as_call()) - } else { - Err(CallError::msg(InvalidCss::Incompat(a, b))) + ExtremeError::Incompatible(a, b) => { + CallError::msg(InvalidCss::Incompat(a, b)) } } - Err(_) => Ok(as_call()), } } -fn find_extreme_inner( - v: &[Value], - pref: Ordering, -) -> Result, ExtremeError> { - if let Some((first, rest)) = v.split_first() { - let va = Numeric::try_from(first.clone()) - .map_err(|_| ExtremeError::NonNumeric(first.clone()))?; - if let Some(vb) = find_extreme_inner(rest, pref)? { - if let Some(o) = va.partial_cmp(&vb) { - Ok(Some(if o == pref { va } else { vb })) - } else if va.is_no_unit() || vb.is_no_unit() { - if let Some(o) = va.value.partial_cmp(&vb.value) { - Ok(Some(if o == pref { va } else { vb })) - } else { - Err(ExtremeError::Incomparable(va, vb)) +fn css_fn_arg(v: Value) -> Result { + match v { + Value::Literal(s) if s.quotes() == Quotes::None => Ok(s.into()), + Value::BinOp(op) => { + let a = css_fn_arg(op.a().clone())?; + let b = css_fn_arg(op.b().clone())?; + let op = op.op(); + if let (Some(adim), Some(bdim)) = (css_dim(&a), css_dim(&b)) { + if (op == Operator::Plus || op == Operator::Minus) + && adim != bdim + { + return Err(CallError::incompatible_values(a, b)); } - } else { - Err(ExtremeError::Incompatible(va, vb)) } - } else { - Ok(Some(va)) + Ok(BinOp::new(a, true, op, true, b).into()) } - } else { - Ok(None) + Value::Paren(v) => match v.as_ref() { + l @ Value::Paren(_) => Ok(l.clone()), + l @ Value::BinOp(..) => Ok(l.clone()), + _ => Ok(Value::Paren(v)), + }, + list @ Value::List(..) => { + // FIXME: Check if list seems good, as above? + Ok(list) + } + v => NumOrSpecial::in_calc(v) + .and_then(|ns| { + ns.try_map(|num| { + if num.unit.valid_in_css() { + Ok(num) + } else { + Err(format!( + "Number {} isn't compatible with CSS calculations.", + Value::from(num).introspect() + )) + } + }) + }) + .map_err(CallError::msg) + .map(Value::from), } } -#[derive(Debug)] -enum ExtremeError { - NonNumeric(Value), - Incompatible(Numeric, Numeric), - Incomparable(Numeric, Numeric), +// Note: None here is for unknown, e.g. the dimension of something that is not a number. +fn css_dim(v: &Value) -> Option { + match v { + Value::Numeric(num, _) => known_dim(num), + _ => None, + } +} +fn known_dim(v: &Numeric) -> Option { + let u = &v.unit; + if u.is_known() && !u.is_percent() { + Some(u.css_dimension()) + } else { + None + } } fn intrand(lim: i64) -> i64 { @@ -553,34 +375,3 @@ fn diff_units_msg( } ) } - -fn diff_units_msg2(one: &Numeric, other: &Numeric) -> String { - format!( - "{} and {} are incompatible.", - one.format(Format::introspect()), - other.format(Format::introspect()), - ) -} - -pub(crate) fn clamp_fn(s: &ResolvedArgs) -> Result { - let min_v = s.get::(name!(min))?; - let check_numeric_compat_unit = |v: Value| -> Result { - let v = Numeric::try_from(v)?; - if (v.is_no_unit() != min_v.is_no_unit()) - || !v.unit.is_compatible(&min_v.unit) - { - return Err(diff_units_msg(&v, &min_v, name!(min))); - } - Ok(v) - }; - let mut num = s.get_map(name!(number), check_numeric_compat_unit)?; - let max_v = s.get_map(name!(max), check_numeric_compat_unit)?; - - if num >= max_v { - num = max_v; - } - if num <= min_v { - num = min_v; - } - Ok(Value::Numeric(num, true)) -} diff --git a/rsass/src/sass/functions/math/css.rs b/rsass/src/sass/functions/math/css.rs new file mode 100644 index 00000000..f320a25e --- /dev/null +++ b/rsass/src/sass/functions/math/css.rs @@ -0,0 +1,374 @@ +use super::{ + css_fn_arg, deg_value, diff_units_msg, expected_to, known_dim, + num2radians, CallError, CheckedArg, FunctionMap, NumOrSpecial, + ResolvedArgs, +}; +use crate::css::{self, BinOp, Value}; +use crate::sass::{ArgsError, Name}; +use crate::value::{Numeric, Quotes, UnitSet}; +use std::f64::consts::E; +use std::ops::Rem; + +type Result = std::result::Result; + +pub fn global(global: &mut FunctionMap) { + def_va!(global, exp(number), |s| { + Ok(match one_number_or_special(s, unitless)? { + NumOrSpecial::Num(v) => Value::scalar(v.exp()), + NumOrSpecial::Special(v) => Value::call("exp", [v]), + }) + }); + def_va!(global, log(number), |s| { + let mut args = args_iter(s)?; + let num = required_arg(args.next())??; + let base = args + .next() + .transpose()? + .unwrap_or(NumOrSpecial::Num(Numeric::scalar(E))); + check_excess_args(2, args.count())?; + match (num, base) { + (NumOrSpecial::Num(n), NumOrSpecial::Num(b)) => { + let n = unitless(n).map_err(CallError::msg)?; + let b = unitless(b).map_err(CallError::msg)?; + Ok(Value::scalar(n.log(b))) + } + (n, b) => Ok(Value::call("log", [n, b])), + } + }); + def_va!(global, pow(number), |s| { + match two_num_or_special(s)? { + (NumOrSpecial::Num(base), NumOrSpecial::Num(exponent)) => { + Ok(Value::scalar( + unitless(base) + .and_then(|b| unitless(exponent).map(|e| (b.powf(e)))) + .map_err(CallError::msg)?, + )) + } + (base, exp) => Ok(Value::call("pow", [base, exp])), + } + }); + def_va!(global, sqrt(number), |s| { + Ok(match one_number_or_special(s, unitless)? { + NumOrSpecial::Num(v) => Value::scalar(v.sqrt()), + NumOrSpecial::Special(v) => Value::call("sqrt", [v]), + }) + }); + fn num_rad(v: Numeric) -> Result { + num2radians(v).named(name!(number)) + } + def_va!(global, sin(number), |s| { + Ok(match one_number_or_special(s, Ok)? { + NumOrSpecial::Num(v) => Value::scalar(num_rad(v)?.sin()), + NumOrSpecial::Special(v) => Value::call("sin", [v]), + }) + }); + def_va!(global, cos(number), |s| { + Ok(match one_number_or_special(s, Ok)? { + NumOrSpecial::Num(v) => Value::scalar(num_rad(v)?.cos()), + NumOrSpecial::Special(v) => Value::call("cos", [v]), + }) + }); + def_va!(global, tan(number), |s| { + Ok(match one_number_or_special(s, Ok)? { + NumOrSpecial::Num(v) => Value::scalar(num_rad(v)?.tan()), + NumOrSpecial::Special(v) => Value::call("tan", [v]), + }) + }); + def_va!(global, asin(number), |s| { + Ok(match one_number_or_special(s, unitless)? { + NumOrSpecial::Num(v) => deg_value(v.asin()), + NumOrSpecial::Special(v) => Value::call("asin", [v]), + }) + }); + def_va!(global, acos(number), |s| { + Ok(match one_number_or_special(s, unitless)? { + NumOrSpecial::Num(v) => deg_value(v.acos()), + NumOrSpecial::Special(v) => Value::call("acos", [v]), + }) + }); + def_va!(global, atan(number), |s| { + Ok(match one_number_or_special(s, unitless)? { + NumOrSpecial::Num(v) => deg_value(v.atan()), + NumOrSpecial::Special(v) => Value::call("atan", [v]), + }) + }); + def_va!(global, atan2(number), |s| { + match two_num_or_special(s)? { + (NumOrSpecial::Num(x), NumOrSpecial::Num(y)) => { + if x.unit.is_percent() || y.unit.is_percent() { + Ok(Value::call("atan2", [x, y])) + } else if let Some(x) = x.as_unitset(&y.unit) { + Ok(deg_value(f64::from(x).atan2(f64::from(y.value)))) + } else if !x.unit.is_known() || !y.unit.is_known() { + Ok(Value::call("atan2", [x, y])) + } else { + Err(CallError::msg(diff_units_msg(&x, &y, name!(y)))) + } + } + (x, y) => Ok(Value::call("atan2", [x, y])), + } + }); + def_va!(global, sign(number), |s| { + Ok(match one_number_or_special(s, Ok)? { + NumOrSpecial::Num(v) => { + Numeric::new(v.value.signum(), v.unit).into() + } + NumOrSpecial::Special(v) => Value::call("sign", [v]), + }) + }); + def_va!(global, mod(number), |s| { + match two_num_or_special(s)? { + (NumOrSpecial::Num(y), NumOrSpecial::Num(x)) => { + fn allow(u: &UnitSet) -> bool { + u.is_percent() || !u.is_known() + } + if let Some(x) = x.as_unitset(&y.unit) { + let unit = y.unit; + let m = f64::from(x); + let mut y = f64::from(y.value).rem(m); + if (y * m.signum()).is_sign_negative() { + if m.is_finite() { + y += m; + y = y.rem(m); + } else { + y = f64::NAN; + } + } + Ok(Numeric::new(y.abs() * m.signum(), unit).into()) + } else if allow(&y.unit) || allow(&x.unit) { + Ok(Value::call("mod", [y, x])) + } else { + Err(CallError::incompatible_values(y, x)) + } + }, + (x, y) => Ok(Value::call("mod", [x, y])), + } + }); + def_va!(global, rem(number), |s| { + match two_num_or_special(s)? { + (NumOrSpecial::Num(y), NumOrSpecial::Num(x)) => { + fn allow(u: &UnitSet) -> bool { + u.is_percent() || !u.is_known() + } + if let Some(x) = x.as_unitset(&y.unit) { + let r = f64::from(y.value).rem(f64::from(x)); + Ok(Numeric::new(r, y.unit).into()) + } else if allow(&y.unit) || allow(&x.unit) { + Ok(Value::call("rem", [y, x])) + } else { + Err(CallError::incompatible_values(y, x)) + } + } + (x, y) => Ok(Value::call("rem", [x, y])), + } + }); + def_va!(global, clamp(number), |s| { + let mut args = args_iter(s)?; + let min = required_arg(args.next())??; + let number = args.next().transpose()?; + let max = args.next().transpose()?; + check_excess_args(3, args.count())?; + match (min, number, max) { + (NumOrSpecial::Num(_min), None, _) => Err(missing_arg_nm(3, 1)), + (NumOrSpecial::Num(_), Some(NumOrSpecial::Num(_)), None) => { + Err(missing_arg_nm(3, 2)) + } + ( + NumOrSpecial::Num(min), + Some(NumOrSpecial::Num(mut number)), + Some(NumOrSpecial::Num(max)), + ) => { + let min_d = known_dim(&min); + let num_d = known_dim(&number); + let max_d = known_dim(&max); + if min_d.is_some() && num_d.is_some() && min_d != num_d { + Err(CallError::incompatible_values(min, number)) + } else if min_d.is_some() && max_d.is_some() && min_d != max_d + { + Err(CallError::incompatible_values(min, max)) + } else if min_d == num_d && num_d == max_d { + if number >= max { + number = max; + } + if number <= min { + number = min; + } + Ok(number.into()) + } else { + Ok(Value::call("clamp", [min, number, max])) + } + } + (min, Some(number), Some(max)) => { + Ok(Value::call("clamp", [min, number, max])) + } + (min, Some(number), None) => { + Ok(Value::call("clamp", [min, number])) + } + (arg, None, _) => Ok(Value::call("clamp", [arg])), + } + }); + def_va!(global, calc(number), |s| { + fn pre_calc(v: &Value) -> bool { + match v { + Value::Numeric(..) => true, + Value::Call(ref name, _) => css::is_calc_name(name), + Value::Literal(s) => s.is_css_calc(), + Value::Paren(s) => pre_calc(s), + _ => false, + } + } + fn do_eval(v: Value) -> Result { + match v { + Value::Literal(s) if s.quotes() == Quotes::None => { + let s = s.value(); + if s.eq_ignore_ascii_case("e") { + Ok(Value::scalar(std::f64::consts::E)) + } else if s.eq_ignore_ascii_case("pi") { + Ok(Value::scalar(std::f64::consts::PI)) + } else if s.eq_ignore_ascii_case("infinity") { + Ok(Value::scalar(f64::INFINITY)) + } else if s.eq_ignore_ascii_case("-infinity") { + Ok(Value::scalar(-f64::INFINITY)) + } else if s.eq_ignore_ascii_case("NaN") { + Ok(Value::scalar(f64::NAN)) + } else if let Some(arg) = s + .strip_prefix("calc(") + .and_then(|s| s.strip_suffix(')')) + { + Ok(Value::Paren(Box::new(arg.into()))) + } else { + Ok(s.into()) + } + } + Value::Call(name, args) => { + if name == "calc" { + let arg = args.get_single().unwrap(); + match do_eval(arg.clone())? { + Value::Literal(s) if s.is_name() => Ok(s.into()), + arg => Ok(Value::Paren(Box::new(arg))), + } + } else { + Ok(Value::Call(name, args)) + } + } + Value::BinOp(op) => { + let a = do_eval(op.a().clone())?; + let b = do_eval(op.b().clone())?; + let op = op.op(); + if let Ok(Some(result)) = op.eval(a.clone(), b.clone()) { + return Ok(result); + } + Ok(BinOp::new(a, true, op, true, b).into()) + } + Value::Paren(v) => match v.as_ref() { + l @ Value::Paren(_) => Ok(l.clone()), + l @ Value::BinOp(..) => Ok(l.clone()), + _ => Ok(Value::Paren(v)), + }, + Value::List(v, sep, bra) => { + fn seems_numeric(v: &Value) -> bool { + match v { + Value::Numeric(..) => true, + Value::Call(n, _) if n == "calc" => true, + Value::Literal(s) => { + // FIXME: This requires more ops, and + // are still a silly way to do it. + s.quotes() == Quotes::None + && !s.value().starts_with('-') + && !s.value().starts_with('+') + && !s.value().ends_with('-') + && !s.value().ends_with('+') + } + _ => false, + } + } + if v.windows(2).all(|p| p.iter().all(seems_numeric)) { + Err(CallError::msg("Missing math operator.")) + } else { + Ok(Value::List(v, sep, bra)) + } + } + v => NumOrSpecial::in_calc(v) + .map_err(CallError::msg) + .map(Value::from), + } + } + let mut args = raw_args_iter(s)?; + let v = required_arg(args.next())?; + check_excess_args(1, args.count())?; + + let v = do_eval(v)?; + if pre_calc(&v) { + match v { + Value::Paren(v) => Ok(*v), + v => Ok(v), + } + } else { + Ok(Value::call("calc", [css_fn_arg(v)?])) + } + }); +} + +fn one_number_or_special( + s: &ResolvedArgs, + f: F, +) -> Result> +where + F: Fn(Numeric) -> Result, +{ + let mut args = args_iter(s)?; + let arg = required_arg(args.next())??; + check_excess_args(1, args.count())?; + arg.try_map(f).map_err(CallError::msg) +} + +fn raw_args_iter(s: &ResolvedArgs) -> Result> { + Ok(s.get_va::(name!(number))?.into_iter()) +} + +fn args_iter( + s: &ResolvedArgs, +) -> Result>> { + Ok(raw_args_iter(s)?.map(|v| v.try_into().map_err(CallError::msg))) +} + +fn two_num_or_special( + s: &ResolvedArgs, +) -> Result<(NumOrSpecial, NumOrSpecial)> { + let mut args = args_iter(s)?; + let arg1 = required_arg(args.next())??; + let arg2 = args.next().ok_or_else(|| missing_arg_nm(2, 1))??; + check_excess_args(2, args.count())?; + Ok((arg1, arg2)) +} + +pub(super) fn required_arg(arg: Option) -> Result { + arg.ok_or_else(|| bad_argument("Missing argument.")) +} + +fn missing_arg_nm(n: usize, m: usize) -> CallError { + bad_argument(format!( + "{n} arguments required, but only {m} {} passed.", + if m == 1 { "was" } else { "were" }, + )) +} + +pub(super) fn check_excess_args(prev: usize, remain: usize) -> Result<()> { + if remain > 0 { + Err(bad_argument(ArgsError::TooMany(prev, prev + remain))) + } else { + Ok(()) + } +} + +fn bad_argument(msg: S) -> CallError { + CallError::BadArgument(Name::from_static(""), msg.to_string()) +} + +fn unitless(val: Numeric) -> Result { + if val.is_no_unit() { + Ok(val.value.into()) + } else { + Err(expected_to(val, "have no units")) + } +} diff --git a/rsass/src/sass/functions/math/distance.rs b/rsass/src/sass/functions/math/distance.rs index 9d8689d3..0485e53f 100644 --- a/rsass/src/sass/functions/math/distance.rs +++ b/rsass/src/sass/functions/math/distance.rs @@ -1,15 +1,15 @@ -use super::super::{check, CallError, FunctionMap, ResolvedArgs}; -use super::{diff_units_msg, number}; +use super::super::color::eval_inner; +use super::super::{CallError, CheckedArg, FunctionMap, ResolvedArgs}; +use super::css::{check_excess_args, required_arg}; +use super::{css_fn_arg, diff_units_msg, NumOrSpecial}; use crate::css::{is_not, CallArgs, Value}; -use crate::sass::functions::color::eval_inner; -use crate::sass::functions::CheckedArg; use crate::value::Numeric; -use crate::{sass::functions::css_fn_arg, Scope}; +use crate::Scope; pub fn in_module(module: &mut Scope) { def!(module, abs(number), sass_abs); def_va!(module, hypot(number), |s| { - hypot(&s.get_map(name!(number), check::va_list)?) + hypot(&s.get_va(name!(number))?) }); } @@ -20,28 +20,18 @@ pub fn global(global: &mut FunctionMap) { let fa = FormalArgs::new(vec![one_arg!(number)]); return sass_abs(&eval_inner(&name!(abs), &fa, s, args)?); } - if args.positional.len() > 1 { - return Err(CallError::msg(format!( - "Only 1 argument allowed, but {} were passed.", - args.positional.len(), - ))); + let mut args = args.positional.into_iter(); + let arg = required_arg(args.next())?; + check_excess_args(1, args.count())?; + match NumOrSpecial::try_from(arg).named(name!(number))? { + NumOrSpecial::Num(v) => { + Ok(Numeric::new(v.value.abs(), v.unit).into()) + } + NumOrSpecial::Special(arg) => Ok(Value::call("abs", [arg])), } - let arg = args - .positional - .into_iter() - .next() - .ok_or_else(|| CallError::msg("Missing argument."))?; - Numeric::try_from(arg) - .map(|v| number(v.value.abs(), v.unit)) - .or_else(|e| match css_fn_arg(e.value().clone()) { - Ok(v) => { - Ok(Value::Call("abs".into(), CallArgs::from_single(v))) - } - Err(_) => Err(e.to_string()).named(name!(number)), - }) }); def_va!(global, hypot(number), |s| { - let args = s.get_map(name!(number), check::va_list)?; + let args = s.get_va(name!(number))?; match hypot(&args) { Ok(value) => Ok(value), Err(_) => { @@ -51,8 +41,8 @@ pub fn global(global: &mut FunctionMap) { let args = args .into_iter() .map(css_fn_arg) - .collect::>()?; - Ok(Value::Call("hypot".into(), CallArgs::from_list(args))) + .collect::, _>>()?; + Ok(Value::call("hypot", args)) } } } @@ -61,13 +51,13 @@ pub fn global(global: &mut FunctionMap) { fn sass_abs(s: &ResolvedArgs) -> Result { let v: Numeric = s.get(name!(number))?; - Ok(number(v.value.abs(), v.unit)) + Ok(Numeric::new(v.value.abs(), v.unit).into()) } fn hypot(args: &[Value]) -> Result { match args { [Value::Numeric(v, _)] => { - Ok(number(v.value.clone().abs(), v.unit.clone())) + Ok(Numeric::new(v.value.clone().abs(), v.unit.clone()).into()) } [v] => Err(is_not(v, "a number")).named(name!(number)), v => { @@ -88,7 +78,7 @@ fn hypot(args: &[Value]) -> Result { .named(format!("numbers[{}]", i + 2).into())?; sum += f64::from(scaled).powi(2); } - Ok(number(sum.sqrt(), unit)) + Ok(Numeric::new(sum.sqrt(), unit).into()) } else { Err(CallError::msg("At least one argument must be passed.")) } diff --git a/rsass/src/sass/functions/math/round.rs b/rsass/src/sass/functions/math/round.rs index 009fd18c..7e74c370 100644 --- a/rsass/src/sass/functions/math/round.rs +++ b/rsass/src/sass/functions/math/round.rs @@ -1,15 +1,15 @@ -use super::{ - diff_units_msg2, known_dim, number, CallArgs, CallError, ResolvedArgs, -}; +use super::super::CheckedArg as _; +use super::css::required_arg; +use super::{known_dim, CallArgs, CallError, NumOrSpecial, ResolvedArgs}; use crate::css::{CssString, Value}; use crate::output::Format; use crate::sass::functions::color::eval_inner; -use crate::sass::FormalArgs; +use crate::sass::{ArgsError, FormalArgs}; use crate::value::{Number, Numeric, Quotes}; pub fn sass_round(s: &ResolvedArgs) -> Result { let val: Numeric = s.get(name!(number))?; - Ok(number(val.value.round(), val.unit)) + Ok(Numeric::new(val.value.round(), val.unit).into()) } pub fn css_round(s: &ResolvedArgs) -> Result { @@ -19,69 +19,76 @@ pub fn css_round(s: &ResolvedArgs) -> Result { return sass_round(&eval_inner(&name!(round), &fa, s, args)?); } if args.positional.len() > 3 { - return Err(CallError::msg(format!( - "Only 3 arguments allowed, but {} were passed.", + return Err(CallError::msg(ArgsError::TooMany( + 3, args.positional.len(), ))); } let mut args = args.positional.into_iter(); - let (strategy, number, step) = - match (args.next(), args.next(), args.next()) { - (Some(v0), Some(v1), v2) => { - match (Strategy::try_from(&v0), v1, v2) { - (Ok(_), num, None) if num.type_name() == "number" => { - return Err(CallError::msg( - "If strategy is not null, step is required.", - )) - } - (Ok(s), num, None) => (Some(s), num, None), - (Ok(s), num, Some(step)) => (Some(s), num, Some(step)), - (Err(()), num, Some(step)) => { - return if v0.type_name() == "variable" { - fallback(Some(v0), num, Some(step)) - } else { - Err(CallError::msg(format!( - "{} must be either nearest, up, down or to-zero.", - v0.format(Format::introspect()), - ))) - }; - } - (Err(()), step, None) => (None, v0, Some(step)), - } - } - (Some(v), None, _) => (None, v, None), - (None, ..) => { - return Err(CallError::msg("Missing argument.")); - } - }; - real_round(strategy.unwrap_or_default(), &number, step.as_ref()) - .unwrap_or_else(|| fallback(strategy.map(Value::from), number, step)) -} - -fn real_round( - strategy: Strategy, - num: &Value, - step: Option<&Value>, -) -> Option> { - let val = Numeric::try_from(num.clone()).ok()?; - let step = match step.cloned().map(Numeric::try_from) { - Some(Ok(v)) => { - if let Some(step) = v.as_unitset(&val.unit) { - Some(step) - } else if known_dim(&val) - .and_then(|dim1| known_dim(&v).map(|dim2| dim1 == dim2)) + let (strategy, number, step) = match ( + Strategy::try_from(required_arg(args.next())?), + args.next(), + args.next(), + ) { + (Ok(_), Some(num), None) if num.type_name() == "number" => { + return Err(CallError::msg( + "If strategy is not null, step is required.", + )) + } + (Ok(s), Some(num), None) => (Some(s), num, None), + (Ok(s), Some(num), Some(step)) => (Some(s), num, Some(step)), + (Ok(_), None, _) => return Err(CallError::msg("xyzzy")), + (Err(v0), Some(num), Some(step)) => { + return if v0.type_name() == "variable" { + Ok(Value::call("round", [v0, num, step])) + } else { + Err(CallError::msg(format!( + "{} must be either nearest, up, down or to-zero.", + v0.format(Format::introspect()), + ))) + }; + } + (Err(v0), Some(step), None) => (None, v0, Some(step)), + (Err(arg @ Value::BinOp(_)), None, _) => { + return Err(CallError::msg(format!( + "Single argument {} expected to be simplifiable.", + arg.format(Format::introspect()), + ))); + } + (Err(v), None, _) => (None, v, None), + }; + let number = NumOrSpecial::try_from(number).named(name!(number))?; + let step = step + .map(NumOrSpecial::in_calc) + .transpose() + .map_err(CallError::msg)?; + match (number, step) { + (NumOrSpecial::Num(num), Some(NumOrSpecial::Num(step))) => { + if let Some(step) = step.as_unitset(&num.unit) { + Ok(real_round(strategy.unwrap_or_default(), num, Some(step))) + } else if known_dim(&num) + .and_then(|dim1| known_dim(&step).map(|dim2| dim1 == dim2)) .unwrap_or(true) { - return None; + Ok(fallback(strategy, num.into(), Some(step.into()))) } else { - return Some(Err(CallError::msg(diff_units_msg2(&val, &v)))); + Err(CallError::incompatible_values(num, step)) } } - Some(Err(_)) => return None, - None => None, - }; + (NumOrSpecial::Num(num), None) => { + Ok(real_round(strategy.unwrap_or_default(), num, None)) + } + (number, step) => Ok(fallback(strategy, number, step)), + } +} + +fn real_round( + strategy: Strategy, + val: Numeric, + step: Option, +) -> Value { let (val, unit) = (val.value, val.unit); - Some(Ok(number( + Value::from(Numeric::new( if let Some(step) = step { if step.is_finite() { if (strategy == Strategy::ToZero) && step.is_negative() { @@ -104,30 +111,22 @@ fn real_round( strategy.apply(val) }, unit, - ))) + )) } fn fallback( - strategy: Option, - number: Value, - step: Option, -) -> Result { - let mut args = Vec::new(); - let is_single = strategy.is_none() && step.is_none(); - if let Some(v) = strategy { - args.push(v); - } - if is_single && matches!(&number, Value::BinOp(_)) { - return Err(CallError::msg(format!( - "Single argument {} expected to be simplifiable.", - number.format(Format::introspect()), - ))); - } - args.push(super::css_fn_arg(number)?); - if let Some(step) = step { - args.push(super::css_fn_arg(step)?); + strategy: Option, + number: NumOrSpecial, + step: Option, +) -> Value { + match (strategy.map(Value::from), number, step) { + (Some(a1), a2, Some(a3)) => { + Value::call("round", [a1, a2.into(), a3.into()]) + } + (Some(a1), a2, None) => Value::call("round", [a1, a2.into()]), + (None, a1, Some(a2)) => Value::call("round", [a1, a2]), + (None, a1, None) => Value::call("round", [a1]), } - Ok(Value::Call("round".into(), CallArgs::from_list(args))) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -155,31 +154,30 @@ impl Default for Strategy { } } -impl TryFrom<&CssString> for Strategy { - type Error = (); +impl TryFrom for Strategy { + type Error = CssString; - fn try_from(value: &CssString) -> Result { + fn try_from(value: CssString) -> Result { if value.quotes() != Quotes::None { - return Err(()); + return Err(value); } match value.value() { "nearest" => Ok(Self::Nearest), "up" => Ok(Self::Up), "to-zero" | "to_zero" => Ok(Self::ToZero), "down" => Ok(Self::Down), - _ => Err(()), + _ => Err(value), } } } -impl TryFrom<&Value> for Strategy { - type Error = (); +impl TryFrom for Strategy { + type Error = Value; - fn try_from(value: &Value) -> Result { - if let Value::Literal(s) = value { - s.try_into() - } else { - Err(()) + fn try_from(value: Value) -> Result { + match value { + Value::Literal(s) => s.try_into().map_err(Value::Literal), + other => Err(other), } } } diff --git a/rsass/src/sass/functions/meta.rs b/rsass/src/sass/functions/meta.rs index 1e23ed5d..93b3b577 100644 --- a/rsass/src/sass/functions/meta.rs +++ b/rsass/src/sass/functions/meta.rs @@ -1,4 +1,4 @@ -use super::{is_not, looks_like_call, CallError, FunctionMap, ResolvedArgs}; +use super::{is_not, CallError, FunctionMap, ResolvedArgs}; use crate::css::{is_calc_name, CallArgs, CssString, Value}; use crate::sass::{Call, Function, MixinDecl, Name}; use crate::value::Quotes; @@ -23,22 +23,12 @@ pub fn create_module() -> Scope { Ok(Value::Call(name, args)) } } - Value::Literal(s) if looks_like_call(&s) => { - let s = s.value(); - let i = s.find('(').unwrap(); - Ok(s[i + 1..s.len() - 1].into()) - } v => Err(is_not(&v, "a calculation")), }) }); def!(f, calc_name(calc), |s| { let name = s.get_map(name!(calc), |v| match v { Value::Call(name, _) => Ok(name), - Value::Literal(s) if looks_like_call(&s) => { - let s = s.value(); - let i = s.find('(').unwrap(); - Ok(s[..i].to_string()) - } v => Err(is_not(&v, "a calculation")), })?; Ok(CssString::new(name, Quotes::Double).into()) diff --git a/rsass/src/sass/functions/mod.rs b/rsass/src/sass/functions/mod.rs index 66eaf491..ff3f8403 100644 --- a/rsass/src/sass/functions/mod.rs +++ b/rsass/src/sass/functions/mod.rs @@ -1,8 +1,6 @@ use super::{Call, Closure, FormalArgs, Name}; -use crate::css::{self, is_not, BinOp, CallArgs, CssString, Value}; +use crate::css::{is_not, Value}; use crate::input::SourcePos; -use crate::output::Format; -use crate::value::{CssDimensionSet, Numeric, Operator, Quotes}; use crate::{Scope, ScopeRef}; use lazy_static::lazy_static; use std::collections::BTreeMap; @@ -18,11 +16,13 @@ mod list; mod map; mod math; mod meta; +mod num_or_special; mod resolvedargs; mod selector; mod string; pub use call_error::CallError; +use num_or_special::NumOrSpecial; pub use resolvedargs::ResolvedArgs; type BuiltinFn = @@ -210,110 +210,6 @@ lazy_static! { Ok(s.get(name!(if_false))?) } }); - def!(f, calc(expr), |s| { - fn pre_calc(v: &Value) -> bool { - match v { - Value::Numeric(..) => true, - Value::Call(ref name, _) => css::is_calc_name(name), - Value::Literal(s) => s.is_css_calc(), - Value::Paren(s) => pre_calc(s), - _ => false, - } - } - fn do_eval(v: Value) -> Result { - match v { - Value::Literal(s) if s.quotes() == Quotes::None => { - let s = s.value(); - if s.eq_ignore_ascii_case("e") { - Ok(Value::scalar(std::f64::consts::E)) - } else if s.eq_ignore_ascii_case("pi") { - Ok(Value::scalar(std::f64::consts::PI)) - } else if s.eq_ignore_ascii_case("infinity") { - Ok(Value::scalar(f64::INFINITY)) - } else if s.eq_ignore_ascii_case("-infinity") { - Ok(Value::scalar(-f64::INFINITY)) - } else if s.eq_ignore_ascii_case("NaN") { - Ok(Value::scalar(f64::NAN)) - } else if let Some(arg) = s - .strip_prefix("calc(") - .and_then(|s| s.strip_suffix(')')) - { - Ok(Value::Paren(Box::new(arg.into()))) - } else { - Ok(s.into()) - } - } - Value::Call(name, args) => { - if name == "calc" { - let arg = args.get_single().unwrap(); - match do_eval(arg.clone())? { - Value::Literal(s) if s.is_name() => { - Ok(s.into()) - } - arg => Ok(Value::Paren(Box::new(arg))), - } - } else { - Ok(Value::Call(name, args)) - } - } - Value::BinOp(op) => { - let a = do_eval(op.a().clone())?; - let b = do_eval(op.b().clone())?; - let op = op.op(); - if let Ok(Some(result)) = - op.eval(a.clone(), b.clone()) - { - return Ok(result); - } - Ok(BinOp::new(a, true, op, true, b).into()) - } - num @ Value::Numeric(..) => Ok(num), - Value::Paren(v) => match v.as_ref() { - l @ Value::Paren(_) => Ok(l.clone()), - l @ Value::BinOp(..) => Ok(l.clone()), - _ => Ok(Value::Paren(v)), - }, - Value::List(v, sep, bra) => { - fn seems_numeric(v: &Value) -> bool { - match v { - Value::Numeric(..) => true, - Value::Call(n, _) if n == "calc" => true, - Value::Literal(s) => { - // FIXME: This requires more ops, and - // are still a silly way to do it. - s.quotes() == Quotes::None - && !s.value().starts_with('-') - && !s.value().starts_with('+') - && !s.value().ends_with('-') - && !s.value().ends_with('+') - } - _ => false, - } - } - if v.windows(2).all(|p| p.iter().all(seems_numeric)) { - Err(CallError::msg("Missing math operator.")) - } else { - Ok(Value::List(v, sep, bra)) - } - } - v => Err(CallError::msg(format!( - "Value {} can't be used in a calculation.", - v.format(Format::introspect()) - ))), - } - } - let v = s.get(name!(expr))?; - let v = do_eval(v)?; - if pre_calc(&v) { - match v { - Value::Paren(v) => Ok(*v), - v => Ok(v), - } - } else { - let arg = css_fn_arg(v)?; - Ok(Value::Call("calc".into(), CallArgs::from_single(arg))) - } - }); color::expose(MODULES.get("sass:color").unwrap(), &mut f); list::expose(MODULES.get("sass:list").unwrap(), &mut f); map::expose(MODULES.get("sass:map").unwrap(), &mut f); @@ -325,66 +221,6 @@ lazy_static! { }; } -fn css_fn_arg(v: Value) -> Result { - match v { - Value::Literal(s) if s.quotes() == Quotes::None => Ok(s.into()), - Value::Call(name, args) => Ok(Value::Call(name, args)), - Value::BinOp(op) => { - let a = css_fn_arg(op.a().clone())?; - let b = css_fn_arg(op.b().clone())?; - let op = op.op(); - if let (Some(adim), Some(bdim)) = (css_dim(&a), css_dim(&b)) { - if (op == Operator::Plus || op == Operator::Minus) - && adim != bdim - { - return Err(CallError::incompatible_values(&a, &b)); - } - } - Ok(BinOp::new(a, true, op, true, b).into()) - } - Value::Numeric(num, c) => { - if num.unit.valid_in_css() { - Ok(Value::Numeric(num, c)) - } else { - Err(CallError::msg(format!( - "Number {} isn't compatible with CSS calculations.", - Value::Numeric(num, c).introspect() - ))) - } - } - Value::Paren(v) => match v.as_ref() { - l @ Value::Paren(_) => Ok(l.clone()), - l @ Value::BinOp(..) => Ok(l.clone()), - _ => Ok(Value::Paren(v)), - }, - list @ Value::List(..) => { - // FIXME: Check if list seems good, as above? - Ok(list) - } - v => Err(CallError::msg(format!( - "Value {} can't be used in a calculation.", - v.format(Format::introspect()) - ))), - } -} - -// Note: None here is for unknown, e.g. the dimension of something that is not a number. -fn css_dim(v: &Value) -> Option { - match v { - // TODO: Handle BinOp recursively (again) (or let in_calc return (Value, CssDimension)?) - Value::Numeric(num, _) => known_dim(num), - _ => None, - } -} -fn known_dim(v: &Numeric) -> Option { - let u = &v.unit; - if u.is_known() && !u.is_percent() { - Some(u.css_dimension()) - } else { - None - } -} - // argument helpers for the actual functions trait CheckedArg { @@ -400,21 +236,6 @@ fn expected_to>(value: T, cond: &str) -> String { format!("Expected {} to {}.", value.into().introspect(), cond) } -fn is_special(v: &Value) -> bool { - match v { - Value::Call(..) => true, - Value::Literal(s) if looks_like_call(s) => true, - Value::BinOp(..) => true, - _ => false, - } -} - -fn looks_like_call(s: &CssString) -> bool { - s.quotes().is_none() - && s.value().contains('(') - && s.value().ends_with(')') -} - /// Convert an error for a specific argument to not mentioning the argument. /// /// Ok results and other errors are returned unmodified. @@ -430,7 +251,7 @@ fn unnamed(result: Result) -> Result { mod check { use super::{expected_to, is_not}; use crate::css::Value; - use crate::value::{ListSeparator, Number, Numeric}; + use crate::value::{Number, Numeric}; pub fn int(v: Value) -> Result { Numeric::try_from(v)? @@ -461,17 +282,6 @@ mod check { .into_integer() .map_err(|v| is_not(&v, "an int")) } - - pub fn va_list(v: Value) -> Result, String> { - match v { - Value::ArgList(args) => { - args.check_no_named().map_err(|e| e.to_string())?; - Ok(args.positional) - } - Value::List(v, Some(ListSeparator::Comma), _) => Ok(v), - single => Ok(vec![single]), - } - } } #[test] diff --git a/rsass/src/sass/functions/num_or_special.rs b/rsass/src/sass/functions/num_or_special.rs new file mode 100644 index 00000000..cb05eeed --- /dev/null +++ b/rsass/src/sass/functions/num_or_special.rs @@ -0,0 +1,69 @@ +use crate::css::{CssString, IsNot, Value}; +use crate::output::Format; +use crate::parser::input_span; +use crate::value::Numeric; + +#[derive(Clone, Debug)] +pub(crate) enum NumOrSpecial { + Num(T), + Special(Value), +} + +impl NumOrSpecial { + // A `try_from` with alternative error message. + pub fn in_calc(value: Value) -> Result { + Self::try_from(value).map_err(|e| { + format!( + "Value {} can't be used in a calculation.", + e.value().format(Format::introspect()) + ) + }) + } + pub fn try_map(self, f: F) -> Result, E> + where + F: Fn(Numeric) -> Result, + { + match self { + NumOrSpecial::Num(n) => Ok(NumOrSpecial::Num(f(n)?)), + NumOrSpecial::Special(s) => Ok(NumOrSpecial::Special(s)), + } + } +} + +impl TryFrom for NumOrSpecial { + type Error = IsNot; + + fn try_from(value: Value) -> Result { + match value { + v @ (Value::Call(..) | Value::BinOp(..)) => { + Ok(NumOrSpecial::Special(v)) + } + Value::Literal(s) if like_call_or_num(&s) => { + Ok(NumOrSpecial::Special(Value::Literal(s))) + } + v => Ok(NumOrSpecial::Num(v.try_into()?)), + } + } +} + +fn like_call_or_num(s: &CssString) -> bool { + s.quotes().is_none() + && ((s.value().contains('(') && s.value().ends_with(')')) + || crate::parser::value::number(input_span(s.value()).borrow()) + .is_ok()) +} + +impl From for NumOrSpecial { + fn from(value: Numeric) -> Self { + NumOrSpecial::Num(value) + } +} + +impl From for Value { + fn from(value: NumOrSpecial) -> Self { + match value { + NumOrSpecial::Num(n) => n.into(), + NumOrSpecial::Special(v) => v, + } + } +} diff --git a/rsass/src/value/numeric.rs b/rsass/src/value/numeric.rs index 6a066f51..947fdc8e 100644 --- a/rsass/src/value/numeric.rs +++ b/rsass/src/value/numeric.rs @@ -37,6 +37,11 @@ impl Numeric { unit: UnitSet::scalar(), } } + /// Create a new numeric that is a percentage from a number where + /// 1 maps to 100%. + pub(crate) fn percentage(value: impl Into) -> Numeric { + Numeric::new(value.into() * 100, Unit::Percent) + } /// Convert this numeric value to a given unit, if possible. /// diff --git a/rsass/tests/misc/basic.rs b/rsass/tests/misc/basic.rs index 8aea9749..451cd1ce 100644 --- a/rsass/tests/misc/basic.rs +++ b/rsass/tests/misc/basic.rs @@ -430,24 +430,6 @@ fn rel() { ); } -#[test] -fn minmax_length_units() { - init_logger(); - assert_eq!( - rsass( - b"sel {\ - \n a: max(-.8em, -.2vw);\ - \n a: min(-.8em, -.2vw);\ - \n}\n" - ) - .unwrap(), - "sel {\ - \n a: max(-0.8em, -0.2vw);\ - \n a: min(-0.8em, -0.2vw);\ - \n}\n" - ); -} - /// If one argument to min or max is an unquoted string /// (e.g. interpolation) that looks like a css number, that should be /// ok for the css min or max function. diff --git a/rsass/tests/spec/values/calculation/acos.rs b/rsass/tests/spec/values/calculation/acos.rs index 63066d4b..31b70a24 100644 --- a/rsass/tests/spec/values/calculation/acos.rs +++ b/rsass/tests/spec/values/calculation/acos.rs @@ -50,7 +50,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: acos()}\n"), @@ -63,7 +62,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: acos(0, 0)}\n"), @@ -93,7 +91,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn complex() { assert_eq!( runner().err("a {b: acos(-7px / 4em)}\n"), @@ -106,7 +103,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn known() { assert_eq!( runner().err("a {b: acos(1px)}\n"), @@ -119,7 +115,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn unknown() { assert_eq!( runner().err("a {b: acos(1%)}\n"), diff --git a/rsass/tests/spec/values/calculation/asin.rs b/rsass/tests/spec/values/calculation/asin.rs index ce324a72..ecf6f6bf 100644 --- a/rsass/tests/spec/values/calculation/asin.rs +++ b/rsass/tests/spec/values/calculation/asin.rs @@ -50,7 +50,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: asin()}\n"), @@ -63,7 +62,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: asin(0, 0)}\n"), @@ -93,7 +91,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn complex() { assert_eq!( runner().err("a {b: asin(-7px / 4em)}\n"), @@ -106,7 +103,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn known() { assert_eq!( runner().err("a {b: asin(1px)}\n"), @@ -119,7 +115,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn unknown() { assert_eq!( runner().err("a {b: asin(1%)}\n"), diff --git a/rsass/tests/spec/values/calculation/atan.rs b/rsass/tests/spec/values/calculation/atan.rs index 1a331c12..1ce67b68 100644 --- a/rsass/tests/spec/values/calculation/atan.rs +++ b/rsass/tests/spec/values/calculation/atan.rs @@ -32,7 +32,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: atan()}\n"), @@ -45,7 +44,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: atan(0, 0)}\n"), @@ -75,7 +73,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn complex() { assert_eq!( runner().err("a {b: atan(-7px / 4em)}\n"), @@ -88,7 +85,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn known() { assert_eq!( runner().err("a {b: atan(1px)}\n"), @@ -101,7 +97,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn unknown() { assert_eq!( runner().err("a {b: atan(1%)}\n"), diff --git a/rsass/tests/spec/values/calculation/atan2.rs b/rsass/tests/spec/values/calculation/atan2.rs index bfd757e4..f732efd6 100644 --- a/rsass/tests/spec/values/calculation/atan2.rs +++ b/rsass/tests/spec/values/calculation/atan2.rs @@ -50,7 +50,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: atan2(0)}\n"), @@ -63,7 +62,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: atan2(0, 0, 0)}\n"), diff --git a/rsass/tests/spec/values/calculation/calc/error/syntax.rs b/rsass/tests/spec/values/calculation/calc/error/syntax.rs index 5f33ae27..cc3eac79 100644 --- a/rsass/tests/spec/values/calculation/calc/error/syntax.rs +++ b/rsass/tests/spec/values/calculation/calc/error/syntax.rs @@ -32,7 +32,6 @@ fn double_operator() { ); } #[test] -#[ignore] // wrong error fn empty() { assert_eq!( runner().err("a {b: calc()}\n"), @@ -106,7 +105,6 @@ fn leading_operator() { ); } #[test] -#[ignore] // wrong error fn multiple_args() { assert_eq!( runner().err("a {b: calc(1px, 2px)}\n"), diff --git a/rsass/tests/spec/values/calculation/clamp.rs b/rsass/tests/spec/values/calculation/clamp.rs index 45a59644..b1cb6552 100644 --- a/rsass/tests/spec/values/calculation/clamp.rs +++ b/rsass/tests/spec/values/calculation/clamp.rs @@ -85,7 +85,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn four_args() { assert_eq!( runner().err("a {b: clamp(1px, 2px, 3px, 4px)}\n"), @@ -111,7 +110,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn no_args() { assert_eq!( runner().err("a {b: clamp()}\n"), @@ -124,7 +122,6 @@ mod error { ); } #[test] - #[ignore] // missing error fn one_arg() { assert_eq!( runner().err("a {b: clamp(1px)}\n"), @@ -150,7 +147,6 @@ mod error { ); } #[test] - #[ignore] // missing error fn two_args() { assert_eq!( runner().err("a {b: clamp(1px, 2px)}\n"), @@ -205,6 +201,7 @@ mod preserved { use super::runner; #[test] + #[ignore] // unexepected error fn interpolation() { assert_eq!( runner().ok("a {b: clamp(#{c})}\n"), @@ -214,6 +211,7 @@ mod preserved { ); } #[test] + #[ignore] // unexepected error fn unquoted_string() { assert_eq!( runner().ok("$a: b;\ diff --git a/rsass/tests/spec/values/calculation/cos.rs b/rsass/tests/spec/values/calculation/cos.rs index 11047bb9..80e24909 100644 --- a/rsass/tests/spec/values/calculation/cos.rs +++ b/rsass/tests/spec/values/calculation/cos.rs @@ -59,7 +59,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: cos()}\n"), @@ -72,7 +71,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: cos(0, 0)}\n"), diff --git a/rsass/tests/spec/values/calculation/exp.rs b/rsass/tests/spec/values/calculation/exp.rs index 42c98eba..716ddc46 100644 --- a/rsass/tests/spec/values/calculation/exp.rs +++ b/rsass/tests/spec/values/calculation/exp.rs @@ -50,7 +50,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: exp()}\n"), @@ -63,7 +62,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: exp(0, 0)}\n"), @@ -93,7 +91,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn known() { assert_eq!( runner().err("a {b: exp(1px)}\n"), @@ -111,7 +108,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn unknown() { assert_eq!( runner().err("a {b: exp(1%)}\n"), diff --git a/rsass/tests/spec/values/calculation/log.rs b/rsass/tests/spec/values/calculation/log.rs index acb2424a..1126c651 100644 --- a/rsass/tests/spec/values/calculation/log.rs +++ b/rsass/tests/spec/values/calculation/log.rs @@ -126,7 +126,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: log()}\n"), @@ -139,7 +138,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: log(0, 0, 0)}\n"), @@ -156,7 +154,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn complex_and_unknown() { assert_eq!( runner().err("a {b: log(1px*2px, 10%)}\n"), @@ -169,7 +166,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn known() { assert_eq!( runner().err("a {b: log(3px)}\n"), @@ -182,7 +178,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn known_incompatible() { assert_eq!( runner().err("a {b: log(1deg, 1px)}\n"), @@ -195,7 +190,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn unitless_and_real() { assert_eq!( runner().err("a {b: log(1, 1px)}\n"), @@ -208,7 +202,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn unknown() { assert_eq!( runner().err("a {b: log(1%)}\n"), diff --git a/rsass/tests/spec/values/calculation/max.rs b/rsass/tests/spec/values/calculation/max.rs index 6af33129..5bb0a961 100644 --- a/rsass/tests/spec/values/calculation/max.rs +++ b/rsass/tests/spec/values/calculation/max.rs @@ -81,7 +81,7 @@ mod error { } } #[test] - #[ignore] // missing error + #[ignore] // wrong error fn potentially_incompatible_before_unitless() { assert_eq!( runner().err("a {b: max(1c, 2d, 3)}\n"), @@ -383,7 +383,6 @@ mod simplified { ); } #[test] - #[ignore] // unexepected error fn unitless_between_potentially_incompatible() { assert_eq!( runner().ok("a {b: max(1d, 2, 3e)}\n"), diff --git a/rsass/tests/spec/values/calculation/min.rs b/rsass/tests/spec/values/calculation/min.rs index b772b204..d7c5556c 100644 --- a/rsass/tests/spec/values/calculation/min.rs +++ b/rsass/tests/spec/values/calculation/min.rs @@ -383,7 +383,6 @@ mod simplified { ); } #[test] - #[ignore] // unexepected error fn unitless_between_potentially_incompatible() { assert_eq!( runner().ok("a {b: min(3d, 2, 1e)}\n"), diff --git a/rsass/tests/spec/values/calculation/pow.rs b/rsass/tests/spec/values/calculation/pow.rs index 67e1daaf..c2b3d409 100644 --- a/rsass/tests/spec/values/calculation/pow.rs +++ b/rsass/tests/spec/values/calculation/pow.rs @@ -90,7 +90,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: pow(3)}\n"), @@ -103,7 +102,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: pow(3, 2, 1)}\n"), @@ -120,7 +118,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn compatible() { assert_eq!( runner().err("a {b: pow(10px, 10px)}\n"), @@ -133,7 +130,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn real_and_unitless() { assert_eq!( runner().err("a {b: pow(10px, 10)}\n"), @@ -146,7 +142,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn unknown_and_unitless() { assert_eq!( runner().err("a {b: pow(10%, 10)}\n"), diff --git a/rsass/tests/spec/values/calculation/rem.rs b/rsass/tests/spec/values/calculation/rem.rs index 12b41713..46fdebb8 100644 --- a/rsass/tests/spec/values/calculation/rem.rs +++ b/rsass/tests/spec/values/calculation/rem.rs @@ -85,7 +85,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: rem(3)}\n"), @@ -98,7 +97,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: rem(3, 2, 1)}\n"), diff --git a/rsass/tests/spec/values/calculation/round/error.rs b/rsass/tests/spec/values/calculation/round/error.rs index 04826837..7a8974ec 100644 --- a/rsass/tests/spec/values/calculation/round/error.rs +++ b/rsass/tests/spec/values/calculation/round/error.rs @@ -45,7 +45,6 @@ mod one_argument { } } #[test] - #[ignore] // wrong error fn test_type() { assert_eq!( runner().err("a {b: round(\"0\")}\n"), diff --git a/rsass/tests/spec/values/calculation/sign.rs b/rsass/tests/spec/values/calculation/sign.rs index 7a515344..14e29b1f 100644 --- a/rsass/tests/spec/values/calculation/sign.rs +++ b/rsass/tests/spec/values/calculation/sign.rs @@ -50,7 +50,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: sign()}\n"), @@ -63,7 +62,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: sign(0, 0)}\n"), diff --git a/rsass/tests/spec/values/calculation/sin.rs b/rsass/tests/spec/values/calculation/sin.rs index c8639cdf..dc490edc 100644 --- a/rsass/tests/spec/values/calculation/sin.rs +++ b/rsass/tests/spec/values/calculation/sin.rs @@ -59,7 +59,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: sin()}\n"), @@ -72,7 +71,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: sin(0, 0)}\n"), diff --git a/rsass/tests/spec/values/calculation/sqrt.rs b/rsass/tests/spec/values/calculation/sqrt.rs index 52ae69d7..479c7a98 100644 --- a/rsass/tests/spec/values/calculation/sqrt.rs +++ b/rsass/tests/spec/values/calculation/sqrt.rs @@ -49,7 +49,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: sqrt()}\n"), @@ -63,7 +62,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: sqrt(3, 4)}\n"), @@ -93,7 +91,6 @@ mod error { use super::runner; #[test] - #[ignore] // wrong error fn real() { assert_eq!( runner().err("a {b: sqrt(16px)}\n"), @@ -106,7 +103,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn unknown() { assert_eq!( runner().err("a {b: sqrt(1%)}\n"), diff --git a/rsass/tests/spec/values/calculation/tan.rs b/rsass/tests/spec/values/calculation/tan.rs index ded9b45a..864b2543 100644 --- a/rsass/tests/spec/values/calculation/tan.rs +++ b/rsass/tests/spec/values/calculation/tan.rs @@ -59,7 +59,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: tan()}\n"), @@ -72,7 +71,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: tan(0, 0)}\n"), diff --git a/rsass/tests/spec/values/calculation/test_mod.rs b/rsass/tests/spec/values/calculation/test_mod.rs index 5eb542f1..3bc518f3 100644 --- a/rsass/tests/spec/values/calculation/test_mod.rs +++ b/rsass/tests/spec/values/calculation/test_mod.rs @@ -85,7 +85,6 @@ mod error { } } #[test] - #[ignore] // wrong error fn too_few_args() { assert_eq!( runner().err("a {b: mod(3)}\n"), @@ -98,7 +97,6 @@ mod error { ); } #[test] - #[ignore] // wrong error fn too_many_args() { assert_eq!( runner().err("a {b: mod(3, 2, 1)}\n"),