1use std::collections::{HashMap, HashSet};
14use std::fmt::Write as _;
15
16use super::Language;
17use super::diagnostics::{Result, type_error, unsupported};
18use super::emit_common::{
19 CtorStyle, Dependence, EmitOptions, EmitState, Emitted, mutual_groups, order_declarations,
20 unthreaded,
21};
22use super::ir::{
23 ByZero, Case, DataDecl, Decl, Expr, FnDecl, Hints, LitValue, Node, Pattern, Plan, Program,
24 Prop, Semantics, TheoremDecl, rename_function, rename_theorem,
25};
26use super::lean_root_names::LEAN_ROOT_NAMES;
27use super::output::thread_output;
28use super::proof::prop_functions;
29use super::surface::{BinaryOp, Flavor, Rounding, UnaryOp};
30use super::types::Type;
31
32mod arrays;
33mod helpers;
34mod main;
35mod math;
36use self::helpers::HELPERS;
37
38const KEYWORDS: &[&str] = &[
39 "abbrev",
40 "at",
41 "attribute",
42 "axiom",
43 "by",
44 "calc",
45 "class",
46 "def",
47 "deriving",
48 "do",
49 "else",
50 "end",
51 "example",
52 "export",
53 "extends",
54 "for",
55 "from",
56 "fun",
57 "have",
58 "if",
59 "import",
60 "in",
61 "inductive",
62 "instance",
63 "let",
64 "local",
65 "match",
66 "mut",
67 "mutual",
68 "namespace",
69 "noncomputable",
70 "open",
71 "partial",
72 "private",
73 "protected",
74 "return",
75 "section",
76 "set_option",
77 "show",
78 "structure",
79 "suffices",
80 "then",
81 "theorem",
82 "lemma",
83 "unsafe",
84 "universe",
85 "variable",
86 "where",
87 "with",
88 "termination_by",
89 "decreasing_by",
90 "macro",
91 "syntax",
92 "notation",
93 "infix",
94 "infixl",
95 "infixr",
96 "prefix",
97 "postfix",
98 "macro_rules",
99 "elab",
100 "opaque",
101 "omit",
102 "include",
103 "nomatch",
104 "nofun",
105 "try",
106 "catch",
107 "finally",
108 "unless",
109 "break",
110 "continue",
111 "Type",
112 "Prop",
113 "Sort",
114 "this",
115 "using",
116 "obtain",
117 "Nat",
119 "Int",
120 "String",
121 "Bool",
122 "Unit",
123 "IO",
124 "true",
125 "false",
126 "main",
127 "toString",
128 "decide",
129 "panic",
130 "Float",
131 "rec",
132 "recOn",
133 "casesOn",
134 "noConfusion",
135 "noConfusionType",
136 "below",
137 "brecOn",
138 "binductionOn",
139 "ibelow",
140 "ctorIdx",
141 "toCtorIdx",
142 "sizeOf_spec",
143 "injEq",
144 "inj",
145 "induct",
146 "eq_def",
147 "mk",
148];
149
150fn ident(name: &str) -> String {
152 let mut result: String = name
153 .chars()
154 .map(|character| {
155 if character.is_ascii_alphanumeric()
156 || character == '_'
157 || character == '\''
158 || ('\u{C0}'..='\u{FFFF}').contains(&character)
159 {
160 character
161 } else {
162 '_'
163 }
164 })
165 .collect();
166 if result.starts_with(|character: char| character.is_ascii_digit() || character == '\'') {
167 result = format!("x{result}");
168 }
169 if KEYWORDS.contains(&result.as_str()) {
170 result = format!("«{result}»");
171 }
172 result
173}
174
175fn generated(name: &str) -> Vec<String> {
177 vec![format!("{name}.eq_1")]
178}
179
180pub fn emit_lean(source: &Program) -> Result<Emitted> {
185 let program = &*thread_output(source)?;
186 let state = EmitState::new(
187 program,
188 Language::Lean,
189 ident,
190 KEYWORDS,
191 EmitOptions {
192 ctor_style: CtorStyle::Data,
193 generated: Some(generated),
194 root_reserved: LEAN_ROOT_NAMES,
195 ..EmitOptions::default()
196 },
197 );
198 LeanEmitter {
199 program,
200 state,
201 helpers: HashSet::new(),
202 successors: HashMap::new(),
203 uses_number: Dependence::numbers(program),
204 uses_partial: Dependence::partial(program),
205 }
206 .file()
207}
208
209struct LeanEmitter<'p> {
210 program: &'p Program,
211 state: EmitState<'p>,
212 helpers: HashSet<&'static str>,
213 successors: HashMap<String, String>,
216 uses_number: Dependence,
217 uses_partial: Dependence,
218}
219
220impl LeanEmitter<'_> {
221 fn file(mut self) -> Result<Emitted> {
222 let mut blocks = Vec::new();
223 let mut open: Vec<String> = Vec::new();
224 let groups = mutual_groups(self.program);
225 for entry in order_declarations(self.program, false)? {
226 self.move_to(&mut open, entry.module_path(), &mut blocks);
227 let block = match (entry, groups.get(entry.full_name())) {
228 (_, Some(group)) if std::ptr::eq(group[0], entry) => self.mutual(group)?,
229 (_, Some(_)) => continue,
230 (Decl::Data(data), None) => self.data(entry, data)?,
231 (Decl::Fn(function), None) => self.function(entry, function)?,
232 (Decl::Theorem(theorem), None) => self.theorem(entry, theorem)?,
233 };
234 blocks.push(block);
235 }
236 self.move_to(&mut open, &[], &mut blocks);
237 let main = match &self.program.main {
238 Some(main) => Some(self.main(main)?),
239 None => None,
240 };
241 let mut lines = vec![
242 format!(
243 "-- Translated from {} by meta-language: portable core, Lean target.",
244 self.program.source_language.as_str()
245 ),
246 "-- Source binders are kept even where unused or named like a constructor, and proof hints are shared by every closing tactic.".to_owned(),
247 "set_option linter.unusedVariables false".to_owned(),
248 "set_option linter.unusedSimpArgs false".to_owned(),
249 "set_option linter.constructorNameAsVariable false".to_owned(),
250 String::new(),
251 ];
252 for (name, text) in HELPERS {
253 if self.helpers.contains(name) {
254 lines.push(text.to_owned());
255 lines.push(String::new());
256 }
257 }
258 for block in blocks {
259 lines.push(block);
260 lines.push(String::new());
261 }
262 let entry = main.map(|main| {
263 lines.push(main);
264 lines.push(String::new());
265 "main".to_owned()
266 });
267 Ok(self.state.finish(lines.join("\n"), entry))
268 }
269
270 fn move_to(&self, open: &mut Vec<String>, path: &[String], blocks: &mut Vec<String>) {
272 let common = open
273 .iter()
274 .zip(path)
275 .take_while(|(left, right)| left == right)
276 .count();
277 while open.len() > common {
278 blocks.push(format!("end {}", self.module_name(open)));
279 open.pop();
280 }
281 while open.len() < path.len() {
282 open.push(path[open.len()].clone());
283 blocks.push(format!("namespace {}", self.module_name(open)));
284 }
285 }
286
287 fn module_name(&self, path: &[String]) -> String {
288 self.state.module_name(path).unwrap_or_default().to_owned()
289 }
290
291 fn ty(&mut self, ty: &Type) -> Result<String> {
292 Ok(match ty {
293 Type::Nat => "Nat".to_owned(),
294 Type::Int => "Int".to_owned(),
295 Type::Fixed { signed, .. } => {
296 let represented = if *signed { "Int" } else { "Nat" };
297 self.state.machine_integer(ty, represented);
298 represented.to_owned()
299 }
300 Type::Float => {
301 self.state.encode(
302 "floats",
303 "a JavaScript Number is a Lean Float, the same IEEE-754 binary64 with the same arithmetic; % is ml_float_rem, the exact truncated remainder, and ml_js_number prints a value as JavaScript does",
304 );
305 "Float".to_owned()
306 }
307 Type::Bool => "Bool".to_owned(),
308 Type::String => "String".to_owned(),
309 Type::Unit => "Unit".to_owned(),
310 Type::Output => "List String".to_owned(),
311 Type::Data { name } => self.state.reference(name, "."),
312 Type::Array { element } => {
313 self.state.encode(
314 "arrays",
315 "a JavaScript array, which the portable core never mutates, is a Lean Array; a read outside it panics",
316 );
317 format!("(Array {})", self.ty(element)?)
318 }
319 other => {
320 return Err(type_error(
321 format!("no Lean type for {}", other.kind()),
322 None,
323 ));
324 }
325 })
326 }
327
328 fn data(&mut self, entry: &Decl, data: &DataDecl) -> Result<String> {
329 let name = self.state.local_name(&data.full_name).to_owned();
330 self.state.map(entry, &name);
331 let mut lines = vec![format!("inductive {name} where")];
332 for ctor in &data.ctors {
333 let mut fields = String::new();
334 for field in &ctor.fields {
335 let ty = self.ty(&field.ty)?;
336 let _ = write!(fields, " ({} : {ty})", ident(&field.name));
337 }
338 let local = self.state.ctor_local(&data.full_name, &ctor.name);
339 lines.push(format!(" | {local}{fields} : {name}"));
340 }
341 let deriving = if data.output {
344 "Inhabited"
345 } else if self.undecidable(&data.full_name, &mut HashSet::new()) {
346 "Repr, Inhabited"
347 } else {
348 "Repr, DecidableEq, Inhabited"
349 };
350 lines.push(format!(" deriving {deriving}"));
351 Ok(lines.join("\n"))
352 }
353
354 fn definition(&mut self, entry: &Decl, function: &FnDecl) -> Result<String> {
355 let (params, body) = rename_function(function, &ident, &self.state.local_reserved());
356 let name = self.state.local_name(&function.full_name).to_owned();
357 self.state.map(entry, &name);
358 let mut binders = String::new();
359 for param in ¶ms {
360 let ty = self.ty(¶m.ty)?;
361 let _ = write!(binders, " ({} : {ty})", param.name);
362 }
363 let ret = self.ty(&function.ret)?;
364 let body = self.expr(&body, 1)?;
365 Ok(format!("def {name}{binders} : {ret} :=\n {body}"))
366 }
367
368 fn function(&mut self, entry: &Decl, function: &FnDecl) -> Result<String> {
369 let text = self.definition(entry, function)?;
370 if !function.recursive {
371 return Ok(text);
372 }
373 match function.decreasing {
374 None => {
375 self.state.encode(
376 "general-recursion",
377 "recursion without a structurally decreasing argument is a Lean partial def: it runs as the source does but its equations are opaque to proofs",
378 );
379 Ok(format!("partial {text}"))
380 }
381 Some(_) => Ok(text),
385 }
386 }
387
388 fn mutual(&mut self, group: &[&Decl]) -> Result<String> {
390 self.state.encode(
391 "mutual-recursion",
392 "mutually recursive functions are a Lean mutual block of partial defs: they run as the source does but their equations are opaque to proofs",
393 );
394 let mut blocks = vec!["mutual".to_owned()];
395 for entry in group {
396 if let Decl::Fn(function) = entry {
397 blocks.push(format!("partial {}", self.definition(entry, function)?));
398 }
399 }
400 blocks.push("end".to_owned());
401 Ok(blocks.join("\n\n"))
402 }
403
404 fn theorem(&mut self, entry: &Decl, theorem: &TheoremDecl) -> Result<String> {
405 let (binders, prop, plan) = rename_theorem(theorem, &ident, &self.state.local_reserved());
406 let name = self.state.local_name(&theorem.full_name).to_owned();
407 self.state.map(entry, &name);
408 let mut functions = prop_functions(&prop, Vec::new());
409 for unfold in collect_unfold(&plan) {
410 if !functions.contains(unfold) {
411 functions.push(unfold.clone());
412 }
413 }
414 let mut statement = format!("theorem {name}");
415 for binder in &binders {
416 let ty = self.ty(&binder.ty)?;
417 let _ = write!(statement, " ({} : {ty})", binder.name);
418 }
419 let _ = write!(statement, " : {} := by", self.prop(&prop)?);
420 self.state
421 .theorem(&theorem.full_name, &name, binders.is_empty(), false);
422 Ok(format!("{statement}\n{}", self.plan(&plan, &functions, 1)))
423 }
424
425 fn plan(&self, plan: &Plan, functions: &[String], depth: usize) -> String {
426 let pad = pad(depth);
427 let (tactic, split) = match plan {
428 Plan::Close { hints } => {
429 return format!("{pad}{}", self.closer(hints, functions, depth));
430 }
431 Plan::Induction(split) => ("induction", split),
432 Plan::Cases(split) => ("cases", split),
433 };
434 let mut lines = vec![format!("{pad}{tactic} {}", split.variable)];
436 for kase in &split.cases {
437 let ctor = match &split.ty {
438 Type::Nat => kase.ctor.as_str(),
439 other => self
440 .state
441 .ctor_local(other.data_name().unwrap_or_default(), &kase.ctor),
442 };
443 let ihs: &[String] = if tactic == "induction" {
444 &kase.ihs
445 } else {
446 &[]
447 };
448 let names: String =
449 kase.fields
450 .iter()
451 .chain(ihs)
452 .fold(String::new(), |mut names, item| {
453 let _ = write!(names, " {item}");
454 names
455 });
456 lines.push(format!("{pad}case {ctor}{names} =>"));
457 lines.push(self.plan(&kase.plan, functions, depth + 1));
458 }
459 lines.join("\n")
460 }
461
462 fn closer(&self, hints: &Hints, functions: &[String], depth: usize) -> String {
463 let mut unfold: Vec<&String> = Vec::new();
464 for function in functions.iter().chain(&hints.unfold) {
465 if !unfold.contains(&function) {
466 unfold.push(function);
467 }
468 }
469 let mut rules: Vec<String> = unfold
470 .into_iter()
471 .map(|function| self.state.reference(function, "."))
472 .collect();
473 rules.extend(
474 hints
475 .lemmas
476 .iter()
477 .map(|lemma| self.state.reference(lemma, ".")),
478 );
479 rules.extend(hints.hyps.iter().cloned());
480 let ring = ["Nat.mul_add", "Nat.add_mul", "Int.mul_add", "Int.add_mul"];
481 let list = rules.join(", ");
482 let mut alternatives = vec!["rfl".to_owned(), "decide".to_owned()];
483 if rules.is_empty() {
484 alternatives.push("omega".to_owned());
485 alternatives.push(format!("simp_all [{}] <;> omega", ring.join(", ")));
486 } else {
487 let with_ring: Vec<&str> = rules.iter().map(String::as_str).chain(ring).collect();
488 alternatives.extend([
489 format!("simp only [{list}] <;> done"),
490 format!("simp_all only [{list}] <;> omega"),
491 format!("simp [{list}] <;> done"),
492 format!("simp_all [{list}] <;> omega"),
493 format!("simp_all [{}] <;> omega", with_ring.join(", ")),
494 ]);
495 }
496 let alternatives: Vec<String> = alternatives
500 .iter()
501 .map(|alternative| format!("try {alternative}"))
502 .collect();
503 alternatives.join(&format!("\n{}", pad(depth)))
504 }
505
506 fn prop(&mut self, prop: &Prop) -> Result<String> {
507 Ok(match prop {
508 Prop::Forall { binders, body } => {
509 let mut parts = Vec::new();
510 for binder in binders {
511 parts.push(format!("({} : {})", binder.name, self.ty(&binder.ty)?));
512 }
513 format!("(∀ {}, {})", parts.join(" "), self.prop(body)?)
514 }
515 Prop::And { left, right } => {
516 let left = self.prop(left)?;
517 format!("({left} ∧ {})", self.prop(right)?)
518 }
519 Prop::Or { left, right } => {
520 let left = self.prop(left)?;
521 format!("({left} ∨ {})", self.prop(right)?)
522 }
523 Prop::Implies { left, right } => {
524 let left = self.prop(left)?;
525 format!("({left} → {})", self.prop(right)?)
526 }
527 Prop::Not { arg } => format!("(¬ {})", self.prop(arg)?),
528 Prop::Bool { expr } => format!("({} = true)", self.expr(expr, 0)?),
529 other => {
530 let (op, comparison) = other.comparison().expect("comparison");
531 let operator = match op {
532 BinaryOp::Eq => "=",
533 BinaryOp::Ne => "≠",
534 BinaryOp::Lt => "<",
535 BinaryOp::Le => "≤",
536 BinaryOp::Gt => ">",
537 _ => "≥",
538 };
539 let left = self.expr(&comparison.left, 0)?;
540 format!("({left} {operator} {})", self.expr(&comparison.right, 0)?)
541 }
542 })
543 }
544
545 fn expr(&mut self, e: &Expr, depth: usize) -> Result<String> {
546 Ok(match &e.node {
547 Node::Lit { value } => {
548 if e.ty.is_float() {
549 self.ty(&e.ty)?;
550 }
551 literal(&e.ty, value)?
552 }
553 Node::Unit => "()".to_owned(),
554 Node::OutNil => "([] : List String)".to_owned(),
555 Node::OutCons { head, tail } => {
556 format!(
557 "({} :: {})",
558 self.expr(head, depth)?,
559 self.expr(tail, depth)?
560 )
561 }
562 Node::Var { name } => self.successors.get(name).unwrap_or(name).clone(),
563 Node::Call { func, args } => {
564 let head = self.state.reference(func, ".");
565 self.application(head, args, depth)?
566 }
567 Node::Ctor { data, ctor, args } => {
568 let head = self.state.ctor_ref(data, ctor, ".");
569 self.application(head, args, depth)?
570 }
571 Node::Unary { op, arg, semantics } => {
572 if *op == UnaryOp::Not {
573 format!("(!{})", self.expr(arg, depth)?)
574 } else {
575 if *semantics == Some(Semantics::Checked) {
576 unthreaded("a machine-integer negation");
577 }
578 format!("(-{})", self.expr(arg, depth)?)
579 }
580 }
581 Node::Binary { .. } => self.binary(e, depth)?,
582 Node::If {
583 cond,
584 then,
585 otherwise,
586 } => {
587 let cond = self.expr(cond, depth)?;
588 let then = self.expr(then, depth)?;
589 format!(
590 "(if {cond} then {then} else {})",
591 self.expr(otherwise, depth)?
592 )
593 }
594 Node::Let { name, value, body } => {
595 let value = self.expr(value, depth)?;
596 format!(
597 "(let {name} := {value};\n{}{})",
598 pad(depth + 1),
599 self.expr(body, depth + 1)?
600 )
601 }
602 Node::Match { scrutinee, cases } => self.match_expr(scrutinee, cases, depth)?,
603 Node::ToString { arg, console } => {
604 if arg.ty == Type::String {
605 self.expr(arg, depth)?
606 } else {
607 self.text_of(arg, depth, *console)?
608 }
609 }
610 Node::Cast {
611 arg,
612 from,
613 to,
614 flavor,
615 ..
616 } => self.cast(arg, from, to, *flavor, depth)?,
617 Node::Abort { .. } => unthreaded("an abort"),
618 Node::Array { .. } | Node::Append { .. } | Node::Index { .. } | Node::Length { .. } => {
619 self.array_expr(e, depth)?
620 }
621 Node::Math { .. } => self.math(e, depth)?,
622 Node::Print { .. } => {
624 return Err(type_error(
625 "print with its output not threaded".to_owned(),
626 e.span,
627 ));
628 }
629 })
630 }
631
632 fn application(&mut self, head: String, args: &[Expr], depth: usize) -> Result<String> {
634 if args.is_empty() {
635 return Ok(head);
636 }
637 let mut parts = vec![head];
638 for arg in args {
639 parts.push(self.expr(arg, depth)?);
640 }
641 Ok(format!("({})", parts.join(" ")))
642 }
643
644 fn text_of(&mut self, arg: &Expr, depth: usize, console: bool) -> Result<String> {
645 if matches!(arg.ty, Type::Data { .. } | Type::Unit) {
646 return Err(unsupported(
647 "output of structured values",
648 &format!("a {} value has no portable textual form", arg.ty.kind()),
649 arg.span,
650 ));
651 }
652 if arg.ty.is_float() {
653 self.helpers.insert("jsNumber");
654 if console {
655 self.helpers.insert("jsConsole");
656 }
657 let function = if console {
658 "ml_js_console"
659 } else {
660 "ml_js_number"
661 };
662 return Ok(format!("({function} {})", self.expr(arg, depth)?));
663 }
664 Ok(format!("(toString {})", self.expr(arg, depth)?))
665 }
666
667 fn binary(&mut self, e: &Expr, depth: usize) -> Result<String> {
668 let Node::Binary {
669 op, left, right, ..
670 } = &e.node
671 else {
672 unreachable!("binary expressions only");
673 };
674 let left_text = self.expr(left, depth)?;
675 let right_text = self.expr(right, depth)?;
676 let comparison = match op {
677 BinaryOp::And => return Ok(format!("({left_text} && {right_text})")),
678 BinaryOp::Or => return Ok(format!("({left_text} || {right_text})")),
679 BinaryOp::Concat => return Ok(format!("({left_text} ++ {right_text})")),
680 BinaryOp::Eq => return Ok(format!("({left_text} == {right_text})")),
681 BinaryOp::Ne => return Ok(format!("({left_text} != {right_text})")),
682 BinaryOp::Lt => "<",
683 BinaryOp::Le => "≤",
684 BinaryOp::Gt => ">",
685 BinaryOp::Ge => "≥",
686 _ => return self.arithmetic(e, &left_text, &right_text),
687 };
688 Ok(format!("(decide ({left_text} {comparison} {right_text}))"))
689 }
690
691 fn arithmetic(&mut self, e: &Expr, left_text: &str, right_text: &str) -> Result<String> {
692 let Node::Binary {
693 op,
694 domain,
695 rounding,
696 by_zero,
697 semantics,
698 ..
699 } = &e.node
700 else {
701 unreachable!("binary expressions only");
702 };
703 if *semantics == Some(Semantics::Ieee) {
704 let operator = match op {
705 BinaryOp::Add => "+",
706 BinaryOp::Sub => "-",
707 BinaryOp::Mul => "*",
708 BinaryOp::Div => "/",
709 _ => {
710 self.helpers.insert("floatRem");
711 return Ok(format!("(ml_float_rem {left_text} {right_text})"));
712 }
713 };
714 return Ok(format!("({left_text} {operator} {right_text})"));
715 }
716 if *semantics == Some(Semantics::Checked)
718 || *by_zero == Some(ByZero::Abort)
719 || matches!(e.ty, Type::Fixed { .. })
720 {
721 unthreaded(&format!("a machine-integer {}", op.name()));
722 }
723 match op {
724 BinaryOp::Add => Ok(format!("({left_text} + {right_text})")),
725 BinaryOp::Mul => Ok(format!("({left_text} * {right_text})")),
726 BinaryOp::Sub => Ok(format!("({left_text} - {right_text})")),
727 BinaryOp::Div | BinaryOp::Rem => {
728 let division = *op == BinaryOp::Div;
729 if matches!(domain, Some(Type::Nat)) {
730 return Ok(format!(
731 "({left_text} {} {right_text})",
732 if division { "/" } else { "%" }
733 ));
734 }
735 if *rounding == Some(Rounding::Trunc) {
736 return Ok(format!(
737 "(Int.{} {left_text} {right_text})",
738 if division { "tdiv" } else { "tmod" }
739 ));
740 }
741 if *rounding == Some(Rounding::Floor) {
742 return Ok(format!(
743 "(Int.{} {left_text} {right_text})",
744 if division { "fdiv" } else { "fmod" }
745 ));
746 }
747 Ok(format!(
748 "({left_text} {} {right_text})",
749 if division { "/" } else { "%" }
750 ))
751 }
752 other => Err(type_error(
753 format!("no Lean operator {}", operator_name(*other)),
754 None,
755 )),
756 }
757 }
758
759 fn match_expr(&mut self, scrutinee: &Expr, cases: &[Case], depth: usize) -> Result<String> {
760 let subject = self.expr(scrutinee, depth)?;
761 let mut arms = Vec::new();
762 for kase in cases {
763 let text = match &kase.pattern {
764 Pattern::NatZero => "0".to_owned(),
765 Pattern::NatSucc { name } => format!("{name} + 1"),
766 Pattern::Wild => "_".to_owned(),
767 Pattern::Bind { name } => name.clone(),
768 Pattern::Ctor { data, ctor, binds } => {
769 let mut parts = vec![self.state.ctor_ref(data, ctor, ".")];
770 parts.extend(
771 binds
772 .iter()
773 .map(|bind| bind.clone().unwrap_or_else(|| "_".to_owned())),
774 );
775 parts.join(" ")
776 }
777 };
778 let saved = self.successors.clone();
779 if let (Pattern::NatSucc { name }, Some(variable)) =
780 (&kase.pattern, scrutinee.var_name())
781 {
782 self.successors
783 .insert(variable.to_owned(), format!("({name} + 1)"));
784 }
785 let body = self.expr(&kase.body, depth + 2);
786 self.successors = saved;
787 arms.push(format!("{}| {text} => {}", pad(depth + 1), body?));
788 }
789 Ok(format!("(match {subject} with\n{})", arms.join("\n")))
790 }
791
792 fn cast(
793 &mut self,
794 arg: &Expr,
795 from: &Type,
796 to: &Type,
797 flavor: Flavor,
798 depth: usize,
799 ) -> Result<String> {
800 let arg = self.expr(arg, depth)?;
801 let natural_from = from.is_natural();
802 let natural_to = to.is_natural();
803 if natural_from == natural_to {
804 return Ok(arg);
805 }
806 if natural_from {
807 return Ok(format!("(Int.ofNat {arg})"));
808 }
809 if flavor == Flavor::Checked {
810 unthreaded("a checked conversion to a natural");
811 }
812 Ok(format!("(Int.toNat {arg})"))
813 }
814}
815
816fn literal(ty: &Type, value: &LitValue) -> Result<String> {
817 Ok(match ty {
818 Type::Float => lean_float(&value.text()),
819 Type::Nat => format!("({} : Nat)", value.text()),
820 Type::Int => format!("({} : Int)", value.text()),
821 Type::Fixed { signed, .. } => {
822 format!(
823 "({} : {})",
824 value.text(),
825 if *signed { "Int" } else { "Nat" }
826 )
827 }
828 Type::Bool => value.text(),
829 Type::String => json_string(&value.text()),
830 other => {
831 return Err(type_error(
832 format!("no Lean literal for {}", other.kind()),
833 None,
834 ));
835 }
836 })
837}
838
839fn lean_float(value: &str) -> String {
842 let text = match value {
843 "NaN" => "0.0 / 0.0".to_owned(),
844 "Infinity" => "1.0 / 0.0".to_owned(),
845 "-Infinity" => "-1.0 / 0.0".to_owned(),
846 _ => value.replacen("e+", "e", 1),
847 };
848 format!("({text} : Float)")
849}
850
851fn json_string(text: &str) -> String {
853 serde_json::to_string(text).unwrap_or_default()
854}
855
856fn operator_name(op: BinaryOp) -> String {
858 serde_json::to_value(op)
859 .ok()
860 .and_then(|value| value.as_str().map(str::to_owned))
861 .unwrap_or_default()
862}
863
864fn collect_unfold(plan: &Plan) -> Vec<&String> {
865 match plan {
866 Plan::Close { hints } => hints.unfold.iter().collect(),
867 Plan::Induction(split) | Plan::Cases(split) => split
868 .cases
869 .iter()
870 .flat_map(|kase| collect_unfold(&kase.plan))
871 .collect(),
872 }
873}
874
875fn pad(depth: usize) -> String {
876 " ".repeat(depth)
877}