use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use logicaffeine_base::{Arena, Interner};
use logicaffeine_language::{
ast::{self, LogicExpr, Term},
analysis::DiscoveryPass,
arena_ctx::AstContext,
compile::{compile_forest, compile_forest_with_options},
drs,
error::socratic_explanation,
lexer::Lexer,
mwe,
parser::Parser,
pragmatics,
registry::SymbolRegistry,
semantics,
token::TokenType,
CompileOptions, OutputFormat, ParseError,
};
use logicaffeine_proof::{DerivationTree, ProofExpr, ProofTerm};
pub use crate::interpreter::InterpreterResult;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TokenCategory {
Quantifier,
Noun,
Verb,
Adjective,
Connective,
Determiner,
Preposition,
Pronoun,
Modal,
Punctuation,
Proper,
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenInfo {
pub start: usize,
pub end: usize,
pub text: String,
pub category: TokenCategory,
}
fn categorize_token(kind: &TokenType, _interner: &Interner) -> TokenCategory {
match kind {
TokenType::All | TokenType::Some | TokenType::No | TokenType::Any
| TokenType::Most | TokenType::Few | TokenType::Many
| TokenType::Cardinal(_) | TokenType::AtLeast(_) | TokenType::AtMost(_) => TokenCategory::Quantifier,
TokenType::Noun(_) => TokenCategory::Noun,
TokenType::Verb { .. } => TokenCategory::Verb,
TokenType::Adjective(_) | TokenType::NonIntersectiveAdjective(_) => TokenCategory::Adjective,
TokenType::And | TokenType::Or | TokenType::Not | TokenType::If | TokenType::Then
| TokenType::Iff | TokenType::Because => TokenCategory::Connective,
TokenType::Article(_) => TokenCategory::Determiner,
TokenType::Preposition(_) => TokenCategory::Preposition,
TokenType::Pronoun { .. } => TokenCategory::Pronoun,
TokenType::Must | TokenType::Can | TokenType::Should | TokenType::Shall
| TokenType::Would | TokenType::Could | TokenType::May | TokenType::Cannot
| TokenType::Might => TokenCategory::Modal,
TokenType::Period | TokenType::Comma => TokenCategory::Punctuation,
TokenType::ProperName(_) => TokenCategory::Proper,
_ => TokenCategory::Other,
}
}
pub fn tokenize_for_ui(input: &str) -> Vec<TokenInfo> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
tokens.iter().map(|t| TokenInfo {
start: t.span.start,
end: t.span.end,
text: input[t.span.start..t.span.end].to_string(),
category: categorize_token(&t.kind, &interner),
}).collect()
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AstNode {
pub label: String,
pub node_type: String,
pub children: Vec<AstNode>,
}
impl AstNode {
pub fn leaf(label: &str, node_type: &str) -> Self {
AstNode {
label: label.to_string(),
node_type: node_type.to_string(),
children: Vec::new(),
}
}
pub fn with_children(label: &str, node_type: &str, children: Vec<AstNode>) -> Self {
AstNode {
label: label.to_string(),
node_type: node_type.to_string(),
children,
}
}
}
pub fn expr_to_ast_node(expr: &LogicExpr, interner: &Interner) -> AstNode {
match expr {
LogicExpr::Predicate { name, args, .. } => {
let name_str = interner.resolve(*name);
let arg_nodes: Vec<AstNode> = args.iter()
.map(|t| term_to_ast_node(t, interner))
.collect();
AstNode::with_children(
&format!("{}({})", name_str, args.len()),
"predicate",
arg_nodes,
)
}
LogicExpr::Quantifier { kind, variable, body, .. } => {
let var_str = interner.resolve(*variable);
let symbol = match kind {
ast::QuantifierKind::Universal => "∀",
ast::QuantifierKind::Existential => "∃",
ast::QuantifierKind::Most => "MOST",
ast::QuantifierKind::Few => "FEW",
ast::QuantifierKind::Many => "MANY",
ast::QuantifierKind::Cardinal(n) => return AstNode::with_children(
&format!("∃={}{}", n, var_str),
"quantifier",
vec![expr_to_ast_node(body, interner)],
),
ast::QuantifierKind::AtLeast(n) => return AstNode::with_children(
&format!("∃≥{}{}", n, var_str),
"quantifier",
vec![expr_to_ast_node(body, interner)],
),
ast::QuantifierKind::AtMost(n) => return AstNode::with_children(
&format!("∃≤{}{}", n, var_str),
"quantifier",
vec![expr_to_ast_node(body, interner)],
),
ast::QuantifierKind::Generic => "GEN",
};
AstNode::with_children(
&format!("{}{}", symbol, var_str),
"quantifier",
vec![expr_to_ast_node(body, interner)],
)
}
LogicExpr::BinaryOp { left, op, right } => {
let op_str = match op {
TokenType::And => "∧",
TokenType::Or => "∨",
TokenType::If | TokenType::Then => "→",
TokenType::Iff => "↔",
_ => "?",
};
AstNode::with_children(
op_str,
"binary_op",
vec![
expr_to_ast_node(left, interner),
expr_to_ast_node(right, interner),
],
)
}
LogicExpr::UnaryOp { op, operand } => {
let op_str = match op {
TokenType::Not => "¬",
_ => "?",
};
AstNode::with_children(
op_str,
"unary_op",
vec![expr_to_ast_node(operand, interner)],
)
}
LogicExpr::Identity { left, right } => {
AstNode::with_children(
"=",
"identity",
vec![
term_to_ast_node(left, interner),
term_to_ast_node(right, interner),
],
)
}
LogicExpr::Modal { vector, operand } => {
AstNode::with_children(
&format!("□{:?}", vector.domain),
"modal",
vec![expr_to_ast_node(operand, interner)],
)
}
LogicExpr::Lambda { variable, body } => {
let var_str = interner.resolve(*variable);
AstNode::with_children(
&format!("λ{}", var_str),
"lambda",
vec![expr_to_ast_node(body, interner)],
)
}
LogicExpr::SpeechAct { performer, act_type, content } => {
AstNode::with_children(
&format!(
"{}!{}",
interner.resolve(*act_type),
interner.resolve(*performer)
),
"speech_act",
vec![expr_to_ast_node(content, interner)],
)
}
_ => AstNode::leaf(&format!("{:?}", expr), "other"),
}
}
fn term_to_ast_node(term: &Term, interner: &Interner) -> AstNode {
match term {
Term::Constant(sym) => AstNode::leaf(interner.resolve(*sym), "constant"),
Term::Variable(sym) => AstNode::leaf(interner.resolve(*sym), "variable"),
Term::Function(name, args) => {
let name_str = interner.resolve(*name);
let arg_nodes: Vec<AstNode> = args.iter()
.map(|t| term_to_ast_node(t, interner))
.collect();
AstNode::with_children(&format!("{}()", name_str), "function", arg_nodes)
}
Term::Group(terms) => {
let term_nodes: Vec<AstNode> = terms.iter()
.map(|t| term_to_ast_node(t, interner))
.collect();
AstNode::with_children("⊕", "group", term_nodes)
}
_ => AstNode::leaf(&format!("{:?}", term), "term"),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompileResult {
pub logic: Option<String>,
pub simple_logic: Option<String>,
pub kripke_logic: Option<String>,
pub ast: Option<AstNode>,
pub readings: Vec<String>,
pub simple_readings: Vec<String>,
pub kripke_readings: Vec<String>,
pub tokens: Vec<TokenInfo>,
pub error: Option<String>,
}
pub fn compile_for_ui(input: &str) -> CompileResult {
let tokens = tokenize_for_ui(input);
let readings = compile_forest(input);
let simple_readings: Vec<String> = {
let raw = compile_forest_with_options(input, CompileOptions { format: OutputFormat::SimpleFOL, pragmatic: false });
let mut seen = HashSet::new();
raw.into_iter().filter(|r| seen.insert(r.clone())).collect()
};
let kripke_readings = compile_forest_with_options(input, CompileOptions { format: OutputFormat::Kripke, pragmatic: false });
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let lex_tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let lex_tokens = mwe::apply_mwe_pipeline(lex_tokens, &mwe_trie, &mut interner);
let type_registry = {
let mut discovery = DiscoveryPass::new(&lex_tokens, &mut interner);
discovery.run()
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let ctx = AstContext::new(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
);
let mut world_state = drs::WorldState::new();
let mut parser = Parser::new(lex_tokens, &mut world_state, &mut interner, ctx, type_registry);
match parser.parse() {
Ok(ast) => {
let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, &mut interner);
let ast = pragmatics::apply_pragmatics(ast, ctx.exprs, &interner);
let ast_node = expr_to_ast_node(ast, &interner);
let mut registry = SymbolRegistry::new();
let logic = ast.transpile_discourse(&mut registry, &interner, OutputFormat::Unicode);
let simple_logic = ast.transpile_discourse(&mut registry, &interner, OutputFormat::SimpleFOL);
let kripke_ast = semantics::apply_kripke_lowering(ast, ctx.exprs, ctx.terms, &mut interner);
let kripke_logic = kripke_ast.transpile_discourse(&mut registry, &interner, OutputFormat::Kripke);
CompileResult {
logic: Some(logic),
simple_logic: Some(simple_logic),
kripke_logic: Some(kripke_logic),
ast: Some(ast_node),
readings,
simple_readings,
kripke_readings,
tokens,
error: None,
}
}
Err(e) => {
let advice = socratic_explanation(&e, &interner);
CompileResult {
logic: None,
simple_logic: None,
kripke_logic: None,
ast: None,
readings: Vec::new(),
simple_readings: Vec::new(),
kripke_readings: Vec::new(),
tokens,
error: Some(advice),
}
}
}
}
#[derive(Debug, Clone)]
pub struct ProofCompileResult {
pub proof_expr: Option<ProofExpr>,
pub logic_string: Option<String>,
pub error: Option<String>,
}
pub fn compile_for_proof(input: &str) -> ProofCompileResult {
use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let lex_tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let lex_tokens = mwe::apply_mwe_pipeline(lex_tokens, &mwe_trie, &mut interner);
let type_registry = {
let mut discovery = DiscoveryPass::new(&lex_tokens, &mut interner);
discovery.run()
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let ctx = AstContext::new(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
);
let mut world_state = drs::WorldState::new();
let mut parser = Parser::new(lex_tokens, &mut world_state, &mut interner, ctx, type_registry);
match parser.parse() {
Ok(ast) => {
let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, &mut interner);
let ast = pragmatics::apply_pragmatics(ast, ctx.exprs, &interner);
let mut registry = SymbolRegistry::new();
let logic_string = ast.transpile(&mut registry, &interner, OutputFormat::SimpleFOL);
let proof_expr = logic_expr_to_proof_expr(ast, &interner);
ProofCompileResult {
proof_expr: Some(proof_expr),
logic_string: Some(logic_string),
error: None,
}
}
Err(e) => {
let advice = socratic_explanation(&e, &interner);
ProofCompileResult {
proof_expr: None,
logic_string: None,
error: Some(advice),
}
}
}
}
#[derive(Debug, Clone)]
pub struct TheoremCompileResult {
pub name: String,
pub premises: Vec<ProofExpr>,
pub goal: Option<ProofExpr>,
pub goal_string: Option<String>,
pub derivation: Option<DerivationTree>,
pub verified: bool,
pub verification_error: Option<String>,
pub answer: Option<Vec<String>>,
pub grid: Option<SolvedGrid>,
pub error: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SolvedGrid {
pub row_label: String,
pub rows: Vec<String>,
pub columns: Vec<GridColumn>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GridColumn {
pub label: String,
pub values: Vec<String>,
pub cells: Vec<Option<String>>,
}
struct ParsedTheorem {
name: String,
premises: Vec<ProofExpr>,
goal: ProofExpr,
goal_string: String,
is_auto: bool,
script: Option<String>,
premise_names: Vec<Option<String>>,
}
fn parse_theorem(input: &str) -> Result<ParsedTheorem, String> {
use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let type_registry = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
discovery.run()
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let ctx = AstContext::new(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
);
let mut world_state = drs::WorldState::new();
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
let statements = parser
.parse_program()
.map_err(|e| format!("Parse error: {:?}", e))?;
let theorem = statements
.iter()
.find_map(|stmt| if let ast::Stmt::Theorem(t) = stmt { Some(t) } else { None })
.ok_or_else(|| "No theorem block found".to_string())?;
let premises: Vec<ProofExpr> = theorem
.premises
.iter()
.map(|p| logic_expr_to_proof_expr(p, &interner))
.collect();
let goal = logic_expr_to_proof_expr(theorem.goal, &interner);
let mut registry = SymbolRegistry::new();
let goal_string = theorem.goal.transpile(&mut registry, &interner, OutputFormat::SimpleFOL);
let script = match &theorem.strategy {
ast::theorem::ProofStrategy::Script(s) => Some(s.clone()),
_ => None,
};
Ok(ParsedTheorem {
name: theorem.name.clone(),
premises,
goal,
goal_string,
is_auto: matches!(theorem.strategy, ast::theorem::ProofStrategy::Auto),
script,
premise_names: theorem.premise_names.clone(),
})
}
#[derive(Debug, Clone)]
pub struct TheoryTheoremResult {
pub name: String,
pub verified: bool,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TheoryCompileResult {
pub theory_name: Option<String>,
pub axiom_count: usize,
pub theorems: Vec<TheoryTheoremResult>,
pub parse_error: Option<String>,
}
impl TheoryCompileResult {
pub fn all_verified(&self) -> bool {
self.parse_error.is_none()
&& !self.theorems.is_empty()
&& self.theorems.iter().all(|t| t.verified)
}
}
pub fn compile_theory_for_ui(input: &str) -> TheoryCompileResult {
use logicaffeine_proof::development::parse_development;
use logicaffeine_proof::formula::parse_formula;
use logicaffeine_proof::verify::{prove_library_with_axioms, LibraryTheorem};
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let type_registry = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
discovery.run()
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let ctx = AstContext::new(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
);
let mut world_state = drs::WorldState::new();
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
let statements = match parser.parse_program() {
Ok(s) => s,
Err(e) => {
return TheoryCompileResult {
theory_name: None,
axiom_count: 0,
theorems: Vec::new(),
parse_error: Some(format!("Parse error: {:?}", e)),
}
}
};
let mut axioms: Vec<ProofExpr> = Vec::new();
let mut theorems: Vec<LibraryTheorem> = Vec::new();
let mut theory_name: Option<String> = None;
for stmt in &statements {
match stmt {
ast::Stmt::Axiom(a) => match parse_formula(&a.formula) {
Ok(expr) => axioms.push(expr),
Err(e) => {
return TheoryCompileResult {
theory_name,
axiom_count: axioms.len(),
theorems: Vec::new(),
parse_error: Some(format!("Axiom '{}': {}", a.name, e)),
}
}
},
ast::Stmt::Theory(t) => {
if theory_name.is_none() {
theory_name = Some(t.name.clone());
}
if !t.body.trim().is_empty() {
match parse_development(&t.body) {
Ok(dev) => {
axioms.extend(dev.axiom_exprs());
theorems.extend(dev.theorems);
}
Err(e) => {
return TheoryCompileResult {
theory_name,
axiom_count: axioms.len(),
theorems: Vec::new(),
parse_error: Some(format!("Theory '{}': {}", t.name, e)),
}
}
}
}
}
_ => {}
}
}
let results = prove_library_with_axioms(&axioms, &theorems);
let theorem_results = theorems
.iter()
.zip(results)
.map(|(t, r)| TheoryTheoremResult {
name: t.name.clone(),
verified: r.verified,
error: r.verification_error,
})
.collect();
TheoryCompileResult {
theory_name,
axiom_count: axioms.len(),
theorems: theorem_results,
parse_error: None,
}
}
pub fn grounded_grid_problem(input: &str) -> Option<(Vec<ProofExpr>, ProofExpr)> {
let parsed = parse_theorem(input).ok()?;
if !parsed.is_auto || !looks_like_grid(&parsed.premises) {
return None;
}
let solver_input = prepare_premises_opts(&parsed.premises, true);
let g = erase_tense(&parsed.goal);
Some((solver_input, g))
}
pub fn compile_theorem_for_ui(input: &str) -> TheoremCompileResult {
let parsed = match parse_theorem(input) {
Ok(p) => p,
Err(e) => {
return TheoremCompileResult {
name: String::new(),
premises: Vec::new(),
goal: None,
goal_string: None,
derivation: None,
verified: false,
verification_error: None,
answer: None,
grid: None,
error: Some(e),
};
}
};
let ParsedTheorem { name, premises, goal, goal_string, is_auto, script, premise_names } =
parsed;
let grid = if is_auto && looks_like_grid(&premises) {
solve_grid_from_premises(&premises, input)
} else {
None
};
let (derivation, verified, verification_error, answer) =
if is_auto {
if let ProofExpr::Exists { variable, body } = &goal {
let witnesses = answer_wh(&premises, variable, body);
let verified = !witnesses.is_empty();
let verr = (!verified)
.then(|| "no individual in the domain satisfies the question".to_string());
(None, verified, verr, Some(witnesses))
} else {
let outcome = if looks_like_grid(&premises) {
let g = erase_tense(&goal);
let solver_input = prepare_premises_opts(&premises, true);
let solve = || {
let trace = std::env::var("LOGOS_TRACE").is_ok();
let t_solve = trace.then(std::time::Instant::now);
let tree = logicaffeine_proof::grid_solver::grid_prove(&solver_input, &g);
if let Some(t_solve) = t_solve {
let n = tree.as_ref().map(count_tree_nodes).unwrap_or(0);
eprintln!("[grid] solve+emit {:.2?} ({} tree nodes)", t_solve.elapsed(), n);
}
let t_cert = trace.then(std::time::Instant::now);
let solved = tree
.map(|tree| logicaffeine_proof::verify::check_derivation(&solver_input, &g, tree))
.filter(|vp| vp.verified);
if let Some(t_cert) = t_cert {
eprintln!("[grid] kernel-certify {:.2?} (verified={})", t_cert.elapsed(), solved.is_some());
}
match solved {
Some(vp) => vp,
None => {
if matches!(
logicaffeine_proof::rup::entails_certified(&solver_input, &g),
Some(logicaffeine_proof::rup::Verdict::NotEntailed)
) {
return logicaffeine_proof::verify::VerifiedProof {
derivation: None,
proof_term: None,
kernel_ctx: Default::default(),
verified: false,
verification_error: Some(
"goal is not entailed (RUP-certified)".to_string(),
),
};
}
let grounded = prepare_premises(&premises);
logicaffeine_proof::verify::prove_certify_check_bounded(&grounded, &g, 60)
}
}
};
#[cfg(not(target_arch = "wasm32"))]
{
std::thread::scope(|s| {
std::thread::Builder::new()
.stack_size(512 * 1024 * 1024)
.spawn_scoped(s, solve)
.expect("spawn grid-solve thread")
.join()
.expect("grid-solve thread panicked")
})
}
#[cfg(target_arch = "wasm32")]
{
solve()
}
} else {
logicaffeine_proof::verify::prove_certify_check(&premises, &goal)
};
(outcome.derivation, outcome.verified, outcome.verification_error, None)
}
} else if let Some(src) = &script {
use logicaffeine_proof::tactic::ProofState;
let run = || {
let mut st =
ProofState::start_with_names(premises.clone(), &premise_names, goal.clone());
match st.run_script(src) {
Ok(_) => match st.qed() {
Ok(vp) => (vp.derivation, vp.verified, vp.verification_error),
Err(e) => (None, false, Some(format!("{e:?}"))),
},
Err(e) => (None, false, Some(e.to_string())),
}
};
#[cfg(not(target_arch = "wasm32"))]
let (derivation, verified, verr) = std::thread::scope(|s| {
std::thread::Builder::new()
.stack_size(256 * 1024 * 1024)
.spawn_scoped(s, run)
.expect("spawn proof-script thread")
.join()
.expect("proof-script thread panicked")
});
#[cfg(target_arch = "wasm32")]
let (derivation, verified, verr) = run();
(derivation, verified, verr, None)
} else {
(None, false, None, None)
};
TheoremCompileResult {
name,
premises,
goal: Some(goal),
goal_string: Some(goal_string),
derivation,
verified,
verification_error,
answer,
grid,
error: None,
}
}
fn count_tree_nodes(t: &logicaffeine_proof::DerivationTree) -> usize {
1 + t.premises.iter().map(count_tree_nodes).sum::<usize>()
}
#[cfg(feature = "codegen")]
pub fn generate_rust_code(source: &str) -> Result<String, ParseError> {
let (imperative_src, math_src) = partition_mixed(source);
let proven = math_src.as_deref().and_then(mixed_proven_module);
generate_rust_code_with_proven(&imperative_src, proven.as_deref())
}
#[cfg(feature = "codegen")]
pub fn generate_rust_code_with_proven(source: &str, proven: Option<&str>) -> Result<String, ParseError> {
use logicaffeine_language::ast::stmt::{Stmt, Expr, TypeExpr};
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = drs::WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let type_env = crate::analysis::types::TypeEnv::infer_program(&stmts, &interner, &codegen_registry);
let rust_code = crate::codegen::codegen_program_with_proven(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env, &crate::optimization::OptimizationConfig::from_env(), "proven", proven);
Ok(rust_code)
}
fn send_escape_rejection(stmts: &[logicaffeine_language::ast::stmt::Stmt]) -> Option<InterpreterResult> {
crate::concurrency::check_send_escape(stmts)
.first()
.map(|d| InterpreterResult { lines: Vec::new(), error: Some(d.message.clone()) })
}
fn send_dimension_rejection(
stmts: &[logicaffeine_language::ast::stmt::Stmt],
interner: &Interner,
) -> Option<InterpreterResult> {
crate::analysis::dimension_check::DimensionChecker::new(interner)
.check_program(stmts)
.err()
.map(|e| InterpreterResult { lines: Vec::new(), error: Some(e.message) })
}
pub async fn interpret_for_ui(input: &str) -> InterpreterResult {
interpret_for_ui_with_args(input, &[]).await
}
pub async fn interpret_for_ui_with_args(
input: &str,
program_args: &[String],
) -> InterpreterResult {
let needs_async = with_parsed_program(input, |parsed, _| match parsed {
Ok((stmts, _, _)) => crate::interpreter::needs_async(stmts),
Err(_) => false,
});
if !needs_async {
return interpret_for_ui_sync_with_args(input, program_args);
}
use logicaffeine_language::ast::stmt::{Stmt, Expr, TypeExpr};
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut world_state = drs::WorldState::new();
let type_registry_for_interp = type_registry.clone();
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
match parser.parse_program() {
Ok(stmts) => {
if let Some(rejection) = send_escape_rejection(&stmts) {
return rejection;
}
if crate::concurrency::uses_scheduler(&stmts) {
return run_program_concurrent_streaming(
&stmts,
&type_registry_for_interp,
policy_registry,
&interner,
program_args,
None,
None,
None,
0,
)
.await;
}
let mut interp = crate::interpreter::Interpreter::new(&interner)
.with_type_registry(&type_registry_for_interp)
.with_policies(policy_registry)
.with_program_args(program_args.to_vec());
match interp.run(&stmts).await {
Ok(()) => InterpreterResult {
lines: interp.output,
error: None,
},
Err(e) => InterpreterResult {
lines: interp.output,
error: Some(e),
},
}
}
Err(e) => {
let advice = socratic_explanation(&e, &interner);
InterpreterResult {
lines: vec![],
error: Some(advice),
}
}
}
}
pub fn with_parsed_program<R>(
input: &str,
f: impl for<'a> FnOnce(
Result<
(
&'a [logicaffeine_language::ast::stmt::Stmt<'a>],
&'a logicaffeine_language::analysis::TypeRegistry,
logicaffeine_language::analysis::PolicyRegistry,
),
String,
>,
&'a Interner,
) -> R,
) -> R {
use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
let implicit = implicit_main(input);
let input = implicit.as_deref().unwrap_or(input);
let prelude_src = crate::loader::apply_prelude(input);
let input = prelude_src.as_ref();
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut world_state = drs::WorldState::new();
let type_registry_for_engines = type_registry.clone();
let (parsed, opt_flags) = {
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
let stmts = parser.parse_program();
let flags = parser.program_opt_flags();
(stmts, flags)
};
match parsed {
Ok(stmts) => {
let mut run_cfg =
crate::optimization::OptimizationConfig::from_env().merged(&opt_flags);
run_cfg.normalize();
let resolved = crate::resolve_division::resolve_divisions(
&stmts,
&stmt_arena,
&imperative_expr_arena,
&interner,
run_cfg.is_on(crate::optimization::Opt::Comptime),
);
let pre = resolved.unwrap_or(stmts.as_slice());
match crate::tail_call::rewrite_accumulators(
pre,
&stmt_arena,
&imperative_expr_arena,
&mut interner,
) {
Some(rw) => f(Ok((rw, &type_registry_for_engines, policy_registry)), &interner),
None => f(Ok((pre, &type_registry_for_engines, policy_registry)), &interner),
}
}
Err(e) => {
let advice = socratic_explanation(&e, &interner);
f(Err(advice), &interner)
}
}
}
pub fn with_optimized_program<R>(
input: &str,
f: impl for<'a> FnOnce(
Result<
(
&'a [logicaffeine_language::ast::stmt::Stmt<'a>],
&'a logicaffeine_language::analysis::TypeRegistry,
logicaffeine_language::analysis::PolicyRegistry,
),
String,
>,
&'a Interner,
) -> R,
) -> R {
let tier = crate::optimization::HotswapConfig::from_env().run_tier();
with_optimized_program_tiered(input, tier, f)
}
pub fn with_optimized_program_tiered<R>(
input: &str,
tier: crate::optimization::Tier,
f: impl for<'a> FnOnce(
Result<
(
&'a [logicaffeine_language::ast::stmt::Stmt<'a>],
&'a logicaffeine_language::analysis::TypeRegistry,
logicaffeine_language::analysis::PolicyRegistry,
),
String,
>,
&'a Interner,
) -> R,
) -> R {
use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
let implicit = implicit_main(input);
let input = implicit.as_deref().unwrap_or(input);
let prelude_src = crate::loader::apply_prelude(input);
let input = prelude_src.as_ref();
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut world_state = drs::WorldState::new();
let type_registry_for_engines = type_registry.clone();
let (parsed, opt_flags, tier_pins) = {
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
let stmts = parser.parse_program();
let flags = parser.program_opt_flags();
let pins = parser.program_tier_pins();
(stmts, flags, pins)
};
match parsed {
Ok(stmts) => {
let mut run_cfg =
crate::optimization::OptimizationConfig::from_env().merged(&opt_flags);
run_cfg.normalize();
let mut hotswap = crate::optimization::HotswapConfig::from_env();
hotswap.pins.overlay(&tier_pins);
let resolved = crate::resolve_division::resolve_divisions(
&stmts,
&stmt_arena,
&imperative_expr_arena,
&interner,
run_cfg.is_on(crate::optimization::Opt::Comptime),
);
let pre: Vec<_> = match resolved {
Some(rw) => rw.to_vec(),
None => stmts,
};
let optimized = crate::optimize::optimize_for_run_tiered(
pre,
&imperative_expr_arena,
&stmt_arena,
&mut interner,
&run_cfg,
&hotswap,
tier,
);
match crate::tail_call::rewrite_accumulators(
&optimized,
&stmt_arena,
&imperative_expr_arena,
&mut interner,
) {
Some(rw) => f(Ok((rw, &type_registry_for_engines, policy_registry)), &interner),
None => f(Ok((&optimized, &type_registry_for_engines, policy_registry)), &interner),
}
}
Err(e) => {
let advice = socratic_explanation(&e, &interner);
f(Err(advice), &interner)
}
}
}
pub fn with_v2_optimized_program<R>(
input: &str,
f: impl for<'a> FnOnce(
Result<
(
&'a [logicaffeine_language::ast::stmt::Stmt<'a>],
&'a logicaffeine_language::analysis::TypeRegistry,
logicaffeine_language::analysis::PolicyRegistry,
),
String,
>,
&'a Interner,
) -> R,
) -> R {
use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
let implicit = implicit_main(input);
let input = implicit.as_deref().unwrap_or(input);
let prelude_src = crate::loader::apply_prelude(input);
let input = prelude_src.as_ref();
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut world_state = drs::WorldState::new();
let type_registry_for_engines = type_registry.clone();
let parsed = {
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
parser.parse_program()
};
match parsed {
Ok(stmts) => {
let optimized = crate::optimize::optimize_program(
stmts,
&imperative_expr_arena,
&stmt_arena,
&mut interner,
&crate::optimization::OptimizationConfig::from_env(),
);
f(Ok((&optimized, &type_registry_for_engines, policy_registry)), &interner)
}
Err(e) => {
let advice = socratic_explanation(&e, &interner);
f(Err(advice), &interner)
}
}
}
pub fn interpret_for_ui_sync(input: &str) -> InterpreterResult {
interpret_for_ui_sync_with_args(input, &[])
}
#[cfg(not(target_arch = "wasm32"))]
thread_local! {
static PENDING_AOT: std::cell::RefCell<Vec<(String, Box<dyn crate::vm::NativeFn>)>> =
const { std::cell::RefCell::new(Vec::new()) };
}
#[cfg(not(target_arch = "wasm32"))]
pub fn set_pending_aot_natives(natives: Vec<(String, Box<dyn crate::vm::NativeFn>)>) {
PENDING_AOT.with(|p| *p.borrow_mut() = natives);
}
#[cfg(not(target_arch = "wasm32"))]
fn install_pending_aot_natives(
vm: &mut crate::vm::Vm,
program: &crate::vm::CompiledProgram,
interner: &Interner,
) {
let pending = PENDING_AOT.with(|p| std::mem::take(&mut *p.borrow_mut()));
for (name, nf) in pending {
if let Some(fi) = program
.fn_index
.iter()
.find(|(s, _)| interner.resolve(**s) == name)
.map(|(_, i)| *i as usize)
{
vm.install_aot_native(fi, nf);
if std::env::var_os("LOGOS_ENGINE_TRACE").is_some() {
eprintln!("logos-engine: aot-native installed for '{name}'");
}
}
}
}
pub fn interpret_for_ui_sync_with_args(input: &str, program_args: &[String]) -> InterpreterResult {
let trace = |engine: &str| {
if std::env::var_os("LOGOS_ENGINE_TRACE").is_some() {
eprintln!("logos-engine: {engine}");
}
};
with_optimized_program(input, |parsed, interner| match parsed {
Ok((stmts, type_registry, policies)) => {
if let Some(rejection) = send_escape_rejection(stmts) {
return rejection;
}
if let Some(rejection) = send_dimension_rejection(stmts, interner) {
return rejection;
}
if crate::interpreter::needs_async(stmts) || crate::concurrency::uses_scheduler(stmts) {
trace("treewalker (async)");
return run_treewalker(stmts, type_registry, policies, interner, true, program_args);
}
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
match crate::vm::Compiler::compile_with_oracle(
stmts,
interner,
Some(type_registry),
Some(oracle),
) {
Ok(program) => {
trace("vm+jit");
let mut vm = crate::vm::Vm::new(&program)
.with_policy_ctx(&policies, interner)
.with_program_args(program_args.to_vec());
if let Some(tier) = crate::vm::installed_native_tier() {
vm = vm.with_native_tier(tier);
}
#[cfg(not(target_arch = "wasm32"))]
install_pending_aot_natives(&mut vm, &program, interner);
let error = vm.run().err();
let result = InterpreterResult { lines: vm.into_lines(), error };
#[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
{
let shadow = run_treewalker(
stmts,
type_registry,
policies.clone(),
interner,
false,
program_args,
);
assert_eq!(
(&result.lines, &result.error),
(&shadow.lines, &shadow.error),
"VM diverged from the tree-walker oracle for:\n{input}"
);
}
result
}
Err(_) => {
trace("treewalker (vm-reject)");
run_treewalker(stmts, type_registry, policies, interner, false, program_args)
}
}
}
Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
})
}
pub async fn interpret_for_ui_baseline(input: &str) -> InterpreterResult {
interpret_for_ui_baseline_with_args(input, &[]).await
}
pub async fn interpret_for_ui_baseline_with_args(
input: &str,
program_args: &[String],
) -> InterpreterResult {
let needs_async = with_parsed_program(input, |parsed, _| match parsed {
Ok((stmts, _, _)) => crate::interpreter::needs_async(stmts),
Err(_) => false,
});
if needs_async {
return interpret_for_ui_with_args(input, program_args).await;
}
interpret_for_ui_baseline_sync_with_args(input, program_args)
}
pub fn interpret_for_ui_baseline_sync_with_args(
input: &str,
program_args: &[String],
) -> InterpreterResult {
let trace = |engine: &str| {
if std::env::var_os("LOGOS_ENGINE_TRACE").is_some() {
eprintln!("logos-engine: {engine}");
}
};
with_parsed_program(input, |parsed, interner| match parsed {
Ok((stmts, type_registry, policies)) => {
if let Some(rejection) = send_escape_rejection(stmts) {
return rejection;
}
if let Some(rejection) = send_dimension_rejection(stmts, interner) {
return rejection;
}
if crate::interpreter::needs_async(stmts) || crate::concurrency::uses_scheduler(stmts) {
trace("treewalker (async)");
return run_treewalker(stmts, type_registry, policies, interner, true, program_args);
}
match crate::vm::Compiler::compile_with_types(stmts, interner, Some(type_registry)) {
Ok(program) => {
trace("vm (baseline)");
let mut vm = crate::vm::Vm::new(&program)
.with_policy_ctx(&policies, interner)
.with_program_args(program_args.to_vec());
if let Some(tier) = crate::vm::installed_native_tier() {
vm = vm.with_native_tier(tier);
}
#[cfg(not(target_arch = "wasm32"))]
install_pending_aot_natives(&mut vm, &program, interner);
let error = vm.run().err();
let result = InterpreterResult { lines: vm.into_lines(), error };
#[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
{
let shadow = run_treewalker(
stmts,
type_registry,
policies.clone(),
interner,
false,
program_args,
);
assert_eq!(
(&result.lines, &result.error),
(&shadow.lines, &shadow.error),
"baseline VM diverged from the tree-walker oracle for:\n{input}"
);
}
result
}
Err(_) => {
trace("treewalker (vm-reject)");
run_treewalker(stmts, type_registry, policies, interner, false, program_args)
}
}
}
Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
})
}
fn run_program_concurrent<'a>(
stmts: &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
type_registry: &logicaffeine_language::analysis::TypeRegistry,
policies: logicaffeine_language::analysis::PolicyRegistry,
interner: &'a Interner,
program_args: &[String],
vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
stream: Option<crate::interpreter::OutputCallback>,
seed: u64,
) -> InterpreterResult {
use crate::concurrency::bridge::YieldState;
use crate::concurrency::driver::InterpreterTask;
use logicaffeine_runtime::{run_with_seed, RunOutcome, SchedSeed, SchedulerConfig};
use std::cell::RefCell;
use std::rc::Rc;
let output_sink: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let sink = output_sink.clone();
let callback: crate::interpreter::OutputCallback =
Rc::new(RefCell::new(move |line: String| {
if let Some(s) = &stream {
(s.borrow_mut())(line.clone());
}
sink.borrow_mut().push(line);
}));
let err_sink: crate::concurrency::driver::ErrSink = Rc::new(RefCell::new(None));
let mut main = crate::interpreter::Interpreter::new(interner)
.with_type_registry(type_registry)
.with_policies(policies)
.with_program_args(program_args.to_vec())
.with_output_callback(callback);
if let Some(v) = vfs {
main = main.with_vfs(v);
}
let main_ys = Rc::new(RefCell::new(YieldState::new()));
main.install_yield_state(main_ys.clone());
let main_fut = Box::pin(async move { main.run(stmts).await });
let main_task = InterpreterTask::new(main_fut, main_ys, Some(err_sink.clone()));
let (outcome, _trace) =
run_with_seed(SchedulerConfig::default(), SchedSeed(seed), move |sched| {
sched.spawn_main(Box::new(main_task));
});
let mut error = err_sink.borrow().clone();
if error.is_none() {
match outcome {
RunOutcome::Deadlock => {
error = Some("deadlock: every task is blocked waiting".to_string());
}
RunOutcome::WaitingForIo => {
error = Some(
"networking requires the async runtime; this program was run on the \
synchronous scheduler"
.to_string(),
);
}
RunOutcome::Done(_) => {}
}
}
let lines = output_sink.borrow().clone();
InterpreterResult { lines, error }
}
pub type ObserverCallback = std::rc::Rc<std::cell::RefCell<dyn FnMut(logicaffeine_runtime::SchedSnapshot)>>;
async fn yield_macrotask() {
#[cfg(target_arch = "wasm32")]
{
gloo_timers::future::TimeoutFuture::new(0).await;
}
}
async fn yield_to_reactor() {
#[cfg(target_arch = "wasm32")]
{
gloo_timers::future::TimeoutFuture::new(0).await;
}
#[cfg(not(target_arch = "wasm32"))]
{
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct YieldOnce(bool);
impl Future for YieldOnce {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.0 {
Poll::Ready(())
} else {
self.0 = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
YieldOnce(false).await;
}
}
#[allow(clippy::too_many_arguments)]
async fn run_program_concurrent_streaming<'a>(
stmts: &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
type_registry: &logicaffeine_language::analysis::TypeRegistry,
policies: logicaffeine_language::analysis::PolicyRegistry,
interner: &'a Interner,
program_args: &[String],
vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
stream: Option<crate::interpreter::OutputCallback>,
observer: Option<ObserverCallback>,
seed: u64,
) -> InterpreterResult {
use crate::concurrency::bridge::YieldState;
use crate::concurrency::driver::InterpreterTask;
use logicaffeine_runtime::{Chooser, RunOutcome, SchedSeed, Scheduler, SchedulerConfig};
use std::cell::RefCell;
use std::rc::Rc;
const SLICE_STEPS: usize = 256;
let output_sink: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let sink = output_sink.clone();
let callback: crate::interpreter::OutputCallback = Rc::new(RefCell::new(move |line: String| {
if let Some(s) = &stream {
(s.borrow_mut())(line.clone());
}
sink.borrow_mut().push(line);
}));
let err_sink: crate::concurrency::driver::ErrSink = Rc::new(RefCell::new(None));
let mut main = crate::interpreter::Interpreter::new(interner)
.with_type_registry(type_registry)
.with_policies(policies)
.with_program_args(program_args.to_vec())
.with_output_callback(callback);
if let Some(v) = vfs {
main = main.with_vfs(v);
}
let main_ys = Rc::new(RefCell::new(YieldState::new()));
main.install_yield_state(main_ys.clone());
let main_fut = Box::pin(async move { main.run(stmts).await });
let main_task = InterpreterTask::new(main_fut, main_ys, Some(err_sink.clone()));
let mut sched = Scheduler::new(SchedulerConfig::default(), Chooser::record(SchedSeed(seed)));
sched.spawn_main(Box::new(main_task));
let outcome = loop {
match sched.run_slice(SLICE_STEPS) {
Some(RunOutcome::WaitingForIo) => {
if let Some(ob) = &observer {
(ob.borrow_mut())(sched.snapshot());
}
yield_to_reactor().await;
sched.wake_io();
}
Some(o) => break o,
None => {
if let Some(ob) = &observer {
(ob.borrow_mut())(sched.snapshot());
}
yield_macrotask().await;
}
}
};
let mut error = err_sink.borrow().clone();
if error.is_none() {
if let RunOutcome::Deadlock = outcome {
error = Some("deadlock: every task is blocked waiting".to_string());
}
}
let lines = output_sink.borrow().clone();
InterpreterResult { lines, error }
}
pub fn run_treewalker_concurrent_seeded(input: &str, seed: u64) -> InterpreterResult {
with_parsed_program(input, |parsed, interner| match parsed {
Ok((stmts, type_registry, policies)) => {
run_program_concurrent(stmts, type_registry, policies, interner, &[], None, None, seed)
}
Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
})
}
pub fn run_vm_concurrent(input: &str) -> InterpreterResult {
run_vm_concurrent_seeded(input, 0)
}
pub fn run_vm_concurrent_seeded(input: &str, seed: u64) -> InterpreterResult {
use crate::concurrency::vm_driver::VmTask;
use logicaffeine_runtime::{run_with_seed, RunOutcome, SchedSeed, SchedulerConfig};
use std::cell::RefCell;
use std::rc::Rc;
with_parsed_program(input, |parsed, interner| match parsed {
Ok((stmts, type_registry, policies)) => {
let program = match crate::vm::Compiler::compile_with_types(
stmts,
interner,
Some(type_registry),
) {
Ok(p) => p,
Err(e) => return InterpreterResult { lines: vec![], error: Some(e) },
};
let output: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let err_sink: crate::concurrency::driver::ErrSink = Rc::new(RefCell::new(None));
let mut vm = crate::vm::Vm::new(&program).with_policy_ctx(&policies, interner);
#[cfg(not(target_arch = "wasm32"))]
if let Some(tier) = crate::vm::installed_native_tier() {
vm = vm.with_native_tier(tier);
}
let main_task = VmTask::new(vm, output.clone(), Some(err_sink.clone()));
let (outcome, _trace) =
run_with_seed(SchedulerConfig::default(), SchedSeed(seed), move |sched| {
sched.spawn_main(Box::new(main_task));
});
let mut error = err_sink.borrow().clone();
if error.is_none() {
if let RunOutcome::Deadlock = outcome {
error = Some("deadlock: every task is blocked waiting".to_string());
}
}
let lines = output.borrow().clone();
InterpreterResult { lines, error }
}
Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
})
}
async fn vm_net_poll_tick() {
#[cfg(not(target_arch = "wasm32"))]
logicaffeine_system::tokio::time::sleep(std::time::Duration::from_millis(2)).await;
#[cfg(target_arch = "wasm32")]
gloo_timers::future::TimeoutFuture::new(2).await;
}
fn vm_peer_topic_of(value: &crate::interpreter::RuntimeValue) -> Result<String, String> {
use crate::interpreter::RuntimeValue;
match value {
RuntimeValue::Peer(topic) => Ok((**topic).clone()),
RuntimeValue::Text(s) => Ok(logicaffeine_system::addr::canonical_topic(s)),
other => Err(format!(
"Send/Await expects a PeerAgent or address string, got {}",
other.type_name()
)),
}
}
async fn service_vm_net_block(
vm: &mut crate::vm::Vm<'_>,
netbox: &mut crate::concurrency::net_inbox::NetInbox,
req: crate::vm::VmBlock,
) -> Result<(), String> {
use crate::concurrency::marshal::{self};
use crate::interpreter::RuntimeValue;
use crate::vm::{Value, VmBlock};
match req {
VmBlock::NetConnect(url_payload) => {
let raw = marshal::rebuild(url_payload).to_display_string();
let url = logicaffeine_system::addr::multiaddr_to_ws_url(&raw).map_err(|e| {
format!("Connect address '{raw}' is not a ws:// URL or supported multiaddr: {e}")
})?;
if !crate::concurrency::net_inbox::net_is_offline() {
let net = logicaffeine_system::net::Net::connect(&url)
.await
.map_err(|e| format!("Connect to relay '{url}' failed: {e}"))?;
netbox.net = Some(net);
}
vm.deliver_resume(Value::nothing());
Ok(())
}
VmBlock::NetListen(topic_payload) => {
let raw = marshal::rebuild(topic_payload).to_display_string();
let topic = logicaffeine_system::addr::canonical_topic(&raw);
let hs_topic = crate::concurrency::net_inbox::handshake_topic_for(&topic);
if let Some(net) = netbox.net.as_mut() {
net.subscribe(&topic).await?;
net.subscribe(&hs_topic).await?;
}
netbox.inbox = Some(std::rc::Rc::new(topic));
vm.deliver_resume(Value::nothing());
Ok(())
}
VmBlock::NetSend(to_payload, msg_payload) => {
let topic = vm_peer_topic_of(&marshal::rebuild(to_payload))?;
if let Some(hs) = netbox.first_contact_handshake(&topic) {
netbox.publish(&crate::concurrency::net_inbox::handshake_topic_for(&topic), hs)?;
}
let value = marshal::rebuild(msg_payload);
let from = netbox.inbox.as_ref().map(|t| t.to_string()).unwrap_or_default();
let bytes = netbox.encode_negotiated(&from, &value, &topic, netbox.my_registry.clone())?;
netbox.publish(&topic, bytes)?;
vm.deliver_resume(Value::nothing());
Ok(())
}
VmBlock::NetStream(to_payload, values_payload) => {
let topic = vm_peer_topic_of(&marshal::rebuild(to_payload))?;
if let Some(hs) = netbox.first_contact_handshake(&topic) {
netbox.publish(&crate::concurrency::net_inbox::handshake_topic_for(&topic), hs)?;
}
let list = marshal::rebuild(values_payload);
let items = match &list {
RuntimeValue::List(rc) => rc.borrow().to_values(),
other => vec![other.clone()],
};
let from = netbox.inbox.as_ref().map(|t| t.to_string()).unwrap_or_default();
let blob = marshal::frame_stream_message(&from, &items)?;
netbox.publish(&topic, blob)?;
vm.deliver_resume(Value::nothing());
Ok(())
}
VmBlock::NetAwait(from_payload, stream) => {
let want = vm_peer_topic_of(&marshal::rebuild(from_payload))?;
if netbox.inbox.is_none() {
return Err("Await requires a prior Listen to establish an inbox".to_string());
}
loop {
let got = if stream {
netbox.try_take_stream(&want)
} else {
netbox.try_take_message(&want, false)
};
if let Some(v) = got {
vm.deliver_resume(Value::from_runtime(v));
return Ok(());
}
netbox.drain(netbox.my_registry.clone());
let got = if stream {
netbox.try_take_stream(&want)
} else {
netbox.try_take_message(&want, false)
};
if let Some(v) = got {
vm.deliver_resume(Value::from_runtime(v));
return Ok(());
}
vm_net_poll_tick().await;
}
}
VmBlock::NetMakePeer(addr_payload) => {
let raw = marshal::rebuild(addr_payload).to_display_string();
let topic = logicaffeine_system::addr::canonical_topic(&raw);
vm.deliver_resume(Value::from_runtime(RuntimeValue::Peer(std::rc::Rc::new(topic))));
Ok(())
}
VmBlock::NetSync(topic_payload, current_payload) => {
let topic = marshal::rebuild(topic_payload).to_display_string();
let current = marshal::rebuild(current_payload);
let publish_bytes = crate::semantics::arith::crdt_to_wire(¤t);
let merged = if let Some(net) = netbox.net.as_mut() {
net.subscribe(&topic).await?;
if let Some(bytes) = publish_bytes {
net.publish(&topic, bytes)?;
}
let incoming = net.drain();
let mut merged = current;
for (_t, data) in incoming {
merged = crate::semantics::arith::crdt_merge_wire(merged, &data);
}
merged
} else {
current
};
vm.deliver_resume(Value::from_runtime(merged));
Ok(())
}
_ => Err("run_vm_net_async services only peer-networking blocks; this program mixes \
channels/tasks with networking, which the VM net runner does not yet drive"
.to_string()),
}
}
pub async fn run_vm_net_async(input: &str) -> InterpreterResult {
use logicaffeine_language::ast::stmt::{Expr, Stmt, TypeExpr};
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut world_state = drs::WorldState::new();
let type_registry_for_vm = type_registry.clone();
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
let stmts = match parser.parse_program() {
Ok(s) => s,
Err(e) => return InterpreterResult { lines: vec![], error: Some(format!("{e:?}")) },
};
let program =
match crate::vm::Compiler::compile_with_types(&stmts, &interner, Some(&type_registry_for_vm)) {
Ok(p) => p,
Err(e) => return InterpreterResult { lines: vec![], error: Some(e) },
};
let mut vm = crate::vm::Vm::new(&program).with_policy_ctx(&policy_registry, &interner);
let mut netbox = crate::concurrency::net_inbox::NetInbox::new();
{
let mut structs: Vec<(String, Vec<String>)> = Vec::new();
let mut enums: Vec<(String, Vec<String>)> = Vec::new();
for (name_sym, type_def) in type_registry_for_vm.iter_types() {
let type_name = interner.resolve(*name_sym).to_string();
match type_def {
crate::analysis::registry::TypeDef::Struct { fields, .. } => {
structs.push((type_name, fields.iter().map(|f| interner.resolve(f.name).to_string()).collect()));
}
crate::analysis::registry::TypeDef::Enum { variants, .. } => {
enums.push((type_name, variants.iter().map(|v| interner.resolve(v.name).to_string()).collect()));
}
_ => {}
}
}
netbox.set_registry(
crate::concurrency::marshal::WireTypeRegistry::new(structs).with_enums(enums),
);
}
let mut lines: Vec<String> = Vec::new();
let mut error = None;
loop {
let step = vm.run_until_block();
lines.extend(vm.drain_lines());
match step {
Ok(crate::vm::VmStep::Done(_)) => break,
Ok(crate::vm::VmStep::Blocked) => {
let req = match vm.take_pending() {
Some(r) => r,
None => break,
};
if let Err(e) = service_vm_net_block(&mut vm, &mut netbox, req).await {
error = Some(e);
break;
}
}
Ok(crate::vm::VmStep::Paused) => break,
Err(e) => {
error = Some(e);
break;
}
}
}
InterpreterResult { lines, error }
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run_vm_workstealing_seeded(input: &str, seed: u64, workers: usize) -> InterpreterResult {
use crate::concurrency::vm_driver::VmTask;
use logicaffeine_runtime::{
run_workstealing_seeded, RunOutcome, SchedSeed, SchedulerConfig, SpawnDesc, Task,
};
with_parsed_program(input, |parsed, interner| match parsed {
Ok((stmts, type_registry, policies)) => {
let program = match crate::vm::Compiler::compile_with_types(
stmts,
interner,
Some(type_registry),
) {
Ok(p) => p,
Err(e) => return InterpreterResult { lines: vec![], error: Some(e) },
};
let build = |desc: SpawnDesc| -> Box<dyn Task<'_> + '_> {
let mut vm = crate::vm::Vm::new(&program).with_policy_ctx(&policies, interner);
if !desc.is_main {
vm.setup_task(desc.func, &desc.args);
}
Box::new(VmTask::work_stealing(vm, None))
};
let main = SpawnDesc { func: 0, args: vec![], priority: 0, is_main: true };
let config = SchedulerConfig::default().with_workers(workers.max(1));
let result = run_workstealing_seeded(config, SchedSeed(seed), main, build);
let error = match result.outcome {
RunOutcome::Deadlock => {
Some("deadlock: every task is blocked waiting".to_string())
}
_ => None,
};
InterpreterResult { lines: result.output, error }
}
Err(advice) => InterpreterResult { lines: vec![], error: Some(advice) },
})
}
pub fn repl_global_bindings(input: &str, program_args: &[String]) -> Option<Vec<(String, String, String)>> {
with_parsed_program(input, |parsed, interner| match parsed {
Ok((stmts, type_registry, policies)) => {
if crate::interpreter::needs_async(stmts) || crate::concurrency::uses_scheduler(stmts) {
return None;
}
let mut interp = crate::interpreter::Interpreter::new(interner)
.with_type_registry(type_registry)
.with_policies(policies)
.with_program_args(program_args.to_vec());
interp.run_sync(stmts).ok()?;
Some(interp.global_bindings())
}
Err(_) => None,
})
}
pub(crate) fn run_treewalker<'a>(
stmts: &'a [logicaffeine_language::ast::stmt::Stmt<'a>],
type_registry: &logicaffeine_language::analysis::TypeRegistry,
policies: logicaffeine_language::analysis::PolicyRegistry,
interner: &'a Interner,
force_async: bool,
program_args: &[String],
) -> InterpreterResult {
if crate::concurrency::uses_scheduler(stmts) {
return run_program_concurrent(
stmts, type_registry, policies, interner, program_args, None, None, 0,
);
}
let mut interp = crate::interpreter::Interpreter::new(interner)
.with_type_registry(type_registry)
.with_policies(policies)
.with_program_args(program_args.to_vec());
let run_result = if force_async {
futures::executor::block_on(interp.run(stmts))
} else {
interp.run_sync(stmts)
};
match run_result {
Ok(()) => InterpreterResult { lines: interp.output, error: None },
Err(e) => InterpreterResult { lines: interp.output, error: Some(e) },
}
}
pub async fn interpret_streaming<F>(input: &str, on_output: std::rc::Rc<std::cell::RefCell<F>>) -> InterpreterResult
where
F: FnMut(String) + 'static,
{
interpret_streaming_with_vfs(input, on_output, None).await
}
pub async fn interpret_streaming_with_vfs<F>(
input: &str,
on_output: std::rc::Rc<std::cell::RefCell<F>>,
vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
) -> InterpreterResult
where
F: FnMut(String) + 'static,
{
interpret_streaming_impl(input, on_output, vfs, None).await
}
pub async fn interpret_streaming_with_vfs_observer<F>(
input: &str,
on_output: std::rc::Rc<std::cell::RefCell<F>>,
vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
observer: ObserverCallback,
) -> InterpreterResult
where
F: FnMut(String) + 'static,
{
interpret_streaming_impl(input, on_output, vfs, Some(observer)).await
}
async fn interpret_streaming_impl<F>(
input: &str,
on_output: std::rc::Rc<std::cell::RefCell<F>>,
vfs: Option<std::sync::Arc<dyn logicaffeine_system::fs::Vfs>>,
observer: Option<ObserverCallback>,
) -> InterpreterResult
where
F: FnMut(String) + 'static,
{
use logicaffeine_language::ast::stmt::{Stmt, Expr, TypeExpr};
use crate::interpreter::OutputCallback;
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut world_state = drs::WorldState::new();
let type_registry_for_interp = type_registry.clone();
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
match parser.parse_program() {
Ok(stmts) => {
if let Some(rejection) = send_escape_rejection(&stmts) {
return rejection;
}
let callback: OutputCallback = std::rc::Rc::new(std::cell::RefCell::new(move |line: String| {
(on_output.borrow_mut())(line);
}));
if crate::concurrency::uses_scheduler(&stmts) {
return run_program_concurrent_streaming(
&stmts,
&type_registry_for_interp,
policy_registry,
&interner,
&[],
vfs,
Some(callback),
observer,
0,
)
.await;
}
let mut interp = crate::interpreter::Interpreter::new(&interner)
.with_type_registry(&type_registry_for_interp)
.with_policies(policy_registry)
.with_output_callback(callback);
if let Some(v) = vfs {
interp = interp.with_vfs(v);
}
match interp.run(&stmts).await {
Ok(()) => InterpreterResult {
lines: interp.output,
error: None,
},
Err(e) => InterpreterResult {
lines: interp.output,
error: Some(e),
},
}
}
Err(e) => {
let advice = socratic_explanation(&e, &interner);
InterpreterResult {
lines: vec![],
error: Some(advice),
}
}
}
}
use logicaffeine_language::ast::Stmt;
use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
use crate::kernel;
pub fn verify_theorem(input: &str) -> Result<(kernel::Term, kernel::Context), ParseError> {
let (proof_exprs, goal_expr, definitions) = theorem_proof_exprs_with_defs(input)?;
let outcome = logicaffeine_proof::verify::prove_certify_check_with_defs(
&proof_exprs,
&goal_expr,
&definitions,
);
if outcome.verified {
Ok((
outcome
.proof_term
.expect("a verified outcome always carries a proof term"),
outcome.kernel_ctx,
))
} else {
Err(ParseError {
kind: logicaffeine_language::error::ParseErrorKind::Custom(
outcome
.verification_error
.unwrap_or_else(|| "Theorem verification failed".to_string()),
),
span: logicaffeine_language::token::Span::default(),
})
}
}
#[derive(Debug, Clone)]
pub struct TheoremTrace {
pub verified: bool,
pub premises: Vec<String>,
pub goal: String,
pub trace: Option<String>,
pub error: Option<String>,
}
pub fn prove_theorem_trace(input: &str) -> Result<TheoremTrace, ParseError> {
let (proof_exprs, goal_expr, definitions) = theorem_proof_exprs_with_defs(input)?;
let outcome = logicaffeine_proof::verify::prove_certify_check_with_defs(
&proof_exprs,
&goal_expr,
&definitions,
);
Ok(TheoremTrace {
verified: outcome.verified,
premises: proof_exprs.iter().map(|p| p.to_string()).collect(),
goal: goal_expr.to_string(),
trace: outcome.derivation.as_ref().map(|d| d.display_tree()),
error: outcome.verification_error,
})
}
fn user_defined_entries(ctx: &kernel::Context) -> Vec<String> {
let baseline = kernel::interface::Repl::new();
let base = baseline.context();
let mut base_names: HashSet<String> = HashSet::new();
for (name, _) in base.iter_inductives() {
base_names.insert(name.to_string());
}
for (name, _, _) in base.iter_definitions() {
base_names.insert(name.to_string());
}
let mut entries = Vec::new();
for (name, _) in ctx.iter_inductives() {
if !base_names.contains(name) && inductive_is_emittable(ctx, name) {
entries.push(name.to_string());
}
}
for (name, ty, _) in ctx.iter_definitions() {
if !base_names.contains(name)
&& type_is_emittable(ctx, ty, &[])
&& crate::extraction::is_extractable(ctx, name)
{
entries.push(name.to_string());
}
}
entries.sort();
entries.dedup();
entries
}
fn type_is_emittable(ctx: &kernel::Context, ty: &kernel::Term, generics: &[String]) -> bool {
use kernel::Term;
match ty {
Term::Global(name) => {
crate::extraction::primitive_rust_type(name).is_some()
|| (ctx.is_inductive(name)
&& !ctx.get_constructors(name).is_empty()
&& !crate::extraction::is_logical_type(name))
}
Term::Var(v) => generics.iter().any(|g| g == v),
Term::Pi { param_type, body_type, .. } => {
type_is_emittable(ctx, param_type, generics) && type_is_emittable(ctx, body_type, generics)
}
Term::App(f, a) => {
type_is_emittable(ctx, f, generics) && type_is_emittable(ctx, a, generics)
}
_ => false,
}
}
fn inductive_generics(ctx: &kernel::Context, ind: &str) -> Vec<String> {
use kernel::Term;
let mut names = Vec::new();
let mut cur = match ctx.get_global(ind) {
Some(t) => t,
None => return names,
};
while let Term::Pi { param, param_type, body_type } = cur {
if matches!(param_type.as_ref(), Term::Sort(_)) {
names.push(param.clone());
cur = body_type;
} else {
break;
}
}
names
}
fn inductive_is_emittable(ctx: &kernel::Context, ind: &str) -> bool {
use kernel::Term;
let ctors = ctx.get_constructors(ind);
if ctors.is_empty() || crate::extraction::is_logical_type(ind) {
return false;
}
let generics = inductive_generics(ctx, ind);
for (_, ty) in &ctors {
let mut cur = *ty;
for _ in 0..generics.len() {
if let Term::Pi { body_type, .. } = cur {
cur = body_type;
} else {
break;
}
}
while let Term::Pi { param_type, body_type, .. } = cur {
if !type_is_emittable(ctx, param_type, &generics) {
return false;
}
cur = body_type;
}
}
true
}
pub fn extract_math_rust(ctx: &kernel::Context) -> Result<String, String> {
let entries = user_defined_entries(ctx);
if entries.is_empty() {
return Ok("// nothing defined yet — add a Definition or Inductive".to_string());
}
let module = extract_math_module(ctx)?;
let checks: Vec<(String, Vec<kernel::Term>)> =
property_checks(ctx).into_iter().map(|(n, _, p)| (n, p)).collect();
Ok(format!("{module}{}", math_demo_main(ctx, &entries, &checks)))
}
pub fn extract_math_module(ctx: &kernel::Context) -> Result<String, String> {
let entries = user_defined_entries(ctx);
if entries.is_empty() {
return Ok("// nothing defined yet — add a Definition or Inductive".to_string());
}
let refs: Vec<&str> = entries.iter().map(|s| s.as_str()).collect();
let mut module = crate::extraction::extract_programs(ctx, &refs).map_err(|e| e.to_string())?;
let mut check_names: std::collections::HashSet<String> = std::collections::HashSet::new();
for (name, check_fn, _) in property_checks(ctx) {
module.push_str(&check_fn);
check_names.insert(name);
}
let base = baseline_names();
let mut notes: Vec<String> = Vec::new();
for (name, ty, _) in ctx.iter_definitions() {
if base.contains(name) || check_names.contains(name) {
continue;
}
if def_type_is_proposition(ty) {
notes.push(format!(
"// note: `{name}` is a proof of a proposition — proof-irrelevant (no \
computational content), so it has no runnable form; any constructive \
definitions it relies on are extracted above.\n"
));
}
}
notes.sort();
for n in notes {
module.push_str(&n);
}
Ok(module)
}
fn def_type_is_proposition(ty: &kernel::Term) -> bool {
use kernel::Term;
let mut cur = ty;
while let Term::Pi { body_type, .. } = cur {
cur = body_type;
}
let mut head = cur;
while let Term::App(f, _) = head {
head = f;
}
matches!(head, Term::Global(n) if crate::extraction::is_logical_type(n))
}
fn property_checks(ctx: &kernel::Context) -> Vec<(String, String, Vec<kernel::Term>)> {
let base = baseline_names();
let mut checks: Vec<(String, String, Vec<kernel::Term>)> = Vec::new();
for (name, ty, _) in ctx.iter_definitions() {
if base.contains(name) {
continue;
}
if let Some(check_fn) = crate::extraction::emit_property_check(ctx, name, ty) {
checks.push((name.to_string(), check_fn, pi_param_types(ty)));
}
}
checks.sort_by(|a, b| a.0.cmp(&b.0));
checks
}
fn baseline_names() -> std::collections::HashSet<String> {
let baseline = kernel::interface::Repl::new();
let base = baseline.context();
let mut names = std::collections::HashSet::new();
for (n, _) in base.iter_inductives() {
names.insert(n.to_string());
}
for (n, _, _) in base.iter_definitions() {
names.insert(n.to_string());
}
names
}
fn term_uses_div_or_mod(term: &kernel::Term) -> bool {
use kernel::Term;
match term {
Term::Global(n) => n == "div" || n == "mod",
Term::App(f, a) => term_uses_div_or_mod(f) || term_uses_div_or_mod(a),
Term::Lambda { param_type, body, .. } => {
term_uses_div_or_mod(param_type) || term_uses_div_or_mod(body)
}
Term::Pi { param_type, body_type, .. } => {
term_uses_div_or_mod(param_type) || term_uses_div_or_mod(body_type)
}
Term::Fix { body, .. } => term_uses_div_or_mod(body),
Term::Match { discriminant, motive, cases } => {
term_uses_div_or_mod(discriminant)
|| term_uses_div_or_mod(motive)
|| cases.iter().any(term_uses_div_or_mod)
}
_ => false,
}
}
fn math_demo_main(
ctx: &kernel::Context,
entries: &[String],
checks: &[(String, Vec<kernel::Term>)],
) -> String {
use kernel::Term;
let mut lines = Vec::new();
for name in entries {
let Some(body) = ctx.get_definition_body(name) else {
continue; };
if term_uses_div_or_mod(body) {
continue;
}
if matches!(body, Term::Lambda { .. } | Term::Fix { .. }) {
let Some(ty) = ctx.get_definition_type(name) else { continue };
let params = pi_param_types(ty);
let samples: Option<Vec<Term>> =
params.iter().map(|p| sample_value(ctx, p)).collect();
let Some(samples) = samples else { continue }; if samples.is_empty() {
continue;
}
let args_rust: Vec<String> =
samples.iter().map(|s| crate::extraction::emit_value(ctx, s)).collect();
let mut app = Term::Global(name.clone());
for s in &samples {
app = Term::App(Box::new(app), Box::new(s.clone()));
}
let expected = crate::extraction::emit_value(ctx, &kernel::normalize(ctx, &app));
let call = format!("{}({})", name, args_rust.join(", "));
lines.push(format!(" assert_eq!({call}, {expected});"));
lines.push(format!(" println!(\"{name}(..) = {{:?}}\", {call});"));
} else {
let expected = crate::extraction::emit_value(ctx, &kernel::normalize(ctx, body));
lines.push(format!(" assert_eq!({name}(), {expected});"));
lines.push(format!(" println!(\"{name} = {{:?}}\", {name}());"));
}
}
for (name, param_types) in checks {
let samples: Option<Vec<kernel::Term>> =
param_types.iter().map(|p| sample_value(ctx, p)).collect();
let Some(samples) = samples else { continue };
let args: Vec<String> =
samples.iter().map(|s| crate::extraction::emit_value(ctx, s)).collect();
let call = format!("check_{}({})", name, args.join(", "));
lines.push(format!(" assert!({call}, \"theorem {name} failed on sample\");"));
lines.push(format!(" println!(\"\\u{{2713}} {name} holds (checked on a sample)\");"));
}
if lines.is_empty() {
return "\nfn main() {}\n".to_string();
}
format!("\nfn main() {{\n{}\n}}\n", lines.join("\n"))
}
fn pi_param_types(ty: &kernel::Term) -> Vec<kernel::Term> {
use kernel::Term;
let mut params = Vec::new();
let mut cur = ty;
while let Term::Pi { param_type, body_type, .. } = cur {
params.push((**param_type).clone());
cur = body_type;
}
params
}
fn sample_value(ctx: &kernel::Context, ty: &kernel::Term) -> Option<kernel::Term> {
use kernel::{Literal, Term};
match ty {
Term::Global(name) => match name.as_str() {
"Int" => Some(Term::Lit(Literal::Int(0))),
"Float" => Some(Term::Lit(Literal::Float(0.0))),
"Text" => Some(Term::Lit(Literal::Text(String::new()))),
_ if ctx.is_inductive(name) => ctx
.get_constructors(name)
.into_iter()
.find(|(_, cty)| !matches!(cty, Term::Pi { .. }))
.map(|(cname, _)| Term::Global(cname.to_string())),
_ => None,
},
_ => None,
}
}
pub fn extract_math_rust_from_source(input: &str) -> String {
let mut repl = kernel::interface::Repl::new();
let stmts = parse_math_statements(input);
let _ = repl.execute_batch(&stmts);
match extract_math_rust(repl.context()) {
Ok(rust) => rust,
Err(e) => format!("// extraction error: {e}"),
}
}
pub fn extract_math_module_from_source(input: &str) -> String {
let mut repl = kernel::interface::Repl::new();
let stmts = parse_math_statements(input);
let _ = repl.execute_batch(&stmts);
match extract_math_module(repl.context()) {
Ok(rust) => rust,
Err(e) => format!("// extraction error: {e}"),
}
}
pub fn implicit_main(source: &str) -> Option<String> {
if source.lines().any(|l| l.trim_start().starts_with("##")) {
return None;
}
let first = source.lines().find(|l| !l.trim().is_empty())?;
const STATEMENT_HEADS: &[&str] = &[
"Let ", "Show ", "Set ", "If ", "While ", "Repeat ", "Push ",
"Pop ", "Add ", "Remove ", "Call ", "Return ", "Increase ",
"Decrease ", "Break", "Inspect ", "Assert ",
];
let t = first.trim_start();
if STATEMENT_HEADS.iter().any(|h| t.starts_with(h)) {
Some(format!("## Main\n{}", source))
} else {
None
}
}
pub fn partition_mixed(source: &str) -> (String, Option<String>) {
if let Some(wrapped) = implicit_main(source) {
return (wrapped, None);
}
let lines: Vec<&str> = source.lines().collect();
let mut is_math = vec![false; lines.len()];
let mut i = 0;
let mut any = false;
while i < lines.len() {
let t = lines[i].trim();
if !is_math_block_start(t) {
i += 1;
continue;
}
any = true;
if t.starts_with("## Theorem:") || t.starts_with("## Lemma:") {
is_math[i] = true;
i += 1;
while i < lines.len() {
let nt = lines[i].trim();
let indented = lines[i].starts_with(' ') || lines[i].starts_with('\t');
if nt.is_empty() {
is_math[i] = true;
i += 1;
continue;
}
if indented || nt.starts_with("Statement:") || nt.starts_with("Proof:") {
is_math[i] = true;
i += 1;
if nt.starts_with("Proof:") && nt.ends_with('.') {
break;
}
} else {
break;
}
}
} else {
let mut ended = t.ends_with('.');
is_math[i] = true;
i += 1;
while !ended && i < lines.len() {
if lines[i].trim_start().starts_with("## ") {
break;
}
is_math[i] = true;
ended = lines[i].trim().ends_with('.');
i += 1;
}
}
}
if !any {
return (source.to_string(), None);
}
let imp: Vec<&str> = lines
.iter()
.enumerate()
.map(|(j, l)| if is_math[j] { "" } else { *l })
.collect();
let math: Vec<&str> = lines
.iter()
.enumerate()
.filter(|(j, _)| is_math[*j])
.map(|(_, l)| *l)
.collect();
(imp.join("\n"), Some(math.join("\n")))
}
fn is_math_block_start(trimmed: &str) -> bool {
const COQ: [&str; 6] = [
"Definition ", "Inductive ", "Axiom ", "Theorem ", "Lemma ", "Fixpoint ",
];
COQ.iter().any(|k| trimmed.starts_with(k))
|| trimmed.starts_with("## Theorem:")
|| trimmed.starts_with("## Lemma:")
}
pub(crate) fn mixed_proven_module(math_src: &str) -> Option<String> {
let mut repl = kernel::interface::Repl::new();
let stmts = parse_math_statements(math_src);
let _ = repl.execute_batch(&stmts);
let ctx = repl.context();
let mut rust = match extract_math_module(ctx) {
Ok(r) => r,
Err(_) => return None,
};
if !(rust.contains("pub fn") || rust.contains("pub enum") || rust.contains("pub struct")) {
return None;
}
let base = baseline_names();
let mut inds: Vec<String> = Vec::new();
for (name, _) in ctx.iter_inductives() {
if !base.contains(name)
&& inductive_is_emittable(ctx, name)
&& inductive_generics(ctx, name).is_empty()
{
inds.push(name.to_string());
}
}
inds.sort();
for ind in inds {
rust.push_str(&format!(
"impl Showable for {ind} {{ fn format_show(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{ write!(f, \"{{:?}}\", self) }} }}\n"
));
}
Some(rust)
}
pub fn parse_math_statements(code: &str) -> Vec<String> {
let mut statements = Vec::new();
let lines: Vec<&str> = code.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("--") {
i += 1;
continue;
}
if trimmed.starts_with("## To ") {
let mut block = String::new();
block.push_str(trimmed);
i += 1;
while i < lines.len() {
let next_line = lines[i];
let next_trimmed = next_line.trim();
if next_trimmed.is_empty() {
i += 1;
continue;
}
if next_trimmed.starts_with("--") {
i += 1;
continue;
}
let is_indented = next_line.starts_with(' ') || next_line.starts_with('\t');
let is_continuation = next_trimmed.starts_with("Consider ")
|| next_trimmed.starts_with("When ")
|| next_trimmed.starts_with("Yield ");
if is_indented || is_continuation {
block.push(' ');
block.push_str(next_trimmed);
i += 1;
} else {
break;
}
}
statements.push(block);
continue;
}
if trimmed.starts_with("## Theorem:") {
let mut block = String::new();
block.push_str(trimmed);
i += 1;
while i < lines.len() {
let next_line = lines[i];
let next_trimmed = next_line.trim();
if next_trimmed.is_empty() {
i += 1;
continue;
}
if next_trimmed.starts_with("--") {
i += 1;
continue;
}
let is_indented = next_line.starts_with(' ') || next_line.starts_with('\t');
let is_theorem_part = next_trimmed.starts_with("Statement:")
|| next_trimmed.starts_with("Proof:");
if is_indented || is_theorem_part {
block.push('\n');
block.push_str(next_line);
i += 1;
if next_trimmed.starts_with("Proof:") && next_trimmed.ends_with('.') {
break;
}
} else {
break;
}
}
statements.push(block);
continue;
}
if (trimmed.starts_with("A ") || trimmed.starts_with("An ")) && trimmed.contains(" is either")
{
if trimmed.ends_with('.') && !trimmed.trim_end_matches('.').ends_with(':') {
statements.push(trimmed.to_string());
i += 1;
continue;
}
let mut block = String::new();
block.push_str(trimmed);
i += 1;
while i < lines.len() {
let next_line = lines[i];
let next_trimmed = next_line.trim();
if next_trimmed.is_empty() {
i += 1;
continue;
}
if next_trimmed.starts_with("--") {
i += 1;
continue;
}
let is_indented = next_line.starts_with(' ') || next_line.starts_with('\t');
let looks_like_variant = next_trimmed.starts_with("a ")
|| next_trimmed
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false);
if is_indented
|| (looks_like_variant
&& !next_trimmed.starts_with("A ")
&& !next_trimmed.starts_with("An "))
{
if !block.ends_with(':') {
block.push_str(" or ");
} else {
block.push(' ');
}
block.push_str(next_trimmed.trim_end_matches('.'));
i += 1;
} else {
break;
}
}
if !block.ends_with('.') {
block.push('.');
}
statements.push(block);
continue;
}
let mut current_stmt = String::new();
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("--") {
i += 1;
continue;
}
if !current_stmt.is_empty() {
current_stmt.push(' ');
}
current_stmt.push_str(trimmed);
i += 1;
if trimmed.ends_with('.') {
break;
}
}
if !current_stmt.is_empty() {
statements.push(current_stmt);
}
}
statements
}
pub fn extract_logic_rust(input: &str) -> Result<String, String> {
extract_logic_impl(input, true)
}
pub fn extract_logic_module(input: &str) -> Result<String, String> {
extract_logic_impl(input, false)
}
fn extract_logic_impl(input: &str, emit_main: bool) -> Result<String, String> {
use crate::extraction::fol_model::{fol_to_model_checker, fol_to_model_checker_module};
let emit = |premises: &[logicaffeine_proof::ProofExpr],
goal: &logicaffeine_proof::ProofExpr,
english: &str,
fol: &str| {
if emit_main {
fol_to_model_checker(premises, goal, english, fol)
} else {
fol_to_model_checker_module(premises, goal, english, fol)
}
};
if let Ok((premises, goal)) = theorem_proof_exprs(input) {
if looks_like_grid(&premises) {
return Ok("// this is a finite-domain puzzle — run it with Execute. \
Compiling it to Rust would run the full solver synchronously."
.to_string());
}
let fol = if premises.is_empty() {
goal.to_string()
} else {
format!(
"{} ⊢ {}",
premises.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", "),
goal
)
};
return Ok(emit(&premises, &goal, input, &fol));
}
let proof = compile_for_proof(input);
if let Some(expr) = proof.proof_expr {
let fol = proof.logic_string.clone().unwrap_or_else(|| expr.to_string());
return Ok(emit(&[], &expr, input, &fol));
}
Ok("// could not parse this input into a logical formula to compile".to_string())
}
#[cfg(feature = "verification")]
fn smt_theory_for(
premises: &[ProofExpr],
goal: Option<&ProofExpr>,
) -> logicaffeine_proof::oracle::SmtTheory {
let mut exprs: Vec<ProofExpr> = premises.to_vec();
if let Some(g) = goal {
exprs.push(g.clone());
}
let cumulative_predicates = logicaffeine_proof::oracle::predicate_names(&exprs)
.into_iter()
.filter(|name| logicaffeine_language::lexicon::is_mass_noun(name))
.collect();
logicaffeine_proof::oracle::SmtTheory {
cumulative_predicates,
}
}
#[cfg(feature = "verification")]
pub fn check_theorem_smt(
input: &str,
) -> Result<logicaffeine_proof::oracle::SmtVerdict, ParseError> {
let (proof_exprs, goal_expr) = theorem_proof_exprs(input)?;
let theory = smt_theory_for(&proof_exprs, Some(&goal_expr));
Ok(logicaffeine_proof::oracle::oracle_entails_with_theory(
&proof_exprs,
&goal_expr,
&theory,
))
}
#[cfg(feature = "verification")]
pub fn check_theorem_premises_consistent(
input: &str,
) -> Result<logicaffeine_proof::oracle::SmtConsistency, ParseError> {
let (proof_exprs, _goal) = theorem_proof_exprs(input)?;
let theory = smt_theory_for(&proof_exprs, None);
Ok(logicaffeine_proof::oracle::oracle_consistent_with_theory(
&proof_exprs,
&theory,
))
}
#[cfg(feature = "verification")]
pub fn check_theorem_defeasible(
input: &str,
) -> Result<logicaffeine_proof::oracle::SmtVerdict, ParseError> {
let (proof_exprs, goal, defaults, _definitions) = theorem_problem(input, true)?;
let theory = smt_theory_for(&proof_exprs, Some(&goal));
Ok(crate::defeasible::defeasible_entails(
&proof_exprs,
&goal,
&defaults,
&theory,
))
}
#[cfg(feature = "verification")]
pub fn check_theorem_defeasible_consistent(
input: &str,
) -> Result<logicaffeine_proof::oracle::SmtConsistency, ParseError> {
let (proof_exprs, _goal, defaults, _definitions) = theorem_problem(input, true)?;
let theory = smt_theory_for(&proof_exprs, None);
Ok(crate::defeasible::defeasible_consistent(
&proof_exprs,
&defaults,
&theory,
))
}
pub fn theorem_proof_exprs(input: &str) -> Result<(Vec<ProofExpr>, ProofExpr), ParseError> {
let (premises, goal, _defaults, _definitions) = theorem_problem(input, false)?;
Ok((premises, goal))
}
pub fn theorem_proof_exprs_with_defs(
input: &str,
) -> Result<
(
Vec<ProofExpr>,
ProofExpr,
Vec<logicaffeine_proof::verify::Definition>,
),
ParseError,
> {
let (premises, goal, _defaults, definitions) = theorem_problem(input, false)?;
Ok((premises, goal, definitions))
}
pub fn theorem_dependency_graph(
input: &str,
) -> Result<logicaffeine_proof::verify::DependencyGraph, ParseError> {
let (premises, goal, definitions) = theorem_proof_exprs_with_defs(input)?;
Ok(logicaffeine_proof::verify::dependency_graph(
&definitions,
&premises,
&goal,
))
}
pub fn answer_question(input: &str) -> Result<Vec<String>, ParseError> {
let (premises, goal) = theorem_proof_exprs(input)?;
match &goal {
ProofExpr::Exists { variable, body } => Ok(answer_wh(&premises, variable, body)),
_ => Err(ParseError {
kind: logicaffeine_language::error::ParseErrorKind::Custom(
"Prove goal is not a question (expected a wh-question ∃-form)".to_string(),
),
span: logicaffeine_language::token::Span::default(),
}),
}
}
fn answer_wh(premises: &[ProofExpr], var: &str, body: &ProofExpr) -> Vec<String> {
let mut candidates: Vec<String> = Vec::new();
for p in premises {
collect_constants(p, &mut candidates);
}
candidates.sort();
candidates.dedup();
let trace = std::env::var("LOGOS_TRACE").is_ok();
let t0 = trace.then(std::time::Instant::now);
let prepared = prepare_premises(premises);
if let Some(t0) = t0 {
eprintln!(
"[answer] {} premises → prepared ({} clauses) in {:.2?}; {} candidates",
premises.len(),
prepared.len(),
t0.elapsed(),
candidates.len()
);
}
let mut answers = Vec::new();
for c in &candidates {
let tc = trace.then(std::time::Instant::now);
let candidate_goal =
logicaffeine_language::proof_convert::instantiate_var_with_constant(body, var, c);
let ok = candidate_entailed_prepared(&prepared, &candidate_goal);
if let Some(tc) = tc {
eprintln!("[answer] {:<14} {} ({:.2?})", c, ok, tc.elapsed());
}
if ok {
answers.push(c.clone());
}
}
if let Some(t0) = t0 {
eprintln!("[answer] total {:.2?} → {:?}", t0.elapsed(), answers);
}
answers
}
pub fn solve_grid(input: &str) -> Option<SolvedGrid> {
let parsed = parse_theorem(input).ok()?;
if !looks_like_grid(&parsed.premises) {
return None;
}
solve_grid_from_premises(&parsed.premises, input)
}
struct GridClosure {
var: String,
row_sort: String,
disjuncts: Vec<(String, ProofExpr)>,
}
fn flatten_or<'a>(e: &'a ProofExpr, out: &mut Vec<&'a ProofExpr>) {
match e {
ProofExpr::Or(l, r) => {
flatten_or(l, out);
flatten_or(r, out);
}
other => out.push(other),
}
}
fn antecedent_sort(e: &ProofExpr, var: &str) -> Option<String> {
let is_var = |t: &ProofTerm| matches!(t, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == var);
match e {
ProofExpr::Predicate { name, args, .. } if args.len() == 1 && is_var(&args[0]) => {
Some(name.clone())
}
ProofExpr::And(l, r) => antecedent_sort(l, var).or_else(|| antecedent_sort(r, var)),
_ => None,
}
}
fn disjunct_value(d: &ProofExpr, var: &str) -> Option<String> {
let is_var = |t: &ProofTerm| matches!(t, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == var);
match d {
ProofExpr::Predicate { name, args, .. } => match args.as_slice() {
[a] if is_var(a) => Some(name.clone()),
[a, ProofTerm::Constant(c)] if is_var(a) => Some(c.clone()),
[ProofTerm::Constant(c), a] if is_var(a) => Some(c.clone()),
_ => None,
},
_ => None,
}
}
fn extract_grid_closures(premises: &[ProofExpr]) -> Vec<GridClosure> {
fn from_forall(e: &ProofExpr, out: &mut Vec<GridClosure>) {
if let ProofExpr::ForAll { variable, body } = e {
match body.as_ref() {
ProofExpr::Implies(ante, cons) => {
let mut leaves = Vec::new();
flatten_or(cons, &mut leaves);
let disjuncts: Vec<(String, ProofExpr)> = leaves
.iter()
.filter_map(|d| disjunct_value(d, variable).map(|v| (v, (*d).clone())))
.collect();
if let Some(row_sort) = antecedent_sort(ante, variable) {
if !disjuncts.is_empty() && disjuncts.len() == leaves.len() {
out.push(GridClosure { var: variable.clone(), row_sort, disjuncts });
}
}
}
ProofExpr::ForAll { .. } => from_forall(body, out),
_ => {}
}
}
}
let mut out = Vec::new();
for p in premises {
from_forall(&erase_tense(p), &mut out);
}
out
}
fn title_case(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
}
fn grid_column_label(
values: &[String],
sorts: &std::collections::HashMap<String, Vec<ProofTerm>>,
idx: usize,
) -> String {
let want: Vec<String> = values.iter().map(|v| v.to_lowercase()).collect();
let mut keys: Vec<&String> = sorts.keys().collect();
keys.sort();
for k in keys {
let dom: std::collections::HashSet<String> = sorts[k]
.iter()
.filter_map(|t| match t {
ProofTerm::Constant(c) => Some(c.to_lowercase()),
_ => None,
})
.collect();
if !dom.is_empty() && want.iter().all(|v| dom.contains(v)) {
return title_case(k);
}
}
format!("Category {}", idx + 1)
}
fn category_stem(s: &str) -> String {
let mut w = s.to_lowercase();
if w.len() > 5 && w.ends_with("ing") {
w.truncate(w.len() - 3);
}
if w.len() > 3 && w.ends_with('e') {
w.truncate(w.len() - 1);
}
w
}
fn surface_form_map(input: &str) -> std::collections::HashMap<String, String> {
let mut map = std::collections::HashMap::new();
for word in input.split(|c: char| !c.is_alphanumeric()) {
if !word.is_empty() {
map.entry(category_stem(word)).or_insert_with(|| word.to_string());
}
}
map
}
fn solve_grid_from_premises(premises: &[ProofExpr], input: &str) -> Option<SolvedGrid> {
let closures = extract_grid_closures(premises);
if closures.is_empty() {
return None;
}
let untensed: Vec<ProofExpr> = premises.iter().map(erase_tense).collect();
let sorts = logicaffeine_proof::grounding::sort_domains(&untensed);
let row_sort = closures[0].row_sort.clone();
let rows: Vec<String> = sorts
.get(&row_sort)
.map(|dom| {
dom.iter()
.filter_map(|t| match t {
ProofTerm::Constant(c) => Some(c.clone()),
_ => None,
})
.collect()
})
.unwrap_or_default();
if rows.is_empty() {
return None;
}
let prepared = prepare_premises_opts(premises, true);
let surface = surface_form_map(input);
let display = |v: &str| surface.get(&category_stem(v)).cloned().unwrap_or_else(|| v.to_string());
let mut columns = Vec::new();
for clo in &closures {
if clo.row_sort != row_sort {
continue;
}
let values: Vec<String> = clo.disjuncts.iter().map(|(v, _)| display(v)).collect();
let mut cells = Vec::with_capacity(rows.len());
for r in &rows {
let mut found = None;
for (label, dj) in &clo.disjuncts {
let atom = erase_tense(
&logicaffeine_language::proof_convert::instantiate_var_with_constant(
dj, &clo.var, r,
),
);
if candidate_entailed_prepared(&prepared, &atom) {
found = Some(display(label));
break;
}
}
cells.push(found);
}
let label = grid_column_label(&values, &sorts, columns.len());
columns.push(GridColumn { label, values, cells });
}
Some(SolvedGrid { row_label: title_case(&row_sort), rows, columns })
}
fn looks_like_grid(premises: &[ProofExpr]) -> bool {
fn has_disjunctive_closure(e: &ProofExpr) -> bool {
match e {
ProofExpr::ForAll { body, .. } => match body.as_ref() {
ProofExpr::Implies(_, c) => matches!(c.as_ref(), ProofExpr::Or(..)),
ProofExpr::ForAll { .. } => has_disjunctive_closure(body),
_ => false,
},
ProofExpr::Temporal { body, .. } => has_disjunctive_closure(body),
_ => false,
}
}
!logicaffeine_proof::grounding::at_most_one_lemmas(premises).is_empty()
|| premises.iter().any(has_disjunctive_closure)
}
fn prepare_premises(premises: &[ProofExpr]) -> Vec<ProofExpr> {
prepare_premises_opts(premises, false)
}
fn prepare_premises_opts(premises: &[ProofExpr], with_functionality: bool) -> Vec<ProofExpr> {
let mut untensed: Vec<ProofExpr> = premises.iter().map(erase_tense).collect();
let lemmas = logicaffeine_proof::grounding::at_most_one_lemmas(&untensed);
untensed.extend(lemmas);
let defns = logicaffeine_proof::grounding::definite_property_implications(&untensed);
untensed.extend(defns);
if with_functionality {
let func = logicaffeine_proof::grounding::functionality_lemmas(&untensed);
untensed.extend(func);
let cols = logicaffeine_proof::grounding::column_closure_lemmas(&untensed);
untensed.extend(cols);
}
let fallback = logicaffeine_proof::grounding::domain_constants(&untensed);
let mut sorts = logicaffeine_proof::grounding::sort_domains(&untensed);
bind_occasion_synonyms_to_row_domain(&untensed, &mut sorts);
let grounded: Vec<ProofExpr> = untensed
.iter()
.map(|p| logicaffeine_proof::grounding::ground_sorted(p, &sorts, &fallback))
.collect();
let discharged = logicaffeine_proof::grounding::discharge_unary_facts(&grounded);
logicaffeine_proof::grounding::simplify_trivial_identities(&discharged)
}
#[cfg(not(feature = "verification"))]
fn candidate_entailed_prepared(prepared: &[ProofExpr], goal: &ProofExpr) -> bool {
let g = erase_tense(goal);
match logicaffeine_proof::rup::entails_certified(prepared, &g) {
Some(logicaffeine_proof::rup::Verdict::Entailed) => return true,
Some(logicaffeine_proof::rup::Verdict::NotEntailed) => return false,
None => {}
}
logicaffeine_proof::verify::prove_certify_check_bounded(prepared, &g, 100).verified
}
fn bind_occasion_synonyms_to_row_domain(
premises: &[ProofExpr],
sorts: &mut std::collections::HashMap<String, Vec<logicaffeine_proof::ProofTerm>>,
) {
use logicaffeine_language::lexicon::lookup_sort;
use logicaffeine_proof::ProofTerm;
let is_occasion = |n: &str| lookup_sort(n).map_or(false, |s| s.is_occasion());
let mut row_domain: Vec<ProofTerm> = Vec::new();
for (name, dom) in sorts.iter() {
if is_occasion(name) {
for c in dom {
if !row_domain.contains(c) {
row_domain.push(c.clone());
}
}
}
}
if row_domain.is_empty() {
return;
}
let mut names = HashSet::new();
for p in premises {
collect_unary_predicate_names(p, &mut names);
}
for name in names {
if is_occasion(&name) {
sorts.entry(name).or_insert_with(|| row_domain.clone());
}
}
}
fn collect_unary_predicate_names(e: &ProofExpr, out: &mut HashSet<String>) {
match e {
ProofExpr::Predicate { name, args, .. } if args.len() == 1 => {
out.insert(name.clone());
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_unary_predicate_names(l, out);
collect_unary_predicate_names(r, out);
}
ProofExpr::Counterfactual { antecedent, consequent } => {
collect_unary_predicate_names(antecedent, out);
collect_unary_predicate_names(consequent, out);
}
ProofExpr::Not(x)
| ProofExpr::ForAll { body: x, .. }
| ProofExpr::Exists { body: x, .. }
| ProofExpr::Temporal { body: x, .. }
| ProofExpr::Modal { body: x, .. } => collect_unary_predicate_names(x, out),
_ => {}
}
}
#[cfg(feature = "verification")]
fn candidate_entailed_prepared(prepared: &[ProofExpr], goal: &ProofExpr) -> bool {
let g = erase_tense(goal);
match logicaffeine_proof::rup::entails_certified(prepared, &g) {
Some(logicaffeine_proof::rup::Verdict::Entailed) => return true,
Some(logicaffeine_proof::rup::Verdict::NotEntailed) => return false,
None => {}
}
if logicaffeine_proof::verify::prove_certify_check_bounded(prepared, &g, 40).verified {
return true;
}
matches!(
logicaffeine_proof::oracle::oracle_entails(prepared, &g),
logicaffeine_proof::oracle::SmtVerdict::Entailed
)
}
fn erase_tense(e: &ProofExpr) -> ProofExpr {
match e {
ProofExpr::Temporal { body, .. } => erase_tense(body),
ProofExpr::And(l, r) => {
ProofExpr::And(Box::new(erase_tense(l)), Box::new(erase_tense(r)))
}
ProofExpr::Or(l, r) => ProofExpr::Or(Box::new(erase_tense(l)), Box::new(erase_tense(r))),
ProofExpr::Implies(l, r) => {
ProofExpr::Implies(Box::new(erase_tense(l)), Box::new(erase_tense(r)))
}
ProofExpr::Iff(l, r) => ProofExpr::Iff(Box::new(erase_tense(l)), Box::new(erase_tense(r))),
ProofExpr::Not(x) => ProofExpr::Not(Box::new(erase_tense(x))),
ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(erase_tense(body)),
},
ProofExpr::Exists { variable, body } => ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(erase_tense(body)),
},
other => other.clone(),
}
}
fn collect_constants(e: &ProofExpr, out: &mut Vec<String>) {
use logicaffeine_proof::ProofTerm;
fn term(t: &ProofTerm, out: &mut Vec<String>) {
match t {
ProofTerm::Constant(s) => out.push(s.clone()),
ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
args.iter().for_each(|a| term(a, out))
}
_ => {}
}
}
match e {
ProofExpr::Predicate { args, .. } => args.iter().for_each(|a| term(a, out)),
ProofExpr::Identity(a, b) => {
term(a, out);
term(b, out);
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_constants(l, out);
collect_constants(r, out);
}
ProofExpr::Not(x) => collect_constants(x, out),
ProofExpr::ForAll { body, .. }
| ProofExpr::Exists { body, .. }
| ProofExpr::Temporal { body, .. } => collect_constants(body, out),
ProofExpr::Term(t) => term(t, out),
_ => {}
}
}
fn lower_definition(
def: &logicaffeine_language::ast::DefinitionBlock,
interner: &Interner,
) -> Option<logicaffeine_proof::verify::Definition> {
use logicaffeine_language::proof_convert::logic_expr_to_proof_expr;
let (name, params) = match logic_expr_to_proof_expr(def.definiendum, interner) {
ProofExpr::Predicate { name, args, .. } => {
let params = args
.iter()
.filter_map(|t| match t {
ProofTerm::Constant(n) | ProofTerm::Variable(n) => Some(n.clone()),
_ => None,
})
.collect();
(name, params)
}
_ => return None,
};
let definiens = logic_expr_to_proof_expr(def.definiens, interner);
Some(logicaffeine_proof::verify::Definition {
name,
params,
definiens,
})
}
fn theorem_problem(
input: &str,
defeasible: bool,
) -> Result<
(
Vec<ProofExpr>,
ProofExpr,
Vec<logicaffeine_language::proof_convert::DefaultRule>,
Vec<logicaffeine_proof::verify::Definition>,
),
ParseError,
> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(input, &mut interner);
let tokens = lexer.tokenize();
let mwe_trie = mwe::build_mwe_trie();
let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
let type_registry = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
discovery.run()
};
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let ctx = AstContext::new(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
);
let mut world_state = drs::WorldState::new();
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
if defeasible {
parser.set_pragmatic_mode(true);
}
let statements = parser.parse_program()?;
let theorem = statements
.iter()
.find_map(|stmt| {
if let Stmt::Theorem(t) = stmt {
Some(t)
} else {
None
}
})
.ok_or_else(|| ParseError {
kind: logicaffeine_language::error::ParseErrorKind::Custom("No theorem block found in input".to_string()),
span: logicaffeine_language::token::Span::default(),
})?;
let mut defaults = Vec::new();
let proof_exprs: Vec<ProofExpr> = theorem
.premises
.iter()
.map(|premise| {
if defeasible {
logicaffeine_language::proof_convert::logic_expr_to_proof_expr_defeasible(
premise,
&interner,
&mut defaults,
)
} else {
logic_expr_to_proof_expr(premise, &interner)
}
})
.collect();
let goal_expr = logic_expr_to_proof_expr(theorem.goal, &interner);
let definitions: Vec<logicaffeine_proof::verify::Definition> = statements
.iter()
.filter_map(|stmt| match stmt {
Stmt::Definition(d) => lower_definition(d, &interner),
_ => None,
})
.collect();
Ok((proof_exprs, goal_expr, defaults, definitions))
}