Skip to main content

patch_prolog_core/
database.rs

1use crate::index::{build_index, lookup_clauses, PredicateIndex};
2use crate::term::{Clause, StringInterner, Term};
3use serde::{Deserialize, Serialize};
4
5/// A compiled, indexed Prolog knowledge base.
6/// Built at compile time, serialized with bincode, and embedded in the binary.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CompiledDatabase {
9    pub interner: StringInterner,
10    pub clauses: Vec<Clause>,
11    pub predicate_index: PredicateIndex,
12}
13
14impl CompiledDatabase {
15    /// Build a compiled database from clauses and interner.
16    pub fn new(mut interner: StringInterner, clauses: Vec<Clause>) -> Self {
17        // Ensure required atoms are always interned
18        interner.intern("[]");
19        interner.intern("!");
20        let predicate_index = build_index(&clauses);
21        CompiledDatabase {
22            interner,
23            clauses,
24            predicate_index,
25        }
26    }
27
28    /// Look up candidate clause indices for a goal.
29    pub fn lookup(&self, goal: &Term) -> Vec<usize> {
30        lookup_clauses(&self.predicate_index, goal, &self.clauses)
31    }
32
33    /// Serialize to bytes.
34    pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
35        bincode::serialize(self).map_err(|e| format!("Serialization error: {}", e))
36    }
37
38    /// Deserialize from bytes.
39    pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
40        bincode::deserialize(data).map_err(|e| format!("Deserialization error: {}", e))
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::parser::Parser;
48
49    fn build_db(source: &str) -> CompiledDatabase {
50        let mut interner = StringInterner::new();
51        let clauses = Parser::parse_program(source, &mut interner).unwrap();
52        CompiledDatabase::new(interner, clauses)
53    }
54
55    #[test]
56    fn test_roundtrip_serialization() {
57        let db = build_db("parent(tom, mary). parent(tom, james).");
58        let bytes = db.to_bytes().unwrap();
59        let restored = CompiledDatabase::from_bytes(&bytes).unwrap();
60        assert_eq!(restored.clauses.len(), 2);
61        assert_eq!(restored.interner.resolve(0), db.interner.resolve(0));
62    }
63
64    #[test]
65    fn test_lookup_indexed() {
66        let db = build_db("color(red). color(blue). color(green). shape(circle). shape(square).");
67        // color/1 should have 3 clauses
68        let color_id = db.interner.lookup("color").unwrap();
69        let goal = Term::Compound {
70            functor: color_id,
71            args: vec![Term::Var(0)],
72        };
73        let results = db.lookup(&goal);
74        assert_eq!(results.len(), 3);
75
76        // shape/1 should have 2 clauses
77        let shape_id = db.interner.lookup("shape").unwrap();
78        let goal = Term::Compound {
79            functor: shape_id,
80            args: vec![Term::Var(0)],
81        };
82        let results = db.lookup(&goal);
83        assert_eq!(results.len(), 2);
84    }
85
86    #[test]
87    fn test_lookup_specific_first_arg() {
88        let db = build_db(
89            "component(engine, piston). component(engine, crankshaft). component(brake, pad).",
90        );
91        let comp_id = db.interner.lookup("component").unwrap();
92        let brake_id = db.interner.lookup("brake").unwrap();
93
94        let goal = Term::Compound {
95            functor: comp_id,
96            args: vec![Term::Atom(brake_id), Term::Var(0)],
97        };
98        let results = db.lookup(&goal);
99        assert_eq!(results.len(), 1);
100    }
101}