diff --git a/c/src/atom.rs b/c/src/atom.rs index d9b43e892..aa038ad02 100644 --- a/c/src/atom.rs +++ b/c/src/atom.rs @@ -14,6 +14,8 @@ use std::collections::HashSet; use std::sync::atomic::{AtomicPtr, Ordering}; use hyperon::matcher::{Bindings, BindingsSet}; +use hyperon::metta::runner::bool::Bool; +use hyperon::metta::runner::number::Number; // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // Atom Interface @@ -269,6 +271,36 @@ pub extern "C" fn atom_gnd(gnd: *mut gnd_t) -> atom_t { Atom::gnd(CGrounded(AtomicPtr::new(gnd))).into() } +/// @ingroup atom_group +/// @param[in] b boolean value +/// @return an `atom_t` for the Bool Grounded atom +/// @note The caller must take ownership responsibility for the returned `atom_t` +/// +#[no_mangle] +pub extern "C" fn atom_bool(b: bool) -> atom_t { + Atom::gnd(Bool(b)).into() +} + +/// @ingroup atom_group +/// @param[in] n integer number +/// @return an `atom_t` for the Number Grounded atom +/// @note The caller must take ownership responsibility for the returned `atom_t` +/// +#[no_mangle] +pub extern "C" fn atom_int(n: i64) -> atom_t { + Atom::gnd(Number::Integer(n)).into() +} + +/// @ingroup atom_group +/// @param[in] f float number +/// @return an `atom_t` for the Number Grounded atom +/// @note The caller must take ownership responsibility for the returned `atom_t` +/// +#[no_mangle] +pub extern "C" fn atom_float(f: f64) -> atom_t { + Atom::gnd(Number::Float(f)).into() +} + /// @brief Creates a Grounded Atom referencing a Space /// @ingroup atom_group /// @param[in] space A pointer to an `space_t` for accessing the space @@ -757,6 +789,18 @@ pub extern "C" fn exec_error_no_reduce() -> exec_error_t { ExecError::NoReduce.into() } +/// @brief Creates a new `exec_error_t` representing a "Incorrect Argument" status, telling the +/// MeTTa interpreter that argument was not recognized by the function implementation. +/// @ingroup grounded_atom_group +/// @return The newly created `exec_error_t` +/// @note The caller must take ownership responsibility for the returned `exec_error_t`, and ultimately free +/// it with `exec_error_free()` or return it from an `execute` function +/// +#[no_mangle] +pub extern "C" fn exec_error_incorrect_argument() -> exec_error_t { + ExecError::IncorrectArgument.into() +} + /// @brief Creates a new `exec_error_t` representing a "No Error" status. This is the default interpreter status /// @ingroup grounded_atom_group /// @return The newly created `exec_error_t` diff --git a/c/src/metta.rs b/c/src/metta.rs index 0c8595b8a..ff189fe60 100644 --- a/c/src/metta.rs +++ b/c/src/metta.rs @@ -619,6 +619,20 @@ pub extern "C" fn atom_error_message(atom: *const atom_ref_t, buf: *mut c_char, /// #[no_mangle] pub extern "C" fn ATOM_TYPE_UNIT() -> atom_t { hyperon::metta::UNIT_TYPE.into() } +/// @brief Creates an atom used to indicate that an atom's type is a Number type. +/// @ingroup metta_language_group +/// @return The `atom_t` representing the atom +/// @note The returned `atom_t` must be freed with `atom_free()` +/// +#[no_mangle] pub extern "C" fn ATOM_TYPE_NUMBER() -> atom_t { hyperon::metta::runner::number::ATOM_TYPE_NUMBER.into() } + +/// @brief Creates an atom used to indicate that an atom's type is a Bool type. +/// @ingroup metta_language_group +/// @return The `atom_t` representing the atom +/// @note The returned `atom_t` must be freed with `atom_free()` +/// +#[no_mangle] pub extern "C" fn ATOM_TYPE_BOOL() -> atom_t { hyperon::metta::runner::bool::ATOM_TYPE_BOOL.into() } + /// @brief Creates a Symbol atom for the special MeTTa symbol used to indicate empty results /// returned by function. /// @ingroup metta_language_group diff --git a/lib/examples/sorted_list.rs b/lib/examples/sorted_list.rs index 89a35c8aa..aa1160157 100644 --- a/lib/examples/sorted_list.rs +++ b/lib/examples/sorted_list.rs @@ -1,6 +1,6 @@ use hyperon::*; use hyperon::metta::runner::*; -use hyperon::metta::runner::arithmetics::*; +use hyperon::metta::runner::number::Number; use hyperon::metta::text::SExprParser; fn main() -> Result<(), String> { diff --git a/lib/src/atom/mod.rs b/lib/src/atom/mod.rs index 34aa3aff5..0e29aaf1b 100644 --- a/lib/src/atom/mod.rs +++ b/lib/src/atom/mod.rs @@ -60,7 +60,7 @@ /// ``` /// #[macro_use] /// use hyperon::expr; -/// use hyperon::metta::runner::arithmetics::MulOp; +/// use hyperon::metta::runner::stdlib::arithmetics::MulOp; /// /// let sym = expr!("A"); /// let var = expr!(x); @@ -367,6 +367,11 @@ pub enum ExecError { /// Returned intentionally to let [crate::metta::interpreter] algorithm /// know that this expression should be returned "as is" without reducing. NoReduce, + /// Argument is not recognized by function implementation. It can be + /// argument of incorrect type or in incorrect format. Interpreter handles + /// this error similarly to the situation when pure function definition + /// is not matched (see [crate::metta::interpreter]). + IncorrectArgument, } impl From for ExecError { diff --git a/lib/src/metta/interpreter.rs b/lib/src/metta/interpreter.rs index 845f866af..e879d56c8 100644 --- a/lib/src/metta/interpreter.rs +++ b/lib/src/metta/interpreter.rs @@ -492,6 +492,8 @@ fn eval_impl<'a, T: Space>(to_eval: Atom, space: T, bindings: Bindings, prev: Op // TODO: we could remove ExecError::NoReduce and explicitly // return NOT_REDUCIBLE_SYMBOL from the grounded function instead. finished_result(return_not_reducible(), bindings, prev), + Err(ExecError::IncorrectArgument) => + finished_result(return_not_reducible(), bindings, prev), } }, } @@ -1464,7 +1466,13 @@ mod tests { #[test] fn interpret_atom_evaluate_grounded_expression_noreduce() { let result = call_interpret(&space(""), &expr!("eval" ({NonReducible()} {6}))); - assert_eq!(result, vec![expr!("NotReducible")]); + assert_eq!(result, vec![NOT_REDUCIBLE_SYMBOL]); + } + + #[test] + fn interpret_atom_evaluate_grounded_expression_incorrect_argument() { + let result = call_interpret(&space(""), &expr!("eval" ({IncorrectArgument()} {6.5}))); + assert_eq!(result, vec![NOT_REDUCIBLE_SYMBOL]); } #[test] @@ -1831,6 +1839,30 @@ mod tests { } } + #[derive(PartialEq, Clone, Debug)] + struct IncorrectArgument(); + + impl Grounded for IncorrectArgument { + fn type_(&self) -> Atom { + expr!("->" "u32" "u32") + } + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } + } + + impl CustomExecute for IncorrectArgument { + fn execute(&self, _args: &[Atom]) -> Result, ExecError> { + Err(ExecError::IncorrectArgument) + } + } + + impl Display for IncorrectArgument { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "incorrect-argument") + } + } + #[derive(PartialEq, Clone, Debug)] struct MulXUndefinedType(i32); diff --git a/lib/src/metta/runner/bool.rs b/lib/src/metta/runner/bool.rs new file mode 100644 index 000000000..7766261a1 --- /dev/null +++ b/lib/src/metta/runner/bool.rs @@ -0,0 +1,90 @@ +use crate::*; +use crate::atom::serial; +use crate::atom::serial::ConvertingSerializer; + +use std::fmt::Display; + +pub const ATOM_TYPE_BOOL : Atom = sym!("Bool"); + +#[derive(Clone, PartialEq, Debug)] +pub struct Bool(pub bool); + +impl Bool { + pub fn from_str(b: &str) -> Self { + match b { + "True" => Self(true), + "False" => Self(false), + _ => panic!("Could not parse Bool value: {}", b), + } + } + + pub fn from_atom(atom: &Atom) -> Option { + BoolSerializer::convert(atom) + } +} + +impl Into for bool { + fn into(self) -> Bool { + Bool(self) + } +} + +impl Display for Bool { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + true => write!(f, "True"), + false => write!(f, "False"), + } + } +} + +impl Grounded for Bool { + fn type_(&self) -> Atom { + ATOM_TYPE_BOOL + } + + fn as_match(&self) -> Option<&dyn CustomMatch> { + Some(self) + } + + fn serialize(&self, serializer: &mut dyn serial::Serializer) -> serial::Result { + serializer.serialize_bool(self.0) + } +} + +impl CustomMatch for Bool { + fn match_(&self, other: &Atom) -> matcher::MatchResultIter { + match_by_bidirectional_equality(self, other) + } +} + +#[derive(Default)] +struct BoolSerializer { + value: Option, +} + +impl serial::Serializer for BoolSerializer { + fn serialize_bool(&mut self, v: bool) -> serial::Result { + self.value = Some(Bool(v)); + Ok(()) + } +} + +impl serial::ConvertingSerializer for BoolSerializer { + fn into_type(self) -> Option { + self.value + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bool() { + assert_eq!(Bool::from_str("True"), Bool(true)); + assert_eq!(Bool::from_str("False"), Bool(false)); + assert_eq!(format!("{}", Bool(true)), "True"); + assert_eq!(format!("{}", Bool(false)), "False"); + } +} diff --git a/lib/src/metta/runner/environment.rs b/lib/src/metta/runner/environment.rs index 319a5e833..e75e66653 100644 --- a/lib/src/metta/runner/environment.rs +++ b/lib/src/metta/runner/environment.rs @@ -455,8 +455,8 @@ fn git_catalog_from_cfg_atom(atom: &ExpressionAtom, env: &Environment) -> Result let refresh_time = refresh_time.ok_or_else(|| format!("Error in environment.metta. \"refreshTime\" property required for #gitCatalog"))? .parse::().map_err(|e| format!("Error in environment.metta. Error parsing \"refreshTime\": {e}"))?; - let catalog_name = crate::metta::runner::string::strip_quotes(catalog_name); - let catalog_url = crate::metta::runner::string::strip_quotes(catalog_url); + let catalog_name = crate::metta::runner::str::strip_quotes(catalog_name); + let catalog_url = crate::metta::runner::str::strip_quotes(catalog_url); let mut managed_remote_catalog = LocalCatalog::new(caches_dir, catalog_name).unwrap(); let remote_catalog = GitCatalog::new(caches_dir, env.fs_mod_formats.clone(), catalog_name, catalog_url, refresh_time).unwrap(); @@ -474,7 +474,7 @@ fn include_path_from_cfg_atom(atom: &ExpressionAtom, env: &Environment) -> Resul None => return Err(format!("Error in environment.metta. #includePath missing path value")) }; let path = <&crate::SymbolAtom>::try_from(path_atom)?.name(); - let path = crate::metta::runner::string::strip_quotes(path); + let path = crate::metta::runner::str::strip_quotes(path); //TODO-FUTURE: In the future we may want to replace dyn-fmt with strfmt, and do something a // little bit nicer than this diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 9e65b97ab..d0dd2b617 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -94,8 +94,9 @@ use stdlib::CoreLibLoader; mod builtin_mods; use builtin_mods::*; -pub mod arithmetics; -pub mod string; +pub mod bool; +pub mod number; +pub mod str; const EXEC_SYMBOL : Atom = sym!("!"); diff --git a/lib/src/metta/runner/number.rs b/lib/src/metta/runner/number.rs new file mode 100644 index 000000000..9aa1aa3fa --- /dev/null +++ b/lib/src/metta/runner/number.rs @@ -0,0 +1,165 @@ +use crate::*; +use crate::atom::serial; +use crate::atom::serial::ConvertingSerializer; + +use std::fmt::Display; + +pub const ATOM_TYPE_NUMBER : Atom = sym!("Number"); + +#[derive(Clone, Debug)] +pub enum Number { + Integer(i64), + Float(f64), +} + +impl PartialEq for Number { + fn eq(&self, other: &Self) -> bool { + let (a, b) = Number::promote(self.clone(), other.clone()); + match (a, b) { + (Number::Integer(a), Number::Integer(b)) => a == b, + (Number::Float(a), Number::Float(b)) => a == b, + _ => panic!("Unexpected state!"), + } + } +} + +impl Into for i64 { + fn into(self) -> Number { + Number::Integer(self) + } +} + +impl Into for f64 { + fn into(self) -> Number { + Number::Float(self) + } +} + +impl Into for Number { + fn into(self) -> i64 { + match self { + Number::Integer(n) => n, + Number::Float(n) => n as i64, + } + } +} + +impl Into for Number { + fn into(self) -> f64 { + match self { + Number::Integer(n) => n as f64, + Number::Float(n) => n, + } + } +} + +impl Number { + pub fn from_int_str(num: &str) -> Result { + let n = num.parse::().map_err(|e| format!("Could not parse integer: '{num}', {e}"))?; + Ok(Self::Integer(n)) + } + + pub fn from_float_str(num: &str) -> Result { + let n = num.parse::().map_err(|e| format!("Could not parse float: '{num}', {e}"))?; + Ok(Self::Float(n)) + } + + pub fn promote(a: Number, b: Number) -> (Number, Number) { + let res_type = &NumberType::widest_type(a.get_type(), b.get_type()); + (a.cast(res_type), b.cast(res_type)) + } + + pub fn from_atom(atom: &Atom) -> Option { + NumberSerializer::convert(atom) + } + + fn get_type(&self) -> NumberType { + match self { + Number::Integer(_) => NumberType::Integer, + Number::Float(_) => NumberType::Float, + } + } + + fn cast(self, t: &NumberType) -> Number { + match t { + NumberType::Integer => Number::Integer(self.into()), + NumberType::Float => Number::Float(self.into()), + } + } +} + +#[derive(PartialEq)] +enum NumberType { + Integer, + Float, +} + +impl NumberType { + fn widest_type(a: NumberType, b: NumberType) -> NumberType { + // wanted using std::cmp::max but looks like this approach is much much simpler + if a == NumberType::Float || b == NumberType::Float { + NumberType::Float + } else { + NumberType::Integer + } + } +} + +impl Display for Number { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Integer(n) => write!(f, "{}", n), + Self::Float(n) => write!(f, "{:?}", n), + } + } +} + +impl Grounded for Number { + fn type_(&self) -> Atom { + ATOM_TYPE_NUMBER + } + + fn serialize(&self, serializer: &mut dyn serial::Serializer) -> serial::Result { + match self { + &Self::Integer(n) => serializer.serialize_i64(n), + &Self::Float(n) => serializer.serialize_f64(n), + } + } +} + +#[derive(Default)] +struct NumberSerializer { + value: Option, +} + +impl serial::Serializer for NumberSerializer { + fn serialize_i64(&mut self, v: i64) -> serial::Result { + self.value = Some(Number::Integer(v)); + Ok(()) + } + fn serialize_f64(&mut self, v: f64) -> serial::Result { + self.value = Some(Number::Float(v)); + Ok(()) + } +} + +impl serial::ConvertingSerializer for NumberSerializer { + fn into_type(self) -> Option { + self.value + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn number() { + assert_eq!(Number::from_int_str("12345").unwrap(), Number::Integer(12345i64)); + assert_eq!(Number::from_float_str("123.45").unwrap(), Number::Float(123.45f64)); + assert_eq!(Number::from_float_str("12345e-02").unwrap(), Number::Float(123.45f64)); + assert_eq!(Number::from_float_str("1.2345e+2").unwrap(), Number::Float(123.45f64)); + assert_eq!(format!("{}", Number::Integer(12345i64)), "12345"); + assert_eq!(format!("{}", Number::Float(123.45f64)), "123.45"); + } +} diff --git a/lib/src/metta/runner/arithmetics.rs b/lib/src/metta/runner/stdlib/arithmetics.rs similarity index 53% rename from lib/src/metta/runner/arithmetics.rs rename to lib/src/metta/runner/stdlib/arithmetics.rs index c978fea49..bc96e2ee5 100644 --- a/lib/src/metta/runner/arithmetics.rs +++ b/lib/src/metta/runner/stdlib/arithmetics.rs @@ -1,227 +1,12 @@ use crate::*; use crate::metta::*; -use crate::atom::serial; -use crate::atom::serial::ConvertingSerializer; +use crate::metta::text::Tokenizer; +use super::regex; +use crate::metta::runner::number::*; +use crate::metta::runner::bool::*; use std::fmt::Display; -pub const ATOM_TYPE_NUMBER : Atom = sym!("Number"); -pub const ATOM_TYPE_BOOL : Atom = sym!("Bool"); - -#[derive(Clone, Debug)] -pub enum Number { - Integer(i64), - Float(f64), -} - -impl PartialEq for Number { - fn eq(&self, other: &Self) -> bool { - let (a, b) = Number::promote(self.clone(), other.clone()); - match (a, b) { - (Number::Integer(a), Number::Integer(b)) => a == b, - (Number::Float(a), Number::Float(b)) => a == b, - _ => panic!("Unexpected state!"), - } - } -} - -impl Into for i64 { - fn into(self) -> Number { - Number::Integer(self) - } -} - -impl Into for f64 { - fn into(self) -> Number { - Number::Float(self) - } -} - -impl Into for Number { - fn into(self) -> i64 { - match self { - Number::Integer(n) => n, - Number::Float(n) => n as i64, - } - } -} - -impl Into for Number { - fn into(self) -> f64 { - match self { - Number::Integer(n) => n as f64, - Number::Float(n) => n, - } - } -} - -impl Number { - pub fn from_int_str(num: &str) -> Result { - let n = num.parse::().map_err(|e| format!("Could not parse integer: '{num}', {e}"))?; - Ok(Self::Integer(n)) - } - - pub fn from_float_str(num: &str) -> Result { - let n = num.parse::().map_err(|e| format!("Could not parse float: '{num}', {e}"))?; - Ok(Self::Float(n)) - } - - pub fn promote(a: Number, b: Number) -> (Number, Number) { - let res_type = &NumberType::widest_type(a.get_type(), b.get_type()); - (a.cast(res_type), b.cast(res_type)) - } - - pub fn from_atom(atom: &Atom) -> Option { - NumberSerializer::convert(atom) - } - - fn get_type(&self) -> NumberType { - match self { - Number::Integer(_) => NumberType::Integer, - Number::Float(_) => NumberType::Float, - } - } - - fn cast(self, t: &NumberType) -> Number { - match t { - NumberType::Integer => Number::Integer(self.into()), - NumberType::Float => Number::Float(self.into()), - } - } -} - -#[derive(PartialEq)] -enum NumberType { - Integer, - Float, -} - -impl NumberType { - fn widest_type(a: NumberType, b: NumberType) -> NumberType { - // wanted using std::cmp::max but looks like this approach is much much simpler - if a == NumberType::Float || b == NumberType::Float { - NumberType::Float - } else { - NumberType::Integer - } - } -} - -impl Display for Number { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Integer(n) => write!(f, "{}", n), - Self::Float(n) => write!(f, "{}", n), - } - } -} - -impl Grounded for Number { - fn type_(&self) -> Atom { - ATOM_TYPE_NUMBER - } - - fn serialize(&self, serializer: &mut dyn serial::Serializer) -> serial::Result { - match self { - &Self::Integer(n) => serializer.serialize_i64(n), - &Self::Float(n) => serializer.serialize_f64(n), - } - } -} - -#[derive(Default)] -struct NumberSerializer { - value: Option, -} - -impl serial::Serializer for NumberSerializer { - fn serialize_i64(&mut self, v: i64) -> serial::Result { - self.value = Some(Number::Integer(v)); - Ok(()) - } - fn serialize_f64(&mut self, v: f64) -> serial::Result { - self.value = Some(Number::Float(v)); - Ok(()) - } -} - -impl serial::ConvertingSerializer for NumberSerializer { - fn into_type(self) -> Option { - self.value - } -} - -#[derive(Clone, PartialEq, Debug)] -pub struct Bool(pub bool); - -impl Bool { - pub fn from_str(b: &str) -> Self { - match b { - "True" => Self(true), - "False" => Self(false), - _ => panic!("Could not parse Bool value: {}", b), - } - } - - pub fn from_atom(atom: &Atom) -> Option { - BoolSerializer::convert(atom) - } -} - -impl Into for bool { - fn into(self) -> Bool { - Bool(self) - } -} - -impl Display for Bool { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0 { - true => write!(f, "True"), - false => write!(f, "False"), - } - } -} - -impl Grounded for Bool { - fn type_(&self) -> Atom { - ATOM_TYPE_BOOL - } - - fn as_match(&self) -> Option<&dyn CustomMatch> { - Some(self) - } - - fn serialize(&self, serializer: &mut dyn serial::Serializer) -> serial::Result { - serializer.serialize_bool(self.0) - } -} - -impl CustomMatch for Bool { - fn match_(&self, other: &Atom) -> matcher::MatchResultIter { - match_by_bidirectional_equality(self, other) - } -} - -#[derive(Default)] -struct BoolSerializer { - value: Option, -} - -impl serial::Serializer for BoolSerializer { - fn serialize_bool(&mut self, v: bool) -> serial::Result { - self.value = Some(Bool(v)); - Ok(()) - } -} - -impl serial::ConvertingSerializer for BoolSerializer { - fn into_type(self) -> Option { - self.value - } -} - - macro_rules! def_binary_number_op { ($name:ident, $op:tt, $r:ident, $ret_type:ident) => { #[derive(Clone, PartialEq, Debug)] @@ -245,7 +30,7 @@ macro_rules! def_binary_number_op { impl CustomExecute for $name { fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from(concat!(stringify!($op), " expects two number arguments")); + let arg_error = || ExecError::IncorrectArgument; let a = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?; let b = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?; @@ -295,7 +80,7 @@ macro_rules! def_binary_bool_op { impl CustomExecute for $name { fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from(concat!(stringify!($disp), " expects two boolean arguments")); + let arg_error = || ExecError::IncorrectArgument; let Bool(a) = args.get(0).and_then(Bool::from_atom).ok_or_else(arg_error)?; let Bool(b) = args.get(1).and_then(Bool::from_atom).ok_or_else(arg_error)?; @@ -333,35 +118,56 @@ impl Grounded for NotOp { impl CustomExecute for NotOp { fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("not expects one boolean arguments"); + let arg_error = || ExecError::IncorrectArgument; let &Bool(a) = args.get(0).and_then(Atom::as_gnd).ok_or_else(arg_error)?; Ok(vec![Atom::gnd(Bool(!a))]) } } +pub fn register_rust_stdlib_tokens(tref: &mut Tokenizer) { + tref.register_fallible_token(regex(r"[\-\+]?\d+"), + |token| { Ok(Atom::gnd(Number::from_int_str(token)?)) }); + tref.register_fallible_token(regex(r"[\-\+]?\d+\.\d+"), + |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) }); + tref.register_fallible_token(regex(r"[\-\+]?\d+(\.\d+)?[eE][\-\+]?\d+"), + |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) }); + tref.register_token(regex(r"True|False"), + |token| { Atom::gnd(Bool::from_str(token)) }); + + let sum_op = Atom::gnd(SumOp{}); + tref.register_token(regex(r"\+"), move |_| { sum_op.clone() }); + let sub_op = Atom::gnd(SubOp{}); + tref.register_token(regex(r"\-"), move |_| { sub_op.clone() }); + let mul_op = Atom::gnd(MulOp{}); + tref.register_token(regex(r"\*"), move |_| { mul_op.clone() }); + let div_op = Atom::gnd(DivOp{}); + tref.register_token(regex(r"/"), move |_| { div_op.clone() }); + let mod_op = Atom::gnd(ModOp{}); + tref.register_token(regex(r"%"), move |_| { mod_op.clone() }); + let lt_op = Atom::gnd(LessOp{}); + tref.register_token(regex(r"<"), move |_| { lt_op.clone() }); + let gt_op = Atom::gnd(GreaterOp{}); + tref.register_token(regex(r">"), move |_| { gt_op.clone() }); + let le_op = Atom::gnd(LessEqOp{}); + tref.register_token(regex(r"<="), move |_| { le_op.clone() }); + let ge_op = Atom::gnd(GreaterEqOp{}); + tref.register_token(regex(r">="), move |_| { ge_op.clone() }); + let and_op = Atom::gnd(AndOp{}); + tref.register_token(regex(r"and"), move |_| { and_op.clone() }); + let or_op = Atom::gnd(OrOp{}); + tref.register_token(regex(r"or"), move |_| { or_op.clone() }); + let not_op = Atom::gnd(NotOp{}); + tref.register_token(regex(r"not"), move |_| { not_op.clone() }); + // NOTE: xor is absent in Python intentionally for conversion testing + let xor_op = Atom::gnd(XorOp{}); + tref.register_token(regex(r"xor"), move |_| { xor_op.clone() }); +} + #[cfg(test)] mod tests { use super::*; - #[test] - fn number() { - assert_eq!(Number::from_int_str("12345").unwrap(), Number::Integer(12345i64)); - assert_eq!(Number::from_float_str("123.45").unwrap(), Number::Float(123.45f64)); - assert_eq!(Number::from_float_str("12345e-02").unwrap(), Number::Float(123.45f64)); - assert_eq!(Number::from_float_str("1.2345e+2").unwrap(), Number::Float(123.45f64)); - assert_eq!(format!("{}", Number::Integer(12345i64)), "12345"); - assert_eq!(format!("{}", Number::Float(123.45f64)), "123.45"); - } - - #[test] - fn bool() { - assert_eq!(Bool::from_str("True"), Bool(true)); - assert_eq!(Bool::from_str("False"), Bool(false)); - assert_eq!(format!("{}", Bool(true)), "True"); - assert_eq!(format!("{}", Bool(false)), "False"); - } - macro_rules! assert_binary_op { ($name:ident, $a: expr, $b: expr, $r: expr) => { assert_eq!($name{}.execute(&mut vec![Atom::gnd($a), Atom::gnd($b)]), Ok(vec![Atom::gnd($r)])); diff --git a/lib/src/metta/runner/stdlib/atom.rs b/lib/src/metta/runner/stdlib/atom.rs index b6085004f..6c03ce6ee 100644 --- a/lib/src/metta/runner/stdlib/atom.rs +++ b/lib/src/metta/runner/stdlib/atom.rs @@ -5,9 +5,8 @@ use crate::metta::text::Tokenizer; use crate::metta::types::{get_atom_types, get_meta_type}; use crate::common::multitrie::MultiTrie; use crate::space::grounding::atom_to_trie_key; -#[cfg(feature = "pkg_mgmt")] -use crate::metta::runner::stdlib::{grounded_op, regex}; -use crate::metta::runner::arithmetics::*; +use super::{grounded_op, regex}; +use crate::metta::runner::number::*; use std::convert::TryInto; @@ -442,10 +441,11 @@ mod tests { use super::*; use crate::metta::text::SExprParser; use crate::metta::runner::EnvBuilder; - use crate::metta::runner::string::Str; + use crate::metta::runner::str::Str; use crate::common::test_utils::metta_space; use crate::metta::runner::stdlib::tests::run_program; use crate::metta::runner::Metta; + use crate::metta::runner::stdlib::arithmetics::*; #[test] fn metta_car_atom() { @@ -570,9 +570,6 @@ mod tests { } - - - #[test] fn unique_op() { let unique_op = UniqueAtomOp{}; @@ -699,4 +696,4 @@ mod tests { assert_eq_no_order!(get_type_op.execute(&mut vec![expr!("f" "\"test\""), expr!({space.clone()})]).unwrap(), vec![EMPTY_SYMBOL]); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/core.rs b/lib/src/metta/runner/stdlib/core.rs index b6b6349a3..508736ef8 100644 --- a/lib/src/metta/runner/stdlib/core.rs +++ b/lib/src/metta/runner/stdlib/core.rs @@ -4,14 +4,13 @@ use crate::metta::*; use crate::metta::text::Tokenizer; use crate::common::shared::Shared; use crate::common::CachingMapper; -#[cfg(feature = "pkg_mgmt")] use crate::metta::runner::Metta; +use crate::metta::runner::bool::*; use std::convert::TryInto; use std::collections::HashMap; -use crate::metta::runner::stdlib::{interpret_no_error, grounded_op, unit_result, regex, interpret}; -use crate::metta::runner::arithmetics::*; +use super::{interpret_no_error, grounded_op, unit_result, regex, interpret}; #[derive(Clone, Debug)] pub struct PragmaOp { @@ -532,4 +531,4 @@ mod tests { let val = SealedOp{}.execute(&mut vec![expr!(x y), expr!("="(y z))]); assert!(crate::atom::matcher::atoms_are_equivalent(&val.unwrap()[0], &expr!("="(y z)))); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/math.rs b/lib/src/metta/runner/stdlib/math.rs index 8b9cf195b..a07d822e0 100644 --- a/lib/src/metta/runner/stdlib/math.rs +++ b/lib/src/metta/runner/stdlib/math.rs @@ -1,12 +1,12 @@ use crate::*; use crate::metta::*; -#[cfg(feature = "pkg_mgmt")] +use crate::metta::text::Tokenizer; +use super::{grounded_op, regex}; +use crate::metta::runner::number::*; +use crate::metta::runner::bool::*; use std::convert::TryInto; -use crate::metta::text::Tokenizer; -use crate::metta::runner::{arithmetics::*,stdlib::grounded_op, stdlib::regex}; - #[derive(Clone, Debug)] pub struct PowMathOp {} grounded_op!(PowMathOp, "pow-math"); @@ -740,4 +740,4 @@ mod tests { let res = IsInfMathOp {}.execute(&mut vec![expr!("A")]); assert_eq!(res, Err(ExecError::from("isinf-math expects one argument: input number"))); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/mod.rs b/lib/src/metta/runner/stdlib/mod.rs index 50655dbe9..8a38a631e 100644 --- a/lib/src/metta/runner/stdlib/mod.rs +++ b/lib/src/metta/runner/stdlib/mod.rs @@ -9,6 +9,7 @@ pub mod string; pub mod debug; pub mod space; pub mod core; +pub mod arithmetics; use crate::*; use crate::space::*; @@ -16,9 +17,9 @@ use crate::metta::*; use crate::metta::text::{Tokenizer, SExprParser}; use crate::common::shared::Shared; use crate::metta::runner::{Metta, RunContext, ModuleLoader}; -use regex::Regex; +use super::str::*; -use super::{arithmetics::*, string::*}; +use regex::Regex; macro_rules! grounded_op { ($name:ident, $disp:literal) => { @@ -113,45 +114,9 @@ pub fn register_rust_stdlib_tokens(target: &mut Tokenizer) { let mut rust_tokens = Tokenizer::new(); let tref = &mut rust_tokens; - tref.register_fallible_token(regex(r"[\-\+]?\d+"), - |token| { Ok(Atom::gnd(Number::from_int_str(token)?)) }); - tref.register_fallible_token(regex(r"[\-\+]?\d+\.\d+"), - |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) }); - tref.register_fallible_token(regex(r"[\-\+]?\d+(\.\d+)?[eE][\-\+]?\d+"), - |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) }); - tref.register_token(regex(r"True|False"), - |token| { Atom::gnd(Bool::from_str(token)) }); - tref.register_token(regex(r#"(?s)^".*"$"#), - |token| { let mut s = String::from(token); s.remove(0); s.pop(); Atom::gnd(Str::from_string(s)) }); - let sum_op = Atom::gnd(SumOp{}); - tref.register_token(regex(r"\+"), move |_| { sum_op.clone() }); - let sub_op = Atom::gnd(SubOp{}); - tref.register_token(regex(r"\-"), move |_| { sub_op.clone() }); - let mul_op = Atom::gnd(MulOp{}); - tref.register_token(regex(r"\*"), move |_| { mul_op.clone() }); - let div_op = Atom::gnd(DivOp{}); - tref.register_token(regex(r"/"), move |_| { div_op.clone() }); - let mod_op = Atom::gnd(ModOp{}); - tref.register_token(regex(r"%"), move |_| { mod_op.clone() }); - let lt_op = Atom::gnd(LessOp{}); - tref.register_token(regex(r"<"), move |_| { lt_op.clone() }); - let gt_op = Atom::gnd(GreaterOp{}); - tref.register_token(regex(r">"), move |_| { gt_op.clone() }); - let le_op = Atom::gnd(LessEqOp{}); - tref.register_token(regex(r"<="), move |_| { le_op.clone() }); - let ge_op = Atom::gnd(GreaterEqOp{}); - tref.register_token(regex(r">="), move |_| { ge_op.clone() }); - let and_op = Atom::gnd(AndOp{}); - tref.register_token(regex(r"and"), move |_| { and_op.clone() }); - let or_op = Atom::gnd(OrOp{}); - tref.register_token(regex(r"or"), move |_| { or_op.clone() }); - let not_op = Atom::gnd(NotOp{}); - tref.register_token(regex(r"not"), move |_| { not_op.clone() }); - // NOTE: xor is absent in Python intentionally for conversion testing - let xor_op = Atom::gnd(XorOp{}); - tref.register_token(regex(r"xor"), move |_| { xor_op.clone() }); - core::register_rust_stdlib_tokens(tref); + arithmetics::register_rust_stdlib_tokens(tref); + string::register_rust_stdlib_tokens(tref); target.move_front(&mut rust_tokens); } @@ -190,9 +155,11 @@ mod tests { use super::*; use crate::metta::text::SExprParser; use crate::metta::runner::EnvBuilder; - use crate::metta::runner::string::Str; + use crate::metta::runner::str::Str; use crate::matcher::atoms_are_equivalent; use crate::common::Operation; + use crate::metta::runner::bool::Bool; + use crate::metta::runner::number::Number; use std::fmt::Display; use regex::Regex; @@ -804,4 +771,4 @@ mod tests { assert_eq_metta_results!(metta.run(parser), Ok(vec![vec![]])); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/module.rs b/lib/src/metta/runner/stdlib/module.rs index dfc64fb6a..5b2bed8a2 100644 --- a/lib/src/metta/runner/stdlib/module.rs +++ b/lib/src/metta/runner/stdlib/module.rs @@ -3,14 +3,12 @@ use crate::space::*; use crate::metta::*; use crate::metta::text::Tokenizer; use crate::common::shared::Shared; -#[cfg(feature = "pkg_mgmt")] use crate::metta::runner::{Metta, RunContext, ResourceKey}; +use crate::metta::runner::str::*; +use super::{grounded_op, regex, unit_result}; use regex::Regex; -use crate::metta::runner::string::*; -use crate::metta::runner::stdlib::{grounded_op, regex, unit_result}; - #[derive(Clone, Debug)] pub struct ImportOp { //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP @@ -323,4 +321,4 @@ mod tests { assert!(constr.is_some()); assert_eq!(constr.unwrap()("&my"), Ok(sym!("definition"))); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/package.rs b/lib/src/metta/runner/stdlib/package.rs index 5db9569af..b70a5f5e8 100644 --- a/lib/src/metta/runner/stdlib/package.rs +++ b/lib/src/metta/runner/stdlib/package.rs @@ -1,12 +1,12 @@ -use crate::metta::runner::stdlib::{grounded_op, regex, unit_result}; +use super::{grounded_op, regex, unit_result}; use crate::*; use crate::metta::*; use crate::metta::text::Tokenizer; -use crate::metta::runner::{Metta, RunContext, string::Str, - git_catalog::ModuleGitLocation, mod_name_from_url, pkg_mgmt::UpdateMode}; - -use crate::metta::runner::string::*; - +use crate::metta::runner::{Metta, RunContext, + git_catalog::ModuleGitLocation, + mod_name_from_url, + pkg_mgmt::UpdateMode}; +use crate::metta::runner::str::*; /// Provides a way to access [Metta::load_module_at_path] from within MeTTa code #[derive(Clone, Debug)] @@ -128,4 +128,4 @@ pub fn register_pkg_mgmt_tokens(tref: &mut Tokenizer, metta: &Metta) { tref.register_token(regex(r"register-module!"), move |_| { register_module_op.clone() }); let git_module_op = Atom::gnd(GitModuleOp::new(metta.clone())); tref.register_token(regex(r"git-module!"), move |_| { git_module_op.clone() }); -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/random.rs b/lib/src/metta/runner/stdlib/random.rs index b78337d42..27c70488e 100644 --- a/lib/src/metta/runner/stdlib/random.rs +++ b/lib/src/metta/runner/stdlib/random.rs @@ -1,9 +1,9 @@ use crate::*; use crate::metta::*; -#[cfg(feature = "pkg_mgmt")] - +use super::{grounded_op, regex}; use crate::metta::text::Tokenizer; -use crate::metta::runner::{arithmetics::*, stdlib::grounded_op, stdlib::regex}; +use crate::metta::runner::number::*; +use crate::metta::runner::bool::*; use std::fmt::Display; use rand::Rng; @@ -135,4 +135,4 @@ mod tests { let res = RandomFloatOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(0)})]); assert_eq!(res, Err(ExecError::from("Range is empty"))); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/space.rs b/lib/src/metta/runner/stdlib/space.rs index 12b60617b..0fe7058ea 100644 --- a/lib/src/metta/runner/stdlib/space.rs +++ b/lib/src/metta/runner/stdlib/space.rs @@ -2,7 +2,6 @@ use crate::*; use crate::space::*; use crate::metta::*; use crate::metta::text::Tokenizer; -#[cfg(feature = "pkg_mgmt")] use crate::metta::runner::stdlib::{grounded_op, unit_result, regex}; use std::rc::Rc; @@ -318,4 +317,4 @@ mod tests { let result = GetStateOp{}.execute(&mut vec![new_state.clone()]); assert_eq!(result, Ok(vec![expr!("C" "D")])) } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/stdlib/string.rs b/lib/src/metta/runner/stdlib/string.rs index 1e997d0ac..5185caa02 100644 --- a/lib/src/metta/runner/stdlib/string.rs +++ b/lib/src/metta/runner/stdlib/string.rs @@ -1,10 +1,8 @@ use crate::*; use crate::metta::*; use crate::metta::text::Tokenizer; -#[cfg(feature = "pkg_mgmt")] -use crate::metta::runner::string::*; - -use crate::metta::runner::stdlib::{grounded_op, atom_to_string, unit_result, regex}; +use crate::metta::runner::str::*; +use super::{grounded_op, atom_to_string, unit_result, regex}; use std::convert::TryInto; @@ -69,6 +67,11 @@ pub fn register_runner_tokens(tref: &mut Tokenizer) { tref.register_token(regex(r"format-args"), move |_| { format_args_op.clone() }); } +pub fn register_rust_stdlib_tokens(tref: &mut Tokenizer) { + tref.register_token(regex(r#"(?s)^".*"$"#), + |token| { let mut s = String::from(token); s.remove(0); s.pop(); Atom::gnd(Str::from_string(s)) }); +} + #[cfg(test)] mod tests { use super::*; @@ -77,4 +80,4 @@ mod tests { fn println_op() { assert_eq!(PrintlnOp{}.execute(&mut vec![sym!("A")]), unit_result()); } -} \ No newline at end of file +} diff --git a/lib/src/metta/runner/string.rs b/lib/src/metta/runner/str.rs similarity index 100% rename from lib/src/metta/runner/string.rs rename to lib/src/metta/runner/str.rs diff --git a/lib/src/metta/text.rs b/lib/src/metta/text.rs index ebec17aec..434921abb 100644 --- a/lib/src/metta/text.rs +++ b/lib/src/metta/text.rs @@ -672,7 +672,7 @@ mod tests { // contours can't be captured by a regex. let mut tokenizer = Tokenizer::new(); tokenizer.register_fallible_token(Regex::new(r"[\-\+]?\d+.\d+").unwrap(), - |token| Ok(Atom::gnd(metta::runner::arithmetics::Number::from_float_str(token)?)) + |token| Ok(Atom::gnd(metta::runner::number::Number::from_float_str(token)?)) ); let mut parser = SExprParser::new("12345678901234567:8901234567890"); assert!(parser.parse(&tokenizer).is_err()); diff --git a/lib/tests/types.rs b/lib/tests/types.rs index 318798524..788f27469 100644 --- a/lib/tests/types.rs +++ b/lib/tests/types.rs @@ -2,7 +2,8 @@ use hyperon::*; use hyperon::metta::*; use hyperon::metta::text::*; use hyperon::metta::runner::{Metta, EnvBuilder}; -use hyperon::metta::runner::arithmetics::*; +use hyperon::metta::runner::number::Number; +use hyperon::metta::runner::bool::Bool; #[test] fn test_types_in_metta() { diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 2acedad0d..166aef264 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -124,6 +124,8 @@ class AtomType: GROUNDED = Atom._from_catom(hp.CAtomType.GROUNDED) GROUNDED_SPACE = Atom._from_catom(hp.CAtomType.GROUNDED_SPACE) UNIT = Atom._from_catom(hp.CAtomType.UNIT) + NUMBER = Atom._from_catom(hp.CAtomType.NUMBER) + BOOL = Atom._from_catom(hp.CAtomType.BOOL) class Atoms: @@ -191,8 +193,34 @@ def _priv_gnd_get_object(atom): def G(object, type=AtomType.UNDEFINED): """A convenient method to construct a GroundedAtom""" - assert hasattr(object, "copy"), "Method copy should be implemented by grounded object" - return GroundedAtom(hp.atom_gnd(object, type.catom)) + return GroundedAtom(_priv_atom_gnd(object, type)) + +def _priv_atom_gnd(obj, type): + """ + Converts Python object into grounded atom. It has special processing for + the object which has cspace attribute and for ValueObject instances of primitive + types. Spaces usually should be treated by a special way. Primitive atoms + are converted into the MeTTa primitives. + """ + catom = None + if hasattr(obj, "cspace"): + assert type == AtomType.UNDEFINED, f"Grounded Space Atoms {obj} can't have a custom type {type}" + catom = hp.atom_space(obj.cspace) + elif isinstance(obj, ValueObject): + value = obj.value + if isinstance(value, bool): + assert type == AtomType.BOOL or type == AtomType.UNDEFINED, f"Grounded bool {obj} can't have a custom type {type}" + catom = hp.atom_bool(value) + elif isinstance(value, int): + assert type == AtomType.NUMBER or type == AtomType.UNDEFINED, f"Grounded int {obj} can't have a custom type {type}" + catom = hp.atom_int(value) + elif isinstance(value, float): + assert type == AtomType.NUMBER or type == AtomType.UNDEFINED, f"Grounded float {obj} can't have a custom type {type}" + catom = hp.atom_float(value) + if catom is None: + assert hasattr(obj, "copy"), f"Method copy should be implemented by grounded object {obj}" + catom = hp.atom_py(obj, type.catom) + return catom def _priv_call_execute_on_grounded_atom(gnd, typ, args): """ @@ -309,6 +337,15 @@ class NoReduceError(Exception): """Custom exception; raised when a reduction operation cannot be performed.""" pass +class IncorrectArgumentError(Exception): + """ + Argument is not recognized by function implementation. It can be + argument of incorrect type or in incorrect format. Interpreter handles + this error similarly to the situation when pure function definition + is not matched. + """ + pass + class MettaError(Exception): """Custom exception; raised when a error should be returned from OperationAtom, , but we don't want to output Python error stack.""" @@ -531,9 +568,27 @@ def OperationAtom(name, op, type_names=None, unwrap=True): return G(OperationObject(name, op, unwrap), _type_sugar(type_names)) def ValueAtom(value, type_name=None, atom_id=None): - """Creates a GroundedAtom that wraps a given value, optionally specifying its type and identifier.""" + """ + Creates a GroundedAtom that wraps a given value, optionally specifying its + type and identifier. It has special processing for the objects which have + cspace attribute and for ValueObject instances of primitive types. Spaces + usually should be treated by a special way. Primitive atoms are converted + into the MeTTa primitives. + """ return G(ValueObject(value, atom_id), _type_sugar(type_name)) +def PrimitiveAtom(value, type_name=None, atom_id=None): + """ + Creates a GroundedAtom that wraps a given Python primitive value without + converting it into the MeTTa primitive. By default ValueAtom function + converts Python primitives into MeTTa ones. This function is added to + override this rule if needed. + """ + PRIMITIVE_TYPES = (int, float, bool) + assert isinstance(value, PRIMITIVE_TYPES), f"Primitive value {PRIMITIVE_TYPES} is expected" + type = _type_sugar(type_name) + return GroundedAtom(hp.atom_py(ValueObject(value, atom_id), type.catom)) + def MatchableAtom(value, type_name=None, atom_id=None): """ Creates a Grounded Atom that wraps a matchable value, optionally specifying its type and identifier. diff --git a/python/hyperon/runner.py b/python/hyperon/runner.py index 8f3aefd76..4960c6b96 100644 --- a/python/hyperon/runner.py +++ b/python/hyperon/runner.py @@ -151,6 +151,8 @@ def register_token(self, regexp, constr): """Registers a token""" self.tokenizer().register_token(regexp, constr) + # FIXME: for operation atoms name is passed twice: first argument and + # field of the OperationAtom def register_atom(self, name, symbol): """Registers an Atom""" self.register_token(name, lambda _: symbol) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index c5ed1d27b..bd5124f71 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -26,43 +26,6 @@ def __eq__(self, other): return self.char == other.char return False -@register_atoms -def arithm_ops(): - subAtom = OperationAtom('-', lambda a, b: a - b, ['Number', 'Number', 'Number']) - mulAtom = OperationAtom('*', lambda a, b: a * b, ['Number', 'Number', 'Number']) - addAtom = OperationAtom('+', lambda a, b: a + b, ['Number', 'Number', 'Number']) - divAtom = OperationAtom('/', lambda a, b: a / b, ['Number', 'Number', 'Number']) - modAtom = OperationAtom('%', lambda a, b: a % b, ['Number', 'Number', 'Number']) - return { - r"\+": addAtom, - r"-": subAtom, - r"\*": mulAtom, - r"/": divAtom, - r"%": modAtom - } - -@register_atoms -def bool_ops(): - equalAtom = OperationAtom('==', lambda a, b: [ValueAtom(a == b, 'Bool')], - ['$t', '$t', 'Bool'], unwrap=False) - greaterAtom = OperationAtom('>', lambda a, b: a > b, ['Number', 'Number', 'Bool']) - lessAtom = OperationAtom('<', lambda a, b: a < b, ['Number', 'Number', 'Bool']) - greaterEqAtom = OperationAtom('>=', lambda a, b: a >= b, ['Number', 'Number', 'Bool']) - lessEqAtom = OperationAtom('<=', lambda a, b: a <= b, ['Number', 'Number', 'Bool']) - orAtom = OperationAtom('or', lambda a, b: a or b, ['Bool', 'Bool', 'Bool']) - andAtom = OperationAtom('and', lambda a, b: a and b, ['Bool', 'Bool', 'Bool']) - notAtom = OperationAtom('not', lambda a: not a, ['Bool', 'Bool']) - return { - r"==": equalAtom, - r"<": lessAtom, - r">": greaterAtom, - r"<=": lessEqAtom, - r">=": greaterEqAtom, - r"or": orAtom, - r"and": andAtom, - r"not": notAtom - } - class RegexMatchableObject(MatchableObject): ''' To match atoms with regular expressions''' @@ -116,12 +79,8 @@ def text_ops(run_context): @register_tokens def type_tokens(): return { - r"[-+]?\d+" : lambda token: ValueAtom(int(token), 'Number'), - r"[-+]?\d+\.\d+": lambda token: ValueAtom(float(token), 'Number'), - r"[-+]?\d+(\.\d+)?[eE][-+]?\d+": lambda token: ValueAtom(float(token), 'Number'), r"(?s)^\".*\"$": lambda token: ValueAtom(str(token[1:-1]), 'String'), "\'[^\']\'": lambda token: ValueAtom(Char(token[1]), 'Char'), - r"True|False": lambda token: ValueAtom(token == 'True', 'Bool'), r'regex:"[^"]*"': lambda token: G(RegexMatchableObject(token), AtomType.UNDEFINED) } diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index 2eece4d12..6a0c40326 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -166,6 +166,7 @@ exec_error_t py_execute(const struct gnd_t* _cgnd, const struct atom_vec_t* _arg py::object hyperon = py::module_::import("hyperon.atoms"); py::function _priv_call_execute_on_grounded_atom = hyperon.attr("_priv_call_execute_on_grounded_atom"); py::handle NoReduceError = hyperon.attr("NoReduceError"); + py::handle IncorrectArgumentError = hyperon.attr("IncorrectArgumentError"); py::object pyobj = static_cast(_cgnd)->pyobj; CAtom pytyp = static_cast(_cgnd)->typ; try { @@ -185,6 +186,8 @@ exec_error_t py_execute(const struct gnd_t* _cgnd, const struct atom_vec_t* _arg } catch (py::error_already_set &e) { if (e.matches(NoReduceError)) { return exec_error_no_reduce(); + } else if (e.matches(IncorrectArgumentError)) { + return exec_error_incorrect_argument(); } else { char message[4096]; snprintf(message, lenghtof(message), "Exception caught:\n%s", e.what()); @@ -670,22 +673,22 @@ PYBIND11_MODULE(hyperonpy, m) { } return CAtom(atom_expr(children, size)); }, "Create expression atom"); - m.def("atom_gnd", [](py::object object, CAtom ctyp) { - if (py::hasattr(object, "cspace")) { - //TODO: We should make static constant type atoms, so we don't need to allocate and then - // free them, just to test a constant - atom_t undefined = ATOM_TYPE_UNDEFINED(); - if (!atom_eq(ctyp.ptr(), &undefined)) { - throw std::runtime_error("Grounded Space Atoms can't have a custom type"); - } - atom_free(undefined); - space_t* space = object.attr("cspace").cast().ptr(); - return CAtom(atom_gnd_for_space(space)); - } else { - atom_t typ = atom_clone(ctyp.ptr()); - return CAtom(atom_gnd(new GroundedObject(object, typ))); - } - }, "Create grounded atom"); + m.def("atom_space", [](CSpace& atom) { + return CAtom(atom_gnd_for_space(atom.ptr())); + }, "Create Space grounded atom"); + m.def("atom_py", [](py::object object, CAtom ctyp) { + atom_t typ = atom_clone(ctyp.ptr()); + return CAtom(atom_gnd(new GroundedObject(object, typ))); + }, "Create general grounded atom from Python object"); + m.def("atom_bool", [](bool b) { + return CAtom(atom_bool(b)); + }, "Create bool grounded atom"); + m.def("atom_int", [](long long n) { + return CAtom(atom_int(n)); + }, "Create int grounded atom"); + m.def("atom_float", [](double d) { + return CAtom(atom_float(d)); + }, "Create float grounded atom"); m.def("atom_free", [](CAtom atom) { atom_free(atom.obj); }, "Free C atom"); m.def("atom_eq", [](CAtom& a, CAtom& b) -> bool { return atom_eq(a.ptr(), b.ptr()); }, "Test if two atoms are equal"); @@ -939,7 +942,9 @@ PYBIND11_MODULE(hyperonpy, m) { ADD_TYPE(EXPRESSION, "Expression") ADD_TYPE(GROUNDED, "Grounded") ADD_TYPE(GROUNDED_SPACE, "Space") - ADD_TYPE(UNIT, "Unit"); + ADD_TYPE(UNIT, "Unit") + ADD_TYPE(NUMBER, "Number") + ADD_TYPE(BOOL, "Bool"); m.def("check_type", [](CSpace space, CAtom& atom, CAtom& type) { return check_type(space.ptr(), atom.ptr(), type.ptr()); }, "Check if atom is an instance of the passed type"); @@ -1117,12 +1122,10 @@ PYBIND11_MODULE(hyperonpy, m) { py::object obj; // separate assignments are needed to get different types if(ll == ld) { - obj = ValueObject(ll); + atom = atom_int(ll); } else { - obj = ValueObject(ld); + atom = atom_float(ld); } - atom = atom_gnd(new GroundedObject(obj, - atom_sym("Number"))); } catch(...) { atom = atom_sym(str.c_str()); diff --git a/python/tests/test_atom.py b/python/tests/test_atom.py index 8ae1b673a..7d042f40d 100644 --- a/python/tests/test_atom.py +++ b/python/tests/test_atom.py @@ -51,8 +51,8 @@ def test_grounded_grounded_type(self): def test_grounded_no_copy(self): with self.assertRaises(AssertionError) as context: atom = G(GroundedNoCopy(), S("GroundedNoCopy")) - self.assertEqual("Method copy should be implemented by grounded object", - str(context.exception)) + self.assertTrue(str(context.exception) + .startswith("Method copy should be implemented by grounded object")) # def test_grounded_execute_default(self): # self.assertEqual(ValueAtom(1.0).get_object().execute(VecAtom(), @@ -98,7 +98,7 @@ def test_interpret(self): def test_grounded_returns_python_value_unwrap_false(self): def x2_op(atom): return [2 * atom.get_object().value] - x2Atom = OperationAtom('*2', x2_op, type_names=["int", "int"], unwrap=False) + x2Atom = OperationAtom('*2', x2_op, type_names=["Number", "Number"], unwrap=False) expr = E(x2Atom, ValueAtom(1)) space = GroundingSpaceRef() @@ -124,6 +124,13 @@ def test_no_reduce(self): self.assertEqual(interpret(space, expr), [E(noReduceAtom, ValueAtom(1))]) + def test_incorrect_argument(self): + space = GroundingSpaceRef() + expr = E(Atoms.METTA, E(incorrectArgumentAtom, ValueAtom(1)), + AtomType.UNDEFINED, G(space)) + self.assertEqual(interpret(space, expr), + [E(incorrectArgumentAtom, ValueAtom(1))]) + def test_match_(self): space = GroundingSpaceRef() match_atom = MatchableAtomTest(S("MatchableAtom"), type_name=None, atom_id=None) @@ -141,12 +148,16 @@ def test_match_(self): # No unwrap def x2_op(atom): return [ValueAtom(2 * atom.get_object().value)] -x2Atom = OperationAtom('*2', x2_op, type_names=["int", "int"], unwrap=False) +x2Atom = OperationAtom('*2', x2_op, type_names=["Number", "Number"], unwrap=False) def no_reduce_op(atom): raise NoReduceError() noReduceAtom = OperationAtom('no-reduce', no_reduce_op, unwrap=False) +def incorrect_argument_op(atom): + raise IncorrectArgumentError() +incorrectArgumentAtom = OperationAtom('incorrect-argument', incorrect_argument_op, unwrap=False) + class GroundedNoCopy: pass diff --git a/python/tests/test_examples.py b/python/tests/test_examples.py index a0e4cfe82..166715a2a 100644 --- a/python/tests/test_examples.py +++ b/python/tests/test_examples.py @@ -69,7 +69,7 @@ def test_new_object(self): pglob = Global(10) ploc = 10 metta.register_token("pglob", lambda _: ValueAtom(pglob)) - metta.register_token("ploc", lambda _: ValueAtom(ploc)) + metta.register_token("ploc", lambda _: PrimitiveAtom(ploc)) metta.register_token("Setter", lambda token: newNewAtom(token, Setter)) metta.register_token("SetAtom", lambda token: newNewAtom(token, Setter, False)) # Just checking that interpretation of "pglob" gives us @@ -99,7 +99,7 @@ def test_new_object(self): # "ploc" creates ValueAtom(ploc) on each occurrence self.assertEqual(metta.run('! ploc')[0][0].get_object().value, 10) # Another way is to return the same atom each time - ploca = ValueAtom(ploc) + ploca = PrimitiveAtom(ploc) metta.register_token("ploca", lambda _: ploca) # It will be not affected by assigning unwrapped values: # we are still copying values while unwrapping diff --git a/python/tests/test_grounded_type.py b/python/tests/test_grounded_type.py index ffeb6b346..8cf13110d 100644 --- a/python/tests/test_grounded_type.py +++ b/python/tests/test_grounded_type.py @@ -30,6 +30,7 @@ def test_apply_type(self): def test_higher_func(self): metta = MeTTa(env_builder=Environment.test_env()) + metta.register_atom(r"plus", OperationAtom("plus", lambda a, b: a + b)) metta.register_atom( r"curry_num", OperationAtom( @@ -38,23 +39,28 @@ def test_higher_func(self): 'lmd', lambda y: op.get_object().op(x.get_object().value, y), ['Number', 'Number'])], - #FIXME: interpreter refuses to execute typed curry_num - #[['Number', 'Number', 'Number'], 'Number', ['Number', 'Number']], + [['Number', 'Number', 'Number'], 'Number', ['Number', 'Number']], unwrap=False)) - self.assertEqual(metta.run("!((curry_num + 1) 2)"), + self.assertEqual(metta.run("!((curry_num plus 1) 2)"), metta.run("! 3")) def test_meta_types(self): metta = MeTTa(env_builder=Environment.test_env()) ### Basic functional types metta.register_atom(r"id_num", OperationAtom("id_num", lambda x: x, ['Number', 'Number'])) - metta.register_atom(r"as_int", OperationAtom("as_int", lambda x: x, ['Number', 'Int'])) + metta.register_atom(r"as_int", OperationAtom("as_int", lambda x: x, ['Number', 'Number'])) v1 = metta.run("!(id_num (+ 2 2))")[0] v2 = metta.run("! 4")[0] v3 = metta.run("!(as_int (+ 2 2))")[0] self.assertEqual(v1, v2) self.assertEqual(v1[0].get_grounded_type(), v2[0].get_grounded_type()) - self.assertNotEqual(v1[0].get_grounded_type(), v3[0].get_grounded_type()) + # Python int is represented by the Rust Number grounded atom. Number is + # implemented to have constant type "Number" which is common + # implementation of the grounded atoms in Rust. Usually type is an + # attribute of the atom and cannot be changed by reassigning. Thus one + # cannot override type of the value by calling (-> Number Int) + # function. It doesn't work for usual Rust grounded atoms. + self.assertEqual(v1[0].get_grounded_type(), v3[0].get_grounded_type()) # Untyped symbols don't cause type error, but the expression is not reduced self.assertEqual(metta.run("!(id_num untyp)"), [metta.parse_all("(id_num untyp)")]) # Typed symbols cause type error when evaluated @@ -141,7 +147,6 @@ def test_undefined_operation_type(self): metta.parse_single("untyped").get_grounded_type()) def test_conversion_between_rust_and_python(self): - self.maxDiff = None metta = MeTTa(env_builder=Environment.test_env()) integer = metta.run('!(+ 1 (random-int 4 5))', flat=True)[0].get_object() self.assertEqual(integer, ValueObject(5)) @@ -150,8 +155,33 @@ def test_conversion_between_rust_and_python(self): bool = metta.run('!(not (flip))', flat=True)[0].get_object() self.assertTrue(bool.value or not bool.value) false = metta.run('!(not True)', flat=True)[0].get_object() - self.assertEquals(false, ValueObject(False)) + self.assertEqual(false, ValueObject(False)) + def test_python_value_conversion(self): + metta = MeTTa(env_builder=Environment.test_env()) + metta.register_atom("return-int", OperationAtom("return-int", lambda: 42)) + integer = metta.run('!(return-int)', flat=True)[0].get_object() + self.assertEqual(integer, ValueObject(42)) + metta.register_atom("return-float", OperationAtom("return-float", lambda: 4.2)) + float = metta.run('!(return-float)', flat=True)[0].get_object() + self.assertEqual(float, ValueObject(4.2)) + metta.register_atom("return-bool", OperationAtom("return-bool", lambda: True)) + float = metta.run('!(return-bool)', flat=True)[0].get_object() + self.assertEqual(float, ValueObject(True)) + + def test_assert_grounded_value_type(self): + with self.assertRaises(AssertionError) as cm: + ValueAtom(42, "Int") + msg = str(cm.exception) + self.assertTrue(msg.startswith("Grounded int 42 can't have a custom type Int"), f"Unexpected message \"{msg}\"") + with self.assertRaises(AssertionError) as cm: + ValueAtom(4.2, "Float") + msg = str(cm.exception) + self.assertTrue(msg.startswith("Grounded float 4.2 can't have a custom type Float"), f"Unexpected message \"{msg}\"") + with self.assertRaises(AssertionError) as cm: + ValueAtom(True, "bool") + msg = str(cm.exception) + self.assertTrue(msg.startswith("Grounded bool True can't have a custom type bool"), f"Unexpected message \"{msg}\"") if __name__ == "__main__": unittest.main()