From d7b22a41a6f21c0f18631e2ab9174ee4597e781c Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 1 Mar 2020 12:03:14 -0500 Subject: [PATCH] Properly handle & in most contexts --- src/args.rs | 14 +++++- src/atrule/mod.rs | 16 +++++-- src/atrule/unknown.rs | 7 +-- src/function.rs | 6 ++- src/lib.rs | 6 ++- src/mixin.rs | 5 ++- src/selector.rs | 66 ++++++++++++++++++--------- src/style.rs | 17 ++++--- src/utils.rs | 39 +++++++--------- src/value/parse.rs | 100 +++++++++++++++-------------------------- tests/interpolation.rs | 4 +- tests/selectors.rs | 10 +++++ tests/styles.rs | 30 +++++++++++++ 13 files changed, 184 insertions(+), 136 deletions(-) diff --git a/src/args.rs b/src/args.rs index 9bdbb7b..a9f702a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,6 +3,7 @@ use std::iter::Peekable; use crate::common::{Scope, Symbol}; use crate::error::SassResult; +use crate::selector::Selector; use crate::utils::{devour_whitespace, devour_whitespace_or_comment}; use crate::value::Value; use crate::{Token, TokenKind}; @@ -46,6 +47,7 @@ impl CallArgs { pub(crate) fn eat_func_args>( toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { let mut args: Vec = Vec::new(); @@ -74,6 +76,7 @@ pub(crate) fn eat_func_args>( default: Some(Value::from_tokens( &mut default.into_iter().peekable(), scope, + super_selector, )?), }); break; @@ -84,6 +87,7 @@ pub(crate) fn eat_func_args>( default: Some(Value::from_tokens( &mut default.into_iter().peekable(), scope, + super_selector, )?), }); break; @@ -105,6 +109,7 @@ pub(crate) fn eat_func_args>( Some(Value::from_tokens( &mut default.into_iter().peekable(), scope, + super_selector, )?) }, }); @@ -133,6 +138,7 @@ pub(crate) fn eat_func_args>( pub(crate) fn eat_call_args>( toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { let mut args: BTreeMap = BTreeMap::new(); devour_whitespace_or_comment(toks); @@ -164,7 +170,7 @@ pub(crate) fn eat_call_args>( TokenKind::Symbol(Symbol::CloseParen) => { args.insert( name, - Value::from_tokens(&mut val.into_iter().peekable(), scope)?, + Value::from_tokens(&mut val.into_iter().peekable(), scope, super_selector)?, ); return Ok(CallArgs(args)); } @@ -179,7 +185,11 @@ pub(crate) fn eat_call_args>( args.insert( name, - Value::from_tokens(&mut val.clone().into_iter().peekable(), scope)?, + Value::from_tokens( + &mut val.clone().into_iter().peekable(), + scope, + super_selector, + )?, ); val.clear(); devour_whitespace_or_comment(toks); diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index 572186e..1ef6f21 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -67,11 +67,11 @@ impl AtRule { AtRule::Debug(pos, message) } AtRuleKind::Mixin => { - let (name, mixin) = Mixin::decl_from_tokens(toks, scope)?; + let (name, mixin) = Mixin::decl_from_tokens(toks, scope, super_selector)?; AtRule::Mixin(name, Box::new(mixin)) } AtRuleKind::Function => { - let (name, func) = Function::decl_from_tokens(toks, scope.clone())?; + let (name, func) = Function::decl_from_tokens(toks, scope.clone(), super_selector)?; AtRule::Function(name, Box::new(func)) } AtRuleKind::Return => { @@ -137,7 +137,11 @@ impl AtRule { _ => from_toks.push(tok), } } - let from = match Value::from_tokens(&mut from_toks.into_iter().peekable(), scope)? { + let from = match Value::from_tokens( + &mut from_toks.into_iter().peekable(), + scope, + super_selector, + )? { Value::Dimension(n, _) => match n.to_integer().to_usize() { Some(v) => v, None => return Err(format!("{} is not a int.", n).into()), @@ -152,7 +156,11 @@ impl AtRule { _ => to_toks.push(tok), } } - let to = match Value::from_tokens(&mut to_toks.into_iter().peekable(), scope)? { + let to = match Value::from_tokens( + &mut to_toks.into_iter().peekable(), + scope, + super_selector, + )? { Value::Dimension(n, _) => match n.to_integer().to_usize() { Some(v) => v, None => return Err(format!("{} is not a int.", n).into()), diff --git a/src/atrule/unknown.rs b/src/atrule/unknown.rs index f542ec6..f398c08 100644 --- a/src/atrule/unknown.rs +++ b/src/atrule/unknown.rs @@ -27,12 +27,7 @@ impl UnknownAtRule { match tok.kind { TokenKind::Symbol(Symbol::OpenCurlyBrace) => break, TokenKind::Interpolation => { - params.push_str( - &parse_interpolation(toks, scope)? - .into_iter() - .map(|x| x.kind.to_string()) - .collect::(), - ); + params.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string()); continue; } TokenKind::Variable(..) => params.push('$'), diff --git a/src/function.rs b/src/function.rs index 8b1a86e..8bd7321 100644 --- a/src/function.rs +++ b/src/function.rs @@ -24,6 +24,7 @@ impl Function { pub fn decl_from_tokens>( toks: &mut Peekable, mut scope: Scope, + super_selector: &Selector, ) -> SassResult<(String, Function)> { let Token { kind, .. } = toks .next() @@ -38,7 +39,7 @@ impl Function { Some(Token { kind: TokenKind::Symbol(Symbol::OpenParen), .. - }) => eat_func_args(toks, &scope)?, + }) => eat_func_args(toks, &scope, super_selector)?, _ => return Err("expected \"(\".".into()), }; @@ -83,13 +84,14 @@ impl Function { Ok(self) } - pub fn call(&self) -> SassResult { + pub fn call(&self, super_selector: &Selector) -> SassResult { for rule in &self.body { match rule { AtRule::Return(toks) => { return Value::from_tokens( &mut toks.clone().into_iter().peekable(), &self.scope, + super_selector, ) } _ => todo!("unimplemented at rule in function body"), diff --git a/src/lib.rs b/src/lib.rs index 5dadf58..191b016 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -417,7 +417,7 @@ impl<'a> StyleSheetParser<'a> { self.error(pos, "unexpected variable use at toplevel"); } let VariableDecl { val, default } = - eat_variable_value(&mut self.lexer, &self.global_scope)?; + eat_variable_value(&mut self.lexer, &self.global_scope, &Selector::new())?; if !default || self.global_scope.get_var(&name).is_err() { self.global_scope.insert_var(&name, val)?; } @@ -629,6 +629,7 @@ pub(crate) fn eat_expr>( return Ok(Some(Expr::Selector(Selector::from_tokens( &mut values.into_iter().peekable(), scope, + super_selector, )?))); } TokenKind::Variable(_) => { @@ -644,7 +645,8 @@ pub(crate) fn eat_expr>( { toks.next(); devour_whitespace(toks); - let VariableDecl { val, default } = eat_variable_value(toks, scope)?; + let VariableDecl { val, default } = + eat_variable_value(toks, scope, super_selector)?; if !default || scope.get_var(&name).is_err() { return Ok(Some(Expr::VariableDecl(name, Box::new(val)))); } diff --git a/src/mixin.rs b/src/mixin.rs index ab89a0f..37ea43c 100644 --- a/src/mixin.rs +++ b/src/mixin.rs @@ -24,6 +24,7 @@ impl Mixin { pub fn decl_from_tokens>( toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult<(String, Mixin)> { let Token { kind, .. } = toks .next() @@ -38,7 +39,7 @@ impl Mixin { Some(Token { kind: TokenKind::Symbol(Symbol::OpenParen), .. - }) => eat_func_args(toks, scope)?, + }) => eat_func_args(toks, scope, super_selector)?, Some(Token { kind: TokenKind::Symbol(Symbol::OpenCurlyBrace), .. @@ -144,7 +145,7 @@ pub(crate) fn eat_include>( match tok.kind { TokenKind::Symbol(Symbol::SemiColon) => CallArgs::new(), TokenKind::Symbol(Symbol::OpenParen) => { - let tmp = eat_call_args(toks, scope)?; + let tmp = eat_call_args(toks, scope, super_selector)?; devour_whitespace(toks); if let Some(tok) = toks.next() { assert_eq!(tok.kind, TokenKind::Symbol(Symbol::SemiColon)); diff --git a/src/selector.rs b/src/selector.rs index 5a6da84..f77fcc6 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1,5 +1,6 @@ use crate::common::{Scope, Symbol, Whitespace}; use crate::error::SassResult; +use crate::lexer::Lexer; use crate::utils::{ devour_whitespace, devour_whitespace_or_comment, flatten_ident, parse_interpolation, parse_quoted_string, IsWhitespace, @@ -8,7 +9,6 @@ use crate::{Token, TokenKind}; use std::fmt::{self, Display, Write}; use std::iter::Peekable; use std::string::ToString; -use std::vec::IntoIter; #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Selector(pub Vec); @@ -160,20 +160,25 @@ impl Display for SelectorKind { struct SelectorParser<'a> { scope: &'a Scope, + super_selector: &'a Selector, selectors: Vec, is_interpolated: bool, } impl<'a> SelectorParser<'a> { - const fn new(scope: &'a Scope) -> SelectorParser<'a> { + const fn new(scope: &'a Scope, super_selector: &'a Selector) -> SelectorParser<'a> { SelectorParser { scope, + super_selector, selectors: Vec::new(), is_interpolated: false, } } - fn all_selectors(mut self, tokens: &'a mut Peekable>) -> SassResult { + fn all_selectors>( + mut self, + tokens: &'a mut Peekable, + ) -> SassResult { self.tokens_to_selectors(tokens)?; // remove trailing whitespace while let Some(x) = self.selectors.pop() { @@ -185,9 +190,9 @@ impl<'a> SelectorParser<'a> { Ok(Selector(self.selectors)) } - fn consume_pseudo_selector( + fn consume_pseudo_selector>( &mut self, - tokens: &'_ mut Peekable>, + tokens: &'_ mut Peekable, ) -> SassResult<()> { if let Some(tok) = tokens.next() { match tok.kind { @@ -232,14 +237,20 @@ impl<'a> SelectorParser<'a> { Ok(()) } - fn tokens_to_selectors(&mut self, tokens: &'_ mut Peekable>) -> SassResult<()> { + fn tokens_to_selectors>( + &mut self, + tokens: &'_ mut Peekable, + ) -> SassResult<()> { while tokens.peek().is_some() { self.consume_selector(tokens)?; } Ok(()) } - fn consume_selector(&mut self, tokens: &'_ mut Peekable>) -> SassResult<()> { + fn consume_selector>( + &mut self, + tokens: &'_ mut Peekable, + ) -> SassResult<()> { if devour_whitespace_or_comment(tokens) { if let Some(Token { kind: TokenKind::Symbol(Symbol::Comma), @@ -283,15 +294,21 @@ impl<'a> SelectorParser<'a> { TokenKind::Interpolation => { self.is_interpolated = true; self.tokens_to_selectors( - &mut parse_interpolation(tokens, self.scope)? - .into_iter() - .peekable(), + &mut Lexer::new( + &parse_interpolation( + tokens, + self.scope, + &Selector(vec![SelectorKind::Element(String::from("&"))]), + )? + .to_string(), + ) + .peekable(), )?; self.is_interpolated = false; } - TokenKind::Symbol(Symbol::OpenSquareBrace) => self - .selectors - .push(Attribute::from_tokens(tokens, self.scope)?), + TokenKind::Symbol(Symbol::OpenSquareBrace) => self.selectors.push( + Attribute::from_tokens(tokens, self.scope, self.super_selector)?, + ), _ => todo!("unimplemented selector"), }; } @@ -300,11 +317,12 @@ impl<'a> SelectorParser<'a> { } impl Selector { - pub fn from_tokens<'a>( - tokens: &'a mut Peekable>, + pub fn from_tokens<'a, I: Iterator>( + tokens: &'a mut Peekable, scope: &'a Scope, + super_selector: &'a Selector, ) -> SassResult { - SelectorParser::new(scope).all_selectors(tokens) + SelectorParser::new(scope, super_selector).all_selectors(tokens) } pub fn zip(&self, other: &Selector) -> Selector { @@ -390,20 +408,24 @@ pub(crate) struct Attribute { } impl Attribute { - pub fn from_tokens( - toks: &mut Peekable>, + pub fn from_tokens>( + toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { 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.push_str(&flatten_ident(toks, scope, super_selector)?); s } + TokenKind::Interpolation => { + parse_interpolation(toks, scope, super_selector)?.to_string() + } q @ TokenKind::Symbol(Symbol::DoubleQuote) | q @ TokenKind::Symbol(Symbol::SingleQuote) => { - parse_quoted_string(toks, scope, &q)?.to_string() + parse_quoted_string(toks, scope, &q, super_selector)?.to_string() } _ => return Err("Expected identifier.".into()), } @@ -460,12 +482,12 @@ impl Attribute { let value = if let Some(t) = toks.next() { match t.kind { TokenKind::Ident(mut s) => { - s.push_str(&flatten_ident(toks, scope)?); + s.push_str(&flatten_ident(toks, scope, super_selector)?); s } q @ TokenKind::Symbol(Symbol::DoubleQuote) | q @ TokenKind::Symbol(Symbol::SingleQuote) => { - parse_quoted_string(toks, scope, &q)?.to_string() + parse_quoted_string(toks, scope, &q, super_selector)?.to_string() } _ => return Err("Expected identifier.".into()), } diff --git a/src/style.rs b/src/style.rs index 456f819..443a973 100644 --- a/src/style.rs +++ b/src/style.rs @@ -88,7 +88,9 @@ impl<'a> StyleParser<'a> { | ref q @ TokenKind::Symbol(Symbol::SingleQuote) => { let q = q.clone(); toks.next(); - let (s, q) = if let Value::Ident(s, q) = parse_quoted_string(toks, scope, &q)? { + let (s, q) = if let Value::Ident(s, q) = + parse_quoted_string(toks, scope, &q, self.super_selector)? + { (s, q) } else { unreachable!() @@ -107,7 +109,7 @@ impl<'a> StyleParser<'a> { | TokenKind::Symbol(Symbol::SemiColon) => break, TokenKind::Symbol(Symbol::BitAnd) => { style.push(Token { - kind: TokenKind::Ident(self.super_selector.to_string()), + kind: TokenKind::Symbol(Symbol::BitAnd), pos: Pos::new(), }); toks.next(); @@ -117,7 +119,11 @@ impl<'a> StyleParser<'a> { }; style.push(toks.next().unwrap()); } - Value::from_tokens(&mut style.into_iter().peekable(), self.scope) + Value::from_tokens( + &mut style.into_iter().peekable(), + self.scope, + self.super_selector, + ) } pub(crate) fn eat_style_group>( @@ -236,10 +242,7 @@ impl<'a> StyleParser<'a> { TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => continue, TokenKind::Ident(ref s) => property.push_str(s), TokenKind::Interpolation => property.push_str( - &parse_interpolation(toks, self.scope)? - .iter() - .map(|x| x.kind.to_string()) - .collect::(), + &parse_interpolation(toks, self.scope, self.super_selector)?.to_string(), ), TokenKind::Symbol(Symbol::Colon) => break, TokenKind::Symbol(Symbol::BitAnd) => { diff --git a/src/utils.rs b/src/utils.rs index ff48813..db9a6f3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ use crate::common::{Keyword, QuoteKind, Symbol}; use crate::error::SassResult; use crate::lexer::Lexer; +use crate::selector::Selector; use crate::value::Value; use crate::{Scope, Token, TokenKind}; use std::iter::{Iterator, Peekable}; @@ -44,7 +45,8 @@ pub(crate) fn devour_whitespace_or_comment, W: IsWhitespac pub(crate) fn parse_interpolation>( tokens: &mut Peekable, scope: &Scope, -) -> SassResult> { + super_selector: &Selector, +) -> SassResult { let mut val = String::new(); while let Some(tok) = tokens.next() { match tok.kind { @@ -54,27 +56,27 @@ pub(crate) fn parse_interpolation>( } q @ TokenKind::Symbol(Symbol::DoubleQuote) | q @ TokenKind::Symbol(Symbol::SingleQuote) => { - val.push_str(&parse_quoted_string(tokens, scope, &q)?.to_string()) + val.push_str(&parse_quoted_string(tokens, scope, &q, super_selector)?.to_string()) } TokenKind::Variable(ref v) => { val.push_str(&scope.get_var(v)?.clone().unquote().to_string()) } TokenKind::Interpolation => val.push_str( - &parse_interpolation(tokens, scope)? - .iter() + &Lexer::new(&parse_interpolation(tokens, scope, super_selector)?.to_string()) .map(|x| x.kind.to_string()) .collect::(), ), _ => val.push_str(&tok.kind.to_string()), } } - Ok(Lexer::new( - &Value::from_tokens(&mut Lexer::new(&val).peekable(), scope)? + if val.trim().is_empty() { + return Ok(Value::Ident(String::new(), QuoteKind::None)); + } + Ok( + Value::from_tokens(&mut Lexer::new(&val).peekable(), scope, super_selector)? .eval()? - .unquote() - .to_string(), + .unquote(), ) - .collect()) } pub(crate) struct VariableDecl { @@ -91,6 +93,7 @@ impl VariableDecl { pub(crate) fn eat_variable_value>( toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { devour_whitespace(toks); let mut default = false; @@ -122,25 +125,21 @@ pub(crate) fn eat_variable_value>( } } devour_whitespace(toks); - let val = Value::from_tokens(&mut raw.into_iter().peekable(), scope).unwrap(); + let val = Value::from_tokens(&mut raw.into_iter().peekable(), scope, super_selector).unwrap(); Ok(VariableDecl::new(val, default)) } pub(crate) fn flatten_ident>( toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { let mut s = String::new(); while let Some(tok) = toks.peek() { match tok.kind.clone() { TokenKind::Interpolation => { toks.next(); - s.push_str( - &parse_interpolation(toks, scope)? - .iter() - .map(|x| x.kind.to_string()) - .collect::(), - ) + s.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string()) } TokenKind::Ident(ref i) => { toks.next(); @@ -160,6 +159,7 @@ pub(crate) fn parse_quoted_string>( toks: &mut Peekable, scope: &Scope, q: &TokenKind, + super_selector: &Selector, ) -> SassResult { let mut s = String::new(); let mut is_escaped = false; @@ -194,12 +194,7 @@ pub(crate) fn parse_quoted_string>( } TokenKind::Interpolation if !is_escaped => { found_interpolation = true; - s.push_str( - &parse_interpolation(toks, scope)? - .iter() - .map(|x| x.kind.to_string()) - .collect::(), - ); + s.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string()); continue; } TokenKind::Interpolation => { diff --git a/src/value/parse.rs b/src/value/parse.rs index d1e4dea..47a258b 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -10,8 +10,11 @@ use crate::builtin::GLOBAL_FUNCTIONS; use crate::color::Color; use crate::common::{Keyword, ListSeparator, Op, QuoteKind, Scope, Symbol}; use crate::error::SassResult; +use crate::selector::Selector; use crate::units::Unit; -use crate::utils::{devour_whitespace_or_comment, parse_interpolation, parse_quoted_string}; +use crate::utils::{ + devour_whitespace_or_comment, flatten_ident, parse_interpolation, parse_quoted_string, +}; use crate::value::Value; use crate::{Token, TokenKind}; @@ -65,42 +68,13 @@ fn parse_hex(s: &str) -> Value { } } -fn flatten_ident>( - toks: &mut Peekable, - scope: &Scope, -) -> SassResult { - let mut s = String::new(); - while let Some(tok) = toks.peek() { - match tok.kind.clone() { - TokenKind::Interpolation => { - toks.next(); - s.push_str( - &parse_interpolation(toks, scope)? - .iter() - .map(|x| x.kind.to_string()) - .collect::(), - ) - } - TokenKind::Ident(ref i) => { - toks.next(); - s.push_str(i) - } - TokenKind::Number(ref n) => { - toks.next(); - s.push_str(n) - } - _ => break, - } - } - Ok(s) -} - impl Value { pub fn from_tokens>( toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { - let left = Self::_from_tokens(toks, scope)?; + let left = Self::_from_tokens(toks, scope, super_selector)?; let whitespace = devour_whitespace_or_comment(toks); let next = match toks.peek() { Some(x) => x, @@ -113,7 +87,7 @@ impl Value { TokenKind::Symbol(Symbol::Comma) => { toks.next(); devour_whitespace_or_comment(toks); - let right = match Self::from_tokens(toks, scope) { + let right = match Self::from_tokens(toks, scope, super_selector) { Ok(x) => x, Err(_) => return Ok(left), }; @@ -136,7 +110,7 @@ impl Value { }; toks.next(); devour_whitespace_or_comment(toks); - let right = match Self::from_tokens(toks, scope) { + let right = match Self::from_tokens(toks, scope, super_selector) { Ok(x) => x, Err(_) => return Ok(left), }; @@ -144,7 +118,7 @@ impl Value { } _ => { devour_whitespace_or_comment(toks); - let right = match Self::from_tokens(toks, scope) { + let right = match Self::from_tokens(toks, scope, super_selector) { Ok(x) => x, Err(_) => return Ok(left), }; @@ -156,6 +130,7 @@ impl Value { fn _from_tokens>( toks: &mut Peekable, scope: &Scope, + super_selector: &Selector, ) -> SassResult { let kind = if let Some(tok) = toks.next() { tok.kind @@ -203,7 +178,7 @@ impl Value { } TokenKind::Symbol(Symbol::OpenParen) => { devour_whitespace_or_comment(toks); - let val = Self::from_tokens(toks, scope)?; + let val = Self::from_tokens(toks, scope, super_selector)?; assert_eq!( toks.next().unwrap().kind, TokenKind::Symbol(Symbol::CloseParen) @@ -211,20 +186,13 @@ impl Value { Ok(Value::Paren(Box::new(val))) } TokenKind::Symbol(Symbol::BitAnd) => { - Ok(Value::Ident(String::from("&"), QuoteKind::None)) + Ok(Value::Ident(super_selector.to_string(), QuoteKind::None)) + } + TokenKind::Symbol(Symbol::Hash) => { + Ok(parse_hex(&flatten_ident(toks, scope, super_selector)?)) } - TokenKind::Symbol(Symbol::Hash) => Ok(parse_hex(&flatten_ident(toks, scope)?)), - // TokenKind::Interpolation => { - // Ok(Value::Ident( - // parse_interpolation(toks, scope) - // .iter() - // .map(|x| x.kind.to_string()) - // .collect::(), - // QuoteKind::None - // )) - // } TokenKind::Ident(mut s) => { - s.push_str(&flatten_ident(toks, scope)?); + s.push_str(&flatten_ident(toks, scope, super_selector)?); match toks.peek() { Some(Token { kind: TokenKind::Symbol(Symbol::OpenParen), @@ -234,7 +202,12 @@ impl Value { let func = match scope.get_fn(&s) { Ok(f) => f, Err(_) => match GLOBAL_FUNCTIONS.get(&s) { - Some(f) => return f(&mut eat_call_args(toks, scope)?, scope), + Some(f) => { + return f( + &mut eat_call_args(toks, scope, super_selector)?, + scope, + ) + } None => { s.push('('); let mut unclosed_parens = 0; @@ -244,10 +217,8 @@ impl Value { unclosed_parens += 1; } TokenKind::Interpolation => s.push_str( - &parse_interpolation(toks, scope)? - .iter() - .map(|x| x.kind.to_string()) - .collect::(), + &parse_interpolation(toks, scope, super_selector)? + .to_string(), ), TokenKind::Variable(v) => { s.push_str(&scope.get_var(v)?.to_string()) @@ -270,8 +241,8 @@ impl Value { }; Ok(func .clone() - .args(&mut eat_call_args(toks, scope)?)? - .call()?) + .args(&mut eat_call_args(toks, scope, super_selector)?)? + .call(super_selector)?) } _ => { if let Ok(c) = crate::color::ColorName::try_from(s.as_ref()) { @@ -283,22 +254,18 @@ impl Value { } } q @ TokenKind::Symbol(Symbol::DoubleQuote) - | q @ TokenKind::Symbol(Symbol::SingleQuote) => parse_quoted_string(toks, scope, &q), + | q @ TokenKind::Symbol(Symbol::SingleQuote) => { + parse_quoted_string(toks, scope, &q, super_selector) + } TokenKind::Variable(ref v) => Ok(scope.get_var(v)?.clone()), TokenKind::Interpolation => { - let mut s = parse_interpolation(toks, scope)? - .iter() - .map(|x| x.kind.to_string()) - .collect::(); + let mut s = parse_interpolation(toks, scope, super_selector)?.to_string(); while let Some(tok) = toks.peek() { match tok.kind.clone() { TokenKind::Interpolation => { toks.next(); s.push_str( - &parse_interpolation(toks, scope)? - .iter() - .map(|x| x.kind.to_string()) - .collect::(), + &parse_interpolation(toks, scope, super_selector)?.to_string(), ) } TokenKind::Ident(ref i) => { @@ -318,7 +285,10 @@ impl Value { TokenKind::Keyword(Keyword::Through(s)) => Ok(Value::Ident(s, QuoteKind::None)), TokenKind::Keyword(Keyword::To(s)) => Ok(Value::Ident(s, QuoteKind::None)), TokenKind::Unknown(c) => Ok(Value::Ident(c.to_string(), QuoteKind::None)), - _ => Err("Unexpected token in value parsing".into()), + v => { + dbg!(v); + panic!("Unexpected token in value parsing") + } } } } diff --git a/tests/interpolation.rs b/tests/interpolation.rs index 318db4f..4d68ee5 100644 --- a/tests/interpolation.rs +++ b/tests/interpolation.rs @@ -25,11 +25,11 @@ test!( ); test!( preserves_inner_single_quotes, - "a {\n color: #{\"''\"};\n}\n", + "a {\n color: #{\"''\"};\n}\n", "a {\n color: '';\n}\n" ); test!( single_quotes_converted_to_double_when_interpolated, - "a {\n color: '#{foo}';\n}\n", + "a {\n color: '#{foo}';\n}\n", "a {\n color: \"foo\";\n}\n" ); diff --git a/tests/selectors.rs b/tests/selectors.rs index c0b2cd3..3e95e81 100644 --- a/tests/selectors.rs +++ b/tests/selectors.rs @@ -95,6 +95,11 @@ test!( selector_el_attribute_descendant, "a [attr] {\n color: red;\n}\n" ); +test!( + selector_attribute_interpolated, + "a {\n [#{&}] {\n color: red;\n }\n}\n", + "a [a] {\n color: red;\n}\n" +); test!(selector_el_mul_el, "a, b {\n color: red;\n}\n"); test!( selector_el_immediate_child_el, @@ -293,3 +298,8 @@ test!( "a, %b, c {\n color: red;\n}\n", "a, c {\n color: red;\n}\n" ); +// test!( +// removes_leading_space, +// "#{&} a {\n color: red;\n}\n", +// "a {\n color: red;\n}\n" +// ); diff --git a/tests/styles.rs b/tests/styles.rs index 240d1fc..2bbb9c7 100644 --- a/tests/styles.rs +++ b/tests/styles.rs @@ -155,3 +155,33 @@ test!( styles_after_quoted, "a {\n color: \"red\";\n color: blue;\n}\n" ); +test!( + interpolated_super_selector_in_style, + "a {\n color: #{&};\n}\n", + "a {\n color: a;\n}\n" +); +test!( + interpolated_super_selector_in_style_symbols, + "* .a #b:foo() {\n color: #{&};\n}\n", + "* .a #b:foo() {\n color: * .a #b:foo();\n}\n" +); +test!( + uninterpolated_super_selector, + "* .a #b:foo() {\n color: &;\n}\n", + "* .a #b:foo() {\n color: * .a #b:foo();\n}\n" +); +test!( + interpolated_super_selector_in_selector_and_style, + "a {\n b #{&} {\n color: &;\n }\n}\n", + "a b a {\n color: a b a;\n}\n" +); +test!( + emits_leading_whitespace, + "a {\n color: unquote(\" foo\");\n}\n", + "a {\n color: foo;\n}\n" +); +test!( + emits_trailing_whitespace, + "a {\n color: unquote(\"foo \");\n}\n", + "a {\n color: foo ;\n}\n" +);