Skip to main content

portalis_transpiler/
stdlib_mapper.rs

1//! Python Standard Library to Rust Crate Mapping
2//!
3//! Maps Python imports to equivalent Rust crates and provides translation rules
4
5use std::collections::HashMap;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ModuleMapping {
10    /// Python module name
11    pub python_module: String,
12    /// Rust crate name (for Cargo.toml)
13    pub rust_crate: Option<String>,
14    /// Rust use statement
15    pub rust_use: String,
16    /// Additional dependencies needed
17    pub dependencies: Vec<String>,
18    /// Version constraint
19    pub version: String,
20    /// WASM compatibility status
21    pub wasm_compatible: WasmCompatibility,
22    /// Notes about limitations or special handling
23    pub notes: Option<String>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
27pub enum WasmCompatibility {
28    /// Fully compatible with WASM
29    Full,
30    /// Partially compatible (some features may not work)
31    Partial,
32    /// Requires WASI support
33    RequiresWasi,
34    /// Requires JS interop
35    RequiresJsInterop,
36    /// Not compatible with WASM
37    Incompatible,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct FunctionMapping {
42    /// Python function name
43    pub python_name: String,
44    /// Rust equivalent
45    pub rust_equiv: String,
46    /// Requires module import
47    pub requires_use: Option<String>,
48    /// WASM compatibility
49    pub wasm_compatible: WasmCompatibility,
50    /// Transformation notes (e.g., parameter changes)
51    pub transform_notes: Option<String>,
52}
53
54pub struct StdlibMapper {
55    modules: HashMap<String, ModuleMapping>,
56    functions: HashMap<String, HashMap<String, FunctionMapping>>,
57}
58
59impl StdlibMapper {
60    pub fn new() -> Self {
61        let mut mapper = Self {
62            modules: HashMap::new(),
63            functions: HashMap::new(),
64        };
65        mapper.init_mappings();
66        mapper
67    }
68
69    fn init_mappings(&mut self) {
70        // Use comprehensive mappings from the new module
71        let comprehensive_mappings = crate::stdlib_mappings_comprehensive::init_critical_mappings();
72
73        for (module_mapping, function_mappings) in comprehensive_mappings {
74            self.add_module(module_mapping.clone());
75            for func_mapping in function_mappings {
76                self.add_function_mapping(&module_mapping.python_module, func_mapping);
77            }
78        }
79
80        // Add legacy basic mappings that might not be in comprehensive yet
81        self.add_legacy_mappings();
82    }
83
84    fn add_legacy_mappings(&mut self) {
85        // Random module (if not already added)
86        if !self.modules.contains_key("random") {
87            self.add_module(ModuleMapping {
88                python_module: "random".to_string(),
89                rust_crate: Some("rand".to_string()),
90                rust_use: "rand".to_string(),
91                dependencies: vec![],
92                version: "0.8".to_string(),
93                wasm_compatible: WasmCompatibility::RequiresJsInterop,
94                notes: Some("Requires getrandom with js feature for WASM".to_string()),
95            });
96
97            self.add_function_mapping("random", FunctionMapping {
98                python_name: "random".to_string(),
99                rust_equiv: "rand::random::<f64>".to_string(),
100                requires_use: Some("rand".to_string()),
101                wasm_compatible: WasmCompatibility::RequiresJsInterop,
102                transform_notes: None,
103            });
104
105            self.add_function_mapping("random", FunctionMapping {
106                python_name: "randint".to_string(),
107                rust_equiv: "rand::thread_rng().gen_range".to_string(),
108                requires_use: Some("rand::Rng".to_string()),
109                wasm_compatible: WasmCompatibility::RequiresJsInterop,
110                transform_notes: Some("Takes range as (start..=end)".to_string()),
111            });
112        }
113
114        // Datetime module (if not already added)
115        if !self.modules.contains_key("datetime") {
116            self.add_module(ModuleMapping {
117                python_module: "datetime".to_string(),
118                rust_crate: Some("chrono".to_string()),
119                rust_use: "chrono".to_string(),
120                dependencies: vec![],
121                version: "0.4".to_string(),
122                wasm_compatible: WasmCompatibility::RequiresJsInterop,
123                notes: Some("Uses JS Date in browser, native in Node.js".to_string()),
124            });
125
126            self.add_function_mapping("datetime", FunctionMapping {
127                python_name: "datetime.now".to_string(),
128                rust_equiv: "chrono::Utc::now".to_string(),
129                requires_use: Some("chrono::Utc".to_string()),
130                wasm_compatible: WasmCompatibility::RequiresJsInterop,
131                transform_notes: None,
132            });
133        }
134
135        // Re (regex) module (if not already added)
136        if !self.modules.contains_key("re") {
137            self.add_module(ModuleMapping {
138                python_module: "re".to_string(),
139                rust_crate: Some("regex".to_string()),
140                rust_use: "regex::Regex".to_string(),
141                dependencies: vec![],
142                version: "1".to_string(),
143                wasm_compatible: WasmCompatibility::Full,
144                notes: None,
145            });
146
147            self.add_function_mapping("re", FunctionMapping {
148                python_name: "compile".to_string(),
149                rust_equiv: "Regex::new".to_string(),
150                requires_use: Some("regex::Regex".to_string()),
151                wasm_compatible: WasmCompatibility::Full,
152                transform_notes: None,
153            });
154
155            self.add_function_mapping("re", FunctionMapping {
156                python_name: "match".to_string(),
157                rust_equiv: "Regex::is_match".to_string(),
158                requires_use: Some("regex::Regex".to_string()),
159                wasm_compatible: WasmCompatibility::Full,
160                transform_notes: None,
161            });
162        }
163
164        // Sys module
165        if !self.modules.contains_key("sys") {
166            self.add_module(ModuleMapping {
167                python_module: "sys".to_string(),
168                rust_crate: None,
169                rust_use: "std::env".to_string(),
170                dependencies: vec![],
171                version: "*".to_string(),
172                wasm_compatible: WasmCompatibility::Partial,
173                notes: Some("Limited functionality in WASM".to_string()),
174            });
175
176            self.add_function_mapping("sys", FunctionMapping {
177                python_name: "argv".to_string(),
178                rust_equiv: "std::env::args".to_string(),
179                requires_use: Some("std::env".to_string()),
180                wasm_compatible: WasmCompatibility::Incompatible,
181                transform_notes: Some("Not available in browser WASM".to_string()),
182            });
183        }
184
185        // OS module (enhanced)
186        if !self.modules.contains_key("os") {
187            self.add_module(ModuleMapping {
188                python_module: "os".to_string(),
189                rust_crate: None,
190                rust_use: "std::env".to_string(),
191                dependencies: vec![],
192                version: "*".to_string(),
193                wasm_compatible: WasmCompatibility::RequiresWasi,
194                notes: Some("Most functions require WASI".to_string()),
195            });
196
197            self.add_function_mapping("os", FunctionMapping {
198                python_name: "getcwd".to_string(),
199                rust_equiv: "std::env::current_dir".to_string(),
200                requires_use: Some("std::env".to_string()),
201                wasm_compatible: WasmCompatibility::RequiresWasi,
202                transform_notes: None,
203            });
204
205            self.add_function_mapping("os", FunctionMapping {
206                python_name: "getenv".to_string(),
207                rust_equiv: "std::env::var".to_string(),
208                requires_use: Some("std::env".to_string()),
209                wasm_compatible: WasmCompatibility::RequiresWasi,
210                transform_notes: None,
211            });
212        }
213
214        // JSON module
215        if !self.modules.contains_key("json") {
216            self.add_module(ModuleMapping {
217                python_module: "json".to_string(),
218                rust_crate: Some("serde_json".to_string()),
219                rust_use: "serde_json".to_string(),
220                dependencies: vec!["serde".to_string()],
221                version: "1.0".to_string(),
222                wasm_compatible: WasmCompatibility::Full,
223                notes: None,
224            });
225
226            self.add_function_mapping("json", FunctionMapping {
227                python_name: "loads".to_string(),
228                rust_equiv: "serde_json::from_str".to_string(),
229                requires_use: Some("serde_json".to_string()),
230                wasm_compatible: WasmCompatibility::Full,
231                transform_notes: None,
232            });
233
234            self.add_function_mapping("json", FunctionMapping {
235                python_name: "dumps".to_string(),
236                rust_equiv: "serde_json::to_string".to_string(),
237                requires_use: Some("serde_json".to_string()),
238                wasm_compatible: WasmCompatibility::Full,
239                transform_notes: None,
240            });
241        }
242    }
243
244    fn add_module(&mut self, mapping: ModuleMapping) {
245        self.modules.insert(mapping.python_module.clone(), mapping);
246    }
247
248    fn add_function_mapping(&mut self, module: &str, mapping: FunctionMapping) {
249        self.functions
250            .entry(module.to_string())
251            .or_insert_with(HashMap::new)
252            .insert(mapping.python_name.clone(), mapping);
253    }
254
255    pub fn get_module_mapping(&self, module: &str) -> Option<&ModuleMapping> {
256        self.modules.get(module)
257    }
258
259    pub fn get_function_mapping(&self, module: &str, function: &str) -> Option<&FunctionMapping> {
260        self.functions.get(module)?.get(function)
261    }
262
263    pub fn generate_use_statements(&self, modules: &[String]) -> Vec<String> {
264        modules
265            .iter()
266            .filter_map(|module| {
267                self.get_module_mapping(module)
268                    .map(|mapping| format!("use {};", mapping.rust_use))
269            })
270            .collect()
271    }
272
273    pub fn generate_cargo_dependencies(&self, modules: &[String]) -> HashMap<String, String> {
274        let mut deps = HashMap::new();
275
276        for module in modules {
277            if let Some(mapping) = self.get_module_mapping(module) {
278                if let Some(crate_name) = &mapping.rust_crate {
279                    deps.insert(crate_name.clone(), mapping.version.clone());
280                }
281
282                for dep in &mapping.dependencies {
283                    deps.insert(dep.clone(), "*".to_string());
284                }
285            }
286        }
287
288        deps
289    }
290
291    /// Get WASM compatibility info for a module
292    pub fn get_wasm_compatibility(&self, module: &str) -> Option<WasmCompatibility> {
293        self.get_module_mapping(module)
294            .map(|m| m.wasm_compatible.clone())
295    }
296
297    /// Get all mapped modules
298    pub fn get_all_modules(&self) -> Vec<&str> {
299        self.modules.keys().map(|s| s.as_str()).collect()
300    }
301
302    // Compatibility aliases for existing code
303    pub fn get_module(&self, module: &str) -> Option<&ModuleMapping> {
304        self.get_module_mapping(module)
305    }
306
307    pub fn get_function(&self, module: &str, function: &str) -> Option<String> {
308        self.get_function_mapping(module, function)
309            .map(|f| f.rust_equiv.clone())
310    }
311
312    pub fn collect_use_statements(&self, modules: &[String]) -> Vec<String> {
313        self.generate_use_statements(modules)
314    }
315
316    /// Get statistics
317    pub fn get_stats(&self) -> StdlibStats {
318        let total = self.modules.len();
319        let full_compat = self.modules.values()
320            .filter(|m| m.wasm_compatible == WasmCompatibility::Full)
321            .count();
322        let partial_compat = self.modules.values()
323            .filter(|m| m.wasm_compatible == WasmCompatibility::Partial)
324            .count();
325        let requires_wasi = self.modules.values()
326            .filter(|m| m.wasm_compatible == WasmCompatibility::RequiresWasi)
327            .count();
328        let requires_js = self.modules.values()
329            .filter(|m| m.wasm_compatible == WasmCompatibility::RequiresJsInterop)
330            .count();
331        let incompatible = self.modules.values()
332            .filter(|m| m.wasm_compatible == WasmCompatibility::Incompatible)
333            .count();
334
335        StdlibStats {
336            total_mapped: total,
337            full_wasm_compat: full_compat,
338            partial_wasm_compat: partial_compat,
339            requires_wasi,
340            requires_js_interop: requires_js,
341            incompatible,
342        }
343    }
344}
345
346#[derive(Debug)]
347pub struct StdlibStats {
348    pub total_mapped: usize,
349    pub full_wasm_compat: usize,
350    pub partial_wasm_compat: usize,
351    pub requires_wasi: usize,
352    pub requires_js_interop: usize,
353    pub incompatible: usize,
354}
355
356impl Default for StdlibMapper {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn test_math_module_mapping() {
368        let mapper = StdlibMapper::new();
369        let math_mod = mapper.get_module_mapping("math");
370        assert!(math_mod.is_some());
371        assert_eq!(math_mod.unwrap().wasm_compatible, WasmCompatibility::Full);
372    }
373
374    #[test]
375    fn test_json_module_mapping() {
376        let mapper = StdlibMapper::new();
377        let json_mod = mapper.get_module_mapping("json");
378        assert!(json_mod.is_some());
379        assert_eq!(json_mod.unwrap().rust_crate, Some("serde_json".to_string()));
380    }
381
382    #[test]
383    fn test_function_mapping() {
384        let mapper = StdlibMapper::new();
385        let sqrt_func = mapper.get_function_mapping("math", "sqrt");
386        assert!(sqrt_func.is_some());
387        assert_eq!(sqrt_func.unwrap().rust_equiv, "f64::sqrt");
388    }
389
390    #[test]
391    fn test_cargo_dependencies() {
392        let mapper = StdlibMapper::new();
393        let deps = mapper.generate_cargo_dependencies(&vec!["json".to_string()]);
394        assert!(deps.contains_key("serde_json"));
395    }
396
397    #[test]
398    fn test_stats() {
399        let mapper = StdlibMapper::new();
400        let stats = mapper.get_stats();
401        assert!(stats.total_mapped > 0);
402        println!("Stdlib mapping stats: {:?}", stats);
403    }
404}