Skip to main content

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    /// `__extension__ operand`, which turns the pedantic diagnostics off inside it.
186    Extension(ExprId),
187}
188
189/// An operator with one operand.
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum UnaryOp {
192    /// `+`, which is not a no-op: it promotes.
193    Plus,
194    /// `-`.
195    Minus,
196    /// `!`.
197    Not,
198    /// `~`.
199    BitNot,
200    /// `*`.
201    Deref,
202    /// `&`.
203    AddrOf,
204    /// `++x`.
205    PreInc,
206    /// `--x`.
207    PreDec,
208    /// `x++`.
209    PostInc,
210    /// `x--`.
211    PostDec,
212    /// `__real__ x`, GNU.
213    Real,
214    /// `__imag__ x`, GNU.
215    Imag,
216}
217
218impl UnaryOp {
219    /// The spelling, for the printer and for diagnostics.
220    #[must_use]
221    pub const fn spelling(self) -> &'static str {
222        match self {
223            UnaryOp::Plus => "+",
224            UnaryOp::Minus => "-",
225            UnaryOp::Not => "!",
226            UnaryOp::BitNot => "~",
227            UnaryOp::Deref => "*",
228            UnaryOp::AddrOf => "&",
229            UnaryOp::PreInc | UnaryOp::PostInc => "++",
230            UnaryOp::PreDec | UnaryOp::PostDec => "--",
231            UnaryOp::Real => "__real__",
232            UnaryOp::Imag => "__imag__",
233        }
234    }
235
236    /// Whether the operator is written after its operand.
237    #[must_use]
238    pub const fn is_postfix(self) -> bool {
239        matches!(self, UnaryOp::PostInc | UnaryOp::PostDec)
240    }
241}
242
243/// An operator with two operands.
244///
245/// The comma operator is not here; it is [`Expr::Comma`]. Assignment is not here either, and
246/// the operator part of a compound assignment reuses this enum, which is why the values that
247/// cannot appear in one ([`BinaryOp::LogAnd`] and the comparisons) are simply never built by
248/// the parser rather than being a second enum.
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub enum BinaryOp {
251    /// `*`.
252    Mul,
253    /// `/`.
254    Div,
255    /// `%`.
256    Rem,
257    /// `+`.
258    Add,
259    /// `-`.
260    Sub,
261    /// `<<`.
262    Shl,
263    /// `>>`.
264    Shr,
265    /// `<`.
266    Lt,
267    /// `>`.
268    Gt,
269    /// `<=`.
270    Le,
271    /// `>=`.
272    Ge,
273    /// `==`.
274    Eq,
275    /// `!=`.
276    Ne,
277    /// `&`.
278    BitAnd,
279    /// `^`.
280    BitXor,
281    /// `|`.
282    BitOr,
283    /// `&&`, which does not evaluate its right operand unless it has to.
284    LogAnd,
285    /// `||`, likewise.
286    LogOr,
287}
288
289impl BinaryOp {
290    /// The spelling, for the printer and for diagnostics.
291    #[must_use]
292    pub const fn spelling(self) -> &'static str {
293        match self {
294            BinaryOp::Mul => "*",
295            BinaryOp::Div => "/",
296            BinaryOp::Rem => "%",
297            BinaryOp::Add => "+",
298            BinaryOp::Sub => "-",
299            BinaryOp::Shl => "<<",
300            BinaryOp::Shr => ">>",
301            BinaryOp::Lt => "<",
302            BinaryOp::Gt => ">",
303            BinaryOp::Le => "<=",
304            BinaryOp::Ge => ">=",
305            BinaryOp::Eq => "==",
306            BinaryOp::Ne => "!=",
307            BinaryOp::BitAnd => "&",
308            BinaryOp::BitXor => "^",
309            BinaryOp::BitOr => "|",
310            BinaryOp::LogAnd => "&&",
311            BinaryOp::LogOr => "||",
312        }
313    }
314
315    /// Whether the operator sequences its left operand before its right, which only the two
316    /// short-circuiting ones do.
317    #[must_use]
318    pub const fn is_short_circuit(self) -> bool {
319        matches!(self, BinaryOp::LogAnd | BinaryOp::LogOr)
320    }
321}
322
323/// One arm of a `_Generic` selection.
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub struct GenericAssoc {
326    /// The type this arm matches, and `None` for the `default` arm.
327    pub ty: Option<TypeNameId>,
328    /// The expression the arm gives, which is only evaluated if the arm is chosen.
329    pub value: ExprId,
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335
336    #[test]
337    fn an_expression_is_sixteen_bytes() {
338        // The arena is the biggest array in the frontend and the one every pass walks. If this
339        // fails, a variant grew and the fix is a side table, not a bigger node.
340        assert_eq!(size_of::<Expr>(), 16);
341    }
342
343    #[test]
344    fn an_expression_id_is_four_bytes_even_when_optional() {
345        assert_eq!(size_of::<ExprId>(), 4);
346        assert_eq!(size_of::<Option<ExprId>>(), 4);
347    }
348
349    #[test]
350    fn postfix_increment_is_the_only_kind_that_is_postfix() {
351        assert!(UnaryOp::PostInc.is_postfix());
352        assert!(UnaryOp::PostDec.is_postfix());
353        assert!(!UnaryOp::PreInc.is_postfix());
354        assert_eq!(UnaryOp::PostInc.spelling(), UnaryOp::PreInc.spelling());
355    }
356
357    #[test]
358    fn only_the_logical_operators_short_circuit() {
359        assert!(BinaryOp::LogAnd.is_short_circuit());
360        assert!(BinaryOp::LogOr.is_short_circuit());
361        assert!(!BinaryOp::BitAnd.is_short_circuit());
362    }
363}