use crate::check::diagnose;
use voltaic_diag::Diagnostic;
use std::path::Path;
pub fn transpile(path: &Path, text: &str) -> Result<String, Vec<Diagnostic>> {
let mut program =
voltaic_parser::parse_source(text).map_err(|failure| diagnose(path, text, &failure))?;
let analysis = crate::annotate::analyse_at(&mut program, path);
if !analysis.open.is_empty() {
return Err(crate::annotate::refusals(
path,
text,
analysis.open.as_slice(),
));
}
let shown = path.display().to_string();
let scope = voltaic_emit::MethodScope::enter(voltaic_emit::MethodTable::from_spans(
analysis.methods.as_slice(),
));
let emitted = voltaic_emit::emit_with_map(&program, text, &shown);
drop(scope);
Ok(emitted.0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Side {
Original,
Migrated,
}
#[allow(unused_imports)]
use Side::*;
#[derive(Debug, Clone)]
pub enum Gate {
Identical,
Differs {
line: usize,
was: String,
now: String,
},
Unreadable {
side: Side,
problems: Vec<Diagnostic>,
},
}
#[allow(unused_imports)]
use Gate::*;
pub fn gate(path: &Path, original: &str, migrated: &str) -> Gate {
match transpile(path, original) {
Err(problems) => Gate::Unreadable {
side: Side::Original,
problems,
},
Ok(before) => match transpile(path, migrated) {
Err(problems) => Gate::Unreadable {
side: Side::Migrated,
problems,
},
Ok(after) => match first_difference(&before, &after) {
Some(found) => Gate::Differs {
line: found.0,
was: found.1,
now: found.2,
},
None => Gate::Identical,
},
},
}
}
pub fn first_difference(before: &str, after: &str) -> Option<(usize, String, String)> {
let mine: Vec<&str> = before.lines().collect();
let theirs: Vec<&str> = after.lines().collect();
let mut i = 0;
while i < mine.len() || i < theirs.len() {
let left = mine.get(i).copied().unwrap_or("");
let right = theirs.get(i).copied().unwrap_or("");
if left != right {
return Some((i + 1, left.to_string(), right.to_string()));
}
i += 1;
}
None
}
impl Gate {
pub fn passed(&self) -> bool {
match self {
Gate::Identical => true,
_ => false,
}
}
pub fn explain(&self) -> String {
match self {
Gate::Identical => "the generated Rust is byte-identical".to_string(),
Gate::Differs { line, was, now } => format!(
"the generated Rust differs at line {}:\n was: {}\n now: {}",
line, was, now
),
Gate::Unreadable { side, problems } => {
let which = match side {
Side::Original => "the original",
Side::Migrated => "the migrated source",
};
format!("{} does not compile: {} diagnostics", which, problems.len())
}
}
}
}
pub fn apply(path: &Path, migrated: &str) -> Result<Gate, String> {
let shown = path.display().to_string();
let original =
std::fs::read_to_string(path).map_err(|e| format!("cannot read {}: {}", shown, e))?;
let verdict = gate(path, &original, migrated);
if !verdict.passed() {
return Ok(verdict);
}
std::fs::write(path, migrated).map_err(|e| format!("cannot write {}: {}", shown, e))?;
Ok(verdict)
}
#[cfg(test)]
mod __vlt_tests {
use super::*;
#[test]
fn an_unchanged_file_passes_the_gate() {
let source = "twice : u64 -> u64\ntwice n = n + n\n";
gate(Path::new("probe.vlt"), source, source).passed();
}
#[test]
fn a_changed_body_fails_the_gate_and_names_the_line() {
let before = "twice : u64 -> u64\ntwice n = n + n\n";
let after = "twice : u64 -> u64\ntwice n = n * 2\n";
match gate(Path::new("probe.vlt"), before, after) {
Gate::Differs { line, .. } => line > 0,
_ => false,
};
}
#[test]
fn a_spelling_change_with_the_same_lowering_passes() {
let before = "pick : u64 -> u64\npick n = n |> .max 3\n";
let after = "pick : u64 -> u64\npick n = .max 3 n\n";
gate(Path::new("probe.vlt"), before, after).passed();
}
#[test]
fn source_the_compiler_cannot_read_is_rejected_not_compared() {
let before = "twice : u64 -> u64\ntwice n = n + n\n";
match gate(Path::new("probe.vlt"), before, "twice n = = =\n") {
Gate::Unreadable { side, .. } => side == Side::Migrated,
_ => false,
};
}
}