//! Atoms represent predicates applied to terms. use std::fmt; use super::term::Term; /// An atom is a predicate symbol applied to a tuple of terms. /// Example: Parent(alice, bob) or Ancestor(?X, ?Y) #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Atom { /// The predicate name. pub predicate: String, /// The arguments to the predicate. pub terms: Vec, } impl Atom { /// Create a new atom with the given predicate and terms. pub fn new(predicate: impl Into, terms: Vec) -> Self { Atom { predicate: predicate.into(), terms, } } /// Returns the arity (number of arguments) of this atom. pub fn arity(&self) -> usize { self.terms.len() } /// Returns true if this atom is ground (contains no variables). pub fn is_ground(&self) -> bool { self.terms.iter().all(|t| t.is_ground()) } /// Get all variables in this atom. pub fn variables(&self) -> Vec<&String> { self.terms .iter() .filter_map(|t| match t { Term::Variable(v) => Some(v), _ => None, }) .collect() } } impl fmt::Display for Atom { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}(", self.predicate)?; for (i, term) in self.terms.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", term)?; } write!(f, ")") } } #[cfg(test)] mod tests { use super::*; #[test] fn test_atom_creation() { let atom = Atom::new( "Parent", vec![Term::constant("alice"), Term::constant("bob")], ); assert_eq!(atom.predicate, "Parent"); assert_eq!(atom.arity(), 2); assert!(atom.is_ground()); } #[test] fn test_atom_with_variables() { let atom = Atom::new("Ancestor", vec![Term::var("X"), Term::var("Y")]); assert!(!atom.is_ground()); assert_eq!(atom.variables().len(), 2); } #[test] fn test_atom_display() { let atom = Atom::new( "Parent", vec![Term::constant("alice"), Term::constant("bob")], ); assert_eq!(format!("{}", atom), "Parent(alice, bob)"); } }