1use std::{
2 path::PathBuf,
3 fs::File,
4 io::Write as ioWrite,
5 process::Command,
6 io::BufRead,
7 io::BufReader,
8 collections::HashSet,
9};
10
11use regex::Regex;
12
13use log::{
14 error,
15 info
16};
17
18pub fn coq_verification(
25 original_coq: &PathBuf,
26 refactored_coq: &PathBuf,
27 top_level_function: &String,
28) -> Result<(PathBuf, PathBuf, PathBuf, bool), Box<dyn std::error::Error>> {
29
30 let dir: PathBuf = original_coq.parent().unwrap().to_path_buf(); let coq_path: PathBuf = create_coq_project_file(
34 &original_coq,
35 &refactored_coq,
36 &dir,
37 )?;
38
39 info!("Created _CoqProject file");
40
41 let equiv_check: PathBuf = create_equiv_check_file(
43 &original_coq,
44 &refactored_coq,
45 &top_level_function,
46 &dir
47 )?;
48
49 info!("Created EquivCheck.v file");
50
51 let (result, primitives) = verify_coq_files(
53 &original_coq,
54 &refactored_coq,
55 &equiv_check,
56 &dir,
57 )?;
58
59 if result {
60 info!("CoQ verification completed successfully");
61 } else {
62 error!("CoQ verification failed");
63 }
64
65 cleanup_directory(&dir)?;
68
69 Ok((
70 coq_path,
71 equiv_check,
72 primitives,
73 result
74 ))
75}
76
77fn create_coq_project_file(
81 original_coq: &PathBuf,
82 refactored_coq: &PathBuf,
83 dir: &PathBuf,
84) -> Result<PathBuf, Box<dyn std::error::Error>> {
85 let mut coq_project = File::create(dir.join("_CoqProject"))?;
95 writeln!(coq_project, "-R . AutoEquiv")?;
96 writeln!(coq_project, "")?;
97 writeln!(coq_project, "Primitives.v")?;
98 writeln!(coq_project, "{}", original_coq.file_name().unwrap().to_str().unwrap())?;
99 writeln!(coq_project, "{}", refactored_coq.file_name().unwrap().to_str().unwrap())?;
100 writeln!(coq_project, "EquivCheck.v")?;
101
102 let coq_path = dir.join("_CoqProject");
104 Ok(coq_path)
105}
106
107fn create_equiv_check_file(
112 original_coq: &PathBuf,
113 refactored_coq: &PathBuf,
114 top_level_function: &String,
115 dir: &PathBuf,
116) -> Result<PathBuf, Box<dyn std::error::Error>> {
117 info!("Directory: {:?}", dir);
132
133 let equiv_check_path = dir.join("EquivCheck.v");
134 let mut equiv_check = File::create(&equiv_check_path)?;
135
136 writeln!(equiv_check, "(** EquivCheck.v Autogenerated by REM-verification **)")?;
138
139 writeln!(equiv_check, "Require Import Primitives.")?;
143 writeln!(equiv_check, "Import Primitives.")?;
144
145 writeln!(
148 equiv_check,
149 "Require Import {}.",
150 original_coq.file_stem().unwrap().to_str().unwrap()
151 )?;
152 writeln!(
153 equiv_check,
154 "Require Import {}.",
155 refactored_coq.file_stem().unwrap().to_str().unwrap()
156 )?;
157
158 let original_imports: Vec<String> = extract_imports(original_coq)?;
160 let refactored_imports: Vec<String> = extract_imports(refactored_coq)?;
161 let combined_imports: String = combine_imports(original_imports, refactored_imports);
162
163 if !combined_imports.is_empty() {
164 writeln!(
165 equiv_check,
166 "{}",
167 combined_imports
168 )?;
169 }
170
171 writeln!(equiv_check, "Local Open Scope Primitives_scope.")?;
172 writeln!(equiv_check, "\n")?;
173
174 let proof: String = generate_equiv_check_proof(&top_level_function, &original_coq, &refactored_coq)?;
178 writeln!(equiv_check, "{}", proof)?;
179 info!("Proof generated successfully");
180
181 Ok(dir.join("EquivCheck.v"))
182}
183
184fn extract_imports(coq_path: &PathBuf) -> Result<Vec<String>, Box<dyn std::error::Error>> {
188 let file: File = File::open(coq_path)?;
189 let reader = BufReader::new(file);
190 let mut imports: Vec<String> = Vec::new();
191
192 for line in reader.lines() {
193 let line = line?;
194 let trimmed = line.trim();
195 if trimmed.starts_with("Local Open Scope") {
197 break;
198 }
199 if trimmed.starts_with("Module") {
201 break;
202 }
203 if trimmed.starts_with("Require") || trimmed.starts_with("Import") {
205 imports.push(trimmed.to_string());
206 } else if !trimmed.is_empty() {
207 break;
209 }
210 }
211 Ok(imports)
212}
213
214fn combine_imports(imports1: Vec<String>, imports2: Vec<String>) -> String {
217 let mut set = HashSet::new();
218 for line in imports1.into_iter().chain(imports2.into_iter()) {
220 set.insert(line);
221 }
222 let mut combined: Vec<_> = set.into_iter().collect::<Vec<String>>();
224 combined.retain(|line| !line.contains("Primitives"));
226 combined.join("\n")
228}
229
230
231struct CoqArgument {
232 name: String,
233 ty: String,
234}
235
236fn generate_equiv_check_proof(
238 top_level_function: &str,
239 original_coq: &PathBuf,
240 refactored_coq: &PathBuf,
241) -> Result<String, Box<dyn std::error::Error>> {
242 let content = std::fs::read_to_string(original_coq)?;
244
245
246 let def_pattern = format!(r"Definition\s+{}\s+((?:\([^)]*\)\s*)+):", top_level_function);
250 let def_re = Regex::new(&def_pattern)?;
251
252 let caps = def_re.captures(&content)
254 .ok_or("Could not find the function definition in the original Coq file")?;
255 let args_str = caps.get(1).unwrap().as_str();
256
257 let arg_re = Regex::new(r"\(\s*([^:\s]+)\s*:\s*([^)]+)\s*\)")?;
259 let mut args: Vec<CoqArgument> = Vec::new();
260 for cap in arg_re.captures_iter(args_str) {
261 let name = cap.get(1).unwrap().as_str().trim().to_string();
262 let ty = cap.get(2).unwrap().as_str().trim().to_string();
263 args.push(CoqArgument { name, ty });
264 }
265
266 let forall_part = args.iter()
268 .map(|arg| format!("({} : {})", arg.name, arg.ty))
269 .collect::<Vec<_>>()
270 .join(" ");
271 let args_names = args.iter()
272 .map(|arg| arg.name.clone())
273 .collect::<Vec<_>>()
274 .join(" ");
275
276 let lemma_name = format!("{}_equiv_check", top_level_function);
278
279 let original_module = original_coq
281 .file_stem()
282 .unwrap()
283 .to_str()
284 .unwrap();
285 let refactored_module = refactored_coq
286 .file_stem()
287 .unwrap()
288 .to_str()
289 .unwrap();
290
291 let proof = format!(
293r"Lemma {} : forall {},
294 {}.{} {} = {}.{} {}.
295Proof.
296 intros {}.
297 unfold {}.{}.
298 unfold {}.{}.
299 reflexivity.
300Qed.",
301 lemma_name, forall_part, original_module, top_level_function, args_names, refactored_module, top_level_function, args_names, args_names, original_module, top_level_function, refactored_module, top_level_function, );
308
309 info!("Proof script:\n{}", proof);
311
312 Ok(proof)
313}
314
315
316fn verify_coq_files(
329 original_coq: &PathBuf,
330 refactored_coq: &PathBuf,
331 equiv_check: &PathBuf,
332 dir: &PathBuf, ) -> Result<(bool, PathBuf), Box<dyn std::error::Error>> {
334 let dir_str = dir.to_str().ok_or("Invalid directory path")?;
336
337 info!("Directory: {}", dir_str);
338
339 let primitives: PathBuf = dir.join("Primitives.v");
341 let primitives_coq_output = Command::new("coqc")
342 .arg("-R")
343 .arg(dir_str)
344 .arg("Primitives")
345 .arg(&primitives)
346 .output()?;
347 if !primitives_coq_output.status.success() {
348 let stdout = String::from_utf8_lossy(&primitives_coq_output.stdout);
349 let stderr = String::from_utf8_lossy(&primitives_coq_output.stderr);
350 error!("Primitives.v failed to compile:\nstdout: {}\nstderr: {}", stdout, stderr);
351 return Ok((false, primitives));
352 }
353 info!("Primitives.v compiled successfully");
354
355 let original_coq_output = Command::new("coqc")
357 .arg("-R")
358 .arg(dir_str)
359 .arg("Primitives")
360 .arg(&original_coq)
361 .output()?;
362 if !original_coq_output.status.success() {
363 let stdout = String::from_utf8_lossy(&original_coq_output.stdout);
364 let stderr = String::from_utf8_lossy(&original_coq_output.stderr);
365 error!("Original Coq file failed to compile:\nstdout: {}\nstderr: {}", stdout, stderr);
366 return Ok((false, primitives));
367 }
368 info!("Original Coq file compiled successfully");
369
370 let refactored_coq_output = Command::new("coqc")
372 .arg("-R")
373 .arg(dir_str)
374 .arg("Primitives")
375 .arg(&refactored_coq)
376 .output()?;
377 if !refactored_coq_output.status.success() {
378 let stdout = String::from_utf8_lossy(&refactored_coq_output.stdout);
379 let stderr = String::from_utf8_lossy(&refactored_coq_output.stderr);
380 error!("Refactored Coq file failed to compile:\nstdout: {}\nstderr: {}", stdout, stderr);
381 return Ok((false, primitives));
382 }
383 info!("Refactored Coq file compiled successfully");
384
385 let equiv_check_output = Command::new("coqc")
387 .arg("-R")
388 .arg(dir_str)
389 .arg("Primitives")
390 .arg(&equiv_check)
391 .output()?;
392 if !equiv_check_output.status.success() {
393 let stdout = String::from_utf8_lossy(&equiv_check_output.stdout);
394 let stderr = String::from_utf8_lossy(&equiv_check_output.stderr);
395 error!("EquivCheck.v failed to compile:\nstdout: {}\nstderr: {}", stdout, stderr);
396 return Ok((false, primitives));
397 }
398 info!("EquivCheck.v compiled successfully");
399
400 Ok((true, primitives))
401}
402
403fn cleanup_directory(dir: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
408 let files = std::fs::read_dir(dir)?;
409 for file in files {
410 let file = file?;
411 let path = file.path();
412 let extension = path.extension();
413 if let Some(extension) = extension {
414 if extension == "glob"
415 || extension == "vo"
416 || extension == "vok"
417 || extension == "vos"
418 || extension == "aux"
419 || extension == "cache"
420 {
421 std::fs::remove_file(path)?;
422 }
423 }
424 }
425
426 Ok(())
427}