1use sha2::{Digest, Sha256};
11
12use crate::translation::check::check_program;
13use crate::translation::diagnostics::TranslationError;
14use crate::translation::emit_common::{Assumption, Emitted, Encoding, IN_BOUNDS_READS, Mapping};
15use crate::translation::{
16 Span, emit_javascript::emit_javascript, emit_lean::emit_lean, emit_rocq::emit_rocq,
17 emit_rust::emit_rust, javascript::parse_javascript, lean::parse_lean, rocq::parse_rocq,
18 rust::parse_rust,
19};
20
21const PROVENANCE_MARKER: &str = "meta-language:translation-provenance:v1";
22const TRANSLATOR: &str = "meta-language portable-core translator";
23
24pub 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";
26
27pub const SEMANTIC_ENCODING: &str =
29 "portable-core translation; the encodings it chose are listed in semantics.encodings";
30
31#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct TranslationObligation {
34 pub source: String,
36 pub target: String,
38 pub kind: String,
40 pub closed_goal: bool,
42 pub discharge: String,
44 pub check: Option<String>,
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct TranslationProvenance {
51 pub translator: &'static str,
53 pub source_language: &'static str,
55 pub source_sha256: String,
57 pub source_bytes: usize,
59 pub header: String,
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct SemanticTranslation {
66 pub entry: Option<String>,
68 pub observation_procedure: &'static str,
70 pub encodings: Vec<Encoding>,
72 pub assumptions: Vec<Assumption>,
74 pub obligations: Vec<TranslationObligation>,
76 pub mappings: Vec<Mapping>,
78 pub runtime_dependencies: Vec<String>,
80 pub provenance: TranslationProvenance,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
86pub struct TranslationDiagnostic {
87 pub kind: &'static str,
89 pub message: String,
91 pub span: Option<Span>,
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct ReadTranslationProvenance {
98 pub source_language: &'static str,
100 pub source_sha256: String,
102 pub source_bytes: usize,
104}
105
106pub fn emit(
108 source: &str,
109 source_language: &str,
110 target_language: &str,
111) -> Result<Emitted, TranslationDiagnostic> {
112 let surface = match source_language {
113 "JavaScript" => parse_javascript(source),
114 "Rust" => parse_rust(source),
115 "Lean" => parse_lean(source),
116 _ => parse_rocq(source),
117 };
118 let emitted = surface
119 .and_then(|surface| check_program(&surface))
120 .and_then(|program| match target_language {
121 "JavaScript" => emit_javascript(&program),
122 "Rust" => emit_rust(&program),
123 "Lean" => emit_lean(&program),
124 _ => emit_rocq(&program),
125 });
126 emitted.map_err(|error: TranslationError| TranslationDiagnostic {
127 kind: error.kind.as_str(),
128 message: error.message(),
129 span: error.span,
130 })
131}
132
133const fn comment(target_language: &str) -> (&'static str, &'static str) {
134 match target_language.as_bytes() {
135 b"Lean" => ("-- ", ""),
136 b"Rocq" => ("(* ", " *)"),
137 _ => ("// ", ""),
138 }
139}
140
141pub fn record(
143 source: &str,
144 source_language: &'static str,
145 target_language: &str,
146 emitted: Emitted,
147) -> (String, SemanticTranslation) {
148 let source_sha256 = Sha256::digest(source.as_bytes()).iter().fold(
149 String::with_capacity(64),
150 |mut hex, byte| {
151 use std::fmt::Write as _;
152 let _ = write!(hex, "{byte:02x}");
153 hex
154 },
155 );
156 let (open, close) = comment(target_language);
157 let header = format!(
158 "{open}{PROVENANCE_MARKER} source={source_language} sha256={source_sha256} bytes={}{close}",
159 source.len()
160 );
161 let kernel_target = matches!(target_language, "Lean" | "Rocq");
162 let obligations = emitted
163 .theorems
164 .into_iter()
165 .map(|theorem| TranslationObligation {
166 source: theorem.source,
167 target: theorem.target,
168 kind: theorem.kind,
169 closed_goal: theorem.closed_goal,
170 discharge: theorem.discharge.unwrap_or_else(|| {
171 if kernel_target {
172 "target-kernel"
173 } else {
174 "runtime-assertion"
175 }
176 .to_owned()
177 }),
178 check: theorem.check,
179 })
180 .collect();
181 let semantics = SemanticTranslation {
182 entry: emitted.entry,
183 observation_procedure: observation_procedure(target_language),
184 encodings: emitted.encodings,
185 assumptions: emitted.assumptions,
186 obligations,
187 mappings: emitted.mappings,
188 runtime_dependencies: runtime_dependencies(target_language, &emitted.text),
189 provenance: TranslationProvenance {
190 translator: TRANSLATOR,
191 source_language,
192 source_sha256,
193 source_bytes: source.len(),
194 header: header.clone(),
195 },
196 };
197 (format!("{header}\n{}", emitted.text), semantics)
198}
199
200pub fn assumption_statements(assumptions: &[Assumption]) -> &'static [&'static str] {
202 const NONE: &[&str] = &[];
203 const READS: &[&str] = &[IN_BOUNDS_READS.statement];
204 let ids: Vec<&str> = assumptions
205 .iter()
206 .map(|assumption| assumption.id.as_str())
207 .collect();
208 match ids.as_slice() {
209 [] => NONE,
210 [first] if *first == IN_BOUNDS_READS.id => READS,
211 _ => unreachable!("unknown assumptions {ids:?}"),
212 }
213}
214
215const fn observation_procedure(target_language: &str) -> &'static str {
216 match target_language.as_bytes() {
217 b"JavaScript" => {
218 "run the module with node; --ml-check-theorems evaluates the theorem properties instead of main"
219 }
220 b"Rust" => {
221 "compile with rustc and run the binary; --ml-check-theorems evaluates the theorem properties instead of main"
222 }
223 b"Lean" => {
224 "lean --run executes main after the Lean kernel has checked every definition and theorem"
225 }
226 _ => {
227 "rocq compile checks every definition and proof; Eval vm_compute in main prints the list of output lines"
228 }
229 }
230}
231
232fn runtime_dependencies(target_language: &str, code: &str) -> Vec<String> {
234 let mut dependencies = Vec::new();
235 match target_language {
236 "JavaScript" => {
237 dependencies.push("ECMAScript 2026 host with BigInt".to_owned());
238 if code.contains("process.argv") {
239 dependencies.push("Node.js process.argv (selects --ml-check-theorems)".to_owned());
240 }
241 }
242 "Rust" => {
243 dependencies.push("Rust 1.99.0 standard library".to_owned());
244 if code.contains("\npub mod ml {") {
245 dependencies.push(
246 "ml::Big arbitrary-precision integers, defined inside the artifact".to_owned(),
247 );
248 }
249 if code.contains("\npub mod ml_number {") {
250 dependencies.push(
251 "ml_number JavaScript Number formatting and SameValue, defined inside the artifact"
252 .to_owned(),
253 );
254 }
255 }
256 "Lean" => {
257 dependencies.push("Lean 4.34.1 core library".to_owned());
258 dependencies.extend(
259 code.lines()
260 .filter_map(|line| line.strip_prefix("import "))
261 .map(|module| module.trim().to_owned()),
262 );
263 }
264 _ => {
265 dependencies.push("Rocq 9.3".to_owned());
266 for line in code.lines() {
267 let Some(rest) = line.strip_prefix("From ") else {
268 continue;
269 };
270 let Some((library, modules)) = rest.split_once(" Require Import ") else {
271 continue;
272 };
273 let Some(modules) = modules.strip_suffix('.') else {
274 continue;
275 };
276 if library.is_empty() || !library.chars().all(|c| c.is_ascii_alphabetic()) {
277 continue;
278 }
279 dependencies.extend(
280 modules
281 .split_whitespace()
282 .map(|module| format!("{library}.{module}")),
283 );
284 }
285 }
286 }
287 dependencies
288}
289
290pub fn read_provenance(
292 code: &str,
293 target_language: &str,
294) -> Result<ReadTranslationProvenance, String> {
295 let (open, close) = comment(target_language);
296 let first_line = code.split('\n').next().unwrap_or_default();
297 let fields = first_line
298 .strip_prefix(open)
299 .and_then(|line| line.strip_prefix(PROVENANCE_MARKER))
300 .and_then(|line| line.strip_prefix(' '))
301 .and_then(|line| line.strip_suffix(close))
302 .ok_or_else(|| format!("missing {target_language} provenance comment"))?;
303 let field = |name: &str| {
304 fields.split(' ').find_map(|field| {
305 field
306 .strip_prefix(name)
307 .and_then(|rest| rest.strip_prefix('='))
308 })
309 };
310 let invalid = || "expected source, sha256 and bytes fields".to_owned();
311 let source_language = field("source")
312 .and_then(crate::language_support)
313 .ok_or_else(invalid)?
314 .name;
315 let source_sha256 = field("sha256")
316 .filter(|hash| {
317 hash.len() == 64
318 && hash
319 .bytes()
320 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
321 })
322 .ok_or_else(invalid)?
323 .to_owned();
324 let source_bytes = field("bytes")
325 .and_then(|bytes| bytes.parse::<usize>().ok())
326 .ok_or_else(invalid)?;
327 Ok(ReadTranslationProvenance {
328 source_language,
329 source_sha256,
330 source_bytes,
331 })
332}