1use 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
14pub struct Repl {
18 ctx: Context,
19}
20
21impl Repl {
22 pub fn new() -> Self {
24 let mut ctx = Context::new();
25 StandardLibrary::register(&mut ctx);
26 Self { ctx }
27 }
28
29 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 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 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 let ty = ty.unwrap_or(inferred_ty);
56
57 self.ctx.add_definition(name.clone(), ty, body);
59
60 if implicit_count > 0 {
63 self.ctx.set_implicit_args(&name, implicit_count);
64 }
65
66 if is_hint {
68 self.ctx.add_hint(&name);
69 }
70
71 Ok(String::new()) }
73
74 Command::Check(term) => {
75 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 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 let poly_sort = build_polymorphic_sort(¶ms, sort);
99
100 self.ctx.add_inductive(&name, poly_sort);
102
103 for (ctor_name, ctor_ty) in constructors {
105 let poly_ctor_ty = build_polymorphic_constructor(¶ms, ctor_ty);
110 self.ctx
114 .add_constructor_checked(&ctor_name, &name, poly_ctor_ty)?;
115 }
116
117 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()) }
127 }
128 }
129
130 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 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 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 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 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 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 let node = comp[0];
257 results[node] = self.execute(run[node].0);
258 } else {
259 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 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 pub fn context(&self) -> &Context {
311 &self.ctx
312 }
313
314 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
326fn build_polymorphic_sort(params: &[(String, Term)], base_sort: Term) -> Term {
331 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
341fn build_polymorphic_constructor(params: &[(String, Term)], ctor_ty: Term) -> Term {
349 if params.is_empty() {
350 return ctor_ty;
351 }
352
353 let param_names: Vec<&str> = params.iter().map(|(n, _)| n.as_str()).collect();
355 let body = substitute_globals_with_vars(&ctor_ty, ¶m_names);
356
357 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
367fn 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, }
418}
419
420fn 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
464fn 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 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 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 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}