Skip to main content

logicaffeine_kernel/interface/lean/
intake.rs

1//! Intake: the strict front door of a Lean → LOGOS result.
2//!
3//! The reader (`super`) is deliberately tolerant — it is also the Lean Kernel Arena benchmark
4//! checker, and reads any `3.1.0` export. Intake is the door a *result* goes through, and it holds an
5//! export to an explicit compatibility rule before any declaration is translated:
6//!
7//! * the header names the pinned profile ([`STAGE_A`]: Lean 4.14.0, lean4export 3.1.0, format
8//!   3.1.0) — an unknown format version is [`UnsupportedExportVersion`](DiagnosticCode::UnsupportedExportVersion),
9//!   any other producer is [`UnsupportedToolchain`](DiagnosticCode::UnsupportedToolchain);
10//! * every record has exactly the fields format 3.1.0 defines for its kind — an unknown field is
11//!   [`UnknownField`](DiagnosticCode::UnknownField), a missing or mistyped one
12//!   [`MalformedExport`](DiagnosticCode::MalformedExport), an unknown kind
13//!   [`UnknownRecordKind`](DiagnosticCode::UnknownRecordKind), a metavariable or free variable
14//!   [`UnresolvedMetavariable`](DiagnosticCode::UnresolvedMetavariable);
15//! * every referenced constant is declared ([`MissingDeclaration`](DiagnosticCode::MissingDeclaration))
16//!   and none is declared twice ([`DuplicateDeclaration`](DiagnosticCode::DuplicateDeclaration)).
17//!
18//! It also answers the question retention, emission and packaging share: which declarations does a
19//! target depend on ([`Intake::closure`]), in dependency order.
20
21use super::{canonical_name, declined, rejected, DiagnosticCode, Result};
22use serde_json::{Map, Value};
23use std::collections::{HashMap, HashSet};
24
25/// The toolchain and assumption profile a result is produced under.
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct Profile {
28    pub lean_version: &'static str,
29    pub format_version: &'static str,
30    pub exporter_name: &'static str,
31    pub exporter_version: &'static str,
32    /// The assumptions a verified result may depend on, by Lean name.
33    pub allowed_axioms: &'static [&'static str],
34}
35
36/// Stage A: Lean 4.14.0, lean4export 3.1.0 (revision [`EXPORTER_REVISION`]), export format 3.1.0,
37/// Lean core only, and Lean's three standard axioms.
38pub const STAGE_A: Profile = Profile {
39    lean_version: "4.14.0",
40    format_version: "3.1.0",
41    exporter_name: "lean4export",
42    exporter_version: "3.1.0",
43    allowed_axioms: &["propext", "Quot.sound", "Classical.choice"],
44};
45
46/// The elan toolchain name of the pinned Lean.
47pub const TOOLCHAIN: &str = "leanprover/lean4:v4.14.0";
48/// The pinned lean4export revision.
49pub const EXPORTER_REVISION: &str = "20c4a64aa1b76f7c79a6db15d089084359f8c3dc";
50/// SHA-256 of the pinned exporter's sources at [`EXPORTER_REVISION`]. The exporter is identified by
51/// its content, never by the name of the directory holding it.
52pub const EXPORTER_SOURCES: [(&str, &str); 2] = [
53    (
54        "Main.lean",
55        "bd8e350903f1382b25fe767f9086dd9882000f01085f7046ea06545e7848842a",
56    ),
57    (
58        "Export.lean",
59        "16fa1fec370500f4ca03f042fc3e65cac27839b640b32c5a921e085206c7031b",
60    ),
61];
62/// The exporter flags every result is produced with: metadata and unsafe declarations are exported
63/// so that the reader, not the exporter, decides what they mean.
64pub const EXPORTER_FLAGS: [&str; 2] = ["--export-mdata", "--export-unsafe"];
65
66/// The export's first record.
67#[derive(Clone, Debug, PartialEq, Eq)]
68pub struct Header {
69    pub lean_version: String,
70    pub lean_githash: String,
71    pub exporter_name: String,
72    pub exporter_version: String,
73    pub format_version: String,
74}
75
76/// What a declaration record declares.
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum DeclarationKind {
79    Axiom,
80    Definition,
81    Theorem,
82    Opaque,
83    Quotient,
84    Inductive,
85    Constructor,
86    Recursor,
87}
88
89impl DeclarationKind {
90    pub fn as_str(self) -> &'static str {
91        match self {
92            DeclarationKind::Axiom => "axiom",
93            DeclarationKind::Definition => "def",
94            DeclarationKind::Theorem => "thm",
95            DeclarationKind::Opaque => "opaque",
96            DeclarationKind::Quotient => "quot",
97            DeclarationKind::Inductive => "inductive",
98            DeclarationKind::Constructor => "ctor",
99            DeclarationKind::Recursor => "rec",
100        }
101    }
102}
103
104/// One declared constant.
105#[derive(Clone, Debug, PartialEq, Eq)]
106pub struct Declaration {
107    /// The kernel identity the reader registers it under.
108    pub kernel_name: String,
109    /// The dotted Lean name.
110    pub lean_name: String,
111    pub kind: DeclarationKind,
112    /// The 1-based export line of its record.
113    pub line: usize,
114    /// Its record: the members of one inductive block share one.
115    record: usize,
116}
117
118/// One declaration record's dependency roots.
119struct Record {
120    roots: Vec<usize>,
121    members: Vec<usize>,
122}
123
124/// An export that passed intake.
125pub struct Intake {
126    header: Header,
127    declarations: Vec<Declaration>,
128    records: Vec<Record>,
129    by_kernel_name: HashMap<String, usize>,
130    /// Expression id → (children, referenced constant's name index).
131    expressions: HashMap<usize, (Vec<usize>, Option<usize>)>,
132    /// Name index → declaration index.
133    declared: HashMap<usize, usize>,
134}
135
136impl Intake {
137    pub fn header(&self) -> &Header {
138        &self.header
139    }
140    /// Every declared constant, in export order — which lean4export makes dependency order.
141    pub fn declarations(&self) -> &[Declaration] {
142        &self.declarations
143    }
144    /// The declaration registered under `kernel_name`.
145    pub fn declaration(&self, kernel_name: &str) -> Option<&Declaration> {
146        self.by_kernel_name
147            .get(kernel_name)
148            .map(|&i| &self.declarations[i])
149    }
150    /// The dependency closure of `kernel_name`: the declaration itself, every constant its type and
151    /// value mention, transitively, and every member of each inductive block reached — in export
152    /// order, so the target comes after everything it depends on.
153    pub fn closure(&self, kernel_name: &str) -> Result<Vec<String>> {
154        let &start = self.by_kernel_name.get(kernel_name).ok_or_else(|| {
155            rejected(
156                DiagnosticCode::MissingDeclaration,
157                format!("the export does not declare {}", lean_name(kernel_name)),
158            )
159            .in_declaration(lean_name(kernel_name))
160        })?;
161        let mut records_seen = HashSet::new();
162        let mut expressions_seen = HashSet::new();
163        let mut in_closure = vec![false; self.declarations.len()];
164        let mut pending_records = vec![self.declarations[start].record];
165        let mut pending_expressions = Vec::new();
166        while let Some(record) = pending_records.pop() {
167            if !records_seen.insert(record) {
168                continue;
169            }
170            for &member in &self.records[record].members {
171                in_closure[member] = true;
172            }
173            pending_expressions.extend(self.records[record].roots.iter().copied());
174            while let Some(expression) = pending_expressions.pop() {
175                if !expressions_seen.insert(expression) {
176                    continue;
177                }
178                let Some((children, constant)) = self.expressions.get(&expression) else {
179                    continue;
180                };
181                pending_expressions.extend(children.iter().copied());
182                if let Some(declaration) = constant.and_then(|name| self.declared.get(&name)) {
183                    pending_records.push(self.declarations[*declaration].record);
184                }
185            }
186        }
187        Ok(self
188            .declarations
189            .iter()
190            .zip(in_closure)
191            .filter(|(_, member)| *member)
192            .map(|(d, _)| d.kernel_name.clone())
193            .collect())
194    }
195}
196
197/// Read and validate an export under `profile`.
198pub fn read(export: &str, profile: &Profile) -> Result<Intake> {
199    let mut lines = export.lines();
200    let first = lines.next().ok_or_else(|| {
201        declined(DiagnosticCode::MalformedExport, "the export is empty").at_line(1)
202    })?;
203    let header = read_header(first, profile).map_err(|v| v.at_line(1))?;
204    let mut reader = Reader::default();
205    reader.names.insert(0, String::new());
206    reader.levels.insert(0);
207    for (offset, text) in lines.enumerate() {
208        let line = offset + 2;
209        if text.trim().is_empty() {
210            continue;
211        }
212        let record: Value = serde_json::from_str(text).map_err(|e| {
213            declined(DiagnosticCode::MalformedExport, format!("line {line}: {e}")).at_line(line)
214        })?;
215        reader.record(&record, line).map_err(|v| v.at_line(line))?;
216    }
217    reader.finish(header)
218}
219
220/// The header, held to the profile: the format version first (a newer format may legitimately add
221/// fields), then the header's own fields, then the producing toolchain.
222pub fn read_header(first: &str, profile: &Profile) -> Result<Header> {
223    let value: Value = serde_json::from_str(first).map_err(|e| {
224        declined(
225            DiagnosticCode::MalformedExport,
226            format!("the export header is not JSON: {e}"),
227        )
228    })?;
229    if let Some(format) = value
230        .pointer("/meta/format/version")
231        .and_then(Value::as_str)
232    {
233        if format != profile.format_version {
234            return Err(declined(
235                DiagnosticCode::UnsupportedExportVersion,
236                format!(
237                    "export format {format} is not the supported format {}",
238                    profile.format_version
239                ),
240            ));
241        }
242    }
243    let root = object(&value, "the export header")?;
244    fields(root, "the export header", &["meta"], &[])?;
245    let meta = object(&root["meta"], "meta")?;
246    fields(meta, "meta", &["exporter", "lean", "format"], &[])?;
247    let exporter = object(&meta["exporter"], "meta.exporter")?;
248    fields(exporter, "meta.exporter", &["name", "version"], &[])?;
249    let lean = object(&meta["lean"], "meta.lean")?;
250    fields(lean, "meta.lean", &["githash", "version"], &[])?;
251    let format = object(&meta["format"], "meta.format")?;
252    fields(format, "meta.format", &["version"], &[])?;
253    let header = Header {
254        lean_version: string(&lean["version"], "meta.lean.version")?,
255        lean_githash: string(&lean["githash"], "meta.lean.githash")?,
256        exporter_name: string(&exporter["name"], "meta.exporter.name")?,
257        exporter_version: string(&exporter["version"], "meta.exporter.version")?,
258        format_version: string(&format["version"], "meta.format.version")?,
259    };
260    if header.format_version != profile.format_version {
261        return Err(declined(
262            DiagnosticCode::UnsupportedExportVersion,
263            format!(
264                "export format {} is not the supported format {}",
265                header.format_version, profile.format_version
266            ),
267        ));
268    }
269    if header.lean_version != profile.lean_version
270        || header.exporter_name != profile.exporter_name
271        || header.exporter_version != profile.exporter_version
272    {
273        return Err(declined(
274            DiagnosticCode::UnsupportedToolchain,
275            format!(
276                "the export was produced by Lean {} with {} {}; the supported profile is Lean {} with {} {}",
277                header.lean_version,
278                header.exporter_name,
279                header.exporter_version,
280                profile.lean_version,
281                profile.exporter_name,
282                profile.exporter_version
283            ),
284        ));
285    }
286    Ok(header)
287}
288
289fn object<'v>(value: &'v Value, what: &str) -> Result<&'v Map<String, Value>> {
290    value.as_object().ok_or_else(|| {
291        declined(
292            DiagnosticCode::MalformedExport,
293            format!("{what} is not a JSON object"),
294        )
295    })
296}
297
298fn string(value: &Value, what: &str) -> Result<String> {
299    value.as_str().map(str::to_string).ok_or_else(|| {
300        declined(
301            DiagnosticCode::MalformedExport,
302            format!("{what} is not a string"),
303        )
304    })
305}
306
307/// `object` has every field in `required`, possibly some in `optional`, and nothing else.
308fn fields(
309    object: &Map<String, Value>,
310    what: &str,
311    required: &[&str],
312    optional: &[&str],
313) -> Result<()> {
314    if let Some(unknown) = object
315        .keys()
316        .find(|k| !required.contains(&k.as_str()) && !optional.contains(&k.as_str()))
317    {
318        return Err(declined(
319            DiagnosticCode::UnknownField,
320            format!("{what} has the field {unknown:?}, which export format 3.1.0 does not define"),
321        ));
322    }
323    if let Some(missing) = required.iter().find(|k| !object.contains_key(**k)) {
324        return Err(declined(
325            DiagnosticCode::MalformedExport,
326            format!("{what} lacks its mandatory field {missing:?}"),
327        ));
328    }
329    Ok(())
330}
331
332fn natural(value: &Value, what: &str) -> Result<usize> {
333    value
334        .as_u64()
335        .and_then(|n| usize::try_from(n).ok())
336        .ok_or_else(|| {
337            declined(
338                DiagnosticCode::MalformedExport,
339                format!("{what} is not a natural number"),
340            )
341        })
342}
343
344fn boolean(value: &Value, what: &str) -> Result<()> {
345    value.as_bool().map(|_| ()).ok_or_else(|| {
346        declined(
347            DiagnosticCode::MalformedExport,
348            format!("{what} is not a boolean"),
349        )
350    })
351}
352
353fn naturals(value: &Value, what: &str) -> Result<Vec<usize>> {
354    value
355        .as_array()
356        .ok_or_else(|| {
357            declined(
358                DiagnosticCode::MalformedExport,
359                format!("{what} is not an array"),
360            )
361        })?
362        .iter()
363        .map(|v| natural(v, what))
364        .collect()
365}
366
367#[derive(Default)]
368struct Reader {
369    names: HashMap<usize, String>,
370    levels: HashSet<usize>,
371    expressions: HashMap<usize, (Vec<usize>, Option<usize>)>,
372    /// The first line at which each constant name index is referenced by an expression.
373    referenced: HashMap<usize, usize>,
374    declarations: Vec<Declaration>,
375    records: Vec<Record>,
376    by_kernel_name: HashMap<String, usize>,
377    declared: HashMap<usize, usize>,
378}
379
380impl Reader {
381    fn record(&mut self, record: &Value, line: usize) -> Result<()> {
382        let map = record.as_object().ok_or_else(|| {
383            declined(
384                DiagnosticCode::MalformedExport,
385                "the record is not a JSON object",
386            )
387        })?;
388        if map.contains_key("in") {
389            return self.name(map);
390        }
391        if map.contains_key("il") {
392            return self.level(map);
393        }
394        if map.contains_key("ie") {
395            return self.expression(map, line);
396        }
397        if map.len() != 1 {
398            let kinds: Vec<&String> = map.keys().collect();
399            return Err(declined(
400                DiagnosticCode::UnknownRecordKind,
401                format!("a record with fields {kinds:?} is no record kind of export format 3.1.0"),
402            ));
403        }
404        let (kind, body) = map.iter().next().expect("one field");
405        let body = object(body, kind)?;
406        match kind.as_str() {
407            "axiom" => {
408                fields(
409                    body,
410                    "axiom",
411                    &["name", "levelParams", "type", "isUnsafe"],
412                    &[],
413                )?;
414                boolean(&body["isUnsafe"], "axiom.isUnsafe")?;
415                self.simple(body, DeclarationKind::Axiom, line, &["type"])
416            }
417            "def" => {
418                fields(
419                    body,
420                    "def",
421                    &[
422                        "name",
423                        "levelParams",
424                        "type",
425                        "value",
426                        "hints",
427                        "safety",
428                        "all",
429                    ],
430                    &[],
431                )?;
432                naturals(&body["all"], "def.all")?;
433                match &body["safety"] {
434                    Value::String(s) if ["safe", "unsafe", "partial"].contains(&s.as_str()) => {}
435                    other => {
436                        return Err(declined(
437                            DiagnosticCode::MalformedExport,
438                            format!("def.safety {other} is not safe, unsafe or partial"),
439                        ))
440                    }
441                }
442                self.simple(body, DeclarationKind::Definition, line, &["type", "value"])
443            }
444            "opaque" => {
445                fields(
446                    body,
447                    "opaque",
448                    &["name", "levelParams", "type", "value", "isUnsafe", "all"],
449                    &[],
450                )?;
451                boolean(&body["isUnsafe"], "opaque.isUnsafe")?;
452                naturals(&body["all"], "opaque.all")?;
453                self.simple(body, DeclarationKind::Opaque, line, &["type", "value"])
454            }
455            "thm" => {
456                fields(
457                    body,
458                    "thm",
459                    &["name", "levelParams", "type", "value", "all"],
460                    &[],
461                )?;
462                naturals(&body["all"], "thm.all")?;
463                self.simple(body, DeclarationKind::Theorem, line, &["type", "value"])
464            }
465            "quot" => {
466                fields(body, "quot", &["name", "levelParams", "type", "kind"], &[])?;
467                match body["kind"].as_str() {
468                    Some("type" | "ctor" | "lift" | "ind") => {}
469                    _ => {
470                        return Err(declined(
471                            DiagnosticCode::UnknownRecordKind,
472                            format!(
473                                "quot.kind {} is no quotient kind of export format 3.1.0",
474                                body["kind"]
475                            ),
476                        ))
477                    }
478                }
479                self.simple(body, DeclarationKind::Quotient, line, &["type"])
480            }
481            "inductive" => self.inductive(body, line),
482            other => Err(declined(
483                DiagnosticCode::UnknownRecordKind,
484                format!("{other:?} is no declaration kind of export format 3.1.0"),
485            )),
486        }
487    }
488
489    fn name(&mut self, map: &Map<String, Value>) -> Result<()> {
490        let index = natural(&map["in"], "in")?;
491        let (part, body) = if map.contains_key("str") {
492            fields(map, "a name record", &["in", "str"], &[])?;
493            let body = object(&map["str"], "str")?;
494            fields(body, "str", &["pre", "str"], &[])?;
495            let text = string(&body["str"], "str.str")?;
496            (
497                format!(
498                    "s:{}",
499                    serde_json::to_string(&text).expect("a string serializes")
500                ),
501                body,
502            )
503        } else if map.contains_key("num") {
504            fields(map, "a name record", &["in", "num"], &[])?;
505            let body = object(&map["num"], "num")?;
506            fields(body, "num", &["pre", "i"], &[])?;
507            (format!("n:{}", natural(&body["i"], "num.i")?), body)
508        } else {
509            return Err(declined(
510                DiagnosticCode::UnknownRecordKind,
511                "a name record that is neither str nor num",
512            ));
513        };
514        let prefix = self
515            .names
516            .get(&natural(&body["pre"], "pre")?)
517            .ok_or_else(|| {
518                declined(
519                    DiagnosticCode::MalformedExport,
520                    "a name's prefix is not an earlier name",
521                )
522            })?
523            .clone();
524        if self
525            .names
526            .insert(index, format!("{prefix}/{part}"))
527            .is_some()
528        {
529            return Err(declined(
530                DiagnosticCode::MalformedExport,
531                format!("name index {index} is defined twice"),
532            ));
533        }
534        Ok(())
535    }
536
537    fn level(&mut self, map: &Map<String, Value>) -> Result<()> {
538        let index = natural(&map["il"], "il")?;
539        let kind = ["succ", "max", "imax", "param"]
540            .into_iter()
541            .find(|k| map.contains_key(*k))
542            .ok_or_else(|| {
543                declined(
544                    DiagnosticCode::UnknownRecordKind,
545                    "a level record of no level kind of export format 3.1.0",
546                )
547            })?;
548        fields(map, "a level record", &["il", kind], &[])?;
549        let earlier = |this: &Self, v: &Value| -> Result<()> {
550            let j = natural(v, kind)?;
551            if this.levels.contains(&j) {
552                Ok(())
553            } else {
554                Err(declined(
555                    DiagnosticCode::MalformedExport,
556                    format!("level {index} refers to level {j}, which is not an earlier level"),
557                ))
558            }
559        };
560        match kind {
561            "succ" => earlier(self, &map["succ"])?,
562            "param" => {
563                let name = natural(&map["param"], "param")?;
564                if !self.names.contains_key(&name) {
565                    return Err(declined(
566                        DiagnosticCode::MalformedExport,
567                        "a universe parameter's name is not an earlier name",
568                    ));
569                }
570            }
571            _ => {
572                let pair = map[kind]
573                    .as_array()
574                    .filter(|p| p.len() == 2)
575                    .ok_or_else(|| {
576                        declined(
577                            DiagnosticCode::MalformedExport,
578                            format!("{kind} is not a pair of levels"),
579                        )
580                    })?;
581                earlier(self, &pair[0])?;
582                earlier(self, &pair[1])?;
583            }
584        }
585        if !self.levels.insert(index) {
586            return Err(declined(
587                DiagnosticCode::MalformedExport,
588                format!("level index {index} is defined twice"),
589            ));
590        }
591        Ok(())
592    }
593
594    fn expression(&mut self, map: &Map<String, Value>, line: usize) -> Result<()> {
595        let index = natural(&map["ie"], "ie")?;
596        if map.contains_key("mvar") || map.contains_key("fvar") {
597            return Err(rejected(
598                DiagnosticCode::UnresolvedMetavariable,
599                format!("expression {index} is a metavariable or free variable: the declaration was never fully elaborated"),
600            ));
601        }
602        const KINDS: [&str; 11] = [
603            "bvar", "sort", "const", "app", "lam", "forallE", "letE", "proj", "natVal", "strVal",
604            "mdata",
605        ];
606        let kind = KINDS
607            .into_iter()
608            .find(|k| map.contains_key(*k))
609            .ok_or_else(|| {
610                let fields: Vec<&String> = map.keys().collect();
611                declined(DiagnosticCode::UnknownRecordKind, format!("expression {index} ({fields:?}) is no expression kind of export format 3.1.0"))
612            })?;
613        fields(map, "an expression record", &["ie", kind], &[])?;
614        let body = &map[kind];
615        let mut children = Vec::new();
616        let mut constant = None;
617        match kind {
618            "bvar" => {
619                natural(body, "bvar")?;
620            }
621            "sort" => self.level_ref(body)?,
622            "natVal" | "strVal" => {
623                string(body, kind)?;
624            }
625            "const" => {
626                let c = object(body, "const")?;
627                fields(c, "const", &["name", "us"], &[])?;
628                let name = natural(&c["name"], "const.name")?;
629                if !self.names.contains_key(&name) {
630                    return Err(declined(
631                        DiagnosticCode::MalformedExport,
632                        "a constant's name is not an earlier name",
633                    ));
634                }
635                for u in c["us"].as_array().ok_or_else(|| {
636                    declined(DiagnosticCode::MalformedExport, "const.us is not an array")
637                })? {
638                    self.level_ref(u)?;
639                }
640                self.referenced.entry(name).or_insert(line);
641                constant = Some(name);
642            }
643            "app" => {
644                let a = object(body, "app")?;
645                fields(a, "app", &["fn", "arg"], &[])?;
646                children.push(natural(&a["fn"], "app.fn")?);
647                children.push(natural(&a["arg"], "app.arg")?);
648            }
649            "lam" | "forallE" => {
650                let b = object(body, kind)?;
651                fields(b, kind, &["name", "type", "body", "binderInfo"], &[])?;
652                self.name_ref(&b["name"])?;
653                match b["binderInfo"].as_str() {
654                    Some("default" | "implicit" | "strictImplicit" | "instImplicit") => {}
655                    _ => {
656                        return Err(declined(
657                            DiagnosticCode::UnknownField,
658                            format!(
659                            "{kind}.binderInfo {} is no binder information of export format 3.1.0",
660                            b["binderInfo"]
661                        ),
662                        ))
663                    }
664                }
665                children.push(natural(&b["type"], kind)?);
666                children.push(natural(&b["body"], kind)?);
667            }
668            "letE" => {
669                let b = object(body, "letE")?;
670                fields(b, "letE", &["name", "type", "value", "body", "nondep"], &[])?;
671                self.name_ref(&b["name"])?;
672                boolean(&b["nondep"], "letE.nondep")?;
673                for part in ["type", "value", "body"] {
674                    children.push(natural(&b[part], "letE")?);
675                }
676            }
677            "proj" => {
678                let p = object(body, "proj")?;
679                fields(p, "proj", &["typeName", "idx", "struct"], &[])?;
680                let structure = natural(&p["typeName"], "proj.typeName")?;
681                if !self.names.contains_key(&structure) {
682                    return Err(declined(
683                        DiagnosticCode::MalformedExport,
684                        "a projection's structure is not an earlier name",
685                    ));
686                }
687                natural(&p["idx"], "proj.idx")?;
688                self.referenced.entry(structure).or_insert(line);
689                constant = Some(structure);
690                children.push(natural(&p["struct"], "proj.struct")?);
691            }
692            "mdata" => {
693                let m = object(body, "mdata")?;
694                fields(m, "mdata", &["expr", "data"], &[])?;
695                object(&m["data"], "mdata.data")?;
696                children.push(natural(&m["expr"], "mdata.expr")?);
697            }
698            _ => unreachable!("every expression kind is matched"),
699        }
700        if let Some(child) = children.iter().find(|c| !self.expressions.contains_key(c)) {
701            return Err(declined(DiagnosticCode::MalformedExport, format!("expression {index} refers to expression {child}, which is not an earlier expression")));
702        }
703        if self
704            .expressions
705            .insert(index, (children, constant))
706            .is_some()
707        {
708            return Err(declined(
709                DiagnosticCode::MalformedExport,
710                format!("expression index {index} is defined twice"),
711            ));
712        }
713        Ok(())
714    }
715
716    fn level_ref(&self, value: &Value) -> Result<()> {
717        let level = natural(value, "level")?;
718        if self.levels.contains(&level) {
719            Ok(())
720        } else {
721            Err(declined(
722                DiagnosticCode::MalformedExport,
723                format!("level {level} is not an earlier level"),
724            ))
725        }
726    }
727
728    fn name_ref(&self, value: &Value) -> Result<usize> {
729        let name = natural(value, "name")?;
730        if self.names.contains_key(&name) {
731            Ok(name)
732        } else {
733            Err(declined(
734                DiagnosticCode::MalformedExport,
735                format!("name {name} is not an earlier name"),
736            ))
737        }
738    }
739
740    fn expression_ref(&self, value: &Value, what: &str) -> Result<usize> {
741        let expression = natural(value, what)?;
742        if self.expressions.contains_key(&expression) {
743            Ok(expression)
744        } else {
745            Err(declined(
746                DiagnosticCode::MalformedExport,
747                format!(
748                    "{what} refers to expression {expression}, which is not an earlier expression"
749                ),
750            ))
751        }
752    }
753
754    fn level_params(&self, value: &Value, what: &str) -> Result<()> {
755        for name in naturals(value, what)? {
756            if !self.names.contains_key(&name) {
757                return Err(declined(
758                    DiagnosticCode::MalformedExport,
759                    format!("{what} names an unknown universe parameter"),
760                ));
761            }
762        }
763        Ok(())
764    }
765
766    /// A single-declaration record whose dependency roots are the expressions under `roots`.
767    fn simple(
768        &mut self,
769        body: &Map<String, Value>,
770        kind: DeclarationKind,
771        line: usize,
772        roots: &[&str],
773    ) -> Result<()> {
774        self.level_params(&body["levelParams"], "levelParams")?;
775        let roots = roots
776            .iter()
777            .map(|field| self.expression_ref(&body[*field], field))
778            .collect::<Result<Vec<_>>>()?;
779        let name = self.name_ref(&body["name"])?;
780        let record = self.records.len();
781        self.records.push(Record {
782            roots,
783            members: Vec::new(),
784        });
785        self.declare(name, kind, line, record)
786    }
787
788    fn inductive(&mut self, body: &Map<String, Value>, line: usize) -> Result<()> {
789        fields(body, "inductive", &["types", "ctors", "recs"], &[])?;
790        let record = self.records.len();
791        self.records.push(Record {
792            roots: Vec::new(),
793            members: Vec::new(),
794        });
795        let list = |field: &str| -> Result<Vec<Value>> {
796            body[field].as_array().cloned().ok_or_else(|| {
797                declined(
798                    DiagnosticCode::MalformedExport,
799                    format!("inductive.{field} is not an array"),
800                )
801            })
802        };
803        let (types, ctors, recs) = (list("types")?, list("ctors")?, list("recs")?);
804        let mut roots = Vec::new();
805        let mut members = Vec::new();
806        for t in &types {
807            let t = object(t, "an inductive type")?;
808            fields(
809                t,
810                "an inductive type",
811                &[
812                    "name",
813                    "levelParams",
814                    "type",
815                    "numParams",
816                    "numIndices",
817                    "all",
818                    "ctors",
819                    "numNested",
820                    "isRec",
821                    "isUnsafe",
822                    "isReflexive",
823                ],
824                &[],
825            )?;
826            for n in ["numParams", "numIndices", "numNested"] {
827                natural(&t[n], n)?;
828            }
829            for b in ["isRec", "isUnsafe", "isReflexive"] {
830                boolean(&t[b], b)?;
831            }
832            naturals(&t["all"], "all")?;
833            naturals(&t["ctors"], "ctors")?;
834            self.level_params(&t["levelParams"], "levelParams")?;
835            roots.push(self.expression_ref(&t["type"], "type")?);
836            members.push((self.name_ref(&t["name"])?, DeclarationKind::Inductive));
837        }
838        for c in &ctors {
839            let c = object(c, "a constructor")?;
840            fields(
841                c,
842                "a constructor",
843                &[
844                    "name",
845                    "levelParams",
846                    "type",
847                    "induct",
848                    "cidx",
849                    "numParams",
850                    "numFields",
851                    "isUnsafe",
852                ],
853                &[],
854            )?;
855            for n in ["induct", "cidx", "numParams", "numFields"] {
856                natural(&c[n], n)?;
857            }
858            boolean(&c["isUnsafe"], "isUnsafe")?;
859            self.level_params(&c["levelParams"], "levelParams")?;
860            roots.push(self.expression_ref(&c["type"], "type")?);
861            members.push((self.name_ref(&c["name"])?, DeclarationKind::Constructor));
862        }
863        for r in &recs {
864            let r = object(r, "a recursor")?;
865            fields(
866                r,
867                "a recursor",
868                &[
869                    "name",
870                    "levelParams",
871                    "type",
872                    "all",
873                    "numParams",
874                    "numIndices",
875                    "numMotives",
876                    "numMinors",
877                    "rules",
878                    "k",
879                    "isUnsafe",
880                ],
881                &[],
882            )?;
883            for n in ["numParams", "numIndices", "numMotives", "numMinors"] {
884                natural(&r[n], n)?;
885            }
886            for b in ["k", "isUnsafe"] {
887                boolean(&r[b], b)?;
888            }
889            naturals(&r["all"], "all")?;
890            self.level_params(&r["levelParams"], "levelParams")?;
891            roots.push(self.expression_ref(&r["type"], "type")?);
892            for rule in r["rules"]
893                .as_array()
894                .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "rules is not an array"))?
895            {
896                let rule = object(rule, "a recursor rule")?;
897                fields(rule, "a recursor rule", &["ctor", "nfields", "rhs"], &[])?;
898                self.name_ref(&rule["ctor"])?;
899                natural(&rule["nfields"], "nfields")?;
900                roots.push(self.expression_ref(&rule["rhs"], "rhs")?);
901            }
902            members.push((self.name_ref(&r["name"])?, DeclarationKind::Recursor));
903        }
904        self.records[record].roots = roots;
905        for (name, kind) in members {
906            self.declare(name, kind, line, record)?;
907        }
908        Ok(())
909    }
910
911    fn declare(
912        &mut self,
913        name: usize,
914        kind: DeclarationKind,
915        line: usize,
916        record: usize,
917    ) -> Result<()> {
918        let encoded = &self.names[&name];
919        let kernel_name = canonical_name(encoded);
920        let lean = lean_name(&kernel_name);
921        if let Some(&earlier) = self.by_kernel_name.get(&kernel_name) {
922            return Err(rejected(
923                DiagnosticCode::DuplicateDeclaration,
924                format!(
925                    "{lean} is declared again (first declared at line {})",
926                    self.declarations[earlier].line
927                ),
928            )
929            .in_declaration(lean));
930        }
931        let index = self.declarations.len();
932        self.by_kernel_name.insert(kernel_name.clone(), index);
933        self.declared.insert(name, index);
934        self.records[record].members.push(index);
935        self.declarations.push(Declaration {
936            kernel_name,
937            lean_name: lean,
938            kind,
939            line,
940            record,
941        });
942        Ok(())
943    }
944
945    /// Every referenced constant must be declared somewhere in the export. The refusal names the
946    /// first declaration (in export order) whose closure reaches the missing constant.
947    fn finish(self, header: Header) -> Result<Intake> {
948        let mut missing: Vec<(usize, usize)> = self
949            .referenced
950            .iter()
951            .filter(|(name, _)| !self.declared.contains_key(name))
952            .map(|(&name, &line)| (line, name))
953            .collect();
954        missing.sort();
955        let intake = Intake {
956            header,
957            declarations: self.declarations,
958            records: self.records,
959            by_kernel_name: self.by_kernel_name,
960            expressions: self.expressions,
961            declared: self.declared,
962        };
963        let Some(&(first_line, name)) = missing.first() else {
964            return Ok(intake);
965        };
966        let lean = lean_name(&canonical_name(&self.names[&name]));
967        let missing_names: HashSet<usize> = missing.iter().map(|(_, n)| *n).collect();
968        let referrer = intake.declarations.iter().find(|d| {
969            let mut seen = HashSet::new();
970            let mut work = intake.records[d.record].roots.clone();
971            while let Some(e) = work.pop() {
972                if !seen.insert(e) {
973                    continue;
974                }
975                if let Some((children, constant)) = intake.expressions.get(&e) {
976                    if constant.is_some_and(|c| missing_names.contains(&c)) {
977                        return true;
978                    }
979                    work.extend(children.iter().copied());
980                }
981            }
982            false
983        });
984        let refusal = rejected(
985            DiagnosticCode::MissingDeclaration,
986            format!("{lean} is referenced but never declared"),
987        );
988        Err(match referrer {
989            Some(d) => refusal.at_line(d.line).in_declaration(d.lean_name.clone()),
990            None => refusal.at_line(first_line),
991        })
992    }
993}
994
995impl std::fmt::Debug for Intake {
996    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
997        f.debug_struct("Intake")
998            .field("header", &self.header)
999            .field("declarations", &self.declarations.len())
1000            .finish()
1001    }
1002}
1003
1004// ============================================================================================
1005// LEAN NAMES ↔ KERNEL NAMES
1006// ============================================================================================
1007
1008/// One component of a Lean name.
1009#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1010pub enum Component {
1011    Str(String),
1012    Num(u64),
1013}
1014
1015/// Parse a dotted Lean name as Lean prints it: components separated by `.`, a component that is not
1016/// an identifier written `«…»`, a component of digits numeric.
1017pub fn parse_lean_name(text: &str) -> Result<Vec<Component>> {
1018    let invalid = || {
1019        declined(
1020            DiagnosticCode::Usage,
1021            format!("{text:?} is not a Lean name"),
1022        )
1023    };
1024    let mut components = Vec::new();
1025    let mut rest = text;
1026    loop {
1027        let (component, after) = if let Some(quoted) = rest.strip_prefix('«') {
1028            let end = quoted.find('»').ok_or_else(invalid)?;
1029            (
1030                Component::Str(quoted[..end].to_string()),
1031                &quoted[end + '»'.len_utf8()..],
1032            )
1033        } else {
1034            let end = rest.find('.').unwrap_or(rest.len());
1035            let part = &rest[..end];
1036            if part.is_empty() {
1037                return Err(invalid());
1038            }
1039            let component = if part.bytes().all(|b| b.is_ascii_digit()) {
1040                Component::Num(part.parse().map_err(|_| invalid())?)
1041            } else {
1042                Component::Str(part.to_string())
1043            };
1044            (component, &rest[end..])
1045        };
1046        components.push(component);
1047        if after.is_empty() {
1048            return Ok(components);
1049        }
1050        rest = after.strip_prefix('.').ok_or_else(invalid)?;
1051        if rest.is_empty() {
1052            return Err(invalid());
1053        }
1054    }
1055}
1056
1057/// The structural encoding of a Lean name, before the reader's reserved identities apply.
1058pub fn encode(components: &[Component]) -> String {
1059    components
1060        .iter()
1061        .map(|c| match c {
1062            Component::Str(s) => format!(
1063                "/s:{}",
1064                serde_json::to_string(s).expect("a string serializes")
1065            ),
1066            Component::Num(n) => format!("/n:{n}"),
1067        })
1068        .collect()
1069}
1070
1071/// The kernel identity the reader registers the dotted Lean name `lean` under.
1072pub fn kernel_name(lean: &str) -> Result<String> {
1073    Ok(canonical_name(&encode(&parse_lean_name(lean)?)))
1074}
1075
1076/// The components of a kernel identity, when it is the encoding of a Lean name.
1077pub fn components(kernel: &str) -> Option<Vec<Component>> {
1078    let encoded = uncanonical(kernel)?;
1079    let mut rest = encoded.as_str();
1080    let mut out = Vec::new();
1081    while !rest.is_empty() {
1082        rest = rest.strip_prefix('/')?;
1083        if let Some(s) = rest.strip_prefix("s:") {
1084            let mut stream = serde_json::Deserializer::from_str(s).into_iter::<String>();
1085            let text = stream.next()?.ok()?;
1086            let used = stream.byte_offset();
1087            out.push(Component::Str(text));
1088            rest = &s[used..];
1089        } else if let Some(n) = rest.strip_prefix("n:") {
1090            let end = n.find('/').unwrap_or(n.len());
1091            out.push(Component::Num(n[..end].parse().ok()?));
1092            rest = &n[end..];
1093        } else {
1094            return None;
1095        }
1096    }
1097    (!out.is_empty()).then_some(out)
1098}
1099
1100/// The dotted Lean name a kernel identity encodes, as Lean prints it; a kernel name that encodes no
1101/// Lean name is returned unchanged.
1102pub fn lean_name(kernel: &str) -> String {
1103    match components(kernel) {
1104        Some(components) => print_lean_name(&components),
1105        None => kernel.to_string(),
1106    }
1107}
1108
1109/// Print components as Lean prints a name.
1110pub fn print_lean_name(components: &[Component]) -> String {
1111    components
1112        .iter()
1113        .map(|c| match c {
1114            Component::Num(n) => n.to_string(),
1115            Component::Str(s) if is_identifier(s) => s.clone(),
1116            Component::Str(s) => format!("«{s}»"),
1117        })
1118        .collect::<Vec<_>>()
1119        .join(".")
1120}
1121
1122fn is_identifier(s: &str) -> bool {
1123    let mut chars = s.chars();
1124    let Some(first) = chars.next() else {
1125        return false;
1126    };
1127    (first.is_alphabetic() || first == '_')
1128        && !s.bytes().all(|b| b.is_ascii_digit())
1129        && chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '\'' | '!' | '?'))
1130}
1131
1132/// Undo [`canonical_name`]'s reserved identities.
1133fn uncanonical(kernel: &str) -> Option<String> {
1134    use crate::quotient::Kind;
1135    let quotients = [
1136        (Kind::Type, "/s:\"Quot\""),
1137        (Kind::Constructor, "/s:\"Quot\"/s:\"mk\""),
1138        (Kind::Lift, "/s:\"Quot\"/s:\"lift\""),
1139        (Kind::Induction, "/s:\"Quot\"/s:\"ind\""),
1140    ];
1141    if let Some((_, encoded)) = quotients.iter().find(|(kind, _)| kind.name() == kernel) {
1142        return Some(encoded.to_string());
1143    }
1144    Some(match kernel {
1145        "Nat" => "/s:\"Nat\"".to_string(),
1146        "Zero" => "/s:\"Nat\"/s:\"zero\"".to_string(),
1147        "Succ" => "/s:\"Nat\"/s:\"succ\"".to_string(),
1148        _ => match kernel.strip_prefix("Nat/") {
1149            Some(suffix) => format!("/s:\"Nat\"/{suffix}"),
1150            None if kernel.starts_with('/') => kernel.to_string(),
1151            None => return None,
1152        },
1153    })
1154}