portalis_transpiler/
lib.rs

1//! Transpiler Agent - Python to Rust Code Generation
2//!
3//! Generates idiomatic Rust code from analyzed Python AST.
4
5mod code_generator;
6pub mod class_translator;
7pub mod class_inheritance;
8pub mod decorator_translator;
9pub mod generator_translator;
10pub mod asyncio_orchestrator;
11pub mod threading_translator;
12pub mod type_inference;
13pub mod lifetime_analysis;
14pub mod generic_translator;
15pub mod reference_optimizer;
16pub mod version_resolver;
17pub mod numpy_translator;
18pub mod pandas_translator;
19pub mod common_libraries_translator;
20pub mod python_ast;
21pub mod python_to_rust;
22pub mod python_parser;
23pub mod expression_translator;
24pub mod statement_translator;
25pub mod simple_parser;
26pub mod indented_parser;
27pub mod feature_translator;
28pub mod stdlib_mapper;
29pub mod stdlib_mappings_comprehensive;
30pub mod wasi_core;
31pub mod wasi_directory;
32pub mod wasi_fs;
33pub mod wasi_filesystem;
34pub mod wasi_fetch;
35pub mod wasi_websocket;
36pub mod wasi_threading;
37pub mod wasi_async_runtime;
38pub mod web_workers;
39pub mod py_to_rust_fs;
40pub mod py_to_rust_http;
41pub mod py_to_rust_asyncio;
42pub mod import_analyzer;
43pub mod cargo_generator;
44pub mod external_packages;
45pub mod dependency_graph;
46pub mod dependency_resolver;
47pub mod version_compatibility;
48pub mod build_optimizer;
49pub mod dead_code_eliminator;
50pub mod code_splitter;
51pub mod wasm_bundler;
52pub mod npm_package_generator;
53pub mod typescript_generator;
54pub mod multi_target_builder;
55pub mod advanced_features;
56
57// WASM bindings (only compile for wasm32 target)
58#[cfg(target_arch = "wasm32")]
59pub mod wasm;
60
61#[cfg(test)]
62mod test_import_aliases;
63
64#[cfg(test)]
65mod day3_features_test;
66
67#[cfg(test)]
68mod day4_5_features_test;
69
70#[cfg(test)]
71mod day6_7_features_test;
72
73#[cfg(test)]
74mod day8_9_features_test;
75
76#[cfg(test)]
77mod day10_11_features_test;
78
79#[cfg(test)]
80mod day12_13_features_test;
81
82#[cfg(test)]
83mod day14_15_features_test;
84
85#[cfg(test)]
86mod day16_17_features_test;
87
88#[cfg(test)]
89mod day18_19_features_test;
90
91#[cfg(test)]
92mod day20_21_features_test;
93
94#[cfg(test)]
95mod day22_23_features_test;
96
97#[cfg(test)]
98mod day24_25_features_test;
99
100#[cfg(test)]
101mod day26_27_features_test;
102
103#[cfg(test)]
104mod day28_29_features_test;
105
106#[cfg(test)]
107mod day30_features_test;
108
109#[cfg(not(target_arch = "wasm32"))]
110use async_trait::async_trait;
111
112use code_generator::CodeGenerator;
113pub use class_translator::ClassTranslator;
114use python_parser::PythonParser;
115use python_to_rust::PythonToRustTranslator;
116use feature_translator::FeatureTranslator;
117
118#[cfg(not(target_arch = "wasm32"))]
119use portalis_core::{Agent, AgentCapability, AgentId, ArtifactMetadata, Error, Result};
120
121#[cfg(target_arch = "wasm32")]
122pub type Result<T> = std::result::Result<T, Error>;
123
124#[cfg(target_arch = "wasm32")]
125#[derive(Debug)]
126pub enum Error {
127    CodeGeneration(String),
128    Parse(String),
129    Other(String),
130}
131
132#[cfg(target_arch = "wasm32")]
133impl std::fmt::Display for Error {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        match self {
136            Error::CodeGeneration(msg) => write!(f, "Code generation error: {}", msg),
137            Error::Parse(msg) => write!(f, "Parse error: {}", msg),
138            Error::Other(msg) => write!(f, "Error: {}", msg),
139        }
140    }
141}
142
143#[cfg(target_arch = "wasm32")]
144impl std::error::Error for Error {}
145
146#[cfg(target_arch = "wasm32")]
147impl From<String> for Error {
148    fn from(s: String) -> Self {
149        Error::Other(s)
150    }
151}
152
153#[cfg(target_arch = "wasm32")]
154impl From<&str> for Error {
155    fn from(s: &str) -> Self {
156        Error::Other(s.to_string())
157    }
158}
159
160use serde::{Deserialize, Serialize};
161
162#[cfg(all(feature = "nemo", not(target_arch = "wasm32")))]
163use portalis_nemo_bridge::{NeMoClient, TranslateRequest};
164
165/// Input from Analysis Agent
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct TranspilerInput {
168    pub typed_functions: Vec<serde_json::Value>,
169    #[serde(default)]
170    pub typed_classes: Vec<serde_json::Value>,
171    #[serde(default)]
172    pub use_statements: Vec<String>,
173    #[serde(default)]
174    pub cargo_dependencies: Vec<serde_json::Value>,
175    pub api_contract: serde_json::Value,
176}
177
178/// Generated Rust code
179#[cfg(not(target_arch = "wasm32"))]
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct TranspilerOutput {
182    pub rust_code: String,
183    pub metadata: ArtifactMetadata,
184}
185
186/// Translation mode configuration
187#[cfg(not(target_arch = "wasm32"))]
188#[derive(Debug, Clone)]
189pub enum TranslationMode {
190    /// Pattern-based translation (CPU, no external dependencies)
191    /// Uses CodeGenerator for JSON-based typed functions
192    PatternBased,
193    /// Full AST-based translation pipeline
194    /// Uses PythonParser → PythonToRustTranslator for complete Python source
195    AstBased,
196    /// Feature-based translation with stdlib mapping
197    /// Uses FeatureTranslator for comprehensive Python → Rust translation
198    FeatureBased,
199    /// NeMo-powered translation (GPU-accelerated, requires NeMo service)
200    #[cfg(feature = "nemo")]
201    NeMo {
202        service_url: String,
203        mode: String,
204        temperature: f32,
205    },
206}
207
208#[cfg(not(target_arch = "wasm32"))]
209impl Default for TranslationMode {
210    fn default() -> Self {
211        Self::PatternBased
212    }
213}
214
215/// Transpiler Agent implementation
216#[cfg(not(target_arch = "wasm32"))]
217pub struct TranspilerAgent {
218    id: AgentId,
219    translation_mode: TranslationMode,
220}
221
222#[cfg(not(target_arch = "wasm32"))]
223impl TranspilerAgent {
224    pub fn new() -> Self {
225        Self {
226            id: AgentId::new(),
227            translation_mode: TranslationMode::default(),
228        }
229    }
230
231    /// Create transpiler with specific translation mode
232    pub fn with_mode(translation_mode: TranslationMode) -> Self {
233        Self {
234            id: AgentId::new(),
235            translation_mode,
236        }
237    }
238
239    /// Create transpiler with AST-based translation mode
240    pub fn with_ast_mode() -> Self {
241        Self::with_mode(TranslationMode::AstBased)
242    }
243
244    /// Create transpiler with feature-based translation mode
245    pub fn with_feature_mode() -> Self {
246        Self::with_mode(TranslationMode::FeatureBased)
247    }
248
249    /// Get current translation mode
250    pub fn translation_mode(&self) -> &TranslationMode {
251        &self.translation_mode
252    }
253
254    /// Generate Rust function from typed function info
255    #[cfg(not(target_arch = "wasm32"))]
256    fn generate_function(&self, func: &serde_json::Value) -> Result<String> {
257        // Check if source code is available for AST-based translation
258        if let Some(source_code) = self.extract_python_source(func) {
259            match &self.translation_mode {
260                TranslationMode::AstBased => {
261                    return self.translate_with_ast(&source_code);
262                }
263                TranslationMode::FeatureBased => {
264                    return self.translate_with_features(&source_code);
265                }
266                _ => {}
267            }
268        }
269
270        // Fallback to pattern-based CodeGenerator
271        let mut generator = CodeGenerator::new();
272        generator.generate_function(func)
273    }
274
275    /// Translate Python source code using AST-based translator
276    #[cfg(not(target_arch = "wasm32"))]
277    fn translate_with_ast(&self, python_code: &str) -> Result<String> {
278        // Parse Python source
279        let parser = PythonParser::new(python_code.to_string(), "<string>".to_string());
280        let module = parser.parse()?;
281
282        // Translate to Rust
283        let mut translator = PythonToRustTranslator::new();
284        translator.translate_module(&module)
285    }
286
287    /// Translate Python source code using feature-based translator
288    #[cfg(not(target_arch = "wasm32"))]
289    fn translate_with_features(&self, python_code: &str) -> Result<String> {
290        let mut translator = FeatureTranslator::new();
291        translator.translate(python_code)
292    }
293
294    /// Translate complete Python module to Rust (public API)
295    ///
296    /// This method provides a high-level interface for translating Python source code
297    /// using the configured translation mode.
298    #[cfg(not(target_arch = "wasm32"))]
299    pub fn translate_python_module(&self, python_source: &str) -> Result<String> {
300        match &self.translation_mode {
301            TranslationMode::PatternBased => {
302                // For pattern-based, we need to parse and extract functions
303                // This is a simplified approach - in production you'd parse the module
304                Err(Error::CodeGeneration(
305                    "PatternBased mode requires pre-analyzed JSON input. Use AstBased or FeatureBased for raw Python source.".into()
306                ))
307            }
308            TranslationMode::AstBased => {
309                self.translate_with_ast(python_source)
310            }
311            TranslationMode::FeatureBased => {
312                self.translate_with_features(python_source)
313            }
314            #[cfg(feature = "nemo")]
315            TranslationMode::NeMo { service_url, mode, temperature } => {
316                // NeMo requires async, this is a sync method
317                Err(Error::CodeGeneration(
318                    "NeMo mode requires async translation. Use translate_with_nemo directly.".into()
319                ))
320            }
321        }
322    }
323
324    /// Translate Python code using NeMo service
325    #[cfg(feature = "nemo")]
326    async fn translate_with_nemo(
327        &self,
328        python_code: &str,
329        service_url: &str,
330        mode: &str,
331        temperature: f32,
332    ) -> Result<String> {
333        let client = NeMoClient::new(service_url)?;
334
335        let request = TranslateRequest {
336            python_code: python_code.to_string(),
337            mode: mode.to_string(),
338            temperature,
339            include_metrics: true,
340        };
341
342        let response = client.translate(request).await?;
343
344        tracing::info!(
345            "NeMo translation complete: confidence={:.2}%, gpu_util={:.1}%, time={:.1}ms",
346            response.confidence * 100.0,
347            response.metrics.gpu_utilization * 100.0,
348            response.metrics.total_time_ms
349        );
350
351        Ok(response.rust_code)
352    }
353
354    /// Extract Python source code from function JSON
355    fn extract_python_source(&self, func: &serde_json::Value) -> Option<String> {
356        func.get("source_code")
357            .and_then(|s| s.as_str())
358            .map(|s| s.to_string())
359    }
360
361    /// Fallback generate (old implementation)
362    #[allow(dead_code)]
363    #[cfg(not(target_arch = "wasm32"))]
364    fn generate_function_old(&self, func: &serde_json::Value) -> Result<String> {
365        let name = func.get("name")
366            .and_then(|n| n.as_str())
367            .ok_or_else(|| Error::CodeGeneration("Missing function name".into()))?;
368
369        let params: Vec<serde_json::Value> = func.get("params")
370            .and_then(|p| p.as_array())
371            .cloned()
372            .unwrap_or_default();
373
374        let return_type = func.get("return_type")
375            .and_then(|r| r.as_object())
376            .and_then(|r| {
377                // Handle enum variants
378                if let Some(variant) = r.keys().next() {
379                    Some(variant.as_str())
380                } else {
381                    None
382                }
383            })
384            .unwrap_or("Unknown");
385
386        // Convert Rust type enum to actual Rust type string
387        let return_str = match return_type {
388            "I32" => "i32",
389            "I64" => "i64",
390            "F64" => "f64",
391            "String" => "String",
392            "Bool" => "bool",
393            "Unknown" => "()",
394            _ => "()",
395        };
396
397        // Format parameters
398        let param_strs: Vec<String> = params.iter().map(|p| {
399            let param_name = p.get("name")
400                .and_then(|n| n.as_str())
401                .unwrap_or("arg");
402
403            let rust_type = p.get("rust_type")
404                .and_then(|r| r.as_object())
405                .and_then(|r| r.keys().next().map(|s| s.as_str()))
406                .unwrap_or("I32");
407
408            let type_str = match rust_type {
409                "I32" => "i32",
410                "I64" => "i64",
411                "F64" => "f64",
412                "String" => "String",
413                "Bool" => "bool",
414                _ => "i32",
415            };
416
417            format!("{}: {}", param_name, type_str)
418        }).collect();
419
420        let params_str = param_strs.join(", ");
421
422        // Generate function body (simple template for POC)
423        let mut code = String::new();
424        code.push_str(&format!("pub fn {}({}) -> {} {{\n", name, params_str, return_str));
425
426        // Add simple implementation based on function name and return type
427        if name == "add" && return_str == "i32" {
428            code.push_str("    a + b\n");
429        } else if name == "multiply" && return_str == "i32" {
430            code.push_str("    x * y\n");
431        } else if name == "subtract" && return_str == "i32" {
432            code.push_str("    a - b\n");
433        } else if name == "divide" && return_str == "i32" {
434            code.push_str("    a / b\n");
435        } else if name == "fibonacci" && return_str == "i32" {
436            code.push_str("    if n <= 1 {\n");
437            code.push_str("        return n;\n");
438            code.push_str("    }\n");
439            code.push_str("    fibonacci(n - 1) + fibonacci(n - 2)\n");
440        } else if return_str == "()" {
441            // Unit return type - just empty body or comment
442            code.push_str("    // TODO: Implement function body\n");
443        } else {
444            // Default implementation based on return type
445            code.push_str("    // TODO: Implement function body\n");
446            match return_str {
447                "i32" | "i64" => code.push_str("    0\n"),
448                "f64" => code.push_str("    0.0\n"),
449                "bool" => code.push_str("    false\n"),
450                "String" => code.push_str("    String::new()\n"),
451                _ => code.push_str("    Default::default()\n"),
452            }
453        }
454
455        code.push_str("}\n");
456
457        Ok(code)
458    }
459}
460
461#[cfg(not(target_arch = "wasm32"))]
462impl Default for TranspilerAgent {
463    fn default() -> Self {
464        Self::new()
465    }
466}
467
468#[cfg(not(target_arch = "wasm32"))]
469#[async_trait]
470impl Agent for TranspilerAgent {
471    type Input = TranspilerInput;
472    type Output = TranspilerOutput;
473
474    async fn execute(&self, input: Self::Input) -> Result<Self::Output> {
475        tracing::info!("Transpiling Python to Rust");
476
477        let mut rust_code = String::new();
478
479        // Add standard imports and attributes
480        rust_code.push_str("// Generated by Portalis Transpiler\n");
481        rust_code.push_str("#![allow(unused)]\n\n");
482
483        // Add use statements (dependencies)
484        if !input.use_statements.is_empty() {
485            for use_stmt in &input.use_statements {
486                rust_code.push_str(use_stmt);
487                rust_code.push('\n');
488            }
489            rust_code.push('\n');
490        }
491
492        // Generate classes first
493        if !input.typed_classes.is_empty() {
494            let mut class_translator = ClassTranslator::new();
495            for class in &input.typed_classes {
496                let class_code = class_translator.generate_class(class)?;
497                rust_code.push_str(&class_code);
498                rust_code.push('\n');
499            }
500        }
501
502        // Generate each function
503        for func in &input.typed_functions {
504            let func_code = self.generate_function(func)?;
505            rust_code.push_str(&func_code);
506            rust_code.push('\n');
507        }
508
509        let metadata = ArtifactMetadata::new(self.name())
510            .with_tag("functions", input.typed_functions.len().to_string())
511            .with_tag("classes", input.typed_classes.len().to_string())
512            .with_tag("dependencies", input.cargo_dependencies.len().to_string())
513            .with_tag("lines", rust_code.lines().count().to_string());
514
515        Ok(TranspilerOutput {
516            rust_code,
517            metadata,
518        })
519    }
520
521    fn id(&self) -> AgentId {
522        self.id
523    }
524
525    fn name(&self) -> &str {
526        "TranspilerAgent"
527    }
528
529    fn capabilities(&self) -> Vec<AgentCapability> {
530        vec![AgentCapability::CodeGeneration]
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537    use serde_json::json;
538
539    #[tokio::test]
540    async fn test_generate_simple_function() {
541        let agent = TranspilerAgent::new();
542
543        let input = TranspilerInput {
544            typed_functions: vec![json!({
545                "name": "add",
546                "params": [
547                    {"name": "a", "rust_type": {"I32": null}},
548                    {"name": "b", "rust_type": {"I32": null}}
549                ],
550                "return_type": {"I32": null}
551            })],
552            typed_classes: vec![],
553            use_statements: vec![],
554            cargo_dependencies: vec![],
555            api_contract: json!({}),
556        };
557
558        let output = agent.execute(input).await.unwrap();
559
560        assert!(output.rust_code.contains("pub fn add"));
561        assert!(output.rust_code.contains("a: i32, b: i32"));
562        assert!(output.rust_code.contains("-> i32"));
563    }
564
565    #[tokio::test]
566    async fn test_generate_fibonacci() {
567        let agent = TranspilerAgent::new();
568
569        let input = TranspilerInput {
570            typed_functions: vec![json!({
571                "name": "fibonacci",
572                "params": [
573                    {"name": "n", "rust_type": {"I32": null}}
574                ],
575                "return_type": {"I32": null}
576            })],
577            typed_classes: vec![],
578            use_statements: vec![],
579            cargo_dependencies: vec![],
580            api_contract: json!({}),
581        };
582
583        let output = agent.execute(input).await.unwrap();
584
585        assert!(output.rust_code.contains("pub fn fibonacci"));
586        assert!(output.rust_code.contains("if n <= 1"));
587        assert!(output.rust_code.contains("fibonacci(n - 1) + fibonacci(n - 2)"));
588    }
589
590    #[test]
591    fn test_feature_based_translation() {
592        let agent = TranspilerAgent::with_feature_mode();
593
594        let python_code = r#"
595x = 42
596y = 3.14
597msg = "hello"
598result = x + 10
599"#;
600
601        let rust_code = agent.translate_python_module(python_code).unwrap();
602
603        assert!(rust_code.contains("let x: i32 = 42"));
604        assert!(rust_code.contains("let y: f64 = 3.14"));
605        assert!(rust_code.contains("let msg: String = \"hello\""));
606        assert!(rust_code.contains("let result: i32 = x + 10"));
607    }
608
609    #[test]
610    fn test_ast_based_translation() {
611        let agent = TranspilerAgent::with_ast_mode();
612
613        let python_code = r#"
614def add(a, b):
615    return a + b
616"#;
617
618        let rust_code = agent.translate_python_module(python_code).unwrap();
619
620        assert!(rust_code.contains("fn add"));
621        assert!(rust_code.contains("return a + b"));
622    }
623
624    #[test]
625    fn test_feature_based_with_imports() {
626        let agent = TranspilerAgent::with_feature_mode();
627
628        let python_code = r#"
629import math
630x = math.sqrt(16)
631"#;
632
633        let rust_code = agent.translate_python_module(python_code).unwrap();
634
635        // Should have use statements for math module
636        assert!(rust_code.contains("use") || rust_code.contains("math"));
637    }
638
639    #[test]
640    fn test_feature_based_with_control_flow() {
641        let agent = TranspilerAgent::with_feature_mode();
642
643        let python_code = r#"
644x = 10
645if x > 5:
646    y = 20
647else:
648    y = 30
649"#;
650
651        let rust_code = agent.translate_python_module(python_code).unwrap();
652
653        assert!(rust_code.contains("let x: i32 = 10"));
654        assert!(rust_code.contains("if x > 5"));
655    }
656
657    #[tokio::test]
658    async fn test_ast_mode_with_source_code() {
659        let agent = TranspilerAgent::with_ast_mode();
660
661        let input = TranspilerInput {
662            typed_functions: vec![json!({
663                "name": "add",
664                "params": [
665                    {"name": "a", "rust_type": {"I32": null}},
666                    {"name": "b", "rust_type": {"I32": null}}
667                ],
668                "return_type": {"I32": null},
669                "source_code": "def add(a, b):\n    return a + b"
670            })],
671            typed_classes: vec![],
672            use_statements: vec![],
673            cargo_dependencies: vec![],
674            api_contract: json!({}),
675        };
676
677        let output = agent.execute(input).await.unwrap();
678
679        // Should use AST translation since source_code is available
680        assert!(output.rust_code.contains("fn add"));
681    }
682}