1use hyperon_atom::{matcher::BindingsSet, *};
4use hyperon_common::FlexRef;
5#[cfg(test)]
6use hyperon_space::DynSpace;
7
8use std::fmt::{Debug, Display};
9use std::collections::HashSet;
10use hyperon_space::{complex_query, index::{AllowDuplication, AtomIndex, DuplicationStrategy, ALLOW_DUPLICATION}, Space, SpaceCommon, SpaceEvent, SpaceMut, SpaceVisitor};
11
12#[derive(Clone)]
17pub struct GroundingSpace<D: DuplicationStrategy = AllowDuplication> {
18 index: AtomIndex<D>,
19 common: SpaceCommon,
20 name: Option<String>,
21}
22
23impl GroundingSpace {
24 pub fn new() -> Self {
26 Self::with_strategy(ALLOW_DUPLICATION)
27 }
28
29 pub fn from_vec(atoms: Vec<Atom>) -> Self {
31 let mut index = AtomIndex::with_strategy(ALLOW_DUPLICATION);
32 for atom in atoms {
33 index.insert(atom);
34 }
35 Self{
36 index,
37 common: SpaceCommon::default(),
38 name: None,
39 }
40 }
41}
42
43impl<D: DuplicationStrategy> GroundingSpace<D> {
44 pub fn with_strategy(strategy: D) -> Self {
46 Self {
47 index: AtomIndex::with_strategy(strategy),
48 common: SpaceCommon::default(),
49 name: None,
50 }
51 }
52
53 pub fn add(&mut self, atom: Atom) {
71 log::debug!("GroundingSpace::add: {}, atom: {}", self, atom);
72 self.index.insert(atom.clone());
73 self.common.notify_all_observers(&SpaceEvent::Add(atom));
74 }
75
76 pub fn remove(&mut self, atom: &Atom) -> bool {
93 log::debug!("GroundingSpace::remove: {}, atom: {}", self, atom);
94 let is_removed = self.index.remove(atom);
95 if is_removed {
96 self.common.notify_all_observers(&SpaceEvent::Remove(atom.clone()));
97 }
98 is_removed
99 }
100
101 pub fn replace(&mut self, from: &Atom, to: Atom) -> bool {
120 let is_replaced = self.index.remove(from);
121 if is_replaced {
122 self.index.insert(to.clone());
123 self.common.notify_all_observers(&SpaceEvent::Replace(from.clone(), to));
124 }
125 is_replaced
126 }
127
128 pub fn query(&self, query: &Atom) -> BindingsSet {
148 complex_query(query, |query| self.single_query(query))
149 }
150
151 fn single_query(&self, query: &Atom) -> BindingsSet {
153 log::debug!("GroundingSpace::single_query: {} query: {}", self, query);
154 let mut result = BindingsSet::empty();
155 let query_vars: HashSet<&VariableAtom> = query.iter().filter_type::<&VariableAtom>().collect();
156 for bindings in self.index.query(query) {
157 let bindings = bindings.narrow_vars(&query_vars);
158 log::trace!("single_query: push result: {}", bindings);
159 result.push(bindings);
160 }
161 log::debug!("GroundinSpace::single_query: {} result: {}", self, result);
162 result
163 }
164
165 pub fn set_name(&mut self, name: String) {
167 self.name = Some(name);
168 }
169
170 pub fn name(&self) -> Option<&str> {
172 self.name.as_ref().map(|s| s.as_str())
173 }
174
175 #[cfg(test)]
176 fn into_vec(&self) -> Vec<Atom> {
177 self.index.iter().map(|a| a.into_owned()).collect()
178 }
179}
180
181impl Space for GroundingSpace {
182 fn common(&self) -> FlexRef<'_, SpaceCommon> {
183 FlexRef::from_simple(&self.common)
184 }
185 fn query(&self, query: &Atom) -> BindingsSet {
186 GroundingSpace::query(self, query)
187 }
188 fn atom_count(&self) -> Option<usize> {
189 Some(self.index.iter().count())
190 }
191 fn visit(&self, v: &mut dyn SpaceVisitor) -> Result<(), ()> {
192 Ok(self.index.iter().for_each(|atom| v.accept(atom)))
193 }
194 fn as_any(&self) -> &dyn std::any::Any {
195 self
196 }
197}
198
199impl SpaceMut for GroundingSpace {
200 fn add(&mut self, atom: Atom) {
201 GroundingSpace::add(self, atom)
202 }
203 fn remove(&mut self, atom: &Atom) -> bool {
204 GroundingSpace::remove(self, atom)
205 }
206 fn replace(&mut self, from: &Atom, to: Atom) -> bool {
207 GroundingSpace::replace(self, from, to)
208 }
209 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
210 self
211 }
212}
213
214impl<D: DuplicationStrategy> Debug for GroundingSpace<D> {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 match &self.name {
217 Some(name) => write!(f, "GroundingSpace-{name} ({self:p})"),
218 None => write!(f, "GroundingSpace-{self:p}")
219 }
220 }
221}
222
223impl<D: DuplicationStrategy> Display for GroundingSpace<D> {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 match &self.name {
226 Some(name) => write!(f, "GroundingSpace-{name}"),
227 None => write!(f, "GroundingSpace-{self:p}")
228 }
229 }
230}
231
232#[cfg(test)]
233use crate::metta::text::*;
234
235#[cfg(test)]
236pub(crate) fn metta_space(text: &str) -> DynSpace {
237 let mut space = GroundingSpace::new();
238 let mut parser = SExprParser::new(text);
239 while let Some(atom) = parser.parse(&Tokenizer::new()).unwrap() {
240 space.add(atom);
241 }
242 space.into()
243}
244
245#[cfg(test)]
246mod test {
247 use std::borrow::Cow;
248
249 use super::*;
250 use hyperon_atom::matcher::*;
251 use hyperon_common::assert_eq_no_order;
252 use hyperon_space::SpaceObserver;
253
254 struct SpaceEventCollector {
255 events: Vec<SpaceEvent>,
256 }
257
258 impl SpaceEventCollector {
259 fn new() -> Self {
260 Self{ events: Vec::new() }
261 }
262 }
263
264 impl SpaceObserver for SpaceEventCollector {
265 fn notify(&mut self, event: &SpaceEvent) {
266 self.events.push(event.clone());
267 }
268 }
269
270 #[test]
271 fn add_atom() {
272 let mut space = GroundingSpace::new();
273 let observer = space.common.register_observer(SpaceEventCollector::new());
274
275 space.add(expr!("a"));
276 space.add(expr!("b"));
277 space.add(expr!("c"));
278
279 assert_eq_no_order!(space.into_vec(), vec![expr!("a"), expr!("b"), expr!("c")]);
280 assert_eq!(observer.borrow().events, vec![SpaceEvent::Add(sym!("a")),
281 SpaceEvent::Add(sym!("b")), SpaceEvent::Add(sym!("c"))]);
282 }
283
284 #[test]
285 fn remove_atom() {
286 let mut space = GroundingSpace::new();
287 let observer = space.common.register_observer(SpaceEventCollector::new());
288
289 space.add(expr!("a"));
290 space.add(expr!("b"));
291 space.add(expr!("c"));
292 assert_eq!(space.remove(&expr!("b")), true);
293
294 assert_eq_no_order!(space.into_vec(), vec![expr!("a"), expr!("c")]);
295 assert_eq!(observer.borrow().events, vec![SpaceEvent::Add(sym!("a")),
296 SpaceEvent::Add(sym!("b")), SpaceEvent::Add(sym!("c")),
297 SpaceEvent::Remove(sym!("b"))]);
298 }
299
300 #[test]
301 fn remove_duplicated_atom() {
302 let mut space = GroundingSpace::new();
303 let observer = space.common.register_observer(SpaceEventCollector::new());
304
305 space.add(expr!("a"));
306 space.add(expr!("a"));
307 space.add(expr!("a"));
308 assert_eq!(space.remove(&expr!("a")), true);
309
310 assert_eq_no_order!(space.into_vec(), vec![expr!("a"), expr!("a")]);
311 assert_eq!(observer.borrow().events, vec![SpaceEvent::Add(sym!("a")),
312 SpaceEvent::Add(sym!("a")), SpaceEvent::Add(sym!("a")),
313 SpaceEvent::Remove(sym!("a"))]);
314 }
315
316 #[test]
317 fn remove_atom_not_found() {
318 let mut space = GroundingSpace::new();
319 let observer = space.common.register_observer(SpaceEventCollector::new());
320
321 space.add(expr!("a"));
322 assert_eq!(space.remove(&expr!("b")), false);
323
324 assert_eq_no_order!(space.into_vec(), vec![expr!("a")]);
325 assert_eq!(observer.borrow().events, vec![SpaceEvent::Add(sym!("a"))]);
326 }
327
328 #[test]
329 fn replace_atom() {
330 let mut space = GroundingSpace::new();
331 let observer = space.common.register_observer(SpaceEventCollector::new());
332
333 space.add(expr!("a"));
334 space.add(expr!("b"));
335 space.add(expr!("c"));
336 assert_eq!(space.replace(&expr!("b"), expr!("d")), true);
337
338 assert_eq_no_order!(space.into_vec(), vec![expr!("a"), expr!("d"), expr!("c")]);
339 assert_eq!(observer.borrow().events, vec![SpaceEvent::Add(sym!("a")),
340 SpaceEvent::Add(sym!("b")), SpaceEvent::Add(sym!("c")),
341 SpaceEvent::Replace(sym!("b"), sym!("d"))]);
342 }
343
344 #[test]
345 fn replace_atom_not_found() {
346 let mut space = GroundingSpace::new();
347 let observer = space.common.register_observer(SpaceEventCollector::new());
348
349 space.add(expr!("a"));
350 assert_eq!(space.replace(&expr!("b"), expr!("d")), false);
351
352 assert_eq_no_order!(space.into_vec(), vec![expr!("a")]);
353 assert_eq!(observer.borrow().events, vec![SpaceEvent::Add(sym!("a"))]);
354 }
355
356 #[test]
357 fn remove_replaced_atom() {
358 let mut space = GroundingSpace::new();
359 let observer = space.common.register_observer(SpaceEventCollector::new());
360
361 space.add(expr!("a"));
362 space.replace(&expr!("a"), expr!("b"));
363 assert_eq!(space.remove(&expr!("b")), true);
364
365 assert_eq_no_order!(space.into_vec(), Vec::<Atom>::new());
366 assert_eq!(observer.borrow().events, vec![SpaceEvent::Add(sym!("a")),
367 SpaceEvent::Replace(expr!("a"), expr!("b")),
368 SpaceEvent::Remove(expr!("b"))]);
369 }
370
371 #[test]
372 fn get_atom_after_removed() {
373 let mut space = GroundingSpace::new();
374
375 space.add(Atom::sym("A"));
376 space.add(Atom::sym("B"));
377 space.remove(&Atom::sym("A"));
378
379 assert_eq!(space.query(&Atom::sym("B")), BindingsSet::single());
380 }
381
382 #[test]
383 fn iter_empty() {
384 let space = GroundingSpace::from_vec(vec![]);
385
386 assert_eq!(space.atom_count(), Some(0));
387 }
388
389 #[test]
390 fn iter_after_remove() {
391 let mut space = GroundingSpace::from_vec(vec![expr!("a"), expr!("b"), expr!("c")]);
392 space.remove(&expr!("b"));
393
394 let mut atoms = Vec::new();
395 assert_eq!(Ok(()), space.visit(&mut |atom: Cow<Atom>| atoms.push(atom.into_owned())));
396 assert_eq_no_order!(atoms, vec![expr!("a"), expr!("c")]);
397 }
398
399 #[test]
400 fn mut_cloned_atomspace() {
401 let mut first = GroundingSpace::new();
402 let mut second = first.clone();
403
404 first.add(expr!("b"));
405 second.add(expr!("d"));
406
407 assert_eq_no_order!(first.into_vec(), vec![expr!("b")]);
408 assert_eq_no_order!(second.into_vec(), vec![expr!("d")]);
409 }
410
411 #[test]
412 fn test_match_symbol() {
413 let mut space = GroundingSpace::new();
414 space.add(expr!("foo"));
415 assert_eq!(space.query(&expr!("foo")), BindingsSet::single());
416 }
417
418 #[test]
419 fn test_match_variable() {
420 let mut space = GroundingSpace::new();
421 space.add(expr!("foo"));
422 assert_eq!(space.query(&expr!(x)), bind_set![{x: expr!("foo")}]);
423 }
424
425 #[test]
426 fn test_match_expression() {
427 let mut space = GroundingSpace::new();
428 space.add(expr!("+" "a" ("*" "b" "c")));
429 assert_eq!(space.query(&expr!("+" "a" ("*" "b" "c"))), BindingsSet::single());
430 }
431
432 #[test]
433 fn test_match_expression_with_variables() {
434 let mut space = GroundingSpace::new();
435 space.add(expr!("+" "A" ("*" "B" "C")));
436 assert_eq!(space.query(&expr!("+" a ("*" b c))),
437 bind_set![{a: expr!("A"), b: expr!("B"), c: expr!("C") }]);
438 }
439
440 #[test]
441 fn test_match_different_value_for_variable() {
442 let mut space = GroundingSpace::new();
443 space.add(expr!("+" "A" ("*" "B" "C")));
444 assert_eq!(space.query(&expr!("+" a ("*" a c))), BindingsSet::empty());
445 }
446
447 #[test]
448 fn test_match_query_variable_has_priority() {
449 let mut space = GroundingSpace::new();
450 space.add(expr!("equals" x x));
451
452 let result = space.query(&expr!("equals" y z));
453 assert_eq!(result, bind_set![{ y: expr!(z) }]);
454 }
455
456 #[test]
457 fn test_match_query_variable_via_data_variable() {
458 let mut space = GroundingSpace::new();
459 space.add(expr!(x x));
460 assert_eq!(space.query(&expr!(y (z))), bind_set![{y: expr!((z))}]);
461 }
462
463 #[test]
464 fn test_match_if_then_with_x() {
465 let mut space = GroundingSpace::new();
466 space.add(expr!("=" ("if" "True" then) then));
467 assert_eq!(space.query(&expr!("=" ("if" "True" "42") X)),
468 bind_set![{X: expr!("42")}]);
469 }
470
471 #[test]
472 fn test_match_combined_query() {
473 let mut space = GroundingSpace::new();
474 space.add(expr!("posesses" "Sam" "baloon"));
475 space.add(expr!("likes" "Sam" ("blue" "stuff")));
476 space.add(expr!("has-color" "baloon" "blue"));
477
478 let result = space.query(&expr!("," ("posesses" "Sam" object)
479 ("likes" "Sam" (color "stuff"))
480 ("has-color" object color)));
481 assert_eq!(result, bind_set![{object: expr!("baloon"), color: expr!("blue")}]);
482 }
483
484 #[test]
485 fn test_unify_variables_inside_conjunction_query() {
486 let mut space = GroundingSpace::new();
487 space.add(expr!("lst1" ("Cons" "a1" ("Cons" "b2" "b3"))));
488 space.add(expr!("lst2" ("Cons" "a2" ("Cons" "b3" "b4"))));
489 space.add(expr!("Concat" x1 x2 x3));
490
491 let result = space.subst(
492 &expr!("," ("lst1" l1) ("lst2" l2) ("Concat" l1 "a2" "a3")),
493 &expr!(l1));
494 assert_eq!(result, vec![expr!("Cons" "a1" ("Cons" "b2" "b3"))]);
495 }
496
497 #[test]
498 fn test_type_check_in_query() {
499 let mut space = GroundingSpace::new();
500 space.add(expr!(":" "Human" "Type"));
501 space.add(expr!(":" "Socrates" "Human"));
502 space.add(expr!("Cons" "Socrates" "Nil"));
503
504 let result = space.query(&expr!("," (":" h "Human") ("Cons" h t)));
505 assert_eq!(result, bind_set![{h: expr!("Socrates"), t: expr!("Nil")}]);
506 }
507
508 #[test]
509 fn cleanup_observer() {
510 let mut space = GroundingSpace::new();
511 {
512 let _observer = space.common.register_observer(SpaceEventCollector::new());
513 assert_eq!(space.common.observers.borrow().len(), 1);
514 }
515
516 space.add(expr!("a"));
517
518 assert_eq_no_order!(space.into_vec(), vec![expr!("a")]);
519 assert_eq!(space.common.observers.borrow().len(), 0);
520 }
521
522 #[test]
523 fn complex_query_applying_bindings_to_next_pattern() {
524 let mut space = GroundingSpace::new();
525 space.add(expr!(":=" ("sum" a b) ("+" a b)));
526 space.add(expr!(":=" "a" {4}));
527
528 let result = space.query(&expr!("," (":=" "a" b) (":=" ("sum" {3} b) W)));
529
530 assert_eq!(result.len(), 1);
531 let result = &result[0];
532 assert_eq!(result.resolve(&VariableAtom::new("W")), Some(expr!("+" {3} {4})));
533 assert_eq!(result.resolve(&VariableAtom::new("b")), Some(expr!({4})));
534 }
535
536 #[test]
537 fn complex_query_chain_of_bindings() {
538 let mut space = GroundingSpace::new();
539 space.add(expr!("implies" ("B" x) ("C" x)));
540 space.add(expr!("implies" ("A" x) ("B" x)));
541 space.add(expr!("A" "Sam"));
542
543 let result = space.query(&expr!("," ("implies" ("B" x) z) ("implies" ("A" x) y) ("A" x)));
544 assert_eq!(result.len(), 1);
546 let result = result.into_iter().next().unwrap();
547 assert_eq!(result.resolve(&VariableAtom::new("x")), Some(sym!("Sam")));
548 assert_eq!(result.resolve(&VariableAtom::new("y")), Some(expr!("B" "Sam")));
549 assert_eq!(result.resolve(&VariableAtom::new("z")), Some(expr!("C" "Sam")));
550 }
551
552 #[test]
553 fn test_match_results_with_variable_loop() {
554 let space = GroundingSpace::from_vec(vec![
555 expr!("R" a a),
556 ]);
557 let result = space.query(&expr!("R" b ("S" b)));
558 assert_eq!(result, bind_set![]);
559 }
560
561 #[test]
562 fn test_custom_match_with_space() {
563 let space = GroundingSpace::from_vec(vec![
564 expr!("A" {1} x "a"),
565 expr!("B" {1} x "b"),
566 expr!("A" {2} x "c"),
567 ]);
568 let result: BindingsSet = match_atoms(&Atom::gnd(DynSpace::new(space)), &expr!("A" {1} x x)).collect();
569 assert_eq!(result, bind_set![{x: sym!("a")}]);
570 }
571}