use sha2::{Digest, Sha256};
use crate::translation::check::check_program;
use crate::translation::diagnostics::TranslationError;
use crate::translation::emit_common::{Assumption, Emitted, Encoding, IN_BOUNDS_READS, Mapping};
use crate::translation::{
Span, emit_javascript::emit_javascript, emit_lean::emit_lean, emit_rocq::emit_rocq,
emit_rust::emit_rust, javascript::parse_javascript, lean::parse_lean, rocq::parse_rocq,
rust::parse_rust,
};
const PROVENANCE_MARKER: &str = "meta-language:translation-provenance:v1";
const TRANSLATOR: &str = "meta-language portable-core translator";
pub const SEMANTIC_OBSERVATION: &str = "the lines main prints, in order, on executions that do not abort, and the proposition of every source theorem and assertion over the translated definitions";
pub const SEMANTIC_ENCODING: &str =
"portable-core translation; the encodings it chose are listed in semantics.encodings";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TranslationObligation {
pub source: String,
pub target: String,
pub kind: String,
pub closed_goal: bool,
pub discharge: String,
pub check: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TranslationProvenance {
pub translator: &'static str,
pub source_language: &'static str,
pub source_sha256: String,
pub source_bytes: usize,
pub header: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SemanticTranslation {
pub entry: Option<String>,
pub observation_procedure: &'static str,
pub encodings: Vec<Encoding>,
pub assumptions: Vec<Assumption>,
pub obligations: Vec<TranslationObligation>,
pub mappings: Vec<Mapping>,
pub runtime_dependencies: Vec<String>,
pub provenance: TranslationProvenance,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TranslationDiagnostic {
pub kind: &'static str,
pub message: String,
pub span: Option<Span>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReadTranslationProvenance {
pub source_language: &'static str,
pub source_sha256: String,
pub source_bytes: usize,
}
pub fn emit(
source: &str,
source_language: &str,
target_language: &str,
) -> Result<Emitted, TranslationDiagnostic> {
let surface = match source_language {
"JavaScript" => parse_javascript(source),
"Rust" => parse_rust(source),
"Lean" => parse_lean(source),
_ => parse_rocq(source),
};
let emitted = surface
.and_then(|surface| check_program(&surface))
.and_then(|program| match target_language {
"JavaScript" => emit_javascript(&program),
"Rust" => emit_rust(&program),
"Lean" => emit_lean(&program),
_ => emit_rocq(&program),
});
emitted.map_err(|error: TranslationError| TranslationDiagnostic {
kind: error.kind.as_str(),
message: error.message(),
span: error.span,
})
}
const fn comment(target_language: &str) -> (&'static str, &'static str) {
match target_language.as_bytes() {
b"Lean" => ("-- ", ""),
b"Rocq" => ("(* ", " *)"),
_ => ("// ", ""),
}
}
pub fn record(
source: &str,
source_language: &'static str,
target_language: &str,
emitted: Emitted,
) -> (String, SemanticTranslation) {
let source_sha256 = Sha256::digest(source.as_bytes()).iter().fold(
String::with_capacity(64),
|mut hex, byte| {
use std::fmt::Write as _;
let _ = write!(hex, "{byte:02x}");
hex
},
);
let (open, close) = comment(target_language);
let header = format!(
"{open}{PROVENANCE_MARKER} source={source_language} sha256={source_sha256} bytes={}{close}",
source.len()
);
let kernel_target = matches!(target_language, "Lean" | "Rocq");
let obligations = emitted
.theorems
.into_iter()
.map(|theorem| TranslationObligation {
source: theorem.source,
target: theorem.target,
kind: theorem.kind,
closed_goal: theorem.closed_goal,
discharge: theorem.discharge.unwrap_or_else(|| {
if kernel_target {
"target-kernel"
} else {
"runtime-assertion"
}
.to_owned()
}),
check: theorem.check,
})
.collect();
let semantics = SemanticTranslation {
entry: emitted.entry,
observation_procedure: observation_procedure(target_language),
encodings: emitted.encodings,
assumptions: emitted.assumptions,
obligations,
mappings: emitted.mappings,
runtime_dependencies: runtime_dependencies(target_language, &emitted.text),
provenance: TranslationProvenance {
translator: TRANSLATOR,
source_language,
source_sha256,
source_bytes: source.len(),
header: header.clone(),
},
};
(format!("{header}\n{}", emitted.text), semantics)
}
pub fn assumption_statements(assumptions: &[Assumption]) -> &'static [&'static str] {
const NONE: &[&str] = &[];
const READS: &[&str] = &[IN_BOUNDS_READS.statement];
let ids: Vec<&str> = assumptions
.iter()
.map(|assumption| assumption.id.as_str())
.collect();
match ids.as_slice() {
[] => NONE,
[first] if *first == IN_BOUNDS_READS.id => READS,
_ => unreachable!("unknown assumptions {ids:?}"),
}
}
const fn observation_procedure(target_language: &str) -> &'static str {
match target_language.as_bytes() {
b"JavaScript" => {
"run the module with node; --ml-check-theorems evaluates the theorem properties instead of main"
}
b"Rust" => {
"compile with rustc and run the binary; --ml-check-theorems evaluates the theorem properties instead of main"
}
b"Lean" => {
"lean --run executes main after the Lean kernel has checked every definition and theorem"
}
_ => {
"rocq compile checks every definition and proof; Eval vm_compute in main prints the list of output lines"
}
}
}
fn runtime_dependencies(target_language: &str, code: &str) -> Vec<String> {
let mut dependencies = Vec::new();
match target_language {
"JavaScript" => {
dependencies.push("ECMAScript 2026 host with BigInt".to_owned());
if code.contains("process.argv") {
dependencies.push("Node.js process.argv (selects --ml-check-theorems)".to_owned());
}
}
"Rust" => {
dependencies.push("Rust 1.99.0 standard library".to_owned());
if code.contains("\npub mod ml {") {
dependencies.push(
"ml::Big arbitrary-precision integers, defined inside the artifact".to_owned(),
);
}
if code.contains("\npub mod ml_number {") {
dependencies.push(
"ml_number JavaScript Number formatting and SameValue, defined inside the artifact"
.to_owned(),
);
}
}
"Lean" => {
dependencies.push("Lean 4.34.1 core library".to_owned());
dependencies.extend(
code.lines()
.filter_map(|line| line.strip_prefix("import "))
.map(|module| module.trim().to_owned()),
);
}
_ => {
dependencies.push("Rocq 9.3".to_owned());
for line in code.lines() {
let Some(rest) = line.strip_prefix("From ") else {
continue;
};
let Some((library, modules)) = rest.split_once(" Require Import ") else {
continue;
};
let Some(modules) = modules.strip_suffix('.') else {
continue;
};
if library.is_empty() || !library.chars().all(|c| c.is_ascii_alphabetic()) {
continue;
}
dependencies.extend(
modules
.split_whitespace()
.map(|module| format!("{library}.{module}")),
);
}
}
}
dependencies
}
pub fn read_provenance(
code: &str,
target_language: &str,
) -> Result<ReadTranslationProvenance, String> {
let (open, close) = comment(target_language);
let first_line = code.split('\n').next().unwrap_or_default();
let fields = first_line
.strip_prefix(open)
.and_then(|line| line.strip_prefix(PROVENANCE_MARKER))
.and_then(|line| line.strip_prefix(' '))
.and_then(|line| line.strip_suffix(close))
.ok_or_else(|| format!("missing {target_language} provenance comment"))?;
let field = |name: &str| {
fields.split(' ').find_map(|field| {
field
.strip_prefix(name)
.and_then(|rest| rest.strip_prefix('='))
})
};
let invalid = || "expected source, sha256 and bytes fields".to_owned();
let source_language = field("source")
.and_then(crate::language_support)
.ok_or_else(invalid)?
.name;
let source_sha256 = field("sha256")
.filter(|hash| {
hash.len() == 64
&& hash
.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
})
.ok_or_else(invalid)?
.to_owned();
let source_bytes = field("bytes")
.and_then(|bytes| bytes.parse::<usize>().ok())
.ok_or_else(invalid)?;
Ok(ReadTranslationProvenance {
source_language,
source_sha256,
source_bytes,
})
}