Skip to content

Commit

Permalink
Check that primitive types doesn't have an incorrect type
Browse files Browse the repository at this point in the history
Add Number and Bool atom types to the C API. Add asserts to check that
caller either use correct type or no type at all. Add unit test. Fix old
unit tests.
  • Loading branch information
vsbogd committed Nov 25, 2024
1 parent 03e1f69 commit 5d5586d
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 7 deletions.
14 changes: 14 additions & 0 deletions c/src/metta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::arithmetics::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::arithmetics::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
Expand Down
8 changes: 5 additions & 3 deletions python/hyperon/atoms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -207,13 +209,13 @@ def _priv_atom_gnd(obj, type):
elif isinstance(obj, ValueObject):
value = obj.value
if isinstance(value, bool):
# FIXME: add assert on type like for space
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):
# FIXME: add assert on type like for space
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):
# FIXME: add assert on type like for space
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}"
Expand Down
4 changes: 3 additions & 1 deletion python/hyperonpy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -942,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");
Expand Down
4 changes: 2 additions & 2 deletions python/tests/test_atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.assertTrue(str(context.exception)
.startswith("Method copy should be implemented by grounded object"))
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(),
Expand Down
19 changes: 18 additions & 1 deletion python/tests/test_grounded_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ 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]
Expand Down Expand Up @@ -163,6 +163,23 @@ def test_python_value_conversion(self):
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()

0 comments on commit 5d5586d

Please sign in to comment.