diff --git a/src/common.rs b/src/common.rs index 2e85755..eeb718e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::fmt::{self, Display}; +use crate::error::SassResult; use crate::function::Function; use crate::mixin::Mixin; use crate::value::Value; @@ -357,10 +358,10 @@ impl Scope { } } - pub fn get_var(&self, v: &str) -> Result<&Value, String> { + pub fn get_var(&self, v: &str) -> SassResult<&Value> { match self.vars.get(&v.replace('_', "-")) { Some(v) => Ok(v), - None => Err(format!("Undefined variable `{}`.", v)), + None => Err(format!("Undefined variable: ${}.", v).into()), } } diff --git a/src/lib.rs b/src/lib.rs index 4630092..564be16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -517,7 +517,7 @@ pub(crate) fn eat_expr>( scope, super_selector, String::new(), - ); + )?; return Ok(Some(Style::from_tokens(toks, scope, super_selector, prop)?)); } else { values.push(tok.unwrap()); @@ -529,7 +529,7 @@ pub(crate) fn eat_expr>( // special edge case where there was no space between the colon // in a style `color:red`. todo: refactor let mut v = values.into_iter().peekable(); - let property = Style::parse_property(&mut v, scope, super_selector, String::new()); + let property = Style::parse_property(&mut v, scope, super_selector, String::new())?; let value = Style::parse_value(&mut v, scope, super_selector)?; return Ok(Some(Expr::Style(Style { property, value }))); } @@ -544,7 +544,7 @@ pub(crate) fn eat_expr>( // in a style `color:red`. todo: refactor let mut v = values.into_iter().peekable(); let property = - Style::parse_property(&mut v, scope, super_selector, String::new()); + Style::parse_property(&mut v, scope, super_selector, String::new())?; let value = Style::parse_value(&mut v, scope, super_selector)?; return Ok(Some(Expr::Style(Style { property, value }))); } @@ -555,7 +555,7 @@ pub(crate) fn eat_expr>( return Ok(Some(Expr::Selector(Selector::from_tokens( &mut values.into_iter().peekable(), scope, - )))); + )?))); } TokenKind::Variable(_) => { let tok = toks diff --git a/src/selector.rs b/src/selector.rs index 04e704f..3fae6f6 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1,4 +1,5 @@ use crate::common::{Scope, Symbol}; +use crate::error::SassResult; use crate::utils::{ devour_whitespace, devour_whitespace_or_comment, parse_interpolation, IsWhitespace, }; @@ -199,8 +200,8 @@ impl<'a> SelectorParser<'a> { } } - fn all_selectors(mut self, tokens: &'a mut Peekable>) -> Selector { - self.tokens_to_selectors(tokens); + 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() { if x != SelectorKind::Whitespace { @@ -208,7 +209,7 @@ impl<'a> SelectorParser<'a> { break; } } - Selector(self.selectors) + Ok(Selector(self.selectors)) } fn consume_pseudo_selector(&mut self, tokens: &'_ mut Peekable>) { @@ -254,13 +255,14 @@ impl<'a> SelectorParser<'a> { } } - fn tokens_to_selectors(&mut self, tokens: &'_ mut Peekable>) { + fn tokens_to_selectors(&mut self, tokens: &'_ mut Peekable>) -> SassResult<()> { while tokens.peek().is_some() { - self.consume_selector(tokens) + self.consume_selector(tokens)?; } + Ok(()) } - fn consume_selector(&mut self, tokens: &'_ mut Peekable>) { + fn consume_selector(&mut self, tokens: &'_ mut Peekable>) -> SassResult<()> { if devour_whitespace_or_comment(tokens) { if let Some(Token { kind: TokenKind::Symbol(Symbol::Comma), @@ -269,10 +271,10 @@ impl<'a> SelectorParser<'a> { { tokens.next(); self.selectors.push(SelectorKind::Multiple); - return; + return Ok(()); } self.selectors.push(SelectorKind::Whitespace); - return; + return Ok(()); } if let Some(Token { kind, .. }) = tokens.next() { match kind { @@ -298,16 +300,17 @@ impl<'a> SelectorParser<'a> { TokenKind::Interpolation => { self.is_interpolated = true; self.tokens_to_selectors( - &mut parse_interpolation(tokens, self.scope) + &mut parse_interpolation(tokens, self.scope)? .into_iter() .peekable(), - ); + )?; self.is_interpolated = false; } TokenKind::Attribute(attr) => self.selectors.push(SelectorKind::Attribute(attr)), _ => todo!("unimplemented selector"), }; } + Ok(()) } } @@ -315,7 +318,7 @@ impl Selector { pub fn from_tokens<'a>( tokens: &'a mut Peekable>, scope: &'a Scope, - ) -> Selector { + ) -> SassResult { SelectorParser::new(scope).all_selectors(tokens) } diff --git a/src/style.rs b/src/style.rs index 1cc98a6..6689abd 100644 --- a/src/style.rs +++ b/src/style.rs @@ -26,7 +26,7 @@ impl Style { scope: &Scope, super_selector: &Selector, super_property: String, - ) -> String { + ) -> SassResult { StyleParser::new(scope, super_selector).parse_property(toks, super_property) } @@ -116,7 +116,7 @@ impl<'a> StyleParser<'a> { toks.next(); devour_whitespace(toks); loop { - let property = self.parse_property(toks, super_property.clone()); + let property = self.parse_property(toks, super_property.clone())?; if let Some(tok) = toks.peek() { match tok.kind { TokenKind::Symbol(Symbol::OpenCurlyBrace) => { @@ -169,14 +169,14 @@ impl<'a> StyleParser<'a> { &self, toks: &mut Peekable, mut super_property: String, - ) -> String { + ) -> SassResult { let mut property = String::new(); while let Some(Token { kind, .. }) = toks.next() { match kind { TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => continue, TokenKind::Ident(ref s) => property.push_str(s), TokenKind::Interpolation => property.push_str( - &parse_interpolation(toks, self.scope) + &parse_interpolation(toks, self.scope)? .iter() .map(|x| x.kind.to_string()) .collect::(), @@ -190,12 +190,12 @@ impl<'a> StyleParser<'a> { } devour_whitespace(toks); if super_property.is_empty() { - property + Ok(property) } else { super_property.reserve(1 + property.len()); super_property.push('-'); super_property.push_str(&property); - super_property + Ok(super_property) } } } diff --git a/src/utils.rs b/src/utils.rs index 109798e..9e125a1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -44,7 +44,7 @@ pub(crate) fn devour_whitespace_or_comment, W: IsWhitespac pub(crate) fn parse_interpolation>( tokens: &mut I, scope: &Scope, -) -> Vec { +) -> SassResult> { let mut val = Vec::new(); while let Some(tok) = tokens.next() { match tok.kind { @@ -52,20 +52,21 @@ pub(crate) fn parse_interpolation>( TokenKind::Symbol(Symbol::OpenCurlyBrace) => { todo!("invalid character in interpolation") } - TokenKind::Variable(ref v) => val - .extend(Lexer::new(&scope.get_var(v).unwrap().to_string()).collect::>()), - TokenKind::Interpolation => val.extend(parse_interpolation(tokens, scope)), + TokenKind::Variable(ref v) => { + val.extend(Lexer::new(&scope.get_var(v)?.to_string()).collect::>()) + } + TokenKind::Interpolation => val.extend(parse_interpolation(tokens, scope)?), _ => val.push(tok), } } - Lexer::new( + Ok(Lexer::new( &Value::from_tokens(&mut val.into_iter().peekable(), scope) .unwrap() .to_string() .replace("\"", "") .replace("'", ""), ) - .collect::>() + .collect::>()) } pub(crate) struct VariableDecl { diff --git a/src/value/parse.rs b/src/value/parse.rs index 569bbac..46214d4 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -53,14 +53,17 @@ fn parse_hex(s: String) -> Value { } } -fn flatten_ident>(toks: &mut Peekable, scope: &Scope) -> String { +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) + &parse_interpolation(toks, scope)? .iter() .map(|x| x.kind.to_string()) .collect::(), @@ -77,7 +80,7 @@ fn flatten_ident>(toks: &mut Peekable, scope: &Scop _ => break, } } - s + Ok(s) } impl Value { @@ -202,7 +205,7 @@ impl Value { TokenKind::Symbol(Symbol::BitAnd) => { Ok(Value::Ident(String::from("&"), QuoteKind::None)) } - TokenKind::Symbol(Symbol::Hash) => Ok(parse_hex(flatten_ident(toks, scope))), + TokenKind::Symbol(Symbol::Hash) => Ok(parse_hex(flatten_ident(toks, scope)?)), // TokenKind::Interpolation => { // Ok(Value::Ident( // parse_interpolation(toks, scope) @@ -213,7 +216,7 @@ impl Value { // )) // } TokenKind::Ident(mut s) => { - s.push_str(&flatten_ident(toks, scope)); + s.push_str(&flatten_ident(toks, scope)?); match toks.peek() { Some(Token { kind: TokenKind::Symbol(Symbol::OpenParen), @@ -233,17 +236,14 @@ impl Value { unclosed_parens += 1; } TokenKind::Interpolation => s.push_str( - &parse_interpolation(toks, scope) + &parse_interpolation(toks, scope)? .iter() .map(|x| x.kind.to_string()) .collect::(), ), - TokenKind::Variable(v) => s.push_str( - &scope - .get_var(v) - .expect("expected variable to exist") - .to_string(), - ), + TokenKind::Variable(v) => { + s.push_str(&scope.get_var(v)?.to_string()) + } TokenKind::Symbol(Symbol::CloseParen) => { if unclosed_parens <= 1 { s.push(')'); @@ -298,9 +298,9 @@ impl Value { } Ok(Value::Ident(s, QuoteKind::Single)) } - TokenKind::Variable(ref v) => Ok(scope.get_var(v).expect("expected variable").clone()), + TokenKind::Variable(ref v) => Ok(scope.get_var(v)?.clone()), TokenKind::Interpolation => { - let mut s = parse_interpolation(toks, scope) + let mut s = parse_interpolation(toks, scope)? .iter() .map(|x| x.kind.to_string()) .collect::(); @@ -309,7 +309,7 @@ impl Value { TokenKind::Interpolation => { toks.next(); s.push_str( - &parse_interpolation(toks, scope) + &parse_interpolation(toks, scope)? .iter() .map(|x| x.kind.to_string()) .collect::(),