zerodds_lang_cst/c/mod.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The C concrete syntax tree. Spec §1.
4//!
5//! C has no namespaces and no templates, and says with a `typedef` what
6//! C++ says with a class. The tree says the same: it is not the C++ tree
7//! with parts switched off, because a tree that pretended otherwise
8//! would push the difference into the renderer.
9
10use crate::common::{BodyText, Doc, Ident, Literal};
11use alloc::boxed::Box;
12use alloc::string::{String, ToString};
13use alloc::vec::Vec;
14
15/// A C header or source file.
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
17pub struct File {
18 /// The leading comment.
19 pub doc: Doc,
20 /// The include guard, when the file has one.
21 pub guard: Option<Ident>,
22 /// `#include` lines, in written order.
23 pub includes: Vec<Include>,
24 /// Declarations, in written order.
25 pub decls: Vec<Decl>,
26}
27
28/// One `#include`.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Include {
31 /// The path between the delimiters.
32 pub path: String,
33 /// Whether it is `<system>` rather than `"local"`.
34 pub system: bool,
35}
36
37impl Include {
38 /// `#include <path>`
39 #[must_use]
40 pub fn system(path: &str) -> Self {
41 Self {
42 path: path.to_string(),
43 system: true,
44 }
45 }
46
47 /// `#include "path"`
48 #[must_use]
49 pub fn local(path: &str) -> Self {
50 Self {
51 path: path.to_string(),
52 system: false,
53 }
54 }
55}
56
57/// One declaration.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum Decl {
60 /// `struct Name { ... };`
61 Struct(Struct),
62 /// `union Name { ... };`
63 Union(Union),
64 /// `enum Name { ... };`
65 Enum(Enum),
66 /// `typedef ... Name;`
67 Typedef(Typedef),
68 /// A function declaration or definition.
69 Function(Function),
70 /// A variable at file scope.
71 Variable(Variable),
72 /// `#define NAME value`
73 Define(Define),
74 /// A comment standing on its own.
75 Comment(Doc),
76 /// `@verbatim` text, copied into the output unchanged.
77 ///
78 /// OMG IDL 4.2 §8.3.5.1: the annotation's `text` "shall be copied
79 /// verbatim" into the generated artifact — raw lines, never a
80 /// comment. One node per directive, lines split on `\n`.
81 Verbatim(String),
82}
83
84/// `struct Name { ... };`
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct Struct {
87 /// Documentation.
88 pub doc: Doc,
89 /// The tag, absent for an anonymous struct inside a typedef.
90 pub name: Option<Ident>,
91 /// Members, in written order.
92 pub members: Vec<Member>,
93 /// `@verbatim` text at the start and the end of the member list.
94 pub body_text: BodyText,
95}
96
97/// `union Name { ... };`
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct Union {
100 /// Documentation.
101 pub doc: Doc,
102 /// The tag.
103 pub name: Option<Ident>,
104 /// Members, in written order.
105 pub members: Vec<Member>,
106}
107
108/// One member of a struct or union.
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct Member {
111 /// Documentation.
112 pub doc: Doc,
113 /// Type.
114 pub type_: Type,
115 /// Name.
116 pub name: Ident,
117 /// Array dimensions, outermost first — `char name[4][8]`.
118 pub dims: Vec<Expr>,
119 /// A bitfield width, when written.
120 pub bits: Option<u16>,
121}
122
123/// `enum Name { ... };`
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct Enum {
126 /// Documentation.
127 pub doc: Doc,
128 /// The tag.
129 pub name: Option<Ident>,
130 /// Enumerators, in written order.
131 pub enumerators: Vec<Enumerator>,
132}
133
134/// One enumerator.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct Enumerator {
137 /// Documentation.
138 pub doc: Doc,
139 /// Name.
140 pub name: Ident,
141 /// `= value`, when written.
142 pub value: Option<Expr>,
143}
144
145/// `typedef <type> <name>;`
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct Typedef {
148 /// Documentation.
149 pub doc: Doc,
150 /// What is aliased.
151 pub type_: Type,
152 /// The new name.
153 pub name: Ident,
154 /// Array dimensions, for `typedef long Row[4];`.
155 pub dims: Vec<Expr>,
156}
157
158/// A function.
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct Function {
161 /// Documentation.
162 pub doc: Doc,
163 /// `static`.
164 pub is_static: bool,
165 /// `inline`.
166 pub is_inline: bool,
167 /// `__attribute__((unused))`, for a `static` function whose having a
168 /// caller depends on a setting rather than on the model.
169 ///
170 /// Generated codecs are compiled under `-Werror=unused-function`,
171 /// and that is worth keeping: a codec nothing calls is normally a
172 /// generator that emitted the wrong thing. The exception is a
173 /// primitive whose codec is reached from one position and inlined at
174 /// another — a `Char16` under a framing that gives a member and a
175 /// collection element different octets — where whether the shared
176 /// function has a caller is a property of the dialect and not of the
177 /// type graph. Set there and nowhere else.
178 pub is_maybe_unused: bool,
179 /// Return type.
180 pub returns: Type,
181 /// Name.
182 pub name: Ident,
183 /// Parameters, in order.
184 pub params: Vec<Param>,
185 /// The body, absent for a declaration.
186 pub body: Option<Block>,
187}
188
189/// One parameter.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct Param {
192 /// Type.
193 pub type_: Type,
194 /// Name, absent in a prototype that gives only types.
195 pub name: Option<Ident>,
196}
197
198/// A variable at file scope.
199#[derive(Debug, Clone, PartialEq, Eq)]
200pub struct Variable {
201 /// Documentation.
202 pub doc: Doc,
203 /// `static`.
204 pub is_static: bool,
205 /// `const`.
206 pub is_const: bool,
207 /// Type.
208 pub type_: Type,
209 /// Name.
210 pub name: Ident,
211 /// Initialiser.
212 pub value: Option<Expr>,
213}
214
215/// `#define NAME value`
216#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct Define {
218 /// Documentation.
219 pub doc: Doc,
220 /// Name.
221 pub name: Ident,
222 /// The replacement, absent for a bare `#define NAME`.
223 pub value: Option<Expr>,
224}
225
226/// A C type as it will be written.
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub enum Type {
229 /// A built-in.
230 Primitive(Primitive),
231 /// A name: a typedef, or a tag used as a type in C99 style.
232 Named(Ident),
233 /// `struct Tag`
234 StructTag(Ident),
235 /// `union Tag`
236 UnionTag(Ident),
237 /// `enum Tag`
238 EnumTag(Ident),
239 /// `T *`
240 Pointer(Box<Type>),
241 /// `const T`
242 Const(Box<Type>),
243 /// `void`
244 Void,
245}
246
247/// A C built-in type.
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
249pub enum Primitive {
250 /// `char`
251 Char,
252 /// `bool`, from `<stdbool.h>`.
253 Bool,
254 /// `int8_t`
255 Int8,
256 /// `int16_t`
257 Int16,
258 /// `int32_t`
259 Int32,
260 /// `int64_t`
261 Int64,
262 /// `uint8_t`
263 UInt8,
264 /// `uint16_t`
265 UInt16,
266 /// `uint32_t`
267 UInt32,
268 /// `uint64_t`
269 UInt64,
270 /// `float`
271 Float,
272 /// `double`
273 Double,
274 /// `long double`
275 LongDouble,
276 /// `size_t`
277 Size,
278}
279
280impl Primitive {
281 /// Every built-in the tree can hold.
282 pub const ALL: [Self; 14] = [
283 Self::Char,
284 Self::Bool,
285 Self::Int8,
286 Self::Int16,
287 Self::Int32,
288 Self::Int64,
289 Self::UInt8,
290 Self::UInt16,
291 Self::UInt32,
292 Self::UInt64,
293 Self::Float,
294 Self::Double,
295 Self::LongDouble,
296 Self::Size,
297 ];
298
299 /// How it is written.
300 #[must_use]
301 pub const fn as_str(self) -> &'static str {
302 match self {
303 Self::Char => "char",
304 Self::Bool => "bool",
305 Self::Int8 => "int8_t",
306 Self::Int16 => "int16_t",
307 Self::Int32 => "int32_t",
308 Self::Int64 => "int64_t",
309 Self::UInt8 => "uint8_t",
310 Self::UInt16 => "uint16_t",
311 Self::UInt32 => "uint32_t",
312 Self::UInt64 => "uint64_t",
313 Self::Float => "float",
314 Self::Double => "double",
315 Self::LongDouble => "long double",
316 Self::Size => "size_t",
317 }
318 }
319
320 /// The header it comes from, when it needs one.
321 #[must_use]
322 pub const fn header(self) -> Option<&'static str> {
323 match self {
324 Self::Int8
325 | Self::Int16
326 | Self::Int32
327 | Self::Int64
328 | Self::UInt8
329 | Self::UInt16
330 | Self::UInt32
331 | Self::UInt64 => Some("stdint.h"),
332 Self::Bool => Some("stdbool.h"),
333 Self::Size => Some("stddef.h"),
334 Self::Char | Self::Float | Self::Double | Self::LongDouble => None,
335 }
336 }
337}
338
339/// A block of statements.
340#[derive(Debug, Clone, Default, PartialEq, Eq)]
341pub struct Block {
342 /// The statements, in order.
343 pub statements: Vec<Statement>,
344}
345
346/// One statement.
347#[derive(Debug, Clone, PartialEq, Eq)]
348pub enum Statement {
349 /// A declaration with an optional initialiser.
350 Declare {
351 /// Type.
352 type_: Type,
353 /// Name.
354 name: Ident,
355 /// Initialiser.
356 value: Option<Expr>,
357 },
358 /// An expression followed by `;`.
359 Expr(Expr),
360 /// `return expr;`
361 Return(Option<Expr>),
362 /// `if (cond) { .. } else { .. }`
363 If {
364 /// Condition.
365 condition: Expr,
366 /// Then-block.
367 then: Block,
368 /// Else-block.
369 otherwise: Option<Block>,
370 },
371 /// `for (init; cond; step) { .. }`
372 For {
373 /// Initialiser.
374 init: Box<Statement>,
375 /// Condition.
376 condition: Expr,
377 /// Step.
378 step: Expr,
379 /// Body.
380 body: Block,
381 },
382 /// `while (cond) { .. }`
383 ///
384 /// Not a `for` with the two clauses left empty. A decoder walks a
385 /// `@mutable` aggregate's headers until the delimiter is used up
386 /// and a PL_CDR1 list until its sentinel; neither has a counter,
387 /// and writing one as `for (; cond;)` says there was an induction
388 /// variable somebody forgot.
389 While {
390 /// Condition.
391 condition: Expr,
392 /// Body.
393 body: Block,
394 },
395 /// `break;`
396 ///
397 /// A PL_CDR1 parameter list ends at `PID_LIST_END` and not at a
398 /// count, so the loop leaves from the middle. A flag standing in
399 /// for it would keep reading a list that has already ended.
400 Break,
401 /// A comment standing on its own line.
402 Comment(Doc),
403}
404
405/// An expression.
406#[derive(Debug, Clone, PartialEq, Eq)]
407pub enum Expr {
408 /// A literal.
409 Literal(Literal),
410 /// A name.
411 Name(Ident),
412 /// `f(args)`
413 Call {
414 /// What is called.
415 callee: Box<Expr>,
416 /// Arguments.
417 args: Vec<Expr>,
418 },
419 /// `a.b`
420 Member {
421 /// The value.
422 value: Box<Expr>,
423 /// The member.
424 name: Ident,
425 },
426 /// `a->b`
427 Arrow {
428 /// The pointer.
429 value: Box<Expr>,
430 /// The member.
431 name: Ident,
432 },
433 /// `a[i]`
434 Index {
435 /// The array.
436 value: Box<Expr>,
437 /// The index.
438 index: Box<Expr>,
439 },
440 /// `op a`, written as given — `!a`, `-a`, `~a`.
441 ///
442 /// The tree carries no precedence, so a caller that needs the
443 /// operand bracketed writes [`Expr::Paren`] around it.
444 Unary {
445 /// The operator, as written.
446 op: &'static str,
447 /// What it applies to.
448 value: Box<Expr>,
449 },
450 /// `&a`
451 AddressOf(Box<Expr>),
452 /// `*a`
453 Deref(Box<Expr>),
454 /// `a op b`
455 Binary {
456 /// The operator, as written.
457 op: &'static str,
458 /// Left.
459 lhs: Box<Expr>,
460 /// Right.
461 rhs: Box<Expr>,
462 },
463 /// `(T)value`
464 Cast {
465 /// The target type.
466 type_: Box<Type>,
467 /// The value.
468 value: Box<Expr>,
469 },
470 /// `sizeof(T)`
471 SizeOf(Box<Type>),
472 /// `(a)` — parentheses written on purpose.
473 ///
474 /// The tree carries no operator precedence, so a renderer walking it
475 /// cannot know where C needs a bracket and where it does not. A
476 /// builder that needs one says so. The case that made this
477 /// necessary is a `#define`: `#define MOVING (Flags)1 << 4` expands
478 /// inside `x & MOVING` as `(x & (Flags)1) << 4`, which is a
479 /// different number and compiles without a word.
480 Paren(Box<Expr>),
481 /// `{ a, b }`
482 Initializer(Vec<Expr>),
483}
484
485/// The name a C keyword collision escapes to.
486///
487/// A trailing underscore, which is what the emitters this replaces did
488/// and what the fixture output records.
489#[must_use]
490pub fn escape_keyword(name: &str) -> String {
491 if RESERVED.contains(&name) {
492 let mut out = name.to_string();
493 out.push('_');
494 return out;
495 }
496 name.to_string()
497}
498
499/// The C keywords a generated identifier may collide with. C11 §6.4.1.
500const RESERVED: &[&str] = &[
501 "auto",
502 "break",
503 "case",
504 "char",
505 "const",
506 "continue",
507 "default",
508 "do",
509 "double",
510 "else",
511 "enum",
512 "extern",
513 "float",
514 "for",
515 "goto",
516 "if",
517 "inline",
518 "int",
519 "long",
520 "register",
521 "restrict",
522 "return",
523 "short",
524 "signed",
525 "sizeof",
526 "static",
527 "struct",
528 "switch",
529 "typedef",
530 "union",
531 "unsigned",
532 "void",
533 "volatile",
534 "while",
535 "_Bool",
536 "_Complex",
537 "_Imaginary",
538 "_Alignas",
539 "_Alignof",
540 "_Atomic",
541 "_Generic",
542 "_Noreturn",
543 "_Static_assert",
544 "_Thread_local",
545];
546
547#[cfg(test)]
548mod tests {
549 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
550 use super::*;
551
552 #[test]
553 fn every_primitive_writes_its_own_spelling() {
554 let mut seen = Vec::new();
555 for primitive in Primitive::ALL {
556 let text = primitive.as_str();
557 assert!(!seen.contains(&text), "{text} is used twice");
558 seen.push(text);
559 }
560 }
561
562 #[test]
563 fn a_fixed_width_primitive_names_the_header_it_needs() {
564 // A generator that emitted `int32_t` without `<stdint.h>` would
565 // produce a file that does not compile.
566 assert_eq!(Primitive::Int32.header(), Some("stdint.h"));
567 assert_eq!(Primitive::Bool.header(), Some("stdbool.h"));
568 assert_eq!(Primitive::Double.header(), None);
569 }
570
571 #[test]
572 fn a_keyword_escapes_with_a_trailing_underscore() {
573 assert_eq!(escape_keyword("struct"), "struct_");
574 assert_eq!(escape_keyword("value"), "value");
575 }
576
577 #[test]
578 fn an_array_member_keeps_its_dimensions_in_order() {
579 let member = Member {
580 doc: Doc::none(),
581 type_: Type::Primitive(Primitive::Int32),
582 name: Ident::new("grid"),
583 dims: alloc::vec![
584 Expr::Literal(Literal::int(2)),
585 Expr::Literal(Literal::int(3))
586 ],
587 bits: None,
588 };
589 assert_eq!(member.dims.len(), 2);
590 }
591}