grass/src/selector.rs

526 lines
19 KiB
Rust
Raw Normal View History

use crate::common::{Scope, Symbol, Whitespace};
use crate::error::SassResult;
use crate::utils::{
devour_whitespace, devour_whitespace_or_comment, flatten_ident, parse_interpolation,
parse_quoted_string, IsWhitespace,
};
use crate::{Token, TokenKind};
use std::fmt::{self, Display, Write};
use std::iter::Peekable;
2020-01-11 19:16:59 -05:00
use std::string::ToString;
2020-01-18 20:24:28 -05:00
use std::vec::IntoIter;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Selector(pub Vec<SelectorKind>);
impl Selector {
2020-02-22 17:57:13 -05:00
pub const fn new() -> Selector {
Selector(Vec::new())
}
}
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(_)
2020-02-02 15:06:46 -05:00
| SelectorKind::PseudoElement(_)
2020-01-11 19:16:59 -05:00
| SelectorKind::PseudoParen(..)
| SelectorKind::Class
| SelectorKind::Id
| SelectorKind::Universal
| SelectorKind::InterpolatedSuper
| SelectorKind::Element(_) => {
write!(f, "{}", s)?;
if devour_whitespace(&mut iter) {
match iter.peek() {
Some(SelectorKind::Attribute(_))
| Some(SelectorKind::Pseudo(_))
2020-02-02 15:06:46 -05:00
| Some(SelectorKind::PseudoElement(_))
2020-01-11 19:16:59 -05:00
| Some(SelectorKind::PseudoParen(..))
| Some(SelectorKind::Class)
| Some(SelectorKind::Id)
| Some(SelectorKind::Universal)
| Some(SelectorKind::InterpolatedSuper)
| Some(SelectorKind::Element(_)) => {
2020-01-11 16:12:23 -05:00
write!(f, " ")?;
}
_ => {}
}
}
}
2020-01-26 19:16:26 -05:00
SelectorKind::Multiple => {
devour_whitespace(&mut iter);
while let Some(sel) = iter.peek() {
if sel != &&SelectorKind::Multiple {
write!(f, ",")?;
if sel == &&SelectorKind::Newline {
iter.next();
f.write_char('\n')?;
} else {
f.write_char(' ')?;
}
2020-01-26 19:16:26 -05:00
break;
}
iter.next();
devour_whitespace(&mut iter);
}
while let Some(sel) = iter.peek() {
if sel != &&SelectorKind::Multiple
&& sel != &&SelectorKind::Newline
&& !sel.is_whitespace()
{
break;
}
iter.next();
}
2020-01-26 19:16:26 -05:00
}
_ => write!(f, "{}", s)?,
}
}
write!(f, "")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) 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,
/// Newline (significant if after `SelectorKind::Multiple`)
Newline,
/// 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),
2020-02-02 15:06:46 -05:00
/// Pseudo element selector: `::before`
PseudoElement(String),
2020-01-11 19:16:59 -05:00
/// Pseudo selector with additional parens: `:any(h1, h2, h3, h4, h5, h6)`
PseudoParen(String, String),
/// Use the super selector: `&.red`
2020-01-11 16:12:23 -05:00
Super,
2020-01-26 11:07:57 -05:00
/// Super selector in an interpolated context: `a #{&}`
InterpolatedSuper,
/// Placeholder selector: `%alert`
Placeholder,
Whitespace,
}
impl IsWhitespace for SelectorKind {
fn is_whitespace(&self) -> bool {
self == &Self::Whitespace
}
}
impl IsWhitespace for &SelectorKind {
fn is_whitespace(&self) -> bool {
self == &&SelectorKind::Whitespace
}
}
impl Display for SelectorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
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::Newline => writeln!(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),
2020-02-02 15:06:46 -05:00
SelectorKind::PseudoElement(s) => write!(f, "::{}", s),
2020-02-02 18:01:09 -05:00
SelectorKind::PseudoParen(s, val) => write!(f, ":{}({})", s, val),
SelectorKind::Super | SelectorKind::InterpolatedSuper => write!(f, ""),
SelectorKind::Placeholder => write!(f, "%"),
}
}
}
struct SelectorParser<'a> {
scope: &'a Scope,
selectors: Vec<SelectorKind>,
2020-01-26 11:07:57 -05:00
is_interpolated: bool,
}
impl<'a> SelectorParser<'a> {
2020-01-18 19:44:34 -05:00
const fn new(scope: &'a Scope) -> SelectorParser<'a> {
SelectorParser {
scope,
selectors: Vec::new(),
2020-01-26 11:07:57 -05:00
is_interpolated: false,
}
}
fn all_selectors(mut self, tokens: &'a mut Peekable<IntoIter<Token>>) -> SassResult<Selector> {
self.tokens_to_selectors(tokens)?;
// remove trailing whitespace
while let Some(x) = self.selectors.pop() {
2020-01-11 16:12:23 -05:00
if x != SelectorKind::Whitespace {
self.selectors.push(x);
2020-01-11 16:12:23 -05:00
break;
}
}
Ok(Selector(self.selectors))
}
fn consume_pseudo_selector(
&mut self,
tokens: &'_ mut Peekable<IntoIter<Token>>,
) -> SassResult<()> {
2020-02-02 15:06:46 -05:00
if let Some(tok) = tokens.next() {
match tok.kind {
TokenKind::Ident(s) => {
if let Some(Token {
kind: TokenKind::Symbol(Symbol::OpenParen),
..
}) = tokens.peek()
{
tokens.next();
devour_whitespace_or_comment(tokens);
let mut toks = String::new();
2020-02-02 15:06:46 -05:00
while let Some(Token { kind, .. }) = tokens.peek() {
if kind == &TokenKind::Symbol(Symbol::CloseParen) {
tokens.next();
2020-02-02 15:06:46 -05:00
break;
}
let tok = tokens.next().unwrap();
toks.push_str(&tok.kind.to_string());
if devour_whitespace(tokens) {
toks.push(' ');
}
2020-02-02 15:06:46 -05:00
}
2020-02-02 18:01:09 -05:00
self.selectors
.push(SelectorKind::PseudoParen(s, toks.trim_end().to_owned()))
2020-02-02 15:06:46 -05:00
} else {
self.selectors.push(SelectorKind::Pseudo(s))
2020-01-11 19:16:59 -05:00
}
}
2020-02-02 15:06:46 -05:00
TokenKind::Symbol(Symbol::Colon) => {
if let Some(Token {
kind: TokenKind::Ident(s),
..
}) = tokens.next()
{
self.selectors.push(SelectorKind::PseudoElement(s))
}
}
_ => return Err("Expected identifier.".into()),
2020-01-11 19:16:59 -05:00
}
}
Ok(())
2020-01-11 19:16:59 -05:00
}
fn tokens_to_selectors(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) -> SassResult<()> {
while tokens.peek().is_some() {
self.consume_selector(tokens)?;
}
Ok(())
}
fn consume_selector(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) -> SassResult<()> {
2020-01-26 16:55:06 -05:00
if devour_whitespace_or_comment(tokens) {
2020-01-18 20:24:28 -05:00
if let Some(Token {
2020-01-11 16:12:23 -05:00
kind: TokenKind::Symbol(Symbol::Comma),
..
}) = tokens.peek()
2020-01-11 16:12:23 -05:00
{
tokens.next();
self.selectors.push(SelectorKind::Multiple);
return Ok(());
2020-01-11 16:12:23 -05:00
}
self.selectors.push(SelectorKind::Whitespace);
return Ok(());
}
if let Some(Token { kind, .. }) = tokens.next() {
2020-01-18 20:24:28 -05:00
match kind {
TokenKind::Ident(v) | TokenKind::Number(v) => {
self.selectors.push(SelectorKind::Element(v))
}
TokenKind::Symbol(Symbol::Period) => self.selectors.push(SelectorKind::Class),
TokenKind::Symbol(Symbol::Hash) => self.selectors.push(SelectorKind::Id),
TokenKind::Symbol(Symbol::Colon) => self.consume_pseudo_selector(tokens)?,
TokenKind::Symbol(Symbol::Comma) => {
self.selectors.push(SelectorKind::Multiple);
if tokens.peek().unwrap().kind == TokenKind::Whitespace(Whitespace::Newline) {
self.selectors.push(SelectorKind::Newline);
devour_whitespace(tokens);
}
}
TokenKind::Symbol(Symbol::Gt) => self.selectors.push(SelectorKind::ImmediateChild),
TokenKind::Symbol(Symbol::Plus) => self.selectors.push(SelectorKind::Following),
TokenKind::Symbol(Symbol::Tilde) => self.selectors.push(SelectorKind::Preceding),
TokenKind::Symbol(Symbol::Mul) => self.selectors.push(SelectorKind::Universal),
2020-01-29 21:02:32 -05:00
TokenKind::Symbol(Symbol::Percent) => {
self.selectors.push(SelectorKind::Placeholder)
}
2020-01-26 11:07:57 -05:00
TokenKind::Symbol(Symbol::BitAnd) => self.selectors.push(if self.is_interpolated {
SelectorKind::InterpolatedSuper
} else {
SelectorKind::Super
}),
TokenKind::Interpolation => {
self.is_interpolated = true;
2020-02-09 19:07:44 -05:00
self.tokens_to_selectors(
&mut parse_interpolation(tokens, self.scope)?
.into_iter()
.peekable(),
)?;
2020-01-26 11:07:57 -05:00
self.is_interpolated = false;
}
TokenKind::Symbol(Symbol::OpenSquareBrace) => self
.selectors
.push(Attribute::from_tokens(tokens, self.scope)?),
_ => todo!("unimplemented selector"),
};
}
Ok(())
}
}
impl Selector {
pub fn from_tokens<'a>(
2020-01-18 20:24:28 -05:00
tokens: &'a mut Peekable<IntoIter<Token>>,
scope: &'a Scope,
) -> SassResult<Selector> {
2020-01-18 19:44:34 -05:00
SelectorParser::new(scope).all_selectors(tokens)
}
2020-01-18 21:05:26 -05:00
pub fn zip(&self, other: &Selector) -> Selector {
if self.0.is_empty() {
2020-01-18 21:05:26 -05:00
return Selector(other.0.clone());
2020-02-22 15:34:32 -05:00
} else if other.0.is_empty() {
return self.clone();
}
2020-01-11 16:12:23 -05:00
let mut rules: Vec<SelectorKind> = Vec::with_capacity(self.0.len() + other.0.len());
2020-01-18 21:05:26 -05:00
let sel1_split: Vec<&[SelectorKind]> =
self.0.split(|sel| sel == &SelectorKind::Multiple).collect();
let sel2_split: Vec<&[SelectorKind]> = other
.0
.split(|sel| sel == &SelectorKind::Multiple)
.collect();
2020-01-18 21:05:26 -05:00
let len1 = sel1_split.len();
let len2 = sel2_split.len();
for (idx, sel1) in sel1_split.into_iter().enumerate() {
for (idx2, sel2) in sel2_split.iter().enumerate() {
2020-01-18 21:05:26 -05:00
let mut this_selector: Vec<SelectorKind> = Vec::with_capacity(other.0.len());
2020-01-11 16:12:23 -05:00
let mut found_super = false;
2020-01-18 21:05:26 -05:00
for sel in *sel2 {
2020-01-11 16:12:23 -05:00
if sel == &SelectorKind::Super {
2020-01-18 21:05:26 -05:00
this_selector.extend(sel1.to_vec());
2020-01-11 16:12:23 -05:00
found_super = true;
2020-01-26 11:07:57 -05:00
} else if sel == &SelectorKind::InterpolatedSuper {
this_selector.extend(sel1.to_vec());
2020-01-11 16:12:23 -05:00
} else {
this_selector.push(sel.clone());
}
}
if !found_super {
2020-01-18 21:05:26 -05:00
rules.extend(sel1.to_vec());
2020-01-11 16:12:23 -05:00
rules.push(SelectorKind::Whitespace);
}
rules.extend(this_selector);
2020-01-18 21:05:26 -05:00
if !(idx + 1 == len1 && idx2 + 1 == len2) {
rules.push(SelectorKind::Multiple);
}
}
}
Selector(rules)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Attribute {
pub attr: String,
pub value: String,
pub modifier: String,
pub kind: AttributeKind,
}
impl Attribute {
pub fn from_tokens(
toks: &mut Peekable<IntoIter<Token>>,
scope: &Scope,
) -> SassResult<SelectorKind> {
devour_whitespace(toks);
let attr = if let Some(t) = toks.next() {
match t.kind {
TokenKind::Ident(mut s) => {
s.push_str(&flatten_ident(toks, scope)?);
s
}
q @ TokenKind::Symbol(Symbol::DoubleQuote)
| q @ TokenKind::Symbol(Symbol::SingleQuote) => {
2020-02-29 16:13:57 -05:00
parse_quoted_string(toks, scope, &q)?.to_string()
}
_ => return Err("Expected identifier.".into()),
}
} else {
todo!()
};
devour_whitespace(toks);
let kind = if let Some(t) = toks.next() {
match t.kind {
TokenKind::Ident(s) if s.len() == 1 => {
devour_whitespace(toks);
match toks.next().unwrap().kind {
TokenKind::Symbol(Symbol::CloseSquareBrace) => {}
_ => return Err("expected \"]\".".into()),
}
return Ok(SelectorKind::Attribute(Attribute {
kind: AttributeKind::Any,
attr,
value: String::new(),
modifier: s,
}));
}
TokenKind::Symbol(Symbol::CloseSquareBrace) => {
return Ok(SelectorKind::Attribute(Attribute {
kind: AttributeKind::Any,
attr,
value: String::new(),
modifier: String::new(),
}));
}
TokenKind::Symbol(Symbol::Equal) => AttributeKind::Equals,
TokenKind::Symbol(Symbol::Tilde) => AttributeKind::InList,
TokenKind::Symbol(Symbol::BitOr) => AttributeKind::BeginsWithHyphenOrExact,
TokenKind::Symbol(Symbol::Xor) => AttributeKind::StartsWith,
TokenKind::Symbol(Symbol::Dollar) => AttributeKind::EndsWith,
TokenKind::Symbol(Symbol::Mul) => AttributeKind::Contains,
_ => return Err("Expected \"]\".".into()),
}
} else {
todo!()
};
if kind != AttributeKind::Equals {
match toks.next().unwrap().kind {
TokenKind::Symbol(Symbol::Equal) => {}
_ => return Err("expected \"=\".".into()),
}
}
devour_whitespace(toks);
let value = if let Some(t) = toks.next() {
match t.kind {
TokenKind::Ident(mut s) => {
s.push_str(&flatten_ident(toks, scope)?);
s
}
q @ TokenKind::Symbol(Symbol::DoubleQuote)
| q @ TokenKind::Symbol(Symbol::SingleQuote) => {
2020-02-29 16:13:57 -05:00
parse_quoted_string(toks, scope, &q)?.to_string()
}
_ => return Err("Expected identifier.".into()),
}
} else {
todo!()
};
devour_whitespace(toks);
let modifier = if let Some(t) = toks.next() {
match t.kind {
TokenKind::Symbol(Symbol::CloseSquareBrace) => {
return Ok(SelectorKind::Attribute(Attribute {
kind,
attr,
value,
modifier: String::new(),
}))
}
TokenKind::Ident(s) if s.len() == 1 => {
match toks.next().unwrap().kind {
TokenKind::Symbol(Symbol::CloseSquareBrace) => {}
_ => return Err("expected \"]\".".into()),
}
format!(" {}", s)
}
_ => return Err("Expected \"]\".".into()),
}
} else {
todo!()
};
Ok(SelectorKind::Attribute(Attribute {
kind,
attr,
value,
modifier,
}))
}
}
impl Display for Attribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
AttributeKind::Any => write!(f, "[{}{}]", self.attr, self.modifier),
AttributeKind::Equals => write!(f, "[{}={}{}]", self.attr, self.value, self.modifier),
AttributeKind::InList => write!(f, "[{}~={}{}]", self.attr, self.value, self.modifier),
AttributeKind::BeginsWithHyphenOrExact => {
write!(f, "[{}|={}{}]", self.attr, self.value, self.modifier)
}
AttributeKind::StartsWith => {
write!(f, "[{}^={}{}]", self.attr, self.value, self.modifier)
}
AttributeKind::EndsWith => {
write!(f, "[{}$={}{}]", self.attr, self.value, self.modifier)
}
AttributeKind::Contains => {
write!(f, "[{}*={}{}]", self.attr, self.value, self.modifier)
}
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum AttributeKind {
/// [attr]
/// Represents elements with an attribute name of `attr`
Any,
/// [attr=value]
/// Represents elements with an attribute name of `attr` whose value is exactly `value`
Equals,
/// [attr~=value]
/// Represents elements with an attribute name of `attr` whose value is a whitespace-separated list of words, one of which is exactly `value`
InList,
/// [attr|=value]
/// Represents elements with an attribute name of `attr` whose value can be exactly value or can begin with `value` immediately followed by a hyphen (`-`)
BeginsWithHyphenOrExact,
/// [attr^=value]
StartsWith,
/// [attr$=value]
EndsWith,
/// [attr*=value]
/// Represents elements with an attribute name of `attr` whose value contains at least one occurrence of `value` within the string
Contains,
}