Skip to main content

logicaffeine_kernel/interface/lean/
reference.rs

1//! The reference translator: a deliberately small, unoptimized reading of a Lean export's declaration
2//! TYPES into kernel terms, sharing no code with the production reader's conversion, sharing or
3//! caching machinery. It is the independent oracle a result's statement is compared against
4//! (competition rule R8): a faulty production translation and a faulty emitter cannot agree with it
5//! by construction.
6//!
7//! # What it reads, and what it refuses
8//!
9//! One pass over the NDJSON records builds three tables — names, levels, expressions — keeping each
10//! record as the JSON it arrived as. The requested declaration's type is then translated by a plain
11//! post-order walk: `sort`, `bvar`, `const`, `app`, `lam`, `forallE`, `letE`, `mdata` (transparent),
12//! `natVal` and `strVal`. Nothing is shared, cached, reduced or checked here; the kernel decides what the
13//! resulting term means.
14//!
15//! A `strVal` string literal is read as the constructor application the export's own Lean release
16//! defines it to be, from the header's `meta.lean.version` and nothing else: Lean 4.14.0, whose
17//! `String` is the structure `String.mk (data : List Char)`, reads `"ab"` as
18//! `String.mk [Char.ofNat 97, Char.ofNat 98]`; Lean 4.29.1, 4.30.0 and 4.33.0, whose `String` is UTF-8
19//! bytes, read it as `String.ofList [Char.ofNat 97, Char.ofNat 98]`. Under any other release a
20//! literal has no reference reading and is declined. Nothing about the export's `String` is looked
21//! up or checked here; the kernel decides whether the reading is well-typed.
22//!
23//! `proj` is refused as unsupported: the production reader lowers it through typed machinery (a
24//! projection needs its structure's constructor telescope), and reproducing that machinery would
25//! make this oracle neither small nor independent. A statement that needs one is reported as having
26//! no reference translation, which is an explicit gap and never a pass.
27//!
28//! # The identity contract
29//!
30//! Names are the export's hierarchical names written component by component — `/s:"T06_002"/s:"x"`
31//! for a string component, `/n:3` for a numeric one — and universe parameters are spelled the same
32//! way. The kernel's primitive identities are the one place a Lean name is not its own spelling:
33//! `Nat`, `Nat.zero` and `Nat.succ` are the kernel's `Nat`, `Zero` and `Succ` (every other `Nat.x`
34//! lives under `Nat/`), and Lean's four quotient primitives are the kernel's certified
35//! `kernel.quot.*` declarations. That table is the translation's contract with the kernel, written
36//! out here in full rather than imported, so a change to it on either side is a visible
37//! disagreement.
38
39use super::{declined, rejected, DiagnosticCode, Result};
40use crate::{Literal, Term, Universe};
41use serde_json::Value;
42use std::collections::HashMap;
43
44/// The largest term (in tree nodes) the reference translation will build. Exports share
45/// subexpressions as a DAG; a tree unfolding is bounded so a hostile export cannot make the oracle
46/// allocate without limit.
47pub const REFERENCE_NODE_BUDGET: usize = 1_000_000;
48
49/// Independent decimal workload ceiling. This bounds parsing effort, not the value of a Nat.
50const REFERENCE_NATURAL_WORK_BUDGET: u64 = 1_000_000;
51
52/// Admit bytes before examining them and decimal limb work before conversion. Return the
53/// significant suffix without allocation; ASCII validation and zero-prefix discovery share
54/// one pass. The caller carries `work` across every expanded literal occurrence.
55fn admit_natural_literal<'a>(payload: &'a Value, work: &mut u64) -> Result<&'a str> {
56    let digits = payload
57        .as_str()
58        .ok_or_else(|| malformed("natural literal"))?;
59    *work = work
60        .checked_add(u64::try_from(digits.len()).unwrap_or(u64::MAX))
61        .filter(|sum| *sum <= REFERENCE_NATURAL_WORK_BUDGET)
62        .ok_or_else(|| {
63            declined(
64                DiagnosticCode::ResourceLimit,
65                "reference natural literal workload",
66            )
67        })?;
68    if digits.is_empty() {
69        return Err(malformed("natural literal"));
70    }
71    let mut first = digits.len();
72    for (index, byte) in digits.bytes().enumerate() {
73        if !byte.is_ascii_digit() {
74            return Err(malformed("natural literal"));
75        }
76        if first == digits.len() && byte != b'0' {
77            first = index;
78        }
79    }
80    let significant = &digits[first..];
81    // Fewer than four bits per decimal digit: at most n/16+1 64-bit limbs.
82    // Each digit's multiply-by-ten and add visit at most that many limbs.
83    let n = significant.len() as u64;
84    let conversion = n
85        .checked_mul(n / 16 + 1)
86        .and_then(|n| n.checked_mul(2))
87        .ok_or_else(|| {
88            declined(
89                DiagnosticCode::ResourceLimit,
90                "reference natural literal workload",
91            )
92        })?;
93    *work = work
94        .checked_add(conversion)
95        .filter(|sum| *sum <= REFERENCE_NATURAL_WORK_BUDGET)
96        .ok_or_else(|| {
97            declined(
98                DiagnosticCode::ResourceLimit,
99                "reference natural literal workload",
100            )
101        })?;
102    Ok(significant)
103}
104
105/// Raw-source tree bounds, checked before allocating any kernel term or universe.
106#[derive(Clone, Copy, Debug, Default)]
107pub(super) struct SourceMetrics {
108    pub(super) depth: usize,
109    pub(super) tree_nodes: u64,
110    /// Cumulative decimal-byte and conversion work across all admitted source roots.
111    pub(super) natural_work: u64,
112    pub(super) has_projection: bool,
113    pub(super) has_generated: bool,
114}
115
116impl Reference {
117    /// Decode source metadata and indices without building terms. Shape reading itself does
118    /// not authorize a block or its computational rules.
119    pub(super) fn recursor_block_shape(
120        &self,
121        declaration: &str,
122    ) -> Result<ReferenceInductiveBlock<u64>> {
123        self.tables
124            .read_recursor_block(declaration, Lookup::Mixed, &mut |root| Ok(root))
125    }
126
127    /// Independent family/constructor metadata by exact kernel identity. No source expression is translated,
128    /// and no uncarried recursor declaration or rule is inspected.
129    pub(super) fn family_shape(&self, declaration: &str) -> Result<ReferenceInductiveBlock<u64>> {
130        let block = self.tables.containing_family_block(declaration)?;
131        self.tables.read_block(block, false, &mut |root| Ok(root))
132    }
133
134    /// Exact translated root occurrences, including every rule RHS.
135    pub(super) fn recursor_block_roots(&self, declaration: &str) -> Result<Vec<u64>> {
136        Ok(self.recursor_block_shape(declaration)?.expression_roots())
137    }
138
139    /// Term-free admission of complete source blocks. Shared blocks are charged once, matching
140    /// a canonical reader that caches each block once; every repeated root within a block counts.
141    pub(super) fn preflight_recursor_blocks(
142        &self,
143        declarations: &[String],
144        max_depth: usize,
145        max_tree_nodes: u64,
146    ) -> Result<SourceMetrics> {
147        let mut total = SourceMetrics::default();
148        self.preflight_recursor_blocks_into(
149            declarations,
150            max_depth,
151            max_tree_nodes,
152            &mut total,
153            Lookup::Mixed,
154        )?;
155        Ok(total)
156    }
157
158    pub(super) fn preflight_recursor_blocks_kernel(
159        &self,
160        declarations: &[String],
161        max_depth: usize,
162        max_tree_nodes: u64,
163    ) -> Result<SourceMetrics> {
164        let mut total = SourceMetrics::default();
165        self.preflight_recursor_blocks_into(
166            declarations,
167            max_depth,
168            max_tree_nodes,
169            &mut total,
170            Lookup::Kernel,
171        )?;
172        Ok(total)
173    }
174
175    fn preflight_recursor_blocks_into(
176        &self,
177        declarations: &[String],
178        max_depth: usize,
179        max_tree_nodes: u64,
180        total: &mut SourceMetrics,
181        lookup: Lookup,
182    ) -> Result<()> {
183        let mut seen = HashMap::<Vec<String>, ()>::new();
184        for declaration in declarations {
185            let shape = self
186                .tables
187                .read_recursor_block(declaration, lookup, &mut |root| Ok(root))?;
188            let identity: Vec<_> = shape
189                .recursors
190                .iter()
191                .map(|recursor| recursor.declaration.name.clone())
192                .collect();
193            if seen.insert(identity, ()).is_some() {
194                continue;
195            }
196            total.has_generated = true;
197            for root in shape.expression_roots() {
198                self.tables
199                    .preflight_root(root, max_depth, max_tree_nodes, total)?;
200            }
201        }
202        Ok(())
203    }
204
205    /// Independently decode the containing block, using the default honest projection refusal.
206    pub(super) fn recursor_block(
207        &self,
208        declaration: &str,
209    ) -> Result<ReferenceInductiveBlock<Term>> {
210        self.recursor_block_with(declaration, &mut NoProjection)
211    }
212
213    /// Admit ALL source roots before allocating the first translated expression or invoking a
214    /// projection callback. Project callers additionally admit against their declared limits and
215    /// reserve a worker for the canonical generator's full permitted output depth.
216    pub(super) fn recursor_block_with(
217        &self,
218        declaration: &str,
219        projections: &mut impl ReferenceProjection,
220    ) -> Result<ReferenceInductiveBlock<Term>> {
221        self.preflight_recursor_blocks(
222            &[declaration.to_string()],
223            REFERENCE_NODE_BUDGET,
224            REFERENCE_NODE_BUDGET as u64,
225        )?;
226        self.tables
227            .read_recursor_block(declaration, Lookup::Mixed, &mut |root| {
228                self.tables.translate_with(&Value::from(root), projections)
229            })
230    }
231
232    /// Exact kernel identity: certificate names cannot be mistaken for a different Lean spelling.
233    pub(super) fn recursor_block_kernel_with(
234        &self,
235        declaration: &str,
236        projections: &mut impl ReferenceProjection,
237    ) -> Result<ReferenceInductiveBlock<Term>> {
238        self.preflight_recursor_blocks_kernel(
239            &[declaration.to_string()],
240            REFERENCE_NODE_BUDGET,
241            REFERENCE_NODE_BUDGET as u64,
242        )?;
243        self.tables
244            .read_recursor_block(declaration, Lookup::Kernel, &mut |root| {
245                self.tables.translate_with(&Value::from(root), projections)
246            })
247    }
248
249    /// Resolve an existing source identity without translating its type.
250    pub(super) fn source_name(&self, name: &str) -> Result<String> {
251        let (record, _) = self.tables.find(name)?;
252        self.tables.kernel_name(&record["name"])
253    }
254
255    /// Fields explicitly labelled Lean spelling never fall back to kernel aliases.
256    pub(super) fn source_lean_name(&self, name: &str) -> Result<String> {
257        let (record, _) = self.tables.find_lookup(name, Lookup::Lean)?;
258        self.tables.kernel_name(&record["name"])
259    }
260
261    /// Resolve an exact kernel identity and its Lean spelling without lowering a type.
262    pub(super) fn source_spelling(&self, name: &str) -> Result<(String, String)> {
263        let (record, _) = self.tables.find_kernel(name)?;
264        Ok((
265            self.tables.kernel_name(&record["name"])?,
266            self.tables.lean_name(&record["name"])?,
267        ))
268    }
269
270    /// Exact one-root metrics for a pending decode named by kernel identity. Complete recursor-block admission remains
271    /// the separate preflight contract used before choosing a worker or checking a certificate.
272    pub(super) fn source_root_metrics(
273        &self,
274        name: &str,
275        value: bool,
276        max_depth: usize,
277        max_tree_nodes: u64,
278    ) -> Result<SourceMetrics> {
279        let (record, _) = self.tables.find_kernel(name)?;
280        let root = if value {
281            &record["value"]
282        } else {
283            &record["type"]
284        };
285        let mut metrics = SourceMetrics::default();
286        self.tables
287            .preflight_root(id(root)?, max_depth, max_tree_nodes, &mut metrics)?;
288        Ok(metrics)
289    }
290
291    /// Admit exactly the source types and transparent bodies a binding operation will read.
292    /// `body` is false for theorem proofs: proving the same statement does not require their bytes.
293    pub(super) fn preflight(
294        &self,
295        declarations: &[(String, bool)],
296        max_depth: usize,
297        max_tree_nodes: u64,
298    ) -> Result<SourceMetrics> {
299        self.preflight_lookup(declarations, max_depth, max_tree_nodes, Lookup::Mixed)
300    }
301
302    pub(super) fn preflight_kernel(
303        &self,
304        declarations: &[(String, bool)],
305        max_depth: usize,
306        max_tree_nodes: u64,
307    ) -> Result<SourceMetrics> {
308        self.preflight_lookup(declarations, max_depth, max_tree_nodes, Lookup::Kernel)
309    }
310
311    fn preflight_lookup(
312        &self,
313        declarations: &[(String, bool)],
314        max_depth: usize,
315        max_tree_nodes: u64,
316        lookup: Lookup,
317    ) -> Result<SourceMetrics> {
318        let mut total = SourceMetrics::default();
319        let mut recursors = Vec::new();
320        for (name, body) in declarations {
321            let (record, kind) = self.tables.find_lookup(name, lookup)?;
322            if kind == ReferenceKind::Recursor {
323                recursors.push(name.clone());
324            }
325            let mut roots = vec![&record["type"]];
326            if *body {
327                roots.push(&record["value"]);
328            }
329            for root in roots {
330                self.tables
331                    .preflight_root(id(root)?, max_depth, max_tree_nodes, &mut total)?;
332            }
333        }
334        self.preflight_recursor_blocks_into(
335            &recursors,
336            max_depth,
337            max_tree_nodes,
338            &mut total,
339            lookup,
340        )?;
341        Ok(total)
342    }
343}
344
345impl Tables {
346    /// Find a unique recursor declaration and its unique containing block. Lookup is by the
347    /// same Lean/raw/kernel identity contract as `declaration`, never by stripping `.rec`.
348    fn containing_recursor_block(&self, declaration: &str, lookup: Lookup) -> Result<&Value> {
349        let (record, kind) = self.find_lookup(declaration, lookup)?;
350        if kind != ReferenceKind::Recursor {
351            return Err(malformed("requested declaration is not a recursor"));
352        }
353        let wanted = self.kernel_name(&record["name"])?;
354        let mut found = None;
355        for outer in &self.declarations {
356            let Some(block) = outer.get("inductive") else {
357                continue;
358            };
359            for recursor in block_array(block, "recs")? {
360                if self.kernel_name(&recursor["name"])? == wanted {
361                    if found.replace(block).is_some() {
362                        return Err(malformed("recursor has more than one containing block"));
363                    }
364                }
365            }
366        }
367        found.ok_or_else(|| malformed("recursor has no containing inductive block"))
368    }
369
370    fn containing_family_block(&self, declaration: &str) -> Result<&Value> {
371        let (record, kind) = self.find_kernel(declaration)?;
372        if kind != ReferenceKind::Inductive {
373            return Err(malformed(
374                "requested projection parent is not an inductive family",
375            ));
376        }
377        let wanted = self.kernel_name(&record["name"])?;
378        let mut found = None;
379        for outer in &self.declarations {
380            let Some(block) = outer.get("inductive") else {
381                continue;
382            };
383            for member in block_array(block, "types")? {
384                if self.kernel_name(&member["name"])? == wanted {
385                    if found.replace(block).is_some() {
386                        return Err(malformed("family has more than one containing block"));
387                    }
388                }
389            }
390        }
391        found.ok_or_else(|| malformed("family has no containing inductive block"))
392    }
393
394    fn block_names(&self, value: &Value, field: &str) -> Result<Vec<String>> {
395        block_array(value, field)?
396            .iter()
397            .map(|name| self.kernel_name(name))
398            .collect()
399    }
400
401    fn block_declaration<E>(
402        &self,
403        record: &Value,
404        expression: &mut impl FnMut(u64) -> Result<E>,
405    ) -> Result<ReferenceBlockDeclaration<E>> {
406        let level_params = block_array(record, "levelParams")?
407            .iter()
408            .map(|level| self.raw_name(level))
409            .collect::<Result<Vec<_>>>()?;
410        let mut seen = std::collections::HashSet::new();
411        if level_params.iter().any(|level| !seen.insert(level)) {
412            return Err(malformed("duplicate universe parameter in source block"));
413        }
414        Ok(ReferenceBlockDeclaration {
415            name: self.kernel_name(&record["name"])?,
416            lean_name: self.lean_name(&record["name"])?,
417            level_params,
418            ty: expression(id(&record["type"])?)?,
419            is_unsafe: block_bool(record, "isUnsafe")?,
420        })
421    }
422
423    fn read_recursor_block<E>(
424        &self,
425        declaration: &str,
426        lookup: Lookup,
427        expression: &mut impl FnMut(u64) -> Result<E>,
428    ) -> Result<ReferenceInductiveBlock<E>> {
429        let block = self.containing_recursor_block(declaration, lookup)?;
430        self.read_block(block, true, expression)
431    }
432
433    fn read_block<E>(
434        &self,
435        block: &Value,
436        include_recursors: bool,
437        expression: &mut impl FnMut(u64) -> Result<E>,
438    ) -> Result<ReferenceInductiveBlock<E>> {
439        block_fields(block, &["types", "ctors", "recs"])?;
440        let types = block_array(block, "types")?;
441        if types.is_empty() {
442            return Err(malformed("empty source inductive block"));
443        }
444        let mut members = Vec::new();
445        for member in types {
446            block_fields(
447                member,
448                &[
449                    "name",
450                    "levelParams",
451                    "type",
452                    "numParams",
453                    "numIndices",
454                    "numNested",
455                    "isUnsafe",
456                    "isRec",
457                    "isReflexive",
458                    "ctors",
459                    "all",
460                ],
461            )?;
462            members.push(ReferenceInductiveMember {
463                declaration: self.block_declaration(member, expression)?,
464                num_params: block_count(member, "numParams")?,
465                num_indices: block_count(member, "numIndices")?,
466                num_nested: block_count(member, "numNested")?,
467                is_recursive: block_bool(member, "isRec")?,
468                is_reflexive: block_bool(member, "isReflexive")?,
469                constructors: self.block_names(member, "ctors")?,
470                all: self.block_names(member, "all")?,
471            });
472        }
473        let mut constructors = Vec::new();
474        for constructor in block_array(block, "ctors")? {
475            block_fields(
476                constructor,
477                &[
478                    "name",
479                    "levelParams",
480                    "type",
481                    "numParams",
482                    "numFields",
483                    "isUnsafe",
484                    "induct",
485                    "cidx",
486                ],
487            )?;
488            constructors.push(ReferenceConstructor {
489                declaration: self.block_declaration(constructor, expression)?,
490                parent: self.kernel_name(&constructor["induct"])?,
491                index: block_count(constructor, "cidx")?,
492                num_params: block_count(constructor, "numParams")?,
493                num_fields: block_count(constructor, "numFields")?,
494            });
495        }
496        let mut recursors = Vec::new();
497        for recursor in if include_recursors {
498            block_array(block, "recs")?
499        } else {
500            &[]
501        } {
502            block_fields(
503                recursor,
504                &[
505                    "name",
506                    "levelParams",
507                    "type",
508                    "numParams",
509                    "numIndices",
510                    "numMotives",
511                    "numMinors",
512                    "isUnsafe",
513                    "all",
514                    "k",
515                    "rules",
516                ],
517            )?;
518            let declaration = self.block_declaration(recursor, expression)?;
519            let mut rules = Vec::new();
520            for rule in block_array(recursor, "rules")? {
521                block_fields(rule, &["ctor", "nfields", "rhs"])?;
522                rules.push(ReferenceRecursorRule {
523                    constructor: self.kernel_name(&rule["ctor"])?,
524                    num_fields: block_count(rule, "nfields")?,
525                    rhs: expression(id(&rule["rhs"])?)?,
526                });
527            }
528            recursors.push(ReferenceRecursor {
529                declaration,
530                num_params: block_count(recursor, "numParams")?,
531                num_indices: block_count(recursor, "numIndices")?,
532                num_motives: block_count(recursor, "numMotives")?,
533                num_minors: block_count(recursor, "numMinors")?,
534                all: self.block_names(recursor, "all")?,
535                is_k: block_bool(recursor, "k")?,
536                rules,
537            });
538        }
539        let mut names = std::collections::HashSet::new();
540        for name in members
541            .iter()
542            .map(|member| &member.declaration.name)
543            .chain(constructors.iter().map(|ctor| &ctor.declaration.name))
544            .chain(recursors.iter().map(|recursor| &recursor.declaration.name))
545        {
546            if !names.insert(name) {
547                return Err(malformed(
548                    "duplicate declaration identity in source inductive block",
549                ));
550            }
551            // Check the complete export for duplicates of this exact kernel identity.
552            self.find_kernel(name)?;
553        }
554        Ok(ReferenceInductiveBlock {
555            members,
556            constructors,
557            recursors,
558        })
559    }
560
561    fn preflight_root(
562        &self,
563        root: u64,
564        max_depth: usize,
565        max_tree_nodes: u64,
566        total: &mut SourceMetrics,
567    ) -> Result<()> {
568        // Walk each occurrence, not just distinct DAG nodes: the lowerer allocates a tree.
569        // Depth/size are bounded during traversal, so cyclic input also cannot loop unboundedly.
570        enum SourceNode {
571            Expression(u64),
572            Level(u64, usize),
573        }
574        let mut pending = vec![(SourceNode::Expression(root), 1usize)];
575        let mut root_nodes = 0u64;
576        while let Some((node, depth)) = pending.pop() {
577            if depth > max_depth {
578                return Err(declined(
579                    DiagnosticCode::ResourceLimit,
580                    "source expression depth exceeds the environment limit",
581                ));
582            }
583            total.depth = total.depth.max(depth);
584            let mut cost = 1u64;
585            match node {
586                SourceNode::Level(index, nesting) => {
587                    // Match the existing reference level reader's nesting limit.
588                    if nesting > 512 {
589                        return Err(declined(
590                            DiagnosticCode::ResourceLimit,
591                            "universe level nesting",
592                        ));
593                    }
594                    if index != 0 {
595                        let row = self
596                            .levels
597                            .get(&index)
598                            .ok_or_else(|| malformed("unknown source level index"))?;
599                        if let Some(inner) = row.get("succ") {
600                            pending.push((
601                                SourceNode::Level(id(inner)?, nesting + 1),
602                                depth.saturating_add(1),
603                            ));
604                        } else if let Some(pair) = row.get("max").or_else(|| row.get("imax")) {
605                            let pair = pair
606                                .as_array()
607                                .filter(|pair| pair.len() == 2)
608                                .ok_or_else(|| malformed("level pair"))?;
609                            for child in pair {
610                                pending.push((
611                                    SourceNode::Level(id(child)?, nesting + 1),
612                                    depth.saturating_add(1),
613                                ));
614                            }
615                        } else if row.get("param").is_none() {
616                            return Err(malformed("unknown source level record"));
617                        }
618                    }
619                }
620                SourceNode::Expression(index) => {
621                    let row = self
622                        .exprs
623                        .get(&index)
624                        .ok_or_else(|| malformed("unknown source expression index"))?;
625                    if let Some(natural) = row.get("natVal") {
626                        admit_natural_literal(natural, &mut total.natural_work)?;
627                    }
628                    if let Some(level) = row.get("sort") {
629                        pending.push((SourceNode::Level(id(level)?, 0), depth.saturating_add(1)));
630                    } else if let Some(constant) = row.get("const") {
631                        for level in constant["us"]
632                            .as_array()
633                            .ok_or_else(|| malformed("constant levels"))?
634                        {
635                            pending
636                                .push((SourceNode::Level(id(level)?, 0), depth.saturating_add(1)));
637                        }
638                    } else if row.get("strVal").is_some() {
639                        // Count this reference reader's own expansion independently: each scalar
640                        // adds eight term nodes and one list-constant universe; the empty packed
641                        // list has five term nodes and one universe. No production lowering runs.
642                        let scalars = row["strVal"]
643                            .as_str()
644                            .ok_or_else(|| malformed("string literal"))?
645                            .chars()
646                            .count();
647                        cost = u64::try_from(scalars)
648                            .unwrap_or(u64::MAX)
649                            .saturating_mul(9)
650                            .saturating_add(6);
651                        let literal_depth = if scalars == 0 {
652                            4
653                        } else {
654                            scalars.saturating_add(5)
655                        };
656                        let expanded_depth = depth.saturating_add(literal_depth.saturating_sub(1));
657                        if expanded_depth > max_depth {
658                            return Err(declined(
659                                DiagnosticCode::ResourceLimit,
660                                "source string expansion depth exceeds the environment limit",
661                            ));
662                        }
663                        total.depth = total.depth.max(expanded_depth);
664                    }
665                    let children: Vec<&Value> = if let Some(app) = row.get("app") {
666                        vec![&app["fn"], &app["arg"]]
667                    } else if let Some(metadata) = row.get("mdata") {
668                        vec![&metadata["expr"]]
669                    } else if let Some(projection) = row.get("proj") {
670                        total.has_projection = true;
671                        vec![&projection["struct"]]
672                    } else if let Some(binding) = row.get("letE") {
673                        vec![&binding["type"], &binding["value"], &binding["body"]]
674                    } else if let Some(binding) = row.get("lam").or_else(|| row.get("forallE")) {
675                        vec![&binding["type"], &binding["body"]]
676                    } else {
677                        Vec::new()
678                    };
679                    for child in children {
680                        pending.push((SourceNode::Expression(id(child)?), depth.saturating_add(1)));
681                    }
682                }
683            }
684            root_nodes = root_nodes.checked_add(cost).ok_or_else(|| {
685                declined(DiagnosticCode::ResourceLimit, "source tree size overflow")
686            })?;
687            total.tree_nodes = total.tree_nodes.checked_add(cost).ok_or_else(|| {
688                declined(
689                    DiagnosticCode::ResourceLimit,
690                    "source aggregate tree size overflow",
691                )
692            })?;
693            if root_nodes > REFERENCE_NODE_BUDGET as u64 || total.tree_nodes > max_tree_nodes {
694                return Err(declined(
695                    DiagnosticCode::ResourceLimit,
696                    "source expanded tree nodes exceed the reference or environment limit",
697                ));
698            }
699        }
700        Ok(())
701    }
702}
703
704/// Independently decoded source syntax for a whole inductive block. `E` is a raw expression
705/// index during admission, and a kernel term after reference translation. No field is a claim
706/// that the block or its computation rules have been checked by either kernel.
707#[derive(Clone, Debug, PartialEq)]
708pub(super) struct ReferenceInductiveBlock<E> {
709    pub members: Vec<ReferenceInductiveMember<E>>,
710    pub constructors: Vec<ReferenceConstructor<E>>,
711    pub recursors: Vec<ReferenceRecursor<E>>,
712}
713
714#[derive(Clone, Debug, PartialEq)]
715pub(super) struct ReferenceBlockDeclaration<E> {
716    pub name: String,
717    pub lean_name: String,
718    pub level_params: Vec<String>,
719    pub ty: E,
720    pub is_unsafe: bool,
721}
722
723#[derive(Clone, Debug, PartialEq)]
724pub(super) struct ReferenceInductiveMember<E> {
725    pub declaration: ReferenceBlockDeclaration<E>,
726    pub num_params: usize,
727    pub num_indices: usize,
728    pub num_nested: usize,
729    pub is_recursive: bool,
730    pub is_reflexive: bool,
731    pub constructors: Vec<String>,
732    pub all: Vec<String>,
733}
734
735#[derive(Clone, Debug, PartialEq)]
736pub(super) struct ReferenceConstructor<E> {
737    pub declaration: ReferenceBlockDeclaration<E>,
738    pub parent: String,
739    pub index: usize,
740    pub num_params: usize,
741    pub num_fields: usize,
742}
743
744#[derive(Clone, Debug, PartialEq)]
745pub(super) struct ReferenceRecursor<E> {
746    pub declaration: ReferenceBlockDeclaration<E>,
747    pub num_params: usize,
748    pub num_indices: usize,
749    pub num_motives: usize,
750    pub num_minors: usize,
751    pub all: Vec<String>,
752    pub is_k: bool,
753    pub rules: Vec<ReferenceRecursorRule<E>>,
754}
755
756#[derive(Clone, Debug, PartialEq)]
757pub(super) struct ReferenceRecursorRule<E> {
758    pub constructor: String,
759    pub num_fields: usize,
760    pub rhs: E,
761}
762
763impl ReferenceInductiveBlock<u64> {
764    /// Every expression translated by `recursor_block`, in translation order. Repeated roots
765    /// remain repeated: each occurrence contributes to the later tree-allocation budget.
766    pub(super) fn expression_roots(&self) -> Vec<u64> {
767        let mut roots = Vec::new();
768        roots.extend(self.members.iter().map(|member| member.declaration.ty));
769        roots.extend(self.constructors.iter().map(|ctor| ctor.declaration.ty));
770        for recursor in &self.recursors {
771            roots.push(recursor.declaration.ty);
772            roots.extend(recursor.rules.iter().map(|rule| rule.rhs));
773        }
774        roots
775    }
776}
777
778/// What a Lean declaration record is, as far as the statement binding needs to know.
779#[derive(Clone, Copy, Debug, PartialEq, Eq)]
780pub enum ReferenceKind {
781    Definition,
782    Theorem,
783    Opaque,
784    Axiom,
785    Inductive,
786    Constructor,
787    Recursor,
788    Quotient,
789}
790
791impl ReferenceKind {
792    pub fn as_str(self) -> &'static str {
793        match self {
794            ReferenceKind::Definition => "def",
795            ReferenceKind::Theorem => "thm",
796            ReferenceKind::Opaque => "opaque",
797            ReferenceKind::Axiom => "axiom",
798            ReferenceKind::Inductive => "inductive",
799            ReferenceKind::Constructor => "ctor",
800            ReferenceKind::Recursor => "rec",
801            ReferenceKind::Quotient => "quot",
802        }
803    }
804}
805
806/// One declaration as the reference translator reads it.
807#[derive(Clone, Debug, PartialEq)]
808pub struct ReferenceDeclaration {
809    /// The kernel spelling of the declaration's name.
810    pub name: String,
811    /// The Lean spelling (components joined by `.`).
812    pub lean_name: String,
813    pub kind: ReferenceKind,
814    /// Universe parameters, in declaration order, in the kernel spelling.
815    pub level_params: Vec<String>,
816    pub ty: Term,
817}
818
819/// The type of `declaration` as the reference translator reads it from `export`.
820///
821/// `declaration` is either the Lean name (`T06_002.transport`) or its kernel spelling
822/// (`/s:"T06_002"/s:"transport"`).
823pub fn reference_type(export: &str, declaration: &str) -> Result<Term> {
824    reference_declaration(export, declaration).map(|d| d.ty)
825}
826
827/// The whole reference reading of one declaration: kernel name, kind, universe parameters and type.
828pub fn reference_declaration(export: &str, declaration: &str) -> Result<ReferenceDeclaration> {
829    Reference::read(export)?.declaration(declaration)
830}
831
832/// A binder surrounding a source projection. Syntax is borrowed from the independent
833/// decoder's current output stack; entries are ordered from outermost to innermost.
834pub(super) struct ReferenceBinding<'a> {
835    pub name: &'a str,
836    pub ty: &'a Term,
837    pub value: Option<&'a Term>,
838}
839
840/// Typed interpretation of a source projection. The source decoder still resolves the
841/// parent identity, field ordinal, discriminant syntax and lexical scope independently.
842/// Implementations must account for their generated syntax against `budget`.
843pub(super) trait ReferenceProjection {
844    /// Maximum generated term depth, including source wrappers around callback output.
845    /// None preserves the pure reader's source-only iterative admission contract.
846    fn generated_depth_limit(&self) -> Option<usize> {
847        None
848    }
849
850    fn project(
851        &mut self,
852        parent: &str,
853        index: usize,
854        discriminant: Term,
855        scope: &[ReferenceBinding<'_>],
856        budget: &mut usize,
857    ) -> Result<Term>;
858}
859
860/// Cached output shape: term nodes only; universe trees retain their separate admission.
861/// Source wrappers combine this metadata in constant time and never rescan their children.
862#[derive(Clone, Copy)]
863struct OutputShape {
864    depth: usize,
865    nodes: usize,
866    generated: bool,
867}
868
869impl OutputShape {
870    const LEAF: Self = Self {
871        depth: 1,
872        nodes: 1,
873        generated: false,
874    };
875
876    fn wrapper(children: &[Self], limit: Option<usize>) -> Result<Self> {
877        let mut result = Self::LEAF;
878        for child in children {
879            result.depth = result.depth.max(child.depth.checked_add(1).ok_or_else(|| {
880                declined(
881                    DiagnosticCode::ResourceLimit,
882                    "reference output depth overflow",
883                )
884            })?);
885            result.nodes = result.nodes.checked_add(child.nodes).ok_or_else(|| {
886                declined(
887                    DiagnosticCode::ResourceLimit,
888                    "reference output size overflow",
889                )
890            })?;
891            result.generated |= child.generated;
892        }
893        result.admit(limit)?;
894        Ok(result)
895    }
896
897    fn admit(self, limit: Option<usize>) -> Result<()> {
898        if self.nodes > REFERENCE_NODE_BUDGET {
899            return Err(declined(
900                DiagnosticCode::ResourceLimit,
901                "reference output tree node limit",
902            ));
903        }
904        if self.generated && limit.is_some_and(|limit| self.depth > limit) {
905            return Err(declined(
906                DiagnosticCode::ResourceLimit,
907                "reference generated output depth limit",
908            ));
909        }
910        Ok(())
911    }
912
913    /// The callback owns its allocation accounting. This independent, single iterative pass
914    /// measures its actual returned tree without charging those materialized nodes again.
915    fn callback(term: &Term, limit: Option<usize>) -> Result<Self> {
916        let mut shape = Self {
917            depth: 0,
918            nodes: 0,
919            generated: true,
920        };
921        let mut pending = vec![(term, 1usize)];
922        while let Some((term, depth)) = pending.pop() {
923            shape.nodes += 1;
924            shape.depth = shape.depth.max(depth);
925            shape.admit(limit)?;
926            let next = depth.checked_add(1).ok_or_else(|| {
927                declined(
928                    DiagnosticCode::ResourceLimit,
929                    "reference output depth overflow",
930                )
931            })?;
932            // Reserve traversal inventory before extending any variable-length child list.
933            let child_count = match term {
934                Term::Pi { .. } | Term::Lambda { .. } | Term::App(..) => Some(2),
935                Term::Let { .. } => Some(3),
936                Term::Match { cases, .. } => cases.len().checked_add(2),
937                Term::MutualFix { defs, .. } => Some(defs.len()),
938                Term::Fix { .. } | Term::Annotated { .. } => Some(1),
939                _ => Some(0),
940            };
941            let child_count = child_count.ok_or_else(|| {
942                declined(
943                    DiagnosticCode::ResourceLimit,
944                    "reference output child inventory overflow",
945                )
946            })?;
947            if child_count
948                > REFERENCE_NODE_BUDGET
949                    .saturating_sub(shape.nodes)
950                    .saturating_sub(pending.len())
951            {
952                return Err(declined(
953                    DiagnosticCode::ResourceLimit,
954                    "reference output child inventory limit",
955                ));
956            }
957            match term {
958                Term::Pi {
959                    param_type,
960                    body_type,
961                    ..
962                } => {
963                    pending.push((param_type, next));
964                    pending.push((body_type, next));
965                }
966                Term::Lambda {
967                    param_type, body, ..
968                } => {
969                    pending.push((param_type, next));
970                    pending.push((body, next));
971                }
972                Term::App(fun, arg) => {
973                    pending.push((fun, next));
974                    pending.push((arg, next));
975                }
976                Term::Let {
977                    ty, value, body, ..
978                } => {
979                    pending.push((ty, next));
980                    pending.push((value, next));
981                    pending.push((body, next));
982                }
983                Term::Match {
984                    discriminant,
985                    motive,
986                    cases,
987                } => {
988                    pending.push((discriminant, next));
989                    pending.push((motive, next));
990                    pending.extend(cases.iter().map(|term| (term, next)));
991                }
992                Term::Fix { body, .. } => pending.push((body, next)),
993                Term::MutualFix { defs, .. } => {
994                    pending.extend(defs.iter().map(|(_, term)| (term, next)))
995                }
996                Term::Annotated { term, .. } => pending.push((term, next)),
997                Term::Sort(_)
998                | Term::Const { .. }
999                | Term::Var(_)
1000                | Term::Global(_)
1001                | Term::Lit(_)
1002                | Term::Hole => {}
1003            }
1004        }
1005        Ok(shape)
1006    }
1007}
1008
1009struct OutputTerm {
1010    term: Term,
1011    shape: OutputShape,
1012}
1013impl OutputTerm {
1014    fn leaf(term: Term) -> Self {
1015        Self {
1016            term,
1017            shape: OutputShape::LEAF,
1018        }
1019    }
1020}
1021
1022struct NoProjection;
1023impl ReferenceProjection for NoProjection {
1024    fn project(
1025        &mut self,
1026        _: &str,
1027        _: usize,
1028        _: Term,
1029        _: &[ReferenceBinding<'_>],
1030        _: &mut usize,
1031    ) -> Result<Term> {
1032        Err(unsupported(
1033            "the reference translator needs a typed projection reader",
1034        ))
1035    }
1036}
1037
1038/// An export read once, for reading many declarations from it.
1039pub struct Reference {
1040    tables: Tables,
1041}
1042
1043impl Reference {
1044    pub fn read(export: &str) -> Result<Reference> {
1045        Ok(Reference {
1046            tables: Tables::read(export)?,
1047        })
1048    }
1049
1050    /// One declaration, by its Lean or kernel spelling.
1051    pub fn declaration(&self, declaration: &str) -> Result<ReferenceDeclaration> {
1052        self.declaration_with(declaration, &mut NoProjection)
1053    }
1054
1055    pub(super) fn declaration_with(
1056        &self,
1057        declaration: &str,
1058        projections: &mut impl ReferenceProjection,
1059    ) -> Result<ReferenceDeclaration> {
1060        self.declaration_lookup_with(declaration, projections, Lookup::Mixed)
1061    }
1062
1063    pub(super) fn declaration_kernel(&self, declaration: &str) -> Result<ReferenceDeclaration> {
1064        self.declaration_kernel_with(declaration, &mut NoProjection)
1065    }
1066
1067    pub(super) fn declaration_kernel_with(
1068        &self,
1069        declaration: &str,
1070        projections: &mut impl ReferenceProjection,
1071    ) -> Result<ReferenceDeclaration> {
1072        self.declaration_lookup_with(declaration, projections, Lookup::Kernel)
1073    }
1074
1075    fn declaration_lookup_with(
1076        &self,
1077        declaration: &str,
1078        projections: &mut impl ReferenceProjection,
1079        lookup: Lookup,
1080    ) -> Result<ReferenceDeclaration> {
1081        let tables = &self.tables;
1082        let (record, kind) = tables.find_lookup(declaration, lookup)?;
1083        let name = tables.kernel_name(&record["name"])?;
1084        let lean_name = tables.lean_name(&record["name"])?;
1085        let level_params = match record.get("levelParams") {
1086            Some(Value::Array(params)) => params
1087                .iter()
1088                .map(|p| tables.raw_name(p))
1089                .collect::<Result<Vec<_>>>()?,
1090            _ => return Err(malformed("declaration has no universe parameter list")),
1091        };
1092        let ty = tables.translate_with(&record["type"], projections)?;
1093        Ok(ReferenceDeclaration {
1094            name,
1095            lean_name,
1096            kind,
1097            level_params,
1098            ty,
1099        })
1100    }
1101
1102    /// Decode a declaration's source body without admitting it to a kernel context. Callers must
1103    /// check the returned syntax under the intended dependency environment before trusting it.
1104    pub(super) fn value(&self, declaration: &str) -> Result<Term> {
1105        self.value_with(declaration, &mut NoProjection)
1106    }
1107
1108    pub(super) fn value_with(
1109        &self,
1110        declaration: &str,
1111        projections: &mut impl ReferenceProjection,
1112    ) -> Result<Term> {
1113        self.value_lookup_with(declaration, projections, Lookup::Mixed)
1114    }
1115
1116    pub(super) fn value_kernel(&self, declaration: &str) -> Result<Term> {
1117        self.value_kernel_with(declaration, &mut NoProjection)
1118    }
1119
1120    pub(super) fn value_kernel_with(
1121        &self,
1122        declaration: &str,
1123        projections: &mut impl ReferenceProjection,
1124    ) -> Result<Term> {
1125        self.value_lookup_with(declaration, projections, Lookup::Kernel)
1126    }
1127
1128    fn value_lookup_with(
1129        &self,
1130        declaration: &str,
1131        projections: &mut impl ReferenceProjection,
1132        lookup: Lookup,
1133    ) -> Result<Term> {
1134        let (record, kind) = self.tables.find_lookup(declaration, lookup)?;
1135        if !matches!(
1136            kind,
1137            ReferenceKind::Definition | ReferenceKind::Theorem | ReferenceKind::Opaque
1138        ) {
1139            return Err(unsupported("the source declaration has no body"));
1140        }
1141        self.tables.translate_with(&record["value"], projections)
1142    }
1143
1144    /// Every declared name, in the kernel spelling, with its kind — without translating any type.
1145    pub fn names(&self) -> Result<Vec<(String, ReferenceKind)>> {
1146        let mut seen = std::collections::HashSet::new();
1147        let mut names = Vec::new();
1148        for (record, kind) in self.tables.candidates() {
1149            let name = self.tables.kernel_name(&record["name"])?;
1150            if !seen.insert(name.clone()) {
1151                return Err(malformed(format!(
1152                    "the export declares kernel identity {name} more than once"
1153                )));
1154            }
1155            names.push((name, kind));
1156        }
1157        Ok(names)
1158    }
1159}
1160
1161/// The kernel spelling of a Lean name given as its hierarchical components.
1162///
1163/// Every component is a string component; a numeric Lean component cannot be told apart from a
1164/// digit string by its dotted spelling, so callers holding an export resolve such names through
1165/// [`reference_declaration`], which matches against the export's own name table.
1166pub fn kernel_name_of_components(components: &[&str]) -> String {
1167    let mut raw = String::new();
1168    for component in components {
1169        raw.push_str("/s:");
1170        raw.push_str(&serde_json::to_string(component).expect("a string serializes"));
1171    }
1172    primitive_identity(&raw)
1173}
1174
1175// This decoder owns its schema checks; it does not call the production reader's validators.
1176fn block_fields(value: &Value, expected: &[&str]) -> Result<()> {
1177    let object = value
1178        .as_object()
1179        .ok_or_else(|| malformed("expected a source block record"))?;
1180    if object.len() != expected.len() || expected.iter().any(|field| !object.contains_key(*field)) {
1181        return Err(malformed(
1182            "source block record has missing or unexpected fields",
1183        ));
1184    }
1185    Ok(())
1186}
1187
1188fn block_array<'a>(value: &'a Value, field: &str) -> Result<&'a [Value]> {
1189    value
1190        .get(field)
1191        .and_then(Value::as_array)
1192        .map(Vec::as_slice)
1193        .ok_or_else(|| malformed(format!("source block {field} must be an array")))
1194}
1195
1196fn block_bool(value: &Value, field: &str) -> Result<bool> {
1197    value
1198        .get(field)
1199        .and_then(Value::as_bool)
1200        .ok_or_else(|| malformed(format!("source block {field} must be a Boolean")))
1201}
1202
1203fn block_count(value: &Value, field: &str) -> Result<usize> {
1204    let count = value
1205        .get(field)
1206        .and_then(Value::as_u64)
1207        .ok_or_else(|| malformed(format!("source block {field} must be an unsigned integer")))?;
1208    usize::try_from(count)
1209        .map_err(|_| malformed(format!("source block {field} exceeds platform size")))
1210}
1211
1212fn malformed(message: impl Into<String>) -> super::Verdict {
1213    declined(DiagnosticCode::MalformedExport, message)
1214}
1215
1216fn unsupported(message: impl Into<String>) -> super::Verdict {
1217    declined(DiagnosticCode::UnsupportedFeature, message)
1218}
1219
1220/// The kernel's primitive identities, applied to a raw hierarchical spelling.
1221fn primitive_identity(raw: &str) -> String {
1222    const NAT: &str = "/s:\"Nat\"";
1223    match raw {
1224        "/s:\"Quot\"" => "kernel.quot.type".into(),
1225        "/s:\"Quot\"/s:\"mk\"" => "kernel.quot.mk".into(),
1226        "/s:\"Quot\"/s:\"lift\"" => "kernel.quot.lift".into(),
1227        "/s:\"Quot\"/s:\"ind\"" => "kernel.quot.ind".into(),
1228        NAT => "Nat".into(),
1229        "/s:\"Nat\"/s:\"zero\"" => "Zero".into(),
1230        "/s:\"Nat\"/s:\"succ\"" => "Succ".into(),
1231        _ => match raw.strip_prefix("/s:\"Nat\"/") {
1232            Some(rest) => format!("Nat/{rest}"),
1233            None => raw.to_string(),
1234        },
1235    }
1236}
1237
1238/// A name as its components: `(raw spelling, Lean spelling)`.
1239#[derive(Clone)]
1240struct Name {
1241    raw: String,
1242    lean: String,
1243}
1244
1245/// External mixed-name convenience APIs keep their existing ambiguity rejection.
1246/// Internal callers already hold canonical kernel identities and must use only that namespace.
1247#[derive(Clone, Copy)]
1248enum Lookup {
1249    Mixed,
1250    Kernel,
1251    Lean,
1252}
1253
1254struct Tables {
1255    names: HashMap<u64, Name>,
1256    levels: HashMap<u64, Value>,
1257    exprs: HashMap<u64, Value>,
1258    declarations: Vec<Value>,
1259    /// The header's `meta.lean.version`, which alone decides how a string literal reads.
1260    release: Option<String>,
1261}
1262
1263/// The constant a Lean release applies to a string literal's `List Char`, as each release's own
1264/// `Init.Prelude` defines `String`. `None`: no reading is pinned for that release.
1265fn string_literal_packing(release: Option<&str>) -> Option<&'static str> {
1266    match release? {
1267        "4.14.0" => Some("/s:\"String\"/s:\"mk\""),
1268        "4.29.1" | "4.30.0" | "4.33.0" => Some("/s:\"String\"/s:\"ofList\""),
1269        _ => None,
1270    }
1271}
1272
1273fn id(v: &Value) -> Result<u64> {
1274    v.as_u64().ok_or_else(|| malformed("expected an index"))
1275}
1276
1277impl Tables {
1278    fn read(export: &str) -> Result<Tables> {
1279        let mut lines = export.lines();
1280        let header: Value =
1281            serde_json::from_str(lines.next().ok_or_else(|| malformed("empty export"))?)
1282                .map_err(|e| malformed(format!("export header: {e}")))?;
1283        if header["meta"]["format"]["version"] != "3.1.0" {
1284            return Err(declined(
1285                DiagnosticCode::UnsupportedExportVersion,
1286                "reference translation reads format 3.1.0 only",
1287            ));
1288        }
1289        let mut tables = Tables {
1290            names: HashMap::from([(
1291                0,
1292                Name {
1293                    raw: String::new(),
1294                    lean: String::new(),
1295                },
1296            )]),
1297            levels: HashMap::new(),
1298            exprs: HashMap::new(),
1299            declarations: Vec::new(),
1300            release: header["meta"]["lean"]["version"]
1301                .as_str()
1302                .map(str::to_owned),
1303        };
1304        for (at, line) in lines.enumerate() {
1305            let record: Value = serde_json::from_str(line)
1306                .map_err(|e| malformed(format!("line {}: {e}", at + 2)))?;
1307            if let Some(index) = record.get("in") {
1308                let index = id(index)?;
1309                let (tag, part) = if let Some(x) = record.get("str") {
1310                    let s = x["str"]
1311                        .as_str()
1312                        .ok_or_else(|| malformed("string name component"))?;
1313                    (
1314                        "s",
1315                        (
1316                            serde_json::to_string(s).expect("a string serializes"),
1317                            s.to_string(),
1318                        ),
1319                    )
1320                } else if let Some(x) = record.get("num") {
1321                    let n = id(&x["i"])?;
1322                    ("n", (n.to_string(), n.to_string()))
1323                } else {
1324                    return Err(malformed("unknown name record"));
1325                };
1326                let pre_record = record
1327                    .get("str")
1328                    .or_else(|| record.get("num"))
1329                    .expect("tagged above");
1330                let prefix = tables
1331                    .names
1332                    .get(&id(&pre_record["pre"])?)
1333                    .ok_or_else(|| malformed("name prefix is not an earlier name"))?
1334                    .clone();
1335                let lean = if prefix.lean.is_empty() {
1336                    part.1
1337                } else {
1338                    format!("{}.{}", prefix.lean, part.1)
1339                };
1340                let name = Name {
1341                    raw: format!("{}/{tag}:{}", prefix.raw, part.0),
1342                    lean,
1343                };
1344                if tables.names.insert(index, name).is_some() {
1345                    return Err(malformed("duplicate name index"));
1346                }
1347            } else if let Some(index) = record.get("il") {
1348                if tables.levels.insert(id(index)?, record).is_some() {
1349                    return Err(malformed("duplicate level index"));
1350                }
1351            } else if let Some(index) = record.get("ie") {
1352                if tables.exprs.insert(id(index)?, record).is_some() {
1353                    return Err(malformed("duplicate expression index"));
1354                }
1355            } else {
1356                tables.declarations.push(record);
1357            }
1358        }
1359        Ok(tables)
1360    }
1361
1362    fn name_entry(&self, v: &Value) -> Result<&Name> {
1363        self.names
1364            .get(&id(v)?)
1365            .ok_or_else(|| malformed("unknown name index"))
1366    }
1367
1368    fn raw_name(&self, v: &Value) -> Result<String> {
1369        Ok(self.name_entry(v)?.raw.clone())
1370    }
1371
1372    fn kernel_name(&self, v: &Value) -> Result<String> {
1373        Ok(primitive_identity(&self.name_entry(v)?.raw))
1374    }
1375
1376    fn lean_name(&self, v: &Value) -> Result<String> {
1377        Ok(self.name_entry(v)?.lean.clone())
1378    }
1379
1380    /// Every declaration-shaped record with its kind: top-level declarations, and the types,
1381    /// constructors and recursors inside inductive blocks.
1382    fn candidates(&self) -> Vec<(&Value, ReferenceKind)> {
1383        let mut out = Vec::new();
1384        for record in &self.declarations {
1385            let Some(object) = record.as_object() else {
1386                continue;
1387            };
1388            for (tag, body) in object {
1389                match tag.as_str() {
1390                    "def" => out.push((body, ReferenceKind::Definition)),
1391                    "thm" => out.push((body, ReferenceKind::Theorem)),
1392                    "opaque" => out.push((body, ReferenceKind::Opaque)),
1393                    "axiom" => out.push((body, ReferenceKind::Axiom)),
1394                    "quot" => out.push((body, ReferenceKind::Quotient)),
1395                    "inductive" => {
1396                        for (field, kind) in [
1397                            ("types", ReferenceKind::Inductive),
1398                            ("ctors", ReferenceKind::Constructor),
1399                            ("recs", ReferenceKind::Recursor),
1400                        ] {
1401                            if let Some(Value::Array(items)) = body.get(field) {
1402                                out.extend(items.iter().map(|item| (item, kind)));
1403                            }
1404                        }
1405                    }
1406                    _ => {}
1407                }
1408            }
1409        }
1410        out
1411    }
1412
1413    fn find(&self, declaration: &str) -> Result<(&Value, ReferenceKind)> {
1414        self.find_lookup(declaration, Lookup::Mixed)
1415    }
1416
1417    fn find_kernel(&self, declaration: &str) -> Result<(&Value, ReferenceKind)> {
1418        self.find_lookup(declaration, Lookup::Kernel)
1419    }
1420
1421    fn find_lookup(&self, declaration: &str, lookup: Lookup) -> Result<(&Value, ReferenceKind)> {
1422        let mut found = Vec::new();
1423        for (record, kind) in self.candidates() {
1424            let name = self.name_entry(&record["name"])?;
1425            let matches = match lookup {
1426                Lookup::Mixed => {
1427                    name.lean == declaration
1428                        || name.raw == declaration
1429                        || primitive_identity(&name.raw) == declaration
1430                }
1431                Lookup::Kernel => primitive_identity(&name.raw) == declaration,
1432                Lookup::Lean => name.lean == declaration,
1433            };
1434            if matches {
1435                found.push((record, kind));
1436            }
1437        }
1438        match found.len() {
1439            0 => Err(rejected(
1440                DiagnosticCode::InvalidProof,
1441                format!("the export declares no {declaration}"),
1442            )),
1443            1 => Ok(found.remove(0)),
1444            _ => Err(rejected(
1445                DiagnosticCode::MalformedExport,
1446                format!("the export declares {declaration} more than once"),
1447            )),
1448        }
1449    }
1450
1451    fn level(&self, v: &Value) -> Result<Universe> {
1452        let index = id(v)?;
1453        if index == 0 {
1454            return Ok(Universe::Prop);
1455        }
1456        enum Task {
1457            Enter(u64, usize),
1458            Succ,
1459            Max,
1460            IMax,
1461        }
1462        let mut work = vec![Task::Enter(index, 0)];
1463        let mut out: Vec<Universe> = Vec::new();
1464        while let Some(task) = work.pop() {
1465            match task {
1466                Task::Enter(i, depth) => {
1467                    if depth > 512 {
1468                        return Err(declined(
1469                            DiagnosticCode::ResourceLimit,
1470                            "universe level nesting",
1471                        ));
1472                    }
1473                    if i == 0 {
1474                        out.push(Universe::Prop);
1475                        continue;
1476                    }
1477                    let record = self
1478                        .levels
1479                        .get(&i)
1480                        .ok_or_else(|| malformed("unknown level index"))?;
1481                    if let Some(inner) = record.get("succ") {
1482                        work.push(Task::Succ);
1483                        work.push(Task::Enter(id(inner)?, depth + 1));
1484                    } else if let Some(param) = record.get("param") {
1485                        out.push(Universe::Var(self.raw_name(param)?));
1486                    } else if let Some(pair) = record.get("max").or_else(|| record.get("imax")) {
1487                        let pair = pair
1488                            .as_array()
1489                            .filter(|p| p.len() == 2)
1490                            .ok_or_else(|| malformed("level pair"))?;
1491                        work.push(if record.get("max").is_some() {
1492                            Task::Max
1493                        } else {
1494                            Task::IMax
1495                        });
1496                        work.push(Task::Enter(id(&pair[1])?, depth + 1));
1497                        work.push(Task::Enter(id(&pair[0])?, depth + 1));
1498                    } else {
1499                        return Err(malformed("unknown level record"));
1500                    }
1501                }
1502                Task::Succ => {
1503                    let u = out.pop().expect("successor operand");
1504                    out.push(u.succ());
1505                }
1506                Task::Max | Task::IMax => {
1507                    let b = out.pop().expect("right operand");
1508                    let a = out.pop().expect("left operand");
1509                    out.push(if matches!(task, Task::Max) {
1510                        Universe::Max(Box::new(a), Box::new(b))
1511                    } else {
1512                        Universe::IMax(Box::new(a), Box::new(b))
1513                    });
1514                }
1515            }
1516        }
1517        Ok(out.pop().expect("one level"))
1518    }
1519
1520    /// Translate one expression, post-order, with an explicit stack. Bound variables are named by
1521    /// binder depth (`ref.<depth>`), so a de Bruijn index resolves to exactly one binder.
1522    fn translate(&self, root: &Value) -> Result<Term> {
1523        self.translate_with(root, &mut NoProjection)
1524    }
1525
1526    fn translate_with(
1527        &self,
1528        root: &Value,
1529        projections: &mut impl ReferenceProjection,
1530    ) -> Result<Term> {
1531        // Direct decoding preserves the existing iterative million-node envelope. Package
1532        // callers first admit these roots against their stricter declared environment limits.
1533        // Do this before *any* source allocation or callback, including on the `_with` APIs.
1534        self.preflight_root(
1535            id(root)?,
1536            REFERENCE_NODE_BUDGET,
1537            REFERENCE_NODE_BUDGET as u64,
1538            &mut SourceMetrics::default(),
1539        )?;
1540        enum Task<'a> {
1541            Enter(u64),
1542            App,
1543            Projection { parent: String, index: usize },
1544            Binder { kind: &'a str, record: &'a Value },
1545            Close { kind: &'a str, name: String },
1546        }
1547        let mut work = vec![Task::Enter(id(root)?)];
1548        let mut out: Vec<OutputTerm> = Vec::new();
1549        let generated_depth_limit = projections.generated_depth_limit();
1550        let mut depth: usize = 0;
1551        // Domains and let values stay below the body on `out` until Close. Indices avoid
1552        // copying a whole telescope every time a nested binder is entered.
1553        let mut scope: Vec<(String, usize, Option<usize>)> = Vec::new();
1554        let mut budget = REFERENCE_NODE_BUDGET;
1555        let mut natural_work = 0u64;
1556        while let Some(task) = work.pop() {
1557            match task {
1558                Task::Enter(i) => {
1559                    budget = budget.checked_sub(1).ok_or_else(|| {
1560                        declined(
1561                            DiagnosticCode::ResourceLimit,
1562                            "reference translation node budget",
1563                        )
1564                    })?;
1565                    let record = self
1566                        .exprs
1567                        .get(&i)
1568                        .ok_or_else(|| malformed("unknown expression index"))?;
1569                    if let Some(u) = record.get("sort") {
1570                        out.push(OutputTerm::leaf(Term::Sort(self.level(u)?)));
1571                    } else if let Some(k) = record.get("bvar") {
1572                        let k = usize::try_from(id(k)?).map_err(|_| malformed("bound index"))?;
1573                        let at = depth.checked_sub(k + 1).ok_or_else(|| {
1574                            rejected(DiagnosticCode::MalformedExport, "loose bound variable")
1575                        })?;
1576                        out.push(OutputTerm::leaf(Term::Var(format!("ref.{at}"))));
1577                    } else if let Some(c) = record.get("const") {
1578                        let name = self.kernel_name(&c["name"])?;
1579                        let levels = c["us"]
1580                            .as_array()
1581                            .ok_or_else(|| malformed("constant levels"))?
1582                            .iter()
1583                            .map(|u| self.level(u))
1584                            .collect::<Result<Vec<_>>>()?;
1585                        out.push(OutputTerm::leaf(if levels.is_empty() {
1586                            Term::Global(name)
1587                        } else {
1588                            Term::Const { name, levels }
1589                        }));
1590                    } else if let Some(a) = record.get("app") {
1591                        work.push(Task::App);
1592                        work.push(Task::Enter(id(&a["arg"])?));
1593                        work.push(Task::Enter(id(&a["fn"])?));
1594                    } else if let Some(m) = record.get("mdata") {
1595                        work.push(Task::Enter(id(&m["expr"])?));
1596                    } else if let Some((kind, b)) = ["forallE", "lam", "letE"]
1597                        .iter()
1598                        .find_map(|k| record.get(*k).map(|b| (*k, b)))
1599                    {
1600                        // The domain (and a let's value) live outside the binder; the body inside.
1601                        work.push(Task::Binder { kind, record: b });
1602                        if kind == "letE" {
1603                            work.push(Task::Enter(id(&b["value"])?));
1604                        }
1605                        work.push(Task::Enter(id(&b["type"])?));
1606                    } else if let Some(n) = record.get("natVal") {
1607                        let significant = admit_natural_literal(n, &mut natural_work)?;
1608                        let value = if significant.is_empty() {
1609                            crate::BigInt::zero()
1610                        } else {
1611                            crate::BigInt::parse_decimal(significant)
1612                                .ok_or_else(|| malformed("natural literal"))?
1613                        };
1614                        out.push(OutputTerm::leaf(Term::Lit(Literal::Nat(value))));
1615                    } else if let Some(proj) = record.get("proj") {
1616                        let parent = self.kernel_name(&proj["typeName"])?;
1617                        let index = usize::try_from(id(&proj["idx"])?)
1618                            .map_err(|_| malformed("projection index"))?;
1619                        work.push(Task::Projection { parent, index });
1620                        work.push(Task::Enter(id(&proj["struct"])?));
1621                    } else if let Some(text) = record.get("strVal") {
1622                        let text = text.as_str().ok_or_else(|| malformed("string literal"))?;
1623                        let packing = string_literal_packing(self.release.as_deref()).ok_or_else(|| declined(
1624                            DiagnosticCode::UnsupportedExportVersion,
1625                            format!("the reference translator has no string-literal reading for Lean release {:?}", self.release),
1626                        ))?;
1627                        // `packing (List.cons Char (Char.ofNat c₁) (… (List.nil Char)))`: four nodes per
1628                        // scalar value plus the nil, the element type and the packing, spent up front.
1629                        let scalars: Vec<char> = text.chars().collect();
1630                        // The source preflight already admits this complete expansion. Record
1631                        // its term-only shape once; do not scan the growing list after each cons.
1632                        let shape = OutputShape {
1633                            depth: if scalars.is_empty() {
1634                                3
1635                            } else {
1636                                scalars.len() + 4
1637                            },
1638                            nodes: scalars
1639                                .len()
1640                                .checked_mul(8)
1641                                .and_then(|n| n.checked_add(5))
1642                                .ok_or_else(|| {
1643                                    declined(
1644                                        DiagnosticCode::ResourceLimit,
1645                                        "reference string output size overflow",
1646                                    )
1647                                })?,
1648                            generated: false,
1649                        };
1650                        shape.admit(generated_depth_limit)?;
1651                        budget = scalars
1652                            .len()
1653                            .checked_mul(4)
1654                            .and_then(|n| n.checked_add(3))
1655                            .and_then(|n| budget.checked_sub(n))
1656                            .ok_or_else(|| {
1657                                declined(
1658                                    DiagnosticCode::ResourceLimit,
1659                                    "reference translation node budget",
1660                                )
1661                            })?;
1662                        let character = || Term::Global("/s:\"Char\"".into());
1663                        let list_constant = |component: &str| Term::Const {
1664                            name: format!("/s:\"List\"/s:\"{component}\""),
1665                            levels: vec![Universe::Prop],
1666                        };
1667                        let mut list =
1668                            Term::App(Box::new(list_constant("nil")), Box::new(character()));
1669                        for scalar in scalars.into_iter().rev() {
1670                            let code_point = Term::App(
1671                                Box::new(Term::Global("/s:\"Char\"/s:\"ofNat\"".into())),
1672                                Box::new(Term::Lit(Literal::Nat(crate::BigInt::from_u64(
1673                                    u64::from(scalar),
1674                                )))),
1675                            );
1676                            let cons =
1677                                Term::App(Box::new(list_constant("cons")), Box::new(character()));
1678                            list = Term::App(
1679                                Box::new(Term::App(Box::new(cons), Box::new(code_point))),
1680                                Box::new(list),
1681                            );
1682                        }
1683                        out.push(OutputTerm {
1684                            term: Term::App(Box::new(Term::Global(packing.into())), Box::new(list)),
1685                            shape,
1686                        });
1687                    } else {
1688                        return Err(malformed("unknown expression record"));
1689                    }
1690                }
1691                Task::App => {
1692                    let arg = out.pop().expect("argument");
1693                    let fun = out.pop().expect("function");
1694                    let shape =
1695                        OutputShape::wrapper(&[fun.shape, arg.shape], generated_depth_limit)?;
1696                    out.push(OutputTerm {
1697                        term: Term::App(Box::new(fun.term), Box::new(arg.term)),
1698                        shape,
1699                    });
1700                }
1701                Task::Projection { parent, index } => {
1702                    let discriminant = out.pop().expect("projection discriminant");
1703                    let bindings: Vec<_> = scope
1704                        .iter()
1705                        .map(|(name, ty, value)| ReferenceBinding {
1706                            name,
1707                            ty: &out[*ty].term,
1708                            value: value.map(|at| &out[at].term),
1709                        })
1710                        .collect();
1711                    let projected = projections.project(
1712                        &parent,
1713                        index,
1714                        discriminant.term,
1715                        &bindings,
1716                        &mut budget,
1717                    )?;
1718                    let shape = OutputShape::callback(&projected, generated_depth_limit)?;
1719                    out.push(OutputTerm {
1720                        term: projected,
1721                        shape,
1722                    });
1723                }
1724                Task::Binder { kind, record } => {
1725                    let name = format!("ref.{depth}");
1726                    let (ty, value) = if kind == "letE" {
1727                        (out.len() - 2, Some(out.len() - 1))
1728                    } else {
1729                        (out.len() - 1, None)
1730                    };
1731                    scope.push((name.clone(), ty, value));
1732                    depth += 1;
1733                    work.push(Task::Close { kind, name });
1734                    work.push(Task::Enter(id(&record["body"])?));
1735                }
1736                Task::Close { kind, name } => {
1737                    scope.pop().expect("active source binder");
1738                    depth -= 1;
1739                    let body = out.pop().expect("binder body");
1740                    let output = match kind {
1741                        "letE" => {
1742                            let value = out.pop().expect("let value");
1743                            let ty = out.pop().expect("let type");
1744                            let shape = OutputShape::wrapper(
1745                                &[ty.shape, value.shape, body.shape],
1746                                generated_depth_limit,
1747                            )?;
1748                            OutputTerm {
1749                                term: Term::Let {
1750                                    name,
1751                                    ty: Box::new(ty.term),
1752                                    value: Box::new(value.term),
1753                                    body: Box::new(body.term),
1754                                },
1755                                shape,
1756                            }
1757                        }
1758                        "lam" => {
1759                            let ty = out.pop().expect("binder domain");
1760                            let shape = OutputShape::wrapper(
1761                                &[ty.shape, body.shape],
1762                                generated_depth_limit,
1763                            )?;
1764                            OutputTerm {
1765                                term: Term::Lambda {
1766                                    param: name,
1767                                    param_type: Box::new(ty.term),
1768                                    body: Box::new(body.term),
1769                                },
1770                                shape,
1771                            }
1772                        }
1773                        _ => {
1774                            let ty = out.pop().expect("binder domain");
1775                            let shape = OutputShape::wrapper(
1776                                &[ty.shape, body.shape],
1777                                generated_depth_limit,
1778                            )?;
1779                            OutputTerm {
1780                                term: Term::Pi {
1781                                    param: name,
1782                                    param_type: Box::new(ty.term),
1783                                    body_type: Box::new(body.term),
1784                                },
1785                                shape,
1786                            }
1787                        }
1788                    };
1789                    out.push(output);
1790                }
1791            }
1792        }
1793        Ok(out.pop().expect("one term").term)
1794    }
1795}
1796
1797#[cfg(test)]
1798#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/reference/projection_scope_controls.rs"]
1799mod projection_scope_controls;
1800
1801#[cfg(test)]
1802#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/reference/block_controls.rs"]
1803mod block_controls;
1804
1805#[cfg(test)]
1806#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/reference/wrapper_controls.rs"]
1807mod wrapper_controls;
1808
1809#[cfg(test)]
1810#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/reference/natural_work_controls.rs"]
1811mod natural_work_controls;
1812
1813#[cfg(test)]
1814#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/reference/identity_controls.rs"]
1815mod identity_controls;