Skip to main content

voltaic_driver/
migrate.rs

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}