diff --git a/c/src/atom.rs b/c/src/atom.rs index 2a2456c72..7958c5c40 100644 --- a/c/src/atom.rs +++ b/c/src/atom.rs @@ -435,6 +435,23 @@ pub unsafe extern "C" fn atom_get_object(atom: *const atom_ref_t) -> *mut gnd_t } } +/// @brief Check if the atom refers to CGrounded object +/// @ingroup atom_group +/// @param[in] atom A pointer to an `atom_t` or an `atom_ref_t` to access +/// @return True if the atom_get_object can be used on this atom without panic +/// +#[no_mangle] +pub unsafe extern "C" fn atom_is_cgrounded(atom: *const atom_ref_t) -> bool { + if let Atom::Grounded(ref g) = (&*atom).borrow() { + match (*g).as_any_ref().downcast_ref::() { + Some(_g) => true, + None => false, + } + } else { + false + } +} + /// @brief Access the space wrapped inside a Grounded atom /// @ingroup atom_group /// @see atom_gnd_for_space diff --git a/c/src/metta.rs b/c/src/metta.rs index c03ec1c83..29f0ed9bf 100644 --- a/c/src/metta.rs +++ b/c/src/metta.rs @@ -1395,8 +1395,13 @@ pub extern "C" fn env_builder_add_include_path(builder: *mut env_builder_t, path *builder_arg_ref = builder.into(); } +/// @brief Access the value of a grounded i64 atom +/// @ingroup metta_language_group +/// @param[in] n A pointer to an `atom_t` or an `atom_ref_t` to access +/// @param[out] res A pointer to the variable into which to write the result +/// @return True if the atom was a grounded i64 atom, and the result was successfully written #[no_mangle] -pub extern "C" fn grounded_number_to_longlong(n: *const atom_t, res: *mut c_longlong) -> bool { +pub extern "C" fn grounded_number_get_longlong(n: *const atom_ref_t, res: *mut c_longlong) -> bool { let atom = unsafe { (*n).borrow() }; match atom { Atom::Grounded(gnd) => { @@ -1412,8 +1417,36 @@ pub extern "C" fn grounded_number_to_longlong(n: *const atom_t, res: *mut c_long } } +/// @brief Access the value of a grounded bool atom +/// @ingroup metta_language_group +/// @param[in] n A pointer to an `atom_t` or an `atom_ref_t` to access +/// @param[out] res A pointer to the variable into which to write the result +/// @return True if the atom was a grounded bool atom, and the result was successfully written +#[no_mangle] +pub extern "C" fn grounded_bool_get_bool(n: *const atom_ref_t, res: *mut bool) -> bool { + // NOTE: there is no c_bool, so we have to choose particular int type + let atom = unsafe { (*n).borrow() }; + match atom { + Atom::Grounded(gnd) => { + match gnd.as_any_ref().downcast_ref::() { + Some(Bool(b)) => { + unsafe { *res = *b }; + true + } + _ => false, + } + }, + _ => false, + } +} + +/// @brief Access the value of a grounded f64 atom +/// @ingroup metta_language_group +/// @param[in] n A pointer to an `atom_t` or an `atom_ref_t` to access +/// @param[out] res A pointer to the variable into which to write the result +/// @return True if the atom was a grounded f64 atom, and the result was successfully written #[no_mangle] -pub extern "C" fn grounded_number_to_double(n: *const atom_t, res: *mut c_double) -> bool { +pub extern "C" fn grounded_number_get_double(n: *const atom_ref_t, res: *mut c_double) -> bool { let atom = unsafe { (*n).borrow() }; match atom { Atom::Grounded(gnd) => { @@ -1430,11 +1463,11 @@ pub extern "C" fn grounded_number_to_double(n: *const atom_t, res: *mut c_double } #[no_mangle] -pub extern "C" fn longlong_to_grounded_number(n: c_longlong) -> atom_t { +pub extern "C" fn longlong_into_grounded_number(n: c_longlong) -> atom_t { Atom::gnd(Number::Integer(n)).into() } #[no_mangle] -pub extern "C" fn double_to_grounded_number(d: c_double) -> atom_t { +pub extern "C" fn double_into_grounded_number(d: c_double) -> atom_t { Atom::gnd(Number::Float(d)).into() } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index e4bc2a628..01a233810 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -12,6 +12,7 @@ env_logger = "0.8.4" directories = "5.0.1" # For Environment to find platform-specific config location smallvec = "1.10.0" im = "15.1.0" +rand = "0.8.5" bitset = "0.1.2" [lib] diff --git a/lib/src/metta/runner/arithmetics.rs b/lib/src/metta/runner/arithmetics.rs index c46eaf01e..f3e30b541 100644 --- a/lib/src/metta/runner/arithmetics.rs +++ b/lib/src/metta/runner/arithmetics.rs @@ -193,6 +193,34 @@ macro_rules! def_binary_bool_op { def_binary_bool_op!(AndOp, and, &&); def_binary_bool_op!(OrOp, or, ||); +// NOTE: xor and flip are absent in Python intentionally for conversion testing +def_binary_bool_op!(XorOp, xor, ^); + +use rand; +#[derive(Clone, PartialEq, Debug)] +pub struct FlipOp{} + +impl Display for FlipOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "flip") + } +} + +impl Grounded for FlipOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_BOOL]) + } + + fn execute(&self, _args: &[Atom]) -> Result, ExecError> { + Ok(vec![Atom::gnd(Bool(rand::random()))]) + } + + fn match_(&self, other: &Atom) -> MatchResultIter { + match_by_equality(self, other) + } +} + + #[derive(Clone, PartialEq, Debug)] pub struct NotOp{} diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index cf292ccb5..0d36593b1 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -1253,6 +1253,11 @@ pub fn register_rust_tokens(metta: &Metta) { 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 and flip are absent in Python intentionally for conversion testing + let xor_op = Atom::gnd(XorOp{}); + tref.register_token(regex(r"xor"), move |_| { xor_op.clone() }); + let flip_op = Atom::gnd(FlipOp{}); + tref.register_token(regex(r"flip"), move |_| { flip_op.clone() }); metta.tokenizer().borrow_mut().move_front(&mut rust_tokens); } diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 6fbd3ca7f..ed592284d 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -138,12 +138,20 @@ def __init__(self, catom): super().__init__(catom) def get_object(self): - """Returns the GroundedAtom object, or the Space wrapped inside a GroundedAtom""" - from .base import SpaceRef - if self.get_grounded_type() == AtomType.GROUNDED_SPACE: - return SpaceRef._from_cspace(hp.atom_get_space(self.catom)) - else: + """Returns the GroundedAtom object, or the Space wrapped inside a GroundedAtom, + or convert supported Rust grounded objects into corresponding ValueObjects + """ + if hp.atom_is_cgrounded(self.catom): return hp.atom_get_object(self.catom) + typ = self.get_grounded_type() + if typ == AtomType.GROUNDED_SPACE: + from .base import SpaceRef + return SpaceRef._from_cspace(hp.atom_get_space(self.catom)) + # NOTE: Rust and Python may have the same grounded type names, but we already + # distiguished them above + elif typ == S('Bool'): + return ValueObject(hp.gnd_get_bool(self.catom)) + raise TypeError("Cannot get_object of unsupported non-C {self.catom}") def get_grounded_type(self): """Retrieve the grounded type of the GroundedAtom.""" diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index ef7dd86b5..94dc0e0e6 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -541,6 +541,9 @@ PYBIND11_MODULE(hyperonpy, m) { m.def("atom_get_object", [](CAtom& atom) { return static_cast(atom_get_object(atom.ptr()))->pyobj; }, "Get object of the grounded atom"); + m.def("atom_is_cgrounded", [](CAtom& atom) { + return py::bool_(atom_is_cgrounded(atom.ptr())); + }, "Check if atom is CGrounded"); m.def("atom_get_grounded_type", [](CAtom& atom) { return CAtom(atom_get_grounded_type(atom.ptr())); }, "Get object of the grounded atom"); @@ -860,26 +863,33 @@ PYBIND11_MODULE(hyperonpy, m) { m.def("env_builder_set_is_test", [](EnvBuilder& builder, bool is_test) { env_builder_set_is_test(builder.ptr(), is_test); }, "Disables the config dir in the environment"); m.def("env_builder_add_include_path", [](EnvBuilder& builder, std::string path) { env_builder_add_include_path(builder.ptr(), path.c_str()); }, "Adds an include path to the environment"); - m.def("gnd_to_int", [](CAtom atom) -> py::object { + m.def("gnd_get_int", [](CAtom atom) -> py::object { long long n; - if (grounded_number_to_longlong(atom.ptr(), &n)) { + if (grounded_number_get_longlong(atom.ptr(), &n)) { return py::int_(n); } else return py::none(); }, "Convert MeTTa stdlib number to Python int"); - m.def("gnd_to_float", [](CAtom atom) -> py::object { + m.def("gnd_get_bool", [](CAtom atom) -> py::object { + bool b; + if (grounded_bool_get_bool(atom.ptr(), &b)) { + return py::bool_(b); + } else + return py::none(); + }, "Convert MeTTa-Rust bool to Python bool"); + m.def("gnd_get_float", [](CAtom atom) -> py::object { double d; - if (grounded_number_to_double(atom.ptr(), &d)) + if (grounded_number_get_double(atom.ptr(), &d)) return py::float_(d); else return py::none(); }, "Convert MeTTa stdlib number to Python float"); - m.def("number_to_gnd", [](py::object n) { + m.def("number_into_gnd", [](py::object n) { if (py::isinstance(n)) { - return CAtom(longlong_to_grounded_number(n.cast())); + return CAtom(longlong_into_grounded_number(n.cast())); } if (py::isinstance(n)) { - return CAtom(double_to_grounded_number(n.cast())); + return CAtom(double_into_grounded_number(n.cast())); } throw std::runtime_error("int of float number is expected as an argument"); }, "Convert Python number to MeTTa stdlib number"); diff --git a/python/sandbox/test_gnd_conv.metta b/python/sandbox/test_gnd_conv.metta new file mode 100644 index 000000000..262f704ff --- /dev/null +++ b/python/sandbox/test_gnd_conv.metta @@ -0,0 +1,11 @@ +; should work as passing Rust Bool to Rust function +!(xor (flip) (flip)) + +; should work via automatic Rust->Python conversion +!(and (flip) (flip)) + +; should work as well +!(and (flip) True) + +; Should not work atm because of no backward conversion of Python Bool to Rust +!(xor True (flip)) diff --git a/python/tests/test_grounded_type.py b/python/tests/test_grounded_type.py index 1a9ad864a..abe155631 100644 --- a/python/tests/test_grounded_type.py +++ b/python/tests/test_grounded_type.py @@ -142,17 +142,16 @@ def test_undefined_operation_type(self): metta.parse_single("untyped").get_grounded_type()) def test_number_conversion(self): - num = Atom._from_catom(hp.number_to_gnd(123)) + num = Atom._from_catom(hp.number_into_gnd(123)) self.assertEqual(type(num), GroundedAtom) - self.assertEqual(hp.gnd_to_int(num.catom), 123) + self.assertEqual(hp.gnd_get_int(num.catom), 123) - num = Atom._from_catom(hp.number_to_gnd(123.456)) + num = Atom._from_catom(hp.number_into_gnd(123.456)) self.assertEqual(type(num), GroundedAtom) - self.assertTrue(abs(hp.gnd_to_float(num.catom) - 123.456) < 0.0001) + self.assertTrue(abs(hp.gnd_get_float(num.catom) - 123.456) < 0.0001) - sym = S("sym") - self.assertEqual(hp.gnd_to_int(S("sym").catom), None) - self.assertEqual(hp.gnd_to_float(sym.catom), None) + self.assertEqual(hp.gnd_get_int(S("sym").catom), None) + self.assertEqual(hp.gnd_get_float(S("sym").catom), None) if __name__ == "__main__": unittest.main()