use rucc_ast::{Ast, Decl, DeclId, Expr, ExprId, Stmt, StmtId};
use rucc_diag::{Diagnostic, Errors};
use rucc_lex::Punct;
use crate::cursor::Cursor;
pub fn push_about<P: Poison>(errors: &mut Errors, ast: &Ast, about: P, diagnostic: Diagnostic) {
errors.push_unless(about.is_poisoned(ast), diagnostic);
}
pub trait Poison: Copy {
fn is_poisoned(self, ast: &Ast) -> bool;
}
impl Poison for ExprId {
#[inline]
fn is_poisoned(self, ast: &Ast) -> bool {
matches!(ast[self], Expr::Error)
}
}
impl Poison for StmtId {
#[inline]
fn is_poisoned(self, ast: &Ast) -> bool {
matches!(ast[self], Stmt::Error)
}
}
impl Poison for DeclId {
#[inline]
fn is_poisoned(self, ast: &Ast) -> bool {
matches!(ast[self], Decl::Error)
}
}
pub fn skip_to_statement_end(cursor: &mut Cursor<'_>) {
let mut depth = 0u32;
while !cursor.is_eof() {
match cursor.current().punct() {
Some(Punct::Semi) if depth == 0 => {
cursor.bump();
return;
}
Some(Punct::RBrace) if depth == 0 => return,
Some(Punct::LBrace | Punct::LParen | Punct::LBracket) => depth += 1,
Some(Punct::RBrace | Punct::RParen | Punct::RBracket) if depth > 0 => depth -= 1,
_ => {}
}
cursor.bump();
}
}
pub fn skip_past_declaration(cursor: &mut Cursor<'_>) {
let mut depth = 0u32;
while !cursor.is_eof() {
match cursor.current().punct() {
Some(Punct::Semi) if depth == 0 => {
cursor.bump();
return;
}
Some(Punct::LBrace | Punct::LParen | Punct::LBracket) => depth += 1,
Some(Punct::RBrace | Punct::RParen | Punct::RBracket) if depth > 0 => {
depth -= 1;
if depth == 0 && cursor.at_punct(Punct::RBrace) {
cursor.bump();
cursor.eat_punct(Punct::Semi);
return;
}
}
_ => {}
}
cursor.bump();
}
}
#[cfg(test)]
mod tests {
use rucc_diag::Span;
use rucc_lex::{Token, TokenFlags, TokenKind};
use super::*;
fn stream(puncts: &[Punct]) -> Vec<Token> {
let mut tokens: Vec<Token> = puncts
.iter()
.enumerate()
.map(|(i, &punct)| Token {
kind: TokenKind::Punct(punct),
flags: TokenFlags::EMPTY,
value: 0,
span: Span::new(i as u32, i as u32 + 1),
})
.collect();
let end = puncts.len() as u32;
tokens.push(Token {
kind: TokenKind::Eof,
flags: TokenFlags::EMPTY,
value: 0,
span: Span::empty_at(end),
});
tokens
}
#[test]
fn a_statement_resumes_after_its_semicolon() {
let tokens =
stream(&[Punct::LParen, Punct::Semi, Punct::RParen, Punct::Semi, Punct::Comma]);
let mut cursor = Cursor::new(&tokens);
skip_to_statement_end(&mut cursor);
assert!(cursor.at_punct(Punct::Comma));
}
#[test]
fn a_statement_stops_at_the_brace_that_closes_its_block() {
let tokens = stream(&[Punct::Comma, Punct::RBrace, Punct::Semi]);
let mut cursor = Cursor::new(&tokens);
skip_to_statement_end(&mut cursor);
assert!(cursor.at_punct(Punct::RBrace));
}
#[test]
fn a_nested_block_does_not_end_the_statement() {
let tokens = stream(&[
Punct::Comma,
Punct::LBrace,
Punct::Semi,
Punct::RBrace,
Punct::Semi,
Punct::Comma,
]);
let mut cursor = Cursor::new(&tokens);
skip_to_statement_end(&mut cursor);
assert!(cursor.at_punct(Punct::Comma));
assert_eq!(cursor.index(), 5);
}
#[test]
fn a_declaration_resumes_after_the_body_it_turned_out_to_have() {
let tokens = stream(&[
Punct::LParen,
Punct::RParen,
Punct::LBrace,
Punct::Semi,
Punct::RBrace,
Punct::Comma,
]);
let mut cursor = Cursor::new(&tokens);
skip_past_declaration(&mut cursor);
assert!(cursor.at_punct(Punct::Comma));
}
#[test]
fn a_record_takes_the_semicolon_after_its_brace_with_it() {
let tokens =
stream(&[Punct::LBrace, Punct::Semi, Punct::RBrace, Punct::Semi, Punct::Comma]);
let mut cursor = Cursor::new(&tokens);
skip_past_declaration(&mut cursor);
assert!(cursor.at_punct(Punct::Comma));
}
#[test]
fn a_declaration_resumes_after_its_semicolon() {
let tokens = stream(&[Punct::Star, Punct::Semi, Punct::Comma]);
let mut cursor = Cursor::new(&tokens);
skip_past_declaration(&mut cursor);
assert!(cursor.at_punct(Punct::Comma));
}
#[test]
fn a_skip_always_reaches_the_end() {
let tokens = stream(&[Punct::RParen, Punct::RBracket, Punct::Comma]);
let mut cursor = Cursor::new(&tokens);
skip_to_statement_end(&mut cursor);
assert!(cursor.is_eof());
let mut cursor = Cursor::new(&tokens);
skip_past_declaration(&mut cursor);
assert!(cursor.is_eof());
}
#[test]
fn a_poisoned_node_holds_back_the_message_about_it() {
let mut ast = Ast::new();
let bad = ast.expr(Expr::Error, Span::empty_at(0));
let good = ast.expr(Expr::Bool(true), Span::new(0, 4));
let mut errors = Errors::default();
let at = Span::empty_at(0);
push_about(&mut errors, &ast, bad, Diagnostic::error("about the broken one", at));
assert!(errors.is_empty());
push_about(&mut errors, &ast, good, Diagnostic::error("about the good one", at));
assert_eq!(errors.len(), 1);
}
}