track span_before when parsing values

this allows us to remove many panics on invalid input
This commit is contained in:
ConnorSkees 2020-05-24 15:30:06 -04:00
parent 812e9fec9c
commit e5cceb60ec
22 changed files with 171 additions and 89 deletions

View File

@ -109,7 +109,10 @@ impl CallArgs {
super_selector: &Selector, super_selector: &Selector,
) -> Option<SassResult<Spanned<Value>>> { ) -> Option<SassResult<Spanned<Value>>> {
match self.0.remove(&CallArg::Named(val.into())) { match self.0.remove(&CallArg::Named(val.into())) {
Some(v) => Some(Value::from_vec(v, scope, super_selector)), Some(v) => {
let span_before = v[0].pos;
Some(Value::from_vec(v, scope, super_selector, span_before))
}
None => None, None => None,
} }
} }
@ -124,7 +127,10 @@ impl CallArgs {
super_selector: &Selector, super_selector: &Selector,
) -> Option<SassResult<Spanned<Value>>> { ) -> Option<SassResult<Spanned<Value>>> {
match self.0.remove(&CallArg::Positional(val)) { match self.0.remove(&CallArg::Positional(val)) {
Some(v) => Some(Value::from_vec(v, scope, super_selector)), Some(v) => {
let span_before = v[0].pos;
Some(Value::from_vec(v, scope, super_selector, span_before))
}
None => None, None => None,
} }
} }
@ -159,7 +165,8 @@ impl CallArgs {
}; };
args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2)); args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2));
for arg in args { for arg in args {
vals.push(Value::from_vec(arg.1, scope, super_selector)?); let span_before = arg.1[0].pos;
vals.push(Value::from_vec(arg.1, scope, super_selector, span_before)?);
} }
Ok(vals) Ok(vals)
} }

View File

@ -119,7 +119,12 @@ pub(crate) fn parse_each<I: Iterator<Item = Token>>(
return Err(("Expected \"in\".", i.span).into()); return Err(("Expected \"in\".", i.span).into());
} }
devour_whitespace(toks); devour_whitespace(toks);
let iter_val = Value::from_vec(read_until_open_curly_brace(toks)?, scope, super_selector)?; let iter_val = Value::from_vec(
read_until_open_curly_brace(toks)?,
scope,
super_selector,
i.span,
)?;
let iter = match iter_val.node.eval(iter_val.span)?.node { let iter = match iter_val.node.eval(iter_val.span)?.node {
Value::List(v, ..) => v, Value::List(v, ..) => v,
Value::Map(m) => m Value::Map(m) => m

View File

@ -133,7 +133,7 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
} }
} }
devour_whitespace(toks); devour_whitespace(toks);
let from_val = Value::from_vec(from_toks, scope, super_selector)?; let from_val = Value::from_vec(from_toks, scope, super_selector, span_before)?;
let from = match from_val.node.eval(from_val.span)?.node { let from = match from_val.node.eval(from_val.span)?.node {
Value::Dimension(n, _) => match n.to_integer().to_isize() { Value::Dimension(n, _) => match n.to_integer().to_isize() {
Some(v) => v, Some(v) => v,
@ -150,7 +150,7 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
let to_toks = read_until_open_curly_brace(toks)?; let to_toks = read_until_open_curly_brace(toks)?;
toks.next(); toks.next();
let to_val = Value::from_vec(to_toks, scope, super_selector)?; let to_val = Value::from_vec(to_toks, scope, super_selector, from_val.span)?;
let to = match to_val.node.eval(to_val.span)?.node { let to = match to_val.node.eval(to_val.span)?.node {
Value::Dimension(n, _) => match n.to_integer().to_isize() { Value::Dimension(n, _) => match n.to_integer().to_isize() {
Some(v) => v, Some(v) => v,

View File

@ -88,11 +88,7 @@ impl Function {
let val = match args.get(idx, arg.name.clone(), &scope, super_selector) { let val = match args.get(idx, arg.name.clone(), &scope, super_selector) {
Some(v) => v?, Some(v) => v?,
None => match arg.default.as_mut() { None => match arg.default.as_mut() {
Some(v) => Value::from_tokens( Some(v) => Value::from_vec(mem::take(v), &scope, super_selector, args.span())?,
&mut mem::take(v).into_iter().peekmore(),
&scope,
super_selector,
)?,
None => { None => {
return Err( return Err(
(format!("Missing argument ${}.", &arg.name), args.span()).into() (format!("Missing argument ${}.", &arg.name), args.span()).into()
@ -137,7 +133,7 @@ impl Function {
match stmt.node { match stmt.node {
Stmt::AtRule(AtRule::Return(toks)) => { Stmt::AtRule(AtRule::Return(toks)) => {
return Ok(Some( return Ok(Some(
Value::from_vec(toks, &self.scope, super_selector)?.node, Value::from_vec(toks, &self.scope, super_selector, stmt.span)?.node,
)); ));
} }
Stmt::AtRule(AtRule::For(f)) => { Stmt::AtRule(AtRule::For(f)) => {
@ -169,7 +165,8 @@ impl Function {
} }
Stmt::AtRule(AtRule::While(w)) => { Stmt::AtRule(AtRule::While(w)) => {
let scope = &mut self.scope.clone(); let scope = &mut self.scope.clone();
let mut val = Value::from_vec(w.cond.clone(), scope, super_selector)?; let mut val =
Value::from_vec(w.cond.clone(), scope, super_selector, stmt.span)?;
while val.node.is_true(val.span)? { while val.node.is_true(val.span)? {
let while_stmts = eat_stmts( let while_stmts = eat_stmts(
&mut w.body.clone().into_iter().peekmore(), &mut w.body.clone().into_iter().peekmore(),
@ -181,7 +178,7 @@ impl Function {
if let Some(v) = self.call(super_selector, while_stmts)? { if let Some(v) = self.call(super_selector, while_stmts)? {
return Ok(Some(v)); return Ok(Some(v));
} }
val = Value::from_vec(w.cond.clone(), scope, super_selector)?; val = Value::from_vec(w.cond.clone(), scope, super_selector, val.span)?;
} }
} }
Stmt::AtRule(AtRule::Each(..)) => todo!("@each in @function"), Stmt::AtRule(AtRule::Each(..)) => todo!("@each in @function"),

View File

@ -42,10 +42,19 @@ impl If {
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
let mut branches = Vec::new(); let mut branches = Vec::new();
let init_cond_toks = read_until_open_curly_brace(toks)?; let init_cond_toks = read_until_open_curly_brace(toks)?;
if init_cond_toks.is_empty() || toks.next().is_none() { if init_cond_toks.is_empty() {
return Err(("Expected expression.", span_before).into()); return Err(("Expected expression.", span_before).into());
} }
let init_cond = Value::from_vec(init_cond_toks, scope, super_selector)?; let span_before = match toks.next() {
Some(t) => t.pos,
None => return Err(("Expected expression.", span_before).into()),
};
let init_cond = Value::from_vec(
init_cond_toks,
scope,
super_selector,
span_before,
)?;
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
let mut init_toks = read_until_closing_curly_brace(toks)?; let mut init_toks = read_until_closing_curly_brace(toks)?;
if let Some(tok) = toks.next() { if let Some(tok) = toks.next() {
@ -78,11 +87,12 @@ impl If {
devour_whitespace(toks); devour_whitespace(toks);
match tok.kind.to_ascii_lowercase() { match tok.kind.to_ascii_lowercase() {
'i' if toks.next().unwrap().kind.to_ascii_lowercase() == 'f' => { 'i' if toks.next().unwrap().kind.to_ascii_lowercase() == 'f' => {
toks.next(); let pos = toks.next().unwrap().pos;
let cond = Value::from_vec( let cond = Value::from_vec(
read_until_open_curly_brace(toks)?, read_until_open_curly_brace(toks)?,
scope, scope,
super_selector, super_selector,
pos,
)?; )?;
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);

View File

@ -29,9 +29,9 @@ impl Media {
match tok.kind { match tok.kind {
'{' => break, '{' => break,
'#' => { '#' => {
if toks.peek().unwrap().kind == '{' { if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
toks.next(); toks.next();
let interpolation = parse_interpolation(toks, scope, super_selector)?; let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
params.push_str(&interpolation.node.to_css_string(interpolation.span)?); params.push_str(&interpolation.node.to_css_string(interpolation.span)?);
continue; continue;
} else { } else {

View File

@ -79,11 +79,7 @@ impl Mixin {
let val = match args.get(idx, arg.name.clone(), scope, super_selector) { let val = match args.get(idx, arg.name.clone(), scope, super_selector) {
Some(v) => v?, Some(v) => v?,
None => match arg.default.as_mut() { None => match arg.default.as_mut() {
Some(v) => Value::from_tokens( Some(v) => Value::from_vec(mem::take(v), scope, super_selector, args.span())?,
&mut std::mem::take(v).into_iter().peekmore(),
scope,
super_selector,
)?,
None => { None => {
return Err( return Err(
(format!("Missing argument ${}.", &arg.name), args.span()).into() (format!("Missing argument ${}.", &arg.name), args.span()).into()

View File

@ -72,6 +72,7 @@ impl AtRule {
read_until_semicolon_or_closing_curly_brace(toks)?, read_until_semicolon_or_closing_curly_brace(toks)?,
scope, scope,
super_selector, super_selector,
kind_span,
)?; )?;
return Err((message.inspect(span)?.to_string(), span.merge(kind_span)).into()); return Err((message.inspect(span)?.to_string(), span.merge(kind_span)).into());
@ -84,6 +85,7 @@ impl AtRule {
read_until_semicolon_or_closing_curly_brace(toks)?, read_until_semicolon_or_closing_curly_brace(toks)?,
scope, scope,
super_selector, super_selector,
kind_span,
)?; )?;
span.merge(kind_span); span.merge(kind_span);
if toks.peek().unwrap().kind == ';' { if toks.peek().unwrap().kind == ';' {
@ -106,6 +108,7 @@ impl AtRule {
read_until_semicolon_or_closing_curly_brace(toks)?, read_until_semicolon_or_closing_curly_brace(toks)?,
scope, scope,
super_selector, super_selector,
kind_span,
)?; )?;
span.merge(kind_span); span.merge(kind_span);
if toks.peek().unwrap().kind == ';' { if toks.peek().unwrap().kind == ';' {

View File

@ -41,9 +41,9 @@ impl UnknownAtRule {
match tok.kind { match tok.kind {
'{' => break, '{' => break,
'#' => { '#' => {
if let Some(Token { kind: '{', .. }) = toks.peek() { if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
toks.next(); toks.next();
let interpolation = parse_interpolation(toks, scope, super_selector)?; let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
params.push_str(&interpolation.node.to_css_string(interpolation.span)?); params.push_str(&interpolation.node.to_css_string(interpolation.span)?);
} else { } else {
params.push(tok.kind); params.push(tok.kind);

View File

@ -28,7 +28,7 @@ impl While {
content: Option<&[Spanned<Stmt>]>, content: Option<&[Spanned<Stmt>]>,
) -> SassResult<Vec<Spanned<Stmt>>> { ) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
let mut val = Value::from_vec(self.cond.clone(), scope, super_selector)?; let mut val = Value::from_vec(self.cond.clone(), scope, super_selector, self.cond[0].pos)?;
let scope = &mut scope.clone(); let scope = &mut scope.clone();
while val.node.is_true(val.span)? { while val.node.is_true(val.span)? {
ruleset_eval( ruleset_eval(
@ -39,7 +39,7 @@ impl While {
content, content,
&mut stmts, &mut stmts,
)?; )?;
val = Value::from_vec(self.cond.clone(), scope, super_selector)?; val = Value::from_vec(self.cond.clone(), scope, super_selector, self.cond[0].pos)?;
} }
Ok(stmts) Ok(stmts)
} }

View File

@ -232,7 +232,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
String::new(), String::new(),
span_before, span_before,
)?; )?;
let value = Style::parse_value(&mut v, scope, super_selector)?; let value = Style::parse_value(&mut v, scope, super_selector, span_before)?;
return Ok(Some(Spanned { return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style { property, value })), node: Expr::Style(Box::new(Style { property, value })),
span, span,
@ -259,7 +259,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
String::new(), String::new(),
tok.pos, tok.pos,
)?; )?;
let value = Style::parse_value(&mut v, scope, super_selector)?; let value = Style::parse_value(&mut v, scope, super_selector, tok.pos)?;
return Ok(Some(Spanned { return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style { property, value })), node: Expr::Style(Box::new(Style { property, value })),
span, span,
@ -298,7 +298,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
val, val,
default, default,
global, global,
} = eat_variable_value(toks, scope, super_selector)?; } = eat_variable_value(toks, scope, super_selector, name.span)?;
if global { if global {
insert_global_var(&name.node, val.clone())?; insert_global_var(&name.node, val.clone())?;
} }

View File

@ -233,10 +233,10 @@ impl Selector {
span = span.merge(tok.pos()); span = span.merge(tok.pos());
match tok.kind { match tok.kind {
'#' => { '#' => {
if toks.peek().is_some() && toks.peek().unwrap().kind == '{' { if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
toks.next(); toks.next();
string.push_str( string.push_str(
&parse_interpolation(toks, scope, super_selector)? &parse_interpolation(toks, scope, super_selector, pos)?
.to_css_string(span)?, .to_css_string(span)?,
); );
} else { } else {

View File

@ -49,8 +49,9 @@ impl Style {
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
span_before: Span,
) -> SassResult<Spanned<Value>> { ) -> SassResult<Spanned<Value>> {
StyleParser::new(scope, super_selector).parse_style_value(toks, scope) StyleParser::new(scope, super_selector).parse_style_value(toks, scope, span_before)
} }
pub fn from_tokens<I: Iterator<Item = Token>>( pub fn from_tokens<I: Iterator<Item = Token>>(
@ -80,9 +81,10 @@ impl<'a> StyleParser<'a> {
&self, &self,
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
span_before: Span,
) -> SassResult<Spanned<Value>> { ) -> SassResult<Spanned<Value>> {
devour_whitespace(toks); devour_whitespace(toks);
Value::from_tokens(toks, scope, self.super_selector) Value::from_tokens(toks, scope, self.super_selector, span_before)
} }
pub(crate) fn eat_style_group<I: Iterator<Item = Token>>( pub(crate) fn eat_style_group<I: Iterator<Item = Token>>(
@ -93,7 +95,7 @@ impl<'a> StyleParser<'a> {
) -> SassResult<Expr> { ) -> SassResult<Expr> {
let mut styles = Vec::new(); let mut styles = Vec::new();
devour_whitespace(toks); devour_whitespace(toks);
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek().cloned() {
match tok.kind { match tok.kind {
'{' => { '{' => {
let span_before = toks.next().unwrap().pos; let span_before = toks.next().unwrap().pos;
@ -121,7 +123,7 @@ impl<'a> StyleParser<'a> {
continue; continue;
} }
} }
let value = self.parse_style_value(toks, scope)?; let value = self.parse_style_value(toks, scope, span_before)?;
match toks.peek().unwrap().kind { match toks.peek().unwrap().kind {
'}' => { '}' => {
styles.push(Style { property, value }); styles.push(Style { property, value });
@ -160,7 +162,7 @@ impl<'a> StyleParser<'a> {
} }
} }
_ => { _ => {
let value = self.parse_style_value(toks, scope)?; let value = self.parse_style_value(toks, scope, tok.pos)?;
let t = toks.peek().ok_or(("expected more input.", value.span))?; let t = toks.peek().ok_or(("expected more input.", value.span))?;
match t.kind { match t.kind {
'}' => {} '}' => {}

View File

@ -195,14 +195,15 @@ impl<'a> StyleSheetParser<'a> {
let whitespace = peek_whitespace(self.lexer); let whitespace = peek_whitespace(self.lexer);
match self.lexer.peek() { match self.lexer.peek() {
Some(Token { kind: ':', .. }) => { Some(Token { kind: ':', pos }) => {
let pos = *pos;
self.lexer self.lexer
.take(name.node.chars().count() + whitespace + 1) .take(name.node.chars().count() + whitespace + 1)
.for_each(drop); .for_each(drop);
devour_whitespace(self.lexer); devour_whitespace(self.lexer);
let VariableDecl { val, default, .. } = let VariableDecl { val, default, .. } =
eat_variable_value(self.lexer, &Scope::new(), &Selector::new())?; eat_variable_value(self.lexer, &Scope::new(), &Selector::new(), pos)?;
if !(default && global_var_exists(&name.node)) { if !(default && global_var_exists(&name.node)) {
insert_global_var(&name.node, val)?; insert_global_var(&name.node, val)?;

View File

@ -100,16 +100,20 @@ pub(crate) fn eat_comment<I: Iterator<Item = Token>>(
}; };
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
span = span.merge(tok.pos()); span = span.merge(tok.pos());
if tok.kind == '*' && toks.peek().unwrap().kind == '/' { match (tok.kind, toks.peek()) {
('*', Some(Token { kind: '/', .. })) => {
toks.next(); toks.next();
break; break;
} else if tok.kind == '#' && toks.peek().unwrap().kind == '{' { }
('#', Some(Token { kind: '{', .. })) => {
toks.next(); toks.next();
comment comment.push_str(
.push_str(&parse_interpolation(toks, scope, super_selector)?.to_css_string(span)?); &parse_interpolation(toks, scope, super_selector, span)?.to_css_string(span)?,
);
continue; continue;
} }
comment.push(tok.kind); (..) => comment.push(tok.kind),
}
} }
devour_whitespace(toks); devour_whitespace(toks);
Ok(Spanned { Ok(Spanned {

View File

@ -1,6 +1,6 @@
use std::iter::Iterator; use std::iter::Iterator;
use codemap::Spanned; use codemap::{Span, Spanned};
use peekmore::PeekMoreIterator; use peekmore::PeekMoreIterator;
@ -15,8 +15,14 @@ pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
span_before: Span,
) -> SassResult<Spanned<Value>> { ) -> SassResult<Spanned<Value>> {
let val = Value::from_vec(read_until_closing_curly_brace(toks)?, scope, super_selector)?; let val = Value::from_vec(
read_until_closing_curly_brace(toks)?,
scope,
super_selector,
span_before,
)?;
toks.next(); toks.next();
Ok(Spanned { Ok(Spanned {
node: val.node.eval(val.span)?.node.unquote(), node: val.node.eval(val.span)?.node.unquote(),

View File

@ -97,11 +97,11 @@ fn interpolated_ident_body<I: Iterator<Item = Token>>(
buf.push_str(&escape(toks, false)?); buf.push_str(&escape(toks, false)?);
} }
'#' => { '#' => {
if let Some(Token { kind: '{', .. }) = toks.peek_forward(1) { if let Some(Token { kind: '{', pos }) = toks.peek_forward(1).cloned() {
toks.next(); toks.next();
toks.next(); toks.next();
// TODO: if ident, interpolate literally // TODO: if ident, interpolate literally
let interpolation = parse_interpolation(toks, scope, super_selector)?; let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
buf.push_str(&interpolation.node.to_css_string(interpolation.span)?); buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
} else { } else {
toks.reset_view(); toks.reset_view();
@ -207,20 +207,21 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
// (first == '#' && scanner.peekChar(1) == $lbrace) // (first == '#' && scanner.peekChar(1) == $lbrace)
} else if first == '#' { } else if first == '#' {
toks.next(); toks.next();
if toks.peek().is_none() { let Token { kind, pos } = if let Some(tok) = toks.peek() {
*tok
} else {
return Err(("Expected identifier.", pos).into()); return Err(("Expected identifier.", pos).into());
} };
let Token { kind, pos } = toks.peek().unwrap(); if kind == '{' {
if kind == &'{' {
toks.next(); toks.next();
text.push_str( text.push_str(
&match parse_interpolation(toks, scope, super_selector)?.node { &match parse_interpolation(toks, scope, super_selector, pos)?.node {
Value::String(s, ..) => s, Value::String(s, ..) => s,
v => v.to_css_string(span)?.into(), v => v.to_css_string(span)?.into(),
}, },
); );
} else { } else {
return Err(("Expected identifier.", *pos).into()); return Err(("Expected identifier.", pos).into());
} }
} else { } else {
return Err(("Expected identifier.", pos).into()); return Err(("Expected identifier.", pos).into());
@ -293,9 +294,9 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
'"' if q == '"' => break, '"' if q == '"' => break,
'\'' if q == '\'' => break, '\'' if q == '\'' => break,
'#' => { '#' => {
if toks.peek().unwrap().kind == '{' { if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
toks.next(); toks.next();
let interpolation = parse_interpolation(toks, scope, super_selector)?; let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
s.push_str(&match interpolation.node { s.push_str(&match interpolation.node {
Value::String(s, ..) => s, Value::String(s, ..) => s,
v => v.to_css_string(interpolation.span)?.into(), v => v.to_css_string(interpolation.span)?.into(),

View File

@ -1,6 +1,6 @@
use std::iter::Iterator; use std::iter::Iterator;
use codemap::Spanned; use codemap::{Span, Spanned};
use peekmore::PeekMoreIterator; use peekmore::PeekMoreIterator;
@ -34,6 +34,7 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
span_before: Span,
) -> SassResult<VariableDecl> { ) -> SassResult<VariableDecl> {
devour_whitespace(toks); devour_whitespace(toks);
let mut default = false; let mut default = false;
@ -126,6 +127,6 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
} }
} }
devour_whitespace(toks); devour_whitespace(toks);
let val = Value::from_vec(val_toks, scope, super_selector)?; let val = Value::from_vec(val_toks, scope, super_selector, span_before)?;
Ok(VariableDecl::new(val, default, global)) Ok(VariableDecl::new(val, default, global))
} }

View File

@ -1,3 +1,5 @@
use codemap::{Span, Spanned};
use peekmore::PeekMoreIterator; use peekmore::PeekMoreIterator;
use crate::error::SassResult; use crate::error::SassResult;
@ -7,6 +9,7 @@ use crate::utils::{
devour_whitespace, parse_interpolation, peek_escape, peek_until_closing_curly_brace, devour_whitespace, parse_interpolation, peek_escape, peek_until_closing_curly_brace,
peek_whitespace, peek_whitespace,
}; };
use crate::value::Value;
use crate::Token; use crate::Token;
pub(crate) fn eat_calc_args<I: Iterator<Item = Token>>( pub(crate) fn eat_calc_args<I: Iterator<Item = Token>>(
@ -26,10 +29,10 @@ pub(crate) fn eat_calc_args<I: Iterator<Item = Token>>(
} }
'#' => { '#' => {
if toks.peek().is_some() && toks.peek().unwrap().kind == '{' { if toks.peek().is_some() && toks.peek().unwrap().kind == '{' {
let span = toks.next().unwrap().pos(); let span_before = toks.next().unwrap().pos();
buf.push_str( let interpolation =
&parse_interpolation(toks, scope, super_selector)?.to_css_string(span)?, parse_interpolation(toks, scope, super_selector, span_before)?;
); buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
} else { } else {
buf.push('#'); buf.push('#');
} }
@ -106,11 +109,11 @@ pub(crate) fn try_eat_url<I: Iterator<Item = Token>>(
} else if kind == '\\' { } else if kind == '\\' {
buf.push_str(&peek_escape(toks)?); buf.push_str(&peek_escape(toks)?);
} else if kind == '#' { } else if kind == '#' {
let next = toks.peek(); if let Some(Token { kind: '{', pos }) = toks.peek() {
if next.is_some() && next.unwrap().kind == '{' { let pos = *pos;
toks.move_forward(1); toks.move_forward(1);
peek_counter += 1; peek_counter += 1;
let (interpolation, count) = peek_interpolation(toks, scope, super_selector)?; let (interpolation, count) = peek_interpolation(toks, scope, super_selector, pos)?;
peek_counter += count; peek_counter += count;
buf.push_str(&match interpolation.node { buf.push_str(&match interpolation.node {
Value::String(s, ..) => s, Value::String(s, ..) => s,
@ -144,18 +147,16 @@ pub(crate) fn try_eat_url<I: Iterator<Item = Token>>(
Ok(None) Ok(None)
} }
use crate::value::Value;
use codemap::Spanned;
fn peek_interpolation<I: Iterator<Item = Token>>( fn peek_interpolation<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
span_before: Span,
) -> SassResult<(Spanned<Value>, usize)> { ) -> SassResult<(Spanned<Value>, usize)> {
let vec = peek_until_closing_curly_brace(toks); let vec = peek_until_closing_curly_brace(toks);
let peek_counter = vec.len(); let peek_counter = vec.len();
toks.move_forward(1); toks.move_forward(1);
let val = Value::from_vec(vec, scope, super_selector)?; let val = Value::from_vec(vec, scope, super_selector, span_before)?;
Ok(( Ok((
Spanned { Spanned {
node: val.node.eval(val.span)?.node.unquote(), node: val.node.eval(val.span)?.node.unquote(),

View File

@ -189,7 +189,12 @@ fn parse_paren(
let paren_toks = &mut t.node.into_iter().peekmore(); let paren_toks = &mut t.node.into_iter().peekmore();
let mut map = SassMap::new(); let mut map = SassMap::new();
let key = Value::from_vec(read_until_char(paren_toks, ':')?, scope, super_selector)?; let key = Value::from_vec(
read_until_char(paren_toks, ':')?,
scope,
super_selector,
t.span,
)?;
if paren_toks.peek().is_none() { if paren_toks.peek().is_none() {
return Ok(Spanned { return Ok(Spanned {
@ -198,7 +203,12 @@ fn parse_paren(
}); });
} }
let val = Value::from_vec(read_until_char(paren_toks, ',')?, scope, super_selector)?; let val = Value::from_vec(
read_until_char(paren_toks, ',')?,
scope,
super_selector,
key.span,
)?;
map.insert(key.node, val.node); map.insert(key.node, val.node);
@ -212,9 +222,19 @@ fn parse_paren(
let mut span = key.span; let mut span = key.span;
loop { loop {
let key = Value::from_vec(read_until_char(paren_toks, ':')?, scope, super_selector)?; let key = Value::from_vec(
read_until_char(paren_toks, ':')?,
scope,
super_selector,
span,
)?;
devour_whitespace(paren_toks); devour_whitespace(paren_toks);
let val = Value::from_vec(read_until_char(paren_toks, ',')?, scope, super_selector)?; let val = Value::from_vec(
read_until_char(paren_toks, ',')?,
scope,
super_selector,
key.span,
)?;
span = span.merge(val.span); span = span.merge(val.span);
devour_whitespace(paren_toks); devour_whitespace(paren_toks);
if map.insert(key.node, val.node) { if map.insert(key.node, val.node) {
@ -387,7 +407,7 @@ fn single_value<I: Iterator<Item = Token>>(
IntermediateValue::Whitespace => unreachable!(), IntermediateValue::Whitespace => unreachable!(),
IntermediateValue::Comma => return Err(("Expected expression.", span).into()), IntermediateValue::Comma => return Err(("Expected expression.", span).into()),
IntermediateValue::Bracketed(t) => { IntermediateValue::Bracketed(t) => {
let v = Value::from_vec(t, scope, super_selector)?; let v = Value::from_vec(t, scope, super_selector, span)?;
match v.node { match v.node {
Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed), Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed),
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed), v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed),
@ -416,10 +436,11 @@ impl Value {
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
span_before: Span,
) -> SassResult<Spanned<Self>> { ) -> SassResult<Spanned<Self>> {
let span = match toks.peek() { let span = match toks.peek() {
Some(Token { pos, .. }) => *pos, Some(Token { pos, .. }) => *pos,
None => todo!("Expected expression."), None => return Err(("Expected expression.", span_before).into()),
}; };
devour_whitespace(toks); devour_whitespace(toks);
let mut last_was_whitespace = false; let mut last_was_whitespace = false;
@ -490,13 +511,15 @@ impl Value {
); );
continue; continue;
} }
space_separated.push(match Value::from_vec(t, scope, super_selector)?.node { space_separated.push(
match Value::from_vec(t, scope, super_selector, val.span)?.node {
Value::List(v, sep, Brackets::None) => { Value::List(v, sep, Brackets::None) => {
Value::List(v, sep, Brackets::Bracketed).span(val.span) Value::List(v, sep, Brackets::Bracketed).span(val.span)
} }
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed) v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed)
.span(val.span), .span(val.span),
}) },
)
} }
IntermediateValue::Paren(t) => { IntermediateValue::Paren(t) => {
last_was_whitespace = false; last_was_whitespace = false;
@ -547,8 +570,17 @@ impl Value {
toks: Vec<Token>, toks: Vec<Token>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
span_before: Span,
) -> SassResult<Spanned<Value>> { ) -> SassResult<Spanned<Value>> {
Self::from_tokens(&mut toks.into_iter().peekmore(), scope, super_selector) if toks.is_empty() {
return Err(("Expected expression.", span_before).into());
}
Self::from_tokens(
&mut toks.into_iter().peekmore(),
scope,
super_selector,
span_before,
)
} }
fn ident<I: Iterator<Item = Token>>( fn ident<I: Iterator<Item = Token>>(

View File

@ -208,3 +208,19 @@ error!(
at_else_alone, at_else_alone,
"@else {}", "Error: This at-rule is not allowed here." "@else {}", "Error: This at-rule is not allowed here."
); );
error!(
no_expression_for_variable,
"a {$color: {ed;}", "Error: Expected expression."
);
error!(
empty_style_value_no_semicolon,
"a {color:}", "Error: Expected expression."
);
error!(
empty_style_value_semicolon,
"a {color:;}", "Error: Expected expression."
);
error!(
ident_colon_closing_brace,
"r:}", "Error: Expected expression."
);

View File

@ -87,7 +87,7 @@ test!(
test!( test!(
non_ascii_numeric_interpreted_as_unit, non_ascii_numeric_interpreted_as_unit,
"a {\n color: 2߄;\n}\n", "a {\n color: 2߄;\n}\n",
"@charset \"UTF-8\";\na {\n color: 2߄;\n}" "@charset \"UTF-8\";\na {\n color: 2߄;\n}\n"
); );
error!( error!(
display_single_mul, display_single_mul,