diff --git a/src/lib.rs b/src/lib.rs index 7f439b3..7a3f06a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,13 @@ impl Token { pub fn equals_symbol(&self, s: Symbol) -> bool { self.kind.equals_symbol(s) } + + pub fn from_string(s: String) -> Self { + Token { + kind: TokenKind::Ident(s), + pos: Pos::new(), + } + } } impl IsWhitespace for Token { diff --git a/src/style.rs b/src/style.rs index 1e98c7e..70bb245 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,7 +1,7 @@ use crate::common::{Pos, Scope, Symbol}; use crate::error::SassResult; use crate::selector::Selector; -use crate::utils::{devour_whitespace, parse_interpolation}; +use crate::utils::{devour_whitespace, parse_interpolation, parse_quoted_string}; use crate::value::Value; use crate::{Expr, Token, TokenKind}; use std::fmt::{self, Display}; @@ -35,7 +35,7 @@ impl Style { scope: &Scope, super_selector: &Selector, ) -> SassResult { - StyleParser::new(scope, super_selector).parse_style_value(toks) + StyleParser::new(scope, super_selector).parse_style_value(toks, scope) } pub fn from_tokens>( @@ -44,7 +44,7 @@ impl Style { super_selector: &Selector, super_property: String, ) -> SassResult { - StyleParser::new(scope, super_selector).eat_style_group(toks, super_property) + StyleParser::new(scope, super_selector).eat_style_group(toks, super_property, scope) } } @@ -64,6 +64,7 @@ impl<'a> StyleParser<'a> { pub(crate) fn parse_style_value>( &self, toks: &mut Peekable, + scope: &Scope, ) -> SassResult { let mut style = Vec::new(); let mut n = 0; @@ -83,6 +84,16 @@ impl<'a> StyleParser<'a> { n -= 1; } } + ref q @ TokenKind::Symbol(Symbol::DoubleQuote) + | ref q @ TokenKind::Symbol(Symbol::SingleQuote) => { + let q = q.clone(); + let tok = toks.next().unwrap(); + style.push(tok.clone()); + style.push(Token::from_string( + parse_quoted_string(toks, scope, q)?.unquote().to_string(), + )); + style.push(tok); + } TokenKind::Symbol(Symbol::OpenCurlyBrace) | TokenKind::Symbol(Symbol::SemiColon) => break, TokenKind::Symbol(Symbol::BitAnd) => { @@ -104,6 +115,7 @@ impl<'a> StyleParser<'a> { &self, toks: &mut Peekable, super_property: String, + scope: &Scope, ) -> SassResult { let mut styles = Vec::new(); devour_whitespace(toks); @@ -116,7 +128,7 @@ impl<'a> StyleParser<'a> { let property = self.parse_property(toks, super_property.clone())?; if let Some(tok) = toks.peek() { if tok.equals_symbol(Symbol::OpenCurlyBrace) { - match self.eat_style_group(toks, property)? { + match self.eat_style_group(toks, property, scope)? { Expr::Styles(s) => styles.extend(s), Expr::Style(s) => styles.push(*s), _ => unreachable!(), @@ -134,7 +146,7 @@ impl<'a> StyleParser<'a> { continue; } } - let value = self.parse_style_value(toks)?; + let value = self.parse_style_value(toks, scope)?; match toks.peek().unwrap().kind { TokenKind::Symbol(Symbol::CloseCurlyBrace) => { styles.push(Style { property, value }); @@ -149,7 +161,7 @@ impl<'a> StyleParser<'a> { property: property.clone(), value, }); - match self.eat_style_group(toks, property)? { + match self.eat_style_group(toks, property, scope)? { Expr::Style(s) => styles.push(*s), Expr::Styles(s) => styles.extend(s), _ => unreachable!(), @@ -173,7 +185,7 @@ impl<'a> StyleParser<'a> { } } _ => { - let val = self.parse_style_value(toks)?; + let val = self.parse_style_value(toks, scope)?; match toks.peek().unwrap().kind { TokenKind::Symbol(Symbol::CloseCurlyBrace) => {} TokenKind::Symbol(Symbol::SemiColon) => { @@ -185,7 +197,7 @@ impl<'a> StyleParser<'a> { property: super_property.clone(), value: val, }]; - match self.eat_style_group(toks, super_property)? { + match self.eat_style_group(toks, super_property, scope)? { Expr::Style(s) => v.push(*s), Expr::Styles(s) => v.extend(s), _ => unreachable!(), diff --git a/src/utils.rs b/src/utils.rs index 939521c..7d961ae 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -171,14 +171,26 @@ pub(crate) fn parse_quoted_string>( { break } + TokenKind::Symbol(Symbol::DoubleQuote) if is_escaped => { + s.push('\\'); + s.push('"'); + is_escaped = false; + continue; + } TokenKind::Symbol(Symbol::SingleQuote) if !is_escaped && q == TokenKind::Symbol(Symbol::SingleQuote) => { break } + TokenKind::Symbol(Symbol::SingleQuote) if is_escaped => { + s.push('\\'); + s.push('\''); + is_escaped = false; + continue; + } TokenKind::Symbol(Symbol::BackSlash) if !is_escaped => is_escaped = true, TokenKind::Symbol(Symbol::BackSlash) => s.push('\\'), - TokenKind::Interpolation => { + TokenKind::Interpolation if !is_escaped => { found_interpolation = true; s.push_str( &parse_interpolation(toks, scope)? @@ -188,12 +200,20 @@ pub(crate) fn parse_quoted_string>( ); continue; } + TokenKind::Interpolation => { + s.push('#'); + s.push('{'); + is_escaped = false; + continue; + } _ => {} } if is_escaped && tok.kind != TokenKind::Symbol(Symbol::BackSlash) { is_escaped = false; } - s.push_str(&tok.kind.to_string()); + if tok.kind != TokenKind::Symbol(Symbol::BackSlash) { + s.push_str(&tok.kind.to_string()); + } } let quotes = if found_interpolation { QuoteKind::Double diff --git a/tests/styles.rs b/tests/styles.rs index 221f600..36be4cc 100644 --- a/tests/styles.rs +++ b/tests/styles.rs @@ -141,3 +141,9 @@ test!( "foo {\n a: b {\n c: d {\n e: f;\n }\n }\n}\n", "foo {\n a: b;\n a-c: d;\n a-c-e: f;\n}\n" ); +test!(curly_braces_in_quotes, "a {\n color: \"{foo}\";\n}\n"); +test!( + escaped_interpolation, + "a {\n color: \"\\#{foo}\";\n}\n", + "a {\n color: \"#{foo}\";\n}\n" +);