From 1c122ce58c8559992e4e78fcaed435ac27f36140 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 21 Jun 2024 14:17:51 +0300 Subject: [PATCH 01/26] Add minimal MeTTa with Rust interpreter feature --- lib/Cargo.toml | 4 +- lib/benches/interpreter_minimal.rs | 3 + lib/src/metta/interpreter_minimal_rust.rs | 1286 +++++++++++++++++++++ lib/src/metta/mod.rs | 4 +- lib/src/metta/runner/mod.rs | 4 +- lib/src/metta/runner/modules/mod.rs | 6 +- lib/src/metta/runner/stdlib_minimal.rs | 6 +- 7 files changed, 1307 insertions(+), 6 deletions(-) create mode 100644 lib/src/metta/interpreter_minimal_rust.rs diff --git a/lib/Cargo.toml b/lib/Cargo.toml index a64912a9c..f7e037cb6 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -27,10 +27,12 @@ path = "src/lib.rs" crate-type = ["lib"] [features] -default = ["pkg_mgmt"] +default = ["minimal", "minimal_rust", "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 +old_interpreter = [] # enables minimal MeTTa interpreter minimal = [] # enables minimal MeTTa interpreter +minimal_rust = [] # enables minimal MeTTa interpreter with 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..36cfaf6c3 100644 --- a/lib/benches/interpreter_minimal.rs +++ b/lib/benches/interpreter_minimal.rs @@ -9,7 +9,10 @@ use test::Bencher; use hyperon::*; use hyperon::space::grounding::*; use hyperon::metta::*; +#[cfg(not(feature = "minimal_rust"))] use hyperon::metta::interpreter_minimal::*; +#[cfg(feature = "minimal_rust")] +use hyperon::metta::interpreter_minimal_rust::*; fn chain_atom(size: isize) -> Atom { let mut atom = Atom::expr([CHAIN_SYMBOL, Atom::sym("A"), Atom::var("x"), Atom::var("x")]); diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs new file mode 100644 index 000000000..2125ee950 --- /dev/null +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -0,0 +1,1286 @@ +//! MeTTa assembly language implementation. +//! +//! # Algorithm +//! +//! TODO: explain an algorithm + +use crate::*; +use crate::atom::matcher::*; +use crate::space::*; +use crate::metta::*; + +use std::fmt::{Debug, Display, Formatter}; +use std::convert::TryFrom; +use std::rc::Rc; +use std::fmt::Write; +use std::cell::RefCell; + +macro_rules! match_atom { + ($atom:tt ~ $pattern:tt => $succ:tt , _ => $error:tt) => { + match_atom!{ $atom ~ $pattern if true => $succ , _ => $error } + }; + ($atom:tt ~ $pattern:tt if $cond:expr => $succ:tt , _ => $error:tt) => { + match atom_as_slice(&$atom) { + #[allow(unused_variables)] + Some($pattern) if $cond => { + match atom_into_array($atom) { + Some($pattern) => $succ, + _ => panic!("Unexpected state"), + } + } + _ => $error, + } + }; +} + +/// 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 +/// arguments are the result of the nested operation. Handler returns +/// None when it is not ready to provide new stack (it can happen in case of +/// collapse-bind operation) or new stack with variable bindings to continue +/// execution of the program. +type ReturnHandler = fn(Rc>, Atom, Bindings) -> Option<(Stack, Bindings)>; + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +struct Stack { + // Internal mutability is required to implement collapse-bind. All alternatives + // reference the same collapse-bind Stack instance. When some alternative + // finishes it modifies the collapse-bind state adding the result to the + // collapse-bind list of results. + // TODO: Try representing Option via Stack::Bottom + prev: Option>>, + atom: Atom, + ret: ReturnHandler, + // TODO: Could it be replaced by calling a return handler when setting the flag? + finished: bool, + vars: Variables, +} + +fn no_handler(_stack: Rc>, _atom: Atom, _bindings: Bindings) -> Option<(Stack, Bindings)> { + panic!("Unexpected state"); +} + +impl Stack { + fn from_prev_with_vars(prev: Option>>, atom: Atom, vars: Variables, ret: ReturnHandler) -> Self { + Self{ prev, atom, ret, finished: false, vars } + } + + fn from_prev_keep_vars(prev: Option>>, atom: Atom, ret: ReturnHandler) -> Self { + let vars = Self::vars_copy(&prev); + Self{ prev, atom, ret, finished: false, vars } + } + + fn finished(prev: Option>>, atom: Atom) -> Self { + Self{ prev, atom, ret: no_handler, finished: true, vars: Variables::new() } + } + + fn len(&self) -> usize { + self.fold(0, |len, _stack| len + 1) + } + + // TODO: should it be replaced by Iterator implementation? + fn fold T>(&self, mut val: T, mut app: F) -> T { + val = app(val, self); + match &self.prev { + None => val, + Some(prev) => prev.borrow().fold(val, app), + } + } + + fn vars_copy(prev: &Option>>) -> Variables { + match prev { + Some(prev) => prev.borrow().vars.clone(), + None => Variables::new(), + } + } + + fn add_vars_it<'a, I: 'a + Iterator>(prev: &Option>>, vars: I) -> Variables { + match prev { + Some(prev) => prev.borrow().vars.clone().insert_all(vars), + None => vars.cloned().collect(), + } + } +} + +impl Display for Stack { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + fn print_level(buffer: &mut String, level: usize, last: bool, stack: &Stack) -> std::fmt::Result { + let prefix = if last { "=> " } else { " " }; + let ret = if stack.finished { "return " } else { "" }; + write!(buffer, "{}{:05} {}{} {}\n", prefix, level, ret, stack.atom, stack.vars) + } + + let buffer = &mut String::new(); + let last_level = self.len(); + let res = print_level(buffer, last_level, true, self); + self.prev.as_ref().map_or(res, |prev| { + prev.borrow().fold((res, last_level - 1), |(res, level), top| { + (res.and_then(|_| print_level(buffer, level, false, top)), level - 1) + }).0 + }) + .and_then(|_| write!(f, "{}", buffer)) + } +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +struct InterpretedAtom(Stack, Bindings); + +impl Display for InterpretedAtom { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + if self.1.is_empty() { + write!(f, "{}", self.0) + } else { + write!(f, "{}\n{}", self.1, self.0) + } + } +} + +#[derive(Debug)] +struct InterpreterContext { + space: T, +} + +impl InterpreterContext { + fn new(space: T) -> Self { + Self{ space } + } +} + +// TODO: This wrapper is for compatibility with interpreter.rs only +pub trait SpaceRef<'a> : Space + 'a {} +impl<'a, T: Space + 'a> SpaceRef<'a> for T {} + +#[derive(Debug)] +pub struct InterpreterState<'a, T: SpaceRef<'a>> { + plan: Vec, + finished: Vec, + context: InterpreterContext, + phantom: std::marker::PhantomData>, +} + +fn atom_as_slice(atom: &Atom) -> Option<&[Atom]> { + <&[Atom]>::try_from(atom).ok() +} + +fn atom_as_slice_mut(atom: &mut Atom) -> Option<&mut [Atom]> { + <&mut [Atom]>::try_from(atom).ok() +} + +fn atom_into_array(atom: Atom) -> Option<[Atom; N]> { + <[Atom; N]>::try_from(atom).ok() +} + +impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { + + /// INTERNAL USE ONLY. Create an InterpreterState that is ready to yield results + #[allow(dead_code)] //TODO: MINIMAL only silence the warning until interpreter_minimal replaces interpreter + pub(crate) fn new_finished(space: T, results: Vec) -> Self { + Self { + plan: vec![], + finished: results, + context: InterpreterContext::new(space), + phantom: std::marker::PhantomData, + } + } + + pub fn has_next(&self) -> bool { + !self.plan.is_empty() + } + + pub fn into_result(self) -> Result, String> { + if self.has_next() { + Err("Evaluation is not finished".into()) + } else { + Ok(self.finished) + } + } + + fn pop(&mut self) -> Option { + self.plan.pop() + } + + fn push(&mut self, atom: InterpretedAtom) { + if atom.0.prev.is_none() && atom.0.finished { + let InterpretedAtom(stack, bindings) = atom; + if stack.atom != EMPTY_SYMBOL { + let atom = apply_bindings_to_atom_move(stack.atom, &bindings); + self.finished.push(atom); + } + } else { + self.plan.push(atom); + } + } +} + +impl<'a, T: SpaceRef<'a>> std::fmt::Display for InterpreterState<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}\n", self.plan) + } +} + +/// Initialize interpreter and returns the starting interpreter state. +/// See [crate::metta::interpreter_minimal] for algorithm explanation. +/// +/// # Arguments +/// * `space` - atomspace to query for interpretation +/// * `expr` - atom to interpret +pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterState<'a, T> { + let context = InterpreterContext::new(space); + InterpreterState { + plan: vec![InterpretedAtom(atom_to_stack(expr.clone(), None), Bindings::new())], + finished: vec![], + context, + phantom: std::marker::PhantomData, + } +} + +/// Perform next step of the interpretation return the resulting interpreter +/// state. See [crate::metta::interpreter_minimal] for algorithm explanation. +/// +/// # Arguments +/// * `state` - interpreter state from the previous step. +pub fn interpret_step<'a, T: Space + 'a>(mut state: InterpreterState<'a, T>) -> InterpreterState<'a, T> { + let interpreted_atom = state.pop().unwrap(); + log::debug!("interpret_step:\n{}", interpreted_atom); + let InterpretedAtom(stack, bindings) = interpreted_atom; + for result in interpret_stack(&state.context, stack, bindings) { + state.push(result); + } + state +} + +/// Interpret passed atom and return a new plan, result or error. This function +/// blocks until result is calculated. For step by step interpretation one +/// should use [interpret_init] and [interpret_step] functions. +/// # Arguments +/// * `space` - atomspace to query for interpretation +/// * `expr` - atom to interpret +pub fn interpret(space: T, expr: &Atom) -> Result, String> { + let mut state = interpret_init(space, expr); + while state.has_next() { + state = interpret_step(state); + } + state.into_result() +} + +fn is_embedded_op(atom: &Atom) -> bool { + let expr = atom_as_slice(&atom); + match expr { + Some([op, ..]) => *op == EVAL_SYMBOL + || *op == CHAIN_SYMBOL + || *op == UNIFY_SYMBOL + || *op == CONS_ATOM_SYMBOL + || *op == DECONS_ATOM_SYMBOL + || *op == FUNCTION_SYMBOL + || *op == COLLAPSE_BIND_SYMBOL + || *op == SUPERPOSE_BIND_SYMBOL, + _ => false, + } +} + +fn is_op(atom: &Atom, op: &Atom) -> bool { + let expr = atom_as_slice(&atom); + match expr { + Some([opp, ..]) => opp == op, + _ => false, + } +} + +fn is_function_op(atom: &Atom) -> bool { + is_op(atom, &FUNCTION_SYMBOL) +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +struct Variables(im::HashSet); + +impl Variables { + fn new() -> Self { + Self(im::HashSet::new()) + } + fn insert(&mut self, var: VariableAtom) -> Option { + self.0.insert(var) + } + fn insert_all<'a, I: 'a + Iterator>(mut self, it: I) -> Self { + it.for_each(|var| { self.insert(var.clone()); }); + self + } +} +fn vars_from_atom(atom: &Atom) -> impl Iterator { + atom.iter().filter_type::<&VariableAtom>() +} + +impl FromIterator for Variables { + fn from_iter>(iter: I) -> Self { + Self(im::HashSet::from_iter(iter)) + } +} + +impl VariableSet for Variables { + type Iter<'a> = im::hashset::Iter<'a, atom::VariableAtom> where Self: 'a; + + fn contains(&self, var: &VariableAtom) -> bool { + self.0.contains(var) + } + fn iter(&self) -> Self::Iter<'_> { + self.0.iter() + } +} + +impl Display for Variables { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "[") + .and_then(|_| self.iter().take(1).fold(Ok(()), + |res, atom| res.and_then(|_| write!(f, "{}", atom)))) + .and_then(|_| self.iter().skip(1).fold(Ok(()), + |res, atom| res.and_then(|_| write!(f, " {}", atom)))) + .and_then(|_| write!(f, "]")) + } +} + +fn interpret_stack<'a, T: Space>(context: &InterpreterContext, stack: Stack, mut bindings: Bindings) -> Vec { + if stack.finished { + // first executed minimal operation returned error + if stack.prev.is_none() { + return vec![InterpretedAtom(stack, bindings)]; + } + let Stack{ prev, mut atom, ret: _, finished: _, vars: _ } = stack; + let prev = match prev { + Some(prev) => prev, + None => panic!("Unexpected state"), + }; + { + let outer_vars = &prev.borrow().vars; + bindings.apply_and_retain(&mut atom, |v| outer_vars.contains(v)); + } + let ret = prev.borrow().ret; + ret(prev, atom, bindings) + .map_or(vec![], |(stack, bindings)| vec![InterpretedAtom(stack, bindings)]) + } else { + let expr = atom_as_slice(&stack.atom); + let result = match expr { + Some([op, ..]) if *op == EVAL_SYMBOL => { + eval(context, stack, bindings) + }, + Some([op, ..]) if *op == CHAIN_SYMBOL => { + chain(stack, bindings) + }, + Some([op, ..]) if *op == FUNCTION_SYMBOL => { + panic!("Unexpected state") + }, + Some([op, ..]) if *op == COLLAPSE_BIND_SYMBOL => { + collapse_bind(stack, bindings) + }, + Some([op, ..]) if *op == UNIFY_SYMBOL => { + unify(stack, bindings) + }, + Some([op, ..]) if *op == DECONS_ATOM_SYMBOL => { + decons_atom(stack, bindings) + }, + Some([op, ..]) if *op == CONS_ATOM_SYMBOL => { + cons_atom(stack, bindings) + }, + Some([op, ..]) if *op == SUPERPOSE_BIND_SYMBOL => { + superpose_bind(stack, bindings) + }, + _ => { + let stack = Stack::finished(stack.prev, stack.atom); + vec![InterpretedAtom(stack, bindings)] + }, + }; + result + } +} + +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 finished_result(atom: Atom, bindings: Bindings, prev: Option>>) -> Vec { + vec![InterpretedAtom(Stack::finished(prev, atom), bindings)] +} + +fn eval<'a, T: Space>(context: &InterpreterContext, stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: eval, ret: _, finished: _, vars } = stack; + let to_eval = match_atom!{ + eval ~ [_op, to_eval] => to_eval, + _ => { + let error = format!("expected: ({} ), found: {}", EVAL_SYMBOL, eval); + return finished_result(error_atom(eval, error), bindings, prev); + } + }; + log::debug!("eval: to_eval: {}", to_eval); + match atom_as_slice(&to_eval) { + Some([Atom::Grounded(op), args @ ..]) => { + match op.as_grounded().as_execute() { + None => finished_result(return_not_reducible(), bindings, prev), + Some(executable) => { + let exec_res = executable.execute(args); + log::debug!("eval: execution results: {:?}", exec_res); + match exec_res { + Ok(results) => { + if results.is_empty() { + // There is no valid reason to return empty result from + // the grounded function. If alternative should be removed + // from the plan then EMPTY_SYMBOL is a proper result. + // If grounded atom returns no value then UNIT_ATOM() + // should be returned. NotReducible or Exec::NoReduce + // can be returned to let a caller know that function + // is not defined on a passed input data. Thus we can + // interpreter empty result by any way we like. + finished_result(EMPTY_SYMBOL, bindings, prev) + } else { + let call_stack = call_to_stack(to_eval, vars, prev.clone()); + results.into_iter() + .map(|res| eval_result(prev.clone(), res, &call_stack, bindings.clone())) + .collect() + } + }, + Err(ExecError::Runtime(err)) => + finished_result(error_atom(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. + finished_result(return_not_reducible(), bindings, prev), + } + }, + } + }, + _ if is_embedded_op(&to_eval) => + vec![InterpretedAtom(atom_to_stack(to_eval, prev), bindings)], + _ => query(&context.space, prev, to_eval, bindings, vars), + } +} + +fn eval_result(prev: Option>>, res: Atom, call_stack: &Rc>, mut bindings: Bindings) -> InterpretedAtom { + let stack = if is_function_op(&res) { + let mut stack = function_to_stack(res, Some(call_stack.clone())); + let call_stack = call_stack.borrow(); + // Apply arguments bindings is required to replace formal argument + // variables by matched actual argument variables. Otherwise the + // equality of formal and actual argument variables will be cleaned up + // on any return from the nested function call. + // TODO: we could instead add formal argument variables of the called + // functions into stack.vars collection. One way of doing it is getting + // list of formal var parameters from the function definition atom + // but we don't have it returned from the query. Another way is to + // find all variables equalities in bindings with variables + // from call_stack.vars. + bindings.apply_and_retain(&mut stack.atom, |v| call_stack.vars.contains(v)); + stack + } else { + Stack::finished(prev, res) + }; + InterpretedAtom(stack, bindings) +} + +fn call_to_stack(call: Atom, vars: Variables, prev: Option>>) -> Rc> { + let vars = vars.insert_all(vars_from_atom(&call)); + let stack = Stack::from_prev_with_vars(prev, call, vars, call_ret); + Rc::new(RefCell::new(stack)) +} + +#[cfg(not(feature = "variable_operation"))] +fn is_variable_op(atom: &Atom) -> bool { + match atom { + Atom::Expression(expr) => { + match expr.children().get(0) { + Some(Atom::Variable(_)) => true, + _ => false, + } + }, + _ => false, + } +} + +fn query<'a, T: Space>(space: T, prev: Option>>, to_eval: Atom, bindings: Bindings, vars: Variables) -> Vec { + #[cfg(not(feature = "variable_operation"))] + if is_variable_op(&to_eval) { + // TODO: This is a hotfix. Better way of doing this is adding + // a function which modifies minimal MeTTa interpreter code + // in order to skip such evaluations in metta-call function. + return finished_result(return_not_reducible(), bindings, prev) + } + let var_x = &VariableAtom::new("X").make_unique(); + let query = Atom::expr([EQUAL_SYMBOL, to_eval.clone(), Atom::Variable(var_x.clone())]); + let results = space.query(&query); + if results.is_empty() { + finished_result(return_not_reducible(), bindings, prev) + } else { + log::debug!("interpreter_minimal::query: query: {}", query); + log::debug!("interpreter_minimal::query: results.len(): {}, bindings.len(): {}, results: {} bindings: {}", + results.len(), bindings.len(), results, bindings); + let call_stack = call_to_stack(to_eval, vars, prev.clone()); + let result = |res, bindings| eval_result(prev.clone(), res, &call_stack, bindings); + results.into_iter().flat_map(|b| { + log::debug!("interpreter_minimal::query: b: {}", b); + b.merge_v2(&bindings).into_iter() + }).filter_map(move |b| { + let res = b.resolve(&var_x).unwrap(); + if b.has_loops() { + None + } else { + Some(result(res, b)) + } + }) + .collect() + } +} + +fn atom_to_stack(atom: Atom, prev: Option>>) -> Stack { + let expr = atom_as_slice(&atom); + let result = match expr { + Some([op, ..]) if *op == CHAIN_SYMBOL => + chain_to_stack(atom, prev), + Some([op, ..]) if *op == FUNCTION_SYMBOL => + function_to_stack(atom, prev), + Some([op, ..]) if *op == EVAL_SYMBOL => + Stack::from_prev_keep_vars(prev, atom, no_handler), + Some([op, ..]) if *op == UNIFY_SYMBOL => + unify_to_stack(atom, prev), + _ => + Stack::from_prev_keep_vars(prev, atom, no_handler), + }; + result +} + +fn chain_to_stack(mut atom: Atom, prev: Option>>) -> Stack { + let mut nested = Atom::sym("%Nested%"); + let (nested_arg, templ_arg) = match atom_as_slice_mut(&mut atom) { + 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)); + }, + }; + std::mem::swap(nested_arg, &mut nested); + let nested_vars: im::HashSet<&VariableAtom> = vars_from_atom(&nested).collect(); + let templ_vars: im::HashSet<&VariableAtom> = vars_from_atom(templ_arg).collect(); + let both_vars = nested_vars.intersection(templ_vars).into_iter(); + let vars = Stack::add_vars_it(&prev, both_vars); + let cur = Stack::from_prev_with_vars(prev, atom, vars, chain_ret); + atom_to_stack(nested, Some(Rc::new(RefCell::new(cur)))) +} + +fn chain_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { + let mut stack = (*stack.borrow()).clone(); + let nested = atom; + let Stack{ prev: _, atom: chain, ret: _, finished: _, vars: _} = &mut stack; + let arg = match atom_as_slice_mut(chain) { + Some([_op, nested, Atom::Variable(_var), _templ]) => nested, + _ => panic!("Unexpected state"), + }; + *arg = nested; + Some((stack, bindings)) +} + +fn chain(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: chain, ret: _, finished: _, vars: _} = stack; + let (nested, var, templ) = match_atom!{ + chain ~ [_op, nested, Atom::Variable(var), templ] => (nested, var, templ), + _ => { + panic!("Unexpected state") + } + }; + let b = Bindings::new().add_var_binding_v2(var, nested).unwrap(); + let templ = apply_bindings_to_atom_move(templ, &b); + vec![InterpretedAtom(atom_to_stack(templ, prev), bindings)] +} + +fn function_to_stack(mut atom: Atom, prev: Option>>) -> Stack { + let mut nested = Atom::sym("%Nested%"); + let nested_arg = match atom_as_slice_mut(&mut atom) { + Some([_op, nested @ Atom::Expression(_)]) => nested, + _ => { + let error: String = format!("expected: ({} (: Expression)), found: {}", FUNCTION_SYMBOL, atom); + return Stack::finished(prev, error_atom(atom, error)); + }, + }; + std::mem::swap(nested_arg, &mut nested); + let cur = Stack::from_prev_keep_vars(prev, atom, function_ret); + atom_to_stack(nested, Some(Rc::new(RefCell::new(cur)))) +} + +fn call_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { + let stack = Stack::finished(stack.borrow().prev.clone(), atom); + Some((stack, bindings)) +} + +fn function_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { + match_atom!{ + atom ~ [op, result] if *op == RETURN_SYMBOL => { + let stack = Stack::finished(stack.borrow().prev.clone(), result); + Some((stack, bindings)) + }, + _ => { + Some((atom_to_stack(atom, Some(stack)), bindings)) + } + } +} + +fn collapse_bind(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: mut collapse, ret: _, finished: _, vars } = stack; + + let mut nested = Atom::expr([]); + match &mut collapse { + Atom::Expression(expr) => { + std::mem::swap(&mut nested, &mut expr.children_mut()[1]); + expr.children_mut().push(Atom::value(bindings.clone())) + }, + _ => panic!("Unexpected state"), + } + + let prev = Stack::from_prev_with_vars(prev, collapse, vars, collapse_bind_ret); + let cur = atom_to_stack(nested, Some(Rc::new(RefCell::new(prev)))); + vec![InterpretedAtom(cur, bindings)] +} + +fn collapse_bind_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { + let nested = atom; + { + let stack_ref = &mut *stack.borrow_mut(); + let Stack{ prev: _, atom: collapse, ret: _, finished: _, vars: _ } = stack_ref; + let finished = match atom_as_slice_mut(collapse) { + Some([_op, Atom::Expression(finished), _bindings]) => finished, + _ => panic!("Unexpected state"), + }; + finished.children_mut().push(atom_bindings_into_atom(nested, bindings)); + } + + // all alternatives are evaluated + match Rc::into_inner(stack).map(RefCell::into_inner) { + Some(stack) => { + let Stack{ prev, atom: collapse, ret: _, finished: _, vars: _ } = stack; + let (result, bindings) = match atom_into_array(collapse) { + Some([_op, result, bindings]) => (result, atom_into_bindings(bindings)), + None => panic!("Unexpected state"), + }; + Some((Stack::finished(prev, result), bindings)) + }, + None => None, + } +} + +fn atom_bindings_into_atom(atom: Atom, bindings: Bindings) -> Atom { + Atom::expr([atom, Atom::value(bindings)]) +} + +fn unify_to_stack(mut atom: Atom, prev: Option>>) -> Stack { + let () = match atom_as_slice_mut(&mut atom) { + Some([_op, _a, _b, _then, _else]) => (), + _ => { + let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, atom); + return Stack::finished(prev, error_atom(atom, error)); + }, + }; + Stack::from_prev_with_vars(prev, atom, Variables::new(), no_handler) +} + +fn unify(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: unify, ret: _, finished: _, vars: _ } = stack; + let (atom, pattern, then, else_) = match_atom!{ + 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); + } + }; + + let matches: Vec = match_atoms(&atom, &pattern).collect(); + if matches.is_empty() { + finished_result(else_, bindings, prev) + } else { + let result = |bindings| { + let stack = Stack::finished(prev.clone(), then.clone()); + InterpretedAtom(stack, bindings) + }; + matches.into_iter().flat_map(move |b| { + b.merge_v2(&bindings).into_iter().filter_map(move |b| { + if b.has_loops() { + None + } else { + Some(result(b)) + } + }) + }) + .collect() + } +} + +fn decons_atom(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: decons, ret: _, finished: _, vars: _ } = stack; + let expr = match_atom!{ + 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); + } + }; + let mut children = expr.into_children(); + let head = children.remove(0); + let tail = children; + finished_result(Atom::expr([head, Atom::expr(tail)]), bindings, prev) +} + +fn cons_atom(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: cons, ret: _, finished: _, vars: _ } = stack; + let (head, tail) = match_atom!{ + 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); + } + }; + let mut children = vec![head]; + children.extend(tail.into_children()); + finished_result(Atom::expr(children), bindings, prev) +} + +fn atom_into_atom_bindings(pair: Atom) -> (Atom, Bindings) { + match_atom!{ + pair ~ [atom, bindings] => (atom, atom_into_bindings(bindings)), + _ => { + panic!("(Atom Bindings) pair is expected, {} was received", pair) + } + } +} + +fn atom_into_bindings(bindings: Atom) -> Bindings { + match bindings.as_gnd::() { + Some(bindings) => { + // TODO: cloning is ineffective, but it is not possible + // to convert grounded atom into internal value at the + // moment + bindings.clone() + }, + _ => panic!("Unexpected state: second item cannot be converted to Bindings"), + } +} + +fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: superpose, ret: _, finished: _, vars: _ } = stack; + let collapsed = match_atom!{ + 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); + } + }; + collapsed.into_children().into_iter() + .map(atom_into_atom_bindings) + .flat_map(|(atom, b)| { + let result = |atom, bindings| { + let stack = Stack::finished(prev.clone(), atom); + InterpretedAtom(stack, bindings) + }; + b.merge_v2(&bindings).into_iter().filter_map(move |b| { + if b.has_loops() { + None + } else { + Some(result(atom.clone(), b)) + } + }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::test_utils::{metta_atom, metta_space}; + + #[test] + fn interpret_atom_evaluate_incorrect_args() { + assert_eq!(call_interpret(&space(""), &metta_atom("(eval)")), + vec![expr!("Error" ("eval") "expected: (eval ), found: (eval)")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(eval a b)")), + vec![expr!("Error" ("eval" "a" "b") "expected: (eval ), found: (eval a b)")]); + } + + #[test] + fn interpret_atom_evaluate_atom() { + let result = call_interpret(&space("(= a b)"), &metta_atom("(eval a)")); + assert_eq!(result, vec![metta_atom("b")]); + } + + #[test] + fn interpret_atom_evaluate_atom_no_definition() { + let result = call_interpret(&space(""), &metta_atom("(eval a)")); + assert_eq!(result, vec![metta_atom("NotReducible")]); + } + + #[test] + fn interpret_atom_evaluate_empty_expression() { + let result = call_interpret(&space(""), &metta_atom("(eval ())")); + assert_eq!(result, vec![metta_atom("NotReducible")]); + } + + #[test] + fn interpret_atom_evaluate_grounded_value() { + let result = call_interpret(&space(""), &expr!("eval" {6})); + assert_eq!(result, vec![metta_atom("NotReducible")]); + } + + + #[test] + fn interpret_atom_evaluate_pure_expression() { + let space = space("(= (foo $a B) $a)"); + let result = call_interpret(&space, &metta_atom("(eval (foo A $b))")); + assert_eq!(result, vec![metta_atom("A")]); + } + + #[test] + fn interpret_atom_evaluate_pure_expression_non_determinism() { + let space = space(" + (= color red) + (= color green) + (= color blue) + "); + let result = call_interpret(&space, &metta_atom("(eval color)")); + assert_eq_no_order!(result, vec![ + metta_atom("red"), + metta_atom("green"), + metta_atom("blue"), + ]); + } + + #[test] + fn interpret_atom_evaluate_pure_expression_no_definition() { + let result = call_interpret(&space(""), &metta_atom("(eval (foo A))")); + assert_eq!(result, vec![metta_atom("NotReducible")]); + } + + #[test] + fn interpret_atom_evaluate_pure_expression_variable_name_conflict() { + let space = space("(= (foo ($W)) True)"); + let result = call_interpret(&space, &metta_atom("(eval (foo $W))")); + assert_eq!(result[0], sym!("True")); + } + + #[test] + fn interpret_atom_evaluate_grounded_expression() { + let result = call_interpret(&space(""), &expr!("eval" ({MulXUndefinedType(7)} {6}))); + assert_eq!(result, vec![expr!({42})]); + } + + #[test] + fn interpret_atom_evaluate_grounded_expression_empty() { + let result = call_interpret(&space(""), &expr!("eval" ({ReturnNothing()} {6}))); + assert_eq!(result, vec![]); + } + + #[test] + fn interpret_atom_evaluate_grounded_expression_noreduce() { + let result = call_interpret(&space(""), &expr!("eval" ({NonReducible()} {6}))); + assert_eq!(result, vec![expr!("NotReducible")]); + } + + #[test] + fn interpret_atom_evaluate_grounded_expression_error() { + let result = call_interpret(&space(""), &expr!("eval" ({ThrowError()} {"Test error"}))); + assert_eq!(result, vec![expr!("Error" ({ThrowError()} {"Test error"}) "Test error")]); + } + + #[test] + fn interpret_atom_evaluate_variable_operation() { + let space = space("(= (foo $a B) $a)"); + let result = call_interpret(&space, &metta_atom("(eval ($a A $b))")); + #[cfg(feature = "variable_operation")] + assert_eq!(result, vec![metta_atom("A")]); + #[cfg(not(feature = "variable_operation"))] + assert_eq!(result, vec![NOT_REDUCIBLE_SYMBOL]); + } + + #[test] + fn interpret_atom_evaluate_variable_via_call_direct_equality() { + let space = space(" + (= (bar) (function (return ()))) + (= (foo $b) (function + (chain (eval (bar)) $_ + (unify $b value + (return ()) + (return (Error () \"Unexpected error\")) ))))"); + let result = call_interpret(&space, + &metta_atom("(chain (eval (foo $a)) $_ $a)")); + assert_eq!(result[0], sym!("value")); + } + + #[test] + fn interpret_atom_evaluate_variable_via_call_struct_equality() { + let formal_arg_struct = space(" + (= (bar) (function (return ()))) + (= (foo ($b)) (function + (chain (eval (bar)) $_ + (unify $b value + (return ()) + (return (Error () \"Unexpected error\")) ))))"); + let result = call_interpret(&formal_arg_struct, + &metta_atom("(chain (eval (foo $a)) $_ $a)")); + assert_eq!(result[0], expr!(("value"))); + + let actual_arg_struct = space(" + (= (bar) (function (return ()))) + (= (foo $b) (function + (chain (eval (bar)) $_ + (unify $b (value) + (return ()) + (return (Error () \"Unexpected error\")) ))))"); + let result = call_interpret(&actual_arg_struct, + &metta_atom("(chain (eval (foo ($a))) $_ $a)")); + assert_eq!(result[0], sym!("value")); + } + + + #[test] + fn interpret_atom_chain_incorrect_args() { + assert_eq!(call_interpret(&space(""), &metta_atom("(chain n $v t o)")), + vec![expr!("Error" ("chain" "n" v "t" "o") "expected: (chain (: Variable) ), found: (chain n $v t o)")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(chain n v t)")), + vec![expr!("Error" ("chain" "n" "v" "t") "expected: (chain (: Variable) ), found: (chain n v t)")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(chain n $v)")), + vec![expr!("Error" ("chain" "n" v) "expected: (chain (: Variable) ), found: (chain n $v)")]); + } + + #[test] + fn interpret_atom_chain_atom() { + let result = call_interpret(&space(""), &expr!("chain" ("A" () {6} y) x ("bar" x))); + assert_eq!(result, vec![expr!("bar" ("A" () {6} y))]); + } + + + #[test] + fn interpret_atom_chain_evaluation() { + let space = space("(= (foo $a B) $a)"); + let result = call_interpret(&space, &metta_atom("(chain (eval (foo A $b)) $x (bar $x))")); + assert_eq!(result, vec![metta_atom("(bar A)")]); + } + + #[test] + fn interpret_atom_chain_nested_evaluation() { + let space = space("(= (foo $a B) $a)"); + let result = call_interpret(&space, &metta_atom("(chain (chain (eval (foo A $b)) $x (bar $x)) $y (baz $y))")); + assert_eq!(result, vec![metta_atom("(baz (bar A))")]); + } + + #[test] + fn interpret_atom_chain_nested_value() { + let result = call_interpret(&space(""), &metta_atom("(chain (chain A $x (bar $x)) $y (baz $y))")); + assert_eq!(result, vec![metta_atom("(baz (bar A))")]); + } + + #[test] + fn interpret_atom_chain_expression_non_determinism() { + let space = space(" + (= (color) red) + (= (color) green) + (= (color) blue) + "); + let result = call_interpret(&space, &metta_atom("(chain (eval (color)) $x (bar $x))")); + assert_eq_no_order!(result, vec![ + metta_atom("(bar red)"), + metta_atom("(bar green)"), + metta_atom("(bar blue))"), + ]); + } + + #[test] + fn interpret_atom_chain_return() { + let result = call_interpret(&space(""), &metta_atom("(chain Empty $x (bar $x))")); + assert_eq!(result, vec![metta_atom("(bar Empty)")]); + } + + #[test] + fn interpret_atom_chain_keep_var_from_evaluated_part() { + let result = call_interpret(&space("(= (even 4) True)"), &metta_atom("(chain (eval (even $x)) $res (= (is-even $x) $res))")); + assert_eq!(result, vec![metta_atom("(= (is-even 4) True)")]); + } + + + #[test] + fn interpret_atom_unify_incorrect_args() { + assert_eq!(call_interpret(&space(""), &metta_atom("(unify a p t e o)")), + vec![expr!("Error" ("unify" "a" "p" "t" "e" "o") "expected: (unify ), found: (unify a p t e o)")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(unify a p t)")), + vec![expr!("Error" ("unify" "a" "p" "t") "expected: (unify ), found: (unify a p t)")]); + } + + #[test] + fn interpret_atom_unify_then() { + let result = call_interpret(&space(""), &metta_atom("(unify (A $b) ($a B) ($a $b) Empty)")); + assert_eq!(result, vec![metta_atom("(A B)")]); + } + + #[test] + fn interpret_atom_unify_else() { + let result = call_interpret(&space(""), &metta_atom("(unify (A $b C) ($a B D) ($a $b) Empty)")); + assert_eq!(result, vec![]); + } + + + #[test] + fn interpret_atom_decons_atom_incorrect_args() { + assert_eq!(call_interpret(&space(""), &metta_atom("(decons-atom a)")), + vec![expr!("Error" ("decons-atom" "a") "expected: (decons-atom (: Expression)), found: (decons-atom a)")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(decons-atom (a) (b))")), + vec![expr!("Error" ("decons-atom" ("a") ("b")) "expected: (decons-atom (: Expression)), found: (decons-atom (a) (b))")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(decons-atom)")), + vec![expr!("Error" ("decons-atom") "expected: (decons-atom (: Expression)), found: (decons-atom)")]); + } + + #[test] + fn interpret_atom_decons_atom_empty() { + let result = call_interpret(&space(""), &metta_atom("(decons-atom ())")); + assert_eq!(result, vec![expr!("Error" ("decons-atom" ()) "expected: (decons-atom (: Expression)), found: (decons-atom ())")]); + } + + #[test] + fn interpret_atom_decons_atom_single() { + let result = call_interpret(&space(""), &metta_atom("(decons-atom (a))")); + assert_eq!(result, vec![metta_atom("(a ())")]); + } + + #[test] + fn interpret_atom_decons_atom_list() { + let result = call_interpret(&space(""), &metta_atom("(decons-atom (a b c))")); + assert_eq!(result, vec![metta_atom("(a (b c))")]); + } + + + #[test] + fn interpret_atom_cons_atom_incorrect_args() { + assert_eq!(call_interpret(&space(""), &metta_atom("(cons-atom a (e) o)")), + vec![expr!("Error" ("cons-atom" "a" ("e") "o") "expected: (cons-atom (: Expression)), found: (cons-atom a (e) o)")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(cons-atom a e)")), + vec![expr!("Error" ("cons-atom" "a" "e") "expected: (cons-atom (: Expression)), found: (cons-atom a e)")]); + assert_eq!(call_interpret(&space(""), &metta_atom("(cons-atom a)")), + vec![expr!("Error" ("cons-atom" "a") "expected: (cons-atom (: Expression)), found: (cons-atom a)")]); + } + + #[test] + fn interpret_atom_cons_atom_empty() { + let result = call_interpret(&space(""), &metta_atom("(cons-atom a ())")); + assert_eq!(result, vec![metta_atom("(a)")]); + } + + #[test] + fn interpret_atom_cons_atom_single() { + let result = call_interpret(&space(""), &metta_atom("(cons-atom a (b))")); + assert_eq!(result, vec![metta_atom("(a b)")]); + } + + #[test] + fn interpret_atom_cons_atom_list() { + let result = call_interpret(&space(""), &metta_atom("(cons-atom a (b c))")); + assert_eq!(result, vec![metta_atom("(a b c)")]); + } + + + #[test] + fn test_superpose_bind() { + let vars: Variables = [ "a", "b", "c" ].into_iter().map(VariableAtom::new).collect(); + let atom = Atom::expr([Atom::sym("superpose-bind"), + Atom::expr([atom_bindings_into_atom(expr!("foo" a b), bind!{ a: expr!("A"), c: expr!("C") })])]); + let stack = Stack{ prev: None, atom, ret: no_handler, finished: false, vars: vars.clone() }; + + let result = superpose_bind(stack, bind!{ b: expr!("B"), d: expr!("D") }); + + assert_eq!(result, vec![InterpretedAtom( + Stack{ prev: None, atom: expr!("foo" a b), ret: no_handler, finished: true, vars: Variables::new() }, + bind!{ a: expr!("A"), b: expr!("B"), c: expr!("C"), d: expr!("D") } + )]); + } + + #[test] + fn metta_turing_machine() { + let space = space(" + (= (tm $rule $state $tape) + (function (eval (tm-body $rule $state $tape))) ) + + (= (tm-body $rule $state $tape) + (unify $state HALT + (return $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-body $rule $next-state $next-tape)) ) + (return (Error (tm-body $rule $state $tape) \"Incorrect state\")) ))))) + + (= (read ($head $hole $tail)) $hole) + + (= (move ($head $hole $tail) $char N) ($head $char $tail)) + (= (move ($head $hole $tail) $char L) (function + (chain (cons-atom $char $head) $next-head + (chain (decons-atom $tail) $list + (unify $list ($next-hole $next-tail) + (return ($next-head $next-hole $next-tail)) + (return ($next-head 0 ())) ))))) + (= (move ($head $hole $tail) $char R) (function + (chain (cons-atom $char $tail) $next-tail + (chain (decons-atom $head) $list + (unify $list ($next-hole $next-head) + (return ($next-head $next-hole $next-tail)) + (return (() 0 $next-tail)) ))))) + + (= (busy-beaver A 0) (B 1 R)) + (= (busy-beaver A 1) (C 1 L)) + + (= (busy-beaver B 0) (A 1 L)) + (= (busy-beaver B 1) (B 1 R)) + + (= (busy-beaver C 0) (B 1 L)) + (= (busy-beaver C 1) (HALT 1 N)) + + "); + let result = interpret(space, &metta_atom("(eval (tm busy-beaver A (() 0 ())))")); + assert_eq!(result, Ok(vec![metta_atom("((1 1) 1 (1 1 1))")])); + } + + #[test] + fn interpret_minimal_metta_smoketest() { + let space = space(" + (= (foo $a B) $a) + (= (fu $x) (function (chain (eval (foo $x B)) $r (return $r)))) + (= (color) red) + (= (color) green) + (= (color) blue) + "); + let result = interpret(&space, &metta_atom("(chain (chain A $x $x) $y $y)")); + assert_eq!(result, Ok(vec![metta_atom("A")])); + let result = interpret(&space, &metta_atom("(chain (chain (eval (foo A $b)) $x (bar $x)) $y (baz $y))")); + assert_eq!(result, Ok(vec![metta_atom("(baz (bar A))")])); + let result = interpret(&space, &metta_atom("(chain (chain (eval (fu A)) $x (bar $x)) $y (baz $y))")); + assert_eq!(result, Ok(vec![metta_atom("(baz (bar A))")])); + let result = interpret(&space, &metta_atom("(unify (A $b) ($a B) ($a $b) Empty)")); + assert_eq!(result, Ok(vec![metta_atom("(A B)")])); + let result = interpret(&space, &metta_atom("(decons-atom (a b c))")); + assert_eq!(result, Ok(vec![metta_atom("(a (b c))")])); + let result = interpret(&space, &metta_atom("(cons-atom a (b c))")); + assert_eq!(result, Ok(vec![metta_atom("(a b c)")])); + let result = interpret(&space, &metta_atom("(chain (collapse-bind (eval (color))) $collapsed (superpose-bind $collapsed))")).unwrap(); + assert_eq_no_order!(result, vec![metta_atom("red"), metta_atom("green"), metta_atom("blue")]); + let result = interpret(&space, &metta_atom("((P $a B) $a)")); + assert_eq!(result, Ok(vec![metta_atom("((P $a B) $a)")])); + let result = interpret(&space, &metta_atom("(collapse-bind (eval (color)))")).unwrap(); + assert_eq!(result.len(), 1); + assert_eq_no_order!(atom_as_slice(&result[0]).unwrap(), [ + atom_bindings_into_atom(expr!("red"), bind!{}), + atom_bindings_into_atom(expr!("green"), bind!{}), + atom_bindings_into_atom(expr!("blue"), bind!{}) + ]); + } + + fn space(text: &str) -> GroundingSpace { + metta_space(text) + } + + fn call_interpret<'a, T: Space>(space: T, atom: &Atom) -> Vec { + let _ = env_logger::builder().is_test(true).try_init(); + let result = interpret(space, atom); + assert!(result.is_ok()); + result.unwrap() + } + + #[derive(PartialEq, Clone, Debug)] + struct ThrowError(); + + impl Grounded for ThrowError { + fn type_(&self) -> Atom { + expr!("->" "&str" "Error") + } + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } + } + + impl CustomExecute for ThrowError { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + Err((*args[0].as_gnd::<&str>().unwrap()).into()) + } + } + + impl Display for ThrowError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "throw-error") + } + } + + #[derive(PartialEq, Clone, Debug)] + struct NonReducible(); + + impl Grounded for NonReducible { + fn type_(&self) -> Atom { + expr!("->" "u32" "u32") + } + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } + } + + impl CustomExecute for NonReducible { + fn execute(&self, _args: &[Atom]) -> Result, ExecError> { + Err(ExecError::NoReduce) + } + } + + impl Display for NonReducible { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "non-reducible") + } + } + + #[derive(PartialEq, Clone, Debug)] + struct MulXUndefinedType(i32); + + impl Grounded for MulXUndefinedType { + fn type_(&self) -> Atom { + ATOM_TYPE_UNDEFINED + } + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } + } + + impl CustomExecute for MulXUndefinedType { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + Ok(vec![Atom::value(self.0 * args.get(0).unwrap().as_gnd::().unwrap())]) + } + } + + impl Display for MulXUndefinedType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "x{}", self.0) + } + } + + #[derive(PartialEq, Clone, Debug)] + struct ReturnNothing(); + + impl Grounded for ReturnNothing { + fn type_(&self) -> Atom { + ATOM_TYPE_UNDEFINED + } + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } + } + + impl CustomExecute for ReturnNothing { + fn execute(&self, _args: &[Atom]) -> Result, ExecError> { + Ok(vec![]) + } + } + + impl Display for ReturnNothing { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "return-nothing") + } + } +} diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index f2641939a..55ea5c658 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -2,8 +2,10 @@ pub mod text; pub mod interpreter; -#[cfg(feature = "minimal")] +#[cfg(all(feature = "minimal", not(feature = "minimal_rust")))] pub mod interpreter_minimal; +#[cfg(all(feature = "minimal", feature = "minimal_rust"))] +pub mod interpreter_minimal_rust; pub mod types; pub mod runner; diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 7c139f00c..8c41c2928 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -92,8 +92,10 @@ use super::interpreter::{interpret, interpret_init, interpret_step, InterpreterS #[cfg(feature = "minimal")] pub mod stdlib_minimal; -#[cfg(feature = "minimal")] +#[cfg(all(feature = "minimal", not(feature = "minimal_rust")))] use super::interpreter_minimal::{interpret, interpret_init, interpret_step, InterpreterState}; +#[cfg(all(feature = "minimal", feature = "minimal_rust"))] +use super::interpreter_minimal_rust::{interpret, interpret_init, interpret_step, InterpreterState}; #[cfg(feature = "minimal")] use stdlib_minimal::*; diff --git a/lib/src/metta/runner/modules/mod.rs b/lib/src/metta/runner/modules/mod.rs index 400668e2d..acbd03d92 100644 --- a/lib/src/metta/runner/modules/mod.rs +++ b/lib/src/metta/runner/modules/mod.rs @@ -10,8 +10,10 @@ use regex::Regex; #[cfg(not(feature = "minimal"))] use super::stdlib::*; -#[cfg(feature = "minimal")] +#[cfg(all(feature = "minimal", not(feature = "minimal_rust")))] use super::interpreter_minimal::interpret; +#[cfg(all(feature = "minimal", feature = "minimal_rust"))] +use super::interpreter_minimal_rust::interpret; #[cfg(feature = "minimal")] use super::stdlib_minimal::*; @@ -777,4 +779,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_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 58ac73a31..8325059ba 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -145,7 +145,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::interpreter_minimal::interpret(space, &expr) + #[cfg(not(feature = "minimal_rust"))] + let result = crate::metta::interpreter_minimal::interpret(space, &expr); + #[cfg(feature = "minimal_rust")] + let result = crate::metta::interpreter_minimal_rust::interpret(space, &expr); + result } fn assert_results_equal(actual: &Vec, expected: &Vec, atom: &Atom) -> Result, ExecError> { From 59273cdbbc53eae4cd5416727f406827c1380446 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Mon, 24 Jun 2024 17:05:11 +0300 Subject: [PATCH 02/26] Implement interpreter operation in Rust --- lib/src/metta/interpreter_minimal_rust.rs | 99 ++++++++++++++++++++++- lib/src/metta/runner/stdlib_minimal.metta | 10 +-- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 2125ee950..3caa932c8 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -8,6 +8,7 @@ use crate::*; use crate::atom::matcher::*; use crate::space::*; use crate::metta::*; +use crate::metta::types::*; use std::fmt::{Debug, Display, Formatter}; use std::convert::TryFrom; @@ -276,7 +277,8 @@ 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 == INTERPRET_SYMBOL, _ => false, } } @@ -386,6 +388,9 @@ 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 == INTERPRET_SYMBOL => { + interpret_sym(stack, bindings) + }, _ => { let stack = Stack::finished(stack.prev, stack.atom); vec![InterpretedAtom(stack, bindings)] @@ -791,6 +796,98 @@ fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { .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: {}", INTERPRET_SYMBOL, interpret); + return finished_result(error_atom(interpret, error), bindings, prev); + } + }; + + interpret_impl(atom, typ, space, bindings) + .map(|(atom, bindings)| InterpretedAtom(atom_to_stack(atom, prev.clone()), bindings)) + .collect() +} + +type MettaResult = Box>; + +fn once(atom: Atom, bindings: Bindings) -> MettaResult { + Box::new(std::iter::once((atom, bindings))) +} + +fn empty() -> MettaResult { + Box::new(std::iter::empty()) +} + +fn interpret_impl(atom: Atom, typ: Atom, space: Atom, bindings: Bindings) -> MettaResult { + let meta = get_meta_type(&atom); + if typ == ATOM_TYPE_ATOM { + once(atom, bindings) + } else if typ == meta { + once(atom, bindings) + } else { + if meta == ATOM_TYPE_VARIABLE { + once(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 { + todo!() + } + } +} + +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(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(atom, bindings), + None => empty(), + } + } +} + +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() { + Box::new(std::iter::once((els, bindings.clone()))) + } else { + Box::new(result + .flat_map(move |b| b.merge_v2(&bindings).into_iter()) + .map(move |b| (then.clone(), b))) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/src/metta/runner/stdlib_minimal.metta b/lib/src/metta/runner/stdlib_minimal.metta index 9a3a04cbe..abaf1c8b4 100644 --- a/lib/src/metta/runner/stdlib_minimal.metta +++ b/lib/src/metta/runner/stdlib_minimal.metta @@ -180,13 +180,13 @@ (return $atom) (eval (if-equal $type $meta (return $atom) - (eval (switch ($type $meta) ( - (($type Variable) (return $atom)) - (($type Symbol) + (eval (switch $meta ( + (Variable (return $atom)) + (Symbol (chain (eval (type-cast $atom $type $space)) $ret (return $ret))) - (($type Grounded) + (Grounded (chain (eval (type-cast $atom $type $space)) $ret (return $ret))) - (($type Expression) + (Expression (chain (eval (check-alternatives (eval (interpret-expression $atom $type $space)))) $ret (return $ret))) )))))))))) From 80ae4001db2b8413065f2f683438373cdc455ead Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 25 Jun 2024 17:31:00 +0300 Subject: [PATCH 03/26] Implement Clone for the iterators over results --- lib/src/atom/matcher.rs | 16 +++++++++++++++- lib/src/metta/interpreter_minimal_rust.rs | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) 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/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 3caa932c8..0741866d8 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -812,7 +812,20 @@ fn interpret_sym(stack: Stack, bindings: Bindings) -> Vec { .collect() } -type MettaResult = Box>; +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_() + } +} fn once(atom: Atom, bindings: Bindings) -> MettaResult { Box::new(std::iter::once((atom, bindings))) From 833174b03ff6ad4b21138da1291bf85856cb3345 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 25 Jun 2024 17:31:49 +0300 Subject: [PATCH 04/26] Add expression interpreter skeleton --- lib/src/metta/interpreter_minimal_rust.rs | 66 ++++++++++++++++++++++- lib/src/metta/mod.rs | 1 + 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 0741866d8..4341bca15 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -278,7 +278,8 @@ fn is_embedded_op(atom: &Atom) -> bool { || *op == FUNCTION_SYMBOL || *op == COLLAPSE_BIND_SYMBOL || *op == SUPERPOSE_BIND_SYMBOL - || *op == INTERPRET_SYMBOL, + || *op == INTERPRET_SYMBOL + || *op == CALL_NATIVE_SYMBOL, _ => false, } } @@ -391,6 +392,9 @@ fn interpret_stack<'a, T: Space>(context: &InterpreterContext, stack: Stack, Some([op, ..]) if *op == INTERPRET_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)] @@ -796,6 +800,25 @@ 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 (func, args) = match_atom!{ + call ~ [_op, func, args] + if func.as_gnd::().is_some() => (func, args), + _ => { + let error = format!("expected: ({} func args), found: {}", CALL_NATIVE_SYMBOL, call); + return finished_result(error_atom(call, error), bindings, prev); + } + }; + + let func = func.as_gnd::().expect("Unexpected state"); + func(args, bindings) + .map(|(atom, bindings)| InterpretedAtom(atom_to_stack(atom, prev.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!{ @@ -835,6 +858,10 @@ fn empty() -> MettaResult { Box::new(std::iter::empty()) } +fn call_native_atom(func: NativeFunc, args: Atom) -> Atom { + Atom::expr([CALL_NATIVE_SYMBOL, Atom::value(func), args]) +} + fn interpret_impl(atom: Atom, typ: Atom, space: Atom, bindings: Bindings) -> MettaResult { let meta = get_meta_type(&atom); if typ == ATOM_TYPE_ATOM { @@ -849,7 +876,12 @@ fn interpret_impl(atom: Atom, typ: Atom, space: Atom, bindings: Bindings) -> Met } else if meta == ATOM_TYPE_GROUNDED { type_cast(space, atom, typ, bindings) } else { - todo!() + let var = Atom::Variable(VariableAtom::new("x").make_unique()); + once(Atom::expr([CHAIN_SYMBOL, + Atom::expr([COLLAPSE_BIND_SYMBOL, call_native_atom(interpret_expression, Atom::expr([atom, typ, space]))]), + var.clone(), + call_native_atom(check_alternatives, Atom::expr([var])) + ]), bindings) } } } @@ -901,6 +933,36 @@ fn match_types(type1: Atom, type2: Atom, then: Atom, els: Atom, bindings: Bindin } } +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 Box::new(once(error_atom(call_native_atom(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)).peekable(); + let err = results.filter(|(atom, _bindings)| atom_is_error(&atom)); + 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 Box::new(once(error_atom(call_native_atom(interpret_expression, args), error), bindings)); + } + }; + let space = space.as_gnd::().unwrap(); + Box::new(once(atom, bindings)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index 55ea5c658..ef7bcc870 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -43,6 +43,7 @@ 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 CALL_NATIVE_SYMBOL : Atom = sym!("call-native"); //TODO: convert these from functions to static strcutures, when Atoms are Send+Sync #[allow(non_snake_case)] From b44eff08ac5bf5bbadccf12fdaea8943ef3966bd Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 26 Jun 2024 07:16:28 +0300 Subject: [PATCH 05/26] Implement interpreting tuples without known function type --- lib/src/metta/interpreter_minimal_rust.rs | 180 ++++++++++++++++++++-- 1 file changed, 164 insertions(+), 16 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 4341bca15..3293dd79e 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -9,6 +9,7 @@ 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; @@ -34,6 +35,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 @@ -412,6 +419,11 @@ fn error_atom(atom: Atom, err: String) -> Atom { Atom::expr([Atom::sym("Error"), atom, Atom::sym(err)]) } +// FIXME: rename +fn error_atom_(atom: Atom, err: Atom) -> Atom { + Atom::expr([Atom::sym("Error"), atom, err]) +} + fn finished_result(atom: Atom, bindings: Bindings, prev: Option>>) -> Vec { vec![InterpretedAtom(Stack::finished(prev, atom), bindings)] } @@ -805,7 +817,7 @@ 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 (func, args) = match_atom!{ - call ~ [_op, func, args] + call ~ [_op, _name, func, args] if func.as_gnd::().is_some() => (func, args), _ => { let error = format!("expected: ({} func args), found: {}", CALL_NATIVE_SYMBOL, call); @@ -850,16 +862,29 @@ impl Clone for MettaResult { } } +#[inline] fn once(atom: Atom, bindings: Bindings) -> MettaResult { Box::new(std::iter::once((atom, bindings))) } +#[inline] fn empty() -> MettaResult { Box::new(std::iter::empty()) } -fn call_native_atom(func: NativeFunc, args: Atom) -> Atom { - Atom::expr([CALL_NATIVE_SYMBOL, Atom::value(func), args]) +#[inline] +fn call_native_atom(func: NativeFunc, name: &str, args: Atom) -> 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(atom: Atom, typ: Atom, space: Atom, bindings: Bindings) -> MettaResult { @@ -877,10 +902,8 @@ fn interpret_impl(atom: Atom, typ: Atom, space: Atom, bindings: Bindings) -> Met type_cast(space, atom, typ, bindings) } else { let var = Atom::Variable(VariableAtom::new("x").make_unique()); - once(Atom::expr([CHAIN_SYMBOL, - Atom::expr([COLLAPSE_BIND_SYMBOL, call_native_atom(interpret_expression, Atom::expr([atom, typ, space]))]), - var.clone(), - call_native_atom(check_alternatives, Atom::expr([var])) + once(Atom::expr([CHAIN_SYMBOL, Atom::expr([COLLAPSE_BIND_SYMBOL, call_native!(interpret_expression, Atom::expr([atom, typ, space]))]), var.clone(), + call_native!(check_alternatives, Atom::expr([var])) ]), bindings) } } @@ -907,7 +930,7 @@ fn type_cast(space: Atom, atom: Atom, expected_type: Atom, bindings: Bindings) - .next(); match first_match { Some((_atom, bindings)) => once(atom, bindings), - None => empty(), + None => once(error_atom_(atom, BAD_TYPE_SYMBOL), bindings), } } } @@ -924,7 +947,7 @@ fn match_types(type1: Atom, type2: Atom, then: Atom, els: Atom, bindings: Bindin } else { let mut result = match_atoms(&type1, &type2).peekable(); if result.peek().is_none() { - Box::new(std::iter::once((els, bindings.clone()))) + once(els, bindings.clone()) } else { Box::new(result .flat_map(move |b| b.merge_v2(&bindings).into_iter()) @@ -938,12 +961,16 @@ fn check_alternatives(args: Atom, bindings: Bindings) -> MettaResult { args ~ [Atom::Expression(expr)] => expr, _ => { let error = format!("expected args: ((: expr Expression)), found: {}", args); - return Box::new(once(error_atom(call_native_atom(check_alternatives, args), error), bindings)); + return once(error_atom(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)).peekable(); - let err = results.filter(|(atom, _bindings)| atom_is_error(&atom)); + let results = expr.into_children().into_iter() + .map(atom_into_atom_bindings); + let mut succ = results.clone() + .filter(|(atom, _bindings)| !atom_is_error(&atom)) + .peekable(); + let err = results + .filter(|(atom, _bindings)| atom_is_error(&atom)); match succ.peek() { Some(_) => Box::new(succ), None => Box::new(err), @@ -956,11 +983,132 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { if space.as_gnd::().is_some() => (atom, typ, space), _ => { let error = format!("expected args: (atom type space), found: {}", args); - return Box::new(once(error_atom(call_native_atom(interpret_expression, args), error), bindings)); + return once(error_atom(call_native!(interpret_expression, args), error), bindings); } }; - let space = space.as_gnd::().unwrap(); - Box::new(once(atom, bindings)) + match atom_as_slice(&atom) { + Some([op, args @ ..]) => { + let space_ref = space.as_gnd::().unwrap(); + let 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 = actual_types.partition_point(|a| !is_func(a)); + let mut tuple_types = actual_types[..func].into_iter().peekable(); + let func_types = &actual_types[func..]; + + 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, function_atom(call_native!(interpret_tuple, Atom::expr([atom, space.clone()]))), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ, space])), result.clone(), + result + ]) + ]), bindings) + } else { + empty() + }; + Box::new(std::iter::empty().chain(tuple)) + }, + _ => 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_atom(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([INTERPRET_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, function_atom(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 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(error_atom(call_native!(metta_call, args), error), bindings); + } + }; + if atom_is_error(&atom) { + once(atom, bindings) + } else { + let space_ref = space.as_gnd::().unwrap(); + let res = query_(space_ref, &atom, bindings) + .map(move |(result, bindings)| { + if result == NOT_REDUCIBLE_SYMBOL { + (atom.clone(), bindings) + } else if result == EMPTY_SYMBOL { + (EMPTY_SYMBOL, bindings) + } else if atom_is_error(&result) { + (result, bindings) + } else { + let ret = Atom::Variable(VariableAtom::new("ret").make_unique()); + (Atom::expr([CHAIN_SYMBOL, Atom::expr([INTERPRET_SYMBOL, result, typ.clone(), space.clone()]), ret.clone(), + ret]), bindings) + } + }); + Box::new(res) + } +} + +fn query_(space: &DynSpace, to_eval: &Atom, bindings: Bindings) -> MettaResult { + #[cfg(not(feature = "variable_operation"))] + if is_variable_op(to_eval) { + // TODO: this is a hotfix. better way of doing this is adding + // a function which modifies minimal metta interpreter code + // in order to skip such evaluations in metta-call function. + return once(NOT_REDUCIBLE_SYMBOL, bindings) + } + let var_x = VariableAtom::new("X").make_unique(); + let query = Atom::expr([EQUAL_SYMBOL, to_eval.clone(), Atom::Variable(var_x.clone())]); + let results = space.query(&query); + if results.is_empty() { + once(NOT_REDUCIBLE_SYMBOL, bindings) + } else { + log::debug!("interpreter_minimal::query_: query: {}", query); + log::debug!("interpreter_minimal::query_: results.len(): {}, bindings.len(): {}, results: {} bindings: {}", + results.len(), bindings.len(), results, bindings); + let res = results.into_iter().flat_map(move |b| { + log::debug!("interpreter_minimal::query_: b: {}", b); + b.merge_v2(&bindings).into_iter() + }).filter_map(move |b| { + let res = b.resolve(&var_x).unwrap(); + if b.has_loops() { + None + } else { + Some((res, b)) + } + }); + Box::new(res) + } } #[cfg(test)] From 814f28cfce94317a7fd46748b2036f3272d9a288 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 26 Jun 2024 16:54:10 +0300 Subject: [PATCH 06/26] Interpreting typed function with first type found --- lib/src/metta/interpreter_minimal_rust.rs | 101 ++++++++++++++++------ 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 3293dd79e..7ea6c1bab 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -989,19 +989,32 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { match atom_as_slice(&atom) { Some([op, args @ ..]) => { let space_ref = space.as_gnd::().unwrap(); - let actual_types = get_atom_types(space_ref, op); + 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 = actual_types.partition_point(|a| !is_func(a)); - let mut tuple_types = actual_types[..func].into_iter().peekable(); - let func_types = &actual_types[func..]; + let func_start_index = actual_types.partition_point(|a| !is_func(a)); + let mut 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, function_atom(call_native!(interpret_tuple, Atom::expr([atom, space.clone()]))), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, function_atom(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(), + result + ]) + ]), bindings.clone()) + } else { + empty() + }; + + let func = if let Some(op_type) = func_types.next() { + let reduced = Atom::Variable(VariableAtom::new("reduced").make_unique()); + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + once( + Atom::expr([CHAIN_SYMBOL, function_atom(call_native!(interpret_function, Atom::expr([atom, op_type, typ.clone(), space.clone()]))), reduced.clone(), Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ, space])), result.clone(), result ]) @@ -1009,7 +1022,8 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { } else { empty() }; - Box::new(std::iter::empty().chain(tuple)) + + Box::new(std::iter::empty().chain(tuple).chain(func)) }, _ => type_cast(space, atom, typ, bindings), } @@ -1048,6 +1062,18 @@ fn interpret_tuple(args: Atom, bindings: Bindings) -> MettaResult { } } +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() => (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_atom(call_native!(interpret_function, args), error)), bindings); + } + }; + once(return_atom(Atom::Expression(atom)), bindings) +} + fn metta_call(args: Atom, bindings: Bindings) -> MettaResult { let (atom, typ, space) = match_atom!{ args ~ [atom, typ, space] @@ -1061,7 +1087,7 @@ fn metta_call(args: Atom, bindings: Bindings) -> MettaResult { once(atom, bindings) } else { let space_ref = space.as_gnd::().unwrap(); - let res = query_(space_ref, &atom, bindings) + let res = evaluate(space_ref, atom.clone(), bindings) .map(move |(result, bindings)| { if result == NOT_REDUCIBLE_SYMBOL { (atom.clone(), bindings) @@ -1079,35 +1105,54 @@ fn metta_call(args: Atom, bindings: Bindings) -> MettaResult { } } -fn query_(space: &DynSpace, to_eval: &Atom, bindings: Bindings) -> MettaResult { +fn evaluate(space: &DynSpace, to_eval: Atom, bindings: Bindings) -> MettaResult { #[cfg(not(feature = "variable_operation"))] - if is_variable_op(to_eval) { + if is_variable_op(&to_eval) { // TODO: this is a hotfix. better way of doing this is adding // a function which modifies minimal metta interpreter code // in order to skip such evaluations in metta-call function. return once(NOT_REDUCIBLE_SYMBOL, bindings) } - let var_x = VariableAtom::new("X").make_unique(); - let query = Atom::expr([EQUAL_SYMBOL, to_eval.clone(), Atom::Variable(var_x.clone())]); - let results = space.query(&query); - if results.is_empty() { - once(NOT_REDUCIBLE_SYMBOL, bindings) - } else { - log::debug!("interpreter_minimal::query_: query: {}", query); - log::debug!("interpreter_minimal::query_: results.len(): {}, bindings.len(): {}, results: {} bindings: {}", - results.len(), bindings.len(), results, bindings); - let res = results.into_iter().flat_map(move |b| { - log::debug!("interpreter_minimal::query_: b: {}", b); - b.merge_v2(&bindings).into_iter() - }).filter_map(move |b| { - let res = b.resolve(&var_x).unwrap(); - if b.has_loops() { - None + + match atom_as_slice(&to_eval) { + + // executable grounded function + Some([Atom::Grounded(op), args @ ..]) if op.as_grounded().as_execute().is_some() => { + let execute = op.as_grounded().as_execute().unwrap(); + match execute.execute(args) { + Ok(results) if results.is_empty() => once(EMPTY_SYMBOL, bindings), + Ok(results) => Box::new(results.into_iter() + .map(move |a| (a, bindings.clone()))), + Err(ExecError::NoReduce) => once(NOT_REDUCIBLE_SYMBOL, bindings), + Err(ExecError::Runtime(msg)) => once(error_atom(to_eval, msg), bindings), + } + }, + + // pure function + _ => { + let var_x = VariableAtom::new("X").make_unique(); + let query = Atom::expr([EQUAL_SYMBOL, to_eval.clone(), Atom::Variable(var_x.clone())]); + let results = space.query(&query); + if results.is_empty() { + once(NOT_REDUCIBLE_SYMBOL, bindings) } else { - Some((res, b)) + log::debug!("interpreter_minimal::evaluate: query: {}", query); + log::debug!("interpreter_minimal::evaluate: results.len(): {}, bindings.len(): {}, results: {} bindings: {}", + results.len(), bindings.len(), results, bindings); + let res = results.into_iter().flat_map(move |b| { + log::debug!("interpreter_minimal::evaluate: b: {}", b); + b.merge_v2(&bindings).into_iter() + }).filter_map(move |b| { + let res = b.resolve(&var_x).unwrap(); + if b.has_loops() { + None + } else { + Some((res, b)) + } + }); + Box::new(res) } - }); - Box::new(res) + }, } } From 763e095eb993c54b5e187a9f6ca00043c0cf46d3 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 27 Jun 2024 09:16:44 +0300 Subject: [PATCH 07/26] Interpreting typed functions with arguments type checking --- lib/src/metta/interpreter_minimal_rust.rs | 71 ++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 7ea6c1bab..070a6f325 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -1065,13 +1065,80 @@ fn interpret_tuple(args: Atom, bindings: Bindings) -> MettaResult { 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() => (atom, 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_atom(call_native!(interpret_function, args), error)), bindings); } }; - once(return_atom(Atom::Expression(atom)), 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([INTERPRET_SYMBOL, head, Atom::Expression(op_type), space.clone()]), rop.clone(), + Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), rop.clone(), + Atom::expr([CHAIN_SYMBOL, function_atom(call_native!(interpret_args, Atom::expr([Atom::Expression(atom), Atom::expr(args), arg_types, ret_type, space.clone()]))), rargs.clone(), + Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), 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_atom(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() { + match_types(types_head, ret_type, + return_atom(Atom::Expression(args)), + return_atom(error_atom_(atom, BAD_TYPE_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, function_atom(call_native!(interpret_args, Atom::expr([atom, Atom::expr(args_tail), Atom::expr(types_tail), ret_type, space.clone()]))), rtail.clone(), + Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), 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([INTERPRET_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(), + Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), rhead, + recursion + ])]) + ])]) + ]), bindings) + } } fn metta_call(args: Atom, bindings: Bindings) -> MettaResult { From d1635c65bc412b41ea1249ecd24c01e644beacfd Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 28 Jun 2024 13:20:50 +0300 Subject: [PATCH 08/26] Remove unnecessary eval wrapper around interpret operation --- lib/src/metta/interpreter_minimal_rust.rs | 2 +- lib/src/metta/runner/mod.rs | 3 +- lib/src/metta/runner/stdlib_minimal.rs | 74 +++++++++++------------ 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 070a6f325..d100bc1af 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -987,7 +987,7 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { } }; match atom_as_slice(&atom) { - Some([op, args @ ..]) => { + 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 diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 8c41c2928..c1f525146 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -1191,8 +1191,7 @@ impl<'i> InputStream<'i> { 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 + interpret } // *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-* diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 8325059ba..abb3d2423 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -677,42 +677,42 @@ mod tests { #[test] fn metta_interpret_single_atom_as_atom() { - let result = run_program("!(eval (interpret A Atom &self))"); + let result = run_program("!(interpret 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("!(interpret A Symbol &self)"), Ok(vec![vec![expr!("A")]])); + assert_eq!(run_program("!(interpret $x Variable &self)"), Ok(vec![vec![expr!(x)]])); + assert_eq!(run_program("!(interpret (A B) Expression &self)"), Ok(vec![vec![expr!("A" "B")]])); + assert_eq!(run_program("!(interpret 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) !(interpret a A &self)"), Ok(vec![vec![expr!("a")]])); + assert_eq!(run_program("(: a A) !(interpret a B &self)"), Ok(vec![vec![expr!("Error" "a" "BadType")]])); + assert_eq!(run_program("!(interpret 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("!(interpret $x %Undefined% &self)"), Ok(vec![vec![expr!(x)]])); + assert_eq!(run_program("!(interpret $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("!(interpret () %Undefined% &self)"), Ok(vec![vec![expr!(())]])); + assert_eq!(run_program("!(interpret () 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 (interpret S $t &self) $res (: $res $t)) "); assert_eq!(result, Ok(vec![vec![expr!(":" "S" "Int")]])); } @@ -724,14 +724,14 @@ mod tests { (: foo (-> T T)) (= (foo $x) $x) (= (bar $x) $x) - !(eval (interpret (foo (bar a)) %Undefined% &self)) + !(interpret (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)) + !(interpret (foo b) %Undefined% &self) "); assert_eq!(result, Ok(vec![vec![expr!("Error" "b" "BadType")]])); let result = run_program(" @@ -739,34 +739,34 @@ mod tests { (: Z Nat) (: S (-> Nat Nat)) (: Cons (-> $t (List $t) (List $t))) - !(eval (interpret (Cons S (Cons Z Nil)) %Undefined% &self)) + !(interpret (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("!(interpret () %Undefined% &self)"), Ok(vec![vec![expr!(())]])); + assert_eq!(run_program("!(interpret (a) %Undefined% &self)"), Ok(vec![vec![expr!(("a"))]])); + assert_eq!(run_program("!(interpret (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)) + !(interpret ((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) !(interpret (foo a) %Undefined% &self)"), Ok(vec![vec![expr!("a")]])); + assert_eq!(run_program("!(interpret (foo a) %Undefined% &self)"), Ok(vec![vec![expr!("foo" "a")]])); + assert_eq!(run_program("!(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))"); + let result = run_program("(: a A) (: a B) !(interpret a %Undefined% &self)"); assert_eq!(result, Ok(vec![vec![expr!("a")]])); } @@ -897,7 +897,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)) + !(interpret (if (and (is $x croaks) (is $x eats-flies)) (= (is $x frog) True) Empty) %Undefined% &self) "; assert_eq!(run_program(program), @@ -911,7 +911,7 @@ mod tests { (= (color) red) (= (color) green) - !(eval (interpret (color) %Undefined% &self)) + !(interpret (color) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -925,8 +925,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)) + !(interpret (eq (plus Z $n) $n) %Undefined% &self) + !(interpret (eq (plus (S Z) $n) $n) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -941,7 +941,7 @@ mod tests { (= (a $z) (mynot (b $z))) (= (b d) F) - !(eval (interpret (myif (a $x) $x) %Undefined% &self)) + !(interpret (myif (a $x) $x) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -953,7 +953,7 @@ mod tests { let program = " (= (a ($W)) True) - !(eval (interpret (a $W) %Undefined% &self)) + !(interpret (a $W) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -965,7 +965,7 @@ mod tests { let program = " (= (b ($x $y)) (c $x $y)) - !(eval (interpret (a (b $a) $x $y) %Undefined% &self)) + !(interpret (a (b $a) $x $y) %Undefined% &self) "; let result = run_program(program); @@ -981,7 +981,7 @@ mod tests { (= (foo) bar) (= (bar $x) $x) - !(eval (interpret ((foo) a) %Undefined% &self)) + !(interpret ((foo) a) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), Ok(vec![vec![expr!("a")]])); @@ -1004,7 +1004,7 @@ mod tests { (: id_a (-> A A)) (= (id_a $a) $a) - !(eval (interpret (id_a myAtom) %Undefined% &self)) + !(interpret (id_a myAtom) %Undefined% &self) "; let metta = Metta::new(Some(EnvBuilder::test_env())); @@ -1015,7 +1015,7 @@ mod tests { Ok(vec![vec![expr!("Error" "myAtom" "BadType")]])); let program2 = " - !(eval (interpret (id_num myAtom) %Undefined% &self)) + !(interpret (id_num myAtom) %Undefined% &self) "; assert_eq!(metta.run(SExprParser::new(program2)), @@ -1031,7 +1031,7 @@ mod tests { (: foo (-> A B C)) (= (foo $a $b) c) - !(eval (interpret (foo a b) %Undefined% &self)) + !(interpret (foo a b) %Undefined% &self) "; let metta = Metta::new(Some(EnvBuilder::test_env())); @@ -1041,12 +1041,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 = "!(interpret (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 = "!(interpret (foo a b c) %Undefined% &self)"; assert_eq!(metta.run(SExprParser::new(program3)), Ok(vec![vec![expr!("Error" ("foo" "a" "b" "c") "IncorrectNumberOfArguments")]])); From 53c6dae24479b9a608bfdd9b29897cb6b7b3302c Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 28 Jun 2024 15:54:49 +0300 Subject: [PATCH 09/26] Fix "bare minimal" unit test --- lib/src/metta/runner/stdlib_minimal.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index abb3d2423..8950d94d5 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -1064,8 +1064,9 @@ mod tests { let program = " (= (bar) baz) (= (foo) (bar)) - !(eval (foo)) + !(foo) !(pragma! interpreter bare-minimal) + !(foo) !(eval (foo)) "; @@ -1073,6 +1074,7 @@ mod tests { Ok(vec![ vec![expr!("baz")], vec![UNIT_ATOM()], + vec![expr!(("foo"))], vec![expr!(("bar"))], ])); } From 5a6d05e82783293c9b2a2cecd8e210eba7068c56 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 2 Jul 2024 12:22:13 +0300 Subject: [PATCH 10/26] Fix return vs replace semantics for the Rust minimal interpreter --- lib/src/metta/interpreter_minimal_rust.rs | 154 +++++++--------------- 1 file changed, 44 insertions(+), 110 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index d100bc1af..12f483d4d 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -842,9 +842,7 @@ fn interpret_sym(stack: Stack, bindings: Bindings) -> Vec { } }; - interpret_impl(atom, typ, space, bindings) - .map(|(atom, bindings)| InterpretedAtom(atom_to_stack(atom, prev.clone()), bindings)) - .collect() + vec![InterpretedAtom(atom_to_stack(call_native!(interpret_impl, Atom::expr([atom, typ, space])), prev), bindings)] } trait MettaResultIter: Iterator { @@ -874,7 +872,7 @@ fn empty() -> MettaResult { #[inline] fn call_native_atom(func: NativeFunc, name: &str, args: Atom) -> Atom { - Atom::expr([CALL_NATIVE_SYMBOL, Atom::sym(name), Atom::value(func), args]) + function_atom(Atom::expr([CALL_NATIVE_SYMBOL, Atom::sym(name), Atom::value(func), args])) } #[inline] @@ -887,23 +885,35 @@ fn function_atom(atom: Atom) -> Atom { Atom::expr([FUNCTION_SYMBOL, atom]) } -fn interpret_impl(atom: Atom, typ: Atom, space: Atom, bindings: Bindings) -> MettaResult { +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_atom(call_native!(interpret_impl, args), error)), bindings); + } + }; + let meta = get_meta_type(&atom); if typ == ATOM_TYPE_ATOM { - once(atom, bindings) + once(return_atom(atom), bindings) } else if typ == meta { - once(atom, bindings) + once(return_atom(atom), bindings) } else { if meta == ATOM_TYPE_VARIABLE { - once(atom, bindings) + 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(), - call_native!(check_alternatives, Atom::expr([var])) + Atom::expr([CHAIN_SYMBOL, call_native!(check_alternatives, Atom::expr([var])), res.clone(), + return_atom(res) + ]) ]), bindings) } } @@ -921,7 +931,7 @@ fn get_meta_type(atom: &Atom) -> Atom { fn type_cast(space: Atom, atom: Atom, expected_type: Atom, bindings: Bindings) -> MettaResult { let meta = get_meta_type(&atom); if expected_type == meta { - once(atom, bindings) + once(return_atom(atom), bindings) } else { let space = space.as_gnd::().unwrap(); let first_match = get_atom_types(space, &atom).into_iter() @@ -929,8 +939,8 @@ fn type_cast(space: Atom, atom: Atom, expected_type: Atom, bindings: Bindings) - .filter(|(atom, _bindings)| *atom.as_gnd::().unwrap()) .next(); match first_match { - Some((_atom, bindings)) => once(atom, bindings), - None => once(error_atom_(atom, BAD_TYPE_SYMBOL), bindings), + Some((_atom, bindings)) => once(return_atom(atom), bindings), + None => once(return_atom(error_atom_(atom, BAD_TYPE_SYMBOL)), bindings), } } } @@ -961,16 +971,18 @@ fn check_alternatives(args: Atom, bindings: Bindings) -> MettaResult { args ~ [Atom::Expression(expr)] => expr, _ => { let error = format!("expected args: ((: expr Expression)), found: {}", args); - return once(error_atom(call_native!(check_alternatives, args), error), bindings); + return once(return_atom(error_atom(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)); + .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), @@ -983,7 +995,7 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { if space.as_gnd::().is_some() => (atom, typ, space), _ => { let error = format!("expected args: (atom type space), found: {}", args); - return once(error_atom(call_native!(interpret_expression, args), error), bindings); + return once(return_atom(error_atom(call_native!(interpret_expression, args), error)), bindings); } }; match atom_as_slice(&atom) { @@ -1001,9 +1013,9 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { let reduced = Atom::Variable(VariableAtom::new("reduced").make_unique()); let result = Atom::Variable(VariableAtom::new("result").make_unique()); once( - Atom::expr([CHAIN_SYMBOL, function_atom(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(), - result + Atom::expr([CHAIN_SYMBOL, call_native!(interpret_tuple, Atom::expr([atom.clone(), space.clone()])), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("metta-call"), reduced, typ.clone(), space.clone()])]), result.clone(), + return_atom(result) ]) ]), bindings.clone()) } else { @@ -1014,9 +1026,9 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { let reduced = Atom::Variable(VariableAtom::new("reduced").make_unique()); let result = Atom::Variable(VariableAtom::new("result").make_unique()); once( - Atom::expr([CHAIN_SYMBOL, function_atom(call_native!(interpret_function, Atom::expr([atom, op_type, typ.clone(), space.clone()]))), reduced.clone(), - Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ, space])), result.clone(), - result + Atom::expr([CHAIN_SYMBOL, call_native!(interpret_function, Atom::expr([atom, op_type, typ.clone(), space.clone()])), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("metta-call"), reduced, typ, space])]), result.clone(), + return_atom(result) ]) ]), bindings) } else { @@ -1050,7 +1062,7 @@ fn interpret_tuple(args: Atom, bindings: Bindings) -> MettaResult { once( Atom::expr([CHAIN_SYMBOL, Atom::expr([INTERPRET_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, function_atom(call_native!(interpret_tuple, Atom::expr([Atom::expr(tail), space.clone()]))), rtail.clone(), + 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) @@ -1084,7 +1096,7 @@ fn interpret_function(args: Atom, bindings: Bindings) -> MettaResult { once( Atom::expr([CHAIN_SYMBOL, Atom::expr([INTERPRET_SYMBOL, head, Atom::Expression(op_type), space.clone()]), rop.clone(), Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), rop.clone(), - Atom::expr([CHAIN_SYMBOL, function_atom(call_native!(interpret_args, Atom::expr([Atom::Expression(atom), Atom::expr(args), arg_types, ret_type, space.clone()]))), rargs.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(), Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), rargs.clone(), Atom::expr([CHAIN_SYMBOL, Atom::expr([CONS_ATOM_SYMBOL, rop, rargs]), result.clone(), return_atom(result) @@ -1111,10 +1123,14 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { let types_head = types.remove(0); let types_tail = types; if args.children().is_empty() { - match_types(types_head, ret_type, - return_atom(Atom::Expression(args)), - return_atom(error_atom_(atom, BAD_TYPE_SYMBOL)), - bindings) + 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); @@ -1122,7 +1138,7 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { 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, function_atom(call_native!(interpret_args, Atom::expr([atom, Atom::expr(args_tail), Atom::expr(types_tail), ret_type, space.clone()]))), rtail.clone(), + 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(), Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), rtail.clone(), Atom::expr([CHAIN_SYMBOL, Atom::expr([CONS_ATOM_SYMBOL, rhead.clone(), rtail.clone()]), result.clone(), return_atom(result) @@ -1141,88 +1157,6 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { } } -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(error_atom(call_native!(metta_call, args), error), bindings); - } - }; - if atom_is_error(&atom) { - once(atom, bindings) - } else { - let space_ref = space.as_gnd::().unwrap(); - let res = evaluate(space_ref, atom.clone(), bindings) - .map(move |(result, bindings)| { - if result == NOT_REDUCIBLE_SYMBOL { - (atom.clone(), bindings) - } else if result == EMPTY_SYMBOL { - (EMPTY_SYMBOL, bindings) - } else if atom_is_error(&result) { - (result, bindings) - } else { - let ret = Atom::Variable(VariableAtom::new("ret").make_unique()); - (Atom::expr([CHAIN_SYMBOL, Atom::expr([INTERPRET_SYMBOL, result, typ.clone(), space.clone()]), ret.clone(), - ret]), bindings) - } - }); - Box::new(res) - } -} - -fn evaluate(space: &DynSpace, to_eval: Atom, bindings: Bindings) -> MettaResult { - #[cfg(not(feature = "variable_operation"))] - if is_variable_op(&to_eval) { - // TODO: this is a hotfix. better way of doing this is adding - // a function which modifies minimal metta interpreter code - // in order to skip such evaluations in metta-call function. - return once(NOT_REDUCIBLE_SYMBOL, bindings) - } - - match atom_as_slice(&to_eval) { - - // executable grounded function - Some([Atom::Grounded(op), args @ ..]) if op.as_grounded().as_execute().is_some() => { - let execute = op.as_grounded().as_execute().unwrap(); - match execute.execute(args) { - Ok(results) if results.is_empty() => once(EMPTY_SYMBOL, bindings), - Ok(results) => Box::new(results.into_iter() - .map(move |a| (a, bindings.clone()))), - Err(ExecError::NoReduce) => once(NOT_REDUCIBLE_SYMBOL, bindings), - Err(ExecError::Runtime(msg)) => once(error_atom(to_eval, msg), bindings), - } - }, - - // pure function - _ => { - let var_x = VariableAtom::new("X").make_unique(); - let query = Atom::expr([EQUAL_SYMBOL, to_eval.clone(), Atom::Variable(var_x.clone())]); - let results = space.query(&query); - if results.is_empty() { - once(NOT_REDUCIBLE_SYMBOL, bindings) - } else { - log::debug!("interpreter_minimal::evaluate: query: {}", query); - log::debug!("interpreter_minimal::evaluate: results.len(): {}, bindings.len(): {}, results: {} bindings: {}", - results.len(), bindings.len(), results, bindings); - let res = results.into_iter().flat_map(move |b| { - log::debug!("interpreter_minimal::evaluate: b: {}", b); - b.merge_v2(&bindings).into_iter() - }).filter_map(move |b| { - let res = b.resolve(&var_x).unwrap(); - if b.has_loops() { - None - } else { - Some((res, b)) - } - }); - Box::new(res) - } - }, - } -} - #[cfg(test)] mod tests { use super::*; From 3cbe1e2f537ec52ccd4016e3d37a28b4379c37d0 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 2 Jul 2024 12:58:11 +0300 Subject: [PATCH 11/26] Rename error_atom to error_msg --- lib/src/metta/interpreter_minimal_rust.rs | 51 +++++++++++------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 12f483d4d..8fa806d8e 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -415,13 +415,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)) } -// FIXME: rename -fn error_atom_(atom: Atom, err: Atom) -> Atom { - Atom::expr([Atom::sym("Error"), atom, 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 { @@ -434,7 +433,7 @@ 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); } }; log::debug!("eval: to_eval: {}", to_eval); @@ -465,7 +464,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. @@ -578,7 +577,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); @@ -621,7 +620,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); @@ -698,7 +697,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) @@ -710,7 +709,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); } }; @@ -741,7 +740,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(); @@ -756,7 +755,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]; @@ -791,7 +790,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() @@ -821,7 +820,7 @@ fn call_native_symbol(stack: Stack, bindings: Bindings) -> Vec if func.as_gnd::().is_some() => (func, args), _ => { let error = format!("expected: ({} func args), found: {}", CALL_NATIVE_SYMBOL, call); - return finished_result(error_atom(call, error), bindings, prev); + return finished_result(error_msg(call, error), bindings, prev); } }; @@ -838,7 +837,7 @@ fn interpret_sym(stack: Stack, bindings: Bindings) -> Vec { if space.as_gnd::().is_some() => (atom, typ, space), _ => { let error = format!("expected: ({} atom type space), found: {}", INTERPRET_SYMBOL, interpret); - return finished_result(error_atom(interpret, error), bindings, prev); + return finished_result(error_msg(interpret, error), bindings, prev); } }; @@ -891,7 +890,7 @@ fn interpret_impl(args: Atom, bindings: Bindings) -> MettaResult { if space.as_gnd::().is_some() => (atom, typ, space), _ => { let error = format!("expected args: (atom type space), found: {}", args); - return once(return_atom(error_atom(call_native!(interpret_impl, args), error)), bindings); + return once(return_atom(error_msg(call_native!(interpret_impl, args), error)), bindings); } }; @@ -940,7 +939,7 @@ fn type_cast(space: Atom, atom: Atom, expected_type: Atom, bindings: Bindings) - .next(); match first_match { Some((_atom, bindings)) => once(return_atom(atom), bindings), - None => once(return_atom(error_atom_(atom, BAD_TYPE_SYMBOL)), bindings), + None => once(return_atom(error_atom(atom, BAD_TYPE_SYMBOL)), bindings), } } } @@ -971,7 +970,7 @@ fn check_alternatives(args: Atom, bindings: Bindings) -> MettaResult { args ~ [Atom::Expression(expr)] => expr, _ => { let error = format!("expected args: ((: expr Expression)), found: {}", args); - return once(return_atom(error_atom(call_native!(check_alternatives, args), error)), bindings); + return once(return_atom(error_msg(call_native!(check_alternatives, args), error)), bindings); } }; let results = expr.into_children().into_iter() @@ -995,7 +994,7 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { if space.as_gnd::().is_some() => (atom, typ, space), _ => { let error = format!("expected args: (atom type space), found: {}", args); - return once(return_atom(error_atom(call_native!(interpret_expression, args), error)), bindings); + return once(return_atom(error_msg(call_native!(interpret_expression, args), error)), bindings); } }; match atom_as_slice(&atom) { @@ -1047,7 +1046,7 @@ fn interpret_tuple(args: Atom, bindings: Bindings) -> MettaResult { if space.as_gnd::().is_some() => (expr, space), _ => { let error = format!("expected args: ((: expr Expression) space), found: {}", args); - return once(return_atom(error_atom(call_native!(interpret_tuple, args), error)), bindings); + return once(return_atom(error_msg(call_native!(interpret_tuple, args), error)), bindings); } }; if expr.children().is_empty() { @@ -1081,7 +1080,7 @@ fn interpret_function(args: Atom, bindings: Bindings) -> MettaResult { 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_atom(call_native!(interpret_function, args), error)), bindings); + return once(return_atom(error_msg(call_native!(interpret_function, args), error)), bindings); } }; let mut call = atom.clone().into_children(); @@ -1113,12 +1112,12 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { 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_atom(call_native!(interpret_args, args_), error)), bindings); + 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); + return once(return_atom(error_atom(atom, INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings); } let types_head = types.remove(0); let types_tail = types; @@ -1126,10 +1125,10 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { if types_tail.is_empty() { match_types(types_head, ret_type, return_atom(Atom::Expression(args)), - return_atom(error_atom_(atom, BAD_TYPE_SYMBOL)), + return_atom(error_atom(atom, BAD_TYPE_SYMBOL)), bindings) } else { - once(return_atom(error_atom_(atom, INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings) + once(return_atom(error_atom(atom, INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings) } } else { let mut args = args.into_children(); From 2b76581fb4a6aa21c303b91e866d181c8a72a278 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 2 Jul 2024 13:06:51 +0300 Subject: [PATCH 12/26] Implement return-on-error as a native function --- lib/src/metta/interpreter_minimal_rust.rs | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 8fa806d8e..ce4e131b4 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -1094,15 +1094,15 @@ fn interpret_function(args: Atom, bindings: Bindings) -> MettaResult { let result = Atom::Variable(VariableAtom::new("result").make_unique()); once( Atom::expr([CHAIN_SYMBOL, Atom::expr([INTERPRET_SYMBOL, head, Atom::Expression(op_type), space.clone()]), rop.clone(), - Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), 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(), - Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), 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) } @@ -1138,24 +1138,41 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { 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(), - Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), 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([INTERPRET_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(), - Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("return-on-error"), rhead, + 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) + } +} + #[cfg(test)] mod tests { use super::*; From 7018c518168599d88fc54b2dcdb13c83b0485541 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 2 Jul 2024 13:50:56 +0300 Subject: [PATCH 13/26] Add native metta-call implementation --- lib/src/metta/interpreter_minimal_rust.rs | 51 ++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index ce4e131b4..7b3ec9038 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -1013,7 +1013,7 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { 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, Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("metta-call"), reduced, typ.clone(), space.clone()])]), result.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ.clone(), space.clone()])), result.clone(), return_atom(result) ]) ]), bindings.clone()) @@ -1026,7 +1026,7 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { let result = Atom::Variable(VariableAtom::new("result").make_unique()); once( Atom::expr([CHAIN_SYMBOL, call_native!(interpret_function, Atom::expr([atom, op_type, typ.clone(), space.clone()])), reduced.clone(), - Atom::expr([CHAIN_SYMBOL, Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::sym("metta-call"), reduced, typ, space])]), result.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ, space])), result.clone(), return_atom(result) ]) ]), bindings) @@ -1173,6 +1173,53 @@ fn return_on_error(args: Atom, bindings: Bindings) -> MettaResult { } } +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([INTERPRET_SYMBOL, result, typ, space]), ret.clone(), + return_atom(ret) + ]), bindings) + } +} + #[cfg(test)] mod tests { use super::*; From c965593c369e022c41b61e570fde917ac0dc47db Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 2 Jul 2024 20:34:20 +0300 Subject: [PATCH 14/26] Add call to the stack when native function is called --- lib/src/metta/interpreter_minimal_rust.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 7b3ec9038..7d489fda8 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -814,19 +814,20 @@ fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { 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 (func, args) = match_atom!{ - call ~ [_op, _name, func, args] - if func.as_gnd::().is_some() => (func, args), + 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, prev.clone()), bindings)) + .map(|(atom, bindings)| InterpretedAtom(atom_to_stack(atom, call_stack.clone()), bindings)) .collect() } From 12a41f2d791ffbb15b2183481ee5fdd075bf1b4b Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 3 Jul 2024 10:04:12 +0300 Subject: [PATCH 15/26] Apply bindings before evaluating grounded atoms It is needed because grounded atoms doesn't take into account bindings contents. --- lib/src/metta/interpreter_minimal_rust.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 7d489fda8..628ccf428 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -436,6 +436,7 @@ fn eval<'a, T: Space>(context: &InterpreterContext, stack: Stack, bindings: B 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 @ ..]) => { From 52d165c425d85bba889c8e7e313d87639e17f628 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 3 Jul 2024 10:49:17 +0300 Subject: [PATCH 16/26] Take into account any function type available --- lib/src/metta/interpreter_minimal_rust.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs index 628ccf428..d5acaa349 100644 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ b/lib/src/metta/interpreter_minimal_rust.rs @@ -1007,7 +1007,8 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { // 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 mut func_types = actual_types.split_off(func_start_index).into_iter(); + 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() { @@ -1023,15 +1024,17 @@ fn interpret_expression(args: Atom, bindings: Bindings) -> MettaResult { empty() }; - let func = if let Some(op_type) = func_types.next() { - 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_function, Atom::expr([atom, op_type, typ.clone(), space.clone()])), reduced.clone(), - Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, typ, space])), result.clone(), + 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) ]) - ]), bindings) + ]) + }) + .map(move |atom| (atom, bindings.clone()))) } else { empty() }; From d90cf8650ab4fffdd95b30192716b81b53eaa2e5 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 3 Jul 2024 17:19:02 +0300 Subject: [PATCH 17/26] Remove deprecated arithmetics module --- lib/src/atom/mod.rs | 4 +- lib/src/common/arithmetics.rs | 147 ---------------------------------- lib/src/common/mod.rs | 3 - lib/tests/types.rs | 80 +++++++++++++----- 4 files changed, 63 insertions(+), 171 deletions(-) delete mode 100644 lib/src/common/arithmetics.rs diff --git a/lib/src/atom/mod.rs b/lib/src/atom/mod.rs index d2b7767b1..5ebfc2e61 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/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))]) + } +} + From f6b73edcbb766ab73feb1fd1ea6ed3b54b7e8756 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 3 Jul 2024 18:08:24 +0300 Subject: [PATCH 18/26] Use minimal Rust interpreter by default old_interpreter feature can be used to switch back to the previous Rust interpreter. Add (interpret atom type space) to the old interpreter for compatibility. Remove previous minimal MeTTa interpreter. Fix unit tests which depends on a bare interpreter. --- lib/Cargo.toml | 6 +- lib/benches/interpreter_minimal.rs | 5 +- lib/src/metta/interpreter.rs | 6 +- lib/src/metta/interpreter_minimal.rs | 458 +++++- lib/src/metta/interpreter_minimal_rust.rs | 1720 --------------------- lib/src/metta/mod.rs | 7 +- lib/src/metta/runner/mod.rs | 19 +- lib/src/metta/runner/modules/mod.rs | 8 +- lib/src/metta/runner/stdlib.rs | 16 +- lib/src/metta/runner/stdlib_minimal.rs | 5 +- python/tests/test_atom.py | 17 +- python/tests/test_examples.py | 6 +- repl/Cargo.toml | 4 +- 13 files changed, 490 insertions(+), 1787 deletions(-) delete mode 100644 lib/src/metta/interpreter_minimal_rust.rs diff --git a/lib/Cargo.toml b/lib/Cargo.toml index f7e037cb6..905150100 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -27,12 +27,10 @@ path = "src/lib.rs" crate-type = ["lib"] [features] -default = ["minimal", "minimal_rust", "pkg_mgmt"] +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 -old_interpreter = [] # enables minimal MeTTa interpreter -minimal = [] # enables minimal MeTTa interpreter -minimal_rust = [] # enables minimal MeTTa interpreter with Rust 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 36cfaf6c3..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; @@ -9,10 +9,7 @@ use test::Bencher; use hyperon::*; use hyperon::space::grounding::*; use hyperon::metta::*; -#[cfg(not(feature = "minimal_rust"))] use hyperon::metta::interpreter_minimal::*; -#[cfg(feature = "minimal_rust")] -use hyperon::metta::interpreter_minimal_rust::*; fn chain_atom(size: isize) -> Atom { let mut atom = Atom::expr([CHAIN_SYMBOL, Atom::sym("A"), Atom::var("x"), Atom::var("x")]); diff --git a/lib/src/metta/interpreter.rs b/lib/src/metta/interpreter.rs index 794a1c2a9..8da3f84db 100644 --- a/lib/src/metta/interpreter.rs +++ b/lib/src/metta/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 == INTERPRET_SYMBOL => atom, + _ => expr, + }; let context = InterpreterContextRef::new(space); interpret_as_type_plan(context, InterpretedAtom(expr.clone(), Bindings::new()), diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index 2125ee950..d5acaa349 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -8,6 +8,8 @@ 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 +35,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 @@ -276,7 +284,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 == INTERPRET_SYMBOL + || *op == CALL_NATIVE_SYMBOL, _ => false, } } @@ -386,6 +396,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 == INTERPRET_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 +415,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 +433,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 +465,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 +578,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 +621,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 +698,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 +710,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 +741,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 +756,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 +791,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 +812,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: {}", INTERPRET_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([INTERPRET_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([INTERPRET_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([INTERPRET_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([INTERPRET_SYMBOL, result, typ, space]), ret.clone(), + return_atom(ret) + ]), bindings) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/src/metta/interpreter_minimal_rust.rs b/lib/src/metta/interpreter_minimal_rust.rs deleted file mode 100644 index d5acaa349..000000000 --- a/lib/src/metta/interpreter_minimal_rust.rs +++ /dev/null @@ -1,1720 +0,0 @@ -//! MeTTa assembly language implementation. -//! -//! # Algorithm -//! -//! TODO: explain an algorithm - -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; -use std::rc::Rc; -use std::fmt::Write; -use std::cell::RefCell; - -macro_rules! match_atom { - ($atom:tt ~ $pattern:tt => $succ:tt , _ => $error:tt) => { - match_atom!{ $atom ~ $pattern if true => $succ , _ => $error } - }; - ($atom:tt ~ $pattern:tt if $cond:expr => $succ:tt , _ => $error:tt) => { - match atom_as_slice(&$atom) { - #[allow(unused_variables)] - Some($pattern) if $cond => { - match atom_into_array($atom) { - Some($pattern) => $succ, - _ => panic!("Unexpected state"), - } - } - _ => $error, - } - }; -} - -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 -/// arguments are the result of the nested operation. Handler returns -/// None when it is not ready to provide new stack (it can happen in case of -/// collapse-bind operation) or new stack with variable bindings to continue -/// execution of the program. -type ReturnHandler = fn(Rc>, Atom, Bindings) -> Option<(Stack, Bindings)>; - -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq))] -struct Stack { - // Internal mutability is required to implement collapse-bind. All alternatives - // reference the same collapse-bind Stack instance. When some alternative - // finishes it modifies the collapse-bind state adding the result to the - // collapse-bind list of results. - // TODO: Try representing Option via Stack::Bottom - prev: Option>>, - atom: Atom, - ret: ReturnHandler, - // TODO: Could it be replaced by calling a return handler when setting the flag? - finished: bool, - vars: Variables, -} - -fn no_handler(_stack: Rc>, _atom: Atom, _bindings: Bindings) -> Option<(Stack, Bindings)> { - panic!("Unexpected state"); -} - -impl Stack { - fn from_prev_with_vars(prev: Option>>, atom: Atom, vars: Variables, ret: ReturnHandler) -> Self { - Self{ prev, atom, ret, finished: false, vars } - } - - fn from_prev_keep_vars(prev: Option>>, atom: Atom, ret: ReturnHandler) -> Self { - let vars = Self::vars_copy(&prev); - Self{ prev, atom, ret, finished: false, vars } - } - - fn finished(prev: Option>>, atom: Atom) -> Self { - Self{ prev, atom, ret: no_handler, finished: true, vars: Variables::new() } - } - - fn len(&self) -> usize { - self.fold(0, |len, _stack| len + 1) - } - - // TODO: should it be replaced by Iterator implementation? - fn fold T>(&self, mut val: T, mut app: F) -> T { - val = app(val, self); - match &self.prev { - None => val, - Some(prev) => prev.borrow().fold(val, app), - } - } - - fn vars_copy(prev: &Option>>) -> Variables { - match prev { - Some(prev) => prev.borrow().vars.clone(), - None => Variables::new(), - } - } - - fn add_vars_it<'a, I: 'a + Iterator>(prev: &Option>>, vars: I) -> Variables { - match prev { - Some(prev) => prev.borrow().vars.clone().insert_all(vars), - None => vars.cloned().collect(), - } - } -} - -impl Display for Stack { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - fn print_level(buffer: &mut String, level: usize, last: bool, stack: &Stack) -> std::fmt::Result { - let prefix = if last { "=> " } else { " " }; - let ret = if stack.finished { "return " } else { "" }; - write!(buffer, "{}{:05} {}{} {}\n", prefix, level, ret, stack.atom, stack.vars) - } - - let buffer = &mut String::new(); - let last_level = self.len(); - let res = print_level(buffer, last_level, true, self); - self.prev.as_ref().map_or(res, |prev| { - prev.borrow().fold((res, last_level - 1), |(res, level), top| { - (res.and_then(|_| print_level(buffer, level, false, top)), level - 1) - }).0 - }) - .and_then(|_| write!(f, "{}", buffer)) - } -} - -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] -struct InterpretedAtom(Stack, Bindings); - -impl Display for InterpretedAtom { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - if self.1.is_empty() { - write!(f, "{}", self.0) - } else { - write!(f, "{}\n{}", self.1, self.0) - } - } -} - -#[derive(Debug)] -struct InterpreterContext { - space: T, -} - -impl InterpreterContext { - fn new(space: T) -> Self { - Self{ space } - } -} - -// TODO: This wrapper is for compatibility with interpreter.rs only -pub trait SpaceRef<'a> : Space + 'a {} -impl<'a, T: Space + 'a> SpaceRef<'a> for T {} - -#[derive(Debug)] -pub struct InterpreterState<'a, T: SpaceRef<'a>> { - plan: Vec, - finished: Vec, - context: InterpreterContext, - phantom: std::marker::PhantomData>, -} - -fn atom_as_slice(atom: &Atom) -> Option<&[Atom]> { - <&[Atom]>::try_from(atom).ok() -} - -fn atom_as_slice_mut(atom: &mut Atom) -> Option<&mut [Atom]> { - <&mut [Atom]>::try_from(atom).ok() -} - -fn atom_into_array(atom: Atom) -> Option<[Atom; N]> { - <[Atom; N]>::try_from(atom).ok() -} - -impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { - - /// INTERNAL USE ONLY. Create an InterpreterState that is ready to yield results - #[allow(dead_code)] //TODO: MINIMAL only silence the warning until interpreter_minimal replaces interpreter - pub(crate) fn new_finished(space: T, results: Vec) -> Self { - Self { - plan: vec![], - finished: results, - context: InterpreterContext::new(space), - phantom: std::marker::PhantomData, - } - } - - pub fn has_next(&self) -> bool { - !self.plan.is_empty() - } - - pub fn into_result(self) -> Result, String> { - if self.has_next() { - Err("Evaluation is not finished".into()) - } else { - Ok(self.finished) - } - } - - fn pop(&mut self) -> Option { - self.plan.pop() - } - - fn push(&mut self, atom: InterpretedAtom) { - if atom.0.prev.is_none() && atom.0.finished { - let InterpretedAtom(stack, bindings) = atom; - if stack.atom != EMPTY_SYMBOL { - let atom = apply_bindings_to_atom_move(stack.atom, &bindings); - self.finished.push(atom); - } - } else { - self.plan.push(atom); - } - } -} - -impl<'a, T: SpaceRef<'a>> std::fmt::Display for InterpreterState<'a, T> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}\n", self.plan) - } -} - -/// Initialize interpreter and returns the starting interpreter state. -/// See [crate::metta::interpreter_minimal] for algorithm explanation. -/// -/// # Arguments -/// * `space` - atomspace to query for interpretation -/// * `expr` - atom to interpret -pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterState<'a, T> { - let context = InterpreterContext::new(space); - InterpreterState { - plan: vec![InterpretedAtom(atom_to_stack(expr.clone(), None), Bindings::new())], - finished: vec![], - context, - phantom: std::marker::PhantomData, - } -} - -/// Perform next step of the interpretation return the resulting interpreter -/// state. See [crate::metta::interpreter_minimal] for algorithm explanation. -/// -/// # Arguments -/// * `state` - interpreter state from the previous step. -pub fn interpret_step<'a, T: Space + 'a>(mut state: InterpreterState<'a, T>) -> InterpreterState<'a, T> { - let interpreted_atom = state.pop().unwrap(); - log::debug!("interpret_step:\n{}", interpreted_atom); - let InterpretedAtom(stack, bindings) = interpreted_atom; - for result in interpret_stack(&state.context, stack, bindings) { - state.push(result); - } - state -} - -/// Interpret passed atom and return a new plan, result or error. This function -/// blocks until result is calculated. For step by step interpretation one -/// should use [interpret_init] and [interpret_step] functions. -/// # Arguments -/// * `space` - atomspace to query for interpretation -/// * `expr` - atom to interpret -pub fn interpret(space: T, expr: &Atom) -> Result, String> { - let mut state = interpret_init(space, expr); - while state.has_next() { - state = interpret_step(state); - } - state.into_result() -} - -fn is_embedded_op(atom: &Atom) -> bool { - let expr = atom_as_slice(&atom); - match expr { - Some([op, ..]) => *op == EVAL_SYMBOL - || *op == CHAIN_SYMBOL - || *op == UNIFY_SYMBOL - || *op == CONS_ATOM_SYMBOL - || *op == DECONS_ATOM_SYMBOL - || *op == FUNCTION_SYMBOL - || *op == COLLAPSE_BIND_SYMBOL - || *op == SUPERPOSE_BIND_SYMBOL - || *op == INTERPRET_SYMBOL - || *op == CALL_NATIVE_SYMBOL, - _ => false, - } -} - -fn is_op(atom: &Atom, op: &Atom) -> bool { - let expr = atom_as_slice(&atom); - match expr { - Some([opp, ..]) => opp == op, - _ => false, - } -} - -fn is_function_op(atom: &Atom) -> bool { - is_op(atom, &FUNCTION_SYMBOL) -} - -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq))] -struct Variables(im::HashSet); - -impl Variables { - fn new() -> Self { - Self(im::HashSet::new()) - } - fn insert(&mut self, var: VariableAtom) -> Option { - self.0.insert(var) - } - fn insert_all<'a, I: 'a + Iterator>(mut self, it: I) -> Self { - it.for_each(|var| { self.insert(var.clone()); }); - self - } -} -fn vars_from_atom(atom: &Atom) -> impl Iterator { - atom.iter().filter_type::<&VariableAtom>() -} - -impl FromIterator for Variables { - fn from_iter>(iter: I) -> Self { - Self(im::HashSet::from_iter(iter)) - } -} - -impl VariableSet for Variables { - type Iter<'a> = im::hashset::Iter<'a, atom::VariableAtom> where Self: 'a; - - fn contains(&self, var: &VariableAtom) -> bool { - self.0.contains(var) - } - fn iter(&self) -> Self::Iter<'_> { - self.0.iter() - } -} - -impl Display for Variables { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "[") - .and_then(|_| self.iter().take(1).fold(Ok(()), - |res, atom| res.and_then(|_| write!(f, "{}", atom)))) - .and_then(|_| self.iter().skip(1).fold(Ok(()), - |res, atom| res.and_then(|_| write!(f, " {}", atom)))) - .and_then(|_| write!(f, "]")) - } -} - -fn interpret_stack<'a, T: Space>(context: &InterpreterContext, stack: Stack, mut bindings: Bindings) -> Vec { - if stack.finished { - // first executed minimal operation returned error - if stack.prev.is_none() { - return vec![InterpretedAtom(stack, bindings)]; - } - let Stack{ prev, mut atom, ret: _, finished: _, vars: _ } = stack; - let prev = match prev { - Some(prev) => prev, - None => panic!("Unexpected state"), - }; - { - let outer_vars = &prev.borrow().vars; - bindings.apply_and_retain(&mut atom, |v| outer_vars.contains(v)); - } - let ret = prev.borrow().ret; - ret(prev, atom, bindings) - .map_or(vec![], |(stack, bindings)| vec![InterpretedAtom(stack, bindings)]) - } else { - let expr = atom_as_slice(&stack.atom); - let result = match expr { - Some([op, ..]) if *op == EVAL_SYMBOL => { - eval(context, stack, bindings) - }, - Some([op, ..]) if *op == CHAIN_SYMBOL => { - chain(stack, bindings) - }, - Some([op, ..]) if *op == FUNCTION_SYMBOL => { - panic!("Unexpected state") - }, - Some([op, ..]) if *op == COLLAPSE_BIND_SYMBOL => { - collapse_bind(stack, bindings) - }, - Some([op, ..]) if *op == UNIFY_SYMBOL => { - unify(stack, bindings) - }, - Some([op, ..]) if *op == DECONS_ATOM_SYMBOL => { - decons_atom(stack, bindings) - }, - Some([op, ..]) if *op == CONS_ATOM_SYMBOL => { - cons_atom(stack, bindings) - }, - Some([op, ..]) if *op == SUPERPOSE_BIND_SYMBOL => { - superpose_bind(stack, bindings) - }, - Some([op, ..]) if *op == INTERPRET_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)] - }, - }; - result - } -} - -fn return_not_reducible() -> Atom { - NOT_REDUCIBLE_SYMBOL -} - -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 { - vec![InterpretedAtom(Stack::finished(prev, atom), bindings)] -} - -fn eval<'a, T: Space>(context: &InterpreterContext, stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: eval, ret: _, finished: _, vars } = stack; - let to_eval = match_atom!{ - eval ~ [_op, to_eval] => to_eval, - _ => { - let error = format!("expected: ({} ), found: {}", EVAL_SYMBOL, eval); - 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 @ ..]) => { - match op.as_grounded().as_execute() { - None => finished_result(return_not_reducible(), bindings, prev), - Some(executable) => { - let exec_res = executable.execute(args); - log::debug!("eval: execution results: {:?}", exec_res); - match exec_res { - Ok(results) => { - if results.is_empty() { - // There is no valid reason to return empty result from - // the grounded function. If alternative should be removed - // from the plan then EMPTY_SYMBOL is a proper result. - // If grounded atom returns no value then UNIT_ATOM() - // should be returned. NotReducible or Exec::NoReduce - // can be returned to let a caller know that function - // is not defined on a passed input data. Thus we can - // interpreter empty result by any way we like. - finished_result(EMPTY_SYMBOL, bindings, prev) - } else { - let call_stack = call_to_stack(to_eval, vars, prev.clone()); - results.into_iter() - .map(|res| eval_result(prev.clone(), res, &call_stack, bindings.clone())) - .collect() - } - }, - Err(ExecError::Runtime(err)) => - 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. - finished_result(return_not_reducible(), bindings, prev), - } - }, - } - }, - _ if is_embedded_op(&to_eval) => - vec![InterpretedAtom(atom_to_stack(to_eval, prev), bindings)], - _ => query(&context.space, prev, to_eval, bindings, vars), - } -} - -fn eval_result(prev: Option>>, res: Atom, call_stack: &Rc>, mut bindings: Bindings) -> InterpretedAtom { - let stack = if is_function_op(&res) { - let mut stack = function_to_stack(res, Some(call_stack.clone())); - let call_stack = call_stack.borrow(); - // Apply arguments bindings is required to replace formal argument - // variables by matched actual argument variables. Otherwise the - // equality of formal and actual argument variables will be cleaned up - // on any return from the nested function call. - // TODO: we could instead add formal argument variables of the called - // functions into stack.vars collection. One way of doing it is getting - // list of formal var parameters from the function definition atom - // but we don't have it returned from the query. Another way is to - // find all variables equalities in bindings with variables - // from call_stack.vars. - bindings.apply_and_retain(&mut stack.atom, |v| call_stack.vars.contains(v)); - stack - } else { - Stack::finished(prev, res) - }; - InterpretedAtom(stack, bindings) -} - -fn call_to_stack(call: Atom, vars: Variables, prev: Option>>) -> Rc> { - let vars = vars.insert_all(vars_from_atom(&call)); - let stack = Stack::from_prev_with_vars(prev, call, vars, call_ret); - Rc::new(RefCell::new(stack)) -} - -#[cfg(not(feature = "variable_operation"))] -fn is_variable_op(atom: &Atom) -> bool { - match atom { - Atom::Expression(expr) => { - match expr.children().get(0) { - Some(Atom::Variable(_)) => true, - _ => false, - } - }, - _ => false, - } -} - -fn query<'a, T: Space>(space: T, prev: Option>>, to_eval: Atom, bindings: Bindings, vars: Variables) -> Vec { - #[cfg(not(feature = "variable_operation"))] - if is_variable_op(&to_eval) { - // TODO: This is a hotfix. Better way of doing this is adding - // a function which modifies minimal MeTTa interpreter code - // in order to skip such evaluations in metta-call function. - return finished_result(return_not_reducible(), bindings, prev) - } - let var_x = &VariableAtom::new("X").make_unique(); - let query = Atom::expr([EQUAL_SYMBOL, to_eval.clone(), Atom::Variable(var_x.clone())]); - let results = space.query(&query); - if results.is_empty() { - finished_result(return_not_reducible(), bindings, prev) - } else { - log::debug!("interpreter_minimal::query: query: {}", query); - log::debug!("interpreter_minimal::query: results.len(): {}, bindings.len(): {}, results: {} bindings: {}", - results.len(), bindings.len(), results, bindings); - let call_stack = call_to_stack(to_eval, vars, prev.clone()); - let result = |res, bindings| eval_result(prev.clone(), res, &call_stack, bindings); - results.into_iter().flat_map(|b| { - log::debug!("interpreter_minimal::query: b: {}", b); - b.merge_v2(&bindings).into_iter() - }).filter_map(move |b| { - let res = b.resolve(&var_x).unwrap(); - if b.has_loops() { - None - } else { - Some(result(res, b)) - } - }) - .collect() - } -} - -fn atom_to_stack(atom: Atom, prev: Option>>) -> Stack { - let expr = atom_as_slice(&atom); - let result = match expr { - Some([op, ..]) if *op == CHAIN_SYMBOL => - chain_to_stack(atom, prev), - Some([op, ..]) if *op == FUNCTION_SYMBOL => - function_to_stack(atom, prev), - Some([op, ..]) if *op == EVAL_SYMBOL => - Stack::from_prev_keep_vars(prev, atom, no_handler), - Some([op, ..]) if *op == UNIFY_SYMBOL => - unify_to_stack(atom, prev), - _ => - Stack::from_prev_keep_vars(prev, atom, no_handler), - }; - result -} - -fn chain_to_stack(mut atom: Atom, prev: Option>>) -> Stack { - let mut nested = Atom::sym("%Nested%"); - let (nested_arg, templ_arg) = match atom_as_slice_mut(&mut atom) { - Some([_op, nested, Atom::Variable(_var), templ]) => (nested, templ), - _ => { - let error: String = format!("expected: ({} (: Variable) ), found: {}", CHAIN_SYMBOL, atom); - return Stack::finished(prev, error_msg(atom, error)); - }, - }; - std::mem::swap(nested_arg, &mut nested); - let nested_vars: im::HashSet<&VariableAtom> = vars_from_atom(&nested).collect(); - let templ_vars: im::HashSet<&VariableAtom> = vars_from_atom(templ_arg).collect(); - let both_vars = nested_vars.intersection(templ_vars).into_iter(); - let vars = Stack::add_vars_it(&prev, both_vars); - let cur = Stack::from_prev_with_vars(prev, atom, vars, chain_ret); - atom_to_stack(nested, Some(Rc::new(RefCell::new(cur)))) -} - -fn chain_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { - let mut stack = (*stack.borrow()).clone(); - let nested = atom; - let Stack{ prev: _, atom: chain, ret: _, finished: _, vars: _} = &mut stack; - let arg = match atom_as_slice_mut(chain) { - Some([_op, nested, Atom::Variable(_var), _templ]) => nested, - _ => panic!("Unexpected state"), - }; - *arg = nested; - Some((stack, bindings)) -} - -fn chain(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: chain, ret: _, finished: _, vars: _} = stack; - let (nested, var, templ) = match_atom!{ - chain ~ [_op, nested, Atom::Variable(var), templ] => (nested, var, templ), - _ => { - panic!("Unexpected state") - } - }; - let b = Bindings::new().add_var_binding_v2(var, nested).unwrap(); - let templ = apply_bindings_to_atom_move(templ, &b); - vec![InterpretedAtom(atom_to_stack(templ, prev), bindings)] -} - -fn function_to_stack(mut atom: Atom, prev: Option>>) -> Stack { - let mut nested = Atom::sym("%Nested%"); - let nested_arg = match atom_as_slice_mut(&mut atom) { - Some([_op, nested @ Atom::Expression(_)]) => nested, - _ => { - let error: String = format!("expected: ({} (: Expression)), found: {}", FUNCTION_SYMBOL, atom); - return Stack::finished(prev, error_msg(atom, error)); - }, - }; - std::mem::swap(nested_arg, &mut nested); - let cur = Stack::from_prev_keep_vars(prev, atom, function_ret); - atom_to_stack(nested, Some(Rc::new(RefCell::new(cur)))) -} - -fn call_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { - let stack = Stack::finished(stack.borrow().prev.clone(), atom); - Some((stack, bindings)) -} - -fn function_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { - match_atom!{ - atom ~ [op, result] if *op == RETURN_SYMBOL => { - let stack = Stack::finished(stack.borrow().prev.clone(), result); - Some((stack, bindings)) - }, - _ => { - Some((atom_to_stack(atom, Some(stack)), bindings)) - } - } -} - -fn collapse_bind(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: mut collapse, ret: _, finished: _, vars } = stack; - - let mut nested = Atom::expr([]); - match &mut collapse { - Atom::Expression(expr) => { - std::mem::swap(&mut nested, &mut expr.children_mut()[1]); - expr.children_mut().push(Atom::value(bindings.clone())) - }, - _ => panic!("Unexpected state"), - } - - let prev = Stack::from_prev_with_vars(prev, collapse, vars, collapse_bind_ret); - let cur = atom_to_stack(nested, Some(Rc::new(RefCell::new(prev)))); - vec![InterpretedAtom(cur, bindings)] -} - -fn collapse_bind_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { - let nested = atom; - { - let stack_ref = &mut *stack.borrow_mut(); - let Stack{ prev: _, atom: collapse, ret: _, finished: _, vars: _ } = stack_ref; - let finished = match atom_as_slice_mut(collapse) { - Some([_op, Atom::Expression(finished), _bindings]) => finished, - _ => panic!("Unexpected state"), - }; - finished.children_mut().push(atom_bindings_into_atom(nested, bindings)); - } - - // all alternatives are evaluated - match Rc::into_inner(stack).map(RefCell::into_inner) { - Some(stack) => { - let Stack{ prev, atom: collapse, ret: _, finished: _, vars: _ } = stack; - let (result, bindings) = match atom_into_array(collapse) { - Some([_op, result, bindings]) => (result, atom_into_bindings(bindings)), - None => panic!("Unexpected state"), - }; - Some((Stack::finished(prev, result), bindings)) - }, - None => None, - } -} - -fn atom_bindings_into_atom(atom: Atom, bindings: Bindings) -> Atom { - Atom::expr([atom, Atom::value(bindings)]) -} - -fn unify_to_stack(mut atom: Atom, prev: Option>>) -> Stack { - let () = match atom_as_slice_mut(&mut atom) { - Some([_op, _a, _b, _then, _else]) => (), - _ => { - let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, atom); - return Stack::finished(prev, error_msg(atom, error)); - }, - }; - Stack::from_prev_with_vars(prev, atom, Variables::new(), no_handler) -} - -fn unify(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: unify, ret: _, finished: _, vars: _ } = stack; - let (atom, pattern, then, else_) = match_atom!{ - unify ~ [_op, atom, pattern, then, else_] => (atom, pattern, then, else_), - _ => { - let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, unify); - return finished_result(error_msg(unify, error), bindings, prev); - } - }; - - let matches: Vec = match_atoms(&atom, &pattern).collect(); - if matches.is_empty() { - finished_result(else_, bindings, prev) - } else { - let result = |bindings| { - let stack = Stack::finished(prev.clone(), then.clone()); - InterpretedAtom(stack, bindings) - }; - matches.into_iter().flat_map(move |b| { - b.merge_v2(&bindings).into_iter().filter_map(move |b| { - if b.has_loops() { - None - } else { - Some(result(b)) - } - }) - }) - .collect() - } -} - -fn decons_atom(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: decons, ret: _, finished: _, vars: _ } = stack; - let expr = match_atom!{ - 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_msg(decons, error), bindings, prev); - } - }; - let mut children = expr.into_children(); - let head = children.remove(0); - let tail = children; - finished_result(Atom::expr([head, Atom::expr(tail)]), bindings, prev) -} - -fn cons_atom(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: cons, ret: _, finished: _, vars: _ } = stack; - let (head, tail) = match_atom!{ - cons ~ [_op, head, Atom::Expression(tail)] => (head, tail), - _ => { - let error: String = format!("expected: ({} (: Expression)), found: {}", CONS_ATOM_SYMBOL, cons); - return finished_result(error_msg(cons, error), bindings, prev); - } - }; - let mut children = vec![head]; - children.extend(tail.into_children()); - finished_result(Atom::expr(children), bindings, prev) -} - -fn atom_into_atom_bindings(pair: Atom) -> (Atom, Bindings) { - match_atom!{ - pair ~ [atom, bindings] => (atom, atom_into_bindings(bindings)), - _ => { - panic!("(Atom Bindings) pair is expected, {} was received", pair) - } - } -} - -fn atom_into_bindings(bindings: Atom) -> Bindings { - match bindings.as_gnd::() { - Some(bindings) => { - // TODO: cloning is ineffective, but it is not possible - // to convert grounded atom into internal value at the - // moment - bindings.clone() - }, - _ => panic!("Unexpected state: second item cannot be converted to Bindings"), - } -} - -fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: superpose, ret: _, finished: _, vars: _ } = stack; - let collapsed = match_atom!{ - superpose ~ [_op, Atom::Expression(collapsed)] => collapsed, - _ => { - let error: String = format!("expected: ({} (: Expression)), found: {}", SUPERPOSE_BIND_SYMBOL, superpose); - return finished_result(error_msg(superpose, error), bindings, prev); - } - }; - collapsed.into_children().into_iter() - .map(atom_into_atom_bindings) - .flat_map(|(atom, b)| { - let result = |atom, bindings| { - let stack = Stack::finished(prev.clone(), atom); - InterpretedAtom(stack, bindings) - }; - b.merge_v2(&bindings).into_iter().filter_map(move |b| { - if b.has_loops() { - None - } else { - Some(result(atom.clone(), b)) - } - }) - }) - .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: {}", INTERPRET_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([INTERPRET_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([INTERPRET_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([INTERPRET_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([INTERPRET_SYMBOL, result, typ, space]), ret.clone(), - return_atom(ret) - ]), bindings) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::common::test_utils::{metta_atom, metta_space}; - - #[test] - fn interpret_atom_evaluate_incorrect_args() { - assert_eq!(call_interpret(&space(""), &metta_atom("(eval)")), - vec![expr!("Error" ("eval") "expected: (eval ), found: (eval)")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(eval a b)")), - vec![expr!("Error" ("eval" "a" "b") "expected: (eval ), found: (eval a b)")]); - } - - #[test] - fn interpret_atom_evaluate_atom() { - let result = call_interpret(&space("(= a b)"), &metta_atom("(eval a)")); - assert_eq!(result, vec![metta_atom("b")]); - } - - #[test] - fn interpret_atom_evaluate_atom_no_definition() { - let result = call_interpret(&space(""), &metta_atom("(eval a)")); - assert_eq!(result, vec![metta_atom("NotReducible")]); - } - - #[test] - fn interpret_atom_evaluate_empty_expression() { - let result = call_interpret(&space(""), &metta_atom("(eval ())")); - assert_eq!(result, vec![metta_atom("NotReducible")]); - } - - #[test] - fn interpret_atom_evaluate_grounded_value() { - let result = call_interpret(&space(""), &expr!("eval" {6})); - assert_eq!(result, vec![metta_atom("NotReducible")]); - } - - - #[test] - fn interpret_atom_evaluate_pure_expression() { - let space = space("(= (foo $a B) $a)"); - let result = call_interpret(&space, &metta_atom("(eval (foo A $b))")); - assert_eq!(result, vec![metta_atom("A")]); - } - - #[test] - fn interpret_atom_evaluate_pure_expression_non_determinism() { - let space = space(" - (= color red) - (= color green) - (= color blue) - "); - let result = call_interpret(&space, &metta_atom("(eval color)")); - assert_eq_no_order!(result, vec![ - metta_atom("red"), - metta_atom("green"), - metta_atom("blue"), - ]); - } - - #[test] - fn interpret_atom_evaluate_pure_expression_no_definition() { - let result = call_interpret(&space(""), &metta_atom("(eval (foo A))")); - assert_eq!(result, vec![metta_atom("NotReducible")]); - } - - #[test] - fn interpret_atom_evaluate_pure_expression_variable_name_conflict() { - let space = space("(= (foo ($W)) True)"); - let result = call_interpret(&space, &metta_atom("(eval (foo $W))")); - assert_eq!(result[0], sym!("True")); - } - - #[test] - fn interpret_atom_evaluate_grounded_expression() { - let result = call_interpret(&space(""), &expr!("eval" ({MulXUndefinedType(7)} {6}))); - assert_eq!(result, vec![expr!({42})]); - } - - #[test] - fn interpret_atom_evaluate_grounded_expression_empty() { - let result = call_interpret(&space(""), &expr!("eval" ({ReturnNothing()} {6}))); - assert_eq!(result, vec![]); - } - - #[test] - fn interpret_atom_evaluate_grounded_expression_noreduce() { - let result = call_interpret(&space(""), &expr!("eval" ({NonReducible()} {6}))); - assert_eq!(result, vec![expr!("NotReducible")]); - } - - #[test] - fn interpret_atom_evaluate_grounded_expression_error() { - let result = call_interpret(&space(""), &expr!("eval" ({ThrowError()} {"Test error"}))); - assert_eq!(result, vec![expr!("Error" ({ThrowError()} {"Test error"}) "Test error")]); - } - - #[test] - fn interpret_atom_evaluate_variable_operation() { - let space = space("(= (foo $a B) $a)"); - let result = call_interpret(&space, &metta_atom("(eval ($a A $b))")); - #[cfg(feature = "variable_operation")] - assert_eq!(result, vec![metta_atom("A")]); - #[cfg(not(feature = "variable_operation"))] - assert_eq!(result, vec![NOT_REDUCIBLE_SYMBOL]); - } - - #[test] - fn interpret_atom_evaluate_variable_via_call_direct_equality() { - let space = space(" - (= (bar) (function (return ()))) - (= (foo $b) (function - (chain (eval (bar)) $_ - (unify $b value - (return ()) - (return (Error () \"Unexpected error\")) ))))"); - let result = call_interpret(&space, - &metta_atom("(chain (eval (foo $a)) $_ $a)")); - assert_eq!(result[0], sym!("value")); - } - - #[test] - fn interpret_atom_evaluate_variable_via_call_struct_equality() { - let formal_arg_struct = space(" - (= (bar) (function (return ()))) - (= (foo ($b)) (function - (chain (eval (bar)) $_ - (unify $b value - (return ()) - (return (Error () \"Unexpected error\")) ))))"); - let result = call_interpret(&formal_arg_struct, - &metta_atom("(chain (eval (foo $a)) $_ $a)")); - assert_eq!(result[0], expr!(("value"))); - - let actual_arg_struct = space(" - (= (bar) (function (return ()))) - (= (foo $b) (function - (chain (eval (bar)) $_ - (unify $b (value) - (return ()) - (return (Error () \"Unexpected error\")) ))))"); - let result = call_interpret(&actual_arg_struct, - &metta_atom("(chain (eval (foo ($a))) $_ $a)")); - assert_eq!(result[0], sym!("value")); - } - - - #[test] - fn interpret_atom_chain_incorrect_args() { - assert_eq!(call_interpret(&space(""), &metta_atom("(chain n $v t o)")), - vec![expr!("Error" ("chain" "n" v "t" "o") "expected: (chain (: Variable) ), found: (chain n $v t o)")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(chain n v t)")), - vec![expr!("Error" ("chain" "n" "v" "t") "expected: (chain (: Variable) ), found: (chain n v t)")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(chain n $v)")), - vec![expr!("Error" ("chain" "n" v) "expected: (chain (: Variable) ), found: (chain n $v)")]); - } - - #[test] - fn interpret_atom_chain_atom() { - let result = call_interpret(&space(""), &expr!("chain" ("A" () {6} y) x ("bar" x))); - assert_eq!(result, vec![expr!("bar" ("A" () {6} y))]); - } - - - #[test] - fn interpret_atom_chain_evaluation() { - let space = space("(= (foo $a B) $a)"); - let result = call_interpret(&space, &metta_atom("(chain (eval (foo A $b)) $x (bar $x))")); - assert_eq!(result, vec![metta_atom("(bar A)")]); - } - - #[test] - fn interpret_atom_chain_nested_evaluation() { - let space = space("(= (foo $a B) $a)"); - let result = call_interpret(&space, &metta_atom("(chain (chain (eval (foo A $b)) $x (bar $x)) $y (baz $y))")); - assert_eq!(result, vec![metta_atom("(baz (bar A))")]); - } - - #[test] - fn interpret_atom_chain_nested_value() { - let result = call_interpret(&space(""), &metta_atom("(chain (chain A $x (bar $x)) $y (baz $y))")); - assert_eq!(result, vec![metta_atom("(baz (bar A))")]); - } - - #[test] - fn interpret_atom_chain_expression_non_determinism() { - let space = space(" - (= (color) red) - (= (color) green) - (= (color) blue) - "); - let result = call_interpret(&space, &metta_atom("(chain (eval (color)) $x (bar $x))")); - assert_eq_no_order!(result, vec![ - metta_atom("(bar red)"), - metta_atom("(bar green)"), - metta_atom("(bar blue))"), - ]); - } - - #[test] - fn interpret_atom_chain_return() { - let result = call_interpret(&space(""), &metta_atom("(chain Empty $x (bar $x))")); - assert_eq!(result, vec![metta_atom("(bar Empty)")]); - } - - #[test] - fn interpret_atom_chain_keep_var_from_evaluated_part() { - let result = call_interpret(&space("(= (even 4) True)"), &metta_atom("(chain (eval (even $x)) $res (= (is-even $x) $res))")); - assert_eq!(result, vec![metta_atom("(= (is-even 4) True)")]); - } - - - #[test] - fn interpret_atom_unify_incorrect_args() { - assert_eq!(call_interpret(&space(""), &metta_atom("(unify a p t e o)")), - vec![expr!("Error" ("unify" "a" "p" "t" "e" "o") "expected: (unify ), found: (unify a p t e o)")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(unify a p t)")), - vec![expr!("Error" ("unify" "a" "p" "t") "expected: (unify ), found: (unify a p t)")]); - } - - #[test] - fn interpret_atom_unify_then() { - let result = call_interpret(&space(""), &metta_atom("(unify (A $b) ($a B) ($a $b) Empty)")); - assert_eq!(result, vec![metta_atom("(A B)")]); - } - - #[test] - fn interpret_atom_unify_else() { - let result = call_interpret(&space(""), &metta_atom("(unify (A $b C) ($a B D) ($a $b) Empty)")); - assert_eq!(result, vec![]); - } - - - #[test] - fn interpret_atom_decons_atom_incorrect_args() { - assert_eq!(call_interpret(&space(""), &metta_atom("(decons-atom a)")), - vec![expr!("Error" ("decons-atom" "a") "expected: (decons-atom (: Expression)), found: (decons-atom a)")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(decons-atom (a) (b))")), - vec![expr!("Error" ("decons-atom" ("a") ("b")) "expected: (decons-atom (: Expression)), found: (decons-atom (a) (b))")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(decons-atom)")), - vec![expr!("Error" ("decons-atom") "expected: (decons-atom (: Expression)), found: (decons-atom)")]); - } - - #[test] - fn interpret_atom_decons_atom_empty() { - let result = call_interpret(&space(""), &metta_atom("(decons-atom ())")); - assert_eq!(result, vec![expr!("Error" ("decons-atom" ()) "expected: (decons-atom (: Expression)), found: (decons-atom ())")]); - } - - #[test] - fn interpret_atom_decons_atom_single() { - let result = call_interpret(&space(""), &metta_atom("(decons-atom (a))")); - assert_eq!(result, vec![metta_atom("(a ())")]); - } - - #[test] - fn interpret_atom_decons_atom_list() { - let result = call_interpret(&space(""), &metta_atom("(decons-atom (a b c))")); - assert_eq!(result, vec![metta_atom("(a (b c))")]); - } - - - #[test] - fn interpret_atom_cons_atom_incorrect_args() { - assert_eq!(call_interpret(&space(""), &metta_atom("(cons-atom a (e) o)")), - vec![expr!("Error" ("cons-atom" "a" ("e") "o") "expected: (cons-atom (: Expression)), found: (cons-atom a (e) o)")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(cons-atom a e)")), - vec![expr!("Error" ("cons-atom" "a" "e") "expected: (cons-atom (: Expression)), found: (cons-atom a e)")]); - assert_eq!(call_interpret(&space(""), &metta_atom("(cons-atom a)")), - vec![expr!("Error" ("cons-atom" "a") "expected: (cons-atom (: Expression)), found: (cons-atom a)")]); - } - - #[test] - fn interpret_atom_cons_atom_empty() { - let result = call_interpret(&space(""), &metta_atom("(cons-atom a ())")); - assert_eq!(result, vec![metta_atom("(a)")]); - } - - #[test] - fn interpret_atom_cons_atom_single() { - let result = call_interpret(&space(""), &metta_atom("(cons-atom a (b))")); - assert_eq!(result, vec![metta_atom("(a b)")]); - } - - #[test] - fn interpret_atom_cons_atom_list() { - let result = call_interpret(&space(""), &metta_atom("(cons-atom a (b c))")); - assert_eq!(result, vec![metta_atom("(a b c)")]); - } - - - #[test] - fn test_superpose_bind() { - let vars: Variables = [ "a", "b", "c" ].into_iter().map(VariableAtom::new).collect(); - let atom = Atom::expr([Atom::sym("superpose-bind"), - Atom::expr([atom_bindings_into_atom(expr!("foo" a b), bind!{ a: expr!("A"), c: expr!("C") })])]); - let stack = Stack{ prev: None, atom, ret: no_handler, finished: false, vars: vars.clone() }; - - let result = superpose_bind(stack, bind!{ b: expr!("B"), d: expr!("D") }); - - assert_eq!(result, vec![InterpretedAtom( - Stack{ prev: None, atom: expr!("foo" a b), ret: no_handler, finished: true, vars: Variables::new() }, - bind!{ a: expr!("A"), b: expr!("B"), c: expr!("C"), d: expr!("D") } - )]); - } - - #[test] - fn metta_turing_machine() { - let space = space(" - (= (tm $rule $state $tape) - (function (eval (tm-body $rule $state $tape))) ) - - (= (tm-body $rule $state $tape) - (unify $state HALT - (return $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-body $rule $next-state $next-tape)) ) - (return (Error (tm-body $rule $state $tape) \"Incorrect state\")) ))))) - - (= (read ($head $hole $tail)) $hole) - - (= (move ($head $hole $tail) $char N) ($head $char $tail)) - (= (move ($head $hole $tail) $char L) (function - (chain (cons-atom $char $head) $next-head - (chain (decons-atom $tail) $list - (unify $list ($next-hole $next-tail) - (return ($next-head $next-hole $next-tail)) - (return ($next-head 0 ())) ))))) - (= (move ($head $hole $tail) $char R) (function - (chain (cons-atom $char $tail) $next-tail - (chain (decons-atom $head) $list - (unify $list ($next-hole $next-head) - (return ($next-head $next-hole $next-tail)) - (return (() 0 $next-tail)) ))))) - - (= (busy-beaver A 0) (B 1 R)) - (= (busy-beaver A 1) (C 1 L)) - - (= (busy-beaver B 0) (A 1 L)) - (= (busy-beaver B 1) (B 1 R)) - - (= (busy-beaver C 0) (B 1 L)) - (= (busy-beaver C 1) (HALT 1 N)) - - "); - let result = interpret(space, &metta_atom("(eval (tm busy-beaver A (() 0 ())))")); - assert_eq!(result, Ok(vec![metta_atom("((1 1) 1 (1 1 1))")])); - } - - #[test] - fn interpret_minimal_metta_smoketest() { - let space = space(" - (= (foo $a B) $a) - (= (fu $x) (function (chain (eval (foo $x B)) $r (return $r)))) - (= (color) red) - (= (color) green) - (= (color) blue) - "); - let result = interpret(&space, &metta_atom("(chain (chain A $x $x) $y $y)")); - assert_eq!(result, Ok(vec![metta_atom("A")])); - let result = interpret(&space, &metta_atom("(chain (chain (eval (foo A $b)) $x (bar $x)) $y (baz $y))")); - assert_eq!(result, Ok(vec![metta_atom("(baz (bar A))")])); - let result = interpret(&space, &metta_atom("(chain (chain (eval (fu A)) $x (bar $x)) $y (baz $y))")); - assert_eq!(result, Ok(vec![metta_atom("(baz (bar A))")])); - let result = interpret(&space, &metta_atom("(unify (A $b) ($a B) ($a $b) Empty)")); - assert_eq!(result, Ok(vec![metta_atom("(A B)")])); - let result = interpret(&space, &metta_atom("(decons-atom (a b c))")); - assert_eq!(result, Ok(vec![metta_atom("(a (b c))")])); - let result = interpret(&space, &metta_atom("(cons-atom a (b c))")); - assert_eq!(result, Ok(vec![metta_atom("(a b c)")])); - let result = interpret(&space, &metta_atom("(chain (collapse-bind (eval (color))) $collapsed (superpose-bind $collapsed))")).unwrap(); - assert_eq_no_order!(result, vec![metta_atom("red"), metta_atom("green"), metta_atom("blue")]); - let result = interpret(&space, &metta_atom("((P $a B) $a)")); - assert_eq!(result, Ok(vec![metta_atom("((P $a B) $a)")])); - let result = interpret(&space, &metta_atom("(collapse-bind (eval (color)))")).unwrap(); - assert_eq!(result.len(), 1); - assert_eq_no_order!(atom_as_slice(&result[0]).unwrap(), [ - atom_bindings_into_atom(expr!("red"), bind!{}), - atom_bindings_into_atom(expr!("green"), bind!{}), - atom_bindings_into_atom(expr!("blue"), bind!{}) - ]); - } - - fn space(text: &str) -> GroundingSpace { - metta_space(text) - } - - fn call_interpret<'a, T: Space>(space: T, atom: &Atom) -> Vec { - let _ = env_logger::builder().is_test(true).try_init(); - let result = interpret(space, atom); - assert!(result.is_ok()); - result.unwrap() - } - - #[derive(PartialEq, Clone, Debug)] - struct ThrowError(); - - impl Grounded for ThrowError { - fn type_(&self) -> Atom { - expr!("->" "&str" "Error") - } - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for ThrowError { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - Err((*args[0].as_gnd::<&str>().unwrap()).into()) - } - } - - impl Display for ThrowError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "throw-error") - } - } - - #[derive(PartialEq, Clone, Debug)] - struct NonReducible(); - - impl Grounded for NonReducible { - fn type_(&self) -> Atom { - expr!("->" "u32" "u32") - } - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for NonReducible { - fn execute(&self, _args: &[Atom]) -> Result, ExecError> { - Err(ExecError::NoReduce) - } - } - - impl Display for NonReducible { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "non-reducible") - } - } - - #[derive(PartialEq, Clone, Debug)] - struct MulXUndefinedType(i32); - - impl Grounded for MulXUndefinedType { - fn type_(&self) -> Atom { - ATOM_TYPE_UNDEFINED - } - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for MulXUndefinedType { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - Ok(vec![Atom::value(self.0 * args.get(0).unwrap().as_gnd::().unwrap())]) - } - } - - impl Display for MulXUndefinedType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "x{}", self.0) - } - } - - #[derive(PartialEq, Clone, Debug)] - struct ReturnNothing(); - - impl Grounded for ReturnNothing { - fn type_(&self) -> Atom { - ATOM_TYPE_UNDEFINED - } - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for ReturnNothing { - fn execute(&self, _args: &[Atom]) -> Result, ExecError> { - Ok(vec![]) - } - } - - impl Display for ReturnNothing { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "return-nothing") - } - } -} diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index ef7bcc870..5a189ba86 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -1,11 +1,12 @@ //! Contains MeTTa specific types, constants and functions. pub mod text; +#[cfg(feature = "old_interpreter")] pub mod interpreter; -#[cfg(all(feature = "minimal", not(feature = "minimal_rust")))] +#[cfg(not(feature = "old_interpreter"))] pub mod interpreter_minimal; -#[cfg(all(feature = "minimal", feature = "minimal_rust"))] -pub mod interpreter_minimal_rust; +#[cfg(not(feature = "old_interpreter"))] +pub use interpreter_minimal as interpreter; pub mod types; pub mod runner; diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index c1f525146..548fbef5f 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -87,16 +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(all(feature = "minimal", not(feature = "minimal_rust")))] -use super::interpreter_minimal::{interpret, interpret_init, interpret_step, InterpreterState}; -#[cfg(all(feature = "minimal", feature = "minimal_rust"))] -use super::interpreter_minimal_rust::{interpret, interpret_init, interpret_step, InterpreterState}; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] use stdlib_minimal::*; use stdlib::CoreLibLoader; @@ -444,10 +439,10 @@ impl Metta { state.run_to_completion() } - // TODO: this method is deprecated and should be removed after switching + // TODO: MINIMAL this method is deprecated and should be removed after switching // to the minimal 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 { @@ -1068,7 +1063,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 { @@ -1094,7 +1089,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()) } @@ -1187,7 +1182,7 @@ 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]); diff --git a/lib/src/metta/runner/modules/mod.rs b/lib/src/metta/runner/modules/mod.rs index acbd03d92..b285a9c16 100644 --- a/lib/src/metta/runner/modules/mod.rs +++ b/lib/src/metta/runner/modules/mod.rs @@ -7,14 +7,12 @@ use crate::metta::runner::*; use regex::Regex; -#[cfg(not(feature = "minimal"))] +#[cfg(feature = "old_interpreter")] use super::stdlib::*; -#[cfg(all(feature = "minimal", not(feature = "minimal_rust")))] +#[cfg(not(feature = "old_interpreter"))] use super::interpreter_minimal::interpret; -#[cfg(all(feature = "minimal", feature = "minimal_rust"))] -use super::interpreter_minimal_rust::interpret; -#[cfg(feature = "minimal")] +#[cfg(not(feature = "old_interpreter"))] use super::stdlib_minimal::*; mod mod_names; 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.rs b/lib/src/metta/runner/stdlib_minimal.rs index 8950d94d5..5e76d3997 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -145,10 +145,7 @@ 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())])]); - #[cfg(not(feature = "minimal_rust"))] - let result = crate::metta::interpreter_minimal::interpret(space, &expr); - #[cfg(feature = "minimal_rust")] - let result = crate::metta::interpreter_minimal_rust::interpret(space, &expr); + let result = crate::metta::interpreter::interpret(space, &expr); result } diff --git a/python/tests/test_atom.py b/python/tests/test_atom.py index 411156c3f..9b3f038e3 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(S('interpret'), E(x2Atom, ValueAtom(1)), S('%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(S('interpret'), expr, S('%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,16 @@ def print_op(input): print(input) printExpr = E(OperationAtom('print', print_op, type_names=["Atom", "->"], unwrap=False), S("test")) + printExpr = E(S('interpret'), printExpr, S('%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(S('interpret'), printExpr, S('%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(S('interpret'), E(noReduceAtom, ValueAtom(1)), S('%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..d4621c6eb 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(S('interpret'), target, + S('%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(S('interpret'), target, S('%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 1b4f676c5..91684f700 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 -git = ["hyperon/git"] \ No newline at end of file +old_interpreter = ["hyperon/old_interpreter"] +git = ["hyperon/git"] From 2e1d532e80184f5165f2a8e81e7b4491f091537e Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 4 Jul 2024 16:42:15 +0300 Subject: [PATCH 19/26] Rename interpret to metta Add Python atom to designate MeTTa interpreter call and use it in unit tests. --- c/src/metta.rs | 10 ++++++++++ lib/src/metta/interpreter.rs | 2 +- lib/src/metta/interpreter_minimal.rs | 14 +++++++------- lib/src/metta/mod.rs | 2 +- lib/src/metta/runner/mod.rs | 2 +- lib/src/metta/runner/stdlib_minimal.rs | 2 +- python/hyperon/atoms.py | 1 + python/hyperonpy.cpp | 3 ++- python/tests/test_atom.py | 11 ++++++----- python/tests/test_examples.py | 6 +++--- 10 files changed, 33 insertions(+), 20 deletions(-) diff --git a/c/src/metta.rs b/c/src/metta.rs index 0382c834f..b1c6441a6 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/lib/src/metta/interpreter.rs b/lib/src/metta/interpreter.rs index 8da3f84db..7f00b1e7c 100644 --- a/lib/src/metta/interpreter.rs +++ b/lib/src/metta/interpreter.rs @@ -178,7 +178,7 @@ 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 == INTERPRET_SYMBOL => atom, + Some([op, atom, _typ, _space]) if *op == METTA_SYMBOL => atom, _ => expr, }; let context = InterpreterContextRef::new(space); diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index d5acaa349..c805c5178 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -285,7 +285,7 @@ fn is_embedded_op(atom: &Atom) -> bool { || *op == FUNCTION_SYMBOL || *op == COLLAPSE_BIND_SYMBOL || *op == SUPERPOSE_BIND_SYMBOL - || *op == INTERPRET_SYMBOL + || *op == METTA_SYMBOL || *op == CALL_NATIVE_SYMBOL, _ => false, } @@ -396,7 +396,7 @@ 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 == INTERPRET_SYMBOL => { + Some([op, ..]) if *op == METTA_SYMBOL => { interpret_sym(stack, bindings) }, Some([op, ..]) if *op == CALL_NATIVE_SYMBOL => { @@ -838,7 +838,7 @@ fn interpret_sym(stack: Stack, bindings: Bindings) -> Vec { interpret ~ [_op, atom, typ, space] if space.as_gnd::().is_some() => (atom, typ, space), _ => { - let error = format!("expected: ({} atom type space), found: {}", INTERPRET_SYMBOL, interpret); + let error = format!("expected: ({} atom type space), found: {}", METTA_SYMBOL, interpret); return finished_result(error_msg(interpret, error), bindings, prev); } }; @@ -1064,7 +1064,7 @@ fn interpret_tuple(args: Atom, bindings: Bindings) -> MettaResult { 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([INTERPRET_SYMBOL, head, ATOM_TYPE_UNDEFINED, space.clone()]), rhead.clone(), + 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), @@ -1098,7 +1098,7 @@ fn interpret_function(args: Atom, bindings: Bindings) -> MettaResult { 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([INTERPRET_SYMBOL, head, Atom::Expression(op_type), space.clone()]), rop.clone(), + 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(), @@ -1150,7 +1150,7 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { ])) ]); once( - Atom::expr([CHAIN_SYMBOL, Atom::expr([INTERPRET_SYMBOL, args_head.clone(), types_head, space.clone()]), rhead.clone(), + 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, @@ -1219,7 +1219,7 @@ fn metta_call_return(args: Atom, bindings: Bindings) -> MettaResult { } else { let ret = Atom::Variable(VariableAtom::new("ret").make_unique()); once( - Atom::expr([CHAIN_SYMBOL, Atom::expr([INTERPRET_SYMBOL, result, typ, space]), ret.clone(), + Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, result, typ, space]), ret.clone(), return_atom(ret) ]), bindings) } diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index 5a189ba86..f46502dbc 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -43,7 +43,7 @@ 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 diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 548fbef5f..02b348010 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -1185,7 +1185,7 @@ impl<'i> InputStream<'i> { #[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 interpret = Atom::expr([METTA_SYMBOL, atom, ATOM_TYPE_UNDEFINED, space]); interpret } diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 5e76d3997..304cb12f7 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -144,7 +144,7 @@ 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())])]); + let expr = Atom::expr([EVAL_SYMBOL, Atom::expr([METTA_SYMBOL, expr.clone(), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())])]); let result = crate::metta::interpreter::interpret(space, &expr); result } diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 4eef04b41..9b9574ee4 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -123,6 +123,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 8d6c3dcdd..b1d7932ae 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -954,7 +954,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 9b3f038e3..8ae1b673a 100644 --- a/python/tests/test_atom.py +++ b/python/tests/test_atom.py @@ -92,7 +92,7 @@ def test_groundingspace_equals(self): def test_interpret(self): space = GroundingSpaceRef() - x2 = E(S('interpret'), E(x2Atom, ValueAtom(1)), S('%Undefined%'), G(space)) + 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): @@ -102,7 +102,7 @@ def x2_op(atom): expr = E(x2Atom, ValueAtom(1)) space = GroundingSpaceRef() - self.assertEqual(interpret(space, E(S('interpret'), expr, S('%Undefined%'), G(space))), + 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,15 +111,16 @@ def print_op(input): print(input) printExpr = E(OperationAtom('print', print_op, type_names=["Atom", "->"], unwrap=False), S("test")) - printExpr = E(S('interpret'), printExpr, S('%Undefined%'), G(space)) + 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(S('interpret'), printExpr, S('%Undefined%'), G(space)) + printExpr = E(Atoms.METTA, printExpr, AtomType.UNDEFINED, G(space)) self.assertEqual(interpret(space, printExpr), [E()]) def test_no_reduce(self): space = GroundingSpaceRef() - expr = E(S('interpret'), E(noReduceAtom, ValueAtom(1)), S('%Undefined%'), G(space)) + expr = E(Atoms.METTA, E(noReduceAtom, ValueAtom(1)), + AtomType.UNDEFINED, G(space)) self.assertEqual(interpret(space, expr), [E(noReduceAtom, ValueAtom(1))]) diff --git a/python/tests/test_examples.py b/python/tests/test_examples.py index d4621c6eb..a0e4cfe82 100644 --- a/python/tests/test_examples.py +++ b/python/tests/test_examples.py @@ -15,8 +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(), E(S('interpret'), target, - S('%Undefined%'), G(metta2.space()))) + 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 @@ -90,7 +90,7 @@ 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(), E(S('interpret'), target, S('%Undefined%'), + 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 From 67d5cb4e29430717f266c20514b3593f94a9c58f Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 5 Jul 2024 13:17:26 +0300 Subject: [PATCH 20/26] Remove eval which is not needed anymore --- lib/src/metta/runner/stdlib_minimal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 304cb12f7..33d766510 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -144,7 +144,7 @@ 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([METTA_SYMBOL, expr.clone(), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())])]); + let expr = Atom::expr([METTA_SYMBOL, expr.clone(), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())]); let result = crate::metta::interpreter::interpret(space, &expr); result } From 4de1cedde0e922be5a2cc8e2e889219c2b08cb40 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 5 Jul 2024 13:20:25 +0300 Subject: [PATCH 21/26] Replace interpret by metta in unit tests --- lib/src/metta/runner/stdlib_minimal.rs | 78 +++++++++++++------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 33d766510..9e0a87b43 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -380,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 @@ -674,42 +674,42 @@ mod tests { #[test] fn metta_interpret_single_atom_as_atom() { - let result = run_program("!(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("!(interpret A Symbol &self)"), Ok(vec![vec![expr!("A")]])); - assert_eq!(run_program("!(interpret $x Variable &self)"), Ok(vec![vec![expr!(x)]])); - assert_eq!(run_program("!(interpret (A B) Expression &self)"), Ok(vec![vec![expr!("A" "B")]])); - assert_eq!(run_program("!(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) !(interpret a A &self)"), Ok(vec![vec![expr!("a")]])); - assert_eq!(run_program("(: a A) !(interpret a B &self)"), Ok(vec![vec![expr!("Error" "a" "BadType")]])); - assert_eq!(run_program("!(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("!(interpret $x %Undefined% &self)"), Ok(vec![vec![expr!(x)]])); - assert_eq!(run_program("!(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("!(interpret () %Undefined% &self)"), Ok(vec![vec![expr!(())]])); - assert_eq!(run_program("!(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 (interpret S $t &self) $res (: $res $t)) + !(chain (metta S $t &self) $res (: $res $t)) "); assert_eq!(result, Ok(vec![vec![expr!(":" "S" "Int")]])); } @@ -721,14 +721,14 @@ mod tests { (: foo (-> T T)) (= (foo $x) $x) (= (bar $x) $x) - !(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) - !(interpret (foo b) %Undefined% &self) + !(metta (foo b) %Undefined% &self) "); assert_eq!(result, Ok(vec![vec![expr!("Error" "b" "BadType")]])); let result = run_program(" @@ -736,34 +736,34 @@ mod tests { (: Z Nat) (: S (-> Nat Nat)) (: Cons (-> $t (List $t) (List $t))) - !(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("!(interpret () %Undefined% &self)"), Ok(vec![vec![expr!(())]])); - assert_eq!(run_program("!(interpret (a) %Undefined% &self)"), Ok(vec![vec![expr!(("a"))]])); - assert_eq!(run_program("!(interpret (a b) %Undefined% &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) - !(interpret ((foo A) (foo B)) %Undefined% &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) !(interpret (foo a) %Undefined% &self)"), Ok(vec![vec![expr!("a")]])); - assert_eq!(run_program("!(interpret (foo a) %Undefined% &self)"), Ok(vec![vec![expr!("foo" "a")]])); - assert_eq!(run_program("!(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) !(interpret a %Undefined% &self)"); + let result = run_program("(: a A) (: a B) !(metta a %Undefined% &self)"); assert_eq!(result, Ok(vec![vec![expr!("a")]])); } @@ -894,7 +894,7 @@ mod tests { (= (is Tweety yellow) True) (= (is Tweety eats-flies) True) - !(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), @@ -908,7 +908,7 @@ mod tests { (= (color) red) (= (color) green) - !(interpret (color) %Undefined% &self) + !(metta (color) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -922,8 +922,8 @@ mod tests { (= (plus Z $y) $y) (= (plus (S $k) $y) (S (plus $k $y))) - !(interpret (eq (plus Z $n) $n) %Undefined% &self) - !(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), @@ -938,7 +938,7 @@ mod tests { (= (a $z) (mynot (b $z))) (= (b d) F) - !(interpret (myif (a $x) $x) %Undefined% &self) + !(metta (myif (a $x) $x) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -950,7 +950,7 @@ mod tests { let program = " (= (a ($W)) True) - !(interpret (a $W) %Undefined% &self) + !(metta (a $W) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), @@ -962,7 +962,7 @@ mod tests { let program = " (= (b ($x $y)) (c $x $y)) - !(interpret (a (b $a) $x $y) %Undefined% &self) + !(metta (a (b $a) $x $y) %Undefined% &self) "; let result = run_program(program); @@ -978,7 +978,7 @@ mod tests { (= (foo) bar) (= (bar $x) $x) - !(interpret ((foo) a) %Undefined% &self) + !(metta ((foo) a) %Undefined% &self) "; assert_eq_metta_results!(run_program(program), Ok(vec![vec![expr!("a")]])); @@ -1001,7 +1001,7 @@ mod tests { (: id_a (-> A A)) (= (id_a $a) $a) - !(interpret (id_a myAtom) %Undefined% &self) + !(metta (id_a myAtom) %Undefined% &self) "; let metta = Metta::new(Some(EnvBuilder::test_env())); @@ -1012,7 +1012,7 @@ mod tests { Ok(vec![vec![expr!("Error" "myAtom" "BadType")]])); let program2 = " - !(interpret (id_num myAtom) %Undefined% &self) + !(metta (id_num myAtom) %Undefined% &self) "; assert_eq!(metta.run(SExprParser::new(program2)), @@ -1028,7 +1028,7 @@ mod tests { (: foo (-> A B C)) (= (foo $a $b) c) - !(interpret (foo a b) %Undefined% &self) + !(metta (foo a b) %Undefined% &self) "; let metta = Metta::new(Some(EnvBuilder::test_env())); @@ -1038,12 +1038,12 @@ mod tests { assert_eq!(metta.run(SExprParser::new(program1)), Ok(vec![vec![expr!("c")]])); - let program2 = "!(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 = "!(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")]])); From 148cec48f39fd03facc70c70491e9b4109088642 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 5 Jul 2024 13:26:07 +0300 Subject: [PATCH 22/26] Remove MeTTa interpreter implemented in MeTTa --- lib/src/metta/runner/stdlib_minimal.metta | 159 ++++------------------ 1 file changed, 25 insertions(+), 134 deletions(-) diff --git a/lib/src/metta/runner/stdlib_minimal.metta b/lib/src/metta/runner/stdlib_minimal.metta index abaf1c8b4..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 $meta ( - (Variable (return $atom)) - (Symbol - (chain (eval (type-cast $atom $type $space)) $ret (return $ret))) - (Grounded - (chain (eval (type-cast $atom $type $space)) $ret (return $ret))) - (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 ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From ac4a00f9b4dc74e9274794852be072170455527e Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 5 Jul 2024 13:40:42 +0300 Subject: [PATCH 23/26] Switch additional GitHub CI job to old interpreter testing --- .../workflows/{minimal.yml => old_interpreter.yml} | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename .github/workflows/{minimal.yml => old_interpreter.yml} (87%) 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 From 2bd75027e925c39e784b906398a5e479c42acb1d Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 10 Jul 2024 09:02:59 +0300 Subject: [PATCH 24/26] Rename interpreter.rs into old_interpreter.rs --- lib/src/metta/mod.rs | 4 +++- lib/src/metta/{interpreter.rs => old_interpreter.rs} | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename lib/src/metta/{interpreter.rs => old_interpreter.rs} (100%) diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index f46502dbc..bf089a947 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -2,7 +2,9 @@ pub mod text; #[cfg(feature = "old_interpreter")] -pub mod 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"))] diff --git a/lib/src/metta/interpreter.rs b/lib/src/metta/old_interpreter.rs similarity index 100% rename from lib/src/metta/interpreter.rs rename to lib/src/metta/old_interpreter.rs From 8cae12ec52b343b73fc0cb700a5d2a0cc6a4c0b4 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 10 Jul 2024 09:34:29 +0300 Subject: [PATCH 25/26] Add a note about Rust MeTTa interpreter in minimal MeTTa --- docs/minimal-metta.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 From e7e6dfad09c9d6c34d615c17bdeaa932c694298e Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 10 Jul 2024 10:16:41 +0300 Subject: [PATCH 26/26] Document minimal MeTTa interpreter structures and methods --- lib/src/metta/interpreter_minimal.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index c805c5178..636e76975 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -1,8 +1,5 @@ -//! 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::*; @@ -157,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>, } @@ -194,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())