1use crate::term::{AtomId, Clause, FirstArgKey, Term};
2use fnv::FnvHashMap;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct PredicateEntry {
9 pub arg_index: FnvHashMap<FirstArgKey, Vec<usize>>,
11 pub var_clause_indices: Vec<usize>,
13 pub all_clause_indices: Vec<usize>,
15}
16
17pub type PredicateIndex = FnvHashMap<(AtomId, usize), PredicateEntry>;
19
20pub fn build_index(clauses: &[Clause]) -> PredicateIndex {
22 let mut index: PredicateIndex = FnvHashMap::default();
23
24 for (clause_idx, clause) in clauses.iter().enumerate() {
25 let (functor, arity) = match clause.head.functor_arity() {
26 Some(fa) => fa,
27 None => continue, };
29
30 let entry = index
31 .entry((functor, arity))
32 .or_insert_with(|| PredicateEntry {
33 arg_index: FnvHashMap::default(),
34 var_clause_indices: Vec::new(),
35 all_clause_indices: Vec::new(),
36 });
37
38 entry.all_clause_indices.push(clause_idx);
39
40 match clause.head.first_arg_key() {
41 Some(key) => {
42 entry
43 .arg_index
44 .entry(key)
45 .or_insert_with(Vec::new)
46 .push(clause_idx);
47 }
48 None => {
49 if let Term::Compound { args, .. } = &clause.head {
51 if !args.is_empty() && args[0].is_var() {
52 entry.var_clause_indices.push(clause_idx);
53 }
54 }
55 }
57 }
58 }
59
60 index
61}
62
63pub fn lookup_clauses(index: &PredicateIndex, goal: &Term, clauses: &[Clause]) -> Vec<usize> {
66 let _ = clauses; let (functor, arity) = match goal.functor_arity() {
68 Some(fa) => fa,
69 None => return vec![],
70 };
71
72 let entry = match index.get(&(functor, arity)) {
73 Some(e) => e,
74 None => return vec![],
75 };
76
77 if !entry.var_clause_indices.is_empty() {
79 return entry.all_clause_indices.clone();
80 }
81
82 let first_arg_key = match goal {
84 Term::Compound { args, .. } if !args.is_empty() => {
85 match &args[0] {
86 Term::Atom(id) => Some(FirstArgKey::Atom(*id)),
87 Term::Integer(n) => Some(FirstArgKey::Integer(*n)),
88 Term::Compound { functor, args } => {
89 Some(FirstArgKey::Functor(*functor, args.len()))
90 }
91 _ => None, }
93 }
94 _ => None,
95 };
96
97 match first_arg_key {
98 Some(key) => {
99 match entry.arg_index.get(&key) {
100 Some(indices) => indices.clone(),
101 None => vec![], }
103 }
104 None => entry.all_clause_indices.clone(),
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::term::{Clause, Term};
112
113 fn make_fact(functor: AtomId, args: Vec<Term>) -> Clause {
114 Clause {
115 head: Term::Compound { functor, args },
116 body: vec![],
117 }
118 }
119
120 #[test]
121 fn test_build_index_basic() {
122 let clauses = vec![
123 make_fact(0, vec![Term::Atom(1)]),
124 make_fact(0, vec![Term::Atom(2)]),
125 make_fact(0, vec![Term::Atom(3)]),
126 ];
127 let index = build_index(&clauses);
128 let entry = index.get(&(0, 1)).unwrap();
129 assert_eq!(entry.all_clause_indices, vec![0, 1, 2]);
130 assert!(entry.var_clause_indices.is_empty());
131 assert_eq!(entry.arg_index.len(), 3);
132 }
133
134 #[test]
135 fn test_lookup_with_ground_arg() {
136 let clauses = vec![
137 make_fact(0, vec![Term::Atom(1)]),
138 make_fact(0, vec![Term::Atom(2)]),
139 make_fact(0, vec![Term::Atom(3)]),
140 ];
141 let index = build_index(&clauses);
142
143 let goal = Term::Compound {
145 functor: 0,
146 args: vec![Term::Atom(2)],
147 };
148 let result = lookup_clauses(&index, &goal, &clauses);
149 assert_eq!(result, vec![1]); }
151
152 #[test]
153 fn test_lookup_with_variable_arg() {
154 let clauses = vec![
155 make_fact(0, vec![Term::Atom(1)]),
156 make_fact(0, vec![Term::Atom(2)]),
157 make_fact(0, vec![Term::Atom(3)]),
158 ];
159 let index = build_index(&clauses);
160
161 let goal = Term::Compound {
163 functor: 0,
164 args: vec![Term::Var(0)],
165 };
166 let result = lookup_clauses(&index, &goal, &clauses);
167 assert_eq!(result, vec![0, 1, 2]);
168 }
169
170 #[test]
171 fn test_lookup_unknown_predicate() {
172 let clauses = vec![make_fact(0, vec![Term::Atom(1)])];
173 let index = build_index(&clauses);
174
175 let goal = Term::Compound {
176 functor: 99,
177 args: vec![Term::Atom(1)],
178 };
179 let result = lookup_clauses(&index, &goal, &clauses);
180 assert!(result.is_empty());
181 }
182
183 #[test]
184 fn test_mixed_ground_and_var_clauses() {
185 let clauses = vec![
187 make_fact(0, vec![Term::Atom(1)]),
188 make_fact(0, vec![Term::Atom(2)]),
189 Clause {
190 head: Term::Compound {
191 functor: 0,
192 args: vec![Term::Var(0)],
193 },
194 body: vec![],
195 },
196 ];
197 let index = build_index(&clauses);
198
199 let goal = Term::Compound {
200 functor: 0,
201 args: vec![Term::Atom(1)],
202 };
203 let result = lookup_clauses(&index, &goal, &clauses);
204 assert_eq!(result, vec![0, 1, 2]);
206 }
207
208 #[test]
209 fn test_multiple_predicates() {
210 let clauses = vec![
211 make_fact(0, vec![Term::Atom(10)]), make_fact(0, vec![Term::Atom(11)]), make_fact(1, vec![Term::Atom(20)]), ];
215 let index = build_index(&clauses);
216
217 let goal_color = Term::Compound {
219 functor: 0,
220 args: vec![Term::Var(0)],
221 };
222 assert_eq!(lookup_clauses(&index, &goal_color, &clauses), vec![0, 1]);
223
224 let goal_shape = Term::Compound {
226 functor: 1,
227 args: vec![Term::Var(0)],
228 };
229 assert_eq!(lookup_clauses(&index, &goal_shape, &clauses), vec![2]);
230 }
231
232 #[test]
233 fn test_no_match_returns_empty() {
234 let clauses = vec![
235 make_fact(0, vec![Term::Atom(1)]),
236 make_fact(0, vec![Term::Atom(2)]),
237 ];
238 let index = build_index(&clauses);
239
240 let goal = Term::Compound {
242 functor: 0,
243 args: vec![Term::Atom(99)],
244 };
245 let result = lookup_clauses(&index, &goal, &clauses);
246 assert_eq!(result, vec![]);
247 }
248}