1use std::collections::{BTreeMap, HashMap};
2
3#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
5pub enum Atom {
6 String(String),
7 Number(isize),
8}
9
10impl From<isize> for Atom {
11 fn from(value: isize) -> Self {
12 Atom::Number(value)
13 }
14}
15
16impl From<&str> for Atom {
17 fn from(value: &str) -> Self {
18 Atom::String(value.to_string())
19 }
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct Variable {
25 pub name: String,
26}
27
28impl From<&str> for Variable {
29 fn from(value: &str) -> Self {
30 Variable {
31 name: value.to_string(),
32 }
33 }
34}
35
36#[derive(Debug, PartialEq, Clone)]
38pub enum Term {
39 Literal(Atom),
40 Variable(Variable),
41}
42
43#[derive(Debug, PartialEq, Clone)]
45pub struct Fact {
46 pub entity: Atom,
47 pub attribute: Atom,
48 pub value: Atom,
49}
50
51#[derive(Debug, PartialEq, Clone)]
53pub struct Pattern {
54 pub entity: Term,
55 pub attribute: Term,
56 pub value: Term,
57}
58
59pub type Bindings = HashMap<Variable, Atom>;
61
62pub fn unify(pattern: &Pattern, fact: &Fact, bindings: &Bindings) -> Option<Bindings> {
64 let mut bindings = bindings.clone();
65 unify_slot(&pattern.entity, &fact.entity, &mut bindings)?;
66 unify_slot(&pattern.attribute, &fact.attribute, &mut bindings)?;
67 unify_slot(&pattern.value, &fact.value, &mut bindings)?;
68 Some(bindings)
69}
70
71fn unify_slot(term: &Term, atom: &Atom, bindings: &mut Bindings) -> Option<()> {
72 match term {
73 Term::Literal(l) => (l == atom).then_some(()),
74 Term::Variable(v) => match bindings.get(v) {
75 Some(l) => (l == atom).then_some(()),
76 None => {
77 bindings.insert(v.clone(), atom.clone());
78 Some(())
79 }
80 },
81 }
82}
83
84pub fn match_rule<'a, F>(conditions: &[Pattern], facts: F, bindings: &Bindings) -> Vec<Bindings>
86where
87 F: IntoIterator<Item = &'a Fact> + Clone,
88{
89 match conditions.split_first() {
90 Some((first, rest)) => {
91 let mut results = Vec::new();
92 for fact in facts.clone() {
93 if let Some(new_bindings) = unify(first, fact, bindings) {
94 results.extend(match_rule(rest, facts.clone(), &new_bindings));
95 }
96 }
97 results
98 }
99 None => vec![bindings.clone()],
100 }
101}
102
103#[derive(Default)]
109pub struct WorkingMemory {
110 memory: BTreeMap<(Atom, Atom), Fact>,
112}
113
114#[derive(Debug, PartialEq)]
116pub enum InsertResult {
117 Added,
119 Updated(Fact),
121 Unchanged,
123}
124
125impl WorkingMemory {
126 pub fn insert(&mut self, fact: Fact) -> InsertResult {
127 use std::collections::btree_map::Entry;
128
129 match self
130 .memory
131 .entry((fact.entity.clone(), fact.attribute.clone()))
132 {
133 Entry::Vacant(e) => {
134 e.insert(fact);
135 InsertResult::Added
136 }
137 Entry::Occupied(mut e) => {
138 if e.get().value == fact.value {
139 InsertResult::Unchanged
140 } else {
141 InsertResult::Updated(e.insert(fact))
142 }
143 }
144 }
145 }
146
147 pub fn retract(&mut self, entity: Atom, attribute: Atom) -> Option<Fact> {
148 self.memory.remove(&(entity, attribute))
149 }
150
151 pub fn facts(&self) -> impl Iterator<Item = &Fact> + Clone {
152 self.memory.values()
153 }
154}
155
156#[macro_export]
157macro_rules! atom {
158 ($lit:literal) => {
159 Atom::from($lit)
160 };
161 ($name:ident) => {
162 Atom::from(stringify!($name))
163 };
164}
165
166#[macro_export]
167macro_rules! variable {
168 ($name:ident) => {
169 Variable::from(stringify!($name))
170 };
171}
172
173#[macro_export]
174macro_rules! fact {
175 ($e: tt, $a: tt, $v: tt) => {
176 Fact {
177 entity: atom!($e),
178 attribute: atom!($a),
179 value: atom!($v),
180 }
181 };
182}
183
184#[macro_export]
185macro_rules! pattern {
186 (? $e:ident, $($rest:tt)*) => {
187 pattern!(@attr Term::Variable(variable!($e)), $($rest)*)
188 };
189 ($e:tt, $($rest:tt)*) => {
190 pattern!(@attr Term::Literal(atom!($e)), $($rest)*)
191 };
192 (@attr $entity:expr, ? $a:ident, $($rest:tt)*) => {
193 pattern!(@val $entity, Term::Variable(variable!($a)), $($rest)*)
194 };
195 (@attr $entity:expr, $a:tt, $($rest:tt)*) => {
196 pattern!(@val $entity, Term::Literal(atom!($a)), $($rest)*)
197 };
198 (@val $entity:expr, $attribute:expr, ? $v:ident) => {
199 Pattern {
200 entity: $entity,
201 attribute: $attribute,
202 value: Term::Variable(variable!($v))
203 }
204 };
205 (@val $entity:expr, $attribute:expr, $v:tt) => {
206 Pattern {
207 entity: $entity,
208 attribute: $attribute,
209 value: Term::Literal(atom!($v))
210 }
211 };
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use test_that::prelude::*;
218
219 #[test_that::test]
220 fn test_atom() {
221 let atom = atom!(10);
222 expect_that!(atom, eq(Atom::Number(10)));
223
224 let atom = atom!(player);
225 expect_that!(atom, eq(Atom::String("player".to_string())));
226 }
227
228 #[test_that::test]
229 fn test_fact() {
230 let fact = fact!(player, health, 10);
231
232 expect_that!(
233 fact,
234 eq(Fact {
235 entity: Atom::String("player".to_string()),
236 attribute: Atom::String("health".to_string()),
237 value: Atom::Number(10),
238 })
239 );
240 }
241
242 #[test_that::test]
243 fn test_variable() {
244 let variable = variable!(e);
245 expect_that!(variable, eq(Variable::from("e")));
246 }
247
248 #[test_that::test]
249 fn test_pattern() {
250 let pattern = pattern!(?e, health, 10);
251 expect_that!(
252 pattern,
253 eq(Pattern {
254 entity: Term::Variable("e".into()),
255 attribute: Term::Literal("health".into()),
256 value: Term::Literal(10.into()),
257 })
258 );
259 let pattern = pattern!(?e, ?a, ?v);
260 expect_that!(
261 pattern,
262 eq(Pattern {
263 entity: Term::Variable("e".into()),
264 attribute: Term::Variable("a".into()),
265 value: Term::Variable("v".into()),
266 })
267 );
268 }
269
270 #[test_that::test]
271 fn test_unify() {
272 let bindings = Bindings::new();
274 let result = unify(&pattern!(?e, age, ?a), &fact!(alice, age, 30), &bindings);
275 let expected = Bindings::from([
276 (variable!(e), Atom::from("alice")),
277 (variable!(a), Atom::from(30)),
278 ]);
279 expect_that!(result, some(eq(expected)));
280
281 let bindings = Bindings::from([(variable!(e), Atom::from("alice"))]);
283 let result = unify(
284 &pattern!(?e, likes, pizza),
285 &fact!(bob, likes, pizza),
286 &bindings,
287 );
288 expect_that!(result, none());
289 }
290
291 #[test_that::test]
292 fn test_match_rule() {
293 let facts = [
294 fact!(alice, likes, pizza),
295 fact!(alice, age, 30),
296 fact!(bob, likes, pasta),
297 fact!(bob, age, 25),
298 ];
299 let conditions = [pattern!(?e, age, ?a), pattern!(?e, likes, pizza)];
301 let bindings = Bindings::new();
302
303 let result = match_rule(&conditions, &facts, &bindings);
304
305 let expected = Bindings::from([
306 (variable!(e), Atom::from("alice")),
307 (variable!(a), Atom::from(30)),
308 ]);
309 expect_that!(result, contains_exactly!(eq(expected)));
310
311 let conditions = [pattern!(?e, age, ?a)];
313 let bindings = Bindings::new();
314
315 let result = match_rule(&conditions, &facts, &bindings);
316
317 let expected_alice = Bindings::from([
318 (variable!(e), Atom::from("alice")),
319 (variable!(a), Atom::from(30)),
320 ]);
321 let expected_bob = Bindings::from([
322 (variable!(e), Atom::from("bob")),
323 (variable!(a), Atom::from(25)),
324 ]);
325 expect_that!(
326 result,
327 contains_exactly!(eq(expected_alice), eq(expected_bob))
328 );
329
330 let conditions = [pattern!(?e, likes, dosa)];
332 let bindings = Bindings::new();
333
334 let result = match_rule(&conditions, &facts, &bindings);
335
336 expect_that!(result, empty());
337
338 let bindings = Bindings::from([(variable!(e), Atom::from("alice"))]);
340
341 let result = match_rule(&[], &facts, &bindings);
342
343 expect_that!(result, contains_exactly!(eq(bindings)));
344 }
345
346 #[test_that::test]
347 fn test_working_memory() {
348 let mut wm = WorkingMemory::default();
349
350 let result = wm.insert(fact!(player, health, 10));
351 expect_that!(result, eq(InsertResult::Added));
352
353 let result = wm.insert(fact!(player, health, 8));
354 expect_that!(result, eq(InsertResult::Updated(fact!(player, health, 10))));
355 let facts: Vec<Fact> = wm.facts().cloned().collect();
356 expect_that!(facts, contains_exactly!(eq(fact!(player, health, 8))));
357
358 let result = wm.retract("player".into(), "health".into());
359 let facts: Vec<Fact> = wm.facts().cloned().collect();
360 expect_that!(facts.len(), eq(0));
361 expect_that!(result, some(eq(fact!(player, health, 8))));
362 }
363}