From 3bc743a95378cc604446afff3c215dd19a37a54a Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 20 Nov 2024 17:59:55 +0300 Subject: [PATCH 01/10] Display Rust float Number using dot notation --- lib/src/metta/runner/arithmetics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/metta/runner/arithmetics.rs b/lib/src/metta/runner/arithmetics.rs index fbc08afef..8bfaf1623 100644 --- a/lib/src/metta/runner/arithmetics.rs +++ b/lib/src/metta/runner/arithmetics.rs @@ -111,7 +111,7 @@ 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), + Self::Float(n) => write!(f, "{:?}", n), } } } From 8fe38b0d1b21ec8896e7c8684e1ec01b24cd956e Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 20 Nov 2024 18:16:56 +0300 Subject: [PATCH 02/10] Replace Python int and float by MeTTa primitive values This breaks c1_grounded_basic.metta because now < is not a part of Python stdlib. Rust < return Rust true value and it cannot be checked for equality with Python True value. --- c/src/atom.rs | 21 +++++++++++++ python/hyperon/atoms.py | 47 ++++++++++++++++++++++++++++-- python/hyperon/runner.py | 2 ++ python/hyperon/stdlib.py | 26 ----------------- python/hyperonpy.cpp | 35 ++++++++++------------ python/tests/test_atom.py | 8 ++--- python/tests/test_examples.py | 4 +-- python/tests/test_grounded_type.py | 25 ++++++++++++---- 8 files changed, 107 insertions(+), 61 deletions(-) diff --git a/c/src/atom.rs b/c/src/atom.rs index d9b43e892..bc2a6a185 100644 --- a/c/src/atom.rs +++ b/c/src/atom.rs @@ -14,6 +14,7 @@ use std::collections::HashSet; use std::sync::atomic::{AtomicPtr, Ordering}; use hyperon::matcher::{Bindings, BindingsSet}; +use hyperon::metta::runner::arithmetics::Number; // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // Atom Interface @@ -269,6 +270,26 @@ pub extern "C" fn atom_gnd(gnd: *mut gnd_t) -> atom_t { Atom::gnd(CGrounded(AtomicPtr::new(gnd))).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 diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 2acedad0d..bf54eb970 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -191,8 +191,31 @@ 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, int) and not isinstance(value, bool): + # FIXME: add assert on type like for space + catom = hp.atom_int(value) + elif isinstance(value, float): + # FIXME: add assert on type like for space + 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): """ @@ -531,9 +554,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) + 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..e2365c50c 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -26,38 +26,15 @@ 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 @@ -116,9 +93,6 @@ 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'), diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index 2eece4d12..d4ab16309 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -670,22 +670,19 @@ 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_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"); @@ -1117,12 +1114,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..dee59714f 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() @@ -141,7 +141,7 @@ 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() 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 9c0beb012..b914726ce 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,10 +39,9 @@ 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): @@ -54,7 +54,13 @@ def test_meta_types(self): 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 @@ -139,7 +145,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)) @@ -148,8 +153,16 @@ 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)) if __name__ == "__main__": unittest.main() From e8a1ede299ce90667c6f04185330171746c61716 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 22 Nov 2024 13:54:39 +0300 Subject: [PATCH 03/10] Introduce IncorrectArgument to treat argument errors as no match --- c/src/atom.rs | 12 ++++++++++ lib/src/atom/mod.rs | 5 +++++ lib/src/metta/interpreter.rs | 34 ++++++++++++++++++++++++++++- lib/src/metta/runner/arithmetics.rs | 2 +- python/hyperon/atoms.py | 9 ++++++++ python/hyperonpy.cpp | 3 +++ python/tests/test_atom.py | 11 ++++++++++ 7 files changed, 74 insertions(+), 2 deletions(-) diff --git a/c/src/atom.rs b/c/src/atom.rs index bc2a6a185..fba32f43e 100644 --- a/c/src/atom.rs +++ b/c/src/atom.rs @@ -778,6 +778,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/lib/src/atom/mod.rs b/lib/src/atom/mod.rs index 34aa3aff5..aecb05e51 100644 --- a/lib/src/atom/mod.rs +++ b/lib/src/atom/mod.rs @@ -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 cccc483bb..9df20fe48 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/arithmetics.rs b/lib/src/metta/runner/arithmetics.rs index 8bfaf1623..979d657a9 100644 --- a/lib/src/metta/runner/arithmetics.rs +++ b/lib/src/metta/runner/arithmetics.rs @@ -245,7 +245,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)?; diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index bf54eb970..1462b54bc 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -332,6 +332,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.""" diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index d4ab16309..ca735448a 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()); diff --git a/python/tests/test_atom.py b/python/tests/test_atom.py index dee59714f..6a360c975 100644 --- a/python/tests/test_atom.py +++ b/python/tests/test_atom.py @@ -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) @@ -147,6 +154,10 @@ 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 From 03e1f695f2ff07d9833bb68279df6dd2b4b4b4ad Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 22 Nov 2024 14:00:25 +0300 Subject: [PATCH 04/10] Replace bool in Python stdlib by the Rust primitive type --- c/src/atom.rs | 11 +++++++++++ python/hyperon/atoms.py | 7 +++++-- python/hyperon/stdlib.py | 15 --------------- python/hyperonpy.cpp | 3 +++ 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/c/src/atom.rs b/c/src/atom.rs index fba32f43e..461daa3ec 100644 --- a/c/src/atom.rs +++ b/c/src/atom.rs @@ -14,6 +14,7 @@ use std::collections::HashSet; use std::sync::atomic::{AtomicPtr, Ordering}; use hyperon::matcher::{Bindings, BindingsSet}; +use hyperon::metta::runner::arithmetics::Bool; use hyperon::metta::runner::arithmetics::Number; // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -270,6 +271,16 @@ 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 diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 1462b54bc..a9bc2f21e 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -206,7 +206,10 @@ def _priv_atom_gnd(obj, type): catom = hp.atom_space(obj.cspace) elif isinstance(obj, ValueObject): value = obj.value - if isinstance(value, int) and not isinstance(value, bool): + if isinstance(value, bool): + # FIXME: add assert on type like for space + catom = hp.atom_bool(value) + elif isinstance(value, int): # FIXME: add assert on type like for space catom = hp.atom_int(value) elif isinstance(value, float): @@ -579,7 +582,7 @@ def PrimitiveAtom(value, type_name=None, atom_id=None): converts Python primitives into MeTTa ones. This function is added to override this rule if needed. """ - PRIMITIVE_TYPES = (int, float) + 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)) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index e2365c50c..bd5124f71 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -26,20 +26,6 @@ def __eq__(self, other): return self.char == other.char return False -@register_atoms -def bool_ops(): - equalAtom = OperationAtom('==', lambda a, b: [ValueAtom(a == b, 'Bool')], - ['$t', '$t', 'Bool'], unwrap=False) - 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"or": orAtom, - r"and": andAtom, - r"not": notAtom - } - class RegexMatchableObject(MatchableObject): ''' To match atoms with regular expressions''' @@ -95,7 +81,6 @@ def type_tokens(): return { 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 ca735448a..ac77eaa92 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -680,6 +680,9 @@ PYBIND11_MODULE(hyperonpy, m) { 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"); From 5d5586d704d6e591dc707fed7e9634f251902446 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Mon, 25 Nov 2024 12:28:12 +0300 Subject: [PATCH 05/10] Check that primitive types doesn't have an incorrect type 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. --- c/src/metta.rs | 14 ++++++++++++++ python/hyperon/atoms.py | 8 +++++--- python/hyperonpy.cpp | 4 +++- python/tests/test_atom.py | 4 ++-- python/tests/test_grounded_type.py | 19 ++++++++++++++++++- 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/c/src/metta.rs b/c/src/metta.rs index 0c8595b8a..a8c791840 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::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 diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index a9bc2f21e..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: @@ -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}" diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index ac77eaa92..6a0c40326 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -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"); diff --git a/python/tests/test_atom.py b/python/tests/test_atom.py index 6a360c975..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.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(), diff --git a/python/tests/test_grounded_type.py b/python/tests/test_grounded_type.py index b914726ce..aba1b4ff5 100644 --- a/python/tests/test_grounded_type.py +++ b/python/tests/test_grounded_type.py @@ -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] @@ -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() From 211842d5eabc92c4478febb5417f6b7e7f2cc5e6 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 3 Dec 2024 21:20:52 +0300 Subject: [PATCH 06/10] Return IncorrectArgument from boolean operations --- lib/src/metta/runner/arithmetics.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/metta/runner/arithmetics.rs b/lib/src/metta/runner/arithmetics.rs index 7bfb4f985..050fbbdef 100644 --- a/lib/src/metta/runner/arithmetics.rs +++ b/lib/src/metta/runner/arithmetics.rs @@ -295,7 +295,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,7 +333,7 @@ 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))]) From c2d2504e46a09fc4b2994fc103de736fcd6d0fda Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 10 Dec 2024 08:25:13 +0300 Subject: [PATCH 07/10] Split arithmetics module on stdlib/arithmetics, bool and number --- c/src/atom.rs | 4 +- c/src/metta.rs | 4 +- lib/examples/sorted_list.rs | 2 +- lib/src/atom/mod.rs | 2 +- lib/src/metta/runner/bool.rs | 90 ++++++ lib/src/metta/runner/mod.rs | 3 +- lib/src/metta/runner/number.rs | 165 +++++++++++ .../metta/runner/{ => stdlib}/arithmetics.rs | 280 +++--------------- lib/src/metta/runner/stdlib/atom.rs | 10 +- lib/src/metta/runner/stdlib/core.rs | 6 +- lib/src/metta/runner/stdlib/math.rs | 6 +- lib/src/metta/runner/stdlib/mod.rs | 43 +-- lib/src/metta/runner/stdlib/random.rs | 6 +- lib/src/metta/text.rs | 2 +- lib/tests/types.rs | 3 +- 15 files changed, 330 insertions(+), 296 deletions(-) create mode 100644 lib/src/metta/runner/bool.rs create mode 100644 lib/src/metta/runner/number.rs rename lib/src/metta/runner/{ => stdlib}/arithmetics.rs (54%) diff --git a/c/src/atom.rs b/c/src/atom.rs index 461daa3ec..aa038ad02 100644 --- a/c/src/atom.rs +++ b/c/src/atom.rs @@ -14,8 +14,8 @@ use std::collections::HashSet; use std::sync::atomic::{AtomicPtr, Ordering}; use hyperon::matcher::{Bindings, BindingsSet}; -use hyperon::metta::runner::arithmetics::Bool; -use hyperon::metta::runner::arithmetics::Number; +use hyperon::metta::runner::bool::Bool; +use hyperon::metta::runner::number::Number; // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // Atom Interface diff --git a/c/src/metta.rs b/c/src/metta.rs index a8c791840..ff189fe60 100644 --- a/c/src/metta.rs +++ b/c/src/metta.rs @@ -624,14 +624,14 @@ pub extern "C" fn atom_error_message(atom: *const atom_ref_t, buf: *mut c_char, /// @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() } +#[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::arithmetics::ATOM_TYPE_BOOL.into() } +#[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. 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 aecb05e51..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); 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/mod.rs b/lib/src/metta/runner/mod.rs index 9e65b97ab..0889c9b4b 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -94,7 +94,8 @@ use stdlib::CoreLibLoader; mod builtin_mods; use builtin_mods::*; -pub mod arithmetics; +pub mod bool; +pub mod number; pub mod string; 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 54% rename from lib/src/metta/runner/arithmetics.rs rename to lib/src/metta/runner/stdlib/arithmetics.rs index 050fbbdef..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)] @@ -340,28 +125,49 @@ impl CustomExecute for NotOp { } } +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..a26a6f352 100644 --- a/lib/src/metta/runner/stdlib/atom.rs +++ b/lib/src/metta/runner/stdlib/atom.rs @@ -6,8 +6,8 @@ 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; @@ -446,6 +446,7 @@ mod tests { 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 +571,6 @@ mod tests { } - - - #[test] fn unique_op() { let unique_op = UniqueAtomOp{}; @@ -699,4 +697,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..d39f8bb5c 100644 --- a/lib/src/metta/runner/stdlib/core.rs +++ b/lib/src/metta/runner/stdlib/core.rs @@ -6,12 +6,12 @@ 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 +532,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..7ac44101e 100644 --- a/lib/src/metta/runner/stdlib/math.rs +++ b/lib/src/metta/runner/stdlib/math.rs @@ -5,7 +5,9 @@ use crate::metta::*; use std::convert::TryInto; use crate::metta::text::Tokenizer; -use crate::metta::runner::{arithmetics::*,stdlib::grounded_op, stdlib::regex}; +use super::{grounded_op, regex}; +use crate::metta::runner::number::*; +use crate::metta::runner::bool::*; #[derive(Clone, Debug)] pub struct PowMathOp {} @@ -740,4 +742,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..89c074b83 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::*; @@ -18,7 +19,7 @@ use crate::common::shared::Shared; use crate::metta::runner::{Metta, RunContext, ModuleLoader}; use regex::Regex; -use super::{arithmetics::*, string::*}; +use super::string::*; macro_rules! grounded_op { ($name:ident, $disp:literal) => { @@ -113,44 +114,10 @@ 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() }); + arithmetics::register_rust_stdlib_tokens(tref); core::register_rust_stdlib_tokens(tref); target.move_front(&mut rust_tokens); @@ -193,6 +160,8 @@ mod tests { use crate::metta::runner::string::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 +773,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/random.rs b/lib/src/metta/runner/stdlib/random.rs index b78337d42..1556ddc26 100644 --- a/lib/src/metta/runner/stdlib/random.rs +++ b/lib/src/metta/runner/stdlib/random.rs @@ -2,8 +2,10 @@ 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 +137,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/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() { From 3c0ad343e2ba768dad9ddef698c20cb517434f85 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 10 Dec 2024 08:33:59 +0300 Subject: [PATCH 08/10] Remove pkg_mgmt where it is not needed --- lib/src/metta/runner/stdlib/atom.rs | 1 - lib/src/metta/runner/stdlib/core.rs | 1 - lib/src/metta/runner/stdlib/math.rs | 6 ++---- lib/src/metta/runner/stdlib/module.rs | 8 +++----- lib/src/metta/runner/stdlib/package.rs | 4 ++-- lib/src/metta/runner/stdlib/random.rs | 2 -- lib/src/metta/runner/stdlib/space.rs | 3 +-- lib/src/metta/runner/stdlib/string.rs | 6 ++---- 8 files changed, 10 insertions(+), 21 deletions(-) diff --git a/lib/src/metta/runner/stdlib/atom.rs b/lib/src/metta/runner/stdlib/atom.rs index a26a6f352..402a930c0 100644 --- a/lib/src/metta/runner/stdlib/atom.rs +++ b/lib/src/metta/runner/stdlib/atom.rs @@ -5,7 +5,6 @@ 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 super::{grounded_op, regex}; use crate::metta::runner::number::*; diff --git a/lib/src/metta/runner/stdlib/core.rs b/lib/src/metta/runner/stdlib/core.rs index d39f8bb5c..508736ef8 100644 --- a/lib/src/metta/runner/stdlib/core.rs +++ b/lib/src/metta/runner/stdlib/core.rs @@ -4,7 +4,6 @@ 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::*; diff --git a/lib/src/metta/runner/stdlib/math.rs b/lib/src/metta/runner/stdlib/math.rs index 7ac44101e..a07d822e0 100644 --- a/lib/src/metta/runner/stdlib/math.rs +++ b/lib/src/metta/runner/stdlib/math.rs @@ -1,14 +1,12 @@ use crate::*; use crate::metta::*; -#[cfg(feature = "pkg_mgmt")] - -use std::convert::TryInto; - use crate::metta::text::Tokenizer; use super::{grounded_op, regex}; use crate::metta::runner::number::*; use crate::metta::runner::bool::*; +use std::convert::TryInto; + #[derive(Clone, Debug)] pub struct PowMathOp {} grounded_op!(PowMathOp, "pow-math"); diff --git a/lib/src/metta/runner/stdlib/module.rs b/lib/src/metta/runner/stdlib/module.rs index dfc64fb6a..787d06acd 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 regex::Regex; - use crate::metta::runner::string::*; use crate::metta::runner::stdlib::{grounded_op, regex, unit_result}; +use regex::Regex; + #[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..e958f00b7 100644 --- a/lib/src/metta/runner/stdlib/package.rs +++ b/lib/src/metta/runner/stdlib/package.rs @@ -1,4 +1,4 @@ -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; @@ -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 1556ddc26..27c70488e 100644 --- a/lib/src/metta/runner/stdlib/random.rs +++ b/lib/src/metta/runner/stdlib/random.rs @@ -1,7 +1,5 @@ use crate::*; use crate::metta::*; -#[cfg(feature = "pkg_mgmt")] - use super::{grounded_op, regex}; use crate::metta::text::Tokenizer; use crate::metta::runner::number::*; 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..71f9af1b7 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 super::{grounded_op, atom_to_string, unit_result, regex}; use std::convert::TryInto; @@ -77,4 +75,4 @@ mod tests { fn println_op() { assert_eq!(PrintlnOp{}.execute(&mut vec![sym!("A")]), unit_result()); } -} \ No newline at end of file +} From b09e0285a57552f92d784329fe0646e1e3792b7d Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 10 Dec 2024 08:40:12 +0300 Subject: [PATCH 09/10] Rename string to str to align it with number and bool modules --- lib/src/metta/runner/environment.rs | 6 +++--- lib/src/metta/runner/mod.rs | 2 +- lib/src/metta/runner/stdlib/atom.rs | 2 +- lib/src/metta/runner/stdlib/mod.rs | 6 +++--- lib/src/metta/runner/stdlib/module.rs | 4 ++-- lib/src/metta/runner/stdlib/package.rs | 10 +++++----- lib/src/metta/runner/stdlib/string.rs | 2 +- lib/src/metta/runner/{string.rs => str.rs} | 0 8 files changed, 16 insertions(+), 16 deletions(-) rename lib/src/metta/runner/{string.rs => str.rs} (100%) 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 0889c9b4b..d0dd2b617 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -96,7 +96,7 @@ use builtin_mods::*; pub mod bool; pub mod number; -pub mod string; +pub mod str; const EXEC_SYMBOL : Atom = sym!("!"); diff --git a/lib/src/metta/runner/stdlib/atom.rs b/lib/src/metta/runner/stdlib/atom.rs index 402a930c0..6c03ce6ee 100644 --- a/lib/src/metta/runner/stdlib/atom.rs +++ b/lib/src/metta/runner/stdlib/atom.rs @@ -441,7 +441,7 @@ 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; diff --git a/lib/src/metta/runner/stdlib/mod.rs b/lib/src/metta/runner/stdlib/mod.rs index 89c074b83..0e9e3a7b5 100644 --- a/lib/src/metta/runner/stdlib/mod.rs +++ b/lib/src/metta/runner/stdlib/mod.rs @@ -17,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::string::*; +use regex::Regex; macro_rules! grounded_op { ($name:ident, $disp:literal) => { @@ -157,7 +157,7 @@ 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; diff --git a/lib/src/metta/runner/stdlib/module.rs b/lib/src/metta/runner/stdlib/module.rs index 787d06acd..5b2bed8a2 100644 --- a/lib/src/metta/runner/stdlib/module.rs +++ b/lib/src/metta/runner/stdlib/module.rs @@ -4,8 +4,8 @@ use crate::metta::*; use crate::metta::text::Tokenizer; use crate::common::shared::Shared; use crate::metta::runner::{Metta, RunContext, ResourceKey}; -use crate::metta::runner::string::*; -use crate::metta::runner::stdlib::{grounded_op, regex, unit_result}; +use crate::metta::runner::str::*; +use super::{grounded_op, regex, unit_result}; use regex::Regex; diff --git a/lib/src/metta/runner/stdlib/package.rs b/lib/src/metta/runner/stdlib/package.rs index e958f00b7..b70a5f5e8 100644 --- a/lib/src/metta/runner/stdlib/package.rs +++ b/lib/src/metta/runner/stdlib/package.rs @@ -2,11 +2,11 @@ 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)] diff --git a/lib/src/metta/runner/stdlib/string.rs b/lib/src/metta/runner/stdlib/string.rs index 71f9af1b7..c655678a1 100644 --- a/lib/src/metta/runner/stdlib/string.rs +++ b/lib/src/metta/runner/stdlib/string.rs @@ -1,7 +1,7 @@ use crate::*; use crate::metta::*; use crate::metta::text::Tokenizer; -use crate::metta::runner::string::*; +use crate::metta::runner::str::*; use super::{grounded_op, atom_to_string, unit_result, regex}; use std::convert::TryInto; 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 From 554210db11875c2b275e594107ce1352da3c20b4 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 10 Dec 2024 08:44:55 +0300 Subject: [PATCH 10/10] Move string token into string module --- lib/src/metta/runner/stdlib/mod.rs | 6 ++---- lib/src/metta/runner/stdlib/string.rs | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/metta/runner/stdlib/mod.rs b/lib/src/metta/runner/stdlib/mod.rs index 0e9e3a7b5..8a38a631e 100644 --- a/lib/src/metta/runner/stdlib/mod.rs +++ b/lib/src/metta/runner/stdlib/mod.rs @@ -114,11 +114,9 @@ pub fn register_rust_stdlib_tokens(target: &mut Tokenizer) { let mut rust_tokens = Tokenizer::new(); let tref = &mut rust_tokens; - tref.register_token(regex(r#"(?s)^".*"$"#), - |token| { let mut s = String::from(token); s.remove(0); s.pop(); Atom::gnd(Str::from_string(s)) }); - - arithmetics::register_rust_stdlib_tokens(tref); core::register_rust_stdlib_tokens(tref); + arithmetics::register_rust_stdlib_tokens(tref); + string::register_rust_stdlib_tokens(tref); target.move_front(&mut rust_tokens); } diff --git a/lib/src/metta/runner/stdlib/string.rs b/lib/src/metta/runner/stdlib/string.rs index c655678a1..5185caa02 100644 --- a/lib/src/metta/runner/stdlib/string.rs +++ b/lib/src/metta/runner/stdlib/string.rs @@ -67,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::*;