1use serde::{Deserialize, Serialize};
31use std::collections::HashSet;
32
33use logicaffeine_base::{Arena, Interner};
34use logicaffeine_language::{
35 ast::{self, LogicExpr, Term},
36 analysis::DiscoveryPass,
37 arena_ctx::AstContext,
38 compile::{compile_forest, compile_forest_with_options},
39 drs,
40 error::socratic_explanation,
41 lexer::Lexer,
42 mwe,
43 parser::Parser,
44 pragmatics,
45 registry::SymbolRegistry,
46 semantics,
47 token::TokenType,
48 CompileOptions, OutputFormat, ParseError,
49};
50use logicaffeine_proof::{DerivationTree, ProofExpr, ProofTerm};
51
52pub use crate::interpreter::InterpreterResult;
54
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
61pub enum TokenCategory {
62 Quantifier,
64 Noun,
66 Verb,
68 Adjective,
70 Connective,
72 Determiner,
74 Preposition,
76 Pronoun,
78 Modal,
80 Punctuation,
82 Proper,
84 Other,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct TokenInfo {
91 pub start: usize,
93 pub end: usize,
95 pub text: String,
97 pub category: TokenCategory,
99}
100
101fn categorize_token(kind: &TokenType, _interner: &Interner) -> TokenCategory {
102 match kind {
103 TokenType::All | TokenType::Some | TokenType::No | TokenType::Any
104 | TokenType::Most | TokenType::Few | TokenType::Many
105 | TokenType::Cardinal(_) | TokenType::AtLeast(_) | TokenType::AtMost(_) => TokenCategory::Quantifier,
106 TokenType::Noun(_) => TokenCategory::Noun,
107 TokenType::Verb { .. } => TokenCategory::Verb,
108 TokenType::Adjective(_) | TokenType::NonIntersectiveAdjective(_) => TokenCategory::Adjective,
109 TokenType::And | TokenType::Or | TokenType::Not | TokenType::If | TokenType::Then
110 | TokenType::Iff | TokenType::Because => TokenCategory::Connective,
111 TokenType::Article(_) => TokenCategory::Determiner,
112 TokenType::Preposition(_) => TokenCategory::Preposition,
113 TokenType::Pronoun { .. } => TokenCategory::Pronoun,
114 TokenType::Must | TokenType::Can | TokenType::Should | TokenType::Shall
115 | TokenType::Would | TokenType::Could | TokenType::May | TokenType::Cannot
116 | TokenType::Might => TokenCategory::Modal,
117 TokenType::Period | TokenType::Comma => TokenCategory::Punctuation,
118 TokenType::ProperName(_) => TokenCategory::Proper,
119 _ => TokenCategory::Other,
120 }
121}
122
123pub fn tokenize_for_ui(input: &str) -> Vec<TokenInfo> {
128 let mut interner = Interner::new();
129 let mut lexer = Lexer::new(input, &mut interner);
130 let tokens = lexer.tokenize();
131
132 tokens.iter().map(|t| TokenInfo {
133 start: t.span.start,
134 end: t.span.end,
135 text: input[t.span.start..t.span.end].to_string(),
136 category: categorize_token(&t.kind, &interner),
137 }).collect()
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
146pub struct AstNode {
147 pub label: String,
149 pub node_type: String,
151 pub children: Vec<AstNode>,
153}
154
155impl AstNode {
156 pub fn leaf(label: &str, node_type: &str) -> Self {
157 AstNode {
158 label: label.to_string(),
159 node_type: node_type.to_string(),
160 children: Vec::new(),
161 }
162 }
163
164 pub fn with_children(label: &str, node_type: &str, children: Vec<AstNode>) -> Self {
165 AstNode {
166 label: label.to_string(),
167 node_type: node_type.to_string(),
168 children,
169 }
170 }
171}
172
173pub fn expr_to_ast_node(expr: &LogicExpr, interner: &Interner) -> AstNode {
179 match expr {
180 LogicExpr::Predicate { name, args, .. } => {
181 let name_str = interner.resolve(*name);
182 let arg_nodes: Vec<AstNode> = args.iter()
183 .map(|t| term_to_ast_node(t, interner))
184 .collect();
185 AstNode::with_children(
186 &format!("{}({})", name_str, args.len()),
187 "predicate",
188 arg_nodes,
189 )
190 }
191 LogicExpr::Quantifier { kind, variable, body, .. } => {
192 let var_str = interner.resolve(*variable);
193 let symbol = match kind {
194 ast::QuantifierKind::Universal => "∀",
195 ast::QuantifierKind::Existential => "∃",
196 ast::QuantifierKind::Most => "MOST",
197 ast::QuantifierKind::Few => "FEW",
198 ast::QuantifierKind::Many => "MANY",
199 ast::QuantifierKind::Cardinal(n) => return AstNode::with_children(
200 &format!("∃={}{}", n, var_str),
201 "quantifier",
202 vec![expr_to_ast_node(body, interner)],
203 ),
204 ast::QuantifierKind::AtLeast(n) => return AstNode::with_children(
205 &format!("∃≥{}{}", n, var_str),
206 "quantifier",
207 vec![expr_to_ast_node(body, interner)],
208 ),
209 ast::QuantifierKind::AtMost(n) => return AstNode::with_children(
210 &format!("∃≤{}{}", n, var_str),
211 "quantifier",
212 vec![expr_to_ast_node(body, interner)],
213 ),
214 ast::QuantifierKind::Generic => "GEN",
215 };
216 AstNode::with_children(
217 &format!("{}{}", symbol, var_str),
218 "quantifier",
219 vec![expr_to_ast_node(body, interner)],
220 )
221 }
222 LogicExpr::BinaryOp { left, op, right } => {
223 let op_str = match op {
224 TokenType::And => "∧",
225 TokenType::Or => "∨",
226 TokenType::If | TokenType::Then => "→",
227 TokenType::Iff => "↔",
228 _ => "?",
229 };
230 AstNode::with_children(
231 op_str,
232 "binary_op",
233 vec![
234 expr_to_ast_node(left, interner),
235 expr_to_ast_node(right, interner),
236 ],
237 )
238 }
239 LogicExpr::UnaryOp { op, operand } => {
240 let op_str = match op {
241 TokenType::Not => "¬",
242 _ => "?",
243 };
244 AstNode::with_children(
245 op_str,
246 "unary_op",
247 vec![expr_to_ast_node(operand, interner)],
248 )
249 }
250 LogicExpr::Identity { left, right } => {
251 AstNode::with_children(
252 "=",
253 "identity",
254 vec![
255 term_to_ast_node(left, interner),
256 term_to_ast_node(right, interner),
257 ],
258 )
259 }
260 LogicExpr::Modal { vector, operand } => {
261 AstNode::with_children(
262 &format!("□{:?}", vector.domain),
263 "modal",
264 vec![expr_to_ast_node(operand, interner)],
265 )
266 }
267 LogicExpr::Lambda { variable, body } => {
268 let var_str = interner.resolve(*variable);
269 AstNode::with_children(
270 &format!("λ{}", var_str),
271 "lambda",
272 vec![expr_to_ast_node(body, interner)],
273 )
274 }
275 LogicExpr::SpeechAct { performer, act_type, content } => {
276 AstNode::with_children(
277 &format!(
278 "{}!{}",
279 interner.resolve(*act_type),
280 interner.resolve(*performer)
281 ),
282 "speech_act",
283 vec![expr_to_ast_node(content, interner)],
284 )
285 }
286 _ => AstNode::leaf(&format!("{:?}", expr), "other"),
287 }
288}
289
290fn term_to_ast_node(term: &Term, interner: &Interner) -> AstNode {
291 match term {
292 Term::Constant(sym) => AstNode::leaf(interner.resolve(*sym), "constant"),
293 Term::Variable(sym) => AstNode::leaf(interner.resolve(*sym), "variable"),
294 Term::Function(name, args) => {
295 let name_str = interner.resolve(*name);
296 let arg_nodes: Vec<AstNode> = args.iter()
297 .map(|t| term_to_ast_node(t, interner))
298 .collect();
299 AstNode::with_children(&format!("{}()", name_str), "function", arg_nodes)
300 }
301 Term::Group(terms) => {
302 let term_nodes: Vec<AstNode> = terms.iter()
303 .map(|t| term_to_ast_node(t, interner))
304 .collect();
305 AstNode::with_children("⊕", "group", term_nodes)
306 }
307 _ => AstNode::leaf(&format!("{:?}", term), "term"),
308 }
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct CompileResult {
318 pub logic: Option<String>,
320 pub simple_logic: Option<String>,
322 pub kripke_logic: Option<String>,
324 pub ast: Option<AstNode>,
326 pub readings: Vec<String>,
328 pub simple_readings: Vec<String>,
330 pub kripke_readings: Vec<String>,
332 pub tokens: Vec<TokenInfo>,
334 pub error: Option<String>,
336}
337
338pub fn compile_for_ui(input: &str) -> CompileResult {
340 let tokens = tokenize_for_ui(input);
341 let readings = compile_forest(input);
342
343 let simple_readings: Vec<String> = {
345 let raw = compile_forest_with_options(input, CompileOptions { format: OutputFormat::SimpleFOL, pragmatic: false });
346 let mut seen = HashSet::new();
347 raw.into_iter().filter(|r| seen.insert(r.clone())).collect()
348 };
349
350 let kripke_readings = compile_forest_with_options(input, CompileOptions { format: OutputFormat::Kripke, pragmatic: false });
352
353 let mut interner = Interner::new();
354 let mut lexer = Lexer::new(input, &mut interner);
355 let lex_tokens = lexer.tokenize();
356
357 let mwe_trie = mwe::build_mwe_trie();
358 let lex_tokens = mwe::apply_mwe_pipeline(lex_tokens, &mwe_trie, &mut interner);
359
360 let type_registry = {
362 let mut discovery = DiscoveryPass::new(&lex_tokens, &mut interner);
363 discovery.run()
364 };
365
366 let expr_arena = Arena::new();
367 let term_arena = Arena::new();
368 let np_arena = Arena::new();
369 let sym_arena = Arena::new();
370 let role_arena = Arena::new();
371 let pp_arena = Arena::new();
372
373 let ctx = AstContext::new(
374 &expr_arena,
375 &term_arena,
376 &np_arena,
377 &sym_arena,
378 &role_arena,
379 &pp_arena,
380 );
381
382 let mut world_state = drs::WorldState::new();
384 let mut parser = Parser::new(lex_tokens, &mut world_state, &mut interner, ctx, type_registry);
385
386 match parser.parse() {
387 Ok(ast) => {
388 let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, &mut interner);
389 let ast = pragmatics::apply_pragmatics(ast, ctx.exprs, &interner);
390 let ast_node = expr_to_ast_node(ast, &interner);
391 let mut registry = SymbolRegistry::new();
392 let logic = ast.transpile_discourse(&mut registry, &interner, OutputFormat::Unicode);
393 let simple_logic = ast.transpile_discourse(&mut registry, &interner, OutputFormat::SimpleFOL);
394
395 let kripke_ast = semantics::apply_kripke_lowering(ast, ctx.exprs, ctx.terms, &mut interner);
396 let kripke_logic = kripke_ast.transpile_discourse(&mut registry, &interner, OutputFormat::Kripke);
397
398 CompileResult {
399 logic: Some(logic),
400 simple_logic: Some(simple_logic),
401 kripke_logic: Some(kripke_logic),
402 ast: Some(ast_node),
403 readings,
404 simple_readings,
405 kripke_readings,
406 tokens,
407 error: None,
408 }
409 }
410 Err(e) => {
411 let advice = socratic_explanation(&e, &interner);
412 CompileResult {
413 logic: None,
414 simple_logic: None,
415 kripke_logic: None,
416 ast: None,
417 readings: Vec::new(),
418 simple_readings: Vec::new(),
419 kripke_readings: Vec::new(),
420 tokens,
421 error: Some(advice),
422 }
423 }
424 }
425}
426
427#[derive(Debug, Clone)]
437pub struct ProofCompileResult {
438 pub proof_expr: Option<ProofExpr>,
440 pub logic_string: Option<String>,
442 pub error: Option<String>,
444}
445
446pub fn compile_for_proof(input: &str) -> ProofCompileResult {
448 use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
449
450 let mut interner = Interner::new();
451 let mut lexer = Lexer::new(input, &mut interner);
452 let lex_tokens = lexer.tokenize();
453
454 let mwe_trie = mwe::build_mwe_trie();
455 let lex_tokens = mwe::apply_mwe_pipeline(lex_tokens, &mwe_trie, &mut interner);
456
457 let type_registry = {
458 let mut discovery = DiscoveryPass::new(&lex_tokens, &mut interner);
459 discovery.run()
460 };
461
462 let expr_arena = Arena::new();
463 let term_arena = Arena::new();
464 let np_arena = Arena::new();
465 let sym_arena = Arena::new();
466 let role_arena = Arena::new();
467 let pp_arena = Arena::new();
468
469 let ctx = AstContext::new(
470 &expr_arena,
471 &term_arena,
472 &np_arena,
473 &sym_arena,
474 &role_arena,
475 &pp_arena,
476 );
477
478 let mut world_state = drs::WorldState::new();
479 let mut parser = Parser::new(lex_tokens, &mut world_state, &mut interner, ctx, type_registry);
480
481 match parser.parse() {
482 Ok(ast) => {
483 let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, &mut interner);
484 let ast = pragmatics::apply_pragmatics(ast, ctx.exprs, &interner);
485
486 let mut registry = SymbolRegistry::new();
487 let logic_string = ast.transpile(&mut registry, &interner, OutputFormat::SimpleFOL);
488 let proof_expr = logic_expr_to_proof_expr(ast, &interner);
489
490 ProofCompileResult {
491 proof_expr: Some(proof_expr),
492 logic_string: Some(logic_string),
493 error: None,
494 }
495 }
496 Err(e) => {
497 let advice = socratic_explanation(&e, &interner);
498 ProofCompileResult {
499 proof_expr: None,
500 logic_string: None,
501 error: Some(advice),
502 }
503 }
504 }
505}
506
507#[derive(Debug, Clone)]
512pub struct TheoremCompileResult {
513 pub name: String,
515 pub premises: Vec<ProofExpr>,
517 pub goal: Option<ProofExpr>,
519 pub goal_string: Option<String>,
521 pub derivation: Option<DerivationTree>,
523 pub verified: bool,
526 pub verification_error: Option<String>,
528 pub answer: Option<Vec<String>>,
531 pub grid: Option<SolvedGrid>,
534 pub error: Option<String>,
536}
537
538#[derive(Debug, Clone, PartialEq)]
542pub struct SolvedGrid {
543 pub row_label: String,
545 pub rows: Vec<String>,
547 pub columns: Vec<GridColumn>,
549}
550
551#[derive(Debug, Clone, PartialEq)]
553pub struct GridColumn {
554 pub label: String,
556 pub values: Vec<String>,
558 pub cells: Vec<Option<String>>,
561}
562
563struct ParsedTheorem {
568 name: String,
569 premises: Vec<ProofExpr>,
570 goal: ProofExpr,
571 goal_string: String,
572 is_auto: bool,
573 script: Option<String>,
576 premise_names: Vec<Option<String>>,
579}
580
581fn parse_theorem(input: &str) -> Result<ParsedTheorem, String> {
582 use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
583
584 let mut interner = Interner::new();
585 let mut lexer = Lexer::new(input, &mut interner);
586 let tokens = lexer.tokenize();
587
588 let mwe_trie = mwe::build_mwe_trie();
589 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
590
591 let type_registry = {
592 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
593 discovery.run()
594 };
595
596 let expr_arena = Arena::new();
597 let term_arena = Arena::new();
598 let np_arena = Arena::new();
599 let sym_arena = Arena::new();
600 let role_arena = Arena::new();
601 let pp_arena = Arena::new();
602
603 let ctx = AstContext::new(
604 &expr_arena,
605 &term_arena,
606 &np_arena,
607 &sym_arena,
608 &role_arena,
609 &pp_arena,
610 );
611
612 let mut world_state = drs::WorldState::new();
613 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
614
615 let statements = parser
616 .parse_program()
617 .map_err(|e| format!("Parse error: {:?}", e))?;
618
619 let theorem = statements
620 .iter()
621 .find_map(|stmt| if let ast::Stmt::Theorem(t) = stmt { Some(t) } else { None })
622 .ok_or_else(|| "No theorem block found".to_string())?;
623
624 let premises: Vec<ProofExpr> = theorem
625 .premises
626 .iter()
627 .map(|p| logic_expr_to_proof_expr(p, &interner))
628 .collect();
629 let goal = logic_expr_to_proof_expr(theorem.goal, &interner);
630
631 let mut registry = SymbolRegistry::new();
632 let goal_string = theorem.goal.transpile(&mut registry, &interner, OutputFormat::SimpleFOL);
633
634 let script = match &theorem.strategy {
635 ast::theorem::ProofStrategy::Script(s) => Some(s.clone()),
636 _ => None,
637 };
638
639 Ok(ParsedTheorem {
640 name: theorem.name.clone(),
641 premises,
642 goal,
643 goal_string,
644 is_auto: matches!(theorem.strategy, ast::theorem::ProofStrategy::Auto),
645 script,
646 premise_names: theorem.premise_names.clone(),
647 })
648}
649
650#[derive(Debug, Clone)]
652pub struct TheoryTheoremResult {
653 pub name: String,
654 pub verified: bool,
655 pub error: Option<String>,
656}
657
658#[derive(Debug, Clone)]
662pub struct TheoryCompileResult {
663 pub theory_name: Option<String>,
664 pub axiom_count: usize,
665 pub theorems: Vec<TheoryTheoremResult>,
666 pub parse_error: Option<String>,
667}
668
669impl TheoryCompileResult {
670 pub fn all_verified(&self) -> bool {
672 self.parse_error.is_none()
673 && !self.theorems.is_empty()
674 && self.theorems.iter().all(|t| t.verified)
675 }
676}
677
678pub fn compile_theory_for_ui(input: &str) -> TheoryCompileResult {
683 use logicaffeine_proof::development::parse_development;
684 use logicaffeine_proof::formula::parse_formula;
685 use logicaffeine_proof::verify::{prove_library_with_axioms, LibraryTheorem};
686
687 let mut interner = Interner::new();
689 let mut lexer = Lexer::new(input, &mut interner);
690 let tokens = lexer.tokenize();
691 let mwe_trie = mwe::build_mwe_trie();
692 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
693 let type_registry = {
694 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
695 discovery.run()
696 };
697 let expr_arena = Arena::new();
698 let term_arena = Arena::new();
699 let np_arena = Arena::new();
700 let sym_arena = Arena::new();
701 let role_arena = Arena::new();
702 let pp_arena = Arena::new();
703 let ctx = AstContext::new(
704 &expr_arena,
705 &term_arena,
706 &np_arena,
707 &sym_arena,
708 &role_arena,
709 &pp_arena,
710 );
711 let mut world_state = drs::WorldState::new();
712 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
713 let statements = match parser.parse_program() {
714 Ok(s) => s,
715 Err(e) => {
716 return TheoryCompileResult {
717 theory_name: None,
718 axiom_count: 0,
719 theorems: Vec::new(),
720 parse_error: Some(format!("Parse error: {:?}", e)),
721 }
722 }
723 };
724
725 let mut axioms: Vec<ProofExpr> = Vec::new();
727 let mut theorems: Vec<LibraryTheorem> = Vec::new();
728 let mut theory_name: Option<String> = None;
729
730 for stmt in &statements {
731 match stmt {
732 ast::Stmt::Axiom(a) => match parse_formula(&a.formula) {
733 Ok(expr) => axioms.push(expr),
734 Err(e) => {
735 return TheoryCompileResult {
736 theory_name,
737 axiom_count: axioms.len(),
738 theorems: Vec::new(),
739 parse_error: Some(format!("Axiom '{}': {}", a.name, e)),
740 }
741 }
742 },
743 ast::Stmt::Theory(t) => {
744 if theory_name.is_none() {
745 theory_name = Some(t.name.clone());
746 }
747 if !t.body.trim().is_empty() {
748 match parse_development(&t.body) {
749 Ok(dev) => {
750 axioms.extend(dev.axiom_exprs());
751 theorems.extend(dev.theorems);
752 }
753 Err(e) => {
754 return TheoryCompileResult {
755 theory_name,
756 axiom_count: axioms.len(),
757 theorems: Vec::new(),
758 parse_error: Some(format!("Theory '{}': {}", t.name, e)),
759 }
760 }
761 }
762 }
763 }
764 _ => {}
765 }
766 }
767
768 let results = prove_library_with_axioms(&axioms, &theorems);
769 let theorem_results = theorems
770 .iter()
771 .zip(results)
772 .map(|(t, r)| TheoryTheoremResult {
773 name: t.name.clone(),
774 verified: r.verified,
775 error: r.verification_error,
776 })
777 .collect();
778
779 TheoryCompileResult {
780 theory_name,
781 axiom_count: axioms.len(),
782 theorems: theorem_results,
783 parse_error: None,
784 }
785}
786
787pub fn grounded_grid_problem(input: &str) -> Option<(Vec<ProofExpr>, ProofExpr)> {
792 let parsed = parse_theorem(input).ok()?;
793 if !parsed.is_auto || !looks_like_grid(&parsed.premises) {
794 return None;
795 }
796 let solver_input = prepare_premises_opts(&parsed.premises, true);
797 let g = erase_tense(&parsed.goal);
798 Some((solver_input, g))
799}
800
801pub fn compile_theorem_for_ui(input: &str) -> TheoremCompileResult {
803 let parsed = match parse_theorem(input) {
804 Ok(p) => p,
805 Err(e) => {
806 return TheoremCompileResult {
807 name: String::new(),
808 premises: Vec::new(),
809 goal: None,
810 goal_string: None,
811 derivation: None,
812 verified: false,
813 verification_error: None,
814 answer: None,
815 grid: None,
816 error: Some(e),
817 };
818 }
819 };
820 let ParsedTheorem { name, premises, goal, goal_string, is_auto, script, premise_names } =
821 parsed;
822
823 let grid = if is_auto && looks_like_grid(&premises) {
828 solve_grid_from_premises(&premises, input)
829 } else {
830 None
831 };
832
833 let (derivation, verified, verification_error, answer) =
834 if is_auto {
835 if let ProofExpr::Exists { variable, body } = &goal {
840 let witnesses = answer_wh(&premises, variable, body);
841 let verified = !witnesses.is_empty();
842 let verr = (!verified)
843 .then(|| "no individual in the domain satisfies the question".to_string());
844 (None, verified, verr, Some(witnesses))
845 } else {
846 let outcome = if looks_like_grid(&premises) {
852 let g = erase_tense(&goal);
853 let solver_input = prepare_premises_opts(&premises, true);
860 let solve = || {
861 let trace = std::env::var("LOGOS_TRACE").is_ok();
862 let t_solve = trace.then(std::time::Instant::now);
863 let tree = logicaffeine_proof::grid_solver::grid_prove(&solver_input, &g);
864 if let Some(t_solve) = t_solve {
865 let n = tree.as_ref().map(count_tree_nodes).unwrap_or(0);
866 eprintln!("[grid] solve+emit {:.2?} ({} tree nodes)", t_solve.elapsed(), n);
867 }
868 let t_cert = trace.then(std::time::Instant::now);
869 let solved = tree
870 .map(|tree| logicaffeine_proof::verify::check_derivation(&solver_input, &g, tree))
871 .filter(|vp| vp.verified);
872 if let Some(t_cert) = t_cert {
873 eprintln!("[grid] kernel-certify {:.2?} (verified={})", t_cert.elapsed(), solved.is_some());
874 }
875 match solved {
876 Some(vp) => vp,
877 None => {
878 if matches!(
885 logicaffeine_proof::rup::entails_certified(&solver_input, &g),
886 Some(logicaffeine_proof::rup::Verdict::NotEntailed)
887 ) {
888 return logicaffeine_proof::verify::VerifiedProof {
889 derivation: None,
890 proof_term: None,
891 kernel_ctx: Default::default(),
892 verified: false,
893 verification_error: Some(
894 "goal is not entailed (RUP-certified)".to_string(),
895 ),
896 };
897 }
898 let grounded = prepare_premises(&premises);
899 logicaffeine_proof::verify::prove_certify_check_bounded(&grounded, &g, 60)
900 }
901 }
902 };
903 #[cfg(not(target_arch = "wasm32"))]
907 {
908 std::thread::scope(|s| {
909 std::thread::Builder::new()
910 .stack_size(512 * 1024 * 1024)
911 .spawn_scoped(s, solve)
912 .expect("spawn grid-solve thread")
913 .join()
914 .expect("grid-solve thread panicked")
915 })
916 }
917 #[cfg(target_arch = "wasm32")]
918 {
919 solve()
920 }
921 } else {
922 logicaffeine_proof::verify::prove_certify_check(&premises, &goal)
923 };
924 (outcome.derivation, outcome.verified, outcome.verification_error, None)
925 }
926 } else if let Some(src) = &script {
927 use logicaffeine_proof::tactic::ProofState;
930 let run = || {
931 let mut st =
932 ProofState::start_with_names(premises.clone(), &premise_names, goal.clone());
933 match st.run_script(src) {
934 Ok(_) => match st.qed() {
935 Ok(vp) => (vp.derivation, vp.verified, vp.verification_error),
936 Err(e) => (None, false, Some(format!("{e:?}"))),
937 },
938 Err(e) => (None, false, Some(e.to_string())),
939 }
940 };
941 #[cfg(not(target_arch = "wasm32"))]
942 let (derivation, verified, verr) = std::thread::scope(|s| {
943 std::thread::Builder::new()
944 .stack_size(256 * 1024 * 1024)
945 .spawn_scoped(s, run)
946 .expect("spawn proof-script thread")
947 .join()
948 .expect("proof-script thread panicked")
949 });
950 #[cfg(target_arch = "wasm32")]
951 let (derivation, verified, verr) = run();
952 (derivation, verified, verr, None)
953 } else {
954 (None, false, None, None)
955 };
956
957 TheoremCompileResult {
958 name,
959 premises,
960 goal: Some(goal),
961 goal_string: Some(goal_string),
962 derivation,
963 verified,
964 verification_error,
965 answer,
966 grid,
967 error: None,
968 }
969}
970
971fn count_tree_nodes(t: &logicaffeine_proof::DerivationTree) -> usize {
972 1 + t.premises.iter().map(count_tree_nodes).sum::<usize>()
973}
974
975#[cfg(feature = "codegen")]
984pub fn generate_rust_code(source: &str) -> Result<String, ParseError> {
985 let (imperative_src, math_src) = partition_mixed(source);
986 let proven = math_src.as_deref().and_then(mixed_proven_module);
987 generate_rust_code_with_proven(&imperative_src, proven.as_deref())
988}
989
990#[cfg(feature = "codegen")]
992pub fn generate_rust_code_with_proven(source: &str, proven: Option<&str>) -> Result<String, ParseError> {
993 use logicaffeine_language::ast::stmt::{Stmt, Expr, TypeExpr};
994
995 let mut interner = Interner::new();
996 let mut lexer = Lexer::new(source, &mut interner);
997 let tokens = lexer.tokenize();
998
999 let mwe_trie = mwe::build_mwe_trie();
1000 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
1001
1002 let (type_registry, policy_registry) = {
1003 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1004 let result = discovery.run_full();
1005 (result.types, result.policies)
1006 };
1007 let codegen_registry = type_registry.clone();
1008 let codegen_policies = policy_registry.clone();
1009
1010 let mut world_state = drs::WorldState::new();
1011 let expr_arena = Arena::new();
1012 let term_arena = Arena::new();
1013 let np_arena = Arena::new();
1014 let sym_arena = Arena::new();
1015 let role_arena = Arena::new();
1016 let pp_arena = Arena::new();
1017 let stmt_arena: Arena<Stmt> = Arena::new();
1018 let imperative_expr_arena: Arena<Expr> = Arena::new();
1019 let type_expr_arena: Arena<TypeExpr> = Arena::new();
1020
1021 let ast_ctx = AstContext::with_types(
1022 &expr_arena,
1023 &term_arena,
1024 &np_arena,
1025 &sym_arena,
1026 &role_arena,
1027 &pp_arena,
1028 &stmt_arena,
1029 &imperative_expr_arena,
1030 &type_expr_arena,
1031 );
1032
1033 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
1034 let stmts = parser.parse_program()?;
1035
1036 let type_env = crate::analysis::types::TypeEnv::infer_program(&stmts, &interner, &codegen_registry);
1037 let rust_code = crate::codegen::codegen_program_with_proven(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env, &crate::optimization::OptimizationConfig::from_env(), "proven", proven);
1038 Ok(rust_code)
1039}
1040
1041fn send_escape_rejection(stmts: &[logicaffeine_language::ast::stmt::Stmt]) -> Option<InterpreterResult> {
1057 crate::concurrency::check_send_escape(stmts)
1058 .first()
1059 .map(|d| InterpreterResult { lines: Vec::new(), error: Some(d.message.clone()) })
1060}
1061
1062fn send_dimension_rejection(
1066 stmts: &[logicaffeine_language::ast::stmt::Stmt],
1067 interner: &Interner,
1068) -> Option<InterpreterResult> {
1069 crate::analysis::dimension_check::DimensionChecker::new(interner)
1070 .check_program(stmts)
1071 .err()
1072 .map(|e| InterpreterResult { lines: Vec::new(), error: Some(e.message) })
1073}
1074
1075pub async fn interpret_for_ui(input: &str) -> InterpreterResult {
1076 interpret_for_ui_with_args(input, &[]).await
1077}
1078
1079pub async fn interpret_for_ui_with_args(
1083 input: &str,
1084 program_args: &[String],
1085) -> InterpreterResult {
1086 let needs_async = with_parsed_program(input, |parsed, _| match parsed {
1090 Ok((stmts, _, _)) => crate::interpreter::needs_async(stmts),
1091 Err(_) => false,
1092 });
1093 if !needs_async {
1094 return interpret_for_ui_sync_with_args(input, program_args);
1095 }
1096
1097 use logicaffeine_language::ast::stmt::{Stmt, Expr, TypeExpr};
1098
1099 let mut interner = Interner::new();
1100 let mut lexer = Lexer::new(input, &mut interner);
1101 let tokens = lexer.tokenize();
1102
1103 let mwe_trie = mwe::build_mwe_trie();
1104 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
1105
1106 let (type_registry, policy_registry) = {
1107 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1108 let result = discovery.run_full();
1109 (result.types, result.policies)
1110 };
1111
1112 let expr_arena = Arena::new();
1113 let term_arena = Arena::new();
1114 let np_arena = Arena::new();
1115 let sym_arena = Arena::new();
1116 let role_arena = Arena::new();
1117 let pp_arena = Arena::new();
1118 let stmt_arena: Arena<Stmt> = Arena::new();
1119 let imperative_expr_arena: Arena<Expr> = Arena::new();
1120 let type_expr_arena: Arena<TypeExpr> = Arena::new();
1121
1122 let ctx = AstContext::with_types(
1123 &expr_arena,
1124 &term_arena,
1125 &np_arena,
1126 &sym_arena,
1127 &role_arena,
1128 &pp_arena,
1129 &stmt_arena,
1130 &imperative_expr_arena,
1131 &type_expr_arena,
1132 );
1133
1134 let mut world_state = drs::WorldState::new();
1135 let type_registry_for_interp = type_registry.clone();
1136 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
1137
1138 match parser.parse_program() {
1139 Ok(stmts) => {
1140 if let Some(rejection) = send_escape_rejection(&stmts) {
1141 return rejection;
1142 }
1143 if crate::concurrency::uses_scheduler(&stmts) {
1149 return run_program_concurrent_streaming(
1150 &stmts,
1151 &type_registry_for_interp,
1152 policy_registry,
1153 &interner,
1154 program_args,
1155 None,
1156 None,
1157 None,
1158 0,
1159 )
1160 .await;
1161 }
1162 let mut interp = crate::interpreter::Interpreter::new(&interner)
1163 .with_type_registry(&type_registry_for_interp)
1164 .with_policies(policy_registry)
1165 .with_program_args(program_args.to_vec());
1166 match interp.run(&stmts).await {
1167 Ok(()) => InterpreterResult {
1168 lines: interp.output,
1169 error: None,
1170 },
1171 Err(e) => InterpreterResult {
1172 lines: interp.output,
1173 error: Some(e),
1174 },
1175 }
1176 }
1177 Err(e) => {
1178 let advice = socratic_explanation(&e, &interner);
1179 InterpreterResult {
1180 lines: vec![],
1181 error: Some(advice),
1182 }
1183 }
1184 }
1185}
1186
1187pub fn with_parsed_program<R>(
1197 input: &str,
1198 f: impl for<'a> FnOnce(
1199 Result<
1200 (
1201 &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
1202 &'a logicaffeine_language::analysis::TypeRegistry,
1203 logicaffeine_language::analysis::PolicyRegistry,
1204 ),
1205 String,
1206 >,
1207 &'a Interner,
1208 ) -> R,
1209) -> R {
1210 use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
1211
1212 let implicit = implicit_main(input);
1214 let input = implicit.as_deref().unwrap_or(input);
1215
1216 let prelude_src = crate::loader::apply_prelude(input);
1219 let input = prelude_src.as_ref();
1220
1221 let mut interner = Interner::new();
1222 let mut lexer = Lexer::new(input, &mut interner);
1223 let tokens = lexer.tokenize();
1224
1225 let mwe_trie = mwe::build_mwe_trie();
1226 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
1227
1228 let (type_registry, policy_registry) = {
1229 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1230 let result = discovery.run_full();
1231 (result.types, result.policies)
1232 };
1233
1234 let expr_arena = Arena::new();
1235 let term_arena = Arena::new();
1236 let np_arena = Arena::new();
1237 let sym_arena = Arena::new();
1238 let role_arena = Arena::new();
1239 let pp_arena = Arena::new();
1240 let stmt_arena: Arena<Stmt> = Arena::new();
1241 let imperative_expr_arena: Arena<Expr> = Arena::new();
1242 let type_expr_arena: Arena<TypeExpr> = Arena::new();
1243
1244 let ctx = AstContext::with_types(
1245 &expr_arena,
1246 &term_arena,
1247 &np_arena,
1248 &sym_arena,
1249 &role_arena,
1250 &pp_arena,
1251 &stmt_arena,
1252 &imperative_expr_arena,
1253 &type_expr_arena,
1254 );
1255
1256 let mut world_state = drs::WorldState::new();
1257 let type_registry_for_engines = type_registry.clone();
1258 let (parsed, opt_flags) = {
1259 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
1260 let stmts = parser.parse_program();
1261 let flags = parser.program_opt_flags();
1262 (stmts, flags)
1263 };
1264
1265 match parsed {
1266 Ok(stmts) => {
1267 let mut run_cfg =
1275 crate::optimization::OptimizationConfig::from_env().merged(&opt_flags);
1276 run_cfg.normalize();
1277 let resolved = crate::resolve_division::resolve_divisions(
1278 &stmts,
1279 &stmt_arena,
1280 &imperative_expr_arena,
1281 &interner,
1282 run_cfg.is_on(crate::optimization::Opt::Comptime),
1283 );
1284 let pre = resolved.unwrap_or(stmts.as_slice());
1285 match crate::tail_call::rewrite_accumulators(
1286 pre,
1287 &stmt_arena,
1288 &imperative_expr_arena,
1289 &mut interner,
1290 ) {
1291 Some(rw) => f(Ok((rw, &type_registry_for_engines, policy_registry)), &interner),
1292 None => f(Ok((pre, &type_registry_for_engines, policy_registry)), &interner),
1293 }
1294 }
1295 Err(e) => {
1296 let advice = socratic_explanation(&e, &interner);
1297 f(Err(advice), &interner)
1298 }
1299 }
1300}
1301
1302pub fn with_optimized_program<R>(
1309 input: &str,
1310 f: impl for<'a> FnOnce(
1311 Result<
1312 (
1313 &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
1314 &'a logicaffeine_language::analysis::TypeRegistry,
1315 logicaffeine_language::analysis::PolicyRegistry,
1316 ),
1317 String,
1318 >,
1319 &'a Interner,
1320 ) -> R,
1321) -> R {
1322 let tier = crate::optimization::HotswapConfig::from_env().run_tier();
1326 with_optimized_program_tiered(input, tier, f)
1327}
1328
1329pub fn with_optimized_program_tiered<R>(
1336 input: &str,
1337 tier: crate::optimization::Tier,
1338 f: impl for<'a> FnOnce(
1339 Result<
1340 (
1341 &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
1342 &'a logicaffeine_language::analysis::TypeRegistry,
1343 logicaffeine_language::analysis::PolicyRegistry,
1344 ),
1345 String,
1346 >,
1347 &'a Interner,
1348 ) -> R,
1349) -> R {
1350 use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
1351
1352 let implicit = implicit_main(input);
1354 let input = implicit.as_deref().unwrap_or(input);
1355
1356 let prelude_src = crate::loader::apply_prelude(input);
1359 let input = prelude_src.as_ref();
1360
1361 let mut interner = Interner::new();
1362 let mut lexer = Lexer::new(input, &mut interner);
1363 let tokens = lexer.tokenize();
1364
1365 let mwe_trie = mwe::build_mwe_trie();
1366 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
1367
1368 let (type_registry, policy_registry) = {
1369 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1370 let result = discovery.run_full();
1371 (result.types, result.policies)
1372 };
1373
1374 let expr_arena = Arena::new();
1375 let term_arena = Arena::new();
1376 let np_arena = Arena::new();
1377 let sym_arena = Arena::new();
1378 let role_arena = Arena::new();
1379 let pp_arena = Arena::new();
1380 let stmt_arena: Arena<Stmt> = Arena::new();
1381 let imperative_expr_arena: Arena<Expr> = Arena::new();
1382 let type_expr_arena: Arena<TypeExpr> = Arena::new();
1383
1384 let ctx = AstContext::with_types(
1385 &expr_arena,
1386 &term_arena,
1387 &np_arena,
1388 &sym_arena,
1389 &role_arena,
1390 &pp_arena,
1391 &stmt_arena,
1392 &imperative_expr_arena,
1393 &type_expr_arena,
1394 );
1395
1396 let mut world_state = drs::WorldState::new();
1397 let type_registry_for_engines = type_registry.clone();
1398 let (parsed, opt_flags, tier_pins) = {
1399 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
1400 let stmts = parser.parse_program();
1401 let flags = parser.program_opt_flags();
1402 let pins = parser.program_tier_pins();
1403 (stmts, flags, pins)
1404 };
1405
1406 match parsed {
1407 Ok(stmts) => {
1408 let mut run_cfg =
1411 crate::optimization::OptimizationConfig::from_env().merged(&opt_flags);
1412 run_cfg.normalize();
1413 let mut hotswap = crate::optimization::HotswapConfig::from_env();
1416 hotswap.pins.overlay(&tier_pins);
1417 let resolved = crate::resolve_division::resolve_divisions(
1421 &stmts,
1422 &stmt_arena,
1423 &imperative_expr_arena,
1424 &interner,
1425 run_cfg.is_on(crate::optimization::Opt::Comptime),
1426 );
1427 let pre: Vec<_> = match resolved {
1428 Some(rw) => rw.to_vec(),
1429 None => stmts,
1430 };
1431 let optimized = crate::optimize::optimize_for_run_tiered(
1432 pre,
1433 &imperative_expr_arena,
1434 &stmt_arena,
1435 &mut interner,
1436 &run_cfg,
1437 &hotswap,
1438 tier,
1439 );
1440 match crate::tail_call::rewrite_accumulators(
1443 &optimized,
1444 &stmt_arena,
1445 &imperative_expr_arena,
1446 &mut interner,
1447 ) {
1448 Some(rw) => f(Ok((rw, &type_registry_for_engines, policy_registry)), &interner),
1449 None => f(Ok((&optimized, &type_registry_for_engines, policy_registry)), &interner),
1450 }
1451 }
1452 Err(e) => {
1453 let advice = socratic_explanation(&e, &interner);
1454 f(Err(advice), &interner)
1455 }
1456 }
1457}
1458
1459pub fn with_v2_optimized_program<R>(
1465 input: &str,
1466 f: impl for<'a> FnOnce(
1467 Result<
1468 (
1469 &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
1470 &'a logicaffeine_language::analysis::TypeRegistry,
1471 logicaffeine_language::analysis::PolicyRegistry,
1472 ),
1473 String,
1474 >,
1475 &'a Interner,
1476 ) -> R,
1477) -> R {
1478 use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
1479
1480 let implicit = implicit_main(input);
1482 let input = implicit.as_deref().unwrap_or(input);
1483
1484 let prelude_src = crate::loader::apply_prelude(input);
1487 let input = prelude_src.as_ref();
1488
1489 let mut interner = Interner::new();
1490 let mut lexer = Lexer::new(input, &mut interner);
1491 let tokens = lexer.tokenize();
1492
1493 let mwe_trie = mwe::build_mwe_trie();
1494 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
1495
1496 let (type_registry, policy_registry) = {
1497 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1498 let result = discovery.run_full();
1499 (result.types, result.policies)
1500 };
1501
1502 let expr_arena = Arena::new();
1503 let term_arena = Arena::new();
1504 let np_arena = Arena::new();
1505 let sym_arena = Arena::new();
1506 let role_arena = Arena::new();
1507 let pp_arena = Arena::new();
1508 let stmt_arena: Arena<Stmt> = Arena::new();
1509 let imperative_expr_arena: Arena<Expr> = Arena::new();
1510 let type_expr_arena: Arena<TypeExpr> = Arena::new();
1511
1512 let ctx = AstContext::with_types(
1513 &expr_arena,
1514 &term_arena,
1515 &np_arena,
1516 &sym_arena,
1517 &role_arena,
1518 &pp_arena,
1519 &stmt_arena,
1520 &imperative_expr_arena,
1521 &type_expr_arena,
1522 );
1523
1524 let mut world_state = drs::WorldState::new();
1525 let type_registry_for_engines = type_registry.clone();
1526 let parsed = {
1527 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
1528 parser.parse_program()
1529 };
1530
1531 match parsed {
1532 Ok(stmts) => {
1533 let optimized = crate::optimize::optimize_program(
1534 stmts,
1535 &imperative_expr_arena,
1536 &stmt_arena,
1537 &mut interner,
1538 &crate::optimization::OptimizationConfig::from_env(),
1539 );
1540 f(Ok((&optimized, &type_registry_for_engines, policy_registry)), &interner)
1541 }
1542 Err(e) => {
1543 let advice = socratic_explanation(&e, &interner);
1544 f(Err(advice), &interner)
1545 }
1546 }
1547}
1548
1549pub fn interpret_for_ui_sync(input: &str) -> InterpreterResult {
1557 interpret_for_ui_sync_with_args(input, &[])
1558}
1559
1560#[cfg(not(target_arch = "wasm32"))]
1561thread_local! {
1562 static PENDING_AOT: std::cell::RefCell<Vec<(String, Box<dyn crate::vm::NativeFn>)>> =
1568 const { std::cell::RefCell::new(Vec::new()) };
1569}
1570
1571#[cfg(not(target_arch = "wasm32"))]
1574pub fn set_pending_aot_natives(natives: Vec<(String, Box<dyn crate::vm::NativeFn>)>) {
1575 PENDING_AOT.with(|p| *p.borrow_mut() = natives);
1576}
1577
1578#[cfg(not(target_arch = "wasm32"))]
1581fn install_pending_aot_natives(
1582 vm: &mut crate::vm::Vm,
1583 program: &crate::vm::CompiledProgram,
1584 interner: &Interner,
1585) {
1586 let pending = PENDING_AOT.with(|p| std::mem::take(&mut *p.borrow_mut()));
1587 for (name, nf) in pending {
1588 if let Some(fi) = program
1589 .fn_index
1590 .iter()
1591 .find(|(s, _)| interner.resolve(**s) == name)
1592 .map(|(_, i)| *i as usize)
1593 {
1594 vm.install_aot_native(fi, nf);
1595 if std::env::var_os("LOGOS_ENGINE_TRACE").is_some() {
1596 eprintln!("logos-engine: aot-native installed for '{name}'");
1597 }
1598 }
1599 }
1600}
1601
1602pub fn interpret_for_ui_sync_with_args(input: &str, program_args: &[String]) -> InterpreterResult {
1608 let trace = |engine: &str| {
1612 if std::env::var_os("LOGOS_ENGINE_TRACE").is_some() {
1613 eprintln!("logos-engine: {engine}");
1614 }
1615 };
1616 with_optimized_program(input, |parsed, interner| match parsed {
1621 Ok((stmts, type_registry, policies)) => {
1622 if let Some(rejection) = send_escape_rejection(stmts) {
1623 return rejection;
1624 }
1625 if let Some(rejection) = send_dimension_rejection(stmts, interner) {
1626 return rejection;
1627 }
1628 if crate::interpreter::needs_async(stmts) || crate::concurrency::uses_scheduler(stmts) {
1629 trace("treewalker (async)");
1630 return run_treewalker(stmts, type_registry, policies, interner, true, program_args);
1631 }
1632 let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
1635 match crate::vm::Compiler::compile_with_oracle(
1636 stmts,
1637 interner,
1638 Some(type_registry),
1639 Some(oracle),
1640 ) {
1641 Ok(program) => {
1642 trace("vm+jit");
1643 let mut vm = crate::vm::Vm::new(&program)
1644 .with_policy_ctx(&policies, interner)
1645 .with_program_args(program_args.to_vec());
1646 if let Some(tier) = crate::vm::installed_native_tier() {
1647 vm = vm.with_native_tier(tier);
1648 }
1649 #[cfg(not(target_arch = "wasm32"))]
1652 install_pending_aot_natives(&mut vm, &program, interner);
1653 let error = vm.run().err();
1654 let result = InterpreterResult { lines: vm.into_lines(), error };
1655
1656 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
1661 {
1662 let shadow = run_treewalker(
1663 stmts,
1664 type_registry,
1665 policies.clone(),
1666 interner,
1667 false,
1668 program_args,
1669 );
1670 assert_eq!(
1671 (&result.lines, &result.error),
1672 (&shadow.lines, &shadow.error),
1673 "VM diverged from the tree-walker oracle for:\n{input}"
1674 );
1675 }
1676 result
1677 }
1678 Err(_) => {
1681 trace("treewalker (vm-reject)");
1682 run_treewalker(stmts, type_registry, policies, interner, false, program_args)
1683 }
1684 }
1685 }
1686 Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
1687 })
1688}
1689
1690pub async fn interpret_for_ui_baseline(input: &str) -> InterpreterResult {
1702 interpret_for_ui_baseline_with_args(input, &[]).await
1703}
1704
1705pub async fn interpret_for_ui_baseline_with_args(
1708 input: &str,
1709 program_args: &[String],
1710) -> InterpreterResult {
1711 let needs_async = with_parsed_program(input, |parsed, _| match parsed {
1716 Ok((stmts, _, _)) => crate::interpreter::needs_async(stmts),
1717 Err(_) => false,
1718 });
1719 if needs_async {
1720 return interpret_for_ui_with_args(input, program_args).await;
1721 }
1722 interpret_for_ui_baseline_sync_with_args(input, program_args)
1723}
1724
1725pub fn interpret_for_ui_baseline_sync_with_args(
1729 input: &str,
1730 program_args: &[String],
1731) -> InterpreterResult {
1732 let trace = |engine: &str| {
1733 if std::env::var_os("LOGOS_ENGINE_TRACE").is_some() {
1734 eprintln!("logos-engine: {engine}");
1735 }
1736 };
1737 with_parsed_program(input, |parsed, interner| match parsed {
1738 Ok((stmts, type_registry, policies)) => {
1739 if let Some(rejection) = send_escape_rejection(stmts) {
1740 return rejection;
1741 }
1742 if let Some(rejection) = send_dimension_rejection(stmts, interner) {
1743 return rejection;
1744 }
1745 if crate::interpreter::needs_async(stmts) || crate::concurrency::uses_scheduler(stmts) {
1746 trace("treewalker (async)");
1747 return run_treewalker(stmts, type_registry, policies, interner, true, program_args);
1748 }
1749 match crate::vm::Compiler::compile_with_types(stmts, interner, Some(type_registry)) {
1750 Ok(program) => {
1751 trace("vm (baseline)");
1752 let mut vm = crate::vm::Vm::new(&program)
1753 .with_policy_ctx(&policies, interner)
1754 .with_program_args(program_args.to_vec());
1755 if let Some(tier) = crate::vm::installed_native_tier() {
1756 vm = vm.with_native_tier(tier);
1757 }
1758 #[cfg(not(target_arch = "wasm32"))]
1761 install_pending_aot_natives(&mut vm, &program, interner);
1762 let error = vm.run().err();
1763 let result = InterpreterResult { lines: vm.into_lines(), error };
1764
1765 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
1770 {
1771 let shadow = run_treewalker(
1772 stmts,
1773 type_registry,
1774 policies.clone(),
1775 interner,
1776 false,
1777 program_args,
1778 );
1779 assert_eq!(
1780 (&result.lines, &result.error),
1781 (&shadow.lines, &shadow.error),
1782 "baseline VM diverged from the tree-walker oracle for:\n{input}"
1783 );
1784 }
1785 result
1786 }
1787 Err(_) => {
1790 trace("treewalker (vm-reject)");
1791 run_treewalker(stmts, type_registry, policies, interner, false, program_args)
1792 }
1793 }
1794 }
1795 Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
1796 })
1797}
1798
1799fn run_program_concurrent<'a>(
1805 stmts: &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
1806 type_registry: &logicaffeine_language::analysis::TypeRegistry,
1807 policies: logicaffeine_language::analysis::PolicyRegistry,
1808 interner: &'a Interner,
1809 program_args: &[String],
1810 vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
1811 stream: Option<crate::interpreter::OutputCallback>,
1812 seed: u64,
1813) -> InterpreterResult {
1814 use crate::concurrency::bridge::YieldState;
1815 use crate::concurrency::driver::InterpreterTask;
1816 use logicaffeine_runtime::{run_with_seed, RunOutcome, SchedSeed, SchedulerConfig};
1817 use std::cell::RefCell;
1818 use std::rc::Rc;
1819
1820 let output_sink: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
1821 let sink = output_sink.clone();
1822 let callback: crate::interpreter::OutputCallback =
1826 Rc::new(RefCell::new(move |line: String| {
1827 if let Some(s) = &stream {
1828 (s.borrow_mut())(line.clone());
1829 }
1830 sink.borrow_mut().push(line);
1831 }));
1832 let err_sink: crate::concurrency::driver::ErrSink = Rc::new(RefCell::new(None));
1833
1834 let mut main = crate::interpreter::Interpreter::new(interner)
1835 .with_type_registry(type_registry)
1836 .with_policies(policies)
1837 .with_program_args(program_args.to_vec())
1838 .with_output_callback(callback);
1839 if let Some(v) = vfs {
1840 main = main.with_vfs(v);
1841 }
1842 let main_ys = Rc::new(RefCell::new(YieldState::new()));
1843 main.install_yield_state(main_ys.clone());
1844
1845 let main_fut = Box::pin(async move { main.run(stmts).await });
1846 let main_task = InterpreterTask::new(main_fut, main_ys, Some(err_sink.clone()));
1847
1848 let (outcome, _trace) =
1849 run_with_seed(SchedulerConfig::default(), SchedSeed(seed), move |sched| {
1850 sched.spawn_main(Box::new(main_task));
1851 });
1852
1853 let mut error = err_sink.borrow().clone();
1854 if error.is_none() {
1855 match outcome {
1856 RunOutcome::Deadlock => {
1857 error = Some("deadlock: every task is blocked waiting".to_string());
1858 }
1859 RunOutcome::WaitingForIo => {
1863 error = Some(
1864 "networking requires the async runtime; this program was run on the \
1865 synchronous scheduler"
1866 .to_string(),
1867 );
1868 }
1869 RunOutcome::Done(_) => {}
1870 }
1871 }
1872 let lines = output_sink.borrow().clone();
1873 InterpreterResult { lines, error }
1874}
1875
1876pub type ObserverCallback = std::rc::Rc<std::cell::RefCell<dyn FnMut(logicaffeine_runtime::SchedSnapshot)>>;
1879
1880async fn yield_macrotask() {
1885 #[cfg(target_arch = "wasm32")]
1886 {
1887 gloo_timers::future::TimeoutFuture::new(0).await;
1888 }
1889}
1890
1891async fn yield_to_reactor() {
1898 #[cfg(target_arch = "wasm32")]
1899 {
1900 gloo_timers::future::TimeoutFuture::new(0).await;
1901 }
1902 #[cfg(not(target_arch = "wasm32"))]
1903 {
1904 use std::future::Future;
1905 use std::pin::Pin;
1906 use std::task::{Context, Poll};
1907 struct YieldOnce(bool);
1908 impl Future for YieldOnce {
1909 type Output = ();
1910 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
1911 if self.0 {
1912 Poll::Ready(())
1913 } else {
1914 self.0 = true;
1915 cx.waker().wake_by_ref();
1916 Poll::Pending
1917 }
1918 }
1919 }
1920 YieldOnce(false).await;
1921 }
1922}
1923
1924#[allow(clippy::too_many_arguments)]
1930async fn run_program_concurrent_streaming<'a>(
1931 stmts: &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
1932 type_registry: &logicaffeine_language::analysis::TypeRegistry,
1933 policies: logicaffeine_language::analysis::PolicyRegistry,
1934 interner: &'a Interner,
1935 program_args: &[String],
1936 vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
1937 stream: Option<crate::interpreter::OutputCallback>,
1938 observer: Option<ObserverCallback>,
1939 seed: u64,
1940) -> InterpreterResult {
1941 use crate::concurrency::bridge::YieldState;
1942 use crate::concurrency::driver::InterpreterTask;
1943 use logicaffeine_runtime::{Chooser, RunOutcome, SchedSeed, Scheduler, SchedulerConfig};
1944 use std::cell::RefCell;
1945 use std::rc::Rc;
1946
1947 const SLICE_STEPS: usize = 256;
1951
1952 let output_sink: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
1953 let sink = output_sink.clone();
1954 let callback: crate::interpreter::OutputCallback = Rc::new(RefCell::new(move |line: String| {
1955 if let Some(s) = &stream {
1956 (s.borrow_mut())(line.clone());
1957 }
1958 sink.borrow_mut().push(line);
1959 }));
1960 let err_sink: crate::concurrency::driver::ErrSink = Rc::new(RefCell::new(None));
1961
1962 let mut main = crate::interpreter::Interpreter::new(interner)
1963 .with_type_registry(type_registry)
1964 .with_policies(policies)
1965 .with_program_args(program_args.to_vec())
1966 .with_output_callback(callback);
1967 if let Some(v) = vfs {
1968 main = main.with_vfs(v);
1969 }
1970 let main_ys = Rc::new(RefCell::new(YieldState::new()));
1971 main.install_yield_state(main_ys.clone());
1972
1973 let main_fut = Box::pin(async move { main.run(stmts).await });
1974 let main_task = InterpreterTask::new(main_fut, main_ys, Some(err_sink.clone()));
1975
1976 let mut sched = Scheduler::new(SchedulerConfig::default(), Chooser::record(SchedSeed(seed)));
1977 sched.spawn_main(Box::new(main_task));
1978
1979 let outcome = loop {
1980 match sched.run_slice(SLICE_STEPS) {
1981 Some(RunOutcome::WaitingForIo) => {
1982 if let Some(ob) = &observer {
1986 (ob.borrow_mut())(sched.snapshot());
1987 }
1988 yield_to_reactor().await;
1989 sched.wake_io();
1990 }
1991 Some(o) => break o,
1992 None => {
1993 if let Some(ob) = &observer {
1994 (ob.borrow_mut())(sched.snapshot());
1995 }
1996 yield_macrotask().await;
1997 }
1998 }
1999 };
2000
2001 let mut error = err_sink.borrow().clone();
2002 if error.is_none() {
2003 if let RunOutcome::Deadlock = outcome {
2004 error = Some("deadlock: every task is blocked waiting".to_string());
2005 }
2006 }
2007 let lines = output_sink.borrow().clone();
2008 InterpreterResult { lines, error }
2009}
2010
2011pub fn run_treewalker_concurrent_seeded(input: &str, seed: u64) -> InterpreterResult {
2015 with_parsed_program(input, |parsed, interner| match parsed {
2016 Ok((stmts, type_registry, policies)) => {
2017 run_program_concurrent(stmts, type_registry, policies, interner, &[], None, None, seed)
2018 }
2019 Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
2020 })
2021}
2022
2023pub fn run_vm_concurrent(input: &str) -> InterpreterResult {
2030 run_vm_concurrent_seeded(input, 0)
2031}
2032
2033pub fn run_vm_concurrent_seeded(input: &str, seed: u64) -> InterpreterResult {
2036 use crate::concurrency::vm_driver::VmTask;
2037 use logicaffeine_runtime::{run_with_seed, RunOutcome, SchedSeed, SchedulerConfig};
2038 use std::cell::RefCell;
2039 use std::rc::Rc;
2040
2041 with_parsed_program(input, |parsed, interner| match parsed {
2042 Ok((stmts, type_registry, policies)) => {
2043 let program = match crate::vm::Compiler::compile_with_types(
2044 stmts,
2045 interner,
2046 Some(type_registry),
2047 ) {
2048 Ok(p) => p,
2049 Err(e) => return InterpreterResult { lines: vec![], error: Some(e) },
2050 };
2051 let output: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
2052 let err_sink: crate::concurrency::driver::ErrSink = Rc::new(RefCell::new(None));
2053 let mut vm = crate::vm::Vm::new(&program).with_policy_ctx(&policies, interner);
2054 #[cfg(not(target_arch = "wasm32"))]
2060 if let Some(tier) = crate::vm::installed_native_tier() {
2061 vm = vm.with_native_tier(tier);
2062 }
2063 let main_task = VmTask::new(vm, output.clone(), Some(err_sink.clone()));
2064 let (outcome, _trace) =
2065 run_with_seed(SchedulerConfig::default(), SchedSeed(seed), move |sched| {
2066 sched.spawn_main(Box::new(main_task));
2067 });
2068 let mut error = err_sink.borrow().clone();
2069 if error.is_none() {
2070 if let RunOutcome::Deadlock = outcome {
2071 error = Some("deadlock: every task is blocked waiting".to_string());
2072 }
2073 }
2074 let lines = output.borrow().clone();
2075 InterpreterResult { lines, error }
2076 }
2077 Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
2078 })
2079}
2080
2081async fn vm_net_poll_tick() {
2084 #[cfg(not(target_arch = "wasm32"))]
2085 logicaffeine_system::tokio::time::sleep(std::time::Duration::from_millis(2)).await;
2086 #[cfg(target_arch = "wasm32")]
2087 gloo_timers::future::TimeoutFuture::new(2).await;
2088}
2089
2090fn vm_peer_topic_of(value: &crate::interpreter::RuntimeValue) -> Result<String, String> {
2092 use crate::interpreter::RuntimeValue;
2093 match value {
2094 RuntimeValue::Peer(topic) => Ok((**topic).clone()),
2095 RuntimeValue::Text(s) => Ok(logicaffeine_system::addr::canonical_topic(s)),
2096 other => Err(format!(
2097 "Send/Await expects a PeerAgent or address string, got {}",
2098 other.type_name()
2099 )),
2100 }
2101}
2102
2103async fn service_vm_net_block(
2108 vm: &mut crate::vm::Vm<'_>,
2109 netbox: &mut crate::concurrency::net_inbox::NetInbox,
2110 req: crate::vm::VmBlock,
2111) -> Result<(), String> {
2112 use crate::concurrency::marshal::{self};
2113 use crate::interpreter::RuntimeValue;
2114 use crate::vm::{Value, VmBlock};
2115 match req {
2116 VmBlock::NetConnect(url_payload) => {
2117 let raw = marshal::rebuild(url_payload).to_display_string();
2118 let url = logicaffeine_system::addr::multiaddr_to_ws_url(&raw).map_err(|e| {
2119 format!("Connect address '{raw}' is not a ws:// URL or supported multiaddr: {e}")
2120 })?;
2121 if !crate::concurrency::net_inbox::net_is_offline() {
2124 let net = logicaffeine_system::net::Net::connect(&url)
2125 .await
2126 .map_err(|e| format!("Connect to relay '{url}' failed: {e}"))?;
2127 netbox.net = Some(net);
2128 }
2129 vm.deliver_resume(Value::nothing());
2130 Ok(())
2131 }
2132 VmBlock::NetListen(topic_payload) => {
2133 let raw = marshal::rebuild(topic_payload).to_display_string();
2134 let topic = logicaffeine_system::addr::canonical_topic(&raw);
2135 let hs_topic = crate::concurrency::net_inbox::handshake_topic_for(&topic);
2136 if let Some(net) = netbox.net.as_mut() {
2139 net.subscribe(&topic).await?;
2140 net.subscribe(&hs_topic).await?;
2143 }
2144 netbox.inbox = Some(std::rc::Rc::new(topic));
2145 vm.deliver_resume(Value::nothing());
2146 Ok(())
2147 }
2148 VmBlock::NetSend(to_payload, msg_payload) => {
2149 let topic = vm_peer_topic_of(&marshal::rebuild(to_payload))?;
2150 if let Some(hs) = netbox.first_contact_handshake(&topic) {
2153 netbox.publish(&crate::concurrency::net_inbox::handshake_topic_for(&topic), hs)?;
2154 }
2155 let value = marshal::rebuild(msg_payload);
2156 let from = netbox.inbox.as_ref().map(|t| t.to_string()).unwrap_or_default();
2157 let bytes = netbox.encode_negotiated(&from, &value, &topic, netbox.my_registry.clone())?;
2161 netbox.publish(&topic, bytes)?;
2162 vm.deliver_resume(Value::nothing());
2163 Ok(())
2164 }
2165 VmBlock::NetStream(to_payload, values_payload) => {
2166 let topic = vm_peer_topic_of(&marshal::rebuild(to_payload))?;
2167 if let Some(hs) = netbox.first_contact_handshake(&topic) {
2168 netbox.publish(&crate::concurrency::net_inbox::handshake_topic_for(&topic), hs)?;
2169 }
2170 let list = marshal::rebuild(values_payload);
2171 let items = match &list {
2172 RuntimeValue::List(rc) => rc.borrow().to_values(),
2173 other => vec![other.clone()],
2174 };
2175 let from = netbox.inbox.as_ref().map(|t| t.to_string()).unwrap_or_default();
2176 let blob = marshal::frame_stream_message(&from, &items)?;
2177 netbox.publish(&topic, blob)?;
2178 vm.deliver_resume(Value::nothing());
2179 Ok(())
2180 }
2181 VmBlock::NetAwait(from_payload, stream) => {
2182 let want = vm_peer_topic_of(&marshal::rebuild(from_payload))?;
2183 if netbox.inbox.is_none() {
2184 return Err("Await requires a prior Listen to establish an inbox".to_string());
2185 }
2186 loop {
2189 let got = if stream {
2190 netbox.try_take_stream(&want)
2191 } else {
2192 netbox.try_take_message(&want, false)
2193 };
2194 if let Some(v) = got {
2195 vm.deliver_resume(Value::from_runtime(v));
2196 return Ok(());
2197 }
2198 netbox.drain(netbox.my_registry.clone());
2201 let got = if stream {
2202 netbox.try_take_stream(&want)
2203 } else {
2204 netbox.try_take_message(&want, false)
2205 };
2206 if let Some(v) = got {
2207 vm.deliver_resume(Value::from_runtime(v));
2208 return Ok(());
2209 }
2210 vm_net_poll_tick().await;
2211 }
2212 }
2213 VmBlock::NetMakePeer(addr_payload) => {
2214 let raw = marshal::rebuild(addr_payload).to_display_string();
2215 let topic = logicaffeine_system::addr::canonical_topic(&raw);
2216 vm.deliver_resume(Value::from_runtime(RuntimeValue::Peer(std::rc::Rc::new(topic))));
2217 Ok(())
2218 }
2219 VmBlock::NetSync(topic_payload, current_payload) => {
2220 let topic = marshal::rebuild(topic_payload).to_display_string();
2221 let current = marshal::rebuild(current_payload);
2222 let publish_bytes = crate::semantics::arith::crdt_to_wire(¤t);
2225 let merged = if let Some(net) = netbox.net.as_mut() {
2228 net.subscribe(&topic).await?;
2229 if let Some(bytes) = publish_bytes {
2230 net.publish(&topic, bytes)?;
2231 }
2232 let incoming = net.drain();
2233 let mut merged = current;
2234 for (_t, data) in incoming {
2235 merged = crate::semantics::arith::crdt_merge_wire(merged, &data);
2236 }
2237 merged
2238 } else {
2239 current
2240 };
2241 vm.deliver_resume(Value::from_runtime(merged));
2242 Ok(())
2243 }
2244 _ => Err("run_vm_net_async services only peer-networking blocks; this program mixes \
2245 channels/tasks with networking, which the VM net runner does not yet drive"
2246 .to_string()),
2247 }
2248}
2249
2250pub async fn run_vm_net_async(input: &str) -> InterpreterResult {
2255 use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
2256
2257 let mut interner = Interner::new();
2258 let mut lexer = Lexer::new(input, &mut interner);
2259 let tokens = lexer.tokenize();
2260 let mwe_trie = mwe::build_mwe_trie();
2261 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
2262 let (type_registry, policy_registry) = {
2263 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
2264 let result = discovery.run_full();
2265 (result.types, result.policies)
2266 };
2267 let expr_arena = Arena::new();
2268 let term_arena = Arena::new();
2269 let np_arena = Arena::new();
2270 let sym_arena = Arena::new();
2271 let role_arena = Arena::new();
2272 let pp_arena = Arena::new();
2273 let stmt_arena: Arena<Stmt> = Arena::new();
2274 let imperative_expr_arena: Arena<Expr> = Arena::new();
2275 let type_expr_arena: Arena<TypeExpr> = Arena::new();
2276 let ctx = AstContext::with_types(
2277 &expr_arena,
2278 &term_arena,
2279 &np_arena,
2280 &sym_arena,
2281 &role_arena,
2282 &pp_arena,
2283 &stmt_arena,
2284 &imperative_expr_arena,
2285 &type_expr_arena,
2286 );
2287 let mut world_state = drs::WorldState::new();
2288 let type_registry_for_vm = type_registry.clone();
2289 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
2290 let stmts = match parser.parse_program() {
2291 Ok(s) => s,
2292 Err(e) => return InterpreterResult { lines: vec![], error: Some(format!("{e:?}")) },
2293 };
2294 let program =
2295 match crate::vm::Compiler::compile_with_types(&stmts, &interner, Some(&type_registry_for_vm)) {
2296 Ok(p) => p,
2297 Err(e) => return InterpreterResult { lines: vec![], error: Some(e) },
2298 };
2299 let mut vm = crate::vm::Vm::new(&program).with_policy_ctx(&policy_registry, &interner);
2300 let mut netbox = crate::concurrency::net_inbox::NetInbox::new();
2301 {
2305 let mut structs: Vec<(String, Vec<String>)> = Vec::new();
2306 let mut enums: Vec<(String, Vec<String>)> = Vec::new();
2307 for (name_sym, type_def) in type_registry_for_vm.iter_types() {
2308 let type_name = interner.resolve(*name_sym).to_string();
2309 match type_def {
2310 crate::analysis::registry::TypeDef::Struct { fields, .. } => {
2311 structs.push((type_name, fields.iter().map(|f| interner.resolve(f.name).to_string()).collect()));
2312 }
2313 crate::analysis::registry::TypeDef::Enum { variants, .. } => {
2314 enums.push((type_name, variants.iter().map(|v| interner.resolve(v.name).to_string()).collect()));
2315 }
2316 _ => {}
2317 }
2318 }
2319 netbox.set_registry(
2320 crate::concurrency::marshal::WireTypeRegistry::new(structs).with_enums(enums),
2321 );
2322 }
2323 let mut lines: Vec<String> = Vec::new();
2324 let mut error = None;
2325 loop {
2326 let step = vm.run_until_block();
2327 lines.extend(vm.drain_lines());
2328 match step {
2329 Ok(crate::vm::VmStep::Done(_)) => break,
2330 Ok(crate::vm::VmStep::Blocked) => {
2331 let req = match vm.take_pending() {
2332 Some(r) => r,
2333 None => break,
2334 };
2335 if let Err(e) = service_vm_net_block(&mut vm, &mut netbox, req).await {
2336 error = Some(e);
2337 break;
2338 }
2339 }
2340 Ok(crate::vm::VmStep::Paused) => break,
2341 Err(e) => {
2342 error = Some(e);
2343 break;
2344 }
2345 }
2346 }
2347 InterpreterResult { lines, error }
2348}
2349
2350#[cfg(not(target_arch = "wasm32"))]
2362pub fn run_vm_workstealing_seeded(input: &str, seed: u64, workers: usize) -> InterpreterResult {
2363 use crate::concurrency::vm_driver::VmTask;
2364 use logicaffeine_runtime::{
2365 run_workstealing_seeded, RunOutcome, SchedSeed, SchedulerConfig, SpawnDesc, Task,
2366 };
2367
2368 with_parsed_program(input, |parsed, interner| match parsed {
2369 Ok((stmts, type_registry, policies)) => {
2370 let program = match crate::vm::Compiler::compile_with_types(
2371 stmts,
2372 interner,
2373 Some(type_registry),
2374 ) {
2375 Ok(p) => p,
2376 Err(e) => return InterpreterResult { lines: vec![], error: Some(e) },
2377 };
2378 let build = |desc: SpawnDesc| -> Box<dyn Task<'_> + '_> {
2388 let mut vm = crate::vm::Vm::new(&program).with_policy_ctx(&policies, interner);
2389 if !desc.is_main {
2390 vm.setup_task(desc.func, &desc.args);
2391 }
2392 Box::new(VmTask::work_stealing(vm, None))
2393 };
2394 let main = SpawnDesc { func: 0, args: vec![], priority: 0, is_main: true };
2395 let config = SchedulerConfig::default().with_workers(workers.max(1));
2396 let result = run_workstealing_seeded(config, SchedSeed(seed), main, build);
2397 let error = match result.outcome {
2398 RunOutcome::Deadlock => {
2399 Some("deadlock: every task is blocked waiting".to_string())
2400 }
2401 _ => None,
2402 };
2403 InterpreterResult { lines: result.output, error }
2404 }
2405 Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
2406 })
2407}
2408
2409pub fn repl_global_bindings(input: &str, program_args: &[String]) -> Option<Vec<(String, String, String)>> {
2414 with_parsed_program(input, |parsed, interner| match parsed {
2415 Ok((stmts, type_registry, policies)) => {
2416 if crate::interpreter::needs_async(stmts) || crate::concurrency::uses_scheduler(stmts) {
2417 return None;
2418 }
2419 let mut interp = crate::interpreter::Interpreter::new(interner)
2420 .with_type_registry(type_registry)
2421 .with_policies(policies)
2422 .with_program_args(program_args.to_vec());
2423 interp.run_sync(stmts).ok()?;
2424 Some(interp.global_bindings())
2425 }
2426 Err(_) => None,
2427 })
2428}
2429
2430pub(crate) fn run_treewalker<'a>(
2431 stmts: &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
2432 type_registry: &logicaffeine_language::analysis::TypeRegistry,
2433 policies: logicaffeine_language::analysis::PolicyRegistry,
2434 interner: &'a Interner,
2435 force_async: bool,
2436 program_args: &[String],
2437) -> InterpreterResult {
2438 if crate::concurrency::uses_scheduler(stmts) {
2439 return run_program_concurrent(
2440 stmts, type_registry, policies, interner, program_args, None, None, 0,
2441 );
2442 }
2443 let mut interp = crate::interpreter::Interpreter::new(interner)
2444 .with_type_registry(type_registry)
2445 .with_policies(policies)
2446 .with_program_args(program_args.to_vec());
2447 let run_result = if force_async {
2448 futures::executor::block_on(interp.run(stmts))
2449 } else {
2450 interp.run_sync(stmts)
2451 };
2452 match run_result {
2453 Ok(()) => InterpreterResult { lines: interp.output, error: None },
2454 Err(e) => InterpreterResult { lines: interp.output, error: Some(e) },
2455 }
2456}
2457
2458pub async fn interpret_streaming<F>(input: &str, on_output: std::rc::Rc<std::cell::RefCell<F>>) -> InterpreterResult
2481where
2482 F: FnMut(String) + 'static,
2483{
2484 interpret_streaming_with_vfs(input, on_output, None).await
2485}
2486
2487pub async fn interpret_streaming_with_vfs<F>(
2493 input: &str,
2494 on_output: std::rc::Rc<std::cell::RefCell<F>>,
2495 vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
2496) -> InterpreterResult
2497where
2498 F: FnMut(String) + 'static,
2499{
2500 interpret_streaming_impl(input, on_output, vfs, None).await
2501}
2502
2503pub async fn interpret_streaming_with_vfs_observer<F>(
2507 input: &str,
2508 on_output: std::rc::Rc<std::cell::RefCell<F>>,
2509 vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
2510 observer: ObserverCallback,
2511) -> InterpreterResult
2512where
2513 F: FnMut(String) + 'static,
2514{
2515 interpret_streaming_impl(input, on_output, vfs, Some(observer)).await
2516}
2517
2518async fn interpret_streaming_impl<F>(
2519 input: &str,
2520 on_output: std::rc::Rc<std::cell::RefCell<F>>,
2521 vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
2522 observer: Option<ObserverCallback>,
2523) -> InterpreterResult
2524where
2525 F: FnMut(String) + 'static,
2526{
2527 use logicaffeine_language::ast::stmt::{Stmt, Expr, TypeExpr};
2528 use crate::interpreter::OutputCallback;
2529
2530 let mut interner = Interner::new();
2531 let mut lexer = Lexer::new(input, &mut interner);
2532 let tokens = lexer.tokenize();
2533
2534 let mwe_trie = mwe::build_mwe_trie();
2535 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
2536
2537 let (type_registry, policy_registry) = {
2538 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
2539 let result = discovery.run_full();
2540 (result.types, result.policies)
2541 };
2542
2543 let expr_arena = Arena::new();
2544 let term_arena = Arena::new();
2545 let np_arena = Arena::new();
2546 let sym_arena = Arena::new();
2547 let role_arena = Arena::new();
2548 let pp_arena = Arena::new();
2549 let stmt_arena: Arena<Stmt> = Arena::new();
2550 let imperative_expr_arena: Arena<Expr> = Arena::new();
2551 let type_expr_arena: Arena<TypeExpr> = Arena::new();
2552
2553 let ctx = AstContext::with_types(
2554 &expr_arena,
2555 &term_arena,
2556 &np_arena,
2557 &sym_arena,
2558 &role_arena,
2559 &pp_arena,
2560 &stmt_arena,
2561 &imperative_expr_arena,
2562 &type_expr_arena,
2563 );
2564
2565 let mut world_state = drs::WorldState::new();
2566 let type_registry_for_interp = type_registry.clone();
2567 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
2568
2569 match parser.parse_program() {
2570 Ok(stmts) => {
2571 if let Some(rejection) = send_escape_rejection(&stmts) {
2572 return rejection;
2573 }
2574 let callback: OutputCallback = std::rc::Rc::new(std::cell::RefCell::new(move |line: String| {
2576 (on_output.borrow_mut())(line);
2577 }));
2578
2579 if crate::concurrency::uses_scheduler(&stmts) {
2585 return run_program_concurrent_streaming(
2586 &stmts,
2587 &type_registry_for_interp,
2588 policy_registry,
2589 &interner,
2590 &[],
2591 vfs,
2592 Some(callback),
2593 observer,
2594 0,
2595 )
2596 .await;
2597 }
2598
2599 let mut interp = crate::interpreter::Interpreter::new(&interner)
2600 .with_type_registry(&type_registry_for_interp)
2601 .with_policies(policy_registry)
2602 .with_output_callback(callback);
2603 if let Some(v) = vfs {
2604 interp = interp.with_vfs(v);
2605 }
2606
2607 match interp.run(&stmts).await {
2608 Ok(()) => InterpreterResult {
2609 lines: interp.output,
2610 error: None,
2611 },
2612 Err(e) => InterpreterResult {
2613 lines: interp.output,
2614 error: Some(e),
2615 },
2616 }
2617 }
2618 Err(e) => {
2619 let advice = socratic_explanation(&e, &interner);
2620 InterpreterResult {
2621 lines: vec![],
2622 error: Some(advice),
2623 }
2624 }
2625 }
2626}
2627
2628use logicaffeine_language::ast::Stmt;
2633use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
2634use crate::kernel;
2635
2636pub fn verify_theorem(input: &str) -> Result<(kernel::Term, kernel::Context), ParseError> {
2646 let (proof_exprs, goal_expr, definitions) = theorem_proof_exprs_with_defs(input)?;
2647
2648 let outcome = logicaffeine_proof::verify::prove_certify_check_with_defs(
2650 &proof_exprs,
2651 &goal_expr,
2652 &definitions,
2653 );
2654 if outcome.verified {
2655 Ok((
2656 outcome
2657 .proof_term
2658 .expect("a verified outcome always carries a proof term"),
2659 outcome.kernel_ctx,
2660 ))
2661 } else {
2662 Err(ParseError {
2663 kind: logicaffeine_language::error::ParseErrorKind::Custom(
2664 outcome
2665 .verification_error
2666 .unwrap_or_else(|| "Theorem verification failed".to_string()),
2667 ),
2668 span: logicaffeine_language::token::Span::default(),
2669 })
2670 }
2671}
2672
2673#[derive(Debug, Clone)]
2678pub struct TheoremTrace {
2679 pub verified: bool,
2681 pub premises: Vec<String>,
2683 pub goal: String,
2685 pub trace: Option<String>,
2688 pub error: Option<String>,
2690}
2691
2692pub fn prove_theorem_trace(input: &str) -> Result<TheoremTrace, ParseError> {
2696 let (proof_exprs, goal_expr, definitions) = theorem_proof_exprs_with_defs(input)?;
2697 let outcome = logicaffeine_proof::verify::prove_certify_check_with_defs(
2698 &proof_exprs,
2699 &goal_expr,
2700 &definitions,
2701 );
2702 Ok(TheoremTrace {
2703 verified: outcome.verified,
2704 premises: proof_exprs.iter().map(|p| p.to_string()).collect(),
2705 goal: goal_expr.to_string(),
2706 trace: outcome.derivation.as_ref().map(|d| d.display_tree()),
2707 error: outcome.verification_error,
2708 })
2709}
2710
2711fn user_defined_entries(ctx: &kernel::Context) -> Vec<String> {
2720 let baseline = kernel::interface::Repl::new();
2721 let base = baseline.context();
2722 let mut base_names: HashSet<String> = HashSet::new();
2723 for (name, _) in base.iter_inductives() {
2724 base_names.insert(name.to_string());
2725 }
2726 for (name, _, _) in base.iter_definitions() {
2727 base_names.insert(name.to_string());
2728 }
2729
2730 let mut entries = Vec::new();
2731 for (name, _) in ctx.iter_inductives() {
2732 if !base_names.contains(name) && inductive_is_emittable(ctx, name) {
2736 entries.push(name.to_string());
2737 }
2738 }
2739 for (name, ty, _) in ctx.iter_definitions() {
2740 if !base_names.contains(name)
2744 && type_is_emittable(ctx, ty, &[])
2745 && crate::extraction::is_extractable(ctx, name)
2746 {
2747 entries.push(name.to_string());
2748 }
2749 }
2750 entries.sort();
2752 entries.dedup();
2753 entries
2754}
2755
2756fn type_is_emittable(ctx: &kernel::Context, ty: &kernel::Term, generics: &[String]) -> bool {
2761 use kernel::Term;
2762 match ty {
2763 Term::Global(name) => {
2764 crate::extraction::primitive_rust_type(name).is_some()
2765 || (ctx.is_inductive(name)
2766 && !ctx.get_constructors(name).is_empty()
2767 && !crate::extraction::is_logical_type(name))
2768 }
2769 Term::Var(v) => generics.iter().any(|g| g == v),
2772 Term::Pi { param_type, body_type, .. } => {
2773 type_is_emittable(ctx, param_type, generics) && type_is_emittable(ctx, body_type, generics)
2774 }
2775 Term::App(f, a) => {
2776 type_is_emittable(ctx, f, generics) && type_is_emittable(ctx, a, generics)
2777 }
2778 _ => false,
2779 }
2780}
2781
2782fn inductive_generics(ctx: &kernel::Context, ind: &str) -> Vec<String> {
2784 use kernel::Term;
2785 let mut names = Vec::new();
2786 let mut cur = match ctx.get_global(ind) {
2787 Some(t) => t,
2788 None => return names,
2789 };
2790 while let Term::Pi { param, param_type, body_type } = cur {
2791 if matches!(param_type.as_ref(), Term::Sort(_)) {
2792 names.push(param.clone());
2793 cur = body_type;
2794 } else {
2795 break;
2796 }
2797 }
2798 names
2799}
2800
2801fn inductive_is_emittable(ctx: &kernel::Context, ind: &str) -> bool {
2804 use kernel::Term;
2805 let ctors = ctx.get_constructors(ind);
2806 if ctors.is_empty() || crate::extraction::is_logical_type(ind) {
2807 return false;
2808 }
2809 let generics = inductive_generics(ctx, ind);
2810 for (_, ty) in &ctors {
2811 let mut cur = *ty;
2812 for _ in 0..generics.len() {
2813 if let Term::Pi { body_type, .. } = cur {
2814 cur = body_type;
2815 } else {
2816 break;
2817 }
2818 }
2819 while let Term::Pi { param_type, body_type, .. } = cur {
2820 if !type_is_emittable(ctx, param_type, &generics) {
2821 return false;
2822 }
2823 cur = body_type;
2824 }
2825 }
2826 true
2827}
2828
2829pub fn extract_math_rust(ctx: &kernel::Context) -> Result<String, String> {
2834 let entries = user_defined_entries(ctx);
2835 if entries.is_empty() {
2836 return Ok("// nothing defined yet — add a Definition or Inductive".to_string());
2837 }
2838 let module = extract_math_module(ctx)?;
2839 let checks: Vec<(String, Vec<kernel::Term>)> =
2840 property_checks(ctx).into_iter().map(|(n, _, p)| (n, p)).collect();
2841 Ok(format!("{module}{}", math_demo_main(ctx, &entries, &checks)))
2843}
2844
2845pub fn extract_math_module(ctx: &kernel::Context) -> Result<String, String> {
2851 let entries = user_defined_entries(ctx);
2852 if entries.is_empty() {
2853 return Ok("// nothing defined yet — add a Definition or Inductive".to_string());
2854 }
2855 let refs: Vec<&str> = entries.iter().map(|s| s.as_str()).collect();
2856 let mut module = crate::extraction::extract_programs(ctx, &refs).map_err(|e| e.to_string())?;
2857 let mut check_names: std::collections::HashSet<String> = std::collections::HashSet::new();
2861 for (name, check_fn, _) in property_checks(ctx) {
2862 module.push_str(&check_fn);
2863 check_names.insert(name);
2864 }
2865 let base = baseline_names();
2871 let mut notes: Vec<String> = Vec::new();
2872 for (name, ty, _) in ctx.iter_definitions() {
2873 if base.contains(name) || check_names.contains(name) {
2874 continue;
2875 }
2876 if def_type_is_proposition(ty) {
2877 notes.push(format!(
2878 "// note: `{name}` is a proof of a proposition — proof-irrelevant (no \
2879 computational content), so it has no runnable form; any constructive \
2880 definitions it relies on are extracted above.\n"
2881 ));
2882 }
2883 }
2884 notes.sort();
2885 for n in notes {
2886 module.push_str(&n);
2887 }
2888 Ok(module)
2889}
2890
2891fn def_type_is_proposition(ty: &kernel::Term) -> bool {
2896 use kernel::Term;
2897 let mut cur = ty;
2898 while let Term::Pi { body_type, .. } = cur {
2899 cur = body_type;
2900 }
2901 let mut head = cur;
2902 while let Term::App(f, _) = head {
2903 head = f;
2904 }
2905 matches!(head, Term::Global(n) if crate::extraction::is_logical_type(n))
2906}
2907
2908fn property_checks(ctx: &kernel::Context) -> Vec<(String, String, Vec<kernel::Term>)> {
2913 let base = baseline_names();
2914 let mut checks: Vec<(String, String, Vec<kernel::Term>)> = Vec::new();
2915 for (name, ty, _) in ctx.iter_definitions() {
2916 if base.contains(name) {
2917 continue;
2918 }
2919 if let Some(check_fn) = crate::extraction::emit_property_check(ctx, name, ty) {
2920 checks.push((name.to_string(), check_fn, pi_param_types(ty)));
2921 }
2922 }
2923 checks.sort_by(|a, b| a.0.cmp(&b.0));
2924 checks
2925}
2926
2927fn baseline_names() -> std::collections::HashSet<String> {
2929 let baseline = kernel::interface::Repl::new();
2930 let base = baseline.context();
2931 let mut names = std::collections::HashSet::new();
2932 for (n, _) in base.iter_inductives() {
2933 names.insert(n.to_string());
2934 }
2935 for (n, _, _) in base.iter_definitions() {
2936 names.insert(n.to_string());
2937 }
2938 names
2939}
2940
2941fn term_uses_div_or_mod(term: &kernel::Term) -> bool {
2944 use kernel::Term;
2945 match term {
2946 Term::Global(n) => n == "div" || n == "mod",
2947 Term::App(f, a) => term_uses_div_or_mod(f) || term_uses_div_or_mod(a),
2948 Term::Lambda { param_type, body, .. } => {
2949 term_uses_div_or_mod(param_type) || term_uses_div_or_mod(body)
2950 }
2951 Term::Pi { param_type, body_type, .. } => {
2952 term_uses_div_or_mod(param_type) || term_uses_div_or_mod(body_type)
2953 }
2954 Term::Fix { body, .. } => term_uses_div_or_mod(body),
2955 Term::Match { discriminant, motive, cases } => {
2956 term_uses_div_or_mod(discriminant)
2957 || term_uses_div_or_mod(motive)
2958 || cases.iter().any(term_uses_div_or_mod)
2959 }
2960 _ => false,
2961 }
2962}
2963
2964fn math_demo_main(
2968 ctx: &kernel::Context,
2969 entries: &[String],
2970 checks: &[(String, Vec<kernel::Term>)],
2971) -> String {
2972 use kernel::Term;
2973 let mut lines = Vec::new();
2974 for name in entries {
2975 let Some(body) = ctx.get_definition_body(name) else {
2976 continue; };
2978 if term_uses_div_or_mod(body) {
2983 continue;
2984 }
2985 if matches!(body, Term::Lambda { .. } | Term::Fix { .. }) {
2986 let Some(ty) = ctx.get_definition_type(name) else { continue };
2988 let params = pi_param_types(ty);
2989 let samples: Option<Vec<Term>> =
2990 params.iter().map(|p| sample_value(ctx, p)).collect();
2991 let Some(samples) = samples else { continue }; if samples.is_empty() {
2993 continue;
2994 }
2995 let args_rust: Vec<String> =
2996 samples.iter().map(|s| crate::extraction::emit_value(ctx, s)).collect();
2997 let mut app = Term::Global(name.clone());
2998 for s in &samples {
2999 app = Term::App(Box::new(app), Box::new(s.clone()));
3000 }
3001 let expected = crate::extraction::emit_value(ctx, &kernel::normalize(ctx, &app));
3002 let call = format!("{}({})", name, args_rust.join(", "));
3003 lines.push(format!(" assert_eq!({call}, {expected});"));
3004 lines.push(format!(" println!(\"{name}(..) = {{:?}}\", {call});"));
3005 } else {
3006 let expected = crate::extraction::emit_value(ctx, &kernel::normalize(ctx, body));
3008 lines.push(format!(" assert_eq!({name}(), {expected});"));
3009 lines.push(format!(" println!(\"{name} = {{:?}}\", {name}());"));
3010 }
3011 }
3012 for (name, param_types) in checks {
3014 let samples: Option<Vec<kernel::Term>> =
3015 param_types.iter().map(|p| sample_value(ctx, p)).collect();
3016 let Some(samples) = samples else { continue };
3017 let args: Vec<String> =
3018 samples.iter().map(|s| crate::extraction::emit_value(ctx, s)).collect();
3019 let call = format!("check_{}({})", name, args.join(", "));
3020 lines.push(format!(" assert!({call}, \"theorem {name} failed on sample\");"));
3021 lines.push(format!(" println!(\"\\u{{2713}} {name} holds (checked on a sample)\");"));
3022 }
3023 if lines.is_empty() {
3024 return "\nfn main() {}\n".to_string();
3025 }
3026 format!("\nfn main() {{\n{}\n}}\n", lines.join("\n"))
3027}
3028
3029fn pi_param_types(ty: &kernel::Term) -> Vec<kernel::Term> {
3031 use kernel::Term;
3032 let mut params = Vec::new();
3033 let mut cur = ty;
3034 while let Term::Pi { param_type, body_type, .. } = cur {
3035 params.push((**param_type).clone());
3036 cur = body_type;
3037 }
3038 params
3039}
3040
3041fn sample_value(ctx: &kernel::Context, ty: &kernel::Term) -> Option<kernel::Term> {
3044 use kernel::{Literal, Term};
3045 match ty {
3046 Term::Global(name) => match name.as_str() {
3047 "Int" => Some(Term::Lit(Literal::Int(0))),
3048 "Float" => Some(Term::Lit(Literal::Float(0.0))),
3049 "Text" => Some(Term::Lit(Literal::Text(String::new()))),
3050 _ if ctx.is_inductive(name) => ctx
3052 .get_constructors(name)
3053 .into_iter()
3054 .find(|(_, cty)| !matches!(cty, Term::Pi { .. }))
3055 .map(|(cname, _)| Term::Global(cname.to_string())),
3056 _ => None,
3057 },
3058 _ => None,
3059 }
3060}
3061
3062pub fn extract_math_rust_from_source(input: &str) -> String {
3067 let mut repl = kernel::interface::Repl::new();
3068 let stmts = parse_math_statements(input);
3073 let _ = repl.execute_batch(&stmts);
3074 match extract_math_rust(repl.context()) {
3075 Ok(rust) => rust,
3076 Err(e) => format!("// extraction error: {e}"),
3077 }
3078}
3079
3080pub fn extract_math_module_from_source(input: &str) -> String {
3083 let mut repl = kernel::interface::Repl::new();
3084 let stmts = parse_math_statements(input);
3089 let _ = repl.execute_batch(&stmts);
3090 match extract_math_module(repl.context()) {
3091 Ok(rust) => rust,
3092 Err(e) => format!("// extraction error: {e}"),
3093 }
3094}
3095
3096pub fn implicit_main(source: &str) -> Option<String> {
3117 if source.lines().any(|l| l.trim_start().starts_with("##")) {
3118 return None;
3119 }
3120 let first = source.lines().find(|l| !l.trim().is_empty())?;
3121 const STATEMENT_HEADS: &[&str] = &[
3122 "Let ", "Show ", "Set ", "If ", "While ", "Repeat ", "Push ",
3123 "Pop ", "Add ", "Remove ", "Call ", "Return ", "Increase ",
3124 "Decrease ", "Break", "Inspect ", "Assert ",
3125 ];
3126 let t = first.trim_start();
3127 if STATEMENT_HEADS.iter().any(|h| t.starts_with(h)) {
3128 Some(format!("## Main\n{}", source))
3129 } else {
3130 None
3131 }
3132}
3133
3134pub fn partition_mixed(source: &str) -> (String, Option<String>) {
3135 if let Some(wrapped) = implicit_main(source) {
3136 return (wrapped, None);
3137 }
3138 let lines: Vec<&str> = source.lines().collect();
3139 let mut is_math = vec![false; lines.len()];
3140 let mut i = 0;
3141 let mut any = false;
3142 while i < lines.len() {
3143 let t = lines[i].trim();
3144 if !is_math_block_start(t) {
3145 i += 1;
3146 continue;
3147 }
3148 any = true;
3149 if t.starts_with("## Theorem:") || t.starts_with("## Lemma:") {
3150 is_math[i] = true;
3152 i += 1;
3153 while i < lines.len() {
3154 let nt = lines[i].trim();
3155 let indented = lines[i].starts_with(' ') || lines[i].starts_with('\t');
3156 if nt.is_empty() {
3157 is_math[i] = true;
3158 i += 1;
3159 continue;
3160 }
3161 if indented || nt.starts_with("Statement:") || nt.starts_with("Proof:") {
3162 is_math[i] = true;
3163 i += 1;
3164 if nt.starts_with("Proof:") && nt.ends_with('.') {
3165 break;
3166 }
3167 } else {
3168 break;
3169 }
3170 }
3171 } else {
3172 let mut ended = t.ends_with('.');
3177 is_math[i] = true;
3178 i += 1;
3179 while !ended && i < lines.len() {
3180 if lines[i].trim_start().starts_with("## ") {
3181 break;
3182 }
3183 is_math[i] = true;
3184 ended = lines[i].trim().ends_with('.');
3185 i += 1;
3186 }
3187 }
3188 }
3189 if !any {
3190 return (source.to_string(), None);
3191 }
3192 let imp: Vec<&str> = lines
3193 .iter()
3194 .enumerate()
3195 .map(|(j, l)| if is_math[j] { "" } else { *l })
3196 .collect();
3197 let math: Vec<&str> = lines
3198 .iter()
3199 .enumerate()
3200 .filter(|(j, _)| is_math[*j])
3201 .map(|(_, l)| *l)
3202 .collect();
3203 (imp.join("\n"), Some(math.join("\n")))
3204}
3205
3206fn is_math_block_start(trimmed: &str) -> bool {
3207 const COQ: [&str; 6] = [
3208 "Definition ", "Inductive ", "Axiom ", "Theorem ", "Lemma ", "Fixpoint ",
3209 ];
3210 COQ.iter().any(|k| trimmed.starts_with(k))
3211 || trimmed.starts_with("## Theorem:")
3212 || trimmed.starts_with("## Lemma:")
3213}
3214
3215pub(crate) fn mixed_proven_module(math_src: &str) -> Option<String> {
3225 let mut repl = kernel::interface::Repl::new();
3226 let stmts = parse_math_statements(math_src);
3228 let _ = repl.execute_batch(&stmts);
3229 let ctx = repl.context();
3230 let mut rust = match extract_math_module(ctx) {
3231 Ok(r) => r,
3232 Err(_) => return None,
3233 };
3234 if !(rust.contains("pub fn") || rust.contains("pub enum") || rust.contains("pub struct")) {
3235 return None;
3236 }
3237 let base = baseline_names();
3240 let mut inds: Vec<String> = Vec::new();
3241 for (name, _) in ctx.iter_inductives() {
3242 if !base.contains(name)
3243 && inductive_is_emittable(ctx, name)
3244 && inductive_generics(ctx, name).is_empty()
3245 {
3246 inds.push(name.to_string());
3247 }
3248 }
3249 inds.sort();
3250 for ind in inds {
3251 rust.push_str(&format!(
3252 "impl Showable for {ind} {{ fn format_show(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{ write!(f, \"{{:?}}\", self) }} }}\n"
3253 ));
3254 }
3255 Some(rust)
3256}
3257
3258pub fn parse_math_statements(code: &str) -> Vec<String> {
3263 let mut statements = Vec::new();
3264 let lines: Vec<&str> = code.lines().collect();
3265 let mut i = 0;
3266
3267 while i < lines.len() {
3268 let line = lines[i];
3269 let trimmed = line.trim();
3270
3271 if trimmed.is_empty() || trimmed.starts_with("--") {
3273 i += 1;
3274 continue;
3275 }
3276
3277 if trimmed.starts_with("## To ") {
3279 let mut block = String::new();
3280 block.push_str(trimmed);
3281 i += 1;
3282
3283 while i < lines.len() {
3284 let next_line = lines[i];
3285 let next_trimmed = next_line.trim();
3286
3287 if next_trimmed.is_empty() {
3288 i += 1;
3289 continue;
3290 }
3291 if next_trimmed.starts_with("--") {
3292 i += 1;
3293 continue;
3294 }
3295
3296 let is_indented = next_line.starts_with(' ') || next_line.starts_with('\t');
3297 let is_continuation = next_trimmed.starts_with("Consider ")
3298 || next_trimmed.starts_with("When ")
3299 || next_trimmed.starts_with("Yield ");
3300
3301 if is_indented || is_continuation {
3302 block.push(' ');
3303 block.push_str(next_trimmed);
3304 i += 1;
3305 } else {
3306 break;
3307 }
3308 }
3309
3310 statements.push(block);
3311 continue;
3312 }
3313
3314 if trimmed.starts_with("## Theorem:") {
3316 let mut block = String::new();
3317 block.push_str(trimmed);
3318 i += 1;
3319
3320 while i < lines.len() {
3321 let next_line = lines[i];
3322 let next_trimmed = next_line.trim();
3323
3324 if next_trimmed.is_empty() {
3325 i += 1;
3326 continue;
3327 }
3328 if next_trimmed.starts_with("--") {
3329 i += 1;
3330 continue;
3331 }
3332
3333 let is_indented = next_line.starts_with(' ') || next_line.starts_with('\t');
3334 let is_theorem_part = next_trimmed.starts_with("Statement:")
3335 || next_trimmed.starts_with("Proof:");
3336
3337 if is_indented || is_theorem_part {
3338 block.push('\n');
3339 block.push_str(next_line);
3340 i += 1;
3341 if next_trimmed.starts_with("Proof:") && next_trimmed.ends_with('.') {
3342 break;
3343 }
3344 } else {
3345 break;
3346 }
3347 }
3348
3349 statements.push(block);
3350 continue;
3351 }
3352
3353 if (trimmed.starts_with("A ") || trimmed.starts_with("An ")) && trimmed.contains(" is either")
3355 {
3356 if trimmed.ends_with('.') && !trimmed.trim_end_matches('.').ends_with(':') {
3357 statements.push(trimmed.to_string());
3358 i += 1;
3359 continue;
3360 }
3361
3362 let mut block = String::new();
3363 block.push_str(trimmed);
3364 i += 1;
3365
3366 while i < lines.len() {
3367 let next_line = lines[i];
3368 let next_trimmed = next_line.trim();
3369
3370 if next_trimmed.is_empty() {
3371 i += 1;
3372 continue;
3373 }
3374 if next_trimmed.starts_with("--") {
3375 i += 1;
3376 continue;
3377 }
3378
3379 let is_indented = next_line.starts_with(' ') || next_line.starts_with('\t');
3380 let looks_like_variant = next_trimmed.starts_with("a ")
3381 || next_trimmed
3382 .chars()
3383 .next()
3384 .map(|c| c.is_uppercase())
3385 .unwrap_or(false);
3386
3387 if is_indented
3388 || (looks_like_variant
3389 && !next_trimmed.starts_with("A ")
3390 && !next_trimmed.starts_with("An "))
3391 {
3392 if !block.ends_with(':') {
3393 block.push_str(" or ");
3394 } else {
3395 block.push(' ');
3396 }
3397 block.push_str(next_trimmed.trim_end_matches('.'));
3398 i += 1;
3399 } else {
3400 break;
3401 }
3402 }
3403
3404 if !block.ends_with('.') {
3405 block.push('.');
3406 }
3407 statements.push(block);
3408 continue;
3409 }
3410
3411 let mut current_stmt = String::new();
3413 while i < lines.len() {
3414 let line = lines[i];
3415 let trimmed = line.trim();
3416
3417 if trimmed.is_empty() || trimmed.starts_with("--") {
3418 i += 1;
3419 continue;
3420 }
3421
3422 if !current_stmt.is_empty() {
3423 current_stmt.push(' ');
3424 }
3425 current_stmt.push_str(trimmed);
3426 i += 1;
3427
3428 if trimmed.ends_with('.') {
3429 break;
3430 }
3431 }
3432
3433 if !current_stmt.is_empty() {
3434 statements.push(current_stmt);
3435 }
3436 }
3437
3438 statements
3439}
3440
3441pub fn extract_logic_rust(input: &str) -> Result<String, String> {
3450 extract_logic_impl(input, true)
3451}
3452
3453pub fn extract_logic_module(input: &str) -> Result<String, String> {
3457 extract_logic_impl(input, false)
3458}
3459
3460fn extract_logic_impl(input: &str, emit_main: bool) -> Result<String, String> {
3461 use crate::extraction::fol_model::{fol_to_model_checker, fol_to_model_checker_module};
3462 let emit = |premises: &[logicaffeine_proof::ProofExpr],
3463 goal: &logicaffeine_proof::ProofExpr,
3464 english: &str,
3465 fol: &str| {
3466 if emit_main {
3467 fol_to_model_checker(premises, goal, english, fol)
3468 } else {
3469 fol_to_model_checker_module(premises, goal, english, fol)
3470 }
3471 };
3472
3473 if let Ok((premises, goal)) = theorem_proof_exprs(input) {
3476 if looks_like_grid(&premises) {
3477 return Ok("// this is a finite-domain puzzle — run it with Execute. \
3478 Compiling it to Rust would run the full solver synchronously."
3479 .to_string());
3480 }
3481 let fol = if premises.is_empty() {
3482 goal.to_string()
3483 } else {
3484 format!(
3485 "{} ⊢ {}",
3486 premises.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", "),
3487 goal
3488 )
3489 };
3490 return Ok(emit(&premises, &goal, input, &fol));
3491 }
3492
3493 let proof = compile_for_proof(input);
3495 if let Some(expr) = proof.proof_expr {
3496 let fol = proof.logic_string.clone().unwrap_or_else(|| expr.to_string());
3497 return Ok(emit(&[], &expr, input, &fol));
3498 }
3499
3500 Ok("// could not parse this input into a logical formula to compile".to_string())
3501}
3502
3503#[cfg(feature = "verification")]
3514fn smt_theory_for(
3515 premises: &[ProofExpr],
3516 goal: Option<&ProofExpr>,
3517) -> logicaffeine_proof::oracle::SmtTheory {
3518 let mut exprs: Vec<ProofExpr> = premises.to_vec();
3519 if let Some(g) = goal {
3520 exprs.push(g.clone());
3521 }
3522 let cumulative_predicates = logicaffeine_proof::oracle::predicate_names(&exprs)
3523 .into_iter()
3524 .filter(|name| logicaffeine_language::lexicon::is_mass_noun(name))
3525 .collect();
3526 logicaffeine_proof::oracle::SmtTheory {
3527 cumulative_predicates,
3528 }
3529}
3530
3531#[cfg(feature = "verification")]
3532pub fn check_theorem_smt(
3533 input: &str,
3534) -> Result<logicaffeine_proof::oracle::SmtVerdict, ParseError> {
3535 let (proof_exprs, goal_expr) = theorem_proof_exprs(input)?;
3536 let theory = smt_theory_for(&proof_exprs, Some(&goal_expr));
3537 Ok(logicaffeine_proof::oracle::oracle_entails_with_theory(
3538 &proof_exprs,
3539 &goal_expr,
3540 &theory,
3541 ))
3542}
3543
3544#[cfg(feature = "verification")]
3550pub fn check_theorem_premises_consistent(
3551 input: &str,
3552) -> Result<logicaffeine_proof::oracle::SmtConsistency, ParseError> {
3553 let (proof_exprs, _goal) = theorem_proof_exprs(input)?;
3554 let theory = smt_theory_for(&proof_exprs, None);
3555 Ok(logicaffeine_proof::oracle::oracle_consistent_with_theory(
3556 &proof_exprs,
3557 &theory,
3558 ))
3559}
3560
3561#[cfg(feature = "verification")]
3569pub fn check_theorem_defeasible(
3570 input: &str,
3571) -> Result<logicaffeine_proof::oracle::SmtVerdict, ParseError> {
3572 let (proof_exprs, goal, defaults, _definitions) = theorem_problem(input, true)?;
3573 let theory = smt_theory_for(&proof_exprs, Some(&goal));
3574 Ok(crate::defeasible::defeasible_entails(
3575 &proof_exprs,
3576 &goal,
3577 &defaults,
3578 &theory,
3579 ))
3580}
3581
3582#[cfg(feature = "verification")]
3585pub fn check_theorem_defeasible_consistent(
3586 input: &str,
3587) -> Result<logicaffeine_proof::oracle::SmtConsistency, ParseError> {
3588 let (proof_exprs, _goal, defaults, _definitions) = theorem_problem(input, true)?;
3589 let theory = smt_theory_for(&proof_exprs, None);
3590 Ok(crate::defeasible::defeasible_consistent(
3591 &proof_exprs,
3592 &defaults,
3593 &theory,
3594 ))
3595}
3596
3597pub fn theorem_proof_exprs(input: &str) -> Result<(Vec<ProofExpr>, ProofExpr), ParseError> {
3604 let (premises, goal, _defaults, _definitions) = theorem_problem(input, false)?;
3605 Ok((premises, goal))
3606}
3607
3608pub fn theorem_proof_exprs_with_defs(
3612 input: &str,
3613) -> Result<
3614 (
3615 Vec<ProofExpr>,
3616 ProofExpr,
3617 Vec<logicaffeine_proof::verify::Definition>,
3618 ),
3619 ParseError,
3620> {
3621 let (premises, goal, _defaults, definitions) = theorem_problem(input, false)?;
3622 Ok((premises, goal, definitions))
3623}
3624
3625pub fn theorem_dependency_graph(
3630 input: &str,
3631) -> Result<logicaffeine_proof::verify::DependencyGraph, ParseError> {
3632 let (premises, goal, definitions) = theorem_proof_exprs_with_defs(input)?;
3633 Ok(logicaffeine_proof::verify::dependency_graph(
3634 &definitions,
3635 &premises,
3636 &goal,
3637 ))
3638}
3639
3640pub fn answer_question(input: &str) -> Result<Vec<String>, ParseError> {
3648 let (premises, goal) = theorem_proof_exprs(input)?;
3649 match &goal {
3650 ProofExpr::Exists { variable, body } => Ok(answer_wh(&premises, variable, body)),
3651 _ => Err(ParseError {
3652 kind: logicaffeine_language::error::ParseErrorKind::Custom(
3653 "Prove goal is not a question (expected a wh-question ∃-form)".to_string(),
3654 ),
3655 span: logicaffeine_language::token::Span::default(),
3656 }),
3657 }
3658}
3659
3660fn answer_wh(premises: &[ProofExpr], var: &str, body: &ProofExpr) -> Vec<String> {
3665 let mut candidates: Vec<String> = Vec::new();
3666 for p in premises {
3667 collect_constants(p, &mut candidates);
3668 }
3669 candidates.sort();
3670 candidates.dedup();
3671 let trace = std::env::var("LOGOS_TRACE").is_ok();
3675 let t0 = trace.then(std::time::Instant::now);
3676 let prepared = prepare_premises(premises);
3677 if let Some(t0) = t0 {
3678 eprintln!(
3679 "[answer] {} premises → prepared ({} clauses) in {:.2?}; {} candidates",
3680 premises.len(),
3681 prepared.len(),
3682 t0.elapsed(),
3683 candidates.len()
3684 );
3685 }
3686 let mut answers = Vec::new();
3687 for c in &candidates {
3688 let tc = trace.then(std::time::Instant::now);
3689 let candidate_goal =
3690 logicaffeine_language::proof_convert::instantiate_var_with_constant(body, var, c);
3691 let ok = candidate_entailed_prepared(&prepared, &candidate_goal);
3692 if let Some(tc) = tc {
3693 eprintln!("[answer] {:<14} {} ({:.2?})", c, ok, tc.elapsed());
3694 }
3695 if ok {
3696 answers.push(c.clone());
3697 }
3698 }
3699 if let Some(t0) = t0 {
3700 eprintln!("[answer] total {:.2?} → {:?}", t0.elapsed(), answers);
3701 }
3702 answers
3703}
3704
3705pub fn solve_grid(input: &str) -> Option<SolvedGrid> {
3709 let parsed = parse_theorem(input).ok()?;
3710 if !looks_like_grid(&parsed.premises) {
3711 return None;
3712 }
3713 solve_grid_from_premises(&parsed.premises, input)
3714}
3715
3716struct GridClosure {
3719 var: String,
3720 row_sort: String,
3721 disjuncts: Vec<(String, ProofExpr)>,
3722}
3723
3724fn flatten_or<'a>(e: &'a ProofExpr, out: &mut Vec<&'a ProofExpr>) {
3726 match e {
3727 ProofExpr::Or(l, r) => {
3728 flatten_or(l, out);
3729 flatten_or(r, out);
3730 }
3731 other => out.push(other),
3732 }
3733}
3734
3735fn antecedent_sort(e: &ProofExpr, var: &str) -> Option<String> {
3738 let is_var = |t: &ProofTerm| matches!(t, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == var);
3739 match e {
3740 ProofExpr::Predicate { name, args, .. } if args.len() == 1 && is_var(&args[0]) => {
3741 Some(name.clone())
3742 }
3743 ProofExpr::And(l, r) => antecedent_sort(l, var).or_else(|| antecedent_sort(r, var)),
3744 _ => None,
3745 }
3746}
3747
3748fn disjunct_value(d: &ProofExpr, var: &str) -> Option<String> {
3752 let is_var = |t: &ProofTerm| matches!(t, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == var);
3753 match d {
3754 ProofExpr::Predicate { name, args, .. } => match args.as_slice() {
3755 [a] if is_var(a) => Some(name.clone()),
3756 [a, ProofTerm::Constant(c)] if is_var(a) => Some(c.clone()),
3757 [ProofTerm::Constant(c), a] if is_var(a) => Some(c.clone()),
3758 _ => None,
3759 },
3760 _ => None,
3761 }
3762}
3763
3764fn extract_grid_closures(premises: &[ProofExpr]) -> Vec<GridClosure> {
3768 fn from_forall(e: &ProofExpr, out: &mut Vec<GridClosure>) {
3769 if let ProofExpr::ForAll { variable, body } = e {
3770 match body.as_ref() {
3771 ProofExpr::Implies(ante, cons) => {
3772 let mut leaves = Vec::new();
3773 flatten_or(cons, &mut leaves);
3774 let disjuncts: Vec<(String, ProofExpr)> = leaves
3775 .iter()
3776 .filter_map(|d| disjunct_value(d, variable).map(|v| (v, (*d).clone())))
3777 .collect();
3778 if let Some(row_sort) = antecedent_sort(ante, variable) {
3779 if !disjuncts.is_empty() && disjuncts.len() == leaves.len() {
3782 out.push(GridClosure { var: variable.clone(), row_sort, disjuncts });
3783 }
3784 }
3785 }
3786 ProofExpr::ForAll { .. } => from_forall(body, out),
3787 _ => {}
3788 }
3789 }
3790 }
3791 let mut out = Vec::new();
3792 for p in premises {
3793 from_forall(&erase_tense(p), &mut out);
3794 }
3795 out
3796}
3797
3798fn title_case(s: &str) -> String {
3800 let mut chars = s.chars();
3801 match chars.next() {
3802 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
3803 None => String::new(),
3804 }
3805}
3806
3807fn grid_column_label(
3810 values: &[String],
3811 sorts: &std::collections::HashMap<String, Vec<ProofTerm>>,
3812 idx: usize,
3813) -> String {
3814 let want: Vec<String> = values.iter().map(|v| v.to_lowercase()).collect();
3815 let mut keys: Vec<&String> = sorts.keys().collect();
3816 keys.sort();
3817 for k in keys {
3818 let dom: std::collections::HashSet<String> = sorts[k]
3819 .iter()
3820 .filter_map(|t| match t {
3821 ProofTerm::Constant(c) => Some(c.to_lowercase()),
3822 _ => None,
3823 })
3824 .collect();
3825 if !dom.is_empty() && want.iter().all(|v| dom.contains(v)) {
3826 return title_case(k);
3827 }
3828 }
3829 format!("Category {}", idx + 1)
3830}
3831
3832fn category_stem(s: &str) -> String {
3837 let mut w = s.to_lowercase();
3838 if w.len() > 5 && w.ends_with("ing") {
3839 w.truncate(w.len() - 3);
3840 }
3841 if w.len() > 3 && w.ends_with('e') {
3842 w.truncate(w.len() - 1);
3843 }
3844 w
3845}
3846
3847fn surface_form_map(input: &str) -> std::collections::HashMap<String, String> {
3853 let mut map = std::collections::HashMap::new();
3854 for word in input.split(|c: char| !c.is_alphanumeric()) {
3855 if !word.is_empty() {
3856 map.entry(category_stem(word)).or_insert_with(|| word.to_string());
3857 }
3858 }
3859 map
3860}
3861
3862fn solve_grid_from_premises(premises: &[ProofExpr], input: &str) -> Option<SolvedGrid> {
3867 let closures = extract_grid_closures(premises);
3868 if closures.is_empty() {
3869 return None;
3870 }
3871 let untensed: Vec<ProofExpr> = premises.iter().map(erase_tense).collect();
3872 let sorts = logicaffeine_proof::grounding::sort_domains(&untensed);
3873 let row_sort = closures[0].row_sort.clone();
3874 let rows: Vec<String> = sorts
3875 .get(&row_sort)
3876 .map(|dom| {
3877 dom.iter()
3878 .filter_map(|t| match t {
3879 ProofTerm::Constant(c) => Some(c.clone()),
3880 _ => None,
3881 })
3882 .collect()
3883 })
3884 .unwrap_or_default();
3885 if rows.is_empty() {
3886 return None;
3887 }
3888 let prepared = prepare_premises_opts(premises, true);
3891 let surface = surface_form_map(input);
3892 let display = |v: &str| surface.get(&category_stem(v)).cloned().unwrap_or_else(|| v.to_string());
3893 let mut columns = Vec::new();
3894 for clo in &closures {
3895 if clo.row_sort != row_sort {
3896 continue;
3897 }
3898 let values: Vec<String> = clo.disjuncts.iter().map(|(v, _)| display(v)).collect();
3899 let mut cells = Vec::with_capacity(rows.len());
3900 for r in &rows {
3901 let mut found = None;
3902 for (label, dj) in &clo.disjuncts {
3903 let atom = erase_tense(
3904 &logicaffeine_language::proof_convert::instantiate_var_with_constant(
3905 dj, &clo.var, r,
3906 ),
3907 );
3908 if candidate_entailed_prepared(&prepared, &atom) {
3909 found = Some(display(label));
3910 break;
3911 }
3912 }
3913 cells.push(found);
3914 }
3915 let label = grid_column_label(&values, &sorts, columns.len());
3916 columns.push(GridColumn { label, values, cells });
3917 }
3918 Some(SolvedGrid { row_label: title_case(&row_sort), rows, columns })
3919}
3920
3921fn looks_like_grid(premises: &[ProofExpr]) -> bool {
3940 fn has_disjunctive_closure(e: &ProofExpr) -> bool {
3941 match e {
3942 ProofExpr::ForAll { body, .. } => match body.as_ref() {
3943 ProofExpr::Implies(_, c) => matches!(c.as_ref(), ProofExpr::Or(..)),
3944 ProofExpr::ForAll { .. } => has_disjunctive_closure(body),
3945 _ => false,
3946 },
3947 ProofExpr::Temporal { body, .. } => has_disjunctive_closure(body),
3948 _ => false,
3949 }
3950 }
3951 !logicaffeine_proof::grounding::at_most_one_lemmas(premises).is_empty()
3952 || premises.iter().any(has_disjunctive_closure)
3953}
3954
3955fn prepare_premises(premises: &[ProofExpr]) -> Vec<ProofExpr> {
3956 prepare_premises_opts(premises, false)
3957}
3958
3959fn prepare_premises_opts(premises: &[ProofExpr], with_functionality: bool) -> Vec<ProofExpr> {
3964 let mut untensed: Vec<ProofExpr> = premises.iter().map(erase_tense).collect();
3965 let lemmas = logicaffeine_proof::grounding::at_most_one_lemmas(&untensed);
3969 untensed.extend(lemmas);
3970 let defns = logicaffeine_proof::grounding::definite_property_implications(&untensed);
3975 untensed.extend(defns);
3976 if with_functionality {
3977 let func = logicaffeine_proof::grounding::functionality_lemmas(&untensed);
3978 untensed.extend(func);
3979 let cols = logicaffeine_proof::grounding::column_closure_lemmas(&untensed);
3984 untensed.extend(cols);
3985 }
3986 let fallback = logicaffeine_proof::grounding::domain_constants(&untensed);
3987 let mut sorts = logicaffeine_proof::grounding::sort_domains(&untensed);
3988 bind_occasion_synonyms_to_row_domain(&untensed, &mut sorts);
3989 let grounded: Vec<ProofExpr> = untensed
3990 .iter()
3991 .map(|p| logicaffeine_proof::grounding::ground_sorted(p, &sorts, &fallback))
3992 .collect();
3993 let discharged = logicaffeine_proof::grounding::discharge_unary_facts(&grounded);
3997 logicaffeine_proof::grounding::simplify_trivial_identities(&discharged)
4000}
4001
4002#[cfg(not(feature = "verification"))]
4009fn candidate_entailed_prepared(prepared: &[ProofExpr], goal: &ProofExpr) -> bool {
4010 let g = erase_tense(goal);
4011 match logicaffeine_proof::rup::entails_certified(prepared, &g) {
4016 Some(logicaffeine_proof::rup::Verdict::Entailed) => return true,
4017 Some(logicaffeine_proof::rup::Verdict::NotEntailed) => return false,
4018 None => {}
4019 }
4020 logicaffeine_proof::verify::prove_certify_check_bounded(prepared, &g, 100).verified
4021}
4022
4023fn bind_occasion_synonyms_to_row_domain(
4040 premises: &[ProofExpr],
4041 sorts: &mut std::collections::HashMap<String, Vec<logicaffeine_proof::ProofTerm>>,
4042) {
4043 use logicaffeine_language::lexicon::lookup_sort;
4044 use logicaffeine_proof::ProofTerm;
4045 let is_occasion = |n: &str| lookup_sort(n).map_or(false, |s| s.is_occasion());
4046
4047 let mut row_domain: Vec<ProofTerm> = Vec::new();
4050 for (name, dom) in sorts.iter() {
4051 if is_occasion(name) {
4052 for c in dom {
4053 if !row_domain.contains(c) {
4054 row_domain.push(c.clone());
4055 }
4056 }
4057 }
4058 }
4059 if row_domain.is_empty() {
4060 return;
4061 }
4062
4063 let mut names = HashSet::new();
4066 for p in premises {
4067 collect_unary_predicate_names(p, &mut names);
4068 }
4069 for name in names {
4070 if is_occasion(&name) {
4071 sorts.entry(name).or_insert_with(|| row_domain.clone());
4072 }
4073 }
4074}
4075
4076fn collect_unary_predicate_names(e: &ProofExpr, out: &mut HashSet<String>) {
4079 match e {
4080 ProofExpr::Predicate { name, args, .. } if args.len() == 1 => {
4081 out.insert(name.clone());
4082 }
4083 ProofExpr::And(l, r)
4084 | ProofExpr::Or(l, r)
4085 | ProofExpr::Implies(l, r)
4086 | ProofExpr::Iff(l, r) => {
4087 collect_unary_predicate_names(l, out);
4088 collect_unary_predicate_names(r, out);
4089 }
4090 ProofExpr::Counterfactual { antecedent, consequent } => {
4091 collect_unary_predicate_names(antecedent, out);
4092 collect_unary_predicate_names(consequent, out);
4093 }
4094 ProofExpr::Not(x)
4095 | ProofExpr::ForAll { body: x, .. }
4096 | ProofExpr::Exists { body: x, .. }
4097 | ProofExpr::Temporal { body: x, .. }
4098 | ProofExpr::Modal { body: x, .. } => collect_unary_predicate_names(x, out),
4099 _ => {}
4100 }
4101}
4102
4103#[cfg(feature = "verification")]
4104fn candidate_entailed_prepared(prepared: &[ProofExpr], goal: &ProofExpr) -> bool {
4105 let g = erase_tense(goal);
4107 match logicaffeine_proof::rup::entails_certified(prepared, &g) {
4111 Some(logicaffeine_proof::rup::Verdict::Entailed) => return true,
4112 Some(logicaffeine_proof::rup::Verdict::NotEntailed) => return false,
4113 None => {}
4114 }
4115 if logicaffeine_proof::verify::prove_certify_check_bounded(prepared, &g, 40).verified {
4116 return true;
4117 }
4118 matches!(
4119 logicaffeine_proof::oracle::oracle_entails(prepared, &g),
4120 logicaffeine_proof::oracle::SmtVerdict::Entailed
4121 )
4122}
4123
4124fn erase_tense(e: &ProofExpr) -> ProofExpr {
4128 match e {
4129 ProofExpr::Temporal { body, .. } => erase_tense(body),
4130 ProofExpr::And(l, r) => {
4131 ProofExpr::And(Box::new(erase_tense(l)), Box::new(erase_tense(r)))
4132 }
4133 ProofExpr::Or(l, r) => ProofExpr::Or(Box::new(erase_tense(l)), Box::new(erase_tense(r))),
4134 ProofExpr::Implies(l, r) => {
4135 ProofExpr::Implies(Box::new(erase_tense(l)), Box::new(erase_tense(r)))
4136 }
4137 ProofExpr::Iff(l, r) => ProofExpr::Iff(Box::new(erase_tense(l)), Box::new(erase_tense(r))),
4138 ProofExpr::Not(x) => ProofExpr::Not(Box::new(erase_tense(x))),
4139 ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
4140 variable: variable.clone(),
4141 body: Box::new(erase_tense(body)),
4142 },
4143 ProofExpr::Exists { variable, body } => ProofExpr::Exists {
4144 variable: variable.clone(),
4145 body: Box::new(erase_tense(body)),
4146 },
4147 other => other.clone(),
4148 }
4149}
4150
4151fn collect_constants(e: &ProofExpr, out: &mut Vec<String>) {
4152 use logicaffeine_proof::ProofTerm;
4153 fn term(t: &ProofTerm, out: &mut Vec<String>) {
4154 match t {
4155 ProofTerm::Constant(s) => out.push(s.clone()),
4156 ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
4157 args.iter().for_each(|a| term(a, out))
4158 }
4159 _ => {}
4160 }
4161 }
4162 match e {
4163 ProofExpr::Predicate { args, .. } => args.iter().for_each(|a| term(a, out)),
4164 ProofExpr::Identity(a, b) => {
4165 term(a, out);
4166 term(b, out);
4167 }
4168 ProofExpr::And(l, r)
4169 | ProofExpr::Or(l, r)
4170 | ProofExpr::Implies(l, r)
4171 | ProofExpr::Iff(l, r) => {
4172 collect_constants(l, out);
4173 collect_constants(r, out);
4174 }
4175 ProofExpr::Not(x) => collect_constants(x, out),
4176 ProofExpr::ForAll { body, .. }
4177 | ProofExpr::Exists { body, .. }
4178 | ProofExpr::Temporal { body, .. } => collect_constants(body, out),
4179 ProofExpr::Term(t) => term(t, out),
4180 _ => {}
4181 }
4182}
4183
4184fn lower_definition(
4191 def: &logicaffeine_language::ast::DefinitionBlock,
4192 interner: &Interner,
4193) -> Option<logicaffeine_proof::verify::Definition> {
4194 use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
4195 let (name, params) = match logic_expr_to_proof_expr(def.definiendum, interner) {
4196 ProofExpr::Predicate { name, args, .. } => {
4197 let params = args
4198 .iter()
4199 .filter_map(|t| match t {
4200 ProofTerm::Constant(n) | ProofTerm::Variable(n) => Some(n.clone()),
4201 _ => None,
4202 })
4203 .collect();
4204 (name, params)
4205 }
4206 _ => return None,
4207 };
4208 let definiens = logic_expr_to_proof_expr(def.definiens, interner);
4209 Some(logicaffeine_proof::verify::Definition {
4210 name,
4211 params,
4212 definiens,
4213 })
4214}
4215
4216fn theorem_problem(
4221 input: &str,
4222 defeasible: bool,
4223) -> Result<
4224 (
4225 Vec<ProofExpr>,
4226 ProofExpr,
4227 Vec<logicaffeine_language::proof_convert::DefaultRule>,
4228 Vec<logicaffeine_proof::verify::Definition>,
4229 ),
4230 ParseError,
4231> {
4232 let mut interner = Interner::new();
4234 let mut lexer = Lexer::new(input, &mut interner);
4235 let tokens = lexer.tokenize();
4236
4237 let mwe_trie = mwe::build_mwe_trie();
4238 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
4239
4240 let type_registry = {
4241 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
4242 discovery.run()
4243 };
4244
4245 let expr_arena = Arena::new();
4246 let term_arena = Arena::new();
4247 let np_arena = Arena::new();
4248 let sym_arena = Arena::new();
4249 let role_arena = Arena::new();
4250 let pp_arena = Arena::new();
4251
4252 let ctx = AstContext::new(
4253 &expr_arena,
4254 &term_arena,
4255 &np_arena,
4256 &sym_arena,
4257 &role_arena,
4258 &pp_arena,
4259 );
4260
4261 let mut world_state = drs::WorldState::new();
4262 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
4263 if defeasible {
4264 parser.set_pragmatic_mode(true);
4267 }
4268 let statements = parser.parse_program()?;
4269
4270 let theorem = statements
4271 .iter()
4272 .find_map(|stmt| {
4273 if let Stmt::Theorem(t) = stmt {
4274 Some(t)
4275 } else {
4276 None
4277 }
4278 })
4279 .ok_or_else(|| ParseError {
4280 kind: logicaffeine_language::error::ParseErrorKind::Custom("No theorem block found in input".to_string()),
4281 span: logicaffeine_language::token::Span::default(),
4282 })?;
4283
4284 let mut defaults = Vec::new();
4286 let proof_exprs: Vec<ProofExpr> = theorem
4287 .premises
4288 .iter()
4289 .map(|premise| {
4290 if defeasible {
4291 logicaffeine_language::proof_convert::logic_expr_to_proof_expr_defeasible(
4292 premise,
4293 &interner,
4294 &mut defaults,
4295 )
4296 } else {
4297 logic_expr_to_proof_expr(premise, &interner)
4298 }
4299 })
4300 .collect();
4301 let goal_expr = logic_expr_to_proof_expr(theorem.goal, &interner);
4302
4303 let definitions: Vec<logicaffeine_proof::verify::Definition> = statements
4306 .iter()
4307 .filter_map(|stmt| match stmt {
4308 Stmt::Definition(d) => lower_definition(d, &interner),
4309 _ => None,
4310 })
4311 .collect();
4312
4313 Ok((proof_exprs, goal_expr, defaults, definitions))
4314}