Skip to main content

zerodds_idl_rust/
emitter.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Top-Level-Emitter: AST → Rust-String.
4
5use zerodds_idl::ast::types::{
6    ConstrTypeDecl, Declarator, Definition, ExceptDecl, ModuleDef, Specification, TypeDecl,
7};
8
9use crate::error::Result;
10use crate::struct_emit::emit_struct;
11use crate::type_map::{escape_keyword, rust_type_for};
12
13/// Optionen fuer den Codegen.
14#[derive(Debug, Clone, Default)]
15pub struct RustGenOptions {
16    /// Header-Kommentar oben in der erzeugten Datei. Wird verbatim
17    /// uebernommen.
18    pub header_comment: Option<String>,
19}
20
21/// Erzeugt aus einer geparsten IDL-Spec ein einzelnes Rust-Modul-File.
22///
23/// # Errors
24/// `Unsupported` fuer IDL-Konstrukte ausserhalb des DDS-DataType-Scopes.
25pub fn generate_rust_module(spec: &Specification, opts: &RustGenOptions) -> Result<String> {
26    let mut out = String::new();
27    out.push_str("// SPDX-License-Identifier: Apache-2.0\n");
28    if let Some(c) = &opts.header_comment {
29        for line in c.lines() {
30            out.push_str("// ");
31            out.push_str(line);
32            out.push('\n');
33        }
34    }
35    out.push_str("// Auto-generated by `zerodds-idl-rust`. Do not edit by hand.\n");
36    out.push('\n');
37    out.push_str("#![allow(clippy::too_many_lines)]\n");
38    out.push_str("#![allow(unused_imports)]\n");
39    out.push('\n');
40    out.push_str("use zerodds_cdr::{BufferReader, BufferWriter, CdrDecode, CdrEncode, DecodeError, EncodeError, Endianness};\n");
41    out.push('\n');
42
43    let mut path: Vec<String> = Vec::new();
44    for def in &spec.definitions {
45        emit_definition(&mut out, def, &mut path)?;
46    }
47
48    Ok(out)
49}
50
51/// zerodds-lint: recursion-depth 16
52/// IDL-Modul-Schachtelung — 16 Ebenen reichen fuer alle bekannten OMG-
53/// Spec-IDL-Files.
54fn emit_definition(out: &mut String, def: &Definition, path: &mut Vec<String>) -> Result<()> {
55    match def {
56        Definition::Module(m) => emit_module(out, m, path),
57        Definition::Type(t) => emit_type_decl(out, t, path),
58        Definition::Except(e) => {
59            emit_exception(out, e)?;
60            out.push('\n');
61            Ok(())
62        }
63        // Const wird im idl-rust-Pfad nicht emittiert (Compile-Time-
64        // Konstanten sind code-sprachenspezifisch; CORBA-IDL-Const wird
65        // im Code-Sprach-PSM behandelt).
66        Definition::Const(_) => Ok(()),
67        // CORBA-/Component-Decls + alles andere wird im Rust-Codegen
68        // nicht behandelt (ZeroDDS Rust-API ist DDS-zentriert).
69        _ => Ok(()),
70    }
71}
72
73/// zerodds-lint: recursion-depth 16
74fn emit_module(out: &mut String, m: &ModuleDef, path: &mut Vec<String>) -> Result<()> {
75    out.push_str("pub mod ");
76    out.push_str(&escape_keyword(&m.name.text));
77    out.push_str(" {\n");
78    out.push_str("    use zerodds_cdr::{BufferReader, BufferWriter, CdrDecode, CdrEncode, DecodeError, EncodeError, Endianness};\n\n");
79    // Push the raw IDL module name (not Rust-escaped) so TYPE_NAME
80    // emits spec-conform `Module::Sub::Struct`. Conformance §3 / §5.
81    path.push(m.name.text.clone());
82    for inner in &m.definitions {
83        let mut inner_out = String::new();
84        emit_definition(&mut inner_out, inner, path)?;
85        // Indent
86        for line in inner_out.lines() {
87            if line.is_empty() {
88                out.push('\n');
89            } else {
90                out.push_str("    ");
91                out.push_str(line);
92                out.push('\n');
93            }
94        }
95    }
96    path.pop();
97    out.push_str("}\n\n");
98    Ok(())
99}
100
101fn emit_type_decl(out: &mut String, td: &TypeDecl, path: &[String]) -> Result<()> {
102    match td {
103        TypeDecl::Constr(ConstrTypeDecl::Struct(struct_dcl)) => {
104            // StructDcl ist ein Wrapper um Def/Forward.
105            use zerodds_idl::ast::types::StructDcl;
106            match struct_dcl {
107                StructDcl::Def(s) => {
108                    emit_struct(out, s, path)?;
109                    out.push('\n');
110                }
111                StructDcl::Forward(_) => {
112                    // Forward-Declarations werden in Rust nicht benoetigt
113                    // (Order ist via Visibility/Module geregelt).
114                }
115            }
116        }
117        TypeDecl::Constr(ConstrTypeDecl::Enum(e)) => {
118            crate::enum_emit::emit_enum(out, e)?;
119            out.push('\n');
120        }
121        TypeDecl::Constr(ConstrTypeDecl::Union(u)) => {
122            use zerodds_idl::ast::types::UnionDcl;
123            match u {
124                UnionDcl::Def(def) => {
125                    crate::union_emit::emit_union(out, def)?;
126                    out.push('\n');
127                }
128                UnionDcl::Forward(_) => {}
129            }
130        }
131        TypeDecl::Typedef(td) => {
132            crate::typedef_emit::emit_typedef(out, td)?;
133        }
134        TypeDecl::Constr(ConstrTypeDecl::Bitset(b)) => {
135            crate::bitset_emit::emit_bitset(out, b)?;
136            out.push('\n');
137        }
138        TypeDecl::Constr(ConstrTypeDecl::Bitmask(m)) => {
139            crate::bitset_emit::emit_bitmask(out, m)?;
140            out.push('\n');
141        }
142    }
143    Ok(())
144}
145
146/// Emittiert eine IDL-`exception`-Definition als Rust-Struct.
147/// CORBA-Exceptions sind struct-aehnliche Daten-Container, die als
148/// Error-Variant im CORBA-Interface-Error-Enum referenziert werden
149/// (`zerodds-corba-rust` emittiert das Enum). Hier wird nur der Struct-
150/// Body generiert; volle CDR-Marshalling-Impl ist in Phase 2
151/// (DDS-Topic-Pipeline ist nicht betroffen, da Exceptions nicht als
152/// DDS-Sample uebertragen werden).
153fn emit_exception(out: &mut String, e: &ExceptDecl) -> Result<()> {
154    out.push_str("/// Generated by `zerodds-idl-rust` from IDL exception (CORBA 3.3 §7.4.10).\n");
155    out.push_str("#[derive(Debug, Clone, PartialEq, Default)]\n");
156    out.push_str("pub struct ");
157    out.push_str(&escape_keyword(&e.name.text));
158    out.push_str(" {\n");
159    for member in &e.members {
160        let rust_ty = rust_type_for(&member.type_spec)?;
161        for declarator in &member.declarators {
162            let raw = match declarator {
163                Declarator::Simple(n) => &n.text,
164                Declarator::Array(a) => &a.name.text,
165            };
166            let name = escape_keyword(raw);
167            out.push_str(&format!("    pub {name}: {rust_ty},\n"));
168        }
169    }
170    out.push_str("}\n");
171    Ok(())
172}