hyperon/metta/runner/stdlib/
atom.rs

1use hyperon_atom::*;
2use hyperon_space::*;
3use crate::metta::*;
4use crate::metta::text::Tokenizer;
5use crate::metta::types::{AtomType, get_atom_types, get_meta_type};
6use hyperon_atom::matcher::atoms_are_equivalent;
7use hyperon_common::multitrie::{MultiTrie, TrieKey, TrieToken};
8use super::{grounded_op, regex};
9use hyperon_atom::gnd::number::*;
10
11use std::convert::TryInto;
12use std::hash::{DefaultHasher, Hasher};
13
14#[derive(Clone, Debug)]
15pub struct UniqueAtomOp {}
16
17grounded_op!(UniqueAtomOp, "unique-atom");
18
19impl Grounded for UniqueAtomOp {
20    fn type_(&self) -> Atom {
21        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
22    }
23
24    fn as_execute(&self) -> Option<&dyn CustomExecute> {
25        Some(self)
26    }
27}
28
29impl CustomExecute for UniqueAtomOp {    
30    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {    
31        let arg_error = || ExecError::from("unique expects single expression atom as an argument");    
32        let expr = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?;    
33    
34        let mut atoms: Vec<Atom> = expr.children().into();    
35        let mut seen: Vec<Atom> = Vec::new();    
36        atoms.retain(|x| {    
37            let not_contained = !seen.iter().any(|seen_atom| atoms_are_equivalent(seen_atom, x));    
38            if not_contained { seen.push(x.clone()) };    
39            not_contained    
40        });    
41        Ok(vec![Atom::expr(atoms)])    
42    }    
43}
44
45
46#[derive(Clone, Debug)]
47pub struct UnionAtomOp {}
48
49grounded_op!(UnionAtomOp, "union-atom");
50
51impl Grounded for UnionAtomOp {
52    fn type_(&self) -> Atom {
53        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
54    }
55
56    fn as_execute(&self) -> Option<&dyn CustomExecute> {
57        Some(self)
58    }
59}
60
61impl CustomExecute for UnionAtomOp {
62    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
63        let arg_error = || ExecError::from("union expects and executable LHS and RHS atom");
64        let mut lhs: Vec<Atom> = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().into();
65        let rhs: Vec<Atom> = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children().into();
66
67        lhs.extend(rhs);
68
69        Ok(vec![Atom::expr(lhs)])
70    }
71}
72
73#[derive(Clone, Debug)]
74pub struct IntersectionAtomOp {}
75
76grounded_op!(IntersectionAtomOp, "intersection-atom");
77
78impl Grounded for IntersectionAtomOp {
79    fn type_(&self) -> Atom {
80        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
81    }
82
83    fn as_execute(&self) -> Option<&dyn CustomExecute> {
84        Some(self)
85    }
86}
87
88fn atom_to_trie_key(atom: &Atom) -> TrieKey<SymbolAtom> {
89    fn fill_key(atom: &Atom, tokens: &mut Vec<TrieToken<SymbolAtom>>) {
90        match atom {
91            Atom::Symbol(sym) => tokens.push(TrieToken::Exact(sym.clone())),
92            Atom::Expression(expr) => {
93                tokens.push(TrieToken::LeftPar);
94                expr.children().iter().for_each(|child| fill_key(child, tokens));
95                tokens.push(TrieToken::RightPar);
96            },
97            Atom::Grounded(g) if g.as_grounded().as_match().is_none() => {
98                // TODO: Adding Hash on grounded atoms matched by equality is
99                // required in order to make TrieToken::Exact be generated for
100                // them.
101                let mut h = DefaultHasher::new();
102                match (*g).serialize(&mut h) {
103                    Ok(()) => { tokens.push(TrieToken::Exact(SymbolAtom::new(h.finish().to_string().into()))) }
104                    Err(_) => { tokens.push(TrieToken::Wildcard) }
105                }
106            }
107            _ => tokens.push(TrieToken::Wildcard),
108        }
109    }
110
111    let mut tokens = Vec::new();
112    fill_key(atom, &mut tokens);
113    TrieKey::from(tokens)
114}
115
116
117impl CustomExecute for IntersectionAtomOp {
118    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
119        let arg_error = || ExecError::from("intersection expects and executable LHS and RHS atom");
120        let mut lhs: Vec<Atom> = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().into();
121        let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children();
122
123        let mut rhs_index: MultiTrie<SymbolAtom, Vec<usize>> = MultiTrie::new();
124        for (index, rhs_item) in rhs.iter().enumerate() {
125            let k = atom_to_trie_key(&rhs_item);
126            // FIXME this should
127            // a) use a mutable value endpoint which the MultiTrie does not support atm
128            // b) use a linked list, which Rust barely supports atm
129            let r = rhs_index.get(&k).next();
130            match r.cloned() {
131                Some(bucket) => {
132                    rhs_index.remove(&k, &bucket);
133                    let mut nbucket = bucket;
134                    nbucket.push(index);
135                    let nbucket = nbucket;
136                    rhs_index.insert(k, nbucket);
137                }
138                None => { rhs_index.insert(k, vec![index]) }
139            }
140        }
141
142        lhs.retain(|candidate| {
143            let k = atom_to_trie_key(candidate);
144            let r = rhs_index.get(&k).next();
145            match r.cloned() {
146                None => { false }
147                Some(bucket) => {
148                    match bucket.iter().position(|item| &rhs[*item] == candidate) {
149                        None => { false }
150                        Some(i) => {
151                            rhs_index.remove(&k, &bucket);
152                            if bucket.len() > 1 {
153                                let mut nbucket = bucket;
154                                nbucket.remove(i);
155                                rhs_index.insert(k, nbucket);
156                            }
157                            true
158                        }
159                    }
160                }
161            }
162        });
163
164        Ok(vec![Atom::expr(lhs)])
165    }
166}
167
168#[derive(Clone, Debug)]
169pub struct MaxAtomOp {}
170
171grounded_op!(MaxAtomOp, "max-atom");
172
173impl Grounded for MaxAtomOp {
174    fn type_(&self) -> Atom {
175        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, ATOM_TYPE_NUMBER])
176    }
177
178    fn as_execute(&self) -> Option<&dyn CustomExecute> {
179        Some(self)
180    }
181}
182
183impl CustomExecute for MaxAtomOp {
184    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
185        let arg_error = || ExecError::from("max-atom expects one argument: expression");
186        let expr = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?;
187        let children = expr.children();
188        if children.is_empty() {
189            Err(ExecError::from("Empty expression"))
190        } else {
191            children.into_iter().fold(Ok(f64::NEG_INFINITY), |res, x| {
192                match (res, Number::from_atom(x)) {
193                    (res @ Err(_), _) => res,
194                    (_, None) => Err(ExecError::from(format!("Only numbers are allowed in expression: {}", expr))),
195                    (Ok(max), Some(x)) => Ok(f64::max(max, x.into())),
196                }
197            })
198        }.map(|max| vec![Atom::gnd(Number::Float(max))])
199    }
200}
201
202#[derive(Clone, Debug)]
203pub struct MinAtomOp {}
204
205grounded_op!(MinAtomOp, "min-atom");
206
207impl Grounded for MinAtomOp {
208    fn type_(&self) -> Atom {
209        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, ATOM_TYPE_NUMBER])
210    }
211
212    fn as_execute(&self) -> Option<&dyn CustomExecute> {
213        Some(self)
214    }
215}
216
217impl CustomExecute for MinAtomOp {
218    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
219        let arg_error = || ExecError::from("min-atom expects one argument: expression");
220        let expr = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?;
221        let children = expr.children();
222        if children.is_empty() {
223            Err(ExecError::from("Empty expression"))
224        } else {
225            children.into_iter().fold(Ok(f64::INFINITY), |res, x| {
226                match (res, Number::from_atom(x)) {
227                    (res @ Err(_), _) => res,
228                    (_, None) => Err(ExecError::from(format!("Only numbers are allowed in expression: {}", expr))),
229                    (Ok(min), Some(x)) => Ok(f64::min(min, x.into())),
230                }
231            })
232        }.map(|min| vec![Atom::gnd(Number::Float(min))])
233    }
234}
235
236#[derive(Clone, Debug)]
237pub struct SizeAtomOp {}
238
239grounded_op!(SizeAtomOp, "size-atom");
240
241impl Grounded for SizeAtomOp {
242    fn type_(&self) -> Atom {
243        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER])
244    }
245
246    fn as_execute(&self) -> Option<&dyn CustomExecute> {
247        Some(self)
248    }
249}
250
251impl CustomExecute for SizeAtomOp {
252    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
253        let arg_error = || ExecError::from("size-atom expects one argument: expression");
254        let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children();
255        let size = children.len();
256        Ok(vec![Atom::gnd(Number::Integer(size as i64))])
257    }
258}
259
260#[derive(Clone, Debug)]
261pub struct IndexAtomOp {}
262
263grounded_op!(IndexAtomOp, "index-atom");
264
265impl Grounded for IndexAtomOp {
266    fn type_(&self) -> Atom {
267        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER, ATOM_TYPE_ATOM])
268    }
269
270    fn as_execute(&self) -> Option<&dyn CustomExecute> {
271        Some(self)
272    }
273}
274
275impl CustomExecute for IndexAtomOp {
276    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
277        let arg_error = || ExecError::from("index-atom expects two arguments: expression and atom");
278        let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children();
279        let index = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?;
280        match children.get(Into::<i64>::into(index) as usize) {
281            Some(atom) => Ok(vec![atom.clone()]),
282            None => Err(ExecError::from("Index is out of bounds")),
283        }
284    }
285}
286
287#[derive(Clone, Debug)]
288pub struct SubtractionAtomOp {}
289
290grounded_op!(SubtractionAtomOp, "subtraction-atom");
291
292impl Grounded for SubtractionAtomOp {
293    fn type_(&self) -> Atom {
294        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
295    }
296
297    fn as_execute(&self) -> Option<&dyn CustomExecute> {
298        Some(self)
299    }
300}
301
302impl CustomExecute for SubtractionAtomOp {
303    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
304        let arg_error = || ExecError::from("subtraction expects and executable LHS and RHS atom");
305        let mut lhs: Vec<Atom> = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().into();
306        let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children();
307
308        let mut rhs_index: MultiTrie<SymbolAtom, Vec<usize>> = MultiTrie::new();
309        for (index, rhs_item) in rhs.iter().enumerate() {
310            let k = atom_to_trie_key(&rhs_item);
311            // FIXME this should
312            // a) use a mutable value endpoint which the MultiTrie does not support atm
313            // b) use a linked list, which Rust barely supports atm
314            let r = rhs_index.get(&k).next();
315            match r.cloned() {
316                Some(bucket) => {
317                    rhs_index.remove(&k, &bucket);
318                    let mut nbucket = bucket;
319                    nbucket.push(index);
320                    let nbucket = nbucket;
321                    rhs_index.insert(k, nbucket);
322                }
323                None => { rhs_index.insert(k, vec![index]) }
324            }
325        }
326
327        lhs.retain(|candidate| {
328            let k = atom_to_trie_key(candidate);
329            let r = rhs_index.get(&k).next();
330            match r.cloned() {
331                None => { true }
332                Some(bucket) => {
333                    match bucket.iter().position(|item| &rhs[*item] == candidate) {
334                        None => { true }
335                        Some(i) => {
336                            rhs_index.remove(&k, &bucket);
337                            if bucket.len() > 1 {
338                                let mut nbucket = bucket;
339                                nbucket.remove(i);
340                                rhs_index.insert(k, nbucket);
341                            }
342                            false
343                        }
344                    }
345                }
346            }
347        });
348
349        Ok(vec![Atom::expr(lhs)])
350    }
351}
352
353#[derive(Clone, Debug)]
354pub struct GetTypeOp {
355    space: DynSpace,
356}
357
358grounded_op!(GetTypeOp, "get-type");
359
360impl GetTypeOp {
361    pub fn new(space: DynSpace) -> Self {
362        Self{ space }
363    }
364}
365
366impl Grounded for GetTypeOp {
367    fn type_(&self) -> Atom {
368        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED])
369    }
370
371    fn as_execute(&self) -> Option<&dyn CustomExecute> {
372        Some(self)
373    }
374}
375
376impl CustomExecute for GetTypeOp {
377    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
378        let arg_error = || ExecError::from("get-type expects single atom as an argument");
379        let atom = args.get(0).ok_or_else(arg_error)?;
380        let space = match args.get(1) {
381            Some(space) => Atom::as_gnd::<DynSpace>(space)
382                .ok_or("match expects a space as the first argument"),
383            None => Ok(&self.space),
384        }?;
385        let types = get_atom_types(space, atom);
386        if types.iter().all(AtomType::is_error) {
387            Ok(vec![EMPTY_SYMBOL])
388        } else {
389            Ok(types.into_iter().filter(AtomType::is_valid).map(AtomType::into_atom).collect())
390        }
391    }
392}
393
394#[derive(Clone, Debug)]
395pub struct GetMetaTypeOp { }
396
397grounded_op!(GetMetaTypeOp, "get-metatype");
398
399impl Grounded for GetMetaTypeOp {
400    fn type_(&self) -> Atom {
401        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM])
402    }
403
404    fn as_execute(&self) -> Option<&dyn CustomExecute> {
405        Some(self)
406    }
407}
408
409impl CustomExecute for GetMetaTypeOp {
410    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
411        let arg_error = || ExecError::from("get-metatype expects single atom as an argument");
412        let atom = args.get(0).ok_or_else(arg_error)?;
413
414        Ok(vec![get_meta_type(&atom)])
415    }
416}
417
418#[derive(Clone, Debug)]
419pub struct GetTypeSpaceOp {}
420
421grounded_op!(GetTypeSpaceOp, "get-type-space");
422
423impl Grounded for GetTypeSpaceOp {
424    fn type_(&self) -> Atom {
425        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SPACE, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM])
426    }
427
428    fn as_execute(&self) -> Option<&dyn CustomExecute> {
429        Some(self)
430    }
431}
432
433impl CustomExecute for GetTypeSpaceOp {
434    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
435        let arg_error = || ExecError::from("get-type-space expects two arguments: space and atom");
436        let space = args.get(0).ok_or_else(arg_error)?;
437        let space = Atom::as_gnd::<DynSpace>(space).ok_or("get-type-space expects a space as the first argument")?;
438        let atom = args.get(1).ok_or_else(arg_error)?;
439        log::debug!("GetTypeSpaceOp::execute: space: {}, atom: {}", space, atom);
440        let types = get_atom_types(space, atom);
441        if types.iter().all(AtomType::is_error) {
442            Ok(vec![EMPTY_SYMBOL])
443        } else {
444            Ok(types.into_iter().filter(AtomType::is_valid).map(AtomType::into_atom).collect())
445        }
446    }
447}
448
449pub(super) fn register_context_dependent_tokens(tref: &mut Tokenizer, space: &DynSpace) {
450    let get_type_op = Atom::gnd(GetTypeOp::new(space.clone()));
451    tref.register_token(regex(r"get-type"), move |_| { get_type_op.clone() });
452}
453
454pub(super) fn register_context_independent_tokens(tref: &mut Tokenizer) {
455    let get_type_space_op = Atom::gnd(GetTypeSpaceOp{});
456    tref.register_token(regex(r"get-type-space"), move |_| { get_type_space_op.clone() });
457    let get_meta_type_op = Atom::gnd(GetMetaTypeOp{});
458    tref.register_token(regex(r"get-metatype"), move |_| { get_meta_type_op.clone() });
459    let min_atom_op = Atom::gnd(MinAtomOp{});
460    tref.register_token(regex(r"min-atom"), move |_| { min_atom_op.clone() });
461    let max_atom_op = Atom::gnd(MaxAtomOp{});
462    tref.register_token(regex(r"max-atom"), move |_| { max_atom_op.clone() });
463    let size_atom_op = Atom::gnd(SizeAtomOp{});
464    tref.register_token(regex(r"size-atom"), move |_| { size_atom_op.clone() });
465    let index_atom_op = Atom::gnd(IndexAtomOp{});
466    tref.register_token(regex(r"index-atom"), move |_| { index_atom_op.clone() });
467    let unique_op = Atom::gnd(UniqueAtomOp{});
468    tref.register_token(regex(r"unique-atom"), move |_| { unique_op.clone() });
469    let subtraction_op = Atom::gnd(SubtractionAtomOp{});
470    tref.register_token(regex(r"subtraction-atom"), move |_| { subtraction_op.clone() });
471    let intersection_op = Atom::gnd(IntersectionAtomOp{});
472    tref.register_token(regex(r"intersection-atom"), move |_| { intersection_op.clone() });
473    let union_op = Atom::gnd(UnionAtomOp{});
474    tref.register_token(regex(r"union-atom"), move |_| { union_op.clone() });
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480    use crate::metta::text::SExprParser;
481    use crate::metta::runner::EnvBuilder;
482    use hyperon_atom::gnd::str::Str;
483    use crate::space::grounding::metta_space;
484    use crate::metta::runner::run_program;
485    use crate::metta::runner::Metta;
486    use crate::metta::runner::stdlib::arithmetics::SumOp;
487    use hyperon_common::{assert_eq_metta_results, assert_eq_no_order};
488
489    #[test]
490    fn metta_car_atom() {
491        let result = run_program("!(car-atom (A $b))");
492        assert_eq!(result, Ok(vec![vec![expr!("A")]]));
493        let result = run_program("!(car-atom ($a B))");
494        assert_eq!(result, Ok(vec![vec![expr!(a)]]));
495        let result = run_program("!(car-atom ())");
496        assert_eq!(result, Ok(vec![vec![expr!("Error" ("car-atom" ()) {Str::from_str("car-atom expects a non-empty expression as an argument")})]]));
497        let result = run_program("!(car-atom A)");
498        assert_eq!(result, Ok(vec![vec![expr!("Error" ("car-atom" "A") {Str::from_str("car-atom expects a non-empty expression as an argument")})]]));
499    }
500
501    #[test]
502    fn metta_cdr_atom() {
503        assert_eq!(run_program(&format!("!(cdr-atom (a b c))")), Ok(vec![vec![expr!("b" "c")]]));
504        assert_eq!(run_program(&format!("!(cdr-atom ($a b $c))")), Ok(vec![vec![expr!("b" c)]]));
505        assert_eq!(run_program(&format!("!(cdr-atom ())")), Ok(vec![vec![expr!("Error" ("cdr-atom" ()) {Str::from_str("cdr-atom expects a non-empty expression as an argument")})]]));
506        assert_eq!(run_program(&format!("!(cdr-atom a)")), Ok(vec![vec![expr!("Error" ("cdr-atom" "a") {Str::from_str("cdr-atom expects a non-empty expression as an argument")})]]));
507        assert_eq!(run_program(&format!("!(cdr-atom $a)")), Ok(vec![vec![expr!("Error" ("cdr-atom" a) {Str::from_str("cdr-atom expects a non-empty expression as an argument")})]]));
508    }
509
510    #[test]
511    fn metta_size_atom() {
512        assert_eq!(run_program(&format!("!(size-atom (5 4 3 2 1))")), Ok(vec![vec![expr!({Number::Integer(5)})]]));
513        assert_eq!(run_program(&format!("!(size-atom ())")), Ok(vec![vec![expr!({Number::Integer(0)})]]));
514    }
515
516    #[test]
517    fn metta_min_atom() {
518        assert_eq!(run_program(&format!("!(min-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Integer(4)})]]));
519        assert_eq!(run_program(&format!("!(min-atom ())")), Ok(vec![vec![expr!("Error" ({ MinAtomOp{} } ()) "Empty expression")]]));
520        assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression: (3 A B 5)")]]));
521        assert_eq!(run_program(&format!("(= (nums) (5 4 5.5)) !(min-atom (nums))")), Ok(vec![vec![expr!({Number::Integer(4)})]]));
522    }
523
524    #[test]
525    fn metta_max_atom() {
526        assert_eq!(run_program(&format!("!(max-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Float(5.5)})]]));
527        assert_eq!(run_program(&format!("!(max-atom ())")), Ok(vec![vec![expr!("Error" ({ MaxAtomOp{} } ()) "Empty expression")]]));
528        assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression: (3 A B 5)")]]));
529        assert_eq!(run_program(&format!("(= (nums) (5 4 5.5)) !(max-atom (nums))")), Ok(vec![vec![expr!({Number::Float(5.5)})]]));
530    }
531
532    #[test]
533    fn metta_index_atom() {
534        assert_eq!(run_program(&format!("!(index-atom (5 4 3 2 1) 2)")), Ok(vec![vec![expr!({Number::Integer(3)})]]));
535        assert_eq!(run_program(&format!("!(index-atom (A B C D E) 5)")), Ok(vec![vec![expr!("Error" ({ IndexAtomOp{} } ("A" "B" "C" "D" "E") {Number::Integer(5)}) "Index is out of bounds")]]));
536    }
537
538    #[test]
539    fn metta_filter_atom() {
540        assert_eq!(run_program("!(filter-atom () $x (eval (if-error $x False True)))"), Ok(vec![vec![expr!()]]));
541        assert_eq!(run_program("!(filter-atom (a (b) $c) $x (eval (if-error $x False True)))"), Ok(vec![vec![expr!("a" ("b") c)]]));
542        assert_eq!(run_program("!(filter-atom (a (Error (b) \"Test error\") $c) $x (eval (if-error $x False True)))"), Ok(vec![vec![expr!("a" c)]]));
543    }
544
545    #[test]
546    fn metta_map_atom() {
547        assert_eq!(run_program("!(map-atom () $x ($x mapped))"), Ok(vec![vec![expr!()]]));
548        assert_eq!(run_program("!(map-atom (a (b) $c) $x (mapped $x))"), Ok(vec![vec![expr!(("mapped" "a") ("mapped" ("b")) ("mapped" c))]]));
549    }
550
551    #[test]
552    fn metta_foldl_atom() {
553        assert_eq!(run_program("!(foldl-atom () 1 $a $b (eval (+ $a $b)))"), Ok(vec![vec![expr!({Number::Integer(1)})]]));
554        assert_eq!(run_program("!(foldl-atom (1 2 3) 0 $a $b (eval (+ $a $b)))"), Ok(vec![vec![expr!({Number::Integer(6)})]]));
555    }
556
557    #[test]
558    fn size_atom_op() {
559        let res = SizeAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)})]).expect("No result returned");
560        assert_eq!(res, vec![expr!({Number::Integer(5)})]);
561        let res = SizeAtomOp{}.execute(&mut vec![expr!()]).expect("No result returned");
562        assert_eq!(res, vec![expr!({Number::Integer(0)})]);
563    }
564
565    #[test]
566    fn min_atom_op() {
567        let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned");
568        assert_eq!(res, vec![expr!({Number::Integer(4)})]);
569        let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]);
570        assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression: (5 4 A)")));
571        let res = MinAtomOp{}.execute(&mut vec![expr!()]);
572        assert_eq!(res, Err(ExecError::from("Empty expression")));
573    }
574
575    #[test]
576    fn max_atom_op() {
577        let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned");
578        assert_eq!(res, vec![expr!({Number::Float(5.5)})]);
579        let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]);
580        assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression: (5 4 A)")));
581        let res = MaxAtomOp{}.execute(&mut vec![expr!()]);
582        assert_eq!(res, Err(ExecError::from("Empty expression")));
583        let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(4)} {Str::from_str("5")})]);
584        assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression: (4 \"5\")")));
585    }
586
587    #[test]
588    fn index_atom_op() {
589        let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(2)})]).expect("No result returned");
590        assert_eq!(res, vec![expr!({Number::Integer(3)})]);
591        let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(5)})]);
592        assert_eq!(res, Err(ExecError::from("Index is out of bounds")));
593    }
594
595    #[test]
596    fn test_error_is_used_as_an_argument() {
597        let metta = Metta::new(Some(EnvBuilder::test_env()));
598        let parser = SExprParser::new(r#"
599            !(get-type Error)
600            !(get-metatype Error)
601            !(get-type (Error Foo Boo))
602            !(Error (+ 1 2) (+ 1 +))
603        "#);
604
605        assert_eq_metta_results!(metta.run(parser), Ok(vec![
606            vec![expr!("->" "Atom" "Atom" "ErrorType")],
607            vec![expr!("Symbol")],
608            vec![expr!("ErrorType")],
609            vec![expr!("Error" ({SumOp{}} {Number::Integer(1)} {Number::Integer(2)}) ({SumOp{}} {Number::Integer(1)} {SumOp{}}))],
610        ]));
611    }
612
613
614    #[test]
615    fn unique_op() {
616        let unique_op = UniqueAtomOp{};
617        let actual = unique_op.execute(&mut vec![expr!(
618                ("A" ("B" "C"))
619                ("A" ("B" "C"))
620                ("f" "g")
621                ("f" "g")
622                ("f" "g")
623                "Z"
624        )]).unwrap();
625        assert_eq_no_order!(actual,
626                   vec![expr!(("A" ("B" "C")) ("f" "g") "Z")]);
627    }
628
629     #[test]  
630    fn unique_op_() {  
631        let unique_op = UniqueAtomOp{};  
632        let yonas = VariableAtom::new("yonas");  
633        let tol = VariableAtom::new("tol");  
634        let name_var = VariableAtom::new("name");  
635    
636        // Build the input: ((name $yonas) (name $tol) ($name $tol))  
637        let input = Atom::expr([  
638            Atom::expr([Atom::sym("name"), Atom::Variable(yonas.clone())]),  
639            Atom::expr([Atom::sym("name"), Atom::Variable(tol.clone())]),  
640            Atom::expr([Atom::Variable(name_var.clone()), Atom::Variable(tol.clone())])  
641        ]);  
642    
643        let actual = unique_op.execute(&[input]).unwrap();  
644        // the old implementation was giving us only ((name $yonas)) which is incorrect but now it is fixed
645        // Expected: ((name $yonas) ($name $tol))  
646        let expected = vec![Atom::expr([  
647            Atom::expr([Atom::sym("name"), Atom::Variable(yonas.clone())]),  
648            Atom::expr([Atom::Variable(name_var.clone()), Atom::Variable(tol.clone())])  
649        ])];  
650    
651        assert_eq!(actual, expected);  
652    }
653
654    #[test]
655    fn union_op() {
656        let union_op = UnionAtomOp{};
657        let actual = union_op.execute(&mut vec![expr!(
658                ("A" ("B" "C"))
659                ("A" ("B" "C"))
660                ("f" "g")
661                ("f" "g")
662                ("f" "g")
663                "Z"
664            ), expr!(
665                ("A" ("B" "C"))
666                "p"
667                "p"
668                ("Q" "a")
669            )]).unwrap();
670        assert_eq_no_order!(actual,
671                   vec![expr!(("A" ("B" "C")) ("A" ("B" "C"))
672                        ("f" "g") ("f" "g") ("f" "g") "Z"
673                        ("A" ("B" "C")) "p" "p" ("Q" "a"))]);
674    }
675
676    #[test]
677    fn index_atom_to_key() {
678        assert_eq!(atom_to_trie_key(&Atom::sym("A")), TrieKey::from([TrieToken::Exact(SymbolAtom::new("A".into()))]));
679        assert_eq!(atom_to_trie_key(&Atom::value(1)), TrieKey::from([TrieToken::Wildcard]));
680        assert_eq!(atom_to_trie_key(&Atom::var("a")), TrieKey::from([TrieToken::Wildcard]));
681        assert_eq!(atom_to_trie_key(&expr!("A" "B")), TrieKey::from([
682                TrieToken::LeftPar,
683                TrieToken::Exact(SymbolAtom::new("A".into())),
684                TrieToken::Exact(SymbolAtom::new("B".into())),
685                TrieToken::RightPar
686        ]));
687    }
688
689    #[test]
690    fn intersection_op() {
691        let intersection_op = IntersectionAtomOp{};
692        let actual = intersection_op.execute(&mut vec![expr!(
693                "Z"
694                ("A" ("B" "C"))
695                ("A" ("B" "C"))
696                ("f" "g")
697                ("f" "g")
698                ("f" "g")
699                ("P" "b")
700            ), expr!(
701                ("f" "g")
702                ("f" "g")
703                ("A" ("B" "C"))
704                "p"
705                "p"
706                ("Q" "a")
707                "Z"
708            )]).unwrap();
709        assert_eq_no_order!(actual, vec![expr!("Z" ("A" ("B" "C")) ("f" "g") ("f" "g"))]);
710
711        assert_eq_no_order!(intersection_op.execute(&mut vec![expr!(
712                { Number::Integer(5) }
713                { Number::Integer(4) }
714                { Number::Integer(3) }
715                { Number::Integer(2) }
716            ), expr!(
717                { Number::Integer(5) }
718                { Number::Integer(3) }
719            )]).unwrap(), vec![expr!({Number::Integer(5)} {Number::Integer(3)})]);
720    }
721
722    #[test]
723    fn subtraction_op() {
724        let subtraction_op = SubtractionAtomOp{};
725        let actual = subtraction_op.execute(&mut vec![expr!(
726                "Z"
727                "S"
728                "S"
729                ("A" ("B" "C"))
730                ("A" ("B" "C"))
731                ("f" "g")
732                ("f" "g")
733                ("f" "g")
734                ("P" "b")
735            ), expr!(
736                ("f" "g")
737                ("A" ("B" "C"))
738                "p"
739                "P"
740                ("Q" "a")
741                "Z"
742                "S"
743                "S"
744                "S"
745            )]).unwrap();
746        assert_eq_no_order!(actual,
747                   vec![expr!(("A" ("B" "C")) ("f" "g") ("f" "g") ("P" "b"))]);
748    }
749
750    #[test]
751    fn get_type_op() {
752        let space = metta_space("
753            (: B Type)
754            (: C Type)
755            (: A B)
756            (: A C)
757        ");
758
759        let get_type_op = GetTypeOp::new(space.clone());
760        assert_eq_no_order!(get_type_op.execute(&mut vec![sym!("A"), expr!({space.clone()})]).unwrap(),
761            vec![sym!("B"), sym!("C")]);
762    }
763
764    #[test]
765    fn get_type_op_non_valid_atom() {
766        let space = metta_space("
767            (: f (-> Number String))
768            (: 42 Number)
769            (: \"test\" String)
770        ");
771
772        let get_type_op = GetTypeOp::new(space.clone());
773        assert_eq_no_order!(get_type_op.execute(&mut vec![expr!("f" "42"), expr!({space.clone()})]).unwrap(),
774            vec![sym!("String")]);
775        assert_eq_no_order!(get_type_op.execute(&mut vec![expr!("f" "\"test\""), expr!({space.clone()})]).unwrap(),
776            vec![EMPTY_SYMBOL]);
777    }
778}