Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/backend/compiler.rs
Line
Count
Source
1
//! Binary compilation support for Ruchy
2
//! 
3
//! This module provides functionality to compile Ruchy code to standalone binaries
4
//! via Rust compilation toolchain (rustc).
5
6
use anyhow::{Context, Result, bail};
7
use std::fs;
8
use std::path::{Path, PathBuf};
9
use std::process::Command;
10
use tempfile::TempDir;
11
12
use crate::{Parser, Transpiler};
13
14
/// Binary compilation options
15
#[derive(Debug, Clone)]
16
pub struct CompileOptions {
17
    /// Output binary path
18
    pub output: PathBuf,
19
    /// Optimization level (0-3, or 's' for size)
20
    pub opt_level: String,
21
    /// Strip debug symbols
22
    pub strip: bool,
23
    /// Static linking
24
    pub static_link: bool,
25
    /// Target triple (e.g., x86_64-unknown-linux-gnu)
26
    pub target: Option<String>,
27
    /// Additional rustc flags
28
    pub rustc_flags: Vec<String>,
29
}
30
31
impl Default for CompileOptions {
32
1
    fn default() -> Self {
33
1
        Self {
34
1
            output: PathBuf::from("a.out"),
35
1
            opt_level: "2".to_string(),
36
1
            strip: false,
37
1
            static_link: false,
38
1
            target: None,
39
1
            rustc_flags: Vec::new(),
40
1
        }
41
1
    }
42
}
43
44
/// Compile a Ruchy source file to a standalone binary
45
///
46
/// # Examples
47
///
48
/// ```no_run
49
/// use ruchy::backend::{compile_to_binary, CompileOptions};
50
/// use std::path::PathBuf;
51
///
52
/// let options = CompileOptions {
53
///     output: PathBuf::from("my_program"),
54
///     opt_level: "2".to_string(),
55
///     strip: false,
56
///     static_link: false,
57
///     target: None,
58
///     rustc_flags: Vec::new(),
59
/// };
60
/// 
61
/// let result = compile_to_binary(&PathBuf::from("program.ruchy"), &options);
62
/// ```
63
///
64
/// # Errors
65
/// 
66
/// Returns an error if:
67
/// - The source file cannot be read
68
/// - The source code fails to parse
69
/// - The transpilation fails
70
/// - The rustc compilation fails
71
0
pub fn compile_to_binary(source_path: &Path, options: &CompileOptions) -> Result<PathBuf> {
72
    // Read source file
73
0
    let source = fs::read_to_string(source_path)
74
0
        .with_context(|| format!("Failed to read source file: {}", source_path.display()))?;
75
    
76
0
    compile_source_to_binary(&source, options)
77
0
}
78
79
/// Compile Ruchy source code to a standalone binary
80
///
81
/// # Examples
82
///
83
/// ```no_run
84
/// use ruchy::backend::{compile_source_to_binary, CompileOptions};
85
/// use std::path::PathBuf;
86
///
87
/// let source = r#"
88
///     fun main() {
89
///         println("Hello, World!");
90
///     }
91
/// "#;
92
///
93
/// let options = CompileOptions::default();
94
/// let result = compile_source_to_binary(source, &options);
95
/// ```
96
///
97
/// # Errors
98
///
99
/// Returns an error if:
100
/// - The source code fails to parse
101
/// - The transpilation fails
102
/// - The working directory cannot be created
103
/// - The rustc compilation fails
104
1
pub fn compile_source_to_binary(source: &str, options: &CompileOptions) -> Result<PathBuf> {
105
    // Parse the Ruchy source
106
1
    let mut parser = Parser::new(source);
107
1
    let ast = parser.parse()
108
1
        .context("Failed to parse Ruchy source")
?0
;
109
    
110
    // Transpile to Rust
111
1
    eprintln!("DEBUG: About to call transpile_to_program");
112
1
    let mut transpiler = Transpiler::new();
113
1
    let rust_code = transpiler.transpile_to_program(&ast)
114
1
        .context("Failed to transpile to Rust")
?0
;
115
1
    eprintln!("DEBUG: transpile_to_program completed");
116
    
117
    // Create working directory for compilation
118
1
    let temp_dir = TempDir::new()
119
1
        .context("Failed to create temporary directory")
?0
;
120
    
121
    // Write Rust code to working file
122
1
    let rust_file = temp_dir.path().join("main.rs");
123
1
    let rust_code_str = rust_code.to_string();
124
    
125
    // Debug: Also write to /tmp/debug_rust_output.rs for inspection
126
1
    fs::write("/tmp/debug_rust_output.rs", &rust_code_str)
127
1
        .context("Failed to write debug Rust code")
?0
;
128
    
129
1
    fs::write(&rust_file, rust_code_str)
130
1
        .context("Failed to write Rust code to temporary file")
?0
;
131
    
132
    // Build rustc command
133
1
    let mut cmd = Command::new("rustc");
134
1
    cmd.arg(&rust_file)
135
1
        .arg("-o")
136
1
        .arg(&options.output);
137
    
138
    // Add optimization level
139
1
    cmd.arg("-C").arg(format!("opt-level={}", options.opt_level));
140
    
141
    // Add strip flag if requested
142
1
    if options.strip {
143
0
        cmd.arg("-C").arg("strip=symbols");
144
1
    }
145
    
146
    // Add static linking if requested
147
1
    if options.static_link {
148
0
        cmd.arg("-C").arg("target-feature=+crt-static");
149
1
    }
150
    
151
    // Add target if specified
152
1
    if let Some(
target0
) = &options.target {
153
0
        cmd.arg("--target").arg(target);
154
1
    }
155
    
156
    // Add additional flags
157
1
    for 
flag0
in &options.rustc_flags {
158
0
        cmd.arg(flag);
159
0
    }
160
    
161
    // Execute rustc
162
1
    let output = cmd.output()
163
1
        .context("Failed to execute rustc")
?0
;
164
    
165
1
    if !output.status.success() {
166
0
        let stderr = String::from_utf8_lossy(&output.stderr);
167
0
        bail!("Compilation failed:\n{}", stderr);
168
1
    }
169
    
170
    // Ensure the output file exists
171
1
    if !options.output.exists() {
172
0
        bail!("Expected output file not created: {}", options.output.display());
173
1
    }
174
    
175
1
    Ok(options.output.clone())
176
1
}
177
178
/// Check if rustc is available
179
///
180
/// # Examples
181
///
182
/// ```
183
/// use ruchy::backend::compiler::check_rustc_available;
184
///
185
/// if check_rustc_available().is_ok() {
186
///     println!("rustc is available");
187
/// }
188
/// ```
189
///
190
/// # Errors
191
///
192
/// Returns an error if rustc is not installed or cannot be executed
193
1
pub fn check_rustc_available() -> Result<()> {
194
1
    let output = Command::new("rustc")
195
1
        .arg("--version")
196
1
        .output()
197
1
        .context("Failed to execute rustc")
?0
;
198
    
199
1
    if !output.status.success() {
200
0
        bail!("rustc is not available. Please install Rust toolchain.");
201
1
    }
202
    
203
1
    Ok(())
204
1
}
205
206
/// Get rustc version information
207
///
208
/// # Examples
209
///
210
/// ```
211
/// use ruchy::backend::compiler::get_rustc_version;
212
///
213
/// if let Ok(version) = get_rustc_version() {
214
///     println!("rustc version: {}", version);
215
/// }
216
/// ```
217
///
218
/// # Errors
219
///
220
/// Returns an error if rustc is not available or version cannot be retrieved
221
1
pub fn get_rustc_version() -> Result<String> {
222
1
    let output = Command::new("rustc")
223
1
        .arg("--version")
224
1
        .output()
225
1
        .context("Failed to execute rustc")
?0
;
226
    
227
1
    if !output.status.success() {
228
0
        bail!("Failed to get rustc version");
229
1
    }
230
    
231
1
    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
232
1
}
233
234
#[cfg(test)]
235
mod tests {
236
    use super::*;
237
    
238
    #[test]
239
1
    fn test_check_rustc_available() {
240
        // This should pass in any environment with Rust installed
241
1
        assert!(check_rustc_available().is_ok());
242
1
    }
243
    
244
    #[test]
245
1
    fn test_get_rustc_version() {
246
1
        let version = get_rustc_version().unwrap_or_else(|_| 
"unknown"0
.
to_string0
());
247
1
        assert!(version.contains("rustc"));
248
1
    }
249
    
250
    #[test]
251
1
    fn test_compile_simple_program() {
252
1
        let source = r#"
253
1
            fun main() {
254
1
                println("Hello from compiled Ruchy!");
255
1
            }
256
1
        "#;
257
        
258
1
        let options = CompileOptions {
259
1
            output: PathBuf::from("/tmp/test_ruchy_binary"),
260
1
            ..Default::default()
261
1
        };
262
        
263
        // This might fail if the transpiler doesn't support the syntax yet
264
        // but the infrastructure should work
265
1
        let _ = compile_source_to_binary(source, &options);
266
1
    }
267
}