## Coverage Report

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