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.
This commit is contained in:
ConnorSkees 2020-01-11 14:51:31 -05:00
parent dc05c8db2d
commit ebfeb35341
4 changed files with 299 additions and 154 deletions

View File

@ -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");

View File

@ -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 {

View File

@ -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<Token>),
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 {
@ -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<String, Vec<Token>>,
) -> Vec<Stmt> {
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<String, Vec<Token>>) -> Result<Expr, ()> {
fn eat_expr(
&mut self,
vars: &HashMap<String, Vec<Token>>,
super_selector: &Selector,
) -> Result<Expr, ()> {
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!(

View File

@ -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<Selector>, Box<Selector>),
/// And selector: `button.active`
And(Box<Selector>, Box<Selector>),
/// Multiple unrelated selectors: `button, .active`
Multiple(Box<Selector>, Box<Selector>),
/// Select all immediate children: `ul > li`
ImmediateChild(Box<Selector>, Box<Selector>),
/// Select all elements immediately following: `div + p`
Following(Box<Selector>, Box<Selector>),
/// Select elements preceeded by: `p ~ ul`
Preceding(Box<Selector>, Box<Selector>),
/// 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<SelectorKind>);
fn devour_whitespace(i: &mut Peekable<Iter<SelectorKind>>) -> 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<Iter<'a, Token>>,
super_selector: &'a Selector,
}
impl<'a> SelectorParser<'a> {
const fn new(tokens: Peekable<Iter<'a, Token>>) -> SelectorParser<'a> {
SelectorParser { tokens }
const fn new(
tokens: Peekable<Iter<'a, Token>>,
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()))
let mut v = Vec::new();
while let Some(s) = self.consume_selector() {
v.push(s);
}
TokenKind::Symbol(Symbol::Plus) => {
self.tokens.next();
self.devour_whitespace();
return Selector::Following(Box::new(left), Box::new(self.all_selectors()));
Selector(v)
}
TokenKind::Symbol(Symbol::Tilde) => {
self.tokens.next();
self.devour_whitespace();
return Selector::Preceding(Box::new(left), Box::new(self.all_selectors()));
fn consume_selector(&mut self) -> Option<SelectorKind> {
if self.devour_whitespace() {
return Some(SelectorKind::Whitespace);
}
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()),
);
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,57 +207,43 @@ impl<'a> SelectorParser<'a> {
}
found_whitespace
}
fn consume_selector(&mut self) -> Option<Selector> {
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<Iter<Token>>) -> Selector {
SelectorParser::new(tokens).all_selectors()
pub fn from_tokens<'a>(
tokens: Peekable<Iter<'a, Token>>,
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<SelectorKind> = Vec::with_capacity(self.0.len());
let sel1_split: Vec<Vec<SelectorKind>> = self
.0
.split(|sel| sel == &SelectorKind::Multiple)
.map(|x| x.to_vec())
.collect();
let sel2_split: Vec<Vec<SelectorKind>> = 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)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]