Initial implementation of nested styles

This commit is contained in:
ConnorSkees 2020-02-01 19:33:56 -05:00
parent b1d887ee3d
commit 8e42d73c1e
4 changed files with 192 additions and 18 deletions

View File

@ -58,7 +58,10 @@ use crate::lexer::Lexer;
use crate::mixin::{eat_include, Mixin};
use crate::selector::{Attribute, Selector};
use crate::style::Style;
use crate::utils::{devour_whitespace, eat_variable_value, IsComment, IsWhitespace, VariableDecl};
use crate::utils::{
devour_whitespace, eat_variable_value, parse_interpolation, IsComment, IsWhitespace,
VariableDecl,
};
use crate::value::Value;
mod args;
@ -198,6 +201,8 @@ pub(crate) struct RuleSet {
enum Expr {
/// A style: `color: red`
Style(Style),
/// Several styles
Styles(Vec<Style>),
/// A collection of styles, from a mixin or function
// Styles(Vec<Style>),
/// A full selector `a > h1`
@ -450,6 +455,7 @@ impl<'a> StyleSheetParser<'a> {
{
match expr {
Expr::Style(s) => stmts.push(Stmt::Style(s)),
Expr::Styles(s) => stmts.extend(s.into_iter().map(|s| Stmt::Style(s))),
Expr::MixinDecl(name, mixin) => {
scope.mixins.insert(name, mixin);
}
@ -487,6 +493,149 @@ impl<'a> StyleSheetParser<'a> {
}
}
pub(crate) fn eat_style_group<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
super_property: String,
) -> Result<Option<Expr>, (Pos, String)> {
let mut styles = Vec::new();
devour_whitespace(toks);
while let Some(tok) = toks.peek() {
match tok.kind {
TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
toks.next();
devour_whitespace(toks);
loop {
let property =
parse_property(toks, scope, super_selector, super_property.clone());
if let Some(tok) = toks.peek() {
match tok.kind {
TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
if let Some(Expr::Styles(s)) =
eat_style_group(toks, scope, super_selector, property)?
{
styles.extend(s);
}
devour_whitespace(toks);
if let Some(tok) = toks.peek() {
match tok.kind {
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
toks.next();
devour_whitespace(toks);
return Ok(Some(Expr::Styles(styles)));
}
_ => continue,
}
}
continue;
}
_ => {}
}
}
let value = parse_style_value(toks, scope, super_selector);
styles.push(Style { property, value });
if let Some(tok) = toks.peek() {
match tok.kind {
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
toks.next();
devour_whitespace(toks);
return Ok(Some(Expr::Styles(styles)));
}
_ => continue,
}
}
}
}
_ => {
let val = parse_style_value(toks, scope, super_selector);
return Ok(Some(Expr::Style(Style {
property: super_property,
value: val,
})));
}
}
}
Ok(Some(Expr::Styles(styles)))
}
pub(crate) fn parse_style_value<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
) -> Value {
let mut style = Vec::new();
let mut n = 0;
devour_whitespace(toks);
while let Some(tok) = toks.peek() {
dbg!(&tok.pos);
match tok.kind {
TokenKind::MultilineComment(_) => {
toks.next();
continue;
}
TokenKind::Symbol(Symbol::OpenCurlyBrace) | TokenKind::Interpolation => n += 1,
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
if n == 0 {
break;
} else {
// todo: toks.next() and push
n -= 1;
}
}
TokenKind::Symbol(Symbol::SemiColon) => {
toks.next();
break;
}
TokenKind::Symbol(Symbol::BitAnd) => {
style.push(Token {
kind: TokenKind::Ident(super_selector.to_string()),
pos: Pos::new(),
});
toks.next();
continue;
}
_ => {}
};
style.push(toks.next().unwrap());
}
devour_whitespace(toks);
Value::from_tokens(&mut style.into_iter().peekable(), scope).unwrap()
}
pub(crate) fn parse_property<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
mut super_property: String,
) -> String {
let mut property = String::new();
while let Some(Token { kind, .. }) = toks.next() {
match kind {
TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => continue,
TokenKind::Ident(ref s) => property.push_str(s),
TokenKind::Interpolation => property.push_str(
&parse_interpolation(toks, scope)
.iter()
.map(|x| x.kind.to_string())
.collect::<String>(),
),
TokenKind::Symbol(Symbol::Colon) => break,
TokenKind::Symbol(Symbol::BitAnd) => property.push_str(&super_selector.to_string()),
_ => property.push_str(&kind.to_string()),
};
}
devour_whitespace(toks);
if !super_property.is_empty() {
super_property.reserve(1 + property.len());
super_property.push('-');
super_property.push_str(&property);
super_property
} else {
property
}
}
pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
@ -495,6 +644,20 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
let mut values = Vec::with_capacity(5);
while let Some(tok) = toks.peek() {
match &tok.kind {
TokenKind::Symbol(Symbol::Colon) => {
let tok = toks.next();
if devour_whitespace(toks) {
let prop = parse_property(
&mut values.into_iter().peekable(),
scope,
super_selector,
String::new(),
);
return eat_style_group(toks, scope, super_selector, prop);
} else {
values.push(tok.unwrap());
}
}
TokenKind::Symbol(Symbol::SemiColon) => {
toks.next();
devour_whitespace(toks);

View File

@ -94,6 +94,7 @@ impl Mixin {
while let Some(expr) = eat_expr(&mut self.body, &self.scope, super_selector)? {
match expr {
Expr::Style(s) => stmts.push(Stmt::Style(s)),
Expr::Styles(s) => stmts.extend(s.into_iter().map(|s| Stmt::Style(s))),
Expr::Include(s) => stmts.extend(s),
Expr::MixinDecl(..) | Expr::FunctionDecl(..) | Expr::Debug(..) | Expr::Warn(..) => {
todo!()

View File

@ -9,8 +9,8 @@ use std::vec::IntoIter;
/// A style: `color: red`
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Style {
property: String,
value: Value,
pub property: String,
pub value: Value,
}
impl Display for Style {

View File

@ -130,21 +130,6 @@ mod test_variables {
"a {\n $a: red\n}\n\nb {\n color: blue;\n}\n",
"b {\n color: blue;\n}\n"
);
test!(
combinator_following,
"a + {\n b {\n color: red;\n }\n}\n",
"a + b {\n color: red;\n}\n"
);
test!(
combinator_preceding,
"a {\n + b {\n color: red;\n }\n}\n",
"a + b {\n color: red;\n}\n"
);
test!(
combinator_alone,
"a {\n + {\n b {\n color: red;\n }\n}\n",
"a + b {\n color: red;\n}\n"
);
}
mod test_selectors {
@ -363,6 +348,21 @@ mod test_selectors {
"div,, , span, ,, {\n color: red;\n}\n",
"div, span {\n color: red;\n}\n"
);
test!(
combinator_following,
"a + {\n b {\n color: red;\n }\n}\n",
"a + b {\n color: red;\n}\n"
);
test!(
combinator_preceding,
"a {\n + b {\n color: red;\n }\n}\n",
"a + b {\n color: red;\n}\n"
);
test!(
combinator_alone,
"a {\n + {\n b {\n color: red;\n }\n}\n",
"a + b {\n color: red;\n}\n"
);
}
mod test_units {
@ -538,6 +538,16 @@ mod test_styles {
"a {\n b {\n c {}\n foo: bar;\n }\n}\n",
"a b {\n foo: bar;\n}\n"
);
test!(
single_nested_styles,
"a {\n webkit: {\n color: red;\n color: orange\n }\n}\n",
"a {\n webkit-color: red;\n webkit-color: orange;\n}\n"
);
test!(
multiple_nested_styles,
"a {\n webkit: {\n webkit: {\n color: red;\n }\n }\n}\n",
"a {\n webkit-webkit-color: red;\n}\n"
);
}
mod test_misc {