diff --git a/src/args.rs b/src/args.rs index 548b704..482634d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -37,11 +37,21 @@ enum CallArg { Positional(usize), } +impl CallArg { + pub fn position(&self) -> SassResult { + match self { + Self::Named(..) => todo!(), + Self::Positional(p) => Ok(*p), + } + } +} + impl CallArgs { pub fn new() -> Self { CallArgs(HashMap::new()) } + #[allow(dead_code)] pub fn get_named(&self, val: String) -> Option<&Value> { self.0.get(&CallArg::Named(val)) } @@ -50,6 +60,20 @@ impl CallArgs { self.0.get(&CallArg::Positional(val)) } + pub fn get_variadic(self) -> SassResult { + let mut vals = Vec::new(); + let mut args = self + .0 + .into_iter() + .map(|(a, v)| Ok((a.position()?, v))) + .collect::>>()?; + args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2)); + for arg in args { + vals.push(arg.1); + } + Ok(Value::ArgList(vals)) + } + pub fn len(&self) -> usize { self.0.len() } diff --git a/src/atrule/function.rs b/src/atrule/function.rs index a5ce47b..82513e2 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -43,8 +43,12 @@ impl Function { Ok((name, Function::new(scope, args, body))) } - pub fn args(mut self, args: &mut CallArgs) -> SassResult { + pub fn args(mut self, mut args: CallArgs) -> SassResult { for (idx, arg) in self.args.0.iter().enumerate() { + if arg.is_variadic { + self.scope.insert_var(&arg.name, args.get_variadic()?)?; + break; + } let val = match args.remove_positional(idx) { Some(v) => v, None => match args.remove_named(arg.name.clone()) { diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 6367065..590134e 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -59,8 +59,12 @@ impl Mixin { self } - pub fn args(mut self, args: &mut CallArgs) -> SassResult { + pub fn args(mut self, mut args: CallArgs) -> SassResult { for (idx, arg) in self.args.0.iter().enumerate() { + if arg.is_variadic { + self.scope.insert_var(&arg.name, args.get_variadic()?)?; + break; + } let val = match args.remove_positional(idx) { Some(v) => v, None => match args.remove_named(arg.name.clone()) { @@ -128,7 +132,7 @@ pub(crate) fn eat_include>( let mut has_include = false; - let mut args = if let Some(tok) = toks.next() { + let args = if let Some(tok) = toks.next() { match tok.kind { ';' => CallArgs::new(), '(' => { @@ -170,9 +174,6 @@ pub(crate) fn eat_include>( let mixin = scope.get_mixin(&name)?.clone(); - let rules = mixin - .args(&mut args)? - .content(content) - .call(super_selector)?; + let rules = mixin.args(args)?.content(content).call(super_selector)?; Ok(rules) } diff --git a/src/value/mod.rs b/src/value/mod.rs index 2762d52..aec30ca 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -29,6 +29,7 @@ pub(crate) enum Value { Paren(Box), Ident(String, QuoteKind), Map(SassMap), + ArgList(Vec), // Returned by `get-function()` // Function(String) } @@ -120,6 +121,15 @@ impl Display for Value { Self::True => write!(f, "true"), Self::False => write!(f, "false"), Self::Null => write!(f, "null"), + Self::ArgList(args) => write!( + f, + "{}", + args.iter() + .filter(|x| !x.is_null()) + .map(std::string::ToString::to_string) + .collect::>() + .join(", "), + ), } } } @@ -157,6 +167,7 @@ impl Value { Self::Dimension(..) => Ok("number"), Self::List(..) => Ok("list"), // Self::Function(..) => Ok("function"), + Self::ArgList(..) => Ok("arglist"), Self::True | Self::False => Ok("bool"), Self::Null => Ok("null"), Self::Map(..) => Ok("map"), diff --git a/src/value/ops.rs b/src/value/ops.rs index 0fbc94a..e4ed729 100644 --- a/src/value/ops.rs +++ b/src/value/ops.rs @@ -14,7 +14,7 @@ impl Add for Value { } let precedence = Op::Plus.precedence(); Ok(match self { - Self::Map(..) => todo!(), + Self::ArgList(..) | Self::Map(..) => todo!(), Self::Important | Self::True | Self::False => match other { Self::Ident(s, QuoteKind::Double) | Self::Ident(s, QuoteKind::Single) => { Value::Ident(format!("{}{}", self, s), QuoteKind::Double) @@ -86,7 +86,7 @@ impl Add for Value { Self::List(..) => Value::Ident(format!("{}{}", s1, other), quotes1), Self::UnaryOp(..) | Self::BinaryOp(..) => todo!(), Self::Paren(..) => (Self::Ident(s1, quotes1) + other.eval()?)?, - Self::Map(..) => todo!(), + Self::ArgList(..) | Self::Map(..) => todo!(), }, Self::List(..) => match other { Self::Ident(s, q) => Value::Ident(format!("{}{}", self, s), q.normalize()), diff --git a/src/value/parse.rs b/src/value/parse.rs index 307b0be..bb754a1 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -406,7 +406,7 @@ impl Value { }; Ok(IntermediateValue::Value( func.clone() - .args(&mut eat_call_args(toks, scope, super_selector)?)? + .args(eat_call_args(toks, scope, super_selector)?)? .call(super_selector, func.body())?, )) } diff --git a/tests/args.rs b/tests/args.rs index b2e4d21..fa9f323 100644 --- a/tests/args.rs +++ b/tests/args.rs @@ -15,3 +15,13 @@ error!( varargs_two_periods, "@function foo($a..) {\n @return $a;\n}\n", "Error: expected \".\"." ); +test!( + mixin_varargs_are_comma_separated, + "@mixin foo($a...) {\n color: $a;\n}\n\na {\n @include foo(1, 2, 3, 4, 5);\n}\n", + "a {\n color: 1, 2, 3, 4, 5;\n}\n" +); +test!( + function_varargs_are_comma_separated, + "@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" +); diff --git a/tests/meta.rs b/tests/meta.rs index 7f2c2c2..3519f0f 100644 --- a/tests/meta.rs +++ b/tests/meta.rs @@ -164,6 +164,11 @@ test!( "a {\n color: type-of(hi + bye)\n}\n", "a {\n color: string;\n}\n" ); +test!( + type_of_arglist, + "@mixin foo($a...) {color: type-of($a);}\na {@include foo(1, 2, 3, 4, 5);}", + "a {\n color: arglist;\n}\n" +); test!( unitless_px, "a {\n color: unitless(1px)\n}\n",