From f437649103d6b3b8c8c5783d9afa23012dca54b5 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 2 Jul 2020 17:25:52 -0400 Subject: [PATCH] add support for splats --- src/builtin/list.rs | 6 +-- src/parse/args.rs | 89 +++++++++++++++++++++++++++++++++++---------- src/value/map.rs | 7 +++- src/value/mod.rs | 2 +- tests/splat.rs | 55 ++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 tests/splat.rs diff --git a/src/builtin/list.rs b/src/builtin/list.rs index 17ebe2c..e1a9149 100644 --- a/src/builtin/list.rs +++ b/src/builtin/list.rs @@ -76,7 +76,7 @@ fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let (mut list, sep, brackets) = match parser.arg(&mut args, 0, "list")? { Value::List(v, sep, b) => (v, sep, b), - Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None), + Value::Map(m) => (m.as_list(), ListSeparator::Comma, Brackets::None), v => (vec![v], ListSeparator::Space, Brackets::None), }; let n = match parser.arg(&mut args, 1, "n")? { @@ -165,12 +165,12 @@ fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(4)?; let (mut list1, sep1, brackets) = match parser.arg(&mut args, 0, "list1")? { Value::List(v, sep, brackets) => (v, sep, brackets), - Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None), + Value::Map(m) => (m.as_list(), ListSeparator::Comma, Brackets::None), v => (vec![v], ListSeparator::Space, Brackets::None), }; let (list2, sep2) = match parser.arg(&mut args, 1, "list2")? { Value::List(v, sep, ..) => (v, sep), - Value::Map(m) => (m.entries(), ListSeparator::Comma), + Value::Map(m) => (m.as_list(), ListSeparator::Comma), v => (vec![v], ListSeparator::Space), }; let sep = match parser.default_arg( diff --git a/src/parse/args.rs b/src/parse/args.rs index d602a1c..391cb11 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -167,6 +167,8 @@ impl<'a> Parser<'a> { } self.whitespace_or_comment(); + let mut is_splat = false; + while let Some(tok) = self.toks.next() { match tok.kind { ')' => { @@ -197,21 +199,67 @@ impl<'a> Parser<'a> { val.push(tok); val.extend(read_until_closing_quote(self.toks, tok.kind)?); } + '.' => { + if let Some(Token { kind: '.', pos }) = self.toks.peek().cloned() { + if !name.is_empty() { + return Err(("expected \")\".", pos).into()); + } + self.toks.next(); + if let Some(Token { kind: '.', .. }) = self.toks.peek() { + self.toks.next(); + is_splat = true; + break; + } else { + return Err(("expected \".\".", pos).into()); + } + } else { + val.push(tok); + } + } _ => val.push(tok), } } - args.insert( - if name.is_empty() { - CallArg::Positional(args.len()) - } else { - CallArg::Named(name.as_str().into()) - }, - { + if is_splat { + let val = { let val = self.parse_value_from_vec(mem::take(&mut val))?; val.node.eval(val.span)? - }, - ); + }; + match val.node { + Value::ArgList(v) => { + for arg in v { + args.insert(CallArg::Positional(args.len()), arg); + } + } + Value::List(v, ..) => { + for arg in v { + args.insert(CallArg::Positional(args.len()), arg.eval(val.span)?); + } + } + Value::Map(v) => { + for (name, arg) in v.entries() { + let name = name.to_css_string(val.span)?.to_string(); + args.insert(CallArg::Named(name.into()), arg.eval(val.span)?); + } + } + _ => { + args.insert(CallArg::Positional(args.len()), val); + } + } + } else { + args.insert( + if name.is_empty() { + CallArg::Positional(args.len()) + } else { + CallArg::Named(name.as_str().into()) + }, + { + let val = self.parse_value_from_vec(mem::take(&mut val))?; + val.node.eval(val.span)? + }, + ); + } + self.whitespace(); if self.toks.peek().is_none() { @@ -222,8 +270,9 @@ impl<'a> Parser<'a> { } impl<'a> Parser<'a> { + #[allow(clippy::unused_self)] pub fn arg( - &mut self, + &self, args: &mut CallArgs, position: usize, name: &'static str, @@ -231,8 +280,9 @@ impl<'a> Parser<'a> { Ok(args.get_err(position, name)?.node) } + #[allow(clippy::unused_self)] pub fn default_arg( - &mut self, + &self, args: &mut CallArgs, position: usize, name: &'static str, @@ -244,21 +294,19 @@ impl<'a> Parser<'a> { } } - pub fn positional_arg( - &mut self, - args: &mut CallArgs, - position: usize, - ) -> Option> { + #[allow(clippy::unused_self)] + pub fn positional_arg(&self, args: &mut CallArgs, position: usize) -> Option> { args.get_positional(position) } - #[allow(dead_code)] - fn named_arg(&mut self, args: &mut CallArgs, name: &'static str) -> Option> { + #[allow(dead_code, clippy::unused_self)] + fn named_arg(&self, args: &mut CallArgs, name: &'static str) -> Option> { args.get_named(name) } + #[allow(clippy::unused_self)] pub fn default_named_arg( - &mut self, + &self, args: &mut CallArgs, name: &'static str, default: Value, @@ -269,7 +317,8 @@ impl<'a> Parser<'a> { } } - pub fn variadic_args(&mut self, args: CallArgs) -> SassResult>> { + #[allow(clippy::unused_self)] + pub fn variadic_args(&self, args: CallArgs) -> SassResult>> { let mut vals = Vec::new(); let mut args = match args .0 diff --git a/src/value/map.rs b/src/value/map.rs index 3e1fbd5..bf216be 100644 --- a/src/value/map.rs +++ b/src/value/map.rs @@ -48,13 +48,18 @@ impl SassMap { self.0.into_iter().map(|(.., v)| v).collect() } - pub fn entries(self) -> Vec { + pub fn as_list(self) -> Vec { self.0 .into_iter() .map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None)) .collect() } + #[allow(clippy::missing_const_for_fn)] + pub fn entries(self) -> Vec<(Value, Value)> { + self.0 + } + /// Returns true if the key already exists pub fn insert(&mut self, key: Value, value: Value) -> bool { for (ref k, ref mut v) in &mut self.0 { diff --git a/src/value/mod.rs b/src/value/mod.rs index 9401161..66a786e 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -316,7 +316,7 @@ impl Value { pub fn as_list(self) -> Vec { match self { Value::List(v, ..) => v, - Value::Map(m) => m.entries(), + Value::Map(m) => m.as_list(), Value::ArgList(v) => v.into_iter().map(|val| val.node).collect(), v => vec![v], } diff --git a/tests/splat.rs b/tests/splat.rs new file mode 100644 index 0000000..e36e664 --- /dev/null +++ b/tests/splat.rs @@ -0,0 +1,55 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + splat_list_two_elements, + "@function foo($a, $b) { + @return $a+$b; + } + a { + color: foo([1, 2]...); + }", + "a {\n color: 3;\n}\n" +); +test!( + splat_map_single_key, + "@function foo($a) { + @return $a; + } + a { + color: foo((a: b)...); + }", + "a {\n color: b;\n}\n" +); +test!( + splat_single_value, + "@function foo($a) { + @return $a; + } + a { + color: foo(1...); + }", + "a {\n color: 1;\n}\n" +); +error!( + splat_missing_last_period, + "@function foo($a) { + @return $a; + } + a { + color: foo(1..); + }", + "Error: expected \".\"." +); +error!( + splat_with_named_arg, + "@function foo($a) { + @return $a; + } + a { + color: foo($a: 1...); + }", + "Error: expected \")\"." +);