Skip to main content

zerodds_idl_cpp/
emitter.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! AST-Walker, der C++17-Header emittiert.
4//!
5//! Block-A: Header-Layout (`#pragma once`, includes, namespaces).
6//! Block-B: Primitive-Mapping (delegiert an [`crate::type_map`]).
7//! Block-C: struct/enum/union/typedef/sequence/array/inheritance.
8//! Block-D: Exception → `class X : public std::exception`.
9//! Block-E: Time/Duration über DDS::Time_t / DDS::Duration_t.
10//!
11//! Die Emission ist single-pass: zuerst werden alle benoetigten
12//! Standard-Includes durch einen pre-walk gesammelt, dann der Body
13//! emittiert. So bleibt die Header-Praeambel deterministisch
14//! (alphabetisch sortiert).
15
16use std::collections::BTreeSet;
17use std::fmt::Write;
18
19use zerodds_idl::ast::{
20    Annotation, AnnotationParams, Case, CaseLabel, ConstExpr, ConstrTypeDecl, Declarator,
21    Definition, EnumDef, ExceptDecl, Export, InterfaceDcl, InterfaceDef, Literal, LiteralKind,
22    Member, ModuleDef, OpDecl, ParamAttribute, PrimitiveType, ScopedName, Specification,
23    StateVisibility, StructDcl, StructDef, SwitchTypeSpec, TypeDecl, TypeSpec, TypedefDecl,
24    UnionDcl, UnionDef, ValueDef, ValueElement,
25};
26
27use zerodds_idl::semantics::annotations::PlacementKind;
28
29use crate::bitset::{emit_bitmask, emit_bitset};
30use crate::error::CppGenError;
31use crate::type_map::{check_identifier, is_reserved, primitive_to_cpp};
32use crate::verbatim::emit_verbatim_at;
33use crate::{CppGenOptions, TIME_DURATION_TYPES};
34
35/// Bekannte Header-Includes. Reihenfolge ist stabil-alphabetisch.
36#[derive(Debug, Default, Clone)]
37struct Includes {
38    headers: BTreeSet<&'static str>,
39}
40
41impl Includes {
42    fn add(&mut self, h: &'static str) {
43        self.headers.insert(h);
44    }
45}
46
47/// Haupt-Entry: erzeugt den vollstaendigen C++-Header.
48pub(crate) fn emit_header(
49    spec: &Specification,
50    opts: &CppGenOptions,
51) -> Result<String, CppGenError> {
52    // 1. Kollidierende Inheritance-Cycles erkennen (vor Emission).
53    detect_inheritance_cycles(spec)?;
54
55    // 2. Walk pre-pass: includes sammeln.
56    let mut includes = Includes::default();
57    includes.add("<cstdint>"); // stdint.h ist immer praesent (uint8_t etc.).
58    collect_includes(spec, &mut includes);
59
60    // 3. Output-Buffer.
61    let mut out = String::new();
62    write_header_preamble(&mut out, opts, &includes)?;
63
64    // 4. Optionale Top-Level-Namespace-Einbindung (`namespace_prefix`).
65    let mut ctx = EmitCtx::new(opts);
66    let outer_prefix: Option<&str> = opts.namespace_prefix.as_deref().filter(|p| !p.is_empty());
67    if let Some(prefix) = outer_prefix {
68        ctx.open_namespace(&mut out, prefix)?;
69    }
70
71    // 4b. §7.2.2.4.8 — `@verbatim(placement=BEGIN_FILE)` aus allen
72    // Top-Level-Defs. Spec sagt nichts ueber Reihenfolge bei mehreren —
73    // wir nehmen Source-Order.
74    for d in &spec.definitions {
75        if let Some(anns) = top_level_annotations(d) {
76            emit_verbatim_at(&mut out, "", anns, PlacementKind::BeginFile)?;
77        }
78    }
79
80    // 4c. Wenn das Spec mindestens eine Top-Level- oder Modul-nested
81    //     `struct`-Definition enthaelt, emittieren wir nach den
82    //     Standard-Includes auch `dds/topic/TopicTraits.hpp` — dort lebt
83    //     `topic_type_support<T>` und ByteSeq/string-Defaults — sowie
84    //     `dds/topic/xcdr2.hpp` und `dds/topic/xcdr2_md5.hpp` fuer die
85    //     XCDR2 Wire-Encoder/MD5-Helpers, auf denen die spaeter
86    //     emittierten Spezialisierungen aufsetzen.
87    let mut probe_structs: Vec<(String, &StructDef)> = Vec::new();
88    collect_topic_structs(&spec.definitions, "", &mut probe_structs);
89    if !probe_structs.is_empty() {
90        // <array> wird von key_hash() benoetigt, <vector>/<string> von
91        // encode/decode(). Falls die Standard-Walks die nicht schon eingezogen
92        // haben, ueber TopicTraits.hpp transitiv abgedeckt — aber wir
93        // emittieren sie hier explizit, damit der Header ohne Topic-Helpers
94        // noch syntaktisch valid bleibt.
95        writeln!(&mut out, "#include \"dds/topic/TopicTraits.hpp\"").map_err(fmt_err)?;
96        writeln!(&mut out, "#include \"dds/topic/xcdr2.hpp\"").map_err(fmt_err)?;
97        writeln!(&mut out, "#include \"dds/topic/xcdr2_md5.hpp\"").map_err(fmt_err)?;
98        writeln!(&mut out).map_err(fmt_err)?;
99    }
100
101    // 5. Definitions emittieren.
102    for d in &spec.definitions {
103        emit_definition(&mut out, &mut ctx, d)?;
104    }
105
106    // 5b. §7.2.2.4.8 — `@verbatim(placement=END_FILE)` aus allen
107    // Top-Level-Defs.
108    for d in &spec.definitions {
109        if let Some(anns) = top_level_annotations(d) {
110            emit_verbatim_at(&mut out, "", anns, PlacementKind::EndFile)?;
111        }
112    }
113
114    // 6. Optional: Top-Level-Namespace schliessen.
115    if let Some(prefix) = outer_prefix {
116        ctx.close_namespace(&mut out, prefix)?;
117    }
118
119    // 7. `topic_type_support<T>`-Spezialisierungen fuer alle struct-Defs.
120    //    Lebt im `::dds::topic`-Namespace (globaler Scope), daher nach
121    //    `outer_prefix`-Close emittiert mit voll-qualifiziertem T.
122    if !probe_structs.is_empty() {
123        emit_topic_type_support_specs(&mut out, opts, &probe_structs)?;
124    }
125
126    Ok(out)
127}
128
129/// Liefert die Annotation-Liste eines Top-Level-`Definition`s, falls
130/// die Variante eine traegt. Wird fuer File-Level-Verbatim genutzt.
131fn top_level_annotations(d: &Definition) -> Option<&[Annotation]> {
132    match d {
133        Definition::Module(m) => Some(&m.annotations),
134        Definition::Type(TypeDecl::Constr(c)) => match c {
135            ConstrTypeDecl::Struct(StructDcl::Def(s)) => Some(&s.annotations),
136            ConstrTypeDecl::Union(UnionDcl::Def(u)) => Some(&u.annotations),
137            ConstrTypeDecl::Enum(e) => Some(&e.annotations),
138            _ => None,
139        },
140        Definition::Type(TypeDecl::Typedef(t)) => Some(&t.annotations),
141        Definition::Const(c) => Some(&c.annotations),
142        Definition::Except(e) => Some(&e.annotations),
143        _ => None,
144    }
145}
146
147/// Schreibt `// Generated...`, `#pragma once` und Standard-Includes.
148fn write_header_preamble(
149    out: &mut String,
150    opts: &CppGenOptions,
151    includes: &Includes,
152) -> Result<(), CppGenError> {
153    writeln!(out, "// Generated by zerodds idl-cpp. Do not edit.").map_err(fmt_err)?;
154    writeln!(out, "#pragma once").map_err(fmt_err)?;
155    if let Some(guard) = opts.include_guard_prefix.as_deref() {
156        if !guard.is_empty() {
157            // Optionaler Guard-Kommentar fuer Tools, die nur klassische
158            // include-Guards akzeptieren. `#pragma once` bleibt primaer.
159            writeln!(out, "// guard-prefix: {guard}").map_err(fmt_err)?;
160        }
161    }
162    writeln!(out).map_err(fmt_err)?;
163    for h in &includes.headers {
164        writeln!(out, "#include {h}").map_err(fmt_err)?;
165    }
166    writeln!(out).map_err(fmt_err)?;
167    Ok(())
168}
169
170/// Walkt den AST und sammelt benoetigte `<...>`-Includes.
171fn collect_includes(spec: &Specification, inc: &mut Includes) {
172    for d in &spec.definitions {
173        collect_in_def(d, inc);
174    }
175}
176
177/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
178fn collect_in_def(d: &Definition, inc: &mut Includes) {
179    match d {
180        Definition::Module(m) => {
181            for sub in &m.definitions {
182                collect_in_def(sub, inc);
183            }
184        }
185        Definition::Type(td) => collect_in_typedecl(td, inc),
186        Definition::Const(_) => {}
187        Definition::Except(e) => {
188            inc.add("<exception>");
189            for m in &e.members {
190                collect_in_typespec(&m.type_spec, inc);
191                for decl in &m.declarators {
192                    if matches!(decl, Declarator::Array(_)) {
193                        inc.add("<array>");
194                    }
195                }
196            }
197        }
198        Definition::Interface(_)
199        | Definition::ValueBox(_)
200        | Definition::ValueForward(_)
201        | Definition::ValueDef(_)
202        | Definition::TypeId(_)
203        | Definition::TypePrefix(_)
204        | Definition::Import(_)
205        | Definition::Component(_)
206        | Definition::Home(_)
207        | Definition::Event(_)
208        | Definition::Porttype(_)
209        | Definition::Connector(_)
210        | Definition::TemplateModule(_)
211        | Definition::TemplateModuleInst(_)
212        | Definition::Annotation(_)
213        | Definition::VendorExtension(_) => {
214            // Im emit_definition als
215            // UnsupportedConstruct erkannt; hier kein Include sammeln.
216        }
217    }
218}
219
220fn collect_in_typedecl(td: &TypeDecl, inc: &mut Includes) {
221    match td {
222        TypeDecl::Constr(c) => match c {
223            ConstrTypeDecl::Struct(StructDcl::Def(s)) => {
224                for m in &s.members {
225                    collect_in_typespec(&m.type_spec, inc);
226                    for decl in &m.declarators {
227                        if matches!(decl, Declarator::Array(_)) {
228                            inc.add("<array>");
229                        }
230                    }
231                    if has_optional_annotation(&m.annotations) {
232                        inc.add("<optional>");
233                    }
234                    if has_shared_annotation(&m.annotations) {
235                        inc.add("<memory>");
236                    }
237                }
238            }
239            ConstrTypeDecl::Struct(StructDcl::Forward(_)) => {}
240            ConstrTypeDecl::Union(UnionDcl::Def(u)) => {
241                inc.add("<variant>");
242                for c in &u.cases {
243                    collect_in_typespec(&c.element.type_spec, inc);
244                    if matches!(c.element.declarator, Declarator::Array(_)) {
245                        inc.add("<array>");
246                    }
247                }
248            }
249            ConstrTypeDecl::Union(UnionDcl::Forward(_)) => {}
250            ConstrTypeDecl::Enum(_) | ConstrTypeDecl::Bitset(_) | ConstrTypeDecl::Bitmask(_) => {}
251        },
252        TypeDecl::Typedef(t) => {
253            collect_in_typespec(&t.type_spec, inc);
254            for decl in &t.declarators {
255                if matches!(decl, Declarator::Array(_)) {
256                    inc.add("<array>");
257                }
258            }
259        }
260    }
261}
262
263/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
264fn collect_in_typespec(ts: &TypeSpec, inc: &mut Includes) {
265    match ts {
266        TypeSpec::Primitive(_) => {}
267        TypeSpec::Scoped(_) => {}
268        TypeSpec::Sequence(s) => {
269            inc.add("<vector>");
270            collect_in_typespec(&s.elem, inc);
271        }
272        TypeSpec::String(_) => {
273            // sowohl `string` als auch `wstring` sind in `<string>` definiert.
274            inc.add("<string>");
275        }
276        TypeSpec::Map(m) => {
277            inc.add("<map>");
278            collect_in_typespec(&m.key, inc);
279            collect_in_typespec(&m.value, inc);
280        }
281        TypeSpec::Fixed(_) => {
282            // §7.2.4.2.4 — `fixed<digits, scale>` -> `dds::core::Fixed`.
283            // Wir emittieren eine forward-deklarierte Wrapper-Klasse
284            // (Runtime-Implementierung kommt mit `dds-core`-Crate).
285            inc.add("<cstdint>");
286        }
287        TypeSpec::Any => {
288            // §7.3 — `any` -> `dds::core::Any`. Runtime in dds-core.
289            inc.add("<cstdint>");
290        }
291    }
292}
293
294/// Emit-Kontext (Indentation, Output).
295struct EmitCtx<'o> {
296    opts: &'o CppGenOptions,
297    indent_level: usize,
298}
299
300impl<'o> EmitCtx<'o> {
301    fn new(opts: &'o CppGenOptions) -> Self {
302        Self {
303            opts,
304            indent_level: 0,
305        }
306    }
307
308    fn indent(&self) -> String {
309        " ".repeat(self.indent_level * self.opts.indent_width)
310    }
311
312    fn open_namespace(&mut self, out: &mut String, name: &str) -> Result<(), CppGenError> {
313        writeln!(out, "{}namespace {name} {{", self.indent()).map_err(fmt_err)?;
314        self.indent_level += 1;
315        Ok(())
316    }
317
318    fn close_namespace(&mut self, out: &mut String, name: &str) -> Result<(), CppGenError> {
319        self.indent_level = self.indent_level.saturating_sub(1);
320        writeln!(out, "{}}} // namespace {name}", self.indent()).map_err(fmt_err)?;
321        Ok(())
322    }
323}
324
325/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
326fn emit_definition(
327    out: &mut String,
328    ctx: &mut EmitCtx<'_>,
329    def: &Definition,
330) -> Result<(), CppGenError> {
331    match def {
332        Definition::Module(m) => emit_module(out, ctx, m),
333        Definition::Type(td) => emit_type_decl(out, ctx, td),
334        Definition::Const(c) => emit_const_decl(out, ctx, c),
335        Definition::Except(e) => emit_exception(out, ctx, e),
336        Definition::Interface(InterfaceDcl::Def(iface)) => {
337            // Spec idl4-cpp §7.4: IDL interface -> C++ pure-virtual class.
338            // `@service`-Interfaces gehen weiter ueber den RPC-Codegen-
339            // Pfad (siehe `crate::rpc`); hier ist der Legacy-CORBA-Stub-
340            // Pfad fuer non-service Interfaces.
341            emit_interface_stub(out, ctx, iface)
342        }
343        Definition::Interface(InterfaceDcl::Forward(f)) => {
344            check_identifier(&f.name.text)?;
345            writeln!(out, "{}class {};", ctx.indent(), f.name.text).map_err(fmt_err)?;
346            Ok(())
347        }
348        Definition::ValueDef(v) => emit_value_type(out, ctx, v),
349        Definition::ValueBox(_) | Definition::ValueForward(_) => {
350            // ValueBox + ValueForward sind seltene CORBA-Konstrukte;
351            // Foundation laesst sie als no-op (Spec §7.6.x erlaubt
352            // forward-decl mit spaeterer Resolution).
353            Ok(())
354        }
355        Definition::TypeId(_)
356        | Definition::TypePrefix(_)
357        | Definition::Import(_)
358        | Definition::Component(_)
359        | Definition::Home(_)
360        | Definition::Event(_)
361        | Definition::Porttype(_)
362        | Definition::Connector(_)
363        | Definition::TemplateModule(_)
364        | Definition::TemplateModuleInst(_) => Err(CppGenError::UnsupportedConstruct {
365            construct: "corba/ccm/template construct".into(),
366            context: None,
367        }),
368        Definition::Annotation(_) => {
369            // §7.4.15 Annotation-Defs werden nicht direkt zu C++-Code —
370            // Anwendungen werden bei den annotierten Mitgliedern via
371            // Annotation-Bridges (z.B. @key) emittiert.
372            Ok(())
373        }
374        Definition::VendorExtension(v) => Err(CppGenError::UnsupportedConstruct {
375            construct: format!("vendor-extension:{}", v.production_name),
376            context: None,
377        }),
378    }
379}
380
381/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
382fn emit_module(out: &mut String, ctx: &mut EmitCtx<'_>, m: &ModuleDef) -> Result<(), CppGenError> {
383    check_identifier(&m.name.text)?;
384    ctx.open_namespace(out, &m.name.text)?;
385    for d in &m.definitions {
386        emit_definition(out, ctx, d)?;
387    }
388    ctx.close_namespace(out, &m.name.text)?;
389    Ok(())
390}
391
392fn emit_type_decl(
393    out: &mut String,
394    ctx: &mut EmitCtx<'_>,
395    td: &TypeDecl,
396) -> Result<(), CppGenError> {
397    match td {
398        TypeDecl::Constr(c) => match c {
399            ConstrTypeDecl::Struct(StructDcl::Def(s)) => emit_struct(out, ctx, s),
400            ConstrTypeDecl::Struct(StructDcl::Forward(f)) => {
401                check_identifier(&f.name.text)?;
402                writeln!(out, "{}class {};", ctx.indent(), f.name.text).map_err(fmt_err)?;
403                Ok(())
404            }
405            ConstrTypeDecl::Union(UnionDcl::Def(u)) => emit_union(out, ctx, u),
406            ConstrTypeDecl::Union(UnionDcl::Forward(f)) => {
407                check_identifier(&f.name.text)?;
408                writeln!(out, "{}class {};", ctx.indent(), f.name.text).map_err(fmt_err)?;
409                Ok(())
410            }
411            ConstrTypeDecl::Enum(e) => emit_enum(out, ctx, e),
412            ConstrTypeDecl::Bitset(b) => {
413                check_identifier(&b.name.text)?;
414                let ind = ctx.indent();
415                let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
416                emit_bitset(out, &ind, &inner, b)
417            }
418            ConstrTypeDecl::Bitmask(b) => {
419                check_identifier(&b.name.text)?;
420                let ind = ctx.indent();
421                let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
422                emit_bitmask(out, &ind, &inner, b)
423            }
424        },
425        TypeDecl::Typedef(t) => emit_typedef(out, ctx, t),
426    }
427}
428
429fn emit_struct(out: &mut String, ctx: &mut EmitCtx<'_>, s: &StructDef) -> Result<(), CppGenError> {
430    check_identifier(&s.name.text)?;
431    let ind = ctx.indent();
432
433    // §7.2.2.4.8 — `@verbatim(placement=BEFORE_DECLARATION)` vor der
434    // Class-Header-Zeile.
435    emit_verbatim_at(out, &ind, &s.annotations, PlacementKind::BeforeDeclaration)?;
436
437    // Class-Header mit optionaler Inheritance-Klausel.
438    if let Some(base) = &s.base {
439        let base_str = scoped_to_cpp(base);
440        writeln!(out, "{ind}class {} : public {} {{", s.name.text, base_str).map_err(fmt_err)?;
441    } else {
442        writeln!(out, "{ind}class {} {{", s.name.text).map_err(fmt_err)?;
443    }
444    writeln!(out, "{ind}public:").map_err(fmt_err)?;
445
446    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
447
448    // §7.2.2.4.8 — `@verbatim(placement=BEGIN_DECLARATION)` als erste
449    // Zeile innerhalb des `public:`-Blocks.
450    emit_verbatim_at(out, &inner, &s.annotations, PlacementKind::BeginDeclaration)?;
451
452    // Default-Konstruktor.
453    writeln!(out, "{inner}{}() = default;", s.name.text).map_err(fmt_err)?;
454    writeln!(out, "{inner}~{}() = default;", s.name.text).map_err(fmt_err)?;
455    writeln!(out).map_err(fmt_err)?;
456
457    // Member-Felder als private Storage.
458    writeln!(out, "{ind}private:").map_err(fmt_err)?;
459    for m in &s.members {
460        emit_struct_member_field(out, ctx, m)?;
461    }
462    writeln!(out).map_err(fmt_err)?;
463
464    // Reference-Pattern Getter (mutable + const).
465    writeln!(out, "{ind}public:").map_err(fmt_err)?;
466    for m in &s.members {
467        emit_struct_member_accessors(out, ctx, m)?;
468    }
469
470    // §7.2.2.4.8 — `@verbatim(placement=END_DECLARATION)` als letzte
471    // Zeile vor `};`.
472    emit_verbatim_at(out, &inner, &s.annotations, PlacementKind::EndDeclaration)?;
473
474    writeln!(out, "{ind}}};").map_err(fmt_err)?;
475
476    // §7.2.2.4.8 — `@verbatim(placement=AFTER_DECLARATION)` direkt
477    // hinter dem schliessenden `};`.
478    emit_verbatim_at(out, &ind, &s.annotations, PlacementKind::AfterDeclaration)?;
479
480    writeln!(out).map_err(fmt_err)?;
481    Ok(())
482}
483
484fn emit_struct_member_field(
485    out: &mut String,
486    ctx: &EmitCtx<'_>,
487    m: &Member,
488) -> Result<(), CppGenError> {
489    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
490    let optional = has_optional_annotation(&m.annotations);
491    let shared = has_shared_annotation(&m.annotations);
492    for decl in &m.declarators {
493        let cpp_ty = type_for_declarator(&m.type_spec, decl)?;
494        let name = decl.name();
495        check_identifier(&name.text)?;
496        let key_marker = if has_key_annotation(&m.annotations) {
497            " // @key"
498        } else {
499            ""
500        };
501        // §8.1.5 `@shared` -> `std::shared_ptr<T>`. Kombination mit
502        // `@optional` ergibt `std::optional<std::shared_ptr<T>>`.
503        let core_ty = if shared {
504            format!("std::shared_ptr<{cpp_ty}>")
505        } else {
506            cpp_ty
507        };
508        if optional {
509            writeln!(
510                out,
511                "{inner}std::optional<{core_ty}> {}_;{key_marker}",
512                name.text
513            )
514            .map_err(fmt_err)?;
515        } else {
516            writeln!(out, "{inner}{core_ty} {}_;{key_marker}", name.text).map_err(fmt_err)?;
517        }
518    }
519    Ok(())
520}
521
522fn emit_struct_member_accessors(
523    out: &mut String,
524    ctx: &EmitCtx<'_>,
525    m: &Member,
526) -> Result<(), CppGenError> {
527    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
528    let optional = has_optional_annotation(&m.annotations);
529    let shared = has_shared_annotation(&m.annotations);
530    for decl in &m.declarators {
531        let cpp_ty = type_for_declarator(&m.type_spec, decl)?;
532        let name = &decl.name().text;
533        let core_ty = if shared {
534            format!("std::shared_ptr<{cpp_ty}>")
535        } else {
536            cpp_ty.clone()
537        };
538        let storage_ty = if optional {
539            format!("std::optional<{core_ty}>")
540        } else {
541            core_ty
542        };
543        writeln!(out, "{inner}{storage_ty}& {name}() {{ return {name}_; }}").map_err(fmt_err)?;
544        writeln!(
545            out,
546            "{inner}const {storage_ty}& {name}() const {{ return {name}_; }}"
547        )
548        .map_err(fmt_err)?;
549        writeln!(
550            out,
551            "{inner}void {name}(const {storage_ty}& value) {{ {name}_ = value; }}"
552        )
553        .map_err(fmt_err)?;
554    }
555    Ok(())
556}
557
558fn emit_union(out: &mut String, ctx: &mut EmitCtx<'_>, u: &UnionDef) -> Result<(), CppGenError> {
559    check_identifier(&u.name.text)?;
560    let ind = ctx.indent();
561    emit_verbatim_at(out, &ind, &u.annotations, PlacementKind::BeforeDeclaration)?;
562    writeln!(out, "{ind}class {} {{", u.name.text).map_err(fmt_err)?;
563    writeln!(out, "{ind}public:").map_err(fmt_err)?;
564    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
565    emit_verbatim_at(out, &inner, &u.annotations, PlacementKind::BeginDeclaration)?;
566
567    let disc_ty = switch_type_to_cpp(&u.switch_type)?;
568
569    // Variant-Liste aus distinct Element-Typen aufbauen.
570    let mut variant_types: Vec<String> = Vec::new();
571    for c in &u.cases {
572        let cpp_ty = type_for_declarator(&c.element.type_spec, &c.element.declarator)?;
573        if !variant_types.iter().any(|t| t == &cpp_ty) {
574            variant_types.push(cpp_ty);
575        }
576    }
577    let variant_str = if variant_types.is_empty() {
578        "std::monostate".to_string()
579    } else {
580        variant_types.join(", ")
581    };
582
583    writeln!(
584        out,
585        "{inner}using value_type = std::variant<{variant_str}>;"
586    )
587    .map_err(fmt_err)?;
588    writeln!(out, "{inner}{}() = default;", u.name.text).map_err(fmt_err)?;
589    writeln!(out, "{inner}~{}() = default;", u.name.text).map_err(fmt_err)?;
590    writeln!(out).map_err(fmt_err)?;
591
592    // Discriminator.
593    writeln!(
594        out,
595        "{inner}{disc_ty} _d() const {{ return discriminator_; }}"
596    )
597    .map_err(fmt_err)?;
598    writeln!(out, "{inner}void _d({disc_ty} d) {{ discriminator_ = d; }}").map_err(fmt_err)?;
599    writeln!(out, "{inner}value_type& value() {{ return value_; }}").map_err(fmt_err)?;
600    writeln!(
601        out,
602        "{inner}const value_type& value() const {{ return value_; }}"
603    )
604    .map_err(fmt_err)?;
605    writeln!(out).map_err(fmt_err)?;
606
607    // Branch-Marker als Kommentare (Discriminator-Werte).
608    let mut has_default = false;
609    for c in &u.cases {
610        emit_union_case_comment(out, &inner, c, &mut has_default)?;
611    }
612    if !has_default {
613        writeln!(out, "{inner}// no explicit 'default:' branch").map_err(fmt_err)?;
614    }
615
616    writeln!(out).map_err(fmt_err)?;
617    writeln!(out, "{ind}private:").map_err(fmt_err)?;
618    writeln!(out, "{inner}{disc_ty} discriminator_{{}};").map_err(fmt_err)?;
619    writeln!(out, "{inner}value_type value_{{}};").map_err(fmt_err)?;
620    emit_verbatim_at(out, &inner, &u.annotations, PlacementKind::EndDeclaration)?;
621    writeln!(out, "{ind}}};").map_err(fmt_err)?;
622    emit_verbatim_at(out, &ind, &u.annotations, PlacementKind::AfterDeclaration)?;
623    writeln!(out).map_err(fmt_err)?;
624    Ok(())
625}
626
627fn emit_union_case_comment(
628    out: &mut String,
629    inner: &str,
630    c: &Case,
631    has_default: &mut bool,
632) -> Result<(), CppGenError> {
633    for label in &c.labels {
634        match label {
635            CaseLabel::Default => {
636                *has_default = true;
637                writeln!(
638                    out,
639                    "{inner}// case default -> {}",
640                    declarator_name(&c.element.declarator)
641                )
642                .map_err(fmt_err)?;
643            }
644            CaseLabel::Value(expr) => {
645                let val = const_expr_to_cpp(expr);
646                writeln!(
647                    out,
648                    "{inner}// case {val} -> {}",
649                    declarator_name(&c.element.declarator)
650                )
651                .map_err(fmt_err)?;
652            }
653        }
654    }
655    Ok(())
656}
657
658pub(crate) fn declarator_name(d: &Declarator) -> &str {
659    &d.name().text
660}
661
662fn emit_enum(out: &mut String, ctx: &mut EmitCtx<'_>, e: &EnumDef) -> Result<(), CppGenError> {
663    check_identifier(&e.name.text)?;
664    let ind = ctx.indent();
665    emit_verbatim_at(out, &ind, &e.annotations, PlacementKind::BeforeDeclaration)?;
666    writeln!(out, "{ind}enum class {} : int32_t {{", e.name.text).map_err(fmt_err)?;
667    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
668    emit_verbatim_at(out, &inner, &e.annotations, PlacementKind::BeginDeclaration)?;
669    for en in &e.enumerators {
670        check_identifier(&en.name.text)?;
671        writeln!(out, "{inner}{},", en.name.text).map_err(fmt_err)?;
672    }
673    emit_verbatim_at(out, &inner, &e.annotations, PlacementKind::EndDeclaration)?;
674    writeln!(out, "{ind}}};").map_err(fmt_err)?;
675    emit_verbatim_at(out, &ind, &e.annotations, PlacementKind::AfterDeclaration)?;
676    writeln!(out).map_err(fmt_err)?;
677    Ok(())
678}
679
680fn emit_interface_stub(
681    out: &mut String,
682    ctx: &mut EmitCtx<'_>,
683    iface: &InterfaceDef,
684) -> Result<(), CppGenError> {
685    let name = &iface.name.text;
686    check_identifier(name)?;
687    let ind = ctx.indent();
688    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
689
690    emit_verbatim_at(
691        out,
692        &ind,
693        &iface.annotations,
694        PlacementKind::BeforeDeclaration,
695    )?;
696
697    // Bases via public virtual inheritance (CORBA-Pattern fuer Diamond).
698    if iface.bases.is_empty() {
699        writeln!(out, "{ind}class {name} {{").map_err(fmt_err)?;
700    } else {
701        let bases: Vec<String> = iface
702            .bases
703            .iter()
704            .map(|b| format!("public virtual {}", scoped_to_cpp(b)))
705            .collect();
706        writeln!(out, "{ind}class {name} : {} {{", bases.join(", ")).map_err(fmt_err)?;
707    }
708    writeln!(out, "{ind}public:").map_err(fmt_err)?;
709    writeln!(out, "{inner}virtual ~{name}() = default;").map_err(fmt_err)?;
710
711    for export in &iface.exports {
712        match export {
713            Export::Op(op) => emit_interface_op(out, &inner, op)?,
714            Export::Attr(attr) => emit_interface_attr(out, &inner, attr)?,
715            Export::Type(td) => emit_type_decl(out, ctx, td)?,
716            Export::Const(c) => emit_const_decl(out, ctx, c)?,
717            Export::Except(e) => emit_exception(out, ctx, e)?,
718        }
719    }
720
721    writeln!(out, "{ind}}};").map_err(fmt_err)?;
722    emit_verbatim_at(
723        out,
724        &ind,
725        &iface.annotations,
726        PlacementKind::AfterDeclaration,
727    )?;
728    writeln!(out).map_err(fmt_err)?;
729    Ok(())
730}
731
732fn emit_interface_op(out: &mut String, inner: &str, op: &OpDecl) -> Result<(), CppGenError> {
733    check_identifier(&op.name.text)?;
734    let ret = match &op.return_type {
735        None => "void".to_string(),
736        Some(t) => typespec_to_cpp(t)?,
737    };
738    let params: Vec<String> = op
739        .params
740        .iter()
741        .map(|p| -> Result<String, CppGenError> {
742            let ty = typespec_to_cpp(&p.type_spec)?;
743            // Spec §7.4.5: in -> const T& (oder T fuer primitives),
744            // out/inout -> T&. Fuer Foundation: const T&/T& konsistent.
745            let qual = match p.attribute {
746                ParamAttribute::In => format!("const {ty}&"),
747                ParamAttribute::Out | ParamAttribute::InOut => format!("{ty}&"),
748            };
749            Ok(format!("{qual} {}", p.name.text))
750        })
751        .collect::<Result<_, _>>()?;
752    let raises_comment = if op.raises.is_empty() {
753        String::new()
754    } else {
755        let raises: Vec<String> = op.raises.iter().map(scoped_to_cpp).collect();
756        format!(" /* throws {} */", raises.join(", "))
757    };
758    writeln!(
759        out,
760        "{inner}virtual {ret} {}({}) = 0;{raises_comment}",
761        op.name.text,
762        params.join(", ")
763    )
764    .map_err(fmt_err)?;
765    Ok(())
766}
767
768fn emit_interface_attr(
769    out: &mut String,
770    inner: &str,
771    attr: &zerodds_idl::ast::AttrDecl,
772) -> Result<(), CppGenError> {
773    check_identifier(&attr.name.text)?;
774    let ty = typespec_to_cpp(&attr.type_spec)?;
775    // Getter (alle Attribute haben einen).
776    writeln!(out, "{inner}virtual {ty} {}() const = 0;", attr.name.text).map_err(fmt_err)?;
777    // Setter nur fuer non-readonly.
778    if !attr.readonly {
779        writeln!(
780            out,
781            "{inner}virtual void {}(const {ty}& value) = 0;",
782            attr.name.text
783        )
784        .map_err(fmt_err)?;
785    }
786    Ok(())
787}
788
789fn emit_value_type(
790    out: &mut String,
791    ctx: &mut EmitCtx<'_>,
792    v: &ValueDef,
793) -> Result<(), CppGenError> {
794    let name = &v.name.text;
795    check_identifier(name)?;
796    let ind = ctx.indent();
797    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
798
799    emit_verbatim_at(out, &ind, &v.annotations, PlacementKind::BeforeDeclaration)?;
800
801    // Spec idl4-cpp §7.6: valuetype -> C++ class mit pure-virtual
802    // public/protected accessors (state) + factory-Class.
803    // Inheritance: public virtual fuer alle bases + supports-Interfaces.
804    let mut bases: Vec<String> = Vec::new();
805    if let Some(inh) = &v.inheritance {
806        for b in &inh.bases {
807            bases.push(format!("public virtual {}", scoped_to_cpp(b)));
808        }
809        for s in &inh.supports {
810            bases.push(format!("public virtual {}", scoped_to_cpp(s)));
811        }
812    }
813    if bases.is_empty() {
814        writeln!(out, "{ind}class {name} {{").map_err(fmt_err)?;
815    } else {
816        writeln!(out, "{ind}class {name} : {} {{", bases.join(", ")).map_err(fmt_err)?;
817    }
818    writeln!(out, "{ind}public:").map_err(fmt_err)?;
819    writeln!(out, "{inner}virtual ~{name}() = default;").map_err(fmt_err)?;
820
821    // Public-State + Methoden.
822    let mut has_protected = false;
823    for el in &v.elements {
824        match el {
825            ValueElement::State(s) if matches!(s.visibility, StateVisibility::Public) => {
826                let ty = typespec_to_cpp(&s.type_spec)?;
827                for d in &s.declarators {
828                    let n = &d.name().text;
829                    writeln!(out, "{inner}virtual const {ty}& {n}() const = 0;")
830                        .map_err(fmt_err)?;
831                    writeln!(out, "{inner}virtual void {n}(const {ty}& value) = 0;")
832                        .map_err(fmt_err)?;
833                }
834            }
835            ValueElement::State(s) if matches!(s.visibility, StateVisibility::Private) => {
836                has_protected = true;
837            }
838            ValueElement::Export(Export::Op(op)) => emit_interface_op(out, &inner, op)?,
839            ValueElement::Export(Export::Attr(a)) => emit_interface_attr(out, &inner, a)?,
840            _ => {}
841        }
842    }
843
844    // Protected-State (private members in IDL -> protected in C++ per Spec).
845    if has_protected {
846        writeln!(out, "{ind}protected:").map_err(fmt_err)?;
847        for el in &v.elements {
848            if let ValueElement::State(s) = el {
849                if matches!(s.visibility, StateVisibility::Private) {
850                    let ty = typespec_to_cpp(&s.type_spec)?;
851                    for d in &s.declarators {
852                        let n = &d.name().text;
853                        writeln!(out, "{inner}virtual const {ty}& {n}() const = 0;")
854                            .map_err(fmt_err)?;
855                        writeln!(out, "{inner}virtual void {n}(const {ty}& value) = 0;")
856                            .map_err(fmt_err)?;
857                    }
858                }
859            }
860        }
861    }
862
863    writeln!(out, "{ind}}};").map_err(fmt_err)?;
864
865    // Factory-Class pro factory-init (Spec §7.6: <ValueTypeName>_factory).
866    let factories: Vec<&zerodds_idl::ast::InitDcl> = v
867        .elements
868        .iter()
869        .filter_map(|e| {
870            if let ValueElement::Init(i) = e {
871                Some(i)
872            } else {
873                None
874            }
875        })
876        .collect();
877    if !factories.is_empty() {
878        writeln!(out, "{ind}class {name}_factory {{").map_err(fmt_err)?;
879        writeln!(out, "{ind}public:").map_err(fmt_err)?;
880        writeln!(out, "{inner}virtual ~{name}_factory() = default;").map_err(fmt_err)?;
881        for f in &factories {
882            check_identifier(&f.name.text)?;
883            let params: Vec<String> = f
884                .params
885                .iter()
886                .map(|p| -> Result<String, CppGenError> {
887                    let ty = typespec_to_cpp(&p.type_spec)?;
888                    let qual = match p.attribute {
889                        ParamAttribute::In => format!("const {ty}&"),
890                        ParamAttribute::Out | ParamAttribute::InOut => format!("{ty}&"),
891                    };
892                    Ok(format!("{qual} {}", p.name.text))
893                })
894                .collect::<Result<_, _>>()?;
895            writeln!(
896                out,
897                "{inner}virtual std::shared_ptr<{name}> {}({}) = 0;",
898                f.name.text,
899                params.join(", ")
900            )
901            .map_err(fmt_err)?;
902        }
903        writeln!(out, "{ind}}};").map_err(fmt_err)?;
904    }
905
906    emit_verbatim_at(out, &ind, &v.annotations, PlacementKind::AfterDeclaration)?;
907    writeln!(out).map_err(fmt_err)?;
908    Ok(())
909}
910
911fn emit_typedef(
912    out: &mut String,
913    ctx: &mut EmitCtx<'_>,
914    t: &TypedefDecl,
915) -> Result<(), CppGenError> {
916    let ind = ctx.indent();
917    emit_verbatim_at(out, &ind, &t.annotations, PlacementKind::BeforeDeclaration)?;
918    for decl in &t.declarators {
919        let alias = &decl.name().text;
920        check_identifier(alias)?;
921        let target_ty = type_for_declarator(&t.type_spec, decl)?;
922        writeln!(out, "{ind}using {alias} = {target_ty};").map_err(fmt_err)?;
923    }
924    emit_verbatim_at(out, &ind, &t.annotations, PlacementKind::AfterDeclaration)?;
925    writeln!(out).map_err(fmt_err)?;
926    Ok(())
927}
928
929fn emit_const_decl(
930    out: &mut String,
931    ctx: &mut EmitCtx<'_>,
932    c: &zerodds_idl::ast::ConstDecl,
933) -> Result<(), CppGenError> {
934    check_identifier(&c.name.text)?;
935    let ind = ctx.indent();
936    let cpp_ty = match &c.type_ {
937        zerodds_idl::ast::ConstType::Integer(i) => crate::type_map::integer_to_cpp(*i).to_string(),
938        zerodds_idl::ast::ConstType::Floating(f) => {
939            crate::type_map::floating_to_cpp(*f).to_string()
940        }
941        zerodds_idl::ast::ConstType::Boolean => "bool".into(),
942        zerodds_idl::ast::ConstType::Char => "char".into(),
943        zerodds_idl::ast::ConstType::WideChar => "wchar_t".into(),
944        zerodds_idl::ast::ConstType::Octet => "uint8_t".into(),
945        zerodds_idl::ast::ConstType::String { wide: false } => "std::string".into(),
946        zerodds_idl::ast::ConstType::String { wide: true } => "std::wstring".into(),
947        zerodds_idl::ast::ConstType::Scoped(s) => scoped_to_cpp(s),
948        zerodds_idl::ast::ConstType::Fixed => {
949            // §7.2.4.2.4 — Fixed-Constant ohne Digits/Scale-Annotation;
950            // wir emittieren als opaker Wrapper (Caller annotiert den Typ
951            // ueber separates `typedef fixed<D,S> Name;`).
952            "::dds::core::Fixed<31, 0>".into()
953        }
954    };
955    let val = const_expr_to_cpp(&c.value);
956    writeln!(out, "{ind}constexpr {cpp_ty} {} = {val};", c.name.text).map_err(fmt_err)?;
957    Ok(())
958}
959
960fn emit_exception(
961    out: &mut String,
962    ctx: &mut EmitCtx<'_>,
963    e: &ExceptDecl,
964) -> Result<(), CppGenError> {
965    check_identifier(&e.name.text)?;
966    let ind = ctx.indent();
967    writeln!(out, "{ind}class {} : public std::exception {{", e.name.text).map_err(fmt_err)?;
968    writeln!(out, "{ind}public:").map_err(fmt_err)?;
969    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
970    writeln!(out, "{inner}{}() = default;", e.name.text).map_err(fmt_err)?;
971    writeln!(out, "{inner}~{}() override = default;", e.name.text).map_err(fmt_err)?;
972    writeln!(
973        out,
974        "{inner}const char* what() const noexcept override {{ return \"{}\"; }}",
975        e.name.text
976    )
977    .map_err(fmt_err)?;
978    writeln!(out).map_err(fmt_err)?;
979    writeln!(out, "{ind}private:").map_err(fmt_err)?;
980    for m in &e.members {
981        for decl in &m.declarators {
982            let cpp_ty = type_for_declarator(&m.type_spec, decl)?;
983            let name = &decl.name().text;
984            check_identifier(name)?;
985            writeln!(out, "{inner}{cpp_ty} {name}_;").map_err(fmt_err)?;
986        }
987    }
988    writeln!(out, "{ind}}};").map_err(fmt_err)?;
989    writeln!(out).map_err(fmt_err)?;
990    Ok(())
991}
992
993// ---------------------------------------------------------------------------
994// TypeSpec → C++-Type-Ausdruck
995// ---------------------------------------------------------------------------
996
997/// Liefert den C++-Type-Ausdruck fuer ein Member (TypeSpec + Declarator).
998/// Array-Declaratoren werden zu `std::array<T, N>` (mehrdimensional ge-nestet).
999pub(crate) fn type_for_declarator(ts: &TypeSpec, decl: &Declarator) -> Result<String, CppGenError> {
1000    let base = typespec_to_cpp(ts)?;
1001    match decl {
1002        Declarator::Simple(_) => Ok(base),
1003        Declarator::Array(arr) => {
1004            // Innen-zu-aussen wickeln: arr.sizes[0] ist die aeusserste Dimension.
1005            // `int x[2][3]` → `std::array<std::array<int, 3>, 2>`.
1006            let mut out = base;
1007            for size in arr.sizes.iter().rev() {
1008                let n = const_expr_to_usize(size).unwrap_or_default();
1009                out = format!("std::array<{out}, {n}>");
1010            }
1011            Ok(out)
1012        }
1013    }
1014}
1015
1016/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
1017pub(crate) fn typespec_to_cpp(ts: &TypeSpec) -> Result<String, CppGenError> {
1018    match ts {
1019        TypeSpec::Primitive(p) => Ok(primitive_to_cpp(*p).to_string()),
1020        TypeSpec::Scoped(s) => Ok(scoped_to_cpp(s)),
1021        TypeSpec::Sequence(s) => {
1022            let inner = typespec_to_cpp(&s.elem)?;
1023            Ok(format!("std::vector<{inner}>"))
1024        }
1025        TypeSpec::String(s) => {
1026            if s.wide {
1027                Ok("std::wstring".into())
1028            } else {
1029                Ok("std::string".into())
1030            }
1031        }
1032        TypeSpec::Map(m) => {
1033            let k = typespec_to_cpp(&m.key)?;
1034            let v = typespec_to_cpp(&m.value)?;
1035            Ok(format!("std::map<{k}, {v}>"))
1036        }
1037        TypeSpec::Fixed(f) => {
1038            // Spec idl4-cpp §7.2.4.2.4: fixed<digits, scale> ->
1039            // `omg::types::fixed<D, S>` (Spec) bzw. `dds::core::Fixed<D,S>`
1040            // (ZeroDDS-aequivalente Form). Digits/Scale aus AST.
1041            let digits = const_expr_to_u32(&f.digits).unwrap_or(0);
1042            let scale = const_expr_to_u32(&f.scale).unwrap_or(0);
1043            Ok(format!("::dds::core::Fixed<{digits}, {scale}>"))
1044        }
1045        TypeSpec::Any => {
1046            // Spec idl4-cpp §7.3: `any` -> `omg::types::Any`. Wir
1047            // emittieren als ZeroDDS-aequivalente `dds::core::Any`-Klasse
1048            // (Reflective-Container, Runtime-Implementation in
1049            // `dds-core`-Crate).
1050            Ok("::dds::core::Any".into())
1051        }
1052    }
1053}
1054
1055pub(crate) fn switch_type_to_cpp(s: &SwitchTypeSpec) -> Result<String, CppGenError> {
1056    Ok(match s {
1057        SwitchTypeSpec::Integer(i) => crate::type_map::integer_to_cpp(*i).to_string(),
1058        SwitchTypeSpec::Char => "char".into(),
1059        SwitchTypeSpec::Boolean => "bool".into(),
1060        SwitchTypeSpec::Octet => "uint8_t".into(),
1061        SwitchTypeSpec::Scoped(s) => scoped_to_cpp(s),
1062    })
1063}
1064
1065pub(crate) fn scoped_to_cpp(s: &ScopedName) -> String {
1066    // Mapping fuer Time/Duration (Block E).
1067    if s.parts.len() == 1 {
1068        if let Some(mapped) = TIME_DURATION_TYPES
1069            .iter()
1070            .find(|(idl, _)| *idl == s.parts[0].text)
1071            .map(|(_, cpp)| *cpp)
1072        {
1073            return mapped.to_string();
1074        }
1075    }
1076    let parts: Vec<String> = s.parts.iter().map(|p| p.text.clone()).collect();
1077    let joined = parts.join("::");
1078    if s.absolute {
1079        format!("::{joined}")
1080    } else {
1081        joined
1082    }
1083}
1084
1085// ---------------------------------------------------------------------------
1086// ConstExpr → C++-Literal-String (best-effort fuer Foundation)
1087// ---------------------------------------------------------------------------
1088
1089fn const_expr_to_u32(e: &ConstExpr) -> Option<u32> {
1090    if let ConstExpr::Literal(l) = e {
1091        if matches!(l.kind, LiteralKind::Integer) {
1092            return l.raw.parse::<u32>().ok();
1093        }
1094    }
1095    None
1096}
1097
1098pub(crate) fn const_expr_to_cpp(e: &ConstExpr) -> String {
1099    match e {
1100        ConstExpr::Literal(l) => literal_to_cpp(l),
1101        ConstExpr::Scoped(s) => scoped_to_cpp(s),
1102        ConstExpr::Unary { op, operand, .. } => {
1103            let prefix = match op {
1104                zerodds_idl::ast::UnaryOp::Plus => "+",
1105                zerodds_idl::ast::UnaryOp::Minus => "-",
1106                zerodds_idl::ast::UnaryOp::BitNot => "~",
1107            };
1108            format!("{prefix}{}", const_expr_to_cpp(operand))
1109        }
1110        ConstExpr::Binary { op, lhs, rhs, .. } => {
1111            let opstr = match op {
1112                zerodds_idl::ast::BinaryOp::Or => "|",
1113                zerodds_idl::ast::BinaryOp::Xor => "^",
1114                zerodds_idl::ast::BinaryOp::And => "&",
1115                zerodds_idl::ast::BinaryOp::Shl => "<<",
1116                zerodds_idl::ast::BinaryOp::Shr => ">>",
1117                zerodds_idl::ast::BinaryOp::Add => "+",
1118                zerodds_idl::ast::BinaryOp::Sub => "-",
1119                zerodds_idl::ast::BinaryOp::Mul => "*",
1120                zerodds_idl::ast::BinaryOp::Div => "/",
1121                zerodds_idl::ast::BinaryOp::Mod => "%",
1122            };
1123            format!(
1124                "({} {opstr} {})",
1125                const_expr_to_cpp(lhs),
1126                const_expr_to_cpp(rhs)
1127            )
1128        }
1129    }
1130}
1131
1132fn literal_to_cpp(l: &Literal) -> String {
1133    match l.kind {
1134        LiteralKind::Boolean => l.raw.clone(),
1135        LiteralKind::Integer | LiteralKind::Floating => l.raw.clone(),
1136        LiteralKind::Char => l.raw.clone(),
1137        LiteralKind::WideChar => l.raw.clone(),
1138        LiteralKind::String => l.raw.clone(),
1139        LiteralKind::WideString => l.raw.clone(),
1140        LiteralKind::Fixed => l.raw.clone(),
1141    }
1142}
1143
1144fn const_expr_to_usize(e: &ConstExpr) -> Option<usize> {
1145    match e {
1146        ConstExpr::Literal(l) if l.kind == LiteralKind::Integer => l.raw.parse::<usize>().ok(),
1147        _ => None,
1148    }
1149}
1150
1151// ---------------------------------------------------------------------------
1152// Annotation-Helpers
1153// ---------------------------------------------------------------------------
1154
1155fn has_key_annotation(anns: &[Annotation]) -> bool {
1156    has_named_annotation(anns, "key")
1157}
1158
1159fn has_optional_annotation(anns: &[Annotation]) -> bool {
1160    has_named_annotation(anns, "optional")
1161}
1162
1163fn has_shared_annotation(anns: &[Annotation]) -> bool {
1164    has_named_annotation(anns, "shared")
1165}
1166
1167fn has_named_annotation(anns: &[Annotation], name: &str) -> bool {
1168    anns.iter().any(|a| {
1169        a.name.parts.last().is_some_and(|p| p.text == name)
1170            && matches!(a.params, AnnotationParams::None | AnnotationParams::Empty)
1171    })
1172}
1173
1174/// Sucht ein `@<name>(N)` und liefert den uint32-Wert; sonst None.
1175fn find_uint_annotation(anns: &[Annotation], name: &str) -> Option<u32> {
1176    for a in anns {
1177        if a.name.parts.last().is_some_and(|p| p.text == name) {
1178            if let AnnotationParams::Single(expr) = &a.params {
1179                if let Some(v) = const_expr_as_u32(expr) {
1180                    return Some(v);
1181                }
1182            }
1183        }
1184    }
1185    None
1186}
1187/// zerodds-lint: recursion-depth 64 (const_expr_as_u32 bounded by AST depth)
1188/// Versucht, einen ConstExpr als positiven u32 zu deuten (nur Integer-Literal
1189/// oder Unary-Plus auf Integer-Literal).
1190fn const_expr_as_u32(e: &ConstExpr) -> Option<u32> {
1191    match e {
1192        ConstExpr::Literal(Literal {
1193            kind: LiteralKind::Integer,
1194            raw,
1195            ..
1196        }) => parse_int_literal(raw).and_then(|v| u32::try_from(v).ok()),
1197        ConstExpr::Unary {
1198            op: zerodds_idl::ast::UnaryOp::Plus,
1199            operand,
1200            ..
1201        } => const_expr_as_u32(operand),
1202        _ => None,
1203    }
1204}
1205
1206/// Parser fuer integer-Literale (dezimal, hex `0x`, oktal `0...`).
1207fn parse_int_literal(raw: &str) -> Option<u64> {
1208    let s = raw.trim_end_matches(|c: char| matches!(c, 'l' | 'L' | 'u' | 'U'));
1209    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
1210        u64::from_str_radix(hex, 16).ok()
1211    } else if s.len() > 1 && s.starts_with('0') {
1212        u64::from_str_radix(&s[1..], 8).ok()
1213    } else {
1214        s.parse::<u64>().ok()
1215    }
1216}
1217
1218/// Extensibility-Modus eines Structs aus seinen Annotations.
1219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1220enum Extensibility {
1221    Final,
1222    Appendable,
1223    Mutable,
1224}
1225
1226fn struct_extensibility(anns: &[Annotation]) -> Extensibility {
1227    if has_named_annotation(anns, "final") {
1228        Extensibility::Final
1229    } else if has_named_annotation(anns, "mutable") {
1230        Extensibility::Mutable
1231    } else if has_named_annotation(anns, "appendable") {
1232        Extensibility::Appendable
1233    } else {
1234        // Default per zerodds-xcdr2-cpp-1.0 §6: appendable.
1235        Extensibility::Appendable
1236    }
1237}
1238
1239// ---------------------------------------------------------------------------
1240// Inheritance-Cycle-Detection (reine Self/Direct-Loops im Top-Level-Scope).
1241// ---------------------------------------------------------------------------
1242
1243/// Walkt das AST und sammelt `child → parent`-Edges (FQN-Strings).
1244///
1245/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
1246fn collect_inheritance_edges(
1247    defs: &[Definition],
1248    parents: &mut std::collections::HashMap<String, String>,
1249    prefix: &str,
1250) {
1251    for d in defs {
1252        match d {
1253            Definition::Module(m) => {
1254                let new_prefix = if prefix.is_empty() {
1255                    m.name.text.clone()
1256                } else {
1257                    format!("{prefix}::{}", m.name.text)
1258                };
1259                collect_inheritance_edges(&m.definitions, parents, &new_prefix);
1260            }
1261            Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
1262                let key = if prefix.is_empty() {
1263                    s.name.text.clone()
1264                } else {
1265                    format!("{prefix}::{}", s.name.text)
1266                };
1267                if let Some(b) = &s.base {
1268                    let base_str = b
1269                        .parts
1270                        .iter()
1271                        .map(|p| p.text.clone())
1272                        .collect::<Vec<_>>()
1273                        .join("::");
1274                    parents.insert(key, base_str);
1275                }
1276            }
1277            _ => {}
1278        }
1279    }
1280}
1281
1282fn detect_inheritance_cycles(spec: &Specification) -> Result<(), CppGenError> {
1283    use std::collections::HashMap;
1284
1285    let mut parents: HashMap<String, String> = HashMap::new();
1286    collect_inheritance_edges(&spec.definitions, &mut parents, "");
1287
1288    // Cycle-Detection per visited-Set pro Knoten.
1289    for start in parents.keys() {
1290        let mut current = start.clone();
1291        let mut visited: BTreeSet<String> = BTreeSet::new();
1292        visited.insert(current.clone());
1293        while let Some(p) = parents.get(&current) {
1294            // Match flexibel: voller Schluessel oder Suffix-Match.
1295            let resolved = parents
1296                .keys()
1297                .find(|k| *k == p || k.ends_with(&format!("::{p}")))
1298                .cloned()
1299                .unwrap_or_else(|| p.clone());
1300            if visited.contains(&resolved) {
1301                return Err(CppGenError::InheritanceCycle {
1302                    type_name: short_name(&resolved),
1303                });
1304            }
1305            visited.insert(resolved.clone());
1306            // Direktes Self-Reference (Parent == Self):
1307            if resolved == current {
1308                return Err(CppGenError::InheritanceCycle {
1309                    type_name: short_name(&resolved),
1310                });
1311            }
1312            current = resolved;
1313            if !parents.contains_key(&current) {
1314                break;
1315            }
1316        }
1317    }
1318    Ok(())
1319}
1320
1321fn short_name(s: &str) -> String {
1322    s.rsplit("::").next().unwrap_or(s).to_string()
1323}
1324
1325// ---------------------------------------------------------------------------
1326// topic_type_support<T> — DDS-PSM-Cxx Topic-Trait-Spezialisierung
1327// ---------------------------------------------------------------------------
1328//
1329// Sammelt alle Top-Level- und Modul-nested-Structs und emittiert pro Struct
1330// eine `dds::topic::topic_type_support<FQN>`-Spezialisierung mit type_name(),
1331// encode(), encode_be(), decode(), key_hash(), is_keyed() und extensibility().
1332//
1333// Wire-Format ist voll-XCDR2 (XTypes 1.3 §7.4):
1334//   * Plain-CDR2 LE mit Alignment relativ zum Encapsulation-Start.
1335//   * `@final`           -> kein DHEADER.
1336//   * `@appendable`(def) -> DHEADER (4 Byte body-size) prefixed.
1337//   * `@mutable`         -> DHEADER + EMHEADER pro Member (PL_CDR2).
1338//   * `@key`             -> Member geht in Key-Hash (MD5 ueber BE-Plain-CDR2).
1339//   * `@id(N)`           -> EMHEADER member-id.
1340//   * `@optional`        -> EMHEADER skip falls absent (Mutable);
1341//                            1-Byte Present-Flag fuer Final/Appendable.
1342//   * `@must_understand` -> EMHEADER MU-Flag.
1343//
1344// Konformanz: docs/specs/zerodds-xcdr2-bindings-conformance-1.0.md (V-1..V-12).
1345
1346/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
1347fn collect_topic_structs<'a>(
1348    defs: &'a [Definition],
1349    prefix: &str,
1350    out: &mut Vec<(String, &'a StructDef)>,
1351) {
1352    for d in defs {
1353        match d {
1354            Definition::Module(m) => {
1355                let np = if prefix.is_empty() {
1356                    m.name.text.clone()
1357                } else {
1358                    format!("{prefix}::{}", m.name.text)
1359                };
1360                collect_topic_structs(&m.definitions, &np, out);
1361            }
1362            Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
1363                let fqn = if prefix.is_empty() {
1364                    s.name.text.clone()
1365                } else {
1366                    format!("{prefix}::{}", s.name.text)
1367                };
1368                out.push((fqn, s));
1369            }
1370            _ => {}
1371        }
1372    }
1373}
1374
1375fn emit_topic_type_support_specs(
1376    out: &mut String,
1377    opts: &CppGenOptions,
1378    structs: &[(String, &StructDef)],
1379) -> Result<(), CppGenError> {
1380    writeln!(out).map_err(fmt_err)?;
1381    writeln!(
1382        out,
1383        "// DDS-PSM-Cxx topic_type_support<T> -- auto-generiert (XCDR2 Wire, XTypes 1.3 7.4)."
1384    )
1385    .map_err(fmt_err)?;
1386    writeln!(out, "namespace dds {{").map_err(fmt_err)?;
1387    writeln!(out, "namespace topic {{").map_err(fmt_err)?;
1388    writeln!(out).map_err(fmt_err)?;
1389
1390    let user_prefix = opts
1391        .namespace_prefix
1392        .as_deref()
1393        .filter(|p| !p.is_empty())
1394        .unwrap_or("");
1395    for (fqn, s) in structs {
1396        let cpp_fqn = if user_prefix.is_empty() {
1397            format!("::{fqn}")
1398        } else {
1399            format!("::{user_prefix}::{fqn}")
1400        };
1401        emit_topic_type_support_for(out, &cpp_fqn, fqn, s)?;
1402    }
1403
1404    writeln!(out, "}} // namespace topic").map_err(fmt_err)?;
1405    writeln!(out, "}} // namespace dds").map_err(fmt_err)?;
1406    Ok(())
1407}
1408
1409/// Liefert true falls die Member-Annotations encode-fest sind (Codegen
1410/// kann Wire-Bytes erzeugen). False bei `@shared` (Heap-Indirection,
1411/// noch nicht unterstuetzt). `@optional` ist erlaubt.
1412fn member_codegen_supported(m: &Member) -> bool {
1413    !has_shared_annotation(&m.annotations)
1414}
1415
1416/// Liefert true wenn ein Type-Spec vom XCDR2-Codegen verstanden wird.
1417fn typespec_supported(ts: &TypeSpec) -> bool {
1418    match ts {
1419        TypeSpec::Primitive(_) => true,
1420        TypeSpec::String(s) => !s.wide,
1421        TypeSpec::Sequence(seq) => match &*seq.elem {
1422            TypeSpec::Primitive(_) => true,
1423            TypeSpec::String(s) => !s.wide,
1424            _ => false,
1425        },
1426        _ => false,
1427    }
1428}
1429
1430fn emit_topic_type_support_for(
1431    out: &mut String,
1432    cpp_fqn: &str,
1433    type_name: &str,
1434    s: &StructDef,
1435) -> Result<(), CppGenError> {
1436    let ext = struct_extensibility(&s.annotations);
1437
1438    writeln!(out, "template <>").map_err(fmt_err)?;
1439    writeln!(out, "struct topic_type_support<{cpp_fqn}> {{").map_err(fmt_err)?;
1440    writeln!(
1441        out,
1442        "    static const char* type_name() {{ return \"{type_name}\"; }}"
1443    )
1444    .map_err(fmt_err)?;
1445
1446    // is_keyed
1447    let is_keyed = s.members.iter().any(|m| has_key_annotation(&m.annotations));
1448    writeln!(
1449        out,
1450        "    static constexpr bool is_keyed() {{ return {}; }}",
1451        if is_keyed { "true" } else { "false" }
1452    )
1453    .map_err(fmt_err)?;
1454
1455    // extensibility
1456    let ext_lit = match ext {
1457        Extensibility::Final => "FINAL",
1458        Extensibility::Appendable => "APPENDABLE",
1459        Extensibility::Mutable => "MUTABLE",
1460    };
1461    writeln!(
1462        out,
1463        "    static constexpr ::dds::topic::core::policy::DataRepresentationKind extensibility() {{ return ::dds::topic::core::policy::DataRepresentationKind::{ext_lit}; }}"
1464    )
1465    .map_err(fmt_err)?;
1466
1467    // encode (LE)
1468    emit_encode_fn(out, cpp_fqn, s, ext, /*be=*/ false)?;
1469    // encode_be (BE)
1470    emit_encode_fn(out, cpp_fqn, s, ext, /*be=*/ true)?;
1471    // decode (LE)
1472    emit_decode_fn(out, cpp_fqn, s, ext)?;
1473    // key_hash (BE Plain-CDR2 of @key members + MD5)
1474    emit_key_hash_fn(out, cpp_fqn, s, is_keyed)?;
1475
1476    writeln!(out, "}};").map_err(fmt_err)?;
1477    writeln!(out).map_err(fmt_err)?;
1478    Ok(())
1479}
1480
1481fn emit_encode_fn(
1482    out: &mut String,
1483    cpp_fqn: &str,
1484    s: &StructDef,
1485    ext: Extensibility,
1486    be: bool,
1487) -> Result<(), CppGenError> {
1488    let fn_name = if be { "encode_be" } else { "encode" };
1489    writeln!(
1490        out,
1491        "    static std::vector<uint8_t> {fn_name}(const {cpp_fqn}& __v) {{"
1492    )
1493    .map_err(fmt_err)?;
1494    writeln!(out, "        std::vector<uint8_t> __out;").map_err(fmt_err)?;
1495    writeln!(out, "        (void)__v;").map_err(fmt_err)?;
1496
1497    // Suffix for write helpers: write_le or write_be, write_string or write_string_be.
1498    let endian_suffix = if be { "be" } else { "le" };
1499
1500    match ext {
1501        Extensibility::Final => {
1502            // Plain-CDR2, no DHEADER, alignment relative to buffer start.
1503            // origin = 0.
1504            writeln!(out, "        const size_t __origin = 0;").map_err(fmt_err)?;
1505            writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
1506            for m in &s.members {
1507                emit_plain_member_encode(out, m, endian_suffix, "__origin")?;
1508            }
1509        }
1510        Extensibility::Appendable => {
1511            writeln!(
1512                out,
1513                "        const auto __dh = ::dds::topic::xcdr2::dheader_begin(__out);"
1514            )
1515            .map_err(fmt_err)?;
1516            writeln!(out, "        const size_t __origin = __out.size();").map_err(fmt_err)?;
1517            writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
1518            for m in &s.members {
1519                emit_plain_member_encode(out, m, endian_suffix, "__origin")?;
1520            }
1521            writeln!(
1522                out,
1523                "        ::dds::topic::xcdr2::dheader_end(__out, __dh);"
1524            )
1525            .map_err(fmt_err)?;
1526        }
1527        Extensibility::Mutable => {
1528            writeln!(
1529                out,
1530                "        const auto __scope = ::dds::topic::xcdr2::mutable_begin(__out);"
1531            )
1532            .map_err(fmt_err)?;
1533            writeln!(out, "        const size_t __origin = __scope.origin;").map_err(fmt_err)?;
1534            for m in &s.members {
1535                emit_mutable_member_encode(out, m, endian_suffix)?;
1536            }
1537            writeln!(
1538                out,
1539                "        ::dds::topic::xcdr2::mutable_end(__out, __scope);"
1540            )
1541            .map_err(fmt_err)?;
1542        }
1543    }
1544
1545    writeln!(out, "        return __out;").map_err(fmt_err)?;
1546    writeln!(out, "    }}").map_err(fmt_err)?;
1547    Ok(())
1548}
1549
1550/// Emit Plain-CDR2 (LE/BE) encoding for one member at the current
1551/// position; alignment relative to `origin`.
1552fn emit_plain_member_encode(
1553    out: &mut String,
1554    m: &Member,
1555    endian: &str,
1556    origin: &str,
1557) -> Result<(), CppGenError> {
1558    if !member_codegen_supported(m) {
1559        for decl in &m.declarators {
1560            let name = &decl.name().text;
1561            writeln!(
1562                out,
1563                "        // xcdr2: @shared member '{name}' nicht unterstuetzt (skip)"
1564            )
1565            .map_err(fmt_err)?;
1566        }
1567        return Ok(());
1568    }
1569    let is_optional = has_optional_annotation(&m.annotations);
1570    for decl in &m.declarators {
1571        let name = &decl.name().text;
1572        if !matches!(decl, Declarator::Simple(_)) {
1573            writeln!(
1574                out,
1575                "        // xcdr2: array member '{name}' nicht unterstuetzt (skip)"
1576            )
1577            .map_err(fmt_err)?;
1578            continue;
1579        }
1580        if !typespec_supported(&m.type_spec) {
1581            writeln!(
1582                out,
1583                "        // xcdr2: member '{name}' nicht unterstuetzt (nested/enum/wstring/map/fixed; skip)"
1584            )
1585            .map_err(fmt_err)?;
1586            continue;
1587        }
1588        if is_optional {
1589            // Final/Appendable: 1-Byte present-flag, dann Wert wenn vorhanden.
1590            writeln!(out, "        if (__v.{name}().has_value()) {{").map_err(fmt_err)?;
1591            writeln!(out, "            __out.push_back(uint8_t{{1}});").map_err(fmt_err)?;
1592            emit_value_write(
1593                out,
1594                &m.type_spec,
1595                &format!("(*__v.{name}())"),
1596                endian,
1597                origin,
1598                "        ",
1599            )?;
1600            writeln!(out, "        }} else {{").map_err(fmt_err)?;
1601            writeln!(out, "            __out.push_back(uint8_t{{0}});").map_err(fmt_err)?;
1602            writeln!(out, "        }}").map_err(fmt_err)?;
1603        } else {
1604            emit_value_write(
1605                out,
1606                &m.type_spec,
1607                &format!("__v.{name}()"),
1608                endian,
1609                origin,
1610                "    ",
1611            )?;
1612        }
1613    }
1614    Ok(())
1615}
1616
1617/// Emit a single value write at `access` using LE or BE convention.
1618fn emit_value_write(
1619    out: &mut String,
1620    ts: &TypeSpec,
1621    access: &str,
1622    endian: &str,
1623    origin: &str,
1624    indent: &str,
1625) -> Result<(), CppGenError> {
1626    let pre = format!("{indent}    ");
1627    match ts {
1628        TypeSpec::Primitive(PrimitiveType::Boolean) => {
1629            writeln!(
1630                out,
1631                "{pre}::dds::topic::xcdr2::write_bool(__out, {access});"
1632            )
1633            .map_err(fmt_err)?;
1634        }
1635        TypeSpec::Primitive(PrimitiveType::Octet) => {
1636            writeln!(out, "{pre}::dds::topic::xcdr2::write_u8(__out, {access});")
1637                .map_err(fmt_err)?;
1638        }
1639        TypeSpec::Primitive(p) => {
1640            let cpp_ty = primitive_to_cpp(*p);
1641            writeln!(
1642                out,
1643                "{pre}::dds::topic::xcdr2::write_{endian}_origin<{cpp_ty}>(__out, {origin}, {access});"
1644            )
1645            .map_err(fmt_err)?;
1646        }
1647        TypeSpec::String(s) if !s.wide => {
1648            if endian == "be" {
1649                writeln!(
1650                    out,
1651                    "{pre}::dds::topic::xcdr2::write_string_be(__out, {access});"
1652                )
1653                .map_err(fmt_err)?;
1654            } else {
1655                writeln!(
1656                    out,
1657                    "{pre}::dds::topic::xcdr2::write_string_origin(__out, {origin}, {access});"
1658                )
1659                .map_err(fmt_err)?;
1660            }
1661        }
1662        TypeSpec::Sequence(seq) => {
1663            let count_call = if endian == "be" {
1664                format!(
1665                    "{pre}::dds::topic::xcdr2::write_be<uint32_t>(__out, static_cast<uint32_t>({access}.size()));"
1666                )
1667            } else {
1668                format!(
1669                    "{pre}::dds::topic::xcdr2::write_le_origin<uint32_t>(__out, {origin}, static_cast<uint32_t>({access}.size()));"
1670                )
1671            };
1672            writeln!(out, "{count_call}").map_err(fmt_err)?;
1673            writeln!(out, "{pre}for (const auto& __e : {access}) {{").map_err(fmt_err)?;
1674            let elem_indent = format!("{pre}    ");
1675            match &*seq.elem {
1676                TypeSpec::Primitive(PrimitiveType::Boolean) => {
1677                    writeln!(
1678                        out,
1679                        "{elem_indent}::dds::topic::xcdr2::write_bool(__out, __e);"
1680                    )
1681                    .map_err(fmt_err)?;
1682                }
1683                TypeSpec::Primitive(PrimitiveType::Octet) => {
1684                    writeln!(
1685                        out,
1686                        "{elem_indent}::dds::topic::xcdr2::write_u8(__out, __e);"
1687                    )
1688                    .map_err(fmt_err)?;
1689                }
1690                TypeSpec::Primitive(p) => {
1691                    let cpp_ty = primitive_to_cpp(*p);
1692                    if endian == "be" {
1693                        writeln!(
1694                            out,
1695                            "{elem_indent}::dds::topic::xcdr2::write_be<{cpp_ty}>(__out, __e);"
1696                        )
1697                        .map_err(fmt_err)?;
1698                    } else {
1699                        writeln!(
1700                            out,
1701                            "{elem_indent}::dds::topic::xcdr2::write_le_origin<{cpp_ty}>(__out, {origin}, __e);"
1702                        )
1703                        .map_err(fmt_err)?;
1704                    }
1705                }
1706                TypeSpec::String(s) if !s.wide => {
1707                    if endian == "be" {
1708                        writeln!(
1709                            out,
1710                            "{elem_indent}::dds::topic::xcdr2::write_string_be(__out, __e);"
1711                        )
1712                        .map_err(fmt_err)?;
1713                    } else {
1714                        writeln!(
1715                            out,
1716                            "{elem_indent}::dds::topic::xcdr2::write_string_origin(__out, {origin}, __e);"
1717                        )
1718                        .map_err(fmt_err)?;
1719                    }
1720                }
1721                _ => {
1722                    writeln!(
1723                        out,
1724                        "{elem_indent}// xcdr2: nested/wstring sequence-element nicht unterstuetzt"
1725                    )
1726                    .map_err(fmt_err)?;
1727                }
1728            }
1729            writeln!(out, "{pre}}}").map_err(fmt_err)?;
1730        }
1731        _ => {
1732            writeln!(out, "{pre}// xcdr2: member type nicht unterstuetzt (skip)")
1733                .map_err(fmt_err)?;
1734        }
1735    }
1736    Ok(())
1737}
1738
1739/// Emit Mutable-EMHEADER + body for one member.
1740fn emit_mutable_member_encode(
1741    out: &mut String,
1742    m: &Member,
1743    endian: &str,
1744) -> Result<(), CppGenError> {
1745    if !member_codegen_supported(m) {
1746        for decl in &m.declarators {
1747            let name = &decl.name().text;
1748            writeln!(
1749                out,
1750                "        // xcdr2: @shared member '{name}' nicht unterstuetzt (skip)"
1751            )
1752            .map_err(fmt_err)?;
1753        }
1754        return Ok(());
1755    }
1756    let is_optional = has_optional_annotation(&m.annotations);
1757    let must_understand = has_named_annotation(&m.annotations, "must_understand");
1758    let id_override = find_uint_annotation(&m.annotations, "id");
1759    let mu_lit = if must_understand { "true" } else { "false" };
1760
1761    for (idx, decl) in m.declarators.iter().enumerate() {
1762        let name = &decl.name().text;
1763        if !matches!(decl, Declarator::Simple(_)) {
1764            writeln!(
1765                out,
1766                "        // xcdr2: array member '{name}' nicht unterstuetzt (skip)"
1767            )
1768            .map_err(fmt_err)?;
1769            continue;
1770        }
1771        if !typespec_supported(&m.type_spec) {
1772            writeln!(
1773                out,
1774                "        // xcdr2: member '{name}' nicht unterstuetzt (skip)"
1775            )
1776            .map_err(fmt_err)?;
1777            continue;
1778        }
1779        // Member-id: explicit @id override; otherwise auto-id (declaration-order).
1780        // For positional: same id_override applies to all declarators in this Member.
1781        // (IDL convention: @id() applies to the whole declaration; we replicate.)
1782        let _ = idx;
1783        let id_expr = match id_override {
1784            Some(id) => id.to_string(),
1785            None => format!("0x{:x}u", auto_id_for(name)),
1786        };
1787        if is_optional {
1788            // Mutable + optional: skip EMHEADER if absent.
1789            writeln!(out, "        if (__v.{name}().has_value()) {{").map_err(fmt_err)?;
1790            emit_mutable_value_emit(
1791                out,
1792                &m.type_spec,
1793                &format!("(*__v.{name}())"),
1794                &id_expr,
1795                mu_lit,
1796                endian,
1797                "            ",
1798            )?;
1799            writeln!(out, "        }}").map_err(fmt_err)?;
1800        } else {
1801            emit_mutable_value_emit(
1802                out,
1803                &m.type_spec,
1804                &format!("__v.{name}()"),
1805                &id_expr,
1806                mu_lit,
1807                endian,
1808                "        ",
1809            )?;
1810        }
1811    }
1812    Ok(())
1813}
1814
1815/// Auto-id from member name (XTypes "auto" id mode: name-hash truncated to 28 bits).
1816/// Default mode is "sequential" but we use name-hash for stability across re-orderings;
1817/// caller should normally provide @id(N) explicitly.
1818fn auto_id_for(name: &str) -> u32 {
1819    // FNV-1a 32-bit; truncate to 28 bits to fit EMHEADER member-id.
1820    let mut h: u32 = 0x811C9DC5;
1821    for b in name.as_bytes() {
1822        h ^= u32::from(*b);
1823        h = h.wrapping_mul(0x01000193);
1824    }
1825    h & 0x0FFF_FFFF
1826}
1827
1828fn emit_mutable_value_emit(
1829    out: &mut String,
1830    ts: &TypeSpec,
1831    access: &str,
1832    id_expr: &str,
1833    mu_lit: &str,
1834    endian: &str,
1835    indent: &str,
1836) -> Result<(), CppGenError> {
1837    match ts {
1838        TypeSpec::Primitive(PrimitiveType::Boolean) => {
1839            writeln!(
1840                out,
1841                "{indent}::dds::topic::xcdr2::emheader_u8(__out, __origin, {id_expr}, {mu_lit}, static_cast<uint8_t>({access} ? 1 : 0));"
1842            )
1843            .map_err(fmt_err)?;
1844        }
1845        TypeSpec::Primitive(PrimitiveType::Octet) => {
1846            writeln!(
1847                out,
1848                "{indent}::dds::topic::xcdr2::emheader_u8(__out, __origin, {id_expr}, {mu_lit}, {access});"
1849            )
1850            .map_err(fmt_err)?;
1851        }
1852        TypeSpec::Primitive(p) => {
1853            let cpp_ty = primitive_to_cpp(*p);
1854            // Decide LC by size.
1855            let size = primitive_size(*p);
1856            match size {
1857                2 => {
1858                    writeln!(
1859                        out,
1860                        "{indent}::dds::topic::xcdr2::emheader_2<{cpp_ty}>(__out, __origin, {id_expr}, {mu_lit}, {access});"
1861                    )
1862                    .map_err(fmt_err)?;
1863                }
1864                4 => {
1865                    writeln!(
1866                        out,
1867                        "{indent}::dds::topic::xcdr2::emheader_4<{cpp_ty}>(__out, __origin, {id_expr}, {mu_lit}, {access});"
1868                    )
1869                    .map_err(fmt_err)?;
1870                }
1871                8 => {
1872                    writeln!(
1873                        out,
1874                        "{indent}::dds::topic::xcdr2::emheader_8<{cpp_ty}>(__out, __origin, {id_expr}, {mu_lit}, {access});"
1875                    )
1876                    .map_err(fmt_err)?;
1877                }
1878                _ => {
1879                    writeln!(
1880                        out,
1881                        "{indent}// xcdr2: unexpected primitive size {size} (skip)"
1882                    )
1883                    .map_err(fmt_err)?;
1884                }
1885            }
1886        }
1887        TypeSpec::String(s) if !s.wide => {
1888            // EMHEADER LC=3 with NEXTINT, then string body inline.
1889            writeln!(
1890                out,
1891                "{indent}{{ const auto __sub = ::dds::topic::xcdr2::emheader_nextint_begin(__out, __origin, {id_expr}, {mu_lit});"
1892            )
1893            .map_err(fmt_err)?;
1894            // Inside the NEXTINT block, the body itself uses origin = __sub.body_start
1895            // (string-len align is 4, count starts at body_start which is 4-aligned).
1896            let body_endian = if endian == "be" { "be" } else { "le" };
1897            let _ = body_endian;
1898            writeln!(
1899                out,
1900                "{indent}    {{ const auto __body_origin = __sub.body_start; (void)__body_origin;"
1901            )
1902            .map_err(fmt_err)?;
1903            if endian == "be" {
1904                writeln!(
1905                    out,
1906                    "{indent}      ::dds::topic::xcdr2::write_string_be(__out, {access});"
1907                )
1908                .map_err(fmt_err)?;
1909            } else {
1910                writeln!(
1911                    out,
1912                    "{indent}      ::dds::topic::xcdr2::write_string_origin(__out, __body_origin, {access});"
1913                )
1914                .map_err(fmt_err)?;
1915            }
1916            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
1917            writeln!(
1918                out,
1919                "{indent}    ::dds::topic::xcdr2::emheader_nextint_end(__out, __sub); }}"
1920            )
1921            .map_err(fmt_err)?;
1922        }
1923        TypeSpec::Sequence(seq) => {
1924            writeln!(
1925                out,
1926                "{indent}{{ const auto __sub = ::dds::topic::xcdr2::emheader_nextint_begin(__out, __origin, {id_expr}, {mu_lit});"
1927            )
1928            .map_err(fmt_err)?;
1929            writeln!(
1930                out,
1931                "{indent}    {{ const auto __body_origin = __sub.body_start; (void)__body_origin;"
1932            )
1933            .map_err(fmt_err)?;
1934            if endian == "be" {
1935                writeln!(
1936                    out,
1937                    "{indent}      ::dds::topic::xcdr2::write_be<uint32_t>(__out, static_cast<uint32_t>({access}.size()));"
1938                )
1939                .map_err(fmt_err)?;
1940            } else {
1941                writeln!(
1942                    out,
1943                    "{indent}      ::dds::topic::xcdr2::write_le_origin<uint32_t>(__out, __body_origin, static_cast<uint32_t>({access}.size()));"
1944                )
1945                .map_err(fmt_err)?;
1946            }
1947            writeln!(out, "{indent}      for (const auto& __e : {access}) {{").map_err(fmt_err)?;
1948            match &*seq.elem {
1949                TypeSpec::Primitive(PrimitiveType::Boolean) => {
1950                    writeln!(
1951                        out,
1952                        "{indent}        ::dds::topic::xcdr2::write_bool(__out, __e);"
1953                    )
1954                    .map_err(fmt_err)?;
1955                }
1956                TypeSpec::Primitive(PrimitiveType::Octet) => {
1957                    writeln!(
1958                        out,
1959                        "{indent}        ::dds::topic::xcdr2::write_u8(__out, __e);"
1960                    )
1961                    .map_err(fmt_err)?;
1962                }
1963                TypeSpec::Primitive(p) => {
1964                    let cpp_ty = primitive_to_cpp(*p);
1965                    if endian == "be" {
1966                        writeln!(
1967                            out,
1968                            "{indent}        ::dds::topic::xcdr2::write_be<{cpp_ty}>(__out, __e);"
1969                        )
1970                        .map_err(fmt_err)?;
1971                    } else {
1972                        writeln!(out, "{indent}        ::dds::topic::xcdr2::write_le_origin<{cpp_ty}>(__out, __body_origin, __e);").map_err(fmt_err)?;
1973                    }
1974                }
1975                TypeSpec::String(s) if !s.wide => {
1976                    if endian == "be" {
1977                        writeln!(
1978                            out,
1979                            "{indent}        ::dds::topic::xcdr2::write_string_be(__out, __e);"
1980                        )
1981                        .map_err(fmt_err)?;
1982                    } else {
1983                        writeln!(out, "{indent}        ::dds::topic::xcdr2::write_string_origin(__out, __body_origin, __e);").map_err(fmt_err)?;
1984                    }
1985                }
1986                _ => {
1987                    writeln!(
1988                        out,
1989                        "{indent}        // xcdr2: nested seq-elem nicht unterstuetzt"
1990                    )
1991                    .map_err(fmt_err)?;
1992                }
1993            }
1994            writeln!(out, "{indent}      }}").map_err(fmt_err)?;
1995            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
1996            writeln!(
1997                out,
1998                "{indent}    ::dds::topic::xcdr2::emheader_nextint_end(__out, __sub); }}"
1999            )
2000            .map_err(fmt_err)?;
2001        }
2002        _ => {
2003            writeln!(out, "{indent}// xcdr2: nicht unterstuetzter member-type").map_err(fmt_err)?;
2004        }
2005    }
2006    Ok(())
2007}
2008
2009fn primitive_size(p: PrimitiveType) -> usize {
2010    use zerodds_idl::ast::{FloatingType, IntegerType};
2011    match p {
2012        PrimitiveType::Boolean => 1,
2013        PrimitiveType::Octet => 1,
2014        PrimitiveType::Char => 1,
2015        PrimitiveType::WideChar => 2,
2016        PrimitiveType::Integer(i) => match i {
2017            IntegerType::Int8 | IntegerType::UInt8 => 1,
2018            IntegerType::Short | IntegerType::UShort | IntegerType::Int16 | IntegerType::UInt16 => {
2019                2
2020            }
2021            IntegerType::Long | IntegerType::ULong | IntegerType::Int32 | IntegerType::UInt32 => 4,
2022            IntegerType::LongLong
2023            | IntegerType::ULongLong
2024            | IntegerType::Int64
2025            | IntegerType::UInt64 => 8,
2026        },
2027        PrimitiveType::Floating(f) => match f {
2028            FloatingType::Float => 4,
2029            FloatingType::Double => 8,
2030            FloatingType::LongDouble => 16,
2031        },
2032    }
2033}
2034
2035fn emit_decode_fn(
2036    out: &mut String,
2037    cpp_fqn: &str,
2038    s: &StructDef,
2039    ext: Extensibility,
2040) -> Result<(), CppGenError> {
2041    writeln!(
2042        out,
2043        "    static {cpp_fqn} decode(const uint8_t* __buf, size_t __len) {{"
2044    )
2045    .map_err(fmt_err)?;
2046    writeln!(out, "        size_t __pos = 0;").map_err(fmt_err)?;
2047    writeln!(out, "        {cpp_fqn} __v;").map_err(fmt_err)?;
2048    writeln!(out, "        (void)__buf; (void)__len; (void)__pos;").map_err(fmt_err)?;
2049
2050    match ext {
2051        Extensibility::Final => {
2052            writeln!(out, "        const size_t __origin = 0;").map_err(fmt_err)?;
2053            writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
2054            for m in &s.members {
2055                emit_plain_member_decode(out, m, "__origin")?;
2056            }
2057        }
2058        Extensibility::Appendable => {
2059            writeln!(
2060                out,
2061                "        const auto __dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len);"
2062            )
2063            .map_err(fmt_err)?;
2064            writeln!(out, "        const size_t __origin = __pos;").map_err(fmt_err)?;
2065            writeln!(out, "        const size_t __end = __origin + __dh;").map_err(fmt_err)?;
2066            writeln!(out, "        (void)__end;").map_err(fmt_err)?;
2067            for m in &s.members {
2068                emit_plain_member_decode(out, m, "__origin")?;
2069            }
2070            // Skip trailing bytes (forward-compat with appendable extension).
2071            writeln!(out, "        if (__pos < __end) __pos = __end;").map_err(fmt_err)?;
2072        }
2073        Extensibility::Mutable => {
2074            writeln!(
2075                out,
2076                "        const auto __dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len);"
2077            )
2078            .map_err(fmt_err)?;
2079            writeln!(out, "        const size_t __origin = __pos;").map_err(fmt_err)?;
2080            writeln!(out, "        const size_t __end = __origin + __dh;").map_err(fmt_err)?;
2081            writeln!(out, "        while (__pos + 4 <= __end) {{").map_err(fmt_err)?;
2082            writeln!(
2083                out,
2084                "            const auto __h = ::dds::topic::xcdr2::emheader_read(__buf, __pos, __len, __origin);"
2085            )
2086            .map_err(fmt_err)?;
2087            writeln!(out, "            switch (__h.member_id) {{").map_err(fmt_err)?;
2088            for m in &s.members {
2089                emit_mutable_member_decode_case(out, m)?;
2090            }
2091            writeln!(out, "                default: {{").map_err(fmt_err)?;
2092            writeln!(
2093                out,
2094                "                    // unbekannter Member: NEXTINT-Skip falls LC>=3 ohne primitive-mapping."
2095            )
2096            .map_err(fmt_err)?;
2097            writeln!(out, "                    if (__h.lc == 0) {{ ++__pos; }}")
2098                .map_err(fmt_err)?;
2099            writeln!(
2100                out,
2101                "                    else if (__h.lc == 1) {{ __pos += 2; }}"
2102            )
2103            .map_err(fmt_err)?;
2104            writeln!(
2105                out,
2106                "                    else if (__h.lc == 2) {{ __pos += 4; }}"
2107            )
2108            .map_err(fmt_err)?;
2109            writeln!(
2110                out,
2111                "                    else {{ auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len); __pos += __n; }}"
2112            )
2113            .map_err(fmt_err)?;
2114            writeln!(out, "                    break;").map_err(fmt_err)?;
2115            writeln!(out, "                }}").map_err(fmt_err)?;
2116            writeln!(out, "            }}").map_err(fmt_err)?;
2117            writeln!(out, "        }}").map_err(fmt_err)?;
2118            writeln!(out, "        if (__pos < __end) __pos = __end;").map_err(fmt_err)?;
2119        }
2120    }
2121
2122    writeln!(out, "        return __v;").map_err(fmt_err)?;
2123    writeln!(out, "    }}").map_err(fmt_err)?;
2124    Ok(())
2125}
2126
2127fn emit_plain_member_decode(out: &mut String, m: &Member, origin: &str) -> Result<(), CppGenError> {
2128    if !member_codegen_supported(m) {
2129        for decl in &m.declarators {
2130            let name = &decl.name().text;
2131            writeln!(
2132                out,
2133                "        // xcdr2: @shared member '{name}' nicht unterstuetzt (skip)"
2134            )
2135            .map_err(fmt_err)?;
2136        }
2137        return Ok(());
2138    }
2139    let is_optional = has_optional_annotation(&m.annotations);
2140    for decl in &m.declarators {
2141        let name = &decl.name().text;
2142        if !matches!(decl, Declarator::Simple(_)) {
2143            writeln!(
2144                out,
2145                "        // xcdr2: array member '{name}' nicht unterstuetzt (skip)"
2146            )
2147            .map_err(fmt_err)?;
2148            continue;
2149        }
2150        if !typespec_supported(&m.type_spec) {
2151            writeln!(
2152                out,
2153                "        // xcdr2: member '{name}' nicht unterstuetzt (skip)"
2154            )
2155            .map_err(fmt_err)?;
2156            continue;
2157        }
2158        if is_optional {
2159            writeln!(out, "        {{").map_err(fmt_err)?;
2160            writeln!(
2161                out,
2162                "            uint8_t __present = ::dds::topic::xcdr2::read_u8(__buf, __pos, __len);"
2163            )
2164            .map_err(fmt_err)?;
2165            writeln!(out, "            if (__present) {{").map_err(fmt_err)?;
2166            emit_value_read(
2167                out,
2168                &m.type_spec,
2169                &format!("__v.{name}"),
2170                origin,
2171                "                ",
2172                true,
2173            )?;
2174            writeln!(out, "            }} else {{").map_err(fmt_err)?;
2175            writeln!(out, "                __v.{name}(std::nullopt);").map_err(fmt_err)?;
2176            writeln!(out, "            }}").map_err(fmt_err)?;
2177            writeln!(out, "        }}").map_err(fmt_err)?;
2178        } else {
2179            emit_value_read(
2180                out,
2181                &m.type_spec,
2182                &format!("__v.{name}"),
2183                origin,
2184                "        ",
2185                false,
2186            )?;
2187        }
2188    }
2189    Ok(())
2190}
2191
2192fn emit_value_read(
2193    out: &mut String,
2194    ts: &TypeSpec,
2195    setter: &str,
2196    origin: &str,
2197    indent: &str,
2198    is_opt: bool,
2199) -> Result<(), CppGenError> {
2200    let wrap_opt = |v: String| -> String {
2201        if is_opt {
2202            format!("std::optional<decltype({v})>({v})")
2203        } else {
2204            v
2205        }
2206    };
2207    let _ = wrap_opt;
2208    match ts {
2209        TypeSpec::Primitive(PrimitiveType::Boolean) => {
2210            writeln!(
2211                out,
2212                "{indent}{setter}(::dds::topic::xcdr2::read_bool(__buf, __pos, __len));"
2213            )
2214            .map_err(fmt_err)?;
2215        }
2216        TypeSpec::Primitive(PrimitiveType::Octet) => {
2217            writeln!(
2218                out,
2219                "{indent}{setter}(::dds::topic::xcdr2::read_u8(__buf, __pos, __len));"
2220            )
2221            .map_err(fmt_err)?;
2222        }
2223        TypeSpec::Primitive(p) => {
2224            let cpp_ty = primitive_to_cpp(*p);
2225            writeln!(
2226                out,
2227                "{indent}{setter}(::dds::topic::xcdr2::read_le_origin<{cpp_ty}>(__buf, __pos, __len, {origin}));"
2228            )
2229            .map_err(fmt_err)?;
2230        }
2231        TypeSpec::String(s) if !s.wide => {
2232            writeln!(
2233                out,
2234                "{indent}{setter}(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, {origin}));"
2235            )
2236            .map_err(fmt_err)?;
2237        }
2238        TypeSpec::Sequence(seq) => {
2239            let elem_cpp_ty: String = match &*seq.elem {
2240                TypeSpec::Primitive(PrimitiveType::Boolean) => "bool".to_string(),
2241                TypeSpec::Primitive(p) => primitive_to_cpp(*p).to_string(),
2242                TypeSpec::String(s) if !s.wide => "std::string".to_string(),
2243                _ => {
2244                    writeln!(
2245                        out,
2246                        "{indent}// xcdr2: nested seq-elem nicht unterstuetzt (skip)"
2247                    )
2248                    .map_err(fmt_err)?;
2249                    return Ok(());
2250                }
2251            };
2252            writeln!(out, "{indent}{{").map_err(fmt_err)?;
2253            writeln!(out, "{indent}    auto __cnt = ::dds::topic::xcdr2::read_le_origin<uint32_t>(__buf, __pos, __len, {origin});").map_err(fmt_err)?;
2254            writeln!(out, "{indent}    std::vector<{elem_cpp_ty}> __seq;").map_err(fmt_err)?;
2255            writeln!(out, "{indent}    __seq.reserve(__cnt);").map_err(fmt_err)?;
2256            writeln!(
2257                out,
2258                "{indent}    for (uint32_t __i = 0; __i < __cnt; ++__i) {{"
2259            )
2260            .map_err(fmt_err)?;
2261            match &*seq.elem {
2262                TypeSpec::Primitive(PrimitiveType::Boolean) => {
2263                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_bool(__buf, __pos, __len));").map_err(fmt_err)?;
2264                }
2265                TypeSpec::Primitive(PrimitiveType::Octet) => {
2266                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_u8(__buf, __pos, __len));").map_err(fmt_err)?;
2267                }
2268                TypeSpec::Primitive(p) => {
2269                    let cpp_ty = primitive_to_cpp(*p);
2270                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_le_origin<{cpp_ty}>(__buf, __pos, __len, {origin}));").map_err(fmt_err)?;
2271                }
2272                TypeSpec::String(s) if !s.wide => {
2273                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, {origin}));").map_err(fmt_err)?;
2274                }
2275                _ => {}
2276            }
2277            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
2278            writeln!(out, "{indent}    {setter}(std::move(__seq));").map_err(fmt_err)?;
2279            writeln!(out, "{indent}}}").map_err(fmt_err)?;
2280        }
2281        _ => {}
2282    }
2283    Ok(())
2284}
2285
2286fn emit_mutable_member_decode_case(out: &mut String, m: &Member) -> Result<(), CppGenError> {
2287    if !member_codegen_supported(m) {
2288        return Ok(());
2289    }
2290    let id_override = find_uint_annotation(&m.annotations, "id");
2291    let is_optional = has_optional_annotation(&m.annotations);
2292    let _ = is_optional; // mutable optional: same path; absent member just skips this case.
2293    for decl in &m.declarators {
2294        let name = &decl.name().text;
2295        if !matches!(decl, Declarator::Simple(_)) {
2296            continue;
2297        }
2298        if !typespec_supported(&m.type_spec) {
2299            continue;
2300        }
2301        let id_expr = match id_override {
2302            Some(id) => id.to_string(),
2303            None => format!("0x{:x}u", auto_id_for(name)),
2304        };
2305        writeln!(out, "                case {id_expr}: {{").map_err(fmt_err)?;
2306        match &m.type_spec {
2307            TypeSpec::Primitive(PrimitiveType::Boolean) => {
2308                writeln!(out, "                    uint8_t __b = ::dds::topic::xcdr2::read_u8(__buf, __pos, __len);").map_err(fmt_err)?;
2309                if has_optional_annotation(&m.annotations) {
2310                    writeln!(
2311                        out,
2312                        "                    __v.{name}(static_cast<bool>(__b));"
2313                    )
2314                    .map_err(fmt_err)?;
2315                } else {
2316                    writeln!(
2317                        out,
2318                        "                    __v.{name}(static_cast<bool>(__b));"
2319                    )
2320                    .map_err(fmt_err)?;
2321                }
2322            }
2323            TypeSpec::Primitive(PrimitiveType::Octet) => {
2324                writeln!(out, "                    __v.{name}(::dds::topic::xcdr2::read_u8(__buf, __pos, __len));").map_err(fmt_err)?;
2325            }
2326            TypeSpec::Primitive(p) => {
2327                let cpp_ty = primitive_to_cpp(*p);
2328                writeln!(out, "                    __v.{name}(::dds::topic::xcdr2::read_le_raw<{cpp_ty}>(__buf, __pos, __len));").map_err(fmt_err)?;
2329            }
2330            TypeSpec::String(s) if !s.wide => {
2331                writeln!(out, "                    auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len);").map_err(fmt_err)?;
2332                writeln!(out, "                    (void)__n;").map_err(fmt_err)?;
2333                writeln!(out, "                    auto __body_origin = __pos;")
2334                    .map_err(fmt_err)?;
2335                writeln!(out, "                    __v.{name}(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, __body_origin));").map_err(fmt_err)?;
2336            }
2337            TypeSpec::Sequence(seq) => {
2338                let elem_cpp_ty: String = match &*seq.elem {
2339                    TypeSpec::Primitive(PrimitiveType::Boolean) => "bool".to_string(),
2340                    TypeSpec::Primitive(p) => primitive_to_cpp(*p).to_string(),
2341                    TypeSpec::String(s) if !s.wide => "std::string".to_string(),
2342                    _ => "uint8_t".to_string(),
2343                };
2344                writeln!(out, "                    auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len);").map_err(fmt_err)?;
2345                writeln!(out, "                    (void)__n;").map_err(fmt_err)?;
2346                writeln!(out, "                    auto __body_origin = __pos;")
2347                    .map_err(fmt_err)?;
2348                writeln!(out, "                    auto __cnt = ::dds::topic::xcdr2::read_le_origin<uint32_t>(__buf, __pos, __len, __body_origin);").map_err(fmt_err)?;
2349                writeln!(out, "                    std::vector<{elem_cpp_ty}> __seq;")
2350                    .map_err(fmt_err)?;
2351                writeln!(out, "                    __seq.reserve(__cnt);").map_err(fmt_err)?;
2352                writeln!(
2353                    out,
2354                    "                    for (uint32_t __i = 0; __i < __cnt; ++__i) {{"
2355                )
2356                .map_err(fmt_err)?;
2357                match &*seq.elem {
2358                    TypeSpec::Primitive(PrimitiveType::Boolean) => {
2359                        writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_bool(__buf, __pos, __len));").map_err(fmt_err)?;
2360                    }
2361                    TypeSpec::Primitive(PrimitiveType::Octet) => {
2362                        writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_u8(__buf, __pos, __len));").map_err(fmt_err)?;
2363                    }
2364                    TypeSpec::Primitive(p) => {
2365                        let cpp_ty = primitive_to_cpp(*p);
2366                        writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_le_origin<{cpp_ty}>(__buf, __pos, __len, __body_origin));").map_err(fmt_err)?;
2367                    }
2368                    TypeSpec::String(s) if !s.wide => {
2369                        writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, __body_origin));").map_err(fmt_err)?;
2370                    }
2371                    _ => {}
2372                }
2373                writeln!(out, "                    }}").map_err(fmt_err)?;
2374                writeln!(out, "                    __v.{name}(std::move(__seq));")
2375                    .map_err(fmt_err)?;
2376            }
2377            _ => {}
2378        }
2379        writeln!(out, "                    break;").map_err(fmt_err)?;
2380        writeln!(out, "                }}").map_err(fmt_err)?;
2381    }
2382    Ok(())
2383}
2384
2385fn emit_key_hash_fn(
2386    out: &mut String,
2387    cpp_fqn: &str,
2388    s: &StructDef,
2389    is_keyed: bool,
2390) -> Result<(), CppGenError> {
2391    writeln!(
2392        out,
2393        "    static std::array<uint8_t, 16> key_hash(const {cpp_fqn}& __v) {{"
2394    )
2395    .map_err(fmt_err)?;
2396    writeln!(out, "        (void)__v;").map_err(fmt_err)?;
2397    if !is_keyed {
2398        writeln!(
2399            out,
2400            "        return std::array<uint8_t, 16>{{{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}};"
2401        )
2402        .map_err(fmt_err)?;
2403        writeln!(out, "    }}").map_err(fmt_err)?;
2404        return Ok(());
2405    }
2406    writeln!(out, "        std::vector<uint8_t> __out;").map_err(fmt_err)?;
2407    writeln!(out, "        const size_t __origin = 0;").map_err(fmt_err)?;
2408    writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
2409    for m in &s.members {
2410        if !has_key_annotation(&m.annotations) {
2411            continue;
2412        }
2413        emit_plain_member_encode(out, m, "be", "__origin")?;
2414    }
2415    // XTypes 1.3 §7.6.8.4: Holder ≤ 16 octets -> zero-pad; sonst MD5.
2416    writeln!(out, "        std::array<uint8_t, 16> __h{{}};").map_err(fmt_err)?;
2417    writeln!(out, "        if (__out.size() <= 16) {{").map_err(fmt_err)?;
2418    writeln!(
2419        out,
2420        "            std::memcpy(__h.data(), __out.data(), __out.size());"
2421    )
2422    .map_err(fmt_err)?;
2423    writeln!(out, "            return __h;").map_err(fmt_err)?;
2424    writeln!(out, "        }}").map_err(fmt_err)?;
2425    writeln!(out, "        return ::dds::topic::xcdr2_md5::md5(__out);").map_err(fmt_err)?;
2426    writeln!(out, "    }}").map_err(fmt_err)?;
2427    Ok(())
2428}
2429
2430// ---------------------------------------------------------------------------
2431// Helpers: fmt-Error-Bridge
2432// ---------------------------------------------------------------------------
2433
2434fn fmt_err(_: core::fmt::Error) -> CppGenError {
2435    CppGenError::Internal("string formatting failed".into())
2436}
2437
2438#[allow(dead_code)]
2439fn _ensure_used() {
2440    // is_reserved wird von check_identifier genutzt — Compiler-Hint.
2441    let _ = is_reserved("int");
2442}