Skip to main content

zerodds_idl_cpp/
c_mode.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! IDL → C99 codegen mode (vendor spec `zerodds-xcdr2-c-1.0`).
5//!
6//! For each IDL specification, this module emits:
7//! - C99 `typedef struct` definitions per `struct` (with a module prefix
8//!   in the type name, because C99 has no namespaces).
9//! - Static `zerodds_typesupport_t` tables with XCDR2 encoder/
10//!   decoder/key-hash/free function pointers.
11//! - Inline body implementations of the encoders/decoders that produce
12//!   the XCDR2 wire format (XTypes 1.3 §7.4) byte-exactly.
13//!
14//! ## Scope (rc1)
15//!
16//! Supported:
17//! - Structs with `@final`/`@appendable`/`@mutable` extensibility.
18//! - Primitive types (boolean, octet, short/long/long long + unsigned,
19//!   float, double).
20//! - `string` (unbounded).
21//! - `sequence<T>` (unbounded; nested sequences allowed).
22//! - Nested modules → type-name prefix with `::` (cross-language
23//!   convention) and identifier prefix with `_` (C99-conformant).
24//! - `@key` members → key-hash routine via `PlainCdr2BeKeyHolder`.
25//! - `@id(N)` for @mutable.
26//!
27//! Out of scope (errors at the codegen level):
28//! - Unions, enums, bitsets/bitmasks, typedefs, arrays, maps.
29//! - `wstring`, `fixed`, `any`.
30//! - `@optional`/`@external`/`@bit_bound`.
31//!
32//! ## Invocation
33//!
34//! ```rust
35//! use zerodds_idl::config::ParserConfig;
36//! use zerodds_idl_cpp::{generate_c_header, CGenOptions};
37//!
38//! let ast = zerodds_idl::parse("@final struct Point { long x; long y; };",
39//!                              &ParserConfig::default()).unwrap();
40//! let header = generate_c_header(&ast, &CGenOptions::default()).unwrap();
41//! assert!(header.contains("typedef struct Point_s"));
42//! assert!(header.contains("Point_typesupport"));
43//! ```
44
45#![allow(clippy::module_name_repetitions)]
46
47use core::fmt::Write as _;
48
49use zerodds_idl::ast::{
50    Annotation, AnnotationParams, ConstExpr, ConstrTypeDecl, Definition, FloatingType, IntegerType,
51    LiteralKind, ModuleDef, PrimitiveType, Specification, StructDef, TypeDecl, TypeSpec,
52};
53
54use crate::error::CppGenError;
55
56fn unsupported(kind: &'static str) -> CppGenError {
57    CppGenError::UnsupportedConstruct {
58        construct: kind.to_string(),
59        context: None,
60    }
61}
62
63/// Codegen options (parallel to `CppGenOptions`).
64#[derive(Debug, Clone, Default)]
65pub struct CGenOptions {
66    /// Optional include-guard name (default: `ZERODDS_GENERATED_H`).
67    pub include_guard: Option<String>,
68    /// Optional file-header comment.
69    pub file_header: Option<String>,
70}
71
72/// Produces a complete C99 header from an IDL specification.
73///
74/// # Errors
75/// - [`CppGenError::UnsupportedConstruct`]: out-of-scope IDL constructs
76///   (unions, enums, bitsets, typedefs, arrays, maps, wstring, fixed,
77///   any).
78pub fn generate_c_header(ast: &Specification, opts: &CGenOptions) -> Result<String, CppGenError> {
79    let mut ctx = Ctx::new(opts);
80    ctx.emit_preamble();
81    ctx.walk_definitions(&ast.definitions, &[])?;
82    ctx.emit_postamble();
83    Ok(ctx.out)
84}
85
86// ============================================================================
87// Internals
88// ============================================================================
89
90struct Ctx<'a> {
91    out: String,
92    opts: &'a CGenOptions,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96enum Extensibility {
97    Final,
98    Appendable,
99    Mutable,
100}
101
102impl<'a> Ctx<'a> {
103    fn new(opts: &'a CGenOptions) -> Self {
104        Self {
105            out: String::new(),
106            opts,
107        }
108    }
109
110    fn emit_preamble(&mut self) {
111        let guard = self
112            .opts
113            .include_guard
114            .clone()
115            .unwrap_or_else(|| "ZERODDS_GENERATED_H".to_string());
116        if let Some(h) = &self.opts.file_header {
117            for line in h.lines() {
118                let _ = writeln!(self.out, "/* {line} */");
119            }
120        } else {
121            let _ = writeln!(
122                self.out,
123                "/* Generated by zerodds idl-cpp c_mode. Do not edit. */"
124            );
125        }
126        let _ = writeln!(self.out, "#ifndef {guard}");
127        let _ = writeln!(self.out, "#define {guard}");
128        let _ = writeln!(self.out, "#include <stddef.h>");
129        let _ = writeln!(self.out, "#include <stdint.h>");
130        let _ = writeln!(self.out, "#include <string.h>");
131        let _ = writeln!(self.out, "#include <stdlib.h>");
132        let _ = writeln!(self.out, "#include \"zerodds.h\"");
133        let _ = writeln!(self.out, "#include \"zerodds_xcdr2.h\"");
134        let _ = writeln!(self.out, "#ifdef __cplusplus");
135        let _ = writeln!(self.out, "extern \"C\" {{");
136        let _ = writeln!(self.out, "#endif");
137        let _ = writeln!(self.out);
138    }
139
140    fn emit_postamble(&mut self) {
141        let _ = writeln!(self.out, "#ifdef __cplusplus");
142        let _ = writeln!(self.out, "}}");
143        let _ = writeln!(self.out, "#endif");
144        let _ = writeln!(self.out, "#endif");
145    }
146
147    fn walk_definitions(
148        &mut self,
149        defs: &[Definition],
150        scope: &[String],
151    ) -> Result<(), CppGenError> {
152        for d in defs {
153            match d {
154                Definition::Module(m) => self.walk_module(m, scope)?,
155                Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(sd))) => {
156                    if let zerodds_idl::ast::StructDcl::Def(def) = sd {
157                        self.emit_struct(def, scope)?;
158                    }
159                }
160                Definition::Type(_) => {
161                    return Err(unsupported("non-struct type"));
162                }
163                // Constants/annotations occur at top level.
164                Definition::Const(_)
165                | Definition::Annotation(_)
166                | Definition::TypeId(_)
167                | Definition::TypePrefix(_)
168                | Definition::Import(_) => {
169                    // Ignore — no C output needed.
170                }
171                _ => {
172                    return Err(unsupported("non-struct definition"));
173                }
174            }
175        }
176        Ok(())
177    }
178
179    fn walk_module(&mut self, m: &ModuleDef, scope: &[String]) -> Result<(), CppGenError> {
180        let mut new_scope = scope.to_vec();
181        new_scope.push(m.name.text.clone());
182        self.walk_definitions(&m.definitions, &new_scope)
183    }
184
185    fn emit_struct(&mut self, def: &StructDef, scope: &[String]) -> Result<(), CppGenError> {
186        let ext = extensibility_of(&def.annotations);
187        let c_name = c_identifier(scope, &def.name.text);
188        let dds_name = dds_type_name(scope, &def.name.text);
189
190        // ---- typedef struct ----
191        let _ = writeln!(self.out, "typedef struct {c_name}_s {{");
192        for member in &def.members {
193            for decl in &member.declarators {
194                let m_name = decl.name();
195                let c_type = c_type_for(&member.type_spec)?;
196                let _ = writeln!(self.out, "    {c_type} {field};", field = m_name.text);
197            }
198        }
199        if def.members.is_empty() {
200            // C99 forbids empty structs; dummy padding member.
201            let _ = writeln!(self.out, "    uint8_t _zerodds_empty;");
202        }
203        let _ = writeln!(self.out, "}} {c_name}_t;");
204        let _ = writeln!(self.out);
205
206        // ---- encode/decode/free/key_hash declarations ----
207        let _ = writeln!(
208            self.out,
209            "static int {c_name}_encode(const void* sample, uint8_t* out_buf, size_t out_cap, size_t* out_len);"
210        );
211        let _ = writeln!(
212            self.out,
213            "static int {c_name}_decode(const uint8_t* buf, size_t len, void* out_sample);"
214        );
215        let _ = writeln!(self.out, "static void {c_name}_sample_free(void* sample);");
216        let has_key = struct_has_key(def);
217        if has_key {
218            let _ = writeln!(
219                self.out,
220                "static int {c_name}_key_hash(const void* sample, uint8_t out_hash[16]);"
221            );
222        }
223        let _ = writeln!(self.out);
224
225        // ---- typesupport static const ----
226        let _ = writeln!(
227            self.out,
228            "static const char {c_name}_type_name[] = \"{dds_name}\";"
229        );
230        let _ = writeln!(
231            self.out,
232            "static const zerodds_typesupport_t {c_name}_typesupport = {{"
233        );
234        let _ = writeln!(self.out, "    .type_hash = {{0}},");
235        let _ = writeln!(self.out, "    .type_name = {c_name}_type_name,");
236        let _ = writeln!(self.out, "    .is_keyed = {},", if has_key { 1 } else { 0 });
237        let _ = writeln!(self.out, "    .extensibility = {},", ext.as_u8());
238        let _ = writeln!(self.out, "    ._reserved = {{0}},");
239        let _ = writeln!(self.out, "    .encode = {c_name}_encode,");
240        let _ = writeln!(self.out, "    .decode = {c_name}_decode,");
241        if has_key {
242            let _ = writeln!(self.out, "    .key_hash = {c_name}_key_hash,");
243        } else {
244            let _ = writeln!(self.out, "    .key_hash = NULL,");
245        }
246        let _ = writeln!(self.out, "    .sample_free = {c_name}_sample_free,");
247        let _ = writeln!(self.out, "}};");
248        let _ = writeln!(self.out);
249
250        // ---- encode body ----
251        self.emit_encode_body(&c_name, def, ext)?;
252        // ---- decode body ----
253        self.emit_decode_body(&c_name, def, ext)?;
254        // ---- free body ----
255        self.emit_free_body(&c_name, def);
256        // ---- key_hash body ----
257        if has_key {
258            self.emit_key_hash_body(&c_name, def);
259        }
260        Ok(())
261    }
262
263    fn emit_encode_body(
264        &mut self,
265        c_name: &str,
266        def: &StructDef,
267        ext: Extensibility,
268    ) -> Result<(), CppGenError> {
269        let _ = writeln!(
270            self.out,
271            "static int {c_name}_encode(const void* sample, uint8_t* out_buf, size_t out_cap, size_t* out_len) {{"
272        );
273        let _ = writeln!(
274            self.out,
275            "    const {c_name}_t* s = (const {c_name}_t*)sample;"
276        );
277        let _ = writeln!(self.out, "    (void)s;");
278        // Rest mode: 2-pass — first compute the size, then write.
279        // Simplicity approach: we write into an internal
280        // dynamic buffer and copy out at the end.
281        let _ = writeln!(self.out, "    /* Two-pass: grow the buffer, then copy. */");
282        let _ = writeln!(self.out, "    uint8_t* w_buf = NULL;");
283        let _ = writeln!(self.out, "    size_t w_len = 0;");
284        let _ = writeln!(self.out, "    size_t w_cap = 0;");
285        // Defensive caller check: BadParameter if out_buf=NULL when
286        // out_cap>0. Also ensures that the fail-label
287        // reachability is given even for empty structs (V-1).
288        let _ = writeln!(
289            self.out,
290            "    if (out_buf == NULL && out_cap > 0) goto fail;"
291        );
292        match ext {
293            Extensibility::Final => {
294                self.emit_struct_body_writes(def)?;
295            }
296            Extensibility::Appendable => {
297                // DHEADER reserviert; danach Body-Writes; am Ende Length patch.
298                let _ = writeln!(
299                    self.out,
300                    "    if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + 4) != 0) goto fail;"
301                );
302                let _ = writeln!(self.out, "    size_t dheader_pos = w_len;");
303                let _ = writeln!(self.out, "    w_len += 4;");
304                let _ = writeln!(self.out, "    size_t body_start = w_len;");
305                self.emit_struct_body_writes(def)?;
306                let _ = writeln!(
307                    self.out,
308                    "    uint32_t dheader_len = (uint32_t)(w_len - body_start);"
309                );
310                let _ = writeln!(
311                    self.out,
312                    "    zerodds_xcdr2_c_put_u32_at(w_buf, dheader_pos, dheader_len);"
313                );
314            }
315            Extensibility::Mutable => {
316                // DHEADER + EMHEADER per member. LC=4 default (NEXTINT = body length).
317                let _ = writeln!(
318                    self.out,
319                    "    if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + 4) != 0) goto fail;"
320                );
321                let _ = writeln!(self.out, "    size_t dheader_pos = w_len;");
322                let _ = writeln!(self.out, "    w_len += 4;");
323                let _ = writeln!(self.out, "    size_t mut_body_start = w_len;");
324                self.emit_mutable_member_writes(def)?;
325                let _ = writeln!(
326                    self.out,
327                    "    uint32_t dheader_len = (uint32_t)(w_len - mut_body_start);"
328                );
329                let _ = writeln!(
330                    self.out,
331                    "    zerodds_xcdr2_c_put_u32_at(w_buf, dheader_pos, dheader_len);"
332                );
333            }
334        }
335        // Copy the output.
336        let _ = writeln!(self.out, "    if (out_len) *out_len = w_len;");
337        let _ = writeln!(
338            self.out,
339            "    if (out_buf == NULL || out_cap < w_len) {{ free(w_buf); return -13; }}"
340        );
341        let _ = writeln!(
342            self.out,
343            "    if (w_len > 0) memcpy(out_buf, w_buf, w_len);"
344        );
345        let _ = writeln!(self.out, "    free(w_buf);");
346        let _ = writeln!(self.out, "    return 0;");
347        let _ = writeln!(self.out, "fail:");
348        let _ = writeln!(self.out, "    free(w_buf);");
349        let _ = writeln!(self.out, "    return -1;");
350        let _ = writeln!(self.out, "}}");
351        let _ = writeln!(self.out);
352        Ok(())
353    }
354
355    fn emit_struct_body_writes(&mut self, def: &StructDef) -> Result<(), CppGenError> {
356        for member in &def.members {
357            for decl in &member.declarators {
358                let f = &decl.name().text;
359                self.emit_member_write(&format!("s->{f}"), &member.type_spec)?;
360            }
361        }
362        Ok(())
363    }
364
365    fn emit_member_write(&mut self, var: &str, type_spec: &TypeSpec) -> Result<(), CppGenError> {
366        match type_spec {
367            TypeSpec::Primitive(p) => self.emit_primitive_write(var, *p),
368            TypeSpec::String(st) => {
369                if st.wide {
370                    return Err(unsupported("wstring"));
371                }
372                let _ = writeln!(
373                    self.out,
374                    "    if (zerodds_xcdr2_c_write_string(&w_buf, &w_len, &w_cap, {var}) != 0) goto fail;"
375                );
376                Ok(())
377            }
378            TypeSpec::Sequence(seq) => self.emit_sequence_write(var, &seq.elem),
379            TypeSpec::Scoped(_) => Err(unsupported("nested struct reference")),
380            TypeSpec::Fixed(_) => Err(unsupported("fixed")),
381            TypeSpec::Map(_) => Err(unsupported("map")),
382            TypeSpec::Any => Err(unsupported("any")),
383        }
384    }
385
386    fn emit_primitive_write(&mut self, var: &str, p: PrimitiveType) -> Result<(), CppGenError> {
387        let helper = match p {
388            PrimitiveType::Boolean | PrimitiveType::Octet => "u8",
389            PrimitiveType::Char => "u8",
390            PrimitiveType::WideChar => "u16",
391            PrimitiveType::Integer(IntegerType::Short)
392            | PrimitiveType::Integer(IntegerType::Int16) => "i16",
393            PrimitiveType::Integer(IntegerType::UShort)
394            | PrimitiveType::Integer(IntegerType::UInt16) => "u16",
395            PrimitiveType::Integer(IntegerType::Long)
396            | PrimitiveType::Integer(IntegerType::Int32) => "i32",
397            PrimitiveType::Integer(IntegerType::ULong)
398            | PrimitiveType::Integer(IntegerType::UInt32) => "u32",
399            PrimitiveType::Integer(IntegerType::LongLong)
400            | PrimitiveType::Integer(IntegerType::Int64) => "i64",
401            PrimitiveType::Integer(IntegerType::ULongLong)
402            | PrimitiveType::Integer(IntegerType::UInt64) => "u64",
403            PrimitiveType::Integer(IntegerType::Int8) => "i8",
404            PrimitiveType::Integer(IntegerType::UInt8) => "u8",
405            PrimitiveType::Floating(FloatingType::Float) => "f32",
406            PrimitiveType::Floating(FloatingType::Double) => "f64",
407            PrimitiveType::Floating(FloatingType::LongDouble) => {
408                return Err(unsupported("long double"));
409            }
410        };
411        let _ = writeln!(
412            self.out,
413            "    if (zerodds_xcdr2_c_write_{helper}(&w_buf, &w_len, &w_cap, {var}) != 0) goto fail;"
414        );
415        Ok(())
416    }
417
418    fn emit_sequence_write(&mut self, var: &str, elem: &TypeSpec) -> Result<(), CppGenError> {
419        // Sequence-Repraesentation in C: `struct { uint32_t len; T* elems; }`.
420        // XCDR2 §7.4.3.5: non-primitive elements (string, …) get
421        // a DHEADER (uint32 = byte length of [count + elements])
422        // prepended; primitives do not. Cyclone-DDS-verified.
423        let non_primitive = !matches!(elem, TypeSpec::Primitive(_));
424        // Block-scopes the DHEADER locals (multiple sequences per struct).
425        let _ = writeln!(self.out, "    {{");
426        if non_primitive {
427            let _ = writeln!(
428                self.out,
429                "    if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + 4) != 0) goto fail;"
430            );
431            let _ = writeln!(self.out, "    size_t seq_dheader_pos = w_len;");
432            let _ = writeln!(self.out, "    w_len += 4;");
433            let _ = writeln!(self.out, "    size_t seq_body_start = w_len;");
434        }
435        let _ = writeln!(
436            self.out,
437            "    if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, ({var}).len) != 0) goto fail;"
438        );
439        let _ = writeln!(
440            self.out,
441            "    for (uint32_t i = 0; i < ({var}).len; ++i) {{"
442        );
443        match elem {
444            TypeSpec::Primitive(p) => {
445                self.emit_primitive_write(&format!("({var}).elems[i]"), *p)?
446            }
447            TypeSpec::String(st) => {
448                if st.wide {
449                    return Err(unsupported("wstring"));
450                }
451                let _ = writeln!(
452                    self.out,
453                    "        if (zerodds_xcdr2_c_write_string(&w_buf, &w_len, &w_cap, ({var}).elems[i]) != 0) goto fail;"
454                );
455            }
456            _ => {
457                return Err(unsupported("non-primitive sequence element"));
458            }
459        }
460        let _ = writeln!(self.out, "    }}");
461        if non_primitive {
462            let _ = writeln!(
463                self.out,
464                "    uint32_t seq_dheader_len = (uint32_t)(w_len - seq_body_start);"
465            );
466            let _ = writeln!(
467                self.out,
468                "    zerodds_xcdr2_c_put_u32_at(w_buf, seq_dheader_pos, seq_dheader_len);"
469            );
470        }
471        let _ = writeln!(self.out, "    }}");
472        Ok(())
473    }
474
475    fn emit_mutable_member_writes(&mut self, def: &StructDef) -> Result<(), CppGenError> {
476        for member in &def.members {
477            let id = id_annotation(&member.annotations)
478                .ok_or_else(|| unsupported("@mutable member without @id(N)"))?;
479            for decl in &member.declarators {
480                let f = &decl.name().text;
481                // Encode the member body into a temp buffer, then EMHEADER + body.
482                let _ = writeln!(self.out, "    {{");
483                let _ = writeln!(self.out, "        uint8_t* m_buf = NULL;");
484                let _ = writeln!(self.out, "        size_t m_len = 0;");
485                let _ = writeln!(self.out, "        size_t m_cap = 0;");
486                // Member body: temp swap of the buffer variables.
487                self.emit_member_write_to_named_buffer(
488                    &format!("s->{f}"),
489                    &member.type_spec,
490                    "m_buf",
491                    "m_len",
492                    "m_cap",
493                )?;
494                // EMHEADER: M=0, LC=4, id=N → value = (4<<28) | id.
495                let emheader: u32 = (4u32 << 28) | id;
496                let _ = writeln!(
497                    self.out,
498                    "        if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, 0x{emheader:08X}u) != 0) {{ free(m_buf); goto fail; }}"
499                );
500                // NEXTINT: body length.
501                let _ = writeln!(
502                    self.out,
503                    "        if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, (uint32_t)m_len) != 0) {{ free(m_buf); goto fail; }}"
504                );
505                let _ = writeln!(
506                    self.out,
507                    "        if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + m_len) != 0) {{ free(m_buf); goto fail; }}"
508                );
509                let _ = writeln!(
510                    self.out,
511                    "        if (m_len > 0) memcpy(w_buf + w_len, m_buf, m_len);"
512                );
513                let _ = writeln!(self.out, "        w_len += m_len;");
514                let _ = writeln!(self.out, "        free(m_buf);");
515                let _ = writeln!(self.out, "    }}");
516            }
517        }
518        Ok(())
519    }
520
521    fn emit_member_write_to_named_buffer(
522        &mut self,
523        var: &str,
524        type_spec: &TypeSpec,
525        buf: &str,
526        len: &str,
527        cap: &str,
528    ) -> Result<(), CppGenError> {
529        // We use local "aliases" via macro-like inline write.
530        // To keep this simple, we generate an inline mini-encoder
531        // per type spec that operates directly on m_buf/m_len/m_cap.
532        match type_spec {
533            TypeSpec::Primitive(p) => {
534                let helper = primitive_helper(*p)?;
535                let _ = writeln!(
536                    self.out,
537                    "        if (zerodds_xcdr2_c_write_{helper}(&{buf}, &{len}, &{cap}, {var}) != 0) {{ free({buf}); goto fail; }}"
538                );
539                Ok(())
540            }
541            TypeSpec::String(st) => {
542                if st.wide {
543                    return Err(unsupported("wstring"));
544                }
545                let _ = writeln!(
546                    self.out,
547                    "        if (zerodds_xcdr2_c_write_string(&{buf}, &{len}, &{cap}, {var}) != 0) {{ free({buf}); goto fail; }}"
548                );
549                Ok(())
550            }
551            _ => Err(unsupported("complex type in @mutable member")),
552        }
553    }
554
555    fn emit_decode_body(
556        &mut self,
557        c_name: &str,
558        def: &StructDef,
559        ext: Extensibility,
560    ) -> Result<(), CppGenError> {
561        // The decoder is symmetric to the encoder; builds the Rust-stack-style
562        // BufferReader via helper inline functions from `zerodds_xcdr2.h`.
563        let _ = writeln!(
564            self.out,
565            "static int {c_name}_decode(const uint8_t* buf, size_t len, void* out_sample) {{"
566        );
567        let _ = writeln!(self.out, "    {c_name}_t* s = ({c_name}_t*)out_sample;");
568        let _ = writeln!(self.out, "    size_t pos = 0;");
569        match ext {
570            Extensibility::Final => {
571                self.emit_struct_body_reads(def)?;
572            }
573            Extensibility::Appendable => {
574                let _ = writeln!(self.out, "    uint32_t dheader = 0;");
575                let _ = writeln!(
576                    self.out,
577                    "    if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &dheader) != 0) return -7;"
578                );
579                let _ = writeln!(self.out, "    size_t body_end = pos + dheader;");
580                let _ = writeln!(self.out, "    if (body_end > len) return -7;");
581                self.emit_struct_body_reads(def)?;
582                let _ = writeln!(self.out, "    pos = body_end;");
583            }
584            Extensibility::Mutable => {
585                let _ = writeln!(self.out, "    uint32_t dheader = 0;");
586                let _ = writeln!(
587                    self.out,
588                    "    if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &dheader) != 0) return -7;"
589                );
590                let _ = writeln!(self.out, "    size_t body_end = pos + dheader;");
591                let _ = writeln!(self.out, "    if (body_end > len) return -7;");
592                let _ = writeln!(self.out, "    while (pos < body_end) {{");
593                let _ = writeln!(self.out, "        uint32_t emheader = 0;");
594                let _ = writeln!(
595                    self.out,
596                    "        if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &emheader) != 0) return -7;"
597                );
598                let _ = writeln!(self.out, "        uint32_t mid = emheader & 0x0FFFFFFFu;");
599                let _ = writeln!(self.out, "        uint32_t lc = (emheader >> 28) & 0x7u;");
600                let _ = writeln!(self.out, "        uint32_t nextint = 0;");
601                let _ = writeln!(self.out, "        size_t body_len = 0;");
602                let _ = writeln!(
603                    self.out,
604                    "        if (lc == 0) body_len = 1; else if (lc == 1) body_len = 2; else if (lc == 2) body_len = 4; else if (lc == 3) body_len = 8; else {{"
605                );
606                let _ = writeln!(
607                    self.out,
608                    "            if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &nextint) != 0) return -7;"
609                );
610                let _ = writeln!(self.out, "            body_len = nextint;");
611                let _ = writeln!(self.out, "        }}");
612                let _ = writeln!(
613                    self.out,
614                    "        if (pos + body_len > body_end) return -7;"
615                );
616                self.emit_mutable_member_dispatch(def)?;
617                let _ = writeln!(self.out, "    }}");
618            }
619        }
620        let _ = writeln!(self.out, "    (void)s;");
621        let _ = writeln!(self.out, "    return 0;");
622        let _ = writeln!(self.out, "}}");
623        let _ = writeln!(self.out);
624        Ok(())
625    }
626
627    fn emit_struct_body_reads(&mut self, def: &StructDef) -> Result<(), CppGenError> {
628        for member in &def.members {
629            for decl in &member.declarators {
630                let f = &decl.name().text;
631                self.emit_member_read(&format!("s->{f}"), &member.type_spec)?;
632            }
633        }
634        Ok(())
635    }
636
637    fn emit_member_read(&mut self, var: &str, type_spec: &TypeSpec) -> Result<(), CppGenError> {
638        match type_spec {
639            TypeSpec::Primitive(p) => {
640                let helper = primitive_helper(*p)?;
641                let _ = writeln!(
642                    self.out,
643                    "    if (zerodds_xcdr2_c_read_{helper}(buf, len, &pos, &({var})) != 0) return -7;"
644                );
645                Ok(())
646            }
647            TypeSpec::String(st) => {
648                if st.wide {
649                    return Err(unsupported("wstring"));
650                }
651                let _ = writeln!(
652                    self.out,
653                    "    if (zerodds_xcdr2_c_read_string(buf, len, &pos, &({var})) != 0) return -7;"
654                );
655                Ok(())
656            }
657            TypeSpec::Sequence(seq) => self.emit_sequence_read(var, &seq.elem),
658            _ => Err(unsupported("complex member read")),
659        }
660    }
661
662    fn emit_sequence_read(&mut self, var: &str, elem: &TypeSpec) -> Result<(), CppGenError> {
663        let _ = writeln!(self.out, "    {{");
664        // XCDR2 §7.4.3.5: for non-primitive elements, write the DHEADER
665        // (uint32 before [count + elements]) skipped over.
666        if !matches!(elem, TypeSpec::Primitive(_)) {
667            let _ = writeln!(self.out, "        uint32_t seq_dheader = 0;");
668            let _ = writeln!(
669                self.out,
670                "        if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &seq_dheader) != 0) return -7;"
671            );
672            let _ = writeln!(self.out, "        (void)seq_dheader;");
673        }
674        let _ = writeln!(self.out, "        uint32_t seq_len = 0;");
675        let _ = writeln!(
676            self.out,
677            "        if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &seq_len) != 0) return -7;"
678        );
679        let elem_c = c_type_for(elem)?;
680        let _ = writeln!(self.out, "        ({var}).len = seq_len;");
681        let _ = writeln!(
682            self.out,
683            "        ({var}).elems = ({elem_c}*)calloc(seq_len ? seq_len : 1, sizeof({elem_c}));"
684        );
685        let _ = writeln!(
686            self.out,
687            "        if (({var}).elems == NULL && seq_len > 0) return -7;"
688        );
689        let _ = writeln!(
690            self.out,
691            "        for (uint32_t i = 0; i < seq_len; ++i) {{"
692        );
693        match elem {
694            TypeSpec::Primitive(p) => {
695                let helper = primitive_helper(*p)?;
696                let _ = writeln!(
697                    self.out,
698                    "            if (zerodds_xcdr2_c_read_{helper}(buf, len, &pos, &({var}).elems[i]) != 0) return -7;"
699                );
700            }
701            TypeSpec::String(st) => {
702                if st.wide {
703                    return Err(unsupported("wstring"));
704                }
705                let _ = writeln!(
706                    self.out,
707                    "            if (zerodds_xcdr2_c_read_string(buf, len, &pos, &({var}).elems[i]) != 0) return -7;"
708                );
709            }
710            _ => {
711                return Err(unsupported("complex sequence element read"));
712            }
713        }
714        let _ = writeln!(self.out, "        }}");
715        let _ = writeln!(self.out, "    }}");
716        Ok(())
717    }
718
719    fn emit_mutable_member_dispatch(&mut self, def: &StructDef) -> Result<(), CppGenError> {
720        let _ = writeln!(self.out, "        switch (mid) {{");
721        for member in &def.members {
722            let Some(id) = id_annotation(&member.annotations) else {
723                return Err(unsupported("@mutable member without @id(N)"));
724            };
725            for decl in &member.declarators {
726                let f = &decl.name().text;
727                let _ = writeln!(self.out, "        case {id}: {{");
728                self.emit_member_read(&format!("s->{f}"), &member.type_spec)?;
729                let _ = writeln!(self.out, "            break;");
730                let _ = writeln!(self.out, "        }}");
731            }
732        }
733        let _ = writeln!(self.out, "        default: pos += body_len; break;");
734        let _ = writeln!(self.out, "        }}");
735        Ok(())
736    }
737
738    fn emit_free_body(&mut self, c_name: &str, def: &StructDef) {
739        let _ = writeln!(
740            self.out,
741            "static void {c_name}_sample_free(void* sample) {{"
742        );
743        let _ = writeln!(self.out, "    if (sample == NULL) return;");
744        let _ = writeln!(self.out, "    {c_name}_t* s = ({c_name}_t*)sample;");
745        let _ = writeln!(self.out, "    (void)s;");
746        for member in &def.members {
747            for decl in &member.declarators {
748                let f = &decl.name().text;
749                match &member.type_spec {
750                    TypeSpec::String(_) => {
751                        let _ = writeln!(self.out, "    free(s->{f}); s->{f} = NULL;");
752                    }
753                    TypeSpec::Sequence(seq) => match seq.elem.as_ref() {
754                        TypeSpec::String(_) => {
755                            let _ = writeln!(
756                                self.out,
757                                "    for (uint32_t i = 0; i < s->{f}.len; ++i) free(s->{f}.elems[i]);"
758                            );
759                            let _ = writeln!(
760                                self.out,
761                                "    free(s->{f}.elems); s->{f}.elems = NULL; s->{f}.len = 0;"
762                            );
763                        }
764                        _ => {
765                            let _ = writeln!(
766                                self.out,
767                                "    free(s->{f}.elems); s->{f}.elems = NULL; s->{f}.len = 0;"
768                            );
769                        }
770                    },
771                    _ => {}
772                }
773            }
774        }
775        let _ = writeln!(self.out, "}}");
776        let _ = writeln!(self.out);
777    }
778
779    fn emit_key_hash_body(&mut self, c_name: &str, def: &StructDef) {
780        // Spec §7.6.8: collects  members in PlainCdr2BeKeyHolder, then
781        // either zero-pad or MD5. We use the XCDR2 C helpers.
782        let _ = writeln!(
783            self.out,
784            "static int {c_name}_key_hash(const void* sample, uint8_t out_hash[16]) {{"
785        );
786        let _ = writeln!(
787            self.out,
788            "    const {c_name}_t* s = (const {c_name}_t*)sample;"
789        );
790        let _ = writeln!(self.out, "    uint8_t* kh_buf = NULL;");
791        let _ = writeln!(self.out, "    size_t kh_len = 0;");
792        let _ = writeln!(self.out, "    size_t kh_cap = 0;");
793        for member in &def.members {
794            if !is_key(&member.annotations) {
795                continue;
796            }
797            for decl in &member.declarators {
798                let f = &decl.name().text;
799                match &member.type_spec {
800                    TypeSpec::Primitive(p) => {
801                        let helper = match primitive_helper(*p) {
802                            Ok(h) => h,
803                            Err(_) => continue,
804                        };
805                        let _ = writeln!(
806                            self.out,
807                            "    if (zerodds_xcdr2_c_kh_write_{helper}(&kh_buf, &kh_len, &kh_cap, s->{f}) != 0) {{ free(kh_buf); return -1; }}"
808                        );
809                    }
810                    TypeSpec::String(_) => {
811                        let _ = writeln!(
812                            self.out,
813                            "    if (zerodds_xcdr2_c_kh_write_string(&kh_buf, &kh_len, &kh_cap, s->{f}) != 0) {{ free(kh_buf); return -1; }}"
814                        );
815                    }
816                    _ => {}
817                }
818            }
819        }
820        let _ = writeln!(
821            self.out,
822            "    zerodds_xcdr2_c_compute_key_hash(kh_buf, kh_len, /*max_size_static=*/0, out_hash);"
823        );
824        let _ = writeln!(self.out, "    free(kh_buf);");
825        let _ = writeln!(self.out, "    return 0;");
826        let _ = writeln!(self.out, "}}");
827        let _ = writeln!(self.out);
828    }
829}
830
831// ============================================================================
832// Helpers
833// ============================================================================
834
835fn dds_type_name(scope: &[String], name: &str) -> String {
836    if scope.is_empty() {
837        name.to_string()
838    } else {
839        let mut s = scope.join("::");
840        s.push_str("::");
841        s.push_str(name);
842        s
843    }
844}
845
846fn c_identifier(scope: &[String], name: &str) -> String {
847    if scope.is_empty() {
848        name.to_string()
849    } else {
850        let mut s = scope.join("_");
851        s.push('_');
852        s.push_str(name);
853        s
854    }
855}
856
857impl Extensibility {
858    fn as_u8(self) -> u8 {
859        match self {
860            Self::Final => 0,
861            Self::Appendable => 1,
862            Self::Mutable => 2,
863        }
864    }
865}
866
867fn extensibility_of(annotations: &[Annotation]) -> Extensibility {
868    for a in annotations {
869        if let Some(name) = a.name.parts.last() {
870            match name.text.as_str() {
871                "final" | "Final" => return Extensibility::Final,
872                "appendable" | "Appendable" => return Extensibility::Appendable,
873                "mutable" | "Mutable" => return Extensibility::Mutable,
874                _ => {}
875            }
876        }
877    }
878    // Spec-Default (XTypes 1.3 §7.2.2.4.4): appendable.
879    Extensibility::Appendable
880}
881
882fn is_key(annotations: &[Annotation]) -> bool {
883    annotations.iter().any(|a| {
884        a.name
885            .parts
886            .last()
887            .is_some_and(|p| p.text == "key" || p.text == "Key")
888    })
889}
890
891fn struct_has_key(def: &StructDef) -> bool {
892    def.members.iter().any(|m| is_key(&m.annotations))
893}
894
895fn id_annotation(annotations: &[Annotation]) -> Option<u32> {
896    for a in annotations {
897        let last = a.name.parts.last()?;
898        if last.text != "id" && last.text != "Id" {
899            continue;
900        }
901        if let AnnotationParams::Single(ConstExpr::Literal(lit)) = &a.params {
902            if lit.kind == LiteralKind::Integer {
903                if let Ok(v) = lit.raw.parse::<u32>() {
904                    return Some(v);
905                }
906            }
907        }
908    }
909    None
910}
911/// zerodds-lint: recursion-depth 64 (c_type_for bounded by AST depth)
912fn c_type_for(type_spec: &TypeSpec) -> Result<String, CppGenError> {
913    let s = match type_spec {
914        TypeSpec::Primitive(p) => match p {
915            PrimitiveType::Boolean => "uint8_t",
916            PrimitiveType::Octet => "uint8_t",
917            PrimitiveType::Char => "char",
918            PrimitiveType::WideChar => "uint16_t",
919            PrimitiveType::Integer(IntegerType::Short)
920            | PrimitiveType::Integer(IntegerType::Int16) => "int16_t",
921            PrimitiveType::Integer(IntegerType::UShort)
922            | PrimitiveType::Integer(IntegerType::UInt16) => "uint16_t",
923            PrimitiveType::Integer(IntegerType::Long)
924            | PrimitiveType::Integer(IntegerType::Int32) => "int32_t",
925            PrimitiveType::Integer(IntegerType::ULong)
926            | PrimitiveType::Integer(IntegerType::UInt32) => "uint32_t",
927            PrimitiveType::Integer(IntegerType::LongLong)
928            | PrimitiveType::Integer(IntegerType::Int64) => "int64_t",
929            PrimitiveType::Integer(IntegerType::ULongLong)
930            | PrimitiveType::Integer(IntegerType::UInt64) => "uint64_t",
931            PrimitiveType::Integer(IntegerType::Int8) => "int8_t",
932            PrimitiveType::Integer(IntegerType::UInt8) => "uint8_t",
933            PrimitiveType::Floating(FloatingType::Float) => "float",
934            PrimitiveType::Floating(FloatingType::Double) => "double",
935            PrimitiveType::Floating(FloatingType::LongDouble) => {
936                return Err(unsupported("long double"));
937            }
938        },
939        TypeSpec::String(st) => {
940            if st.wide {
941                return Err(unsupported("wstring"));
942            }
943            "char*"
944        }
945        TypeSpec::Sequence(seq) => {
946            let elem = c_type_for(&seq.elem)?;
947            return Ok(format!("struct {{ uint32_t len; {elem}* elems; }}"));
948        }
949        _ => {
950            return Err(unsupported("non-primitive type in field"));
951        }
952    };
953    Ok(s.to_string())
954}
955
956fn primitive_helper(p: PrimitiveType) -> Result<&'static str, CppGenError> {
957    Ok(match p {
958        PrimitiveType::Boolean | PrimitiveType::Octet => "u8",
959        PrimitiveType::Char => "u8",
960        PrimitiveType::WideChar => "u16",
961        PrimitiveType::Integer(IntegerType::Short) | PrimitiveType::Integer(IntegerType::Int16) => {
962            "i16"
963        }
964        PrimitiveType::Integer(IntegerType::UShort)
965        | PrimitiveType::Integer(IntegerType::UInt16) => "u16",
966        PrimitiveType::Integer(IntegerType::Long) | PrimitiveType::Integer(IntegerType::Int32) => {
967            "i32"
968        }
969        PrimitiveType::Integer(IntegerType::ULong)
970        | PrimitiveType::Integer(IntegerType::UInt32) => "u32",
971        PrimitiveType::Integer(IntegerType::LongLong)
972        | PrimitiveType::Integer(IntegerType::Int64) => "i64",
973        PrimitiveType::Integer(IntegerType::ULongLong)
974        | PrimitiveType::Integer(IntegerType::UInt64) => "u64",
975        PrimitiveType::Integer(IntegerType::Int8) => "i8",
976        PrimitiveType::Integer(IntegerType::UInt8) => "u8",
977        PrimitiveType::Floating(FloatingType::Float) => "f32",
978        PrimitiveType::Floating(FloatingType::Double) => "f64",
979        PrimitiveType::Floating(FloatingType::LongDouble) => {
980            return Err(unsupported("long double"));
981        }
982    })
983}
984
985#[cfg(test)]
986mod tests {
987    #![allow(clippy::expect_used)]
988    use super::*;
989    use zerodds_idl::config::ParserConfig;
990
991    fn gen_c(src: &str) -> String {
992        let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse ok");
993        generate_c_header(&ast, &CGenOptions::default()).expect("c-gen ok")
994    }
995
996    #[test]
997    fn empty_final_struct_emits_typedef_and_typesupport() {
998        let h = gen_c("@final struct Empty {};");
999        assert!(h.contains("typedef struct Empty_s"));
1000        assert!(h.contains("Empty_typesupport"));
1001        assert!(h.contains(".extensibility = 0"));
1002    }
1003
1004    #[test]
1005    fn primitive_struct_maps_types() {
1006        let h = gen_c("@final struct Point { long x; long y; };");
1007        assert!(h.contains("int32_t x;"));
1008        assert!(h.contains("int32_t y;"));
1009        assert!(h.contains("\"Point\""));
1010    }
1011
1012    #[test]
1013    fn nested_module_yields_scoped_type_name() {
1014        let h = gen_c("module Outer { module Inner { @final struct S { long x; }; }; };");
1015        assert!(h.contains("typedef struct Outer_Inner_S_s"));
1016        assert!(h.contains("\"Outer::Inner::S\""));
1017    }
1018
1019    #[test]
1020    fn appendable_default_when_no_annotation() {
1021        let h = gen_c("struct V { long a; long b; };");
1022        assert!(h.contains(".extensibility = 1"));
1023    }
1024
1025    #[test]
1026    fn mutable_struct_marks_extensibility() {
1027        let h = gen_c("@mutable struct M { @id(1) long a; };");
1028        assert!(h.contains(".extensibility = 2"));
1029    }
1030
1031    #[test]
1032    fn key_member_sets_is_keyed_and_emits_key_hash() {
1033        let h = gen_c("@final struct Sensor { @key long id; double value; };");
1034        assert!(h.contains(".is_keyed = 1"));
1035        assert!(h.contains("Sensor_key_hash"));
1036    }
1037}