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)?;
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<Value> {
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(

View File

@ -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,10 +199,54 @@ 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),
}
}
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())
@ -212,6 +258,8 @@ impl<'a> Parser<'a> {
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<Spanned<Value>> {
#[allow(clippy::unused_self)]
pub fn positional_arg(&self, args: &mut CallArgs, position: usize) -> Option<Spanned<Value>> {
args.get_positional(position)
}
#[allow(dead_code)]
fn named_arg(&mut self, args: &mut CallArgs, name: &'static str) -> Option<Spanned<Value>> {
#[allow(dead_code, clippy::unused_self)]
fn named_arg(&self, args: &mut CallArgs, name: &'static str) -> Option<Spanned<Value>> {
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<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 args = match args
.0

View File

@ -48,13 +48,18 @@ impl SassMap {
self.0.into_iter().map(|(.., v)| v).collect()
}
pub fn entries(self) -> Vec<Value> {
pub fn as_list(self) -> Vec<Value> {
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 {

View File

@ -316,7 +316,7 @@ impl Value {
pub fn as_list(self) -> Vec<Value> {
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],
}

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