zerodds-corba-rust 1.0.0-rc.3

IDL → Rust code generator for CORBA service constructs (interfaces, valuetypes, components, homes) — analogous to zerodds-idl-cpp/-csharp/-java but for Rust output.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 ZeroDDS Contributors
//! Top-level emitter: AST → CORBA Rust code.

use std::collections::{BTreeMap, HashMap};

use zerodds_idl::ast::types::{Definition, Export, InterfaceDcl, InterfaceDef, Specification};

use crate::error::Result;

/// Collects scope-resolved exception RepositoryIds (simple name → `IDL:…:1.0`):
/// accounts for the definition scope (module/interface path) and `typeprefix`
/// (e.g. `omg.org`), so that `raises(NotFound)` produces byte-exactly the same
/// RepositoryId as a foreign ORB (`IDL:omg.org/CosNaming/NamingContext/NotFound:1.0`).
fn collect_exception_repo_ids(spec: &Specification) -> BTreeMap<String, String> {
    let mut prefixes: BTreeMap<String, String> = BTreeMap::new();
    collect_prefixes(&spec.definitions, &mut prefixes);
    let mut out = BTreeMap::new();
    let mut path: Vec<String> = Vec::new();
    walk_exceptions(&spec.definitions, &mut path, &prefixes, &mut out);
    out
}

/// `typeprefix <target> "<prefix>"` → map (target simple name → prefix).
///
/// zerodds-lint: recursion-depth 64 (Module-Hierarchy; bounded by IDL nesting)
fn collect_prefixes(defs: &[Definition], prefixes: &mut BTreeMap<String, String>) {
    for d in defs {
        match d {
            Definition::TypePrefix(tp) => {
                if let Some(last) = tp.target.parts.last() {
                    prefixes.insert(last.text.clone(), tp.prefix.clone());
                }
            }
            Definition::Module(m) => collect_prefixes(&m.definitions, prefixes),
            _ => {}
        }
    }
}

fn rid_for(path: &[String], name: &str) -> String {
    let parts: Vec<&str> = path.iter().map(String::as_str).collect();
    zerodds_corba_codegen::build_repository_id(&parts, name, 1, 0)
}

/// zerodds-lint: recursion-depth 64 (Module-Hierarchy; bounded by IDL nesting)
fn walk_exceptions(
    defs: &[Definition],
    path: &mut Vec<String>,
    prefixes: &BTreeMap<String, String>,
    out: &mut BTreeMap<String, String>,
) {
    for d in defs {
        match d {
            Definition::Except(e) => {
                out.insert(e.name.text.clone(), rid_for(path, &e.name.text));
            }
            Definition::Module(m) => {
                let mut pushed = 0;
                // typeprefix comes BEFORE the module name (IDL:omg.org/CosNaming/…).
                if let Some(p) = prefixes.get(&m.name.text) {
                    for seg in p.split('/').filter(|s| !s.is_empty()) {
                        path.push(seg.to_string());
                        pushed += 1;
                    }
                }
                path.push(m.name.text.clone());
                pushed += 1;
                walk_exceptions(&m.definitions, path, prefixes, out);
                for _ in 0..pushed {
                    path.pop();
                }
            }
            Definition::Interface(InterfaceDcl::Def(i)) => {
                path.push(i.name.text.clone());
                for ex in &i.exports {
                    if let Export::Except(e) = ex {
                        out.insert(e.name.text.clone(), rid_for(path, &e.name.text));
                    }
                }
                path.pop();
            }
            _ => {}
        }
    }
}

/// Registry of all interface definitions indexed by simple name.
/// Built before the actual emit so that stubs can emit `impl Base for
/// DerivedStub` blocks for all transitive bases.
pub(crate) type InterfaceRegistry<'a> = HashMap<String, &'a InterfaceDef>;

/// Options for the CORBA codegen.
#[derive(Debug, Clone, Default)]
pub struct CorbaRustGenOptions {
    /// Header comment at the top of the generated file. Taken over
    /// verbatim.
    pub header_comment: Option<String>,
}

/// Produces a single Rust module file from a parsed IDL spec, containing
/// CORBA service constructs (interface stubs/skeletons + valuetypes).
///
/// # Errors
/// `Unsupported` for IDL constructs outside the service scope.
pub fn generate_corba_rust_module(
    spec: &Specification,
    opts: &CorbaRustGenOptions,
) -> Result<String> {
    // IDL `any` in the CORBA path → zerodds_cdr::CorbaAny (not DdsAny).
    zerodds_idl_rust::type_map::set_any_target_corba(true);
    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-corba-rust`. Do not edit by hand.\n\n");
    out.push_str("#![allow(clippy::too_many_lines)]\n");
    out.push_str("#![allow(unused_imports, unused_variables)]\n\n");

    let mut registry: InterfaceRegistry<'_> = HashMap::new();
    collect_interfaces(spec, &mut registry);
    // Register interface simple names → rust_scoped maps references to them
    // onto ObjectReference (IOR) instead of the trait itself (avoiding a dyn cycle).
    zerodds_idl_rust::type_map::set_interface_refs(registry.keys().cloned());
    // Scope-resolved exception RepositoryIds (#4(3): definition scope +
    // typeprefix) → correct cross-ORB RepoIds in raises encodings/decodings.
    crate::interface_emit::set_exception_repo_ids(collect_exception_repo_ids(spec));

    // Register valuetype state layouts (inheritance state flattening, §15.3.4).
    crate::valuetype_emit::register_value_states(spec)?;

    for def in &spec.definitions {
        emit_definition(&mut out, def, &registry, &[])?;
    }
    Ok(out)
}

/// zerodds-lint: recursion-depth 16
fn collect_interfaces<'a>(spec: &'a Specification, reg: &mut InterfaceRegistry<'a>) {
    for def in &spec.definitions {
        collect_from_def(def, reg);
    }
}

/// zerodds-lint: recursion-depth 16
fn collect_from_def<'a>(def: &'a Definition, reg: &mut InterfaceRegistry<'a>) {
    use zerodds_idl::ast::types::InterfaceDcl;
    match def {
        Definition::Interface(InterfaceDcl::Def(d)) => {
            reg.insert(d.name.text.clone(), d);
        }
        Definition::Module(m) => {
            for inner in &m.definitions {
                collect_from_def(inner, reg);
            }
        }
        _ => {}
    }
}

/// zerodds-lint: recursion-depth 16
/// IDL module nesting — 16 levels suffice for all known
/// OMG spec IDL files.
fn emit_definition(
    out: &mut String,
    def: &Definition,
    registry: &InterfaceRegistry<'_>,
    scope: &[&str],
) -> Result<()> {
    match def {
        Definition::Module(m) => emit_module(out, m, registry, scope)?,
        Definition::Interface(i_dcl) => {
            use zerodds_idl::ast::types::InterfaceDcl;
            match i_dcl {
                InterfaceDcl::Def(def) => {
                    crate::interface_emit::emit_interface(out, def, registry)?;
                    out.push('\n');
                }
                InterfaceDcl::Forward(_) => {}
            }
        }
        Definition::ValueDef(v) => {
            crate::valuetype_emit::emit_valuetype(out, v, scope)?;
            out.push('\n');
        }
        Definition::Component(c) => {
            crate::component_emit::emit_component(out, c)?;
            out.push('\n');
        }
        Definition::Home(h) => {
            crate::component_emit::emit_home(out, h)?;
            out.push('\n');
        }
        // ValueBox/ValueForward: phase 2.
        // Type/Const/Except live in the idl-rust DataType path.
        _ => {}
    }
    Ok(())
}

/// zerodds-lint: recursion-depth 16
fn emit_module(
    out: &mut String,
    m: &zerodds_idl::ast::types::ModuleDef,
    registry: &InterfaceRegistry<'_>,
    scope: &[&str],
) -> Result<()> {
    out.push_str("pub mod ");
    out.push_str(&m.name.text);
    out.push_str(" {\n");
    let mut child_scope: Vec<&str> = scope.to_vec();
    child_scope.push(&m.name.text);
    for inner in &m.definitions {
        let mut inner_out = String::new();
        emit_definition(&mut inner_out, inner, registry, &child_scope)?;
        for line in inner_out.lines() {
            if line.is_empty() {
                out.push('\n');
            } else {
                out.push_str("    ");
                out.push_str(line);
                out.push('\n');
            }
        }
    }
    out.push_str("}\n\n");
    Ok(())
}