initial implementation of @each
This commit is contained in:
parent
e5b7043480
commit
f7d9d4432a
@ -3,7 +3,7 @@ use std::iter::Peekable;
|
|||||||
|
|
||||||
use num_traits::cast::ToPrimitive;
|
use num_traits::cast::ToPrimitive;
|
||||||
|
|
||||||
use crate::common::Pos;
|
use crate::common::{Brackets, ListSeparator, Pos};
|
||||||
use crate::error::SassResult;
|
use crate::error::SassResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::selector::Selector;
|
use crate::selector::Selector;
|
||||||
@ -39,6 +39,7 @@ pub(crate) enum AtRule {
|
|||||||
Content,
|
Content,
|
||||||
Unknown(UnknownAtRule),
|
Unknown(UnknownAtRule),
|
||||||
For(Vec<Stmt>),
|
For(Vec<Stmt>),
|
||||||
|
Each(Vec<Stmt>),
|
||||||
If(If),
|
If(If),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +104,82 @@ impl AtRule {
|
|||||||
devour_whitespace(toks);
|
devour_whitespace(toks);
|
||||||
AtRule::Charset
|
AtRule::Charset
|
||||||
}
|
}
|
||||||
AtRuleKind::Each => todo!("@each not yet implemented"),
|
AtRuleKind::Each => {
|
||||||
|
let mut stmts = Vec::new();
|
||||||
|
devour_whitespace(toks);
|
||||||
|
let mut vars = Vec::new();
|
||||||
|
loop {
|
||||||
|
match toks.next().ok_or("expected \"$\".")?.kind {
|
||||||
|
'$' => vars.push(eat_ident(toks, scope, super_selector)?),
|
||||||
|
_ => return Err("expected \"$\".".into()),
|
||||||
|
}
|
||||||
|
devour_whitespace(toks);
|
||||||
|
if toks.peek().ok_or("expected \"$\".")?.kind == ',' {
|
||||||
|
toks.next();
|
||||||
|
devour_whitespace(toks);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if toks.peek().is_none()
|
||||||
|
|| eat_ident(toks, scope, super_selector)?.to_ascii_lowercase() != "in"
|
||||||
|
{
|
||||||
|
return Err("Expected \"in\".".into());
|
||||||
|
}
|
||||||
|
devour_whitespace(toks);
|
||||||
|
let iterator = match Value::from_tokens(
|
||||||
|
&mut read_until_open_curly_brace(toks).into_iter().peekable(),
|
||||||
|
scope,
|
||||||
|
super_selector,
|
||||||
|
)? {
|
||||||
|
Value::List(v, ..) => v,
|
||||||
|
Value::Map(m) => m
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None))
|
||||||
|
.collect(),
|
||||||
|
v => vec![v],
|
||||||
|
};
|
||||||
|
toks.next();
|
||||||
|
devour_whitespace(toks);
|
||||||
|
let mut body = read_until_closing_curly_brace(toks);
|
||||||
|
body.push(toks.next().unwrap());
|
||||||
|
devour_whitespace(toks);
|
||||||
|
|
||||||
|
for row in iterator {
|
||||||
|
let this_iterator = match row {
|
||||||
|
Value::List(v, ..) => v,
|
||||||
|
Value::Map(m) => m
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
Value::List(vec![k, v], ListSeparator::Space, Brackets::None)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
v => vec![v],
|
||||||
|
};
|
||||||
|
|
||||||
|
if vars.len() == 1 {
|
||||||
|
scope.insert_var(
|
||||||
|
&vars[0],
|
||||||
|
Value::List(this_iterator, ListSeparator::Space, Brackets::None),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
for (var, val) in vars.clone().into_iter().zip(
|
||||||
|
this_iterator
|
||||||
|
.into_iter()
|
||||||
|
.chain(std::iter::once(Value::Null).cycle()),
|
||||||
|
) {
|
||||||
|
scope.insert_var(&var, val)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmts.extend(eat_stmts(
|
||||||
|
&mut body.clone().into_iter().peekable(),
|
||||||
|
scope,
|
||||||
|
super_selector,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
AtRule::Each(stmts)
|
||||||
|
}
|
||||||
AtRuleKind::Extend => todo!("@extend not yet implemented"),
|
AtRuleKind::Extend => todo!("@extend not yet implemented"),
|
||||||
AtRuleKind::If => AtRule::If(If::from_tokens(toks)?),
|
AtRuleKind::If => AtRule::If(If::from_tokens(toks)?),
|
||||||
AtRuleKind::Else => todo!("@else not yet implemented"),
|
AtRuleKind::Else => todo!("@else not yet implemented"),
|
||||||
|
13
src/lib.rs
13
src/lib.rs
@ -382,7 +382,7 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
AtRule::Return(_) => {
|
AtRule::Return(_) => {
|
||||||
return Err("This at-rule is not allowed here.".into())
|
return Err("This at-rule is not allowed here.".into())
|
||||||
}
|
}
|
||||||
AtRule::For(s) => rules.extend(s),
|
AtRule::Each(s) | AtRule::For(s) => rules.extend(s),
|
||||||
AtRule::Content => return Err("@content is only allowed within mixin declarations.".into()),
|
AtRule::Content => return Err("@content is only allowed within mixin declarations.".into()),
|
||||||
AtRule::If(i) => {
|
AtRule::If(i) => {
|
||||||
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
|
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
|
||||||
@ -412,7 +412,7 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
match expr {
|
match expr {
|
||||||
Expr::Style(s) => stmts.push(Stmt::Style(s)),
|
Expr::Style(s) => stmts.push(Stmt::Style(s)),
|
||||||
Expr::AtRule(a) => match a {
|
Expr::AtRule(a) => match a {
|
||||||
AtRule::For(s) => stmts.extend(s),
|
AtRule::Each(s) | AtRule::For(s) => stmts.extend(s),
|
||||||
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?),
|
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?),
|
||||||
AtRule::Content => {
|
AtRule::Content => {
|
||||||
return Err("@content is only allowed within mixin declarations.".into())
|
return Err("@content is only allowed within mixin declarations.".into())
|
||||||
@ -558,11 +558,9 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
|||||||
continue;
|
continue;
|
||||||
} else if values.is_empty() && peeked.kind == '*' {
|
} else if values.is_empty() && peeked.kind == '*' {
|
||||||
toks.next();
|
toks.next();
|
||||||
return Ok(Some(Expr::MultilineComment(eat_comment(
|
let comment = eat_comment(toks, scope, super_selector)?;
|
||||||
toks,
|
devour_whitespace(toks);
|
||||||
scope,
|
return Ok(Some(Expr::MultilineComment(comment)));
|
||||||
super_selector,
|
|
||||||
)?)));
|
|
||||||
} else {
|
} else {
|
||||||
values.push(tok);
|
values.push(tok);
|
||||||
}
|
}
|
||||||
@ -593,6 +591,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
|||||||
c @ AtRule::Content => Ok(Some(Expr::AtRule(c))),
|
c @ AtRule::Content => Ok(Some(Expr::AtRule(c))),
|
||||||
f @ AtRule::If(..) => Ok(Some(Expr::AtRule(f))),
|
f @ AtRule::If(..) => Ok(Some(Expr::AtRule(f))),
|
||||||
f @ AtRule::For(..) => Ok(Some(Expr::AtRule(f))),
|
f @ AtRule::For(..) => Ok(Some(Expr::AtRule(f))),
|
||||||
|
f @ AtRule::Each(..) => Ok(Some(Expr::AtRule(f))),
|
||||||
u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))),
|
u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
36
tests/each.rs
Normal file
36
tests/each.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
test!(
|
||||||
|
each_space_separated_inner,
|
||||||
|
"a {\n @each $i in 1 2 3 {\n color: $i;\n }\n}\n",
|
||||||
|
"a {\n color: 1;\n color: 2;\n color: 3;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
each_space_separated_outer,
|
||||||
|
"@each $i in 1 2 3 {\n a {\n color: $i;\n }\n}\n",
|
||||||
|
"a {\n color: 1;\n}\n\na {\n color: 2;\n}\n\na {\n color: 3;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
each_two_variables_one_null,
|
||||||
|
"a {\n @each $i, $c in 1 2 3 {\n color: $i;\n }\n}\n",
|
||||||
|
"a {\n color: 1;\n color: 2;\n color: 3;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
each_one_var_in_one_map,
|
||||||
|
"a {\n @each $i in (a: b) {\n color: $i;\n }\n}\n",
|
||||||
|
"a {\n color: a b;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
each_two_vars_in_one_map,
|
||||||
|
"a {\n @each $i, $c in (a: b) {\n color: $i;\n }\n}\n",
|
||||||
|
"a {\n color: a;\n}\n"
|
||||||
|
);
|
||||||
|
// blocked on better parsing of comma and space separated lists
|
||||||
|
// test!(
|
||||||
|
// each_two_vars_in_3_2_list,
|
||||||
|
// "a {\n @each $i, $c in (1 2 3, 4 5) {\n color: $i, $c;\n }\n}\n",
|
||||||
|
// "a {\n color: 1, 2;\n color: 4, 5;\n}\n"
|
||||||
|
// );
|
Loading…
x
Reference in New Issue
Block a user