Skip to content

Commit

Permalink
Merge pull request #544 from trueagi-io/autoconv
Browse files Browse the repository at this point in the history
Rust2Metta gnd conversion
  • Loading branch information
luketpeterson authored Jan 26, 2024
2 parents 664e6ba + 08fe8d2 commit 169da1d
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 23 deletions.
17 changes: 17 additions & 0 deletions c/src/atom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<CGrounded>() {
Some(_g) => true,
None => false,
}
} else {
false
}
}

/// @brief Access the space wrapped inside a Grounded atom
/// @ingroup atom_group
/// @see atom_gnd_for_space
Expand Down
41 changes: 37 additions & 4 deletions c/src/metta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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::<Bool>() {
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) => {
Expand All @@ -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()
}
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
28 changes: 28 additions & 0 deletions lib/src/metta/runner/arithmetics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<Atom>, 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{}

Expand Down
5 changes: 5 additions & 0 deletions lib/src/metta/runner/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
18 changes: 13 additions & 5 deletions python/hyperon/atoms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
24 changes: 17 additions & 7 deletions python/hyperonpy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ PYBIND11_MODULE(hyperonpy, m) {
m.def("atom_get_object", [](CAtom& atom) {
return static_cast<GroundedObject const*>(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");
Expand Down Expand Up @@ -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<py::int_>(n)) {
return CAtom(longlong_to_grounded_number(n.cast<long long>()));
return CAtom(longlong_into_grounded_number(n.cast<long long>()));
}
if (py::isinstance<py::float_>(n)) {
return CAtom(double_to_grounded_number(n.cast<double>()));
return CAtom(double_into_grounded_number(n.cast<double>()));
}
throw std::runtime_error("int of float number is expected as an argument");
}, "Convert Python number to MeTTa stdlib number");
Expand Down
11 changes: 11 additions & 0 deletions python/sandbox/test_gnd_conv.metta
Original file line number Diff line number Diff line change
@@ -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))
13 changes: 6 additions & 7 deletions python/tests/test_grounded_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

0 comments on commit 169da1d

Please sign in to comment.