use crate::ast::Annotations;
use crate::ast::common::{AtomicWord, GeneralTerm};
#[derive(Debug, Clone)]
pub struct SkolemizeInfo<'a> {
pub var: &'a str,
pub skolem_symbol: &'a str,
pub args: Vec<&'a str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParentRef<'a> {
pub name: &'a str,
pub negated: bool,
}
impl<'a> Annotations<'a> {
pub fn inference_rule(&self) -> Option<&'a str> {
match &self.source {
GeneralTerm::Function(AtomicWord::Lower("inference"), args) if args.len() == 3 => {
match &args[0] {
GeneralTerm::Word(AtomicWord::Lower(s)) => Some(*s),
GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => Some(*s),
_ => None,
}
}
_ => None,
}
}
pub fn parent_names(&self) -> Vec<&'a str> {
self.parent_refs().into_iter().map(|r| r.name).collect()
}
pub fn parent_refs(&self) -> Vec<ParentRef<'a>> {
let mut out = Vec::new();
match &self.source {
GeneralTerm::Function(AtomicWord::Lower("inference"), args) if args.len() == 3 => {
if let GeneralTerm::List(items) = &args[2] {
for it in items {
collect_parent_refs(it, false, &mut out);
}
}
}
GeneralTerm::Word(AtomicWord::Lower(s))
| GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => {
out.push(ParentRef {
name: s,
negated: false,
});
}
_ => {}
}
out
}
fn info_items(&self) -> &[GeneralTerm<'a>] {
match &self.source {
GeneralTerm::Function(AtomicWord::Lower("inference"), args) if args.len() == 3 => {
match &args[1] {
GeneralTerm::List(items) => items.as_slice(),
_ => &[],
}
}
_ => &[],
}
}
pub fn status(&self) -> Option<&'a str> {
for it in self.info_items() {
if let GeneralTerm::Function(AtomicWord::Lower("status"), inner) = it
&& let Some(g) = inner.first()
{
match g {
GeneralTerm::Word(AtomicWord::Lower(s)) => return Some(*s),
GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => return Some(*s),
_ => {}
}
}
}
None
}
pub fn new_symbols(&self) -> Vec<&'a str> {
for it in self.info_items() {
if let GeneralTerm::Function(AtomicWord::Lower("new_symbols"), inner) = it
&& inner.len() == 2
&& let GeneralTerm::List(items) = &inner[1]
{
return items
.iter()
.filter_map(|g| match g {
GeneralTerm::Word(AtomicWord::Lower(s)) => Some(*s),
GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => Some(*s),
_ => None,
})
.collect();
}
}
Vec::new()
}
pub fn skolemize_info(&self) -> Option<SkolemizeInfo<'a>> {
for it in self.info_items() {
if let GeneralTerm::Function(AtomicWord::Lower("skolemize"), inner) = it
&& inner.len() == 2
{
let var = match &inner[0] {
GeneralTerm::Variable(v) => *v,
GeneralTerm::Word(AtomicWord::Lower(s)) => *s,
GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => *s,
_ => continue,
};
let (sk_sym, args) = match &inner[1] {
GeneralTerm::Function(AtomicWord::Lower(sym), a) => {
let args: Vec<&str> = a
.iter()
.filter_map(|g| match g {
GeneralTerm::Variable(v) => Some(*v),
_ => None,
})
.collect();
(*sym, args)
}
GeneralTerm::Function(AtomicWord::SingleQuoted(sym), a) => {
let args: Vec<&str> = a
.iter()
.filter_map(|g| match g {
GeneralTerm::Variable(v) => Some(*v),
_ => None,
})
.collect();
(*sym, args)
}
GeneralTerm::Word(AtomicWord::Lower(sym)) => (*sym, Vec::new()),
GeneralTerm::Word(AtomicWord::SingleQuoted(sym)) => (*sym, Vec::new()),
_ => continue,
};
return Some(SkolemizeInfo {
var,
skolem_symbol: sk_sym,
args,
});
}
}
None
}
pub fn file_source(&self) -> Option<(&'a str, &'a str)> {
match &self.source {
GeneralTerm::Function(AtomicWord::Lower("file"), args) if args.len() == 2 => {
let path = match &args[0] {
GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => *s,
GeneralTerm::Word(AtomicWord::Lower(s)) => *s,
_ => return None,
};
let name = match &args[1] {
GeneralTerm::Word(AtomicWord::Lower(s)) => *s,
GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => *s,
GeneralTerm::Number(n) => n.as_str(),
_ => return None,
};
Some((path, name))
}
_ => None,
}
}
}
fn collect_parent_refs<'a>(t: &GeneralTerm<'a>, negated: bool, out: &mut Vec<ParentRef<'a>>) {
match t {
GeneralTerm::Word(AtomicWord::Lower(s))
| GeneralTerm::Word(AtomicWord::SingleQuoted(s)) => {
out.push(ParentRef { name: s, negated });
}
GeneralTerm::Number(n) => {
out.push(ParentRef {
name: n.as_str(),
negated,
});
}
GeneralTerm::Function(AtomicWord::Lower("inference"), args) if args.len() == 3 => {
let next_negated = match &args[0] {
GeneralTerm::Word(AtomicWord::Lower("assume_negation"))
| GeneralTerm::Word(AtomicWord::SingleQuoted("assume_negation")) => !negated,
_ => negated,
};
if let GeneralTerm::List(items) = &args[2] {
for it in items {
collect_parent_refs(it, next_negated, out);
}
}
}
GeneralTerm::ColonPair(left, _bindings) => {
collect_parent_refs(left, negated, out);
}
_ => {}
}
}
pub fn proof_header_link(input: &str) -> Option<&str> {
for line in input.lines() {
let l = line.trim_start();
let Some(l) = l.strip_prefix('%') else {
continue;
};
let l = l.trim_start();
if let Some(rest) = l.strip_prefix("Proof") {
let rest = rest.trim_start();
if let Some(rest) = rest.strip_prefix(':') {
return Some(rest.trim());
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse_tptp;
fn parse_single(input: &str) -> Annotations<'_> {
let problem = parse_tptp(input).expect("parse");
let af = problem
.formulas
.into_iter()
.next()
.expect("at least one annotated formula");
match af {
crate::ast::AnnotatedFormula::FOF(f) => f.annotations.expect("annotations"),
_ => panic!("expected FOF"),
}
}
#[test]
fn bare_atom_source_is_parent() {
let ann = parse_single("fof(c_0_4, axiom, (p(a)), c1).");
let refs = ann.parent_refs();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].name, "c1");
assert!(!refs[0].negated);
assert_eq!(ann.inference_rule(), None);
}
#[test]
fn single_quoted_bare_atom_source_is_parent() {
let ann = parse_single("fof(step, plain, ($true), 'parent_name').");
let refs = ann.parent_refs();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].name, "parent_name");
}
#[test]
fn file_source_is_not_a_parent() {
let ann = parse_single("fof(c1, axiom, (p(a)), file('foo.p', c1)).");
assert!(ann.parent_refs().is_empty());
assert!(ann.file_source().is_some());
}
#[test]
fn inference_source_still_works() {
let ann = parse_single(
"fof(c_0_5, plain, ($false), inference(cn,[status(thm)],[c_0_3, c_0_4])).",
);
let refs = ann.parent_refs();
assert_eq!(refs.len(), 2);
assert_eq!(refs[0].name, "c_0_3");
assert_eq!(refs[1].name, "c_0_4");
}
#[test]
fn metis_colon_pair_parent_is_extracted() {
let ann = parse_single(
"fof(refute_0_11, plain, big_g(z, z), \
inference(subst, [], \
[refute_0_8 : [bind(X, $fot(z)), bind(Y, $fot(z))]])).",
);
let refs = ann.parent_refs();
assert_eq!(refs.len(), 1, "expected exactly one parent, got {refs:?}");
assert_eq!(refs[0].name, "refute_0_8");
assert!(!refs[0].negated);
}
}