Skip to main content

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}