diff --git a/lib/src/atom/matcher.rs b/lib/src/atom/matcher.rs index db2871f8a..041ab35bf 100644 --- a/lib/src/atom/matcher.rs +++ b/lib/src/atom/matcher.rs @@ -1073,24 +1073,10 @@ 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; -impl Clone for MatchResultIter { - fn clone(&self) -> Self { - self.clone_() - } -} +pub type MatchResultIter = Box>; /// Matches two atoms and returns an iterator over results. Atoms are /// treated symmetrically. diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index e6d5707c8..0aeec87df 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -403,7 +403,7 @@ fn interpret_stack<'a, T: Space>(context: &InterpreterContext, stack: Stack, superpose_bind(stack, bindings) }, Some([op, ..]) if *op == METTA_SYMBOL => { - interpret_sym(stack, bindings) + metta_sym(stack, bindings) }, Some([op, ..]) if *op == CALL_NATIVE_SYMBOL => { call_native_symbol(stack, bindings) @@ -665,8 +665,10 @@ fn collapse_bind(stack: Stack, bindings: Bindings) -> Vec { } 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)] + let prev = Rc::new(RefCell::new(prev)); + let cur = atom_to_stack(nested, Some(prev.clone())); + let dummy = Stack::finished(Some(prev), EMPTY_SYMBOL); + vec![InterpretedAtom(dummy, bindings.clone()), InterpretedAtom(cur, bindings)] } fn collapse_bind_ret(stack: Rc>, atom: Atom, bindings: Bindings) -> Option<(Stack, Bindings)> { @@ -678,7 +680,9 @@ fn collapse_bind_ret(stack: Rc>, atom: Atom, bindings: Bindings) Some([_op, Atom::Expression(finished), _bindings]) => finished, _ => panic!("Unexpected state"), }; - finished.children_mut().push(atom_bindings_into_atom(nested, bindings)); + if nested != EMPTY_SYMBOL { + finished.children_mut().push(atom_bindings_into_atom(nested, bindings)); + } } // all alternatives are evaluated @@ -838,42 +842,29 @@ fn call_native_symbol(stack: Stack, bindings: Bindings) -> Vec .collect() } -fn interpret_sym(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: interpret, ret: _, finished: _, vars: _ } = stack; +fn metta_sym(stack: Stack, bindings: Bindings) -> Vec { + let Stack{ prev, atom: metta, ret: _, finished: _, vars: _ } = stack; let (atom, typ, space) = match_atom!{ - interpret ~ [_op, atom, typ, space] + metta ~ [_op, atom, typ, space] if space.as_gnd::().is_some() => (atom, typ, space), _ => { - let error = format!("expected: ({} atom type space), found: {}", METTA_SYMBOL, interpret); - return finished_result(error_msg(interpret, error), bindings, prev); + let error = format!("expected: ({} atom type space), found: {}", METTA_SYMBOL, metta); + return finished_result(error_msg(metta, error), bindings, prev); } }; - vec![InterpretedAtom(atom_to_stack(call_native!(interpret_impl, Atom::expr([atom, typ, space])), prev), bindings)] + vec![InterpretedAtom(atom_to_stack(call_native!(metta_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_() - } -} +type MettaResult = Box>; #[inline] -fn once(atom: Atom, bindings: Bindings) -> MettaResult { - Box::new(std::iter::once((atom, bindings))) +fn once<'a, T: 'a>(data: T) -> Box + 'a> { + Box::new(std::iter::once(data)) } #[inline] -fn empty() -> MettaResult { +fn empty<'a, T: 'a>() -> Box + 'a> { Box::new(std::iter::empty()) } @@ -892,24 +883,24 @@ fn function_atom(atom: Atom) -> Atom { Atom::expr([FUNCTION_SYMBOL, atom]) } -fn interpret_impl(args: Atom, bindings: Bindings) -> MettaResult { +fn metta_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); + return once((return_atom(error_msg(call_native!(metta_impl, args), error)), bindings)); } }; let meta = get_meta_type(&atom); if typ == ATOM_TYPE_ATOM { - once(return_atom(atom), bindings) + once((return_atom(atom), bindings)) } else if typ == meta { - once(return_atom(atom), bindings) + once((return_atom(atom), bindings)) } else { if meta == ATOM_TYPE_VARIABLE { - once(return_atom(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 { @@ -917,11 +908,11 @@ fn interpret_impl(args: Atom, bindings: Bindings) -> MettaResult { } 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(), + 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) + ]), bindings)) } } } @@ -938,37 +929,48 @@ 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(return_atom(atom), bindings) + 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()) + .map(|actual_type| match_types(&expected_type, &actual_type, bindings.clone())) + .filter(|res| res.is_ok()) + .flat_map(|res| { + match res { + Ok(it) => it, + Err(_) => panic!("Unexpected state"), + } + }) .next(); match first_match { - Some((_atom, bindings)) => once(return_atom(atom), bindings), - None => once(return_atom(error_atom(atom, BAD_TYPE_SYMBOL)), bindings), + Some(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) +fn match_types(type1: &Atom, type2: &Atom, bindings: Bindings) -> Result { + if *type1 == ATOM_TYPE_UNDEFINED + || *type2 == ATOM_TYPE_UNDEFINED + || *type1 == ATOM_TYPE_ATOM + || *type2 == ATOM_TYPE_ATOM { + Ok(once(bindings)) } else { - let mut result = match_atoms(&type1, &type2).peekable(); + let bindings_copy = bindings.clone(); + let mut result = match_atoms(type1, type2) + .flat_map(move |b| b.merge_v2(&bindings).into_iter()) + .peekable(); if result.peek().is_none() { - once(els, bindings.clone()) + log::trace!("match_types: no match: {} !~ {}", type1, type2); + Err(once(bindings_copy)) } else { - Box::new(result - .flat_map(move |b| b.merge_v2(&bindings).into_iter()) - .map(move |b| (then.clone(), b))) + if log::log_enabled!(log::Level::Trace) { + let result: Vec = result.collect(); + log::trace!("match_types: match: {} ~ {}, bindings {:?}", type1, type2, result); + Ok(Box::new(result.into_iter())) + } else { + Ok(Box::new(result)) + } } } } @@ -978,7 +980,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_msg(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() @@ -997,57 +999,62 @@ fn check_alternatives(args: Atom, bindings: Bindings) -> MettaResult { } 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 (expr, expr_typ, space) = match_atom!{ + args ~ [expr, expr_typ, space] + if space.as_gnd::().is_some() => (expr, expr_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); + return once((return_atom(error_msg(call_native!(interpret_expression, args), error)), bindings)); } }; - match atom_as_slice(&atom) { + match atom_as_slice(&expr) { 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 actual_types = get_atom_types(space_ref, op); + + let has_tuple_type = actual_types.iter().filter(|typ| !is_func(typ)).next().is_some(); + let tuple = if has_tuple_type { 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(), + once(( + Atom::expr([CHAIN_SYMBOL, call_native!(interpret_tuple, Atom::expr([expr.clone(), space.clone()])), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, expr_typ.clone(), space.clone()])), result.clone(), return_atom(result) ]) - ]), bindings.clone()) + ]), 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()))) + let mut func_types = actual_types.into_iter().filter(|typ| is_func(typ)).peekable(); + let func = if func_types.peek().is_some() { + let ret_typ = expr_typ.clone(); + let type_check_results = func_types.flat_map(|typ| check_if_function_type_is_applicable(&expr, typ, &ret_typ, space_ref, bindings.clone())); + let mut errors = Vec::new(); + for res in type_check_results { + log::debug!("interpret_expression: function type check: expr: {} type: {:?}", expr, res); + match res { + (Ok(op_type), bindings) => { + let reduced = Atom::Variable(VariableAtom::new("reduced").make_unique()); + let result = Atom::Variable(VariableAtom::new("result").make_unique()); + return once((Atom::expr([CHAIN_SYMBOL, call_native!(interpret_function, Atom::expr([expr.clone(), op_type, expr_typ.clone(), space.clone()])), reduced.clone(), + Atom::expr([CHAIN_SYMBOL, call_native!(metta_call, Atom::expr([reduced, expr_typ, space.clone()])), result.clone(), + return_atom(result) + ]) + ]), bindings)); + }, + (Err(err), bindings) => errors.push((err, bindings)), + } + } + Box::new(errors.into_iter() + .map(move |(err, bindings)| (return_atom(err), bindings))) } else { empty() }; Box::new(std::iter::empty().chain(tuple).chain(func)) }, - _ => type_cast(space, atom, typ, bindings), + _ => type_cast(space, expr, expr_typ, bindings), } } @@ -1057,11 +1064,11 @@ 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_msg(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() { - once(return_atom(Atom::Expression(expr)), bindings) + once((return_atom(Atom::Expression(expr)), bindings)) } else { let mut tuple = expr.into_children(); let head = tuple.remove(0); @@ -1069,7 +1076,7 @@ fn interpret_tuple(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()); - once( + once(( Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, head, ATOM_TYPE_UNDEFINED, space.clone()]), rhead.clone(), Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::gnd(IfEqualOp{}), rhead.clone(), EMPTY_SYMBOL, return_atom(EMPTY_SYMBOL), Atom::expr([CHAIN_SYMBOL, call_native!(interpret_tuple, Atom::expr([Atom::expr(tail), space.clone()])), rtail.clone(), @@ -1080,7 +1087,7 @@ fn interpret_tuple(args: Atom, bindings: Bindings) -> MettaResult { ])]) ]) ])]) - ]), bindings) + ]), bindings)) } } @@ -1091,7 +1098,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_msg(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(); @@ -1105,7 +1112,7 @@ fn interpret_function(args: Atom, bindings: Bindings) -> MettaResult { let result = Atom::Variable(VariableAtom::new("result").make_unique()); let unpacked_args = Atom::Variable(VariableAtom::new("unpacked_args").make_unique()); let call_interpret_args = call_native!(interpret_args, Atom::expr([Atom::Expression(atom), Atom::expr(args), arg_types, ret_type, space.clone()])); - once( + once(( Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, head, Atom::Expression(op_type), space.clone()]), rop.clone(), call_native!(return_on_error, Atom::expr([rop.clone(), Atom::expr([CHAIN_SYMBOL, call_interpret_args.clone(), rargs.clone(), @@ -1117,7 +1124,94 @@ fn interpret_function(args: Atom, bindings: Bindings) -> MettaResult { ]) ]) ])) - ]), bindings) + ]), bindings)) +} + +fn check_if_function_type_is_applicable<'a>(expr: &'a Atom, op_type: Atom, expected_type: &'a Atom, space: &'a DynSpace, bindings: Bindings) -> Box, Bindings)> + 'a> { + log::trace!("check_if_function_type_is_applicable: function type check: expr: {}, op_type: {}, expected_type: {}", expr, op_type, expected_type); + let actual_args = match atom_as_slice(expr) { + Some([_op, actual_args @ ..]) => actual_args, + _ => panic!("Unexpected state"), + }; + let arg_types: ExpressionAtom = op_type.clone().try_into().unwrap(); + let mut arg_types = arg_types.into_children(); + let arrow = arg_types.remove(0); + assert_eq!(arrow, ARROW_SYMBOL); + check_if_function_type_is_applicable_(expr, op_type, arg_types, actual_args, expected_type, space, bindings) +} + +fn is_meta_type(atom: &Atom) -> bool { + if *atom == ATOM_TYPE_ATOM + || *atom == ATOM_TYPE_SYMBOL + || *atom == ATOM_TYPE_VARIABLE + || *atom == ATOM_TYPE_EXPRESSION + || *atom == ATOM_TYPE_GROUNDED { + true + } else { + false + } +} + +fn match_meta_types(actual: &Atom, expected: &Atom) -> bool { + if *expected == ATOM_TYPE_ATOM { + true + } else { + actual == expected + } +} + +fn check_if_function_type_is_applicable_<'a>(expr: &'a Atom, op_type: Atom, mut arg_types: Vec, actual_args: &'a[Atom], expected_type: &'a Atom, space: &'a DynSpace, bindings: Bindings) -> Box, Bindings)> + 'a> { + match arg_types.len() { + 0 => once((Err(error_atom(expr.clone(), INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings)), + 1 => { + let ret_type = arg_types.pop().unwrap(); + log::trace!("check_if_function_type_is_applicable_: function type check: expr: {}, ret_type: {}, expected_type: {}", expr, ret_type, expected_type); + match actual_args { + [] => { + // Here expected_type is always some specific type not meta-type. It is because + // there are two places to assign expected_type. First place is passing result of + // the function call to another function. In this case expected_type is an expected + // type of the outer function argument. Second place is explicit call to + // `metta`. In this case expected_type is explicitly set as an argument and handled + // in metta_impl() Rust function which compares it with passed expression + // meta-type. Thus if expected_type is meta-type it is always first compared to the + // expression's meta-type and type check finishes. + match match_types(&ret_type, expected_type, bindings) { + Ok(matches) => Box::new(matches.map(move |bindings| (Ok(op_type.clone()), bindings))), + Err(nomatch) => Box::new(nomatch.map(move |bindings| (Err(error_atom(expr.clone(), BAD_TYPE_SYMBOL)), bindings))), + } + }, + _ => once((Err(error_atom(expr.clone(), INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings)), + } + }, + _ => { + let formal_arg_type = arg_types.remove(0); + let arg_types_tail = arg_types; + match actual_args { + [] => once((Err(error_atom(expr.clone(), INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL)), bindings)), + [actual_arg, args_tail @ ..] => { + if is_meta_type(&formal_arg_type) && match_meta_types(&get_meta_type(actual_arg), &formal_arg_type) { + check_if_function_type_is_applicable_(expr, op_type, arg_types_tail, args_tail, expected_type, space, bindings) + } else { + let mut actual_arg_types = get_atom_types(space, actual_arg).into_iter().peekable(); + if actual_arg_types.peek().is_none() { + return once((Err(error_atom(actual_arg.clone(), BAD_TYPE_SYMBOL)), bindings)) + } + let actual_arg_types = actual_arg_types.inspect(move |typ| log::trace!("check_if_function_type_is_applicable_: function type check: expr: {}, actual_arg: {}, actual_type: {}", expr, actual_arg, typ)); + let iter = actual_arg_types.flat_map(move |actual_arg_type| -> Box, Bindings)> + '_> { + let arg_types_tail = arg_types_tail.clone(); + let op_type = op_type.clone(); + match match_types(&formal_arg_type, &actual_arg_type, bindings.clone()) { + Ok(matches) => Box::new(matches.flat_map(move |bindings| check_if_function_type_is_applicable_(expr, op_type.clone(), arg_types_tail.clone(), args_tail, expected_type, space, bindings))), + Err(nomatch) => Box::new(nomatch.map(|bindings| (Err(error_atom(actual_arg.clone(), BAD_TYPE_SYMBOL)), bindings))), + } + }); + Box::new(iter) + } + }, + } + }, + } } fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { @@ -1126,23 +1220,23 @@ 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_msg(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; if args.children().is_empty() { if types_tail.is_empty() { - match_types(types_head, ret_type, - return_atom(Atom::expr([Atom::sym("Ok"), Atom::Expression(args)])), - return_atom(error_atom(atom, BAD_TYPE_SYMBOL)), - bindings) + match match_types(&types_head, &ret_type, bindings) { + Ok(matches) => Box::new(matches.map(move |bindings| (return_atom(Atom::expr([Atom::sym("Ok"), Atom::Expression(args.clone())])), bindings))), + Err(nomatch) => Box::new(nomatch.map(move |bindings| (return_atom(error_atom(atom.clone(), 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(); @@ -1161,7 +1255,7 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { return_atom(rtail) ]) ]); - once( + once(( Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, args_head.clone(), types_head, space.clone()]), rhead.clone(), Atom::expr([EVAL_SYMBOL, Atom::expr([Atom::gnd(IfEqualOp{}), rhead.clone(), args_head, recursion.clone(), @@ -1169,7 +1263,7 @@ fn interpret_args(args_: Atom, bindings: Bindings) -> MettaResult { recursion ])) ])]) - ]), bindings) + ]), bindings)) } } @@ -1178,15 +1272,15 @@ fn return_on_error(args: Atom, bindings: Bindings) -> MettaResult { 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); + 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) + once((return_atom(return_atom(EMPTY_SYMBOL)), bindings)) } else if atom_is_error(&atom) { - once(return_atom(return_atom(atom)), bindings) + once((return_atom(return_atom(atom)), bindings)) } else { - once(return_atom(then), bindings) + once((return_atom(then), bindings)) } } @@ -1196,20 +1290,29 @@ fn metta_call(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_msg(call_native!(metta_call, args), error)), bindings); + return once((return_atom(error_msg(call_native!(metta_call, args), error)), bindings)); } }; if atom_is_error(&atom) { - once(return_atom(atom), bindings) + 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( + once(( + // TODO: At the moment metta_call() is called we already know + // should we call atom as a tuple or as a function. + // But (eval ( )) inside independently decides whether it + // should call grounded operation , or match (= ( ) ). + // This can lead to the conflict if user defines a function using + // grounded atom (for instance (+3 1 2 3)) and after type analysis + // interpreter decides we need to match it then calling eval will + // analyze the expression again and may call grounded op instead of + // matching. 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) + ]), bindings)) } } @@ -1219,21 +1322,21 @@ fn metta_call_return(args: Atom, bindings: Bindings) -> MettaResult { 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); + return once((return_atom(error_msg(call_native!(metta_call_return, args), error)), bindings)); } }; if NOT_REDUCIBLE_SYMBOL == result { - once(return_atom(atom), bindings) + once((return_atom(atom), bindings)) } else if EMPTY_SYMBOL == result { - once(return_atom(EMPTY_SYMBOL), bindings) + once((return_atom(EMPTY_SYMBOL), bindings)) } else if atom_is_error(&result) { - once(return_atom(result), bindings) + once((return_atom(result), bindings)) } else { let ret = Atom::Variable(VariableAtom::new("ret").make_unique()); - once( + once(( Atom::expr([CHAIN_SYMBOL, Atom::expr([METTA_SYMBOL, result, typ, space]), ret.clone(), return_atom(ret) - ]), bindings) + ]), bindings)) } } @@ -1729,4 +1832,17 @@ mod tests { write!(f, "return-nothing") } } + + #[test] + fn interpret_duplicated_types() { + let space = DynSpace::new(space(" + (: foo (-> A A)) + (: foo (-> A A)) + (: foo (-> Atom A)) + (: a A) + (= (foo $x) a) + ")); + let result = interpret(&space, &Atom::expr([METTA_SYMBOL, expr!("foo" "a"), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())])); + assert_eq!(result, Ok(vec![metta_atom("a")])); + } } diff --git a/lib/src/metta/runner/stdlib_minimal.metta b/lib/src/metta/runner/stdlib_minimal.metta index 1ada687b9..7101c7f66 100644 --- a/lib/src/metta/runner/stdlib_minimal.metta +++ b/lib/src/metta/runner/stdlib_minimal.metta @@ -20,6 +20,7 @@ (: decons-atom (-> Expression Expression)) (: collapse-bind (-> Atom Expression)) (: superpose-bind (-> Expression Atom)) +(: metta (-> Atom Type Grounded Atom)) (: id (-> Atom Atom)) (= (id $x) $x) diff --git a/lib/src/metta/types.rs b/lib/src/metta/types.rs index 69bd333de..0305c39d7 100644 --- a/lib/src/metta/types.rs +++ b/lib/src/metta/types.rs @@ -154,6 +154,76 @@ fn get_args(expr: &ExpressionAtom) -> &[Atom] { &expr.children().as_slice()[1..] } +/// Returns vector of the types for the given `atom` in context of the given +/// `space`. Returns `%Undefined%` if atom has no type assigned. Returns empty +/// vector if atom is a function call but expected types of arguments are not +/// compatible with passed values. +/// +/// # Examples +/// +/// ``` +/// use hyperon::{Atom, expr, assert_eq_no_order}; +/// use hyperon::metta::ATOM_TYPE_UNDEFINED; +/// use hyperon::metta::runner::*; +/// use hyperon::metta::text::SExprParser; +/// use hyperon::metta::types::get_atom_types; +/// +/// let metta = Metta::new(None); +/// metta.run(SExprParser::new(" +/// (: f (-> A B)) +/// (: a A) +/// (: a B) +/// (: b B) +/// ")).unwrap(); +/// +/// let space = metta.space(); +/// assert_eq_no_order!(get_atom_types(&space, &expr!(x)), vec![ATOM_TYPE_UNDEFINED]); +/// assert_eq_no_order!(get_atom_types(&space, &expr!({1})), vec![expr!("i32")]); +/// assert_eq_no_order!(get_atom_types(&space, &expr!("na")), vec![ATOM_TYPE_UNDEFINED]); +/// assert_eq_no_order!(get_atom_types(&space, &expr!("a")), vec![expr!("A"), expr!("B")]); +/// assert_eq_no_order!(get_atom_types(&space, &expr!("a" "b")), vec![expr!("A" "B"), expr!("B" "B"), ATOM_TYPE_UNDEFINED]); +/// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "a")), vec![expr!("B")]); +/// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "b")), Vec::::new()); +/// ``` +#[cfg(not(feature = "old_interpreter"))] +pub fn get_atom_types(space: &dyn Space, atom: &Atom) -> Vec { + log::trace!("get_atom_types: atom: {}", atom); + let types = match atom { + // TODO: type of the variable could be actually a type variable, + // in this case inside each variant of type for the atom we should + // also keep bindings for the type variables. For example, + // we have an expression `(let $n (foo) (+ $n $n))`, where + // `(: let (-> $t $t $r $r))`, `(: foo (-> $tt))`, + // and `(: + (-> Num Num Num))`then type checker can find that + // `{ $r = $t = $tt = Num }`. + Atom::Variable(_) => vec![ATOM_TYPE_UNDEFINED], + Atom::Grounded(gnd) => vec![make_variables_unique(gnd.type_())], + Atom::Symbol(_) => { + let mut types = query_types(space, atom); + if types.is_empty() { + types.push(ATOM_TYPE_UNDEFINED) + } + types + }, + Atom::Expression(expr) => { + let tuples = get_tuple_types(space, atom, expr); + let applications = get_application_types(space, atom, expr); + + let mut types = Vec::new(); + if applications == None { + types.extend(tuples); + types.push(ATOM_TYPE_UNDEFINED); + } else { + types.extend(tuples); + applications.into_iter().for_each(|t| types.extend(t)); + } + types + }, + }; + log::debug!("get_atom_types: return atom {} types {:?}", atom, types); + types +} + /// Returns vector of the types for the given `atom` in context of the given /// `space`. Returns `%Undefined%` if atom has no type assigned. Returns empty /// vector if atom is a function call but expected types of arguments are not @@ -185,6 +255,7 @@ fn get_args(expr: &ExpressionAtom) -> &[Atom] { /// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "a")), vec![expr!("B")]); /// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "b")), Vec::::new()); /// ``` +#[cfg(feature = "old_interpreter")] pub fn get_atom_types(space: &dyn Space, atom: &Atom) -> Vec { log::trace!("get_atom_types: atom: {}", atom); let types = match atom { @@ -833,8 +904,16 @@ mod tests { (: b B) (: b BB) "); + #[cfg(not(feature = "old_interpreter"))] + assert_eq_no_order!(get_atom_types(&space, &atom("(a b)")), + vec![atom("(A B)"), atom("(AA B)"), atom("(A BB)"), atom("(AA BB)"), ATOM_TYPE_UNDEFINED]); + #[cfg(feature = "old_interpreter")] assert_eq_no_order!(get_atom_types(&space, &atom("(a b)")), vec![atom("(A B)"), atom("(AA B)"), atom("(A BB)"), atom("(AA BB)")]); + #[cfg(not(feature = "old_interpreter"))] + assert_eq_no_order!(get_atom_types(&space, &atom("(a c)")), + vec![atom("(A %Undefined%)"), atom("(AA %Undefined%)"), ATOM_TYPE_UNDEFINED]); + #[cfg(feature = "old_interpreter")] assert_eq_no_order!(get_atom_types(&space, &atom("(a c)")), vec![atom("(A %Undefined%)"), atom("(AA %Undefined%)")]); assert_eq_no_order!(get_atom_types(&space, &atom("(c d)")), vec![ATOM_TYPE_UNDEFINED]); @@ -889,9 +968,21 @@ mod tests { //assert_eq!(get_atom_types(&space, &expr!("f_gnd" b)), vec![]); assert_eq!(get_atom_types(&space, &expr!("f_atom" ("b"))), vec![atom("D")]); + // Here and below: when interpreter cannot find a function type for + // expression it evaluates it. Thus any argument expression without + // a function type can potentially suit as a legal argument. + #[cfg(not(feature = "old_interpreter"))] + assert_eq!(get_atom_types(&space, &expr!("f_sym" ("b"))), vec![atom("D")]); + #[cfg(feature = "old_interpreter")] assert_eq!(get_atom_types(&space, &expr!("f_sym" ("b"))), vec![]); assert_eq!(get_atom_types(&space, &expr!("f_expr" ("b"))), vec![atom("D")]); + #[cfg(not(feature = "old_interpreter"))] + assert_eq!(get_atom_types(&space, &expr!("f_var" ("b"))), vec![atom("D")]); + #[cfg(feature = "old_interpreter")] assert_eq!(get_atom_types(&space, &expr!("f_var" ("b"))), vec![]); + #[cfg(not(feature = "old_interpreter"))] + assert_eq!(get_atom_types(&space, &expr!("f_gnd" ("b"))), vec![atom("D")]); + #[cfg(feature = "old_interpreter")] assert_eq!(get_atom_types(&space, &expr!("f_gnd" ("b"))), vec![]); assert_eq!(get_atom_types(&space, &expr!("f_atom" {1})), vec![atom("D")]); diff --git a/python/tests/scripts/c1_grounded_basic.metta b/python/tests/scripts/c1_grounded_basic.metta index 6f3054425..e1334fad2 100644 --- a/python/tests/scripts/c1_grounded_basic.metta +++ b/python/tests/scripts/c1_grounded_basic.metta @@ -94,9 +94,12 @@ ; Custom symbols as arguments of grounded operations ; work similarly (: ln LN) -!(assertEqualToResult - (== 4 (+ ln 2)) - ((Error ln BadType))) +; TODO: This test has different behavior in old_interpreter and minimal interpreter. +; In first case it returns (Error ln BadType). In second case it returns +; (Error (+ ln 2) BadType). Uncomment when old_interpreter feature is removed +;!(assertEqualToResult +; (== 4 (+ ln 2)) +; ((Error (+ ln 2) BadType))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/python/tests/scripts/d2_higherfunc.metta b/python/tests/scripts/d2_higherfunc.metta index 72a0d90da..25f42be1f 100644 --- a/python/tests/scripts/d2_higherfunc.metta +++ b/python/tests/scripts/d2_higherfunc.metta @@ -168,12 +168,15 @@ !(assertEqualToResult (get-type (fmap (curry-a + 2) (Left "5"))) ()) -!(assertEqualToResult - (get-type (fmap (curry-a + 2) (UntypedC "5"))) - ()) -!(assertEqualToResult - (get-type (fmap (curry-a + 2) (UntypedC (Null) 5))) - ()) +; TODO: Two examples below are type-checked successfully because, (UntypedC "5") +; can return result which has an appropriate type. Uncomment when old_interpreter +; feature is removed. +;!(assertEqualToResult +; (get-type (fmap (curry-a + 2) (UntypedC "5"))) +; ()) +;!(assertEqualToResult +; (get-type (fmap (curry-a + 2) (UntypedC (Null) 5))) +; ()) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/python/tests/scripts/d5_auto_types.metta b/python/tests/scripts/d5_auto_types.metta index 85e9f93ba..f62a8181e 100644 --- a/python/tests/scripts/d5_auto_types.metta +++ b/python/tests/scripts/d5_auto_types.metta @@ -59,7 +59,11 @@ Auto type-checking can be enabled ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; let binds without type checking, but errors when used (let $x (+ 5 "S") $x) + (: f (-> $t Number)) -!(assertEqualToResult - (f (+ 5 "S")) - ((Error "S" BadType))) +; TODO: This test has different behavior in old_interpreter and minimal interpreter. +; In first case it returns (Error "S" BadType). In second case it returns +; (Error (+ 5 "S") BadType). Uncomment when old_interpreter feature is removed +;!(assertEqualToResult +; (f (+ 5 "S")) +; ((Error (+ 5 "S") BadType)))