Proper error message for undefined variables

This commit is contained in:
ConnorSkees 2020-02-17 07:18:54 -05:00
parent f550d820b0
commit e7de93bd38
6 changed files with 49 additions and 44 deletions

View File

@ -2,6 +2,7 @@ use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use crate::error::SassResult;
use crate::function::Function; use crate::function::Function;
use crate::mixin::Mixin; use crate::mixin::Mixin;
use crate::value::Value; 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('_', "-")) { match self.vars.get(&v.replace('_', "-")) {
Some(v) => Ok(v), Some(v) => Ok(v),
None => Err(format!("Undefined variable `{}`.", v)), None => Err(format!("Undefined variable: ${}.", v).into()),
} }
} }

View File

@ -517,7 +517,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
scope, scope,
super_selector, super_selector,
String::new(), String::new(),
); )?;
return Ok(Some(Style::from_tokens(toks, scope, super_selector, prop)?)); return Ok(Some(Style::from_tokens(toks, scope, super_selector, prop)?));
} else { } else {
values.push(tok.unwrap()); values.push(tok.unwrap());
@ -529,7 +529,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
// special edge case where there was no space between the colon // special edge case where there was no space between the colon
// in a style `color:red`. todo: refactor // in a style `color:red`. todo: refactor
let mut v = values.into_iter().peekable(); 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)?; let value = Style::parse_value(&mut v, scope, super_selector)?;
return Ok(Some(Expr::Style(Style { property, value }))); return Ok(Some(Expr::Style(Style { property, value })));
} }
@ -544,7 +544,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
// in a style `color:red`. todo: refactor // in a style `color:red`. todo: refactor
let mut v = values.into_iter().peekable(); let mut v = values.into_iter().peekable();
let property = 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)?; let value = Style::parse_value(&mut v, scope, super_selector)?;
return Ok(Some(Expr::Style(Style { property, value }))); return Ok(Some(Expr::Style(Style { property, value })));
} }
@ -555,7 +555,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
return Ok(Some(Expr::Selector(Selector::from_tokens( return Ok(Some(Expr::Selector(Selector::from_tokens(
&mut values.into_iter().peekable(), &mut values.into_iter().peekable(),
scope, scope,
)))); )?)));
} }
TokenKind::Variable(_) => { TokenKind::Variable(_) => {
let tok = toks let tok = toks

View File

@ -1,4 +1,5 @@
use crate::common::{Scope, Symbol}; use crate::common::{Scope, Symbol};
use crate::error::SassResult;
use crate::utils::{ use crate::utils::{
devour_whitespace, devour_whitespace_or_comment, parse_interpolation, IsWhitespace, 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<IntoIter<Token>>) -> Selector { fn all_selectors(mut self, tokens: &'a mut Peekable<IntoIter<Token>>) -> SassResult<Selector> {
self.tokens_to_selectors(tokens); self.tokens_to_selectors(tokens)?;
// remove trailing whitespace // remove trailing whitespace
while let Some(x) = self.selectors.pop() { while let Some(x) = self.selectors.pop() {
if x != SelectorKind::Whitespace { if x != SelectorKind::Whitespace {
@ -208,7 +209,7 @@ impl<'a> SelectorParser<'a> {
break; break;
} }
} }
Selector(self.selectors) Ok(Selector(self.selectors))
} }
fn consume_pseudo_selector(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) { fn consume_pseudo_selector(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) {
@ -254,13 +255,14 @@ impl<'a> SelectorParser<'a> {
} }
} }
fn tokens_to_selectors(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) { fn tokens_to_selectors(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) -> SassResult<()> {
while tokens.peek().is_some() { while tokens.peek().is_some() {
self.consume_selector(tokens) self.consume_selector(tokens)?;
} }
Ok(())
} }
fn consume_selector(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) { fn consume_selector(&mut self, tokens: &'_ mut Peekable<IntoIter<Token>>) -> SassResult<()> {
if devour_whitespace_or_comment(tokens) { if devour_whitespace_or_comment(tokens) {
if let Some(Token { if let Some(Token {
kind: TokenKind::Symbol(Symbol::Comma), kind: TokenKind::Symbol(Symbol::Comma),
@ -269,10 +271,10 @@ impl<'a> SelectorParser<'a> {
{ {
tokens.next(); tokens.next();
self.selectors.push(SelectorKind::Multiple); self.selectors.push(SelectorKind::Multiple);
return; return Ok(());
} }
self.selectors.push(SelectorKind::Whitespace); self.selectors.push(SelectorKind::Whitespace);
return; return Ok(());
} }
if let Some(Token { kind, .. }) = tokens.next() { if let Some(Token { kind, .. }) = tokens.next() {
match kind { match kind {
@ -298,16 +300,17 @@ impl<'a> SelectorParser<'a> {
TokenKind::Interpolation => { TokenKind::Interpolation => {
self.is_interpolated = true; self.is_interpolated = true;
self.tokens_to_selectors( self.tokens_to_selectors(
&mut parse_interpolation(tokens, self.scope) &mut parse_interpolation(tokens, self.scope)?
.into_iter() .into_iter()
.peekable(), .peekable(),
); )?;
self.is_interpolated = false; self.is_interpolated = false;
} }
TokenKind::Attribute(attr) => self.selectors.push(SelectorKind::Attribute(attr)), TokenKind::Attribute(attr) => self.selectors.push(SelectorKind::Attribute(attr)),
_ => todo!("unimplemented selector"), _ => todo!("unimplemented selector"),
}; };
} }
Ok(())
} }
} }
@ -315,7 +318,7 @@ impl Selector {
pub fn from_tokens<'a>( pub fn from_tokens<'a>(
tokens: &'a mut Peekable<IntoIter<Token>>, tokens: &'a mut Peekable<IntoIter<Token>>,
scope: &'a Scope, scope: &'a Scope,
) -> Selector { ) -> SassResult<Selector> {
SelectorParser::new(scope).all_selectors(tokens) SelectorParser::new(scope).all_selectors(tokens)
} }

View File

@ -26,7 +26,7 @@ impl Style {
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
super_property: String, super_property: String,
) -> String { ) -> SassResult<String> {
StyleParser::new(scope, super_selector).parse_property(toks, super_property) StyleParser::new(scope, super_selector).parse_property(toks, super_property)
} }
@ -116,7 +116,7 @@ impl<'a> StyleParser<'a> {
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);
loop { 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() { if let Some(tok) = toks.peek() {
match tok.kind { match tok.kind {
TokenKind::Symbol(Symbol::OpenCurlyBrace) => { TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
@ -169,14 +169,14 @@ impl<'a> StyleParser<'a> {
&self, &self,
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
mut super_property: String, mut super_property: String,
) -> String { ) -> SassResult<String> {
let mut property = String::new(); let mut property = String::new();
while let Some(Token { kind, .. }) = toks.next() { while let Some(Token { kind, .. }) = toks.next() {
match kind { match kind {
TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => continue, TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => continue,
TokenKind::Ident(ref s) => property.push_str(s), TokenKind::Ident(ref s) => property.push_str(s),
TokenKind::Interpolation => property.push_str( TokenKind::Interpolation => property.push_str(
&parse_interpolation(toks, self.scope) &parse_interpolation(toks, self.scope)?
.iter() .iter()
.map(|x| x.kind.to_string()) .map(|x| x.kind.to_string())
.collect::<String>(), .collect::<String>(),
@ -190,12 +190,12 @@ impl<'a> StyleParser<'a> {
} }
devour_whitespace(toks); devour_whitespace(toks);
if super_property.is_empty() { if super_property.is_empty() {
property Ok(property)
} else { } else {
super_property.reserve(1 + property.len()); super_property.reserve(1 + property.len());
super_property.push('-'); super_property.push('-');
super_property.push_str(&property); super_property.push_str(&property);
super_property Ok(super_property)
} }
} }
} }

View File

@ -44,7 +44,7 @@ pub(crate) fn devour_whitespace_or_comment<I: Iterator<Item = W>, W: IsWhitespac
pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>( pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
tokens: &mut I, tokens: &mut I,
scope: &Scope, scope: &Scope,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut val = Vec::new(); let mut val = Vec::new();
while let Some(tok) = tokens.next() { while let Some(tok) = tokens.next() {
match tok.kind { match tok.kind {
@ -52,20 +52,21 @@ pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
TokenKind::Symbol(Symbol::OpenCurlyBrace) => { TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
todo!("invalid character in interpolation") todo!("invalid character in interpolation")
} }
TokenKind::Variable(ref v) => val TokenKind::Variable(ref v) => {
.extend(Lexer::new(&scope.get_var(v).unwrap().to_string()).collect::<Vec<Token>>()), val.extend(Lexer::new(&scope.get_var(v)?.to_string()).collect::<Vec<Token>>())
TokenKind::Interpolation => val.extend(parse_interpolation(tokens, scope)), }
TokenKind::Interpolation => val.extend(parse_interpolation(tokens, scope)?),
_ => val.push(tok), _ => val.push(tok),
} }
} }
Lexer::new( Ok(Lexer::new(
&Value::from_tokens(&mut val.into_iter().peekable(), scope) &Value::from_tokens(&mut val.into_iter().peekable(), scope)
.unwrap() .unwrap()
.to_string() .to_string()
.replace("\"", "") .replace("\"", "")
.replace("'", ""), .replace("'", ""),
) )
.collect::<Vec<Token>>() .collect::<Vec<Token>>())
} }
pub(crate) struct VariableDecl { pub(crate) struct VariableDecl {

View File

@ -53,14 +53,17 @@ fn parse_hex(s: String) -> Value {
} }
} }
fn flatten_ident<I: Iterator<Item = Token>>(toks: &mut Peekable<I>, scope: &Scope) -> String { fn flatten_ident<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
) -> SassResult<String> {
let mut s = String::new(); let mut s = String::new();
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek() {
match tok.kind.clone() { match tok.kind.clone() {
TokenKind::Interpolation => { TokenKind::Interpolation => {
toks.next(); toks.next();
s.push_str( s.push_str(
&parse_interpolation(toks, scope) &parse_interpolation(toks, scope)?
.iter() .iter()
.map(|x| x.kind.to_string()) .map(|x| x.kind.to_string())
.collect::<String>(), .collect::<String>(),
@ -77,7 +80,7 @@ fn flatten_ident<I: Iterator<Item = Token>>(toks: &mut Peekable<I>, scope: &Scop
_ => break, _ => break,
} }
} }
s Ok(s)
} }
impl Value { impl Value {
@ -202,7 +205,7 @@ impl Value {
TokenKind::Symbol(Symbol::BitAnd) => { TokenKind::Symbol(Symbol::BitAnd) => {
Ok(Value::Ident(String::from("&"), QuoteKind::None)) 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 => { // TokenKind::Interpolation => {
// Ok(Value::Ident( // Ok(Value::Ident(
// parse_interpolation(toks, scope) // parse_interpolation(toks, scope)
@ -213,7 +216,7 @@ impl Value {
// )) // ))
// } // }
TokenKind::Ident(mut s) => { TokenKind::Ident(mut s) => {
s.push_str(&flatten_ident(toks, scope)); s.push_str(&flatten_ident(toks, scope)?);
match toks.peek() { match toks.peek() {
Some(Token { Some(Token {
kind: TokenKind::Symbol(Symbol::OpenParen), kind: TokenKind::Symbol(Symbol::OpenParen),
@ -233,17 +236,14 @@ impl Value {
unclosed_parens += 1; unclosed_parens += 1;
} }
TokenKind::Interpolation => s.push_str( TokenKind::Interpolation => s.push_str(
&parse_interpolation(toks, scope) &parse_interpolation(toks, scope)?
.iter() .iter()
.map(|x| x.kind.to_string()) .map(|x| x.kind.to_string())
.collect::<String>(), .collect::<String>(),
), ),
TokenKind::Variable(v) => s.push_str( TokenKind::Variable(v) => {
&scope s.push_str(&scope.get_var(v)?.to_string())
.get_var(v) }
.expect("expected variable to exist")
.to_string(),
),
TokenKind::Symbol(Symbol::CloseParen) => { TokenKind::Symbol(Symbol::CloseParen) => {
if unclosed_parens <= 1 { if unclosed_parens <= 1 {
s.push(')'); s.push(')');
@ -298,9 +298,9 @@ impl Value {
} }
Ok(Value::Ident(s, QuoteKind::Single)) 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 => { TokenKind::Interpolation => {
let mut s = parse_interpolation(toks, scope) let mut s = parse_interpolation(toks, scope)?
.iter() .iter()
.map(|x| x.kind.to_string()) .map(|x| x.kind.to_string())
.collect::<String>(); .collect::<String>();
@ -309,7 +309,7 @@ impl Value {
TokenKind::Interpolation => { TokenKind::Interpolation => {
toks.next(); toks.next();
s.push_str( s.push_str(
&parse_interpolation(toks, scope) &parse_interpolation(toks, scope)?
.iter() .iter()
.map(|x| x.kind.to_string()) .map(|x| x.kind.to_string())
.collect::<String>(), .collect::<String>(),