use super::*;
use crate::ast::Term;
use std::collections::HashMap;
#[test]
fn test_unify_atoms() {
let mut subst = HashMap::new();
let atom1 = Term::Atom("hello".to_string());
let atom2 = Term::Atom("hello".to_string());
assert!(Unifier::unify(&atom1, &atom2, &mut subst));
assert!(subst.is_empty());
let atom3 = Term::Atom("world".to_string());
assert!(!Unifier::unify(&atom1, &atom3, &mut subst));
}
#[test]
fn test_unify_numbers() {
let mut subst = HashMap::new();
let num1 = Term::Number(42);
let num2 = Term::Number(42);
assert!(Unifier::unify(&num1, &num2, &mut subst));
let num3 = Term::Number(17);
assert!(!Unifier::unify(&num1, &num3, &mut subst));
let neg1 = Term::Number(-5);
let neg2 = Term::Number(-5);
assert!(Unifier::unify(&neg1, &neg2, &mut subst));
}
#[test]
fn test_unify_variables() {
let mut subst = HashMap::new();
let var = Term::Variable("X".to_string());
let atom = Term::Atom("hello".to_string());
assert!(Unifier::unify(&var, &atom, &mut subst));
assert_eq!(subst.get("X"), Some(&atom));
let atom2 = Term::Atom("hello".to_string());
assert!(Unifier::unify(&var, &atom2, &mut subst));
let atom3 = Term::Atom("world".to_string());
assert!(!Unifier::unify(&var, &atom3, &mut subst));
}
#[test]
fn test_unify_two_unbound_variables() {
let mut subst = HashMap::new();
let var1 = Term::Variable("X".to_string());
let var2 = Term::Variable("Y".to_string());
assert!(Unifier::unify(&var1, &var2, &mut subst));
assert!(subst.contains_key("X") || subst.contains_key("Y"));
}
#[test]
fn test_unify_same_variable() {
let mut subst = HashMap::new();
let var = Term::Variable("X".to_string());
assert!(Unifier::unify(&var, &var, &mut subst));
assert!(!subst.contains_key("X"));
}
#[test]
fn test_unify_compound_terms() {
let mut subst = HashMap::new();
let term1 = Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string()),
Term::Atom("b".to_string())
]);
let term2 = Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string()),
Term::Atom("b".to_string())
]);
assert!(Unifier::unify(&term1, &term2, &mut subst));
let term3 = Term::Compound("f".to_string(), vec![
Term::Variable("X".to_string()),
Term::Atom("b".to_string())
]);
assert!(Unifier::unify(&term3, &term2, &mut subst));
assert_eq!(subst.get("X"), Some(&Term::Atom("a".to_string())));
}
#[test]
fn test_unify_different_functors() {
let mut subst = HashMap::new();
let term1 = Term::Compound("f".to_string(), vec![Term::Atom("a".to_string())]);
let term2 = Term::Compound("g".to_string(), vec![Term::Atom("a".to_string())]);
assert!(!Unifier::unify(&term1, &term2, &mut subst));
}
#[test]
fn test_unify_different_arities() {
let mut subst = HashMap::new();
let term1 = Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string())
]);
let term2 = Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string()),
Term::Atom("b".to_string())
]);
assert!(!Unifier::unify(&term1, &term2, &mut subst));
}
#[test]
fn test_unify_nested_compounds() {
let mut subst = HashMap::new();
let term1 = Term::Compound("f".to_string(), vec![
Term::Compound("g".to_string(), vec![Term::Variable("X".to_string())]),
Term::Compound("h".to_string(), vec![Term::Atom("a".to_string())])
]);
let term2 = Term::Compound("f".to_string(), vec![
Term::Compound("g".to_string(), vec![Term::Atom("b".to_string())]),
Term::Compound("h".to_string(), vec![Term::Variable("Y".to_string())])
]);
assert!(Unifier::unify(&term1, &term2, &mut subst));
assert_eq!(subst.get("X"), Some(&Term::Atom("b".to_string())));
assert_eq!(subst.get("Y"), Some(&Term::Atom("a".to_string())));
}
#[test]
fn test_occurs_check() {
let mut subst = HashMap::new();
let var = Term::Variable("X".to_string());
let recursive = Term::Compound("f".to_string(), vec![var.clone()]);
assert!(!Unifier::unify(&var, &recursive, &mut subst));
assert!(subst.is_empty());
}
#[test]
fn test_occurs_check_nested() {
let mut subst = HashMap::new();
let var = Term::Variable("X".to_string());
let nested = Term::Compound("f".to_string(), vec![
Term::Compound("g".to_string(), vec![var.clone()])
]);
assert!(!Unifier::unify(&var, &nested, &mut subst));
}
#[test]
fn test_occurs_check_through_substitution() {
let mut subst = HashMap::new();
subst.insert("Y".to_string(), Term::Variable("X".to_string()));
let var_x = Term::Variable("X".to_string());
let term = Term::Compound("f".to_string(), vec![Term::Variable("Y".to_string())]);
assert!(!Unifier::unify(&var_x, &term, &mut subst));
}
#[test]
fn test_apply_substitution() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Atom("hello".to_string()));
subst.insert("Y".to_string(), Term::Number(42));
let var = Term::Variable("X".to_string());
let result = Unifier::apply_substitution(&var, &subst);
assert_eq!(result, Term::Atom("hello".to_string()));
let compound = Term::Compound("f".to_string(), vec![
Term::Variable("X".to_string()),
Term::Variable("Y".to_string())
]);
let result = Unifier::apply_substitution(&compound, &subst);
match result {
Term::Compound(functor, args) => {
assert_eq!(functor, "f");
assert_eq!(args[0], Term::Atom("hello".to_string()));
assert_eq!(args[1], Term::Number(42));
}
_ => panic!("Expected compound term"),
}
}
#[test]
fn test_apply_substitution_unbound_variable() {
let subst = HashMap::new();
let var = Term::Variable("X".to_string());
let result = Unifier::apply_substitution(&var, &subst);
assert_eq!(result, var);
}
#[test]
fn test_apply_substitution_to_atom() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Atom("test".to_string()));
let atom = Term::Atom("hello".to_string());
let result = Unifier::apply_substitution(&atom, &subst);
assert_eq!(result, atom);
}
#[test]
fn test_apply_substitution_to_terms() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Number(1));
subst.insert("Y".to_string(), Term::Number(2));
let terms = vec![
Term::Variable("X".to_string()),
Term::Variable("Y".to_string()),
Term::Atom("a".to_string())
];
let results = Unifier::apply_substitution_to_terms(&terms, &subst);
assert_eq!(results[0], Term::Number(1));
assert_eq!(results[1], Term::Number(2));
assert_eq!(results[2], Term::Atom("a".to_string()));
}
#[test]
fn test_variable_chains() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Variable("Y".to_string()));
subst.insert("Y".to_string(), Term::Variable("Z".to_string()));
subst.insert("Z".to_string(), Term::Atom("hello".to_string()));
let var_x = Term::Variable("X".to_string());
let result = Unifier::apply_substitution(&var_x, &subst);
assert_eq!(result, Term::Atom("hello".to_string()));
}
#[test]
fn test_can_unify() {
let term1 = Term::Variable("X".to_string());
let term2 = Term::Atom("hello".to_string());
assert!(Unifier::can_unify(&term1, &term2));
let term3 = Term::Atom("hello".to_string());
let term4 = Term::Atom("world".to_string());
assert!(!Unifier::can_unify(&term3, &term4));
}
#[test]
fn test_can_unify_doesnt_modify_state() {
let term1 = Term::Variable("X".to_string());
let term2 = Term::Atom("test".to_string());
assert!(Unifier::can_unify(&term1, &term2));
assert!(Unifier::can_unify(&term1, &term2));
assert!(Unifier::can_unify(&term1, &Term::Atom("other".to_string())));
}
#[test]
fn test_rename_variables() {
let term = Term::Compound("f".to_string(), vec![
Term::Variable("X".to_string()),
Term::Atom("a".to_string()),
Term::Variable("Y".to_string())
]);
let renamed = Unifier::rename_variables(&term, "_1");
match renamed {
Term::Compound(functor, args) => {
assert_eq!(functor, "f");
assert_eq!(args[0], Term::Variable("X_1".to_string()));
assert_eq!(args[1], Term::Atom("a".to_string()));
assert_eq!(args[2], Term::Variable("Y_1".to_string()));
}
_ => panic!("Expected compound term"),
}
}
#[test]
fn test_rename_variables_nested() {
let term = Term::Compound("f".to_string(), vec![
Term::Compound("g".to_string(), vec![
Term::Variable("X".to_string())
])
]);
let renamed = Unifier::rename_variables(&term, "_new");
match renamed {
Term::Compound(_, args) => {
match &args[0] {
Term::Compound(_, inner_args) => {
assert_eq!(inner_args[0], Term::Variable("X_new".to_string()));
}
_ => panic!("Expected nested compound"),
}
}
_ => panic!("Expected compound term"),
}
}
#[test]
fn test_rename_variables_preserves_non_variables() {
let term = Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string()),
Term::Number(42)
]);
let renamed = Unifier::rename_variables(&term, "_suffix");
assert_eq!(renamed, term); }
#[test]
fn test_get_all_variables_empty() {
let subst = HashMap::new();
let vars = Unifier::get_all_variables(&subst);
assert!(vars.is_empty());
}
#[test]
fn test_get_all_variables() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Variable("Y".to_string()));
subst.insert("Y".to_string(), Term::Atom("hello".to_string()));
subst.insert("Z".to_string(), Term::Compound("f".to_string(), vec![
Term::Variable("W".to_string())
]));
let vars = Unifier::get_all_variables(&subst);
assert!(vars.contains(&"X".to_string()));
assert!(vars.contains(&"Y".to_string()));
assert!(vars.contains(&"Z".to_string()));
assert!(vars.contains(&"W".to_string()));
}
#[test]
fn test_get_all_variables_sorted() {
let mut subst = HashMap::new();
subst.insert("Z".to_string(), Term::Atom("z".to_string()));
subst.insert("A".to_string(), Term::Atom("a".to_string()));
subst.insert("M".to_string(), Term::Atom("m".to_string()));
let vars = Unifier::get_all_variables(&subst);
assert_eq!(vars, vec!["A", "M", "Z"]);
}
#[test]
fn test_compose_substitutions() {
let mut subst1 = HashMap::new();
subst1.insert("X".to_string(), Term::Variable("Y".to_string()));
let mut subst2 = HashMap::new();
subst2.insert("Y".to_string(), Term::Atom("hello".to_string()));
let composed = Unifier::compose_substitutions(&subst1, &subst2);
let x_result = Unifier::apply_substitution(&Term::Variable("X".to_string()), &composed);
assert_eq!(x_result, Term::Atom("hello".to_string()));
}
#[test]
fn test_compose_substitutions_overlap() {
let mut subst1 = HashMap::new();
subst1.insert("X".to_string(), Term::Atom("first".to_string()));
let mut subst2 = HashMap::new();
subst2.insert("X".to_string(), Term::Atom("second".to_string()));
subst2.insert("Y".to_string(), Term::Atom("new".to_string()));
let composed = Unifier::compose_substitutions(&subst1, &subst2);
assert_eq!(composed.get("X"), Some(&Term::Atom("first".to_string())));
assert_eq!(composed.get("Y"), Some(&Term::Atom("new".to_string())));
}
#[test]
fn test_compose_substitutions_complex() {
let mut subst1 = HashMap::new();
subst1.insert("X".to_string(), Term::Compound("f".to_string(), vec![
Term::Variable("Y".to_string())
]));
let mut subst2 = HashMap::new();
subst2.insert("Y".to_string(), Term::Number(42));
let composed = Unifier::compose_substitutions(&subst1, &subst2);
let expected = Term::Compound("f".to_string(), vec![Term::Number(42)]);
assert_eq!(composed.get("X"), Some(&expected));
}
#[test]
fn test_is_idempotent_empty() {
let subst = HashMap::new();
assert!(Unifier::is_idempotent(&subst));
}
#[test]
fn test_is_idempotent_true() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Atom("hello".to_string()));
subst.insert("Y".to_string(), Term::Number(42));
assert!(Unifier::is_idempotent(&subst));
}
#[test]
fn test_is_idempotent_false() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Variable("Y".to_string()));
subst.insert("Y".to_string(), Term::Atom("hello".to_string()));
assert!(!Unifier::is_idempotent(&subst));
}
#[test]
fn test_is_idempotent_circular() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Variable("Y".to_string()));
subst.insert("Y".to_string(), Term::Variable("X".to_string()));
assert!(!Unifier::is_idempotent(&subst));
}
#[test]
fn test_remove_identity_bindings_empty() {
let mut subst = HashMap::new();
Unifier::remove_identity_bindings(&mut subst);
assert!(subst.is_empty());
}
#[test]
fn test_remove_identity_bindings() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Variable("X".to_string())); subst.insert("Y".to_string(), Term::Variable("Z".to_string())); subst.insert("Z".to_string(), Term::Atom("hello".to_string()));
Unifier::remove_identity_bindings(&mut subst);
assert!(!subst.contains_key("X"));
assert!(subst.contains_key("Y"));
assert!(subst.contains_key("Z"));
}
#[test]
fn test_remove_identity_bindings_preserves_non_variable_bindings() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Atom("X".to_string()));
Unifier::remove_identity_bindings(&mut subst);
assert!(subst.contains_key("X"));
}
#[test]
fn test_unify_lists() {
let mut subst = HashMap::new();
let list1 = Term::Compound(".".to_string(), vec![
Term::Number(1),
Term::Compound(".".to_string(), vec![
Term::Number(2),
Term::Atom("[]".to_string())
])
]);
let list2 = Term::Compound(".".to_string(), vec![
Term::Variable("H".to_string()),
Term::Variable("T".to_string())
]);
assert!(Unifier::unify(&list1, &list2, &mut subst));
assert_eq!(subst.get("H"), Some(&Term::Number(1)));
}
#[test]
fn test_unify_multiple_variables_to_same_term() {
let mut subst = HashMap::new();
let term1 = Term::Compound("f".to_string(), vec![
Term::Variable("X".to_string()),
Term::Variable("X".to_string())
]);
let term2 = Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string()),
Term::Atom("a".to_string())
]);
assert!(Unifier::unify(&term1, &term2, &mut subst));
assert_eq!(subst.get("X"), Some(&Term::Atom("a".to_string())));
let mut subst2 = HashMap::new();
let term3 = Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string()),
Term::Atom("b".to_string())
]);
assert!(!Unifier::unify(&term1, &term3, &mut subst2));
}
#[test]
fn test_unify_preserves_existing_bindings() {
let mut subst = HashMap::new();
subst.insert("X".to_string(), Term::Atom("existing".to_string()));
let var_y = Term::Variable("Y".to_string());
let var_x = Term::Variable("X".to_string());
assert!(Unifier::unify(&var_y, &var_x, &mut subst));
let y_result = Unifier::apply_substitution(&var_y, &subst);
assert_eq!(y_result, Term::Atom("existing".to_string()));
}
#[test]
fn test_substitution_utils_new() {
use super::substitution_utils;
let subst = substitution_utils::new();
assert!(substitution_utils::is_empty(&subst));
assert_eq!(substitution_utils::len(&subst), 0);
}
#[test]
fn test_substitution_utils_operations() {
use super::substitution_utils;
let mut subst = substitution_utils::new();
assert!(substitution_utils::is_empty(&subst));
subst.insert("X".to_string(), Term::Atom("hello".to_string()));
assert!(!substitution_utils::is_empty(&subst));
assert_eq!(substitution_utils::len(&subst), 1);
subst.insert("Y".to_string(), Term::Number(42));
assert_eq!(substitution_utils::len(&subst), 2);
substitution_utils::clear(&mut subst);
assert!(substitution_utils::is_empty(&subst));
assert_eq!(substitution_utils::len(&subst), 0);
}
#[test]
fn test_substitution_utils_format_empty() {
use super::substitution_utils;
let subst = substitution_utils::new();
let formatted = substitution_utils::format_substitution(&subst);
assert_eq!(formatted, "{}");
}
#[test]
fn test_substitution_utils_format_single() {
use super::substitution_utils;
let mut subst = substitution_utils::new();
subst.insert("X".to_string(), Term::Atom("hello".to_string()));
let formatted = substitution_utils::format_substitution(&subst);
assert_eq!(formatted, "{X -> hello}");
}
#[test]
fn test_substitution_utils_format_multiple() {
use super::substitution_utils;
let mut subst = substitution_utils::new();
subst.insert("X".to_string(), Term::Atom("hello".to_string()));
subst.insert("Y".to_string(), Term::Number(42));
let formatted = substitution_utils::format_substitution(&subst);
assert!(formatted.contains("X -> hello"));
assert!(formatted.contains("Y -> 42"));
assert!(formatted.starts_with('{'));
assert!(formatted.ends_with('}'));
}
#[test]
fn test_substitution_utils_format_complex() {
use super::substitution_utils;
let mut subst = substitution_utils::new();
subst.insert("X".to_string(), Term::Compound("f".to_string(), vec![
Term::Atom("a".to_string()),
Term::Variable("Y".to_string())
]));
let formatted = substitution_utils::format_substitution(&subst);
assert!(formatted.contains("X -> f(a, Y)"));
}
#[test]
fn test_substitution_utils_clear() {
use super::substitution_utils;
let mut subst = substitution_utils::new();
subst.insert("X".to_string(), Term::Atom("test".to_string()));
subst.insert("Y".to_string(), Term::Number(123));
assert_eq!(substitution_utils::len(&subst), 2);
substitution_utils::clear(&mut subst);
assert_eq!(substitution_utils::len(&subst), 0);
assert!(substitution_utils::is_empty(&subst));
}
#[test]
fn test_substitution_utils_with_lists() {
use super::substitution_utils;
let mut subst = substitution_utils::new();
let list = Term::Compound(".".to_string(), vec![
Term::Number(1),
Term::Compound(".".to_string(), vec![
Term::Number(2),
Term::Atom("[]".to_string())
])
]);
subst.insert("List".to_string(), list);
let formatted = substitution_utils::format_substitution(&subst);
assert!(formatted.contains("List ->"));
assert!(!substitution_utils::is_empty(&subst));
}