use rucc_ast::{
Asm, AsmId, AsmOperand, AsmOperandList, AsmQuals, AttrList, Expr, ExprId, ForInit, Stmt,
StmtId, StmtList, StrList, SymbolList,
};
use rucc_base::Symbol;
use rucc_diag::Span;
use rucc_lex::{Keyword, Punct};
use crate::parser::Parser;
use crate::recover::skip_to_statement_end;
enum Pending {
Label { name: Symbol, attrs: AttrList },
Case { lo: ExprId, hi: Option<ExprId> },
Default,
}
impl Parser<'_> {
pub(crate) fn compound_stmt(&mut self) -> StmtId {
let start = self.cursor.span();
if !self.enter() {
self.cursor.bump();
return self.poison_stmt(start);
}
self.cursor.bump();
self.scopes.push();
let items = self.block_items();
self.scopes.pop();
self.expect_punct(Punct::RBrace);
self.leave();
let span = self.span_from(start);
self.add_stmt(Stmt::Compound(items), span)
}
fn block_items(&mut self) -> StmtList {
let mut items = Vec::new();
while !self.cursor.at_punct(Punct::RBrace) && !self.cursor.is_eof() && !self.stopped() {
let before = self.cursor.index();
let item = self.block_item();
items.push(item);
if self.cursor.index() == before {
self.cursor.bump();
}
}
self.ast.add_stmt_list(&items)
}
pub(crate) fn block_item(&mut self) -> StmtId {
let start = self.cursor.span();
let attrs = self.leading_attributes();
self.block_item_with(attrs, start)
}
fn block_item_with(&mut self, attrs: AttrList, start: Span) -> StmtId {
if self.at_any_label() {
return self.labeled(attrs, start);
}
let is_decl =
self.starts_declaration() || (!attrs.is_empty() && self.cursor.at_punct(Punct::Semi));
if is_decl {
let decl = self.declaration(attrs, start);
let span = self.ast.decl_span(decl);
return self.add_stmt(Stmt::Decl(decl), span);
}
if !attrs.is_empty() {
let span = self.span_from(start);
self.warn("E0411", "attributes on this statement are ignored", span);
}
self.statement()
}
pub(crate) fn leading_attributes(&mut self) -> AttrList {
if self.at_attribute() { self.attributes() } else { AttrList::EMPTY }
}
pub(crate) fn starts_declaration(&self) -> bool {
self.at_attribute() || self.cursor.at_keyword(Keyword::StaticAssert) || self.at_decl_specs()
}
pub(crate) fn statement(&mut self) -> StmtId {
let start = self.cursor.span();
if let Some(punct) = self.cursor.current().punct() {
match punct {
Punct::LBrace => return self.compound_stmt(),
Punct::Semi => {
self.cursor.bump();
return self.add_stmt(Stmt::Empty, start);
}
_ => {}
}
}
if let Some(word) = self.cursor.current().keyword() {
if let Some(stmt) = self.keyword_stmt(word, start) {
return stmt;
}
}
if self.at_any_label() {
return self.labeled(AttrList::EMPTY, start);
}
self.expr_stmt(start)
}
fn keyword_stmt(&mut self, word: Keyword, start: Span) -> Option<StmtId> {
let stmt = match word {
Keyword::If => self.if_stmt(start),
Keyword::Switch => self.switch_stmt(start),
Keyword::While => self.while_stmt(start),
Keyword::Do => self.do_stmt(start),
Keyword::For => self.for_stmt(start),
Keyword::Goto => self.goto_stmt(start),
Keyword::Continue | Keyword::Break => self.jump_stmt(word, start),
Keyword::Return => self.return_stmt(start),
Keyword::Case | Keyword::Default => self.labeled(AttrList::EMPTY, start),
Keyword::Asm => self.asm_stmt(start),
Keyword::Label => self.local_labels(start),
_ => return None,
};
Some(stmt)
}
fn if_stmt(&mut self, start: Span) -> StmtId {
self.cursor.bump();
let cond = self.controlling_expr();
let then = self.statement();
let otherwise =
if self.cursor.eat_keyword(Keyword::Else) { Some(self.statement()) } else { None };
let span = self.span_from(start);
self.add_stmt(Stmt::If { cond, then, otherwise }, span)
}
fn switch_stmt(&mut self, start: Span) -> StmtId {
self.cursor.bump();
let scrutinee = self.controlling_expr();
let body = self.statement();
let span = self.span_from(start);
self.add_stmt(Stmt::Switch { scrutinee, body }, span)
}
fn while_stmt(&mut self, start: Span) -> StmtId {
self.cursor.bump();
let cond = self.controlling_expr();
let body = self.statement();
let span = self.span_from(start);
self.add_stmt(Stmt::While { cond, body }, span)
}
fn do_stmt(&mut self, start: Span) -> StmtId {
self.cursor.bump();
let body = self.statement();
self.expect_keyword(Keyword::While);
let cond = self.controlling_expr();
self.expect_punct(Punct::Semi);
let span = self.span_from(start);
self.add_stmt(Stmt::DoWhile { body, cond }, span)
}
fn for_stmt(&mut self, start: Span) -> StmtId {
self.cursor.bump();
if !self.enter() {
self.cursor.bump();
return self.poison_stmt(start);
}
self.scopes.push();
self.expect_punct(Punct::LParen);
let init = self.for_init();
let cond = if self.cursor.at_punct(Punct::Semi) { None } else { Some(self.expr()) };
self.expect_punct(Punct::Semi);
let step = if self.cursor.at_punct(Punct::RParen) { None } else { Some(self.expr()) };
self.expect_punct(Punct::RParen);
let body = self.statement();
self.scopes.pop();
self.leave();
let span = self.span_from(start);
self.add_stmt(Stmt::For { init, cond, step, body }, span)
}
fn for_init(&mut self) -> ForInit {
if self.cursor.eat_punct(Punct::Semi) {
return ForInit::None;
}
if self.starts_declaration() {
let start = self.cursor.span();
let attrs = self.leading_attributes();
return ForInit::Decl(self.declaration(attrs, start));
}
let value = self.expr();
self.expect_punct(Punct::Semi);
ForInit::Expr(value)
}
fn goto_stmt(&mut self, start: Span) -> StmtId {
self.cursor.bump();
let stmt = if self.cursor.eat_punct(Punct::Star) {
Stmt::GotoExpr(self.expr())
} else {
match self.expect_ident() {
Some((name, _)) => Stmt::Goto(name),
None => Stmt::Error,
}
};
self.expect_punct(Punct::Semi);
let span = self.span_from(start);
self.add_stmt(stmt, span)
}
fn jump_stmt(&mut self, word: Keyword, start: Span) -> StmtId {
self.cursor.bump();
self.expect_punct(Punct::Semi);
let stmt = if word == Keyword::Continue { Stmt::Continue } else { Stmt::Break };
let span = self.span_from(start);
self.add_stmt(stmt, span)
}
fn return_stmt(&mut self, start: Span) -> StmtId {
self.cursor.bump();
let value = if self.cursor.at_punct(Punct::Semi) { None } else { Some(self.expr()) };
self.expect_punct(Punct::Semi);
let span = self.span_from(start);
self.add_stmt(Stmt::Return(value), span)
}
fn local_labels(&mut self, start: Span) -> StmtId {
self.cursor.bump();
let mut names = Vec::new();
while let Some((name, _)) = self.expect_ident() {
names.push(name);
if !self.cursor.eat_punct(Punct::Comma) {
break;
}
}
self.expect_punct(Punct::Semi);
let names = self.ast.add_symbol_list(&names);
let span = self.span_from(start);
self.add_stmt(Stmt::LocalLabels(names), span)
}
fn expr_stmt(&mut self, start: Span) -> StmtId {
let value = self.expr();
let broken = matches!(self.ast[value], Expr::Error);
if broken || !self.expect_punct(Punct::Semi) {
skip_to_statement_end(&mut self.cursor);
}
let span = self.span_from(start);
self.add_stmt(Stmt::Expr(value), span)
}
fn at_any_label(&self) -> bool {
let token = self.cursor.current();
if matches!(token.keyword(), Some(Keyword::Case | Keyword::Default)) {
return true;
}
token.ident().is_some() && self.cursor.peek(1).punct() == Some(Punct::Colon)
}
fn labeled(&mut self, attrs: AttrList, start: Span) -> StmtId {
let mut labels = Vec::new();
let mut attrs = attrs;
let mut at = start;
loop {
let label = if self.cursor.eat_keyword(Keyword::Case) {
let lo = self.const_expr();
let hi = if self.cursor.eat_punct(Punct::Ellipsis) {
Some(self.const_expr())
} else {
None
};
Pending::Case { lo, hi }
} else if self.cursor.eat_keyword(Keyword::Default) {
Pending::Default
} else {
Pending::Label { name: Symbol::from_raw(self.cursor.bump().value), attrs }
};
self.expect_punct(Punct::Colon);
labels.push((label, at));
at = self.cursor.span();
attrs = self.leading_attributes();
if !self.at_any_label() {
break;
}
}
let body = self.labeled_body(attrs, at);
let end = self.cursor.prev_end();
let mut inner = body;
for (label, at) in labels.into_iter().rev() {
let stmt = match label {
Pending::Label { name, attrs } => Stmt::Label { name, body: inner, attrs },
Pending::Case { lo, hi } => Stmt::Case { lo, hi, body: inner },
Pending::Default => Stmt::Default { body: inner },
};
inner = Some(self.add_stmt(stmt, at.to(end)));
}
match inner {
Some(stmt) => stmt,
None => self.poison_stmt(start),
}
}
fn labeled_body(&mut self, attrs: AttrList, at: Span) -> Option<StmtId> {
if self.cursor.at_punct(Punct::RBrace) || self.cursor.is_eof() {
return None;
}
Some(self.block_item_with(attrs, at))
}
fn controlling_expr(&mut self) -> ExprId {
let at = self.cursor.span();
if !self.enter() {
self.cursor.bump();
return self.poison_expr(at);
}
self.expect_punct(Punct::LParen);
let cond = self.expr();
self.expect_punct(Punct::RParen);
self.leave();
cond
}
fn asm_stmt(&mut self, start: Span) -> StmtId {
let asm = self.asm_body(start);
self.expect_punct(Punct::Semi);
let span = self.span_from(start);
match asm {
Some(asm) => self.add_stmt(Stmt::Asm(asm), span),
None => self.poison_stmt(span),
}
}
pub(crate) fn asm_body(&mut self, start: Span) -> Option<AsmId> {
self.cursor.bump();
let mut quals = AsmQuals::NONE;
loop {
let qual = match self.cursor.current().keyword() {
Some(Keyword::Volatile) => AsmQuals::VOLATILE,
Some(Keyword::Inline) => AsmQuals::INLINE,
Some(Keyword::Goto) => AsmQuals::GOTO,
_ => break,
};
self.cursor.bump();
quals = quals.with(qual);
}
if !self.enter() {
self.cursor.bump();
return None;
}
self.expect_punct(Punct::LParen);
let template = self.string_literal();
let mut outputs = AsmOperandList::EMPTY;
let mut inputs = AsmOperandList::EMPTY;
let mut clobbers = StrList::EMPTY;
let mut labels = SymbolList::EMPTY;
let mut half = false;
if self.asm_colon(&mut half) && !half {
outputs = self.asm_operands();
}
if self.asm_colon(&mut half) && !half {
inputs = self.asm_operands();
}
if self.asm_colon(&mut half) && !half {
clobbers = self.asm_clobbers();
}
if self.asm_colon(&mut half) && !half {
labels = self.asm_labels();
}
self.expect_punct(Punct::RParen);
self.leave();
let span = self.span_from(start);
let template = template?;
Some(self.ast.add_asm(Asm { template, outputs, inputs, clobbers, labels, quals, span }))
}
fn asm_colon(&mut self, half: &mut bool) -> bool {
if *half {
*half = false;
return true;
}
if self.cursor.eat_punct(Punct::Colon) {
return true;
}
if self.cursor.eat_punct(Punct::ColonColon) {
*half = true;
return true;
}
false
}
fn at_asm_section_end(&self) -> bool {
self.cursor.is_eof()
|| matches!(
self.cursor.current().punct(),
Some(Punct::Colon | Punct::ColonColon | Punct::RParen)
)
}
fn asm_operands(&mut self) -> AsmOperandList {
let mut out = Vec::new();
while !self.at_asm_section_end() {
let before = self.cursor.index();
let at = self.cursor.span();
let name = if self.cursor.eat_punct(Punct::LBracket) {
let name = self.expect_ident().map(|(name, _)| name);
self.expect_punct(Punct::RBracket);
name
} else {
None
};
let Some(constraint) = self.string_literal() else { break };
let value = if self.enter() {
self.expect_punct(Punct::LParen);
let value = self.expr();
self.expect_punct(Punct::RParen);
self.leave();
value
} else {
self.cursor.bump();
self.poison_expr(at)
};
let span = self.span_from(at);
out.push(AsmOperand { name, constraint, value, span });
if !self.cursor.eat_punct(Punct::Comma) {
break;
}
if self.cursor.index() == before {
break;
}
}
self.ast.add_asm_operand_list(&out)
}
fn asm_clobbers(&mut self) -> StrList {
let mut out = Vec::new();
while !self.at_asm_section_end() {
match self.string_literal() {
Some(clobber) => out.push(clobber),
None => break,
}
if !self.cursor.eat_punct(Punct::Comma) {
break;
}
}
self.ast.add_str_list(&out)
}
fn asm_labels(&mut self) -> SymbolList {
let mut out = Vec::new();
while !self.at_asm_section_end() {
match self.expect_ident() {
Some((name, _)) => out.push(name),
None => break,
}
if !self.cursor.eat_punct(Punct::Comma) {
break;
}
}
self.ast.add_symbol_list(&out)
}
}