From 923d1b047f71e5f5a533f4e5546e9767e730fe60 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 15 Sep 2023 13:35:33 +0300 Subject: [PATCH 01/23] Add grounded implementation of CaseOp which interprets first argument --- lib/src/metta/mod.rs | 1 + lib/src/metta/runner/stdlib.metta | 3 -- lib/src/metta/runner/stdlib2.rs | 81 ++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index 7db8df5a4..6e3115452 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -40,6 +40,7 @@ pub const CHAIN_SYMBOL : Atom = sym!("chain"); pub const UNIFY_SYMBOL : Atom = sym!("unify"); pub const DECONS_SYMBOL : Atom = sym!("decons"); pub const CONS_SYMBOL : Atom = sym!("cons"); +pub const INTERPRET_SYMBOL : Atom = sym!("interpret"); /// Initializes an error expression atom pub fn error_atom(err_atom: Option, err_code: Option, message: String) -> Atom { diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 4e770b98b..4241e751f 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -184,9 +184,6 @@ (let $pattern $atom (let* $tail $template)) $template ))) -(: case (-> %Undefined% Expression Atom)) -(= (case $atom $cases) (switch $atom $cases)) - (: car (-> Expression Atom)) (= (car $atom) (eval (if-decons $atom $head $_ diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 53db483c7..4ac12f7b8 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -6,7 +6,6 @@ use crate::metta::text::Tokenizer; use crate::metta::runner::Metta; use crate::metta::types::{get_atom_types, get_meta_type}; use crate::common::assert::vec_eq_no_order; -use crate::metta::runner::interpret; use crate::common::shared::Shared; use std::fmt::Display; @@ -122,7 +121,6 @@ impl Grounded for IfEqualOp { // TODO: remove hiding errors completely after making it possible passing // them to the user fn interpret_no_error(space: DynSpace, expr: &Atom) -> Result, String> { - let expr = Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("interpret"), expr.clone(), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())])]); let result = interpret(space, &expr); log::debug!("interpret_no_error: interpretation expr: {}, result {:?}", expr, result); match result { @@ -131,6 +129,11 @@ fn interpret_no_error(space: DynSpace, expr: &Atom) -> Result, String> } } +fn interpret(space: DynSpace, expr: &Atom) -> Result, String> { + let expr = Atom::expr([EVAL_SYMBOL, Atom::expr([INTERPRET_SYMBOL, expr.clone(), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())])]); + crate::metta::interpreter2::interpret(space, &expr) +} + fn assert_results_equal(actual: &Vec, expected: &Vec, atom: &Atom) -> Result, ExecError> { log::debug!("assert_results_equal: actual: {:?}, expected: {:?}, actual atom: {:?}", actual, expected, atom); let report = format!("\nExpected: {:?}\nGot: {:?}", expected, actual); @@ -339,6 +342,66 @@ impl Grounded for PragmaOp { } } +#[derive(Clone, PartialEq, Debug)] +pub struct CaseOp { + space: DynSpace, +} + +impl CaseOp { + pub fn new(space: DynSpace) -> Self { + Self{ space } + } +} + +impl Display for CaseOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "case") + } +} + +impl Grounded for CaseOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM]) + } + + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("case expects two arguments: atom and expression of cases"); + let cases = args.get(1).ok_or_else(arg_error)?; + let atom = args.get(0).ok_or_else(arg_error)?; + log::debug!("CaseOp::execute: atom: {}, cases: {:?}", atom, cases); + + let switch = |interpreted: Atom| -> Atom { + Atom::expr([sym!("switch"), interpreted, cases.clone()]) + }; + + // Interpreting argument inside CaseOp is required because otherwise `Empty` result + // calculated inside interpreter cuts the whole branch of the interpretation. Also we + // cannot use `unify` in a unit test because after interpreting `(chain... (chain (eval + // (interpret (unify ...) Atom ))) ...)` `chain` executes `unify` and also gets + // `Empty` even if we have `Atom` as a resulting type. It can be solved by different ways. + // One way is to invent new type `EmptyType` (type of the `Empty` atom) and use this type + // in a function to allow `Empty` atoms as an input. `EmptyType` type should not be + // casted to the `%Undefined%` thus one cannot pass `Empty` to the function which accepts + // `%Undefined%`. Another way is to introduce "call" level. Thus if function called + // returned the result to the `chain` it should stop reducing it and insert it into the + // last argument. + let results = interpret(self.space.clone(), atom); + log::debug!("CaseOp::execute: atom results: {:?}", results); + let results = match results { + Ok(results) if results.is_empty() => + vec![switch(EMPTY_SYMBOL)], + Ok(results) => + results.into_iter().map(|atom| switch(atom)).collect(), + Err(err) => vec![Atom::expr([ERROR_SYMBOL, atom.clone(), Atom::sym(err)])], + }; + Ok(results) + } + + fn match_(&self, other: &Atom) -> MatchResultIter { + match_by_equality(self, other) + } +} + fn regex(regex: &str) -> Regex { Regex::new(regex).unwrap() } @@ -371,6 +434,8 @@ pub fn register_runner_tokens(metta: &Metta) { tref.register_token(regex(r"collapse"), move |_| { collapse_op.clone() }); let pragma_op = Atom::gnd(PragmaOp::new(metta.settings().clone())); tref.register_token(regex(r"pragma!"), move |_| { pragma_op.clone() }); + let case_op = Atom::gnd(CaseOp::new(space.clone())); + tref.register_token(regex(r"case"), move |_| { case_op.clone() }); // &self should be updated // TODO: adding &self might be done not by stdlib, but by MeTTa itself. // TODO: adding &self introduces self referencing and thus prevents space @@ -475,6 +540,18 @@ mod tests { assert_eq!(result, Ok(vec![vec![]])); } + #[test] + fn metta_case_empty() { + let result = run_program("!(case Empty ( (ok ok) (Empty nok) ))"); + assert_eq!(result, Ok(vec![vec![expr!("nok")]])); + let result = run_program("!(case (unify (C B) (C B) ok Empty) ( (ok ok) (Empty nok) ))"); + assert_eq!(result, Ok(vec![vec![expr!("ok")]])); + let result = run_program("!(case (unify (B C) (C B) ok nok) ( (ok ok) (nok nok) ))"); + assert_eq!(result, Ok(vec![vec![expr!("nok")]])); + let result = run_program("!(case (match (B C) (C B) ok) ( (ok ok) (Empty nok) ))"); + assert_eq!(result, Ok(vec![vec![expr!("nok")]])); + } + #[test] fn metta_is_function() { let result = run_program("!(eval (is-function (-> $t)))"); From 9f4991ae3f4b4ca38924e38aa211ed284337f030 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Mon, 18 Sep 2023 10:58:39 +0300 Subject: [PATCH 02/23] Add quote/unquote implementation into minimal MeTTa stdlib --- lib/src/metta/runner/stdlib.metta | 6 ++++++ lib/src/metta/runner/stdlib2.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 4241e751f..83bb82747 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -195,3 +195,9 @@ (eval (if-decons $atom $_ $tail $tail (Error (cdr $atom) "cdr expects a non-empty expression as an argument") ))) + +(: quote (-> Atom Atom)) +(= (quote $atom) NotReducible) + +(: unquote (-> (Atom %Undefined%))) +(= (unquote (quote $atom)) $atom) diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 4ac12f7b8..d063f9d24 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -798,6 +798,18 @@ mod tests { assert_eq!(result, Ok(vec![vec![]])); } + #[test] + fn metta_quote_unquote() { + let header = " + (= (foo) A) + (= (bar $x) $x) + "; + assert_eq!(run_program(&format!("{header} !(bar (foo))")), Ok(vec![vec![sym!("A")]]), "sanity check"); + assert_eq!(run_program(&format!("{header} !(bar (quote (foo)))")), Ok(vec![vec![expr!("quote" ("foo"))]]), "quote"); + assert_eq!(run_program(&format!("{header} !(bar (unquote (quote (foo))))")), Ok(vec![vec![expr!("A")]]), "unquote before call"); + assert_eq!(run_program(&format!("{header} !(unquote (bar (quote (foo))))")), Ok(vec![vec![expr!("A")]]), "unquote after call"); + } + #[test] fn test_frog_reasoning() { From 0b1461e6c064d513e390dd8ff64f56c5ba4b588d Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 20 Sep 2023 18:55:22 +0300 Subject: [PATCH 03/23] Remove reduce and subst as they are not used in MeTTa implementation --- lib/src/metta/runner/stdlib.metta | 18 ------------------ lib/src/metta/runner/stdlib2.rs | 26 -------------------------- 2 files changed, 44 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 83bb82747..38f2f332f 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -48,24 +48,6 @@ (= (switch-internal $atom (($pattern $template) $tail)) (unify $atom $pattern $template (eval (switch $atom $tail)))) -; FIXME: subst and reduce are not used in interpreter implementation -; we could remove them - -(: subst (-> Atom Variable Atom Atom)) -(= (subst $atom $var $templ) - (unify $atom $var $templ - (Error (subst $atom $var $templ) - "subst expects a variable as a second argument") )) - -(: reduce (-> Atom Variable Atom Atom)) -(= (reduce $atom $var $templ) - (chain (eval $atom) $res - (eval (if-empty $res Empty - (eval (if-error $res $res - (eval (if-not-reducible $res - (eval (subst $atom $var $templ)) - (eval (reduce $res $var $templ)) )))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; MeTTa interpreter implementation ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index d063f9d24..0bad21509 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -562,32 +562,6 @@ mod tests { assert_eq!(result, Ok(vec![vec![expr!({Bool(false)})]])); } - #[test] - fn metta_reduce_chain() { - assert_eq!(run_program(" - (= (foo $x) (bar $x)) - (= (bar $x) (baz $x)) - (= (baz $x) $x) - !(eval (reduce (foo A) $x (reduced $x))) - "), Ok(vec![vec![expr!("reduced" "A")]])); - assert_eq!(run_program(" - !(eval (reduce (foo A) $x (reduced $x))) - "), Ok(vec![vec![expr!("reduced" ("foo" "A"))]])); - assert_eq!(run_program(" - (= (foo A) (Error (foo A) \"Test error\")) - !(eval (reduce (foo A) $x (reduced $x))) - "), Ok(vec![vec![expr!("Error" ("foo" "A") "\"Test error\"")]])); - } - - #[test] - fn metta_reduce_reduce() { - assert_eq!(run_program(" - (= (foo $x) (reduce (bar $x) $t $t)) - (= (bar $x) $x) - !(eval (reduce (foo A) $x $x)) - "), Ok(vec![vec![expr!("A")]])); - } - #[test] fn metta_type_cast() { assert_eq!(run_program("(: a A) !(eval (type-cast a A &self))"), Ok(vec![vec![expr!("a")]])); From 74f4d15cd2ad7aaca5918234483d29584165617e Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 20 Sep 2023 19:03:41 +0300 Subject: [PATCH 04/23] Move if implementation into MeTTa library section --- lib/src/metta/runner/stdlib.metta | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 38f2f332f..626bad9bc 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -1,9 +1,4 @@ -;`$then`, `$else` should be of `Atom` type to avoid evaluation -; and infinite cycle in inference -(: if (-> Bool Atom Atom $t)) -(= (if True $then $else) $then) -(= (if False $then $else) $else) - +(: ErrorType Type) (: Error (-> Atom Atom ErrorType)) (: if-non-empty-expression (-> Atom Atom Atom Atom)) @@ -152,6 +147,12 @@ ; Standard library written in MeTTa ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;`$then`, `$else` should be of `Atom` type to avoid evaluation +; and infinite cycle in inference +(: if (-> Bool Atom Atom $t)) +(= (if True $then $else) $then) +(= (if False $then $else) $else) + (: match (-> Atom Atom Atom %Undefined%)) (= (match $space $pattern $template) (unify $pattern $space $template Empty)) From 290f7e5b07c49859a020ef19f5ba8abf228d3e0b Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 20 Sep 2023 19:08:55 +0300 Subject: [PATCH 05/23] Make chain one step operation, add chain+ to loop in chain Add chain-eval function which interprets the expression until (return $atom) is returned. This allows us prevent the result of the function from further interpreting. --- lib/src/metta/interpreter2.rs | 68 ++++++++++++++++++++---- lib/src/metta/mod.rs | 2 + lib/src/metta/runner/stdlib.metta | 86 ++++++++++++++++++++----------- lib/src/metta/runner/stdlib2.rs | 2 +- 4 files changed, 118 insertions(+), 40 deletions(-) diff --git a/lib/src/metta/interpreter2.rs b/lib/src/metta/interpreter2.rs index 252605e3b..c444291e8 100644 --- a/lib/src/metta/interpreter2.rs +++ b/lib/src/metta/interpreter2.rs @@ -171,6 +171,7 @@ fn is_embedded_op(atom: &Atom) -> bool { match expr { Some([op, ..]) => *op == EVAL_SYMBOL || *op == CHAIN_SYMBOL + || *op == CHAIN_PLUS_SYMBOL || *op == UNIFY_SYMBOL || *op == CONS_SYMBOL || *op == DECONS_SYMBOL, @@ -218,6 +219,21 @@ fn interpret_atom_root<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: Interpre }, } }, + Some([op, args @ ..]) if *op == CHAIN_PLUS_SYMBOL => { + match args { + [_nested, Atom::Variable(_var), _templ] => { + match atom_into_array(atom) { + Some([_, nested, Atom::Variable(var), templ]) => + chain_plus(space, bindings, nested, var, templ), + _ => panic!("Unexpected state"), + } + }, + _ => { + let error: String = format!("expected: ({} (: Variable) ), found: {}", CHAIN_PLUS_SYMBOL, atom); + vec![InterpretedAtom(error_atom(atom, error), bindings)] + }, + } + }, Some([op, args @ ..]) if *op == UNIFY_SYMBOL => { match args { [atom, pattern, then, else_] => match_(bindings, atom, pattern, then, else_), @@ -339,15 +355,32 @@ fn query<'a, T: SpaceRef<'a>>(space: T, atom: Atom, bindings: Bindings) -> Vec>(space: T, bindings: Bindings, nested: Atom, var: VariableAtom, templ: Atom) -> Vec { + if is_embedded_op(&nested) { + let result = interpret_atom_root(space, InterpretedAtom(nested, bindings), false); + result.into_iter() + .map(|InterpretedAtom(r, b)| { + let vb = Bindings::new().add_var_binding_v2(var.clone(), r).unwrap(); + let result = apply_bindings_to_atom(&templ, &vb); + InterpretedAtom(result, b) + }) + .collect() + } else { + let b = Bindings::new().add_var_binding_v2(var, nested).unwrap(); + let result = apply_bindings_to_atom(&templ, &b); + vec![InterpretedAtom(result, bindings)] + } +} + +fn chain_plus<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, nested: Atom, var: VariableAtom, templ: Atom) -> Vec { if is_embedded_op(&nested) { let mut result = interpret_atom_root(space, InterpretedAtom(nested, bindings), false); if result.len() == 1 { let InterpretedAtom(r, b) = result.pop().unwrap(); - vec![InterpretedAtom(Atom::expr([CHAIN_SYMBOL, r, Atom::Variable(var), templ]), b)] + vec![InterpretedAtom(Atom::expr([CHAIN_PLUS_SYMBOL, r, Atom::Variable(var), templ]), b)] } else { result.into_iter() .map(|InterpretedAtom(r, b)| { - InterpretedAtom(Atom::expr([CHAIN_SYMBOL, r, Atom::Variable(var.clone()), templ.clone()]), b) + InterpretedAtom(Atom::expr([CHAIN_PLUS_SYMBOL, r, Atom::Variable(var.clone()), templ.clone()]), b) }) .collect() } @@ -518,20 +551,20 @@ mod tests { fn interpret_atom_chain_evaluation() { let space = space("(= (foo $a B) $a)"); let result = interpret_atom(&space, atom("(chain (eval (foo A $b)) $x (bar $x))", bind!{})); - assert_eq!(result, vec![atom("(chain A $x (bar $x))", bind!{})]); + assert_eq!(result, vec![atom("(bar A)", bind!{})]); } #[test] fn interpret_atom_chain_nested_evaluation() { let space = space("(= (foo $a B) $a)"); let result = interpret_atom(&space, atom("(chain (chain (eval (foo A $b)) $x (bar $x)) $y (baz $y))", bind!{})); - assert_eq!(result, vec![atom("(chain (chain A $x (bar $x)) $y (baz $y))", bind!{})]); + assert_eq!(result, vec![atom("(baz (bar A))", bind!{})]); } #[test] fn interpret_atom_chain_nested_value() { let result = interpret_atom(&space(""), atom("(chain (chain A $x (bar $x)) $y (baz $y))", bind!{})); - assert_eq!(result, vec![atom("(chain (bar A) $y (baz $y))", bind!{})]); + assert_eq!(result, vec![atom("(baz (bar A))", bind!{})]); } #[test] @@ -543,9 +576,9 @@ mod tests { "); let result = interpret_atom(&space, atom("(chain (eval (color)) $x (bar $x))", bind!{})); assert_eq_no_order!(result, vec![ - atom("(chain red $x (bar $x))", bind!{}), - atom("(chain green $x (bar $x))", bind!{}), - atom("(chain blue $x (bar $x))", bind!{}) + atom("(bar red)", bind!{}), + atom("(bar green)", bind!{}), + atom("(bar blue)", bind!{}) ]); } @@ -637,14 +670,29 @@ mod tests { #[test] fn metta_turing_machine() { let space = space(" + (= (if-embedded-op $atom $then $else) + (chain (decons $atom) $list + (unify $list (cons $_) $then + (unify $list (decons $_) $then + (unify $list (chain $_) $then + (unify $list (eval $_) $then + (unify $list (unify $_) $then + $else ))))))) + + (= (chain-loop $atom $var $templ) + (chain $atom $x + (eval (if-embedded-op $x + (eval (chain-loop $x $var $templ)) + (chain $x $var $templ) )))) + (= (tm $rule $state $tape) (unify $state HALT $tape (chain (eval (read $tape)) $char (chain (eval ($rule $state $char)) $res (unify $res ($next-state $next-char $dir) - (chain (eval (move $tape $next-char $dir)) $next-tape - (eval (tm $rule $next-state $next-tape)) ) + (eval (chain-loop (eval (move $tape $next-char $dir)) $next-tape + (eval (tm $rule $next-state $next-tape)) )) (Error (tm $rule $state $tape) \"Incorrect state\") ))))) (= (read ($head $hole $tail)) $hole) diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index 6e3115452..8d992bbf0 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -40,6 +40,8 @@ pub const CHAIN_SYMBOL : Atom = sym!("chain"); pub const UNIFY_SYMBOL : Atom = sym!("unify"); pub const DECONS_SYMBOL : Atom = sym!("decons"); pub const CONS_SYMBOL : Atom = sym!("cons"); +pub const CHAIN_PLUS_SYMBOL : Atom = sym!("chain+"); + pub const INTERPRET_SYMBOL : Atom = sym!("interpret"); /// Initializes an error expression atom diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 626bad9bc..eaf80a6b7 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -1,6 +1,31 @@ (: ErrorType Type) (: Error (-> Atom Atom ErrorType)) +(: eval (-> Atom Atom)) +(: chain (-> Atom Variable Atom Atom)) +(: chain+ (-> Atom Variable Atom Atom)) +(: unify (-> Atom Atom Atom Atom Atom)) +(: cons (-> Atom Atom Atom)) +(: decons (-> Atom Atom)) + +(: ReturnType Type) +(: return (-> Atom ReturnType)) + +(: if-return (-> Atom Atom Atom Atom)) +(= (if-return $atom $then $else) + (eval (if-decons $atom $head $_ + (eval (if-equal $head return $then $else)) + $else ))) + +(= (chain-eval $expr $var $templ) + (chain+ (eval $expr) $res + (eval (if-return $res + (unify $res (return $ret) + (unify $var $ret $templ + (Error (chain-eval $expr $var $templ) "$ret cannot be matched with $var")) + (Error (chain-eval $expr $var $templ) "(return $ret) is expected") ) + (chain $res $var $templ) )))) + (: if-non-empty-expression (-> Atom Atom Atom Atom)) (= (if-non-empty-expression $atom $then $else) (chain (eval (get-metatype $atom)) $type @@ -38,8 +63,9 @@ (: switch (-> %Undefined% Expression Atom)) (= (switch $atom $cases) (chain (decons $cases) $list - (chain (eval (switch-internal $atom $list)) $res - (eval (if-not-reducible $res Empty $res)) ))) + (eval (chain-eval (switch-internal $atom $list) $res + (eval (if-not-reducible $res Empty $res)) )))) + (= (switch-internal $atom (($pattern $template) $tail)) (unify $atom $pattern $template (eval (switch $atom $tail)))) @@ -55,18 +81,20 @@ (unify $type1 $type2 $then $else) ))))))))) (= (type-cast $atom $type $space) - (chain (eval (get-type $atom $space)) $actual-type - (chain (eval (get-metatype $atom)) $meta - (eval (if-equal $type $meta $atom - (eval (match-types $actual-type $type $atom (Error $atom BadType))) ))))) + (chain (eval (get-metatype $atom)) $meta + (eval (if-equal $type $meta $atom + (chain (eval (get-type $atom $space)) $actual-type + (eval (match-types $actual-type $type + $atom + (Error $atom BadType) ))))))) (= (is-function $type) (chain (eval (get-metatype $type)) $meta (eval (switch ($type $meta) ( (($_ Expression) - (chain (eval (car $type)) $head - (unify $head -> True False) )) + (eval (chain-eval (car $type) $head + (unify $head -> True False) ))) ($_ False) ))))) (= (interpret $atom $type $space) @@ -83,65 +111,65 @@ (= (interpret-expression $atom $type $space) (eval (if-decons $atom $op $args (chain (eval (get-type $op $space)) $op-type - (chain (eval (is-function $op-type)) $is-func + (eval (chain-eval (is-function $op-type) $is-func (unify $is-func True - (chain (eval (interpret-func $atom $op-type $type $space)) $reduced-atom - (eval (call $reduced-atom $type $space)) ) - (chain (eval (interpret-tuple $atom $space)) $reduced-atom - (eval (call $reduced-atom $type $space)) )))) + (eval (chain-eval (interpret-func $atom $op-type $type $space) $reduced-atom + (eval (metta-call $reduced-atom $type $space)) )) + (eval (chain-eval (interpret-tuple $atom $space) $reduced-atom + (eval (metta-call $reduced-atom $type $space)) )))))) (eval (type-cast $atom $type $space)) ))) (= (interpret-func $expr $type $ret-type $space) (eval (if-decons $expr $op $args - (chain (eval (interpret $op $type $space)) $reduced-op + (eval (chain-eval (interpret $op $type $space) $reduced-op (eval (return-on-error $reduced-op (eval (if-decons $type $arrow $arg-types - (chain (eval (interpret-args $expr $args $arg-types $ret-type $space)) $reduced-args + (eval (chain-eval (interpret-args $expr $args $arg-types $ret-type $space) $reduced-args (eval (return-on-error $reduced-args - (cons $reduced-op $reduced-args) ))) - (Error $type "Function type expected") ))))) + (chain (cons $reduced-op $reduced-args) $r (return $r)))))) + (Error $type "Function type expected") )))))) (Error $expr "Non-empty expression atom is expected") ))) (= (interpret-args $atom $args $arg-types $ret-type $space) (unify $args () - (chain (eval (car $arg-types)) $actual-ret-type - (eval (match-types $actual-ret-type $ret-type () (Error $atom BadType)))) + (eval (chain-eval (car $arg-types) $actual-ret-type + (eval (match-types $actual-ret-type $ret-type () (Error $atom BadType))))) (eval (if-decons $args $head $tail (eval (if-decons $arg-types $head-type $tail-types - (chain (eval (interpret $head $head-type $space)) $reduced-head + (eval (chain-eval (interpret $head $head-type $space) $reduced-head ; check that head was changed otherwise Error or Empty in the head ; can be just an argument which is passed by intention (eval (if-equal $reduced-head $head (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) (eval (return-on-error $reduced-head - (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) ))))) + (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) )))))) (Error $atom BadType) )) (Error (interpret-atom $atom $args $arg-types $space) "Non-empty expression atom is expected") )))) (= (interpret-args-tail $atom $head $args-tail $args-tail-types $ret-type $space) - (chain (eval (interpret-args $atom $args-tail $args-tail-types $ret-type $space)) $reduced-tail + (eval (chain-eval (interpret-args $atom $args-tail $args-tail-types $ret-type $space) $reduced-tail (eval (return-on-error $reduced-tail - (cons $head $reduced-tail) )))) + (cons $head $reduced-tail) ))))) (= (interpret-tuple $atom $space) (unify $atom () $atom (eval (if-decons $atom $head $tail - (chain (eval (interpret $head %Undefined% $space)) $rhead + (eval (chain-eval (interpret $head %Undefined% $space) $rhead (eval (if-empty $rhead Empty - (chain (eval (interpret-tuple $tail $space)) $rtail + (eval (chain-eval (interpret-tuple $tail $space) $rtail (eval (if-empty $rtail Empty - (cons $rhead $rtail) )))))) + (cons $rhead $rtail) )))))))) (Error (interpret-tuple $atom $space) "Non-empty expression atom is expected as an argument") )))) -(= (call $atom $type $space) +(= (metta-call $atom $type $space) (eval (if-error $atom $atom - (chain (eval $atom) $result + (eval (chain-eval $atom $result (eval (if-not-reducible $result $atom (eval (if-empty $result Empty (eval (if-error $result $result - (eval (interpret $result $type $space)) )))))))))) + (eval (interpret $result $type $space)) ))))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Standard library written in MeTTa ; diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 0bad21509..8461ccb05 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -743,7 +743,7 @@ mod tests { } #[test] - fn metta_let() { + fn metta_let_novar() { let result = run_program("!(let (P A $b) (P $a B) (P $b $a))"); assert_eq!(result, Ok(vec![vec![expr!("P" "B" "A")]])); let result = run_program(" From 20993abe9940e2094876d14665180fdee47cf5de Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 4 Oct 2023 21:56:49 +0300 Subject: [PATCH 06/23] Implement atoms to designate function calls --- lib/src/metta/interpreter2.rs | 137 ++++++++++++++------ lib/src/metta/mod.rs | 3 +- lib/src/metta/runner/stdlib.metta | 205 +++++++++++++++--------------- 3 files changed, 202 insertions(+), 143 deletions(-) diff --git a/lib/src/metta/interpreter2.rs b/lib/src/metta/interpreter2.rs index c444291e8..5c395d453 100644 --- a/lib/src/metta/interpreter2.rs +++ b/lib/src/metta/interpreter2.rs @@ -171,10 +171,26 @@ fn is_embedded_op(atom: &Atom) -> bool { match expr { Some([op, ..]) => *op == EVAL_SYMBOL || *op == CHAIN_SYMBOL - || *op == CHAIN_PLUS_SYMBOL || *op == UNIFY_SYMBOL || *op == CONS_SYMBOL - || *op == DECONS_SYMBOL, + || *op == DECONS_SYMBOL + || *op == FUNCTION_SYMBOL, + _ => false, + } +} + +fn is_function_op(atom: &Atom) -> bool { + let expr = atom_as_slice(&atom); + match expr { + Some([op, ..]) => *op == FUNCTION_SYMBOL, + _ => false, + } +} + +fn is_eval_op(atom: &Atom) -> bool { + let expr = atom_as_slice(&atom); + match expr { + Some([op, ..]) => *op == EVAL_SYMBOL, _ => false, } } @@ -219,21 +235,6 @@ fn interpret_atom_root<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: Interpre }, } }, - Some([op, args @ ..]) if *op == CHAIN_PLUS_SYMBOL => { - match args { - [_nested, Atom::Variable(_var), _templ] => { - match atom_into_array(atom) { - Some([_, nested, Atom::Variable(var), templ]) => - chain_plus(space, bindings, nested, var, templ), - _ => panic!("Unexpected state"), - } - }, - _ => { - let error: String = format!("expected: ({} (: Variable) ), found: {}", CHAIN_PLUS_SYMBOL, atom); - vec![InterpretedAtom(error_atom(atom, error), bindings)] - }, - } - }, Some([op, args @ ..]) if *op == UNIFY_SYMBOL => { match args { [atom, pattern, then, else_] => match_(bindings, atom, pattern, then, else_), @@ -271,6 +272,21 @@ fn interpret_atom_root<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: Interpre }, } }, + Some([op, args @ ..]) if *op == FUNCTION_SYMBOL => { + match args { + [Atom::Expression(_body)] => { + match atom_into_array(atom) { + Some([_, body]) => + function(space, bindings, body), + _ => panic!("Unexpected state"), + } + }, + _ => { + let error: String = format!("expected: ({} (: Expression)), found: {}", FUNCTION_SYMBOL, atom); + vec![InterpretedAtom(error_atom(atom, error), bindings)] + }, + } + }, _ => { vec![InterpretedAtom(return_atom(atom), bindings)] }, @@ -309,8 +325,8 @@ fn eval<'a, T: SpaceRef<'a>>(space: T, atom: Atom, bindings: Bindings) -> Vec>(space: T, atom: Atom, bindings: Bindings) -> Vec + interpret_atom_root(space, InterpretedAtom(atom, bindings), false), _ => query(space, atom, bindings), } } @@ -355,39 +373,74 @@ fn query<'a, T: SpaceRef<'a>>(space: T, atom: Atom, bindings: Bindings) -> Vec>(space: T, bindings: Bindings, nested: Atom, var: VariableAtom, templ: Atom) -> Vec { - if is_embedded_op(&nested) { + fn apply(bindings: Bindings, nested: Atom, var: VariableAtom, templ: &Atom) -> InterpretedAtom { + let b = Bindings::new().add_var_binding_v2(var, nested).unwrap(); + let result = apply_bindings_to_atom(templ, &b); + InterpretedAtom(result, bindings) + } + + let is_eval = is_eval_op(&nested); + if is_function_op(&nested) { + let mut result = interpret_atom_root(space, InterpretedAtom(nested, bindings), false); + if result.len() == 1 { + let InterpretedAtom(r, b) = result.pop().unwrap(); + if is_function_op(&r) { + vec![InterpretedAtom(Atom::expr([CHAIN_SYMBOL, r, Atom::Variable(var), templ]), b)] + } else { + vec![apply(b, r, var.clone(), &templ)] + } + } else { + result.into_iter() + .map(|InterpretedAtom(r, b)| { + if is_function_op(&r) { + InterpretedAtom(Atom::expr([CHAIN_SYMBOL, r, Atom::Variable(var.clone()), templ.clone()]), b) + } else { + apply(b, r, var.clone(), &templ) + } + }) + .collect() + } + } else if is_embedded_op(&nested) { let result = interpret_atom_root(space, InterpretedAtom(nested, bindings), false); result.into_iter() .map(|InterpretedAtom(r, b)| { - let vb = Bindings::new().add_var_binding_v2(var.clone(), r).unwrap(); - let result = apply_bindings_to_atom(&templ, &vb); - InterpretedAtom(result, b) + if is_eval && is_function_op(&r) { + InterpretedAtom(Atom::expr([CHAIN_SYMBOL, r, Atom::Variable(var.clone()), templ.clone()]), b) + } else { + apply(b, r, var.clone(), &templ) + } }) .collect() } else { - let b = Bindings::new().add_var_binding_v2(var, nested).unwrap(); - let result = apply_bindings_to_atom(&templ, &b); - vec![InterpretedAtom(result, bindings)] + vec![apply(bindings, nested, var, &templ)] } } -fn chain_plus<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, nested: Atom, var: VariableAtom, templ: Atom) -> Vec { - if is_embedded_op(&nested) { - let mut result = interpret_atom_root(space, InterpretedAtom(nested, bindings), false); - if result.len() == 1 { - let InterpretedAtom(r, b) = result.pop().unwrap(); - vec![InterpretedAtom(Atom::expr([CHAIN_PLUS_SYMBOL, r, Atom::Variable(var), templ]), b)] - } else { - result.into_iter() - .map(|InterpretedAtom(r, b)| { - InterpretedAtom(Atom::expr([CHAIN_PLUS_SYMBOL, r, Atom::Variable(var.clone()), templ.clone()]), b) - }) - .collect() +fn function<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, body: Atom) -> Vec { + match atom_as_slice(&body) { + Some([op, _result]) if *op == RETURN_SYMBOL => { + if let Some([_, result]) = atom_into_array(body) { + //log::debug!("function: return {:?}", result); + // FIXME: check return arguments size + vec![InterpretedAtom(result, bindings)] + } else { + panic!("Unexpected state"); + } + }, + _ => { + let mut result = interpret_atom_root(space, InterpretedAtom(body, bindings), false); + //log::debug!("function: result of execution {:?}", result); + if result.len() == 1 { + let InterpretedAtom(r, b) = result.pop().unwrap(); + vec![InterpretedAtom(Atom::expr([FUNCTION_SYMBOL, r]), b)] + } else { + result.into_iter() + .map(|InterpretedAtom(r, b)| { + InterpretedAtom(Atom::expr([FUNCTION_SYMBOL, r]), b) + }) + .collect() + } } - } else { - let b = Bindings::new().add_var_binding_v2(var, nested).unwrap(); - let result = apply_bindings_to_atom(&templ, &b); - vec![InterpretedAtom(result, bindings)] } } diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index 8d992bbf0..da0d903b6 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -40,7 +40,8 @@ pub const CHAIN_SYMBOL : Atom = sym!("chain"); pub const UNIFY_SYMBOL : Atom = sym!("unify"); pub const DECONS_SYMBOL : Atom = sym!("decons"); pub const CONS_SYMBOL : Atom = sym!("cons"); -pub const CHAIN_PLUS_SYMBOL : Atom = sym!("chain+"); +pub const FUNCTION_SYMBOL : Atom = sym!("function"); +pub const RETURN_SYMBOL : Atom = sym!("return"); pub const INTERPRET_SYMBOL : Atom = sym!("interpret"); diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index eaf80a6b7..d9adb92de 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -1,175 +1,180 @@ (: ErrorType Type) (: Error (-> Atom Atom ErrorType)) +(: ReturnType Type) +(: return (-> Atom ReturnType)) +(: function (-> Atom Atom)) (: eval (-> Atom Atom)) (: chain (-> Atom Variable Atom Atom)) -(: chain+ (-> Atom Variable Atom Atom)) (: unify (-> Atom Atom Atom Atom Atom)) (: cons (-> Atom Atom Atom)) (: decons (-> Atom Atom)) -(: ReturnType Type) -(: return (-> Atom ReturnType)) - -(: if-return (-> Atom Atom Atom Atom)) -(= (if-return $atom $then $else) - (eval (if-decons $atom $head $_ - (eval (if-equal $head return $then $else)) - $else ))) - -(= (chain-eval $expr $var $templ) - (chain+ (eval $expr) $res - (eval (if-return $res - (unify $res (return $ret) - (unify $var $ret $templ - (Error (chain-eval $expr $var $templ) "$ret cannot be matched with $var")) - (Error (chain-eval $expr $var $templ) "(return $ret) is expected") ) - (chain $res $var $templ) )))) +(: insert (-> Atom Variable Atom Atom)) +(= (insert $atom $var $templ) + (function (chain (eval (quote $atom)) $x + (chain (eval (unquote $x)) $var + (return $templ) )))) (: if-non-empty-expression (-> Atom Atom Atom Atom)) (= (if-non-empty-expression $atom $then $else) - (chain (eval (get-metatype $atom)) $type + (function (chain (eval (get-metatype $atom)) $type (eval (if-equal $type Expression - (eval (if-equal $atom () $else $then)) - $else )))) + (eval (if-equal $atom () (return $else) (return $then))) + (return $else) ))))) (: if-decons (-> Atom Variable Variable Atom Atom Atom)) (= (if-decons $atom $head $tail $then $else) - (eval (if-non-empty-expression $atom + (function (eval (if-non-empty-expression $atom (chain (decons $atom) $list - (unify $list ($head $tail) $then $else) ) - $else ))) + (unify $list ($head $tail) (return $then) (return $else)) ) + (return $else) )))) (: if-empty (-> Atom Atom Atom Atom)) (= (if-empty $atom $then $else) - (eval (if-equal $atom Empty $then $else))) + (function (eval (if-equal $atom Empty (return $then) (return $else)))) ) (: if-not-reducible (-> Atom Atom Atom Atom)) (= (if-not-reducible $atom $then $else) - (eval (if-equal $atom NotReducible $then $else))) + (function (eval (if-equal $atom NotReducible (return $then) (return $else)))) ) (: if-error (-> Atom Atom Atom Atom)) (= (if-error $atom $then $else) - (eval (if-decons $atom $head $_ - (eval (if-equal $head Error $then $else)) - $else ))) + (function (eval (if-decons $atom $head $_ + (eval (if-equal $head Error (return $then) (return $else))) + (return $else) )))) (: return-on-error (-> Atom Atom Atom)) (= (return-on-error $atom $then) - (eval (if-empty $atom Empty - (eval (if-error $atom $atom - $then ))))) + (function (eval (if-empty $atom Empty + (eval (if-error $atom (return (return $atom)) + (return $then) )))))) (: switch (-> %Undefined% Expression Atom)) (= (switch $atom $cases) - (chain (decons $cases) $list - (eval (chain-eval (switch-internal $atom $list) $res - (eval (if-not-reducible $res Empty $res)) )))) + (function (chain (decons $cases) $list + (chain (eval (switch-internal $atom $list)) $res + (chain (eval (if-not-reducible $res Empty $res)) $x (return $x)) )))) (= (switch-internal $atom (($pattern $template) $tail)) - (unify $atom $pattern $template (eval (switch $atom $tail)))) + (function (unify $atom $pattern + (return $template) + (chain (eval (switch $atom $tail)) $ret (return $ret)) ))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; MeTTa interpreter implementation ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (= (match-types $type1 $type2 $then $else) - (eval (if-equal $type1 %Undefined% $then - (eval (if-equal $type2 %Undefined% $then - (eval (if-equal $type1 Atom $then - (eval (if-equal $type2 Atom $then - (unify $type1 $type2 $then $else) ))))))))) + (function (eval (if-equal $type1 %Undefined% + (return $then) + (eval (if-equal $type2 %Undefined% + (return $then) + (eval (if-equal $type1 Atom + (return $then) + (eval (if-equal $type2 Atom + (return $then) + (unify $type1 $type2 (return $then) (return $else)) )))))))))) (= (type-cast $atom $type $space) - (chain (eval (get-metatype $atom)) $meta - (eval (if-equal $type $meta $atom + (function (chain (eval (get-metatype $atom)) $meta + (eval (if-equal $type $meta + (return $atom) (chain (eval (get-type $atom $space)) $actual-type (eval (match-types $actual-type $type - $atom - (Error $atom BadType) ))))))) + (return $atom) + (return (Error $atom BadType)) )))))))) (= (is-function $type) - (chain (eval (get-metatype $type)) $meta - (eval (switch ($type $meta) - ( - (($_ Expression) - (eval (chain-eval (car $type) $head - (unify $head -> True False) ))) - ($_ False) ))))) + (function (chain (eval (get-metatype $type)) $meta + (eval (switch ($type $meta) ( + (($_ Expression) + (eval (if-decons $type $head $_tail + (unify $head -> (return True) (return False)) + (return (Error (is-function $type) "is-function non-empty expression as an argument")) ))) + ($_ (return False)) + )))))) (= (interpret $atom $type $space) - (chain (eval (get-metatype $atom)) $meta - (eval (if-equal $type Atom $atom - (eval (if-equal $type $meta $atom - (eval (switch ($type $meta) - ( - (($_type Variable) $atom) - (($_type Symbol) (eval (type-cast $atom $type $space))) - (($_type Grounded) (eval (type-cast $atom $type $space))) - (($_type Expression) (eval (interpret-expression $atom $type $space))) ))))))))) + (function (chain (eval (get-metatype $atom)) $meta + (eval (if-equal $type Atom + (return $atom) + (eval (if-equal $type $meta + (return $atom) + (eval (switch ($type $meta) ( + (($_type Variable) (return $atom)) + (($_type Symbol) + (chain (eval (type-cast $atom $type $space)) $ret (return $ret))) + (($_type Grounded) + (chain (eval (type-cast $atom $type $space)) $ret (return $ret))) + (($_type Expression) + (chain (eval (interpret-expression $atom $type $space)) $ret (return $ret))) + )))))))))) (= (interpret-expression $atom $type $space) - (eval (if-decons $atom $op $args + (function (eval (if-decons $atom $op $args (chain (eval (get-type $op $space)) $op-type - (eval (chain-eval (is-function $op-type) $is-func + (chain (eval (is-function $op-type)) $is-func (unify $is-func True - (eval (chain-eval (interpret-func $atom $op-type $type $space) $reduced-atom - (eval (metta-call $reduced-atom $type $space)) )) - (eval (chain-eval (interpret-tuple $atom $space) $reduced-atom - (eval (metta-call $reduced-atom $type $space)) )))))) - (eval (type-cast $atom $type $space)) ))) + (chain (eval (interpret-func $atom $op-type $type $space)) $reduced-atom + (chain (eval (metta-call $reduced-atom $type $space)) $ret (return $ret)) ) + (chain (eval (interpret-tuple $atom $space)) $reduced-atom + (chain (eval (metta-call $reduced-atom $type $space)) $ret (return $ret)) )))) + (chain (eval (type-cast $atom $type $space)) $ret (return $ret)) )))) (= (interpret-func $expr $type $ret-type $space) - (eval (if-decons $expr $op $args - (eval (chain-eval (interpret $op $type $space) $reduced-op + (function (eval (if-decons $expr $op $args + (chain (eval (interpret $op $type $space)) $reduced-op (eval (return-on-error $reduced-op (eval (if-decons $type $arrow $arg-types - (eval (chain-eval (interpret-args $expr $args $arg-types $ret-type $space) $reduced-args + (chain (eval (interpret-args $expr $args $arg-types $ret-type $space)) $reduced-args (eval (return-on-error $reduced-args - (chain (cons $reduced-op $reduced-args) $r (return $r)))))) - (Error $type "Function type expected") )))))) - (Error $expr "Non-empty expression atom is expected") ))) + (chain (cons $reduced-op $reduced-args) $r (return $r))))) + (return (Error $type "Function type expected")) ))))) + (return (Error $expr "Non-empty expression atom is expected")) )))) (= (interpret-args $atom $args $arg-types $ret-type $space) - (unify $args () - (eval (chain-eval (car $arg-types) $actual-ret-type - (eval (match-types $actual-ret-type $ret-type () (Error $atom BadType))))) + (function (unify $args () + (eval (if-decons $arg-types $actual-ret-type $_tail + (eval (match-types $actual-ret-type $ret-type + (return ()) + (return (Error $atom BadType)) )) + (return (Error (interpret-args $atom $args $arg-types $ret-type $space) "interpret-args expects a non-empty value for $arg-types argument")) )) (eval (if-decons $args $head $tail (eval (if-decons $arg-types $head-type $tail-types - (eval (chain-eval (interpret $head $head-type $space) $reduced-head + (chain (eval (interpret $head $head-type $space)) $reduced-head ; check that head was changed otherwise Error or Empty in the head ; can be just an argument which is passed by intention (eval (if-equal $reduced-head $head - (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) + (chain (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) $ret (return $ret)) (eval (return-on-error $reduced-head - (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) )))))) - (Error $atom BadType) )) - (Error (interpret-atom $atom $args $arg-types $space) - "Non-empty expression atom is expected") )))) + (chain (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) $ret (return $ret)) ))))) + (return (Error $atom BadType)) )) + (return (Error (interpret-atom $atom $args $arg-types $space) "Non-empty expression atom is expected")) ))))) (= (interpret-args-tail $atom $head $args-tail $args-tail-types $ret-type $space) - (eval (chain-eval (interpret-args $atom $args-tail $args-tail-types $ret-type $space) $reduced-tail + (function (chain (eval (interpret-args $atom $args-tail $args-tail-types $ret-type $space)) $reduced-tail (eval (return-on-error $reduced-tail - (cons $head $reduced-tail) ))))) + (chain (cons $head $reduced-tail) $ret (return $ret)) ))))) (= (interpret-tuple $atom $space) - (unify $atom () - $atom + (function (unify $atom () + (return $atom) (eval (if-decons $atom $head $tail - (eval (chain-eval (interpret $head %Undefined% $space) $rhead - (eval (if-empty $rhead Empty - (eval (chain-eval (interpret-tuple $tail $space) $rtail - (eval (if-empty $rtail Empty - (cons $rhead $rtail) )))))))) - (Error (interpret-tuple $atom $space) "Non-empty expression atom is expected as an argument") )))) + (chain (eval (interpret $head %Undefined% $space)) $rhead + (eval (if-empty $rhead (return Empty) + (chain (eval (interpret-tuple $tail $space)) $rtail + (eval (if-empty $rtail (return Empty) + (chain (cons $rhead $rtail) $ret (return $ret)) )))))) + (return (Error (interpret-tuple $atom $space) "Non-empty expression atom is expected as an argument")) ))))) (= (metta-call $atom $type $space) - (eval (if-error $atom $atom - (eval (chain-eval $atom $result - (eval (if-not-reducible $result $atom - (eval (if-empty $result Empty - (eval (if-error $result $result - (eval (interpret $result $type $space)) ))))))))))) + (function (eval (if-error $atom (return $atom) + (chain (eval $atom) $result + (eval (if-not-reducible $result (return $atom) + (eval (if-empty $result (return Empty) + (eval (if-error $result (return $result) + (chain (eval (interpret $result $type $space)) $ret (return $ret)) ))))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Standard library written in MeTTa ; @@ -210,5 +215,5 @@ (: quote (-> Atom Atom)) (= (quote $atom) NotReducible) -(: unquote (-> (Atom %Undefined%))) +(: unquote (-> %Undefined% %Undefined%)) (= (unquote (quote $atom)) $atom) From 20c2e47036020154e666f98a19c2fbfa161b7603 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 5 Oct 2023 14:11:28 +0300 Subject: [PATCH 07/23] Handle the situation when function body doesn't end with return Show error with function call which led to the situation. --- lib/src/metta/interpreter2.rs | 37 ++++++++++++++++++++++--------- lib/src/metta/runner/stdlib.metta | 9 ++++---- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/lib/src/metta/interpreter2.rs b/lib/src/metta/interpreter2.rs index 5c395d453..d55f90aa1 100644 --- a/lib/src/metta/interpreter2.rs +++ b/lib/src/metta/interpreter2.rs @@ -277,7 +277,14 @@ fn interpret_atom_root<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: Interpre [Atom::Expression(_body)] => { match atom_into_array(atom) { Some([_, body]) => - function(space, bindings, body), + function(space, bindings, body, None), + _ => panic!("Unexpected state"), + } + }, + [Atom::Expression(_body), Atom::Expression(_call)] => { + match atom_into_array(atom) { + Some([_, body, call]) => + function(space, bindings, body, Some(call)), _ => panic!("Unexpected state"), } }, @@ -401,11 +408,15 @@ fn chain<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, nested: Atom, var: V .collect() } } else if is_embedded_op(&nested) { - let result = interpret_atom_root(space, InterpretedAtom(nested, bindings), false); + let result = interpret_atom_root(space, InterpretedAtom(nested.clone(), bindings), false); result.into_iter() .map(|InterpretedAtom(r, b)| { if is_eval && is_function_op(&r) { - InterpretedAtom(Atom::expr([CHAIN_SYMBOL, r, Atom::Variable(var.clone()), templ.clone()]), b) + match atom_into_array(r) { + Some([_, body]) => + InterpretedAtom(Atom::expr([CHAIN_SYMBOL, Atom::expr([FUNCTION_SYMBOL, body, nested.clone()]), Atom::Variable(var.clone()), templ.clone()]), b), + _ => panic!("Unexpected state"), + } } else { apply(b, r, var.clone(), &templ) } @@ -416,31 +427,37 @@ fn chain<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, nested: Atom, var: V } } -fn function<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, body: Atom) -> Vec { +fn function<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, body: Atom, call: Option) -> Vec { + let call = match call { + Some(call) => call, + None => Atom::expr([FUNCTION_SYMBOL, body.clone()]), + }; match atom_as_slice(&body) { Some([op, _result]) if *op == RETURN_SYMBOL => { if let Some([_, result]) = atom_into_array(body) { - //log::debug!("function: return {:?}", result); // FIXME: check return arguments size vec![InterpretedAtom(result, bindings)] } else { panic!("Unexpected state"); } }, - _ => { + _ if is_embedded_op(&body) => { let mut result = interpret_atom_root(space, InterpretedAtom(body, bindings), false); - //log::debug!("function: result of execution {:?}", result); if result.len() == 1 { let InterpretedAtom(r, b) = result.pop().unwrap(); - vec![InterpretedAtom(Atom::expr([FUNCTION_SYMBOL, r]), b)] + vec![InterpretedAtom(Atom::expr([FUNCTION_SYMBOL, r, call]), b)] } else { result.into_iter() .map(|InterpretedAtom(r, b)| { - InterpretedAtom(Atom::expr([FUNCTION_SYMBOL, r]), b) + InterpretedAtom(Atom::expr([FUNCTION_SYMBOL, r, call.clone()]), b) }) .collect() } - } + }, + _ => { + let error = format!("function doesn't have return statement"); + vec![InterpretedAtom(error_atom(call, error), bindings)] + }, } } diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index d9adb92de..23d5746fc 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -10,11 +10,12 @@ (: cons (-> Atom Atom Atom)) (: decons (-> Atom Atom)) +(: id (-> Atom Atom)) +(= (id $x) $x) + (: insert (-> Atom Variable Atom Atom)) (= (insert $atom $var $templ) - (function (chain (eval (quote $atom)) $x - (chain (eval (unquote $x)) $var - (return $templ) )))) + (function (chain (eval (id $atom)) $var (return $templ))) ) (: if-non-empty-expression (-> Atom Atom Atom Atom)) (= (if-non-empty-expression $atom $then $else) @@ -46,7 +47,7 @@ (: return-on-error (-> Atom Atom Atom)) (= (return-on-error $atom $then) - (function (eval (if-empty $atom Empty + (function (eval (if-empty $atom (return (return Empty)) (eval (if-error $atom (return (return $atom)) (return $then) )))))) From f4199538340f08198ad955c4c0a5c0465acc2394 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 12 Oct 2023 15:59:45 +0300 Subject: [PATCH 08/23] Return () instead of void in grounded functions without result Replace exported VOID_SYMBOL in C API by EMPTY_SYMBOL which is still part of the spec. --- c/src/metta.rs | 10 ++++++---- docs/minimal-metta.md | 8 ++++---- lib/src/metta/interpreter2.rs | 8 ++------ lib/src/metta/mod.rs | 12 +++++++++++- lib/src/metta/runner/stdlib.rs | 9 --------- lib/src/metta/runner/stdlib2.rs | 16 +++------------- lib/tests/metta.rs | 5 +---- python/hyperon/atoms.py | 6 +++--- python/hyperonpy.cpp | 2 +- 9 files changed, 31 insertions(+), 45 deletions(-) diff --git a/c/src/metta.rs b/c/src/metta.rs index 30934a163..ad59ce2ac 100644 --- a/c/src/metta.rs +++ b/c/src/metta.rs @@ -554,13 +554,15 @@ pub extern "C" fn atom_error_message(atom: *const atom_ref_t, buf: *mut c_char, /// #[no_mangle] pub extern "C" fn ATOM_TYPE_GROUNDED_SPACE() -> atom_t { rust_type_atom::().into() } -/// @brief Creates a Symbol atom for the special MeTTa symbol used to indicate empty results in -/// case expressions. +/// @brief Creates a Symbol atom for the special MeTTa symbol used to indicate empty results +/// returned by function. /// @ingroup metta_language_group /// @return The `atom_t` representing the Void atom /// @note The returned `atom_t` must be freed with `atom_free()` /// -#[no_mangle] pub extern "C" fn VOID_SYMBOL() -> atom_t { hyperon::metta::VOID_SYMBOL.into() } +#[no_mangle] pub extern "C" fn EMPTY_SYMBOL() -> atom_t { + hyperon::metta::EMPTY_SYMBOL.into() +} /// @brief Checks whether Atom `atom` has Type `typ` in context of `space` /// @ingroup metta_language_group @@ -705,7 +707,7 @@ pub extern "C" fn step_has_next(step: *const step_result_t) -> bool { step.has_next() } -/// @brief Consumes a `step_result_t` and provides the ultimate outcome of a MeTTa interpreter session +/// @brief Consumes a `step_result_t` and provides the ultimate outcome of a MeTTa interpreter session /// @ingroup interpreter_group /// @param[in] step A pointer to a `step_result_t` to render /// @param[in] callback A function that will be called to provide a vector of all atoms resulting from the interpreter session diff --git a/docs/minimal-metta.md b/docs/minimal-metta.md index 1461d3767..e510e5a69 100644 --- a/docs/minimal-metta.md +++ b/docs/minimal-metta.md @@ -32,7 +32,7 @@ allowed developing the first stable version with less effort (see `eval` and `Return`). If an instruction returns the atom which is not from the minimal set it is not interpreted further and returned as a part of the final result. -## Error/Empty/NotReducible/Void +## Error/Empty/NotReducible/() There are atoms which can be returned to designate a special situation in a code: - `(Error )` means the interpretation is finished with error; @@ -46,8 +46,8 @@ There are atoms which can be returned to designate a special situation in a code which returns `NotReducible` explicitly; this atom is introduced to separate the situations when atom should be returned "as is" from `Empty` when atom should be removed from results; -- `Void` is a unit result which is mainly used by functions with side effects - which has no meaningful value to return. +- Empty expression `()` is a unit result which is mainly used by functions with + side effects which has no meaningful value to return. These atoms are not interpreted further as they are not a part of the minimal set of instructions. @@ -72,7 +72,7 @@ returns no results then `NotReducible` atom is a result of the instruction. Grou function can return a list of atoms, empty result, `Error()` or `NoReduce` result. The result of the instruction for a special values are the following: -- empty result returns `Void` atom; +- empty result returns unit `()` result; - `Error()` returns `(Error )` atom; - `NoReduce` returns `NotReducible` atom. diff --git a/lib/src/metta/interpreter2.rs b/lib/src/metta/interpreter2.rs index d55f90aa1..7d57e05a8 100644 --- a/lib/src/metta/interpreter2.rs +++ b/lib/src/metta/interpreter2.rs @@ -307,10 +307,6 @@ fn interpret_atom_root<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: Interpre result } -fn return_unit() -> Atom { - VOID_SYMBOL -} - fn return_not_reducible() -> Atom { NOT_REDUCIBLE_SYMBOL } @@ -338,7 +334,7 @@ fn eval<'a, T: SpaceRef<'a>>(space: T, atom: Atom, bindings: Bindings) -> Vec Atom { + Atom::expr([]) +} + +#[allow(non_snake_case)] +pub fn UNIT_TYPE() -> Atom { + Atom::expr([ARROW_SYMBOL]) +} + /// Initializes an error expression atom pub fn error_atom(err_atom: Option, err_code: Option, message: String) -> Atom { let err_atom = match err_atom { diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index a142f1adf..6b96b5830 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -23,15 +23,6 @@ use super::arithmetics::*; pub const VOID_SYMBOL : Atom = sym!("%void%"); -//TODO: convert these from functions to static strcutures, when Atoms are Send+Sync -#[allow(non_snake_case)] -pub fn UNIT_ATOM() -> Atom { - Atom::expr([]) -} -#[allow(non_snake_case)] -pub fn UNIT_TYPE() -> Atom { - Atom::expr([ARROW_SYMBOL]) -} fn unit_result() -> Result, ExecError> { Ok(vec![UNIT_ATOM()]) } diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 8461ccb05..370dabe6b 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -17,16 +17,6 @@ use super::arithmetics::*; pub const VOID_SYMBOL : Atom = sym!("%void%"); -//TODO: convert these from functions to static strcutures, when Atoms are Send+Sync -#[allow(non_snake_case)] -pub fn UNIT_ATOM() -> Atom { - Atom::expr([]) -} -#[allow(non_snake_case)] -pub fn UNIT_TYPE() -> Atom { - Atom::expr([ARROW_SYMBOL]) -} - #[derive(Clone, PartialEq, Debug)] pub struct GetTypeOp {} @@ -138,7 +128,7 @@ fn assert_results_equal(actual: &Vec, expected: &Vec, atom: &Atom) - log::debug!("assert_results_equal: actual: {:?}, expected: {:?}, actual atom: {:?}", actual, expected, atom); let report = format!("\nExpected: {:?}\nGot: {:?}", expected, actual); match vec_eq_no_order(actual.iter(), expected.iter()) { - Ok(()) => Ok(vec![VOID_SYMBOL]), + Ok(()) => Ok(vec![UNIT_ATOM()]), Err(diff) => Err(ExecError::Runtime(format!("{}\n{}", report, diff))) } } @@ -678,7 +668,7 @@ mod tests { "; assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![])); assert_eq!(metta.run(SExprParser::new("!(assertEqual (foo A) (bar A))")), Ok(vec![ - vec![VOID_SYMBOL], + vec![UNIT_ATOM()], ])); assert_eq!(metta.run(SExprParser::new("!(assertEqual (foo A) (bar B))")), Ok(vec![ vec![expr!("Error" ({assert.clone()} ("foo" "A") ("bar" "B")) "\nExpected: [B]\nGot: [A]\nMissed result: B")], @@ -701,7 +691,7 @@ mod tests { "; assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![])); assert_eq!(metta.run(SExprParser::new("!(assertEqualToResult (foo) (A B))")), Ok(vec![ - vec![VOID_SYMBOL], + vec![UNIT_ATOM()], ])); assert_eq!(metta.run(SExprParser::new("!(assertEqualToResult (bar) (A))")), Ok(vec![ vec![expr!("Error" ({assert.clone()} ("bar") ("A")) "\nExpected: [A]\nGot: [C]\nMissed result: A")], diff --git a/lib/tests/metta.rs b/lib/tests/metta.rs index a4ffe60a1..0d9081038 100644 --- a/lib/tests/metta.rs +++ b/lib/tests/metta.rs @@ -1,7 +1,4 @@ -#[cfg(not(feature = "minimal"))] -use hyperon::metta::runner::stdlib::UNIT_ATOM; -#[cfg(feature = "minimal")] -use hyperon::metta::runner::stdlib2::UNIT_ATOM; +use hyperon::metta::UNIT_ATOM; use hyperon::metta::text::*; use hyperon::metta::runner::{Metta, EnvBuilder}; diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 6dcd4c0e1..0e2beddd7 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -120,7 +120,7 @@ class AtomType: class Atoms: - VOID = Atom._from_catom(hp.CAtoms.VOID) + EMPTY = Atom._from_catom(hp.CAtoms.EMPTY) class GroundedAtom(Atom): """ @@ -186,7 +186,7 @@ def __init__(self, content, id=None): self.id = id def __repr__(self): - """Returns the object's ID if present, or a string representation of + """Returns the object's ID if present, or a string representation of its content if not.""" # Overwrite Python default representation of a string to use # double quotes instead of single quotes. @@ -213,7 +213,7 @@ class ValueObject(GroundedObject): obj1 = ValueObject(5) obj2 = ValueObject(5) obj3 = ValueObject(6) - + print(obj1 == obj2) # True print(obj1 == obj3) # False """ diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index 5466496b0..b8b6c70f1 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -791,7 +791,7 @@ PYBIND11_MODULE(hyperonpy, m) { #define ADD_SYMBOL(t, d) .def_property_readonly_static(#t, [](py::object) { return CAtom(t ## _SYMBOL()); }, d " atom type") py::class_(m, "CAtoms") - ADD_SYMBOL(VOID, "Void"); + ADD_SYMBOL(EMPTY, "Empty"); py::class_(m, "CMetta"); m.def("metta_new", [](CSpace space, EnvBuilder env_builder) { From 928cf02f99375b72ceeb7c3dff850d465e61eae6 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 13 Oct 2023 15:18:56 +0300 Subject: [PATCH 09/23] Rename local match_ function to unify --- lib/src/metta/interpreter2.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/metta/interpreter2.rs b/lib/src/metta/interpreter2.rs index 7d57e05a8..0f749e950 100644 --- a/lib/src/metta/interpreter2.rs +++ b/lib/src/metta/interpreter2.rs @@ -237,7 +237,7 @@ fn interpret_atom_root<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: Interpre }, Some([op, args @ ..]) if *op == UNIFY_SYMBOL => { match args { - [atom, pattern, then, else_] => match_(bindings, atom, pattern, then, else_), + [atom, pattern, then, else_] => unify(bindings, atom, pattern, then, else_), _ => { let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, atom); vec![InterpretedAtom(error_atom(atom, error), bindings)] @@ -457,8 +457,8 @@ fn function<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, body: Atom, call: } } -fn match_(bindings: Bindings, atom: &Atom, pattern: &Atom, then: &Atom, else_: &Atom) -> Vec { - // TODO: Should match_() be symmetrical or not. While it is symmetrical then +fn unify(bindings: Bindings, atom: &Atom, pattern: &Atom, then: &Atom, else_: &Atom) -> Vec { + // TODO: Should unify() be symmetrical or not. While it is symmetrical then // if variable is matched by variable then both variables have the same // priority. Thus interpreter can use any of them further. This sometimes // looks unexpected. For example see `metta_car` unit test where variable From 8d8b3461ba3d2896d72c93ea5629f166747e161e Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 13 Oct 2023 15:20:38 +0300 Subject: [PATCH 10/23] Fix loosing variable state when calculating (eval (unify ...)) --- lib/src/atom/matcher.rs | 7 ++++--- lib/src/metta/interpreter2.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/src/atom/matcher.rs b/lib/src/atom/matcher.rs index 525353695..5a1fd2561 100644 --- a/lib/src/atom/matcher.rs +++ b/lib/src/atom/matcher.rs @@ -1597,10 +1597,11 @@ mod test { fn bindings_cleanup() -> Result<(), &'static str> { let mut bindings = Bindings::new() .add_var_equality(&VariableAtom::new("a"), &VariableAtom::new("b"))? - .add_var_binding_v2(VariableAtom::new("b"), expr!("B"))? - .add_var_binding_v2(VariableAtom::new("c"), expr!("C"))?; + .add_var_binding_v2(VariableAtom::new("b"), expr!("B" d))? + .add_var_binding_v2(VariableAtom::new("c"), expr!("c"))? + .add_var_binding_v2(VariableAtom::new("d"), expr!("D"))?; bindings.cleanup(&[&VariableAtom::new("b")].into()); - assert_eq!(bindings, bind!{ b: expr!("B") }); + assert_eq!(bindings, bind!{ b: expr!("B" d) }); Ok(()) } diff --git a/lib/src/metta/interpreter2.rs b/lib/src/metta/interpreter2.rs index 0f749e950..f93415010 100644 --- a/lib/src/metta/interpreter2.rs +++ b/lib/src/metta/interpreter2.rs @@ -301,7 +301,7 @@ fn interpret_atom_root<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: Interpre if root { result.iter_mut().for_each(|interpreted| { let InterpretedAtom(atom, bindings) = interpreted; - bindings.cleanup(&atom.iter().filter_type::<&VariableAtom>().collect()); + *bindings = bindings.narrow_vars(&atom.iter().filter_type::<&VariableAtom>().collect()); }); } result From adb038b4400db07c002729ca589dd00700e831d1 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 18 Oct 2023 18:42:53 +0300 Subject: [PATCH 11/23] Fix nested chain behaviour Outer chain should wait for nested chain is finished before applying its results. --- lib/src/metta/interpreter2.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/src/metta/interpreter2.rs b/lib/src/metta/interpreter2.rs index f93415010..3f5fad45f 100644 --- a/lib/src/metta/interpreter2.rs +++ b/lib/src/metta/interpreter2.rs @@ -179,20 +179,24 @@ fn is_embedded_op(atom: &Atom) -> bool { } } -fn is_function_op(atom: &Atom) -> bool { +fn is_op(atom: &Atom, op: &Atom) -> bool { let expr = atom_as_slice(&atom); match expr { - Some([op, ..]) => *op == FUNCTION_SYMBOL, + Some([opp, ..]) => opp == op, _ => false, } } +fn is_function_op(atom: &Atom) -> bool { + is_op(atom, &FUNCTION_SYMBOL) +} + fn is_eval_op(atom: &Atom) -> bool { - let expr = atom_as_slice(&atom); - match expr { - Some([op, ..]) => *op == EVAL_SYMBOL, - _ => false, - } + is_op(atom, &EVAL_SYMBOL) +} + +fn is_chain_op(atom: &Atom) -> bool { + is_op(atom, &CHAIN_SYMBOL) } fn interpret_atom<'a, T: SpaceRef<'a>>(space: T, interpreted_atom: InterpretedAtom) -> Vec { @@ -405,7 +409,7 @@ fn chain<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, nested: Atom, var: V } } else if is_embedded_op(&nested) { let result = interpret_atom_root(space, InterpretedAtom(nested.clone(), bindings), false); - result.into_iter() + let result = result.into_iter() .map(|InterpretedAtom(r, b)| { if is_eval && is_function_op(&r) { match atom_into_array(r) { @@ -413,11 +417,14 @@ fn chain<'a, T: SpaceRef<'a>>(space: T, bindings: Bindings, nested: Atom, var: V InterpretedAtom(Atom::expr([CHAIN_SYMBOL, Atom::expr([FUNCTION_SYMBOL, body, nested.clone()]), Atom::Variable(var.clone()), templ.clone()]), b), _ => panic!("Unexpected state"), } + } else if is_chain_op(&r) { + InterpretedAtom(Atom::expr([CHAIN_SYMBOL, r, Atom::Variable(var.clone()), templ.clone()]), b) } else { apply(b, r, var.clone(), &templ) } }) - .collect() + .collect(); + result } else { vec![apply(bindings, nested, var, &templ)] } From d8b88365f904737f65d5f85ce013ecf8c517fa4c Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 18 Oct 2023 18:58:27 +0300 Subject: [PATCH 12/23] Add filter, map, fold and or functions --- lib/src/metta/runner/stdlib.metta | 35 +++++++++++++++++++++++++++++++ lib/src/metta/runner/stdlib2.rs | 19 +++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 23d5746fc..71ebf1885 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -96,6 +96,35 @@ ($_ (return False)) )))))) +(: filter (-> Expression Variable Atom Expression)) +(= (filter $list $var $filter) + (function (eval (if-decons $list $head $tail + (chain (eval (filter $tail $var $filter)) $tail-filtered + (chain (eval (insert $head $var $filter)) $filter-expr + (chain $filter-expr $is-filtered + (eval (if $is-filtered + (chain (cons $head $tail-filtered) $res (return $res)) + (return $tail-filtered) ))))) + (return ()) )))) + +(: map (-> Expression Variable Atom Expression)) +(= (map $list $var $map) + (function (eval (if-decons $list $head $tail + (chain (eval (map $tail $var $map)) $tail-mapped + (chain (eval (insert $head $var $map)) $map-expr + (chain $map-expr $head-mapped + (chain (cons $head-mapped $tail-mapped) $res (return $res)) ))) + (return ()) )))) + +(: foldl (-> Expression Atom Variable Variable Atom Atom)) +(= (foldl $list $init $a $b $op) + (function (eval (if-decons $list $head $tail + (chain (eval (insert $init $a $op)) $op-init + (chain (eval (insert $head $b $op-init)) $op-head + (chain $op-head $head-folded + (chain (eval (foldl $tail $head-folded $a $b $op)) $res (return $res)) ))) + (return $init) )))) + (= (interpret $atom $type $space) (function (chain (eval (get-metatype $atom)) $meta (eval (if-equal $type Atom @@ -187,6 +216,12 @@ (= (if True $then $else) $then) (= (if False $then $else) $else) +(: or (-> Bool Bool Bool)) +(= (or False False) False) +(= (or False True) True) +(= (or True False) True) +(= (or True True) True) + (: match (-> Atom Atom Atom %Undefined%)) (= (match $space $pattern $template) (unify $pattern $space $template Empty)) diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 370dabe6b..b0df89332 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -569,6 +569,25 @@ mod tests { assert_eq!(run_program("(: a A) (: b B) !(eval (type-cast (a b) (A B) &self))"), Ok(vec![vec![expr!("a" "b")]])); } + #[test] + fn metta_filter_out_errors() { + assert_eq!(run_program("!(eval (filter () $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!()]])); + assert_eq!(run_program("!(eval (filter (a (b) $c) $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!("a" ("b") c)]])); + assert_eq!(run_program("!(eval (filter (a (Error (b) \"Test error\") $c) $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!("a" c)]])); + } + + #[test] + fn metta_map() { + assert_eq!(run_program("!(eval (map () $x ($x mapped)))"), Ok(vec![vec![expr!()]])); + assert_eq!(run_program("!(eval (map (a (b) $c) $x (mapped $x)))"), Ok(vec![vec![expr!(("mapped" "a") ("mapped" ("b")) ("mapped" c))]])); + } + + #[test] + fn metta_foldl() { + assert_eq!(run_program("!(eval (foldl () 1 $a $b (eval (+ $a $b))))"), Ok(vec![vec![expr!({Number::Integer(1)})]])); + assert_eq!(run_program("!(eval (foldl (1 2 3) 0 $a $b (eval (+ $a $b))))"), Ok(vec![vec![expr!({Number::Integer(6)})]])); + } + #[test] fn metta_interpret_single_atom_as_atom() { let result = run_program("!(eval (interpret A Atom &self))"); From f6db0e09026b90d6c1d2c97cc10f6a506eb47c0b Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 18 Oct 2023 18:58:51 +0300 Subject: [PATCH 13/23] Fix type-cast, allow it when at least one type can be casted to required --- lib/src/metta/runner/stdlib.metta | 16 ++++++--- lib/src/metta/runner/stdlib2.rs | 41 ++++++++++++++++++++++ python/tests/scripts/b5_types_prelim.metta | 2 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 71ebf1885..0ee08fc32 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -81,10 +81,18 @@ (function (chain (eval (get-metatype $atom)) $meta (eval (if-equal $type $meta (return $atom) - (chain (eval (get-type $atom $space)) $actual-type - (eval (match-types $actual-type $type - (return $atom) - (return (Error $atom BadType)) )))))))) + ; TODO: the proper way to get types is something like + ; `(collapse (get-type ))` but it leads to the infinite + ; recursion because interpreter called by `collapse` evaluates + ; `type-cast` again. + (chain (eval (collapse-get-type $atom $space)) $actual-types + (chain (eval (foldl $actual-types False + $a $b (chain (eval (match-types $b $type True False)) $is-b-comp + (chain (eval (or $a $is-b-comp)) $or $or) ))) $is-some-comp + (eval (if $is-some-comp + (return $atom) + (return (Error $atom BadType)) ))))))))) + (= (is-function $type) (function (chain (eval (get-metatype $type)) $meta diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index b0df89332..f3158ee3e 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -49,6 +49,38 @@ impl Grounded for GetTypeOp { } } +#[derive(Clone, PartialEq, Debug)] +pub struct CollapseGetTypeOp {} + +impl Display for CollapseGetTypeOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "collapse-get-type") + } +} + +impl Grounded for CollapseGetTypeOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION]) + } + + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("get-type expects single atom as an argument"); + let atom = args.get(0).ok_or_else(arg_error)?; + let space = args.get(1).ok_or_else(arg_error)?; + let space = Atom::as_gnd::(space).ok_or("match expects a space as the first argument")?; + let types = get_atom_types(space, atom); + if types.is_empty() { + Ok(vec![Atom::expr([])]) + } else { + Ok(vec![Atom::expr(types)]) + } + } + + fn match_(&self, other: &Atom) -> MatchResultIter { + match_by_equality(self, other) + } +} + #[derive(Clone, PartialEq, Debug)] pub struct GetMetaTypeOp { } @@ -402,6 +434,8 @@ pub fn register_common_tokens(metta: &Metta) { let get_type_op = Atom::gnd(GetTypeOp{}); tref.register_token(regex(r"get-type"), move |_| { get_type_op.clone() }); + let collapse_get_type_op = Atom::gnd(CollapseGetTypeOp{}); + tref.register_token(regex(r"collapse-get-type"), move |_| { collapse_get_type_op.clone() }); let get_meta_type_op = Atom::gnd(GetMetaTypeOp{}); tref.register_token(regex(r"get-metatype"), move |_| { get_meta_type_op.clone() }); let is_equivalent = Atom::gnd(IfEqualOp{}); @@ -567,6 +601,7 @@ mod tests { assert_eq!(run_program("!(eval (type-cast (a b) Expression &self))"), Ok(vec![vec![expr!("a" "b")]])); assert_eq!(run_program("!(eval (type-cast $v Variable &self))"), Ok(vec![vec![expr!(v)]])); assert_eq!(run_program("(: a A) (: b B) !(eval (type-cast (a b) (A B) &self))"), Ok(vec![vec![expr!("a" "b")]])); + assert_eq!(run_program("(: a A) (: a B) !(eval (type-cast a A &self))"), Ok(vec![vec![expr!("a")]])); } #[test] @@ -677,6 +712,12 @@ mod tests { assert_eq!(run_program("!(eval (interpret () SomeType &self))"), Ok(vec![vec![expr!(())]])); } + #[test] + fn metta_interpret_single_atom_with_two_types() { + let result = run_program("(: a A) (: a B) !(eval (interpret a %Undefined% &self))"); + assert_eq!(result, Ok(vec![vec![expr!("a")]])); + } + #[test] fn metta_assert_equal_op() { let metta = Metta::new(Some(EnvBuilder::test_env())); diff --git a/python/tests/scripts/b5_types_prelim.metta b/python/tests/scripts/b5_types_prelim.metta index 17b4fa0ae..275894a34 100644 --- a/python/tests/scripts/b5_types_prelim.metta +++ b/python/tests/scripts/b5_types_prelim.metta @@ -84,7 +84,7 @@ ; This list is badly typed, because S and Z are not the same type !(assertEqualToResult (Cons S (Cons Z Nil)) - ((Error Z BadType))) + ((Error (Cons Z Nil) BadType))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 175d430b03a67b69a0f21675125cf76e53c6cd23 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 19 Oct 2023 10:51:14 +0300 Subject: [PATCH 14/23] Add import! operation into minimal MeTTa standard library --- lib/src/metta/runner/stdlib2.rs | 103 ++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index f3158ee3e..88082c9e2 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -12,11 +12,16 @@ use std::fmt::Display; use regex::Regex; use std::convert::TryInto; use std::collections::HashMap; +use std::path::PathBuf; use super::arithmetics::*; pub const VOID_SYMBOL : Atom = sym!("%void%"); +fn unit_result() -> Result, ExecError> { + Ok(vec![UNIT_ATOM()]) +} + #[derive(Clone, PartialEq, Debug)] pub struct GetTypeOp {} @@ -424,6 +429,102 @@ impl Grounded for CaseOp { } } +#[derive(Clone, Debug)] +pub struct ImportOp { + metta: Metta, +} + +impl PartialEq for ImportOp { + fn eq(&self, _other: &Self) -> bool { true } +} + +impl ImportOp { + pub fn new(metta: Metta) -> Self { + Self{ metta } + } +} + +impl Display for ImportOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "import!") + } +} + +impl Grounded for ImportOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, UNIT_TYPE()]) + } + + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("import! expects two arguments: space and file path"); + let space = args.get(0).ok_or_else(arg_error)?; + let file = args.get(1).ok_or_else(arg_error)?; + let mut module_path = None; + + // TODO: replace Symbol by grounded String? + if let Atom::Symbol(file) = file { + + //Check each include directory in order, until we find the module we're looking for + for include_dir in self.metta.search_paths() { + let mut path: PathBuf = include_dir.into(); + path.push(file.name()); + path = path.canonicalize().unwrap_or(path); + if path.exists() { + module_path = Some(path); + break; + } + } + } else { + return Err("import! expects a file path as a second argument".into()) + } + let module_space = match module_path { + Some(path) => { + log::debug!("import! load file, full path: {}", path.display()); + self.metta.load_module_space(path)? + } + None => return Err(format!("Failed to load module {file:?}; could not locate file").into()) + }; + + match space { + // If the module is to be associated with a new space, + // we register it in the tokenizer - works as "import as" + Atom::Symbol(space) => { + let name = space.name(); + let space_atom = Atom::gnd(module_space); + let regex = Regex::new(name) + .map_err(|err| format!("Could not convert space name {} into regex: {}", name, err))?; + self.metta.tokenizer().borrow_mut() + .register_token(regex, move |_| { space_atom.clone() }); + }, + // If the reference space exists, the module space atom is inserted into it + // (but the token is not added) - works as "import to" + Atom::Grounded(_) => { + let space = Atom::as_gnd::(space) + .ok_or("import! expects a space as a first argument")?; + // Moving space atoms from children to parent + let modules = self.metta.modules().borrow(); + for (_path, mspace) in modules.iter() { + let aspace = Atom::gnd(mspace.clone()); + if module_space.borrow_mut().remove(&aspace) { + self.metta.space().borrow_mut().remove(&aspace); + self.metta.space().borrow_mut().add(aspace); + } + } + let module_space_atom = Atom::gnd(module_space); + if space.borrow_mut().query(&module_space_atom).is_empty() { + space.borrow_mut().add(module_space_atom); + } + }, + _ => return Err("import! expects space as a first argument".into()), + }; + unit_result() + } + + fn match_(&self, other: &Atom) -> MatchResultIter { + match_by_equality(self, other) + } +} + fn regex(regex: &str) -> Regex { Regex::new(regex).unwrap() } @@ -460,6 +561,8 @@ pub fn register_runner_tokens(metta: &Metta) { tref.register_token(regex(r"pragma!"), move |_| { pragma_op.clone() }); let case_op = Atom::gnd(CaseOp::new(space.clone())); tref.register_token(regex(r"case"), move |_| { case_op.clone() }); + let import_op = Atom::gnd(ImportOp::new(metta.clone())); + tref.register_token(regex(r"import!"), move |_| { import_op.clone() }); // &self should be updated // TODO: adding &self might be done not by stdlib, but by MeTTa itself. // TODO: adding &self introduces self referencing and thus prevents space From 4c72f328785e0051b8db11ba0b395b0265f81f9a Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 19 Oct 2023 11:29:27 +0300 Subject: [PATCH 15/23] Fix get-type code to be compatible with Rust interpreter --- lib/src/metta/runner/stdlib2.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 88082c9e2..7139c91f9 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -23,7 +23,17 @@ fn unit_result() -> Result, ExecError> { } #[derive(Clone, PartialEq, Debug)] -pub struct GetTypeOp {} +pub struct GetTypeOp { + // TODO: MINIMAL this is temporary compatibility fix to be removed after + // migration to the minimal MeTTa + space: DynSpace, +} + +impl GetTypeOp { + pub fn new(space: DynSpace) -> Self { + Self{ space } + } +} impl Display for GetTypeOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -39,8 +49,11 @@ impl Grounded for GetTypeOp { fn execute(&self, args: &[Atom]) -> Result, ExecError> { let arg_error = || ExecError::from("get-type expects single atom as an argument"); let atom = args.get(0).ok_or_else(arg_error)?; - let space = args.get(1).ok_or_else(arg_error)?; - let space = Atom::as_gnd::(space).ok_or("match expects a space as the first argument")?; + let space = match args.get(1) { + Some(space) => Atom::as_gnd::(space) + .ok_or("match expects a space as the first argument"), + None => Ok(&self.space), + }?; let types = get_atom_types(space, atom); if types.is_empty() { Ok(vec![EMPTY_SYMBOL]) @@ -532,8 +545,9 @@ fn regex(regex: &str) -> Regex { pub fn register_common_tokens(metta: &Metta) { let tokenizer = metta.tokenizer(); let mut tref = tokenizer.borrow_mut(); + let space = metta.space(); - let get_type_op = Atom::gnd(GetTypeOp{}); + let get_type_op = Atom::gnd(GetTypeOp::new(space.clone())); tref.register_token(regex(r"get-type"), move |_| { get_type_op.clone() }); let collapse_get_type_op = Atom::gnd(CollapseGetTypeOp{}); tref.register_token(regex(r"collapse-get-type"), move |_| { collapse_get_type_op.clone() }); @@ -622,7 +636,7 @@ mod tests { (: A C) ")); - let get_type_op = GetTypeOp{}; + let get_type_op = GetTypeOp::new(space.clone()); assert_eq_no_order!(get_type_op.execute(&mut vec![sym!("A"), expr!({space.clone()})]).unwrap(), vec![sym!("B"), sym!("C")]); } @@ -635,7 +649,7 @@ mod tests { (: \"test\" String) ")); - let get_type_op = GetTypeOp{}; + let get_type_op = GetTypeOp::new(space.clone()); assert_eq_no_order!(get_type_op.execute(&mut vec![expr!("f" "42"), expr!({space.clone()})]).unwrap(), vec![sym!("String")]); assert_eq_no_order!(get_type_op.execute(&mut vec![expr!("f" "\"test\""), expr!({space.clone()})]).unwrap(), From 4bb0443909420e14f70e4d8929afac4a26b8ae05 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 19 Oct 2023 11:47:06 +0300 Subject: [PATCH 16/23] Rename insert to apply to eliminate conflict with function in test --- lib/src/metta/runner/stdlib.metta | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 0ee08fc32..ec2f64e59 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -13,8 +13,8 @@ (: id (-> Atom Atom)) (= (id $x) $x) -(: insert (-> Atom Variable Atom Atom)) -(= (insert $atom $var $templ) +(: apply (-> Atom Variable Atom Atom)) +(= (apply $atom $var $templ) (function (chain (eval (id $atom)) $var (return $templ))) ) (: if-non-empty-expression (-> Atom Atom Atom Atom)) @@ -108,7 +108,7 @@ (= (filter $list $var $filter) (function (eval (if-decons $list $head $tail (chain (eval (filter $tail $var $filter)) $tail-filtered - (chain (eval (insert $head $var $filter)) $filter-expr + (chain (eval (apply $head $var $filter)) $filter-expr (chain $filter-expr $is-filtered (eval (if $is-filtered (chain (cons $head $tail-filtered) $res (return $res)) @@ -119,7 +119,7 @@ (= (map $list $var $map) (function (eval (if-decons $list $head $tail (chain (eval (map $tail $var $map)) $tail-mapped - (chain (eval (insert $head $var $map)) $map-expr + (chain (eval (apply $head $var $map)) $map-expr (chain $map-expr $head-mapped (chain (cons $head-mapped $tail-mapped) $res (return $res)) ))) (return ()) )))) @@ -127,8 +127,8 @@ (: foldl (-> Expression Atom Variable Variable Atom Atom)) (= (foldl $list $init $a $b $op) (function (eval (if-decons $list $head $tail - (chain (eval (insert $init $a $op)) $op-init - (chain (eval (insert $head $b $op-init)) $op-head + (chain (eval (apply $init $a $op)) $op-init + (chain (eval (apply $head $b $op-init)) $op-head (chain $op-head $head-folded (chain (eval (foldl $tail $head-folded $a $b $op)) $res (return $res)) ))) (return $init) )))) From 9b80f1e599698af605e07e8a782a4cdb76441960 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 19 Oct 2023 12:21:43 +0300 Subject: [PATCH 17/23] Fix pragma! return unit --- lib/src/metta/runner/stdlib2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 7139c91f9..8abb624a0 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -178,7 +178,7 @@ fn assert_results_equal(actual: &Vec, expected: &Vec, atom: &Atom) - log::debug!("assert_results_equal: actual: {:?}, expected: {:?}, actual atom: {:?}", actual, expected, atom); let report = format!("\nExpected: {:?}\nGot: {:?}", expected, actual); match vec_eq_no_order(actual.iter(), expected.iter()) { - Ok(()) => Ok(vec![UNIT_ATOM()]), + Ok(()) => unit_result(), Err(diff) => Err(ExecError::Runtime(format!("{}\n{}", report, diff))) } } @@ -374,7 +374,7 @@ impl Grounded for PragmaOp { .map_err(|_| "pragma! expects symbol atom as a key")?.name(); let value = args.get(1).ok_or_else(arg_error)?; self.settings.borrow_mut().insert(key.into(), value.clone()); - Ok(vec![]) + unit_result() } fn match_(&self, other: &Atom) -> MatchResultIter { From a2182061392c1553416d6e12e8792204e2ec71e7 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 20 Oct 2023 13:29:13 +0300 Subject: [PATCH 18/23] Import common grounded operations from stdlib.rs module --- lib/src/metta/runner/mod.rs | 1 - lib/src/metta/runner/stdlib.rs | 2 +- lib/src/metta/runner/stdlib2.rs | 158 ++++---------------------------- 3 files changed, 21 insertions(+), 140 deletions(-) diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 0aa0e2001..b35f5dc6b 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -14,7 +14,6 @@ use std::sync::Arc; mod environment; pub use environment::{Environment, EnvBuilder}; -#[cfg(not(feature = "minimal"))] pub mod stdlib; #[cfg(not(feature = "minimal"))] use super::interpreter::{interpret, interpret_init, interpret_step, InterpreterState}; diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index 6b96b5830..d23ec750d 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -1195,7 +1195,7 @@ pub static METTA_CODE: &'static str = " (: Error (-> Atom Atom ErrorType)) "; -#[cfg(test)] +#[cfg(all(test, not(feature = "minimal")))] mod tests { use super::*; use crate::metta::runner::{Metta, EnvBuilder}; diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 8abb624a0..16ec4dcc2 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -6,13 +6,11 @@ use crate::metta::text::Tokenizer; use crate::metta::runner::Metta; use crate::metta::types::{get_atom_types, get_meta_type}; use crate::common::assert::vec_eq_no_order; -use crate::common::shared::Shared; +use crate::metta::runner::stdlib; use std::fmt::Display; use regex::Regex; use std::convert::TryInto; -use std::collections::HashMap; -use std::path::PathBuf; use super::arithmetics::*; @@ -346,42 +344,6 @@ impl Grounded for CollapseOp { } } -#[derive(Clone, PartialEq, Debug)] -pub struct PragmaOp { - settings: Shared>, -} - -impl PragmaOp { - pub fn new(settings: Shared>) -> Self { - Self{ settings } - } -} - -impl Display for PragmaOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "pragma!") - } -} - -impl Grounded for PragmaOp { - fn type_(&self) -> Atom { - ATOM_TYPE_UNDEFINED - } - - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("pragma! expects key and value as arguments"); - let key = TryInto::<&SymbolAtom>::try_into(args.get(0).ok_or_else(arg_error)?) - .map_err(|_| "pragma! expects symbol atom as a key")?.name(); - let value = args.get(1).ok_or_else(arg_error)?; - self.settings.borrow_mut().insert(key.into(), value.clone()); - unit_result() - } - - fn match_(&self, other: &Atom) -> MatchResultIter { - match_by_equality(self, other) - } -} - #[derive(Clone, PartialEq, Debug)] pub struct CaseOp { space: DynSpace, @@ -442,102 +404,6 @@ impl Grounded for CaseOp { } } -#[derive(Clone, Debug)] -pub struct ImportOp { - metta: Metta, -} - -impl PartialEq for ImportOp { - fn eq(&self, _other: &Self) -> bool { true } -} - -impl ImportOp { - pub fn new(metta: Metta) -> Self { - Self{ metta } - } -} - -impl Display for ImportOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "import!") - } -} - -impl Grounded for ImportOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, UNIT_TYPE()]) - } - - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("import! expects two arguments: space and file path"); - let space = args.get(0).ok_or_else(arg_error)?; - let file = args.get(1).ok_or_else(arg_error)?; - let mut module_path = None; - - // TODO: replace Symbol by grounded String? - if let Atom::Symbol(file) = file { - - //Check each include directory in order, until we find the module we're looking for - for include_dir in self.metta.search_paths() { - let mut path: PathBuf = include_dir.into(); - path.push(file.name()); - path = path.canonicalize().unwrap_or(path); - if path.exists() { - module_path = Some(path); - break; - } - } - } else { - return Err("import! expects a file path as a second argument".into()) - } - let module_space = match module_path { - Some(path) => { - log::debug!("import! load file, full path: {}", path.display()); - self.metta.load_module_space(path)? - } - None => return Err(format!("Failed to load module {file:?}; could not locate file").into()) - }; - - match space { - // If the module is to be associated with a new space, - // we register it in the tokenizer - works as "import as" - Atom::Symbol(space) => { - let name = space.name(); - let space_atom = Atom::gnd(module_space); - let regex = Regex::new(name) - .map_err(|err| format!("Could not convert space name {} into regex: {}", name, err))?; - self.metta.tokenizer().borrow_mut() - .register_token(regex, move |_| { space_atom.clone() }); - }, - // If the reference space exists, the module space atom is inserted into it - // (but the token is not added) - works as "import to" - Atom::Grounded(_) => { - let space = Atom::as_gnd::(space) - .ok_or("import! expects a space as a first argument")?; - // Moving space atoms from children to parent - let modules = self.metta.modules().borrow(); - for (_path, mspace) in modules.iter() { - let aspace = Atom::gnd(mspace.clone()); - if module_space.borrow_mut().remove(&aspace) { - self.metta.space().borrow_mut().remove(&aspace); - self.metta.space().borrow_mut().add(aspace); - } - } - let module_space_atom = Atom::gnd(module_space); - if space.borrow_mut().query(&module_space_atom).is_empty() { - space.borrow_mut().add(module_space_atom); - } - }, - _ => return Err("import! expects space as a first argument".into()), - }; - unit_result() - } - - fn match_(&self, other: &Atom) -> MatchResultIter { - match_by_equality(self, other) - } -} - fn regex(regex: &str) -> Regex { Regex::new(regex).unwrap() } @@ -555,6 +421,20 @@ pub fn register_common_tokens(metta: &Metta) { tref.register_token(regex(r"get-metatype"), move |_| { get_meta_type_op.clone() }); let is_equivalent = Atom::gnd(IfEqualOp{}); tref.register_token(regex(r"if-equal"), move |_| { is_equivalent.clone() }); + let new_space_op = Atom::gnd(stdlib::NewSpaceOp{}); + tref.register_token(regex(r"new-space"), move |_| { new_space_op.clone() }); + let add_atom_op = Atom::gnd(stdlib::AddAtomOp{}); + tref.register_token(regex(r"add-atom"), move |_| { add_atom_op.clone() }); + let remove_atom_op = Atom::gnd(stdlib::RemoveAtomOp{}); + tref.register_token(regex(r"remove-atom"), move |_| { remove_atom_op.clone() }); + let get_atoms_op = Atom::gnd(stdlib::GetAtomsOp{}); + tref.register_token(regex(r"get-atoms"), move |_| { get_atoms_op.clone() }); + let new_state_op = Atom::gnd(stdlib::NewStateOp{}); + tref.register_token(regex(r"new-state"), move |_| { new_state_op.clone() }); + let change_state_op = Atom::gnd(stdlib::ChangeStateOp{}); + tref.register_token(regex(r"change-state!"), move |_| { change_state_op.clone() }); + let get_state_op = Atom::gnd(stdlib::GetStateOp{}); + tref.register_token(regex(r"get-state"), move |_| { get_state_op.clone() }); } pub fn register_runner_tokens(metta: &Metta) { @@ -571,12 +451,14 @@ pub fn register_runner_tokens(metta: &Metta) { tref.register_token(regex(r"superpose"), move |_| { superpose_op.clone() }); let collapse_op = Atom::gnd(CollapseOp::new(space.clone())); tref.register_token(regex(r"collapse"), move |_| { collapse_op.clone() }); - let pragma_op = Atom::gnd(PragmaOp::new(metta.settings().clone())); - tref.register_token(regex(r"pragma!"), move |_| { pragma_op.clone() }); let case_op = Atom::gnd(CaseOp::new(space.clone())); tref.register_token(regex(r"case"), move |_| { case_op.clone() }); - let import_op = Atom::gnd(ImportOp::new(metta.clone())); + let pragma_op = Atom::gnd(stdlib::PragmaOp::new(metta.settings().clone())); + tref.register_token(regex(r"pragma!"), move |_| { pragma_op.clone() }); + let import_op = Atom::gnd(stdlib::ImportOp::new(metta.clone())); tref.register_token(regex(r"import!"), move |_| { import_op.clone() }); + let bind_op = Atom::gnd(stdlib::BindOp::new(tokenizer.clone())); + tref.register_token(regex(r"bind!"), move |_| { bind_op.clone() }); // &self should be updated // TODO: adding &self might be done not by stdlib, but by MeTTa itself. // TODO: adding &self introduces self referencing and thus prevents space From 356b46159eadb7d32e608a28ba29d15e1f9bfa03 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 20 Oct 2023 16:43:45 +0300 Subject: [PATCH 19/23] Rename car and cdr to make them compatible with previous stdlib --- lib/src/metta/runner/stdlib.metta | 17 +++++++++++------ lib/src/metta/runner/stdlib2.rs | 14 +++++++------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index ec2f64e59..44a879a6a 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -244,20 +244,25 @@ (let $pattern $atom (let* $tail $template)) $template ))) -(: car (-> Expression Atom)) -(= (car $atom) +(: car-atom (-> Expression Atom)) +(= (car-atom $atom) (eval (if-decons $atom $head $_ $head - (Error (car $atom) "car expects a non-empty expression as an argument") ))) + (Error (car-atom $atom) "car-atom expects a non-empty expression as an argument") ))) -(: cdr (-> Expression Expression)) -(= (cdr $atom) +(: cdr-atom (-> Expression Expression)) +(= (cdr-atom $atom) (eval (if-decons $atom $_ $tail $tail - (Error (cdr $atom) "cdr expects a non-empty expression as an argument") ))) + (Error (cdr-atom $atom) "cdr-atom expects a non-empty expression as an argument") ))) (: quote (-> Atom Atom)) (= (quote $atom) NotReducible) (: unquote (-> %Undefined% %Undefined%)) (= (unquote (quote $atom)) $atom) + +; TODO: there is no way to define operation which consumes any number of +; arguments and returns unit +(= (nop) ()) +(= (nop $x) ()) diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 16ec4dcc2..8ea39ac74 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -540,17 +540,17 @@ mod tests { #[test] - fn metta_car() { - let result = run_program("!(eval (car (A $b)))"); + fn metta_car_atom() { + let result = run_program("!(eval (car-atom (A $b)))"); assert_eq!(result, Ok(vec![vec![expr!("A")]])); - let result = run_program("!(eval (car ($a B)))"); + let result = run_program("!(eval (car-atom ($a B)))"); //assert_eq!(result, Ok(vec![vec![expr!(a)]])); assert!(result.is_ok_and(|res| res.len() == 1 && res[0].len() == 1 && atoms_are_equivalent(&res[0][0], &expr!(a)))); - let result = run_program("!(eval (car ()))"); - assert_eq!(result, Ok(vec![vec![expr!("Error" ("car" ()) "\"car expects a non-empty expression as an argument\"")]])); - let result = run_program("!(eval (car A))"); - assert_eq!(result, Ok(vec![vec![expr!("Error" ("car" "A") "\"car expects a non-empty expression as an argument\"")]])); + let result = run_program("!(eval (car-atom ()))"); + assert_eq!(result, Ok(vec![vec![expr!("Error" ("car-atom" ()) "\"car-atom expects a non-empty expression as an argument\"")]])); + let result = run_program("!(eval (car-atom A))"); + assert_eq!(result, Ok(vec![vec![expr!("Error" ("car-atom" "A") "\"car-atom expects a non-empty expression as an argument\"")]])); } #[test] From 6b9b68a228789a9557f70efc6c0e61c66dd29e41 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Mon, 23 Oct 2023 14:44:51 +0300 Subject: [PATCH 20/23] Fix tests for minimal MeTTa interpreter Frog reasoning test fails because in minimal MeTTa standard library has more functions and they are matched with `($x croaks)` expressions. Example is rewritten to have predicate on the first place. test_infer_function_application_type fails because `apply` predicate is present in minimal MeTTa standard library. Predicate is renamed into `apply'`. --- lib/src/metta/runner/stdlib.metta | 6 ++++++ python/tests/test_examples.py | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 44a879a6a..90b46345b 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -230,6 +230,12 @@ (= (or True False) True) (= (or True True) True) +(: and (-> Bool Bool Bool)) +(= (and False False) False) +(= (and False True) False) +(= (and True False) False) +(= (and True True) True) + (: match (-> Atom Atom Atom %Undefined%)) (= (match $space $pattern $template) (unify $pattern $space $template Empty)) diff --git a/python/tests/test_examples.py b/python/tests/test_examples.py index 58d907551..10acf5b18 100644 --- a/python/tests/test_examples.py +++ b/python/tests/test_examples.py @@ -96,31 +96,31 @@ def test_frog_reasoning(self): metta = MeTTa(env_builder=Environment.test_env()) metta.run(''' - (= (Fritz croaks) True) - (= (Tweety chirps) True) - (= (Tweety yellow) True) - (= (Tweety eats_flies) True) - (= (Fritz eats_flies) True) + (= (croaks Fritz) True) + (= (chirps Tweety) True) + (= (yellow Tweety) True) + (= (eats_flies Tweety) True) + (= (eats_flies Fritz) True) ''') - fritz_frog = metta.run('!(if (and ($x croaks) ($x eats_flies)) (= ($x frog) True) nop)')[0] - self.assertEqual(metta.parse_all('(= (Fritz frog) True)'), fritz_frog) + fritz_frog = metta.run('!(if (and (croaks $x) (eats_flies $x)) (= (frog $x) True) nop)')[0] + self.assertEqual(metta.parse_all('(= (frog Fritz) True)'), fritz_frog) metta.space().add_atom(fritz_frog[0]) - self.assertEqualMettaRunnerResults([metta.parse_all('(= (Fritz green) True)')], - metta.run('!(if ($x frog) (= ($x green) True) nop)')) + self.assertEqualMettaRunnerResults([metta.parse_all('(= (green Fritz) True)')], + metta.run('!(if (frog $x) (= (green $x) True) nop)')) def test_infer_function_application_type(self): metta = MeTTa(env_builder=Environment.test_env()) metta.run(''' - (= (: (apply $f $x) $r) (and (: $f (=> $a $r)) (: $x $a))) + (= (: (apply\' $f $x) $r) (and (: $f (=> $a $r)) (: $x $a))) (= (: reverse (=> String String)) True) (= (: "Hello" String) True) ''') - output = metta.run('!(if (: (apply reverse "Hello") $t) $t Wrong)') + output = metta.run('!(if (: (apply\' reverse "Hello") $t) $t Wrong)') self.assertEqualMettaRunnerResults(output, [[S('String')]]) def test_plus_reduces_Z(self): From 650e14ecf756c4ffc428532e4fd5310755e57a72 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 24 Oct 2023 11:13:57 +0300 Subject: [PATCH 21/23] Make b5_ test compatible with Rust MeTTa interpreter --- python/tests/scripts/b5_types_prelim.metta | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/tests/scripts/b5_types_prelim.metta b/python/tests/scripts/b5_types_prelim.metta index 275894a34..8e54a5e98 100644 --- a/python/tests/scripts/b5_types_prelim.metta +++ b/python/tests/scripts/b5_types_prelim.metta @@ -84,7 +84,9 @@ ; This list is badly typed, because S and Z are not the same type !(assertEqualToResult (Cons S (Cons Z Nil)) - ((Error (Cons Z Nil) BadType))) + ((Error Z BadType))) + ; TODO: MINIMAL replace test result after migration on minimal MeTTa + ;((Error (Cons Z Nil) BadType))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From e605da3bd10322b1de08ad1ae4e8156c4678d071 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 24 Oct 2023 16:34:41 +0300 Subject: [PATCH 22/23] Rename functions with general names: apply, filter, map, foldl --- lib/src/metta/runner/stdlib.metta | 20 ++++++++++---------- lib/src/metta/runner/stdlib2.rs | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 90b46345b..ead35aaaf 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -86,7 +86,7 @@ ; recursion because interpreter called by `collapse` evaluates ; `type-cast` again. (chain (eval (collapse-get-type $atom $space)) $actual-types - (chain (eval (foldl $actual-types False + (chain (eval (foldl-atom $actual-types False $a $b (chain (eval (match-types $b $type True False)) $is-b-comp (chain (eval (or $a $is-b-comp)) $or $or) ))) $is-some-comp (eval (if $is-some-comp @@ -104,10 +104,10 @@ ($_ (return False)) )))))) -(: filter (-> Expression Variable Atom Expression)) -(= (filter $list $var $filter) +(: filter-atom (-> Expression Variable Atom Expression)) +(= (filter-atom $list $var $filter) (function (eval (if-decons $list $head $tail - (chain (eval (filter $tail $var $filter)) $tail-filtered + (chain (eval (filter-atom $tail $var $filter)) $tail-filtered (chain (eval (apply $head $var $filter)) $filter-expr (chain $filter-expr $is-filtered (eval (if $is-filtered @@ -115,22 +115,22 @@ (return $tail-filtered) ))))) (return ()) )))) -(: map (-> Expression Variable Atom Expression)) -(= (map $list $var $map) +(: map-atom (-> Expression Variable Atom Expression)) +(= (map-atom $list $var $map) (function (eval (if-decons $list $head $tail - (chain (eval (map $tail $var $map)) $tail-mapped + (chain (eval (map-atom $tail $var $map)) $tail-mapped (chain (eval (apply $head $var $map)) $map-expr (chain $map-expr $head-mapped (chain (cons $head-mapped $tail-mapped) $res (return $res)) ))) (return ()) )))) -(: foldl (-> Expression Atom Variable Variable Atom Atom)) -(= (foldl $list $init $a $b $op) +(: foldl-atom (-> Expression Atom Variable Variable Atom Atom)) +(= (foldl-atom $list $init $a $b $op) (function (eval (if-decons $list $head $tail (chain (eval (apply $init $a $op)) $op-init (chain (eval (apply $head $b $op-init)) $op-head (chain $op-head $head-folded - (chain (eval (foldl $tail $head-folded $a $b $op)) $res (return $res)) ))) + (chain (eval (foldl-atom $tail $head-folded $a $b $op)) $res (return $res)) ))) (return $init) )))) (= (interpret $atom $type $space) diff --git a/lib/src/metta/runner/stdlib2.rs b/lib/src/metta/runner/stdlib2.rs index 8ea39ac74..8e5db2455 100644 --- a/lib/src/metta/runner/stdlib2.rs +++ b/lib/src/metta/runner/stdlib2.rs @@ -604,22 +604,22 @@ mod tests { } #[test] - fn metta_filter_out_errors() { - assert_eq!(run_program("!(eval (filter () $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!()]])); - assert_eq!(run_program("!(eval (filter (a (b) $c) $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!("a" ("b") c)]])); - assert_eq!(run_program("!(eval (filter (a (Error (b) \"Test error\") $c) $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!("a" c)]])); + fn metta_filter_atom() { + assert_eq!(run_program("!(eval (filter-atom () $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!()]])); + assert_eq!(run_program("!(eval (filter-atom (a (b) $c) $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!("a" ("b") c)]])); + assert_eq!(run_program("!(eval (filter-atom (a (Error (b) \"Test error\") $c) $x (eval (if-error $x False True))))"), Ok(vec![vec![expr!("a" c)]])); } #[test] - fn metta_map() { - assert_eq!(run_program("!(eval (map () $x ($x mapped)))"), Ok(vec![vec![expr!()]])); - assert_eq!(run_program("!(eval (map (a (b) $c) $x (mapped $x)))"), Ok(vec![vec![expr!(("mapped" "a") ("mapped" ("b")) ("mapped" c))]])); + fn metta_map_atom() { + assert_eq!(run_program("!(eval (map-atom () $x ($x mapped)))"), Ok(vec![vec![expr!()]])); + assert_eq!(run_program("!(eval (map-atom (a (b) $c) $x (mapped $x)))"), Ok(vec![vec![expr!(("mapped" "a") ("mapped" ("b")) ("mapped" c))]])); } #[test] - fn metta_foldl() { - assert_eq!(run_program("!(eval (foldl () 1 $a $b (eval (+ $a $b))))"), Ok(vec![vec![expr!({Number::Integer(1)})]])); - assert_eq!(run_program("!(eval (foldl (1 2 3) 0 $a $b (eval (+ $a $b))))"), Ok(vec![vec![expr!({Number::Integer(6)})]])); + fn metta_foldl_atom() { + assert_eq!(run_program("!(eval (foldl-atom () 1 $a $b (eval (+ $a $b))))"), Ok(vec![vec![expr!({Number::Integer(1)})]])); + assert_eq!(run_program("!(eval (foldl-atom (1 2 3) 0 $a $b (eval (+ $a $b))))"), Ok(vec![vec![expr!({Number::Integer(6)})]])); } #[test] From 1646df9236bf71e1e65f43a80c05a40d16917990 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 24 Oct 2023 16:39:18 +0300 Subject: [PATCH 23/23] Comment out test which has different behavior in minimal MeTTa --- python/tests/scripts/b5_types_prelim.metta | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/tests/scripts/b5_types_prelim.metta b/python/tests/scripts/b5_types_prelim.metta index 8e54a5e98..b98d7e5ea 100644 --- a/python/tests/scripts/b5_types_prelim.metta +++ b/python/tests/scripts/b5_types_prelim.metta @@ -81,12 +81,12 @@ (Cons (S Z) (Cons Z Nil)) ((Cons (S Z) (Cons Z Nil)))) +; TODO: MINIMAL This test has different behavior in old and new versions of the +; interpreter versions. Uncomment it after migration to the minimal MeTTa. ; This list is badly typed, because S and Z are not the same type -!(assertEqualToResult - (Cons S (Cons Z Nil)) - ((Error Z BadType))) - ; TODO: MINIMAL replace test result after migration on minimal MeTTa - ;((Error (Cons Z Nil) BadType))) +;!(assertEqualToResult +; (Cons S (Cons Z Nil)) +; ((Error (Cons Z Nil) BadType))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;