use anyhow::{Context, Result};
use log::{debug, info};
use rustpython_parser::ast;
use std::path::{Path, PathBuf};
pub struct TransformedModule {
pub module_name: String,
pub rust_code: String,
pub build_script: String,
pub cargo_toml: String,
pub build_dir: PathBuf,
}
pub fn transform_ast(ast: &ast::Suite, module_name: &str, optimize_level: u8) -> String {
info!("Transforming Python AST to Rust code");
debug!(
"Module name: {}, Optimization level: {}",
module_name, optimize_level
);
let mut rust_code = String::new();
rust_code.push_str("use pyo3::prelude::*;\n");
rust_code.push_str("use pyo3::wrap_pyfunction;\n\n");
rust_code.push_str(&format!(
"#[pymodule]\nfn {}(_py: Python, m: &PyModule) -> PyResult<()> {{\n",
module_name
));
for func in crate::parser::extract_functions(ast) {
if let ast::Stmt::FunctionDef(func_def) = func {
rust_code.push_str(&format!(
" m.add_function(wrap_pyfunction!({}, m)?)?;\n",
func_def.name
));
}
}
for class in crate::parser::extract_classes(ast) {
if let ast::Stmt::ClassDef(class_def) = class {
rust_code.push_str(&format!(" m.add_class::<{}>()?;\n", class_def.name));
}
}
rust_code.push_str(" Ok(())\n");
rust_code.push_str("}\n\n");
for func in crate::parser::extract_functions(ast) {
if let ast::Stmt::FunctionDef(func_def) = func {
rust_code.push_str(&format!(
"#[pyfunction]\nfn {}(py: Python) -> PyResult<PyObject> {{\n",
func_def.name
));
rust_code.push_str(" // Auto-generated function implementation\n");
rust_code.push_str(" Ok(py.None())\n");
rust_code.push_str("}\n\n");
}
}
for class in crate::parser::extract_classes(ast) {
if let ast::Stmt::ClassDef(class_def) = class {
rust_code.push_str(&format!("#[pyclass]\nstruct {} {{\n", class_def.name));
rust_code.push_str(" // Auto-generated class implementation\n");
rust_code.push_str("}\n\n");
rust_code.push_str(&format!("#[pymethods]\nimpl {} {{\n", class_def.name));
rust_code.push_str(" #[new]\n");
rust_code.push_str(" fn new() -> Self {\n");
rust_code.push_str(&format!(" {}{{ }}\n", class_def.name));
rust_code.push_str(" }\n");
rust_code.push_str("}\n\n");
}
}
debug!("Generated {} lines of Rust code", rust_code.lines().count());
rust_code
}
pub fn generate_cargo_toml(module_name: &str, optimize_level: u8) -> String {
let mut cargo_toml = String::new();
cargo_toml.push_str(&format!("[package]\n"));
cargo_toml.push_str(&format!("name = \"{}\"\n", module_name));
cargo_toml.push_str(&format!("version = \"0.1.0\"\n"));
cargo_toml.push_str(&format!("edition = \"2021\"\n\n"));
cargo_toml.push_str(&format!("[lib]\n"));
cargo_toml.push_str(&format!("name = \"{}\"\n", module_name));
cargo_toml.push_str(&format!("crate-type = [\"cdylib\"]\n\n"));
cargo_toml.push_str(&format!("[package.metadata.maturin]\n"));
cargo_toml.push_str(&format!("name = \"{}\"\n", module_name));
cargo_toml.push_str(&format!("binding = \"pyo3\"\n"));
cargo_toml.push_str(&format!("strip = true\n\n"));
cargo_toml.push_str(&format!("[dependencies]\n"));
cargo_toml.push_str(&format!(
"pyo3 = {{ version = \"0.19\", features = [\"extension-module\"] }}\n"
));
cargo_toml.push_str(&format!("\n[profile.release]\n"));
match optimize_level {
0 => {
cargo_toml.push_str(&format!("opt-level = 0\n"));
}
1 => {
cargo_toml.push_str(&format!("opt-level = 1\n"));
}
2 => {
cargo_toml.push_str(&format!("opt-level = 2\n"));
}
_ => {
cargo_toml.push_str(&format!("opt-level = 3\n"));
cargo_toml.push_str(&format!("lto = true\n"));
cargo_toml.push_str(&format!("codegen-units = 1\n"));
}
}
cargo_toml
}
pub fn transform_file(input_path: &Path, optimize_level: u8) -> Result<TransformedModule> {
info!("Transforming Python file: {}", input_path.display());
let ast = crate::parser::parse_file(input_path)
.with_context(|| format!("Failed to parse Python file: {}", input_path.display()))?;
let module_name = input_path
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| anyhow::anyhow!("Invalid file name"))?;
let rust_code = transform_ast(&ast, module_name, optimize_level);
let cargo_toml = generate_cargo_toml(module_name, optimize_level);
let temp_dir = tempfile::tempdir().with_context(|| "Failed to create temporary directory")?;
let build_dir = temp_dir.path().to_path_buf();
let build_script = "cargo build --release".to_string();
Ok(TransformedModule {
module_name: module_name.to_string(),
rust_code,
build_script,
cargo_toml,
build_dir,
})
}