chase-rs/src/chase/atom.rs
2026-03-09 11:12:18 +01:00

92 lines
2.3 KiB
Rust

//! 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<Term>,
}
impl Atom {
/// Create a new atom with the given predicate and terms.
pub fn new(predicate: impl Into<String>, terms: Vec<Term>) -> 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)");
}
}