add support for splats

This commit is contained in:
Connor Skees 2020-07-02 17:25:52 -04:00
parent 94d94abf7a
commit f437649103
5 changed files with 134 additions and 25 deletions

View File

@ -76,7 +76,7 @@ fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(3)?; args.max_args(3)?;
let (mut list, sep, brackets) = match parser.arg(&mut args, 0, "list")? { let (mut list, sep, brackets) = match parser.arg(&mut args, 0, "list")? {
Value::List(v, sep, b) => (v, sep, b), 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), v => (vec![v], ListSeparator::Space, Brackets::None),
}; };
let n = match parser.arg(&mut args, 1, "n")? { let n = match parser.arg(&mut args, 1, "n")? {
@ -165,12 +165,12 @@ fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(4)?; args.max_args(4)?;
let (mut list1, sep1, brackets) = match parser.arg(&mut args, 0, "list1")? { let (mut list1, sep1, brackets) = match parser.arg(&mut args, 0, "list1")? {
Value::List(v, sep, brackets) => (v, sep, brackets), 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), v => (vec![v], ListSeparator::Space, Brackets::None),
}; };
let (list2, sep2) = match parser.arg(&mut args, 1, "list2")? { let (list2, sep2) = match parser.arg(&mut args, 1, "list2")? {
Value::List(v, sep, ..) => (v, sep), 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), v => (vec![v], ListSeparator::Space),
}; };
let sep = match parser.default_arg( let sep = match parser.default_arg(

View File

@ -167,6 +167,8 @@ impl<'a> Parser<'a> {
} }
self.whitespace_or_comment(); self.whitespace_or_comment();
let mut is_splat = false;
while let Some(tok) = self.toks.next() { while let Some(tok) = self.toks.next() {
match tok.kind { match tok.kind {
')' => { ')' => {
@ -197,21 +199,67 @@ impl<'a> Parser<'a> {
val.push(tok); val.push(tok);
val.extend(read_until_closing_quote(self.toks, tok.kind)?); 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), _ => val.push(tok),
} }
} }
args.insert( if is_splat {
if name.is_empty() { let val = {
CallArg::Positional(args.len())
} else {
CallArg::Named(name.as_str().into())
},
{
let val = self.parse_value_from_vec(mem::take(&mut val))?; let val = self.parse_value_from_vec(mem::take(&mut val))?;
val.node.eval(val.span)? 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(); self.whitespace();
if self.toks.peek().is_none() { if self.toks.peek().is_none() {
@ -222,8 +270,9 @@ impl<'a> Parser<'a> {
} }
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
#[allow(clippy::unused_self)]
pub fn arg( pub fn arg(
&mut self, &self,
args: &mut CallArgs, args: &mut CallArgs,
position: usize, position: usize,
name: &'static str, name: &'static str,
@ -231,8 +280,9 @@ impl<'a> Parser<'a> {
Ok(args.get_err(position, name)?.node) Ok(args.get_err(position, name)?.node)
} }
#[allow(clippy::unused_self)]
pub fn default_arg( pub fn default_arg(
&mut self, &self,
args: &mut CallArgs, args: &mut CallArgs,
position: usize, position: usize,
name: &'static str, name: &'static str,
@ -244,21 +294,19 @@ impl<'a> Parser<'a> {
} }
} }
pub fn positional_arg( #[allow(clippy::unused_self)]
&mut self, pub fn positional_arg(&self, args: &mut CallArgs, position: usize) -> Option<Spanned<Value>> {
args: &mut CallArgs,
position: usize,
) -> Option<Spanned<Value>> {
args.get_positional(position) args.get_positional(position)
} }
#[allow(dead_code)] #[allow(dead_code, clippy::unused_self)]
fn named_arg(&mut self, args: &mut CallArgs, name: &'static str) -> Option<Spanned<Value>> { fn named_arg(&self, args: &mut CallArgs, name: &'static str) -> Option<Spanned<Value>> {
args.get_named(name) args.get_named(name)
} }
#[allow(clippy::unused_self)]
pub fn default_named_arg( pub fn default_named_arg(
&mut self, &self,
args: &mut CallArgs, args: &mut CallArgs,
name: &'static str, name: &'static str,
default: Value, default: Value,
@ -269,7 +317,8 @@ impl<'a> Parser<'a> {
} }
} }
pub fn variadic_args(&mut self, args: CallArgs) -> SassResult<Vec<Spanned<Value>>> { #[allow(clippy::unused_self)]
pub fn variadic_args(&self, args: CallArgs) -> SassResult<Vec<Spanned<Value>>> {
let mut vals = Vec::new(); let mut vals = Vec::new();
let mut args = match args let mut args = match args
.0 .0

View File

@ -48,13 +48,18 @@ impl SassMap {
self.0.into_iter().map(|(.., v)| v).collect() self.0.into_iter().map(|(.., v)| v).collect()
} }
pub fn entries(self) -> Vec<Value> { pub fn as_list(self) -> Vec<Value> {
self.0 self.0
.into_iter() .into_iter()
.map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None)) .map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None))
.collect() .collect()
} }
#[allow(clippy::missing_const_for_fn)]
pub fn entries(self) -> Vec<(Value, Value)> {
self.0
}
/// Returns true if the key already exists /// Returns true if the key already exists
pub fn insert(&mut self, key: Value, value: Value) -> bool { pub fn insert(&mut self, key: Value, value: Value) -> bool {
for (ref k, ref mut v) in &mut self.0 { for (ref k, ref mut v) in &mut self.0 {

View File

@ -316,7 +316,7 @@ impl Value {
pub fn as_list(self) -> Vec<Value> { pub fn as_list(self) -> Vec<Value> {
match self { match self {
Value::List(v, ..) => v, 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(), Value::ArgList(v) => v.into_iter().map(|val| val.node).collect(),
v => vec![v], v => vec![v],
} }

55
tests/splat.rs Normal file
View File

@ -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 \")\"."
);