1use super::command::{Command, InductiveMember};
34use super::surface::{Assoc, Name, OpenClause, OperatorForm, SurfaceDecl};
35use super::term_parser::{is_reserved_word, is_sort_word, scan_name, TermParser, MAX_LEVEL_OFFSET};
36use crate::{Literal, Term, Universe};
37use std::collections::{HashMap, HashSet};
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum PrintError {
42 NoSpelling { what: String },
43}
44
45impl std::fmt::Display for PrintError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 PrintError::NoSpelling { what } => {
49 write!(f, "the LOGOS surface has no spelling for {what}")
50 }
51 }
52 }
53}
54
55fn no_spelling(what: impl Into<String>) -> PrintError {
56 PrintError::NoSpelling { what: what.into() }
57}
58
59pub fn print_term(term: &Term) -> Result<String, PrintError> {
61 let mut printer = Printer::new(&[term]);
62 printer.term(term, Ctx::Top)?;
63 Ok(printer.out)
64}
65
66pub fn print_term_in(term: &Term, locals: &[&str]) -> Result<String, PrintError> {
70 let mut printer = Printer::new(&[term]);
71 for local in locals {
72 printer
73 .scope
74 .entry(local.to_string())
75 .or_default()
76 .push(local.to_string());
77 printer.avoid.insert(local.to_string());
78 }
79 printer.term(term, Ctx::Top)?;
80 Ok(printer.out)
81}
82
83fn print_tactic(step: &crate::tactics::TacticStep) -> Result<String, PrintError> {
85 use crate::tactics::TacticStep;
86 let pattern = |cases: &[Vec<String>]| {
87 if cases.is_empty() {
88 String::new()
89 } else {
90 let cases: Vec<String> = cases.iter().map(|c| c.join(" ")).collect();
91 format!(" as [{}]", cases.join(" | "))
92 }
93 };
94 Ok(match step {
95 TacticStep::Intro(names) => format!("intro {}", names.join(" ")),
96 TacticStep::Exact(t) => format!("exact {}", slot(print_term(t)?, &[";"])),
97 TacticStep::Apply(t) => format!("apply {}", slot(print_term(t)?, &[";"])),
98 TacticStep::Exists(t) => format!("exists {}", slot(print_term(t)?, &[";"])),
99 TacticStep::Change(t) => format!("change {}", slot(print_term(t)?, &[";"])),
100 TacticStep::Have(n, t) => format!("have {n} : {}", slot(print_term(t)?, &[";"])),
101 TacticStep::Rewrite(t, reverse) => {
102 format!(
103 "rw [{}{}]",
104 if *reverse { "<- " } else { "" },
105 slot(print_term(t)?, &[",", "]"])
106 )
107 }
108 TacticStep::Assumption => "assumption".to_string(),
109 TacticStep::Exfalso => "exfalso".to_string(),
110 TacticStep::Constructor => "constructor".to_string(),
111 TacticStep::Left => "left".to_string(),
112 TacticStep::Right => "right".to_string(),
113 TacticStep::Rfl => "rfl".to_string(),
114 TacticStep::Induction(x, cases) => format!("induction {x}{}", pattern(cases)),
115 TacticStep::Cases(x, cases) => format!("cases {x}{}", pattern(cases)),
116 TacticStep::Refine(t) => format!("refine {}", slot(print_term(t)?, &[";"])),
117 TacticStep::Subst(h) => format!("subst {h}"),
118 TacticStep::Injection(h, names) if names.is_empty() => format!("injection {h}"),
119 TacticStep::Injection(h, names) => format!("injection {h} as [{}]", names.join(" ")),
120 TacticStep::Calc(steps) => {
121 let steps = steps
122 .iter()
123 .map(|(r, p)| {
124 Ok(format!(
125 "{} := {}",
126 slot(print_term(r)?, &[":=", ",", "]"]),
127 slot(print_term(p)?, &[",", "]"])
128 ))
129 })
130 .collect::<Result<Vec<_>, PrintError>>()?;
131 format!("calc [{}]", steps.join(", "))
132 }
133 TacticStep::Cc => "cc".to_string(),
134 TacticStep::Decide => "decide".to_string(),
135 TacticStep::Simp(lemmas) if lemmas.is_empty() => "simp".to_string(),
136 TacticStep::Simp(lemmas) => {
137 let lemmas = lemmas
138 .iter()
139 .map(|l| Ok(slot(print_term(l)?, &[",", "]"])))
140 .collect::<Result<Vec<_>, PrintError>>()?;
141 format!("simp [{}]", lemmas.join(", "))
142 }
143 })
144}
145
146pub fn print_command(command: &Command) -> Result<String, PrintError> {
148 match command {
149 Command::Universes { params, inner } => declaration(inner, Some(params)),
150 Command::Definition { .. }
151 | Command::Theorem { .. }
152 | Command::TheoremBy { .. }
153 | Command::Opaque { .. }
154 | Command::Axiom { .. }
155 | Command::InductiveBlock { .. }
156 | Command::Quotient { .. } => declaration(command, None),
157 Command::Inductive {
158 name,
159 params,
160 sort,
161 constructors,
162 } => {
163 if *sort != Term::Sort(Universe::Type(0)) {
164 return Err(no_spelling(format!(
165 "the undeclared-arity inductive `{name}` at `{sort}` \u{2014} that form is always at \
166 `Type`; an inductive at another sort declares its arity, as a block"
167 )));
168 }
169 if constructors.is_empty() {
170 return Err(no_spelling(format!(
171 "the undeclared-arity inductive `{name}` with no constructors \u{2014} that form \
172 needs at least one; an empty inductive declares its arity, as a block"
173 )));
174 }
175 let mut out = format!("Inductive {}", declaration_name(name)?);
176 for (param, ty) in params {
177 out.push_str(¶meter(param, ty)?);
178 }
179 out.push_str(" := ");
180 out.push_str(&constructor_list(constructors)?);
181 out.push('.');
182 Ok(out)
183 }
184 Command::Check(t) => Ok(format!("Check {}.", plain_term(t, "`Check`")?)),
185 Command::Eval(t) => Ok(format!("Eval {}.", plain_term(t, "`Eval`")?)),
186 Command::Comment => Ok("/- -/".to_string()),
187 Command::Structure {
188 inductive,
189 projections,
190 } => {
191 let mut out = format!("Structure {} :=", global_name(inductive)?);
192 for projection in projections {
193 out.push(' ');
194 out.push_str(&global_name(projection)?);
195 }
196 out.push('.');
197 Ok(out)
198 }
199 Command::PrintAxioms(name) => Ok(format!("Print Axioms {}.", name_path(name)?)),
200 Command::Protected(inner) => match inner.as_ref() {
201 Command::Protected(_)
204 | Command::Open(_)
205 | Command::OpenIn { .. }
206 | Command::Comment => Err(no_spelling(
207 "`Protected` in front of a command the qualifier cannot precede",
208 )),
209 _ => Ok(format!("Protected {}", print_command(inner)?)),
210 },
211 Command::Export { namespace, names } => {
212 let names = names.iter().map(name_path).collect::<Result<Vec<_>, _>>()?;
213 if names.is_empty() {
214 return Err(no_spelling("an `Export` naming nothing"));
215 }
216 Ok(format!(
217 "Export {} ({}).",
218 name_path(namespace)?,
219 names.join(" ")
220 ))
221 }
222 Command::Open(clauses) => Ok(format!("Open {}.", open_clauses(clauses)?)),
223 Command::OpenIn { opens, inner } => Ok(format!(
224 "Open {} in {}",
225 open_clauses(opens)?,
226 print_command(inner)?
227 )),
228 Command::Surface(decl) => surface_declaration(decl),
229 }
230}
231
232pub fn inductive_command(ctx: &crate::Context, name: &str) -> Result<Command, PrintError> {
246 if !ctx.is_inductive(name) {
247 return Err(no_spelling(format!(
248 "`{name}` as an inductive \u{2014} the context declares no such type"
249 )));
250 }
251 let block: Vec<String> = ctx
252 .mutual_block_of(name)
253 .map(|b| b.to_vec())
254 .unwrap_or_else(|| vec![name.to_string()]);
255 let telescope = ctx
256 .poly_declaration(name)
257 .map(|(params, _)| params.to_vec());
258 let mut members = Vec::with_capacity(block.len());
259 for member in &block {
260 let arity = ctx
261 .get_global(member)
262 .ok_or_else(|| no_spelling(format!("the header of the block member `{member}`")))?;
263 let count = ctx.inductive_num_params(member);
264 let constructors = ctx.get_constructors(member);
265 let mut avoid: HashSet<String> = HashSet::new();
269 let mut conflicts: HashSet<String> = HashSet::new();
270 for t in std::iter::once(arity).chain(constructors.iter().map(|(_, t)| *t)) {
271 spelled_names(t, &mut avoid);
272 let mut rest = t;
273 for _ in 0..count {
274 if let Term::Pi {
275 param_type,
276 body_type,
277 ..
278 } = rest
279 {
280 constants_and_binders(param_type, &mut conflicts);
281 rest = body_type;
282 }
283 }
284 constants_and_binders(rest, &mut conflicts);
285 }
286 let mut names: Vec<String> = Vec::with_capacity(count);
287 let mut cur = arity;
288 for _ in 0..count {
289 let Term::Pi {
290 param, body_type, ..
291 } = cur
292 else {
293 return Err(no_spelling(format!(
294 "`{member}` declaring {count} parameters with an arity of fewer binders"
295 )));
296 };
297 let keep = plain_atom(param) && !names.contains(param) && !conflicts.contains(param);
298 let chosen = if keep {
299 param.clone()
300 } else {
301 let mut k = 0usize;
302 loop {
303 let candidate = if k == 0 {
304 "A".to_string()
305 } else {
306 format!("A{k}")
307 };
308 k += 1;
309 if !avoid.contains(&candidate) && !names.contains(&candidate) {
310 break candidate;
311 }
312 }
313 };
314 names.push(chosen);
315 cur = body_type;
316 }
317 let (params, sort) = split_telescope(arity, &names, member)?;
318 let split = constructors
319 .iter()
320 .map(|(ctor, ty)| split_telescope(ty, &names, ctor))
321 .collect::<Result<Vec<_>, PrintError>>()?;
322 let full = split
327 .iter()
328 .any(|(restated, _)| restated.iter().zip(¶ms).any(|((_, t), (_, p))| t != p));
329 let constructors = if full {
330 constructors
331 .iter()
332 .map(|(ctor, ty)| (ctor.to_string(), (*ty).clone()))
333 .collect()
334 } else {
335 constructors
336 .iter()
337 .zip(split)
338 .map(|((ctor, _), (_, rest))| (ctor.to_string(), rest))
339 .collect()
340 };
341 members.push(InductiveMember {
342 name: member.clone(),
343 params,
344 sort,
345 constructors,
346 full_constructors: full,
347 });
348 }
349 let block = Command::InductiveBlock { members };
350 Ok(match telescope {
351 Some(params) => Command::Universes {
352 params,
353 inner: Box::new(block),
354 },
355 None => block,
356 })
357}
358
359fn constants_and_binders(t: &Term, out: &mut HashSet<String>) {
362 let mut work = vec![t];
363 while let Some(t) = work.pop() {
364 match t {
365 Term::Global(n) | Term::Const { name: n, .. } => {
366 out.insert(n.clone());
367 if let Some(name) = Name::parse(n) {
368 out.insert(name.parts()[0].clone());
369 }
370 }
371 Term::Pi {
372 param,
373 param_type,
374 body_type: body,
375 }
376 | Term::Lambda {
377 param,
378 param_type,
379 body,
380 } => {
381 out.insert(param.clone());
382 work.push(param_type);
383 work.push(body);
384 }
385 Term::Let {
386 name,
387 ty,
388 value,
389 body,
390 } => {
391 out.insert(name.clone());
392 work.push(ty);
393 work.push(value);
394 work.push(body);
395 }
396 Term::Fix { name, body } => {
397 out.insert(name.clone());
398 work.push(body);
399 }
400 Term::MutualFix { defs, .. } => {
401 for (n, b) in defs {
402 out.insert(n.clone());
403 work.push(b);
404 }
405 }
406 Term::App(f, a) => {
407 work.push(f);
408 work.push(a);
409 }
410 Term::Match {
411 discriminant,
412 motive,
413 cases,
414 } => {
415 work.push(discriminant);
416 work.push(motive);
417 work.extend(cases.iter());
418 }
419 Term::Annotated { term, .. } => work.push(term),
420 Term::Var(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole => {}
421 }
422 }
423}
424
425pub fn structure_commands(ctx: &crate::Context, name: &str) -> Result<Vec<Command>, PrintError> {
431 let Some(info) = ctx.struct_info(name) else {
432 return Ok(Vec::new());
433 };
434 let mut out = Vec::with_capacity(info.projections.len() + 1);
435 for projection in &info.projections {
436 let (params, ty, body) = match (ctx.get_universe_poly(projection), ctx.get_definition_body(projection)) {
437 (Some((params, ty, body)), _) if !params.is_empty() => (params.clone(), ty.clone(), body.clone()),
438 (_, Some(body)) => (
439 Vec::new(),
440 ctx.get_definition_type(projection)
441 .ok_or_else(|| no_spelling(format!("the type of the projection `{projection}`")))?
442 .clone(),
443 body.clone(),
444 ),
445 (Some((params, ty, body)), None) => (params.clone(), ty.clone(), body.clone()),
446 (None, None) => {
447 return Err(no_spelling(format!(
448 "the projection `{projection}` of `{name}`, which the context holds no definition for"
449 )))
450 }
451 };
452 let definition = Command::Definition {
453 name: projection.clone(),
454 ty: Some(ty),
455 body,
456 is_hint: false,
457 implicit_count: 0,
458 proof_goal: None,
459 };
460 out.push(if params.is_empty() {
461 definition
462 } else {
463 Command::Universes {
464 params,
465 inner: Box::new(definition),
466 }
467 });
468 }
469 out.push(Command::Structure {
470 inductive: name.to_string(),
471 projections: info.projections.clone(),
472 });
473 Ok(out)
474}
475
476fn spelled_names(t: &Term, out: &mut HashSet<String>) {
478 let mut work = vec![t];
479 while let Some(t) = work.pop() {
480 match t {
481 Term::Var(n) => {
482 out.insert(n.clone());
483 }
484 Term::Global(n) | Term::Const { name: n, .. } => {
485 out.insert(n.clone());
486 if let Some(name) = Name::parse(n) {
487 out.extend(name.parts().iter().cloned());
488 }
489 }
490 Term::Pi {
491 param,
492 param_type,
493 body_type: body,
494 }
495 | Term::Lambda {
496 param,
497 param_type,
498 body,
499 } => {
500 out.insert(param.clone());
501 work.push(param_type);
502 work.push(body);
503 }
504 Term::Let {
505 name,
506 ty,
507 value,
508 body,
509 } => {
510 out.insert(name.clone());
511 work.push(ty);
512 work.push(value);
513 work.push(body);
514 }
515 Term::Fix { name, body } => {
516 out.insert(name.clone());
517 work.push(body);
518 }
519 Term::MutualFix { defs, .. } => {
520 for (n, b) in defs {
521 out.insert(n.clone());
522 work.push(b);
523 }
524 }
525 Term::App(f, a) => {
526 work.push(f);
527 work.push(a);
528 }
529 Term::Match {
530 discriminant,
531 motive,
532 cases,
533 } => {
534 work.push(discriminant);
535 work.push(motive);
536 work.extend(cases.iter());
537 }
538 Term::Annotated { term, .. } => work.push(term),
539 Term::Sort(_) | Term::Lit(_) | Term::Hole => {}
540 }
541 }
542}
543
544fn split_telescope(
548 ty: &Term,
549 names: &[String],
550 owner: &str,
551) -> Result<(Vec<(String, Term)>, Term), PrintError> {
552 let mut params = Vec::with_capacity(names.len());
553 let mut cur = ty.clone();
554 for name in names {
555 let Term::Pi {
556 param,
557 param_type,
558 body_type,
559 } = cur
560 else {
561 return Err(no_spelling(format!(
562 "`{owner}` with fewer binders than its block's parameters"
563 )));
564 };
565 params.push((name.clone(), *param_type));
566 cur = crate::substitute(&body_type, ¶m, &Term::Global(name.clone()));
567 }
568 Ok((params, cur))
569}
570
571fn declaration(cmd: &Command, universes: Option<&Vec<String>>) -> Result<String, PrintError> {
577 let telescope = match universes {
578 Some(params) => universe_telescope(params)?,
579 None => String::new(),
580 };
581 match cmd {
582 Command::Definition {
583 name,
584 ty,
585 body,
586 is_hint,
587 implicit_count,
588 proof_goal,
589 } => {
590 if *is_hint {
591 return Err(no_spelling(format!(
592 "the hint flag on `{name}` \u{2014} no vernacular command registers a hint"
593 )));
594 }
595 if proof_goal.is_some() {
596 return Err(no_spelling(format!(
597 "the tactic goal carried by `{name}` \u{2014} only a literate `## Theorem` proof \
598 carries one, and the vernacular `Definition` cannot state it"
599 )));
600 }
601 let declared = ty.as_ref().unwrap_or(body);
602 if leading_implicits(declared) != *implicit_count {
603 return Err(no_spelling(format!(
604 "`{name}` with {implicit_count} leading implicit parameter(s) its written type \
605 does not bind \u{2014} the count is read off the `{{…}}` binders, so it cannot \
606 be stated apart from them"
607 )));
608 }
609 let head = format!("Definition {}{telescope}", declaration_name(name)?);
610 match ty {
611 Some(ty) => Ok(format!(
612 "{head} : {} := {}.",
613 slot(print_term(ty)?, &[":="]),
614 plain_term(body, "a definition body under a declared type")?
615 )),
616 None => Ok(format!("{head} := {}.", print_term(body)?)),
617 }
618 }
619 Command::Theorem { name, ty, proof } => Ok(format!(
620 "Theorem {}{telescope} : {} := {}.",
621 declaration_name(name)?,
622 slot(print_term(ty)?, &[":="]),
623 plain_term(proof, "a theorem's proof")?
624 )),
625 Command::TheoremBy { name, ty, script } => {
626 let steps: Vec<String> = script.iter().map(print_tactic).collect::<Result<_, _>>()?;
627 Ok(format!(
628 "Theorem {}{telescope} : {} := by {}.",
629 declaration_name(name)?,
630 slot(print_term(ty)?, &[":="]),
631 steps.join("; ")
632 ))
633 }
634 Command::Opaque { name, ty, value } => Ok(format!(
635 "Opaque {}{telescope} : {} := {}.",
636 declaration_name(name)?,
637 slot(print_term(ty)?, &[":="]),
638 plain_term(value, "an opaque declaration's value")?
639 )),
640 Command::Axiom { name, ty } => Ok(format!(
641 "Axiom {}{telescope} : {}.",
642 declaration_name(name)?,
643 slot(print_term(ty)?, &[":="])
644 )),
645 Command::InductiveBlock { members } => {
646 if members.is_empty() {
647 return Err(no_spelling("an inductive block with no members"));
648 }
649 let members = members
650 .iter()
651 .enumerate()
652 .map(|(i, m)| block_member(m, if i == 0 { &telescope } else { "" }))
653 .collect::<Result<Vec<_>, _>>()?;
654 Ok(format!("Inductive {}.", members.join(" with ")))
655 }
656 Command::Quotient { kind, equality } => {
657 use crate::quotient::Kind;
658 if universes.is_none() {
659 return Err(no_spelling(format!(
660 "the quotient primitive `{}` without its universe parameters \u{2014} they are \
661 always written",
662 kind.name()
663 )));
664 }
665 let word = match kind {
666 Kind::Type => "Type",
667 Kind::Constructor => "Constructor",
668 Kind::Lift => "Lift",
669 Kind::Induction => "Induction",
670 };
671 match (kind, equality) {
672 (Kind::Lift, Some(eq)) => Ok(format!(
673 "Quotient {word}{telescope} using {}.",
674 global_name(eq)?
675 )),
676 (Kind::Lift, None) => Err(no_spelling("a quotient lift naming no equality")),
677 (_, Some(_)) => Err(no_spelling(format!(
678 "`Quotient {word}` naming an equality \u{2014} only `Lift` states one"
679 ))),
680 (_, None) => Ok(format!("Quotient {word}{telescope}.")),
681 }
682 }
683 Command::Universes { .. } => Err(no_spelling(
684 "a universe telescope on a declaration that already has one",
685 )),
686 other => Err(no_spelling(format!(
687 "a universe telescope on {} \u{2014} universe parameters bind over a declaration",
688 match other {
689 Command::Inductive { .. } => "the undeclared-arity `Inductive` form",
690 _ => "a command that declares nothing",
691 }
692 ))),
693 }
694}
695
696fn block_member(m: &InductiveMember, telescope: &str) -> Result<String, PrintError> {
698 let mut out = format!("{}{telescope}", declaration_name(&m.name)?);
699 for (param, ty) in &m.params {
700 out.push_str(¶meter(param, ty)?);
701 }
702 out.push_str(" : ");
703 out.push_str(&slot(
704 plain_term(&m.sort, "an inductive's arity")?,
705 &[":=", "|", "with"],
706 ));
707 if m.full_constructors && m.params.is_empty() {
708 return Err(no_spelling(format!(
709 "restated constructor telescopes on `{}`, which has no parameters to restate",
710 m.name
711 )));
712 }
713 if !m.constructors.is_empty() {
714 out.push_str(" := ");
715 out.push_str(&if m.full_constructors {
716 restated_constructor_list(&m.constructors, m.params.len())?
717 } else {
718 constructor_list(&m.constructors)?
719 });
720 }
721 Ok(out)
722}
723
724fn restated_constructor_list(
727 constructors: &[(String, Term)],
728 params: usize,
729) -> Result<String, PrintError> {
730 let parts = constructors
731 .iter()
732 .map(|(name, ty)| {
733 if has_kind_marker(ty) {
734 return Err(no_spelling(format!(
735 "a binder-kind marker in the constructor `{name}`"
736 )));
737 }
738 let mut printer = Printer::new(&[ty]);
739 let (binders, rest) = printer.telescope(ty, params, name)?;
740 Ok(format!(
741 "{}{binders} : {}",
742 declaration_name(name)?,
743 slot(rest, &[":=", "|", "with"])
744 ))
745 })
746 .collect::<Result<Vec<_>, PrintError>>()?;
747 Ok(parts.join(" | "))
748}
749
750fn parameter(name: &str, ty: &Term) -> Result<String, PrintError> {
752 Ok(format!(
753 " ({} : {})",
754 declaration_name(name)?,
755 plain_term(ty, "a parameter's type")?
756 ))
757}
758
759fn constructor_list(constructors: &[(String, Term)]) -> Result<String, PrintError> {
761 let parts = constructors
762 .iter()
763 .map(|(name, ty)| {
764 Ok(format!(
765 "{} : {}",
766 declaration_name(name)?,
767 slot(
768 plain_term(ty, "a constructor's type")?,
769 &[":=", "|", "with"]
770 )
771 ))
772 })
773 .collect::<Result<Vec<_>, PrintError>>()?;
774 Ok(parts.join(" | "))
775}
776
777fn universe_telescope(params: &[String]) -> Result<String, PrintError> {
779 let names = params
780 .iter()
781 .map(|p| level_name(p))
782 .collect::<Result<Vec<_>, _>>()?;
783 Ok(format!(".{{{}}}", names.join(", ")))
784}
785
786fn plain_term(t: &Term, slot_name: &str) -> Result<String, PrintError> {
790 if has_kind_marker(t) {
791 return Err(no_spelling(format!(
792 "a binder-kind marker (`{{…}}`, `⦃…⦄`, `[…]`) in {slot_name} \u{2014} that slot reads its \
793 term with the kinds stripped, so the binder kind would not survive"
794 )));
795 }
796 print_term(t)
797}
798
799fn slot(text: String, separators: &[&str]) -> String {
802 let splits = separators.iter().any(|sep| {
803 if sep.chars().all(char::is_alphabetic) {
804 top_level_word(&text, sep)
805 } else {
806 TermParser::find_top_level(&text, sep).is_some()
807 }
808 });
809 if splits {
810 format!("({text})")
811 } else {
812 text
813 }
814}
815
816fn top_level_word(text: &str, word: &str) -> bool {
818 let is_ident = |c: char| c.is_alphanumeric() || c == '_' || c == '\'';
819 let mut from = 0;
820 while let Some(rel) = TermParser::find_top_level(&text[from..], word) {
821 let at = from + rel;
822 let before = text[..at].chars().next_back();
823 let after = text[at + word.len()..].chars().next();
824 if before.is_none_or(|c| !is_ident(c)) && after.is_none_or(|c| !is_ident(c)) {
825 return true;
826 }
827 from = at + word.len();
828 }
829 false
830}
831
832fn leading_implicits(t: &Term) -> usize {
835 TermParser::binder_kinds_of(t)
836 .iter()
837 .take_while(|k| **k == TermParser::IMPLICIT)
838 .count()
839}
840
841fn open_clauses(clauses: &[OpenClause]) -> Result<String, PrintError> {
842 if clauses.is_empty() {
843 return Err(no_spelling("an `Open` naming no namespace"));
844 }
845 let mut parts = Vec::with_capacity(clauses.len());
846 for clause in clauses {
847 let parts_of = clause.namespace.parts();
850 let mut part = if matches!(parts_of[0].as_str(), "in" | "hiding" | "renaming") {
851 let mut quoted = format!("\u{ab}{}\u{bb}", parts_of[0]);
852 for p in &parts_of[1..] {
853 quoted.push('.');
854 quoted.push_str(&tail_component(p)?);
855 }
856 quoted
857 } else {
858 name_path(&clause.namespace)?
859 };
860 let forms = clause.only.is_some() as usize
861 + (!clause.hiding.is_empty()) as usize
862 + (!clause.renaming.is_empty()) as usize;
863 if forms > 1 {
864 return Err(no_spelling(format!(
865 "an `Open {part}` clause that both narrows and renames \u{2014} a clause is one form"
866 )));
867 }
868 if let Some(only) = &clause.only {
869 let names = only.iter().map(name_path).collect::<Result<Vec<_>, _>>()?;
870 if names.is_empty() {
871 return Err(no_spelling(format!("an `Open {part} ()` naming nothing")));
872 }
873 part.push_str(&format!(" ({})", names.join(" ")));
874 } else if !clause.hiding.is_empty() {
875 let names = clause
876 .hiding
877 .iter()
878 .map(name_path)
879 .collect::<Result<Vec<_>, _>>()?;
880 part.push_str(&format!(" hiding ({})", names.join(" ")));
881 } else if !clause.renaming.is_empty() {
882 let pairs = clause
883 .renaming
884 .iter()
885 .map(|(member, local)| {
886 Ok(format!("{} -> {}", name_path(member)?, name_path(local)?))
887 })
888 .collect::<Result<Vec<_>, PrintError>>()?;
889 part.push_str(&format!(" renaming {}", pairs.join(", ")));
890 }
891 parts.push(part);
892 }
893 Ok(parts.join(" "))
894}
895
896fn surface_declaration(decl: &SurfaceDecl) -> Result<String, PrintError> {
897 let quoted = |pattern: &str| {
898 if pattern.contains('"') {
899 Err(no_spelling(format!(
900 "the pattern {pattern:?} \u{2014} a notation's pattern is a quoted string with no `\"` in it"
901 )))
902 } else {
903 Ok(format!("\"{pattern}\""))
904 }
905 };
906 let expansion = |template: &str| {
907 let text = template.trim();
908 let sentence = format!("{text}.");
909 if text.is_empty() || super::command_parser::sentence_end(&sentence) != sentence.len() {
910 Err(no_spelling(format!(
911 "the expansion {template:?} \u{2014} it would end the sentence early"
912 )))
913 } else {
914 Ok(text.to_string())
915 }
916 };
917 let prec = |p: &Option<u32>| p.map(|p| format!("{p} ")).unwrap_or_default();
918 match decl {
919 SurfaceDecl::Notation {
920 pattern,
921 template,
922 precedence,
923 assoc,
924 operator_form,
925 } => {
926 let word = match (operator_form, assoc) {
927 (None, Assoc::Right) => "Notation",
928 (Some(OperatorForm::Infix), Assoc::Left) => "Infixl",
929 (Some(OperatorForm::Infix), Assoc::Right) => "Infixr",
930 (Some(OperatorForm::Prefix), Assoc::Right) => "Prefix",
931 (Some(OperatorForm::Postfix), Assoc::Left) => "Postfix",
932 _ => {
933 return Err(no_spelling(format!(
934 "the notation {pattern:?} with {assoc:?} associativity \u{2014} no command \
935 word declares that combination"
936 )))
937 }
938 };
939 if word.starts_with("Infix") && precedence.is_none() {
940 return Err(no_spelling(format!(
941 "the infix notation {pattern:?} with no precedence \u{2014} `{word}` requires one"
942 )));
943 }
944 Ok(format!(
945 "{word} {}{} := {}.",
946 prec(precedence),
947 quoted(pattern)?,
948 expansion(template)?
949 ))
950 }
951 SurfaceDecl::Macro { pattern, template } => Ok(format!(
952 "Macro {} := {}.",
953 quoted(pattern)?,
954 expansion(template)?
955 )),
956 SurfaceDecl::OpenSection(None) => Ok("Section.".to_string()),
957 SurfaceDecl::OpenSection(Some(name)) => Ok(format!("Section {}.", declaration_name(name)?)),
958 SurfaceDecl::CloseSection(None) => Ok("End.".to_string()),
959 SurfaceDecl::CloseSection(Some(name)) => Ok(format!("End {}.", declaration_name(name)?)),
960 SurfaceDecl::OpenNamespace(name) => Ok(format!("Namespace {}.", name_path(name)?)),
961 }
962}
963
964fn plain_atom(s: &str) -> bool {
970 matches!(scan_name(s), Some((_, len)) if len == s.len())
971 && !s.starts_with('\u{ab}')
972 && !is_reserved_word(s)
973 && !is_sort_word(s)
974 && s != "_"
975}
976
977fn quotable(s: &str) -> bool {
979 !s.is_empty() && !s.contains(['\u{ab}', '\u{bb}', '\u{27e8}', '\u{27e9}'])
980}
981
982fn token(s: &str, what: &str) -> Result<String, PrintError> {
984 if plain_atom(s) {
985 Ok(s.to_string())
986 } else if quotable(s) {
987 Ok(format!("\u{ab}{s}\u{bb}"))
988 } else if s.is_empty() {
989 Err(no_spelling(format!("the empty name as {what}")))
990 } else {
991 Err(no_spelling(format!(
992 "the name {s:?} as {what} \u{2014} a name holding a guillemet or an angle bracket has no \
993 quoted form"
994 )))
995 }
996}
997
998fn tail_component(s: &str) -> Result<String, PrintError> {
1001 if matches!(scan_name(s), Some((_, len)) if len == s.len()) && !s.starts_with('\u{ab}') {
1002 Ok(s.to_string())
1003 } else {
1004 token(s, "a name component")
1005 }
1006}
1007
1008fn global_name(key: &str) -> Result<String, PrintError> {
1015 if key == "_" {
1016 return Err(no_spelling(
1017 "the global `_` \u{2014} `_` is a hole, and the kernel's own match-case placeholder; it is \
1018 never a name a term refers to",
1019 ));
1020 }
1021 match Name::parse(key) {
1022 Some(name) if name.parts().len() > 1 => {
1023 let parts = name.parts();
1024 let mut out = token(&parts[0], "a qualified name's head")?;
1025 for part in &parts[1..] {
1026 out.push('.');
1027 out.push_str(&tail_component(part)?);
1028 }
1029 Ok(out)
1030 }
1031 _ => token(key, "a global"),
1032 }
1033}
1034
1035fn declaration_name(key: &str) -> Result<String, PrintError> {
1038 global_name(key)
1039}
1040
1041fn name_path(name: &Name) -> Result<String, PrintError> {
1043 let parts = name.parts();
1044 let mut out = token(&parts[0], "a name")?;
1045 for part in &parts[1..] {
1046 out.push('.');
1047 out.push_str(&tail_component(part)?);
1048 }
1049 Ok(out)
1050}
1051
1052fn level_name(name: &str) -> Result<String, PrintError> {
1054 if plain_atom(name) && name != "max" && name != "imax" {
1055 Ok(name.to_string())
1056 } else {
1057 let quoted = token(name, "a universe variable")?;
1058 if quoted.starts_with('\u{ab}') {
1059 Ok(quoted)
1060 } else {
1061 Ok(format!("\u{ab}{name}\u{bb}"))
1062 }
1063 }
1064}
1065
1066fn level(u: &Universe, atom: bool) -> Result<String, PrintError> {
1073 let text = match u {
1074 Universe::Prop => return Ok("0".to_string()),
1075 Universe::Type(n) => return Ok((u64::from(*n) + 1).to_string()),
1076 Universe::Var(name) => return level_name(name),
1077 Universe::SProp => {
1078 return Err(no_spelling(
1079 "`SProp` inside a universe level \u{2014} it is a sort, not a level, and collapses out \
1080 of every `max`, `imax` and successor",
1081 ))
1082 }
1083 Universe::Succ(_) => {
1084 let mut base = u;
1085 let mut k: u64 = 0;
1086 while let Universe::Succ(inner) = base {
1087 k += 1;
1088 base = inner;
1089 }
1090 let mut text = level(base, true)?;
1091 while k > 0 {
1092 let step = k.min(MAX_LEVEL_OFFSET);
1093 text.push_str(&format!("+{step}"));
1094 k -= step;
1095 }
1096 text
1097 }
1098 Universe::Max(a, b) => format!("max {} {}", level(a, true)?, level(b, true)?),
1099 Universe::IMax(a, b) => format!("imax {} {}", level(a, true)?, level(b, true)?),
1100 };
1101 Ok(if atom { format!("({text})") } else { text })
1102}
1103
1104fn sort(u: &Universe) -> Result<String, PrintError> {
1106 Ok(match u {
1107 Universe::Prop => "Prop".to_string(),
1108 Universe::SProp => "SProp".to_string(),
1109 Universe::Type(0) => "Type".to_string(),
1110 Universe::Type(n) => format!("Type {n}"),
1111 other => format!("Sort {}", level(other, true)?),
1112 })
1113}
1114
1115fn literal(l: &Literal) -> Result<String, PrintError> {
1120 Ok(match l {
1121 Literal::Int(n) => n.to_string(),
1122 Literal::BigInt(n) => {
1123 if n.to_i64().is_some() {
1124 return Err(no_spelling(format!(
1125 "the BigInt literal {n}, which fits a machine integer \u{2014} an integer has ONE \
1126 literal representation, and this value's is `Int`"
1127 )));
1128 }
1129 n.to_string()
1130 }
1131 Literal::Nat(n) => {
1132 if n.is_negative() {
1133 return Err(no_spelling(format!("the negative Nat literal {n}")));
1134 }
1135 format!("{n}n")
1136 }
1137 Literal::Float(x) if x.is_finite() => format!("{x:?}f64"),
1138 Literal::Float(x) => format!("0x{:016x}f64", x.to_bits()),
1139 Literal::Text(s) => text(s),
1140 Literal::Duration(n) => duration(*n),
1141 Literal::Date(d) => date(i64::from(*d)),
1142 Literal::Moment(n) => moment(*n),
1143 })
1144}
1145
1146fn text(s: &str) -> String {
1147 let mut out = String::with_capacity(s.len() + 2);
1148 out.push('"');
1149 for c in s.chars() {
1150 match c {
1151 '"' => out.push_str("\\\""),
1152 '\\' => out.push_str("\\\\"),
1153 '\n' => out.push_str("\\n"),
1154 '\t' => out.push_str("\\t"),
1155 c if c.is_control() || matches!(c, '\u{2028}' | '\u{2029}' | '\u{feff}') => {
1156 out.push_str(&format!("\\u{{{:x}}}", c as u32))
1157 }
1158 c => out.push(c),
1159 }
1160 }
1161 out.push('"');
1162 out
1163}
1164
1165fn annotation_text(annotation: &crate::Annotation) -> String {
1167 use crate::{Annotation, Provenance};
1168 match annotation {
1169 Annotation::Binder(kind) => format!("binder {}", kind.lean_binder_info()),
1170 Annotation::Source(Provenance::Position(position)) => {
1171 format!("source {}", text(position.as_str()))
1172 }
1173 Annotation::Source(Provenance::LeanMetadata { key, value }) => {
1174 format!("mdata {} {}", text(key), text(value))
1175 }
1176 }
1177}
1178
1179fn duration(n: i64) -> String {
1181 if n == 0 {
1182 return "0s".to_string();
1183 }
1184 const UNITS: [(&str, i64); 6] = [
1185 ("h", 3_600_000_000_000),
1186 ("min", 60_000_000_000),
1187 ("s", 1_000_000_000),
1188 ("ms", 1_000_000),
1189 ("us", 1_000),
1190 ("ns", 1),
1191 ];
1192 let (unit, per) = UNITS
1193 .iter()
1194 .find(|(_, per)| n % per == 0)
1195 .expect("every count divides by 1");
1196 format!("{}{unit}", n / per)
1197}
1198
1199fn calendar_date(days: i64) -> String {
1200 let (y, m, d) = super::term_parser::civil_from_days(days);
1201 let year = if y < 0 {
1202 format!("-{:04}", -y)
1203 } else {
1204 format!("{y:04}")
1205 };
1206 format!("{year}-{m:02}-{d:02}")
1207}
1208
1209fn date(days: i64) -> String {
1210 calendar_date(days)
1211}
1212
1213fn moment(nanos: i64) -> String {
1214 let secs = nanos.div_euclid(1_000_000_000);
1215 let frac = nanos.rem_euclid(1_000_000_000);
1216 let days = secs.div_euclid(86_400);
1217 let tod = secs.rem_euclid(86_400);
1218 let mut out = format!(
1219 "{}T{:02}:{:02}:{:02}",
1220 calendar_date(days),
1221 tod / 3600,
1222 (tod % 3600) / 60,
1223 tod % 60
1224 );
1225 if frac != 0 {
1226 let digits = format!("{frac:09}");
1227 out.push('.');
1228 out.push_str(digits.trim_end_matches('0'));
1229 }
1230 out.push('Z');
1231 out
1232}
1233
1234fn negative_literal(l: &Literal) -> bool {
1236 match l {
1237 Literal::Int(n) | Literal::Duration(n) | Literal::Moment(n) => *n < 0,
1238 Literal::BigInt(n) => n.is_negative(),
1239 Literal::Float(x) => x.is_finite() && x.is_sign_negative(),
1240 Literal::Date(d) => super::term_parser::civil_from_days(i64::from(*d)).0 < 0,
1241 Literal::Nat(_) | Literal::Text(_) => false,
1242 }
1243}
1244
1245#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1251enum Ctx {
1252 Top,
1255 BeforeWith,
1258 Operand,
1260 Atom,
1262}
1263
1264#[derive(Clone, Copy, PartialEq, Eq)]
1266enum Shape {
1267 Atom,
1269 App,
1271 Closed,
1273 Open,
1275 Fix,
1277}
1278
1279fn needs_parens(shape: Shape, ctx: Ctx) -> bool {
1280 match ctx {
1281 Ctx::Top => false,
1282 Ctx::BeforeWith => shape == Shape::Fix,
1283 Ctx::Operand => !matches!(shape, Shape::Atom | Shape::App),
1284 Ctx::Atom => shape != Shape::Atom,
1285 }
1286}
1287
1288type BinderId = (usize, usize);
1291
1292fn node_id(t: &Term) -> usize {
1293 t as *const Term as usize
1294}
1295
1296enum Job<'t> {
1298 Text(&'static str),
1299 Owned(String),
1300 Term(&'t Term, Ctx),
1301 Bind(Vec<(String, String)>),
1303 Unbind(usize),
1305}
1306
1307struct Printer {
1308 out: String,
1309 referenced: HashSet<BinderId>,
1311 avoid: HashSet<String>,
1314 taken: HashSet<String>,
1316 fresh: usize,
1317 scope: HashMap<String, Vec<String>>,
1319 scope_order: Vec<String>,
1320}
1321
1322impl Printer {
1323 fn new(roots: &[&Term]) -> Self {
1324 let mut printer = Printer {
1325 out: String::new(),
1326 referenced: HashSet::new(),
1327 avoid: HashSet::new(),
1328 taken: HashSet::new(),
1329 fresh: 0,
1330 scope: HashMap::new(),
1331 scope_order: Vec::new(),
1332 };
1333 for root in roots {
1334 printer.scan(root);
1335 }
1336 printer
1337 }
1338
1339 fn scan(&mut self, root: &Term) {
1341 enum Visit<'t> {
1342 Enter(&'t Term),
1343 Bind(String, BinderId),
1344 Unbind(String),
1345 }
1346 let mut bound: HashMap<String, Vec<BinderId>> = HashMap::new();
1347 let mut work = vec![Visit::Enter(root)];
1348 while let Some(visit) = work.pop() {
1349 let t = match visit {
1350 Visit::Bind(name, id) => {
1351 bound.entry(name).or_default().push(id);
1352 continue;
1353 }
1354 Visit::Unbind(name) => {
1355 if let Some(stack) = bound.get_mut(&name) {
1356 stack.pop();
1357 }
1358 continue;
1359 }
1360 Visit::Enter(t) => t,
1361 };
1362 match t {
1363 Term::Var(n) => {
1364 self.taken.insert(n.clone());
1365 if let Some(id) = bound.get(n).and_then(|s| s.last()) {
1366 self.referenced.insert(*id);
1367 }
1368 }
1369 Term::Global(n) | Term::Const { name: n, .. } => self.note_global(n),
1370 Term::Sort(_) | Term::Lit(_) | Term::Hole => {}
1371 Term::Pi {
1372 param,
1373 param_type,
1374 body_type: body,
1375 }
1376 | Term::Lambda {
1377 param,
1378 param_type,
1379 body,
1380 } => {
1381 self.taken.insert(param.clone());
1382 work.push(Visit::Unbind(param.clone()));
1383 work.push(Visit::Enter(body));
1384 work.push(Visit::Bind(param.clone(), (node_id(t), 0)));
1385 work.push(Visit::Enter(param_type));
1386 }
1387 Term::Let {
1388 name,
1389 ty,
1390 value,
1391 body,
1392 } => {
1393 self.taken.insert(name.clone());
1394 work.push(Visit::Unbind(name.clone()));
1395 work.push(Visit::Enter(body));
1396 work.push(Visit::Bind(name.clone(), (node_id(t), 0)));
1397 work.push(Visit::Enter(value));
1398 work.push(Visit::Enter(ty));
1399 }
1400 Term::Fix { name, body } => {
1401 self.taken.insert(name.clone());
1402 work.push(Visit::Unbind(name.clone()));
1403 work.push(Visit::Enter(body));
1404 work.push(Visit::Bind(name.clone(), (node_id(t), 0)));
1405 }
1406 Term::MutualFix { defs, .. } => {
1407 for (n, _) in defs.iter().rev() {
1408 work.push(Visit::Unbind(n.clone()));
1409 }
1410 for (_, b) in defs.iter().rev() {
1411 work.push(Visit::Enter(b));
1412 }
1413 for (i, (n, _)) in defs.iter().enumerate().rev() {
1414 self.taken.insert(n.clone());
1415 work.push(Visit::Bind(n.clone(), (node_id(t), i)));
1416 }
1417 }
1418 Term::Match {
1419 discriminant,
1420 motive,
1421 cases,
1422 } => {
1423 for c in cases.iter().rev() {
1424 work.push(Visit::Enter(c));
1425 }
1426 work.push(Visit::Enter(motive));
1427 work.push(Visit::Enter(discriminant));
1428 }
1429 Term::App(f, a) => {
1430 if let Some(label_free) = label_positions(t) {
1433 for part in label_free {
1434 work.push(Visit::Enter(part));
1435 }
1436 continue;
1437 }
1438 work.push(Visit::Enter(a));
1439 work.push(Visit::Enter(f));
1440 }
1441 Term::Annotated { term, .. } => work.push(Visit::Enter(term)),
1442 }
1443 }
1444 }
1445
1446 fn note_global(&mut self, key: &str) {
1447 self.taken.insert(key.to_string());
1448 self.avoid.insert(key.to_string());
1449 if let Some(name) = Name::parse(key) {
1450 for part in name.parts() {
1451 self.taken.insert(part.clone());
1452 }
1453 self.avoid.insert(name.parts()[0].clone());
1454 }
1455 }
1456
1457 fn choose(&mut self, name: &str, referenced: bool) -> String {
1460 if name == "_" && !referenced {
1461 return "_".to_string();
1462 }
1463 if !self.avoid.contains(name) && plain_atom(name) {
1468 return name.to_string();
1469 }
1470 loop {
1471 let candidate = if self.fresh == 0 {
1472 "x".to_string()
1473 } else {
1474 format!("x{}", self.fresh)
1475 };
1476 self.fresh += 1;
1477 if !self.taken.contains(&candidate) && !self.avoid.contains(&candidate) {
1478 self.taken.insert(candidate.clone());
1479 return candidate;
1480 }
1481 }
1482 }
1483
1484 fn bind(&mut self, pairs: Vec<(String, String)>) {
1485 for (name, spelled) in pairs {
1486 self.scope.entry(name.clone()).or_default().push(spelled);
1487 self.scope_order.push(name);
1488 }
1489 }
1490
1491 fn unbind(&mut self, n: usize) {
1492 for _ in 0..n {
1493 if let Some(name) = self.scope_order.pop() {
1494 if let Some(stack) = self.scope.get_mut(&name) {
1495 stack.pop();
1496 }
1497 }
1498 }
1499 }
1500
1501 fn telescope(
1505 &mut self,
1506 t: &Term,
1507 count: usize,
1508 owner: &str,
1509 ) -> Result<(String, String), PrintError> {
1510 let mut binders = String::new();
1511 let mut cur = t;
1512 let mut bound = 0;
1513 for _ in 0..count {
1514 let Term::Pi {
1515 param,
1516 param_type,
1517 body_type,
1518 } = cur
1519 else {
1520 return Err(no_spelling(format!(
1521 "`{owner}` restating {count} parameters with fewer binders"
1522 )));
1523 };
1524 if !matches!(split_kind(param_type).0, Kind::Explicit)
1525 || split_default(param_type)?.1.is_some()
1526 {
1527 return Err(no_spelling(format!(
1528 "a marked parameter binder in `{owner}`"
1529 )));
1530 }
1531 let spelled = self.choose(param, true);
1532 let start = self.out.len();
1533 self.term(param_type, Ctx::Top)?;
1534 let ty = self.out.split_off(start);
1535 binders.push_str(&format!(" ({spelled} : {ty})"));
1536 self.bind(vec![(param.clone(), spelled)]);
1537 bound += 1;
1538 cur = body_type;
1539 }
1540 let start = self.out.len();
1541 self.term(cur, Ctx::Top)?;
1542 let rest = self.out.split_off(start);
1543 self.unbind(bound);
1544 Ok((binders, rest))
1545 }
1546
1547 fn term(&mut self, root: &Term, ctx: Ctx) -> Result<(), PrintError> {
1548 let mut work: Vec<Job<'_>> = vec![Job::Term(root, ctx)];
1549 while let Some(job) = work.pop() {
1550 match job {
1551 Job::Text(s) => self.out.push_str(s),
1552 Job::Owned(s) => self.out.push_str(&s),
1553 Job::Bind(pairs) => self.bind(pairs),
1554 Job::Unbind(n) => self.unbind(n),
1555 Job::Term(t, ctx) => {
1556 let shape = self.shape(t);
1557 if needs_parens(shape, ctx) {
1558 work.push(Job::Text(")"));
1559 work.push(Job::Term(t, Ctx::Top));
1560 work.push(Job::Text("("));
1561 continue;
1562 }
1563 let mut jobs = self.expand(t, ctx)?;
1564 jobs.reverse();
1565 work.extend(jobs);
1566 }
1567 }
1568 }
1569 Ok(())
1570 }
1571
1572 fn shape(&self, t: &Term) -> Shape {
1574 match t {
1575 Term::Var(_) | Term::Global(_) | Term::Const { .. } | Term::Hole => Shape::Atom,
1576 Term::Sort(Universe::Prop | Universe::SProp | Universe::Type(0)) => Shape::Atom,
1577 Term::Sort(_) => Shape::App,
1578 Term::Lit(l) if negative_literal(l) => Shape::App,
1579 Term::Lit(_) => Shape::Atom,
1580 Term::App(..) => match spine_form(t) {
1581 SpineForm::Named | SpineForm::Anon | SpineForm::Projection => Shape::Atom,
1582 _ => Shape::App,
1583 },
1584 Term::Match { .. } => Shape::Closed,
1585 Term::Fix { .. } | Term::MutualFix { .. } => Shape::Fix,
1586 Term::Pi { .. } | Term::Lambda { .. } | Term::Let { .. } => Shape::Open,
1587 Term::Annotated { .. } => Shape::App,
1590 }
1591 }
1592
1593 fn expand<'t>(&mut self, t: &'t Term, ctx: Ctx) -> Result<Vec<Job<'t>>, PrintError> {
1595 let tail = if ctx == Ctx::BeforeWith {
1597 Ctx::BeforeWith
1598 } else {
1599 Ctx::Top
1600 };
1601 Ok(match t {
1602 Term::Var(n) => match self.scope.get(n).and_then(|s| s.last()) {
1603 Some(spelled) => vec![Job::Owned(spelled.clone())],
1604 None => {
1605 return Err(no_spelling(format!(
1606 "the free local variable `{n}` \u{2014} the surface reads a free name as a \
1607 GLOBAL, so printing it would denote something else"
1608 )))
1609 }
1610 },
1611 Term::Global(n) => vec![Job::Owned(global_name(n)?)],
1612 Term::Const { name, levels } => {
1613 let levels = levels
1614 .iter()
1615 .map(|l| level(l, false))
1616 .collect::<Result<Vec<_>, _>>()?;
1617 vec![Job::Owned(format!(
1618 "{}.{{{}}}",
1619 global_name(name)?,
1620 levels.join(", ")
1621 ))]
1622 }
1623 Term::Sort(u) => vec![Job::Owned(sort(u)?)],
1624 Term::Lit(l) => vec![Job::Owned(literal(l)?)],
1625 Term::Hole => vec![Job::Text("_")],
1626 Term::App(..) => self.application(t)?,
1627 Term::Pi {
1628 param,
1629 param_type,
1630 body_type,
1631 } => self.pi(t, param, param_type, body_type, tail)?,
1632 Term::Lambda { .. } => self.lambda(t, tail)?,
1633 Term::Let {
1634 name,
1635 ty,
1636 value,
1637 body,
1638 } => {
1639 let spelled = self.choose(name, self.referenced.contains(&(node_id(t), 0)));
1640 let wrap = |x: &Term| {
1641 if matches!(x, Term::Let { .. }) {
1642 Ctx::Operand
1643 } else {
1644 Ctx::Top
1645 }
1646 };
1647 vec![
1648 Job::Text("let "),
1649 Job::Owned(spelled.clone()),
1650 Job::Text(" : "),
1651 Job::Term(ty, wrap(ty)),
1652 Job::Text(" := "),
1653 Job::Term(value, wrap(value)),
1654 Job::Text("; "),
1655 Job::Bind(vec![(name.clone(), spelled)]),
1656 Job::Term(body, tail),
1657 Job::Unbind(1),
1658 ]
1659 }
1660 Term::Fix { name, body } => {
1661 let spelled = self.choose(name, self.referenced.contains(&(node_id(t), 0)));
1662 vec![
1663 Job::Text("fix "),
1664 Job::Owned(spelled.clone()),
1665 Job::Text(" => "),
1666 Job::Bind(vec![(name.clone(), spelled)]),
1667 Job::Term(body, tail),
1668 Job::Unbind(1),
1669 ]
1670 }
1671 Term::MutualFix { defs, index } => {
1672 let mut seen = HashSet::new();
1673 for (n, _) in defs {
1674 if !seen.insert(n.as_str()) {
1675 return Err(no_spelling(format!(
1676 "a mutual fix naming `{n}` twice \u{2014} `return {n}` could not say which \
1677 member the term denotes"
1678 )));
1679 }
1680 }
1681 if *index >= defs.len() {
1682 return Err(no_spelling(format!(
1683 "a mutual fix of {} member(s) selecting member {index}",
1684 defs.len()
1685 )));
1686 }
1687 let spelled: Vec<String> = defs
1688 .iter()
1689 .enumerate()
1690 .map(|(_, (n, _))| self.choose(n, true))
1693 .collect();
1694 let mut jobs = vec![
1695 Job::Text("fix "),
1696 Job::Bind(
1697 defs.iter()
1698 .map(|(n, _)| n.clone())
1699 .zip(spelled.iter().cloned())
1700 .collect(),
1701 ),
1702 ];
1703 for (i, (_, body)) in defs.iter().enumerate() {
1704 if i > 0 {
1705 jobs.push(Job::Text(" with "));
1706 }
1707 jobs.push(Job::Owned(spelled[i].clone()));
1708 jobs.push(Job::Text(" => "));
1709 jobs.push(Job::Term(body, Ctx::BeforeWith));
1710 }
1711 jobs.push(Job::Unbind(defs.len()));
1712 jobs.push(Job::Text(" return "));
1713 jobs.push(Job::Owned(spelled[*index].clone()));
1714 jobs
1715 }
1716 Term::Match {
1717 discriminant,
1718 motive,
1719 cases,
1720 } => self.matching(discriminant, motive, cases)?,
1721 Term::Annotated { annotation, term } => vec![
1722 Job::Text("@["),
1723 Job::Owned(annotation_text(annotation)),
1724 Job::Text("] "),
1725 Job::Term(term, Ctx::Atom),
1726 ],
1727 })
1728 }
1729
1730 fn pi<'t>(
1733 &mut self,
1734 node: &'t Term,
1735 param: &str,
1736 param_type: &'t Term,
1737 body: &'t Term,
1738 tail: Ctx,
1739 ) -> Result<Vec<Job<'t>>, PrintError> {
1740 let referenced = self.referenced.contains(&(node_id(node), 0));
1741 let (kind, declared) = split_kind(param_type);
1742 let (declared, default) = split_default(declared)?;
1743 if kind == Kind::Explicit && default.is_none() && !referenced {
1744 return Ok(vec![
1745 Job::Term(declared, Ctx::Operand),
1746 Job::Text(" -> "),
1747 Job::Term(body, tail),
1748 ]);
1749 }
1750 let spelled = self.choose(param, referenced);
1751 let (open, close) = kind.brackets();
1752 let mut jobs = vec![Job::Text(open)];
1753 let anonymous_instance = kind == Kind::Instance && spelled == "_" && default.is_none();
1754 if !anonymous_instance {
1755 jobs.push(Job::Owned(spelled.clone()));
1756 jobs.push(Job::Text(" : "));
1757 }
1758 jobs.push(Job::Term(declared, Ctx::Top));
1759 match default {
1760 Some(Default::Value(v)) if kind == Kind::Explicit => {
1761 jobs.push(Job::Text(" := "));
1762 jobs.push(Job::Term(v, Ctx::Top));
1763 }
1764 Some(Default::Tactic(tac)) if kind == Kind::Explicit => {
1765 jobs.push(Job::Owned(format!(
1766 " := by {}",
1767 token(tac, "an auto-param tactic")?
1768 )));
1769 }
1770 Some(_) => {
1771 return Err(no_spelling(format!(
1772 "a default on the {} binder `{param}` \u{2014} only an explicit binder carries one",
1773 kind.describe()
1774 )))
1775 }
1776 None => {}
1777 }
1778 jobs.push(Job::Text(close));
1779 jobs.push(Job::Text(" -> "));
1780 jobs.push(Job::Bind(vec![(param.to_string(), spelled)]));
1781 jobs.push(Job::Term(body, tail));
1782 jobs.push(Job::Unbind(1));
1783 Ok(jobs)
1784 }
1785
1786 fn lambda<'t>(&mut self, t: &'t Term, tail: Ctx) -> Result<Vec<Job<'t>>, PrintError> {
1788 let mut jobs = vec![Job::Text("fun")];
1789 let mut cur = t;
1790 let mut bound = 0;
1791 while let Term::Lambda {
1792 param,
1793 param_type,
1794 body,
1795 } = cur
1796 {
1797 let referenced = self.referenced.contains(&(node_id(cur), 0));
1798 let (kind, declared) = split_kind(param_type);
1799 if TermParser::as_default_param(param_type).is_some()
1800 || TermParser::as_auto_param(param_type).is_some()
1801 {
1802 return Err(no_spelling(format!(
1803 "a default on the `fun` binder `{param}` \u{2014} a `fun` binder carries none"
1804 )));
1805 }
1806 let spelled = self.choose(param, referenced);
1807 let (open, close) = kind.brackets();
1808 jobs.push(Job::Text(" "));
1809 match (kind, declared) {
1810 (Kind::Explicit, Term::Hole) => jobs.push(Job::Owned(spelled.clone())),
1811 (Kind::Instance, _) if spelled == "_" => {
1812 jobs.push(Job::Text(open));
1813 jobs.push(Job::Term(declared, Ctx::Top));
1814 jobs.push(Job::Text(close));
1815 }
1816 (_, Term::Hole) => {
1817 jobs.push(Job::Text(open));
1818 jobs.push(Job::Owned(spelled.clone()));
1819 jobs.push(Job::Text(close));
1820 }
1821 _ => {
1822 jobs.push(Job::Text(open));
1823 jobs.push(Job::Owned(spelled.clone()));
1824 jobs.push(Job::Text(" : "));
1825 jobs.push(Job::Term(declared, Ctx::Top));
1826 jobs.push(Job::Text(close));
1827 }
1828 }
1829 jobs.push(Job::Bind(vec![(param.clone(), spelled)]));
1830 bound += 1;
1831 cur = body;
1832 }
1833 jobs.push(Job::Text(" => "));
1834 jobs.push(Job::Term(cur, tail));
1835 jobs.push(Job::Unbind(bound));
1836 Ok(jobs)
1837 }
1838
1839 fn application<'t>(&mut self, t: &'t Term) -> Result<Vec<Job<'t>>, PrintError> {
1841 let (head, args) = spine(t);
1842 let ellipsis = args
1845 .last()
1846 .is_some_and(|a| is_marker(a, TermParser::ELLIPSIS_MARKER))
1847 && !matches!(spine_form(t), SpineForm::Anon);
1848 let mut jobs = if ellipsis {
1849 vec![Job::Text("(")]
1850 } else {
1851 Vec::new()
1852 };
1853 let mut rest: &[&'t Term] = &args;
1854 match spine_form(t) {
1855 SpineForm::Named => {
1856 let (Term::Global(label), value) = (args[0], args[1]) else {
1857 unreachable!()
1858 };
1859 jobs.push(Job::Owned(format!(
1860 "({} := ",
1861 token(label, "a named argument's parameter")?
1862 )));
1863 jobs.push(Job::Term(value, Ctx::Top));
1864 jobs.push(Job::Text(")"));
1865 rest = &args[2..];
1866 }
1867 SpineForm::Anon => {
1868 jobs.push(Job::Text("\u{27e8}"));
1869 for (i, c) in args.iter().enumerate() {
1870 if i > 0 {
1871 jobs.push(Job::Text(", "));
1872 }
1873 if is_marker(c, TermParser::ELLIPSIS_MARKER) {
1874 if i + 1 != args.len() {
1875 return Err(no_spelling(
1876 "an ellipsis before the last component of `⟨…⟩`",
1877 ));
1878 }
1879 jobs.push(Job::Text(".."));
1880 } else {
1881 jobs.push(Job::Term(c, Ctx::Top));
1882 }
1883 }
1884 jobs.push(Job::Text("\u{27e9}"));
1885 rest = &[];
1886 }
1887 SpineForm::Projection => {
1888 let Term::Global(field) = args[1] else {
1889 unreachable!()
1890 };
1891 let field = if !field.is_empty() && field.chars().all(|c| c.is_ascii_digit()) {
1892 if matches!(args[0], Term::Lit(_)) {
1893 return Err(no_spelling(format!(
1894 "the numeric projection `.{field}` of a literal \u{2014} `3.1` is a decimal"
1895 )));
1896 }
1897 field.clone()
1898 } else {
1899 tail_component(field)?
1900 };
1901 jobs.push(Job::Term(args[0], Ctx::Atom));
1902 jobs.push(Job::Owned(format!(".{field}")));
1903 rest = &args[2..];
1904 }
1905 SpineForm::Explicit => {
1906 let named = args[0];
1907 if !starts_with_name(named) {
1908 return Err(no_spelling(
1909 "`@` before something that is not a name \u{2014} `@` binds to a constant's name",
1910 ));
1911 }
1912 jobs.push(Job::Text("@"));
1913 jobs.push(Job::Term(named, Ctx::Atom));
1914 rest = &args[1..];
1915 }
1916 SpineForm::Plain => {
1917 push_spine_atom(&mut jobs, head, rest.first().copied());
1918 }
1919 }
1920 for (i, arg) in rest.iter().enumerate() {
1921 if is_marker(arg, TermParser::ELLIPSIS_MARKER) {
1922 if i + 1 != rest.len() {
1923 return Err(no_spelling(
1924 "an ellipsis before an application's last argument",
1925 ));
1926 }
1927 jobs.push(Job::Text(" .."));
1928 } else {
1929 jobs.push(Job::Text(" "));
1930 push_spine_atom(&mut jobs, arg, rest.get(i + 1).copied());
1931 }
1932 }
1933 if ellipsis {
1934 jobs.push(Job::Text(")"));
1935 }
1936 Ok(jobs)
1937 }
1938
1939 fn matching<'t>(
1942 &mut self,
1943 discriminant: &'t Term,
1944 motive: &'t Term,
1945 cases: &'t [Term],
1946 ) -> Result<Vec<Job<'t>>, PrintError> {
1947 let raw: Vec<Option<(&Term, &Term)>> =
1948 cases.iter().map(super::raw_match::as_case).collect();
1949 let patterned = !cases.is_empty() && raw.iter().all(Option::is_some);
1950 if !patterned && raw.iter().any(Option::is_some) {
1951 return Err(no_spelling(
1952 "a match mixing pattern clauses with positional cases",
1953 ));
1954 }
1955 let mut jobs = vec![Job::Text("match "), Job::Term(discriminant, Ctx::Operand)];
1956 if !(patterned && matches!(motive, Term::Hole)) {
1957 jobs.push(Job::Text(" return "));
1958 jobs.push(Job::Term(motive, Ctx::Operand));
1959 }
1960 jobs.push(Job::Text(" with"));
1961 if patterned {
1962 for clause in raw.into_iter().flatten() {
1963 let (pattern, wrapped) = clause;
1964 let mut binders = Vec::new();
1965 super::raw_match::candidates(pattern, &mut binders);
1966 let mut body = wrapped;
1967 for name in &binders {
1968 match body {
1969 Term::Lambda { param, param_type, body: inner }
1970 if param == name && matches!(param_type.as_ref(), Term::Hole) =>
1971 {
1972 body = inner;
1973 }
1974 _ => {
1975 return Err(no_spelling(format!(
1976 "a pattern clause whose body does not bind its pattern variable `{name}`"
1977 )))
1978 }
1979 }
1980 }
1981 for name in &binders {
1982 if mentions_global(body, name) {
1983 return Err(no_spelling(format!(
1984 "the pattern variable `{name}` over a body naming the global `{name}` \u{2014} \
1985 a pattern variable cannot be renamed, since the same word may name a \
1986 constructor"
1987 )));
1988 }
1989 }
1990 jobs.push(Job::Text(" | "));
1991 jobs.push(Job::Owned(pattern_text(pattern, false)?));
1992 jobs.push(Job::Text(" => "));
1993 let spelled = binders
1994 .iter()
1995 .map(|n| Ok((n.clone(), token(n, "a pattern variable")?)))
1996 .collect::<Result<Vec<_>, PrintError>>()?;
1997 let count = spelled.len();
1998 jobs.push(Job::Bind(spelled));
1999 jobs.push(Job::Term(body, Ctx::Top));
2000 jobs.push(Job::Unbind(count));
2001 }
2002 } else {
2003 for (i, case) in cases.iter().enumerate() {
2004 jobs.push(Job::Text(if i == 0 { " " } else { " | " }));
2005 jobs.push(Job::Term(case, Ctx::Top));
2006 }
2007 }
2008 jobs.push(Job::Text(" end"));
2009 Ok(jobs)
2010 }
2011}
2012
2013fn push_spine_atom<'t>(jobs: &mut Vec<Job<'t>>, t: &'t Term, next: Option<&Term>) {
2019 if matches!(t, Term::Sort(Universe::Type(0))) && next.is_some_and(prints_with_digit) {
2020 jobs.push(Job::Text("(Type)"));
2021 } else {
2022 jobs.push(Job::Term(t, Ctx::Atom));
2023 }
2024}
2025
2026fn prints_with_digit(t: &Term) -> bool {
2028 matches!(t, Term::Lit(l) if !negative_literal(l) && !matches!(l, Literal::Text(_)))
2029}
2030
2031fn pattern_text(p: &Term, atom: bool) -> Result<String, PrintError> {
2033 let name = |n: &str| -> Result<String, PrintError> {
2034 match Name::parse(n) {
2035 Some(parsed) if parsed.parts().len() > 1 => {
2036 let parts = parsed.parts();
2037 let mut out = token(&parts[0], "a pattern's constructor")?;
2038 for part in &parts[1..] {
2039 out.push('.');
2040 out.push_str(&tail_component(part)?);
2041 }
2042 Ok(out)
2043 }
2044 _ => token(n, "a pattern name"),
2045 }
2046 };
2047 match p {
2048 Term::Var(n) | Term::Global(n) => name(n),
2049 Term::Hole => Ok("_".to_string()),
2050 Term::Lit(Literal::Nat(n)) if n.to_string().parse::<u64>().is_ok() => Ok(n.to_string()),
2051 Term::App(..) => {
2052 let (head, args) = spine(p);
2053 let Term::Global(ctor) = head else {
2054 return Err(no_spelling(
2055 "a pattern application whose head is not a constructor name",
2056 ));
2057 };
2058 let mut out = name(ctor)?;
2059 for arg in args {
2060 out.push(' ');
2061 out.push_str(&pattern_text(arg, true)?);
2062 }
2063 Ok(if atom { format!("({out})") } else { out })
2064 }
2065 other => Err(no_spelling(format!("the pattern `{other}`"))),
2066 }
2067}
2068
2069fn spine(t: &Term) -> (&Term, Vec<&Term>) {
2071 let mut args = Vec::new();
2072 let mut head = t;
2073 while let Term::App(f, a) = head {
2074 args.push(a.as_ref());
2075 head = f;
2076 }
2077 args.reverse();
2078 (head, args)
2079}
2080
2081enum SpineForm {
2083 Plain,
2084 Named,
2086 Anon,
2088 Projection,
2090 Explicit,
2092}
2093
2094fn spine_form(t: &Term) -> SpineForm {
2095 let (head, args) = spine(t);
2096 let Term::Global(h) = head else {
2097 return SpineForm::Plain;
2098 };
2099 if h == TermParser::NAMED_ARG_MARKER && args.len() == 2 && matches!(args[0], Term::Global(_)) {
2100 SpineForm::Named
2101 } else if h == crate::elaborate::ANON_CTOR_MARKER {
2102 SpineForm::Anon
2103 } else if h == crate::elaborate::DOT_MARKER
2104 && args.len() >= 2
2105 && matches!(args[1], Term::Global(_))
2106 {
2107 SpineForm::Projection
2108 } else if h == TermParser::EXPLICIT_ALL_MARKER && !args.is_empty() {
2109 SpineForm::Explicit
2110 } else {
2111 SpineForm::Plain
2112 }
2113}
2114
2115fn label_positions(t: &Term) -> Option<Vec<&Term>> {
2118 let Term::App(f, x) = t else { return None };
2119 let Term::App(head, first) = f.as_ref() else {
2120 return None;
2121 };
2122 let Term::Global(m) = head.as_ref() else {
2123 return None;
2124 };
2125 if m == TermParser::NAMED_ARG_MARKER && matches!(first.as_ref(), Term::Global(_)) {
2126 Some(vec![x.as_ref()])
2127 } else if (m == crate::elaborate::DOT_MARKER || m == TermParser::AUTO_PARAM_MARKER)
2128 && matches!(x.as_ref(), Term::Global(_))
2129 {
2130 Some(vec![first.as_ref()])
2131 } else {
2132 None
2133 }
2134}
2135
2136fn is_marker(t: &Term, marker: &str) -> bool {
2137 matches!(t, Term::Global(n) if n == marker)
2138}
2139
2140fn starts_with_name(t: &Term) -> bool {
2142 match t {
2143 Term::Var(_) | Term::Global(_) | Term::Const { .. } => true,
2144 Term::App(..) => {
2145 matches!(spine_form(t), SpineForm::Projection) && {
2146 let (_, args) = spine(t);
2147 starts_with_name(args[0])
2148 }
2149 }
2150 _ => false,
2151 }
2152}
2153
2154#[derive(Clone, Copy, PartialEq, Eq)]
2156enum Kind {
2157 Explicit,
2158 Implicit,
2159 Strict,
2160 Instance,
2161}
2162
2163impl Kind {
2164 fn brackets(self) -> (&'static str, &'static str) {
2165 match self {
2166 Kind::Explicit => ("(", ")"),
2167 Kind::Implicit => ("{", "}"),
2168 Kind::Strict => ("\u{2983}", "\u{2984}"),
2169 Kind::Instance => ("[", "]"),
2170 }
2171 }
2172
2173 fn describe(self) -> &'static str {
2174 match self {
2175 Kind::Explicit => "explicit",
2176 Kind::Implicit => "implicit",
2177 Kind::Strict => "strict-implicit",
2178 Kind::Instance => "instance",
2179 }
2180 }
2181}
2182
2183fn split_kind(param_type: &Term) -> (Kind, &Term) {
2184 if let Term::App(f, declared) = param_type {
2185 if let Term::Global(m) = f.as_ref() {
2186 let kind = if m == TermParser::IMPLICIT_BINDER_MARKER {
2187 Some(Kind::Implicit)
2188 } else if m == TermParser::STRICT_IMPLICIT_BINDER_MARKER {
2189 Some(Kind::Strict)
2190 } else if m == TermParser::INSTANCE_BINDER_MARKER {
2191 Some(Kind::Instance)
2192 } else {
2193 None
2194 };
2195 if let Some(kind) = kind {
2196 return (kind, declared.as_ref());
2197 }
2198 }
2199 }
2200 (Kind::Explicit, param_type)
2201}
2202
2203enum Default<'t> {
2204 Value(&'t Term),
2205 Tactic(&'t str),
2206}
2207
2208fn split_default(t: &Term) -> Result<(&Term, Option<Default<'_>>), PrintError> {
2210 if let Term::App(f, payload) = t {
2211 if let Term::App(m, declared) = f.as_ref() {
2212 if let Term::Global(m) = m.as_ref() {
2213 if m == TermParser::PARAM_DEFAULT_MARKER {
2214 return Ok((declared.as_ref(), Some(Default::Value(payload.as_ref()))));
2215 }
2216 if m == TermParser::AUTO_PARAM_MARKER {
2217 return match payload.as_ref() {
2218 Term::Global(tac) => Ok((declared.as_ref(), Some(Default::Tactic(tac)))),
2219 other => Err(no_spelling(format!("the auto-param tactic `{other}`"))),
2220 };
2221 }
2222 }
2223 }
2224 }
2225 Ok((t, None))
2226}
2227
2228fn mentions_global(t: &Term, name: &str) -> bool {
2230 let mut work = vec![t];
2231 while let Some(t) = work.pop() {
2232 match t {
2233 Term::Global(k) | Term::Const { name: k, .. } => {
2234 if k == name || Name::parse(k).is_some_and(|n| n.parts()[0] == name) {
2235 return true;
2236 }
2237 }
2238 Term::Pi {
2239 param_type,
2240 body_type,
2241 ..
2242 } => {
2243 work.push(param_type);
2244 work.push(body_type);
2245 }
2246 Term::Lambda {
2247 param_type, body, ..
2248 } => {
2249 work.push(param_type);
2250 work.push(body);
2251 }
2252 Term::App(f, a) => {
2253 work.push(f);
2254 work.push(a);
2255 }
2256 Term::Match {
2257 discriminant,
2258 motive,
2259 cases,
2260 } => {
2261 work.push(discriminant);
2262 work.push(motive);
2263 work.extend(cases.iter());
2264 }
2265 Term::Fix { body, .. } => work.push(body),
2266 Term::MutualFix { defs, .. } => work.extend(defs.iter().map(|(_, b)| b)),
2267 Term::Let {
2268 ty, value, body, ..
2269 } => {
2270 work.push(ty);
2271 work.push(value);
2272 work.push(body);
2273 }
2274 Term::Annotated { term, .. } => work.push(term),
2275 _ => {}
2276 }
2277 }
2278 false
2279}
2280
2281fn has_kind_marker(t: &Term) -> bool {
2283 let mut work = vec![t];
2284 while let Some(t) = work.pop() {
2285 match t {
2286 Term::Global(m)
2287 if m == TermParser::IMPLICIT_BINDER_MARKER
2288 || m == TermParser::STRICT_IMPLICIT_BINDER_MARKER
2289 || m == TermParser::INSTANCE_BINDER_MARKER =>
2290 {
2291 return true
2292 }
2293 Term::Pi {
2294 param_type,
2295 body_type,
2296 ..
2297 } => {
2298 work.push(param_type);
2299 work.push(body_type);
2300 }
2301 Term::Lambda {
2302 param_type, body, ..
2303 } => {
2304 work.push(param_type);
2305 work.push(body);
2306 }
2307 Term::App(f, a) => {
2308 work.push(f);
2309 work.push(a);
2310 }
2311 Term::Match {
2312 discriminant,
2313 motive,
2314 cases,
2315 } => {
2316 work.push(discriminant);
2317 work.push(motive);
2318 work.extend(cases.iter());
2319 }
2320 Term::Fix { body, .. } => work.push(body),
2321 Term::MutualFix { defs, .. } => work.extend(defs.iter().map(|(_, b)| b)),
2322 Term::Let {
2323 ty, value, body, ..
2324 } => {
2325 work.push(ty);
2326 work.push(value);
2327 work.push(body);
2328 }
2329 Term::Annotated { term, .. } => work.push(term),
2330 _ => {}
2331 }
2332 }
2333 false
2334}