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}