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//! Diese Module emittiert pro IDL-Specification:
7//! - C99 `typedef struct`-Definitionen pro `struct` (mit Module-prefix
8//!   im Type-Namen, weil C99 keine Namespaces hat).
9//! - Statische `zerodds_typesupport_t`-Tabellen mit XCDR2-Encoder/
10//!   Decoder/KeyHash/Free-Funktionspointern.
11//! - Inline-Body-Implementationen der Encoder/Decoder, die das
12//!   XCDR2-Wire-Format (XTypes 1.3 §7.4) byte-genau erzeugen.
13//!
14//! ## Scope (rc1)
15//!
16//! Unterstuetzt:
17//! - Strukturen mit `@final`/`@appendable`/`@mutable`-Extensibility.
18//! - Primitive-Typen (boolean, octet, short/long/long long + unsigned,
19//!   float, double).
20//! - `string` (unbounded).
21//! - `sequence<T>` (unbounded; nested sequences zulaessig).
22//! - Geschachtelte Module → Type-Name-Praefix mit `::` (Cross-Lang-
23//!   Konvention) und Identifier-Praefix mit `_` (C99-konform).
24//! - `@key`-Members → Key-Hash-Routine ueber `PlainCdr2BeKeyHolder`.
25//! - `@id(N)` fuer @mutable.
26//!
27//! Ausserhalb (Errors auf der Codegen-Ebene):
28//! - Unions, Enums, Bitsets/Bitmasks, Typedefs, Arrays, Maps.
29//! - `wstring`, `fixed`, `any`.
30//! - `@optional`/`@external`/`@bit_bound`.
31//!
32//! ## Aufruf
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-Optionen (parallel zu `CppGenOptions`).
64#[derive(Debug, Clone, Default)]
65pub struct CGenOptions {
66    /// Optionaler Include-Guard-Name (Default: `ZERODDS_GENERATED_H`).
67    pub include_guard: Option<String>,
68    /// Optionaler Datei-Header-Kommentar.
69    pub file_header: Option<String>,
70}
71
72/// Erzeugt einen vollstaendigen C99-Header aus einer IDL-Specification.
73///
74/// # Errors
75/// - [`CppGenError::UnsupportedConstruct`]: out-of-scope IDL-Konstrukte
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                // Konstanten/Annotationen passieren auf Top-Level vor.
164                Definition::Const(_)
165                | Definition::Annotation(_)
166                | Definition::TypeId(_)
167                | Definition::TypePrefix(_)
168                | Definition::Import(_) => {
169                    // Ignorieren — kein C-Output noetig.
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 verbietet leere 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 — erst Size berechnen, dann schreiben.
279        // Einfachheits-Approach: wir schreiben in einen internen
280        // dynamischen Buffer und kopieren am Ende raus.
281        let _ = writeln!(
282            self.out,
283            "    /* Two-pass: Buffer wachsen lassen, dann kopieren. */"
284        );
285        let _ = writeln!(self.out, "    uint8_t* w_buf = NULL;");
286        let _ = writeln!(self.out, "    size_t w_len = 0;");
287        let _ = writeln!(self.out, "    size_t w_cap = 0;");
288        // Defensive Caller-Pruefung: BadParameter wenn out_buf=NULL bei
289        // out_cap>0. Sorgt zusaetzlich dafuer dass die fail-Label-
290        // Reachability auch fuer leere Structs gegeben ist (V-1).
291        let _ = writeln!(
292            self.out,
293            "    if (out_buf == NULL && out_cap > 0) goto fail;"
294        );
295        match ext {
296            Extensibility::Final => {
297                self.emit_struct_body_writes(def)?;
298            }
299            Extensibility::Appendable => {
300                // DHEADER reserviert; danach Body-Writes; am Ende Length patch.
301                let _ = writeln!(
302                    self.out,
303                    "    if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + 4) != 0) goto fail;"
304                );
305                let _ = writeln!(self.out, "    size_t dheader_pos = w_len;");
306                let _ = writeln!(self.out, "    w_len += 4;");
307                let _ = writeln!(self.out, "    size_t body_start = w_len;");
308                self.emit_struct_body_writes(def)?;
309                let _ = writeln!(
310                    self.out,
311                    "    uint32_t dheader_len = (uint32_t)(w_len - body_start);"
312                );
313                let _ = writeln!(
314                    self.out,
315                    "    zerodds_xcdr2_c_put_u32_at(w_buf, dheader_pos, dheader_len);"
316                );
317            }
318            Extensibility::Mutable => {
319                // DHEADER + EMHEADER per member. LC=4 default (NEXTINT = body length).
320                let _ = writeln!(
321                    self.out,
322                    "    if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + 4) != 0) goto fail;"
323                );
324                let _ = writeln!(self.out, "    size_t dheader_pos = w_len;");
325                let _ = writeln!(self.out, "    w_len += 4;");
326                let _ = writeln!(self.out, "    size_t mut_body_start = w_len;");
327                self.emit_mutable_member_writes(def)?;
328                let _ = writeln!(
329                    self.out,
330                    "    uint32_t dheader_len = (uint32_t)(w_len - mut_body_start);"
331                );
332                let _ = writeln!(
333                    self.out,
334                    "    zerodds_xcdr2_c_put_u32_at(w_buf, dheader_pos, dheader_len);"
335                );
336            }
337        }
338        // Output kopieren.
339        let _ = writeln!(self.out, "    if (out_len) *out_len = w_len;");
340        let _ = writeln!(
341            self.out,
342            "    if (out_buf == NULL || out_cap < w_len) {{ free(w_buf); return -13; }}"
343        );
344        let _ = writeln!(
345            self.out,
346            "    if (w_len > 0) memcpy(out_buf, w_buf, w_len);"
347        );
348        let _ = writeln!(self.out, "    free(w_buf);");
349        let _ = writeln!(self.out, "    return 0;");
350        let _ = writeln!(self.out, "fail:");
351        let _ = writeln!(self.out, "    free(w_buf);");
352        let _ = writeln!(self.out, "    return -1;");
353        let _ = writeln!(self.out, "}}");
354        let _ = writeln!(self.out);
355        Ok(())
356    }
357
358    fn emit_struct_body_writes(&mut self, def: &StructDef) -> Result<(), CppGenError> {
359        for member in &def.members {
360            for decl in &member.declarators {
361                let f = &decl.name().text;
362                self.emit_member_write(&format!("s->{f}"), &member.type_spec)?;
363            }
364        }
365        Ok(())
366    }
367
368    fn emit_member_write(&mut self, var: &str, type_spec: &TypeSpec) -> Result<(), CppGenError> {
369        match type_spec {
370            TypeSpec::Primitive(p) => self.emit_primitive_write(var, *p),
371            TypeSpec::String(st) => {
372                if st.wide {
373                    return Err(unsupported("wstring"));
374                }
375                let _ = writeln!(
376                    self.out,
377                    "    if (zerodds_xcdr2_c_write_string(&w_buf, &w_len, &w_cap, {var}) != 0) goto fail;"
378                );
379                Ok(())
380            }
381            TypeSpec::Sequence(seq) => self.emit_sequence_write(var, &seq.elem),
382            TypeSpec::Scoped(_) => Err(unsupported("nested struct reference")),
383            TypeSpec::Fixed(_) => Err(unsupported("fixed")),
384            TypeSpec::Map(_) => Err(unsupported("map")),
385            TypeSpec::Any => Err(unsupported("any")),
386        }
387    }
388
389    fn emit_primitive_write(&mut self, var: &str, p: PrimitiveType) -> Result<(), CppGenError> {
390        let helper = match p {
391            PrimitiveType::Boolean | PrimitiveType::Octet => "u8",
392            PrimitiveType::Char => "u8",
393            PrimitiveType::WideChar => "u16",
394            PrimitiveType::Integer(IntegerType::Short)
395            | PrimitiveType::Integer(IntegerType::Int16) => "i16",
396            PrimitiveType::Integer(IntegerType::UShort)
397            | PrimitiveType::Integer(IntegerType::UInt16) => "u16",
398            PrimitiveType::Integer(IntegerType::Long)
399            | PrimitiveType::Integer(IntegerType::Int32) => "i32",
400            PrimitiveType::Integer(IntegerType::ULong)
401            | PrimitiveType::Integer(IntegerType::UInt32) => "u32",
402            PrimitiveType::Integer(IntegerType::LongLong)
403            | PrimitiveType::Integer(IntegerType::Int64) => "i64",
404            PrimitiveType::Integer(IntegerType::ULongLong)
405            | PrimitiveType::Integer(IntegerType::UInt64) => "u64",
406            PrimitiveType::Integer(IntegerType::Int8) => "i8",
407            PrimitiveType::Integer(IntegerType::UInt8) => "u8",
408            PrimitiveType::Floating(FloatingType::Float) => "f32",
409            PrimitiveType::Floating(FloatingType::Double) => "f64",
410            PrimitiveType::Floating(FloatingType::LongDouble) => {
411                return Err(unsupported("long double"));
412            }
413        };
414        let _ = writeln!(
415            self.out,
416            "    if (zerodds_xcdr2_c_write_{helper}(&w_buf, &w_len, &w_cap, {var}) != 0) goto fail;"
417        );
418        Ok(())
419    }
420
421    fn emit_sequence_write(&mut self, var: &str, elem: &TypeSpec) -> Result<(), CppGenError> {
422        // Sequence-Repraesentation in C: `struct { uint32_t len; T* elems; }`.
423        let _ = writeln!(
424            self.out,
425            "    if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, ({var}).len) != 0) goto fail;"
426        );
427        let _ = writeln!(
428            self.out,
429            "    for (uint32_t i = 0; i < ({var}).len; ++i) {{"
430        );
431        match elem {
432            TypeSpec::Primitive(p) => {
433                self.emit_primitive_write(&format!("({var}).elems[i]"), *p)?
434            }
435            TypeSpec::String(st) => {
436                if st.wide {
437                    return Err(unsupported("wstring"));
438                }
439                let _ = writeln!(
440                    self.out,
441                    "        if (zerodds_xcdr2_c_write_string(&w_buf, &w_len, &w_cap, ({var}).elems[i]) != 0) goto fail;"
442                );
443            }
444            _ => {
445                return Err(unsupported("non-primitive sequence element"));
446            }
447        }
448        let _ = writeln!(self.out, "    }}");
449        Ok(())
450    }
451
452    fn emit_mutable_member_writes(&mut self, def: &StructDef) -> Result<(), CppGenError> {
453        for member in &def.members {
454            let id = id_annotation(&member.annotations)
455                .ok_or_else(|| unsupported("@mutable member without @id(N)"))?;
456            for decl in &member.declarators {
457                let f = &decl.name().text;
458                // Member-Body in temp buffer encoden, dann EMHEADER + body.
459                let _ = writeln!(self.out, "    {{");
460                let _ = writeln!(self.out, "        uint8_t* m_buf = NULL;");
461                let _ = writeln!(self.out, "        size_t m_len = 0;");
462                let _ = writeln!(self.out, "        size_t m_cap = 0;");
463                // Member-Body: temp swap der Buffer-Variablen.
464                self.emit_member_write_to_named_buffer(
465                    &format!("s->{f}"),
466                    &member.type_spec,
467                    "m_buf",
468                    "m_len",
469                    "m_cap",
470                )?;
471                // EMHEADER: M=0, LC=4, id=N → value = (4<<28) | id.
472                let emheader: u32 = (4u32 << 28) | id;
473                let _ = writeln!(
474                    self.out,
475                    "        if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, 0x{emheader:08X}u) != 0) {{ free(m_buf); goto fail; }}"
476                );
477                // NEXTINT: body length.
478                let _ = writeln!(
479                    self.out,
480                    "        if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, (uint32_t)m_len) != 0) {{ free(m_buf); goto fail; }}"
481                );
482                let _ = writeln!(
483                    self.out,
484                    "        if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + m_len) != 0) {{ free(m_buf); goto fail; }}"
485                );
486                let _ = writeln!(
487                    self.out,
488                    "        if (m_len > 0) memcpy(w_buf + w_len, m_buf, m_len);"
489                );
490                let _ = writeln!(self.out, "        w_len += m_len;");
491                let _ = writeln!(self.out, "        free(m_buf);");
492                let _ = writeln!(self.out, "    }}");
493            }
494        }
495        Ok(())
496    }
497
498    fn emit_member_write_to_named_buffer(
499        &mut self,
500        var: &str,
501        type_spec: &TypeSpec,
502        buf: &str,
503        len: &str,
504        cap: &str,
505    ) -> Result<(), CppGenError> {
506        // Wir nutzen lokale "Aliases" via Macro-aehnlichem inline-Write.
507        // Damit das einfach bleibt, generieren wir ein in-line Mini-Encoder
508        // pro Type-Spec, der direkt auf m_buf/m_len/m_cap arbeitet.
509        match type_spec {
510            TypeSpec::Primitive(p) => {
511                let helper = primitive_helper(*p)?;
512                let _ = writeln!(
513                    self.out,
514                    "        if (zerodds_xcdr2_c_write_{helper}(&{buf}, &{len}, &{cap}, {var}) != 0) {{ free({buf}); goto fail; }}"
515                );
516                Ok(())
517            }
518            TypeSpec::String(st) => {
519                if st.wide {
520                    return Err(unsupported("wstring"));
521                }
522                let _ = writeln!(
523                    self.out,
524                    "        if (zerodds_xcdr2_c_write_string(&{buf}, &{len}, &{cap}, {var}) != 0) {{ free({buf}); goto fail; }}"
525                );
526                Ok(())
527            }
528            _ => Err(unsupported("complex type in @mutable member")),
529        }
530    }
531
532    fn emit_decode_body(
533        &mut self,
534        c_name: &str,
535        def: &StructDef,
536        ext: Extensibility,
537    ) -> Result<(), CppGenError> {
538        // Decoder ist symmetrisch zum Encoder; baut den Rust-Stack-style
539        // BufferReader ueber Helper-Inline-Funktionen aus `zerodds_xcdr2.h`.
540        let _ = writeln!(
541            self.out,
542            "static int {c_name}_decode(const uint8_t* buf, size_t len, void* out_sample) {{"
543        );
544        let _ = writeln!(self.out, "    {c_name}_t* s = ({c_name}_t*)out_sample;");
545        let _ = writeln!(self.out, "    size_t pos = 0;");
546        match ext {
547            Extensibility::Final => {
548                self.emit_struct_body_reads(def)?;
549            }
550            Extensibility::Appendable => {
551                let _ = writeln!(self.out, "    uint32_t dheader = 0;");
552                let _ = writeln!(
553                    self.out,
554                    "    if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &dheader) != 0) return -7;"
555                );
556                let _ = writeln!(self.out, "    size_t body_end = pos + dheader;");
557                let _ = writeln!(self.out, "    if (body_end > len) return -7;");
558                self.emit_struct_body_reads(def)?;
559                let _ = writeln!(self.out, "    pos = body_end;");
560            }
561            Extensibility::Mutable => {
562                let _ = writeln!(self.out, "    uint32_t dheader = 0;");
563                let _ = writeln!(
564                    self.out,
565                    "    if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &dheader) != 0) return -7;"
566                );
567                let _ = writeln!(self.out, "    size_t body_end = pos + dheader;");
568                let _ = writeln!(self.out, "    if (body_end > len) return -7;");
569                let _ = writeln!(self.out, "    while (pos < body_end) {{");
570                let _ = writeln!(self.out, "        uint32_t emheader = 0;");
571                let _ = writeln!(
572                    self.out,
573                    "        if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &emheader) != 0) return -7;"
574                );
575                let _ = writeln!(self.out, "        uint32_t mid = emheader & 0x0FFFFFFFu;");
576                let _ = writeln!(self.out, "        uint32_t lc = (emheader >> 28) & 0x7u;");
577                let _ = writeln!(self.out, "        uint32_t nextint = 0;");
578                let _ = writeln!(self.out, "        size_t body_len = 0;");
579                let _ = writeln!(
580                    self.out,
581                    "        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 {{"
582                );
583                let _ = writeln!(
584                    self.out,
585                    "            if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &nextint) != 0) return -7;"
586                );
587                let _ = writeln!(self.out, "            body_len = nextint;");
588                let _ = writeln!(self.out, "        }}");
589                let _ = writeln!(
590                    self.out,
591                    "        if (pos + body_len > body_end) return -7;"
592                );
593                self.emit_mutable_member_dispatch(def)?;
594                let _ = writeln!(self.out, "    }}");
595            }
596        }
597        let _ = writeln!(self.out, "    (void)s;");
598        let _ = writeln!(self.out, "    return 0;");
599        let _ = writeln!(self.out, "}}");
600        let _ = writeln!(self.out);
601        Ok(())
602    }
603
604    fn emit_struct_body_reads(&mut self, def: &StructDef) -> Result<(), CppGenError> {
605        for member in &def.members {
606            for decl in &member.declarators {
607                let f = &decl.name().text;
608                self.emit_member_read(&format!("s->{f}"), &member.type_spec)?;
609            }
610        }
611        Ok(())
612    }
613
614    fn emit_member_read(&mut self, var: &str, type_spec: &TypeSpec) -> Result<(), CppGenError> {
615        match type_spec {
616            TypeSpec::Primitive(p) => {
617                let helper = primitive_helper(*p)?;
618                let _ = writeln!(
619                    self.out,
620                    "    if (zerodds_xcdr2_c_read_{helper}(buf, len, &pos, &({var})) != 0) return -7;"
621                );
622                Ok(())
623            }
624            TypeSpec::String(st) => {
625                if st.wide {
626                    return Err(unsupported("wstring"));
627                }
628                let _ = writeln!(
629                    self.out,
630                    "    if (zerodds_xcdr2_c_read_string(buf, len, &pos, &({var})) != 0) return -7;"
631                );
632                Ok(())
633            }
634            TypeSpec::Sequence(seq) => self.emit_sequence_read(var, &seq.elem),
635            _ => Err(unsupported("complex member read")),
636        }
637    }
638
639    fn emit_sequence_read(&mut self, var: &str, elem: &TypeSpec) -> Result<(), CppGenError> {
640        let _ = writeln!(self.out, "    {{");
641        let _ = writeln!(self.out, "        uint32_t seq_len = 0;");
642        let _ = writeln!(
643            self.out,
644            "        if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &seq_len) != 0) return -7;"
645        );
646        let elem_c = c_type_for(elem)?;
647        let _ = writeln!(self.out, "        ({var}).len = seq_len;");
648        let _ = writeln!(
649            self.out,
650            "        ({var}).elems = ({elem_c}*)calloc(seq_len ? seq_len : 1, sizeof({elem_c}));"
651        );
652        let _ = writeln!(
653            self.out,
654            "        if (({var}).elems == NULL && seq_len > 0) return -7;"
655        );
656        let _ = writeln!(
657            self.out,
658            "        for (uint32_t i = 0; i < seq_len; ++i) {{"
659        );
660        match elem {
661            TypeSpec::Primitive(p) => {
662                let helper = primitive_helper(*p)?;
663                let _ = writeln!(
664                    self.out,
665                    "            if (zerodds_xcdr2_c_read_{helper}(buf, len, &pos, &({var}).elems[i]) != 0) return -7;"
666                );
667            }
668            TypeSpec::String(st) => {
669                if st.wide {
670                    return Err(unsupported("wstring"));
671                }
672                let _ = writeln!(
673                    self.out,
674                    "            if (zerodds_xcdr2_c_read_string(buf, len, &pos, &({var}).elems[i]) != 0) return -7;"
675                );
676            }
677            _ => {
678                return Err(unsupported("complex sequence element read"));
679            }
680        }
681        let _ = writeln!(self.out, "        }}");
682        let _ = writeln!(self.out, "    }}");
683        Ok(())
684    }
685
686    fn emit_mutable_member_dispatch(&mut self, def: &StructDef) -> Result<(), CppGenError> {
687        let _ = writeln!(self.out, "        switch (mid) {{");
688        for member in &def.members {
689            let Some(id) = id_annotation(&member.annotations) else {
690                return Err(unsupported("@mutable member without @id(N)"));
691            };
692            for decl in &member.declarators {
693                let f = &decl.name().text;
694                let _ = writeln!(self.out, "        case {id}: {{");
695                self.emit_member_read(&format!("s->{f}"), &member.type_spec)?;
696                let _ = writeln!(self.out, "            break;");
697                let _ = writeln!(self.out, "        }}");
698            }
699        }
700        let _ = writeln!(self.out, "        default: pos += body_len; break;");
701        let _ = writeln!(self.out, "        }}");
702        Ok(())
703    }
704
705    fn emit_free_body(&mut self, c_name: &str, def: &StructDef) {
706        let _ = writeln!(
707            self.out,
708            "static void {c_name}_sample_free(void* sample) {{"
709        );
710        let _ = writeln!(self.out, "    if (sample == NULL) return;");
711        let _ = writeln!(self.out, "    {c_name}_t* s = ({c_name}_t*)sample;");
712        let _ = writeln!(self.out, "    (void)s;");
713        for member in &def.members {
714            for decl in &member.declarators {
715                let f = &decl.name().text;
716                match &member.type_spec {
717                    TypeSpec::String(_) => {
718                        let _ = writeln!(self.out, "    free(s->{f}); s->{f} = NULL;");
719                    }
720                    TypeSpec::Sequence(seq) => match seq.elem.as_ref() {
721                        TypeSpec::String(_) => {
722                            let _ = writeln!(
723                                self.out,
724                                "    for (uint32_t i = 0; i < s->{f}.len; ++i) free(s->{f}.elems[i]);"
725                            );
726                            let _ = writeln!(
727                                self.out,
728                                "    free(s->{f}.elems); s->{f}.elems = NULL; s->{f}.len = 0;"
729                            );
730                        }
731                        _ => {
732                            let _ = writeln!(
733                                self.out,
734                                "    free(s->{f}.elems); s->{f}.elems = NULL; s->{f}.len = 0;"
735                            );
736                        }
737                    },
738                    _ => {}
739                }
740            }
741        }
742        let _ = writeln!(self.out, "}}");
743        let _ = writeln!(self.out);
744    }
745
746    fn emit_key_hash_body(&mut self, c_name: &str, def: &StructDef) {
747        // Spec §7.6.8: sammelt @key-Members in PlainCdr2BeKeyHolder, dann
748        // entweder zero-pad oder MD5. Wir nutzen die XCDR2-C-Helper.
749        let _ = writeln!(
750            self.out,
751            "static int {c_name}_key_hash(const void* sample, uint8_t out_hash[16]) {{"
752        );
753        let _ = writeln!(
754            self.out,
755            "    const {c_name}_t* s = (const {c_name}_t*)sample;"
756        );
757        let _ = writeln!(self.out, "    uint8_t* kh_buf = NULL;");
758        let _ = writeln!(self.out, "    size_t kh_len = 0;");
759        let _ = writeln!(self.out, "    size_t kh_cap = 0;");
760        for member in &def.members {
761            if !is_key(&member.annotations) {
762                continue;
763            }
764            for decl in &member.declarators {
765                let f = &decl.name().text;
766                match &member.type_spec {
767                    TypeSpec::Primitive(p) => {
768                        let helper = match primitive_helper(*p) {
769                            Ok(h) => h,
770                            Err(_) => continue,
771                        };
772                        let _ = writeln!(
773                            self.out,
774                            "    if (zerodds_xcdr2_c_kh_write_{helper}(&kh_buf, &kh_len, &kh_cap, s->{f}) != 0) {{ free(kh_buf); return -1; }}"
775                        );
776                    }
777                    TypeSpec::String(_) => {
778                        let _ = writeln!(
779                            self.out,
780                            "    if (zerodds_xcdr2_c_kh_write_string(&kh_buf, &kh_len, &kh_cap, s->{f}) != 0) {{ free(kh_buf); return -1; }}"
781                        );
782                    }
783                    _ => {}
784                }
785            }
786        }
787        let _ = writeln!(
788            self.out,
789            "    zerodds_xcdr2_c_compute_key_hash(kh_buf, kh_len, /*max_size_static=*/0, out_hash);"
790        );
791        let _ = writeln!(self.out, "    free(kh_buf);");
792        let _ = writeln!(self.out, "    return 0;");
793        let _ = writeln!(self.out, "}}");
794        let _ = writeln!(self.out);
795    }
796}
797
798// ============================================================================
799// Helpers
800// ============================================================================
801
802fn dds_type_name(scope: &[String], name: &str) -> String {
803    if scope.is_empty() {
804        name.to_string()
805    } else {
806        let mut s = scope.join("::");
807        s.push_str("::");
808        s.push_str(name);
809        s
810    }
811}
812
813fn c_identifier(scope: &[String], name: &str) -> String {
814    if scope.is_empty() {
815        name.to_string()
816    } else {
817        let mut s = scope.join("_");
818        s.push('_');
819        s.push_str(name);
820        s
821    }
822}
823
824impl Extensibility {
825    fn as_u8(self) -> u8 {
826        match self {
827            Self::Final => 0,
828            Self::Appendable => 1,
829            Self::Mutable => 2,
830        }
831    }
832}
833
834fn extensibility_of(annotations: &[Annotation]) -> Extensibility {
835    for a in annotations {
836        if let Some(name) = a.name.parts.last() {
837            match name.text.as_str() {
838                "final" | "Final" => return Extensibility::Final,
839                "appendable" | "Appendable" => return Extensibility::Appendable,
840                "mutable" | "Mutable" => return Extensibility::Mutable,
841                _ => {}
842            }
843        }
844    }
845    // Spec-Default (XTypes 1.3 §7.2.2.4.4): appendable.
846    Extensibility::Appendable
847}
848
849fn is_key(annotations: &[Annotation]) -> bool {
850    annotations.iter().any(|a| {
851        a.name
852            .parts
853            .last()
854            .is_some_and(|p| p.text == "key" || p.text == "Key")
855    })
856}
857
858fn struct_has_key(def: &StructDef) -> bool {
859    def.members.iter().any(|m| is_key(&m.annotations))
860}
861
862fn id_annotation(annotations: &[Annotation]) -> Option<u32> {
863    for a in annotations {
864        let last = a.name.parts.last()?;
865        if last.text != "id" && last.text != "Id" {
866            continue;
867        }
868        if let AnnotationParams::Single(ConstExpr::Literal(lit)) = &a.params {
869            if lit.kind == LiteralKind::Integer {
870                if let Ok(v) = lit.raw.parse::<u32>() {
871                    return Some(v);
872                }
873            }
874        }
875    }
876    None
877}
878/// zerodds-lint: recursion-depth 64 (c_type_for bounded by AST depth)
879fn c_type_for(type_spec: &TypeSpec) -> Result<String, CppGenError> {
880    let s = match type_spec {
881        TypeSpec::Primitive(p) => match p {
882            PrimitiveType::Boolean => "uint8_t",
883            PrimitiveType::Octet => "uint8_t",
884            PrimitiveType::Char => "char",
885            PrimitiveType::WideChar => "uint16_t",
886            PrimitiveType::Integer(IntegerType::Short)
887            | PrimitiveType::Integer(IntegerType::Int16) => "int16_t",
888            PrimitiveType::Integer(IntegerType::UShort)
889            | PrimitiveType::Integer(IntegerType::UInt16) => "uint16_t",
890            PrimitiveType::Integer(IntegerType::Long)
891            | PrimitiveType::Integer(IntegerType::Int32) => "int32_t",
892            PrimitiveType::Integer(IntegerType::ULong)
893            | PrimitiveType::Integer(IntegerType::UInt32) => "uint32_t",
894            PrimitiveType::Integer(IntegerType::LongLong)
895            | PrimitiveType::Integer(IntegerType::Int64) => "int64_t",
896            PrimitiveType::Integer(IntegerType::ULongLong)
897            | PrimitiveType::Integer(IntegerType::UInt64) => "uint64_t",
898            PrimitiveType::Integer(IntegerType::Int8) => "int8_t",
899            PrimitiveType::Integer(IntegerType::UInt8) => "uint8_t",
900            PrimitiveType::Floating(FloatingType::Float) => "float",
901            PrimitiveType::Floating(FloatingType::Double) => "double",
902            PrimitiveType::Floating(FloatingType::LongDouble) => {
903                return Err(unsupported("long double"));
904            }
905        },
906        TypeSpec::String(st) => {
907            if st.wide {
908                return Err(unsupported("wstring"));
909            }
910            "char*"
911        }
912        TypeSpec::Sequence(seq) => {
913            let elem = c_type_for(&seq.elem)?;
914            return Ok(format!("struct {{ uint32_t len; {elem}* elems; }}"));
915        }
916        _ => {
917            return Err(unsupported("non-primitive type in field"));
918        }
919    };
920    Ok(s.to_string())
921}
922
923fn primitive_helper(p: PrimitiveType) -> Result<&'static str, CppGenError> {
924    Ok(match p {
925        PrimitiveType::Boolean | PrimitiveType::Octet => "u8",
926        PrimitiveType::Char => "u8",
927        PrimitiveType::WideChar => "u16",
928        PrimitiveType::Integer(IntegerType::Short) | PrimitiveType::Integer(IntegerType::Int16) => {
929            "i16"
930        }
931        PrimitiveType::Integer(IntegerType::UShort)
932        | PrimitiveType::Integer(IntegerType::UInt16) => "u16",
933        PrimitiveType::Integer(IntegerType::Long) | PrimitiveType::Integer(IntegerType::Int32) => {
934            "i32"
935        }
936        PrimitiveType::Integer(IntegerType::ULong)
937        | PrimitiveType::Integer(IntegerType::UInt32) => "u32",
938        PrimitiveType::Integer(IntegerType::LongLong)
939        | PrimitiveType::Integer(IntegerType::Int64) => "i64",
940        PrimitiveType::Integer(IntegerType::ULongLong)
941        | PrimitiveType::Integer(IntegerType::UInt64) => "u64",
942        PrimitiveType::Integer(IntegerType::Int8) => "i8",
943        PrimitiveType::Integer(IntegerType::UInt8) => "u8",
944        PrimitiveType::Floating(FloatingType::Float) => "f32",
945        PrimitiveType::Floating(FloatingType::Double) => "f64",
946        PrimitiveType::Floating(FloatingType::LongDouble) => {
947            return Err(unsupported("long double"));
948        }
949    })
950}
951
952#[cfg(test)]
953mod tests {
954    #![allow(clippy::expect_used)]
955    use super::*;
956    use zerodds_idl::config::ParserConfig;
957
958    fn gen_c(src: &str) -> String {
959        let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse ok");
960        generate_c_header(&ast, &CGenOptions::default()).expect("c-gen ok")
961    }
962
963    #[test]
964    fn empty_final_struct_emits_typedef_and_typesupport() {
965        let h = gen_c("@final struct Empty {};");
966        assert!(h.contains("typedef struct Empty_s"));
967        assert!(h.contains("Empty_typesupport"));
968        assert!(h.contains(".extensibility = 0"));
969    }
970
971    #[test]
972    fn primitive_struct_maps_types() {
973        let h = gen_c("@final struct Point { long x; long y; };");
974        assert!(h.contains("int32_t x;"));
975        assert!(h.contains("int32_t y;"));
976        assert!(h.contains("\"Point\""));
977    }
978
979    #[test]
980    fn nested_module_yields_scoped_type_name() {
981        let h = gen_c("module Outer { module Inner { @final struct S { long x; }; }; };");
982        assert!(h.contains("typedef struct Outer_Inner_S_s"));
983        assert!(h.contains("\"Outer::Inner::S\""));
984    }
985
986    #[test]
987    fn appendable_default_when_no_annotation() {
988        let h = gen_c("struct V { long a; long b; };");
989        assert!(h.contains(".extensibility = 1"));
990    }
991
992    #[test]
993    fn mutable_struct_marks_extensibility() {
994        let h = gen_c("@mutable struct M { @id(1) long a; };");
995        assert!(h.contains(".extensibility = 2"));
996    }
997
998    #[test]
999    fn key_member_sets_is_keyed_and_emits_key_hash() {
1000        let h = gen_c("@final struct Sensor { @key long id; double value; };");
1001        assert!(h.contains(".is_keyed = 1"));
1002        assert!(h.contains("Sensor_key_hash"));
1003    }
1004}