use crate::utils::common_patterns::ResultContextExt;
use crate::{Parser, Transpiler};
use anyhow::{bail, Context, Result};
use proc_macro2::TokenStream;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
#[derive(Debug, Clone)]
pub struct CompileOptions {
pub output: PathBuf,
pub opt_level: String,
pub strip: bool,
pub static_link: bool,
pub target: Option<String>,
pub rustc_flags: Vec<String>,
pub embed_models: Vec<PathBuf>,
}
impl Default for CompileOptions {
fn default() -> Self {
Self {
output: PathBuf::from("a.out"),
opt_level: "2".to_string(),
strip: false,
static_link: false,
target: None,
rustc_flags: Vec::new(),
embed_models: Vec::new(),
}
}
}
pub fn compile_to_binary(source_path: &Path, options: &CompileOptions) -> Result<PathBuf> {
let source = fs::read_to_string(source_path).file_context("read", source_path)?;
compile_source_to_binary_with_context(&source, options, Some(source_path))
}
pub fn compile_source_to_binary(source: &str, options: &CompileOptions) -> Result<PathBuf> {
compile_source_to_binary_with_context(source, options, None)
}
pub fn compile_source_to_binary_with_context(
source: &str,
options: &CompileOptions,
source_path: Option<&Path>,
) -> Result<PathBuf> {
let mut parser = Parser::new(source);
let ast = parser.parse().parse_context("Ruchy source")?;
let needs_polars = uses_dataframes(&ast);
let needs_json = uses_json(&ast);
let needs_http = uses_http(&ast);
let resolved_ast = if let Some(path) = source_path {
if contains_module_declaration(&ast) {
use crate::backend::module_resolver::ModuleResolver;
let mut resolver = ModuleResolver::new();
if let Some(parent_dir) = path.parent() {
resolver.add_search_path(parent_dir);
if let Some(project_root) = parent_dir.parent() {
resolver.add_search_path(project_root.join("src"));
resolver.add_search_path(project_root.join("lib"));
resolver.add_search_path(project_root.join("modules"));
}
}
resolver
.resolve_imports(ast)
.compile_context("resolve module declarations")?
} else {
ast
}
} else {
ast
};
let mut transpiler = Transpiler::new();
let rust_code = transpiler
.transpile_to_program_with_context(&resolved_ast, source_path)
.compile_context("transpile to Rust")?;
if needs_polars || needs_json || needs_http {
compile_with_cargo(&rust_code, options)
} else {
compile_with_rustc(&rust_code, options)
}
}
fn parse_and_transpile(source: &str) -> Result<TokenStream> {
let mut parser = Parser::new(source);
let ast = parser.parse().parse_context("Ruchy source")?;
let mut transpiler = Transpiler::new();
let rust_code = transpiler
.transpile_to_program(&ast)
.compile_context("transpile to Rust")?;
Ok(rust_code)
}
pub fn uses_dataframes(ast: &crate::frontend::ast::Expr) -> bool {
use crate::frontend::ast::ExprKind;
match &ast.kind {
ExprKind::DataFrame { .. } | ExprKind::DataFrameOperation { .. } => true,
ExprKind::Binary { left, right, .. } => check_binary_for_dataframes(left, right),
ExprKind::Let { value, body, .. } => check_binary_for_dataframes(value, body),
ExprKind::MethodCall { receiver, args, .. } => check_method_for_dataframes(receiver, args),
ExprKind::Call { func, args } => check_call_for_dataframes(func, args),
ExprKind::Function { body, .. } => uses_dataframes(body),
ExprKind::Block(exprs) => exprs.iter().any(uses_dataframes),
_ => false,
}
}
fn check_binary_for_dataframes(
left: &crate::frontend::ast::Expr,
right: &crate::frontend::ast::Expr,
) -> bool {
uses_dataframes(left) || uses_dataframes(right)
}
fn check_method_for_dataframes(
receiver: &crate::frontend::ast::Expr,
args: &[crate::frontend::ast::Expr],
) -> bool {
uses_dataframes(receiver) || args.iter().any(uses_dataframes)
}
fn check_call_for_dataframes(
func: &crate::frontend::ast::Expr,
args: &[crate::frontend::ast::Expr],
) -> bool {
uses_dataframes(func) || args.iter().any(uses_dataframes)
}
pub fn uses_json(ast: &crate::frontend::ast::Expr) -> bool {
use crate::frontend::ast::ExprKind;
match &ast.kind {
ExprKind::Call { func, args } => check_call_for_json(func, args),
ExprKind::Binary { left, right, .. } => check_binary_for_json(left, right),
ExprKind::Let { value, body, .. } => check_binary_for_json(value, body),
ExprKind::MethodCall { receiver, args, .. } => check_method_for_json(receiver, args),
ExprKind::Function { body, .. } => uses_json(body),
ExprKind::Block(exprs) => exprs.iter().any(uses_json),
_ => false,
}
}
fn check_call_for_json(
func: &crate::frontend::ast::Expr,
args: &[crate::frontend::ast::Expr],
) -> bool {
use crate::frontend::ast::ExprKind;
if let ExprKind::Identifier(name) = &func.kind {
if is_json_function(name) {
return true;
}
}
uses_json(func) || args.iter().any(uses_json)
}
fn check_binary_for_json(
left: &crate::frontend::ast::Expr,
right: &crate::frontend::ast::Expr,
) -> bool {
uses_json(left) || uses_json(right)
}
fn check_method_for_json(
receiver: &crate::frontend::ast::Expr,
args: &[crate::frontend::ast::Expr],
) -> bool {
uses_json(receiver) || args.iter().any(uses_json)
}
fn is_json_function(name: &str) -> bool {
matches!(
name,
"json_parse"
| "json_stringify"
| "json_pretty"
| "json_read"
| "json_write"
| "json_validate"
| "json_type"
| "json_merge"
| "json_get"
| "json_set"
)
}
pub fn uses_http(ast: &crate::frontend::ast::Expr) -> bool {
use crate::frontend::ast::ExprKind;
match &ast.kind {
ExprKind::Call { func, args } => check_call_for_http(func, args),
ExprKind::Binary { left, right, .. } => check_binary_for_http(left, right),
ExprKind::Let { value, body, .. } => check_binary_for_http(value, body),
ExprKind::MethodCall { receiver, args, .. } => check_method_for_http(receiver, args),
ExprKind::Function { body, .. } => uses_http(body),
ExprKind::Block(exprs) => exprs.iter().any(uses_http),
_ => false,
}
}
fn check_call_for_http(
func: &crate::frontend::ast::Expr,
args: &[crate::frontend::ast::Expr],
) -> bool {
use crate::frontend::ast::ExprKind;
if let ExprKind::Identifier(name) = &func.kind {
if is_http_function(name) {
return true;
}
}
uses_http(func) || args.iter().any(uses_http)
}
fn check_binary_for_http(
left: &crate::frontend::ast::Expr,
right: &crate::frontend::ast::Expr,
) -> bool {
uses_http(left) || uses_http(right)
}
fn check_method_for_http(
receiver: &crate::frontend::ast::Expr,
args: &[crate::frontend::ast::Expr],
) -> bool {
uses_http(receiver) || args.iter().any(uses_http)
}
fn is_http_function(name: &str) -> bool {
matches!(name, "http_get" | "http_post" | "http_put" | "http_delete")
}
fn contains_module_declaration(ast: &crate::frontend::ast::Expr) -> bool {
use crate::frontend::ast::ExprKind;
fn check_expr(expr: &crate::frontend::ast::Expr) -> bool {
match &expr.kind {
ExprKind::ModuleDeclaration { .. } => true,
ExprKind::Block(exprs) => exprs.iter().any(check_expr),
ExprKind::Function { body, .. } => check_expr(body),
ExprKind::Let { value, body, .. } => check_expr(value) || check_expr(body),
_ => false,
}
}
check_expr(ast)
}
fn generate_cargo_toml(binary_name: &str) -> String {
format!(
r#"[package]
name = "{binary_name}"
version = "0.1.0"
edition = "2021"
[dependencies]
polars = {{ version = "0.35", features = ["lazy"] }}
serde = {{ version = "1.0", features = ["derive"] }}
serde_json = "1.0"
reqwest = {{ version = "0.12", features = ["blocking"] }}
"#
)
}
fn compile_with_cargo(rust_code: &TokenStream, options: &CompileOptions) -> Result<PathBuf> {
let temp_dir = TempDir::new().compile_context("create temporary directory")?;
let project_dir = temp_dir.path();
let src_dir = project_dir.join("src");
fs::create_dir(&src_dir).context("Failed to create src directory")?;
let rust_code_str = rust_code.to_string();
let final_code = if options.embed_models.is_empty() {
rust_code_str
} else {
generate_model_embedding_code(&rust_code_str, &options.embed_models, &src_dir)?
};
let main_file = src_dir.join("main.rs");
fs::write(&main_file, &final_code)?;
let cargo_toml = project_dir.join("Cargo.toml");
let cargo_content = generate_cargo_toml("ruchy_binary");
fs::write(&cargo_toml, cargo_content)?;
let mut cmd = Command::new("cargo");
cmd.arg("build").arg("--release").current_dir(project_dir);
let output = cmd.output().context("Failed to execute cargo build")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("Cargo build failed:\n{stderr}");
}
let compiled_binary = project_dir.join("target/release/ruchy_binary");
if !compiled_binary.exists() {
bail!("Expected binary not found after cargo build");
}
fs::copy(&compiled_binary, &options.output)
.context("Failed to copy compiled binary to output location")?;
Ok(options.output.clone())
}
fn compile_with_rustc(rust_code: &TokenStream, options: &CompileOptions) -> Result<PathBuf> {
let (_temp_dir, rust_file) = prepare_rust_file(rust_code, options)?;
let cmd = build_rustc_command(&rust_file, options);
execute_compilation(cmd)?;
verify_output_exists(&options.output)?;
Ok(options.output.clone())
}
fn prepare_rust_file(
rust_code: &TokenStream,
options: &CompileOptions,
) -> Result<(TempDir, PathBuf)> {
let temp_dir = TempDir::new().compile_context("create temporary directory")?;
let rust_file = temp_dir.path().join("main.rs");
let rust_code_str = rust_code.to_string();
let final_code = if options.embed_models.is_empty() {
rust_code_str
} else {
generate_model_embedding_code(&rust_code_str, &options.embed_models, temp_dir.path())?
};
fs::write("/tmp/debug_rust_output.rs", &final_code)
.context("Failed to write debug Rust code")?;
fs::write(&rust_file, &final_code).context("Failed to write Rust code to temporary file")?;
Ok((temp_dir, rust_file))
}
fn generate_model_embedding_code(
rust_code: &str,
embed_models: &[PathBuf],
temp_dir: &Path,
) -> Result<String> {
let mut model_statics = String::new();
let mut model_loader_fn = String::from(
"\n/// Get embedded model bytes by filename\n\
#[allow(dead_code)]\n\
pub fn get_embedded_model(name: &str) -> Option<&'static [u8]> {\n\
match name {\n",
);
for (i, model_path) in embed_models.iter().enumerate() {
if !model_path.exists() {
bail!("Embedded model file not found: {}", model_path.display());
}
let model_filename = model_path
.file_name()
.ok_or_else(|| anyhow::anyhow!("Invalid model path: {}", model_path.display()))?
.to_string_lossy();
let temp_model_path = temp_dir.join(&*model_filename);
fs::copy(model_path, &temp_model_path)
.with_context(|| format!("Failed to copy model: {}", model_path.display()))?;
let var_name = sanitize_model_name(&model_filename);
model_statics.push_str(&format!(
"/// Embedded model: {model_filename}\n\
static MODEL_{var_name}: &[u8] = include_bytes!(\"{model_filename}\");\n\n",
));
model_loader_fn.push_str(&format!(
" \"{model_filename}\" => Some(MODEL_{var_name}),\n",
));
model_loader_fn.push_str(&format!(" \"model_{i}\" => Some(MODEL_{var_name}),\n"));
}
model_loader_fn.push_str(" _ => None,\n}\n}\n");
Ok(format!(
"// === Embedded Models (ruchy compile --embed-model) ===\n\
{model_statics}\n\
{model_loader_fn}\n\
// === End Embedded Models ===\n\n\
{rust_code}",
))
}
fn sanitize_model_name(filename: &str) -> String {
filename
.chars()
.map(|c| {
if c.is_alphanumeric() {
c.to_ascii_uppercase()
} else {
'_'
}
})
.collect()
}
fn build_rustc_command(rust_file: &Path, options: &CompileOptions) -> Command {
let mut cmd = Command::new("rustc");
cmd.arg(rust_file).arg("-o").arg(&options.output);
cmd.arg("--edition").arg("2021");
cmd.arg("-C")
.arg(format!("opt-level={}", options.opt_level));
apply_optional_flags(&mut cmd, options);
cmd
}
fn apply_optional_flags(cmd: &mut Command, options: &CompileOptions) {
if options.strip {
cmd.arg("-C").arg("strip=symbols");
}
if options.static_link {
cmd.arg("-C").arg("target-feature=+crt-static");
}
if let Some(target) = &options.target {
cmd.arg("--target").arg(target);
}
for flag in &options.rustc_flags {
cmd.arg(flag);
}
}
fn execute_compilation(mut cmd: Command) -> Result<()> {
let output = cmd.output().context("Failed to execute rustc")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("Compilation failed:\n{stderr}");
}
Ok(())
}
fn verify_output_exists(output_path: &Path) -> Result<()> {
if !output_path.exists() {
bail!(
"Expected output file not created: {}",
output_path.display()
);
}
Ok(())
}
pub fn check_rustc_available() -> Result<()> {
if try_rustc_command("rustc").is_ok() {
return Ok(());
}
let fallback_paths = [
format!(
"{}/.cargo/bin/rustc",
std::env::var("HOME").unwrap_or_default()
),
"/usr/local/bin/rustc".to_string(),
"/usr/bin/rustc".to_string(),
];
for path in &fallback_paths {
if try_rustc_command(path).is_ok() {
return Ok(());
}
}
bail!("rustc is not available. Please install Rust toolchain.")
}
fn try_rustc_command(rustc_path: &str) -> Result<()> {
let output = Command::new(rustc_path).arg("--version").output();
match output {
Ok(output) if output.status.success() => Ok(()),
Ok(output) => {
bail!(
"rustc at '{}' failed with exit code {:?}",
rustc_path,
output.status.code()
)
}
Err(e) => {
bail!("Could not execute rustc at '{rustc_path}': {e}")
}
}
}
pub fn get_rustc_version() -> Result<String> {
let output = Command::new("rustc")
.arg("--version")
.output()
.context("Failed to execute rustc")?;
if !output.status.success() {
bail!("Failed to get rustc version");
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_rustc_available() {
assert!(check_rustc_available().is_ok());
}
#[test]
fn test_get_rustc_version() {
let version = get_rustc_version().unwrap_or_else(|_| "unknown".to_string());
assert!(version.contains("rustc"));
}
#[test]
fn test_compile_simple_program() {
let source = r#"
fun main() {
println("Hello from compiled Ruchy!");
}
"#;
let options = CompileOptions {
output: PathBuf::from("/tmp/test_ruchy_binary"),
..Default::default()
};
let _ = compile_source_to_binary(source, &options);
}
#[test]
fn test_compile_options_default() {
let options = CompileOptions::default();
assert_eq!(options.output, PathBuf::from("a.out"));
assert_eq!(options.opt_level, "2");
assert!(!options.strip);
assert!(!options.static_link);
assert!(options.target.is_none());
assert!(options.rustc_flags.is_empty());
assert!(options.embed_models.is_empty());
}
#[test]
fn test_compile_options_custom() {
let options = CompileOptions {
output: PathBuf::from("my_binary"),
opt_level: "3".to_string(),
strip: true,
static_link: true,
target: Some("x86_64-unknown-linux-musl".to_string()),
rustc_flags: vec!["-C".to_string(), "lto=fat".to_string()],
embed_models: Vec::new(),
};
assert_eq!(options.output, PathBuf::from("my_binary"));
assert_eq!(options.opt_level, "3");
assert!(options.strip);
assert!(options.static_link);
assert_eq!(
options.target,
Some("x86_64-unknown-linux-musl".to_string())
);
assert_eq!(options.rustc_flags.len(), 2);
}
#[test]
fn test_sanitize_model_name() {
assert_eq!(
sanitize_model_name("model.safetensors"),
"MODEL_SAFETENSORS"
);
assert_eq!(sanitize_model_name("my-model_v2.gguf"), "MY_MODEL_V2_GGUF");
assert_eq!(sanitize_model_name("123_numbers.bin"), "123_NUMBERS_BIN");
}
#[test]
fn test_generate_model_embedding_code() {
use tempfile::TempDir;
let temp_dir = TempDir::new().expect("create temp dir");
let model_path = temp_dir.path().join("test_model.bin");
fs::write(&model_path, b"fake model data").expect("write model");
let rust_code = "fn main() { println!(\"hello\"); }";
let result = generate_model_embedding_code(rust_code, &[model_path], temp_dir.path());
assert!(result.is_ok());
let code = result.expect("model embedding code");
assert!(code.contains("include_bytes!"));
assert!(code.contains("MODEL_TEST_MODEL_BIN"));
assert!(code.contains("get_embedded_model"));
assert!(code.contains("test_model.bin"));
assert!(code.contains("fn main()"));
}
#[test]
fn test_generate_model_embedding_multiple_models() {
use tempfile::TempDir;
let temp_dir = TempDir::new().expect("create temp dir");
let model1 = temp_dir.path().join("model_a.bin");
let model2 = temp_dir.path().join("model_b.bin");
fs::write(&model1, b"model a data").expect("write model a");
fs::write(&model2, b"model b data").expect("write model b");
let rust_code = "fn main() {}";
let result = generate_model_embedding_code(rust_code, &[model1, model2], temp_dir.path());
assert!(result.is_ok());
let code = result.expect("model embedding code");
assert!(code.contains("MODEL_MODEL_A_BIN"));
assert!(code.contains("MODEL_MODEL_B_BIN"));
assert!(code.contains("\"model_a.bin\""));
assert!(code.contains("\"model_b.bin\""));
assert!(code.contains("\"model_0\""));
assert!(code.contains("\"model_1\""));
}
#[test]
fn test_generate_model_embedding_missing_file() {
use tempfile::TempDir;
let temp_dir = TempDir::new().expect("create temp dir");
let missing_model = temp_dir.path().join("nonexistent.bin");
let result =
generate_model_embedding_code("fn main() {}", &[missing_model], temp_dir.path());
assert!(result.is_err());
let err = result.expect_err("should fail");
assert!(err.to_string().contains("not found"));
}
#[test]
fn test_build_rustc_command() {
let rust_file = Path::new("/tmp/test.rs");
let options = CompileOptions {
opt_level: "2".to_string(),
strip: true,
..Default::default()
};
let _cmd = build_rustc_command(rust_file, &options);
}
#[test]
fn test_apply_optional_flags() {
let mut cmd = Command::new("rustc");
let options = CompileOptions {
strip: true,
static_link: true,
target: Some("x86_64-unknown-linux-musl".to_string()),
rustc_flags: vec!["-C".to_string(), "lto=fat".to_string()],
..Default::default()
};
apply_optional_flags(&mut cmd, &options);
}
#[test]
fn test_prepare_rust_file() {
let rust_code = TokenStream::new();
let options = CompileOptions::default();
let result = prepare_rust_file(&rust_code, &options);
assert!(result.is_ok());
if let Ok((_temp_dir, rust_file)) = result {
assert!(rust_file.exists());
assert!(rust_file.extension() == Some(std::ffi::OsStr::new("rs")));
}
}
#[test]
fn test_parse_and_transpile() {
let source = "fun main() { println(\"Hello\"); }";
let result = parse_and_transpile(source);
let _ = result; }
#[test]
fn test_execute_compilation() {
let mut cmd = Command::new("rustc");
cmd.arg("/non/existent/file.rs");
let result = execute_compilation(cmd);
assert!(result.is_err());
}
#[test]
fn test_verify_output_exists() {
let result = verify_output_exists(Path::new("/non/existent/binary"));
assert!(result.is_err());
let temp_file = tempfile::NamedTempFile::new().expect("operation should succeed in test");
let result = verify_output_exists(temp_file.path());
assert!(result.is_ok());
}
#[test]
fn test_compile_invalid_source() {
let source = "this is not valid Ruchy code!@#$%";
let options = CompileOptions::default();
let result = compile_source_to_binary(source, &options);
assert!(result.is_err());
}
#[test]
fn test_compile_empty_source() {
let source = "";
let options = CompileOptions::default();
let result = compile_source_to_binary(source, &options);
let _ = result; }
#[test]
fn test_compile_whitespace_only() {
let source = " \n\t\n ";
let options = CompileOptions::default();
let result = compile_source_to_binary(source, &options);
let _ = result; }
#[test]
fn test_compile_options_builder_pattern() {
let mut options = CompileOptions::default();
options.output = PathBuf::from("custom_binary");
options.opt_level = "3".to_string();
options.strip = true;
options.rustc_flags.push("--verbose".to_string());
assert_eq!(options.output, PathBuf::from("custom_binary"));
assert_eq!(options.opt_level, "3");
assert!(options.strip);
assert_eq!(options.rustc_flags.len(), 1);
assert_eq!(options.rustc_flags[0], "--verbose");
}
#[test]
fn test_all_optimization_levels() {
let valid_levels = vec!["0", "1", "2", "3", "s", "z"];
for level in valid_levels {
let options = CompileOptions {
opt_level: level.to_string(),
..Default::default()
};
assert_eq!(options.opt_level, level);
let rust_file = Path::new("/tmp/test.rs");
let cmd = build_rustc_command(rust_file, &options);
assert_eq!(cmd.get_program(), "rustc");
}
}
#[test]
fn test_target_triple_combinations() {
let targets = vec![
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-pc-windows-msvc",
"x86_64-apple-darwin",
"aarch64-unknown-linux-gnu",
"wasm32-unknown-unknown",
];
for target in targets {
let options = CompileOptions {
target: Some(target.to_string()),
..Default::default()
};
assert_eq!(options.target, Some(target.to_string()));
let rust_file = Path::new("/tmp/test.rs");
let cmd = build_rustc_command(rust_file, &options);
assert_eq!(cmd.get_program(), "rustc");
}
}
#[test]
fn test_multiple_rustc_flags() {
let flags = vec![
"-C".to_string(),
"lto=fat".to_string(),
"--verbose".to_string(),
"-Z".to_string(),
"print-type-sizes".to_string(),
];
let options = CompileOptions {
rustc_flags: flags.clone(),
..Default::default()
};
assert_eq!(options.rustc_flags.len(), 5);
assert_eq!(options.rustc_flags, flags);
let mut cmd = Command::new("rustc");
apply_optional_flags(&mut cmd, &options);
}
#[test]
fn test_strip_and_static_combinations() {
let combinations = vec![(false, false), (true, false), (false, true), (true, true)];
for (strip, static_link) in combinations {
let options = CompileOptions {
strip,
static_link,
..Default::default()
};
assert_eq!(options.strip, strip);
assert_eq!(options.static_link, static_link);
let mut cmd = Command::new("rustc");
apply_optional_flags(&mut cmd, &options);
}
}
#[test]
fn test_path_handling_extensions() {
let paths = vec![
PathBuf::from("binary"),
PathBuf::from("program.exe"),
PathBuf::from("/tmp/output"),
PathBuf::from("./relative/path"),
PathBuf::from("../parent/binary"),
];
for path in paths {
let options = CompileOptions {
output: path.clone(),
..Default::default()
};
assert_eq!(options.output, path);
let rust_file = Path::new("/tmp/test.rs");
let cmd = build_rustc_command(rust_file, &options);
assert_eq!(cmd.get_program(), "rustc");
}
}
#[test]
fn test_temp_file_creation_cleanup() {
let rust_code = TokenStream::new();
let options = CompileOptions::default();
for _i in 0..5 {
let result = prepare_rust_file(&rust_code, &options);
assert!(result.is_ok());
if let Ok((_temp_dir, rust_file)) = result {
assert!(rust_file.exists());
assert!(
rust_file
.file_name()
.expect("operation should succeed in test")
== "main.rs"
);
assert!(rust_file
.parent()
.expect("operation should succeed in test")
.exists());
}
}
}
#[test]
fn test_execute_compilation_error_messages() {
let mut cmd = Command::new("rustc");
cmd.arg("--invalid-flag-that-does-not-exist");
cmd.arg("/non/existent/file.rs");
let result = execute_compilation(cmd);
assert!(result.is_err());
let error_msg = format!("{}", result.expect_err("operation should fail in test"));
assert!(error_msg.contains("Compilation failed"));
}
#[test]
fn test_complex_source_patterns() {
let complex_sources = vec![
"fun main() { println(\"Hello 世界! 🚀\"); }",
"fun this_is_a_very_long_function_name_that_might_cause_issues() { }",
"fun main() { if (true) { if (false) { println(\"nested\"); } } }",
"// Comment\nfun main() {\n // Another comment\n println(\"test\");\n}",
"fun main() { println(\"Line 1\\nLine 2\\tTabbed\"); }",
];
for source in complex_sources {
let options = CompileOptions {
output: PathBuf::from("/tmp/complex_test"),
..Default::default()
};
let result = compile_source_to_binary(source, &options);
if let Ok(_) = result {
}
}
}
#[test]
fn test_parse_and_transpile_error_handling() {
let invalid_sources = vec![
"{{{[[[@#$%", "fun(", "\"unterminated string", ];
for source in invalid_sources {
let result = compile_source_to_binary(source, &CompileOptions::default());
assert!(result.is_err(), "Expected error for source: '{source}'");
}
}
#[test]
fn test_file_io_edge_cases() {
let empty_tokens = TokenStream::new();
let options = CompileOptions::default();
let result = prepare_rust_file(&empty_tokens, &options);
assert!(result.is_ok());
if let Ok((_temp_dir, rust_file)) = result {
assert!(rust_file.exists());
let contents =
std::fs::read_to_string(&rust_file).expect("operation should succeed in test");
assert!(contents.is_empty());
}
}
#[test]
fn test_verify_output_scenarios() {
let temp_file = tempfile::NamedTempFile::new().expect("operation should succeed in test");
let result = verify_output_exists(temp_file.path());
assert!(result.is_ok());
let temp_dir = tempfile::TempDir::new().expect("operation should succeed in test");
let result = verify_output_exists(temp_dir.path());
assert!(result.is_ok());
let nested_path = Path::new("/non/existent/directory/binary");
let result = verify_output_exists(nested_path);
assert!(result.is_err());
}
#[test]
fn test_command_building_extreme_cases() {
let options = CompileOptions {
output: PathBuf::from("/very/long/path/with/many/segments/binary"),
opt_level: "z".to_string(), strip: true,
static_link: true,
target: Some("wasm32-unknown-unknown".to_string()),
rustc_flags: vec![
"-C".to_string(),
"lto=fat".to_string(),
"-C".to_string(),
"codegen-units=1".to_string(),
"-C".to_string(),
"panic=abort".to_string(),
],
embed_models: Vec::new(),
};
let rust_file = Path::new("/tmp/test.rs");
let cmd = build_rustc_command(rust_file, &options);
assert_eq!(cmd.get_program(), "rustc");
}
#[test]
fn test_compile_options_traits() {
let options = CompileOptions {
output: PathBuf::from("test_binary"),
opt_level: "2".to_string(),
strip: true,
static_link: false,
target: Some("x86_64-unknown-linux-gnu".to_string()),
rustc_flags: vec!["--verbose".to_string()],
embed_models: Vec::new(),
};
let cloned_options = options.clone();
assert_eq!(options.output, cloned_options.output);
assert_eq!(options.opt_level, cloned_options.opt_level);
assert_eq!(options.strip, cloned_options.strip);
assert_eq!(options.static_link, cloned_options.static_link);
assert_eq!(options.target, cloned_options.target);
assert_eq!(options.rustc_flags, cloned_options.rustc_flags);
let debug_str = format!("{options:?}");
assert!(debug_str.contains("CompileOptions"));
assert!(debug_str.contains("test_binary"));
assert!(debug_str.contains('2'));
}
#[test]
fn test_filesystem_integration() {
use std::fs;
let temp_dir = tempfile::TempDir::new().expect("operation should succeed in test");
let source_file = temp_dir.path().join("test_program.ruchy");
let source_content = "fun main() { println(\"Integration test\"); }";
fs::write(&source_file, source_content).expect("operation should succeed in test");
assert!(source_file.exists());
let read_content =
fs::read_to_string(&source_file).expect("operation should succeed in test");
assert_eq!(read_content, source_content);
let output_path = temp_dir.path().join("test_output");
let options = CompileOptions {
output: output_path,
..Default::default()
};
let result = compile_to_binary(&source_file, &options);
if let Ok(_) = result {
}
}
#[test]
fn test_rustc_version_parsing() {
if let Ok(version) = get_rustc_version() {
assert!(version.contains("rustc"));
let has_version_pattern = version.split_whitespace().any(|part| {
part.split('.').count() >= 2 && part.chars().any(|c| c.is_ascii_digit())
});
assert!(has_version_pattern);
}
}
#[test]
fn test_error_context_propagation() {
let invalid_source = "syntax error @#$%";
let result = parse_and_transpile(invalid_source);
if let Err(error) = result {
let error_str = format!("{error}");
assert!(!error_str.is_empty()); }
let invalid_path = Path::new("/root/no_permission/file.ruchy");
let options = CompileOptions::default();
if invalid_path.exists() {
let result = compile_to_binary(invalid_path, &options);
assert!(result.is_err());
}
}
}
#[cfg(test)]
mod property_tests_compiler {
use super::*;
use proptest::proptest;
proptest! {
#[test]
fn test_compile_source_to_binary_never_panics(input: String) {
let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
let result = std::panic::catch_unwind(|| {
let options = CompileOptions::default();
let _ = compile_source_to_binary(&input, &options);
});
assert!(result.is_ok(), "compile_source_to_binary panicked on input: {input:?}");
}
}
}