knave 0.9.11

A small Lisp for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
mod formatting;
mod interrupt_signal;
mod profile;
mod rustc_errors;
mod token_alignment;

use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;

use crate::diagnostic::Diagnostics;
use crate::emit::EmittedCrate;
use crate::source::Span;
pub use formatting::{EmittedFormatting, FormattingError};
pub use interrupt_signal::InterruptSignal;
pub use profile::BuildProfile;
use rustc_errors::RustcDiagnosticMapper;

pub enum BuildError {
    Program(Diagnostics),
    Environment(String),
    Interrupted,
}

pub struct RustCompiler {
    directory: PathBuf,
    profile: BuildProfile,
}

impl RustCompiler {
    const RUSTC: &str = "rustc";
    const SOURCE_FILE: &str = "main.rs";
    const EDITION: &str = "2024";

    pub fn new(directory: PathBuf, profile: BuildProfile) -> Self {
        Self { directory, profile }
    }

    pub fn compile(
        &self,
        emitted: &EmittedCrate,
        crate_name: &str,
        output: &Path,
        entry: Span,
    ) -> Result<(), BuildError> {
        let source = self
            .write_source(emitted)
            .map_err(|error| Self::environment("write the generated Rust", &error))?;
        Self::create_output_directory(output)?;
        let result = self
            .rustc_command(crate_name, output, &source)
            .output()
            .map_err(|error| Self::environment("run rustc", &error))?;
        if result.status.success() {
            return Ok(());
        }
        if InterruptSignal::stopped(result.status) {
            return Err(BuildError::Interrupted);
        }
        let stderr = String::from_utf8_lossy(&result.stderr);
        Err(BuildError::Program(
            RustcDiagnosticMapper::new(emitted, entry).map(&stderr),
        ))
    }

    fn rustc_command(&self, crate_name: &str, output: &Path, source: &Path) -> Command {
        let mut command = Command::new(Self::RUSTC);
        command
            .args([
                "--edition",
                Self::EDITION,
                "--crate-name",
                crate_name,
                "--error-format=json",
                "--cap-lints",
                "allow",
            ])
            .arg("-o")
            .arg(output)
            .arg(source)
            .args(self.profile.rustc_arguments());
        command
    }

    fn write_source(&self, emitted: &EmittedCrate) -> io::Result<PathBuf> {
        fs::create_dir_all(&self.directory)?;
        let source = self.directory.join(Self::SOURCE_FILE);
        fs::write(&source, &emitted.code)?;
        Ok(source)
    }

    fn create_output_directory(output: &Path) -> Result<(), BuildError> {
        let Some(directory) = output.parent() else {
            return Ok(());
        };
        fs::create_dir_all(directory).map_err(|error| {
            Self::environment(&format!("create `{}`", directory.display()), &error)
        })
    }

    fn environment(action: &str, error: &io::Error) -> BuildError {
        BuildError::Environment(format!("cannot {action}: {error}"))
    }
}