zerodds-idl-rust 1.0.0-rc.1

IDL4 → Rust code generator for ZeroDDS DataTypes (impl DdsType from zerodds-dcps).
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 ZeroDDS Contributors
//! Top-Level-Emitter: AST → Rust-String.

use zerodds_idl::ast::types::{
    ConstrTypeDecl, Declarator, Definition, ExceptDecl, ModuleDef, Specification, TypeDecl,
};

use crate::error::Result;
use crate::struct_emit::emit_struct;
use crate::type_map::{escape_keyword, rust_type_for};

/// Optionen fuer den Codegen.
#[derive(Debug, Clone, Default)]
pub struct RustGenOptions {
    /// Header-Kommentar oben in der erzeugten Datei. Wird verbatim
    /// uebernommen.
    pub header_comment: Option<String>,
}

/// Erzeugt aus einer geparsten IDL-Spec ein einzelnes Rust-Modul-File.
///
/// # Errors
/// `Unsupported` fuer IDL-Konstrukte ausserhalb des DDS-DataType-Scopes.
pub fn generate_rust_module(spec: &Specification, opts: &RustGenOptions) -> Result<String> {
    let mut out = String::new();
    out.push_str("// SPDX-License-Identifier: Apache-2.0\n");
    if let Some(c) = &opts.header_comment {
        for line in c.lines() {
            out.push_str("// ");
            out.push_str(line);
            out.push('\n');
        }
    }
    out.push_str("// Auto-generated by `zerodds-idl-rust`. Do not edit by hand.\n");
    out.push('\n');
    out.push_str("#![allow(clippy::too_many_lines)]\n");
    out.push_str("#![allow(unused_imports)]\n");
    out.push('\n');
    out.push_str("use zerodds_cdr::{BufferReader, BufferWriter, CdrDecode, CdrEncode, DecodeError, EncodeError, Endianness};\n");
    out.push('\n');

    let mut path: Vec<String> = Vec::new();
    for def in &spec.definitions {
        emit_definition(&mut out, def, &mut path)?;
    }

    Ok(out)
}

/// zerodds-lint: recursion-depth 16
/// IDL-Modul-Schachtelung — 16 Ebenen reichen fuer alle bekannten OMG-
/// Spec-IDL-Files.
fn emit_definition(out: &mut String, def: &Definition, path: &mut Vec<String>) -> Result<()> {
    match def {
        Definition::Module(m) => emit_module(out, m, path),
        Definition::Type(t) => emit_type_decl(out, t, path),
        Definition::Except(e) => {
            emit_exception(out, e)?;
            out.push('\n');
            Ok(())
        }
        // Const wird im idl-rust-Pfad nicht emittiert (Compile-Time-
        // Konstanten sind code-sprachenspezifisch; CORBA-IDL-Const wird
        // im Code-Sprach-PSM behandelt).
        Definition::Const(_) => Ok(()),
        // CORBA-/Component-Decls + alles andere wird im Rust-Codegen
        // nicht behandelt (ZeroDDS Rust-API ist DDS-zentriert).
        _ => Ok(()),
    }
}

/// zerodds-lint: recursion-depth 16
fn emit_module(out: &mut String, m: &ModuleDef, path: &mut Vec<String>) -> Result<()> {
    out.push_str("pub mod ");
    out.push_str(&escape_keyword(&m.name.text));
    out.push_str(" {\n");
    out.push_str("    use zerodds_cdr::{BufferReader, BufferWriter, CdrDecode, CdrEncode, DecodeError, EncodeError, Endianness};\n\n");
    // Push the raw IDL module name (not Rust-escaped) so TYPE_NAME
    // emits spec-conform `Module::Sub::Struct`. Conformance §3 / §5.
    path.push(m.name.text.clone());
    for inner in &m.definitions {
        let mut inner_out = String::new();
        emit_definition(&mut inner_out, inner, path)?;
        // Indent
        for line in inner_out.lines() {
            if line.is_empty() {
                out.push('\n');
            } else {
                out.push_str("    ");
                out.push_str(line);
                out.push('\n');
            }
        }
    }
    path.pop();
    out.push_str("}\n\n");
    Ok(())
}

fn emit_type_decl(out: &mut String, td: &TypeDecl, path: &[String]) -> Result<()> {
    match td {
        TypeDecl::Constr(ConstrTypeDecl::Struct(struct_dcl)) => {
            // StructDcl ist ein Wrapper um Def/Forward.
            use zerodds_idl::ast::types::StructDcl;
            match struct_dcl {
                StructDcl::Def(s) => {
                    emit_struct(out, s, path)?;
                    out.push('\n');
                }
                StructDcl::Forward(_) => {
                    // Forward-Declarations werden in Rust nicht benoetigt
                    // (Order ist via Visibility/Module geregelt).
                }
            }
        }
        TypeDecl::Constr(ConstrTypeDecl::Enum(e)) => {
            crate::enum_emit::emit_enum(out, e)?;
            out.push('\n');
        }
        TypeDecl::Constr(ConstrTypeDecl::Union(u)) => {
            use zerodds_idl::ast::types::UnionDcl;
            match u {
                UnionDcl::Def(def) => {
                    crate::union_emit::emit_union(out, def)?;
                    out.push('\n');
                }
                UnionDcl::Forward(_) => {}
            }
        }
        TypeDecl::Typedef(td) => {
            crate::typedef_emit::emit_typedef(out, td)?;
        }
        TypeDecl::Constr(ConstrTypeDecl::Bitset(b)) => {
            crate::bitset_emit::emit_bitset(out, b)?;
            out.push('\n');
        }
        TypeDecl::Constr(ConstrTypeDecl::Bitmask(m)) => {
            crate::bitset_emit::emit_bitmask(out, m)?;
            out.push('\n');
        }
    }
    Ok(())
}

/// Emittiert eine IDL-`exception`-Definition als Rust-Struct.
/// CORBA-Exceptions sind struct-aehnliche Daten-Container, die als
/// Error-Variant im CORBA-Interface-Error-Enum referenziert werden
/// (`zerodds-corba-rust` emittiert das Enum). Hier wird nur der Struct-
/// Body generiert; volle CDR-Marshalling-Impl ist in Phase 2
/// (DDS-Topic-Pipeline ist nicht betroffen, da Exceptions nicht als
/// DDS-Sample uebertragen werden).
fn emit_exception(out: &mut String, e: &ExceptDecl) -> Result<()> {
    out.push_str("/// Generated by `zerodds-idl-rust` from IDL exception (CORBA 3.3 §7.4.10).\n");
    out.push_str("#[derive(Debug, Clone, PartialEq, Default)]\n");
    out.push_str("pub struct ");
    out.push_str(&escape_keyword(&e.name.text));
    out.push_str(" {\n");
    for member in &e.members {
        let rust_ty = rust_type_for(&member.type_spec)?;
        for declarator in &member.declarators {
            let raw = match declarator {
                Declarator::Simple(n) => &n.text,
                Declarator::Array(a) => &a.name.text,
            };
            let name = escape_keyword(raw);
            out.push_str(&format!("    pub {name}: {rust_ty},\n"));
        }
    }
    out.push_str("}\n");
    Ok(())
}