Skip to main content

zerodds_corba_rust/
emitter.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Top-level emitter: AST → CORBA Rust code.
4
5use std::collections::{BTreeMap, HashMap};
6
7use zerodds_idl::ast::types::{Definition, Export, InterfaceDcl, InterfaceDef, Specification};
8
9use crate::error::Result;
10
11/// Collects scope-resolved exception RepositoryIds (simple name → `IDL:…:1.0`):
12/// accounts for the definition scope (module/interface path) and `typeprefix`
13/// (e.g. `omg.org`), so that `raises(NotFound)` produces byte-exactly the same
14/// RepositoryId as a foreign ORB (`IDL:omg.org/CosNaming/NamingContext/NotFound:1.0`).
15fn collect_exception_repo_ids(spec: &Specification) -> BTreeMap<String, String> {
16    let mut prefixes: BTreeMap<String, String> = BTreeMap::new();
17    collect_prefixes(&spec.definitions, &mut prefixes);
18    let mut out = BTreeMap::new();
19    let mut path: Vec<String> = Vec::new();
20    walk_exceptions(&spec.definitions, &mut path, &prefixes, &mut out);
21    out
22}
23
24/// `typeprefix <target> "<prefix>"` → map (target simple name → prefix).
25///
26/// zerodds-lint: recursion-depth 64 (Module-Hierarchy; bounded by IDL nesting)
27fn collect_prefixes(defs: &[Definition], prefixes: &mut BTreeMap<String, String>) {
28    for d in defs {
29        match d {
30            Definition::TypePrefix(tp) => {
31                if let Some(last) = tp.target.parts.last() {
32                    prefixes.insert(last.text.clone(), tp.prefix.clone());
33                }
34            }
35            Definition::Module(m) => collect_prefixes(&m.definitions, prefixes),
36            _ => {}
37        }
38    }
39}
40
41fn rid_for(path: &[String], name: &str) -> String {
42    let parts: Vec<&str> = path.iter().map(String::as_str).collect();
43    zerodds_corba_codegen::build_repository_id(&parts, name, 1, 0)
44}
45
46/// zerodds-lint: recursion-depth 64 (Module-Hierarchy; bounded by IDL nesting)
47fn walk_exceptions(
48    defs: &[Definition],
49    path: &mut Vec<String>,
50    prefixes: &BTreeMap<String, String>,
51    out: &mut BTreeMap<String, String>,
52) {
53    for d in defs {
54        match d {
55            Definition::Except(e) => {
56                out.insert(e.name.text.clone(), rid_for(path, &e.name.text));
57            }
58            Definition::Module(m) => {
59                let mut pushed = 0;
60                // typeprefix comes BEFORE the module name (IDL:omg.org/CosNaming/…).
61                if let Some(p) = prefixes.get(&m.name.text) {
62                    for seg in p.split('/').filter(|s| !s.is_empty()) {
63                        path.push(seg.to_string());
64                        pushed += 1;
65                    }
66                }
67                path.push(m.name.text.clone());
68                pushed += 1;
69                walk_exceptions(&m.definitions, path, prefixes, out);
70                for _ in 0..pushed {
71                    path.pop();
72                }
73            }
74            Definition::Interface(InterfaceDcl::Def(i)) => {
75                path.push(i.name.text.clone());
76                for ex in &i.exports {
77                    if let Export::Except(e) = ex {
78                        out.insert(e.name.text.clone(), rid_for(path, &e.name.text));
79                    }
80                }
81                path.pop();
82            }
83            _ => {}
84        }
85    }
86}
87
88/// Registry of all interface definitions indexed by simple name.
89/// Built before the actual emit so that stubs can emit `impl Base for
90/// DerivedStub` blocks for all transitive bases.
91pub(crate) type InterfaceRegistry<'a> = HashMap<String, &'a InterfaceDef>;
92
93/// Options for the CORBA codegen.
94#[derive(Debug, Clone, Default)]
95pub struct CorbaRustGenOptions {
96    /// Header comment at the top of the generated file. Taken over
97    /// verbatim.
98    pub header_comment: Option<String>,
99}
100
101/// Produces a single Rust module file from a parsed IDL spec, containing
102/// CORBA service constructs (interface stubs/skeletons + valuetypes).
103///
104/// # Errors
105/// `Unsupported` for IDL constructs outside the service scope.
106pub fn generate_corba_rust_module(
107    spec: &Specification,
108    opts: &CorbaRustGenOptions,
109) -> Result<String> {
110    // IDL `any` in the CORBA path → zerodds_cdr::CorbaAny (not DdsAny).
111    zerodds_idl_rust::type_map::set_any_target_corba(true);
112    let mut out = String::new();
113    out.push_str("// SPDX-License-Identifier: Apache-2.0\n");
114    if let Some(c) = &opts.header_comment {
115        for line in c.lines() {
116            out.push_str("// ");
117            out.push_str(line);
118            out.push('\n');
119        }
120    }
121    out.push_str("// Auto-generated by `zerodds-corba-rust`. Do not edit by hand.\n\n");
122    out.push_str("#![allow(clippy::too_many_lines)]\n");
123    out.push_str("#![allow(unused_imports, unused_variables)]\n\n");
124
125    let mut registry: InterfaceRegistry<'_> = HashMap::new();
126    collect_interfaces(spec, &mut registry);
127    // Register interface simple names → rust_scoped maps references to them
128    // onto ObjectReference (IOR) instead of the trait itself (avoiding a dyn cycle).
129    zerodds_idl_rust::type_map::set_interface_refs(registry.keys().cloned());
130    // Scope-resolved exception RepositoryIds (#4(3): definition scope +
131    // typeprefix) → correct cross-ORB RepoIds in raises encodings/decodings.
132    crate::interface_emit::set_exception_repo_ids(collect_exception_repo_ids(spec));
133
134    // Register valuetype state layouts (inheritance state flattening, §15.3.4).
135    crate::valuetype_emit::register_value_states(spec)?;
136
137    for def in &spec.definitions {
138        emit_definition(&mut out, def, &registry, &[])?;
139    }
140    Ok(out)
141}
142
143/// zerodds-lint: recursion-depth 16
144fn collect_interfaces<'a>(spec: &'a Specification, reg: &mut InterfaceRegistry<'a>) {
145    for def in &spec.definitions {
146        collect_from_def(def, reg);
147    }
148}
149
150/// zerodds-lint: recursion-depth 16
151fn collect_from_def<'a>(def: &'a Definition, reg: &mut InterfaceRegistry<'a>) {
152    use zerodds_idl::ast::types::InterfaceDcl;
153    match def {
154        Definition::Interface(InterfaceDcl::Def(d)) => {
155            reg.insert(d.name.text.clone(), d);
156        }
157        Definition::Module(m) => {
158            for inner in &m.definitions {
159                collect_from_def(inner, reg);
160            }
161        }
162        _ => {}
163    }
164}
165
166/// zerodds-lint: recursion-depth 16
167/// IDL module nesting — 16 levels suffice for all known
168/// OMG spec IDL files.
169fn emit_definition(
170    out: &mut String,
171    def: &Definition,
172    registry: &InterfaceRegistry<'_>,
173    scope: &[&str],
174) -> Result<()> {
175    match def {
176        Definition::Module(m) => emit_module(out, m, registry, scope)?,
177        Definition::Interface(i_dcl) => {
178            use zerodds_idl::ast::types::InterfaceDcl;
179            match i_dcl {
180                InterfaceDcl::Def(def) => {
181                    crate::interface_emit::emit_interface(out, def, registry)?;
182                    out.push('\n');
183                }
184                InterfaceDcl::Forward(_) => {}
185            }
186        }
187        Definition::ValueDef(v) => {
188            crate::valuetype_emit::emit_valuetype(out, v, scope)?;
189            out.push('\n');
190        }
191        Definition::Component(c) => {
192            crate::component_emit::emit_component(out, c)?;
193            out.push('\n');
194        }
195        Definition::Home(h) => {
196            crate::component_emit::emit_home(out, h)?;
197            out.push('\n');
198        }
199        // ValueBox/ValueForward: phase 2.
200        // Type/Const/Except live in the idl-rust DataType path.
201        _ => {}
202    }
203    Ok(())
204}
205
206/// zerodds-lint: recursion-depth 16
207fn emit_module(
208    out: &mut String,
209    m: &zerodds_idl::ast::types::ModuleDef,
210    registry: &InterfaceRegistry<'_>,
211    scope: &[&str],
212) -> Result<()> {
213    out.push_str("pub mod ");
214    out.push_str(&m.name.text);
215    out.push_str(" {\n");
216    let mut child_scope: Vec<&str> = scope.to_vec();
217    child_scope.push(&m.name.text);
218    for inner in &m.definitions {
219        let mut inner_out = String::new();
220        emit_definition(&mut inner_out, inner, registry, &child_scope)?;
221        for line in inner_out.lines() {
222            if line.is_empty() {
223                out.push('\n');
224            } else {
225                out.push_str("    ");
226                out.push_str(line);
227                out.push('\n');
228            }
229        }
230    }
231    out.push_str("}\n\n");
232    Ok(())
233}