add support for splats
This commit is contained in:
parent
94d94abf7a
commit
f437649103
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
55
tests/splat.rs
Normal 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 \")\"."
|
||||||
|
);
|
Loading…
x
Reference in New Issue
Block a user