From bd4b38550cde66ce60216066c72184ed76652cdd Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 12 Jan 2020 10:54:46 -0500 Subject: [PATCH] Handle interpolation inside selectors and styles --- src/main.rs | 99 ++++++++++++++++++++++++++++++++++++----- src/selector.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++++- src/style.rs | 41 ++++++++++------- 3 files changed, 228 insertions(+), 27 deletions(-) diff --git a/src/main.rs b/src/main.rs index 53afd5e..d6e80ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ clippy::let_underscore_must_use, clippy::module_name_repetitions )] -// todo! handle erroring on styles at the toplevel +// todo! handle erroring on styles at the toplevel use std::collections::HashMap; use std::fmt::{self, Display}; use std::fs; @@ -65,7 +65,6 @@ pub struct Token { pub enum TokenKind { Ident(String), Symbol(Symbol), - Function(String, Vec), AtRule(AtRule), Keyword(Keyword), Number(String), @@ -75,6 +74,7 @@ pub enum TokenKind { Attribute(Attribute), Op(Op), MultilineComment(String), + Interpolation, } impl Display for TokenKind { @@ -87,10 +87,12 @@ impl Display for TokenKind { TokenKind::Unit(s) => write!(f, "{}", s), TokenKind::Whitespace(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), TokenKind::Variable(s) => write!(f, "${}", s), + TokenKind::Interpolation => { + panic!("we don't want to format TokenKind::Interpolation using Display") + } } } } @@ -139,6 +141,8 @@ enum Expr { VariableDecl(String, Vec), /// A multiline comment: `/* foobar */` MultilineComment(String), + // /// Function call: `calc(10vw - 1px)` + // FuncCall(String, Vec), } impl StyleSheet { @@ -200,6 +204,7 @@ impl<'a> StyleSheetParser<'a> { match kind.clone() { TokenKind::Ident(_) | TokenKind::Attribute(_) + | TokenKind::Interpolation | TokenKind::Symbol(Symbol::Hash) | TokenKind::Symbol(Symbol::Colon) | TokenKind::Symbol(Symbol::Mul) @@ -239,7 +244,7 @@ impl<'a> StyleSheetParser<'a> { } else { unsafe { std::hint::unreachable_unchecked() } } - }, + } }; } Ok(StyleSheet { rules }) @@ -314,9 +319,7 @@ impl<'a> StyleSheetParser<'a> { iter2 } - fn eat_func_call(&mut self) { - - } + fn eat_func_call(&mut self) {} fn eat_rules( &mut self, @@ -373,6 +376,7 @@ impl<'a> StyleSheetParser<'a> { return Ok(Expr::Selector(Selector::from_tokens( values.iter().peekable(), super_selector, + vars, ))); } TokenKind::Variable(_) => { @@ -413,6 +417,15 @@ impl<'a> StyleSheetParser<'a> { } } TokenKind::AtRule(_) => self.eat_at_rule(), + TokenKind::Interpolation => { + while let Some(tok) = self.lexer.next() { + if tok.kind == TokenKind::Symbol(Symbol::CloseBrace) { + values.push(tok); + break; + } + values.push(tok); + } + } _ => { if let Some(tok) = self.lexer.next() { values.push(tok.clone()) @@ -716,9 +729,75 @@ mod test_css { "a {\n&--b {\n color: red;\n}\n}\n", "a--b {\n color: red;\n}\n" ); + // test!( + // bem_underscore_selector, + // "a {\n&__b {\n color: red;\n}\n}\n", + // "a__b {\n color: red;\n}\n" + // ); test!( - bem_underscore_selector, - "a {\n&__b {\n color: red;\n}\n}\n", - "a__b {\n color: red;\n}\n" + selector_interpolation_start, + "#{a}bc {\n color: red;\n}\n", + "abc {\n color: red;\n}\n" + ); + test!( + selector_interpolation_middle, + "a#{b}c {\n color: red;\n}\n", + "abc {\n color: red;\n}\n" + ); + test!( + selector_interpolation_end, + "ab#{c} {\n color: red;\n}\n", + "abc {\n color: red;\n}\n" + ); + test!( + selector_interpolation_variable, + "$a: foo;\nab#{$a} {\n color: red;\n}\n", + "abfoo {\n color: red;\n}\n" + ); + test!( + style_interpolation_start, + "a {\n #{c}olor: red;\n}\n", + "a {\n color: red;\n}\n" + ); + test!( + style_interpolation_middle, + "a {\n co#{l}or: red;\n}\n", + "a {\n color: red;\n}\n" + ); + test!( + style_interpolation_end, + "a {\n colo#{r}: red;\n}\n", + "a {\n color: red;\n}\n" + ); + test!( + style_interpolation_variable, + "$a: foo;\na {\n co#{$a}lor: red;\n}\n", + "a {\n cofoolor: red;\n}\n" + ); + + test!( + style_val_interpolation_start, + "a {\n color: #{r}ed;\n}\n", + "a {\n color: red;\n}\n" + ); + test!( + style_val_interpolation_middle, + "a {\n color: r#{e}d;\n}\n", + "a {\n color: red;\n}\n" + ); + test!( + style_val_interpolation_end, + "a {\n color: re#{d};\n}\n", + "a {\n color: red;\n}\n" + ); + test!( + style_val_interpolation_variable, + "$a: foo;\na {\n color: r#{$a}ed;\n}\n", + "a {\n color: rfooed;\n}\n" + ); + test!( + style_whitespace, + "a {\n color : red ; \n}\n", + "a {\n color: red;\n}\n" ); } diff --git a/src/selector.rs b/src/selector.rs index f0bf27d..3ec4361 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1,5 +1,6 @@ use crate::common::Symbol; use crate::{Token, TokenKind}; +use std::collections::HashMap; use std::fmt::{self, Display}; use std::iter::Peekable; use std::slice::Iter; @@ -17,6 +18,19 @@ fn devour_whitespace(i: &mut Peekable>) -> bool { found_whitespace } +fn devour_whitespace_from_tokens(i: &mut Peekable>) -> bool { + let mut found_whitespace = false; + while let Some(Token { + kind: TokenKind::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(); @@ -83,6 +97,8 @@ pub enum SelectorKind { /// Used to signify no selector (when there is no super_selector of a rule) None, Whitespace, + /// Intemediate value to simplify usage with identifier interpolation + Several(Vec), } impl Display for SelectorKind { @@ -106,6 +122,9 @@ impl Display for SelectorKind { toks.iter().map(ToString::to_string).collect::() ), SelectorKind::Super | SelectorKind::None => write!(f, ""), + SelectorKind::Several(_) => { + panic!("SelectorKind::Several should not be rendered using Display") + } } } } @@ -161,23 +180,113 @@ mod test_selector_display { struct SelectorParser<'a> { tokens: Peekable>, super_selector: &'a Selector, + vars: &'a HashMap>, +} + +/// Methods to handle dealing with interpolation +impl<'a> SelectorParser<'a> { + fn consume_interpolation(&mut self) -> SelectorKind { + let mut v = Vec::new(); + let toks = self + .tokens + .by_ref() + .take_while(|x| x.kind != TokenKind::Symbol(Symbol::CloseBrace)) + .cloned() + .collect::>(); //.iter().peekable(); + let mut toks = toks.iter().peekable(); + while let Some(Token { kind, .. }) = toks.peek() { + if let TokenKind::Variable(_) = kind { + toks.next(); + let these_toks = self.deref_variable(kind); + let mut these_toks = these_toks.iter().peekable(); + while let Some(s) = self.selector_from_token_stream(&mut these_toks) { + v.push(s); + } + } else if let Some(s) = self.selector_from_token_stream(&mut toks) { + v.push(s); + } else { + return SelectorKind::Several(v); + } + } + SelectorKind::Several(v) + } + + fn selector_from_token_stream( + &mut self, + toks: &mut Peekable>, + ) -> Option { + if devour_whitespace_from_tokens(toks) { + if let Some(&&Token { + kind: TokenKind::Symbol(Symbol::Comma), + .. + }) = toks.peek() + { + toks.next(); + return Some(SelectorKind::Multiple); + } + return Some(SelectorKind::Whitespace); + } + if let Some(Token { kind, .. }) = toks.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) => return self.consume_pseudo_selector(), + 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::Interpolation => self.consume_interpolation(), + TokenKind::Attribute(attr) => SelectorKind::Attribute(attr.clone()), + _ => todo!("unimplemented selector"), + }); + } + None + } + + fn deref_variable(&mut self, variable: &TokenKind) -> Vec { + let mut val = Vec::with_capacity(25); + let v = match variable { + TokenKind::Variable(ref v) => { + self.vars.get(v).expect("todo! expected variable to exist") + } + _ => todo!("expected variable"), + } + .iter() + .peekable(); + for tok in v { + match &tok.kind { + TokenKind::Variable(_) => val.extend(self.deref_variable(&tok.kind)), + _ => val.push(tok.clone()), + }; + } + val + } } impl<'a> SelectorParser<'a> { const fn new( tokens: Peekable>, super_selector: &'a Selector, + vars: &'a HashMap>, ) -> SelectorParser<'a> { SelectorParser { tokens, super_selector, + vars, } } fn all_selectors(&mut self) -> Selector { let mut v = Vec::with_capacity(self.tokens.len()); while let Some(s) = self.consume_selector() { - v.push(s); + if let SelectorKind::Several(sels) = s { + v.extend(sels); + } else { + v.push(s); + } } while let Some(x) = v.pop() { if x != SelectorKind::Whitespace { @@ -242,6 +351,7 @@ impl<'a> SelectorParser<'a> { TokenKind::Symbol(Symbol::Tilde) => SelectorKind::Preceding, TokenKind::Symbol(Symbol::Mul) => SelectorKind::Universal, TokenKind::Symbol(Symbol::BitAnd) => SelectorKind::Super, + TokenKind::Interpolation => self.consume_interpolation(), TokenKind::Attribute(attr) => SelectorKind::Attribute(attr.clone()), _ => todo!("unimplemented selector"), }); @@ -268,8 +378,9 @@ impl Selector { pub fn from_tokens<'a>( tokens: Peekable>, super_selector: &'a Selector, + vars: &'a HashMap>, ) -> Selector { - SelectorParser::new(tokens, super_selector).all_selectors() + SelectorParser::new(tokens, super_selector, vars).all_selectors() } pub fn zip(self, other: Selector) -> Selector { diff --git a/src/style.rs b/src/style.rs index 1e8f798..630479d 100644 --- a/src/style.rs +++ b/src/style.rs @@ -14,7 +14,7 @@ pub struct Style { impl Display for Style { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{};", self.property, self.value) + write!(f, "{}: {};", self.property, self.value) } } @@ -76,28 +76,33 @@ impl<'a> StyleParser<'a> { } } + fn eat_interpolation(&mut self) -> String { + let mut val = String::new(); + while let Some(Token { kind, .. }) = self.tokens.next() { + match &kind { + TokenKind::Symbol(Symbol::CloseBrace) => break, + TokenKind::Symbol(Symbol::OpenBrace) => todo!("invalid character in interpolation"), + TokenKind::Variable(_) => val.push_str(&self.deref_variable(kind)), + _ => val.push_str(&kind.to_string()), + } + } + val + } + fn parse(&mut self) -> Style { let mut property = String::new(); - // read property + // read property until `:` while let Some(Token { kind, .. }) = self.tokens.next() { match kind { TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => continue, - TokenKind::Ident(ref s) => { - property = s.clone(); - break; - } - _ => todo!(), + TokenKind::Ident(ref s) => property.push_str(s), + TokenKind::Interpolation => property.push_str(&self.eat_interpolation()), + TokenKind::Symbol(Symbol::Colon) => break, + _ => property.push_str(&kind.to_string()), }; } - // read until `:` - while let Some(Token { kind, .. }) = self.tokens.next() { - match kind { - TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => continue, - TokenKind::Symbol(Symbol::Colon) => break, - _ => todo!("found tokens before style value"), - } - } + self.devour_whitespace_or_comment(); let mut value = String::new(); @@ -119,6 +124,11 @@ impl<'a> StyleParser<'a> { break; } } + TokenKind::Interpolation => { + self.tokens.next(); + value.push_str(&self.eat_interpolation()); + break; + } _ => {} } value.push(' '); @@ -127,6 +137,7 @@ impl<'a> StyleParser<'a> { } TokenKind::Variable(_) => value.push_str(&self.deref_variable(&tok.kind)), TokenKind::MultilineComment(_) => continue, + TokenKind::Interpolation => value.push_str(&self.eat_interpolation()), _ => value.push_str(&tok.kind.to_string()), } }