Skip to main content

logicaffeine_kernel/interface/
repl.rs

1//! REPL for the Vernacular interface.
2//!
3//! Orchestrates command parsing and kernel execution.
4
5use super::command::Command;
6use super::command_parser::parse_command;
7use super::error::InterfaceError;
8use crate::prelude::StandardLibrary;
9use crate::{
10    auto_bind_implicits, bind_self_recursion, derive_recursor, fill_match_motives, infer_type,
11    normalize, surface_elaborate, surface_elaborate_against, Context, Term,
12};
13
14/// The Vernacular REPL.
15///
16/// Maintains a kernel context and executes commands against it.
17pub struct Repl {
18    ctx: Context,
19}
20
21impl Repl {
22    /// Create a new REPL with the standard library loaded.
23    pub fn new() -> Self {
24        let mut ctx = Context::new();
25        StandardLibrary::register(&mut ctx);
26        Self { ctx }
27    }
28
29    /// Execute a command string.
30    ///
31    /// Returns the output string (for Check/Eval) or empty string (for Definition/Inductive).
32    pub fn execute(&mut self, input: &str) -> Result<String, InterfaceError> {
33        let cmd = parse_command(input)?;
34
35        match cmd {
36            Command::Definition { name, ty, body, is_hint, implicit_count } => {
37                // Auto-bind free type variables (`id : A -> A`) as leading implicits, then
38                // elaborate the body against its declared type — implicit arguments are
39                // filled from the body's own arguments AND from the expected type.
40                let (ty, body, implicit_count) = match ty {
41                    Some(t) => {
42                        let (t, b, k) = auto_bind_implicits(&self.ctx, &t, &body, implicit_count);
43                        (Some(t), b, k)
44                    }
45                    None => (None, body, implicit_count),
46                };
47                // Recursive-definition sugar: a body that calls the definition by name is
48                // bound with a `fix` (the kernel's termination guard certifies it).
49                let body = bind_self_recursion(&name, &body);
50                let body = fill_match_motives(&self.ctx, &body, ty.as_ref())?;
51                let body = surface_elaborate_against(&self.ctx, &body, ty.as_ref())?;
52                let inferred_ty = infer_type(&self.ctx, &body)?;
53
54                // Use provided type or inferred type
55                let ty = ty.unwrap_or(inferred_ty);
56
57                // Add definition to context
58                self.ctx.add_definition(name.clone(), ty, body);
59
60                // Record how many leading arguments are implicit, so later uses of this
61                // global (`name 0`) get them inserted and inferred.
62                if implicit_count > 0 {
63                    self.ctx.set_implicit_args(&name, implicit_count);
64                }
65
66                // Register as hint if marked
67                if is_hint {
68                    self.ctx.add_hint(&name);
69                }
70
71                Ok(String::new()) // Silent success
72            }
73
74            Command::Check(term) => {
75                // Infer `match` motives, insert implicit arguments, then infer the type.
76                let term = fill_match_motives(&self.ctx, &term, None)?;
77                let term = surface_elaborate(&self.ctx, &term)?;
78                let ty = infer_type(&self.ctx, &term)?;
79                Ok(format!("{} : {}", term, ty))
80            }
81
82            Command::Eval(term) => {
83                // Infer `match` motives, insert implicits, type check, then normalize.
84                let term = fill_match_motives(&self.ctx, &term, None)?;
85                let term = surface_elaborate(&self.ctx, &term)?;
86                let _ = infer_type(&self.ctx, &term)?;
87                let result = normalize(&self.ctx, &term);
88                Ok(format!("{}", result))
89            }
90
91            Command::Inductive {
92                name,
93                params,
94                sort,
95                constructors,
96            } => {
97                // Build polymorphic sort: Π(p1:T1). Π(p2:T2). ... Type
98                let poly_sort = build_polymorphic_sort(&params, sort);
99
100                // Register the inductive type with its polymorphic sort
101                self.ctx.add_inductive(&name, poly_sort);
102
103                // Register constructors with prepended parameters
104                for (ctor_name, ctor_ty) in constructors {
105                    // Prepend params to constructor type:
106                    // If ctor_ty = A -> List A -> List A
107                    // And params = [(A, Type)]
108                    // Result = Π(A:Type). A -> List A -> List A
109                    let poly_ctor_ty = build_polymorphic_constructor(&params, ctor_ty);
110                    // Strict positivity is enforced on the trusted, user-facing
111                    // registration path: a negative-recursive constructor (e.g.
112                    // `Cons : (Bad -> False) -> Bad`) would let one inhabit `False`.
113                    self.ctx
114                        .add_constructor_checked(&ctor_name, &name, poly_ctor_ty)?;
115                }
116
117                // Auto-derive the recursor (the dependent eliminator) — declaring an
118                // inductive gives you induction/recursion for free, as `{Name}_rec`. If
119                // derivation is not yet supported for this shape, the inductive is still
120                // usable; we simply skip the eliminator.
121                if let Ok((rec_ty, rec_term)) = derive_recursor(&self.ctx, &name) {
122                    self.ctx.add_definition(format!("{}_rec", name), rec_ty, rec_term);
123                }
124
125                Ok(String::new()) // Silent success
126            }
127        }
128    }
129
130    /// Execute a batch of Vernacular commands, recovering mutual-inductive grouping.
131    ///
132    /// [`execute`](Self::execute) registers each `Inductive` independently, so a forward
133    /// reference between two separately-declared inductives — `Tree` whose `Node`
134    /// constructor mentions `Forest`, declared next — fails: `Forest` is not yet in scope
135    /// when `Tree`'s constructor is universe-checked. True mutual inductives must be
136    /// registered together, through the trusted [`Context::add_mutual_inductives`], which
137    /// runs whole-block positivity and a header-first universe check.
138    ///
139    /// This entry point recovers that grouping from a flat statement sequence: a maximal
140    /// run of consecutive `Inductive` commands is split into strongly-connected components
141    /// of the "a constructor mentions sibling `b`" graph. A genuine cycle (`Tree`↔`Forest`)
142    /// is registered as one mutual block; an acyclic reference (`Leafy` → `Bare`) becomes
143    /// two singletons registered dependency-first (`Bare`, then `Leafy`) through the
144    /// ordinary single-inductive path. Every non-inductive command — and every inductive
145    /// with no forward reference — runs exactly as `execute` would, in source order, so a
146    /// batch without forward references is byte-identical to running the statements singly.
147    pub fn execute_batch(&mut self, inputs: &[String]) -> Vec<Result<String, InterfaceError>> {
148        let parsed: Vec<Option<Command>> =
149            inputs.iter().map(|s| parse_command(s).ok()).collect();
150        let mut out: Vec<Option<Result<String, InterfaceError>>> =
151            (0..inputs.len()).map(|_| None).collect();
152        let mut i = 0;
153        while i < inputs.len() {
154            if !matches!(parsed[i], Some(Command::Inductive { .. })) {
155                out[i] = Some(self.execute(&inputs[i]));
156                i += 1;
157                continue;
158            }
159            // A maximal run of consecutive inductive commands, registered as a group.
160            let start = i;
161            while i < inputs.len() && matches!(parsed[i], Some(Command::Inductive { .. })) {
162                i += 1;
163            }
164            let run: Vec<(&str, &Command)> = (start..i)
165                .map(|k| (inputs[k].as_str(), parsed[k].as_ref().unwrap()))
166                .collect();
167            for (k, r) in (start..i).zip(self.register_inductive_run(&run)) {
168                out[k] = Some(r);
169            }
170        }
171        out.into_iter().map(|o| o.expect("every position is filled")).collect()
172    }
173
174    /// Register one maximal run of `Inductive` commands, grouping mutual cycles into
175    /// blocks and registering acyclic references dependency-first. See [`execute_batch`].
176    fn register_inductive_run(
177        &mut self,
178        run: &[(&str, &Command)],
179    ) -> Vec<Result<String, InterfaceError>> {
180        use std::collections::{BTreeSet, HashMap};
181        let n = run.len();
182        let names: Vec<&str> = run
183            .iter()
184            .map(|(_, c)| match c {
185                Command::Inductive { name, .. } => name.as_str(),
186                _ => unreachable!("run holds only Inductive commands"),
187            })
188            .collect();
189        let name_idx: HashMap<&str, usize> =
190            names.iter().enumerate().map(|(k, &nm)| (nm, k)).collect();
191
192        // Dependency edges: `a → b` iff a constructor of member `a` mentions sibling `b`
193        // (b ≠ a; a self-reference is handled inside single/mutual registration).
194        let mut deps: Vec<BTreeSet<usize>> = (0..n).map(|_| BTreeSet::new()).collect();
195        for (a, (_, cmd)) in run.iter().enumerate() {
196            if let Command::Inductive { constructors, .. } = cmd {
197                let mut mentioned = BTreeSet::new();
198                for (_, cty) in constructors.iter() {
199                    collect_global_names(cty, &mut mentioned);
200                }
201                for name in &mentioned {
202                    if let Some(&b) = name_idx.get(name.as_str()) {
203                        if b != a {
204                            deps[a].insert(b);
205                        }
206                    }
207                }
208            }
209        }
210
211        let comps = tarjan_scc(&deps);
212        let mut comp_of = vec![0usize; n];
213        for (cid, comp) in comps.iter().enumerate() {
214            for &node in comp {
215                comp_of[node] = cid;
216            }
217        }
218        // Condensation edges: which components each component references.
219        let mut comp_deps: Vec<BTreeSet<usize>> =
220            (0..comps.len()).map(|_| BTreeSet::new()).collect();
221        for (a, edges) in deps.iter().enumerate() {
222            for &b in edges {
223                if comp_of[a] != comp_of[b] {
224                    comp_deps[comp_of[a]].insert(comp_of[b]);
225                }
226            }
227        }
228        // Emit components dependency-first (a referenced component before its referrer),
229        // tie-breaking by earliest source position so independent inductives keep order.
230        let mut emitted = vec![false; comps.len()];
231        let mut order: Vec<usize> = Vec::with_capacity(comps.len());
232        while order.len() < comps.len() {
233            let mut pick: Option<usize> = None;
234            for c in 0..comps.len() {
235                if emitted[c] || !comp_deps[c].iter().all(|d| emitted[*d]) {
236                    continue;
237                }
238                let earliest = *comps[c].iter().min().unwrap();
239                match pick {
240                    Some(p) if earliest >= *comps[p].iter().min().unwrap() => {}
241                    _ => pick = Some(c),
242                }
243            }
244            let c = pick.expect("an SCC condensation is a DAG — a ready component exists");
245            emitted[c] = true;
246            order.push(c);
247        }
248
249        let mut results: Vec<Result<String, InterfaceError>> =
250            (0..n).map(|_| Ok(String::new())).collect();
251        for &cid in &order {
252            let comp = &comps[cid];
253            if comp.len() == 1 {
254                // Singleton (including a self-recursive inductive): the ordinary path, which
255                // also derives the recursor. Its dependencies are already registered.
256                let node = comp[0];
257                results[node] = self.execute(run[node].0);
258            } else {
259                // A genuine mutual cycle → one block through the trusted mutual machinery.
260                let block: Vec<crate::context::MutualInductive> = comp
261                    .iter()
262                    .map(|&node| match run[node].1 {
263                        Command::Inductive { name, params, sort, constructors } => {
264                            crate::context::MutualInductive {
265                                name: name.clone(),
266                                sort: build_polymorphic_sort(params, sort.clone()),
267                                num_params: params.len(),
268                                constructors: constructors
269                                    .iter()
270                                    .map(|(cn, cty)| {
271                                        (cn.clone(), build_polymorphic_constructor(params, cty.clone()))
272                                    })
273                                    .collect(),
274                            }
275                        }
276                        _ => unreachable!(),
277                    })
278                    .collect();
279                match self.ctx.add_mutual_inductives(&block) {
280                    Ok(()) => {
281                        // Best-effort recursor per member, mirroring the single path.
282                        for &node in comp {
283                            if let Command::Inductive { name, .. } = run[node].1 {
284                                if let Ok((rec_ty, rec_term)) = derive_recursor(&self.ctx, name) {
285                                    self.ctx.add_definition(
286                                        format!("{}_rec", name),
287                                        rec_ty,
288                                        rec_term,
289                                    );
290                                }
291                            }
292                        }
293                    }
294                    Err(e) => {
295                        let msg =
296                            format!("mutual inductive block failed to register: {e}");
297                        for &node in comp {
298                            results[node] = Err(InterfaceError::Kernel(
299                                crate::KernelError::CertificationError(msg.clone()),
300                            ));
301                        }
302                    }
303                }
304            }
305        }
306        results
307    }
308
309    /// Get a reference to the underlying context.
310    pub fn context(&self) -> &Context {
311        &self.ctx
312    }
313
314    /// Get a mutable reference to the underlying context.
315    pub fn context_mut(&mut self) -> &mut Context {
316        &mut self.ctx
317    }
318}
319
320impl Default for Repl {
321    fn default() -> Self {
322        Self::new()
323    }
324}
325
326/// Build a polymorphic sort from type parameters.
327///
328/// For params = [(A, Type), (B, Type)] and base_sort = Type,
329/// produces: Π(A:Type). Π(B:Type). Type
330fn build_polymorphic_sort(params: &[(String, Term)], base_sort: Term) -> Term {
331    // Fold right to build nested Pi types
332    params.iter().rev().fold(base_sort, |body, (name, ty)| {
333        Term::Pi {
334            param: name.clone(),
335            param_type: Box::new(ty.clone()),
336            body_type: Box::new(body),
337        }
338    })
339}
340
341/// Build a polymorphic constructor type by prepending parameters.
342///
343/// For params = [(A, Type)] and ctor_ty = A -> List A -> List A,
344/// produces: Π(A:Type). A -> List A -> List A
345///
346/// The kernel uses named variables (Var(String)), so we convert
347/// Global("A") to Var("A") for parameters.
348fn build_polymorphic_constructor(params: &[(String, Term)], ctor_ty: Term) -> Term {
349    if params.is_empty() {
350        return ctor_ty;
351    }
352
353    // Convert Global(param_name) to Var(param_name) for all parameters
354    let param_names: Vec<&str> = params.iter().map(|(n, _)| n.as_str()).collect();
355    let body = substitute_globals_with_vars(&ctor_ty, &param_names);
356
357    // Wrap with Pi bindings (fold right to build nested Pi)
358    params.iter().rev().fold(body, |body, (name, ty)| {
359        Term::Pi {
360            param: name.clone(),
361            param_type: Box::new(ty.clone()),
362            body_type: Box::new(body),
363        }
364    })
365}
366
367/// Convert Global(name) to Var(name) for names in the param list.
368/// This makes parameter references in constructor types into bound variables.
369fn substitute_globals_with_vars(term: &Term, param_names: &[&str]) -> Term {
370    match term {
371        Term::Global(n) if param_names.contains(&n.as_str()) => Term::Var(n.clone()),
372        Term::Global(n) => Term::Global(n.clone()),
373        Term::Const { .. } => term.clone(),
374        Term::Var(n) => Term::Var(n.clone()),
375        Term::Sort(u) => Term::Sort(u.clone()),
376        Term::Lit(l) => Term::Lit(l.clone()),
377        Term::App(f, a) => Term::App(
378            Box::new(substitute_globals_with_vars(f, param_names)),
379            Box::new(substitute_globals_with_vars(a, param_names)),
380        ),
381        Term::Lambda { param, param_type, body } => Term::Lambda {
382            param: param.clone(),
383            param_type: Box::new(substitute_globals_with_vars(param_type, param_names)),
384            body: Box::new(substitute_globals_with_vars(body, param_names)),
385        },
386        Term::Pi { param, param_type, body_type } => Term::Pi {
387            param: param.clone(),
388            param_type: Box::new(substitute_globals_with_vars(param_type, param_names)),
389            body_type: Box::new(substitute_globals_with_vars(body_type, param_names)),
390        },
391        Term::Fix { name, body } => Term::Fix {
392            name: name.clone(),
393            body: Box::new(substitute_globals_with_vars(body, param_names)),
394        },
395        Term::MutualFix { defs, index } => Term::MutualFix {
396            defs: defs
397                .iter()
398                .map(|(n, b)| (n.clone(), substitute_globals_with_vars(b, param_names)))
399                .collect(),
400            index: *index,
401        },
402        Term::Match { discriminant, motive, cases } => Term::Match {
403            discriminant: Box::new(substitute_globals_with_vars(discriminant, param_names)),
404            motive: Box::new(substitute_globals_with_vars(motive, param_names)),
405            cases: cases
406                .iter()
407                .map(|c| substitute_globals_with_vars(c, param_names))
408                .collect(),
409        },
410        Term::Let { name, ty, value, body } => Term::Let {
411            name: name.clone(),
412            ty: Box::new(substitute_globals_with_vars(ty, param_names)),
413            value: Box::new(substitute_globals_with_vars(value, param_names)),
414            body: Box::new(substitute_globals_with_vars(body, param_names)),
415        },
416        Term::Hole => Term::Hole, // Holes are unchanged
417    }
418}
419
420/// Collect the names of every `Global`/`Const` referenced anywhere in a term — used to
421/// find which sibling inductives a constructor mentions.
422fn collect_global_names(term: &Term, out: &mut std::collections::BTreeSet<String>) {
423    match term {
424        Term::Global(n) => {
425            out.insert(n.clone());
426        }
427        Term::Const { name, .. } => {
428            out.insert(name.clone());
429        }
430        Term::App(f, a) => {
431            collect_global_names(f, out);
432            collect_global_names(a, out);
433        }
434        Term::Pi { param_type, body_type, .. } => {
435            collect_global_names(param_type, out);
436            collect_global_names(body_type, out);
437        }
438        Term::Lambda { param_type, body, .. } => {
439            collect_global_names(param_type, out);
440            collect_global_names(body, out);
441        }
442        Term::Match { discriminant, motive, cases } => {
443            collect_global_names(discriminant, out);
444            collect_global_names(motive, out);
445            for c in cases {
446                collect_global_names(c, out);
447            }
448        }
449        Term::Fix { body, .. } => collect_global_names(body, out),
450        Term::MutualFix { defs, .. } => {
451            for (_, b) in defs {
452                collect_global_names(b, out);
453            }
454        }
455        Term::Let { ty, value, body, .. } => {
456            collect_global_names(ty, out);
457            collect_global_names(value, out);
458            collect_global_names(body, out);
459        }
460        Term::Var(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole => {}
461    }
462}
463
464/// Tarjan's strongly-connected components over a small adjacency list. Each returned
465/// vector is one SCC (node indices, ascending); a self-referential singleton is its own
466/// SCC of size one. Runs are tiny (a handful of adjacent inductives), so recursion is safe.
467fn tarjan_scc(adj: &[std::collections::BTreeSet<usize>]) -> Vec<Vec<usize>> {
468    struct State<'a> {
469        adj: &'a [std::collections::BTreeSet<usize>],
470        index: Vec<usize>,
471        low: Vec<usize>,
472        on_stack: Vec<bool>,
473        stack: Vec<usize>,
474        counter: usize,
475        sccs: Vec<Vec<usize>>,
476    }
477    impl State<'_> {
478        fn connect(&mut self, v: usize) {
479            self.index[v] = self.counter;
480            self.low[v] = self.counter;
481            self.counter += 1;
482            self.stack.push(v);
483            self.on_stack[v] = true;
484            let neighbors: Vec<usize> = self.adj[v].iter().copied().collect();
485            for w in neighbors {
486                if self.index[w] == usize::MAX {
487                    self.connect(w);
488                    self.low[v] = self.low[v].min(self.low[w]);
489                } else if self.on_stack[w] {
490                    self.low[v] = self.low[v].min(self.index[w]);
491                }
492            }
493            if self.low[v] == self.index[v] {
494                let mut comp = Vec::new();
495                loop {
496                    let w = self.stack.pop().unwrap();
497                    self.on_stack[w] = false;
498                    comp.push(w);
499                    if w == v {
500                        break;
501                    }
502                }
503                comp.sort_unstable();
504                self.sccs.push(comp);
505            }
506        }
507    }
508    let n = adj.len();
509    let mut st = State {
510        adj,
511        index: vec![usize::MAX; n],
512        low: vec![0; n],
513        on_stack: vec![false; n],
514        stack: Vec::new(),
515        counter: 0,
516        sccs: Vec::new(),
517    };
518    for v in 0..n {
519        if st.index[v] == usize::MAX {
520            st.connect(v);
521        }
522    }
523    st.sccs
524}
525
526#[cfg(test)]
527mod mutual_batch_tests {
528    use super::*;
529
530    fn ctor_names(repl: &Repl, ind: &str) -> Vec<String> {
531        repl.context()
532            .get_constructors(ind)
533            .iter()
534            .map(|(n, _)| n.to_string())
535            .collect()
536    }
537
538    #[test]
539    fn mutual_forward_reference_registers_both_constructor_sets() {
540        // `Tree.Node : Forest -> Tree` forward-references `Forest`, declared next; the two
541        // form a cycle and must register together as a mutual block.
542        let mut repl = Repl::new();
543        let stmts = vec![
544            "Inductive Tree := Leaf : Tree | Node : Forest -> Tree.".to_string(),
545            "Inductive Forest := Nil2 : Forest | Grow : Tree -> Forest -> Forest.".to_string(),
546        ];
547        let results = repl.execute_batch(&stmts);
548        assert!(
549            results.iter().all(|r| r.is_ok()),
550            "batch must register cleanly: {results:?}"
551        );
552        assert!(
553            ctor_names(&repl, "Tree").contains(&"Node".to_string()),
554            "Tree.Node (forward ref to Forest) must register: {:?}",
555            ctor_names(&repl, "Tree")
556        );
557        assert!(
558            ctor_names(&repl, "Forest").contains(&"Grow".to_string()),
559            "Forest.Grow must register: {:?}",
560            ctor_names(&repl, "Forest")
561        );
562        assert!(
563            repl.context().mutual_block_of("Tree").is_some(),
564            "a genuine cycle must be recorded as a mutual block"
565        );
566    }
567
568    #[test]
569    fn acyclic_forward_reference_registers_without_a_mutual_block() {
570        // `Leafy.MkLeafy : Bare -> Leafy` references `Bare`, declared next, but `Bare` does
571        // NOT reference back — no cycle, so two singletons registered `Bare`-first.
572        let mut repl = Repl::new();
573        let stmts = vec![
574            "Inductive Leafy := MkLeafy : Bare -> Leafy.".to_string(),
575            "Inductive Bare := MkBare : Bare.".to_string(),
576        ];
577        let results = repl.execute_batch(&stmts);
578        assert!(
579            results.iter().all(|r| r.is_ok()),
580            "batch must register cleanly: {results:?}"
581        );
582        assert!(
583            ctor_names(&repl, "Leafy").contains(&"MkLeafy".to_string()),
584            "Leafy.MkLeafy (ref to Bare, declared later) must register"
585        );
586        assert!(ctor_names(&repl, "Bare").contains(&"MkBare".to_string()));
587        assert!(
588            repl.context().mutual_block_of("Leafy").is_none(),
589            "an acyclic reference must NOT be recorded as mutual"
590        );
591    }
592
593    #[test]
594    fn plain_inductive_batch_matches_single_execution() {
595        // No forward references: the batch path is byte-identical to running the statement
596        // on its own — same constructors registered, in the same order.
597        let stmts =
598            vec!["Inductive Color := Red : Color | Green : Color | Blue : Color.".to_string()];
599        let mut batched = Repl::new();
600        assert!(batched.execute_batch(&stmts).iter().all(|r| r.is_ok()));
601        let mut single = Repl::new();
602        assert!(single.execute(&stmts[0]).is_ok());
603        assert_eq!(ctor_names(&batched, "Color"), ctor_names(&single, "Color"));
604    }
605}