rem_verification/
convert.rs

1//! This Module is responsible for converting the .llbc files to .v files.
2//! There is the potential for it to be extended to convert to other formats
3//! also supported by AENEAS.
4//! I will need to experiment with verification to see what is most useful
5
6use std::{
7    fs::File as StdFile, // Rename the File struct to stdFile to avoid conflicts
8    io::Write,
9    path::PathBuf,
10    process::Command,
11};
12use log::{info, warn};
13
14use rem_utils::resolve_aeneas_path;
15
16// Local Modules
17use crate::error::AENEASError; // TODO implement specific error handling for this module.
18
19///============================================================================
20///------------------------------- CoQ Conversion -----------------------------
21///============================================================================
22
23/// Top level (public) method to convert the two LLBC files to CoQ files. Also
24/// ensures that we have a Primitives.v file in the same directory.
25/// The output directory is optional, and if not provided, the files will be
26/// saved to the same directory as the original LLBC file. The function returns
27/// the paths to the new CoQ files
28pub fn coq_conversion(
29    original_llbc: &PathBuf,
30    refactored_llbc: &PathBuf,
31    out_dir: &Option<PathBuf>,
32    aeneas_path: &Option<PathBuf>,
33) -> Result<(PathBuf, PathBuf), Box<dyn std::error::Error>> {
34
35    info!("Converting LLBC files to CoQ files");
36
37    // Get the path to the AENEAS binary
38    let aeneas_path: PathBuf = resolve_aeneas_path(aeneas_path)?;
39
40    // Check if the Primitives.v file exists in the directory
41    let primitives_save_path: PathBuf = out_dir.clone().unwrap_or_else(|| {
42        original_llbc
43            .parent() // Get the directory of the original_llbc file.
44            .map(|p| p.to_path_buf())
45            .unwrap_or_else(|| original_llbc.clone())
46    });
47    create_primitives_file(primitives_save_path)?;
48
49    // Convert the LLBC files to CoQ
50    let original_coq_path: PathBuf = convert_llbc_to_coq(&aeneas_path, &original_llbc, out_dir.clone())?;
51    let refactored_coq_path: PathBuf = convert_llbc_to_coq(&aeneas_path, &refactored_llbc, out_dir.clone())?;
52    Ok((original_coq_path, refactored_coq_path))
53}
54
55/// Converts a LLBC file to a CoQ file using AENEAS.
56/// Will call AENEAS as a subprocess and handle errors accordingly
57/// AENEAS is called as follows:
58/// ./<path_to_aeneas> -backend coq <"path_to_llbc"> -dest <"path_to_output">
59/// Returns the path to the new CoQ file.
60fn convert_llbc_to_coq(
61    aeneas_path: &PathBuf,
62    llbc_path: &PathBuf,
63    out_dir: Option<PathBuf>,
64) -> Result<PathBuf, Box<dyn std::error::Error>> {
65    let output_dir: PathBuf = out_dir.unwrap_or_else(|| {
66        llbc_path
67            .parent()
68            .map(|p| p.to_path_buf())
69            .unwrap_or_else(|| llbc_path.clone())
70    });
71
72    info!("Output directory: {:?}", output_dir);
73
74    // Call AENEAS
75    let output = Command::new(aeneas_path)
76        .arg("-backend")
77        .arg("coq")
78        .arg(llbc_path)
79        .arg("-dest")
80        .arg(output_dir.clone())
81        .output()?;
82    if !output.status.success() {
83        // Log the stderr and stdout from the process
84        let stderr = String::from_utf8_lossy(&output.stderr);
85        let stdout = String::from_utf8_lossy(&output.stdout);
86        warn!("AENEAS failed to convert the LLBC file to CoQ");
87        warn!("Stderr: {}", stderr);
88        warn!("Stdout: {}", stdout);
89        return Err(Box::new(AENEASError::RuntimeError));
90    }
91
92    // Convert the path from a llbc path to a CoQ path.
93    // This involves changing the extension to .v
94    // and converting the filname from (for example) `main_ref` to MainRef
95    let file_stem = llbc_path
96        .file_stem()
97        .ok_or(AENEASError::InvalidPathConversion)?
98        .to_str()
99        .unwrap();
100    let coq_file_name = convert_llbc_to_coq_filename(file_stem);
101    let mut returned_path = output_dir.join(coq_file_name);
102    returned_path.set_extension("v");
103    Ok(returned_path)
104}
105
106/// Creates the Primitives.v file if needed.
107/// Encodes the contents of `Primitives.v` directly into the executable using `include_str!`.
108/// This is done to avoid having to deal with file paths and the potential for
109/// the file to be missing.
110/// The primitives_path is the path to the Primitives.v file (determined from
111/// the config file).
112fn create_primitives_file(
113    out_dir: PathBuf, // A pathbuf must be provided here to the folder.
114) -> Result<(), Box<dyn std::error::Error>> {
115    let dest_file: PathBuf = out_dir.join("Primitives.v");
116    if !dest_file.exists(){
117        // Create the file
118        let mut file: StdFile = StdFile::create(dest_file)?;
119        file.write_all(include_str!("Primitives.v").as_bytes())?;
120    } else {
121        info!("Skipping creation of `Primitives.v` as it already exists")
122    }
123    Ok(())
124}
125
126///============================================================================
127/// ------------------------------ Fstar Conversion ---------------------------
128/// ===========================================================================
129
130
131
132/// ============================================================================
133/// ------------------------------ MISC ----------------------------------------
134/// ============================================================================
135
136/// Convert a file stem from the llbc form (e.g. "main_ref")
137/// to the Coq form (e.g. "MainRef").
138fn convert_llbc_to_coq_filename(file_stem: &str) -> String {
139    file_stem
140        .split('_')
141        .map(|s| {
142            let mut chars = s.chars();
143            // Capitalize the first letter and append the rest
144            match chars.next() {
145                None => String::new(),
146                Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
147            }
148        })
149        .collect::<String>()
150}