diff --git a/src/args.rs b/src/args.rs index 429e489..548b704 100644 --- a/src/args.rs +++ b/src/args.rs @@ -28,16 +28,26 @@ impl FuncArgs { } } -#[derive(Debug, Clone, std::default::Default)] -pub(crate) struct CallArgs(pub HashMap); +#[derive(Debug, Clone)] +pub(crate) struct CallArgs(HashMap); + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +enum CallArg { + Named(String), + Positional(usize), +} impl CallArgs { pub fn new() -> Self { CallArgs(HashMap::new()) } - pub fn get(&self, val: &str) -> Option<&Value> { - self.0.get(val) + pub fn get_named(&self, val: String) -> Option<&Value> { + self.0.get(&CallArg::Named(val)) + } + + pub fn get_positional(&self, val: usize) -> Option<&Value> { + self.0.get(&CallArg::Positional(val)) } pub fn len(&self) -> usize { @@ -48,8 +58,12 @@ impl CallArgs { self.0.len() == 0 } - pub fn remove(&mut self, s: &str) -> Option { - self.0.remove(s) + 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)) } } @@ -174,9 +188,9 @@ 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; + let mut name = String::new(); let mut val: Vec = Vec::new(); loop { match toks.peek().unwrap().kind { @@ -190,14 +204,14 @@ pub(crate) fn eat_call_args>( } else { val.push(Token::new(Pos::new(), '$')); val.extend(v.chars().map(|x| Token::new(Pos::new(), x))); - name = args.len().to_string(); + name.clear(); } } ')' => { toks.next(); return Ok(CallArgs(args)); } - _ => name = args.len().to_string(), + _ => name.clear(), } devour_whitespace_or_comment(toks)?; @@ -205,7 +219,11 @@ pub(crate) fn eat_call_args>( match tok.kind { ')' => { args.insert( - name.replace('_', "-"), + if name.is_empty() { + CallArg::Positional(args.len()) + } else { + CallArg::Named(name.replace('_', "-")) + }, Value::from_tokens(&mut val.into_iter().peekable(), scope, super_selector)?, ); return Ok(CallArgs(args)); @@ -228,7 +246,11 @@ pub(crate) fn eat_call_args>( } args.insert( - name.replace('_', "-"), + if name.is_empty() { + CallArg::Positional(args.len()) + } else { + CallArg::Named(name.replace('_', "-")) + }, Value::from_tokens( &mut val.clone().into_iter().peekable(), scope, diff --git a/src/atrule/function.rs b/src/atrule/function.rs index 1b1053e..a5ce47b 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -45,9 +45,9 @@ impl Function { pub fn args(mut self, args: &mut CallArgs) -> SassResult { for (idx, arg) in self.args.0.iter().enumerate() { - let val = match args.remove(&format!("{}", idx)) { + let val = match args.remove_positional(idx) { Some(v) => v, - None => match args.remove(&arg.name) { + None => match args.remove_named(arg.name.clone()) { Some(v) => v, None => match &arg.default { Some(v) => v.clone(), diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 3a9c727..6367065 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -61,9 +61,9 @@ impl Mixin { pub fn args(mut self, args: &mut CallArgs) -> SassResult { for (idx, arg) in self.args.0.iter().enumerate() { - let val = match args.remove(&format!("{}", idx)) { + let val = match args.remove_positional(idx) { Some(v) => v, - None => match args.remove(&arg.name) { + None => match args.remove_named(arg.name.clone()) { Some(v) => v, None => match &arg.default { Some(v) => v.clone(), diff --git a/src/builtin/color/other.rs b/src/builtin/color/other.rs index 0e7ba7f..21c3c1c 100644 --- a/src/builtin/color/other.rs +++ b/src/builtin/color/other.rs @@ -11,7 +11,7 @@ use crate::value::{Number, Value}; macro_rules! opt_rgba { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { let x = $low; - let $name = match arg!($args, -1, $arg = Value::Null) { + let $name = match named_arg!($args, $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()), @@ -22,7 +22,7 @@ macro_rules! opt_rgba { macro_rules! opt_hsl { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { let x = $low; - let $name = match arg!($args, -1, $arg = Value::Null) { + let $name = match named_arg!($args, $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()), @@ -32,7 +32,7 @@ macro_rules! opt_hsl { pub(crate) fn register(f: &mut HashMap) { f.insert("change-color".to_owned(), Box::new(|args, _| { - if args.get("1").is_some() { + if args.get_positional(1).is_some() { return Err("Only one positional argument is allowed. All other arguments must be passed by name.".into()); } @@ -50,7 +50,7 @@ pub(crate) fn register(f: &mut HashMap) { 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 arg!(args, -1, "hue"=Value::Null) { + let hue = match named_arg!(args, "hue"=Value::Null) { Value::Dimension(n, _) => Some(n), Value::Null => None, v => return Err(format!("$hue: {} is not a number.", v).into()), @@ -93,7 +93,7 @@ pub(crate) fn register(f: &mut HashMap) { ))); } - let hue = match arg!(args, -1, "hue" = Value::Null) { + let hue = match named_arg!(args, "hue" = Value::Null) { Value::Dimension(n, _) => Some(n), Value::Null => None, v => return Err(format!("$hue: {} is not a number.", v).into()), @@ -132,7 +132,7 @@ pub(crate) fn register(f: &mut HashMap) { macro_rules! opt_scale_arg { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { let x = $low; - let $name = match arg!($args, -1, $arg = Value::Null) { + let $name = match named_arg!($args, $arg = Value::Null) { Value::Dimension(n, Unit::Percent) => { Some(bound!($arg, n, Unit::Percent, x, $high) / Number::from(100)) } diff --git a/src/builtin/macros.rs b/src/builtin/macros.rs index 950159c..3e4739d 100644 --- a/src/builtin/macros.rs +++ b/src/builtin/macros.rs @@ -1,17 +1,17 @@ macro_rules! arg { ($args:ident, $idx:literal, $name:literal) => { - match $args.remove(stringify!($idx)) { + match $args.remove_positional($idx) { Some(v) => v.eval()?, - None => match $args.remove($name) { + None => match $args.remove_named($name.to_owned()) { Some(v) => v.eval()?, None => return Err(concat!("Missing argument $", $name, ".").into()), }, }; }; ($args:ident, $idx:literal, $name:literal=$default:expr) => { - match $args.remove(stringify!($idx)) { + match $args.remove_positional($idx) { Some(v) => v.eval()?, - None => match $args.remove($name) { + None => match $args.remove_named($name.to_owned()) { Some(v) => v.eval()?, None => $default, }, @@ -19,6 +19,21 @@ macro_rules! arg { }; } +macro_rules! named_arg { + ($args:ident, $name:literal) => { + match $args.remove_named($name.to_owned()) { + 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()?, + None => $default, + }; + }; +} + macro_rules! max_args { ($args:ident, $count:literal) => { if $args.len() > $count {