hyperon/metta/runner/stdlib/
core.rs

1use std::collections::HashSet;
2use hyperon_atom::*;
3use hyperon_space::*;
4use crate::metta::*;
5use crate::metta::text::Tokenizer;
6use hyperon_common::CachingMapper;
7use crate::metta::runner::Metta;
8use crate::metta::runner::PragmaSettings;
9use hyperon_atom::gnd::bool::*;
10use hyperon_atom::gnd::GroundedFunctionAtom;
11use hyperon_atom::matcher::{Bindings, apply_bindings_to_atom_move};
12
13use std::convert::TryInto;
14use super::{grounded_op, unit_result, regex, interpret};
15
16#[derive(Clone, Debug)]
17pub struct PragmaOp {
18    settings: PragmaSettings,
19}
20
21grounded_op!(PragmaOp, "pragma!");
22
23impl PragmaOp {
24    pub fn new(settings: PragmaSettings) -> Self {
25        Self{ settings }
26    }
27}
28
29impl Grounded for PragmaOp {
30    fn type_(&self) -> Atom {
31        ATOM_TYPE_UNDEFINED
32    }
33
34    fn as_execute(&self) -> Option<&dyn CustomExecute> {
35        Some(self)
36    }
37}
38
39impl CustomExecute for PragmaOp {
40    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
41        let arg_error = || ExecError::from("pragma! expects key and value as arguments");
42        let key = <&SymbolAtom>::try_from(args.get(0).ok_or_else(arg_error)?).map_err(|_| "pragma! expects symbol atom as a key")?.name();
43        let value = args.get(1).ok_or_else(arg_error)?;
44        match key {
45            "max-stack-depth" => {
46                value.to_string().parse::<usize>().map_err(|_| "UnsignedIntegerIsExpected")?;
47            },
48            _ => {},
49        }
50        self.settings.set(key.into(), value.clone());
51        unit_result()
52    }
53}
54
55#[derive(Clone, Debug)]
56pub struct NopOp {}
57
58grounded_op!(NopOp, "nop");
59
60impl Grounded for NopOp {
61    fn type_(&self) -> Atom {
62        ATOM_TYPE_UNDEFINED
63    }
64
65    fn as_execute(&self) -> Option<&dyn CustomExecute> {
66        Some(self)
67    }
68}
69
70impl CustomExecute for NopOp {
71    fn execute(&self, _args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
72        unit_result()
73    }
74}
75
76#[derive(Clone, Debug)]
77pub struct SealedOp {}
78
79grounded_op!(SealedOp, "sealed");
80
81impl Grounded for SealedOp {
82    fn type_(&self) -> Atom {
83        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM])
84    }
85
86    fn as_execute(&self) -> Option<&dyn CustomExecute> {
87        Some(self)
88    }
89}
90
91impl CustomExecute for SealedOp {
92    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
93        let arg_error = || ExecError::from("sealed expects two arguments: var_list to be ignored and expression");
94
95        let mut term_to_seal = args.get(1).ok_or_else(arg_error)?.clone();
96        let var_list = args.get(0).ok_or_else(arg_error)?.clone();
97
98        let to_ignore: HashSet<&VariableAtom> = var_list.iter().filter_type::<&VariableAtom>().collect();
99        let mut local_var_mapper = CachingMapper::new(|var: &VariableAtom| var.clone().make_unique());
100
101        term_to_seal.iter_mut().filter_type::<&mut VariableAtom>()
102            .for_each(|var: &mut VariableAtom| {
103                if !to_ignore.contains(var) {
104                    *var = local_var_mapper.replace(var);
105                }
106            });
107
108        let result = vec![term_to_seal.clone()];
109        log::debug!("sealed::execute: ignore_list: {}, term_to_seal: {}, result: {:?}", var_list, term_to_seal, result);
110
111        Ok(result)
112    }
113}
114
115#[derive(Clone, Debug)]
116pub struct EqualOp {}
117
118grounded_op!(EqualOp, "==");
119
120impl Grounded for EqualOp {
121    fn type_(&self) -> Atom {
122        Atom::expr([ARROW_SYMBOL, expr!(t), expr!(t), ATOM_TYPE_BOOL])
123    }
124
125    fn as_execute(&self) -> Option<&dyn CustomExecute> {
126        Some(self)
127    }
128}
129
130impl CustomExecute for EqualOp {
131    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
132        let arg_error = || ExecError::from(concat!(stringify!($op), " expects two arguments"));
133        let a = args.get(0).ok_or_else(arg_error)?;
134        let b = args.get(1).ok_or_else(arg_error)?;
135
136        Ok(vec![Atom::gnd(Bool(a == b))])
137    }
138}
139
140#[derive(Clone, Debug)]
141pub struct MatchOp {}
142
143grounded_op!(MatchOp, "match");
144
145impl Grounded for MatchOp {
146    fn type_(&self) -> Atom {
147        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SPACE, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED])
148    }
149
150    fn as_execute(&self) -> Option<&dyn CustomExecute> {
151        Some(self)
152    }
153}
154
155impl CustomExecute for MatchOp {
156    fn execute_bindings(&self, args: &[Atom]) -> Result<BoxedIter<'static, (Atom, Option<Bindings>)>, ExecError> {
157        let arg_error = || ExecError::from("match expects three arguments: space, pattern and template");
158        let space = args.get(0).ok_or_else(arg_error)?;
159        let pattern = args.get(1).ok_or_else(arg_error)?;
160        let template = args.get(2).ok_or_else(arg_error)?.clone();
161        log::debug!("MatchOp::execute: space: {:?}, pattern: {:?}, template: {:?}", space, pattern, template);
162        let space = Atom::as_gnd::<DynSpace>(space).ok_or("match expects a space as the first argument")?;
163        let results = space.borrow().query(&pattern);
164        let results = results.into_iter().map(move |b| (template.clone(), Some(b)));
165        Ok(Box::new(results))
166    }
167}
168
169#[derive(Clone, Debug)]
170pub struct IfEqualOp { }
171
172grounded_op!(IfEqualOp, "if-equal");
173
174impl Grounded for IfEqualOp {
175    fn type_(&self) -> Atom {
176        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED])
177    }
178
179    fn as_execute(&self) -> Option<&dyn CustomExecute> {
180        Some(self)
181    }
182}
183
184impl CustomExecute for IfEqualOp {
185    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
186        let arg_error = || ExecError::from("if-equal expects <atom> <pattern> <then> <else> as an argument");
187        let atom = args.get(0).ok_or_else(arg_error)?;
188        let pattern = args.get(1).ok_or_else(arg_error)?;
189        let then = args.get(2).ok_or_else(arg_error)?;
190        let else_ = args.get(3).ok_or_else(arg_error)?;
191
192        if hyperon_atom::matcher::atoms_are_equivalent(atom, pattern) {
193            Ok(vec![then.clone()])
194        } else {
195            Ok(vec![else_.clone()])
196        }
197    }
198}
199
200#[derive(Clone, Debug)]
201pub struct SuperposeOp { }
202
203grounded_op!(SuperposeOp, "superpose");
204
205impl Grounded for SuperposeOp {
206    fn type_(&self) -> Atom {
207        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_UNDEFINED])
208    }
209
210    fn as_execute(&self) -> Option<&dyn CustomExecute> {
211        Some(self)
212    }
213}
214
215impl CustomExecute for SuperposeOp {
216    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
217        let arg_error = || ExecError::from("superpose expects single expression as an argument");
218        let atom = args.get(0).ok_or_else(arg_error)?;
219        let expr  = TryInto::<&ExpressionAtom>::try_into(atom).map_err(|_| arg_error())?;
220        Ok(expr.clone().into_children())
221    }
222}
223
224#[derive(Clone, Debug)]
225pub struct CaptureOp {
226    space: DynSpace,
227    settings: PragmaSettings,
228}
229
230grounded_op!(CaptureOp, "capture");
231
232impl CaptureOp {
233    pub fn new(space: DynSpace, settings: PragmaSettings) -> Self {
234        Self{ space, settings }
235    }
236}
237
238impl Grounded for CaptureOp {
239    fn type_(&self) -> Atom {
240        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM])
241    }
242
243    fn as_execute(&self) -> Option<&dyn CustomExecute> {
244        Some(self)
245    }
246}
247
248impl CustomExecute for CaptureOp {
249    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
250        let arg_error = || ExecError::from("capture expects one argument");
251        let atom = args.get(0).ok_or_else(arg_error)?;
252        interpret(self.space.clone(), &atom, self.settings.clone()).map_err(|e| ExecError::from(e))
253    }
254}
255
256fn collapse_add_next_atom_from_collapse_bind_result(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
257    let arg0_error = || ExecError::from("Expression is expected as a first argument");
258    let list = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg0_error)?).map_err(|_| arg0_error())?;
259    let arg1_error = || ExecError::from("(Atom Bindings) pair is expected as a second argument");
260    let atom_bindings = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg1_error)?).map_err(|_| arg1_error())?;
261    let atom = atom_bindings.children().get(0).ok_or_else(arg1_error)?;
262    let bindings = atom_bindings.children().get(1).and_then(|a| a.as_gnd::<Bindings>()).ok_or_else(arg1_error)?;
263
264    let atom = apply_bindings_to_atom_move(atom.clone(), bindings);
265    let mut list = list.clone();
266    list.children_mut().push(atom);
267    Ok(vec![Atom::Expression(list)])
268}
269
270#[derive(Clone, Debug)]
271pub struct MinimalFoldlAtomOp { }
272
273grounded_op!(MinimalFoldlAtomOp, "_minimal-foldl-atom");
274
275impl Grounded for MinimalFoldlAtomOp {
276    fn type_(&self) -> Atom {
277        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM, ATOM_TYPE_VARIABLE, ATOM_TYPE_VARIABLE, ATOM_TYPE_ATOM, ATOM_TYPE_SPACE, ATOM_TYPE_UNDEFINED])
278    }
279
280    fn as_execute(&self) -> Option<&dyn CustomExecute> {
281        Some(self)
282    }
283}
284
285impl CustomExecute for MinimalFoldlAtomOp {
286    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
287        let arg0_error = || ExecError::from("Expression is expected as a first argument");
288        let arg1_error = || ExecError::from("Initial value is expected as a second argument");
289        let arg2_error = || ExecError::from("Variable is expected as a third argument");
290        let arg3_error = || ExecError::from("Variable is expected as a forth argument");
291        let arg4_error = || ExecError::from("Operation expression is expected as a fifth argument");
292        let arg5_error = || ExecError::from("Atomspace is expected as a sixth argument");
293
294        let list = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg0_error)?).map_err(|_| arg0_error())?;
295        let init = args.get(1).ok_or_else(arg1_error)?;
296        let a = TryInto::<&VariableAtom>::try_into(args.get(2).ok_or_else(arg2_error)?).map_err(|_| arg2_error())?;
297        let b = TryInto::<&VariableAtom>::try_into(args.get(3).ok_or_else(arg3_error)?).map_err(|_| arg3_error())?;
298        let op = args.get(4).ok_or_else(arg4_error)?;
299        let space = args.get(5).ok_or_else(arg5_error)?;
300
301
302        let mut children: Vec<Atom> = list.children().into();
303        if children.is_empty() {
304            Ok(vec![Atom::expr([RETURN_SYMBOL, init.clone()])])
305        } else {
306            let head = children.remove(0);
307            let tail = Atom::expr(children);
308            let mut var_mapper = CachingMapper::new(|var: &VariableAtom| -> Atom {
309                if var == a {
310                    init.clone()
311                } else if var == b {
312                    head.clone() 
313                } else {
314                    // FIXME: do we need clone here?
315                    Atom::Variable(var.clone().make_unique())
316                }
317            });
318            let mut step_op = op.clone();
319            step_op.iter_mut().for_each(|atom: &mut Atom| {
320                match atom {
321                    Atom::Variable(var) => *atom = var_mapper.replace(var),
322                    _ => {},
323                }
324            });
325
326            let x = Atom::Variable(VariableAtom::new("x").make_unique());
327            let next = Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, step_op.clone(), ATOM_TYPE_UNDEFINED, space.clone()]), x.clone(),
328                    Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::gnd(self.clone()), tail, x, Atom::Variable(a.clone()), Atom::Variable(b.clone()), op.clone(), space.clone()])]),
329                ]);
330            log::debug!("MinimalFoldlAtomOp:execute({:?}) -> {}", args, next);
331            Ok(vec![next])
332        }
333    }
334}
335
336pub(super) fn register_context_independent_tokens(tref: &mut Tokenizer) {
337    let is_equivalent = Atom::gnd(IfEqualOp{});
338    tref.register_token(regex(r"if-equal"), move |_| { is_equivalent.clone() });
339    let nop_op = Atom::gnd(NopOp{});
340    tref.register_token(regex(r"nop"), move |_| { nop_op.clone() });
341    let match_op = Atom::gnd(MatchOp{});
342    tref.register_token(regex(r"match"), move |_| { match_op.clone() });
343    let sealed_op = Atom::gnd(SealedOp{});
344    tref.register_token(regex(r"sealed"), move |_| { sealed_op.clone() });
345    let eq_op = Atom::gnd(EqualOp{});
346    tref.register_token(regex(r"=="), move |_| { eq_op.clone() });
347    tref.register_function(GroundedFunctionAtom::new(
348        r"_collapse-add-next-atom-from-collapse-bind-result".into(),
349        expr!("->" "Expression" "Expression" "Atom"),
350        collapse_add_next_atom_from_collapse_bind_result,
351    ));
352    let superpose_op = Atom::gnd(SuperposeOp{});
353    tref.register_token(regex(r"superpose"), move |_| { superpose_op.clone() });
354}
355
356pub(super) fn register_context_dependent_tokens(tref: &mut Tokenizer, space: &DynSpace, metta: &Metta) {
357    let capture_op = Atom::gnd(CaptureOp::new(space.clone(), metta.settings().clone()));
358    tref.register_token(regex(r"capture"), move |_| { capture_op.clone() });
359    let pragma_op = Atom::gnd(PragmaOp::new(metta.settings().clone()));
360    tref.register_token(regex(r"pragma!"), move |_| { pragma_op.clone() });
361    let foldl_atom_op = Atom::gnd(MinimalFoldlAtomOp{});
362    tref.register_token(regex(r"_minimal-foldl-atom"), move |_| { foldl_atom_op.clone() });
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368    use crate::metta::runner::run_program;
369    use hyperon_atom::matcher::atoms_are_equivalent;
370    use crate::space::grounding::metta_space;
371    use hyperon_atom::gnd::number::Number;
372    use hyperon_common::{assert_eq_no_order, assert_eq_metta_results};
373
374    use std::convert::TryFrom;
375
376    #[test]
377    fn metta_superpose() {
378        assert_eq_metta_results!(run_program("!(superpose (red yellow green))"),
379            Ok(vec![vec![expr!("red"), expr!("yellow"), expr!("green")]]));
380        let program = "
381            (= (foo) FOO)
382            (= (bar) BAR)
383            !(superpose ((foo) (bar) BAZ))
384        ";
385        assert_eq_metta_results!(run_program(program),
386            Ok(vec![vec![expr!("FOO"), expr!("BAR"), expr!("BAZ")]]));
387    }
388
389    #[test]
390    fn metta_collapse() {
391        let program = "
392            (= (color) red)
393            (= (color) green)
394            (= (color) blue)
395            !(collapse (color))
396        ";
397        let result = run_program(program).expect("Successful result is expected");
398        assert_eq!(result.len(), 1);
399        let result = result.get(0).unwrap();
400        assert_eq!(result.len(), 1);
401        let result = result.get(0).unwrap();
402        let actual = <&ExpressionAtom>::try_from(result)
403            .expect("Expression atom is expected").children();
404        assert_eq_no_order!(actual, vec![expr!("red"), expr!("green"), expr!("blue")]);
405    }
406
407    #[test]
408    fn metta_case_empty() {
409        let result = run_program("!(case Empty ( (ok ok) (Empty nok) ))");
410        assert_eq!(result, Ok(vec![vec![expr!("nok")]]));
411        let result = run_program("!(case (unify (C B) (C B) ok Empty) ( (ok ok) (Empty nok) ))");
412        assert_eq!(result, Ok(vec![vec![expr!("ok")]]));
413        let result = run_program("!(case (unify (B C) (C B) ok nok) ( (ok ok) (nok nok) ))");
414        assert_eq!(result, Ok(vec![vec![expr!("nok")]]));
415        let result = run_program("!(case (unify (B C) (C B) ok Empty) ( (ok ok) (Empty nok) ))");
416        assert_eq!(result, Ok(vec![vec![expr!("nok")]]));
417    }
418
419    #[test]
420    fn metta_case_error() {
421        let program = "
422            (= (err) (Error (err) \"Test error\"))
423            !(case (err) (
424              ((Error $a \"Test error\") ())
425              ($_ (Error $_ \"Error is expected\"))
426              ))
427        ";
428        assert_eq!(run_program(program), Ok(vec![vec![UNIT_ATOM]]));
429    }
430
431    #[test]
432    fn test_pragma_interpreter_bare_minimal() {
433        let program = "
434            (= (bar) baz)
435            (= (foo) (bar))
436            !(foo)
437            !(pragma! interpreter bare-minimal)
438            !(foo)
439            !(eval (foo))
440        ";
441
442        assert_eq_metta_results!(run_program(program),
443            Ok(vec![
444                vec![expr!("baz")],
445                vec![UNIT_ATOM],
446                vec![expr!(("foo"))],
447                vec![expr!(("bar"))],
448            ]));
449    }
450
451    #[test]
452    fn test_pragma_max_stack_depth() {
453        let program = "!(assertEqual (pragma! max-stack-depth -12) (Error (pragma! max-stack-depth -12) UnsignedIntegerIsExpected))";
454        assert_eq_metta_results!(run_program(program), Ok(vec![ vec![expr!()] ]));
455
456        let program = "
457            !(pragma! max-stack-depth 21)
458            !(pragma! max-stack-depth 0)
459            (= (fac $n) (if (== $n 0) 1 (* $n (fac (- $n 1)))))
460            !(fac 6)
461        ";
462        assert_eq_metta_results!(run_program(program),
463            Ok(vec![
464                vec![UNIT_ATOM],
465                vec![UNIT_ATOM],
466                vec![expr!({Number::Integer(720)})],
467            ]));
468
469        let program = "
470            (= (fac $n) (if (== $n 0) 1 (* $n (fac (- $n 1)))))
471            !(pragma! max-stack-depth 200)
472            !(fac 3)
473            !(case (fac 6) (
474               ((Error $a StackOverflow) ())
475               ($_ (Error (fac 6) \"StackOverflow error is expected\")) ))
476        ";
477        assert_eq_metta_results!(run_program(program),
478            Ok(vec![
479                vec![UNIT_ATOM],
480                vec![Atom::gnd(Number::Integer(6))],
481                vec![UNIT_ATOM],
482            ]));
483    }
484
485    #[test]
486    fn use_sealed_to_make_scoped_variable() {
487        assert_eq!(run_program("!(let $x (input $x) (output $x))"), Ok(vec![vec![]]));
488        assert_eq!(run_program("!(let $x (input $y) (output $x))"), Ok(vec![vec![expr!("output" ("input" y))]]));
489        assert_eq!(run_program("!(let (quote ($sv $st)) (sealed () (quote ($x (output $x))))
490               (let $sv (input $x) $st))"), Ok(vec![vec![expr!("output" ("input" x))]]));
491    }
492
493    #[test]
494    fn let_op_variables_visibility_pr262() {
495        let program = "
496            ;; Knowledge
497            (→ P Q)
498            (→ Q R)
499
500            ;; Rule
501            (= (rule (→ $p $q) (→ $q $r)) (→ $p $r))
502
503            ;; Query (does not work as expected)
504            (= (query $kb)
505               (let* (($pq (→ $p $q))
506                      ($qr (→ $q $r)))
507                 (match $kb
508                   ;; Premises
509                   (, $pq $qr)
510                   ;; Conclusion
511                   (rule $pq $qr))))
512
513            ;; Call
514            !(query &self)
515            ;; [(→ P R)]
516        ";
517        assert_eq_metta_results!(run_program(program), Ok(vec![vec![expr!("→" "P" "R")]]));
518    }
519
520    #[test]
521    fn sealed_op_runner() {
522        let nested = run_program("!(sealed ($c) (quote (= ($a $x $c) ($b))))");
523        let simple_replace = run_program("!(sealed ($z $x) (quote (= ($y $z))))");
524
525        assert!(atoms_are_equivalent(&nested.unwrap()[0][0], &expr!("quote" ("=" (a b c) (z)))));
526        assert!(atoms_are_equivalent(&simple_replace.unwrap()[0][0], &expr!("quote" ("=" (y z)))));
527    }
528
529    #[test]
530    fn match_op() {
531        let space = metta_space("(A B)");
532        let match_op = MatchOp{};
533        let actual = match_op.execute_bindings(&mut vec![expr!({space}), expr!("A" "B"), expr!("B" "A")]);
534        assert!(actual.is_ok());
535        assert_eq!(actual.unwrap().collect::<Vec<(Atom, Option<Bindings>)>>(), vec![(expr!("B" "A"), Some(Bindings::new()))]);
536    }
537
538    #[test]
539    fn match_op_issue_530() {
540        let space = metta_space("(A $a $a)");
541        let match_op = MatchOp{};
542        let actual = match_op.execute_bindings(&mut vec![expr!({space}), expr!("A" x y), expr!("A" x y)]);
543        assert!(actual.is_ok());
544        assert_eq!(actual.unwrap().collect::<Vec<(Atom, Option<Bindings>)>>(), vec![(expr!("A" x y), Some(bind!{ x: expr!(y) }))]);
545    }
546
547    #[test]
548    fn nop_op() {
549        assert_eq!(NopOp{}.execute(&mut vec![]), unit_result());
550    }
551
552    #[test]
553    fn sealed_op_execute() {
554        let val = SealedOp{}.execute(&mut vec![expr!(x z), expr!("="(y z))]);
555        assert!(atoms_are_equivalent(&val.unwrap()[0], &expr!("="(y z))));
556    }
557}