emit proper error on unclosed quote

This commit is contained in:
ConnorSkees 2020-05-24 10:04:30 -04:00
parent b653c55ad7
commit 737a6ba90d
16 changed files with 91 additions and 62 deletions

View File

@ -262,7 +262,7 @@ pub(crate) fn eat_func_args<I: Iterator<Item = Token>>(
} }
'(' => { '(' => {
default.push(toks.next().unwrap()); default.push(toks.next().unwrap());
default.extend(read_until_closing_paren(toks)); default.extend(read_until_closing_paren(toks)?);
} }
_ => default.push(toks.next().unwrap()), _ => default.push(toks.next().unwrap()),
} }
@ -380,15 +380,15 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
',' => break, ',' => break,
'[' => { '[' => {
val.push(tok); val.push(tok);
val.extend(read_until_closing_square_brace(toks)); val.extend(read_until_closing_square_brace(toks)?);
} }
'(' => { '(' => {
val.push(tok); val.push(tok);
val.extend(read_until_closing_paren(toks)); val.extend(read_until_closing_paren(toks)?);
} }
'"' | '\'' => { '"' | '\'' => {
val.push(tok); val.push(tok);
val.extend(read_until_closing_quote(toks, tok.kind)); val.extend(read_until_closing_quote(toks, tok.kind)?);
} }
_ => val.push(tok), _ => val.push(tok),
} }

View File

@ -119,7 +119,7 @@ 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)?;
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
@ -130,7 +130,7 @@ pub(crate) fn parse_each<I: Iterator<Item = Token>>(
}; };
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);
let mut body = read_until_closing_curly_brace(toks); let mut body = read_until_closing_curly_brace(toks)?;
body.push(toks.next().unwrap()); body.push(toks.next().unwrap());
devour_whitespace(toks); devour_whitespace(toks);

View File

@ -148,7 +148,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)?;
let to = match to_val.node.eval(to_val.span)?.node { let to = match to_val.node.eval(to_val.span)?.node {
@ -164,7 +164,7 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
.into()) .into())
} }
}; };
let body = read_until_closing_curly_brace(toks); let body = read_until_closing_curly_brace(toks)?;
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);

View File

@ -58,7 +58,7 @@ impl Function {
devour_whitespace(toks); devour_whitespace(toks);
let mut body = read_until_closing_curly_brace(toks); //eat_stmts(toks, &mut scope.clone(), super_selector)?; let mut body = read_until_closing_curly_brace(toks)?; //eat_stmts(toks, &mut scope.clone(), super_selector)?;
body.push(toks.next().unwrap()); body.push(toks.next().unwrap());
devour_whitespace(toks); devour_whitespace(toks);

View File

@ -41,13 +41,13 @@ impl If {
) -> SassResult<If> { ) -> SassResult<If> {
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
let mut branches = Vec::new(); let mut branches = Vec::new();
let init_toks = read_until_open_curly_brace(toks); let init_cond_toks = read_until_open_curly_brace(toks)?;
if init_toks.is_empty() || toks.next().is_none() { if init_cond_toks.is_empty() || toks.next().is_none() {
return Err(("Expected expression.", span_before).into()); return Err(("Expected expression.", span_before).into());
} }
let init_cond = Value::from_vec(init_toks, scope, super_selector)?; let init_cond = Value::from_vec(init_cond_toks, scope, super_selector)?;
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() {
init_toks.push(tok); init_toks.push(tok);
} else { } else {
@ -80,18 +80,18 @@ impl If {
'i' if toks.next().unwrap().kind.to_ascii_lowercase() == 'f' => { 'i' if toks.next().unwrap().kind.to_ascii_lowercase() == 'f' => {
toks.next(); toks.next();
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,
)?; )?;
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);
branches.push(Branch::new(cond, read_until_closing_curly_brace(toks))); branches.push(Branch::new(cond, read_until_closing_curly_brace(toks)?));
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);
} }
'{' => { '{' => {
else_ = read_until_closing_curly_brace(toks); else_ = read_until_closing_curly_brace(toks)?;
toks.next(); toks.next();
break; break;
} }

View File

@ -49,7 +49,7 @@ impl Mixin {
devour_whitespace(toks); devour_whitespace(toks);
let mut body = read_until_closing_curly_brace(toks); let mut body = read_until_closing_curly_brace(toks)?;
body.push(toks.next().unwrap()); body.push(toks.next().unwrap());
Ok(Spanned { Ok(Spanned {

View File

@ -69,7 +69,7 @@ impl AtRule {
node: message, node: message,
span, span,
} = Value::from_vec( } = Value::from_vec(
read_until_semicolon_or_closing_curly_brace(toks), read_until_semicolon_or_closing_curly_brace(toks)?,
scope, scope,
super_selector, super_selector,
)?; )?;
@ -81,7 +81,7 @@ impl AtRule {
node: message, node: message,
span, span,
} = Value::from_vec( } = Value::from_vec(
read_until_semicolon_or_closing_curly_brace(toks), read_until_semicolon_or_closing_curly_brace(toks)?,
scope, scope,
super_selector, super_selector,
)?; )?;
@ -103,7 +103,7 @@ impl AtRule {
node: message, node: message,
span, span,
} = Value::from_vec( } = Value::from_vec(
read_until_semicolon_or_closing_curly_brace(toks), read_until_semicolon_or_closing_curly_brace(toks)?,
scope, scope,
super_selector, super_selector,
)?; )?;
@ -139,7 +139,7 @@ impl AtRule {
} }
} }
AtRuleKind::Return => { AtRuleKind::Return => {
let v = read_until_semicolon_or_closing_curly_brace(toks); let v = read_until_semicolon_or_closing_curly_brace(toks)?;
if toks.peek().unwrap().kind == ';' { if toks.peek().unwrap().kind == ';' {
toks.next(); toks.next();
} }
@ -153,7 +153,7 @@ impl AtRule {
let mut selector = &Selector::replace( let mut selector = &Selector::replace(
super_selector, super_selector,
Selector::from_tokens( Selector::from_tokens(
&mut read_until_open_curly_brace(toks).into_iter().peekmore(), &mut read_until_open_curly_brace(toks)?.into_iter().peekmore(),
scope, scope,
super_selector, super_selector,
)?, )?,
@ -165,7 +165,7 @@ impl AtRule {
} }
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);
let mut body = read_until_closing_curly_brace(toks); let mut body = read_until_closing_curly_brace(toks)?;
body.push(toks.next().unwrap()); body.push(toks.next().unwrap());
devour_whitespace(toks); devour_whitespace(toks);
let mut styles = Vec::new(); let mut styles = Vec::new();
@ -201,7 +201,7 @@ impl AtRule {
} }
} }
AtRuleKind::Charset => { AtRuleKind::Charset => {
read_until_semicolon_or_closing_curly_brace(toks); read_until_semicolon_or_closing_curly_brace(toks)?;
if toks.peek().unwrap().kind == ';' { if toks.peek().unwrap().kind == ';' {
toks.next(); toks.next();
} }

View File

@ -50,7 +50,7 @@ pub(crate) fn parse_while<I: Iterator<Item = Token>>(
span: Span, span: Span,
) -> SassResult<Spanned<AtRule>> { ) -> SassResult<Spanned<AtRule>> {
devour_whitespace(toks); devour_whitespace(toks);
let cond = read_until_open_curly_brace(toks); let cond = read_until_open_curly_brace(toks)?;
if cond.is_empty() { if cond.is_empty() {
return Err(("Expected expression.", span).into()); return Err(("Expected expression.", span).into());
@ -58,7 +58,7 @@ pub(crate) fn parse_while<I: Iterator<Item = Token>>(
toks.next(); toks.next();
let mut body = read_until_closing_curly_brace(toks); let mut body = read_until_closing_curly_brace(toks)?;
body.push(toks.next().unwrap()); body.push(toks.next().unwrap());

View File

@ -375,7 +375,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
Some(Token { kind: '{', .. }) => { Some(Token { kind: '{', .. }) => {
let next = toks.next().unwrap(); let next = toks.next().unwrap();
values.push(next); values.push(next);
values.extend(read_until_closing_curly_brace(toks)); values.extend(read_until_closing_curly_brace(toks)?);
if let Some(tok) = toks.next() { if let Some(tok) = toks.next() {
values.push(tok); values.push(tok);
} else { } else {
@ -395,7 +395,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
// it is causing us to emit nothing on malformed input // it is causing us to emit nothing on malformed input
'(' => { '(' => {
values.push(toks.next().unwrap()); values.push(toks.next().unwrap());
values.extend(read_until_closing_paren(toks)); values.extend(read_until_closing_paren(toks)?);
} }
_ => values.push(toks.next().unwrap()), _ => values.push(toks.next().unwrap()),
}; };

View File

@ -403,7 +403,12 @@ impl Selector {
super_selector: &Selector, super_selector: &Selector,
span_before: Span, span_before: Span,
) -> SassResult<SelectorKind> { ) -> SassResult<SelectorKind> {
let is_pseudo_element = if toks.peek().ok_or(("Expected identifier.", span_before))?.kind == ':' { let is_pseudo_element = if toks
.peek()
.ok_or(("Expected identifier.", span_before))?
.kind
== ':'
{
toks.next(); toks.next();
true true
} else { } else {
@ -419,7 +424,7 @@ impl Selector {
Ok( Ok(
if toks.peek().is_some() && toks.peek().unwrap().kind == '(' { if toks.peek().is_some() && toks.peek().unwrap().kind == '(' {
toks.next(); toks.next();
let mut inner_toks = read_until_closing_paren(toks); let mut inner_toks = read_until_closing_paren(toks)?;
inner_toks.pop(); inner_toks.pop();
let inner = Selector::from_tokens( let inner = Selector::from_tokens(
&mut inner_toks.into_iter().peekmore(), &mut inner_toks.into_iter().peekmore(),

View File

@ -4,6 +4,7 @@ use peekmore::PeekMoreIterator;
use super::read_until_closing_quote; use super::read_until_closing_quote;
use crate::error::SassResult;
use crate::Token; use crate::Token;
/// Reads until the char is found, consuming the char, /// Reads until the char is found, consuming the char,
@ -11,13 +12,13 @@ use crate::Token;
pub(crate) fn read_until_char<I: Iterator<Item = Token>>( pub(crate) fn read_until_char<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
c: char, c: char,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut v = Vec::new(); let mut v = Vec::new();
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
match tok.kind { match tok.kind {
'"' | '\'' => { '"' | '\'' => {
v.push(tok); v.push(tok);
v.extend(read_until_closing_quote(toks, tok.kind)); v.extend(read_until_closing_quote(toks, tok.kind)?);
continue; continue;
} }
t if t == c => break, t if t == c => break,
@ -25,7 +26,7 @@ pub(crate) fn read_until_char<I: Iterator<Item = Token>>(
} }
v.push(tok) v.push(tok)
} }
v Ok(v)
} }
pub(crate) fn hex_char_for(number: u32) -> char { pub(crate) fn hex_char_for(number: u32) -> char {

View File

@ -16,7 +16,7 @@ pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> 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)?;
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

@ -2,6 +2,7 @@ use std::iter::Iterator;
use peekmore::PeekMoreIterator; use peekmore::PeekMoreIterator;
use crate::error::SassResult;
use crate::Token; use crate::Token;
use super::{devour_whitespace, read_until_newline}; use super::{devour_whitespace, read_until_newline};
@ -11,7 +12,7 @@ use super::{devour_whitespace, read_until_newline};
// Does not consume the open curly brace // Does not consume the open curly brace
pub(crate) fn read_until_open_curly_brace<I: Iterator<Item = Token>>( pub(crate) fn read_until_open_curly_brace<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut t = Vec::new(); let mut t = Vec::new();
let mut n = 0; let mut n = 0;
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek() {
@ -33,6 +34,11 @@ pub(crate) fn read_until_open_curly_brace<I: Iterator<Item = Token>>(
} }
continue; continue;
} }
q @ '"' | q @ '\'' => {
t.push(toks.next().unwrap());
t.extend(read_until_closing_quote(toks, q)?);
continue;
}
_ => {} _ => {}
} }
if n == 1 { if n == 1 {
@ -41,19 +47,19 @@ pub(crate) fn read_until_open_curly_brace<I: Iterator<Item = Token>>(
t.push(toks.next().unwrap()); t.push(toks.next().unwrap());
} }
t Ok(t)
} }
pub(crate) fn read_until_closing_curly_brace<I: Iterator<Item = Token>>( pub(crate) fn read_until_closing_curly_brace<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut t = Vec::new(); let mut t = Vec::new();
let mut nesting = 0; let mut nesting = 0;
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek() {
match tok.kind { match tok.kind {
q @ '"' | q @ '\'' => { q @ '"' | q @ '\'' => {
t.push(toks.next().unwrap()); t.push(toks.next().unwrap());
t.extend(read_until_closing_quote(toks, q)); t.extend(read_until_closing_quote(toks, q)?);
} }
'{' => { '{' => {
nesting += 1; nesting += 1;
@ -80,7 +86,7 @@ pub(crate) fn read_until_closing_curly_brace<I: Iterator<Item = Token>>(
} }
'(' => { '(' => {
t.push(toks.next().unwrap()); t.push(toks.next().unwrap());
t.extend(read_until_closing_paren(toks)); t.extend(read_until_closing_paren(toks)?);
} }
'\\' => { '\\' => {
t.push(toks.next().unwrap()); t.push(toks.next().unwrap());
@ -92,13 +98,16 @@ pub(crate) fn read_until_closing_curly_brace<I: Iterator<Item = Token>>(
} }
} }
devour_whitespace(toks); devour_whitespace(toks);
t Ok(t)
} }
/// Read tokens into a vector until a matching closing quote is found
///
/// The closing quote is included in the output
pub(crate) fn read_until_closing_quote<I: Iterator<Item = Token>>( pub(crate) fn read_until_closing_quote<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
q: char, q: char,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut t = Vec::new(); let mut t = Vec::new();
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
match tok.kind { match tok.kind {
@ -121,18 +130,24 @@ pub(crate) fn read_until_closing_quote<I: Iterator<Item = Token>>(
let next = toks.peek().unwrap(); let next = toks.peek().unwrap();
if next.kind == '{' { if next.kind == '{' {
t.push(toks.next().unwrap()); t.push(toks.next().unwrap());
t.append(&mut read_until_closing_curly_brace(toks)); t.append(&mut read_until_closing_curly_brace(toks)?);
} }
} }
_ => t.push(tok), _ => t.push(tok),
} }
} }
t if let Some(tok) = t.pop() {
if tok.kind != q {
return Err((format!("Expected {}.", q), tok.pos).into());
}
t.push(tok);
}
Ok(t)
} }
pub(crate) fn read_until_semicolon_or_closing_curly_brace<I: Iterator<Item = Token>>( pub(crate) fn read_until_semicolon_or_closing_curly_brace<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut t = Vec::new(); let mut t = Vec::new();
let mut nesting = 0; let mut nesting = 0;
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek() {
@ -149,7 +164,7 @@ pub(crate) fn read_until_semicolon_or_closing_curly_brace<I: Iterator<Item = Tok
'"' | '\'' => { '"' | '\'' => {
let quote = toks.next().unwrap(); let quote = toks.next().unwrap();
t.push(quote); t.push(quote);
t.extend(read_until_closing_quote(toks, quote.kind)); t.extend(read_until_closing_quote(toks, quote.kind)?);
} }
'{' => { '{' => {
nesting += 1; nesting += 1;
@ -178,12 +193,12 @@ pub(crate) fn read_until_semicolon_or_closing_curly_brace<I: Iterator<Item = Tok
} }
} }
devour_whitespace(toks); devour_whitespace(toks);
t Ok(t)
} }
pub(crate) fn read_until_closing_paren<I: Iterator<Item = Token>>( pub(crate) fn read_until_closing_paren<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut t = Vec::new(); let mut t = Vec::new();
let mut scope = 0; let mut scope = 0;
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
@ -191,7 +206,7 @@ pub(crate) fn read_until_closing_paren<I: Iterator<Item = Token>>(
')' => { ')' => {
if scope < 1 { if scope < 1 {
t.push(tok); t.push(tok);
return t; return Ok(t);
} else { } else {
scope -= 1; scope -= 1;
} }
@ -199,7 +214,7 @@ pub(crate) fn read_until_closing_paren<I: Iterator<Item = Token>>(
'(' => scope += 1, '(' => scope += 1,
'"' | '\'' => { '"' | '\'' => {
t.push(tok); t.push(tok);
t.extend(read_until_closing_quote(toks, tok.kind)); t.extend(read_until_closing_quote(toks, tok.kind)?);
continue; continue;
} }
'\\' => { '\\' => {
@ -213,12 +228,12 @@ pub(crate) fn read_until_closing_paren<I: Iterator<Item = Token>>(
} }
t.push(tok) t.push(tok)
} }
t Ok(t)
} }
pub(crate) fn read_until_closing_square_brace<I: Iterator<Item = Token>>( pub(crate) fn read_until_closing_square_brace<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
) -> Vec<Token> { ) -> SassResult<Vec<Token>> {
let mut t = Vec::new(); let mut t = Vec::new();
let mut scope = 0; let mut scope = 0;
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
@ -227,7 +242,7 @@ pub(crate) fn read_until_closing_square_brace<I: Iterator<Item = Token>>(
']' => { ']' => {
if scope < 1 { if scope < 1 {
t.push(tok); t.push(tok);
return t; return Ok(t);
} else { } else {
scope -= 1; scope -= 1;
} }
@ -235,7 +250,7 @@ pub(crate) fn read_until_closing_square_brace<I: Iterator<Item = Token>>(
'[' => scope += 1, '[' => scope += 1,
'"' | '\'' => { '"' | '\'' => {
t.push(tok); t.push(tok);
t.extend(read_until_closing_quote(toks, tok.kind)); t.extend(read_until_closing_quote(toks, tok.kind)?);
continue; continue;
} }
'\\' => { '\\' => {
@ -248,5 +263,5 @@ pub(crate) fn read_until_closing_square_brace<I: Iterator<Item = Token>>(
} }
t.push(tok) t.push(tok)
} }
t Ok(t)
} }

View File

@ -56,7 +56,7 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
'"' | '\'' => { '"' | '\'' => {
let quote = toks.next().unwrap(); let quote = toks.next().unwrap();
val_toks.push(quote); val_toks.push(quote);
val_toks.extend(read_until_closing_quote(toks, quote.kind)); val_toks.extend(read_until_closing_quote(toks, quote.kind)?);
} }
'#' => { '#' => {
val_toks.push(toks.next().unwrap()); val_toks.push(toks.next().unwrap());
@ -93,7 +93,7 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
} }
'(' => { '(' => {
val_toks.push(toks.next().unwrap()); val_toks.push(toks.next().unwrap());
val_toks.extend(read_until_closing_paren(toks)); val_toks.extend(read_until_closing_paren(toks)?);
} }
'!' => { '!' => {
let pos = tok.pos(); let pos = tok.pos();

View File

@ -189,7 +189,7 @@ 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)?;
if paren_toks.peek().is_none() { if paren_toks.peek().is_none() {
return Ok(Spanned { return Ok(Spanned {
@ -198,7 +198,7 @@ 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)?;
map.insert(key.node, val.node); map.insert(key.node, val.node);
@ -212,9 +212,9 @@ 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)?;
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)?;
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) {
@ -755,7 +755,10 @@ impl Value {
} }
'(' => { '(' => {
let mut span = toks.next().unwrap().pos(); let mut span = toks.next().unwrap().pos();
let mut inner = read_until_closing_paren(toks); let mut inner = match read_until_closing_paren(toks) {
Ok(v) => v,
Err(e) => return Some(Err(e.into())),
};
// todo: the above shouldn't eat the closing paren // todo: the above shouldn't eat the closing paren
if let Some(last_tok) = inner.pop() { if let Some(last_tok) = inner.pop() {
if last_tok.kind != ')' { if last_tok.kind != ')' {
@ -794,7 +797,10 @@ impl Value {
} }
'[' => { '[' => {
let mut span = toks.next().unwrap().pos(); let mut span = toks.next().unwrap().pos();
let mut inner = read_until_closing_square_brace(toks); let mut inner = match read_until_closing_square_brace(toks) {
Ok(v) => v,
Err(e) => return Some(Err(e.into())),
};
if let Some(last_tok) = inner.pop() { if let Some(last_tok) = inner.pop() {
if last_tok.kind != ']' { if last_tok.kind != ']' {
return Some(Err(("expected \"]\".", span).into())); return Some(Err(("expected \"]\".", span).into()));

View File

@ -126,3 +126,5 @@ error!(
nothing_after_escape, nothing_after_escape,
"@if \\", "Error: Expected expression." "@if \\", "Error: Expected expression."
); );
error!(unclosed_dbl_quote, "@if true \" {}", "Error: Expected \".");
error!(unclosed_sgl_quote, "@if true ' {}", "Error: Expected '.");