rucc_ast/stmt.rs
1//! Statements.
2//!
3//! Design: `spec/06-lexer-and-parser.md` section 6.2.
4//!
5//! Like the expressions, these are not desugared. A `for` loop stays a `for` loop instead of
6//! becoming a `while`, a `switch` keeps its cases as a tree instead of a jump table, and a
7//! `do` loop is not a `while` with the test moved. All of that happens when the IR is built.
8//!
9//! A declaration inside a block is [`Stmt::Decl`], holding the whole declaration and so all of
10//! its declarators, which keeps `int a, b;` one statement instead of two and keeps the shape
11//! the same at block scope as at file scope.
12
13use rucc_base::Symbol;
14
15use crate::asm::AsmId;
16use crate::ast::{AttrList, StmtList, SymbolList};
17use crate::decl::DeclId;
18use crate::expr::ExprId;
19
20/// A statement in the statement arena.
21pub type StmtId = rucc_base::Idx<Stmt>;
22
23/// One statement node.
24///
25/// Twenty bytes, set by [`Stmt::For`], which is the only variant with four operands and which
26/// gets away with it because [`ForInit`] has spare tag values for the statement's own tag to
27/// live in. Splitting the loop's clauses into a side table would buy four bytes a statement and
28/// cost an indirection on the most common loop in C, so they stay inline.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Stmt {
31 /// A parse that did not work out. Poisoned, as [`Expr::Error`](crate::Expr::Error) is.
32 Error,
33 /// `;`.
34 Empty,
35 /// An expression evaluated for its effect.
36 Expr(ExprId),
37 /// A declaration at block scope, with all of its declarators.
38 Decl(DeclId),
39 /// `{ ... }`, which is also a scope.
40 Compound(StmtList),
41 /// `if (cond) then else otherwise`.
42 If {
43 /// The controlling expression.
44 cond: ExprId,
45 /// The statement taken when it is nonzero.
46 then: StmtId,
47 /// The `else` branch, if there was one.
48 otherwise: Option<StmtId>,
49 },
50 /// `switch (scrutinee) body`.
51 Switch {
52 /// The controlling expression.
53 scrutinee: ExprId,
54 /// The body, whose cases are found by walking it.
55 body: StmtId,
56 },
57 /// `while (cond) body`.
58 While {
59 /// The controlling expression, tested before each iteration.
60 cond: ExprId,
61 /// The body.
62 body: StmtId,
63 },
64 /// `do body while (cond);`.
65 DoWhile {
66 /// The body, which runs at least once.
67 body: StmtId,
68 /// The controlling expression, tested after each iteration.
69 cond: ExprId,
70 },
71 /// `for (init; cond; step) body`.
72 For {
73 /// The first clause, which may declare something and so may open a scope.
74 init: ForInit,
75 /// The controlling expression, absent when the clause was left empty, which means the
76 /// loop runs forever.
77 cond: Option<ExprId>,
78 /// The third clause, evaluated after each iteration.
79 step: Option<ExprId>,
80 /// The body.
81 body: StmtId,
82 },
83 /// `goto name;`.
84 Goto(Symbol),
85 /// `goto *expr;`, GNU's computed goto, which Postgres and the kernel both use.
86 GotoExpr(ExprId),
87 /// `continue;`.
88 Continue,
89 /// `break;`.
90 Break,
91 /// `return expr;`, or `return;`.
92 Return(Option<ExprId>),
93 /// `name: body`.
94 Label {
95 /// The label, which lives in a namespace of its own and is scoped to the function.
96 name: Symbol,
97 /// The statement it labels, absent when the label is the last thing in a block, which
98 /// C23 allows and which everybody wrote as `name: ;` before it.
99 body: Option<StmtId>,
100 /// Attributes on the label, such as `[[gnu::hot]]`.
101 attrs: AttrList,
102 },
103 /// `case lo: body`, or GNU's `case lo ... hi: body`.
104 Case {
105 /// The value, or the first value of a range.
106 lo: ExprId,
107 /// The last value of a GNU case range, which is included.
108 hi: Option<ExprId>,
109 /// The statement it labels, absent for the same reason as on a label.
110 body: Option<StmtId>,
111 },
112 /// `default: body`.
113 Default {
114 /// The statement it labels.
115 body: Option<StmtId>,
116 },
117 /// `__label__ a, b;`, GNU's block-local labels, which a macro needs so that two expansions
118 /// in one function do not collide.
119 LocalLabels(SymbolList),
120 /// An `asm` statement.
121 Asm(AsmId),
122}
123
124/// The first clause of a `for` statement.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum ForInit {
127 /// Nothing was written.
128 None,
129 /// An expression.
130 Expr(ExprId),
131 /// A declaration, which C99 allowed and which scopes to the loop.
132 Decl(DeclId),
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn a_statement_is_twenty_bytes() {
141 assert_eq!(size_of::<Stmt>(), 20);
142 }
143
144 #[test]
145 fn a_statement_id_is_four_bytes_even_when_optional() {
146 assert_eq!(size_of::<StmtId>(), 4);
147 assert_eq!(size_of::<Option<StmtId>>(), 4);
148 }
149
150 #[test]
151 fn a_for_clause_is_eight_bytes() {
152 assert_eq!(size_of::<ForInit>(), 8);
153 }
154}