Skip to main content

uika_codegen/
context.rs

1// Build context: type lookups, module mapping, FuncId assignment.
2
3use std::collections::{BTreeMap, HashMap, HashSet};
4
5use crate::config::CodegenConfig;
6use crate::schema::{ClassInfo, EnumInfo, FunctionInfo, StructInfo};
7
8/// Central build context for the codegen pipeline.
9pub struct CodegenContext {
10    /// All classes by name.
11    pub classes: HashMap<String, ClassInfo>,
12    /// All structs by name.
13    pub structs: HashMap<String, StructInfo>,
14    /// All enums by name.
15    pub enums: HashMap<String, EnumInfo>,
16
17    /// Package name → Rust module name.
18    pub package_to_module: HashMap<String, String>,
19    /// Rust module name → Cargo feature name.
20    pub module_to_feature: HashMap<String, String>,
21    /// Set of enabled Rust module names (derived from features).
22    pub enabled_modules: HashSet<String>,
23
24    /// Classes grouped by module name. Sorted for determinism.
25    pub module_classes: BTreeMap<String, Vec<ClassInfo>>,
26    /// Structs grouped by module name.
27    pub module_structs: BTreeMap<String, Vec<StructInfo>>,
28    /// Enums grouped by module name.
29    pub module_enums: BTreeMap<String, Vec<EnumInfo>>,
30
31    /// All exportable functions sorted by (module, class, func) for FuncId assignment.
32    pub func_table: Vec<FuncEntry>,
33}
34
35/// An entry in the global function table.
36#[derive(Clone)]
37pub struct FuncEntry {
38    pub func_id: u32,
39    pub module_name: String,
40    pub class_name: String,
41    pub func_name: String,
42    /// The actual resolved Rust function name (may include _1, _2 suffix for overloads).
43    pub rust_func_name: String,
44    pub func: FunctionInfo,
45    /// The C++ class name (with prefix, e.g. "AActor").
46    pub cpp_class_name: String,
47    /// The header file to include.
48    pub header: String,
49}
50
51impl CodegenContext {
52    pub fn new(
53        classes: Vec<ClassInfo>,
54        structs: Vec<StructInfo>,
55        enums: Vec<EnumInfo>,
56        config: &CodegenConfig,
57    ) -> Self {
58        // Build package→module and module→feature maps from config
59        let mut package_to_module = HashMap::new();
60        let mut module_to_feature_map = HashMap::new();
61
62        for (pkg, mapping) in &config.modules {
63            package_to_module.insert(pkg.clone(), mapping.module.clone());
64            module_to_feature_map.insert(mapping.module.clone(), mapping.feature.clone());
65        }
66
67        // Build enabled modules from features
68        let enabled_features: HashSet<&str> = config.features.iter().map(|s| s.as_str()).collect();
69        let mut enabled_modules = HashSet::new();
70        for (pkg, mapping) in &config.modules {
71            if enabled_features.contains(mapping.feature.as_str()) {
72                enabled_modules.insert(mapping.module.clone());
73                // Also ensure the package mapping exists for auto-derived packages
74                package_to_module.entry(pkg.clone()).or_insert_with(|| mapping.module.clone());
75            }
76        }
77
78        // Auto-derive module names for packages not in config
79        let mut all_packages = HashSet::new();
80        for c in &classes {
81            all_packages.insert(c.package.clone());
82        }
83        for s in &structs {
84            all_packages.insert(s.package.clone());
85        }
86        for e in &enums {
87            all_packages.insert(e.package.clone());
88        }
89
90        for pkg in &all_packages {
91            if !package_to_module.contains_key(pkg) {
92                let module = crate::naming::to_snake_case(pkg);
93                package_to_module.insert(pkg.clone(), module);
94            }
95        }
96
97        // Group types by module
98        let mut module_classes: BTreeMap<String, Vec<ClassInfo>> = BTreeMap::new();
99        let mut module_structs: BTreeMap<String, Vec<StructInfo>> = BTreeMap::new();
100        let mut module_enums: BTreeMap<String, Vec<EnumInfo>> = BTreeMap::new();
101
102        let mut classes_map = HashMap::new();
103        for c in classes {
104            if let Some(module) = package_to_module.get(&c.package) {
105                if enabled_modules.contains(module) {
106                    classes_map.insert(c.name.clone(), c.clone());
107                    module_classes.entry(module.clone()).or_default().push(c);
108                }
109            }
110        }
111
112        let mut structs_map = HashMap::new();
113        for s in structs {
114            if let Some(module) = package_to_module.get(&s.package) {
115                if enabled_modules.contains(module) {
116                    structs_map.insert(s.name.clone(), s.clone());
117                    module_structs.entry(module.clone()).or_default().push(s);
118                }
119            }
120        }
121
122        let mut enums_map = HashMap::new();
123        for e in enums {
124            if let Some(module) = package_to_module.get(&e.package) {
125                if enabled_modules.contains(module) {
126                    enums_map.insert(e.name.clone(), e.clone());
127                    module_enums.entry(module.clone()).or_default().push(e);
128                }
129            }
130        }
131
132        // Sort within each module for determinism
133        for classes in module_classes.values_mut() {
134            classes.sort_by(|a, b| a.name.cmp(&b.name));
135        }
136        for structs in module_structs.values_mut() {
137            structs.sort_by(|a, b| a.name.cmp(&b.name));
138        }
139        for enums in module_enums.values_mut() {
140            enums.sort_by(|a, b| a.name.cmp(&b.name));
141        }
142
143        CodegenContext {
144            classes: classes_map,
145            structs: structs_map,
146            enums: enums_map,
147            package_to_module,
148            module_to_feature: module_to_feature_map,
149            enabled_modules,
150            module_classes,
151            module_structs,
152            module_enums,
153            func_table: Vec::new(),
154        }
155    }
156
157    /// Check if a type (class, struct, or enum) exists in an enabled module.
158    #[allow(dead_code)]
159    pub fn is_type_available(&self, type_name: &str) -> bool {
160        self.classes.contains_key(type_name)
161            || self.structs.contains_key(type_name)
162            || self.enums.contains_key(type_name)
163    }
164
165    /// Get the module name for a package.
166    #[allow(dead_code)]
167    pub fn module_for_package(&self, package: &str) -> Option<&str> {
168        self.package_to_module.get(package).map(|s| s.as_str())
169    }
170
171    /// Get the Cargo feature name for a module.
172    pub fn feature_for_module(&self, module: &str) -> Option<&str> {
173        self.module_to_feature.get(module).map(|s| s.as_str())
174    }
175
176    /// Get the actual Rust repr type for an enum, accounting for signed promotion.
177    /// This must match the logic in `rust_gen/enums.rs`.
178    pub fn enum_actual_repr(&self, enum_name: &str) -> Option<&'static str> {
179        let e = self.enums.get(enum_name)?;
180        let has_negative = e.pairs.iter().any(|(_, v)| *v < 0);
181        Some(if has_negative {
182            match e.underlying_type.as_str() {
183                "uint8" => "i8",
184                "int8" => "i8",
185                "uint16" => "i16",
186                "int16" => "i16",
187                "uint32" => "i32",
188                "int32" => "i32",
189                "uint64" => "i64",
190                "int64" => "i64",
191                _ => "i8",
192            }
193        } else {
194            match e.underlying_type.as_str() {
195                "uint8" => "u8",
196                "int8" => "i8",
197                "uint16" => "u16",
198                "int16" => "i16",
199                "uint32" => "u32",
200                "int32" => "i32",
201                "uint64" => "u64",
202                "int64" => "i64",
203                _ => "u8",
204            }
205        })
206    }
207}