Skip to main content

zerodds_idl_cpp/
emitter.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! AST walker that emits C++17 headers.
4//!
5//! Block-A: Header layout (`#pragma once`, includes, namespaces).
6//! Block-B: Primitive mapping (delegated to [`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 via DDS::Time_t / DDS::Duration_t.
10//!
11//! Emission is single-pass: all required standard includes are collected
12//! via a pre-walk, then the body is emitted. This keeps the header
13//! preamble deterministic (alphabetically sorted).
14
15use core::cell::RefCell;
16use std::collections::{BTreeMap, 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/// Known header includes. Order is stably alphabetical.
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/// Main entry point: generates the complete C++ header.
48/// `true` if `ts` is (or, through sequence nesting, contains) a bounded
49/// collection that the encoder enforces: a bounded `sequence<T, N>` or a bounded
50/// narrow `string<N>`. Drives the conditional `<stdexcept>` include.
51///
52/// zerodds-lint: recursion-depth 64 (bounded by IDL nesting)
53fn type_has_bounded_collection(ts: &TypeSpec) -> bool {
54    match ts {
55        TypeSpec::Sequence(s) => s.bound.is_some() || type_has_bounded_collection(&s.elem),
56        // narrow `string<N>` AND wide `wstring<N>` both throw on over-bound.
57        TypeSpec::String(s) => s.bound.is_some(),
58        _ => false,
59    }
60}
61
62/// `true` if any topic struct in `spec` has a member with a bounded collection.
63fn spec_has_bounded_collection(spec: &Specification) -> bool {
64    let mut structs: Vec<(String, &StructDef)> = Vec::new();
65    collect_topic_structs(&spec.definitions, "", &mut structs);
66    structs.iter().any(|(_, s)| {
67        s.members
68            .iter()
69            .any(|m| type_has_bounded_collection(&m.type_spec))
70    })
71}
72
73pub(crate) fn emit_header(
74    spec: &Specification,
75    opts: &CppGenOptions,
76) -> Result<String, CppGenError> {
77    // 1. Detect colliding inheritance cycles (before emission).
78    detect_inheritance_cycles(spec)?;
79
80    // 1b. Codegen-scoped type registry (enum/struct simple-names) so the XCDR2
81    //     member encoder can classify a `Scoped` member as an enum.
82    set_type_registry(spec);
83
84    // 2. Walk pre-pass: collect includes.
85    let mut includes = Includes::default();
86    includes.add("<cstdint>"); // stdint.h is always present (uint8_t etc.).
87    collect_includes(spec, &mut includes);
88    // Bounded collections throw std::length_error on over-bound encode
89    // (DDS-XTypes §7.4.3) — pull <stdexcept> only when needed, so headers
90    // without bounded types stay byte-identical.
91    if spec_has_bounded_collection(spec) {
92        includes.add("<stdexcept>");
93    }
94
95    // 3. Output buffer.
96    let mut out = String::new();
97    write_header_preamble(&mut out, opts, &includes)?;
98
99    // 4. Optional top-level namespace wrapping (`namespace_prefix`).
100    let mut ctx = EmitCtx::new(opts);
101    let outer_prefix: Option<&str> = opts.namespace_prefix.as_deref().filter(|p| !p.is_empty());
102    if let Some(prefix) = outer_prefix {
103        ctx.open_namespace(&mut out, prefix)?;
104    }
105
106    // 4b. §7.2.2.4.8 — `@verbatim(placement=BEGIN_FILE)` from all
107    // top-level defs. The spec says nothing about ordering for multiple —
108    // we use source order.
109    for d in &spec.definitions {
110        if let Some(anns) = top_level_annotations(d) {
111            emit_verbatim_at(&mut out, "", anns, PlacementKind::BeginFile)?;
112        }
113    }
114
115    // 4c. If the spec contains at least one top-level or module-nested
116    //     `struct` definition, we also emit `dds/topic/TopicTraits.hpp`
117    //     after the standard includes — that header provides
118    //     `topic_type_support<T>` and ByteSeq/string defaults — as well as
119    //     `dds/topic/xcdr2.hpp` and `dds/topic/xcdr2_md5.hpp` for the
120    //     XCDR2 wire-encoder/MD5 helpers that the later-emitted
121    //     specializations depend on.
122    let mut probe_structs: Vec<(String, &StructDef)> = Vec::new();
123    collect_topic_structs(&spec.definitions, "", &mut probe_structs);
124    if !probe_structs.is_empty() {
125        // <array> is needed by key_hash(), <vector>/<string> by
126        // encode/decode(). If the standard walks have not already pulled
127        // them in, they are covered transitively via TopicTraits.hpp —
128        // but we emit them explicitly here so the header remains
129        // syntactically valid without the topic helpers.
130        writeln!(&mut out, "#include \"dds/topic/TopicTraits.hpp\"").map_err(fmt_err)?;
131        writeln!(&mut out, "#include \"dds/topic/xcdr2.hpp\"").map_err(fmt_err)?;
132        writeln!(&mut out, "#include \"dds/topic/xcdr2_md5.hpp\"").map_err(fmt_err)?;
133        writeln!(&mut out).map_err(fmt_err)?;
134    }
135
136    // 5. Emit definitions.
137    for d in &spec.definitions {
138        emit_definition(&mut out, &mut ctx, d)?;
139    }
140
141    // 5b. §7.2.2.4.8 — `@verbatim(placement=END_FILE)` from all
142    // top-level defs.
143    for d in &spec.definitions {
144        if let Some(anns) = top_level_annotations(d) {
145            emit_verbatim_at(&mut out, "", anns, PlacementKind::EndFile)?;
146        }
147    }
148
149    // 6. Optionally close the top-level namespace.
150    if let Some(prefix) = outer_prefix {
151        ctx.close_namespace(&mut out, prefix)?;
152    }
153
154    // 7. `topic_type_support<T>` specializations for all struct defs.
155    //    Lives in the `::dds::topic` namespace (global scope), so emitted
156    //    after the `outer_prefix` close with fully-qualified T.
157    if !probe_structs.is_empty() {
158        emit_topic_type_support_specs(&mut out, opts, &probe_structs)?;
159    }
160
161    Ok(out)
162}
163
164/// Returns the annotation list of a top-level `Definition`, if the
165/// variant carries one. Used for file-level verbatim emission.
166fn top_level_annotations(d: &Definition) -> Option<&[Annotation]> {
167    match d {
168        Definition::Module(m) => Some(&m.annotations),
169        Definition::Type(TypeDecl::Constr(c)) => match c {
170            ConstrTypeDecl::Struct(StructDcl::Def(s)) => Some(&s.annotations),
171            ConstrTypeDecl::Union(UnionDcl::Def(u)) => Some(&u.annotations),
172            ConstrTypeDecl::Enum(e) => Some(&e.annotations),
173            _ => None,
174        },
175        Definition::Type(TypeDecl::Typedef(t)) => Some(&t.annotations),
176        Definition::Const(c) => Some(&c.annotations),
177        Definition::Except(e) => Some(&e.annotations),
178        _ => None,
179    }
180}
181
182/// Writes `// Generated...`, `#pragma once`, and standard includes.
183fn write_header_preamble(
184    out: &mut String,
185    opts: &CppGenOptions,
186    includes: &Includes,
187) -> Result<(), CppGenError> {
188    writeln!(out, "// Generated by zerodds idl-cpp. Do not edit.").map_err(fmt_err)?;
189    writeln!(out, "#pragma once").map_err(fmt_err)?;
190    if let Some(guard) = opts.include_guard_prefix.as_deref() {
191        if !guard.is_empty() {
192            // Optional guard comment for tools that only accept classical
193            // include guards. `#pragma once` remains primary.
194            writeln!(out, "// guard-prefix: {guard}").map_err(fmt_err)?;
195        }
196    }
197    writeln!(out).map_err(fmt_err)?;
198    for h in &includes.headers {
199        writeln!(out, "#include {h}").map_err(fmt_err)?;
200    }
201    writeln!(out).map_err(fmt_err)?;
202    Ok(())
203}
204
205/// Walks the AST and collects required `<...>` includes.
206fn collect_includes(spec: &Specification, inc: &mut Includes) {
207    for d in &spec.definitions {
208        collect_in_def(d, inc);
209    }
210}
211
212/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
213fn collect_in_def(d: &Definition, inc: &mut Includes) {
214    match d {
215        Definition::Module(m) => {
216            for sub in &m.definitions {
217                collect_in_def(sub, inc);
218            }
219        }
220        Definition::Type(td) => collect_in_typedecl(td, inc),
221        Definition::Const(_) => {}
222        Definition::Except(e) => {
223            inc.add("<exception>");
224            for m in &e.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            }
232        }
233        Definition::Interface(_)
234        | Definition::ValueBox(_)
235        | Definition::ValueForward(_)
236        | Definition::ValueDef(_)
237        | Definition::TypeId(_)
238        | Definition::TypePrefix(_)
239        | Definition::Import(_)
240        | Definition::Component(_)
241        | Definition::Home(_)
242        | Definition::Event(_)
243        | Definition::Porttype(_)
244        | Definition::Connector(_)
245        | Definition::TemplateModule(_)
246        | Definition::TemplateModuleInst(_)
247        | Definition::Annotation(_)
248        | Definition::VendorExtension(_) => {
249            // Recognised as UnsupportedConstruct in emit_definition;
250            // no includes to collect here.
251        }
252    }
253}
254
255fn collect_in_typedecl(td: &TypeDecl, inc: &mut Includes) {
256    match td {
257        TypeDecl::Constr(c) => match c {
258            ConstrTypeDecl::Struct(StructDcl::Def(s)) => {
259                for m in &s.members {
260                    collect_in_typespec(&m.type_spec, inc);
261                    for decl in &m.declarators {
262                        if matches!(decl, Declarator::Array(_)) {
263                            inc.add("<array>");
264                        }
265                    }
266                    if has_optional_annotation(&m.annotations) {
267                        inc.add("<optional>");
268                    }
269                    if has_shared_annotation(&m.annotations) {
270                        inc.add("<memory>");
271                    }
272                }
273            }
274            ConstrTypeDecl::Struct(StructDcl::Forward(_)) => {}
275            ConstrTypeDecl::Union(UnionDcl::Def(u)) => {
276                inc.add("<variant>");
277                for c in &u.cases {
278                    collect_in_typespec(&c.element.type_spec, inc);
279                    if matches!(c.element.declarator, Declarator::Array(_)) {
280                        inc.add("<array>");
281                    }
282                }
283            }
284            ConstrTypeDecl::Union(UnionDcl::Forward(_)) => {}
285            ConstrTypeDecl::Enum(_) | ConstrTypeDecl::Bitset(_) | ConstrTypeDecl::Bitmask(_) => {}
286        },
287        TypeDecl::Typedef(t) => {
288            collect_in_typespec(&t.type_spec, inc);
289            for decl in &t.declarators {
290                if matches!(decl, Declarator::Array(_)) {
291                    inc.add("<array>");
292                }
293            }
294        }
295        // `native X;` — opaque type, no includes.
296        TypeDecl::Native(_) => {}
297    }
298}
299
300/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
301fn collect_in_typespec(ts: &TypeSpec, inc: &mut Includes) {
302    match ts {
303        TypeSpec::Primitive(_) => {}
304        TypeSpec::Scoped(_) => {}
305        TypeSpec::Sequence(s) => {
306            inc.add("<vector>");
307            collect_in_typespec(&s.elem, inc);
308        }
309        TypeSpec::String(_) => {
310            // Both `string` and `wstring` are defined in `<string>`.
311            inc.add("<string>");
312        }
313        TypeSpec::Map(m) => {
314            inc.add("<map>");
315            collect_in_typespec(&m.key, inc);
316            collect_in_typespec(&m.value, inc);
317        }
318        TypeSpec::Fixed(_) => {
319            // §7.2.4.2.4 — `fixed<digits, scale>` -> `dds::core::Fixed`.
320            // We emit a forward-declared wrapper class
321            // (runtime implementation comes with the `dds-core` crate).
322            inc.add("<cstdint>");
323        }
324        TypeSpec::Any => {
325            // §7.3 — `any` -> `dds::core::Any`. Runtime in dds-core.
326            inc.add("<cstdint>");
327        }
328    }
329}
330
331/// Emit context (indentation, output).
332struct EmitCtx<'o> {
333    opts: &'o CppGenOptions,
334    indent_level: usize,
335}
336
337impl<'o> EmitCtx<'o> {
338    fn new(opts: &'o CppGenOptions) -> Self {
339        Self {
340            opts,
341            indent_level: 0,
342        }
343    }
344
345    fn indent(&self) -> String {
346        " ".repeat(self.indent_level * self.opts.indent_width)
347    }
348
349    fn open_namespace(&mut self, out: &mut String, name: &str) -> Result<(), CppGenError> {
350        writeln!(out, "{}namespace {name} {{", self.indent()).map_err(fmt_err)?;
351        self.indent_level += 1;
352        Ok(())
353    }
354
355    fn close_namespace(&mut self, out: &mut String, name: &str) -> Result<(), CppGenError> {
356        self.indent_level = self.indent_level.saturating_sub(1);
357        writeln!(out, "{}}} // namespace {name}", self.indent()).map_err(fmt_err)?;
358        Ok(())
359    }
360}
361
362/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
363fn emit_definition(
364    out: &mut String,
365    ctx: &mut EmitCtx<'_>,
366    def: &Definition,
367) -> Result<(), CppGenError> {
368    match def {
369        Definition::Module(m) => emit_module(out, ctx, m),
370        Definition::Type(td) => emit_type_decl(out, ctx, td),
371        Definition::Const(c) => emit_const_decl(out, ctx, c),
372        Definition::Except(e) => emit_exception(out, ctx, e),
373        Definition::Interface(InterfaceDcl::Def(iface)) => {
374            // Spec idl4-cpp §7.4: IDL interface -> C++ pure-virtual class.
375            // `@service` interfaces go on via the RPC codegen path
376            // (see `crate::rpc`); here is the legacy CORBA stub path
377            // for non-service interfaces.
378            emit_interface_stub(out, ctx, iface)
379        }
380        Definition::Interface(InterfaceDcl::Forward(f)) => {
381            check_identifier(&f.name.text)?;
382            writeln!(out, "{}class {};", ctx.indent(), f.name.text).map_err(fmt_err)?;
383            Ok(())
384        }
385        Definition::ValueDef(v) => emit_value_type(out, ctx, v),
386        Definition::ValueBox(_) | Definition::ValueForward(_) => {
387            // ValueBox + ValueForward are rare CORBA constructs;
388            // the foundation leaves them as a no-op (spec §7.6.x allows
389            // a forward decl with later resolution).
390            Ok(())
391        }
392        Definition::TypeId(_)
393        | Definition::TypePrefix(_)
394        | Definition::Import(_)
395        | Definition::Component(_)
396        | Definition::Home(_)
397        | Definition::Event(_)
398        | Definition::Porttype(_)
399        | Definition::Connector(_)
400        | Definition::TemplateModule(_)
401        | Definition::TemplateModuleInst(_) => Err(CppGenError::UnsupportedConstruct {
402            construct: "corba/ccm/template construct".into(),
403            context: None,
404        }),
405        Definition::Annotation(_) => {
406            // §7.4.15 annotation defs do not become C++ code directly —
407            // applications are emitted at the annotated members via
408            // annotation bridges (e.g. @key).
409            Ok(())
410        }
411        Definition::VendorExtension(v) => Err(CppGenError::UnsupportedConstruct {
412            construct: format!("vendor-extension:{}", v.production_name),
413            context: None,
414        }),
415    }
416}
417
418/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
419fn emit_module(out: &mut String, ctx: &mut EmitCtx<'_>, m: &ModuleDef) -> Result<(), CppGenError> {
420    check_identifier(&m.name.text)?;
421    ctx.open_namespace(out, &m.name.text)?;
422    for d in &m.definitions {
423        emit_definition(out, ctx, d)?;
424    }
425    ctx.close_namespace(out, &m.name.text)?;
426    Ok(())
427}
428
429fn emit_type_decl(
430    out: &mut String,
431    ctx: &mut EmitCtx<'_>,
432    td: &TypeDecl,
433) -> Result<(), CppGenError> {
434    match td {
435        TypeDecl::Constr(c) => match c {
436            ConstrTypeDecl::Struct(StructDcl::Def(s)) => emit_struct(out, ctx, s),
437            ConstrTypeDecl::Struct(StructDcl::Forward(f)) => {
438                check_identifier(&f.name.text)?;
439                writeln!(out, "{}class {};", ctx.indent(), f.name.text).map_err(fmt_err)?;
440                Ok(())
441            }
442            ConstrTypeDecl::Union(UnionDcl::Def(u)) => emit_union(out, ctx, u),
443            ConstrTypeDecl::Union(UnionDcl::Forward(f)) => {
444                check_identifier(&f.name.text)?;
445                writeln!(out, "{}class {};", ctx.indent(), f.name.text).map_err(fmt_err)?;
446                Ok(())
447            }
448            ConstrTypeDecl::Enum(e) => emit_enum(out, ctx, e),
449            ConstrTypeDecl::Bitset(b) => {
450                check_identifier(&b.name.text)?;
451                let ind = ctx.indent();
452                let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
453                emit_bitset(out, &ind, &inner, b)
454            }
455            ConstrTypeDecl::Bitmask(b) => {
456                check_identifier(&b.name.text)?;
457                let ind = ctx.indent();
458                let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
459                emit_bitmask(out, &ind, &inner, b)
460            }
461        },
462        TypeDecl::Typedef(t) => emit_typedef(out, ctx, t),
463        // `native X;` — opaque, platform-specific type without a wire
464        // representation; not emitted in the DataType codegen.
465        TypeDecl::Native(_) => Ok(()),
466    }
467}
468
469fn emit_struct(out: &mut String, ctx: &mut EmitCtx<'_>, s: &StructDef) -> Result<(), CppGenError> {
470    check_identifier(&s.name.text)?;
471    let ind = ctx.indent();
472
473    // §7.2.2.4.8 — `@verbatim(placement=BEFORE_DECLARATION)` before the
474    // class header line.
475    emit_verbatim_at(out, &ind, &s.annotations, PlacementKind::BeforeDeclaration)?;
476
477    // Class header with an optional inheritance clause.
478    if let Some(base) = &s.base {
479        let base_str = scoped_to_cpp(base);
480        writeln!(out, "{ind}class {} : public {} {{", s.name.text, base_str).map_err(fmt_err)?;
481    } else {
482        writeln!(out, "{ind}class {} {{", s.name.text).map_err(fmt_err)?;
483    }
484    writeln!(out, "{ind}public:").map_err(fmt_err)?;
485
486    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
487
488    // §7.2.2.4.8 — `@verbatim(placement=BEGIN_DECLARATION)` as the first
489    // line inside the `public:` block.
490    emit_verbatim_at(out, &inner, &s.annotations, PlacementKind::BeginDeclaration)?;
491
492    // Default constructor.
493    writeln!(out, "{inner}{}() = default;", s.name.text).map_err(fmt_err)?;
494    writeln!(out, "{inner}~{}() = default;", s.name.text).map_err(fmt_err)?;
495    writeln!(out).map_err(fmt_err)?;
496
497    // Member fields as private storage.
498    writeln!(out, "{ind}private:").map_err(fmt_err)?;
499    for m in &s.members {
500        emit_struct_member_field(out, ctx, m)?;
501    }
502    writeln!(out).map_err(fmt_err)?;
503
504    // Reference-Pattern Getter (mutable + const).
505    writeln!(out, "{ind}public:").map_err(fmt_err)?;
506    for m in &s.members {
507        emit_struct_member_accessors(out, ctx, m)?;
508    }
509
510    // §7.2.2.4.8 — `@verbatim(placement=END_DECLARATION)` as the last
511    // line before `};`.
512    emit_verbatim_at(out, &inner, &s.annotations, PlacementKind::EndDeclaration)?;
513
514    writeln!(out, "{ind}}};").map_err(fmt_err)?;
515
516    // §7.2.2.4.8 — `@verbatim(placement=AFTER_DECLARATION)` direkt
517    // after the closing `};`.
518    emit_verbatim_at(out, &ind, &s.annotations, PlacementKind::AfterDeclaration)?;
519
520    writeln!(out).map_err(fmt_err)?;
521    Ok(())
522}
523
524fn emit_struct_member_field(
525    out: &mut String,
526    ctx: &EmitCtx<'_>,
527    m: &Member,
528) -> Result<(), CppGenError> {
529    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
530    let optional = has_optional_annotation(&m.annotations);
531    let shared = has_shared_annotation(&m.annotations);
532    for decl in &m.declarators {
533        let cpp_ty = type_for_declarator(&m.type_spec, decl)?;
534        let name = decl.name();
535        check_identifier(&name.text)?;
536        let key_marker = if has_key_annotation(&m.annotations) {
537            " // @key"
538        } else {
539            ""
540        };
541        // §8.1.5 `@shared` -> `std::shared_ptr<T>`. Combination with
542        // `@optional` yields `std::optional<std::shared_ptr<T>>`.
543        let core_ty = if shared {
544            format!("std::shared_ptr<{cpp_ty}>")
545        } else {
546            cpp_ty
547        };
548        if optional {
549            writeln!(
550                out,
551                "{inner}std::optional<{core_ty}> {}_;{key_marker}",
552                name.text
553            )
554            .map_err(fmt_err)?;
555        } else {
556            writeln!(out, "{inner}{core_ty} {}_;{key_marker}", name.text).map_err(fmt_err)?;
557        }
558    }
559    Ok(())
560}
561
562fn emit_struct_member_accessors(
563    out: &mut String,
564    ctx: &EmitCtx<'_>,
565    m: &Member,
566) -> Result<(), CppGenError> {
567    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
568    let optional = has_optional_annotation(&m.annotations);
569    let shared = has_shared_annotation(&m.annotations);
570    for decl in &m.declarators {
571        let cpp_ty = type_for_declarator(&m.type_spec, decl)?;
572        let name = &decl.name().text;
573        let core_ty = if shared {
574            format!("std::shared_ptr<{cpp_ty}>")
575        } else {
576            cpp_ty.clone()
577        };
578        let storage_ty = if optional {
579            format!("std::optional<{core_ty}>")
580        } else {
581            core_ty
582        };
583        writeln!(out, "{inner}{storage_ty}& {name}() {{ return {name}_; }}").map_err(fmt_err)?;
584        writeln!(
585            out,
586            "{inner}const {storage_ty}& {name}() const {{ return {name}_; }}"
587        )
588        .map_err(fmt_err)?;
589        writeln!(
590            out,
591            "{inner}void {name}(const {storage_ty}& value) {{ {name}_ = value; }}"
592        )
593        .map_err(fmt_err)?;
594    }
595    Ok(())
596}
597
598fn emit_union(out: &mut String, ctx: &mut EmitCtx<'_>, u: &UnionDef) -> Result<(), CppGenError> {
599    check_identifier(&u.name.text)?;
600    let ind = ctx.indent();
601    emit_verbatim_at(out, &ind, &u.annotations, PlacementKind::BeforeDeclaration)?;
602    writeln!(out, "{ind}class {} {{", u.name.text).map_err(fmt_err)?;
603    writeln!(out, "{ind}public:").map_err(fmt_err)?;
604    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
605    emit_verbatim_at(out, &inner, &u.annotations, PlacementKind::BeginDeclaration)?;
606
607    let disc_ty = switch_type_to_cpp(&u.switch_type)?;
608
609    // Build the variant list from distinct element types.
610    let mut variant_types: Vec<String> = Vec::new();
611    for c in &u.cases {
612        let cpp_ty = type_for_declarator(&c.element.type_spec, &c.element.declarator)?;
613        if !variant_types.iter().any(|t| t == &cpp_ty) {
614            variant_types.push(cpp_ty);
615        }
616    }
617    let variant_str = if variant_types.is_empty() {
618        "std::monostate".to_string()
619    } else {
620        variant_types.join(", ")
621    };
622
623    writeln!(
624        out,
625        "{inner}using value_type = std::variant<{variant_str}>;"
626    )
627    .map_err(fmt_err)?;
628    writeln!(out, "{inner}{}() = default;", u.name.text).map_err(fmt_err)?;
629    writeln!(out, "{inner}~{}() = default;", u.name.text).map_err(fmt_err)?;
630    writeln!(out).map_err(fmt_err)?;
631
632    // Discriminator.
633    writeln!(
634        out,
635        "{inner}{disc_ty} _d() const {{ return discriminator_; }}"
636    )
637    .map_err(fmt_err)?;
638    writeln!(out, "{inner}void _d({disc_ty} d) {{ discriminator_ = d; }}").map_err(fmt_err)?;
639    writeln!(out, "{inner}value_type& value() {{ return value_; }}").map_err(fmt_err)?;
640    writeln!(
641        out,
642        "{inner}const value_type& value() const {{ return value_; }}"
643    )
644    .map_err(fmt_err)?;
645    writeln!(out).map_err(fmt_err)?;
646
647    // Branch markers as comments (discriminator values).
648    let mut has_default = false;
649    for c in &u.cases {
650        emit_union_case_comment(out, &inner, c, &mut has_default)?;
651    }
652    if !has_default {
653        writeln!(out, "{inner}// no explicit 'default:' branch").map_err(fmt_err)?;
654    }
655
656    writeln!(out).map_err(fmt_err)?;
657    writeln!(out, "{ind}private:").map_err(fmt_err)?;
658    writeln!(out, "{inner}{disc_ty} discriminator_{{}};").map_err(fmt_err)?;
659    writeln!(out, "{inner}value_type value_{{}};").map_err(fmt_err)?;
660    emit_verbatim_at(out, &inner, &u.annotations, PlacementKind::EndDeclaration)?;
661    writeln!(out, "{ind}}};").map_err(fmt_err)?;
662    emit_verbatim_at(out, &ind, &u.annotations, PlacementKind::AfterDeclaration)?;
663    writeln!(out).map_err(fmt_err)?;
664    Ok(())
665}
666
667fn emit_union_case_comment(
668    out: &mut String,
669    inner: &str,
670    c: &Case,
671    has_default: &mut bool,
672) -> Result<(), CppGenError> {
673    for label in &c.labels {
674        match label {
675            CaseLabel::Default => {
676                *has_default = true;
677                writeln!(
678                    out,
679                    "{inner}// case default -> {}",
680                    declarator_name(&c.element.declarator)
681                )
682                .map_err(fmt_err)?;
683            }
684            CaseLabel::Value(expr) => {
685                let val = const_expr_to_cpp(expr);
686                writeln!(
687                    out,
688                    "{inner}// case {val} -> {}",
689                    declarator_name(&c.element.declarator)
690                )
691                .map_err(fmt_err)?;
692            }
693        }
694    }
695    Ok(())
696}
697
698pub(crate) fn declarator_name(d: &Declarator) -> &str {
699    &d.name().text
700}
701
702fn emit_enum(out: &mut String, ctx: &mut EmitCtx<'_>, e: &EnumDef) -> Result<(), CppGenError> {
703    check_identifier(&e.name.text)?;
704    let ind = ctx.indent();
705    emit_verbatim_at(out, &ind, &e.annotations, PlacementKind::BeforeDeclaration)?;
706    writeln!(out, "{ind}enum class {} : int32_t {{", e.name.text).map_err(fmt_err)?;
707    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
708    emit_verbatim_at(out, &inner, &e.annotations, PlacementKind::BeginDeclaration)?;
709    for en in &e.enumerators {
710        check_identifier(&en.name.text)?;
711        writeln!(out, "{inner}{},", en.name.text).map_err(fmt_err)?;
712    }
713    emit_verbatim_at(out, &inner, &e.annotations, PlacementKind::EndDeclaration)?;
714    writeln!(out, "{ind}}};").map_err(fmt_err)?;
715    emit_verbatim_at(out, &ind, &e.annotations, PlacementKind::AfterDeclaration)?;
716    writeln!(out).map_err(fmt_err)?;
717    Ok(())
718}
719
720fn emit_interface_stub(
721    out: &mut String,
722    ctx: &mut EmitCtx<'_>,
723    iface: &InterfaceDef,
724) -> Result<(), CppGenError> {
725    let name = &iface.name.text;
726    check_identifier(name)?;
727    let ind = ctx.indent();
728    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
729
730    emit_verbatim_at(
731        out,
732        &ind,
733        &iface.annotations,
734        PlacementKind::BeforeDeclaration,
735    )?;
736
737    // Bases via public virtual inheritance (CORBA pattern for diamonds).
738    if iface.bases.is_empty() {
739        writeln!(out, "{ind}class {name} {{").map_err(fmt_err)?;
740    } else {
741        let bases: Vec<String> = iface
742            .bases
743            .iter()
744            .map(|b| format!("public virtual {}", scoped_to_cpp(b)))
745            .collect();
746        writeln!(out, "{ind}class {name} : {} {{", bases.join(", ")).map_err(fmt_err)?;
747    }
748    writeln!(out, "{ind}public:").map_err(fmt_err)?;
749    writeln!(out, "{inner}virtual ~{name}() = default;").map_err(fmt_err)?;
750
751    for export in &iface.exports {
752        match export {
753            Export::Op(op) => emit_interface_op(out, &inner, op)?,
754            Export::Attr(attr) => emit_interface_attr(out, &inner, attr)?,
755            Export::Type(td) => emit_type_decl(out, ctx, td)?,
756            Export::Const(c) => emit_const_decl(out, ctx, c)?,
757            Export::Except(e) => emit_exception(out, ctx, e)?,
758        }
759    }
760
761    writeln!(out, "{ind}}};").map_err(fmt_err)?;
762    emit_verbatim_at(
763        out,
764        &ind,
765        &iface.annotations,
766        PlacementKind::AfterDeclaration,
767    )?;
768    writeln!(out).map_err(fmt_err)?;
769    Ok(())
770}
771
772fn emit_interface_op(out: &mut String, inner: &str, op: &OpDecl) -> Result<(), CppGenError> {
773    check_identifier(&op.name.text)?;
774    let ret = match &op.return_type {
775        None => "void".to_string(),
776        Some(t) => typespec_to_cpp(t)?,
777    };
778    let params: Vec<String> = op
779        .params
780        .iter()
781        .map(|p| -> Result<String, CppGenError> {
782            let ty = typespec_to_cpp(&p.type_spec)?;
783            // Spec §7.4.5: in -> const T& (or T for primitives),
784            // out/inout -> T&. For the foundation: const T&/T& consistent.
785            let qual = match p.attribute {
786                ParamAttribute::In => format!("const {ty}&"),
787                ParamAttribute::Out | ParamAttribute::InOut => format!("{ty}&"),
788            };
789            Ok(format!("{qual} {}", p.name.text))
790        })
791        .collect::<Result<_, _>>()?;
792    let raises_comment = if op.raises.is_empty() {
793        String::new()
794    } else {
795        let raises: Vec<String> = op.raises.iter().map(scoped_to_cpp).collect();
796        format!(" /* throws {} */", raises.join(", "))
797    };
798    writeln!(
799        out,
800        "{inner}virtual {ret} {}({}) = 0;{raises_comment}",
801        op.name.text,
802        params.join(", ")
803    )
804    .map_err(fmt_err)?;
805    Ok(())
806}
807
808fn emit_interface_attr(
809    out: &mut String,
810    inner: &str,
811    attr: &zerodds_idl::ast::AttrDecl,
812) -> Result<(), CppGenError> {
813    check_identifier(&attr.name.text)?;
814    let ty = typespec_to_cpp(&attr.type_spec)?;
815    // Getter (every attribute has one).
816    writeln!(out, "{inner}virtual {ty} {}() const = 0;", attr.name.text).map_err(fmt_err)?;
817    // Setter only for non-readonly.
818    if !attr.readonly {
819        writeln!(
820            out,
821            "{inner}virtual void {}(const {ty}& value) = 0;",
822            attr.name.text
823        )
824        .map_err(fmt_err)?;
825    }
826    Ok(())
827}
828
829fn emit_value_type(
830    out: &mut String,
831    ctx: &mut EmitCtx<'_>,
832    v: &ValueDef,
833) -> Result<(), CppGenError> {
834    let name = &v.name.text;
835    check_identifier(name)?;
836    let ind = ctx.indent();
837    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
838
839    emit_verbatim_at(out, &ind, &v.annotations, PlacementKind::BeforeDeclaration)?;
840
841    // Spec idl4-cpp §7.6: valuetype -> C++ class with pure-virtual
842    // public/protected accessors (state) + factory class.
843    // Inheritance: public virtual for all bases + supports interfaces.
844    let mut bases: Vec<String> = Vec::new();
845    if let Some(inh) = &v.inheritance {
846        for b in &inh.bases {
847            bases.push(format!("public virtual {}", scoped_to_cpp(b)));
848        }
849        for s in &inh.supports {
850            bases.push(format!("public virtual {}", scoped_to_cpp(s)));
851        }
852    }
853    if bases.is_empty() {
854        writeln!(out, "{ind}class {name} {{").map_err(fmt_err)?;
855    } else {
856        writeln!(out, "{ind}class {name} : {} {{", bases.join(", ")).map_err(fmt_err)?;
857    }
858    writeln!(out, "{ind}public:").map_err(fmt_err)?;
859    writeln!(out, "{inner}virtual ~{name}() = default;").map_err(fmt_err)?;
860
861    // Public state + methods.
862    let mut has_protected = false;
863    for el in &v.elements {
864        match el {
865            ValueElement::State(s) if matches!(s.visibility, StateVisibility::Public) => {
866                let ty = typespec_to_cpp(&s.type_spec)?;
867                for d in &s.declarators {
868                    let n = &d.name().text;
869                    writeln!(out, "{inner}virtual const {ty}& {n}() const = 0;")
870                        .map_err(fmt_err)?;
871                    writeln!(out, "{inner}virtual void {n}(const {ty}& value) = 0;")
872                        .map_err(fmt_err)?;
873                }
874            }
875            ValueElement::State(s) if matches!(s.visibility, StateVisibility::Private) => {
876                has_protected = true;
877            }
878            ValueElement::Export(Export::Op(op)) => emit_interface_op(out, &inner, op)?,
879            ValueElement::Export(Export::Attr(a)) => emit_interface_attr(out, &inner, a)?,
880            _ => {}
881        }
882    }
883
884    // Protected-State (private members in IDL -> protected in C++ per Spec).
885    if has_protected {
886        writeln!(out, "{ind}protected:").map_err(fmt_err)?;
887        for el in &v.elements {
888            if let ValueElement::State(s) = el {
889                if matches!(s.visibility, StateVisibility::Private) {
890                    let ty = typespec_to_cpp(&s.type_spec)?;
891                    for d in &s.declarators {
892                        let n = &d.name().text;
893                        writeln!(out, "{inner}virtual const {ty}& {n}() const = 0;")
894                            .map_err(fmt_err)?;
895                        writeln!(out, "{inner}virtual void {n}(const {ty}& value) = 0;")
896                            .map_err(fmt_err)?;
897                    }
898                }
899            }
900        }
901    }
902
903    writeln!(out, "{ind}}};").map_err(fmt_err)?;
904
905    // Factory-Class pro factory-init (Spec §7.6: <ValueTypeName>_factory).
906    let factories: Vec<&zerodds_idl::ast::InitDcl> = v
907        .elements
908        .iter()
909        .filter_map(|e| {
910            if let ValueElement::Init(i) = e {
911                Some(i)
912            } else {
913                None
914            }
915        })
916        .collect();
917    if !factories.is_empty() {
918        writeln!(out, "{ind}class {name}_factory {{").map_err(fmt_err)?;
919        writeln!(out, "{ind}public:").map_err(fmt_err)?;
920        writeln!(out, "{inner}virtual ~{name}_factory() = default;").map_err(fmt_err)?;
921        for f in &factories {
922            check_identifier(&f.name.text)?;
923            let params: Vec<String> = f
924                .params
925                .iter()
926                .map(|p| -> Result<String, CppGenError> {
927                    let ty = typespec_to_cpp(&p.type_spec)?;
928                    let qual = match p.attribute {
929                        ParamAttribute::In => format!("const {ty}&"),
930                        ParamAttribute::Out | ParamAttribute::InOut => format!("{ty}&"),
931                    };
932                    Ok(format!("{qual} {}", p.name.text))
933                })
934                .collect::<Result<_, _>>()?;
935            writeln!(
936                out,
937                "{inner}virtual std::shared_ptr<{name}> {}({}) = 0;",
938                f.name.text,
939                params.join(", ")
940            )
941            .map_err(fmt_err)?;
942        }
943        writeln!(out, "{ind}}};").map_err(fmt_err)?;
944    }
945
946    emit_verbatim_at(out, &ind, &v.annotations, PlacementKind::AfterDeclaration)?;
947    writeln!(out).map_err(fmt_err)?;
948    Ok(())
949}
950
951fn emit_typedef(
952    out: &mut String,
953    ctx: &mut EmitCtx<'_>,
954    t: &TypedefDecl,
955) -> Result<(), CppGenError> {
956    let ind = ctx.indent();
957    emit_verbatim_at(out, &ind, &t.annotations, PlacementKind::BeforeDeclaration)?;
958    for decl in &t.declarators {
959        let alias = &decl.name().text;
960        check_identifier(alias)?;
961        let target_ty = type_for_declarator(&t.type_spec, decl)?;
962        writeln!(out, "{ind}using {alias} = {target_ty};").map_err(fmt_err)?;
963    }
964    emit_verbatim_at(out, &ind, &t.annotations, PlacementKind::AfterDeclaration)?;
965    writeln!(out).map_err(fmt_err)?;
966    Ok(())
967}
968
969fn emit_const_decl(
970    out: &mut String,
971    ctx: &mut EmitCtx<'_>,
972    c: &zerodds_idl::ast::ConstDecl,
973) -> Result<(), CppGenError> {
974    check_identifier(&c.name.text)?;
975    let ind = ctx.indent();
976    let cpp_ty = match &c.type_ {
977        zerodds_idl::ast::ConstType::Integer(i) => crate::type_map::integer_to_cpp(*i).to_string(),
978        zerodds_idl::ast::ConstType::Floating(f) => {
979            crate::type_map::floating_to_cpp(*f).to_string()
980        }
981        zerodds_idl::ast::ConstType::Boolean => "bool".into(),
982        zerodds_idl::ast::ConstType::Char => "char".into(),
983        zerodds_idl::ast::ConstType::WideChar => "wchar_t".into(),
984        zerodds_idl::ast::ConstType::Octet => "uint8_t".into(),
985        zerodds_idl::ast::ConstType::String { wide: false } => "std::string".into(),
986        zerodds_idl::ast::ConstType::String { wide: true } => "std::wstring".into(),
987        zerodds_idl::ast::ConstType::Scoped(s) => scoped_to_cpp(s),
988        zerodds_idl::ast::ConstType::Fixed => {
989            // §7.2.4.2.4 — fixed constant without a digits/scale annotation;
990            // we emit it as an opaque wrapper (the caller annotates the type
991            // via a separate `typedef fixed<D,S> Name;`).
992            "::dds::core::Fixed<31, 0>".into()
993        }
994    };
995    let val = const_expr_to_cpp(&c.value);
996    writeln!(out, "{ind}constexpr {cpp_ty} {} = {val};", c.name.text).map_err(fmt_err)?;
997    Ok(())
998}
999
1000fn emit_exception(
1001    out: &mut String,
1002    ctx: &mut EmitCtx<'_>,
1003    e: &ExceptDecl,
1004) -> Result<(), CppGenError> {
1005    check_identifier(&e.name.text)?;
1006    let ind = ctx.indent();
1007    writeln!(out, "{ind}class {} : public std::exception {{", e.name.text).map_err(fmt_err)?;
1008    writeln!(out, "{ind}public:").map_err(fmt_err)?;
1009    let inner = " ".repeat((ctx.indent_level + 1) * ctx.opts.indent_width);
1010    writeln!(out, "{inner}{}() = default;", e.name.text).map_err(fmt_err)?;
1011    writeln!(out, "{inner}~{}() override = default;", e.name.text).map_err(fmt_err)?;
1012    writeln!(
1013        out,
1014        "{inner}const char* what() const noexcept override {{ return \"{}\"; }}",
1015        e.name.text
1016    )
1017    .map_err(fmt_err)?;
1018    writeln!(out).map_err(fmt_err)?;
1019    writeln!(out, "{ind}private:").map_err(fmt_err)?;
1020    for m in &e.members {
1021        for decl in &m.declarators {
1022            let cpp_ty = type_for_declarator(&m.type_spec, decl)?;
1023            let name = &decl.name().text;
1024            check_identifier(name)?;
1025            writeln!(out, "{inner}{cpp_ty} {name}_;").map_err(fmt_err)?;
1026        }
1027    }
1028    writeln!(out, "{ind}}};").map_err(fmt_err)?;
1029    writeln!(out).map_err(fmt_err)?;
1030    Ok(())
1031}
1032
1033// ---------------------------------------------------------------------------
1034// TypeSpec → C++-Type-Ausdruck
1035// ---------------------------------------------------------------------------
1036
1037/// Returns the C++ type expression for a member (TypeSpec + Declarator).
1038/// Array declarators become `std::array<T, N>` (multidimensionally nested).
1039pub(crate) fn type_for_declarator(ts: &TypeSpec, decl: &Declarator) -> Result<String, CppGenError> {
1040    let base = typespec_to_cpp(ts)?;
1041    match decl {
1042        Declarator::Simple(_) => Ok(base),
1043        Declarator::Array(arr) => {
1044            // Wrap inside-out: arr.sizes[0] is the outermost dimension.
1045            // `int x[2][3]` → `std::array<std::array<int, 3>, 2>`.
1046            let mut out = base;
1047            for size in arr.sizes.iter().rev() {
1048                let n = const_expr_to_usize(size).unwrap_or_default();
1049                out = format!("std::array<{out}, {n}>");
1050            }
1051            Ok(out)
1052        }
1053    }
1054}
1055
1056/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
1057pub(crate) fn typespec_to_cpp(ts: &TypeSpec) -> Result<String, CppGenError> {
1058    match ts {
1059        TypeSpec::Primitive(p) => Ok(primitive_to_cpp(*p).to_string()),
1060        TypeSpec::Scoped(s) => Ok(scoped_to_cpp(s)),
1061        TypeSpec::Sequence(s) => {
1062            let inner = typespec_to_cpp(&s.elem)?;
1063            Ok(format!("std::vector<{inner}>"))
1064        }
1065        TypeSpec::String(s) => {
1066            if s.wide {
1067                Ok("std::wstring".into())
1068            } else {
1069                Ok("std::string".into())
1070            }
1071        }
1072        TypeSpec::Map(m) => {
1073            let k = typespec_to_cpp(&m.key)?;
1074            let v = typespec_to_cpp(&m.value)?;
1075            Ok(format!("std::map<{k}, {v}>"))
1076        }
1077        TypeSpec::Fixed(f) => {
1078            // Spec idl4-cpp §7.2.4.2.4: fixed<digits, scale> ->
1079            // `omg::types::fixed<D, S>` (spec) resp. `dds::core::Fixed<D,S>`
1080            // (ZeroDDS-equivalent form). Digits/scale from the AST.
1081            let digits = const_expr_to_u32(&f.digits).unwrap_or(0);
1082            let scale = const_expr_to_u32(&f.scale).unwrap_or(0);
1083            Ok(format!("::dds::core::Fixed<{digits}, {scale}>"))
1084        }
1085        TypeSpec::Any => {
1086            // Spec idl4-cpp §7.3: `any` -> `omg::types::Any`. We emit it
1087            // as the ZeroDDS-equivalent `dds::core::Any` class
1088            // (a reflective container, runtime implementation in the
1089            // `dds-core` crate).
1090            Ok("::dds::core::Any".into())
1091        }
1092    }
1093}
1094
1095pub(crate) fn switch_type_to_cpp(s: &SwitchTypeSpec) -> Result<String, CppGenError> {
1096    Ok(match s {
1097        SwitchTypeSpec::Integer(i) => crate::type_map::integer_to_cpp(*i).to_string(),
1098        SwitchTypeSpec::Char => "char".into(),
1099        SwitchTypeSpec::Boolean => "bool".into(),
1100        SwitchTypeSpec::Octet => "uint8_t".into(),
1101        SwitchTypeSpec::Scoped(s) => scoped_to_cpp(s),
1102    })
1103}
1104
1105pub(crate) fn scoped_to_cpp(s: &ScopedName) -> String {
1106    // Mapping for Time/Duration (block E).
1107    if s.parts.len() == 1 {
1108        if let Some(mapped) = TIME_DURATION_TYPES
1109            .iter()
1110            .find(|(idl, _)| *idl == s.parts[0].text)
1111            .map(|(_, cpp)| *cpp)
1112        {
1113            return mapped.to_string();
1114        }
1115    }
1116    let parts: Vec<String> = s.parts.iter().map(|p| p.text.clone()).collect();
1117    let joined = parts.join("::");
1118    if s.absolute {
1119        format!("::{joined}")
1120    } else {
1121        joined
1122    }
1123}
1124
1125// ---------------------------------------------------------------------------
1126// ConstExpr → C++ literal string (best-effort for the foundation)
1127// ---------------------------------------------------------------------------
1128
1129fn const_expr_to_u32(e: &ConstExpr) -> Option<u32> {
1130    if let ConstExpr::Literal(l) = e {
1131        if matches!(l.kind, LiteralKind::Integer) {
1132            return l.raw.parse::<u32>().ok();
1133        }
1134    }
1135    None
1136}
1137
1138pub(crate) fn const_expr_to_cpp(e: &ConstExpr) -> String {
1139    match e {
1140        ConstExpr::Literal(l) => literal_to_cpp(l),
1141        ConstExpr::Scoped(s) => scoped_to_cpp(s),
1142        ConstExpr::Unary { op, operand, .. } => {
1143            let prefix = match op {
1144                zerodds_idl::ast::UnaryOp::Plus => "+",
1145                zerodds_idl::ast::UnaryOp::Minus => "-",
1146                zerodds_idl::ast::UnaryOp::BitNot => "~",
1147            };
1148            format!("{prefix}{}", const_expr_to_cpp(operand))
1149        }
1150        ConstExpr::Binary { op, lhs, rhs, .. } => {
1151            let opstr = match op {
1152                zerodds_idl::ast::BinaryOp::Or => "|",
1153                zerodds_idl::ast::BinaryOp::Xor => "^",
1154                zerodds_idl::ast::BinaryOp::And => "&",
1155                zerodds_idl::ast::BinaryOp::Shl => "<<",
1156                zerodds_idl::ast::BinaryOp::Shr => ">>",
1157                zerodds_idl::ast::BinaryOp::Add => "+",
1158                zerodds_idl::ast::BinaryOp::Sub => "-",
1159                zerodds_idl::ast::BinaryOp::Mul => "*",
1160                zerodds_idl::ast::BinaryOp::Div => "/",
1161                zerodds_idl::ast::BinaryOp::Mod => "%",
1162            };
1163            format!(
1164                "({} {opstr} {})",
1165                const_expr_to_cpp(lhs),
1166                const_expr_to_cpp(rhs)
1167            )
1168        }
1169    }
1170}
1171
1172fn literal_to_cpp(l: &Literal) -> String {
1173    match l.kind {
1174        LiteralKind::Boolean => l.raw.clone(),
1175        LiteralKind::Integer | LiteralKind::Floating => l.raw.clone(),
1176        LiteralKind::Char => l.raw.clone(),
1177        LiteralKind::WideChar => l.raw.clone(),
1178        LiteralKind::String => l.raw.clone(),
1179        LiteralKind::WideString => l.raw.clone(),
1180        LiteralKind::Fixed => l.raw.clone(),
1181    }
1182}
1183
1184fn const_expr_to_usize(e: &ConstExpr) -> Option<usize> {
1185    match e {
1186        ConstExpr::Literal(l) if l.kind == LiteralKind::Integer => l.raw.parse::<usize>().ok(),
1187        _ => None,
1188    }
1189}
1190
1191// ---------------------------------------------------------------------------
1192// Annotation-Helpers
1193// ---------------------------------------------------------------------------
1194
1195fn has_key_annotation(anns: &[Annotation]) -> bool {
1196    has_named_annotation(anns, "key")
1197}
1198
1199fn has_optional_annotation(anns: &[Annotation]) -> bool {
1200    has_named_annotation(anns, "optional")
1201}
1202
1203fn has_shared_annotation(anns: &[Annotation]) -> bool {
1204    has_named_annotation(anns, "shared")
1205}
1206
1207fn has_named_annotation(anns: &[Annotation], name: &str) -> bool {
1208    anns.iter().any(|a| {
1209        a.name.parts.last().is_some_and(|p| p.text == name)
1210            && matches!(a.params, AnnotationParams::None | AnnotationParams::Empty)
1211    })
1212}
1213
1214/// Looks for a `@<name>(N)` and returns the uint32 value; otherwise None.
1215fn find_uint_annotation(anns: &[Annotation], name: &str) -> Option<u32> {
1216    for a in anns {
1217        if a.name.parts.last().is_some_and(|p| p.text == name) {
1218            if let AnnotationParams::Single(expr) = &a.params {
1219                if let Some(v) = const_expr_as_u32(expr) {
1220                    return Some(v);
1221                }
1222            }
1223        }
1224    }
1225    None
1226}
1227/// zerodds-lint: recursion-depth 64 (const_expr_as_u32 bounded by AST depth)
1228/// Attempts to interpret a ConstExpr as a positive u32 (only an integer
1229/// literal or unary plus on an integer literal).
1230fn const_expr_as_u32(e: &ConstExpr) -> Option<u32> {
1231    match e {
1232        ConstExpr::Literal(Literal {
1233            kind: LiteralKind::Integer,
1234            raw,
1235            ..
1236        }) => parse_int_literal(raw).and_then(|v| u32::try_from(v).ok()),
1237        ConstExpr::Unary {
1238            op: zerodds_idl::ast::UnaryOp::Plus,
1239            operand,
1240            ..
1241        } => const_expr_as_u32(operand),
1242        _ => None,
1243    }
1244}
1245
1246/// Parser for integer literals (decimal, hex `0x`, octal `0...`).
1247fn parse_int_literal(raw: &str) -> Option<u64> {
1248    let s = raw.trim_end_matches(|c: char| matches!(c, 'l' | 'L' | 'u' | 'U'));
1249    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
1250        u64::from_str_radix(hex, 16).ok()
1251    } else if s.len() > 1 && s.starts_with('0') {
1252        u64::from_str_radix(&s[1..], 8).ok()
1253    } else {
1254        s.parse::<u64>().ok()
1255    }
1256}
1257
1258/// Extensibility mode of a struct from its annotations.
1259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1260enum Extensibility {
1261    Final,
1262    Appendable,
1263    Mutable,
1264}
1265
1266fn struct_extensibility(anns: &[Annotation]) -> Extensibility {
1267    if has_named_annotation(anns, "final") {
1268        Extensibility::Final
1269    } else if has_named_annotation(anns, "mutable") {
1270        Extensibility::Mutable
1271    } else if has_named_annotation(anns, "appendable") {
1272        Extensibility::Appendable
1273    } else {
1274        // Default per zerodds-xcdr2-cpp-1.0 §6: appendable.
1275        Extensibility::Appendable
1276    }
1277}
1278
1279// ---------------------------------------------------------------------------
1280// Inheritance-Cycle-Detection (reine Self/Direct-Loops im Top-Level-Scope).
1281// ---------------------------------------------------------------------------
1282
1283/// Walks the AST and collects `child → parent` edges (FQN strings).
1284///
1285/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
1286fn collect_inheritance_edges(
1287    defs: &[Definition],
1288    parents: &mut std::collections::HashMap<String, String>,
1289    prefix: &str,
1290) {
1291    for d in defs {
1292        match d {
1293            Definition::Module(m) => {
1294                let new_prefix = if prefix.is_empty() {
1295                    m.name.text.clone()
1296                } else {
1297                    format!("{prefix}::{}", m.name.text)
1298                };
1299                collect_inheritance_edges(&m.definitions, parents, &new_prefix);
1300            }
1301            Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
1302                let key = if prefix.is_empty() {
1303                    s.name.text.clone()
1304                } else {
1305                    format!("{prefix}::{}", s.name.text)
1306                };
1307                if let Some(b) = &s.base {
1308                    let base_str = b
1309                        .parts
1310                        .iter()
1311                        .map(|p| p.text.clone())
1312                        .collect::<Vec<_>>()
1313                        .join("::");
1314                    parents.insert(key, base_str);
1315                }
1316            }
1317            _ => {}
1318        }
1319    }
1320}
1321
1322fn detect_inheritance_cycles(spec: &Specification) -> Result<(), CppGenError> {
1323    use std::collections::HashMap;
1324
1325    let mut parents: HashMap<String, String> = HashMap::new();
1326    collect_inheritance_edges(&spec.definitions, &mut parents, "");
1327
1328    // Cycle detection via a visited set per node.
1329    for start in parents.keys() {
1330        let mut current = start.clone();
1331        let mut visited: BTreeSet<String> = BTreeSet::new();
1332        visited.insert(current.clone());
1333        while let Some(p) = parents.get(&current) {
1334            // Match flexibly: full key or suffix match.
1335            let resolved = parents
1336                .keys()
1337                .find(|k| *k == p || k.ends_with(&format!("::{p}")))
1338                .cloned()
1339                .unwrap_or_else(|| p.clone());
1340            if visited.contains(&resolved) {
1341                return Err(CppGenError::InheritanceCycle {
1342                    type_name: short_name(&resolved),
1343                });
1344            }
1345            visited.insert(resolved.clone());
1346            // Direktes Self-Reference (Parent == Self):
1347            if resolved == current {
1348                return Err(CppGenError::InheritanceCycle {
1349                    type_name: short_name(&resolved),
1350                });
1351            }
1352            current = resolved;
1353            if !parents.contains_key(&current) {
1354                break;
1355            }
1356        }
1357    }
1358    Ok(())
1359}
1360
1361fn short_name(s: &str) -> String {
1362    s.rsplit("::").next().unwrap_or(s).to_string()
1363}
1364
1365// ---------------------------------------------------------------------------
1366// topic_type_support<T> — DDS-PSM-Cxx Topic-Trait-Spezialisierung
1367// ---------------------------------------------------------------------------
1368//
1369// Collects all top-level and module-nested structs and emits per struct
1370// a `dds::topic::topic_type_support<FQN>` specialization with type_name(),
1371// encode(), encode_be(), decode(), key_hash(), is_keyed() and extensibility().
1372//
1373// The wire format is full XCDR2 (XTypes 1.3 §7.4):
1374//   * Plain-CDR2 LE with alignment relative to the encapsulation start.
1375//   * `@final`           -> no DHEADER.
1376//   * `@appendable`(def) -> DHEADER (4 byte body-size) prefixed.
1377//   * `@mutable`         -> DHEADER + EMHEADER per member (PL_CDR2).
1378//   * `@key`             -> member goes into the key hash (MD5 over BE-Plain-CDR2).
1379//   * `@id(N)`           -> EMHEADER member-id.
1380//   * `@optional`        -> EMHEADER skip if absent (mutable);
1381//                            1-byte present-flag for final/appendable.
1382//   * `@must_understand` -> EMHEADER MU-Flag.
1383//
1384// Konformanz: docs/specs/zerodds-xcdr2-bindings-conformance-1.0.md (V-1..V-12).
1385
1386/// zerodds-lint: recursion-depth 64 (Parser/AST-Walk; bounded by IDL nesting)
1387fn collect_topic_structs<'a>(
1388    defs: &'a [Definition],
1389    prefix: &str,
1390    out: &mut Vec<(String, &'a StructDef)>,
1391) {
1392    for d in defs {
1393        match d {
1394            Definition::Module(m) => {
1395                let np = if prefix.is_empty() {
1396                    m.name.text.clone()
1397                } else {
1398                    format!("{prefix}::{}", m.name.text)
1399                };
1400                collect_topic_structs(&m.definitions, &np, out);
1401            }
1402            Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
1403                let fqn = if prefix.is_empty() {
1404                    s.name.text.clone()
1405                } else {
1406                    format!("{prefix}::{}", s.name.text)
1407                };
1408                out.push((fqn, s));
1409            }
1410            _ => {}
1411        }
1412    }
1413}
1414
1415fn emit_topic_type_support_specs(
1416    out: &mut String,
1417    opts: &CppGenOptions,
1418    structs: &[(String, &StructDef)],
1419) -> Result<(), CppGenError> {
1420    writeln!(out).map_err(fmt_err)?;
1421    writeln!(
1422        out,
1423        "// DDS-PSM-Cxx topic_type_support<T> -- auto-generiert (XCDR2 Wire, XTypes 1.3 7.4)."
1424    )
1425    .map_err(fmt_err)?;
1426    writeln!(out, "namespace dds {{").map_err(fmt_err)?;
1427    writeln!(out, "namespace topic {{").map_err(fmt_err)?;
1428    writeln!(out).map_err(fmt_err)?;
1429
1430    let user_prefix = opts
1431        .namespace_prefix
1432        .as_deref()
1433        .filter(|p| !p.is_empty())
1434        .unwrap_or("");
1435    for (fqn, s) in structs {
1436        let cpp_fqn = if user_prefix.is_empty() {
1437            format!("::{fqn}")
1438        } else {
1439            format!("::{user_prefix}::{fqn}")
1440        };
1441        emit_topic_type_support_for(out, &cpp_fqn, fqn, s)?;
1442    }
1443
1444    writeln!(out, "}} // namespace topic").map_err(fmt_err)?;
1445    writeln!(out, "}} // namespace dds").map_err(fmt_err)?;
1446    Ok(())
1447}
1448
1449/// Returns true if the member annotations are encode-safe (the codegen
1450/// can produce wire bytes). False for `@shared` (heap indirection, not
1451/// yet supported). `@optional` is allowed.
1452fn member_codegen_supported(m: &Member) -> bool {
1453    !has_shared_annotation(&m.annotations)
1454}
1455
1456/// Returns true if a type spec is understood by the XCDR2 codegen.
1457///
1458/// zerodds-lint: recursion-depth 64 (type-spec walk; bounded by IDL nesting)
1459fn typespec_supported(ts: &TypeSpec) -> bool {
1460    match ts {
1461        TypeSpec::Primitive(_) => true,
1462        // narrow `string` AND wide `wstring` (conformance §9.1, UTF-16 wire).
1463        TypeSpec::String(_) => true,
1464        // Sequence elements: primitives, narrow + wide string, enum (-> int32),
1465        // nested struct of ANY extensibility (@final → recursed inline;
1466        // @appendable/@mutable → 4-aligned splice per element, see emit loops),
1467        // nested sequence (recursed, own inner DHEADER) and map (recursed, own
1468        // DHEADER) are all wired.
1469        TypeSpec::Sequence(seq) => match &*seq.elem {
1470            TypeSpec::Primitive(_) => true,
1471            TypeSpec::String(_) => true,
1472            TypeSpec::Scoped(s) => scoped_is_enum(s) || scoped_struct(s).is_some(),
1473            TypeSpec::Sequence(_) => typespec_supported(&seq.elem),
1474            TypeSpec::Map(m) => typespec_supported(&m.key) && typespec_supported(&m.value),
1475            _ => false,
1476        },
1477        // A `Scoped` member resolving to an enum (→ int32) or to a directly-
1478        // encodable struct of ANY extensibility (@final → recursed inline;
1479        // @appendable/@mutable → spliced, see `scoped_struct`) is supported.
1480        // The Sequence-element arm above mirrors this (each non-final element is
1481        // 4-aligned + spliced/sub-decoded per its own DHEADER).
1482        TypeSpec::Scoped(s) => scoped_is_enum(s) || scoped_struct(s).is_some(),
1483        // map<K,V>: supported iff both key and value are themselves supported
1484        // (encode/decode recurse through emit_value_write/read per entry).
1485        TypeSpec::Map(m) => typespec_supported(&m.key) && typespec_supported(&m.value),
1486        _ => false,
1487    }
1488}
1489
1490// Codegen-scoped type registry (thread-local = correct under concurrent header
1491// generation; rebuilt per `emit_header`). Holds the SIMPLE names of all enums
1492// and all structs, so a `Scoped` member can be classified as an enum WITHOUT a
1493// full name resolver: a name is treated as an enum only if it is a known enum
1494// simple-name AND NOT a known struct simple-name — never mis-classifying a
1495// struct (which would emit a broken int32 cast). Ambiguous/relative names fall
1496// back to "skip", i.e. no regression.
1497thread_local! {
1498    static ENUM_NAMES: RefCell<BTreeSet<String>> = const { RefCell::new(BTreeSet::new()) };
1499    static STRUCT_NAMES: RefCell<BTreeSet<String>> = const { RefCell::new(BTreeSet::new()) };
1500    static STRUCT_DEFS: RefCell<BTreeMap<String, StructDef>> = const { RefCell::new(BTreeMap::new()) };
1501    // Monotonic counter for unique nested-struct decode temp-var names
1502    // (`__ns<N>`), so nested-nested decodes do not shadow each other.
1503    static NEST_CTR: core::cell::Cell<u32> = const { core::cell::Cell::new(0) };
1504}
1505
1506fn next_nest_id() -> u32 {
1507    NEST_CTR.with(|c| {
1508        let v = c.get();
1509        c.set(v.wrapping_add(1));
1510        v
1511    })
1512}
1513
1514fn set_type_registry(spec: &Specification) {
1515    let mut enums = BTreeSet::new();
1516    let mut structs = BTreeSet::new();
1517    let mut defs = BTreeMap::new();
1518    collect_type_names(&spec.definitions, &mut enums, &mut structs, &mut defs);
1519    ENUM_NAMES.with(|r| *r.borrow_mut() = enums);
1520    STRUCT_NAMES.with(|r| *r.borrow_mut() = structs);
1521    STRUCT_DEFS.with(|r| *r.borrow_mut() = defs);
1522}
1523
1524/// zerodds-lint: recursion-depth 64 (module/type tree; bounded by IDL nesting)
1525fn collect_type_names(
1526    defs: &[Definition],
1527    enums: &mut BTreeSet<String>,
1528    structs: &mut BTreeSet<String>,
1529    struct_defs: &mut BTreeMap<String, StructDef>,
1530) {
1531    for d in defs {
1532        match d {
1533            Definition::Module(m) => {
1534                collect_type_names(&m.definitions, enums, structs, struct_defs)
1535            }
1536            Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Enum(e))) => {
1537                enums.insert(e.name.text.clone());
1538            }
1539            Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
1540                structs.insert(s.name.text.clone());
1541                struct_defs.insert(s.name.text.clone(), s.clone());
1542            }
1543            _ => {}
1544        }
1545    }
1546}
1547
1548/// `true` if `s` (a member's scoped type name) unambiguously names an enum.
1549fn scoped_is_enum(s: &ScopedName) -> bool {
1550    let Some(last) = s.parts.last().map(|p| p.text.clone()) else {
1551        return false;
1552    };
1553    let is_enum = ENUM_NAMES.with(|r| r.borrow().contains(&last));
1554    let is_struct = STRUCT_NAMES.with(|r| r.borrow().contains(&last));
1555    is_enum && !is_struct
1556}
1557
1558/// If `s` resolves to a `@final` struct whose members are ALL directly encodable
1559/// (single Simple declarator + a type the XCDR2 member encoder handles), returns
1560/// the [`StructDef`] so the encoder can recurse into it inline (Plain-CDR2: no
1561/// DHEADER for `@final`, Spec §7.4.3.4.1). Appendable/mutable nested structs +
1562/// arrays/sub-structs inside the nested struct fall back to "not supported"
1563/// (whole member skipped — never a partial encode).
1564fn scoped_final_struct(s: &ScopedName) -> Option<StructDef> {
1565    match scoped_struct(s) {
1566        Some((def, Extensibility::Final)) => Some(def),
1567        _ => None,
1568    }
1569}
1570
1571/// Like [`scoped_final_struct`] but for **any** extensibility: returns the
1572/// [`StructDef`] + its [`Extensibility`] when `s` resolves to a struct whose
1573/// members are all directly encodable. The member encoder picks the wire form
1574/// per extensibility — `@final` recurses inline (no DHEADER, alignment relative
1575/// to the outer origin), while `@appendable`/`@mutable` are **spliced** from the
1576/// nested type's own `topic_type_support<...>::encode`/`decode`. The latter is
1577/// byte-correct because the nested struct's own DHEADER (Plain-CDR2 for
1578/// appendable, the mutable scope for mutable) forces 4-alignment, and under
1579/// XCDR2 (`max_align == 4`) a 4-aligned splice point preserves every member's
1580/// relative alignment (Spec §7.4.3.4.2).
1581///
1582/// zerodds-lint: recursion-depth 64 (via typespec_supported; bounded by IDL nesting)
1583fn scoped_struct(s: &ScopedName) -> Option<(StructDef, Extensibility)> {
1584    let last = s.parts.last()?.text.clone();
1585    let def = STRUCT_DEFS.with(|r| r.borrow().get(&last).cloned())?;
1586    let ext = struct_extensibility(&def.annotations);
1587    let all_encodable = def.members.iter().all(|m| {
1588        m.declarators.len() == 1
1589            && matches!(m.declarators.first(), Some(Declarator::Simple(_)))
1590            && typespec_supported(&m.type_spec)
1591    });
1592    if all_encodable {
1593        Some((def, ext))
1594    } else {
1595        None
1596    }
1597}
1598
1599fn emit_topic_type_support_for(
1600    out: &mut String,
1601    cpp_fqn: &str,
1602    type_name: &str,
1603    s: &StructDef,
1604) -> Result<(), CppGenError> {
1605    let ext = struct_extensibility(&s.annotations);
1606
1607    writeln!(out, "template <>").map_err(fmt_err)?;
1608    writeln!(out, "struct topic_type_support<{cpp_fqn}> {{").map_err(fmt_err)?;
1609    writeln!(
1610        out,
1611        "    static const char* type_name() {{ return \"{type_name}\"; }}"
1612    )
1613    .map_err(fmt_err)?;
1614
1615    // is_keyed
1616    let is_keyed = s.members.iter().any(|m| has_key_annotation(&m.annotations));
1617    writeln!(
1618        out,
1619        "    static constexpr bool is_keyed() {{ return {}; }}",
1620        if is_keyed { "true" } else { "false" }
1621    )
1622    .map_err(fmt_err)?;
1623
1624    // extensibility
1625    let ext_lit = match ext {
1626        Extensibility::Final => "FINAL",
1627        Extensibility::Appendable => "APPENDABLE",
1628        Extensibility::Mutable => "MUTABLE",
1629    };
1630    writeln!(
1631        out,
1632        "    static constexpr ::dds::topic::core::policy::DataRepresentationKind extensibility() {{ return ::dds::topic::core::policy::DataRepresentationKind::{ext_lit}; }}"
1633    )
1634    .map_err(fmt_err)?;
1635
1636    // encode (LE)
1637    emit_encode_fn(out, cpp_fqn, s, ext, /*be=*/ false)?;
1638    // encode_be (BE)
1639    emit_encode_fn(out, cpp_fqn, s, ext, /*be=*/ true)?;
1640    // decode (LE)
1641    emit_decode_fn(out, cpp_fqn, s, ext)?;
1642    // key_hash (BE Plain-CDR2 of @key members + MD5)
1643    emit_key_hash_fn(out, cpp_fqn, s, is_keyed)?;
1644
1645    writeln!(out, "}};").map_err(fmt_err)?;
1646    writeln!(out).map_err(fmt_err)?;
1647    Ok(())
1648}
1649
1650fn emit_encode_fn(
1651    out: &mut String,
1652    cpp_fqn: &str,
1653    s: &StructDef,
1654    ext: Extensibility,
1655    be: bool,
1656) -> Result<(), CppGenError> {
1657    // Suffix for write helpers: write_le or write_be, write_string or write_string_be.
1658    let endian_suffix = if be { "be" } else { "le" };
1659
1660    if be {
1661        writeln!(
1662            out,
1663            "    static std::vector<uint8_t> encode_be(const {cpp_fqn}& __v) {{"
1664        )
1665        .map_err(fmt_err)?;
1666    } else {
1667        // XCDR2 default delegator + version-aware encode. XCDR2 caps
1668        // 8-byte primitive alignment to 4 (XTypes 1.3 §7.4.3.4.2)
1669        // — symmetric to `decode(.., XcdrVersion)`. XCDR2 is the
1670        // ZeroDDS system default (= dcps DEFAULT_OFFER [XCDR2], encap
1671        // 0x07/0x09/0x0b); for legacy XCDR1 call
1672        // `encode(__v, XcdrVersion::Xcdr1)` explicitly.
1673        writeln!(
1674            out,
1675            "    static std::vector<uint8_t> encode(const {cpp_fqn}& __v) {{"
1676        )
1677        .map_err(fmt_err)?;
1678        writeln!(
1679            out,
1680            "        return encode(__v, ::dds::topic::xcdr2::XcdrVersion::Xcdr2);"
1681        )
1682        .map_err(fmt_err)?;
1683        writeln!(out, "    }}").map_err(fmt_err)?;
1684        writeln!(
1685            out,
1686            "    static std::vector<uint8_t> encode(const {cpp_fqn}& __v, \
1687             ::dds::topic::xcdr2::XcdrVersion __repr) {{"
1688        )
1689        .map_err(fmt_err)?;
1690    }
1691    writeln!(out, "        std::vector<uint8_t> __out;").map_err(fmt_err)?;
1692    writeln!(out, "        (void)__v;").map_err(fmt_err)?;
1693    if !be {
1694        writeln!(
1695            out,
1696            "        const size_t __max_align = ::dds::topic::xcdr2::xcdr_max_align(__repr);"
1697        )
1698        .map_err(fmt_err)?;
1699        writeln!(out, "        (void)__max_align;").map_err(fmt_err)?;
1700    }
1701
1702    match ext {
1703        Extensibility::Final => {
1704            // Plain-CDR2, no DHEADER, alignment relative to buffer start.
1705            // origin = 0.
1706            writeln!(out, "        const size_t __origin = 0;").map_err(fmt_err)?;
1707            writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
1708            for m in &s.members {
1709                emit_plain_member_encode(out, m, endian_suffix, "__origin")?;
1710            }
1711        }
1712        Extensibility::Appendable => {
1713            writeln!(
1714                out,
1715                "        const auto __dh = ::dds::topic::xcdr2::dheader_begin(__out);"
1716            )
1717            .map_err(fmt_err)?;
1718            writeln!(out, "        const size_t __origin = __out.size();").map_err(fmt_err)?;
1719            writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
1720            for m in &s.members {
1721                emit_plain_member_encode(out, m, endian_suffix, "__origin")?;
1722            }
1723            writeln!(
1724                out,
1725                "        ::dds::topic::xcdr2::dheader_end(__out, __dh);"
1726            )
1727            .map_err(fmt_err)?;
1728        }
1729        Extensibility::Mutable => {
1730            writeln!(
1731                out,
1732                "        const auto __scope = ::dds::topic::xcdr2::mutable_begin(__out);"
1733            )
1734            .map_err(fmt_err)?;
1735            writeln!(out, "        const size_t __origin = __scope.origin;").map_err(fmt_err)?;
1736            for m in &s.members {
1737                emit_mutable_member_encode(out, m, endian_suffix)?;
1738            }
1739            writeln!(
1740                out,
1741                "        ::dds::topic::xcdr2::mutable_end(__out, __scope);"
1742            )
1743            .map_err(fmt_err)?;
1744        }
1745    }
1746
1747    writeln!(out, "        return __out;").map_err(fmt_err)?;
1748    writeln!(out, "    }}").map_err(fmt_err)?;
1749    Ok(())
1750}
1751
1752/// Emit Plain-CDR2 (LE/BE) encoding for one member at the current
1753/// position; alignment relative to `origin`.
1754fn emit_plain_member_encode(
1755    out: &mut String,
1756    m: &Member,
1757    endian: &str,
1758    origin: &str,
1759) -> Result<(), CppGenError> {
1760    if !member_codegen_supported(m) {
1761        for decl in &m.declarators {
1762            let name = &decl.name().text;
1763            writeln!(
1764                out,
1765                "        // xcdr2: @shared member '{name}' not supported (skip)"
1766            )
1767            .map_err(fmt_err)?;
1768        }
1769        return Ok(());
1770    }
1771    let is_optional = has_optional_annotation(&m.annotations);
1772    for decl in &m.declarators {
1773        let name = &decl.name().text;
1774        // 1-D fixed array of a leaf type (primitive / string / wstring): XCDR2
1775        // encodes N contiguous elements, no length prefix. Multi-dim arrays and
1776        // arrays of struct/sequence remain a follow-up (need the type registry /
1777        // recursion — see idl-cpp-xcdr2-encoder-gaps.md).
1778        if let Declarator::Array(arr) = decl {
1779            let prim = matches!(m.type_spec, TypeSpec::Primitive(_));
1780            let leaf_1d = arr.sizes.len() == 1
1781                && matches!(m.type_spec, TypeSpec::Primitive(_) | TypeSpec::String(_));
1782            if leaf_1d {
1783                writeln!(out, "        for (const auto& __ae : __v.{name}()) {{")
1784                    .map_err(fmt_err)?;
1785                emit_value_write(out, &m.type_spec, "__ae", endian, origin, "        ")?;
1786                writeln!(out, "        }}").map_err(fmt_err)?;
1787            } else if prim && arr.sizes.len() >= 2 {
1788                // Multi-dim array of a primitive (XTypes §7.4.3): row-major, fixed
1789                // size, NO DHEADER (primitive elements). The C++ type is a nested
1790                // std::array, so N nested range-for loops reach the innermost cell.
1791                let n = arr.sizes.len();
1792                let mut acc = format!("__v.{name}()");
1793                let mut ind = String::from("        ");
1794                for d in 0..n {
1795                    let lv = format!("__a{d}");
1796                    writeln!(out, "{ind}for (const auto& {lv} : {acc}) {{").map_err(fmt_err)?;
1797                    acc = lv;
1798                    ind.push_str("    ");
1799                }
1800                emit_value_write(out, &m.type_spec, &acc, endian, origin, &ind)?;
1801                for _ in 0..n {
1802                    ind.truncate(ind.len() - 4);
1803                    writeln!(out, "{ind}}}").map_err(fmt_err)?;
1804                }
1805            } else if matches!(&m.type_spec, TypeSpec::Scoped(s) if scoped_is_enum(s) || scoped_final_struct(s).is_some())
1806                || (matches!(m.type_spec, TypeSpec::String(_)) && arr.sizes.len() >= 2)
1807            {
1808                // Array of NON-primitive elements (enum / @final struct, any dims;
1809                // string only for >=2 dims — 1-D string keeps the legacy no-DHEADER
1810                // leaf path above): one DHEADER (XTypes §7.4.3.5) wrapping N
1811                // elements inline, row-major, NO count. N nested range-for loops.
1812                let n = arr.sizes.len();
1813                writeln!(out, "        {{").map_err(fmt_err)?;
1814                writeln!(
1815                    out,
1816                    "        const auto __arr_dh = ::dds::topic::xcdr2::dheader_begin(__out);"
1817                )
1818                .map_err(fmt_err)?;
1819                let mut acc = format!("__v.{name}()");
1820                let mut ind = String::from("        ");
1821                for d in 0..n {
1822                    let lv = format!("__a{d}");
1823                    writeln!(out, "{ind}for (const auto& {lv} : {acc}) {{").map_err(fmt_err)?;
1824                    acc = lv;
1825                    ind.push_str("    ");
1826                }
1827                emit_value_write(out, &m.type_spec, &acc, endian, origin, &ind)?;
1828                for _ in 0..n {
1829                    ind.truncate(ind.len() - 4);
1830                    writeln!(out, "{ind}}}").map_err(fmt_err)?;
1831                }
1832                writeln!(
1833                    out,
1834                    "        ::dds::topic::xcdr2::dheader_end(__out, __arr_dh);"
1835                )
1836                .map_err(fmt_err)?;
1837                writeln!(out, "        }}").map_err(fmt_err)?;
1838            } else {
1839                writeln!(
1840                    out,
1841                    "        // xcdr2: array member '{name}' (multi-dim string-1D-only / unsupported elem) not supported (skip)"
1842                )
1843                .map_err(fmt_err)?;
1844            }
1845            continue;
1846        }
1847        if !typespec_supported(&m.type_spec) {
1848            writeln!(
1849                out,
1850                "        // xcdr2: member '{name}' not supported (nested/enum/map/fixed; skip)"
1851            )
1852            .map_err(fmt_err)?;
1853            continue;
1854        }
1855        if is_optional {
1856            // Final/appendable: 1-byte present-flag, then the value if present.
1857            writeln!(out, "        if (__v.{name}().has_value()) {{").map_err(fmt_err)?;
1858            writeln!(out, "            __out.push_back(uint8_t{{1}});").map_err(fmt_err)?;
1859            emit_value_write(
1860                out,
1861                &m.type_spec,
1862                &format!("(*__v.{name}())"),
1863                endian,
1864                origin,
1865                "        ",
1866            )?;
1867            writeln!(out, "        }} else {{").map_err(fmt_err)?;
1868            writeln!(out, "            __out.push_back(uint8_t{{0}});").map_err(fmt_err)?;
1869            writeln!(out, "        }}").map_err(fmt_err)?;
1870        } else {
1871            emit_value_write(
1872                out,
1873                &m.type_spec,
1874                &format!("__v.{name}()"),
1875                endian,
1876                origin,
1877                "    ",
1878            )?;
1879        }
1880    }
1881    Ok(())
1882}
1883
1884/// Emit a single value write at `access` using LE or BE convention.
1885///
1886/// zerodds-lint: recursion-depth 64 (nested type emit; bounded by IDL nesting)
1887fn emit_value_write(
1888    out: &mut String,
1889    ts: &TypeSpec,
1890    access: &str,
1891    endian: &str,
1892    origin: &str,
1893    indent: &str,
1894) -> Result<(), CppGenError> {
1895    let pre = format!("{indent}    ");
1896    match ts {
1897        TypeSpec::Primitive(PrimitiveType::Boolean) => {
1898            writeln!(
1899                out,
1900                "{pre}::dds::topic::xcdr2::write_bool(__out, {access});"
1901            )
1902            .map_err(fmt_err)?;
1903        }
1904        TypeSpec::Primitive(PrimitiveType::Octet) => {
1905            writeln!(out, "{pre}::dds::topic::xcdr2::write_u8(__out, {access});")
1906                .map_err(fmt_err)?;
1907        }
1908        TypeSpec::Primitive(p) => {
1909            let cpp_ty = primitive_to_cpp(*p);
1910            if endian == "be" {
1911                writeln!(
1912                    out,
1913                    "{pre}::dds::topic::xcdr2::write_be_origin<{cpp_ty}>(__out, {origin}, {access});"
1914                )
1915                .map_err(fmt_err)?;
1916            } else {
1917                // LE: representation-aware (XCDR2 deckelt 8-Byte-Align auf 4).
1918                writeln!(
1919                    out,
1920                    "{pre}::dds::topic::xcdr2::write_le_origin<{cpp_ty}>(__out, {origin}, {access}, __max_align);"
1921                )
1922                .map_err(fmt_err)?;
1923            }
1924        }
1925        TypeSpec::String(s) if !s.wide => {
1926            // Bounded narrow `string<N>` (DDS-XTypes §7.4.3): byte-length check
1927            // (std::string::size = bytes = CDR wire length).
1928            if let Some(b) = &s.bound {
1929                let bv = const_expr_to_cpp(b);
1930                writeln!(
1931                    out,
1932                    "{pre}if ({access}.size() > {bv}) throw std::length_error(\"bounded string length exceeds its IDL bound ({bv})\");"
1933                )
1934                .map_err(fmt_err)?;
1935            }
1936            if endian == "be" {
1937                writeln!(
1938                    out,
1939                    "{pre}::dds::topic::xcdr2::write_string_be(__out, {access});"
1940                )
1941                .map_err(fmt_err)?;
1942            } else {
1943                writeln!(
1944                    out,
1945                    "{pre}::dds::topic::xcdr2::write_string_origin(__out, {origin}, {access}, __max_align);"
1946                )
1947                .map_err(fmt_err)?;
1948            }
1949        }
1950        TypeSpec::String(s) if s.wide => {
1951            // Bounded `wstring<N>` (DDS-XTypes §7.4.3): bound is in wide chars
1952            // (std::wstring::size). Wire = UTF-16 (conformance §9.1).
1953            if let Some(b) = &s.bound {
1954                let bv = const_expr_to_cpp(b);
1955                writeln!(
1956                    out,
1957                    "{pre}if ({access}.size() > {bv}) throw std::length_error(\"bounded wstring length exceeds its IDL bound ({bv})\");"
1958                )
1959                .map_err(fmt_err)?;
1960            }
1961            if endian == "be" {
1962                writeln!(
1963                    out,
1964                    "{pre}::dds::topic::xcdr2::write_wstring_be(__out, {access});"
1965                )
1966                .map_err(fmt_err)?;
1967            } else {
1968                writeln!(
1969                    out,
1970                    "{pre}::dds::topic::xcdr2::write_wstring_origin(__out, {origin}, {access}, __max_align);"
1971                )
1972                .map_err(fmt_err)?;
1973            }
1974        }
1975        TypeSpec::Sequence(seq) => {
1976            // Bounded `sequence<T, N>` (DDS-XTypes §7.4.3): over-bound = encode
1977            // error. The encode returns a vector (no Result channel), so this
1978            // throws — strict vendors (OpenDDS) reject on the wire likewise.
1979            if let Some(b) = &seq.bound {
1980                let bv = const_expr_to_cpp(b);
1981                writeln!(
1982                    out,
1983                    "{pre}if ({access}.size() > {bv}) throw std::length_error(\"bounded sequence length exceeds its IDL bound ({bv})\");"
1984                )
1985                .map_err(fmt_err)?;
1986            }
1987            if matches!(&*seq.elem, TypeSpec::Primitive(PrimitiveType::Octet)) {
1988                // sequence<octet>: u32 length + raw byte block, no per-byte loop.
1989                if endian == "be" {
1990                    writeln!(out, "{pre}::dds::topic::xcdr2::write_be<uint32_t>(__out, static_cast<uint32_t>({access}.size()));").map_err(fmt_err)?;
1991                } else {
1992                    writeln!(out, "{pre}::dds::topic::xcdr2::write_le_origin<uint32_t>(__out, {origin}, static_cast<uint32_t>({access}.size()), __max_align);").map_err(fmt_err)?;
1993                }
1994                writeln!(
1995                    out,
1996                    "{pre}__out.insert(__out.end(), {access}.begin(), {access}.end());"
1997                )
1998                .map_err(fmt_err)?;
1999                return Ok(());
2000            }
2001            // XCDR2 §7.4.3.5: sequences with NON-primitive elements
2002            // (string, struct, …) get a DHEADER (uint32 = byte length of
2003            // [count + elements]) prepended; primitives do not.
2004            // Cyclone-DDS-verified (V-5 without, V-6 with).
2005            let seq_non_primitive = !matches!(&*seq.elem, TypeSpec::Primitive(_));
2006            if seq_non_primitive {
2007                writeln!(out, "{pre}{{").map_err(fmt_err)?;
2008                writeln!(
2009                    out,
2010                    "{pre}const auto __seq_dh = ::dds::topic::xcdr2::dheader_begin(__out);"
2011                )
2012                .map_err(fmt_err)?;
2013            }
2014            let count_call = if endian == "be" {
2015                format!(
2016                    "{pre}::dds::topic::xcdr2::write_be<uint32_t>(__out, static_cast<uint32_t>({access}.size()));"
2017                )
2018            } else {
2019                format!(
2020                    "{pre}::dds::topic::xcdr2::write_le_origin<uint32_t>(__out, {origin}, static_cast<uint32_t>({access}.size()), __max_align);"
2021                )
2022            };
2023            writeln!(out, "{count_call}").map_err(fmt_err)?;
2024            writeln!(out, "{pre}for (const auto& __e : {access}) {{").map_err(fmt_err)?;
2025            let elem_indent = format!("{pre}    ");
2026            match &*seq.elem {
2027                TypeSpec::Primitive(PrimitiveType::Boolean) => {
2028                    writeln!(
2029                        out,
2030                        "{elem_indent}::dds::topic::xcdr2::write_bool(__out, __e);"
2031                    )
2032                    .map_err(fmt_err)?;
2033                }
2034                TypeSpec::Primitive(PrimitiveType::Octet) => {
2035                    writeln!(
2036                        out,
2037                        "{elem_indent}::dds::topic::xcdr2::write_u8(__out, __e);"
2038                    )
2039                    .map_err(fmt_err)?;
2040                }
2041                TypeSpec::Primitive(p) => {
2042                    let cpp_ty = primitive_to_cpp(*p);
2043                    if endian == "be" {
2044                        writeln!(
2045                            out,
2046                            "{elem_indent}::dds::topic::xcdr2::write_be<{cpp_ty}>(__out, __e);"
2047                        )
2048                        .map_err(fmt_err)?;
2049                    } else {
2050                        writeln!(
2051                            out,
2052                            "{elem_indent}::dds::topic::xcdr2::write_le_origin<{cpp_ty}>(__out, {origin}, __e, __max_align);"
2053                        )
2054                        .map_err(fmt_err)?;
2055                    }
2056                }
2057                TypeSpec::String(s) if !s.wide => {
2058                    if endian == "be" {
2059                        writeln!(
2060                            out,
2061                            "{elem_indent}::dds::topic::xcdr2::write_string_be(__out, __e);"
2062                        )
2063                        .map_err(fmt_err)?;
2064                    } else {
2065                        writeln!(
2066                            out,
2067                            "{elem_indent}::dds::topic::xcdr2::write_string_origin(__out, {origin}, __e, __max_align);"
2068                        )
2069                        .map_err(fmt_err)?;
2070                    }
2071                }
2072                // wide string (wstring): recurse for the BOM/octet-length wire form.
2073                TypeSpec::String(_) => {
2074                    emit_value_write(out, &seq.elem, "__e", endian, origin, &elem_indent)?;
2075                }
2076                // enum (-> int32) and nested struct of ANY extensibility: recurse
2077                // through emit_value_write, identical to member-level encoding —
2078                // @final inlines (no DHEADER), @appendable/@mutable pad-to-4 +
2079                // splice the element's own [DHEADER+body] (XTypes §7.4.3.5).
2080                TypeSpec::Scoped(sc) if scoped_is_enum(sc) || scoped_struct(sc).is_some() => {
2081                    emit_value_write(out, &seq.elem, "__e", endian, origin, &elem_indent)?;
2082                }
2083                // nested sequence (sequence<sequence<...>>): recurse — the inner
2084                // sequence emits its own DHEADER (XTypes §7.4.3.5).
2085                TypeSpec::Sequence(_) => {
2086                    emit_value_write(out, &seq.elem, "__e", endian, origin, &elem_indent)?;
2087                }
2088                // map element (sequence<map<K,V>>): recurse — the map emits its
2089                // own DHEADER.
2090                TypeSpec::Map(_) => {
2091                    emit_value_write(out, &seq.elem, "__e", endian, origin, &elem_indent)?;
2092                }
2093                _ => {
2094                    writeln!(
2095                        out,
2096                        "{elem_indent}// xcdr2: nested sequence-element not supported"
2097                    )
2098                    .map_err(fmt_err)?;
2099                }
2100            }
2101            writeln!(out, "{pre}}}").map_err(fmt_err)?;
2102            if seq_non_primitive {
2103                writeln!(
2104                    out,
2105                    "{pre}::dds::topic::xcdr2::dheader_end(__out, __seq_dh);"
2106                )
2107                .map_err(fmt_err)?;
2108                writeln!(out, "{pre}}}").map_err(fmt_err)?;
2109            }
2110        }
2111        // map<K,V> member (XTypes §7.4.4.6): a non-primitive collection -> DHEADER
2112        // (uint32 byte-len of [count + entries]); uint32 count; then each entry as
2113        // key.encode + value.encode in key-sorted order. std::map iterates in
2114        // ascending key order, matching the Rust BTreeMap reference encoder
2115        // (crates/cdr/src/composite.rs §7.4.4.6) byte-for-byte.
2116        TypeSpec::Map(m) => {
2117            if let Some(b) = &m.bound {
2118                let bv = const_expr_to_cpp(b);
2119                writeln!(
2120                    out,
2121                    "{pre}if ({access}.size() > {bv}) throw std::length_error(\"bounded map length exceeds its IDL bound ({bv})\");"
2122                )
2123                .map_err(fmt_err)?;
2124            }
2125            writeln!(out, "{pre}{{").map_err(fmt_err)?;
2126            writeln!(
2127                out,
2128                "{pre}const auto __map_dh = ::dds::topic::xcdr2::dheader_begin(__out);"
2129            )
2130            .map_err(fmt_err)?;
2131            if endian == "be" {
2132                writeln!(out, "{pre}::dds::topic::xcdr2::write_be<uint32_t>(__out, static_cast<uint32_t>({access}.size()));").map_err(fmt_err)?;
2133            } else {
2134                writeln!(out, "{pre}::dds::topic::xcdr2::write_le_origin<uint32_t>(__out, {origin}, static_cast<uint32_t>({access}.size()), __max_align);").map_err(fmt_err)?;
2135            }
2136            writeln!(out, "{pre}for (const auto& __kv : {access}) {{").map_err(fmt_err)?;
2137            let kv_indent = format!("{pre}    ");
2138            emit_value_write(out, &m.key, "__kv.first", endian, origin, &kv_indent)?;
2139            emit_value_write(out, &m.value, "__kv.second", endian, origin, &kv_indent)?;
2140            writeln!(out, "{pre}}}").map_err(fmt_err)?;
2141            writeln!(
2142                out,
2143                "{pre}::dds::topic::xcdr2::dheader_end(__out, __map_dh);"
2144            )
2145            .map_err(fmt_err)?;
2146            writeln!(out, "{pre}}}").map_err(fmt_err)?;
2147        }
2148        // enum member: encode as its int32 underlying type (Spec §7.4.1.4.2).
2149        TypeSpec::Scoped(s) if scoped_is_enum(s) => {
2150            if endian == "be" {
2151                writeln!(
2152                    out,
2153                    "{pre}::dds::topic::xcdr2::write_be<int32_t>(__out, static_cast<int32_t>({access}));"
2154                )
2155                .map_err(fmt_err)?;
2156            } else {
2157                writeln!(
2158                    out,
2159                    "{pre}::dds::topic::xcdr2::write_le_origin<int32_t>(__out, {origin}, static_cast<int32_t>({access}), __max_align);"
2160                )
2161                .map_err(fmt_err)?;
2162            }
2163        }
2164        // nested struct member. @final: recurse, encoding each sub-member inline
2165        // (Plain-CDR2, no DHEADER, Spec §7.4.3.4.1). @appendable/@mutable: splice
2166        // the nested type's own encoding — its DHEADER forces 4-alignment, so a
2167        // 4-aligned splice point is byte-identical to standalone under XCDR2.
2168        TypeSpec::Scoped(sc) if scoped_struct(sc).is_some() => {
2169            let Some((def, ext)) = scoped_struct(sc) else {
2170                return Ok(());
2171            };
2172            match ext {
2173                Extensibility::Final => {
2174                    for sm in &def.members {
2175                        let sm_name = &sm.declarators[0].name().text;
2176                        emit_value_write(
2177                            out,
2178                            &sm.type_spec,
2179                            &format!("{access}.{sm_name}()"),
2180                            endian,
2181                            origin,
2182                            &pre,
2183                        )?;
2184                    }
2185                }
2186                Extensibility::Appendable | Extensibility::Mutable => {
2187                    let cpp = scoped_to_cpp(sc);
2188                    let id = next_nest_id();
2189                    writeln!(out, "{pre}{{").map_err(fmt_err)?;
2190                    writeln!(
2191                        out,
2192                        "{pre}    ::dds::topic::xcdr2::pad_to_from_origin(__out, {origin}, 4);"
2193                    )
2194                    .map_err(fmt_err)?;
2195                    if endian == "be" {
2196                        writeln!(
2197                            out,
2198                            "{pre}    auto __nsb{id} = ::dds::topic::topic_type_support<{cpp}>::encode_be({access});"
2199                        )
2200                        .map_err(fmt_err)?;
2201                    } else {
2202                        writeln!(
2203                            out,
2204                            "{pre}    auto __nsb{id} = ::dds::topic::topic_type_support<{cpp}>::encode({access}, __repr);"
2205                        )
2206                        .map_err(fmt_err)?;
2207                    }
2208                    writeln!(
2209                        out,
2210                        "{pre}    __out.insert(__out.end(), __nsb{id}.begin(), __nsb{id}.end());"
2211                    )
2212                    .map_err(fmt_err)?;
2213                    writeln!(out, "{pre}}}").map_err(fmt_err)?;
2214                }
2215            }
2216        }
2217        _ => {
2218            writeln!(out, "{pre}// xcdr2: member type not supported (skip)").map_err(fmt_err)?;
2219        }
2220    }
2221    Ok(())
2222}
2223
2224/// Emit Mutable-EMHEADER + body for one member.
2225fn emit_mutable_member_encode(
2226    out: &mut String,
2227    m: &Member,
2228    endian: &str,
2229) -> Result<(), CppGenError> {
2230    if !member_codegen_supported(m) {
2231        for decl in &m.declarators {
2232            let name = &decl.name().text;
2233            writeln!(
2234                out,
2235                "        // xcdr2: @shared member '{name}' not supported (skip)"
2236            )
2237            .map_err(fmt_err)?;
2238        }
2239        return Ok(());
2240    }
2241    let is_optional = has_optional_annotation(&m.annotations);
2242    let must_understand = has_named_annotation(&m.annotations, "must_understand");
2243    let id_override = find_uint_annotation(&m.annotations, "id");
2244    let mu_lit = if must_understand { "true" } else { "false" };
2245
2246    for (idx, decl) in m.declarators.iter().enumerate() {
2247        let name = &decl.name().text;
2248        if !matches!(decl, Declarator::Simple(_)) {
2249            writeln!(
2250                out,
2251                "        // xcdr2: array member '{name}' not supported (skip)"
2252            )
2253            .map_err(fmt_err)?;
2254            continue;
2255        }
2256        if !typespec_supported(&m.type_spec) {
2257            writeln!(
2258                out,
2259                "        // xcdr2: member '{name}' not supported (skip)"
2260            )
2261            .map_err(fmt_err)?;
2262            continue;
2263        }
2264        // Member-id: explicit @id override; otherwise auto-id (declaration-order).
2265        // For positional: same id_override applies to all declarators in this Member.
2266        // (IDL convention: @id() applies to the whole declaration; we replicate.)
2267        let _ = idx;
2268        let id_expr = match id_override {
2269            Some(id) => id.to_string(),
2270            None => format!("0x{:x}u", auto_id_for(name)),
2271        };
2272        if is_optional {
2273            // Mutable + optional: skip EMHEADER if absent.
2274            writeln!(out, "        if (__v.{name}().has_value()) {{").map_err(fmt_err)?;
2275            emit_mutable_value_emit(
2276                out,
2277                &m.type_spec,
2278                &format!("(*__v.{name}())"),
2279                &id_expr,
2280                mu_lit,
2281                endian,
2282                "            ",
2283            )?;
2284            writeln!(out, "        }}").map_err(fmt_err)?;
2285        } else {
2286            emit_mutable_value_emit(
2287                out,
2288                &m.type_spec,
2289                &format!("__v.{name}()"),
2290                &id_expr,
2291                mu_lit,
2292                endian,
2293                "        ",
2294            )?;
2295        }
2296    }
2297    Ok(())
2298}
2299
2300/// Auto-id from member name (XTypes "auto" id mode: name-hash truncated to 28 bits).
2301/// Default mode is "sequential" but we use name-hash for stability across re-orderings;
2302/// caller should normally provide @id(N) explicitly.
2303fn auto_id_for(name: &str) -> u32 {
2304    // FNV-1a 32-bit; truncate to 28 bits to fit EMHEADER member-id.
2305    let mut h: u32 = 0x811C9DC5;
2306    for b in name.as_bytes() {
2307        h ^= u32::from(*b);
2308        h = h.wrapping_mul(0x01000193);
2309    }
2310    h & 0x0FFF_FFFF
2311}
2312
2313fn emit_mutable_value_emit(
2314    out: &mut String,
2315    ts: &TypeSpec,
2316    access: &str,
2317    id_expr: &str,
2318    mu_lit: &str,
2319    endian: &str,
2320    indent: &str,
2321) -> Result<(), CppGenError> {
2322    match ts {
2323        TypeSpec::Primitive(PrimitiveType::Boolean) => {
2324            writeln!(
2325                out,
2326                "{indent}::dds::topic::xcdr2::emheader_u8(__out, __origin, {id_expr}, {mu_lit}, static_cast<uint8_t>({access} ? 1 : 0));"
2327            )
2328            .map_err(fmt_err)?;
2329        }
2330        TypeSpec::Primitive(PrimitiveType::Octet) => {
2331            writeln!(
2332                out,
2333                "{indent}::dds::topic::xcdr2::emheader_u8(__out, __origin, {id_expr}, {mu_lit}, {access});"
2334            )
2335            .map_err(fmt_err)?;
2336        }
2337        TypeSpec::Primitive(p) => {
2338            let cpp_ty = primitive_to_cpp(*p);
2339            // Decide LC by size.
2340            let size = primitive_size(*p);
2341            match size {
2342                2 => {
2343                    writeln!(
2344                        out,
2345                        "{indent}::dds::topic::xcdr2::emheader_2<{cpp_ty}>(__out, __origin, {id_expr}, {mu_lit}, {access});"
2346                    )
2347                    .map_err(fmt_err)?;
2348                }
2349                4 => {
2350                    writeln!(
2351                        out,
2352                        "{indent}::dds::topic::xcdr2::emheader_4<{cpp_ty}>(__out, __origin, {id_expr}, {mu_lit}, {access});"
2353                    )
2354                    .map_err(fmt_err)?;
2355                }
2356                8 => {
2357                    writeln!(
2358                        out,
2359                        "{indent}::dds::topic::xcdr2::emheader_8<{cpp_ty}>(__out, __origin, {id_expr}, {mu_lit}, {access});"
2360                    )
2361                    .map_err(fmt_err)?;
2362                }
2363                _ => {
2364                    writeln!(
2365                        out,
2366                        "{indent}// xcdr2: unexpected primitive size {size} (skip)"
2367                    )
2368                    .map_err(fmt_err)?;
2369                }
2370            }
2371        }
2372        TypeSpec::String(s) if !s.wide => {
2373            // Bounded narrow `string<N>` (DDS-XTypes §7.4.3): byte-length check.
2374            if let Some(b) = &s.bound {
2375                let bv = const_expr_to_cpp(b);
2376                writeln!(
2377                    out,
2378                    "{indent}if ({access}.size() > {bv}) throw std::length_error(\"bounded string length exceeds its IDL bound ({bv})\");"
2379                )
2380                .map_err(fmt_err)?;
2381            }
2382            // EMHEADER LC=3 with NEXTINT, then string body inline.
2383            writeln!(
2384                out,
2385                "{indent}{{ const auto __sub = ::dds::topic::xcdr2::emheader_nextint_begin(__out, __origin, {id_expr}, {mu_lit});"
2386            )
2387            .map_err(fmt_err)?;
2388            // Inside the NEXTINT block, the body itself uses origin = __sub.body_start
2389            // (string-len align is 4, count starts at body_start which is 4-aligned).
2390            let body_endian = if endian == "be" { "be" } else { "le" };
2391            let _ = body_endian;
2392            writeln!(
2393                out,
2394                "{indent}    {{ const auto __body_origin = __sub.body_start; (void)__body_origin;"
2395            )
2396            .map_err(fmt_err)?;
2397            if endian == "be" {
2398                writeln!(
2399                    out,
2400                    "{indent}      ::dds::topic::xcdr2::write_string_be(__out, {access});"
2401                )
2402                .map_err(fmt_err)?;
2403            } else {
2404                writeln!(
2405                    out,
2406                    "{indent}      ::dds::topic::xcdr2::write_string_origin(__out, __body_origin, {access}, __max_align);"
2407                )
2408                .map_err(fmt_err)?;
2409            }
2410            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
2411            writeln!(
2412                out,
2413                "{indent}    ::dds::topic::xcdr2::emheader_nextint_end(__out, __sub); }}"
2414            )
2415            .map_err(fmt_err)?;
2416        }
2417        TypeSpec::String(s) if s.wide => {
2418            // Bounded `wstring<N>` (DDS-XTypes §7.4.3): wide-char-length check.
2419            if let Some(b) = &s.bound {
2420                let bv = const_expr_to_cpp(b);
2421                writeln!(
2422                    out,
2423                    "{indent}if ({access}.size() > {bv}) throw std::length_error(\"bounded wstring length exceeds its IDL bound ({bv})\");"
2424                )
2425                .map_err(fmt_err)?;
2426            }
2427            // EMHEADER LC=3 with NEXTINT, then wstring body inline (UTF-16).
2428            writeln!(
2429                out,
2430                "{indent}{{ const auto __sub = ::dds::topic::xcdr2::emheader_nextint_begin(__out, __origin, {id_expr}, {mu_lit});"
2431            )
2432            .map_err(fmt_err)?;
2433            writeln!(
2434                out,
2435                "{indent}    {{ const auto __body_origin = __sub.body_start; (void)__body_origin;"
2436            )
2437            .map_err(fmt_err)?;
2438            if endian == "be" {
2439                writeln!(
2440                    out,
2441                    "{indent}      ::dds::topic::xcdr2::write_wstring_be(__out, {access});"
2442                )
2443                .map_err(fmt_err)?;
2444            } else {
2445                writeln!(
2446                    out,
2447                    "{indent}      ::dds::topic::xcdr2::write_wstring_origin(__out, __body_origin, {access}, __max_align);"
2448                )
2449                .map_err(fmt_err)?;
2450            }
2451            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
2452            writeln!(
2453                out,
2454                "{indent}    ::dds::topic::xcdr2::emheader_nextint_end(__out, __sub); }}"
2455            )
2456            .map_err(fmt_err)?;
2457        }
2458        TypeSpec::Sequence(seq) => {
2459            // Bounded `sequence<T, N>` (DDS-XTypes §7.4.3): over-bound = throw.
2460            if let Some(b) = &seq.bound {
2461                let bv = const_expr_to_cpp(b);
2462                writeln!(
2463                    out,
2464                    "{indent}if ({access}.size() > {bv}) throw std::length_error(\"bounded sequence length exceeds its IDL bound ({bv})\");"
2465                )
2466                .map_err(fmt_err)?;
2467            }
2468            writeln!(
2469                out,
2470                "{indent}{{ const auto __sub = ::dds::topic::xcdr2::emheader_nextint_begin(__out, __origin, {id_expr}, {mu_lit});"
2471            )
2472            .map_err(fmt_err)?;
2473            writeln!(
2474                out,
2475                "{indent}    {{ const auto __body_origin = __sub.body_start; (void)__body_origin;"
2476            )
2477            .map_err(fmt_err)?;
2478            // XTypes §7.4.3.5: a non-primitive-element sequence carries its OWN
2479            // DHEADER even inside a mutable EMHEADER NEXTINT frame (the Rust
2480            // reference encoder writes EMHEADER+NEXTINT+DHEADER+count+elements;
2481            // Cyclone-interop-verified). Primitive-element sequences carry none.
2482            let seq_inner_dh = !matches!(&*seq.elem, TypeSpec::Primitive(_));
2483            if seq_inner_dh {
2484                writeln!(
2485                    out,
2486                    "{indent}      const auto __seq_dh = ::dds::topic::xcdr2::dheader_begin(__out);"
2487                )
2488                .map_err(fmt_err)?;
2489            }
2490            if endian == "be" {
2491                writeln!(
2492                    out,
2493                    "{indent}      ::dds::topic::xcdr2::write_be<uint32_t>(__out, static_cast<uint32_t>({access}.size()));"
2494                )
2495                .map_err(fmt_err)?;
2496            } else {
2497                writeln!(
2498                    out,
2499                    "{indent}      ::dds::topic::xcdr2::write_le_origin<uint32_t>(__out, __body_origin, static_cast<uint32_t>({access}.size()), __max_align);"
2500                )
2501                .map_err(fmt_err)?;
2502            }
2503            if matches!(&*seq.elem, TypeSpec::Primitive(PrimitiveType::Octet)) {
2504                // sequence<octet>: raw byte block instead of a per-byte loop.
2505                writeln!(
2506                    out,
2507                    "{indent}      __out.insert(__out.end(), {access}.begin(), {access}.end());"
2508                )
2509                .map_err(fmt_err)?;
2510            } else {
2511                writeln!(out, "{indent}      for (const auto& __e : {access}) {{")
2512                    .map_err(fmt_err)?;
2513                match &*seq.elem {
2514                    TypeSpec::Primitive(PrimitiveType::Boolean) => {
2515                        writeln!(
2516                            out,
2517                            "{indent}        ::dds::topic::xcdr2::write_bool(__out, __e);"
2518                        )
2519                        .map_err(fmt_err)?;
2520                    }
2521                    TypeSpec::Primitive(PrimitiveType::Octet) => {
2522                        writeln!(
2523                            out,
2524                            "{indent}        ::dds::topic::xcdr2::write_u8(__out, __e);"
2525                        )
2526                        .map_err(fmt_err)?;
2527                    }
2528                    TypeSpec::Primitive(p) => {
2529                        let cpp_ty = primitive_to_cpp(*p);
2530                        if endian == "be" {
2531                            writeln!(
2532                            out,
2533                            "{indent}        ::dds::topic::xcdr2::write_be<{cpp_ty}>(__out, __e);"
2534                        )
2535                        .map_err(fmt_err)?;
2536                        } else {
2537                            writeln!(out, "{indent}        ::dds::topic::xcdr2::write_le_origin<{cpp_ty}>(__out, __body_origin, __e, __max_align);").map_err(fmt_err)?;
2538                        }
2539                    }
2540                    TypeSpec::String(s) if !s.wide => {
2541                        if endian == "be" {
2542                            writeln!(
2543                                out,
2544                                "{indent}        ::dds::topic::xcdr2::write_string_be(__out, __e);"
2545                            )
2546                            .map_err(fmt_err)?;
2547                        } else {
2548                            writeln!(out, "{indent}        ::dds::topic::xcdr2::write_string_origin(__out, __body_origin, __e, __max_align);").map_err(fmt_err)?;
2549                        }
2550                    }
2551                    // wstring / enum / nested struct (any extensibility) elements:
2552                    // recurse with the EMHEADER body-origin (identical to the
2553                    // plain-path arms; non-final elements pad-to-4 + splice).
2554                    TypeSpec::String(_) => {
2555                        emit_value_write(
2556                            out,
2557                            &seq.elem,
2558                            "__e",
2559                            endian,
2560                            "__body_origin",
2561                            &format!("{indent}        "),
2562                        )?;
2563                    }
2564                    TypeSpec::Scoped(sc) if scoped_is_enum(sc) || scoped_struct(sc).is_some() => {
2565                        emit_value_write(
2566                            out,
2567                            &seq.elem,
2568                            "__e",
2569                            endian,
2570                            "__body_origin",
2571                            &format!("{indent}        "),
2572                        )?;
2573                    }
2574                    // nested sequence / map element (each emits its own DHEADER).
2575                    TypeSpec::Sequence(_) | TypeSpec::Map(_) => {
2576                        emit_value_write(
2577                            out,
2578                            &seq.elem,
2579                            "__e",
2580                            endian,
2581                            "__body_origin",
2582                            &format!("{indent}        "),
2583                        )?;
2584                    }
2585                    _ => {
2586                        writeln!(
2587                            out,
2588                            "{indent}        // xcdr2: nested seq-elem not supported"
2589                        )
2590                        .map_err(fmt_err)?;
2591                    }
2592                }
2593                writeln!(out, "{indent}      }}").map_err(fmt_err)?;
2594            }
2595            if seq_inner_dh {
2596                writeln!(
2597                    out,
2598                    "{indent}      ::dds::topic::xcdr2::dheader_end(__out, __seq_dh);"
2599                )
2600                .map_err(fmt_err)?;
2601            }
2602            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
2603            writeln!(
2604                out,
2605                "{indent}    ::dds::topic::xcdr2::emheader_nextint_end(__out, __sub); }}"
2606            )
2607            .map_err(fmt_err)?;
2608        }
2609        // enum member: 4-byte int32 -> compact LC=2 EMHEADER (no NEXTINT).
2610        TypeSpec::Scoped(s) if scoped_is_enum(s) => {
2611            writeln!(
2612                out,
2613                "{indent}::dds::topic::xcdr2::emheader_4<int32_t>(__out, __origin, {id_expr}, {mu_lit}, static_cast<int32_t>({access}));"
2614            )
2615            .map_err(fmt_err)?;
2616        }
2617        // nested struct member as a @mutable member: LC=4 NEXTINT frame. @final:
2618        // wrap the inline (no inner DHEADER) body. @appendable/@mutable: splice
2619        // the nested type's own encoding (it carries its own inner DHEADER) into
2620        // the NEXTINT body — byte-identical to the Rust reference (a non-final
2621        // nested struct contributes its DHEADER inside the member frame).
2622        TypeSpec::Scoped(sc) if scoped_struct(sc).is_some() => {
2623            let Some((def, ext)) = scoped_struct(sc) else {
2624                return Ok(());
2625            };
2626            writeln!(
2627                out,
2628                "{indent}{{ const auto __sub = ::dds::topic::xcdr2::emheader_nextint_begin(__out, __origin, {id_expr}, {mu_lit});"
2629            )
2630            .map_err(fmt_err)?;
2631            match ext {
2632                Extensibility::Final => {
2633                    writeln!(
2634                        out,
2635                        "{indent}    {{ const auto __body_origin = __sub.body_start; (void)__body_origin;"
2636                    )
2637                    .map_err(fmt_err)?;
2638                    for sm in &def.members {
2639                        let sm_name = &sm.declarators[0].name().text;
2640                        emit_value_write(
2641                            out,
2642                            &sm.type_spec,
2643                            &format!("{access}.{sm_name}()"),
2644                            endian,
2645                            "__body_origin",
2646                            &format!("{indent}      "),
2647                        )?;
2648                    }
2649                    writeln!(out, "{indent}    }}").map_err(fmt_err)?;
2650                }
2651                Extensibility::Appendable | Extensibility::Mutable => {
2652                    let cpp = scoped_to_cpp(sc);
2653                    let id = next_nest_id();
2654                    writeln!(
2655                        out,
2656                        "{indent}    {{ ::dds::topic::xcdr2::pad_to_from_origin(__out, __sub.body_start, 4);"
2657                    )
2658                    .map_err(fmt_err)?;
2659                    if endian == "be" {
2660                        writeln!(
2661                            out,
2662                            "{indent}      auto __nsb{id} = ::dds::topic::topic_type_support<{cpp}>::encode_be({access});"
2663                        )
2664                        .map_err(fmt_err)?;
2665                    } else {
2666                        writeln!(
2667                            out,
2668                            "{indent}      auto __nsb{id} = ::dds::topic::topic_type_support<{cpp}>::encode({access}, __repr);"
2669                        )
2670                        .map_err(fmt_err)?;
2671                    }
2672                    writeln!(
2673                        out,
2674                        "{indent}      __out.insert(__out.end(), __nsb{id}.begin(), __nsb{id}.end()); }}"
2675                    )
2676                    .map_err(fmt_err)?;
2677                }
2678            }
2679            writeln!(
2680                out,
2681                "{indent}    ::dds::topic::xcdr2::emheader_nextint_end(__out, __sub); }}"
2682            )
2683            .map_err(fmt_err)?;
2684        }
2685        // map<K,V> member: LC=4 NEXTINT frame wrapping [DHEADER + count +
2686        // interleaved entries]. A map is always a non-primitive collection, so —
2687        // like the mutable Sequence arm and the Rust reference encoder — it
2688        // carries its own inner DHEADER inside the NEXTINT frame (Finding 6,
2689        // resolved against the Rust/Cyclone wire).
2690        TypeSpec::Map(m) => {
2691            writeln!(
2692                out,
2693                "{indent}{{ const auto __sub = ::dds::topic::xcdr2::emheader_nextint_begin(__out, __origin, {id_expr}, {mu_lit});"
2694            )
2695            .map_err(fmt_err)?;
2696            writeln!(
2697                out,
2698                "{indent}    {{ const auto __body_origin = __sub.body_start; (void)__body_origin;"
2699            )
2700            .map_err(fmt_err)?;
2701            writeln!(
2702                out,
2703                "{indent}      const auto __map_dh = ::dds::topic::xcdr2::dheader_begin(__out);"
2704            )
2705            .map_err(fmt_err)?;
2706            if endian == "be" {
2707                writeln!(out, "{indent}      ::dds::topic::xcdr2::write_be<uint32_t>(__out, static_cast<uint32_t>({access}.size()));").map_err(fmt_err)?;
2708            } else {
2709                writeln!(out, "{indent}      ::dds::topic::xcdr2::write_le_origin<uint32_t>(__out, __body_origin, static_cast<uint32_t>({access}.size()), __max_align);").map_err(fmt_err)?;
2710            }
2711            writeln!(out, "{indent}      for (const auto& __kv : {access}) {{").map_err(fmt_err)?;
2712            let kv_indent = format!("{indent}        ");
2713            emit_value_write(
2714                out,
2715                &m.key,
2716                "__kv.first",
2717                endian,
2718                "__body_origin",
2719                &kv_indent,
2720            )?;
2721            emit_value_write(
2722                out,
2723                &m.value,
2724                "__kv.second",
2725                endian,
2726                "__body_origin",
2727                &kv_indent,
2728            )?;
2729            writeln!(out, "{indent}      }}").map_err(fmt_err)?;
2730            writeln!(
2731                out,
2732                "{indent}      ::dds::topic::xcdr2::dheader_end(__out, __map_dh);"
2733            )
2734            .map_err(fmt_err)?;
2735            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
2736            writeln!(
2737                out,
2738                "{indent}    ::dds::topic::xcdr2::emheader_nextint_end(__out, __sub); }}"
2739            )
2740            .map_err(fmt_err)?;
2741        }
2742        _ => {
2743            writeln!(out, "{indent}// xcdr2: unsupported member type").map_err(fmt_err)?;
2744        }
2745    }
2746    Ok(())
2747}
2748
2749fn primitive_size(p: PrimitiveType) -> usize {
2750    use zerodds_idl::ast::{FloatingType, IntegerType};
2751    match p {
2752        PrimitiveType::Boolean => 1,
2753        PrimitiveType::Octet => 1,
2754        PrimitiveType::Char => 1,
2755        PrimitiveType::WideChar => 2,
2756        PrimitiveType::Integer(i) => match i {
2757            IntegerType::Int8 | IntegerType::UInt8 => 1,
2758            IntegerType::Short | IntegerType::UShort | IntegerType::Int16 | IntegerType::UInt16 => {
2759                2
2760            }
2761            IntegerType::Long | IntegerType::ULong | IntegerType::Int32 | IntegerType::UInt32 => 4,
2762            IntegerType::LongLong
2763            | IntegerType::ULongLong
2764            | IntegerType::Int64
2765            | IntegerType::UInt64 => 8,
2766        },
2767        PrimitiveType::Floating(f) => match f {
2768            FloatingType::Float => 4,
2769            FloatingType::Double => 8,
2770            FloatingType::LongDouble => 16,
2771        },
2772    }
2773}
2774
2775fn emit_decode_fn(
2776    out: &mut String,
2777    cpp_fqn: &str,
2778    s: &StructDef,
2779    ext: Extensibility,
2780) -> Result<(), CppGenError> {
2781    writeln!(
2782        out,
2783        "    static {cpp_fqn} decode(const uint8_t* __buf, size_t __len, \
2784         ::dds::topic::xcdr2::XcdrVersion __repr) {{"
2785    )
2786    .map_err(fmt_err)?;
2787    writeln!(out, "        size_t __pos = 0;").map_err(fmt_err)?;
2788    writeln!(out, "        {cpp_fqn} __v;").map_err(fmt_err)?;
2789    // The XCDR version controls alignment: XCDR2 caps 8-byte primitives
2790    // to 4-byte boundaries (XTypes 1.3 §7.4.3.4.2), XCDR1 does not.
2791    writeln!(
2792        out,
2793        "        const size_t __max_align = ::dds::topic::xcdr2::xcdr_max_align(__repr);"
2794    )
2795    .map_err(fmt_err)?;
2796    writeln!(
2797        out,
2798        "        (void)__buf; (void)__len; (void)__pos; (void)__max_align;"
2799    )
2800    .map_err(fmt_err)?;
2801
2802    match ext {
2803        Extensibility::Final => {
2804            writeln!(out, "        const size_t __origin = 0;").map_err(fmt_err)?;
2805            writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
2806            for m in &s.members {
2807                emit_plain_member_decode(out, m, "__origin")?;
2808            }
2809        }
2810        Extensibility::Appendable => {
2811            writeln!(
2812                out,
2813                "        const auto __dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len);"
2814            )
2815            .map_err(fmt_err)?;
2816            writeln!(out, "        const size_t __origin = __pos;").map_err(fmt_err)?;
2817            writeln!(out, "        const size_t __end = __origin + __dh;").map_err(fmt_err)?;
2818            writeln!(out, "        (void)__end;").map_err(fmt_err)?;
2819            for m in &s.members {
2820                emit_plain_member_decode(out, m, "__origin")?;
2821            }
2822            // Skip trailing bytes (forward-compat with appendable extension).
2823            writeln!(out, "        if (__pos < __end) __pos = __end;").map_err(fmt_err)?;
2824        }
2825        Extensibility::Mutable => {
2826            writeln!(
2827                out,
2828                "        const auto __dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len);"
2829            )
2830            .map_err(fmt_err)?;
2831            writeln!(out, "        const size_t __origin = __pos;").map_err(fmt_err)?;
2832            writeln!(out, "        const size_t __end = __origin + __dh;").map_err(fmt_err)?;
2833            writeln!(out, "        while (__pos + 4 <= __end) {{").map_err(fmt_err)?;
2834            writeln!(
2835                out,
2836                "            const auto __h = ::dds::topic::xcdr2::emheader_read(__buf, __pos, __len, __origin);"
2837            )
2838            .map_err(fmt_err)?;
2839            writeln!(out, "            switch (__h.member_id) {{").map_err(fmt_err)?;
2840            for m in &s.members {
2841                emit_mutable_member_decode_case(out, m)?;
2842            }
2843            writeln!(out, "                default: {{").map_err(fmt_err)?;
2844            writeln!(
2845                out,
2846                "                    // Unknown member: per-LC skip per XTypes 1.3"
2847            )
2848            .map_err(fmt_err)?;
2849            writeln!(
2850                out,
2851                "                    // §7.4.3.4.2 (LengthCode::body_len). LC0..3 are"
2852            )
2853            .map_err(fmt_err)?;
2854            writeln!(
2855                out,
2856                "                    // fixed 1/2/4/8-byte bodies WITHOUT NEXTINT; LC4/5 NEXTINT="
2857            )
2858            .map_err(fmt_err)?;
2859            writeln!(
2860                out,
2861                "                    // byte length; LC6/7 NEXTINT=element count (4 + 4n / 4 + 8n)."
2862            )
2863            .map_err(fmt_err)?;
2864            writeln!(
2865                out,
2866                "                    if (__h.lc == 0) {{ __pos += 1; }}"
2867            )
2868            .map_err(fmt_err)?;
2869            writeln!(
2870                out,
2871                "                    else if (__h.lc == 1) {{ __pos += 2; }}"
2872            )
2873            .map_err(fmt_err)?;
2874            writeln!(
2875                out,
2876                "                    else if (__h.lc == 2) {{ __pos += 4; }}"
2877            )
2878            .map_err(fmt_err)?;
2879            writeln!(
2880                out,
2881                "                    else if (__h.lc == 3) {{ __pos += 8; }}"
2882            )
2883            .map_err(fmt_err)?;
2884            writeln!(
2885                out,
2886                "                    else if (__h.lc == 4 || __h.lc == 5) {{ auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len); __pos += __n; }}"
2887            )
2888            .map_err(fmt_err)?;
2889            writeln!(
2890                out,
2891                "                    else if (__h.lc == 6) {{ auto __c = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len); __pos += 4 + 4 * static_cast<size_t>(__c); }}"
2892            )
2893            .map_err(fmt_err)?;
2894            writeln!(
2895                out,
2896                "                    else {{ auto __c = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len); __pos += 4 + 8 * static_cast<size_t>(__c); }}"
2897            )
2898            .map_err(fmt_err)?;
2899            writeln!(out, "                    break;").map_err(fmt_err)?;
2900            writeln!(out, "                }}").map_err(fmt_err)?;
2901            writeln!(out, "            }}").map_err(fmt_err)?;
2902            writeln!(out, "        }}").map_err(fmt_err)?;
2903            writeln!(out, "        if (__pos < __end) __pos = __end;").map_err(fmt_err)?;
2904        }
2905    }
2906
2907    writeln!(out, "        return __v;").map_err(fmt_err)?;
2908    writeln!(out, "    }}").map_err(fmt_err)?;
2909    Ok(())
2910}
2911
2912fn emit_plain_member_decode(out: &mut String, m: &Member, origin: &str) -> Result<(), CppGenError> {
2913    if !member_codegen_supported(m) {
2914        for decl in &m.declarators {
2915            let name = &decl.name().text;
2916            writeln!(
2917                out,
2918                "        // xcdr2: @shared member '{name}' not supported (skip)"
2919            )
2920            .map_err(fmt_err)?;
2921        }
2922        return Ok(());
2923    }
2924    let is_optional = has_optional_annotation(&m.annotations);
2925    for decl in &m.declarators {
2926        let name = &decl.name().text;
2927        // 1-D fixed array of a leaf type — read N elements in place (symmetric to
2928        // the plain-encode array path). Multi-dim / array-of-struct: follow-up.
2929        if let Declarator::Array(arr) = decl {
2930            let prim = matches!(m.type_spec, TypeSpec::Primitive(_));
2931            let leaf_1d = arr.sizes.len() == 1
2932                && matches!(m.type_spec, TypeSpec::Primitive(_) | TypeSpec::String(_));
2933            let prim_read_expr = || -> String {
2934                match &m.type_spec {
2935                    TypeSpec::Primitive(PrimitiveType::Boolean) => {
2936                        "::dds::topic::xcdr2::read_bool(__buf, __pos, __len)".to_string()
2937                    }
2938                    TypeSpec::Primitive(PrimitiveType::Octet) => {
2939                        "::dds::topic::xcdr2::read_u8(__buf, __pos, __len)".to_string()
2940                    }
2941                    TypeSpec::Primitive(p) => format!(
2942                        "::dds::topic::xcdr2::read_le_origin<{}>(__buf, __pos, __len, {origin}, __max_align)",
2943                        primitive_to_cpp(*p)
2944                    ),
2945                    TypeSpec::String(s) if s.wide => format!(
2946                        "::dds::topic::xcdr2::read_wstring_origin(__buf, __pos, __len, {origin}, __max_align)"
2947                    ),
2948                    _ => format!(
2949                        "::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, {origin}, __max_align)"
2950                    ),
2951                }
2952            };
2953            if leaf_1d {
2954                let read_expr = prim_read_expr();
2955                writeln!(out, "        {{").map_err(fmt_err)?;
2956                writeln!(out, "            auto __arr = __v.{name}();").map_err(fmt_err)?;
2957                writeln!(
2958                    out,
2959                    "            for (auto& __ae : __arr) {{ __ae = {read_expr}; }}"
2960                )
2961                .map_err(fmt_err)?;
2962                writeln!(out, "            __v.{name}(__arr);").map_err(fmt_err)?;
2963                writeln!(out, "        }}").map_err(fmt_err)?;
2964            } else if prim && arr.sizes.len() >= 2 {
2965                // Multi-dim primitive array: read row-major into the nested
2966                // std::array via N nested loops (symmetric to the encode).
2967                let read_expr = prim_read_expr();
2968                let n = arr.sizes.len();
2969                writeln!(out, "        {{").map_err(fmt_err)?;
2970                writeln!(out, "            auto __arr = __v.{name}();").map_err(fmt_err)?;
2971                let mut acc = String::from("__arr");
2972                let mut ind = String::from("            ");
2973                for d in 0..n {
2974                    let lv = format!("__a{d}");
2975                    writeln!(out, "{ind}for (auto& {lv} : {acc}) {{").map_err(fmt_err)?;
2976                    acc = lv;
2977                    ind.push_str("    ");
2978                }
2979                writeln!(out, "{ind}{acc} = {read_expr};").map_err(fmt_err)?;
2980                for _ in 0..n {
2981                    ind.truncate(ind.len() - 4);
2982                    writeln!(out, "{ind}}}").map_err(fmt_err)?;
2983                }
2984                writeln!(out, "            __v.{name}(__arr);").map_err(fmt_err)?;
2985                writeln!(out, "        }}").map_err(fmt_err)?;
2986            } else if matches!(&m.type_spec, TypeSpec::Scoped(s) if scoped_is_enum(s) || scoped_final_struct(s).is_some())
2987                || (matches!(m.type_spec, TypeSpec::String(_)) && arr.sizes.len() >= 2)
2988            {
2989                // Array of non-primitive elements (any dims; string only >=2-D):
2990                // skip the DHEADER, read N elements in place via N nested loops
2991                // (symmetric to the encode; fixed size, no count).
2992                let n = arr.sizes.len();
2993                writeln!(out, "        {{").map_err(fmt_err)?;
2994                writeln!(out, "        const auto __arr_dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len); (void)__arr_dh;").map_err(fmt_err)?;
2995                writeln!(out, "        auto __arr = __v.{name}();").map_err(fmt_err)?;
2996                let mut acc = String::from("__arr");
2997                let mut ind = String::from("        ");
2998                for d in 0..n {
2999                    let lv = format!("__a{d}");
3000                    writeln!(out, "{ind}for (auto& {lv} : {acc}) {{").map_err(fmt_err)?;
3001                    acc = lv;
3002                    ind.push_str("    ");
3003                }
3004                emit_value_read(out, &m.type_spec, &format!("{acc} ="), origin, &ind, false)?;
3005                for _ in 0..n {
3006                    ind.truncate(ind.len() - 4);
3007                    writeln!(out, "{ind}}}").map_err(fmt_err)?;
3008                }
3009                writeln!(out, "        __v.{name}(__arr);").map_err(fmt_err)?;
3010                writeln!(out, "        }}").map_err(fmt_err)?;
3011            } else {
3012                writeln!(
3013                    out,
3014                    "        // xcdr2: array member '{name}' (1-D string only / unsupported elem) not supported (skip)"
3015                )
3016                .map_err(fmt_err)?;
3017            }
3018            continue;
3019        }
3020        if !typespec_supported(&m.type_spec) {
3021            writeln!(
3022                out,
3023                "        // xcdr2: member '{name}' not supported (skip)"
3024            )
3025            .map_err(fmt_err)?;
3026            continue;
3027        }
3028        if is_optional {
3029            writeln!(out, "        {{").map_err(fmt_err)?;
3030            writeln!(
3031                out,
3032                "            uint8_t __present = ::dds::topic::xcdr2::read_u8(__buf, __pos, __len);"
3033            )
3034            .map_err(fmt_err)?;
3035            writeln!(out, "            if (__present) {{").map_err(fmt_err)?;
3036            emit_value_read(
3037                out,
3038                &m.type_spec,
3039                &format!("__v.{name}"),
3040                origin,
3041                "                ",
3042                true,
3043            )?;
3044            writeln!(out, "            }} else {{").map_err(fmt_err)?;
3045            writeln!(out, "                __v.{name}(std::nullopt);").map_err(fmt_err)?;
3046            writeln!(out, "            }}").map_err(fmt_err)?;
3047            writeln!(out, "        }}").map_err(fmt_err)?;
3048        } else {
3049            emit_value_read(
3050                out,
3051                &m.type_spec,
3052                &format!("__v.{name}"),
3053                origin,
3054                "        ",
3055                false,
3056            )?;
3057        }
3058    }
3059    Ok(())
3060}
3061
3062/// zerodds-lint: recursion-depth 64 (nested type emit; bounded by IDL nesting)
3063fn emit_value_read(
3064    out: &mut String,
3065    ts: &TypeSpec,
3066    setter: &str,
3067    origin: &str,
3068    indent: &str,
3069    is_opt: bool,
3070) -> Result<(), CppGenError> {
3071    let wrap_opt = |v: String| -> String {
3072        if is_opt {
3073            format!("std::optional<decltype({v})>({v})")
3074        } else {
3075            v
3076        }
3077    };
3078    let _ = wrap_opt;
3079    match ts {
3080        TypeSpec::Primitive(PrimitiveType::Boolean) => {
3081            writeln!(
3082                out,
3083                "{indent}{setter}(::dds::topic::xcdr2::read_bool(__buf, __pos, __len));"
3084            )
3085            .map_err(fmt_err)?;
3086        }
3087        TypeSpec::Primitive(PrimitiveType::Octet) => {
3088            writeln!(
3089                out,
3090                "{indent}{setter}(::dds::topic::xcdr2::read_u8(__buf, __pos, __len));"
3091            )
3092            .map_err(fmt_err)?;
3093        }
3094        TypeSpec::Primitive(p) => {
3095            let cpp_ty = primitive_to_cpp(*p);
3096            writeln!(
3097                out,
3098                "{indent}{setter}(::dds::topic::xcdr2::read_le_origin<{cpp_ty}>(__buf, __pos, __len, {origin}, __max_align));"
3099            )
3100            .map_err(fmt_err)?;
3101        }
3102        TypeSpec::String(s) if !s.wide => {
3103            writeln!(
3104                out,
3105                "{indent}{setter}(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, {origin}, __max_align));"
3106            )
3107            .map_err(fmt_err)?;
3108        }
3109        TypeSpec::String(s) if s.wide => {
3110            writeln!(
3111                out,
3112                "{indent}{setter}(::dds::topic::xcdr2::read_wstring_origin(__buf, __pos, __len, {origin}, __max_align));"
3113            )
3114            .map_err(fmt_err)?;
3115        }
3116        TypeSpec::Sequence(seq) => {
3117            if matches!(&*seq.elem, TypeSpec::Primitive(PrimitiveType::Octet)) {
3118                // sequence<octet>: raw byte block directly from the buffer.
3119                writeln!(out, "{indent}{{").map_err(fmt_err)?;
3120                writeln!(out, "{indent}    auto __cnt = ::dds::topic::xcdr2::read_le_origin<uint32_t>(__buf, __pos, __len, {origin}, __max_align);").map_err(fmt_err)?;
3121                writeln!(
3122                    out,
3123                    "{indent}    ::dds::topic::xcdr2::check_avail(__pos, __cnt, __len);"
3124                )
3125                .map_err(fmt_err)?;
3126                writeln!(
3127                    out,
3128                    "{indent}    std::vector<uint8_t> __seq(__buf + __pos, __buf + __pos + __cnt);"
3129                )
3130                .map_err(fmt_err)?;
3131                writeln!(out, "{indent}    __pos += __cnt;").map_err(fmt_err)?;
3132                writeln!(out, "{indent}    {setter}(std::move(__seq));").map_err(fmt_err)?;
3133                writeln!(out, "{indent}}}").map_err(fmt_err)?;
3134                return Ok(());
3135            }
3136            let elem_cpp_ty: String = match &*seq.elem {
3137                TypeSpec::Primitive(PrimitiveType::Boolean) => "bool".to_string(),
3138                TypeSpec::Primitive(p) => primitive_to_cpp(*p).to_string(),
3139                TypeSpec::String(s) if !s.wide => "std::string".to_string(),
3140                // wide string element -> std::wstring (narrow caught above).
3141                TypeSpec::String(_) => "std::wstring".to_string(),
3142                // enum (-> underlying int32, but the vector holds the enum) and
3143                // nested struct elements (any extensibility) use their C++ type.
3144                TypeSpec::Scoped(s) if scoped_is_enum(s) => scoped_to_cpp(s),
3145                TypeSpec::Scoped(s) if scoped_struct(s).is_some() => scoped_to_cpp(s),
3146                // nested sequence element -> std::vector<inner>.
3147                TypeSpec::Sequence(_) => typespec_to_cpp(&seq.elem)?,
3148                // map element -> std::map<K,V>.
3149                TypeSpec::Map(_) => typespec_to_cpp(&seq.elem)?,
3150                _ => {
3151                    writeln!(
3152                        out,
3153                        "{indent}// xcdr2: nested seq-elem not supported (skip)"
3154                    )
3155                    .map_err(fmt_err)?;
3156                    return Ok(());
3157                }
3158            };
3159            writeln!(out, "{indent}{{").map_err(fmt_err)?;
3160            // XCDR2 §7.4.3.5: for non-primitive elements skip the DHEADER.
3161            if !matches!(&*seq.elem, TypeSpec::Primitive(_)) {
3162                writeln!(out, "{indent}    const auto __seq_dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len); (void)__seq_dh;").map_err(fmt_err)?;
3163            }
3164            writeln!(out, "{indent}    auto __cnt = ::dds::topic::xcdr2::read_le_origin<uint32_t>(__buf, __pos, __len, {origin}, __max_align);").map_err(fmt_err)?;
3165            writeln!(out, "{indent}    std::vector<{elem_cpp_ty}> __seq;").map_err(fmt_err)?;
3166            writeln!(out, "{indent}    __seq.reserve(__cnt);").map_err(fmt_err)?;
3167            writeln!(
3168                out,
3169                "{indent}    for (uint32_t __i = 0; __i < __cnt; ++__i) {{"
3170            )
3171            .map_err(fmt_err)?;
3172            match &*seq.elem {
3173                TypeSpec::Primitive(PrimitiveType::Boolean) => {
3174                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_bool(__buf, __pos, __len));").map_err(fmt_err)?;
3175                }
3176                TypeSpec::Primitive(PrimitiveType::Octet) => {
3177                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_u8(__buf, __pos, __len));").map_err(fmt_err)?;
3178                }
3179                TypeSpec::Primitive(p) => {
3180                    let cpp_ty = primitive_to_cpp(*p);
3181                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_le_origin<{cpp_ty}>(__buf, __pos, __len, {origin}, __max_align));").map_err(fmt_err)?;
3182                }
3183                TypeSpec::String(s) if !s.wide => {
3184                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, {origin}, __max_align));").map_err(fmt_err)?;
3185                }
3186                // wide string element.
3187                TypeSpec::String(_) => {
3188                    writeln!(out, "{indent}        __seq.push_back(::dds::topic::xcdr2::read_wstring_origin(__buf, __pos, __len, {origin}, __max_align));").map_err(fmt_err)?;
3189                }
3190                // enum element: read int32, cast back to the enum type.
3191                TypeSpec::Scoped(s) if scoped_is_enum(s) => {
3192                    let cpp_ty = scoped_to_cpp(s);
3193                    writeln!(out, "{indent}        __seq.push_back(static_cast<{cpp_ty}>(::dds::topic::xcdr2::read_le_origin<int32_t>(__buf, __pos, __len, {origin}, __max_align)));").map_err(fmt_err)?;
3194                }
3195                // nested @final struct element: read each sub-member into a fresh
3196                // temp, push the whole object (symmetric to the inline encode).
3197                TypeSpec::Scoped(sc) if scoped_final_struct(sc).is_some() => {
3198                    if let Some(def) = scoped_final_struct(sc) {
3199                        let cpp_ty = scoped_to_cpp(sc);
3200                        let var = format!("__se{}", next_nest_id());
3201                        let binner = format!("{indent}        ");
3202                        writeln!(out, "{binner}{cpp_ty} {var}{{}};").map_err(fmt_err)?;
3203                        for sm in &def.members {
3204                            let sm_name = &sm.declarators[0].name().text;
3205                            emit_value_read(
3206                                out,
3207                                &sm.type_spec,
3208                                &format!("{var}.{sm_name}"),
3209                                origin,
3210                                &binner,
3211                                false,
3212                            )?;
3213                        }
3214                        writeln!(out, "{binner}__seq.push_back({var});").map_err(fmt_err)?;
3215                    }
3216                }
3217                // nested @appendable/@mutable struct element: 4-align, read the
3218                // element's own DHEADER, sub-decode the [DHEADER+body] slice via
3219                // the nested type's `decode`, advance the cursor past it, push it.
3220                TypeSpec::Scoped(sc) if scoped_struct(sc).is_some() => {
3221                    let cpp_ty = scoped_to_cpp(sc);
3222                    let id = next_nest_id();
3223                    let var = format!("__se{id}");
3224                    let binner = format!("{indent}        ");
3225                    writeln!(
3226                        out,
3227                        "{binner}::dds::topic::xcdr2::skip_pad_from_origin(__pos, {origin}, 4);"
3228                    )
3229                    .map_err(fmt_err)?;
3230                    writeln!(out, "{binner}const size_t __nss{id} = __pos;").map_err(fmt_err)?;
3231                    writeln!(out, "{binner}size_t __npk{id} = __pos;").map_err(fmt_err)?;
3232                    writeln!(out, "{binner}const uint32_t __nl{id} = ::dds::topic::xcdr2::dheader_read(__buf, __npk{id}, __len);").map_err(fmt_err)?;
3233                    writeln!(out, "{binner}{cpp_ty} {var} = ::dds::topic::topic_type_support<{cpp_ty}>::decode(__buf + __nss{id}, 4u + __nl{id}, __repr);").map_err(fmt_err)?;
3234                    writeln!(out, "{binner}__pos = __nss{id} + 4u + __nl{id};").map_err(fmt_err)?;
3235                    writeln!(out, "{binner}__seq.push_back(std::move({var}));").map_err(fmt_err)?;
3236                }
3237                // nested sequence element: read the inner sequence into a temp
3238                // via the assignment-setter form, then push it.
3239                TypeSpec::Sequence(_) => {
3240                    let inner_ty = typespec_to_cpp(&seq.elem)?;
3241                    let var = format!("__se{}", next_nest_id());
3242                    let binner = format!("{indent}        ");
3243                    writeln!(out, "{binner}{inner_ty} {var}{{}};").map_err(fmt_err)?;
3244                    emit_value_read(out, &seq.elem, &format!("{var} ="), origin, &binner, false)?;
3245                    writeln!(out, "{binner}__seq.push_back(std::move({var}));").map_err(fmt_err)?;
3246                }
3247                // map element: read the inner map into a temp, then push it.
3248                TypeSpec::Map(_) => {
3249                    let inner_ty = typespec_to_cpp(&seq.elem)?;
3250                    let var = format!("__se{}", next_nest_id());
3251                    let binner = format!("{indent}        ");
3252                    writeln!(out, "{binner}{inner_ty} {var}{{}};").map_err(fmt_err)?;
3253                    emit_value_read(out, &seq.elem, &format!("{var} ="), origin, &binner, false)?;
3254                    writeln!(out, "{binner}__seq.push_back(std::move({var}));").map_err(fmt_err)?;
3255                }
3256                _ => {}
3257            }
3258            writeln!(out, "{indent}    }}").map_err(fmt_err)?;
3259            writeln!(out, "{indent}    {setter}(std::move(__seq));").map_err(fmt_err)?;
3260            writeln!(out, "{indent}}}").map_err(fmt_err)?;
3261        }
3262        // map<K,V> member: read DHEADER, count, then count interleaved key/value
3263        // pairs (symmetric to the encode); insert into a std::map. Key/value are
3264        // read into fresh temps via the assignment-setter form `__k =(...)`
3265        // (emit_value_read always ends in `{setter}(final)`, so `__k =` yields a
3266        // plain assignment that works for primitive/string/enum/struct values).
3267        TypeSpec::Map(m) => {
3268            let k_ty = typespec_to_cpp(&m.key)?;
3269            let v_ty = typespec_to_cpp(&m.value)?;
3270            let id = next_nest_id();
3271            let mapv = format!("__map{id}");
3272            let kv = format!("__mk{id}");
3273            let vv = format!("__mv{id}");
3274            let inner = format!("{indent}    ");
3275            let li = format!("{inner}    ");
3276            writeln!(out, "{indent}{{").map_err(fmt_err)?;
3277            writeln!(out, "{inner}const auto __map_dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len); (void)__map_dh;").map_err(fmt_err)?;
3278            writeln!(out, "{inner}auto __mcnt = ::dds::topic::xcdr2::read_le_origin<uint32_t>(__buf, __pos, __len, {origin}, __max_align);").map_err(fmt_err)?;
3279            writeln!(out, "{inner}std::map<{k_ty}, {v_ty}> {mapv};").map_err(fmt_err)?;
3280            writeln!(out, "{inner}for (uint32_t __i = 0; __i < __mcnt; ++__i) {{")
3281                .map_err(fmt_err)?;
3282            writeln!(out, "{li}{k_ty} {kv}{{}};").map_err(fmt_err)?;
3283            writeln!(out, "{li}{v_ty} {vv}{{}};").map_err(fmt_err)?;
3284            emit_value_read(out, &m.key, &format!("{kv} ="), origin, &li, false)?;
3285            emit_value_read(out, &m.value, &format!("{vv} ="), origin, &li, false)?;
3286            writeln!(out, "{li}{mapv}.emplace(std::move({kv}), std::move({vv}));")
3287                .map_err(fmt_err)?;
3288            writeln!(out, "{inner}}}").map_err(fmt_err)?;
3289            writeln!(out, "{inner}{setter}(std::move({mapv}));").map_err(fmt_err)?;
3290            writeln!(out, "{indent}}}").map_err(fmt_err)?;
3291        }
3292        // enum member: read its int32 underlying value, cast back to the enum.
3293        TypeSpec::Scoped(s) if scoped_is_enum(s) => {
3294            let cpp_ty = scoped_to_cpp(s);
3295            writeln!(
3296                out,
3297                "{indent}{setter}(static_cast<{cpp_ty}>(::dds::topic::xcdr2::read_le_origin<int32_t>(__buf, __pos, __len, {origin}, __max_align)));"
3298            )
3299            .map_err(fmt_err)?;
3300        }
3301        // nested struct member. @final: read each sub-member into a fresh temp
3302        // (symmetric to the inline encode). @appendable/@mutable: read the
3303        // nested DHEADER length, then sub-decode the [DHEADER+body] slice via the
3304        // nested type's own `decode` and advance the cursor past it.
3305        TypeSpec::Scoped(sc) if scoped_struct(sc).is_some() => {
3306            let Some((def, ext)) = scoped_struct(sc) else {
3307                return Ok(());
3308            };
3309            let cpp_ty = scoped_to_cpp(sc);
3310            let id = next_nest_id();
3311            let var = format!("__ns{id}");
3312            let inner = format!("{indent}    ");
3313            writeln!(out, "{indent}{{").map_err(fmt_err)?;
3314            writeln!(out, "{inner}{cpp_ty} {var}{{}};").map_err(fmt_err)?;
3315            match ext {
3316                Extensibility::Final => {
3317                    for sm in &def.members {
3318                        let sm_name = &sm.declarators[0].name().text;
3319                        emit_value_read(
3320                            out,
3321                            &sm.type_spec,
3322                            &format!("{var}.{sm_name}"),
3323                            origin,
3324                            &inner,
3325                            false,
3326                        )?;
3327                    }
3328                }
3329                Extensibility::Appendable | Extensibility::Mutable => {
3330                    writeln!(
3331                        out,
3332                        "{inner}::dds::topic::xcdr2::skip_pad_from_origin(__pos, {origin}, 4);"
3333                    )
3334                    .map_err(fmt_err)?;
3335                    writeln!(out, "{inner}const size_t __nss{id} = __pos;").map_err(fmt_err)?;
3336                    writeln!(out, "{inner}size_t __npk{id} = __pos;").map_err(fmt_err)?;
3337                    writeln!(
3338                        out,
3339                        "{inner}const uint32_t __nl{id} = ::dds::topic::xcdr2::dheader_read(__buf, __npk{id}, __len);"
3340                    )
3341                    .map_err(fmt_err)?;
3342                    writeln!(
3343                        out,
3344                        "{inner}{var} = ::dds::topic::topic_type_support<{cpp_ty}>::decode(__buf + __nss{id}, 4u + __nl{id}, __repr);"
3345                    )
3346                    .map_err(fmt_err)?;
3347                    writeln!(out, "{inner}__pos = __nss{id} + 4u + __nl{id};").map_err(fmt_err)?;
3348                }
3349            }
3350            writeln!(out, "{inner}{setter}({var});").map_err(fmt_err)?;
3351            writeln!(out, "{indent}}}").map_err(fmt_err)?;
3352        }
3353        _ => {}
3354    }
3355    Ok(())
3356}
3357
3358fn emit_mutable_member_decode_case(out: &mut String, m: &Member) -> Result<(), CppGenError> {
3359    if !member_codegen_supported(m) {
3360        return Ok(());
3361    }
3362    let id_override = find_uint_annotation(&m.annotations, "id");
3363    let is_optional = has_optional_annotation(&m.annotations);
3364    let _ = is_optional; // mutable optional: same path; absent member just skips this case.
3365    for decl in &m.declarators {
3366        let name = &decl.name().text;
3367        if !matches!(decl, Declarator::Simple(_)) {
3368            continue;
3369        }
3370        if !typespec_supported(&m.type_spec) {
3371            continue;
3372        }
3373        let id_expr = match id_override {
3374            Some(id) => id.to_string(),
3375            None => format!("0x{:x}u", auto_id_for(name)),
3376        };
3377        writeln!(out, "                case {id_expr}: {{").map_err(fmt_err)?;
3378        match &m.type_spec {
3379            TypeSpec::Primitive(PrimitiveType::Boolean) => {
3380                writeln!(out, "                    uint8_t __b = ::dds::topic::xcdr2::read_u8(__buf, __pos, __len);").map_err(fmt_err)?;
3381                if has_optional_annotation(&m.annotations) {
3382                    writeln!(
3383                        out,
3384                        "                    __v.{name}(static_cast<bool>(__b));"
3385                    )
3386                    .map_err(fmt_err)?;
3387                } else {
3388                    writeln!(
3389                        out,
3390                        "                    __v.{name}(static_cast<bool>(__b));"
3391                    )
3392                    .map_err(fmt_err)?;
3393                }
3394            }
3395            TypeSpec::Primitive(PrimitiveType::Octet) => {
3396                writeln!(out, "                    __v.{name}(::dds::topic::xcdr2::read_u8(__buf, __pos, __len));").map_err(fmt_err)?;
3397            }
3398            TypeSpec::Primitive(p) => {
3399                let cpp_ty = primitive_to_cpp(*p);
3400                writeln!(out, "                    __v.{name}(::dds::topic::xcdr2::read_le_raw<{cpp_ty}>(__buf, __pos, __len));").map_err(fmt_err)?;
3401            }
3402            TypeSpec::String(s) if !s.wide => {
3403                writeln!(out, "                    auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len);").map_err(fmt_err)?;
3404                writeln!(out, "                    (void)__n;").map_err(fmt_err)?;
3405                writeln!(out, "                    auto __body_origin = __pos;")
3406                    .map_err(fmt_err)?;
3407                writeln!(out, "                    __v.{name}(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, __body_origin, __max_align));").map_err(fmt_err)?;
3408            }
3409            TypeSpec::String(s) if s.wide => {
3410                writeln!(out, "                    auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len);").map_err(fmt_err)?;
3411                writeln!(out, "                    (void)__n;").map_err(fmt_err)?;
3412                writeln!(out, "                    auto __body_origin = __pos;")
3413                    .map_err(fmt_err)?;
3414                writeln!(out, "                    __v.{name}(::dds::topic::xcdr2::read_wstring_origin(__buf, __pos, __len, __body_origin, __max_align));").map_err(fmt_err)?;
3415            }
3416            TypeSpec::Sequence(seq) => {
3417                writeln!(out, "                    auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len);").map_err(fmt_err)?;
3418                writeln!(out, "                    (void)__n;").map_err(fmt_err)?;
3419                writeln!(out, "                    auto __body_origin = __pos;")
3420                    .map_err(fmt_err)?;
3421                // Non-primitive-element sequence carries an inner DHEADER inside
3422                // the NEXTINT frame (symmetric to the encode; Finding 6).
3423                if !matches!(&*seq.elem, TypeSpec::Primitive(_)) {
3424                    writeln!(out, "                    {{ const auto __seq_dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len); (void)__seq_dh; }}").map_err(fmt_err)?;
3425                }
3426                writeln!(out, "                    auto __cnt = ::dds::topic::xcdr2::read_le_origin<uint32_t>(__buf, __pos, __len, __body_origin, __max_align);").map_err(fmt_err)?;
3427                if matches!(&*seq.elem, TypeSpec::Primitive(PrimitiveType::Octet)) {
3428                    // sequence<octet>: raw byte block directly from the buffer.
3429                    writeln!(
3430                        out,
3431                        "                    ::dds::topic::xcdr2::check_avail(__pos, __cnt, __len);"
3432                    )
3433                    .map_err(fmt_err)?;
3434                    writeln!(out, "                    std::vector<uint8_t> __seq(__buf + __pos, __buf + __pos + __cnt);").map_err(fmt_err)?;
3435                    writeln!(out, "                    __pos += __cnt;").map_err(fmt_err)?;
3436                    writeln!(out, "                    __v.{name}(std::move(__seq));")
3437                        .map_err(fmt_err)?;
3438                } else {
3439                    let elem_cpp_ty: String = match &*seq.elem {
3440                        TypeSpec::Primitive(PrimitiveType::Boolean) => "bool".to_string(),
3441                        TypeSpec::Primitive(p) => primitive_to_cpp(*p).to_string(),
3442                        TypeSpec::String(s) if !s.wide => "std::string".to_string(),
3443                        TypeSpec::String(_) => "std::wstring".to_string(),
3444                        TypeSpec::Scoped(s) if scoped_is_enum(s) => scoped_to_cpp(s),
3445                        TypeSpec::Scoped(s) if scoped_struct(s).is_some() => scoped_to_cpp(s),
3446                        TypeSpec::Sequence(_) | TypeSpec::Map(_) => typespec_to_cpp(&seq.elem)?,
3447                        _ => "uint8_t".to_string(),
3448                    };
3449                    writeln!(out, "                    std::vector<{elem_cpp_ty}> __seq;")
3450                        .map_err(fmt_err)?;
3451                    writeln!(out, "                    __seq.reserve(__cnt);").map_err(fmt_err)?;
3452                    writeln!(
3453                        out,
3454                        "                    for (uint32_t __i = 0; __i < __cnt; ++__i) {{"
3455                    )
3456                    .map_err(fmt_err)?;
3457                    match &*seq.elem {
3458                        TypeSpec::Primitive(PrimitiveType::Boolean) => {
3459                            writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_bool(__buf, __pos, __len));").map_err(fmt_err)?;
3460                        }
3461                        TypeSpec::Primitive(p) => {
3462                            let cpp_ty = primitive_to_cpp(*p);
3463                            writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_le_origin<{cpp_ty}>(__buf, __pos, __len, __body_origin, __max_align));").map_err(fmt_err)?;
3464                        }
3465                        TypeSpec::String(s) if !s.wide => {
3466                            writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_string_origin(__buf, __pos, __len, __body_origin, __max_align));").map_err(fmt_err)?;
3467                        }
3468                        TypeSpec::String(_) => {
3469                            writeln!(out, "                        __seq.push_back(::dds::topic::xcdr2::read_wstring_origin(__buf, __pos, __len, __body_origin, __max_align));").map_err(fmt_err)?;
3470                        }
3471                        TypeSpec::Scoped(s) if scoped_is_enum(s) => {
3472                            let cpp_ty = scoped_to_cpp(s);
3473                            writeln!(out, "                        __seq.push_back(static_cast<{cpp_ty}>(::dds::topic::xcdr2::read_le_origin<int32_t>(__buf, __pos, __len, __body_origin, __max_align)));").map_err(fmt_err)?;
3474                        }
3475                        TypeSpec::Scoped(sc) if scoped_final_struct(sc).is_some() => {
3476                            if let Some(def) = scoped_final_struct(sc) {
3477                                let cpp_ty = scoped_to_cpp(sc);
3478                                let var = format!("__se{}", next_nest_id());
3479                                writeln!(out, "                        {cpp_ty} {var}{{}};")
3480                                    .map_err(fmt_err)?;
3481                                for sm in &def.members {
3482                                    let sm_name = &sm.declarators[0].name().text;
3483                                    emit_value_read(
3484                                        out,
3485                                        &sm.type_spec,
3486                                        &format!("{var}.{sm_name}"),
3487                                        "__body_origin",
3488                                        "                        ",
3489                                        false,
3490                                    )?;
3491                                }
3492                                writeln!(out, "                        __seq.push_back({var});")
3493                                    .map_err(fmt_err)?;
3494                            }
3495                        }
3496                        // nested @appendable/@mutable struct element: 4-align,
3497                        // read the element DHEADER, sub-decode the [DHEADER+body]
3498                        // slice via the nested type's `decode`, advance, push.
3499                        TypeSpec::Scoped(sc) if scoped_struct(sc).is_some() => {
3500                            let cpp_ty = scoped_to_cpp(sc);
3501                            let id = next_nest_id();
3502                            let var = format!("__se{id}");
3503                            writeln!(out, "                        ::dds::topic::xcdr2::skip_pad_from_origin(__pos, __body_origin, 4);").map_err(fmt_err)?;
3504                            writeln!(
3505                                out,
3506                                "                        const size_t __nss{id} = __pos;"
3507                            )
3508                            .map_err(fmt_err)?;
3509                            writeln!(out, "                        size_t __npk{id} = __pos;")
3510                                .map_err(fmt_err)?;
3511                            writeln!(out, "                        const uint32_t __nl{id} = ::dds::topic::xcdr2::dheader_read(__buf, __npk{id}, __len);").map_err(fmt_err)?;
3512                            writeln!(out, "                        {cpp_ty} {var} = ::dds::topic::topic_type_support<{cpp_ty}>::decode(__buf + __nss{id}, 4u + __nl{id}, __repr);").map_err(fmt_err)?;
3513                            writeln!(
3514                                out,
3515                                "                        __pos = __nss{id} + 4u + __nl{id};"
3516                            )
3517                            .map_err(fmt_err)?;
3518                            writeln!(
3519                                out,
3520                                "                        __seq.push_back(std::move({var}));"
3521                            )
3522                            .map_err(fmt_err)?;
3523                        }
3524                        // nested sequence / map element: read into a temp, push.
3525                        TypeSpec::Sequence(_) | TypeSpec::Map(_) => {
3526                            let inner_ty = typespec_to_cpp(&seq.elem)?;
3527                            let var = format!("__se{}", next_nest_id());
3528                            writeln!(out, "                        {inner_ty} {var}{{}};")
3529                                .map_err(fmt_err)?;
3530                            emit_value_read(
3531                                out,
3532                                &seq.elem,
3533                                &format!("{var} ="),
3534                                "__body_origin",
3535                                "                        ",
3536                                false,
3537                            )?;
3538                            writeln!(
3539                                out,
3540                                "                        __seq.push_back(std::move({var}));"
3541                            )
3542                            .map_err(fmt_err)?;
3543                        }
3544                        _ => {}
3545                    }
3546                    writeln!(out, "                    }}").map_err(fmt_err)?;
3547                    writeln!(out, "                    __v.{name}(std::move(__seq));")
3548                        .map_err(fmt_err)?;
3549                }
3550            }
3551            // enum member: 4-byte int32 read directly (encoded via compact LC=2).
3552            TypeSpec::Scoped(s) if scoped_is_enum(s) => {
3553                let cpp_ty = scoped_to_cpp(s);
3554                writeln!(out, "                    __v.{name}(static_cast<{cpp_ty}>(::dds::topic::xcdr2::read_le_raw<int32_t>(__buf, __pos, __len)));").map_err(fmt_err)?;
3555            }
3556            // nested struct member: skip NEXTINT to the EMHEADER body-origin.
3557            // @final: read inline members. @appendable/@mutable: sub-decode the
3558            // nested type from its own DHEADER inside the body.
3559            TypeSpec::Scoped(sc) if scoped_struct(sc).is_some() => {
3560                if let Some((def, ext)) = scoped_struct(sc) {
3561                    let cpp_ty = scoped_to_cpp(sc);
3562                    let id = next_nest_id();
3563                    let var = format!("__ns{id}");
3564                    writeln!(out, "                    auto __n = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len); (void)__n;").map_err(fmt_err)?;
3565                    writeln!(
3566                        out,
3567                        "                    auto __body_origin = __pos; (void)__body_origin;"
3568                    )
3569                    .map_err(fmt_err)?;
3570                    writeln!(out, "                    {cpp_ty} {var}{{}};").map_err(fmt_err)?;
3571                    match ext {
3572                        Extensibility::Final => {
3573                            for sm in &def.members {
3574                                let sm_name = &sm.declarators[0].name().text;
3575                                emit_value_read(
3576                                    out,
3577                                    &sm.type_spec,
3578                                    &format!("{var}.{sm_name}"),
3579                                    "__body_origin",
3580                                    "                    ",
3581                                    false,
3582                                )?;
3583                            }
3584                        }
3585                        Extensibility::Appendable | Extensibility::Mutable => {
3586                            writeln!(out, "                    const size_t __nss{id} = __pos;")
3587                                .map_err(fmt_err)?;
3588                            writeln!(out, "                    size_t __npk{id} = __pos;")
3589                                .map_err(fmt_err)?;
3590                            writeln!(out, "                    const uint32_t __nl{id} = ::dds::topic::xcdr2::dheader_read(__buf, __npk{id}, __len);").map_err(fmt_err)?;
3591                            writeln!(out, "                    {var} = ::dds::topic::topic_type_support<{cpp_ty}>::decode(__buf + __nss{id}, 4u + __nl{id}, __repr);").map_err(fmt_err)?;
3592                            writeln!(
3593                                out,
3594                                "                    __pos = __nss{id} + 4u + __nl{id};"
3595                            )
3596                            .map_err(fmt_err)?;
3597                        }
3598                    }
3599                    writeln!(out, "                    __v.{name}({var});").map_err(fmt_err)?;
3600                }
3601            }
3602            // map<K,V> member: skip NEXTINT, read count + interleaved entries
3603            // relative to the body-origin (symmetric to the mutable encode).
3604            TypeSpec::Map(m) => {
3605                let k_ty = typespec_to_cpp(&m.key)?;
3606                let v_ty = typespec_to_cpp(&m.value)?;
3607                let id = next_nest_id();
3608                let mapv = format!("__map{id}");
3609                let kv = format!("__mk{id}");
3610                let vv = format!("__mv{id}");
3611                writeln!(out, "                    auto __mn = ::dds::topic::xcdr2::emheader_nextint_read(__buf, __pos, __len); (void)__mn;").map_err(fmt_err)?;
3612                writeln!(out, "                    auto __body_origin = __pos;")
3613                    .map_err(fmt_err)?;
3614                // map is non-primitive -> inner DHEADER inside the NEXTINT frame.
3615                writeln!(out, "                    {{ const auto __map_dh = ::dds::topic::xcdr2::dheader_read(__buf, __pos, __len); (void)__map_dh; }}").map_err(fmt_err)?;
3616                writeln!(out, "                    auto __mcnt = ::dds::topic::xcdr2::read_le_origin<uint32_t>(__buf, __pos, __len, __body_origin, __max_align);").map_err(fmt_err)?;
3617                writeln!(out, "                    std::map<{k_ty}, {v_ty}> {mapv};")
3618                    .map_err(fmt_err)?;
3619                writeln!(
3620                    out,
3621                    "                    for (uint32_t __i = 0; __i < __mcnt; ++__i) {{"
3622                )
3623                .map_err(fmt_err)?;
3624                writeln!(out, "                        {k_ty} {kv}{{}};").map_err(fmt_err)?;
3625                writeln!(out, "                        {v_ty} {vv}{{}};").map_err(fmt_err)?;
3626                emit_value_read(
3627                    out,
3628                    &m.key,
3629                    &format!("{kv} ="),
3630                    "__body_origin",
3631                    "                        ",
3632                    false,
3633                )?;
3634                emit_value_read(
3635                    out,
3636                    &m.value,
3637                    &format!("{vv} ="),
3638                    "__body_origin",
3639                    "                        ",
3640                    false,
3641                )?;
3642                writeln!(
3643                    out,
3644                    "                        {mapv}.emplace(std::move({kv}), std::move({vv}));"
3645                )
3646                .map_err(fmt_err)?;
3647                writeln!(out, "                    }}").map_err(fmt_err)?;
3648                writeln!(out, "                    __v.{name}(std::move({mapv}));")
3649                    .map_err(fmt_err)?;
3650            }
3651            _ => {}
3652        }
3653        writeln!(out, "                    break;").map_err(fmt_err)?;
3654        writeln!(out, "                }}").map_err(fmt_err)?;
3655    }
3656    Ok(())
3657}
3658
3659fn emit_key_hash_fn(
3660    out: &mut String,
3661    cpp_fqn: &str,
3662    s: &StructDef,
3663    is_keyed: bool,
3664) -> Result<(), CppGenError> {
3665    writeln!(
3666        out,
3667        "    static std::array<uint8_t, 16> key_hash(const {cpp_fqn}& __v) {{"
3668    )
3669    .map_err(fmt_err)?;
3670    writeln!(out, "        (void)__v;").map_err(fmt_err)?;
3671    if !is_keyed {
3672        writeln!(
3673            out,
3674            "        return std::array<uint8_t, 16>{{{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}};"
3675        )
3676        .map_err(fmt_err)?;
3677        writeln!(out, "    }}").map_err(fmt_err)?;
3678        return Ok(());
3679    }
3680    writeln!(out, "        std::vector<uint8_t> __out;").map_err(fmt_err)?;
3681    writeln!(out, "        const size_t __origin = 0;").map_err(fmt_err)?;
3682    writeln!(out, "        (void)__origin;").map_err(fmt_err)?;
3683    for m in &s.members {
3684        if !has_key_annotation(&m.annotations) {
3685            continue;
3686        }
3687        emit_plain_member_encode(out, m, "be", "__origin")?;
3688    }
3689    // XTypes 1.3 §7.6.8.4: holder ≤ 16 octets -> zero-pad; otherwise MD5.
3690    writeln!(out, "        std::array<uint8_t, 16> __h{{}};").map_err(fmt_err)?;
3691    writeln!(out, "        if (__out.size() <= 16) {{").map_err(fmt_err)?;
3692    writeln!(
3693        out,
3694        "            std::memcpy(__h.data(), __out.data(), __out.size());"
3695    )
3696    .map_err(fmt_err)?;
3697    writeln!(out, "            return __h;").map_err(fmt_err)?;
3698    writeln!(out, "        }}").map_err(fmt_err)?;
3699    writeln!(out, "        return ::dds::topic::xcdr2_md5::md5(__out);").map_err(fmt_err)?;
3700    writeln!(out, "    }}").map_err(fmt_err)?;
3701    Ok(())
3702}
3703
3704// ---------------------------------------------------------------------------
3705// Helpers: fmt-Error-Bridge
3706// ---------------------------------------------------------------------------
3707
3708fn fmt_err(_: core::fmt::Error) -> CppGenError {
3709    CppGenError::Internal("string formatting failed".into())
3710}
3711
3712#[allow(dead_code)]
3713fn _ensure_used() {
3714    // is_reserved is used by check_identifier — a compiler hint.
3715    let _ = is_reserved("int");
3716}