From ebfeb35341b677fbf80b42cc48db2b0d36e57ea3 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sat, 11 Jan 2020 14:51:31 -0500 Subject: [PATCH] Refactor Selector to be Vec rather than recursive enum The initial implementation of Selector was a recursive enum with boxed variants. This new implementation is linear and relies on only one level of indirection. --- src/format.rs | 2 + src/lexer.rs | 15 +-- src/main.rs | 111 ++++++++++++++--- src/selector.rs | 325 ++++++++++++++++++++++++++++-------------------- 4 files changed, 299 insertions(+), 154 deletions(-) diff --git a/src/format.rs b/src/format.rs index e5ed279..2fc3912 100644 --- a/src/format.rs +++ b/src/format.rs @@ -143,6 +143,7 @@ mod test_scss { test!(selector_el_id_descendant, "a #class {\n}\n"); test!(selector_el_universal_descendant, "a * {\n}\n"); test!(selector_universal_el_descendant, "* a {\n}\n"); + test!(selector_attribute_any, "[attr] {\n}\n"); test!(selector_attribute_equals, "[attr=val] {\n}\n"); test!(selector_attribute_single_quotes, "[attr='val'] {\n}\n"); @@ -164,6 +165,7 @@ mod test_scss { test!(selector_pseudo, ":pseudo {\n}\n"); test!(selector_el_pseudo_and, "a:pseudo {\n}\n"); test!(selector_el_pseudo_descendant, "a :pseudo {\n}\n"); + test!(selector_pseudo_el_descendant, ":pseudo a {\n}\n"); test!(basic_style, "a {\n color: red;\n}\n"); test!(two_styles, "a {\n color: red;\n color: blue;\n}\n"); diff --git a/src/lexer.rs b/src/lexer.rs index ea592c1..a228106 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -3,7 +3,7 @@ use std::iter::Peekable; use std::str::Chars; use crate::common::{AtRule, Keyword, Op, Pos, Symbol}; -use crate::selector::{Attribute, AttributeKind, Selector}; +use crate::selector::{Attribute, AttributeKind}; use crate::units::Unit; use crate::{Token, TokenKind, Whitespace}; @@ -59,6 +59,7 @@ impl<'a> Iterator for Lexer<'a> { '{' => symbol!(self, OpenBrace), '*' => symbol!(self, Mul), '}' => symbol!(self, CloseBrace), + '&' => symbol!(self, BitAnd), '/' => self.lex_forward_slash(), '%' => { self.buf.next(); @@ -226,22 +227,22 @@ impl<'a> Lexer<'a> { .expect("todo! expected kind (should be error)") { ']' => { - return TokenKind::Selector(Selector::Attribute(Attribute { + return TokenKind::Attribute(Attribute { kind: AttributeKind::Any, attr, value: String::new(), case_sensitive: true, - })) + }) } 'i' => { self.devour_whitespace(); assert!(self.buf.next() == Some(']')); - return TokenKind::Selector(Selector::Attribute(Attribute { + return TokenKind::Attribute(Attribute { kind: AttributeKind::Any, attr, value: String::new(), case_sensitive: false, - })); + }); } '=' => AttributeKind::Equals, '~' => AttributeKind::InList, @@ -297,12 +298,12 @@ impl<'a> Lexer<'a> { assert!(self.buf.next() == Some(']')); - TokenKind::Selector(Selector::Attribute(Attribute { + TokenKind::Attribute(Attribute { kind, attr, value, case_sensitive, - })) + }) } fn lex_variable(&mut self) -> TokenKind { diff --git a/src/main.rs b/src/main.rs index 0a02755..f501722 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,8 @@ clippy::unused_self, clippy::too_many_lines, clippy::integer_arithmetic, - clippy::missing_errors_doc + clippy::missing_errors_doc, + clippy::let_underscore_must_use )] use std::collections::HashMap; use std::fmt::{self, Display}; @@ -35,7 +36,7 @@ use crate::css::Css; use crate::error::SassError; use crate::format::PrettyPrinter; use crate::lexer::Lexer; -use crate::selector::Selector; +use crate::selector::{Attribute, Selector}; use crate::style::Style; use crate::units::Unit; @@ -68,7 +69,7 @@ pub enum TokenKind { Unit(Unit), Whitespace(Whitespace), Variable(String), - Selector(Selector), + Attribute(Attribute), Style(Vec), Op(Op), // todo! preserve multi-line comments @@ -84,7 +85,7 @@ impl Display for TokenKind { TokenKind::Op(s) => write!(f, "{}", s), TokenKind::Unit(s) => write!(f, "{}", s), TokenKind::Whitespace(s) => write!(f, "{}", s), - TokenKind::Selector(s) => write!(f, "{}", s), + TokenKind::Attribute(s) => write!(f, "{}", s), TokenKind::Function(name, args) => write!(f, "{}({})", name, args.join(", ")), TokenKind::Keyword(kw) => write!(f, "{}", kw), TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s), @@ -94,7 +95,6 @@ impl Display for TokenKind { } } - /// Represents a parsed SASS stylesheet with nesting #[derive(Debug, Clone, Eq, PartialEq)] pub struct StyleSheet { @@ -112,7 +112,7 @@ pub enum Stmt { } /// Represents a single rule set. Rule sets can contain other rule sets -/// +/// /// a { /// color: blue; /// b { @@ -165,10 +165,10 @@ impl StyleSheet { } /// Print the internal representation of a parsed stylesheet - /// + /// /// Very closely resembles the origin SASS, but contains only things translatable /// to pure CSS - /// + /// /// Used mainly in debugging, but can at times be useful pub fn pretty_print(&self, buf: W) -> io::Result<()> { PrettyPrinter::new(buf).pretty_print(self) @@ -199,12 +199,13 @@ impl<'a> StyleSheetParser<'a> { while let Some(Token { kind, .. }) = self.lexer.peek() { match kind.clone() { TokenKind::Ident(_) - | TokenKind::Selector(_) + | TokenKind::Attribute(_) | TokenKind::Symbol(Symbol::Hash) | TokenKind::Symbol(Symbol::Colon) | TokenKind::Symbol(Symbol::Mul) - | TokenKind::Symbol(Symbol::Period) => rules - .extend(self.eat_rules(&Selector::None, &mut self.global_variables.clone())), + | TokenKind::Symbol(Symbol::Period) => rules.extend( + self.eat_rules(&Selector(Vec::new()), &mut self.global_variables.clone()), + ), TokenKind::Whitespace(_) | TokenKind::Symbol(_) => { self.lexer.next(); continue; @@ -318,7 +319,7 @@ impl<'a> StyleSheetParser<'a> { vars: &mut HashMap>, ) -> Vec { let mut stmts = Vec::new(); - while let Ok(tok) = self.eat_expr(vars) { + while let Ok(tok) = self.eat_expr(vars, super_selector) { match tok { Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::Selector(s) => { @@ -348,7 +349,11 @@ impl<'a> StyleSheetParser<'a> { stmts } - fn eat_expr(&mut self, vars: &HashMap>) -> Result { + fn eat_expr( + &mut self, + vars: &HashMap>, + super_selector: &Selector, + ) -> Result { let mut values = Vec::with_capacity(5); while let Some(tok) = self.lexer.next() { match tok.kind { @@ -360,6 +365,7 @@ impl<'a> StyleSheetParser<'a> { self.devour_whitespace(); return Ok(Expr::Selector(Selector::from_tokens( values.iter().peekable(), + super_selector, ))); } TokenKind::Variable(name) => { @@ -453,10 +459,85 @@ mod test_css { } test!( - nesting_el_mul_el, + selector_nesting_el_mul_el, "a, b {\n a, b {\n color: red\n}\n}\n", - "a a, b a, a b, b b {\n color: red;\n}\n" + "a a, a b, b a, b b {\n color: red;\n}\n" ); + + test!(selector_element, "a {\n color: red;\n}\n"); + test!(selector_id, "#id {\n color: red;\n}\n"); + test!(selector_class, ".class {\n color: red;\n}\n"); + test!(selector_el_descendant, "a a {\n color: red;\n}\n"); + test!(selector_universal, "* {\n color: red;\n}\n"); + test!(selector_el_class_and, "a.class {\n color: red;\n}\n"); + test!(selector_el_id_and, "a#class {\n color: red;\n}\n"); + test!( + selector_el_class_descendant, + "a .class {\n color: red;\n}\n" + ); + test!(selector_el_id_descendant, "a #class {\n color: red;\n}\n"); + test!( + selector_el_universal_descendant, + "a * {\n color: red;\n}\n" + ); + test!( + selector_universal_el_descendant, + "* a {\n color: red;\n}\n" + ); + + test!(selector_attribute_any, "[attr] {\n color: red;\n}\n"); + test!( + selector_attribute_equals, + "[attr=val] {\n color: red;\n}\n" + ); + test!( + selector_attribute_single_quotes, + "[attr='val'] {\n color: red;\n}\n" + ); + test!( + selector_attribute_double_quotes, + "[attr=\"val\"] {\n color: red;\n}\n" + ); + test!(selector_attribute_in, "[attr~=val] {\n color: red;\n}\n"); + test!( + selector_attribute_begins_hyphen_or_exact, + "[attr|=val] {\n color: red;\n}\n" + ); + test!( + selector_attribute_starts_with, + "[attr^=val] {\n color: red;\n}\n" + ); + test!( + selector_attribute_ends_with, + "[attr$=val] {\n color: red;\n}\n" + ); + test!( + selector_attribute_contains, + "[attr*=val] {\n color: red;\n}\n" + ); + test!(selector_el_attribute_and, "a[attr] {\n color: red;\n}\n"); + test!( + selector_el_attribute_descendant, + "a [attr] {\n color: red;\n}\n" + ); + test!(selector_el_mul_el, "a, b {\n color: red;\n}\n"); + test!( + selector_el_immediate_child_el, + "a > b {\n color: red;\n}\n" + ); + test!(selector_el_following_el, "a + b {\n color: red;\n}\n"); + test!(selector_el_preceding_el, "a ~ b {\n color: red;\n}\n"); + test!(selector_pseudo, ":pseudo {\n color: red;\n}\n"); + test!(selector_el_pseudo_and, "a:pseudo {\n color: red;\n}\n"); + test!( + selector_el_pseudo_descendant, + "a :pseudo {\n color: red;\n}\n" + ); + test!( + selector_pseudo_el_descendant, + ":pseudo a {\n color: red;\n}\n" + ); + test!(basic_style, "a {\n color: red;\n}\n"); test!(two_styles, "a {\n color: red;\n color: blue;\n}\n"); test!( diff --git a/src/selector.rs b/src/selector.rs index 9805d80..5d01469 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -5,118 +5,193 @@ use std::iter::Peekable; use std::slice::Iter; #[derive(Clone, Debug, Eq, PartialEq)] -pub enum Selector { - /// An element selector: `button` - Element(String), - /// An id selector: `#footer` - Id(String), - /// A single class selector: `.button-active` - Class(String), - /// A universal selector: `*` - Universal, - /// A simple child selector: `ul li` - Descendant(Box, Box), - /// And selector: `button.active` - And(Box, Box), - /// Multiple unrelated selectors: `button, .active` - Multiple(Box, Box), - /// Select all immediate children: `ul > li` - ImmediateChild(Box, Box), - /// Select all elements immediately following: `div + p` - Following(Box, Box), - /// Select elements preceeded by: `p ~ ul` - Preceding(Box, Box), - /// Select elements with attribute: `html[lang|=en]` - Attribute(Attribute), - /// Pseudo selector: `:hover` - Pseudo(String), - /// Used to signify no selector (when there is no super_selector of a rule) - None, +pub struct Selector(pub Vec); + +fn devour_whitespace(i: &mut Peekable>) -> bool { + let mut found_whitespace = false; + while let Some(SelectorKind::Whitespace) = i.peek() { + i.next(); + found_whitespace = true; + } + found_whitespace } impl Display for Selector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut iter = self.0.iter().peekable(); + + while let Some(s) = iter.next() { + match s { + SelectorKind::Whitespace => continue, + SelectorKind::Attribute(_) + | SelectorKind::Pseudo(_) + | SelectorKind::Class + | SelectorKind::Id + | SelectorKind::Universal + | SelectorKind::Element(_) => { + write!(f, "{}", s)?; + if devour_whitespace(&mut iter) { + match iter.peek() { + Some(SelectorKind::Attribute(_)) + | Some(SelectorKind::Pseudo(_)) + | Some(SelectorKind::Class) + | Some(SelectorKind::Id) + | Some(SelectorKind::Universal) + | Some(SelectorKind::Element(_)) => { + write!(f, " {}", iter.next().expect("already peeked here"))?; + } + _ => {} + } + } + } + _ => write!(f, "{}", s)?, + } + } + write!(f, "") + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SelectorKind { + /// An element selector: `button` + Element(String), + /// An id selector: `#footer` + Id, + /// A single class selector: `.button-active` + Class, + /// A universal selector: `*` + Universal, + /// Multiple unrelated selectors: `button, .active` + Multiple, + /// Select all immediate children: `ul > li` + ImmediateChild, + /// Select all elements immediately following: `div + p` + Following, + /// Select elements preceeded by: `p ~ ul` + Preceding, + /// Select elements with attribute: `html[lang|=en]` + Attribute(Attribute), + /// Pseudo selector: `:hover` + Pseudo(String), + /// Use the super selector: `&.red` + // Super, + /// Used to signify no selector (when there is no super_selector of a rule) + None, + Whitespace, +} + +impl Display for SelectorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Selector::Element(s) => write!(f, "{}", s), - Selector::Id(s) => write!(f, "#{}", s), - Selector::Class(s) => write!(f, ".{}", s), - Selector::Universal => write!(f, "*"), - Selector::Descendant(lhs, rhs) => write!(f, "{} {}", lhs, rhs), - Selector::And(lhs, rhs) => write!(f, "{}{}", lhs, rhs), - Selector::Multiple(lhs, rhs) => write!(f, "{}, {}", lhs, rhs), - Selector::ImmediateChild(lhs, rhs) => write!(f, "{} > {}", lhs, rhs), - Selector::Following(lhs, rhs) => write!(f, "{} + {}", lhs, rhs), - Selector::Preceding(lhs, rhs) => write!(f, "{} ~ {}", lhs, rhs), - Selector::Attribute(attr) => write!(f, "{}", attr), - Selector::Pseudo(s) => write!(f, ":{}", s), - Selector::None => write!(f, ""), + SelectorKind::Element(s) => write!(f, "{}", s), + SelectorKind::Id => write!(f, "#"), + SelectorKind::Class => write!(f, "."), + SelectorKind::Universal => write!(f, "*"), + SelectorKind::Whitespace => write!(f, " "), + SelectorKind::Multiple => write!(f, ", "), + SelectorKind::ImmediateChild => write!(f, " > "), + SelectorKind::Following => write!(f, " + "), + SelectorKind::Preceding => write!(f, " ~ "), + SelectorKind::Attribute(attr) => write!(f, "{}", attr), + SelectorKind::Pseudo(s) => write!(f, ":{}", s), + // SelectorKind::Super => write!(f, "{}"), + SelectorKind::None => write!(f, ""), } } } +#[cfg(test)] +mod test_selector_display { + use super::*; + use SelectorKind::*; + macro_rules! test_selector_display { + + ($func:ident, Selector($tok:tt), $output:literal) => { + #[test] + fn $func() { + assert_eq!(format!("{}", Selector(vec!$tok)), $output); + } + } + } + + test_selector_display!(el, Selector((Element("a".to_string()))), "a"); + test_selector_display!( + keeps_one_whitespace, + Selector(( + Element("a".to_string()), + Whitespace, + Element("b".to_string()), + )), + "a b" + ); + test_selector_display!( + keeps_one_whitespace_with_two, + Selector(( + Element("a".to_string()), + Whitespace, + Whitespace, + Element("c".to_string()), + Whitespace, + )), + "a c" + ); +} + struct SelectorParser<'a> { tokens: Peekable>, + super_selector: &'a Selector, } impl<'a> SelectorParser<'a> { - const fn new(tokens: Peekable>) -> SelectorParser<'a> { - SelectorParser { tokens } + const fn new( + tokens: Peekable>, + super_selector: &'a Selector, + ) -> SelectorParser<'a> { + SelectorParser { + tokens, + super_selector, + } } fn all_selectors(&mut self) -> Selector { - self.devour_whitespace(); - let left = self - .consume_selector() - .expect("expected left handed selector"); - let whitespace: bool = self.devour_whitespace(); - match self.tokens.peek() { - Some(tok) => match tok.kind { - TokenKind::Ident(_) => { - return Selector::Descendant(Box::new(left), Box::new(self.all_selectors())) - } - TokenKind::Symbol(Symbol::Plus) => { - self.tokens.next(); - self.devour_whitespace(); - return Selector::Following(Box::new(left), Box::new(self.all_selectors())); - } - TokenKind::Symbol(Symbol::Tilde) => { - self.tokens.next(); - self.devour_whitespace(); - return Selector::Preceding(Box::new(left), Box::new(self.all_selectors())); - } - TokenKind::Symbol(Symbol::Comma) => { - self.tokens.next(); - self.devour_whitespace(); - return Selector::Multiple(Box::new(left), Box::new(self.all_selectors())); - } - TokenKind::Symbol(Symbol::Gt) => { - self.tokens.next(); - self.devour_whitespace(); - return Selector::ImmediateChild( - Box::new(left), - Box::new(self.all_selectors()), - ); - } - TokenKind::Symbol(Symbol::Colon) - | TokenKind::Symbol(Symbol::Period) - | TokenKind::Symbol(Symbol::Mul) - | TokenKind::Selector(_) - | TokenKind::Symbol(Symbol::Hash) => { - if whitespace { - return Selector::Descendant( - Box::new(left), - Box::new(self.all_selectors()), - ); + let mut v = Vec::new(); + while let Some(s) = self.consume_selector() { + v.push(s); + } + Selector(v) + } + + fn consume_selector(&mut self) -> Option { + if self.devour_whitespace() { + return Some(SelectorKind::Whitespace); + } + if let Some(Token { kind, .. }) = self.tokens.next() { + return Some(match &kind { + TokenKind::Ident(tok) => SelectorKind::Element(tok.clone()), + TokenKind::Symbol(Symbol::Period) => SelectorKind::Class, + TokenKind::Symbol(Symbol::Hash) => SelectorKind::Id, + TokenKind::Symbol(Symbol::Colon) => { + if let Some(Token { + kind: TokenKind::Ident(s), + .. + }) = self.tokens.next() + { + SelectorKind::Pseudo(s.clone()) } else { - return Selector::And(Box::new(left), Box::new(self.all_selectors())); + todo!("expected ident after `:` in selector") } } - TokenKind::Symbol(Symbol::Lt) => {} - _ => todo!(), - }, - None => return left, + TokenKind::Symbol(Symbol::Comma) => SelectorKind::Multiple, + TokenKind::Symbol(Symbol::Gt) => SelectorKind::ImmediateChild, + TokenKind::Symbol(Symbol::Plus) => SelectorKind::Following, + TokenKind::Symbol(Symbol::Tilde) => SelectorKind::Preceding, + TokenKind::Symbol(Symbol::Mul) => SelectorKind::Universal, + // TokenKind::Symbol(Symbol::BitAnd) => SelectorKind::Super, + TokenKind::Attribute(attr) => SelectorKind::Attribute(attr.clone()), + _ => todo!("unimplemented selector"), + }); } - todo!() + None } fn devour_whitespace(&mut self) -> bool { @@ -132,56 +207,42 @@ impl<'a> SelectorParser<'a> { } found_whitespace } - - fn consume_selector(&mut self) -> Option { - if let Some(tok) = self.tokens.next() { - let selector = match &tok.kind { - TokenKind::Symbol(Symbol::Period) => { - match self.tokens.next().expect("expected ident after `.`").kind { - TokenKind::Ident(ref tok) => Selector::Class(tok.clone()), - _ => todo!("there should normally be an ident after `.`"), - } - } - TokenKind::Symbol(Symbol::Mul) => Selector::Universal, - TokenKind::Symbol(Symbol::Hash) => { - match &self.tokens.next().expect("expected ident after `#`").kind { - TokenKind::Ident(ref tok) => Selector::Id(tok.clone()), - _ => todo!("there should normally be an ident after `#`"), - } - } - TokenKind::Symbol(Symbol::Colon) => { - match self.tokens.next().expect("expected ident after `:`").kind { - TokenKind::Ident(ref tok) => Selector::Pseudo(tok.clone()), - _ => todo!("there should normally be an ident after `:`"), - } - } - TokenKind::Ident(tok) => Selector::Element(tok.clone()), - TokenKind::Selector(sel) => sel.clone(), - _ => todo!(), - }; - Some(selector) - } else { - None - } - } } impl Selector { - pub fn from_tokens(tokens: Peekable>) -> Selector { - SelectorParser::new(tokens).all_selectors() + pub fn from_tokens<'a>( + tokens: Peekable>, + super_selector: &'a Selector, + ) -> Selector { + SelectorParser::new(tokens, super_selector).all_selectors() } pub fn zip(self, other: Selector) -> Selector { - if let Selector::Multiple(lhs, rhs) = other { - return Selector::Multiple(Box::new(self.clone().zip(*lhs)), Box::new(self.zip(*rhs))); + if self.0.is_empty() { + return Selector(other.0); } - match self { - Selector::Multiple(lhs, rhs) => { - Selector::Multiple(Box::new(lhs.zip(other.clone())), Box::new(rhs.zip(other))) + let mut rules: Vec = Vec::with_capacity(self.0.len()); + let sel1_split: Vec> = self + .0 + .split(|sel| sel == &SelectorKind::Multiple) + .map(|x| x.to_vec()) + .collect(); + let sel2_split: Vec> = other + .0 + .split(|sel| sel == &SelectorKind::Multiple) + .map(|x| x.to_vec()) + .collect(); + for (idx, sel1) in sel1_split.iter().enumerate() { + for (idx2, sel2) in sel2_split.iter().enumerate() { + rules.extend(sel1.iter().cloned()); + rules.push(SelectorKind::Whitespace); + rules.extend(sel2.iter().cloned()); + if !(idx + 1 == sel1_split.len() && idx2 + 1 == sel2_split.len()) { + rules.push(SelectorKind::Multiple); + } } - Selector::None => other, - _ => Selector::Descendant(Box::new(self), Box::new(other)), } + Selector(rules) } }