diff --git a/src/lib.rs b/src/lib.rs index d5a9aa5..aba7c2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,10 +58,7 @@ 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, parse_interpolation, IsComment, IsWhitespace, - VariableDecl, -}; +use crate::utils::{devour_whitespace, eat_variable_value, IsComment, IsWhitespace, VariableDecl}; use crate::value::Value; mod args; @@ -493,148 +490,6 @@ impl<'a> StyleSheetParser<'a> { } } -pub(crate) fn eat_style_group>( - toks: &mut Peekable, - scope: &Scope, - super_selector: &Selector, - super_property: String, -) -> Result, (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>( - toks: &mut Peekable, - scope: &Scope, - super_selector: &Selector, -) -> Value { - let mut style = Vec::new(); - let mut n = 0; - devour_whitespace(toks); - while let Some(tok) = toks.peek() { - 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>( - toks: &mut Peekable, - 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::(), - ), - 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>( toks: &mut Peekable, scope: &Scope, @@ -646,13 +501,13 @@ pub(crate) fn eat_expr>( TokenKind::Symbol(Symbol::Colon) => { let tok = toks.next(); if devour_whitespace(toks) { - let prop = parse_property( + let prop = Style::parse_property( &mut values.into_iter().peekable(), scope, super_selector, String::new(), ); - return eat_style_group(toks, scope, super_selector, prop); + return Ok(Some(Style::from_tokens(toks, scope, super_selector, prop)?)); } else { values.push(tok.unwrap()); } @@ -660,10 +515,12 @@ pub(crate) fn eat_expr>( TokenKind::Symbol(Symbol::SemiColon) => { toks.next(); devour_whitespace(toks); - return Ok(Some(Expr::Style(match Style::from_tokens(values, scope) { - Ok(x) => x, - Err(_) => return Ok(None), - }))); + // special edge case where there was no space between the colon + // in a style `color:red`. todo: refactor + let mut v = values.clone().into_iter().peekable(); + let property = Style::parse_property(&mut v, scope, super_selector, String::new()); + let value = Style::parse_value(&mut v, scope, super_selector); + return Ok(Some(Expr::Style(Style { property, value }))); } TokenKind::Symbol(Symbol::CloseCurlyBrace) => { if values.is_empty() { @@ -671,10 +528,6 @@ pub(crate) fn eat_expr>( devour_whitespace(toks); return Ok(None); } - return Ok(Some(Expr::Style(match Style::from_tokens(values, scope) { - Ok(x) => x, - Err(_) => return Ok(None), - }))); } TokenKind::Symbol(Symbol::OpenCurlyBrace) => { toks.next(); diff --git a/src/style.rs b/src/style.rs index 4110f04..e7746b6 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,10 +1,10 @@ -use crate::common::{Scope, Symbol}; -use crate::utils::{devour_whitespace_or_comment, parse_interpolation}; +use crate::common::{Pos, Scope, Symbol}; +use crate::selector::Selector; +use crate::utils::{devour_whitespace, parse_interpolation}; use crate::value::Value; -use crate::{Token, TokenKind}; +use crate::{Expr, Token, TokenKind}; use std::fmt::{self, Display}; use std::iter::Peekable; -use std::vec::IntoIter; /// A style: `color: red` #[derive(Clone, Debug, Eq, PartialEq)] @@ -20,47 +20,181 @@ impl Display for Style { } impl Style { - pub fn from_tokens(tokens: Vec, scope: &Scope) -> Result { - Ok(StyleParser::new(tokens, scope)?.parse()) + pub fn parse_property>( + toks: &mut Peekable, + scope: &Scope, + super_selector: &Selector, + super_property: String, + ) -> String { + StyleParser::new(scope, super_selector).parse_property(toks, super_property) + } + + pub fn parse_value>( + toks: &mut Peekable, + scope: &Scope, + super_selector: &Selector, + ) -> Value { + StyleParser::new(scope, super_selector).parse_style_value(toks) + } + + pub fn from_tokens>( + toks: &mut Peekable, + scope: &Scope, + super_selector: &Selector, + super_property: String, + ) -> Result { + StyleParser::new(scope, super_selector).eat_style_group(toks, super_property) } } struct StyleParser<'a> { - tokens: Peekable>, scope: &'a Scope, + super_selector: &'a Selector, } impl<'a> StyleParser<'a> { - fn new(tokens: Vec, scope: &'a Scope) -> Result { - if tokens.is_empty() { - return Err(()); + fn new(scope: &'a Scope, super_selector: &'a Selector) -> Self { + StyleParser { + scope, + super_selector, } - let tokens = tokens.into_iter().peekable(); - Ok(StyleParser { tokens, scope }) } - fn parse(&mut self) -> Style { + pub(crate) fn parse_style_value>( + &self, + toks: &mut Peekable, + ) -> Value { + let mut style = Vec::new(); + let mut n = 0; + devour_whitespace(toks); + while let Some(tok) = toks.peek() { + 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(self.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(), self.scope).unwrap() + } + + pub(crate) fn eat_style_group>( + &self, + toks: &mut Peekable, + super_property: String, + ) -> Result { + 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 = self.parse_property(toks, super_property.clone()); + if let Some(tok) = toks.peek() { + match tok.kind { + TokenKind::Symbol(Symbol::OpenCurlyBrace) => { + if let Expr::Styles(s) = self.eat_style_group(toks, 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(Expr::Styles(styles)); + } + _ => continue, + } + } + continue; + } + _ => {} + } + } + let value = self.parse_style_value(toks); + 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(Expr::Styles(styles)); + } + _ => continue, + } + } + } + } + _ => { + let val = self.parse_style_value(toks); + return Ok(Expr::Style(Style { + property: super_property, + value: val, + })); + } + } + } + Ok(Expr::Styles(styles)) + } + + pub(crate) fn parse_property>( + &self, + toks: &mut Peekable, + mut super_property: String, + ) -> String { let mut property = String::new(); - // read property until `:` - while let Some(Token { kind, .. }) = self.tokens.next() { + 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(&mut self.tokens, self.scope) + &parse_interpolation(toks, self.scope) .iter() .map(|x| x.kind.to_string()) .collect::(), ), TokenKind::Symbol(Symbol::Colon) => break, + TokenKind::Symbol(Symbol::BitAnd) => { + property.push_str(&self.super_selector.to_string()) + } _ => property.push_str(&kind.to_string()), }; } - - devour_whitespace_or_comment(&mut self.tokens); - - let value = Value::from_tokens(&mut self.tokens, self.scope).unwrap(); - - Style { property, value } + 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 + } } } diff --git a/tests/main.rs b/tests/main.rs index d180661..28c896b 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -548,6 +548,11 @@ mod test_styles { "a {\n webkit: {\n webkit: {\n color: red;\n }\n }\n}\n", "a {\n webkit-webkit-color: red;\n}\n" ); + test!( + no_space_between_colon, + "a {\n color:red;\n}\n", + "a {\n color: red;\n}\n" + ); } mod test_misc { @@ -633,7 +638,7 @@ mod test_mixins { ); test!( mixin_removes_empty_ruleset, - "@mixin a {\n color:red; b {\n}\n}\nd {\n @include a;\n}\n", + "@mixin a {\n color: red; b {\n}\n}\nd {\n @include a;\n}\n", "d {\n color: red;\n}\n" ); test!(