diff --git a/src/parse/args.rs b/src/parse/args.rs index d10ac2f..8bb3e9d 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -72,19 +72,12 @@ impl<'a> Parser<'a> { } } '.' => { - let next = self.toks.next().ok_or(("expected \".\".", span))?; - if next.kind != '.' { - return Err(("expected \".\".", next.pos()).into()); - } - let next = self.toks.next().ok_or(("expected \".\".", next.pos()))?; - if next.kind != '.' { - return Err(("expected \".\".", next.pos()).into()); - } + self.expect_char('.')?; + self.expect_char('.')?; + self.whitespace_or_comment(); - let next = self.toks.next().ok_or(("expected \")\".", next.pos()))?; - if next.kind != ')' { - return Err(("expected \")\".", next.pos()).into()); - } + + self.expect_char(')')?; is_variadic = true; @@ -119,6 +112,7 @@ impl<'a> Parser<'a> { } self.whitespace_or_comment(); // TODO: this should NOT eat the opening curly brace + // todo: self.expect_char('{')?; match self.toks.next() { Some(v) if v.kind == '{' => {} Some(..) | None => return Err(("expected \"{\".", close_paren_span).into()), @@ -225,11 +219,7 @@ impl<'a> Parser<'a> { return Err(("expected \")\".", pos).into()); } self.toks.next(); - if let Some(Token { kind: '.', .. }) = self.toks.peek() { - self.toks.next(); - } else { - return Err(("expected \".\".", pos).into()); - } + self.expect_char('.')?; } else { return Err(("expected \")\".", pos).into()); } @@ -323,23 +313,16 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); continue; } - Some(Token { kind: '.', pos }) => { - let pos = *pos; + Some(Token { kind: '.', .. }) => { self.toks.next(); - if let Some(Token { kind: '.', pos }) = self.toks.peek().cloned() { - if !name.is_empty() { - return Err(("expected \")\".", pos).into()); - } - self.toks.next(); - if let Some(Token { kind: '.', .. }) = self.toks.peek() { - self.toks.next(); - } else { - return Err(("expected \".\".", pos).into()); - } - } else { - return Err(("expected \")\".", pos).into()); + self.expect_char('.')?; + + if !name.is_empty() { + return Err(("expected \")\".", self.span_before).into()); } + + self.expect_char('.')?; } Some(Token { pos, .. }) => { return Err(("expected \")\".", *pos).into()); diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index cb0816e..100cb3a 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -24,14 +24,10 @@ impl<'a> Parser<'a> { let init_cond = self.parse_value(true, &|_| false)?.node; - // consume the open curly brace - let span_before = match self.toks.next() { - Some(Token { kind: '{', pos }) => pos, - Some(..) | None => return Err(("expected \"{\".", self.span_before).into()), - }; + self.expect_char('{')?; if self.toks.peek().is_none() { - return Err(("expected \"}\".", span_before).into()); + return Err(("expected \"}\".", self.span_before).into()); } self.whitespace_or_comment(); @@ -89,12 +85,7 @@ impl<'a> Parser<'a> { false } else { let v = self.parse_value(true, &|_| false)?.node.is_true(); - match self.toks.next() { - Some(Token { kind: '{', .. }) => {} - Some(..) | None => { - return Err(("expected \"{\".", self.span_before).into()) - } - } + self.expect_char('{')?; v }; if cond { @@ -164,17 +155,15 @@ impl<'a> Parser<'a> { } pub(super) fn parse_for(&mut self) -> SassResult> { + // todo: whitespace or comment self.whitespace(); - let next = self - .toks - .next() - .ok_or(("expected \"$\".", self.span_before))?; - let var: Spanned = match next.kind { - '$' => self - .parse_identifier_no_interpolation(false)? - .map_node(|i| i.into()), - _ => return Err(("expected \"$\".", self.span_before).into()), - }; + // todo: test for error here + self.expect_char('$')?; + + let var = self + .parse_identifier_no_interpolation(false)? + .map_node(|n| n.into()); + self.whitespace(); self.span_before = match self.toks.peek() { Some(tok) => tok.pos, @@ -278,11 +267,7 @@ impl<'a> Parser<'a> { } }; - // consume the open curly brace - match self.toks.next() { - Some(Token { kind: '{', pos }) => pos, - Some(..) | None => return Err(("expected \"{\".", to_val.span).into()), - }; + self.expect_char('{')?; let body = read_until_closing_curly_brace(self.toks)?; self.toks.next(); @@ -443,15 +428,11 @@ impl<'a> Parser<'a> { let mut vars: Vec> = Vec::new(); loop { - let next = self - .toks - .next() - .ok_or(("expected \"$\".", self.span_before))?; + self.expect_char('$')?; - match next.kind { - '$' => vars.push(self.parse_identifier()?.map_node(|i| i.into())), - _ => return Err(("expected \"$\".", next.pos()).into()), - } + vars.push(self.parse_identifier()?.map_node(|i| i.into())); + + // todo: whitespace or comment self.whitespace(); if self .toks diff --git a/src/parse/function.rs b/src/parse/function.rs index 1f1830a..5b02227 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -40,11 +40,9 @@ impl<'a> Parser<'a> { } self.whitespace_or_comment(); - let args = match self.toks.next() { - Some(Token { kind: '(', .. }) => self.parse_func_args()?, - Some(Token { pos, .. }) => return Err(("expected \"(\".", pos).into()), - None => return Err(("expected \"(\".", span).into()), - }; + self.expect_char('(')?; + + let args = self.parse_func_args()?; self.whitespace(); diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index 4a5caf9..b082d03 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -63,9 +63,8 @@ impl<'a, 'b> KeyframesSelectorParser<'a, 'b> { num.push_str(&eat_whole_number(self.parser.toks)); } - if !matches!(self.parser.toks.next(), Some(Token { kind: '%', .. })) { - return Err(("expected \"%\".", tok.pos).into()); - } + self.parser.expect_char('%')?; + selectors.push(KeyframesSelector::Percent(num.into_boxed_str())); } '{' => break, diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 6fcc1d6..f0764b7 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -104,9 +104,7 @@ impl<'a> Parser<'a> { ident.node.make_ascii_lowercase(); if ident.node == "using" { self.whitespace_or_comment(); - if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) { - return Err(("expected \"(\".", ident.span).into()); - } + self.expect_char('(')?; Some(self.parse_func_args()?) } else { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 7a29a34..418ad7b 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -903,9 +903,7 @@ impl<'a> Parser<'a> { self.whitespace(); - if !matches!(self.toks.next(), Some(Token { kind: '{', .. })) { - return Err(("expected \"{\".", self.span_before).into()); - } + self.expect_char('{')?; let raw_body = self.parse_stmt()?; diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 1962fba..0e59095 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -598,6 +598,7 @@ impl<'a> Parser<'a> { }; // todo: the above shouldn't eat the closing paren if let Some(last_tok) = inner.pop() { + // todo: we should remove this like we did for square braces if last_tok.kind != ')' { return Some(Err(("expected \")\".", span).into())); } diff --git a/src/parse/variable.rs b/src/parse/variable.rs index 78df9b2..6a7429c 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -29,9 +29,9 @@ impl<'a> Parser<'a> { assert!(matches!(self.toks.next(), Some(Token { kind: '$', .. }))); let ident: Identifier = self.parse_identifier_no_interpolation(false)?.node.into(); self.whitespace(); - if !matches!(self.toks.next(), Some(Token { kind: ':', .. })) { - return Err(("expected \":\".", self.span_before).into()); - } + + self.expect_char(':')?; + let VariableValue { val_toks, global, diff --git a/src/selector/attribute.rs b/src/selector/attribute.rs index 3fb7dc1..e1b8129 100644 --- a/src/selector/attribute.rs +++ b/src/selector/attribute.rs @@ -5,7 +5,9 @@ use std::{ use codemap::Span; -use crate::{common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident, value::Value}; +use crate::{ + common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident, value::Value, Token, +}; use super::{Namespace, QualifiedName}; @@ -41,13 +43,8 @@ impl Hash for Attribute { fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult { let next = parser.toks.peek().ok_or(("Expected identifier.", start))?; if next.kind == '*' { - let pos = next.pos; parser.toks.next(); - if parser.toks.peek().ok_or(("expected \"|\".", pos))?.kind != '|' { - return Err(("expected \"|\".", pos).into()); - } - - parser.span_before = parser.toks.next().unwrap().pos(); + parser.expect_char('|')?; let ident = parser.parse_identifier()?.node; return Ok(QualifiedName { @@ -89,19 +86,18 @@ fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult) -> SassResult { - let start = parser.span_before; - let op = match parser.toks.next().ok_or(("Expected \"]\".", start))?.kind { - '=' => return Ok(AttributeOp::Equals), - '~' => AttributeOp::Include, - '|' => AttributeOp::Dash, - '^' => AttributeOp::Prefix, - '$' => AttributeOp::Suffix, - '*' => AttributeOp::Contains, - _ => return Err(("Expected \"]\".", start).into()), + let op = match parser.toks.next() { + Some(Token { kind: '=', .. }) => return Ok(AttributeOp::Equals), + Some(Token { kind: '~', .. }) => AttributeOp::Include, + Some(Token { kind: '|', .. }) => AttributeOp::Dash, + Some(Token { kind: '^', .. }) => AttributeOp::Prefix, + Some(Token { kind: '$', .. }) => AttributeOp::Suffix, + Some(Token { kind: '*', .. }) => AttributeOp::Contains, + Some(..) | None => return Err(("Expected \"]\".", parser.span_before).into()), }; - if parser.toks.next().ok_or(("expected \"=\".", start))?.kind != '=' { - return Err(("expected \"=\".", start).into()); - } + + parser.expect_char('=')?; + Ok(op) } impl Attribute { @@ -145,25 +141,23 @@ impl Attribute { }; parser.whitespace(); - let peek = parser.toks.peek().ok_or(("expected more input.", start))?; - - let modifier = match peek.kind { - c if c.is_alphabetic() => Some(c), + let modifier = match parser.toks.peek().cloned() { + Some(Token { + kind: c @ 'a'..='z', + .. + }) + | Some(Token { + kind: c @ 'A'..='Z', + .. + }) => { + parser.toks.next(); + parser.whitespace(); + Some(c) + } _ => None, }; - let pos = peek.pos(); - - if modifier.is_some() { - parser.toks.next(); - parser.whitespace(); - } - - if parser.toks.peek().ok_or(("expected \"]\".", pos))?.kind != ']' { - return Err(("expected \"]\".", pos).into()); - } - - parser.toks.next(); + parser.expect_char(']')?; Ok(Attribute { op, diff --git a/src/selector/parse.rs b/src/selector/parse.rs index cc7c001..2522bce 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -317,14 +317,14 @@ impl<'a, 'b> SelectorParser<'a, 'b> { if SELECTOR_PSEUDO_ELEMENTS.contains(&unvendored) { selector = Some(Box::new(self.parse_selector_list()?)); self.parser.whitespace(); - self.expect_closing_paren()?; + self.parser.expect_char(')')?; } else { argument = Some(self.declaration_value()?.into_boxed_str()); } } else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) { selector = Some(Box::new(self.parse_selector_list()?)); self.parser.whitespace(); - self.expect_closing_paren()?; + self.parser.expect_char(')')?; } else if unvendored == "nth-child" || unvendored == "nth-last-child" { let mut this_arg = self.parse_a_n_plus_b()?; let found_whitespace = self.parser.whitespace(); @@ -339,7 +339,7 @@ impl<'a, 'b> SelectorParser<'a, 'b> { } _ => {} } - self.expect_closing_paren()?; + self.parser.expect_char(')')?; argument = Some(this_arg.into_boxed_str()); } else { argument = Some( @@ -541,14 +541,6 @@ impl<'a, 'b> SelectorParser<'a, 'b> { Err((format!("Expected \"{}\".", s), self.span).into()) } } - - fn expect_closing_paren(&mut self) -> SassResult<()> { - if let Some(Token { kind: ')', .. }) = self.parser.toks.next() { - Ok(()) - } else { - Err(("expected \")\".", self.span).into()) - } - } } /// Returns whether `c` can start a simple selector other than a type