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 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}