From b7be1705a25c83be77b606d276038d191d3cb162 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sat, 4 Apr 2020 18:17:04 -0400 Subject: [PATCH] args and default values are lazily evaluated --- src/args.rs | 95 +++++++++++++++++++----------------- src/atrule/function.rs | 27 +++++++--- src/atrule/mixin.rs | 32 ++++++++---- src/builtin/color/hsl.rs | 85 +++++++++++++++++--------------- src/builtin/color/opacity.rs | 32 ++++++------ src/builtin/color/other.rs | 86 +++++++++++++++++++------------- src/builtin/color/rgb.rs | 52 +++++++++++--------- src/builtin/list.rs | 66 ++++++++++++++----------- src/builtin/macros.rs | 32 ++++++------ src/builtin/map.rs | 32 ++++++------ src/builtin/math.rs | 26 +++++----- src/builtin/meta.rs | 58 +++++++++++----------- src/builtin/mod.rs | 8 ++- src/builtin/string.rs | 42 ++++++++-------- src/value/function.rs | 14 ++++-- src/value/parse.rs | 7 ++- tests/args.rs | 5 ++ 17 files changed, 395 insertions(+), 304 deletions(-) diff --git a/src/args.rs b/src/args.rs index 98715b4..6018aec 100644 --- a/src/args.rs +++ b/src/args.rs @@ -18,7 +18,7 @@ pub(crate) struct FuncArgs(pub Vec); #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) struct FuncArg { pub name: String, - pub default: Option, + pub default: Option>, pub is_variadic: bool, } @@ -29,7 +29,7 @@ impl FuncArgs { } #[derive(Debug, Clone)] -pub(crate) struct CallArgs(HashMap); +pub(crate) struct CallArgs(HashMap>); #[derive(Debug, Clone, Hash, Eq, PartialEq)] enum CallArg { @@ -58,25 +58,58 @@ impl CallArgs { CallArgs(HashMap::new()) } - #[allow(dead_code)] - pub fn get_named(&self, val: String) -> Option<&Value> { - self.0.get(&CallArg::Named(val)) + /// Get argument by name + /// + /// Removes the argument + pub fn get_named( + &mut self, + val: String, + scope: &Scope, + super_selector: &Selector, + ) -> Option> { + match self.0.remove(&CallArg::Named(val)) { + Some(v) => Some(Value::from_tokens( + &mut v.into_iter().peekable(), + scope, + super_selector, + )), + None => None, + } } - pub fn get_positional(&self, val: usize) -> Option<&Value> { - self.0.get(&CallArg::Positional(val)) + /// Get a positional argument by 0-indexed position + /// + /// Removes the argument + pub fn get_positional( + &mut self, + val: usize, + scope: &Scope, + super_selector: &Selector, + ) -> Option> { + match self.0.remove(&CallArg::Positional(val)) { + Some(v) => Some(Value::from_tokens( + &mut v.into_iter().peekable(), + scope, + super_selector, + )), + None => None, + } } - pub fn get_variadic(self) -> SassResult> { + pub fn get_variadic(self, scope: &Scope, super_selector: &Selector) -> SassResult> { let mut vals = Vec::new(); let mut args = self .0 .into_iter() .map(|(a, v)| Ok((a.position()?, v))) - .collect::>>()?; + .collect::)>>>()?; args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2)); for arg in args { - vals.push(arg.1); + vals.push(Value::from_tokens( + &mut arg.1.into_iter().peekable(), + scope, + super_selector, + )?); } Ok(vals) } @@ -97,14 +130,6 @@ impl CallArgs { pub fn is_empty(&self) -> bool { self.0.len() == 0 } - - pub fn remove_named(&mut self, s: String) -> Option { - self.0.remove(&CallArg::Named(s)) - } - - pub fn remove_positional(&mut self, s: usize) -> Option { - self.0.remove(&CallArg::Positional(s)) - } } pub(crate) fn eat_func_args>( @@ -137,11 +162,7 @@ pub(crate) fn eat_func_args>( toks.next(); args.push(FuncArg { name: name.replace('_', "-"), - default: Some(Value::from_tokens( - &mut default.into_iter().peekable(), - scope, - super_selector, - )?), + default: Some(default), is_variadic, }); break; @@ -149,11 +170,7 @@ pub(crate) fn eat_func_args>( ')' => { args.push(FuncArg { name: name.replace('_', "-"), - default: Some(Value::from_tokens( - &mut default.into_iter().peekable(), - scope, - super_selector, - )?), + default: Some(default), is_variadic, }); break; @@ -181,11 +198,7 @@ pub(crate) fn eat_func_args>( args.push(FuncArg { name: name.replace('_', "-"), - default: Some(Value::from_tokens( - &mut default.into_iter().peekable(), - scope, - super_selector, - )?), + default: Some(default), is_variadic, }); break; @@ -196,11 +209,7 @@ pub(crate) fn eat_func_args>( default: if default.is_empty() { None } else { - Some(Value::from_tokens( - &mut default.into_iter().peekable(), - scope, - super_selector, - )?) + Some(default) }, is_variadic, }); @@ -228,7 +237,7 @@ pub(crate) fn eat_call_args>( scope: &Scope, super_selector: &Selector, ) -> SassResult { - let mut args: HashMap = HashMap::new(); + let mut args: HashMap> = HashMap::new(); devour_whitespace_or_comment(toks)?; let mut name = String::new(); let mut val: Vec = Vec::new(); @@ -264,7 +273,7 @@ pub(crate) fn eat_call_args>( } else { CallArg::Named(name.replace('_', "-")) }, - Value::from_tokens(&mut val.into_iter().peekable(), scope, super_selector)?, + val, ); return Ok(CallArgs(args)); } @@ -291,11 +300,7 @@ pub(crate) fn eat_call_args>( } else { CallArg::Named(name.replace('_', "-")) }, - Value::from_tokens( - &mut val.clone().into_iter().peekable(), - scope, - super_selector, - )?, + val.clone(), ); val.clear(); devour_whitespace(toks); diff --git a/src/atrule/function.rs b/src/atrule/function.rs index 406b216..5789069 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -59,19 +59,30 @@ impl Function { Ok((name, Function::new(scope, args, body, pos))) } - pub fn args(mut self, mut args: CallArgs) -> SassResult { + pub fn args( + mut self, + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, + ) -> SassResult { for (idx, arg) in self.args.0.iter().enumerate() { if arg.is_variadic { - self.scope - .insert_var(&arg.name, Value::ArgList(args.get_variadic()?))?; + self.scope.insert_var( + &arg.name, + Value::ArgList(args.get_variadic(scope, super_selector)?), + )?; break; } - let val = match args.remove_positional(idx) { - Some(v) => v, - None => match args.remove_named(arg.name.clone()) { - Some(v) => v, + let val = match args.get_positional(idx, scope, super_selector) { + Some(v) => v?, + None => match args.get_named(arg.name.clone(), scope, super_selector) { + Some(v) => v?, None => match &arg.default { - Some(v) => v.clone(), + Some(v) => Value::from_tokens( + &mut v.iter().cloned().peekable(), + scope, + super_selector, + )?, None => return Err(format!("Missing argument ${}.", &arg.name).into()), }, }, diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index a80cc3b..0b7939d 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -60,19 +60,30 @@ impl Mixin { self } - pub fn args(mut self, mut args: CallArgs) -> SassResult { + pub fn args( + mut self, + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, + ) -> SassResult { for (idx, arg) in self.args.0.iter().enumerate() { if arg.is_variadic { - self.scope - .insert_var(&arg.name, Value::ArgList(args.get_variadic()?))?; + self.scope.insert_var( + &arg.name, + Value::ArgList(args.get_variadic(scope, super_selector)?), + )?; break; } - let val = match args.remove_positional(idx) { - Some(v) => v, - None => match args.remove_named(arg.name.clone()) { - Some(v) => v, + let val = match args.get_positional(idx, scope, super_selector) { + Some(v) => v?, + None => match args.get_named(arg.name.clone(), scope, super_selector) { + Some(v) => v?, None => match &arg.default { - Some(v) => v.clone(), + Some(v) => Value::from_tokens( + &mut v.iter().cloned().peekable(), + scope, + super_selector, + )?, None => return Err(format!("Missing argument ${}.", &arg.name).into()), }, }, @@ -176,6 +187,9 @@ pub(crate) fn eat_include>( let mixin = scope.get_mixin(&name)?.clone(); - let rules = mixin.args(args)?.content(content).call(super_selector)?; + let rules = mixin + .args(args, scope, super_selector)? + .content(content) + .call(super_selector)?; Ok(rules) } diff --git a/src/builtin/color/hsl.rs b/src/builtin/color/hsl.rs index d7a4df7..fb72017 100644 --- a/src/builtin/color/hsl.rs +++ b/src/builtin/color/hsl.rs @@ -11,13 +11,13 @@ use crate::value::{Number, Value}; pub(crate) fn register(f: &mut HashMap) { f.insert( "hsl".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { if args.is_empty() { return Err("Missing argument $channels.".into()); } if args.len() == 1 { - let mut channels = match arg!(args, 0, "channels") { + let mut channels = match arg!(args, scope, super_selector, 0, "channels") { Value::List(v, ..) => v, _ => return Err("Missing argument $channels.".into()), }; @@ -55,20 +55,22 @@ pub(crate) fn register(f: &mut HashMap) { Number::one(), ))) } else { - let hue = match arg!(args, 0, "hue") { + let hue = match arg!(args, scope, super_selector, 0, "hue") { Value::Dimension(n, _) => n, v => return Err(format!("$hue: {} is not a number.", v).into()), }; - let saturation = match arg!(args, 1, "saturation") { + let saturation = match arg!(args, scope, super_selector, 1, "saturation") { Value::Dimension(n, _) => n / Number::from(100), v => return Err(format!("$saturation: {} is not a number.", v).into()), }; - let lightness = match arg!(args, 2, "lightness") { + let lightness = match arg!(args, scope, super_selector, 2, "lightness") { Value::Dimension(n, _) => n / Number::from(100), v => return Err(format!("$lightness: {} is not a number.", v).into()), }; let alpha = match arg!( args, + scope, + super_selector, 3, "alpha" = Value::Dimension(Number::one(), Unit::None) ) { @@ -89,13 +91,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "hsla".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { if args.is_empty() { return Err("Missing argument $channels.".into()); } if args.len() == 1 { - let mut channels = match arg!(args, 0, "channels") { + let mut channels = match arg!(args, scope, super_selector, 0, "channels") { Value::List(v, ..) => v, _ => return Err("Missing argument $channels.".into()), }; @@ -133,20 +135,22 @@ pub(crate) fn register(f: &mut HashMap) { Number::one(), ))) } else { - let hue = match arg!(args, 0, "hue") { + let hue = match arg!(args, scope, super_selector, 0, "hue") { Value::Dimension(n, _) => n, v => return Err(format!("$hue: {} is not a number.", v).into()), }; - let saturation = match arg!(args, 1, "saturation") { + let saturation = match arg!(args, scope, super_selector, 1, "saturation") { Value::Dimension(n, _) => n / Number::from(100), v => return Err(format!("$saturation: {} is not a number.", v).into()), }; - let lightness = match arg!(args, 2, "lightness") { + let lightness = match arg!(args, scope, super_selector, 2, "lightness") { Value::Dimension(n, _) => n / Number::from(100), v => return Err(format!("$lightness: {} is not a number.", v).into()), }; let alpha = match arg!( args, + scope, + super_selector, 3, "alpha" = Value::Dimension(Number::one(), Unit::None) ) { @@ -167,9 +171,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "hue".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg)), v => Err(format!("$color: {} is not a color.", v).into()), } @@ -177,9 +181,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "saturation".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent)), v => Err(format!("$color: {} is not a color.", v).into()), } @@ -187,9 +191,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "lightness".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent)), v => Err(format!("$color: {} is not a color.", v).into()), } @@ -197,13 +201,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "adjust-hue".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let degrees = match arg!(args, 1, "degrees") { + let degrees = match arg!(args, scope, super_selector, 1, "degrees") { Value::Dimension(n, _) => n, v => return Err(format!("$degrees: {} is not a number.", v).into()), }; @@ -212,13 +216,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "lighten".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), v => return Err(format!("$amount: {} is not a number.", v).into()), }; @@ -227,13 +231,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "darken".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), v => return Err(format!("$amount: {} is not a number.", v).into()), }; @@ -242,20 +246,23 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "saturate".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); if args.len() == 1 { return Ok(Value::Ident( - format!("saturate({})", arg!(args, 0, "amount")), + format!( + "saturate({})", + arg!(args, scope, super_selector, 0, "amount") + ), QuoteKind::None, )); } - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), v => return Err(format!("$amount: {} is not a number.", v).into()), }; - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, Value::Dimension(n, u) => { return Ok(Value::Ident( @@ -270,13 +277,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "desaturate".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), v => return Err(format!("$amount: {} is not a number.", v).into()), }; @@ -285,9 +292,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "grayscale".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, Value::Dimension(n, u) => { return Ok(Value::Ident( @@ -302,9 +309,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "complement".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; @@ -313,17 +320,19 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "invert".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); let weight = match arg!( args, + scope, + super_selector, 1, "weight" = Value::Dimension(Number::from(100), Unit::Percent) ) { Value::Dimension(n, u) => bound!("weight", n, u, 0, 100) / Number::from(100), v => return Err(format!("$weight: {} is not a number.", v).into()), }; - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Color(c.invert(weight))), Value::Dimension(n, Unit::Percent) => { Ok(Value::Ident(format!("invert({}%)", n), QuoteKind::None)) diff --git a/src/builtin/color/opacity.rs b/src/builtin/color/opacity.rs index a74df3b..f21fe94 100644 --- a/src/builtin/color/opacity.rs +++ b/src/builtin/color/opacity.rs @@ -9,9 +9,9 @@ use crate::value::Value; pub(crate) fn register(f: &mut HashMap) { f.insert( "alpha".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)), v => Err(format!("$color: {} is not a color.", v).into()), } @@ -19,9 +19,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "opacity".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)), Value::Dimension(num, unit) => Ok(Value::Ident( format!("opacity({}{})", num, unit), @@ -33,13 +33,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "opacify".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), v => return Err(format!("$amount: {} is not a number.", v).into()), }; @@ -48,13 +48,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "fade-in".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), v => return Err(format!("$amount: {} is not a number.", v).into()), }; @@ -63,13 +63,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "transparentize".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), v => return Err(format!("$amount: {} is not a number.", v).into()), }; @@ -78,13 +78,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "fade-out".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let amount = match arg!(args, 1, "amount") { + let amount = match arg!(args, scope, super_selector, 1, "amount") { Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), v => return Err(format!("$amount: {} is not a number.", v).into()), }; diff --git a/src/builtin/color/other.rs b/src/builtin/color/other.rs index 91e8a90..bd14852 100644 --- a/src/builtin/color/other.rs +++ b/src/builtin/color/other.rs @@ -9,9 +9,9 @@ use crate::unit::Unit; use crate::value::{Number, Value}; macro_rules! opt_rgba { - ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { + ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => { let x = $low; - let $name = match named_arg!($args, $arg = Value::Null) { + let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high)), Value::Null => None, v => return Err(format!("${}: {} is not a number.", $arg, v).into()), @@ -20,9 +20,9 @@ macro_rules! opt_rgba { } macro_rules! opt_hsl { - ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { + ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => { let x = $low; - let $name = match named_arg!($args, $arg = Value::Null) { + let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high) / Number::from(100)), Value::Null => None, v => return Err(format!("${}: {} is not a number.", $arg, v).into()), @@ -31,33 +31,33 @@ macro_rules! opt_hsl { } pub(crate) fn register(f: &mut HashMap) { - f.insert("change-color".to_owned(), Builtin::new(|mut args, _| { - if args.get_positional(1).is_some() { + f.insert("change-color".to_owned(), Builtin::new(|mut args, scope, super_selector| { + if args.get_positional(1, scope, super_selector).is_some() { return Err("Only one positional argument is allowed. All other arguments must be passed by name.".into()); } - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - opt_rgba!(args, alpha, "alpha", 0, 1); - opt_rgba!(args, red, "red", 0, 255); - opt_rgba!(args, green, "green", 0, 255); - opt_rgba!(args, blue, "blue", 0, 255); + opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector); + opt_rgba!(args, red, "red", 0, 255, scope, super_selector); + opt_rgba!(args, green, "green", 0, 255, scope, super_selector); + opt_rgba!(args, blue, "blue", 0, 255, scope, super_selector); if red.is_some() || green.is_some() || blue.is_some() { return Ok(Value::Color(Color::from_rgba(red.unwrap_or(color.red()), green.unwrap_or(color.green()), blue.unwrap_or(color.blue()), alpha.unwrap_or(color.alpha())))) } - let hue = match named_arg!(args, "hue"=Value::Null) { + let hue = match named_arg!(args, scope, super_selector, "hue"=Value::Null) { Value::Dimension(n, _) => Some(n), Value::Null => None, v => return Err(format!("$hue: {} is not a number.", v).into()), }; - opt_hsl!(args, saturation, "saturation", 0, 100); - opt_hsl!(args, luminance, "lightness", 0, 100); + opt_hsl!(args, saturation, "saturation", 0, 100, scope, super_selector); + opt_hsl!(args, luminance, "lightness", 0, 100, scope, super_selector); if hue.is_some() || saturation.is_some() || luminance.is_some() { // Color::as_hsla() returns more exact values than Color::hue(), etc. @@ -73,16 +73,16 @@ pub(crate) fn register(f: &mut HashMap) { })); f.insert( "adjust-color".to_owned(), - Builtin::new(|mut args, _| { - let color = match arg!(args, 0, "color") { + Builtin::new(|mut args, scope, super_selector| { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - opt_rgba!(args, alpha, "alpha", -1, 1); - opt_rgba!(args, red, "red", -255, 255); - opt_rgba!(args, green, "green", -255, 255); - opt_rgba!(args, blue, "blue", -255, 255); + opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector); + opt_rgba!(args, red, "red", -255, 255, scope, super_selector); + opt_rgba!(args, green, "green", -255, 255, scope, super_selector); + opt_rgba!(args, blue, "blue", -255, 255, scope, super_selector); if red.is_some() || green.is_some() || blue.is_some() { return Ok(Value::Color(Color::from_rgba( @@ -93,14 +93,30 @@ pub(crate) fn register(f: &mut HashMap) { ))); } - let hue = match named_arg!(args, "hue" = Value::Null) { + let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) { Value::Dimension(n, _) => Some(n), Value::Null => None, v => return Err(format!("$hue: {} is not a number.", v).into()), }; - opt_hsl!(args, saturation, "saturation", -100, 100); - opt_hsl!(args, luminance, "lightness", -100, 100); + opt_hsl!( + args, + saturation, + "saturation", + -100, + 100, + scope, + super_selector + ); + opt_hsl!( + args, + luminance, + "lightness", + -100, + 100, + scope, + super_selector + ); if hue.is_some() || saturation.is_some() || luminance.is_some() { // Color::as_hsla() returns more exact values than Color::hue(), etc. @@ -123,16 +139,16 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "scale-color".to_owned(), - Builtin::new(|mut args, _| { - let color = match arg!(args, 0, "color") { + Builtin::new(|mut args, scope, super_selector| { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; macro_rules! opt_scale_arg { - ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { + ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => { let x = $low; - let $name = match named_arg!($args, $arg = Value::Null) { + let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { Value::Dimension(n, Unit::Percent) => { Some(bound!($arg, n, Unit::Percent, x, $high) / Number::from(100)) } @@ -147,10 +163,10 @@ pub(crate) fn register(f: &mut HashMap) { }; } - opt_scale_arg!(args, alpha, "alpha", -100, 100); - opt_scale_arg!(args, red, "red", -100, 100); - opt_scale_arg!(args, green, "green", -100, 100); - opt_scale_arg!(args, blue, "blue", -100, 100); + opt_scale_arg!(args, alpha, "alpha", -100, 100, scope, super_selector); + opt_scale_arg!(args, red, "red", -100, 100, scope, super_selector); + opt_scale_arg!(args, green, "green", -100, 100, scope, super_selector); + opt_scale_arg!(args, blue, "blue", -100, 100, scope, super_selector); if red.is_some() || green.is_some() || blue.is_some() { return Ok(Value::Color(Color::from_rgba( @@ -177,8 +193,8 @@ pub(crate) fn register(f: &mut HashMap) { ))); } - opt_scale_arg!(args, saturation, "saturation", -100, 100); - opt_scale_arg!(args, luminance, "lightness", -100, 100); + opt_scale_arg!(args, saturation, "saturation", -100, 100, scope, super_selector); + opt_scale_arg!(args, luminance, "lightness", -100, 100, scope, super_selector); if saturation.is_some() || luminance.is_some() { // Color::as_hsla() returns more exact values than Color::hue(), etc. @@ -209,9 +225,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "ie-hex-str".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; diff --git a/src/builtin/color/rgb.rs b/src/builtin/color/rgb.rs index fd55b94..fee20f2 100644 --- a/src/builtin/color/rgb.rs +++ b/src/builtin/color/rgb.rs @@ -10,13 +10,13 @@ use crate::value::{Number, Value}; pub(crate) fn register(f: &mut HashMap) { f.insert( "rgb".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { if args.is_empty() { return Err("Missing argument $channels.".into()); } if args.len() == 1 { - let mut channels = match arg!(args, 0, "channels") { + let mut channels = match arg!(args, scope, super_selector, 0, "channels") { Value::List(v, ..) => v, _ => return Err("Missing argument $channels.".into()), }; @@ -60,11 +60,11 @@ pub(crate) fn register(f: &mut HashMap) { Ok(Value::Color(color)) } else if args.len() == 2 { - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let alpha = match arg!(args, 1, "alpha") { + let alpha = match arg!(args, scope, super_selector, 1, "alpha") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => n / Number::from(100), v @ Value::Dimension(..) => { @@ -76,7 +76,7 @@ pub(crate) fn register(f: &mut HashMap) { }; Ok(Value::Color(color.with_alpha(alpha))) } else { - let red = match arg!(args, 0, "red") { + let red = match arg!(args, scope, super_selector, 0, "red") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => { (n / Number::from(100)) * Number::from(255) @@ -88,7 +88,7 @@ pub(crate) fn register(f: &mut HashMap) { } v => return Err(format!("$red: {} is not a number.", v).into()), }; - let green = match arg!(args, 1, "green") { + let green = match arg!(args, scope, super_selector, 1, "green") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => { (n / Number::from(100)) * Number::from(255) @@ -100,7 +100,7 @@ pub(crate) fn register(f: &mut HashMap) { } v => return Err(format!("$green: {} is not a number.", v).into()), }; - let blue = match arg!(args, 2, "blue") { + let blue = match arg!(args, scope, super_selector, 2, "blue") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => { (n / Number::from(100)) * Number::from(255) @@ -114,6 +114,8 @@ pub(crate) fn register(f: &mut HashMap) { }; let alpha = match arg!( args, + scope, + super_selector, 3, "alpha" = Value::Dimension(Number::one(), Unit::None) ) { @@ -132,13 +134,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "rgba".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { if args.is_empty() { return Err("Missing argument $channels.".into()); } if args.len() == 1 { - let mut channels = match arg!(args, 0, "channels") { + let mut channels = match arg!(args, scope, super_selector, 0, "channels") { Value::List(v, ..) => v, _ => return Err("Missing argument $channels.".into()), }; @@ -182,11 +184,11 @@ pub(crate) fn register(f: &mut HashMap) { Ok(Value::Color(color)) } else if args.len() == 2 { - let color = match arg!(args, 0, "color") { + let color = match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => c, v => return Err(format!("$color: {} is not a color.", v).into()), }; - let alpha = match arg!(args, 1, "alpha") { + let alpha = match arg!(args, scope, super_selector, 1, "alpha") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => n / Number::from(100), v @ Value::Dimension(..) => { @@ -198,7 +200,7 @@ pub(crate) fn register(f: &mut HashMap) { }; Ok(Value::Color(color.with_alpha(alpha))) } else { - let red = match arg!(args, 0, "red") { + let red = match arg!(args, scope, super_selector, 0, "red") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => { (n / Number::from(100)) * Number::from(255) @@ -210,7 +212,7 @@ pub(crate) fn register(f: &mut HashMap) { } v => return Err(format!("$red: {} is not a number.", v).into()), }; - let green = match arg!(args, 1, "green") { + let green = match arg!(args, scope, super_selector, 1, "green") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => { (n / Number::from(100)) * Number::from(255) @@ -222,7 +224,7 @@ pub(crate) fn register(f: &mut HashMap) { } v => return Err(format!("$green: {} is not a number.", v).into()), }; - let blue = match arg!(args, 2, "blue") { + let blue = match arg!(args, scope, super_selector, 2, "blue") { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => { (n / Number::from(100)) * Number::from(255) @@ -236,6 +238,8 @@ pub(crate) fn register(f: &mut HashMap) { }; let alpha = match arg!( args, + scope, + super_selector, 3, "alpha" = Value::Dimension(Number::one(), Unit::None) ) { @@ -254,9 +258,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "red".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None)), v => Err(format!("$color: {} is not a color.", v).into()), } @@ -264,9 +268,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "green".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None)), v => Err(format!("$color: {} is not a color.", v).into()), } @@ -274,9 +278,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "blue".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "color") { + match arg!(args, scope, super_selector, 0, "color") { Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None)), v => Err(format!("$color: {} is not a color.", v).into()), } @@ -284,20 +288,22 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "mix".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 3); - let color1 = match arg!(args, 0, "color1") { + let color1 = match arg!(args, scope, super_selector, 0, "color1") { Value::Color(c) => c, v => return Err(format!("$color1: {} is not a color.", v).into()), }; - let color2 = match arg!(args, 1, "color2") { + let color2 = match arg!(args, scope, super_selector, 1, "color2") { Value::Color(c) => c, v => return Err(format!("$color2: {} is not a color.", v).into()), }; let weight = match arg!( args, + scope, + super_selector, 2, "weight" = Value::Dimension(Number::from(50), Unit::None) ) { diff --git a/src/builtin/list.rs b/src/builtin/list.rs index c273ad7..5d38d3f 100644 --- a/src/builtin/list.rs +++ b/src/builtin/list.rs @@ -10,9 +10,9 @@ use crate::value::{Number, Value}; pub(crate) fn register(f: &mut HashMap) { f.insert( "length".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let len = match arg!(args, 0, "list") { + let len = match arg!(args, scope, super_selector, 0, "list") { Value::List(v, ..) => Number::from(v.len()), Value::Map(m) => Number::from(m.len()), _ => Number::one(), @@ -22,14 +22,14 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "nth".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let list = match arg!(args, 0, "list") { + let list = match arg!(args, scope, super_selector, 0, "list") { Value::List(v, ..) => v, Value::Map(m) => m.entries(), v => vec![v], }; - let n = match arg!(args, 1, "n") { + let n = match arg!(args, scope, super_selector, 1, "n") { Value::Dimension(num, _) => num, v => return Err(format!("$n: {} is not a number.", v).into()), }; @@ -60,10 +60,10 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "list-separator".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); Ok(Value::Ident( - match arg!(args, 0, "list") { + match arg!(args, scope, super_selector, 0, "list") { Value::List(_, sep, ..) => sep.name(), _ => ListSeparator::Space.name(), } @@ -74,14 +74,14 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "set-nth".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 3); - let (mut list, sep, brackets) = match arg!(args, 0, "list") { + let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") { Value::List(v, sep, b) => (v, sep, b), Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None), v => (vec![v], ListSeparator::Space, Brackets::None), }; - let n = match arg!(args, 1, "n") { + let n = match arg!(args, scope, super_selector, 1, "n") { Value::Dimension(num, _) => num, v => return Err(format!("$n: {} is not a number.", v).into()), }; @@ -102,7 +102,7 @@ pub(crate) fn register(f: &mut HashMap) { return Err(format!("$n: {} is not an int.", n).into()); } - let val = arg!(args, 2, "value"); + let val = arg!(args, scope, super_selector, 2, "value"); if n.is_positive() { list[n.to_integer().to_usize().unwrap() - 1] = val; @@ -115,15 +115,17 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "append".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 3); - let (mut list, sep, brackets) = match arg!(args, 0, "list") { + let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") { Value::List(v, sep, b) => (v, sep, b), v => (vec![v], ListSeparator::Space, Brackets::None), }; - let val = arg!(args, 1, "val"); + let val = arg!(args, scope, super_selector, 1, "val"); let sep = match arg!( args, + scope, + super_selector, 2, "separator" = Value::Ident("auto".to_owned(), QuoteKind::None) ) { @@ -145,20 +147,22 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "join".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 4); - let (mut list1, sep1, brackets) = match arg!(args, 0, "list1") { + let (mut list1, sep1, brackets) = match arg!(args, scope, super_selector, 0, "list1") { Value::List(v, sep, brackets) => (v, sep, brackets), Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None), v => (vec![v], ListSeparator::Space, Brackets::None), }; - let (list2, sep2) = match arg!(args, 1, "list2") { + let (list2, sep2) = match arg!(args, scope, super_selector, 1, "list2") { Value::List(v, sep, ..) => (v, sep), Value::Map(m) => (m.entries(), ListSeparator::Comma), v => (vec![v], ListSeparator::Space), }; let sep = match arg!( args, + scope, + super_selector, 2, "separator" = Value::Ident("auto".to_owned(), QuoteKind::None) ) { @@ -181,6 +185,8 @@ pub(crate) fn register(f: &mut HashMap) { let brackets = match arg!( args, + scope, + super_selector, 3, "bracketed" = Value::Ident("auto".to_owned(), QuoteKind::None) ) { @@ -204,27 +210,29 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "is-bracketed".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - Ok(Value::bool(match arg!(args, 0, "list") { - Value::List(.., brackets) => match brackets { - Brackets::Bracketed => true, - Brackets::None => false, + Ok(Value::bool( + match arg!(args, scope, super_selector, 0, "list") { + Value::List(.., brackets) => match brackets { + Brackets::Bracketed => true, + Brackets::None => false, + }, + _ => false, }, - _ => false, - })) + )) }), ); f.insert( "index".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let list = match arg!(args, 0, "list") { + let list = match arg!(args, scope, super_selector, 0, "list") { Value::List(v, ..) => v, Value::Map(m) => m.entries(), v => vec![v], }; - let value = arg!(args, 1, "value"); + let value = arg!(args, scope, super_selector, 1, "value"); // TODO: find a way around this unwrap. // It should be impossible to hit as the arg is // evaluated prior to checking equality, but @@ -242,9 +250,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "zip".to_owned(), - Builtin::new(|args, _| { + Builtin::new(|args, scope, super_selector| { let lists = args - .get_variadic()? + .get_variadic(scope, super_selector)? .into_iter() .map(|x| match x { Value::List(v, ..) => v, diff --git a/src/builtin/macros.rs b/src/builtin/macros.rs index 3e4739d..165dbee 100644 --- a/src/builtin/macros.rs +++ b/src/builtin/macros.rs @@ -1,18 +1,18 @@ macro_rules! arg { - ($args:ident, $idx:literal, $name:literal) => { - match $args.remove_positional($idx) { - Some(v) => v.eval()?, - None => match $args.remove_named($name.to_owned()) { - Some(v) => v.eval()?, + ($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal) => { + match $args.get_positional($idx, $scope, $super_selector) { + Some(v) => v?.eval()?, + None => match $args.get_named($name.to_owned(), $scope, $super_selector) { + Some(v) => v?.eval()?, None => return Err(concat!("Missing argument $", $name, ".").into()), }, }; }; - ($args:ident, $idx:literal, $name:literal=$default:expr) => { - match $args.remove_positional($idx) { - Some(v) => v.eval()?, - None => match $args.remove_named($name.to_owned()) { - Some(v) => v.eval()?, + ($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal=$default:expr) => { + match $args.get_positional($idx, $scope, $super_selector) { + Some(v) => v?.eval()?, + None => match $args.get_named($name.to_owned(), $scope, $super_selector) { + Some(v) => v?.eval()?, None => $default, }, }; @@ -20,15 +20,15 @@ macro_rules! arg { } macro_rules! named_arg { - ($args:ident, $name:literal) => { - match $args.remove_named($name.to_owned()) { - Some(v) => v.eval()?, + ($args:ident, $scope:ident, $super_selector:ident, $name:literal) => { + match $args.get_named($name.to_owned(), $scope, $super_selector) { + Some(v) => v?.eval()?, None => return Err(concat!("Missing argument $", $name, ".").into()), }; }; - ($args:ident, $name:literal=$default:expr) => { - match $args.remove_named($name.to_owned()) { - Some(v) => v.eval()?, + ($args:ident, $scope:ident, $super_selector:ident, $name:literal=$default:expr) => { + match $args.get_named($name.to_owned(), $scope, $super_selector) { + Some(v) => v?.eval()?, None => $default, }; }; diff --git a/src/builtin/map.rs b/src/builtin/map.rs index 004db8c..35070ae 100644 --- a/src/builtin/map.rs +++ b/src/builtin/map.rs @@ -7,10 +7,10 @@ use crate::value::{SassMap, Value}; pub(crate) fn register(f: &mut HashMap) { f.insert( "map-get".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let key = arg!(args, 1, "key"); - let map = match arg!(args, 0, "map") { + let key = arg!(args, scope, super_selector, 1, "key"); + let map = match arg!(args, scope, super_selector, 0, "map") { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), v => return Err(format!("$map: {} is not a map.", v).into()), @@ -20,10 +20,10 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "map-has-key".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let key = arg!(args, 1, "key"); - let map = match arg!(args, 0, "map") { + let key = arg!(args, scope, super_selector, 1, "key"); + let map = match arg!(args, scope, super_selector, 0, "map") { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), v => return Err(format!("$map: {} is not a map.", v).into()), @@ -33,9 +33,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "map-keys".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let map = match arg!(args, 0, "map") { + let map = match arg!(args, scope, super_selector, 0, "map") { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), v => return Err(format!("$map: {} is not a map.", v).into()), @@ -49,9 +49,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "map-values".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let map = match arg!(args, 0, "map") { + let map = match arg!(args, scope, super_selector, 0, "map") { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), v => return Err(format!("$map: {} is not a map.", v).into()), @@ -65,14 +65,14 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "map-merge".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let mut map1 = match arg!(args, 0, "map1") { + let mut map1 = match arg!(args, scope, super_selector, 0, "map1") { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), v => return Err(format!("$map1: {} is not a map.", v).into()), }; - let map2 = match arg!(args, 1, "map2") { + let map2 = match arg!(args, scope, super_selector, 1, "map2") { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), v => return Err(format!("$map2: {} is not a map.", v).into()), @@ -83,13 +83,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "map-remove".to_owned(), - Builtin::new(|mut args, _| { - let mut map = match arg!(args, 0, "map") { + Builtin::new(|mut args, scope, super_selector| { + let mut map = match arg!(args, scope, super_selector, 0, "map") { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), v => return Err(format!("$map: {} is not a map.", v).into()), }; - let keys = args.get_variadic()?; + let keys = args.get_variadic(scope, super_selector)?; for key in keys { map.remove(&key); } diff --git a/src/builtin/math.rs b/src/builtin/math.rs index 13a164a..bfd77e8 100644 --- a/src/builtin/math.rs +++ b/src/builtin/math.rs @@ -7,9 +7,9 @@ use crate::value::{Number, Value}; pub(crate) fn register(f: &mut HashMap) { f.insert( "percentage".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let num = match arg!(args, 0, "number") { + let num = match arg!(args, scope, super_selector, 0, "number") { Value::Dimension(n, Unit::None) => n * Number::from(100), v @ Value::Dimension(..) => { return Err(format!("$number: Expected {} to have no units.", v).into()) @@ -21,9 +21,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "round".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "number") { + match arg!(args, scope, super_selector, 0, "number") { Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)), v => Err(format!("$number: {} is not a number.", v).into()), } @@ -31,9 +31,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "ceil".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "number") { + match arg!(args, scope, super_selector, 0, "number") { Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)), v => Err(format!("$number: {} is not a number.", v).into()), } @@ -41,9 +41,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "floor".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "number") { + match arg!(args, scope, super_selector, 0, "number") { Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)), v => Err(format!("$number: {} is not a number.", v).into()), } @@ -51,9 +51,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "abs".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "number") { + match arg!(args, scope, super_selector, 0, "number") { Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)), v => Err(format!("$number: {} is not a number.", v).into()), } @@ -61,13 +61,13 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "comparable".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let unit1 = match arg!(args, 0, "number1") { + let unit1 = match arg!(args, scope, super_selector, 0, "number1") { Value::Dimension(_, u) => u, v => return Err(format!("$number1: {} is not a number.", v).into()), }; - let unit2 = match arg!(args, 1, "number2") { + let unit2 = match arg!(args, scope, super_selector, 1, "number2") { Value::Dimension(_, u) => u, v => return Err(format!("$number2: {} is not a number.", v).into()), }; diff --git a/src/builtin/meta.rs b/src/builtin/meta.rs index f403d08..a3711d4 100644 --- a/src/builtin/meta.rs +++ b/src/builtin/meta.rs @@ -9,20 +9,20 @@ use crate::value::{SassFunction, Value}; pub(crate) fn register(f: &mut HashMap) { f.insert( "if".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 3); - if arg!(args, 0, "condition").is_true()? { - Ok(arg!(args, 1, "if-true")) + if arg!(args, scope, super_selector, 0, "condition").is_true()? { + Ok(arg!(args, scope, super_selector, 1, "if-true")) } else { - Ok(arg!(args, 2, "if-false")) + Ok(arg!(args, scope, super_selector, 2, "if-false")) } }), ); f.insert( "feature-exists".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "feature") { + match arg!(args, scope, super_selector, 0, "feature") { Value::Ident(s, _) => match s.as_str() { // A local variable will shadow a global variable unless // `!global` is used. @@ -47,9 +47,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "unit".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let unit = match arg!(args, 0, "number") { + let unit = match arg!(args, scope, super_selector, 0, "number") { Value::Dimension(_, u) => u.to_string(), v => return Err(format!("$number: {} is not a number.", v).into()), }; @@ -58,17 +58,17 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "type-of".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - let value = arg!(args, 0, "value"); + let value = arg!(args, scope, super_selector, 0, "value"); Ok(Value::Ident(value.kind()?.to_owned(), QuoteKind::None)) }), ); f.insert( "unitless".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "number") { + match arg!(args, scope, super_selector, 0, "number") { Value::Dimension(_, Unit::None) => Ok(Value::True), Value::Dimension(_, _) => Ok(Value::False), _ => Ok(Value::True), @@ -77,10 +77,10 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "inspect".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); Ok(Value::Ident( - match arg!(args, 0, "value") { + match arg!(args, scope, super_selector, 0, "value") { Value::List(v, _, brackets) if v.is_empty() => match brackets { Brackets::None => "()".to_string(), Brackets::Bracketed => "[]".to_string(), @@ -94,9 +94,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "variable-exists".to_owned(), - Builtin::new(|mut args, scope| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "name") { + match arg!(args, scope, super_selector, 0, "name") { Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))), v => Err(format!("$name: {} is not a string.", v).into()), } @@ -104,9 +104,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "global-variable-exists".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "name") { + match arg!(args, scope, super_selector, 0, "name") { Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))), v => Err(format!("$name: {} is not a string.", v).into()), } @@ -114,9 +114,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "mixin-exists".to_owned(), - Builtin::new(|mut args, scope| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - match arg!(args, 0, "name") { + match arg!(args, scope, super_selector, 0, "name") { Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))), v => Err(format!("$name: {} is not a string.", v).into()), } @@ -124,9 +124,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "function-exists".to_owned(), - Builtin::new(|mut args, scope| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - match arg!(args, 0, "name") { + match arg!(args, scope, super_selector, 0, "name") { Value::Ident(s, _) => Ok(Value::bool( scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(&s), )), @@ -136,14 +136,14 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "get-function".to_owned(), - Builtin::new(|mut args, scope| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 3); - let name = match arg!(args, 0, "name") { + let name = match arg!(args, scope, super_selector, 0, "name") { Value::Ident(s, _) => s, v => return Err(format!("$name: {} is not a string.", v).into()), }; - let css = arg!(args, 1, "css" = Value::False).is_true()?; - let module = match arg!(args, 2, "module" = Value::Null) { + let css = arg!(args, scope, super_selector, 1, "css" = Value::False).is_true()?; + let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) { Value::Ident(s, ..) => Some(s), Value::Null => None, v => return Err(format!("$module: {} is not a string.", v).into()), @@ -166,12 +166,12 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "call".to_owned(), - Builtin::new(|mut args, scope| { - let func = match arg!(args, 0, "function") { + Builtin::new(|mut args, scope, super_selector| { + let func = match arg!(args, scope, super_selector, 0, "function") { Value::Function(f) => f, v => return Err(format!("$function: {} is not a function reference.", v).into()), }; - func.call(args.decrement(), scope) + func.call(args.decrement(), scope, super_selector) }), ); } diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 73ab12f..083399f 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use crate::args::CallArgs; use crate::error::SassResult; use crate::scope::Scope; +use crate::selector::Selector; use crate::value::Value; #[macro_use] @@ -22,9 +23,12 @@ static FUNCTION_COUNT: AtomicUsize = AtomicUsize::new(0); // TODO: impl Fn #[derive(Clone)] -pub(crate) struct Builtin(pub fn(CallArgs, &Scope) -> SassResult, usize); +pub(crate) struct Builtin( + pub fn(CallArgs, &Scope, &Selector) -> SassResult, + usize, +); impl Builtin { - pub fn new(body: fn(CallArgs, &Scope) -> SassResult) -> Builtin { + pub fn new(body: fn(CallArgs, &Scope, &Selector) -> SassResult) -> Builtin { let count = FUNCTION_COUNT.fetch_add(1, Ordering::Relaxed); Self(body, count) } diff --git a/src/builtin/string.rs b/src/builtin/string.rs index 4978fd4..17e17e2 100644 --- a/src/builtin/string.rs +++ b/src/builtin/string.rs @@ -11,9 +11,9 @@ use crate::value::{Number, Value}; pub(crate) fn register(f: &mut HashMap) { f.insert( "to-upper-case".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "string") { + match arg!(args, scope, super_selector, 0, "string") { Value::Ident(i, q) => Ok(Value::Ident(i.to_ascii_uppercase(), q)), v => Err(format!("$string: {} is not a string.", v).into()), } @@ -21,9 +21,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "to-lower-case".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "string") { + match arg!(args, scope, super_selector, 0, "string") { Value::Ident(i, q) => Ok(Value::Ident(i.to_ascii_lowercase(), q)), v => Err(format!("$string: {} is not a string.", v).into()), } @@ -31,9 +31,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "str-length".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "string") { + match arg!(args, scope, super_selector, 0, "string") { Value::Ident(i, _) => Ok(Value::Dimension( Number::from(i.chars().count()), Unit::None, @@ -44,9 +44,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "quote".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "string") { + match arg!(args, scope, super_selector, 0, "string") { Value::Ident(i, _) => Ok(Value::Ident(i, QuoteKind::Double)), v => Err(format!("$string: {} is not a string.", v).into()), } @@ -54,9 +54,9 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "unquote".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 1); - match arg!(args, 0, "string") { + match arg!(args, scope, super_selector, 0, "string") { i @ Value::Ident(..) => Ok(i.unquote()), v => Err(format!("$string: {} is not a string.", v).into()), } @@ -64,14 +64,14 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "str-slice".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 3); - let (string, quotes) = match arg!(args, 0, "string") { + let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") { Value::Ident(s, q) => (s, q), v => return Err(format!("$string: {} is not a string.", v).into()), }; let str_len = string.chars().count(); - let start = match arg!(args, 1, "start-at") { + let start = match arg!(args, scope, super_selector, 1, "start-at") { Value::Dimension(n, Unit::None) if n.is_decimal() => { return Err(format!("{} is not an int.", n).into()) } @@ -88,7 +88,7 @@ pub(crate) fn register(f: &mut HashMap) { } v => return Err(format!("$start-at: {} is not a number.", v).into()), }; - let mut end = match arg!(args, 2, "end-at" = Value::Null) { + let mut end = match arg!(args, scope, super_selector, 2, "end-at" = Value::Null) { Value::Dimension(n, Unit::None) if n.is_decimal() => { return Err(format!("{} is not an int.", n).into()) } @@ -127,14 +127,14 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "str-index".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 2); - let s1 = match arg!(args, 0, "string") { + let s1 = match arg!(args, scope, super_selector, 0, "string") { Value::Ident(i, _) => i, v => return Err(format!("$string: {} is not a string.", v).into()), }; - let substr = match arg!(args, 1, "substring") { + let substr = match arg!(args, scope, super_selector, 1, "substring") { Value::Ident(i, _) => i, v => return Err(format!("$substring: {} is not a string.", v).into()), }; @@ -147,19 +147,19 @@ pub(crate) fn register(f: &mut HashMap) { ); f.insert( "str-insert".to_owned(), - Builtin::new(|mut args, _| { + Builtin::new(|mut args, scope, super_selector| { max_args!(args, 3); - let (s1, quotes) = match arg!(args, 0, "string") { + let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") { Value::Ident(i, q) => (i, q.normalize()), v => return Err(format!("$string: {} is not a string.", v).into()), }; - let substr = match arg!(args, 1, "insert") { + let substr = match arg!(args, scope, super_selector, 1, "insert") { Value::Ident(i, _) => i, v => return Err(format!("$insert: {} is not a string.", v).into()), }; - let index = match arg!(args, 2, "index") { + let index = match arg!(args, scope, super_selector, 2, "index") { Value::Dimension(n, Unit::None) if n.is_decimal() => { return Err(format!("$index: {} is not an int.", n).into()) } diff --git a/src/value/function.rs b/src/value/function.rs index b8a628a..3cc08d4 100644 --- a/src/value/function.rs +++ b/src/value/function.rs @@ -29,11 +29,19 @@ impl SassFunction { } } - pub fn call(self, args: CallArgs, scope: &Scope) -> SassResult { + pub fn call( + self, + args: CallArgs, + scope: &Scope, + super_selector: &Selector, + ) -> SassResult { match self { - Self::Builtin(f, ..) => f.0(args, scope), + Self::Builtin(f, ..) => f.0(args, scope, super_selector), // todo: superselector - Self::UserDefined(f, ..) => f.clone().args(args)?.call(&Selector::new(), f.body()), + Self::UserDefined(f, ..) => f + .clone() + .args(args, scope, super_selector)? + .call(&Selector::new(), f.body()), } } } diff --git a/src/value/parse.rs b/src/value/parse.rs index 56cd52a..21b6cc6 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -371,6 +371,7 @@ impl Value { return Ok(IntermediateValue::Value(f.0( eat_call_args(toks, scope, super_selector)?, scope, + super_selector, )?)) } None => { @@ -408,7 +409,11 @@ impl Value { }; Ok(IntermediateValue::Value( func.clone() - .args(eat_call_args(toks, scope, super_selector)?)? + .args( + eat_call_args(toks, scope, super_selector)?, + scope, + super_selector, + )? .call(super_selector, func.body())?, )) } diff --git a/tests/args.rs b/tests/args.rs index fa9f323..ca19d43 100644 --- a/tests/args.rs +++ b/tests/args.rs @@ -25,3 +25,8 @@ test!( "@function foo($a...) {\n @return $a;\n}\n\na {\n color: foo(1, 2, 3, 4, 5);\n}\n", "a {\n color: 1, 2, 3, 4, 5;\n}\n" ); +test!( + default_args_are_lazily_evaluated, + "$da: a;\n\n@mixin foo($x: $da) {\n color: $x;\n}\n$da: b;\n\na {\n @include foo();\n}\n", + "a {\n color: b;\n}\n" +);