rucc_ast/expr.rs
1//! Expressions.
2//!
3//! Design: `spec/06-lexer-and-parser.md` section 6.2.
4//!
5//! Nothing here is desugared. `a[i]` is a subscript and not `*(a + i)`, `a += b` is a compound
6//! assignment and not `a = a + b`, and a cast keeps the type the source wrote rather than the
7//! conversion it turns into. That is what makes a diagnostic able to quote the program back to
8//! the person who wrote it, and it is what makes `--emit=ast` worth looking at. The rewriting
9//! happens when the IR is built, in `spec/08-ir.md`.
10
11use rucc_base::Symbol;
12
13use crate::ast::{CharId, DesignatorList, ExprList, FloatId, GenericList, IntId, StrId};
14use crate::decl::TypeNameId;
15use crate::init::InitId;
16use crate::stmt::StmtId;
17
18/// An expression in the expression arena.
19pub type ExprId = rucc_base::Idx<Expr>;
20
21/// One expression node.
22///
23/// Sixteen bytes, which is what the widest variant needs and what the whole arena therefore
24/// costs per node. Anything that would not fit is an index into a side table on
25/// [`Ast`](crate::Ast), which is why a call holds a range and not a vector.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Expr {
28 /// A parse that did not work out.
29 ///
30 /// Poisoned, per section 6.8: semantic analysis says nothing about a node that is already
31 /// the result of a diagnostic, which is the mechanism that stops one syntax error becoming
32 /// forty type errors.
33 Error,
34 /// An identifier, before anything has looked it up.
35 ///
36 /// The parser already knows whether the name is a typedef name, because it had to know to
37 /// parse the surrounding text at all, but it does not resolve it to a declaration. That is
38 /// semantic analysis, which has the scopes and the linkage rules.
39 Name(Symbol),
40 /// An integer constant, in the constant table on [`Ast`](crate::Ast).
41 Int(IntId),
42 /// A floating constant.
43 Float(FloatId),
44 /// A character constant.
45 Char(CharId),
46 /// A string literal, with the adjacent ones already joined onto it by phase 7.
47 Str(StrId),
48 /// `true` or `false`, which C23 made constants rather than macros.
49 Bool(bool),
50 /// `nullptr`.
51 Nullptr,
52 /// `base[index]`, in the order it was written, which is not always the pointer first.
53 Index {
54 /// The left operand.
55 base: ExprId,
56 /// The operand in the brackets.
57 index: ExprId,
58 },
59 /// `callee(args)`.
60 Call {
61 /// What is being called, which is an expression and not necessarily a name.
62 callee: ExprId,
63 /// The arguments, in order.
64 args: ExprList,
65 },
66 /// `base.name` or `base->name`.
67 Member {
68 /// The left operand.
69 base: ExprId,
70 /// The member name, which lives in its own namespace and is not looked up here.
71 name: Symbol,
72 /// Whether it was written with an arrow.
73 arrow: bool,
74 },
75 /// A prefix or postfix operator on one operand.
76 Unary {
77 /// Which operator.
78 op: UnaryOp,
79 /// The operand.
80 operand: ExprId,
81 },
82 /// A binary operator, including the ones that do not evaluate both sides.
83 Binary {
84 /// Which operator.
85 op: BinaryOp,
86 /// The left operand.
87 lhs: ExprId,
88 /// The right operand.
89 rhs: ExprId,
90 },
91 /// `lhs = rhs`, or a compound assignment with the operator kept as written.
92 Assign {
93 /// The operator in a compound assignment, and `None` for a plain one.
94 op: Option<BinaryOp>,
95 /// The left operand.
96 lhs: ExprId,
97 /// The right operand.
98 rhs: ExprId,
99 },
100 /// `cond ? then : otherwise`, where `then` is absent in GNU's `cond ?: otherwise`.
101 Cond {
102 /// The condition.
103 cond: ExprId,
104 /// The second operand, absent when the middle was left out.
105 then: Option<ExprId>,
106 /// The third operand.
107 otherwise: ExprId,
108 },
109 /// `lhs, rhs`.
110 ///
111 /// Its own node rather than a [`BinaryOp`], because the comma operator is a sequence point
112 /// with a discarded left side and shares nothing with arithmetic but its spelling.
113 Comma {
114 /// The operand whose value is thrown away.
115 lhs: ExprId,
116 /// The operand whose value the expression has.
117 rhs: ExprId,
118 },
119 /// `(ty)operand`.
120 Cast {
121 /// The type name in the parentheses.
122 ty: TypeNameId,
123 /// What is being converted.
124 operand: ExprId,
125 },
126 /// `(ty){ ... }`, which is an object and not a conversion.
127 CompoundLiteral {
128 /// The type name in the parentheses.
129 ty: TypeNameId,
130 /// The braced initializer.
131 init: InitId,
132 },
133 /// `sizeof operand`, written without parentheses around a type.
134 SizeofExpr(ExprId),
135 /// `sizeof (ty)`.
136 SizeofType(TypeNameId),
137 /// `alignof operand`, which is GNU's `__alignof__` since ISO C only has the type form.
138 AlignofExpr(ExprId),
139 /// `alignof (ty)`.
140 AlignofType(TypeNameId),
141 /// `_Generic(control, ...)`.
142 Generic {
143 /// The controlling expression, which is never evaluated.
144 control: ExprId,
145 /// The associations, in the order they were written, including the default one.
146 assocs: GenericList,
147 },
148 /// `({ ... })`, GNU's statement expression, whose value is its last expression statement.
149 StmtExpr(StmtId),
150 /// `&&label`, GNU's label address.
151 LabelAddr(Symbol),
152 /// `__builtin_offsetof(ty, path)`, where `path` is a member and not an expression.
153 Offsetof {
154 /// The type being measured.
155 ty: TypeNameId,
156 /// The member path, which is a designator list because `a.b[3].c` is legal here.
157 path: DesignatorList,
158 },
159 /// `__builtin_choose_expr(cond, then, otherwise)`.
160 ///
161 /// The whole reason this exists is that the branch not chosen is never type checked, so it
162 /// has to survive to semantic analysis as itself rather than as a conditional.
163 ChooseExpr {
164 /// The condition, which must be a constant expression.
165 cond: ExprId,
166 /// The branch taken when the condition is nonzero.
167 then: ExprId,
168 /// The other branch.
169 otherwise: ExprId,
170 },
171 /// `__builtin_types_compatible_p(a, b)`.
172 TypesCompatible {
173 /// The first type.
174 a: TypeNameId,
175 /// The second type.
176 b: TypeNameId,
177 },
178 /// `__builtin_va_arg(list, ty)`.
179 VaArg {
180 /// The argument list.
181 list: ExprId,
182 /// The type being fetched.
183 ty: TypeNameId,
184 },
185 /// `__builtin_va_start(list, last)`.
186 VaStart {
187 /// The argument list.
188 list: ExprId,
189 /// The last named parameter, which is a name rather than a value: nothing reads the
190 /// object, and what it is there for is to say where the named arguments stopped. C23
191 /// allows it to be left out, and so does gcc in every dialect, so it is optional here.
192 last: Option<ExprId>,
193 },
194 /// `__builtin_va_end(list)`.
195 VaEnd {
196 /// The argument list.
197 list: ExprId,
198 },
199 /// `__builtin_va_copy(dst, src)`.
200 VaCopy {
201 /// The list being written.
202 dst: ExprId,
203 /// The list being read, which is left where it was.
204 src: ExprId,
205 },
206 /// `__extension__ operand`, which turns the pedantic diagnostics off inside it.
207 Extension(ExprId),
208}
209
210/// An operator with one operand.
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub enum UnaryOp {
213 /// `+`, which is not a no-op: it promotes.
214 Plus,
215 /// `-`.
216 Minus,
217 /// `!`.
218 Not,
219 /// `~`.
220 BitNot,
221 /// `*`.
222 Deref,
223 /// `&`.
224 AddrOf,
225 /// `++x`.
226 PreInc,
227 /// `--x`.
228 PreDec,
229 /// `x++`.
230 PostInc,
231 /// `x--`.
232 PostDec,
233 /// `__real__ x`, GNU.
234 Real,
235 /// `__imag__ x`, GNU.
236 Imag,
237}
238
239impl UnaryOp {
240 /// The spelling, for the printer and for diagnostics.
241 #[must_use]
242 pub const fn spelling(self) -> &'static str {
243 match self {
244 UnaryOp::Plus => "+",
245 UnaryOp::Minus => "-",
246 UnaryOp::Not => "!",
247 UnaryOp::BitNot => "~",
248 UnaryOp::Deref => "*",
249 UnaryOp::AddrOf => "&",
250 UnaryOp::PreInc | UnaryOp::PostInc => "++",
251 UnaryOp::PreDec | UnaryOp::PostDec => "--",
252 UnaryOp::Real => "__real__",
253 UnaryOp::Imag => "__imag__",
254 }
255 }
256
257 /// Whether the operator is written after its operand.
258 #[must_use]
259 pub const fn is_postfix(self) -> bool {
260 matches!(self, UnaryOp::PostInc | UnaryOp::PostDec)
261 }
262}
263
264/// An operator with two operands.
265///
266/// The comma operator is not here; it is [`Expr::Comma`]. Assignment is not here either, and
267/// the operator part of a compound assignment reuses this enum, which is why the values that
268/// cannot appear in one ([`BinaryOp::LogAnd`] and the comparisons) are simply never built by
269/// the parser rather than being a second enum.
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum BinaryOp {
272 /// `*`.
273 Mul,
274 /// `/`.
275 Div,
276 /// `%`.
277 Rem,
278 /// `+`.
279 Add,
280 /// `-`.
281 Sub,
282 /// `<<`.
283 Shl,
284 /// `>>`.
285 Shr,
286 /// `<`.
287 Lt,
288 /// `>`.
289 Gt,
290 /// `<=`.
291 Le,
292 /// `>=`.
293 Ge,
294 /// `==`.
295 Eq,
296 /// `!=`.
297 Ne,
298 /// `&`.
299 BitAnd,
300 /// `^`.
301 BitXor,
302 /// `|`.
303 BitOr,
304 /// `&&`, which does not evaluate its right operand unless it has to.
305 LogAnd,
306 /// `||`, likewise.
307 LogOr,
308}
309
310impl BinaryOp {
311 /// The spelling, for the printer and for diagnostics.
312 #[must_use]
313 pub const fn spelling(self) -> &'static str {
314 match self {
315 BinaryOp::Mul => "*",
316 BinaryOp::Div => "/",
317 BinaryOp::Rem => "%",
318 BinaryOp::Add => "+",
319 BinaryOp::Sub => "-",
320 BinaryOp::Shl => "<<",
321 BinaryOp::Shr => ">>",
322 BinaryOp::Lt => "<",
323 BinaryOp::Gt => ">",
324 BinaryOp::Le => "<=",
325 BinaryOp::Ge => ">=",
326 BinaryOp::Eq => "==",
327 BinaryOp::Ne => "!=",
328 BinaryOp::BitAnd => "&",
329 BinaryOp::BitXor => "^",
330 BinaryOp::BitOr => "|",
331 BinaryOp::LogAnd => "&&",
332 BinaryOp::LogOr => "||",
333 }
334 }
335
336 /// Whether the operator sequences its left operand before its right, which only the two
337 /// short-circuiting ones do.
338 #[must_use]
339 pub const fn is_short_circuit(self) -> bool {
340 matches!(self, BinaryOp::LogAnd | BinaryOp::LogOr)
341 }
342
343 /// Whether the operator is one of the six relational and equality ones.
344 ///
345 /// What the six have in common is the answer rather than the operands: it is one bit, and
346 /// C makes it an `int` holding zero or one. Everything that has to treat them alike asks
347 /// this rather than spelling the six out again.
348 #[must_use]
349 pub const fn is_comparison(self) -> bool {
350 matches!(
351 self,
352 BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge | BinaryOp::Eq | BinaryOp::Ne
353 )
354 }
355}
356
357/// One arm of a `_Generic` selection.
358#[derive(Debug, Clone, Copy, PartialEq, Eq)]
359pub struct GenericAssoc {
360 /// The type this arm matches, and `None` for the `default` arm.
361 pub ty: Option<TypeNameId>,
362 /// The expression the arm gives, which is only evaluated if the arm is chosen.
363 pub value: ExprId,
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn an_expression_is_sixteen_bytes() {
372 // The arena is the biggest array in the frontend and the one every pass walks. If this
373 // fails, a variant grew and the fix is a side table, not a bigger node.
374 assert_eq!(size_of::<Expr>(), 16);
375 }
376
377 #[test]
378 fn an_expression_id_is_four_bytes_even_when_optional() {
379 assert_eq!(size_of::<ExprId>(), 4);
380 assert_eq!(size_of::<Option<ExprId>>(), 4);
381 }
382
383 #[test]
384 fn postfix_increment_is_the_only_kind_that_is_postfix() {
385 assert!(UnaryOp::PostInc.is_postfix());
386 assert!(UnaryOp::PostDec.is_postfix());
387 assert!(!UnaryOp::PreInc.is_postfix());
388 assert_eq!(UnaryOp::PostInc.spelling(), UnaryOp::PreInc.spelling());
389 }
390
391 #[test]
392 fn the_six_relational_and_equality_operators_are_the_comparisons() {
393 let all =
394 [BinaryOp::Lt, BinaryOp::Gt, BinaryOp::Le, BinaryOp::Ge, BinaryOp::Eq, BinaryOp::Ne];
395 assert!(all.iter().all(|op| op.is_comparison()));
396 assert!(!BinaryOp::Add.is_comparison());
397 assert!(!BinaryOp::LogAnd.is_comparison());
398 }
399
400 #[test]
401 fn only_the_logical_operators_short_circuit() {
402 assert!(BinaryOp::LogAnd.is_short_circuit());
403 assert!(BinaryOp::LogOr.is_short_circuit());
404 assert!(!BinaryOp::BitAnd.is_short_circuit());
405 }
406}