1use crate::check::diagnose;
2
3use voltaic_diag::Diagnostic;
4
5use std::path::Path;
6
7pub fn transpile(path: &Path, text: &str) -> Result<String, Vec<Diagnostic>> {
8 let mut program =
9 voltaic_parser::parse_source(text).map_err(|failure| diagnose(path, text, &failure))?;
10 let analysis = crate::annotate::analyse_at(&mut program, path);
11 if !analysis.open.is_empty() {
12 return Err(crate::annotate::refusals(
13 path,
14 text,
15 analysis.open.as_slice(),
16 ));
17 }
18 let shown = path.display().to_string();
19 let scope = voltaic_emit::MethodScope::enter(voltaic_emit::MethodTable::from_spans(
20 analysis.methods.as_slice(),
21 ));
22 let emitted = voltaic_emit::emit_with_map(&program, text, &shown);
23 drop(scope);
24 Ok(emitted.0)
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Side {
29 Original,
30 Migrated,
31}
32#[allow(unused_imports)]
33use Side::*;
34
35#[derive(Debug, Clone)]
36pub enum Gate {
37 Identical,
38 Differs {
39 line: usize,
40 was: String,
41 now: String,
42 },
43 Unreadable {
44 side: Side,
45 problems: Vec<Diagnostic>,
46 },
47}
48#[allow(unused_imports)]
49use Gate::*;
50
51pub fn gate(path: &Path, original: &str, migrated: &str) -> Gate {
52 match transpile(path, original) {
53 Err(problems) => Gate::Unreadable {
54 side: Side::Original,
55 problems,
56 },
57 Ok(before) => match transpile(path, migrated) {
58 Err(problems) => Gate::Unreadable {
59 side: Side::Migrated,
60 problems,
61 },
62 Ok(after) => match first_difference(&before, &after) {
63 Some(found) => Gate::Differs {
64 line: found.0,
65 was: found.1,
66 now: found.2,
67 },
68 None => Gate::Identical,
69 },
70 },
71 }
72}
73
74pub fn first_difference(before: &str, after: &str) -> Option<(usize, String, String)> {
75 let mine: Vec<&str> = before.lines().collect();
76 let theirs: Vec<&str> = after.lines().collect();
77 let mut i = 0;
78 while i < mine.len() || i < theirs.len() {
79 let left = mine.get(i).copied().unwrap_or("");
80 let right = theirs.get(i).copied().unwrap_or("");
81 if left != right {
82 return Some((i + 1, left.to_string(), right.to_string()));
83 }
84 i += 1;
85 }
86 None
87}
88
89impl Gate {
90 pub fn passed(&self) -> bool {
91 match self {
92 Gate::Identical => true,
93 _ => false,
94 }
95 }
96
97 pub fn explain(&self) -> String {
98 match self {
99 Gate::Identical => "the generated Rust is byte-identical".to_string(),
100 Gate::Differs { line, was, now } => format!(
101 "the generated Rust differs at line {}:\n was: {}\n now: {}",
102 line, was, now
103 ),
104 Gate::Unreadable { side, problems } => {
105 let which = match side {
106 Side::Original => "the original",
107 Side::Migrated => "the migrated source",
108 };
109 format!("{} does not compile: {} diagnostics", which, problems.len())
110 }
111 }
112 }
113}
114
115pub fn apply(path: &Path, migrated: &str) -> Result<Gate, String> {
116 let shown = path.display().to_string();
117 let original =
118 std::fs::read_to_string(path).map_err(|e| format!("cannot read {}: {}", shown, e))?;
119 let verdict = gate(path, &original, migrated);
120 if !verdict.passed() {
121 return Ok(verdict);
122 }
123 std::fs::write(path, migrated).map_err(|e| format!("cannot write {}: {}", shown, e))?;
124 Ok(verdict)
125}
126
127#[cfg(test)]
128mod __vlt_tests {
129 use super::*;
130
131 #[test]
132 fn an_unchanged_file_passes_the_gate() {
133 let source = "twice : u64 -> u64\ntwice n = n + n\n";
134 gate(Path::new("probe.vlt"), source, source).passed();
135 }
136
137 #[test]
138 fn a_changed_body_fails_the_gate_and_names_the_line() {
139 let before = "twice : u64 -> u64\ntwice n = n + n\n";
140 let after = "twice : u64 -> u64\ntwice n = n * 2\n";
141 match gate(Path::new("probe.vlt"), before, after) {
142 Gate::Differs { line, .. } => line > 0,
143 _ => false,
144 };
145 }
146
147 #[test]
148 fn a_spelling_change_with_the_same_lowering_passes() {
149 let before = "pick : u64 -> u64\npick n = n |> .max 3\n";
150 let after = "pick : u64 -> u64\npick n = .max 3 n\n";
151 gate(Path::new("probe.vlt"), before, after).passed();
152 }
153
154 #[test]
155 fn source_the_compiler_cannot_read_is_rejected_not_compared() {
156 let before = "twice : u64 -> u64\ntwice n = n + n\n";
157 match gate(Path::new("probe.vlt"), before, "twice n = = =\n") {
158 Gate::Unreadable { side, .. } => side == Side::Migrated,
159 _ => false,
160 };
161 }
162}