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:
parent
dc05c8db2d
commit
ebfeb35341
@ -143,6 +143,7 @@ mod test_scss {
|
|||||||
test!(selector_el_id_descendant, "a #class {\n}\n");
|
test!(selector_el_id_descendant, "a #class {\n}\n");
|
||||||
test!(selector_el_universal_descendant, "a * {\n}\n");
|
test!(selector_el_universal_descendant, "a * {\n}\n");
|
||||||
test!(selector_universal_el_descendant, "* a {\n}\n");
|
test!(selector_universal_el_descendant, "* a {\n}\n");
|
||||||
|
|
||||||
test!(selector_attribute_any, "[attr] {\n}\n");
|
test!(selector_attribute_any, "[attr] {\n}\n");
|
||||||
test!(selector_attribute_equals, "[attr=val] {\n}\n");
|
test!(selector_attribute_equals, "[attr=val] {\n}\n");
|
||||||
test!(selector_attribute_single_quotes, "[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_pseudo, ":pseudo {\n}\n");
|
||||||
test!(selector_el_pseudo_and, "a:pseudo {\n}\n");
|
test!(selector_el_pseudo_and, "a:pseudo {\n}\n");
|
||||||
test!(selector_el_pseudo_descendant, "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!(basic_style, "a {\n color: red;\n}\n");
|
||||||
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
|
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
|
||||||
|
15
src/lexer.rs
15
src/lexer.rs
@ -3,7 +3,7 @@ use std::iter::Peekable;
|
|||||||
use std::str::Chars;
|
use std::str::Chars;
|
||||||
|
|
||||||
use crate::common::{AtRule, Keyword, Op, Pos, Symbol};
|
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::units::Unit;
|
||||||
use crate::{Token, TokenKind, Whitespace};
|
use crate::{Token, TokenKind, Whitespace};
|
||||||
|
|
||||||
@ -59,6 +59,7 @@ impl<'a> Iterator for Lexer<'a> {
|
|||||||
'{' => symbol!(self, OpenBrace),
|
'{' => symbol!(self, OpenBrace),
|
||||||
'*' => symbol!(self, Mul),
|
'*' => symbol!(self, Mul),
|
||||||
'}' => symbol!(self, CloseBrace),
|
'}' => symbol!(self, CloseBrace),
|
||||||
|
'&' => symbol!(self, BitAnd),
|
||||||
'/' => self.lex_forward_slash(),
|
'/' => self.lex_forward_slash(),
|
||||||
'%' => {
|
'%' => {
|
||||||
self.buf.next();
|
self.buf.next();
|
||||||
@ -226,22 +227,22 @@ impl<'a> Lexer<'a> {
|
|||||||
.expect("todo! expected kind (should be error)")
|
.expect("todo! expected kind (should be error)")
|
||||||
{
|
{
|
||||||
']' => {
|
']' => {
|
||||||
return TokenKind::Selector(Selector::Attribute(Attribute {
|
return TokenKind::Attribute(Attribute {
|
||||||
kind: AttributeKind::Any,
|
kind: AttributeKind::Any,
|
||||||
attr,
|
attr,
|
||||||
value: String::new(),
|
value: String::new(),
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
'i' => {
|
'i' => {
|
||||||
self.devour_whitespace();
|
self.devour_whitespace();
|
||||||
assert!(self.buf.next() == Some(']'));
|
assert!(self.buf.next() == Some(']'));
|
||||||
return TokenKind::Selector(Selector::Attribute(Attribute {
|
return TokenKind::Attribute(Attribute {
|
||||||
kind: AttributeKind::Any,
|
kind: AttributeKind::Any,
|
||||||
attr,
|
attr,
|
||||||
value: String::new(),
|
value: String::new(),
|
||||||
case_sensitive: false,
|
case_sensitive: false,
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
'=' => AttributeKind::Equals,
|
'=' => AttributeKind::Equals,
|
||||||
'~' => AttributeKind::InList,
|
'~' => AttributeKind::InList,
|
||||||
@ -297,12 +298,12 @@ impl<'a> Lexer<'a> {
|
|||||||
|
|
||||||
assert!(self.buf.next() == Some(']'));
|
assert!(self.buf.next() == Some(']'));
|
||||||
|
|
||||||
TokenKind::Selector(Selector::Attribute(Attribute {
|
TokenKind::Attribute(Attribute {
|
||||||
kind,
|
kind,
|
||||||
attr,
|
attr,
|
||||||
value,
|
value,
|
||||||
case_sensitive,
|
case_sensitive,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex_variable(&mut self) -> TokenKind {
|
fn lex_variable(&mut self) -> TokenKind {
|
||||||
|
105
src/main.rs
105
src/main.rs
@ -21,7 +21,8 @@
|
|||||||
clippy::unused_self,
|
clippy::unused_self,
|
||||||
clippy::too_many_lines,
|
clippy::too_many_lines,
|
||||||
clippy::integer_arithmetic,
|
clippy::integer_arithmetic,
|
||||||
clippy::missing_errors_doc
|
clippy::missing_errors_doc,
|
||||||
|
clippy::let_underscore_must_use
|
||||||
)]
|
)]
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
@ -35,7 +36,7 @@ use crate::css::Css;
|
|||||||
use crate::error::SassError;
|
use crate::error::SassError;
|
||||||
use crate::format::PrettyPrinter;
|
use crate::format::PrettyPrinter;
|
||||||
use crate::lexer::Lexer;
|
use crate::lexer::Lexer;
|
||||||
use crate::selector::Selector;
|
use crate::selector::{Attribute, Selector};
|
||||||
use crate::style::Style;
|
use crate::style::Style;
|
||||||
use crate::units::Unit;
|
use crate::units::Unit;
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ pub enum TokenKind {
|
|||||||
Unit(Unit),
|
Unit(Unit),
|
||||||
Whitespace(Whitespace),
|
Whitespace(Whitespace),
|
||||||
Variable(String),
|
Variable(String),
|
||||||
Selector(Selector),
|
Attribute(Attribute),
|
||||||
Style(Vec<Token>),
|
Style(Vec<Token>),
|
||||||
Op(Op),
|
Op(Op),
|
||||||
// todo! preserve multi-line comments
|
// todo! preserve multi-line comments
|
||||||
@ -84,7 +85,7 @@ impl Display for TokenKind {
|
|||||||
TokenKind::Op(s) => write!(f, "{}", s),
|
TokenKind::Op(s) => write!(f, "{}", s),
|
||||||
TokenKind::Unit(s) => write!(f, "{}", s),
|
TokenKind::Unit(s) => write!(f, "{}", s),
|
||||||
TokenKind::Whitespace(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::Function(name, args) => write!(f, "{}({})", name, args.join(", ")),
|
||||||
TokenKind::Keyword(kw) => write!(f, "{}", kw),
|
TokenKind::Keyword(kw) => write!(f, "{}", kw),
|
||||||
TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s),
|
TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s),
|
||||||
@ -94,7 +95,6 @@ impl Display for TokenKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Represents a parsed SASS stylesheet with nesting
|
/// Represents a parsed SASS stylesheet with nesting
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct StyleSheet {
|
pub struct StyleSheet {
|
||||||
@ -199,12 +199,13 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
||||||
match kind.clone() {
|
match kind.clone() {
|
||||||
TokenKind::Ident(_)
|
TokenKind::Ident(_)
|
||||||
| TokenKind::Selector(_)
|
| TokenKind::Attribute(_)
|
||||||
| TokenKind::Symbol(Symbol::Hash)
|
| TokenKind::Symbol(Symbol::Hash)
|
||||||
| TokenKind::Symbol(Symbol::Colon)
|
| TokenKind::Symbol(Symbol::Colon)
|
||||||
| TokenKind::Symbol(Symbol::Mul)
|
| TokenKind::Symbol(Symbol::Mul)
|
||||||
| TokenKind::Symbol(Symbol::Period) => rules
|
| TokenKind::Symbol(Symbol::Period) => rules.extend(
|
||||||
.extend(self.eat_rules(&Selector::None, &mut self.global_variables.clone())),
|
self.eat_rules(&Selector(Vec::new()), &mut self.global_variables.clone()),
|
||||||
|
),
|
||||||
TokenKind::Whitespace(_) | TokenKind::Symbol(_) => {
|
TokenKind::Whitespace(_) | TokenKind::Symbol(_) => {
|
||||||
self.lexer.next();
|
self.lexer.next();
|
||||||
continue;
|
continue;
|
||||||
@ -318,7 +319,7 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
vars: &mut HashMap<String, Vec<Token>>,
|
vars: &mut HashMap<String, Vec<Token>>,
|
||||||
) -> Vec<Stmt> {
|
) -> Vec<Stmt> {
|
||||||
let mut stmts = Vec::new();
|
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 {
|
match tok {
|
||||||
Expr::Style(s) => stmts.push(Stmt::Style(s)),
|
Expr::Style(s) => stmts.push(Stmt::Style(s)),
|
||||||
Expr::Selector(s) => {
|
Expr::Selector(s) => {
|
||||||
@ -348,7 +349,11 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
stmts
|
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);
|
let mut values = Vec::with_capacity(5);
|
||||||
while let Some(tok) = self.lexer.next() {
|
while let Some(tok) = self.lexer.next() {
|
||||||
match tok.kind {
|
match tok.kind {
|
||||||
@ -360,6 +365,7 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
self.devour_whitespace();
|
self.devour_whitespace();
|
||||||
return Ok(Expr::Selector(Selector::from_tokens(
|
return Ok(Expr::Selector(Selector::from_tokens(
|
||||||
values.iter().peekable(),
|
values.iter().peekable(),
|
||||||
|
super_selector,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
TokenKind::Variable(name) => {
|
TokenKind::Variable(name) => {
|
||||||
@ -453,10 +459,85 @@ mod test_css {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
nesting_el_mul_el,
|
selector_nesting_el_mul_el,
|
||||||
"a, b {\n a, b {\n color: red\n}\n}\n",
|
"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!(basic_style, "a {\n color: red;\n}\n");
|
||||||
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
|
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
|
||||||
test!(
|
test!(
|
||||||
|
325
src/selector.rs
325
src/selector.rs
@ -5,118 +5,193 @@ use std::iter::Peekable;
|
|||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Selector {
|
pub struct Selector(pub Vec<SelectorKind>);
|
||||||
/// An element selector: `button`
|
|
||||||
Element(String),
|
fn devour_whitespace(i: &mut Peekable<Iter<SelectorKind>>) -> bool {
|
||||||
/// An id selector: `#footer`
|
let mut found_whitespace = false;
|
||||||
Id(String),
|
while let Some(SelectorKind::Whitespace) = i.peek() {
|
||||||
/// A single class selector: `.button-active`
|
i.next();
|
||||||
Class(String),
|
found_whitespace = true;
|
||||||
/// A universal selector: `*`
|
}
|
||||||
Universal,
|
found_whitespace
|
||||||
/// 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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Selector {
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Selector::Element(s) => write!(f, "{}", s),
|
SelectorKind::Element(s) => write!(f, "{}", s),
|
||||||
Selector::Id(s) => write!(f, "#{}", s),
|
SelectorKind::Id => write!(f, "#"),
|
||||||
Selector::Class(s) => write!(f, ".{}", s),
|
SelectorKind::Class => write!(f, "."),
|
||||||
Selector::Universal => write!(f, "*"),
|
SelectorKind::Universal => write!(f, "*"),
|
||||||
Selector::Descendant(lhs, rhs) => write!(f, "{} {}", lhs, rhs),
|
SelectorKind::Whitespace => write!(f, " "),
|
||||||
Selector::And(lhs, rhs) => write!(f, "{}{}", lhs, rhs),
|
SelectorKind::Multiple => write!(f, ", "),
|
||||||
Selector::Multiple(lhs, rhs) => write!(f, "{}, {}", lhs, rhs),
|
SelectorKind::ImmediateChild => write!(f, " > "),
|
||||||
Selector::ImmediateChild(lhs, rhs) => write!(f, "{} > {}", lhs, rhs),
|
SelectorKind::Following => write!(f, " + "),
|
||||||
Selector::Following(lhs, rhs) => write!(f, "{} + {}", lhs, rhs),
|
SelectorKind::Preceding => write!(f, " ~ "),
|
||||||
Selector::Preceding(lhs, rhs) => write!(f, "{} ~ {}", lhs, rhs),
|
SelectorKind::Attribute(attr) => write!(f, "{}", attr),
|
||||||
Selector::Attribute(attr) => write!(f, "{}", attr),
|
SelectorKind::Pseudo(s) => write!(f, ":{}", s),
|
||||||
Selector::Pseudo(s) => write!(f, ":{}", s),
|
// SelectorKind::Super => write!(f, "{}"),
|
||||||
Selector::None => 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> {
|
struct SelectorParser<'a> {
|
||||||
tokens: Peekable<Iter<'a, Token>>,
|
tokens: Peekable<Iter<'a, Token>>,
|
||||||
|
super_selector: &'a Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SelectorParser<'a> {
|
impl<'a> SelectorParser<'a> {
|
||||||
const fn new(tokens: Peekable<Iter<'a, Token>>) -> SelectorParser<'a> {
|
const fn new(
|
||||||
SelectorParser { tokens }
|
tokens: Peekable<Iter<'a, Token>>,
|
||||||
|
super_selector: &'a Selector,
|
||||||
|
) -> SelectorParser<'a> {
|
||||||
|
SelectorParser {
|
||||||
|
tokens,
|
||||||
|
super_selector,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_selectors(&mut self) -> Selector {
|
fn all_selectors(&mut self) -> Selector {
|
||||||
self.devour_whitespace();
|
let mut v = Vec::new();
|
||||||
let left = self
|
while let Some(s) = self.consume_selector() {
|
||||||
.consume_selector()
|
v.push(s);
|
||||||
.expect("expected left handed selector");
|
}
|
||||||
let whitespace: bool = self.devour_whitespace();
|
Selector(v)
|
||||||
match self.tokens.peek() {
|
}
|
||||||
Some(tok) => match tok.kind {
|
|
||||||
TokenKind::Ident(_) => {
|
fn consume_selector(&mut self) -> Option<SelectorKind> {
|
||||||
return Selector::Descendant(Box::new(left), Box::new(self.all_selectors()))
|
if self.devour_whitespace() {
|
||||||
}
|
return Some(SelectorKind::Whitespace);
|
||||||
TokenKind::Symbol(Symbol::Plus) => {
|
}
|
||||||
self.tokens.next();
|
if let Some(Token { kind, .. }) = self.tokens.next() {
|
||||||
self.devour_whitespace();
|
return Some(match &kind {
|
||||||
return Selector::Following(Box::new(left), Box::new(self.all_selectors()));
|
TokenKind::Ident(tok) => SelectorKind::Element(tok.clone()),
|
||||||
}
|
TokenKind::Symbol(Symbol::Period) => SelectorKind::Class,
|
||||||
TokenKind::Symbol(Symbol::Tilde) => {
|
TokenKind::Symbol(Symbol::Hash) => SelectorKind::Id,
|
||||||
self.tokens.next();
|
TokenKind::Symbol(Symbol::Colon) => {
|
||||||
self.devour_whitespace();
|
if let Some(Token {
|
||||||
return Selector::Preceding(Box::new(left), Box::new(self.all_selectors()));
|
kind: TokenKind::Ident(s),
|
||||||
}
|
..
|
||||||
TokenKind::Symbol(Symbol::Comma) => {
|
}) = self.tokens.next()
|
||||||
self.tokens.next();
|
{
|
||||||
self.devour_whitespace();
|
SelectorKind::Pseudo(s.clone())
|
||||||
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()),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Selector::And(Box::new(left), Box::new(self.all_selectors()));
|
todo!("expected ident after `:` in selector")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenKind::Symbol(Symbol::Lt) => {}
|
TokenKind::Symbol(Symbol::Comma) => SelectorKind::Multiple,
|
||||||
_ => todo!(),
|
TokenKind::Symbol(Symbol::Gt) => SelectorKind::ImmediateChild,
|
||||||
},
|
TokenKind::Symbol(Symbol::Plus) => SelectorKind::Following,
|
||||||
None => return left,
|
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 {
|
fn devour_whitespace(&mut self) -> bool {
|
||||||
@ -132,56 +207,42 @@ impl<'a> SelectorParser<'a> {
|
|||||||
}
|
}
|
||||||
found_whitespace
|
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 {
|
impl Selector {
|
||||||
pub fn from_tokens(tokens: Peekable<Iter<Token>>) -> Selector {
|
pub fn from_tokens<'a>(
|
||||||
SelectorParser::new(tokens).all_selectors()
|
tokens: Peekable<Iter<'a, Token>>,
|
||||||
|
super_selector: &'a Selector,
|
||||||
|
) -> Selector {
|
||||||
|
SelectorParser::new(tokens, super_selector).all_selectors()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zip(self, other: Selector) -> Selector {
|
pub fn zip(self, other: Selector) -> Selector {
|
||||||
if let Selector::Multiple(lhs, rhs) = other {
|
if self.0.is_empty() {
|
||||||
return Selector::Multiple(Box::new(self.clone().zip(*lhs)), Box::new(self.zip(*rhs)));
|
return Selector(other.0);
|
||||||
}
|
}
|
||||||
match self {
|
let mut rules: Vec<SelectorKind> = Vec::with_capacity(self.0.len());
|
||||||
Selector::Multiple(lhs, rhs) => {
|
let sel1_split: Vec<Vec<SelectorKind>> = self
|
||||||
Selector::Multiple(Box::new(lhs.zip(other.clone())), Box::new(rhs.zip(other)))
|
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user