1use rucc_ast::{Ast, Decl, DeclId, Expr, ExprId, Stmt, StmtId};
33use rucc_diag::{Diagnostic, Errors};
34use rucc_lex::Punct;
35
36use crate::cursor::Cursor;
37
38pub fn push_about<P: Poison>(errors: &mut Errors, ast: &Ast, about: P, diagnostic: Diagnostic) {
44 errors.push_unless(about.is_poisoned(ast), diagnostic);
45}
46
47pub trait Poison: Copy {
52 fn is_poisoned(self, ast: &Ast) -> bool;
54}
55
56impl Poison for ExprId {
57 #[inline]
58 fn is_poisoned(self, ast: &Ast) -> bool {
59 matches!(ast[self], Expr::Error)
60 }
61}
62
63impl Poison for StmtId {
64 #[inline]
65 fn is_poisoned(self, ast: &Ast) -> bool {
66 matches!(ast[self], Stmt::Error)
67 }
68}
69
70impl Poison for DeclId {
71 #[inline]
72 fn is_poisoned(self, ast: &Ast) -> bool {
73 matches!(ast[self], Decl::Error)
74 }
75}
76
77pub fn skip_to_statement_end(cursor: &mut Cursor<'_>) {
84 let mut depth = 0u32;
85 while !cursor.is_eof() {
86 match cursor.current().punct() {
87 Some(Punct::Semi) if depth == 0 => {
88 cursor.bump();
89 return;
90 }
91 Some(Punct::RBrace) if depth == 0 => return,
92 Some(Punct::LBrace | Punct::LParen | Punct::LBracket) => depth += 1,
93 Some(Punct::RBrace | Punct::RParen | Punct::RBracket) if depth > 0 => depth -= 1,
94 _ => {}
95 }
96 cursor.bump();
97 }
98}
99
100pub fn skip_past_declaration(cursor: &mut Cursor<'_>) {
107 let mut depth = 0u32;
108 while !cursor.is_eof() {
109 match cursor.current().punct() {
110 Some(Punct::Semi) if depth == 0 => {
111 cursor.bump();
112 return;
113 }
114 Some(Punct::LBrace | Punct::LParen | Punct::LBracket) => depth += 1,
115 Some(Punct::RBrace | Punct::RParen | Punct::RBracket) if depth > 0 => {
116 depth -= 1;
117 if depth == 0 && cursor.at_punct(Punct::RBrace) {
118 cursor.bump();
119 cursor.eat_punct(Punct::Semi);
123 return;
124 }
125 }
126 _ => {}
127 }
128 cursor.bump();
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use rucc_diag::Span;
135 use rucc_lex::{Token, TokenFlags, TokenKind};
136
137 use super::*;
138
139 fn stream(puncts: &[Punct]) -> Vec<Token> {
140 let mut tokens: Vec<Token> = puncts
141 .iter()
142 .enumerate()
143 .map(|(i, &punct)| Token {
144 kind: TokenKind::Punct(punct),
145 flags: TokenFlags::EMPTY,
146 value: 0,
147 span: Span::new(i as u32, i as u32 + 1),
148 })
149 .collect();
150 let end = puncts.len() as u32;
151 tokens.push(Token {
152 kind: TokenKind::Eof,
153 flags: TokenFlags::EMPTY,
154 value: 0,
155 span: Span::empty_at(end),
156 });
157 tokens
158 }
159
160 #[test]
161 fn a_statement_resumes_after_its_semicolon() {
162 let tokens =
164 stream(&[Punct::LParen, Punct::Semi, Punct::RParen, Punct::Semi, Punct::Comma]);
165 let mut cursor = Cursor::new(&tokens);
166 skip_to_statement_end(&mut cursor);
167 assert!(cursor.at_punct(Punct::Comma));
168 }
169
170 #[test]
171 fn a_statement_stops_at_the_brace_that_closes_its_block() {
172 let tokens = stream(&[Punct::Comma, Punct::RBrace, Punct::Semi]);
173 let mut cursor = Cursor::new(&tokens);
174 skip_to_statement_end(&mut cursor);
175 assert!(cursor.at_punct(Punct::RBrace));
176 }
177
178 #[test]
179 fn a_nested_block_does_not_end_the_statement() {
180 let tokens = stream(&[
182 Punct::Comma,
183 Punct::LBrace,
184 Punct::Semi,
185 Punct::RBrace,
186 Punct::Semi,
187 Punct::Comma,
188 ]);
189 let mut cursor = Cursor::new(&tokens);
190 skip_to_statement_end(&mut cursor);
191 assert!(cursor.at_punct(Punct::Comma));
192 assert_eq!(cursor.index(), 5);
193 }
194
195 #[test]
196 fn a_declaration_resumes_after_the_body_it_turned_out_to_have() {
197 let tokens = stream(&[
199 Punct::LParen,
200 Punct::RParen,
201 Punct::LBrace,
202 Punct::Semi,
203 Punct::RBrace,
204 Punct::Comma,
205 ]);
206 let mut cursor = Cursor::new(&tokens);
207 skip_past_declaration(&mut cursor);
208 assert!(cursor.at_punct(Punct::Comma));
209 }
210
211 #[test]
212 fn a_record_takes_the_semicolon_after_its_brace_with_it() {
213 let tokens =
215 stream(&[Punct::LBrace, Punct::Semi, Punct::RBrace, Punct::Semi, Punct::Comma]);
216 let mut cursor = Cursor::new(&tokens);
217 skip_past_declaration(&mut cursor);
218 assert!(cursor.at_punct(Punct::Comma));
219 }
220
221 #[test]
222 fn a_declaration_resumes_after_its_semicolon() {
223 let tokens = stream(&[Punct::Star, Punct::Semi, Punct::Comma]);
224 let mut cursor = Cursor::new(&tokens);
225 skip_past_declaration(&mut cursor);
226 assert!(cursor.at_punct(Punct::Comma));
227 }
228
229 #[test]
230 fn a_skip_always_reaches_the_end() {
231 let tokens = stream(&[Punct::RParen, Punct::RBracket, Punct::Comma]);
234 let mut cursor = Cursor::new(&tokens);
235 skip_to_statement_end(&mut cursor);
236 assert!(cursor.is_eof());
237 let mut cursor = Cursor::new(&tokens);
238 skip_past_declaration(&mut cursor);
239 assert!(cursor.is_eof());
240 }
241
242 #[test]
243 fn a_poisoned_node_holds_back_the_message_about_it() {
244 let mut ast = Ast::new();
245 let bad = ast.expr(Expr::Error, Span::empty_at(0));
246 let good = ast.expr(Expr::Bool(true), Span::new(0, 4));
247 let mut errors = Errors::default();
248 let at = Span::empty_at(0);
249 push_about(&mut errors, &ast, bad, Diagnostic::error("about the broken one", at));
250 assert!(errors.is_empty());
251 push_about(&mut errors, &ast, good, Diagnostic::error("about the good one", at));
252 assert_eq!(errors.len(), 1);
253 }
254}