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::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()),
}
}

View File

@ -517,7 +517,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
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<I: Iterator<Item = Token>>(
// 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<I: Iterator<Item = Token>>(
// 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<I: Iterator<Item = Token>>(
return Ok(Some(Expr::Selector(Selector::from_tokens(
&mut values.into_iter().peekable(),
scope,
))));
)?)));
}
TokenKind::Variable(_) => {
let tok = toks

View File

@ -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<IntoIter<Token>>) -> Selector {
self.tokens_to_selectors(tokens);
fn all_selectors(mut self, tokens: &'a mut Peekable<IntoIter<Token>>) -> SassResult<Selector> {
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<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() {
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 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<IntoIter<Token>>,
scope: &'a Scope,
) -> Selector {
) -> SassResult<Selector> {
SelectorParser::new(scope).all_selectors(tokens)
}

View File

@ -26,7 +26,7 @@ impl Style {
scope: &Scope,
super_selector: &Selector,
super_property: String,
) -> String {
) -> SassResult<String> {
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<I>,
mut super_property: String,
) -> String {
) -> SassResult<String> {
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::<String>(),
@ -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)
}
}
}

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>>(
tokens: &mut I,
scope: &Scope,
) -> Vec<Token> {
) -> SassResult<Vec<Token>> {
let mut val = Vec::new();
while let Some(tok) = tokens.next() {
match tok.kind {
@ -52,20 +52,21 @@ pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
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::<Vec<Token>>()),
TokenKind::Interpolation => val.extend(parse_interpolation(tokens, scope)),
TokenKind::Variable(ref v) => {
val.extend(Lexer::new(&scope.get_var(v)?.to_string()).collect::<Vec<Token>>())
}
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::<Vec<Token>>()
.collect::<Vec<Token>>())
}
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();
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::<String>(),
@ -77,7 +80,7 @@ fn flatten_ident<I: Iterator<Item = Token>>(toks: &mut Peekable<I>, 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::<String>(),
),
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::<String>();
@ -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::<String>(),