use std::sync::OnceLock;
use mrs_core::SymbolTable;
use mrs_core::clause::{Clause, ClauseSource};
use mrs_core::display::DisplayWithSymbols;
static PROBLEM_PATH: OnceLock<String> = OnceLock::new();
pub fn set_problem_path(path: impl Into<String>) {
let _ = PROBLEM_PATH.set(path.into());
}
fn problem_path() -> &'static str {
PROBLEM_PATH.get().map(String::as_str).unwrap_or("input")
}
fn status_for_rule(rule: &str) -> &'static str {
match rule {
"skolemisation" => "esa",
"negated_conjecture" => "cth",
_ => "thm",
}
}
pub fn format_tstp(proof: &[Clause], symbols: &SymbolTable) -> String {
let mut proof_sorted = proof.to_vec();
proof_sorted.sort_unstable_by_key(|c| c.id.0);
let mut lines = Vec::new();
let problem_path = problem_path();
for clause in &proof_sorted {
let id = clause.id.0;
let is_formula_step = clause.formula.is_some();
let body = if let Some(formula) = &clause.formula {
format!("{}", formula.display(symbols))
} else if clause.is_empty() {
"$false".to_string()
} else {
format!("{}", clause.display(symbols))
};
let annotation = match &clause.source {
ClauseSource::Input { name, role: _ } => {
format!("file('{}', '{}')", problem_path, name)
}
ClauseSource::Inference { rule, parents } => {
let parent_names: Vec<String> =
parents.iter().map(|p| format!("c{}", p.0)).collect();
let status = status_for_rule(rule);
format!(
"inference({}, [status({})], [{}])",
rule,
status,
parent_names.join(", ")
)
}
ClauseSource::Introduced { symbol } => format!(
"introduced(definition, [new_symbols(definition, [{}])])",
symbols.resolve(*symbol)
),
};
let role = match &clause.source {
ClauseSource::Input { role, .. } => role.as_str(),
ClauseSource::Inference { rule, .. } if *rule == "negated_conjecture" => {
"negated_conjecture"
}
ClauseSource::Inference { .. } => "plain",
ClauseSource::Introduced { .. } => "definition",
};
let wrapper = if is_formula_step { "fof" } else { "cnf" };
lines.push(format!(
"{}(c{}, {}, {}, {}).",
wrapper, id, role, body, annotation
));
}
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
use mrs_core::clause::{Clause, ClauseId, ClauseSource};
use mrs_core::{Atom, Literal, Term};
#[test]
fn format_input_clause_path_annotation() {
let mut syms = SymbolTable::new();
let p = syms.intern("p");
let a = syms.intern("a");
let make_clause = || {
Clause::new(
ClauseId(0),
vec![Literal::pos(Atom::pred(p, vec![Term::constant(a)]))],
ClauseSource::Input {
name: "ax1".into(),
role: "axiom".into(),
},
)
};
let output = format_tstp(&[make_clause()], &syms);
assert!(output.contains("cnf(c0, axiom, p(a), file('input', 'ax1'))."));
set_problem_path("/starexec/sandbox/problems/SEU140+2.p");
let output = format_tstp(&[make_clause()], &syms);
assert!(output.contains(
"cnf(c0, axiom, p(a), file('/starexec/sandbox/problems/SEU140+2.p', 'ax1'))."
));
}
#[test]
fn format_inferred_clause() {
let mut syms = SymbolTable::new();
let p = syms.intern("p");
let c = Clause::new(
ClauseId(5),
vec![Literal::pos(Atom::prop(p))],
ClauseSource::Inference {
rule: "resolution",
parents: vec![ClauseId(0), ClauseId(1)].into(),
},
);
let output = format_tstp(&[c], &syms);
assert!(output.contains("inference(resolution, [status(thm)], [c0, c1])"));
assert!(output.contains("cnf(c5, plain,"));
}
#[test]
fn format_empty_clause() {
let syms = SymbolTable::new();
let c = Clause::new(
ClauseId(10),
vec![],
ClauseSource::Inference {
rule: "resolution",
parents: vec![ClauseId(3), ClauseId(7)].into(),
},
);
let output = format_tstp(&[c], &syms);
assert!(output.contains("$false"));
assert!(output.contains("inference(resolution, [status(thm)], [c3, c7])"));
}
#[test]
fn format_formula_step_uses_fof_wrapper() {
use mrs_core::Formula;
let mut syms = SymbolTable::new();
let p = syms.intern("p");
let a = syms.intern("a");
let leaf = Clause::new_formula_step(
ClauseId(0),
Formula::atom(Atom::pred(p, vec![Term::constant(a)])),
ClauseSource::Input {
name: "ax1".into(),
role: "axiom".into(),
},
);
let nnf_step = Clause::new_formula_step(
ClauseId(1),
Formula::atom(Atom::pred(p, vec![Term::constant(a)])),
ClauseSource::Inference {
rule: "fof_nnf_transformation",
parents: vec![ClauseId(0)].into(),
},
);
let output = format_tstp(&[leaf, nnf_step], &syms);
assert!(output.contains("fof(c0, axiom, p(a), file('input', 'ax1'))."));
assert!(output.contains(
"fof(c1, plain, p(a), inference(fof_nnf_transformation, [status(thm)], [c0]))."
));
assert!(!output.contains("cnf("));
}
#[test]
fn status_is_esa_for_skolemisation_and_cth_for_negated_conjecture() {
use mrs_core::Formula;
let mut syms = SymbolTable::new();
let p = syms.intern("p");
let skolem_step = Clause::new_formula_step(
ClauseId(2),
Formula::atom(Atom::prop(p)),
ClauseSource::Inference {
rule: "skolemisation",
parents: vec![ClauseId(1)].into(),
},
);
let neg_conj_step = Clause::new_formula_step(
ClauseId(3),
Formula::atom(Atom::prop(p)),
ClauseSource::Inference {
rule: "negated_conjecture",
parents: vec![ClauseId(2)].into(),
},
);
let output = format_tstp(&[skolem_step, neg_conj_step], &syms);
assert!(output.contains("inference(skolemisation, [status(esa)], [c1])"));
assert!(output.contains("inference(negated_conjecture, [status(cth)], [c2])"));
assert!(output.contains("fof(c2, plain,"));
assert!(output.contains("fof(c3, negated_conjecture,"));
}
#[test]
fn negated_conjecture_step_has_role_negated_conjecture_not_plain() {
use mrs_core::Formula;
let mut syms = SymbolTable::new();
let p = syms.intern("p");
let conjecture_leaf = Clause::new_formula_step(
ClauseId(0),
Formula::atom(Atom::prop(p)),
ClauseSource::Input {
name: "conj1".into(),
role: "conjecture".into(),
},
);
let negated_conjecture_step = Clause::new_formula_step(
ClauseId(1),
Formula::neg(Formula::atom(Atom::prop(p))),
ClauseSource::Inference {
rule: "negated_conjecture",
parents: vec![ClauseId(0)].into(),
},
);
let output = format_tstp(&[conjecture_leaf, negated_conjecture_step], &syms);
assert!(
output.contains("fof(c1, negated_conjecture,"),
"negated_conjecture step must have its own role set to \
`negated_conjecture`, not `plain` -- GDV requires this to \
accept its relationship to the conjecture-role parent, got: {output}"
);
assert!(!output.contains("fof(c1, plain,"));
}
#[test]
fn cnf_transformation_cites_skolemisation_step() {
let mut syms = SymbolTable::new();
let p = syms.intern("p");
let final_clause = Clause::new(
ClauseId(4),
vec![Literal::pos(Atom::prop(p))],
ClauseSource::Inference {
rule: "cnf_transformation",
parents: vec![ClauseId(2)].into(),
},
);
let output = format_tstp(&[final_clause], &syms);
assert!(
output
.contains("cnf(c4, plain, p, inference(cnf_transformation, [status(thm)], [c2])).")
);
}
#[test]
fn introduced_definition_step_has_no_status_and_no_parents() {
use mrs_core::Formula;
let mut syms = SymbolTable::new();
let def = syms.intern("def_ax1_0");
let q = syms.intern("q");
let def_step = Clause::new_formula_step(
ClauseId(5),
Formula::iff(Formula::atom(Atom::prop(def)), Formula::atom(Atom::prop(q))),
ClauseSource::Introduced { symbol: def },
);
let output = format_tstp(&[def_step], &syms);
assert!(output.contains(
"fof(c5, definition, (def_ax1_0 <=> q), introduced(definition, [new_symbols(definition, [def_ax1_0])]))."
));
}
}