diff --git a/.github/workflows/minimal.yml b/.github/workflows/old_interpreter.yml similarity index 87% rename from .github/workflows/minimal.yml rename to .github/workflows/old_interpreter.yml index b90e9583c..4a18c7374 100644 --- a/.github/workflows/minimal.yml +++ b/.github/workflows/old_interpreter.yml @@ -1,11 +1,11 @@ -# This workflow is intended to run tests on minimal MeTTa interpreter. +# This workflow is intended to run tests on old Rust MeTTa interpreter. # It is indicative and temporary, it doesn't prevent any changes from merging. # This workflow uses actions that are not certified by GitHub. They are # provided by a third-party and are governed by separate terms of service, # privacy policy, and support documentation. -name: minimal +name: old_interpreter on: push: @@ -16,7 +16,7 @@ on: - main jobs: - minimal: + old_interpreter: runs-on: "ubuntu-20.04" steps: @@ -32,13 +32,13 @@ jobs: - name: Build Rust library working-directory: ./lib run: | - cargo check --features minimal - cargo build --features minimal + cargo check --features old_interpreter + cargo build --features old_interpreter - name: Test Rust library working-directory: ./lib run: | - RUST_LOG=hyperon=debug cargo test --features minimal + RUST_LOG=hyperon=debug cargo test --features old_interpreter - name: Install cbindgen uses: actions-rs/cargo@v1.0.1 @@ -106,7 +106,7 @@ jobs: cd build # specify C compiler as conan could not find it automatically # see https://github.com/conan-io/conan/issues/4322 - cmake -DCARGO_ARGS="--features hyperon/minimal" -DCMAKE_BUILD_TYPE=Release -DPython3_EXECUTABLE=`which python` -DCMAKE_C_COMPILER=gcc .. + cmake -DCARGO_ARGS="--features hyperon/old_interpreter" -DCMAKE_BUILD_TYPE=Release -DPython3_EXECUTABLE=`which python` -DCMAKE_C_COMPILER=gcc .. - name: Build C API working-directory: ./build diff --git a/c/src/metta.rs b/c/src/metta.rs index ea41bc3c3..ed925511d 100644 --- a/c/src/metta.rs +++ b/c/src/metta.rs @@ -639,6 +639,16 @@ pub extern "C" fn atom_error_message(atom: *const atom_ref_t, buf: *mut c_char, hyperon::metta::UNIT_ATOM().into() } +/// @brief Creates a Symbol atom for the special MeTTa symbol used to indicate +/// calling MeTTa interpreter. +/// @ingroup metta_language_group +/// @return The `atom_t` representing the interpret atom +/// @note The returned `atom_t` must be freed with `atom_free()` +/// +#[no_mangle] pub extern "C" fn METTA_ATOM() -> atom_t { + hyperon::metta::METTA_SYMBOL.into() +} + /// @brief Checks whether Atom `atom` has Type `typ` in context of `space` /// @ingroup metta_language_group /// @param[in] space A pointer to the `space_t` representing the space context in which to perform the check diff --git a/docs/minimal-metta.md b/docs/minimal-metta.md index 19769768c..a543b777b 100644 --- a/docs/minimal-metta.md +++ b/docs/minimal-metta.md @@ -325,6 +325,23 @@ used on each exit path while nothing in code of function points to this. Using - functions which evaluate result in a loop and have to use `return`; - functions which just replace the calling expression by their bodies. +# MeTTa interpreter written in Rust + +MeTTa interpreter written in minimal MeTTa has poor performance. To fix this +the interpreter is rewritten in Rust. Rust implementation can be called using +`(metta )` operation. To be able represent process of the +interpretation as a list of steps and keep ability to control the inference +`metta` doesn't evaluate passed atom till the end but instead analyses the atom +and returns the plan written in minimal MeTTa. Plan includes steps written as a +Rust functions. These steps are called using `(call_native +)` operation. + +Both `metta` and `call_native` could be written as a grounded operations and be +a part of a standard library. But this requires grounded operations to be able +returning bindings as a results. Returning bindings as results is a nice to +have feature anyway to be able representing any functionality as a grounded +atom. But it is not implemented yet. + # Future work ## Explicit atomspace variable bindings diff --git a/lib/Cargo.toml b/lib/Cargo.toml index a64912a9c..905150100 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -30,7 +30,7 @@ crate-type = ["lib"] default = ["pkg_mgmt"] # Add one of the features below into default list to enable. # See https://doc.rust-lang.org/cargo/reference/features.html#the-features-section -minimal = [] # enables minimal MeTTa interpreter +old_interpreter = [] # enables old Rust interpreter variable_operation = [] # enables evaluation of the expressions which have # a variable on the first position git = ["git2", "pkg_mgmt"] diff --git a/lib/benches/interpreter_minimal.rs b/lib/benches/interpreter_minimal.rs index 5cfdc0427..6e1ef7d11 100644 --- a/lib/benches/interpreter_minimal.rs +++ b/lib/benches/interpreter_minimal.rs @@ -1,5 +1,5 @@ #![feature(test)] -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] mod interpreter_minimal_bench { extern crate test; diff --git a/lib/src/atom/matcher.rs b/lib/src/atom/matcher.rs index 66ac6ba80..142ad81fd 100644 --- a/lib/src/atom/matcher.rs +++ b/lib/src/atom/matcher.rs @@ -1073,10 +1073,24 @@ impl BindingsSet { } } +pub trait BindingsResultIter: Iterator { + fn clone_(&self) -> Box; +} +impl> BindingsResultIter for T { + fn clone_(&self) -> Box { + Box::new(self.clone()) + } +} + /// Iterator over atom matching results. Each result is an instance of [Bindings]. //TODO: A situation where a MatchResultIter returns an unbounded (infinite) number of results // will hang this implementation, on account of `.collect()` -pub type MatchResultIter = Box>; +pub type MatchResultIter = Box; +impl Clone for MatchResultIter { + fn clone(&self) -> Self { + self.clone_() + } +} /// Matches two atoms and returns an iterator over results. Atoms are /// treated symmetrically. diff --git a/lib/src/atom/mod.rs b/lib/src/atom/mod.rs index df1a496f8..0d7cc4d66 100644 --- a/lib/src/atom/mod.rs +++ b/lib/src/atom/mod.rs @@ -60,12 +60,12 @@ /// ``` /// #[macro_use] /// use hyperon::expr; -/// use hyperon::common::MUL; +/// use hyperon::metta::runner::arithmetics::MulOp; /// /// let sym = expr!("A"); /// let var = expr!(x); /// let gnd = expr!({42}); -/// let expr = expr!("=" ("*2" n) ({MUL} n {2})); +/// let expr = expr!("=" ("*2" n) ({MulOp{}} n {2})); /// /// assert_eq!(sym.to_string(), "A"); /// assert_eq!(var.to_string(), "$x"); diff --git a/lib/src/common/arithmetics.rs b/lib/src/common/arithmetics.rs deleted file mode 100644 index b3e434d0b..000000000 --- a/lib/src/common/arithmetics.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::*; -use super::*; - -macro_rules! def_op { - ($x:ident, $o:tt, $e:expr, $t: expr) => { - pub static $x: &Operation = - &Operation{ name: stringify!($o), execute: $e, typ: $t }; - }; -} - -macro_rules! def_bin_op { - ($x:ident, $o:tt, $t:ty, $r:ty) => { - def_op!($x, $o, |_, args| bin_op::<$t,$t,$r>(args, |a, b| a $o b), - concat!("(-> ", stringify!($t), " ", stringify!($t), " ", stringify!($r), ")")); - }; -} - -// TODO: make proper signatures for functions to be compatible with all integer types. -// i32 is kind of simplification for now. -def_bin_op!(SUM, +, i32, i32); -def_bin_op!(SUB, -, i32, i32); -def_bin_op!(MUL, *, i32, i32); - -def_bin_op!(LT, <, i32, bool); -def_bin_op!(GT, >, i32, bool); -def_bin_op!(EQ, ==, i32, bool); - -def_bin_op!(AND, &&, bool, bool); -def_bin_op!(OR, ||, bool, bool); -def_op!(NOT, !, |_, args| unary_op(args, |a: bool| !a), "(-> bool bool)"); - -// TODO: find out correct types for nop and err -def_op!(NOP, nop, |_, _| Ok(vec![]), "(-> ())"); -def_op!(ERR, err, |_, _| Err("Error".into()), "(-> !)"); - -pub static IS_INT: &Operation = &Operation{ - name: "is_int", - execute: |_, args| check_type(args, - // TODO: it is ugly, but I cannot do something more clear without downcasting - |a| is_instance::(a) || is_instance::(a) - || is_instance::(a) || is_instance::(a) - || is_instance::(a) || is_instance::(a) - || is_instance::(a) || is_instance::(a) - ), - typ: "(-> Grounded bool)", -}; - -fn check_type(args: &[Atom], op: fn(&Atom) -> bool) -> Result, ExecError> { - let arg = args.get(0).ok_or_else(|| format!("Unary operation called without arguments"))?; - Ok(vec![Atom::value(op(arg))]) -} - -fn is_instance(arg: &Atom) -> bool -{ - matches!(arg.as_gnd::(), Some(_)) -} - -fn unary_op(args: &[Atom], op: fn(T) -> R) -> Result, ExecError> -where - T: 'static + Copy, - R: AutoGroundedType, -{ - let arg = args.get(0).ok_or_else(|| format!("Unary operation called without arguments"))?; - if let Some(arg) = arg.as_gnd::() { - Ok(vec![Atom::value(op(*arg))]) - } else { - Err(format!("Incorrect type of the unary operation argument: ({})", arg))? - } -} - -fn bin_op(args: &[Atom], op: fn(T1, T2) -> R) -> Result, ExecError> -where - T1: 'static + Copy, - T2: 'static + Copy, - R: AutoGroundedType, -{ - let arg1 = args.get(0).ok_or_else(|| format!("Binary operation called without arguments"))?; - let arg2 = args.get(1).ok_or_else(|| format!("Binary operation called with only argument"))?; - if let (Some(arg1), Some(arg2)) = (arg1.as_gnd::(), arg2.as_gnd::()) { - Ok(vec![Atom::value(op(*arg1, *arg2))]) - } else { - Err(format!("Incorrect type of the binary operation argument: ({}, {})", arg1, arg2))? - } -} - -#[cfg(test)] -mod tests { - #![allow(non_snake_case)] - - use super::*; - use crate::metta::interpreter::*; - use crate::space::grounding::GroundingSpace; - - #[test] - fn test_sum_ints() { - let space = GroundingSpace::new(); - // (+ 3 5) - let expr = expr!({SUM} {3} {5}); - - assert_eq!(interpret(&space, &expr), Ok(vec![Atom::value(8)])); - } - - #[test] - fn test_sum_ints_recursively() { - let space = GroundingSpace::new(); - // (+ 4 (+ 3 5)) - let expr = expr!({SUM} {4} ({SUM} {3} {5})); - - assert_eq!(interpret(&space, &expr), Ok(vec![Atom::value(12)])); - } - - #[test] - fn test_match_factorial() { - let mut space = GroundingSpace::new(); - // (= (fac 0) 1) - space.add(expr!("=" ("fac" {0}) {1})); - // (= (fac n) (* n (fac (- n 1)))) - space.add(expr!("=" ("fac" n) ({MUL} n ("fac" ({SUB} n {1}))))); - - let actual = space.query(&expr!("=" ("fac" {3}) X)); - assert_eq!(actual.len(), 1); - assert_eq!(actual[0].resolve(&VariableAtom::new("X")), - Some(expr!({MUL} {3} ("fac" ({SUB} {3} {1}))))); - } - - #[test] - fn test_factorial() { - let mut space = GroundingSpace::new(); - // NOTE: multiple matches are treated non-deterministically. - // ATM, we don't have means to describe mutually exclusive ordered lists - // of cases for recursive functions typical for FP. This code - // space.add(expr!("=" ("fac" n) ({MUL} n ("fac" ({SUB} n {1}))))); - // space.add(expr!("=" ("fac" {0}) {1})); - // should not work. For now, we have to resort to explicit - // termination conditions: - space.add(expr!(":" "if" ("->" "bool" "Atom" "Atom" "Atom"))); - space.add(expr!("=" ("if" {true} a b) a)); - space.add(expr!("=" ("if" {false} a b) b)); - space.add(expr!("=" ("fac" n) - ("if" ({GT} n {0}) - ({MUL} n ("fac" ({SUB} n {1}))) - {1}))); - - let expr = expr!("fac" {3}); - assert_eq!(interpret(&space, &expr), Ok(vec![Atom::value(6)])); - } -} diff --git a/lib/src/common/mod.rs b/lib/src/common/mod.rs index b4e83cab5..7194640c8 100644 --- a/lib/src/common/mod.rs +++ b/lib/src/common/mod.rs @@ -12,9 +12,6 @@ pub mod owned_or_borrowed; mod flex_ref; pub use flex_ref::FlexRef; -mod arithmetics; -pub use arithmetics::*; - use crate::*; use crate::metta::text::{Tokenizer, SExprParser}; use std::cell::RefCell; diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index 2125ee950..636e76975 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -1,13 +1,12 @@ -//! MeTTa assembly language implementation. -//! -//! # Algorithm -//! -//! TODO: explain an algorithm +//! MeTTa assembly language implementation. See +//! [minimal MeTTa documentation](https://github.com/trueagi-io/hyperon-experimental/blob/main/docs/minimal-metta.md) for details. use crate::*; use crate::atom::matcher::*; use crate::space::*; use crate::metta::*; +use crate::metta::types::*; +use crate::metta::runner::stdlib_minimal::IfEqualOp; use std::fmt::{Debug, Display, Formatter}; use std::convert::TryFrom; @@ -33,6 +32,12 @@ macro_rules! match_atom { }; } +macro_rules! call_native { + ($func:ident, $atom:expr) => { + call_native_atom($func, stringify!($func), $atom) + } +} + /// Operation return handler, it is triggered when nested operation is finished /// and returns its results. First argument gets the reference to the stack /// which on the top has the frame of the wrapping operation. Last two @@ -149,14 +154,20 @@ impl InterpreterContext { } } -// TODO: This wrapper is for compatibility with interpreter.rs only +/// This wrapper is to keep interpreter interface compatible with previous +/// implementation and will be removed in future. +// TODO: MINIMAL: This wrapper is for compatibility with old_interpreter.rs only pub trait SpaceRef<'a> : Space + 'a {} impl<'a, T: Space + 'a> SpaceRef<'a> for T {} +/// State of the interpreter which passed between `interpret_step` calls. #[derive(Debug)] pub struct InterpreterState<'a, T: SpaceRef<'a>> { + /// List of the alternatives to evaluate further. plan: Vec, + /// List of the completely evaluated results to be returned. finished: Vec, + /// Evaluation context. context: InterpreterContext, phantom: std::marker::PhantomData>, } @@ -186,10 +197,13 @@ impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { } } + /// Returns true if there are alternatives which can be evaluated further. pub fn has_next(&self) -> bool { !self.plan.is_empty() } + /// Returns vector of fully evaluated results or error if there are still + /// alternatives to be evaluated. pub fn into_result(self) -> Result, String> { if self.has_next() { Err("Evaluation is not finished".into()) @@ -276,7 +290,9 @@ fn is_embedded_op(atom: &Atom) -> bool { || *op == DECONS_ATOM_SYMBOL || *op == FUNCTION_SYMBOL || *op == COLLAPSE_BIND_SYMBOL - || *op == SUPERPOSE_BIND_SYMBOL, + || *op == SUPERPOSE_BIND_SYMBOL + || *op == METTA_SYMBOL + || *op == CALL_NATIVE_SYMBOL, _ => false, } } @@ -386,6 +402,12 @@ fn interpret_stack<'a, T: Space>(context: &InterpreterContext, stack: Stack, Some([op, ..]) if *op == SUPERPOSE_BIND_SYMBOL => { superpose_bind(stack, bindings) }, + Some([op, ..]) if *op == METTA_SYMBOL => { + interpret_sym(stack, bindings) + }, + Some([op, ..]) if *op == CALL_NATIVE_SYMBOL => { + call_native_symbol(stack, bindings) + }, _ => { let stack = Stack::finished(stack.prev, stack.atom); vec![InterpretedAtom(stack, bindings)] @@ -399,8 +421,12 @@ fn return_not_reducible() -> Atom { NOT_REDUCIBLE_SYMBOL } -fn error_atom(atom: Atom, err: String) -> Atom { - Atom::expr([Atom::sym("Error"), atom, Atom::sym(err)]) +fn error_msg(atom: Atom, err: String) -> Atom { + error_atom(atom, Atom::sym(err)) +} + +fn error_atom(atom: Atom, err: Atom) -> Atom { + Atom::expr([ERROR_SYMBOL, atom, err]) } fn finished_result(atom: Atom, bindings: Bindings, prev: Option>>) -> Vec { @@ -413,9 +439,10 @@ fn eval<'a, T: Space>(context: &InterpreterContext, stack: Stack, bindings: B eval ~ [_op, to_eval] => to_eval, _ => { let error = format!("expected: ({} ), found: {}", EVAL_SYMBOL, eval); - return finished_result(error_atom(eval, error), bindings, prev); + return finished_result(error_msg(eval, error), bindings, prev); } }; + let to_eval = apply_bindings_to_atom_move(to_eval, &bindings); log::debug!("eval: to_eval: {}", to_eval); match atom_as_slice(&to_eval) { Some([Atom::Grounded(op), args @ ..]) => { @@ -444,7 +471,7 @@ fn eval<'a, T: Space>(context: &InterpreterContext, stack: Stack, bindings: B } }, Err(ExecError::Runtime(err)) => - finished_result(error_atom(to_eval, err), bindings, prev), + finished_result(error_msg(to_eval, err), bindings, prev), Err(ExecError::NoReduce) => // TODO: we could remove ExecError::NoReduce and explicitly // return NOT_REDUCIBLE_SYMBOL from the grounded function instead. @@ -557,7 +584,7 @@ fn chain_to_stack(mut atom: Atom, prev: Option>>) -> Stack { Some([_op, nested, Atom::Variable(_var), templ]) => (nested, templ), _ => { let error: String = format!("expected: ({} (: Variable) ), found: {}", CHAIN_SYMBOL, atom); - return Stack::finished(prev, error_atom(atom, error)); + return Stack::finished(prev, error_msg(atom, error)); }, }; std::mem::swap(nested_arg, &mut nested); @@ -600,7 +627,7 @@ fn function_to_stack(mut atom: Atom, prev: Option>>) -> Stack Some([_op, nested @ Atom::Expression(_)]) => nested, _ => { let error: String = format!("expected: ({} (: Expression)), found: {}", FUNCTION_SYMBOL, atom); - return Stack::finished(prev, error_atom(atom, error)); + return Stack::finished(prev, error_msg(atom, error)); }, }; std::mem::swap(nested_arg, &mut nested); @@ -677,7 +704,7 @@ fn unify_to_stack(mut atom: Atom, prev: Option>>) -> Stack { Some([_op, _a, _b, _then, _else]) => (), _ => { let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, atom); - return Stack::finished(prev, error_atom(atom, error)); + return Stack::finished(prev, error_msg(atom, error)); }, }; Stack::from_prev_with_vars(prev, atom, Variables::new(), no_handler) @@ -689,7 +716,7 @@ fn unify(stack: Stack, bindings: Bindings) -> Vec { unify ~ [_op, atom, pattern, then, else_] => (atom, pattern, then, else_), _ => { let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, unify); - return finished_result(error_atom(unify, error), bindings, prev); + return finished_result(error_msg(unify, error), bindings, prev); } }; @@ -720,7 +747,7 @@ fn decons_atom(stack: Stack, bindings: Bindings) -> Vec { decons ~ [_op, Atom::Expression(expr)] if expr.children().len() > 0 => expr, _ => { let error: String = format!("expected: ({} (: Expression)), found: {}", DECONS_ATOM_SYMBOL, decons); - return finished_result(error_atom(decons, error), bindings, prev); + return finished_result(error_msg(decons, error), bindings, prev); } }; let mut children = expr.into_children(); @@ -735,7 +762,7 @@ fn cons_atom(stack: Stack, bindings: Bindings) -> Vec { cons ~ [_op, head, Atom::Expression(tail)] => (head, tail), _ => { let error: String = format!("expected: ({} (: Expression)), found: {}", CONS_ATOM_SYMBOL, cons); - return finished_result(error_atom(cons, error), bindings, prev); + return finished_result(error_msg(cons, error), bindings, prev); } }; let mut children = vec![head]; @@ -770,7 +797,7 @@ fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { superpose ~ [_op, Atom::Expression(collapsed)] => collapsed, _ => { let error: String = format!("expected: ({} (: Expression)), found: {}", SUPERPOSE_BIND_SYMBOL, superpose); - return finished_result(error_atom(superpose, error), bindings, prev); + return finished_result(error_msg(superpose, error), bindings, prev); } }; collapsed.into_children().into_iter() @@ -791,6 +818,419 @@ fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { .collect() } +type NativeFunc = fn(Atom, Bindings) -> MettaResult; + +fn call_native_symbol(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: call, ret: _, finished: _, vars } = stack; + let (name, func, args) = match_atom!{ + call ~ [_op, name, func, args] + if func.as_gnd::().is_some() => (name, func, args), + _ => { + let error = format!("expected: ({} func args), found: {}", CALL_NATIVE_SYMBOL, call); + return finished_result(error_msg(call, error), bindings, prev); + } + }; + + let call_stack = Some(call_to_stack(Atom::expr([name, args.clone()]), vars, prev)); + let func = func.as_gnd::().expect("Unexpected state"); + func(args, bindings) + .map(|(atom, bindings)| InterpretedAtom(atom_to_stack(atom, call_stack.clone()), bindings)) + .collect() +} + +fn interpret_sym(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: interpret, ret: _, finished: _, vars: _ } = stack; + let (atom, typ, space) = match_atom!{ + interpret ~ [_op, atom, typ, space] + if space.as_gnd::().is_some() => (atom, typ, space), + _ => { + let error = format!("expected: ({} atom type space), found: {}", METTA_SYMBOL, interpret); + return finished_result(error_msg(interpret, error), bindings, prev); + } + }; + + vec![InterpretedAtom(atom_to_stack(call_native!(interpret_impl, Atom::expr([atom, typ, space])), prev), bindings)] +} + +trait MettaResultIter: Iterator { + fn clone_(&self) -> Box; +} +impl> MettaResultIter for T { + fn clone_(&self) -> Box { + Box::new(self.clone()) + } +} +type MettaResult = Box; +impl Clone for MettaResult { + fn clone(&self) -> Self { + self.clone_() + } +} + +#[inline] +fn once(atom: Atom, bindings: Bindings) -> MettaResult { + Box::new(std::iter::once((atom, bindings))) +} + +#[inline] +fn empty() -> MettaResult { + Box::new(std::iter::empty()) +} + +#[inline] +fn call_native_atom(func: NativeFunc, name: &str, args: Atom) -> Atom { + function_atom(Atom::expr([CALL_NATIVE_SYMBOL, Atom::sym(name), Atom::value(func), args])) +} + +#[inline] +fn return_atom(atom: Atom) -> Atom { + Atom::expr([RETURN_SYMBOL, atom]) +} + +#[inline] +fn function_atom(atom: Atom) -> Atom { + Atom::expr([FUNCTION_SYMBOL, atom]) +} + +fn interpret_impl(args: Atom, bindings: Bindings) -> MettaResult { + let (atom, typ, space) = match_atom!{ + args ~ [atom, typ, space] + if space.as_gnd::().is_some() => (atom, typ, space), + _ => { + let error = format!("expected args: (atom type space), found: {}", args); + return once(return_atom(error_msg(call_native!(interpret_impl, args), error)), bindings); + } + }; + + let meta = get_meta_type(&atom); + if typ == ATOM_TYPE_ATOM { + once(return_atom(atom), bindings) + } else if typ == meta { + once(return_atom(atom), bindings) + } else { + if meta == ATOM_TYPE_VARIABLE { + once(return_atom(atom), bindings) + } else if meta == ATOM_TYPE_SYMBOL { + type_cast(space, atom, typ, bindings) + } else if meta == ATOM_TYPE_GROUNDED { + type_cast(space, atom, typ, bindings) + } else { + let var = Atom::Variable(VariableAtom::new("x").make_unique()); + let res = Atom::Variable(VariableAtom::new("res").make_unique()); + once(Atom::expr([CHAIN_SYMBOL, Atom::expr([COLLAPSE_BIND_SYMBOL, call_native!(interpret_expression, Atom::expr([atom, typ, space]))]), var.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(check_alternatives, Atom::expr([var])), res.clone(), + return_atom(res) + ]) + ]), bindings) + } + } +} + +fn get_meta_type(atom: &Atom) -> Atom { + match atom { + Atom::Variable(_) => ATOM_TYPE_VARIABLE, + Atom::Symbol(_) => ATOM_TYPE_SYMBOL, + Atom::Expression(_) => ATOM_TYPE_EXPRESSION, + Atom::Grounded(_) => ATOM_TYPE_GROUNDED, + } +} + +fn type_cast(space: Atom, atom: Atom, expected_type: Atom, bindings: Bindings) -> MettaResult { + let meta = get_meta_type(&atom); + if expected_type == meta { + once(return_atom(atom), bindings) + } else { + let space = space.as_gnd::().unwrap(); + let first_match = get_atom_types(space, &atom).into_iter() + .flat_map(|actual_type| match_types(expected_type.clone(), actual_type, Atom::value(true), Atom::value(false), bindings.clone())) + .filter(|(atom, _bindings)| *atom.as_gnd::().unwrap()) + .next(); + match first_match { + Some((_atom, bindings)) => once(return_atom(atom), bindings), + None => once(return_atom(error_atom(atom, BAD_TYPE_SYMBOL)), bindings), + } + } +} + +fn match_types(type1: Atom, type2: Atom, then: Atom, els: Atom, bindings: Bindings) -> MettaResult { + if type1 == ATOM_TYPE_UNDEFINED { + once(then, bindings) + } else if type2 == ATOM_TYPE_UNDEFINED { + once(then, bindings) + } else if type1 == ATOM_TYPE_ATOM { + once(then, bindings) + } else if type2 == ATOM_TYPE_ATOM { + once(then, bindings) + } else { + let mut result = match_atoms(&type1, &type2).peekable(); + if result.peek().is_none() { + once(els, bindings.clone()) + } else { + Box::new(result + .flat_map(move |b| b.merge_v2(&bindings).into_iter()) + .map(move |b| (then.clone(), b))) + } + } +} + +fn check_alternatives(args: Atom, bindings: Bindings) -> MettaResult { + let expr = match_atom!{ + args ~ [Atom::Expression(expr)] => expr, + _ => { + let error = format!("expected args: ((: expr Expression)), found: {}", args); + return once(return_atom(error_msg(call_native!(check_alternatives, args), error)), bindings); + } + }; + let results = expr.into_children().into_iter() + .map(atom_into_atom_bindings); + let mut succ = results.clone() + .filter(|(atom, _bindings)| !atom_is_error(&atom)) + .map(|(atom, bindings)| (return_atom(atom), bindings)) + .peekable(); + let err = results + .filter(|(atom, _bindings)| atom_is_error(&atom)) + .map(|(atom, bindings)| (return_atom(atom), bindings)); + match succ.peek() { + Some(_) => Box::new(succ), + None => Box::new(err), + } +} + +fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { + let (atom, typ, space) = match_atom!{ + args ~ [atom, typ, space] + if space.as_gnd::().is_some() => (atom, typ, space), + _ => { + let error = format!("expected args: (atom type space), found: {}", args); + return once(return_atom(error_msg(call_native!(interpret_expression, args), error)), bindings); + } + }; + match atom_as_slice(&atom) { + Some([op, _args @ ..]) => { + let space_ref = space.as_gnd::().unwrap(); + let mut actual_types = get_atom_types(space_ref, op); + // FIXME: this relies on the fact that get_atom_types() returns + // tuple types first. Either need to sort types or fix behavior + // in get_atom_types contract. + let func_start_index = actual_types.partition_point(|a| !is_func(a)); + let has_func_types = func_start_index < actual_types.len(); + let func_types = actual_types.split_off(func_start_index).into_iter(); + let mut tuple_types = actual_types.into_iter().peekable(); + + let tuple = if tuple_types.peek().is_some() { + let reduced = Atom::Variable(VariableAtom::new("reduced").make_unique()); + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + once( + Atom::expr([CHAIN_SYMBOL, call_native!(interpret_tuple, Atom::expr([atom.clone(), space.clone()])), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ.clone(), space.clone()])), result.clone(), + return_atom(result) + ]) + ]), bindings.clone()) + } else { + empty() + }; + + let func = if has_func_types { + Box::new(func_types.map(move |op_type| { + let reduced = Atom::Variable(VariableAtom::new("reduced").make_unique()); + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + Atom::expr([CHAIN_SYMBOL, call_native!(interpret_function, Atom::expr([atom.clone(), op_type, typ.clone(), space.clone()])), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ.clone(), space.clone()])), result.clone(), + return_atom(result) + ]) + ]) + }) + .map(move |atom| (atom, bindings.clone()))) + } else { + empty() + }; + + Box::new(std::iter::empty().chain(tuple).chain(func)) + }, + _ => type_cast(space, atom, typ, bindings), + } +} + +fn interpret_tuple(args: Atom, bindings: Bindings) -> MettaResult { + let (expr, space) = match_atom!{ + args ~ [Atom::Expression(expr), space] + if space.as_gnd::().is_some() => (expr, space), + _ => { + let error = format!("expected args: ((: expr Expression) space), found: {}", args); + return once(return_atom(error_msg(call_native!(interpret_tuple, args), error)), bindings); + } + }; + if expr.children().is_empty() { + once(return_atom(Atom::Expression(expr)), bindings) + } else { + let mut tuple = expr.into_children(); + let head = tuple.remove(0); + let tail = tuple; + let rhead = Atom::Variable(VariableAtom::new("rhead").make_unique()); + let rtail = Atom::Variable(VariableAtom::new("rtail").make_unique()); + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + once( + Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, head, ATOM_TYPE_UNDEFINED, space.clone()]), rhead.clone(), + Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::gnd(IfEqualOp{}), rhead.clone(), EMPTY_SYMBOL, return_atom(EMPTY_SYMBOL), + Atom::expr([CHAIN_SYMBOL, call_native!(interpret_tuple, Atom::expr([Atom::expr(tail), space.clone()])), rtail.clone(), + Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::gnd(IfEqualOp{}), rtail.clone(), EMPTY_SYMBOL, return_atom(EMPTY_SYMBOL), + Atom::expr([CHAIN_SYMBOL, Atom::expr([CONS_ATOM_SYMBOL, rhead, rtail]), result.clone(), + return_atom(result) + ]) + ])]) + ]) + ])]) + ]), bindings) + } +} + +fn interpret_function(args: Atom, bindings: Bindings) -> MettaResult { + let (atom, op_type, ret_type, space) = match_atom!{ + args ~ [Atom::Expression(atom), Atom::Expression(op_type), ret_type, space] + if space.as_gnd::().is_some() && + op_type.children().get(0) == Some(&ARROW_SYMBOL) => (atom, op_type, ret_type, space), + _ => { + let error = format!("expected args: ((: atom Expression) (: op_type Expression) ret_type space), found: {}", args); + return once(return_atom(error_msg(call_native!(interpret_function, args), error)), bindings); + } + }; + let mut call = atom.clone().into_children(); + let head = call.remove(0); + let args = call; + let mut arg_types = op_type.clone(); + arg_types.children_mut().remove(0); + let arg_types = Atom::Expression(arg_types); + let rop = Atom::Variable(VariableAtom::new("rop").make_unique()); + let rargs = Atom::Variable(VariableAtom::new("rargs").make_unique()); + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + once( + Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, head, Atom::Expression(op_type), space.clone()]), rop.clone(), + call_native!(return_on_error, Atom::expr([rop.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(interpret_args, Atom::expr([Atom::Expression(atom), Atom::expr(args), arg_types, ret_type, space.clone()])), rargs.clone(), + call_native!(return_on_error, Atom::expr([rargs.clone(), + Atom::expr([CHAIN_SYMBOL, Atom::expr([CONS_ATOM_SYMBOL, rop, rargs]), result.clone(), + return_atom(result) + ]) + ])) + ]) + ])) + ]), bindings) +} + +fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { + let (atom, args, arg_types, ret_type, space) = match_atom!{ + args_ ~ [atom, Atom::Expression(args), Atom::Expression(arg_types), ret_type, space] + if space.as_gnd::().is_some() => (atom, args, arg_types, ret_type, space), + _ => { + let error = format!("expected args: (atom (: args Expression) (: arg_types Expression) ret_type space), found: {}", args_); + return once(return_atom(error_msg(call_native!(interpret_args, args_), error)), bindings); + } + }; + let mut types = arg_types.into_children(); + if types.is_empty() { + return once(return_atom(error_atom(atom, INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings); + } + let types_head = types.remove(0); + let types_tail = types; + if args.children().is_empty() { + if types_tail.is_empty() { + match_types(types_head, ret_type, + return_atom(Atom::Expression(args)), + return_atom(error_atom(atom, BAD_TYPE_SYMBOL)), + bindings) + } else { + once(return_atom(error_atom(atom, INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings) + } + } else { + let mut args = args.into_children(); + let args_head = args.remove(0); + let args_tail = args; + let rhead = Atom::Variable(VariableAtom::new("rhead").make_unique()); + let rtail = Atom::Variable(VariableAtom::new("rtail").make_unique()); + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + let recursion = Atom::expr([CHAIN_SYMBOL, call_native!(interpret_args, Atom::expr([atom, Atom::expr(args_tail), Atom::expr(types_tail), ret_type, space.clone()])), rtail.clone(), + call_native!(return_on_error, Atom::expr([rtail.clone(), + Atom::expr([CHAIN_SYMBOL, Atom::expr([CONS_ATOM_SYMBOL, rhead.clone(), rtail.clone()]), result.clone(), + return_atom(result) + ]) + ])) + ]); + once( + Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, args_head.clone(), types_head, space.clone()]), rhead.clone(), + Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::gnd(IfEqualOp{}), rhead.clone(), args_head, + recursion.clone(), + call_native!(return_on_error, Atom::expr([rhead, + recursion + ])) + ])]) + ]), bindings) + } +} + +fn return_on_error(args: Atom, bindings: Bindings) -> MettaResult { + let (atom, then) = match_atom!{ + args ~ [atom, then] => (atom, then), + _ => { + let error = format!("expected args: (atom then), found: {}", args); + return once(return_atom(error_msg(call_native!(return_on_error, args), error)), bindings); + } + }; + if EMPTY_SYMBOL == atom { + once(return_atom(return_atom(EMPTY_SYMBOL)), bindings) + } else if atom_is_error(&atom) { + once(return_atom(return_atom(atom)), bindings) + } else { + once(return_atom(then), bindings) + } +} + +fn metta_call(args: Atom, bindings: Bindings) -> MettaResult { + let (atom, typ, space) = match_atom!{ + args ~ [atom, typ, space] + if space.as_gnd::().is_some() => (atom, typ, space), + _ => { + let error = format!("expected args: (atom type space), found: {}", args); + return once(return_atom(error_msg(call_native!(metta_call, args), error)), bindings); + } + }; + if atom_is_error(&atom) { + once(return_atom(atom), bindings) + } else { + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + let ret = Atom::Variable(VariableAtom::new("ret").make_unique()); + once( + Atom::expr([CHAIN_SYMBOL, Atom::expr([EVAL_SYMBOL, atom.clone()]), result.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call_return, Atom::expr([atom, result, typ, space])), ret.clone(), + return_atom(ret) + ]) + ]), bindings) + } +} + +fn metta_call_return(args: Atom, bindings: Bindings) -> MettaResult { + let (atom, result, typ, space) = match_atom!{ + args ~ [atom, result, typ, space] + if space.as_gnd::().is_some() => (atom, result, typ, space), + _ => { + let error = format!("expected args: (atom result type space), found: {}", args); + return once(return_atom(error_msg(call_native!(metta_call_return, args), error)), bindings); + } + }; + if NOT_REDUCIBLE_SYMBOL == result { + once(return_atom(atom), bindings) + } else if EMPTY_SYMBOL == result { + once(return_atom(EMPTY_SYMBOL), bindings) + } else if atom_is_error(&result) { + once(return_atom(result), bindings) + } else { + let ret = Atom::Variable(VariableAtom::new("ret").make_unique()); + once( + Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, result, typ, space]), ret.clone(), + return_atom(ret) + ]), bindings) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index f2641939a..bf089a947 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -1,9 +1,14 @@ //! Contains MeTTa specific types, constants and functions. pub mod text; -pub mod interpreter; -#[cfg(feature = "minimal")] +#[cfg(feature = "old_interpreter")] +pub mod old_interpreter; +#[cfg(feature = "old_interpreter")] +pub use old_interpreter as interpreter; +#[cfg(not(feature = "old_interpreter"))] pub mod interpreter_minimal; +#[cfg(not(feature = "old_interpreter"))] +pub use interpreter_minimal as interpreter; pub mod types; pub mod runner; @@ -40,7 +45,8 @@ pub const RETURN_SYMBOL : Atom = sym!("return"); pub const COLLAPSE_BIND_SYMBOL : Atom = sym!("collapse-bind"); pub const SUPERPOSE_BIND_SYMBOL : Atom = sym!("superpose-bind"); -pub const INTERPRET_SYMBOL : Atom = sym!("interpret"); +pub const METTA_SYMBOL : Atom = sym!("metta"); +pub const CALL_NATIVE_SYMBOL : Atom = sym!("call-native"); //TODO: convert these from functions to static strcutures, when Atoms are Send+Sync #[allow(non_snake_case)] diff --git a/lib/src/metta/interpreter.rs b/lib/src/metta/old_interpreter.rs similarity index 99% rename from lib/src/metta/interpreter.rs rename to lib/src/metta/old_interpreter.rs index 794a1c2a9..7f00b1e7c 100644 --- a/lib/src/metta/interpreter.rs +++ b/lib/src/metta/old_interpreter.rs @@ -88,7 +88,7 @@ pub struct InterpreterState<'a, T: SpaceRef<'a>> { impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { /// INTERNAL USE ONLY. Create an InterpreterState that is ready to yield results - #[cfg(not(feature = "minimal"))] + #[cfg(feature = "old_interpreter")] pub(crate) fn new_finished(_space: T, results: Vec) -> Self { Self { step_result: StepResult::Return(results.into_iter().map(|atom| InterpretedAtom(atom, Bindings::new())).collect()), @@ -177,6 +177,10 @@ pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterSt } fn interpret_init_internal<'a, T: Space + 'a>(space: T, expr: &Atom) -> StepResult<'a, Results, InterpreterError> { + let expr = match <&[Atom]>::try_from(expr).ok() { + Some([op, atom, _typ, _space]) if *op == METTA_SYMBOL => atom, + _ => expr, + }; let context = InterpreterContextRef::new(space); interpret_as_type_plan(context, InterpretedAtom(expr.clone(), Bindings::new()), diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 27ead5975..842718cdf 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -87,14 +87,11 @@ pub use environment::{Environment, EnvBuilder}; #[macro_use] pub mod stdlib; -#[cfg(not(feature = "minimal"))] use super::interpreter::{interpret, interpret_init, interpret_step, InterpreterState}; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] pub mod stdlib_minimal; -#[cfg(feature = "minimal")] -use super::interpreter_minimal::{interpret, interpret_init, interpret_step, InterpreterState}; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] use stdlib_minimal::*; use stdlib::CoreLibLoader; @@ -443,7 +440,7 @@ impl Metta { } pub fn evaluate_atom(&self, atom: Atom) -> Result, String> { - #[cfg(feature = "minimal")] + #[cfg(not(feature = "old_interpreter"))] let atom = if is_bare_minimal_interpreter(self) { atom } else { @@ -1064,7 +1061,7 @@ impl<'input> RunContext<'_, '_, 'input> { let type_err_exp = Atom::expr([ERROR_SYMBOL, atom, BAD_TYPE_SYMBOL]); self.i_wrapper.interpreter_state = Some(InterpreterState::new_finished(self.module().space().clone(), vec![type_err_exp])); } else { - #[cfg(feature = "minimal")] + #[cfg(not(feature = "old_interpreter"))] let atom = if is_bare_minimal_interpreter(self.metta) { atom } else { @@ -1090,7 +1087,7 @@ impl<'input> RunContext<'_, '_, 'input> { } -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] fn is_bare_minimal_interpreter(metta: &Metta) -> bool { metta.get_setting_string("interpreter") == Some("bare-minimal".into()) } @@ -1183,12 +1180,11 @@ impl<'i> InputStream<'i> { } } -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] fn wrap_atom_by_metta_interpreter(space: DynSpace, atom: Atom) -> Atom { let space = Atom::gnd(space); - let interpret = Atom::expr([Atom::sym("interpret"), atom, ATOM_TYPE_UNDEFINED, space]); - let eval = Atom::expr([EVAL_SYMBOL, interpret]); - eval + let interpret = Atom::expr([METTA_SYMBOL, atom, ATOM_TYPE_UNDEFINED, space]); + interpret } // *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-* diff --git a/lib/src/metta/runner/modules/mod.rs b/lib/src/metta/runner/modules/mod.rs index 400668e2d..b285a9c16 100644 --- a/lib/src/metta/runner/modules/mod.rs +++ b/lib/src/metta/runner/modules/mod.rs @@ -7,12 +7,12 @@ use crate::metta::runner::*; use regex::Regex; -#[cfg(not(feature = "minimal"))] +#[cfg(feature = "old_interpreter")] use super::stdlib::*; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] use super::interpreter_minimal::interpret; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] use super::stdlib_minimal::*; mod mod_names; @@ -777,4 +777,4 @@ mod test { //Also test the case where the inner loader is sucessul, but then the outer loader throws an error. Also make sure neither // module is loaded into the namespace // -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index dbc9cf023..c4b90ff0d 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -1271,7 +1271,7 @@ impl CustomExecute for SubtractionOp { } /// The internal `non_minimal_only_stdlib` module contains code that is never used by the minimal stdlib -#[cfg(not(feature = "minimal"))] +#[cfg(feature = "old_interpreter")] mod non_minimal_only_stdlib { use std::collections::HashSet; use super::*; @@ -1760,7 +1760,7 @@ mod non_minimal_only_stdlib { //TODO: The additional arguments are a temporary hack on account of the way the operation atoms store references // to the runner & module state. https://github.com/trueagi-io/hyperon-experimental/issues/410 - #[cfg(not(feature = "minimal"))] + #[cfg(feature = "old_interpreter")] pub fn register_common_tokens(tref: &mut Tokenizer, tokenizer: Shared, _space: &DynSpace, metta: &Metta) { let match_op = Atom::gnd(MatchOp{}); @@ -1814,7 +1814,7 @@ mod non_minimal_only_stdlib { //TODO: The metta argument is a temporary hack on account of the way the operation atoms store references // to the runner & module state. https://github.com/trueagi-io/hyperon-experimental/issues/410 - #[cfg(not(feature = "minimal"))] + #[cfg(feature = "old_interpreter")] pub fn register_runner_tokens(tref: &mut Tokenizer, _tokenizer: Shared, space: &DynSpace, metta: &Metta) { let capture_op = Atom::gnd(CaptureOp::new(space.clone())); @@ -1859,7 +1859,7 @@ mod non_minimal_only_stdlib { tref.register_token(regex(r"&self"), move |_| { self_atom.clone() }); } - #[cfg(not(feature = "minimal"))] + #[cfg(feature = "old_interpreter")] pub fn register_rust_stdlib_tokens(target: &mut Tokenizer) { let mut rust_tokens = Tokenizer::new(); let tref = &mut rust_tokens; @@ -1912,13 +1912,13 @@ mod non_minimal_only_stdlib { pub static METTA_CODE: &'static str = include_str!("stdlib.metta"); } -#[cfg(not(feature = "minimal"))] +#[cfg(feature = "old_interpreter")] pub use non_minimal_only_stdlib::*; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] use super::stdlib_minimal::*; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] use crate::metta::runner::METTA_CODE; /// Loader to Initialize the corelib module @@ -1963,7 +1963,7 @@ fn mod_space_op() { assert_eq!(result[2], vec![Atom::gnd(stdlib_space)]); } -#[cfg(all(test, not(feature = "minimal")))] +#[cfg(all(test, feature = "old_interpreter"))] mod tests { use super::*; use crate::atom::matcher::atoms_are_equivalent; diff --git a/lib/src/metta/runner/stdlib_minimal.metta b/lib/src/metta/runner/stdlib_minimal.metta index 9a3a04cbe..5e5b4a0e3 100644 --- a/lib/src/metta/runner/stdlib_minimal.metta +++ b/lib/src/metta/runner/stdlib_minimal.metta @@ -72,9 +72,31 @@ (return $template) (chain (eval (switch $atom $tail)) $ret (return $ret)) ))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; MeTTa interpreter implementation ; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; TODO: Type is used here, but there is no definition for the -> type +; constructor for instance, thus in practice it matches because -> has +; %Undefined% type. We need to assign proper type to -> and other type +; constructors but it is not possible until we support vararg types. +(: is-function (-> Type Bool)) +(= (is-function $type) + (function (chain (eval (get-metatype $type)) $meta + (eval (switch ($type $meta) ( + (($type Expression) + (eval (if-decons-expr $type $head $_tail + (unify $head -> (return True) (return False)) + (return (Error (is-function $type) "is-function non-empty expression as an argument")) ))) + (($type $meta) (return False)) + )))))) + +(= (type-cast $atom $type $space) + (function (chain (eval (get-metatype $atom)) $meta + (eval (if-equal $type $meta + (return $atom) + (chain (eval (collapse-bind (eval (get-type $atom $space)))) $collapsed + (chain (eval (map-atom $collapsed $pair (eval (first-from-pair $pair)))) $actual-types + (chain (eval (foldl-atom $actual-types False $a $b (eval (match-type-or $a $b $type)))) $is-some-comp + (eval (if $is-some-comp + (return $atom) + (return (Error $atom BadType)) )))))))))) (= (match-types $type1 $type2 $then $else) (function (eval (if-equal $type1 %Undefined% @@ -98,32 +120,6 @@ (chain (eval (match-types $next $type True False)) $matched (chain (eval (or $folded $matched)) $or (return $or)) ))) -(= (type-cast $atom $type $space) - (function (chain (eval (get-metatype $atom)) $meta - (eval (if-equal $type $meta - (return $atom) - (chain (eval (collapse-bind (eval (get-type $atom $space)))) $collapsed - (chain (eval (map-atom $collapsed $pair (eval (first-from-pair $pair)))) $actual-types - (chain (eval (foldl-atom $actual-types False $a $b (eval (match-type-or $a $b $type)))) $is-some-comp - (eval (if $is-some-comp - (return $atom) - (return (Error $atom BadType)) )))))))))) - -; TODO: Type is used here, but there is no definition for the -> type -; constructor for instance, thus in practice it matches because -> has -; %Undefined% type. We need to assign proper type to -> and other type -; constructors but it is not possible until we support vararg types. -(: is-function (-> Type Bool)) -(= (is-function $type) - (function (chain (eval (get-metatype $type)) $meta - (eval (switch ($type $meta) ( - (($type Expression) - (eval (if-decons-expr $type $head $_tail - (unify $head -> (return True) (return False)) - (return (Error (is-function $type) "is-function non-empty expression as an argument")) ))) - (($type $meta) (return False)) - )))))) - (: filter-atom (-> Expression Variable Atom Expression)) (= (filter-atom $list $var $filter) (function (eval (if-decons-expr $list $head $tail @@ -153,111 +149,6 @@ (chain (eval (foldl-atom $tail $head-folded $a $b $op)) $res (return $res)) ))) (return $init) )))) -(: separate-errors (-> Expression Expression Expression)) -(= (separate-errors $succ-err $res) - (function (unify $succ-err ($suc $err) - (unify $res ($a $_b) - (eval (if-error $a - (chain (cons-atom $res $err) $err' (return ($suc $err'))) - (chain (cons-atom $res $suc) $suc' (return ($suc' $err))) )) - (return $succ-err)) - (return $succ-err) ))) - -(= (check-alternatives $atom) - (function - (chain (collapse-bind $atom) $collapsed - ;(chain (eval (print-alternatives! $atom $collapsed)) $_ - (chain (eval (foldl-atom $collapsed (() ()) $succ-err $res - (eval (separate-errors $succ-err $res)))) $separated - (unify $separated ($success $error) - (chain (eval (if-equal $success () $error $success)) $filtered - (chain (superpose-bind $filtered) $ret (return $ret)) ) - (return (Error (check-alternatives $atom) "list of results was not filtered correctly")) ))))) - -(= (interpret $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 (check-alternatives (eval (interpret-expression $atom $type $space)))) $ret (return $ret))) - )))))))))) - -(= (interpret-expression $atom $type $space) - (function (eval (if-decons-expr $atom $op $args - (chain (eval (get-type $op $space)) $op-type - (chain (eval (is-function $op-type)) $is-func - (unify $is-func True - (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) - (function (eval (if-decons-expr $expr $op $args - (chain (eval (interpret $op $type $space)) $reduced-op - (eval (return-on-error $reduced-op - (eval (if-decons-expr $type $arrow $arg-types - (chain (eval (interpret-args $expr $args $arg-types $ret-type $space)) $reduced-args - (eval (return-on-error $reduced-args - (chain (cons-atom $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) - (function (unify $args () - (eval (if-decons-expr $arg-types $actual-ret-type $type-tail - (chain (eval (== () $type-tail)) $correct-type-len - (eval (if $correct-type-len - (eval (match-types $actual-ret-type $ret-type - (return ()) - (return (Error $atom BadType)) )) - (return (Error $atom IncorrectNumberOfArguments)) ))) - (return (Error $atom IncorrectNumberOfArguments)) )) - (eval (if-decons-expr $args $head $tail - (eval (if-decons-expr $arg-types $head-type $tail-types - (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 - (chain (eval (interpret-args-tail $atom $reduced-head $tail $tail-types $ret-type $space)) $ret (return $ret)) - (eval (return-on-error $reduced-head - (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) - (function (chain (eval (interpret-args $atom $args-tail $args-tail-types $ret-type $space)) $reduced-tail - (eval (return-on-error $reduced-tail - (chain (cons-atom $head $reduced-tail) $ret (return $ret)) ))))) - -(= (interpret-tuple $atom $space) - (function (unify $atom () - (return $atom) - (eval (if-decons-expr $atom $head $tail - (chain (eval (interpret $head %Undefined% $space)) $rhead - (eval (if-equal $rhead Empty (return Empty) - (chain (eval (interpret-tuple $tail $space)) $rtail - (eval (if-equal $rtail Empty (return Empty) - (chain (cons-atom $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) - (function (eval (if-error $atom (return $atom) - (chain (eval $atom) $result - (eval (if-equal $result NotReducible (return $atom) - (eval (if-equal $result Empty (return Empty) - (eval (if-error $result (return $result) - (chain (eval (interpret $result $type $space)) $ret (return $ret)) ))))))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Standard library written in MeTTa ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 58ac73a31..9e0a87b43 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -144,8 +144,9 @@ 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::interpreter_minimal::interpret(space, &expr) + let expr = Atom::expr([METTA_SYMBOL, expr.clone(), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())]); + let result = crate::metta::interpreter::interpret(space, &expr); + result } fn assert_results_equal(actual: &Vec, expected: &Vec, atom: &Atom) -> Result, ExecError> { @@ -379,8 +380,8 @@ impl CustomExecute for CaseOp { // 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 + // cannot use `unify` in a unit test because after interpreting `(chain... (chain + // (metta (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 @@ -673,42 +674,42 @@ mod tests { #[test] fn metta_interpret_single_atom_as_atom() { - let result = run_program("!(eval (interpret A Atom &self))"); + let result = run_program("!(metta A Atom &self)"); assert_eq!(result, Ok(vec![vec![expr!("A")]])); } #[test] fn metta_interpret_single_atom_as_meta_type() { - assert_eq!(run_program("!(eval (interpret A Symbol &self))"), Ok(vec![vec![expr!("A")]])); - assert_eq!(run_program("!(eval (interpret $x Variable &self))"), Ok(vec![vec![expr!(x)]])); - assert_eq!(run_program("!(eval (interpret (A B) Expression &self))"), Ok(vec![vec![expr!("A" "B")]])); - assert_eq!(run_program("!(eval (interpret 42 Grounded &self))"), Ok(vec![vec![expr!({Number::Integer(42)})]])); + assert_eq!(run_program("!(metta A Symbol &self)"), Ok(vec![vec![expr!("A")]])); + assert_eq!(run_program("!(metta $x Variable &self)"), Ok(vec![vec![expr!(x)]])); + assert_eq!(run_program("!(metta (A B) Expression &self)"), Ok(vec![vec![expr!("A" "B")]])); + assert_eq!(run_program("!(metta 42 Grounded &self)"), Ok(vec![vec![expr!({Number::Integer(42)})]])); } #[test] fn metta_interpret_symbol_or_grounded_value_as_type() { - assert_eq!(run_program("(: a A) !(eval (interpret a A &self))"), Ok(vec![vec![expr!("a")]])); - assert_eq!(run_program("(: a A) !(eval (interpret a B &self))"), Ok(vec![vec![expr!("Error" "a" "BadType")]])); - assert_eq!(run_program("!(eval (interpret 42 Number &self))"), Ok(vec![vec![expr!({Number::Integer(42)})]])); + assert_eq!(run_program("(: a A) !(metta a A &self)"), Ok(vec![vec![expr!("a")]])); + assert_eq!(run_program("(: a A) !(metta a B &self)"), Ok(vec![vec![expr!("Error" "a" "BadType")]])); + assert_eq!(run_program("!(metta 42 Number &self)"), Ok(vec![vec![expr!({Number::Integer(42)})]])); } #[test] fn metta_interpret_variable_as_type() { - assert_eq!(run_program("!(eval (interpret $x %Undefined% &self))"), Ok(vec![vec![expr!(x)]])); - assert_eq!(run_program("!(eval (interpret $x SomeType &self))"), Ok(vec![vec![expr!(x)]])); + assert_eq!(run_program("!(metta $x %Undefined% &self)"), Ok(vec![vec![expr!(x)]])); + assert_eq!(run_program("!(metta $x SomeType &self)"), Ok(vec![vec![expr!(x)]])); } #[test] fn metta_interpret_empty_expression_as_type() { - assert_eq!(run_program("!(eval (interpret () %Undefined% &self))"), Ok(vec![vec![expr!(())]])); - assert_eq!(run_program("!(eval (interpret () SomeType &self))"), Ok(vec![vec![expr!(())]])); + assert_eq!(run_program("!(metta () %Undefined% &self)"), Ok(vec![vec![expr!(())]])); + assert_eq!(run_program("!(metta () SomeType &self)"), Ok(vec![vec![expr!(())]])); } #[test] fn metta_interpret_single_atom_as_variable_type() { let result = run_program(" (: S Int) - !(chain (eval (interpret S $t &self)) $res (: $res $t)) + !(chain (metta S $t &self) $res (: $res $t)) "); assert_eq!(result, Ok(vec![vec![expr!(":" "S" "Int")]])); } @@ -720,14 +721,14 @@ mod tests { (: foo (-> T T)) (= (foo $x) $x) (= (bar $x) $x) - !(eval (interpret (foo (bar a)) %Undefined% &self)) + !(metta (foo (bar a)) %Undefined% &self) "); assert_eq!(result, Ok(vec![vec![expr!("a")]])); let result = run_program(" (: b B) (: foo (-> T T)) (= (foo $x) $x) - !(eval (interpret (foo b) %Undefined% &self)) + !(metta (foo b) %Undefined% &self) "); assert_eq!(result, Ok(vec![vec![expr!("Error" "b" "BadType")]])); let result = run_program(" @@ -735,34 +736,34 @@ mod tests { (: Z Nat) (: S (-> Nat Nat)) (: Cons (-> $t (List $t) (List $t))) - !(eval (interpret (Cons S (Cons Z Nil)) %Undefined% &self)) + !(metta (Cons S (Cons Z Nil)) %Undefined% &self) "); assert_eq!(result, Ok(vec![vec![expr!("Error" ("Cons" "Z" "Nil") "BadType")]])); } #[test] fn metta_interpret_tuple() { - assert_eq!(run_program("!(eval (interpret-tuple () &self))"), Ok(vec![vec![expr!(())]])); - assert_eq!(run_program("!(eval (interpret-tuple (a) &self))"), Ok(vec![vec![expr!(("a"))]])); - assert_eq!(run_program("!(eval (interpret-tuple (a b) &self))"), Ok(vec![vec![expr!(("a" "b"))]])); + assert_eq!(run_program("!(metta () %Undefined% &self)"), Ok(vec![vec![expr!(())]])); + assert_eq!(run_program("!(metta (a) %Undefined% &self)"), Ok(vec![vec![expr!(("a"))]])); + assert_eq!(run_program("!(metta (a b) %Undefined% &self)"), Ok(vec![vec![expr!(("a" "b"))]])); assert_eq!(run_program(" (= (foo $x) (bar $x)) (= (bar $x) (baz $x)) (= (baz $x) $x) - !(eval (interpret-tuple ((foo A) (foo B)) &self)) + !(metta ((foo A) (foo B)) %Undefined% &self) "), Ok(vec![vec![expr!("A" "B")]])); } #[test] fn metta_interpret_expression_as_type() { - assert_eq!(run_program("(= (foo $x) $x) !(eval (interpret (foo a) %Undefined% &self))"), Ok(vec![vec![expr!("a")]])); - assert_eq!(run_program("!(eval (interpret (foo a) %Undefined% &self))"), Ok(vec![vec![expr!("foo" "a")]])); - assert_eq!(run_program("!(eval (interpret () SomeType &self))"), Ok(vec![vec![expr!(())]])); + assert_eq!(run_program("(= (foo $x) $x) !(metta (foo a) %Undefined% &self)"), Ok(vec![vec![expr!("a")]])); + assert_eq!(run_program("!(metta (foo a) %Undefined% &self)"), Ok(vec![vec![expr!("foo" "a")]])); + assert_eq!(run_program("!(metta () 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))"); + let result = run_program("(: a A) (: a B) !(metta a %Undefined% &self)"); assert_eq!(result, Ok(vec![vec![expr!("a")]])); } @@ -893,7 +894,7 @@ mod tests { (= (is Tweety yellow) True) (= (is Tweety eats-flies) True) - !(eval (interpret (if (and (is $x croaks) (is $x eats-flies)) (= (is $x frog) True) Empty) %Undefined% &self)) + !(metta (if (and (is $x croaks) (is $x eats-flies)) (= (is $x frog) True) Empty) %Undefined% &self) "; assert_eq!(run_program(program), @@ -907,7 +908,7 @@ mod tests { (= (color) red) (= (color) green) - !(eval (interpret (color) %Undefined% &self)) + !(metta (color) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -921,8 +922,8 @@ mod tests { (= (plus Z $y) $y) (= (plus (S $k) $y) (S (plus $k $y))) - !(eval (interpret (eq (plus Z $n) $n) %Undefined% &self)) - !(eval (interpret (eq (plus (S Z) $n) $n) %Undefined% &self)) + !(metta (eq (plus Z $n) $n) %Undefined% &self) + !(metta (eq (plus (S Z) $n) $n) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -937,7 +938,7 @@ mod tests { (= (a $z) (mynot (b $z))) (= (b d) F) - !(eval (interpret (myif (a $x) $x) %Undefined% &self)) + !(metta (myif (a $x) $x) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -949,7 +950,7 @@ mod tests { let program = " (= (a ($W)) True) - !(eval (interpret (a $W) %Undefined% &self)) + !(metta (a $W) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -961,7 +962,7 @@ mod tests { let program = " (= (b ($x $y)) (c $x $y)) - !(eval (interpret (a (b $a) $x $y) %Undefined% &self)) + !(metta (a (b $a) $x $y) %Undefined% &self) "; let result = run_program(program); @@ -977,7 +978,7 @@ mod tests { (= (foo) bar) (= (bar $x) $x) - !(eval (interpret ((foo) a) %Undefined% &self)) + !(metta ((foo) a) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), Ok(vec![vec![expr!("a")]])); @@ -1000,7 +1001,7 @@ mod tests { (: id_a (-> A A)) (= (id_a $a) $a) - !(eval (interpret (id_a myAtom) %Undefined% &self)) + !(metta (id_a myAtom) %Undefined% &self) "; let metta = Metta::new(Some(EnvBuilder::test_env())); @@ -1011,7 +1012,7 @@ mod tests { Ok(vec![vec![expr!("Error" "myAtom" "BadType")]])); let program2 = " - !(eval (interpret (id_num myAtom) %Undefined% &self)) + !(metta (id_num myAtom) %Undefined% &self) "; assert_eq!(metta.run(SExprParser::new(program2)), @@ -1027,7 +1028,7 @@ mod tests { (: foo (-> A B C)) (= (foo $a $b) c) - !(eval (interpret (foo a b) %Undefined% &self)) + !(metta (foo a b) %Undefined% &self) "; let metta = Metta::new(Some(EnvBuilder::test_env())); @@ -1037,12 +1038,12 @@ mod tests { assert_eq!(metta.run(SExprParser::new(program1)), Ok(vec![vec![expr!("c")]])); - let program2 = "!(eval (interpret (foo a) %Undefined% &self))"; + let program2 = "!(metta (foo a) %Undefined% &self)"; assert_eq!(metta.run(SExprParser::new(program2)), Ok(vec![vec![expr!("Error" ("foo" "a") "IncorrectNumberOfArguments")]])); - let program3 = "!(eval (interpret (foo a b c) %Undefined% &self))"; + let program3 = "!(metta (foo a b c) %Undefined% &self)"; assert_eq!(metta.run(SExprParser::new(program3)), Ok(vec![vec![expr!("Error" ("foo" "a" "b" "c") "IncorrectNumberOfArguments")]])); @@ -1060,8 +1061,9 @@ mod tests { let program = " (= (bar) baz) (= (foo) (bar)) - !(eval (foo)) + !(foo) !(pragma! interpreter bare-minimal) + !(foo) !(eval (foo)) "; @@ -1069,6 +1071,7 @@ mod tests { Ok(vec![ vec![expr!("baz")], vec![UNIT_ATOM()], + vec![expr!(("foo"))], vec![expr!(("bar"))], ])); } diff --git a/lib/tests/types.rs b/lib/tests/types.rs index 482174abd..b76bd05aa 100644 --- a/lib/tests/types.rs +++ b/lib/tests/types.rs @@ -1,24 +1,66 @@ use hyperon::*; -use hyperon::common::*; -use hyperon::metta::interpreter::*; -use hyperon::space::grounding::GroundingSpace; +use hyperon::metta::*; +use hyperon::metta::text::*; +use hyperon::metta::runner::{Metta, EnvBuilder}; +use hyperon::metta::runner::arithmetics::*; #[test] fn test_types_in_metta() { - let mut space = GroundingSpace::new(); - space.add(expr!("=" ("check" (":" n "Int")) ({IS_INT} n))); - space.add(expr!("=" ("check" (":" n "Nat")) ({AND} ("check" (":" n "Int")) ({GT} n {0})))); - space.add(expr!("=" ("if" {true} then else) then)); - space.add(expr!("=" ("if" {false} then else) else)); - space.add(expr!(":" "if" ("->" "bool" "Atom" "Atom" "Atom"))); - space.add(expr!("=" ("fac" n) ("if" ("check" (":" n "Nat")) ("if" ({EQ} n {1}) {1} ({MUL} n ("fac" ({SUB} n {1})))) ({ERR})))); - - assert_eq!(interpret(&space, &expr!("check" (":" {3} "Int"))), Ok(vec![expr!({true})])); - assert_eq!(interpret(&space, &expr!("check" (":" {(-3)} "Int"))), Ok(vec![expr!({true})])); - assert_eq!(interpret(&space, &expr!("check" (":" {3} "Nat"))), Ok(vec![expr!({true})])); - assert_eq!(interpret(&space, &expr!("check" (":" {(-3)} "Nat"))), Ok(vec![expr!({false})])); - assert_eq!(interpret(&space, &expr!("if" ("check" (":" {(3)} "Nat")) "ok" "nok")), Ok(vec![expr!("ok")])); - assert_eq!(interpret(&space, &expr!("if" ("check" (":" {(-3)} "Nat")) "ok" "nok")), Ok(vec![expr!("nok")])); - assert_eq!(interpret(&space, &expr!("fac" {1})), Ok(vec![expr!({1})])); - assert_eq!(interpret(&space, &expr!("fac" {3})), Ok(vec![expr!({6})])); + let program = " + (= (check (: $n Int)) (is-int $n)) + (= (check (: $n Nat)) (and (is-int $n) (> $n 0))) + + (= (fac $n) (if (check (: $n Nat)) + (if (== $n 1) 1 (* $n (fac (- $n 1)))) + (Error (fac $n) BadType) )) + + !(assertEqual (check (: 3 Int)) True) + !(assertEqual (check (: -3 Int)) True) + !(assertEqual (check (: 3 Nat)) True) + !(assertEqual (check (: -3 Nat)) False) + !(assertEqual (fac 1) 1) + !(assertEqual (fac 3) 6) + "; + + let metta = Metta::new(Some(EnvBuilder::test_env())); + metta.tokenizer().borrow_mut().register_token(regex::Regex::new("is-int").unwrap(), |_t| Atom::gnd(IsInt{})); + let result = metta.run(SExprParser::new(program)); + assert_eq!(result, Ok(vec![vec![UNIT_ATOM()], vec![UNIT_ATOM()], vec![UNIT_ATOM()], vec![UNIT_ATOM()], vec![UNIT_ATOM()], vec![UNIT_ATOM()]])); +} + +#[derive(Clone, Debug)] +struct IsInt{} + +impl PartialEq for IsInt { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl std::fmt::Display for IsInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "is-int") + } +} + +impl Grounded for IsInt { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, Atom::sym("Bool")]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } } + +impl CustomExecute for IsInt { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg = args.get(0).ok_or_else(|| format!("Unary operation called without arguments"))?; + let is_int = match arg.as_gnd::() { + Some(Number::Integer(_)) => true, + _ => false, + }; + Ok(vec![Atom::gnd(Bool(is_int))]) + } +} + diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 9bf2c15e0..90a8a3bc5 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -129,6 +129,7 @@ class Atoms: EMPTY = Atom._from_catom(hp.CAtoms.EMPTY) UNIT = Atom._from_catom(hp.CAtoms.UNIT) + METTA = Atom._from_catom(hp.CAtoms.METTA) class GroundedAtom(Atom): """ diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index fc01bcbb5..9587b00f4 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -956,7 +956,8 @@ PYBIND11_MODULE(hyperonpy, m) { py::class_(m, "CAtoms") ADD_ATOM(EMPTY, "Empty") - ADD_ATOM(UNIT, "Unit"); + ADD_ATOM(UNIT, "Unit") + ADD_ATOM(METTA, "metta"); py::class_(m, "CRunContext"); m.def("run_context_init_self_module", [](CRunContext& run_context, CSpace space, char const* resource_dir) { diff --git a/python/tests/test_atom.py b/python/tests/test_atom.py index 411156c3f..8ae1b673a 100644 --- a/python/tests/test_atom.py +++ b/python/tests/test_atom.py @@ -92,8 +92,8 @@ def test_groundingspace_equals(self): def test_interpret(self): space = GroundingSpaceRef() - self.assertEqual(interpret(space, E(x2Atom, ValueAtom(1))), - [ValueAtom(2)]) + x2 = E(Atoms.METTA, E(x2Atom, ValueAtom(1)), AtomType.UNDEFINED, G(space)) + self.assertEqual(interpret(space, x2), [ValueAtom(2)]) def test_grounded_returns_python_value_unwrap_false(self): def x2_op(atom): @@ -102,7 +102,7 @@ def x2_op(atom): expr = E(x2Atom, ValueAtom(1)) space = GroundingSpaceRef() - self.assertEqual(interpret(space, expr), + self.assertEqual(interpret(space, E(Atoms.METTA, expr, AtomType.UNDEFINED, G(space))), [E(S('Error'), expr, S('Grounded operation which is defined using unwrap=False should return atom instead of Python type'))]) def test_grounded_no_return(self): @@ -111,19 +111,17 @@ def print_op(input): print(input) printExpr = E(OperationAtom('print', print_op, type_names=["Atom", "->"], unwrap=False), S("test")) + printExpr = E(Atoms.METTA, printExpr, AtomType.UNDEFINED, G(space)) self.assertTrue(atom_is_error(interpret(space, printExpr)[0])) printExpr = E(OperationAtom('print', print_op, type_names=["Atom", "->"], unwrap=True), ValueAtom("test")) + printExpr = E(Atoms.METTA, printExpr, AtomType.UNDEFINED, G(space)) self.assertEqual(interpret(space, printExpr), [E()]) - def test_plan(self): - space = GroundingSpaceRef() - interpreter = Interpreter(space, E(x2Atom, ValueAtom(1))) - self.assertEqual(str(interpreter.get_step_result()), - "return [(-> int int)] then form alternative plans for expression (*2 1) using types") - def test_no_reduce(self): space = GroundingSpaceRef() - self.assertEqual(interpret(space, E(noReduceAtom, ValueAtom(1))), + expr = E(Atoms.METTA, E(noReduceAtom, ValueAtom(1)), + AtomType.UNDEFINED, G(space)) + self.assertEqual(interpret(space, expr), [E(noReduceAtom, ValueAtom(1))]) def test_match_(self): diff --git a/python/tests/test_examples.py b/python/tests/test_examples.py index 4351beb10..a0e4cfe82 100644 --- a/python/tests/test_examples.py +++ b/python/tests/test_examples.py @@ -15,7 +15,8 @@ def test_grounded_functions(self): # interpreting this target in another space still works, # because substitution '&obj' -> obj is done by metta metta2 = MeTTa(env_builder=Environment.test_env()) - result = interpret(metta2.space(), target) + result = interpret(metta2.space(), E(Atoms.METTA, target, + AtomType.UNDEFINED, G(metta2.space()))) self.assertTrue(obj.called) self.assertEqual(result, [Atoms.UNIT]) # But it will not work if &obj is parsed in another space @@ -89,7 +90,8 @@ def test_new_object(self): # Now we try to change the grounded atom value directly # (equivalent to metta.run but keeping target) target = metta.parse_single('((py-dot (SetAtom ploc 5) latom))') - interpret(metta.space(), target) + interpret(metta.space(), E(Atoms.METTA, target, AtomType.UNDEFINED, + G(metta.space()))) t = target.get_children()[0] # unwrap outermost brackets # "ploc" value in the "target" is changed self.assertEqual(t.get_children()[1].get_children()[1].get_object().value, 5) diff --git a/repl/Cargo.toml b/repl/Cargo.toml index adb18d6c6..a9551a4e1 100644 --- a/repl/Cargo.toml +++ b/repl/Cargo.toml @@ -23,5 +23,5 @@ path = "src/main.rs" default = ["python"] no_python = ["hyperon"] python = ["pyo3", "pep440_rs"] -minimal = ["hyperon/minimal", "no_python"] #WARNING: The interpreter belonging to the hyperon python module will be used if hyperon is run through python +old_interpreter = ["hyperon/old_interpreter"] git = ["hyperon/git"]