initial implementation of @each

This commit is contained in:
ConnorSkees 2020-03-31 01:00:25 -04:00
parent e5b7043480
commit f7d9d4432a
3 changed files with 120 additions and 9 deletions

View File

@ -3,7 +3,7 @@ use std::iter::Peekable;
use num_traits::cast::ToPrimitive;
use crate::common::Pos;
use crate::common::{Brackets, ListSeparator, Pos};
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
@ -39,6 +39,7 @@ pub(crate) enum AtRule {
Content,
Unknown(UnknownAtRule),
For(Vec<Stmt>),
Each(Vec<Stmt>),
If(If),
}
@ -103,7 +104,82 @@ impl AtRule {
devour_whitespace(toks);
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::If => AtRule::If(If::from_tokens(toks)?),
AtRuleKind::Else => todo!("@else not yet implemented"),

View File

@ -382,7 +382,7 @@ impl<'a> StyleSheetParser<'a> {
AtRule::Return(_) => {
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::If(i) => {
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
@ -412,7 +412,7 @@ impl<'a> StyleSheetParser<'a> {
match expr {
Expr::Style(s) => stmts.push(Stmt::Style(s)),
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::Content => {
return Err("@content is only allowed within mixin declarations.".into())
@ -558,11 +558,9 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
continue;
} else if values.is_empty() && peeked.kind == '*' {
toks.next();
return Ok(Some(Expr::MultilineComment(eat_comment(
toks,
scope,
super_selector,
)?)));
let comment = eat_comment(toks, scope, super_selector)?;
devour_whitespace(toks);
return Ok(Some(Expr::MultilineComment(comment)));
} else {
values.push(tok);
}
@ -593,6 +591,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
c @ AtRule::Content => Ok(Some(Expr::AtRule(c))),
f @ AtRule::If(..) => 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))),
};
}

36
tests/each.rs Normal file
View 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"
// );