make parsing of toplevel tokens more robust

This commit is contained in:
ConnorSkees 2020-05-24 07:43:54 -04:00
parent bb89a865d4
commit a01ed981ce
5 changed files with 73 additions and 26 deletions

View File

@ -400,5 +400,12 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
_ => values.push(toks.next().unwrap()), _ => values.push(toks.next().unwrap()),
}; };
} }
// if `values` is not empty, there was an unexpected toplevel token
// that should be part of a selector
if let Some(v) = values.pop() {
return Err(("expected \"{\".", v.pos).into());
}
Ok(None) Ok(None)
} }

View File

@ -151,23 +151,40 @@ struct StyleSheetParser<'a> {
path: &'a Path, path: &'a Path,
} }
fn is_selector_char(c: char) -> bool {
c.is_alphanumeric()
|| matches!(
c,
'_' | '-' | '[' | '#' | ':' | '*' | '%' | '.' | '>' | '\\' | '+'
)
}
impl<'a> StyleSheetParser<'a> { impl<'a> StyleSheetParser<'a> {
fn parse_toplevel(mut self) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> { fn parse_toplevel(mut self) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
let mut rules: Vec<Spanned<Stmt>> = Vec::new(); let mut rules: Vec<Spanned<Stmt>> = Vec::new();
devour_whitespace(self.lexer);
while let Some(Token { kind, .. }) = self.lexer.peek() { while let Some(Token { kind, .. }) = self.lexer.peek() {
match kind { match kind {
_ if is_selector_char(*kind) => { 'a'..='z'
| 'A'..='Z'
| '0'..='9'
| '['
| '\\'
| ']'
| '^'
| '_'
| '-'
| '#'
| ':'
| '*'
| '%'
| '.'
| '>'
| '+'
| '='
| ','
| '('
| ')'
| '<'
| '?'
| '~'
| '|'
| '\u{7f}'..=std::char::MAX => {
rules.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?) rules.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?)
} }
'\t' | '\n' | ' ' => { '\t' | '\n' | ' ' | ';' => {
self.lexer.next(); self.lexer.next();
continue; continue;
} }
@ -331,20 +348,16 @@ impl<'a> StyleSheetParser<'a> {
) )
.into()) .into())
} }
c if c.is_control() => { '\u{0}'..='\u{8}' | '\u{b}'..='\u{1f}' => {
return Err(("expected selector.", self.lexer.next().unwrap().pos).into()); return Err(("expected selector.", self.lexer.next().unwrap().pos).into());
} }
',' | '!' | '(' | ')' => { '{' | '!' => {
return Err(("expected \"{\".", self.lexer.next().unwrap().pos).into());
}
'{' => {
return Err(("expected \"}\".", self.lexer.next().unwrap().pos).into()); return Err(("expected \"}\".", self.lexer.next().unwrap().pos).into());
} }
'`' | '\'' | '"' => { '`' | '\'' | '"' => {
return Err(("expected selector.", self.lexer.next().unwrap().pos).into()); return Err(("expected selector.", self.lexer.next().unwrap().pos).into());
} }
'}' => return Err(("unmatched \"}\".", self.lexer.next().unwrap().pos).into()), '}' => return Err(("unmatched \"}\".", self.lexer.next().unwrap().pos).into()),
_ => todo!("unexpected toplevel token: {:?}", kind),
}; };
} }
Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone()))) Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone())))

View File

@ -83,20 +83,15 @@ error!(
"a {$a", "Error: expected \":\"." "a {$a", "Error: expected \":\"."
); );
error!(toplevel_comma, "a {},", "Error: expected \"{\"."); error!(toplevel_comma, "a {},", "Error: expected \"{\".");
error!(toplevel_exclamation, "! {}", "Error: expected \"{\"."); error!(toplevel_exclamation_alone, "!", "Error: expected \"}\".");
error!(toplevel_exclamation, "! {}", "Error: expected \"}\".");
error!(toplevel_backtick, "` {}", "Error: expected selector."); error!(toplevel_backtick, "` {}", "Error: expected selector.");
error!( error!(
toplevel_open_curly_brace, toplevel_open_curly_brace,
"{ {color: red;}", "Error: expected \"}\"." "{ {color: red;}", "Error: expected \"}\"."
); );
error!( error!(toplevel_open_paren, "(", "Error: expected \"{\".");
toplevel_open_paren, error!(toplevel_close_paren, "(", "Error: expected \"{\".");
"(", "Error: expected \"{\"."
);
error!(
toplevel_close_paren,
"(", "Error: expected \"{\"."
);
error!( error!(
backtick_in_value, backtick_in_value,
"a {color:`red;}", "Error: Expected expression." "a {color:`red;}", "Error: Expected expression."
@ -184,3 +179,24 @@ error!(
nothing_after_gt, nothing_after_gt,
"a {color: 1 >", "Error: Expected expression." "a {color: 1 >", "Error: Expected expression."
); );
error!(toplevel_eq_alone, "=", "Error: expected \"{\".");
error!(toplevel_gt_alone, ">", "Error: expected \"{\".");
error!(toplevel_lt_alone, "<", "Error: expected \"{\".");
error!(toplevel_question_alone, "?", "Error: expected \"{\".");
error!(toplevel_caret_alone, "^", "Error: expected \"{\".");
test!(toplevel_gt_as_selector, "> {}", "");
test!(toplevel_tilde_as_selector, "~ {}", "");
error!(toplevel_lt_as_selector, "< {}", "Error: expected selector.");
error!(
toplevel_question_as_selector,
"? {}", "Error: expected selector."
);
error!(
toplevel_caret_as_selector,
"^ {}", "Error: expected selector."
);
error!(toplevel_eq, "= {}", "Error: expected selector.");
error!(value_after_style, "a {}a", "Error: expected \"{\".");
test!(whitespace_after_style, "a {}\t\n ", "");
test!(toplevel_semicolon, ";", "");
test!(toplevel_semicolon_after_style, "a {};", "");

View File

@ -15,7 +15,7 @@ test!(
test!( test!(
same_function_equal, same_function_equal,
"@function user-defined() {@return null} "@function user-defined() {@return null}
a {b: get-function(user-defined) == get-function(user-defined)}s", a {b: get-function(user-defined) == get-function(user-defined)}",
"a {\n b: true;\n}\n" "a {\n b: true;\n}\n"
); );
test!( test!(

View File

@ -530,3 +530,14 @@ test!(
"+ {\n color: &;\n}\n", "+ {\n color: &;\n}\n",
"+ {\n color: +;\n}\n" "+ {\n color: +;\n}\n"
); );
error!(
#[ignore = "namespaces are not yet parsed correctly"]
empty_namespace,
"| {}", "Error: Expected identifier."
);
test!(
#[ignore = "namespaces are not yet parsed correctly"]
simple_namespace,
"|f {\n color: &;\n}\n",
"|f {\n color: |f;\n}\n"
);