Proper error message for undefined variables
This commit is contained in:
parent
f550d820b0
commit
e7de93bd38
@ -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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
src/style.rs
12
src/style.rs
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/utils.rs
13
src/utils.rs
@ -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 {
|
||||||
|
@ -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>(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user