1use super::intake::{self, Component};
63use super::{DiagnosticCode, Verdict};
64use serde_json::{json, Value};
65use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
66
67pub const ELIMINATED_AXIOM: &str = "Lean.ofReduceBool";
69
70pub const INFORMATION_CATEGORY: &str = "NATIVE_DECIDE_REDERIVED";
72
73pub const METHOD: &str = "kernel_evaluation";
75
76const OF_DECIDE_EQ_TRUE: &[&str] = &["of_decide_eq_true"];
77const OF_REDUCE_BOOL: &[&str] = &["Lean", "ofReduceBool"];
78const REDUCE_BOOL: &[&str] = &["Lean", "reduceBool"];
79const DECIDE: &[&str] = &["Decidable", "decide"];
80const DECIDABLE: &[&str] = &["Decidable"];
81const EQ: &[&str] = &["Eq"];
82const EQ_REFL: &[&str] = &["Eq", "refl"];
83const BOOL: &[&str] = &["Bool"];
84const BOOL_FALSE: &[&str] = &["Bool", "false"];
85const BOOL_TRUE: &[&str] = &["Bool", "true"];
86
87#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct NativeDecision {
90 pub leaf: usize,
92 pub premise: usize,
94 pub appeal: usize,
96 pub evaluation: usize,
99 pub proposition: usize,
101 pub instance: usize,
103 pub decision: String,
105}
106
107impl NativeDecision {
108 pub fn to_json(&self) -> Value {
109 json!({
110 "leaf_expression": self.leaf,
111 "appeal_expression": self.appeal,
112 "evaluation_expression": self.evaluation,
113 "proposition_expression": self.proposition,
114 "instance_expression": self.instance,
115 "decision": self.decision,
116 })
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct Rederivation {
123 pub declaration: String,
125 pub lean: String,
127 pub decisions: Vec<NativeDecision>,
129}
130
131impl Rederivation {
132 pub fn detail(&self) -> String {
134 let decisions: Vec<&str> = self.decisions.iter().map(|d| d.decision.as_str()).collect();
135 let steps = if decisions.len() == 1 {
136 "one step".to_string()
137 } else {
138 format!("{} steps", decisions.len())
139 };
140 format!(
141 "{}: Lean proved {steps} by native_decide, resting on {ELIMINATED_AXIOM} (Lean's compiled \
142 evaluation of {}). LOGOS re-derived each step by kernel evaluation: Decidable.decide reduces \
143 to Bool.true in the primary kernel and in the independent re-checker. The proof does not rest \
144 on {ELIMINATED_AXIOM}; the statement is unchanged.",
145 self.lean,
146 decisions.join(", "),
147 )
148 }
149
150 pub fn to_json(&self) -> Value {
151 json!({
152 "declaration": self.declaration,
153 "lean": self.lean,
154 "eliminated": ELIMINATED_AXIOM,
155 "method": METHOD,
156 "decisions": self.decisions.iter().map(NativeDecision::to_json).collect::<Vec<_>>(),
157 })
158 }
159}
160
161#[derive(Clone, Debug, Default, PartialEq, Eq)]
163pub struct Elimination {
164 pub rederivations: Vec<Rederivation>,
166 pub escape_remains: bool,
168}
169
170impl Elimination {
171 pub fn eliminates(&self) -> bool {
173 !self.rederivations.is_empty() && !self.escape_remains
174 }
175
176 pub fn lean_after_rederivation(
179 &self,
180 lean: &[String],
181 ) -> std::result::Result<Vec<String>, String> {
182 if !self.eliminates() {
183 return Ok(lean.to_vec());
184 }
185 if !lean.iter().any(|a| a == ELIMINATED_AXIOM) {
186 return Err(format!(
187 "the export's proof rests on {ELIMINATED_AXIOM} through native decisions ({}), but Lean's #print \
188 axioms reports {lean:?}",
189 self.leaves().join(", ")
190 ));
191 }
192 Ok(lean
193 .iter()
194 .filter(|a| *a != ELIMINATED_AXIOM)
195 .cloned()
196 .collect())
197 }
198
199 pub fn information_changes(&self) -> Vec<(String, String)> {
201 if !self.eliminates() {
202 return Vec::new();
203 }
204 self.rederivations
205 .iter()
206 .map(|r| (INFORMATION_CATEGORY.to_string(), r.detail()))
207 .collect()
208 }
209
210 pub fn disclosed_by<'a>(
213 &self,
214 stated: impl IntoIterator<Item = (&'a str, &'a str)>,
215 ) -> std::result::Result<(), String> {
216 let stated: Vec<(String, String)> = stated
217 .into_iter()
218 .filter(|(category, _)| *category == INFORMATION_CATEGORY)
219 .map(|(category, detail)| (category.to_string(), detail.to_string()))
220 .collect();
221 let expected = self.information_changes();
222 if stated == expected {
223 return Ok(());
224 }
225 Err(format!(
226 "the manifest states {} {INFORMATION_CATEGORY} change(s), but the export's proof re-derives {}",
227 stated.len(),
228 if expected.is_empty() { "none".to_string() } else { self.leaves().join(", ") }
229 ))
230 }
231
232 fn leaves(&self) -> Vec<String> {
233 self.rederivations
234 .iter()
235 .flat_map(|r| r.decisions.iter().map(|d| d.decision.clone()))
236 .collect()
237 }
238}
239
240pub struct NativeDecisions {
242 tables: Option<Tables>,
243 decisions: BTreeMap<usize, NativeDecision>,
244 holds_leaf: HashSet<usize>,
246}
247
248impl NativeDecisions {
249 pub fn read(export: &str) -> NativeDecisions {
253 let none = NativeDecisions {
254 tables: None,
255 decisions: BTreeMap::new(),
256 holds_leaf: HashSet::new(),
257 };
258 if !export.contains("\"ofReduceBool\"") {
259 return none;
260 }
261 let Some(tables) = Tables::read(export) else {
262 return none;
263 };
264 let decisions: BTreeMap<usize, NativeDecision> = if tables.canonical_environment() {
265 tables
266 .order
267 .iter()
268 .filter_map(|&e| tables.leaf(e).map(|d| (e, d)))
269 .collect()
270 } else {
271 BTreeMap::new()
272 };
273 let mut holds_leaf = HashSet::new();
274 for &e in &tables.order {
275 if decisions.contains_key(&e)
276 || tables
277 .children(e, &decisions)
278 .iter()
279 .any(|c| holds_leaf.contains(c))
280 {
281 holds_leaf.insert(e);
282 }
283 }
284 NativeDecisions {
285 tables: Some(tables),
286 decisions,
287 holds_leaf,
288 }
289 }
290
291 pub fn is_empty(&self) -> bool {
293 self.decisions.is_empty()
294 }
295
296 pub fn decisions(&self) -> impl Iterator<Item = &NativeDecision> {
298 self.decisions.values()
299 }
300
301 pub fn rederive(&self, record: Value) -> Value {
305 if self.decisions.is_empty() {
306 return record;
307 }
308 let Some(decision) = record
309 .get("ie")
310 .and_then(Value::as_u64)
311 .and_then(|e| self.decisions.get(&(e as usize)))
312 else {
313 return record;
314 };
315 json!({ "ie": decision.leaf, "app": { "fn": decision.premise, "arg": decision.evaluation } })
316 }
317
318 pub fn rederivations(&self) -> Vec<Rederivation> {
320 let Some(tables) = &self.tables else {
321 return Vec::new();
322 };
323 tables
324 .declared
325 .iter()
326 .filter_map(|&name| self.rederivation(tables, name))
327 .collect()
328 }
329
330 pub fn closure_of(&self, target: &str) -> Elimination {
335 let Some(tables) = &self.tables else {
336 return Elimination::default();
337 };
338 let Some(&start) = tables.by_kernel_name.get(target) else {
339 return Elimination::default();
340 };
341 let mut elimination = Elimination::default();
342 let mut constants = vec![start];
343 let mut constants_seen = HashSet::new();
344 let mut expressions_seen = HashSet::new();
345 let mut rederived = BTreeSet::new();
346 while let Some(name) = constants.pop() {
347 if !constants_seen.insert(name) {
348 continue;
349 }
350 let Some(constant) = tables.constants.get(&name) else {
351 continue;
352 };
353 let roots: Vec<usize> = match &constant.kind {
354 Kind::Axiom | Kind::Quotient => Vec::new(),
355 Kind::Definition | Kind::Theorem | Kind::Opaque => {
356 if self.rederivation(tables, name).is_some() {
357 rederived.insert(name);
358 }
359 std::iter::once(constant.ty).chain(constant.value).collect()
360 }
361 Kind::Inductive { constructors } => {
362 constants.extend(constructors.iter().copied());
363 vec![constant.ty]
364 }
365 Kind::Constructor { .. } | Kind::Recursor => vec![constant.ty],
366 };
367 let mut work = roots;
368 while let Some(e) = work.pop() {
369 if !expressions_seen.insert(e) {
370 continue;
371 }
372 if let Some((named, _)) = tables.constant(e) {
373 if tables.name_is(named, OF_REDUCE_BOOL) {
374 elimination.escape_remains = true;
375 }
376 constants.push(named);
377 }
378 work.extend(tables.children(e, &self.decisions));
379 }
380 }
381 elimination.rederivations = tables
382 .declared
383 .iter()
384 .filter(|name| rederived.contains(name))
385 .filter_map(|&name| self.rederivation(tables, name))
386 .collect();
387 elimination
388 }
389
390 pub fn explain(&self, verdict: Verdict, declaration: Option<usize>) -> Verdict {
393 let Some(tables) = &self.tables else {
394 return verdict;
395 };
396 let Some(rederivation) = declaration.and_then(|name| self.rederivation(tables, name))
397 else {
398 return verdict;
399 };
400 if !matches!(
401 verdict.reason().code(),
402 DiagnosticCode::InvalidProof | DiagnosticCode::ResourceLimit
403 ) {
404 return verdict;
405 }
406 let decisions: Vec<&str> = rederivation
407 .decisions
408 .iter()
409 .map(|d| d.decision.as_str())
410 .collect();
411 verdict.located(format!(
412 "{} re-derives its native decision(s) {} by kernel evaluation instead of {ELIMINATED_AXIOM}, and \
413 the kernel must reduce each Decidable.decide to Bool.true",
414 rederivation.lean,
415 decisions.join(", ")
416 ))
417 }
418
419 fn rederivation(&self, tables: &Tables, name: usize) -> Option<Rederivation> {
420 let constant = tables.constants.get(&name)?;
421 if !matches!(
422 constant.kind,
423 Kind::Definition | Kind::Theorem | Kind::Opaque
424 ) {
425 return None;
426 }
427 let mut leaves = BTreeSet::new();
428 let mut work: Vec<usize> = std::iter::once(constant.ty).chain(constant.value).collect();
429 let mut seen = HashSet::new();
430 while let Some(e) = work.pop() {
431 if !self.holds_leaf.contains(&e) || !seen.insert(e) {
432 continue;
433 }
434 if self.decisions.contains_key(&e) {
435 leaves.insert(e);
436 }
437 work.extend(tables.children(e, &self.decisions));
438 }
439 if leaves.is_empty() {
440 return None;
441 }
442 Some(Rederivation {
443 declaration: tables.kernel_name(name),
444 lean: intake::print_lean_name(&tables.names[&name]),
445 decisions: leaves
446 .into_iter()
447 .map(|e| self.decisions[&e].clone())
448 .collect(),
449 })
450 }
451}
452
453#[derive(Clone, Debug, PartialEq, Eq)]
455enum Level {
456 Zero,
457 Succ(Box<Level>),
458 Max(Box<Level>, Box<Level>),
459 IMax(Box<Level>, Box<Level>),
460 Param(Vec<Component>),
461}
462
463fn one() -> Level {
464 Level::Succ(Box::new(Level::Zero))
465}
466
467#[derive(Clone, Debug, PartialEq, Eq)]
468enum Kind {
469 Definition,
470 Theorem,
471 Opaque,
472 Axiom,
473 Quotient,
474 Inductive { constructors: Vec<usize> },
475 Constructor { inductive: usize },
476 Recursor,
477}
478
479struct Constant {
481 kind: Kind,
482 level_params: usize,
483 ty: usize,
484 value: Option<usize>,
485}
486
487enum Shape {
489 Prop,
490 Constant(&'static [&'static str], Vec<Level>),
491 Bound(u64),
492 Apply(Box<Shape>, Box<Shape>),
493 Forall(Box<Shape>, Box<Shape>),
494}
495
496fn c(name: &'static [&'static str]) -> Shape {
497 Shape::Constant(name, Vec::new())
498}
499fn at(name: &'static [&'static str], levels: Vec<Level>) -> Shape {
500 Shape::Constant(name, levels)
501}
502fn b(index: u64) -> Shape {
503 Shape::Bound(index)
504}
505fn ap(f: Shape, args: impl IntoIterator<Item = Shape>) -> Shape {
506 args.into_iter()
507 .fold(f, |f, a| Shape::Apply(Box::new(f), Box::new(a)))
508}
509fn pi(domain: Shape, body: Shape) -> Shape {
510 Shape::Forall(Box::new(domain), Box::new(body))
511}
512fn eq_bool(lhs: Shape, rhs: Shape) -> Shape {
513 ap(at(EQ, vec![one()]), [c(BOOL), lhs, rhs])
514}
515
516struct Tables {
518 names: HashMap<usize, Vec<Component>>,
519 levels: HashMap<usize, Level>,
520 exprs: HashMap<usize, Value>,
521 order: Vec<usize>,
523 constants: HashMap<usize, Constant>,
524 declared: Vec<usize>,
526 by_kernel_name: HashMap<String, usize>,
527}
528
529impl Tables {
530 fn read(export: &str) -> Option<Tables> {
531 let mut tables = Tables {
532 names: HashMap::from([(0, Vec::new())]),
533 levels: HashMap::from([(0, Level::Zero)]),
534 exprs: HashMap::new(),
535 order: Vec::new(),
536 constants: HashMap::new(),
537 declared: Vec::new(),
538 by_kernel_name: HashMap::new(),
539 };
540 for line in export.lines().skip(1) {
541 let record: Value = serde_json::from_str(line).ok()?;
542 if let Some(i) = record.get("in") {
543 let i = index(i)?;
544 let (part, pre) = if let Some(x) = record.get("str") {
545 (
546 Component::Str(x["str"].as_str()?.to_string()),
547 index(&x["pre"])?,
548 )
549 } else {
550 let x = record.get("num")?;
551 (Component::Num(x["i"].as_u64()?), index(&x["pre"])?)
552 };
553 let mut name = tables.names.get(&pre)?.clone();
554 name.push(part);
555 tables.names.insert(i, name);
556 } else if let Some(i) = record.get("il") {
557 let i = index(i)?;
558 let level = if let Some(x) = record.get("succ") {
559 Level::Succ(Box::new(tables.levels.get(&index(x)?)?.clone()))
560 } else if let Some(x) = record.get("param") {
561 Level::Param(tables.names.get(&index(x)?)?.clone())
562 } else if let Some(pair) = record.get("max").or_else(|| record.get("imax")) {
563 let pair = pair.as_array()?;
564 let lhs = Box::new(tables.levels.get(&index(pair.first()?)?)?.clone());
565 let rhs = Box::new(tables.levels.get(&index(pair.get(1)?)?)?.clone());
566 if record.get("max").is_some() {
567 Level::Max(lhs, rhs)
568 } else {
569 Level::IMax(lhs, rhs)
570 }
571 } else {
572 return None;
573 };
574 tables.levels.insert(i, level);
575 } else if let Some(i) = record.get("ie") {
576 let i = index(i)?;
577 tables.order.push(i);
578 tables.exprs.insert(i, record);
579 } else {
580 tables.declaration(&record)?;
581 }
582 }
583 Some(tables)
584 }
585
586 fn declaration(&mut self, record: &Value) -> Option<()> {
587 let (tag, body) = record.as_object()?.iter().next()?;
588 let simple = |kind: Kind, body: &Value| -> Option<(usize, Constant)> {
589 let name = index(&body["name"])?;
590 let value = if matches!(kind, Kind::Axiom | Kind::Quotient) {
591 None
592 } else {
593 Some(index(&body["value"])?)
594 };
595 let level_params = body["levelParams"].as_array()?.len();
596 Some((
597 name,
598 Constant {
599 kind,
600 level_params,
601 ty: index(&body["type"])?,
602 value,
603 },
604 ))
605 };
606 let mut declared = Vec::new();
607 match tag.as_str() {
608 "def" => declared.push(simple(Kind::Definition, body)?),
609 "thm" => declared.push(simple(Kind::Theorem, body)?),
610 "opaque" => declared.push(simple(Kind::Opaque, body)?),
611 "axiom" => declared.push(simple(Kind::Axiom, body)?),
612 "quot" => declared.push(simple(Kind::Quotient, body)?),
613 "inductive" => {
614 for ty in body["types"].as_array()? {
615 let constructors = ty["ctors"]
616 .as_array()?
617 .iter()
618 .map(index)
619 .collect::<Option<Vec<_>>>()?;
620 declared.push(simple_typed(Kind::Inductive { constructors }, ty)?);
621 }
622 for ctor in body["ctors"].as_array()? {
623 declared.push(simple_typed(
624 Kind::Constructor {
625 inductive: index(&ctor["induct"])?,
626 },
627 ctor,
628 )?);
629 }
630 for rec in body["recs"].as_array()? {
631 declared.push(simple_typed(Kind::Recursor, rec)?);
632 }
633 }
634 _ => return None,
635 }
636 for (name, constant) in declared {
637 let kernel = self.kernel_name(name);
638 self.by_kernel_name.insert(kernel, name);
639 self.declared.push(name);
640 self.constants.insert(name, constant);
641 }
642 Some(())
643 }
644
645 fn kernel_name(&self, name: usize) -> String {
646 super::canonical_name(&intake::encode(&self.names[&name]))
647 }
648
649 fn name_is(&self, name: usize, expected: &[&str]) -> bool {
650 self.names.get(&name).is_some_and(|components| {
651 components.len() == expected.len()
652 && components
653 .iter()
654 .zip(expected)
655 .all(|(c, e)| matches!(c, Component::Str(s) if s == e))
656 })
657 }
658
659 fn named(&self, expected: &[&str]) -> Option<(usize, &Constant)> {
660 self.declared
661 .iter()
662 .filter(|&&name| self.name_is(name, expected))
663 .map(|&name| (name, &self.constants[&name]))
664 .next()
665 }
666
667 fn app(&self, e: usize) -> Option<(usize, usize)> {
668 let x = self.exprs.get(&e)?.get("app")?;
669 Some((index(&x["fn"])?, index(&x["arg"])?))
670 }
671
672 fn constant(&self, e: usize) -> Option<(usize, Vec<Level>)> {
673 let x = self.exprs.get(&e)?.get("const")?;
674 let levels = x["us"]
675 .as_array()?
676 .iter()
677 .map(|u| self.levels.get(&index(u)?).cloned())
678 .collect::<Option<Vec<_>>>()?;
679 Some((index(&x["name"])?, levels))
680 }
681
682 fn is_constant(&self, e: usize, name: &[&str], levels: &[Level]) -> bool {
683 self.constant(e)
684 .is_some_and(|(n, us)| self.name_is(n, name) && us == levels)
685 }
686
687 fn children(&self, e: usize, leaves: &BTreeMap<usize, NativeDecision>) -> Vec<usize> {
689 if let Some(leaf) = leaves.get(&e) {
690 return vec![leaf.premise, leaf.evaluation];
691 }
692 let Some(record) = self.exprs.get(&e) else {
693 return Vec::new();
694 };
695 let fields: &[(&str, &[&str])] = &[
696 ("app", &["fn", "arg"]),
697 ("mdata", &["expr"]),
698 ("proj", &["struct"]),
699 ("letE", &["type", "value", "body"]),
700 ("lam", &["type", "body"]),
701 ("forallE", &["type", "body"]),
702 ];
703 for (tag, children) in fields {
704 if let Some(x) = record.get(*tag) {
705 return children.iter().filter_map(|c| index(&x[*c])).collect();
706 }
707 }
708 Vec::new()
709 }
710
711 fn fits(&self, e: usize, shape: &Shape) -> bool {
713 let Some(record) = self.exprs.get(&e) else {
714 return false;
715 };
716 match shape {
717 Shape::Prop => record
718 .get("sort")
719 .and_then(index)
720 .is_some_and(|l| self.levels.get(&l) == Some(&Level::Zero)),
721 Shape::Constant(name, levels) => self.is_constant(e, name, levels),
722 Shape::Bound(i) => record.get("bvar").and_then(Value::as_u64) == Some(*i),
723 Shape::Apply(f, a) => self
724 .app(e)
725 .is_some_and(|(ef, ea)| self.fits(ef, f) && self.fits(ea, a)),
726 Shape::Forall(domain, body) => record.get("forallE").is_some_and(|x| {
727 index(&x["type"]).is_some_and(|t| self.fits(t, domain))
728 && index(&x["body"]).is_some_and(|t| self.fits(t, body))
729 }),
730 }
731 }
732
733 fn declares(&self, name: &'static [&'static str], kind: fn(&Kind) -> bool, ty: Shape) -> bool {
734 self.named(name).is_some_and(|(_, constant)| {
735 kind(&constant.kind) && constant.level_params == 0 && self.fits(constant.ty, &ty)
736 })
737 }
738
739 fn canonical_environment(&self) -> bool {
741 let of_reduce_bool = pi(
742 c(BOOL),
743 pi(
744 c(BOOL),
745 pi(
746 eq_bool(ap(c(REDUCE_BOOL), [b(1)]), b(0)),
747 eq_bool(b(2), b(1)),
748 ),
749 ),
750 );
751 let of_decide_eq_true = pi(
752 Shape::Prop,
753 pi(
754 ap(c(DECIDABLE), [b(0)]),
755 pi(eq_bool(ap(c(DECIDE), [b(1), b(0)]), c(BOOL_TRUE)), b(2)),
756 ),
757 );
758 let decide = pi(Shape::Prop, pi(ap(c(DECIDABLE), [b(0)]), c(BOOL)));
759 self.declares(OF_REDUCE_BOOL, |k| *k == Kind::Axiom, of_reduce_bool)
760 && self.declares(REDUCE_BOOL, |k| *k == Kind::Opaque, pi(c(BOOL), c(BOOL)))
761 && self.declares(
762 OF_DECIDE_EQ_TRUE,
763 |k| *k == Kind::Theorem,
764 of_decide_eq_true,
765 )
766 && self.declares(DECIDE, |k| *k == Kind::Definition, decide)
767 && self.inductive_with_constructors(BOOL, &[BOOL_FALSE, BOOL_TRUE])
768 && self.inductive_with_constructors(EQ, &[EQ_REFL])
769 }
770
771 fn inductive_with_constructors(
772 &self,
773 name: &'static [&'static str],
774 constructors: &[&[&str]],
775 ) -> bool {
776 let Some((inductive, constant)) = self.named(name) else {
777 return false;
778 };
779 let Kind::Inductive {
780 constructors: declared,
781 } = &constant.kind
782 else {
783 return false;
784 };
785 declared.len() == constructors.len()
786 && declared.iter().zip(constructors).all(|(&ctor, expected)| {
787 self.name_is(ctor, expected)
788 && self
789 .constants
790 .get(&ctor)
791 .is_some_and(|k| k.kind == Kind::Constructor { inductive })
792 })
793 }
794
795 fn leaf(&self, e: usize) -> Option<NativeDecision> {
797 let (premise, appeal) = self.app(e)?;
798 let (partial, instance) = self.app(premise)?;
799 let (head, proposition) = self.app(partial)?;
800 if !self.is_constant(head, OF_DECIDE_EQ_TRUE, &[]) {
801 return None;
802 }
803 let (applied, evaluation) = self.app(appeal)?;
804 let (appealed, answer) = self.app(applied)?;
805 let (escape, decision) = self.app(appealed)?;
806 if !self.is_constant(escape, OF_REDUCE_BOOL, &[])
807 || !self.is_constant(answer, BOOL_TRUE, &[])
808 {
809 return None;
810 }
811 if !self.fits(
812 evaluation,
813 &ap(at(EQ_REFL, vec![one()]), [c(BOOL), c(BOOL_TRUE)]),
814 ) {
815 return None;
816 }
817 let (decision_name, levels) = self.constant(decision)?;
818 let constant = self.constants.get(&decision_name)?;
819 if !levels.is_empty()
820 || constant.kind != Kind::Definition
821 || constant.level_params != 0
822 || !self.is_constant(constant.ty, BOOL, &[])
823 {
824 return None;
825 }
826 let (decided, decided_instance) = self.app(constant.value?)?;
827 let (decide, decided_proposition) = self.app(decided)?;
828 if !self.is_constant(decide, DECIDE, &[])
829 || !self.same(decided_proposition, proposition)
830 || !self.same(decided_instance, instance)
831 {
832 return None;
833 }
834 Some(NativeDecision {
835 leaf: e,
836 premise,
837 appeal,
838 evaluation,
839 proposition,
840 instance,
841 decision: intake::print_lean_name(&self.names[&decision_name]),
842 })
843 }
844
845 fn same(&self, a: usize, b: usize) -> bool {
847 let mut work = vec![(a, b)];
848 let mut seen = HashSet::new();
849 while let Some((a, b)) = work.pop() {
850 if a == b || !seen.insert((a, b)) {
851 continue;
852 }
853 if self.same_node(a, b, &mut work).is_none() {
854 return false;
855 }
856 }
857 true
858 }
859
860 fn same_node(&self, a: usize, b: usize, work: &mut Vec<(usize, usize)>) -> Option<()> {
862 let (x, y) = (self.exprs.get(&a)?, self.exprs.get(&b)?);
863 let mut pair = |field: &str, keys: &[&str]| -> Option<()> {
864 let (x, y) = (x.get(field)?, y.get(field)?);
865 for key in keys {
866 work.push((index(&x[*key])?, index(&y[*key])?));
867 }
868 Some(())
869 };
870 if x.get("app").is_some() {
871 pair("app", &["fn", "arg"])
872 } else if x.get("lam").is_some() {
873 pair("lam", &["type", "body"])
874 } else if x.get("forallE").is_some() {
875 pair("forallE", &["type", "body"])
876 } else if let Some(xl) = x.get("letE") {
877 (xl.get("nondep") == y.get("letE")?.get("nondep")).then_some(())?;
878 pair("letE", &["type", "value", "body"])
879 } else if let Some(xm) = x.get("mdata") {
880 (xm["data"] == y.get("mdata")?["data"]).then_some(())?;
881 pair("mdata", &["expr"])
882 } else if let Some(xp) = x.get("proj") {
883 let yp = y.get("proj")?;
884 let names = (
885 self.names.get(&index(&xp["typeName"])?),
886 self.names.get(&index(&yp["typeName"])?),
887 );
888 (xp["idx"] == yp["idx"] && names.0.is_some() && names.0 == names.1).then_some(())?;
889 pair("proj", &["struct"])
890 } else if x.get("const").is_some() {
891 let (xn, xl) = self.constant(a)?;
892 let (yn, yl) = self.constant(b)?;
893 (self.names.get(&xn).is_some()
894 && self.names.get(&xn) == self.names.get(&yn)
895 && xl == yl)
896 .then_some(())
897 } else if let Some(xs) = x.get("sort") {
898 let (xs, ys) = (
899 self.levels.get(&index(xs)?),
900 self.levels.get(&index(y.get("sort")?)?),
901 );
902 (xs.is_some() && xs == ys).then_some(())
903 } else {
904 let tag = ["bvar", "natVal", "strVal"]
905 .into_iter()
906 .find(|k| x.get(*k).is_some())?;
907 (y.get(tag).is_some() && x.get(tag) == y.get(tag)).then_some(())
908 }
909 }
910}
911
912fn simple_typed(kind: Kind, body: &Value) -> Option<(usize, Constant)> {
913 let name = index(&body["name"])?;
914 let level_params = body["levelParams"].as_array()?.len();
915 Some((
916 name,
917 Constant {
918 kind,
919 level_params,
920 ty: index(&body["type"])?,
921 value: None,
922 },
923 ))
924}
925
926fn index(v: &Value) -> Option<usize> {
927 v.as_u64().and_then(|n| usize::try_from(n).ok())
928}