use super::{CodegenError, CodegenResult};
use std::fs;
use std::path::Path;
use std::process::Command;
fn validate_path(path: &str) -> CodegenResult<()> {
if path.starts_with('-') {
return Err(CodegenError::LinkerError {
message: format!("Invalid path '{}': cannot start with '-'", path),
});
}
let path_obj = Path::new(path);
for component in path_obj.components() {
if component.as_os_str() == ".." {
return Err(CodegenError::LinkerError {
message: format!("Invalid path '{}': cannot contain '..'", path),
});
}
}
Ok(())
}
pub fn link_program(ir_code: &str, runtime_lib: &str, output: &str) -> CodegenResult<()> {
validate_path(runtime_lib)?;
validate_path(output)?;
let ll_file = format!("{}.ll", output);
fs::write(&ll_file, ir_code).map_err(|e| CodegenError::LinkerError {
message: format!("Failed to write {}: {}", ll_file, e),
})?;
let status = Command::new("clang")
.arg(&ll_file)
.arg(runtime_lib)
.arg("-o")
.arg(output)
.arg("-O2") .arg("-Wno-override-module") .status()
.map_err(|e| CodegenError::LinkerError {
message: format!("Failed to execute clang: {}", e),
})?;
if !status.success() {
return Err(CodegenError::LinkerError {
message: format!("clang exited with status: {}", status),
});
}
println!("Generated: {}", ll_file);
println!("Executable: {}", output);
Ok(())
}
pub fn link_program_default(ir_code: &str, output: &str) -> CodegenResult<()> {
link_program(ir_code, "runtime/libcem_runtime.a", output)
}
pub fn compile_to_object(ir_code: &str, output: &str) -> CodegenResult<()> {
validate_path(output)?;
let ll_file = format!("{}.ll", output);
fs::write(&ll_file, ir_code).map_err(|e| CodegenError::LinkerError {
message: format!("Failed to write {}: {}", ll_file, e),
})?;
let status = Command::new("clang")
.arg("-c")
.arg(&ll_file)
.arg("-o")
.arg(format!("{}.o", output))
.arg("-O2") .arg("-Wno-override-module") .status()
.map_err(|e| CodegenError::LinkerError {
message: format!("Failed to execute clang: {}", e),
})?;
if !status.success() {
return Err(CodegenError::LinkerError {
message: format!("clang exited with status: {}", status),
});
}
println!("Generated: {}", ll_file);
println!("Object file: {}.o", output);
Ok(())
}
pub fn check_clang() -> CodegenResult<String> {
let output = Command::new("clang")
.arg("--version")
.output()
.map_err(|e| CodegenError::LinkerError {
message: format!("clang not found. Please install LLVM/clang: {}", e),
})?;
let version = String::from_utf8_lossy(&output.stdout);
Ok(version.lines().next().unwrap_or("unknown").to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_clang() {
let version = check_clang().unwrap();
assert!(version.contains("clang") || version.contains("LLVM"));
}
}