From 22ef3063dc0d16aff1d81806cae2b491a7971a80 Mon Sep 17 00:00:00 2001 From: Sergey Rodionov Date: Thu, 18 Apr 2024 15:08:34 +0300 Subject: [PATCH 01/39] add py-tuple py-list py-dict py-chain into stdlib --- python/hyperon/stdlib.py | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index a35142ab3..c7a0febb1 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -3,7 +3,7 @@ import os from .atoms import ExpressionAtom, E, GroundedAtom, OperationAtom, ValueAtom, NoReduceError, AtomType, MatchableObject, \ - G, S, Atoms + G, S, Atoms, ValueObject, OperationObject from .base import Tokenizer, SExprParser from .ext import register_atoms, register_tokens import hyperonpy as hp @@ -212,3 +212,52 @@ def load_ascii_atom(space, name): return { r"load-ascii": loadAtom } + +def groundedatom_to_python_object(a): + obj = a.get_object() + if isinstance(obj, ValueObject): + obj = obj.value + if isinstance(obj, OperationObject): + obj = obj.content + return obj + +# convert nested tuples to nested python tuples or lists +def _py_tuple_list(tuple_list, *atoms): + rez = [] + for a in atoms: + if isinstance(a, GroundedAtom): + rez.append(groundedatom_to_python_object(a)) + elif isinstance(a, ExpressionAtom): + rez.append(_py_tuple_list(tuple_list, *a.get_children())) + return tuple_list(rez) + +def py_tuple(*atoms): + return [ValueAtom(_py_tuple_list(tuple, *atoms))] + +def py_list(*atoms): + return [ValueAtom(_py_tuple_list(list, *atoms))] + +def tuple_to_keyvalue(a): + ac = a.get_children() + if len(ac) != 2: + raise Exception("Syntax error in tuple_to_keyvalue") + return groundedatom_to_python_object(ac[0]), groundedatom_to_python_object(ac[1]) + +# convert pair of tuples to python dictionary +def py_dict(*atoms): + return [ValueAtom(dict([tuple_to_keyvalue(a) for a in atoms]))] + +# chain python objects with | (syntactic sugar for langchain) +def py_chain(*atoms): + objects = [groundedatom_to_python_object(a) for a in atoms] + result = objects[0] + for obj in objects[1:]: + result = result | obj + return [ValueAtom(result)] + +@register_atoms() +def py_funs(): + return {"py-tuple": OperationAtom("py-tuple", py_tuple, unwrap = False), + "py-list": OperationAtom("py-list", py_list, unwrap = False), + "py-dict": OperationAtom("py-dict", py_dict, unwrap = False), + "py-chain": OperationAtom("py-chain", py_chain, unwrap = False)} From a99ec53997a5eaf0bc254701fa0a049063163ea2 Mon Sep 17 00:00:00 2001 From: Sergey Rodionov Date: Thu, 18 Apr 2024 15:11:38 +0300 Subject: [PATCH 02/39] update sandbox simple_import examples --- python/sandbox/simple_import/example_01.metta | 11 +- .../simple_import/example_02_numpy.metta | 24 ++- .../simple_import/example_03_langchain.metta | 23 ++- .../example_04_numpy_simple_import.metta | 18 +-- python/sandbox/simple_import/simple_import.py | 147 ------------------ 5 files changed, 31 insertions(+), 192 deletions(-) delete mode 100644 python/sandbox/simple_import/simple_import.py diff --git a/python/sandbox/simple_import/example_01.metta b/python/sandbox/simple_import/example_01.metta index f2b14c383..70ca152a5 100644 --- a/python/sandbox/simple_import/example_01.metta +++ b/python/sandbox/simple_import/example_01.metta @@ -1,11 +1,10 @@ -!(import! &self simple_import) - -!(import_from example_01 import simple_fun) -!(import_from example_01 import SimpleObject) +!(bind! simple_fun (py-atom example_01.simple_fun)) +!(bind! SimpleObject (py-atom example_01.SimpleObject)) !(bind! so (SimpleObject)) + ; it is important that obj will have type SimpleObject when passed to simple_fun! -!(simple_fun 1 2 "3" (kwarg1 2) (obj so) ) +!(simple_fun 1 2 "3" (Kwargs (kwarg1 2) (obj so)) ) -!(call_dot so method "arg1" "arg2" (arg3 3)) \ No newline at end of file +!( (py-dot so method) "arg1" "arg2" (Kwargs (arg3 3)) ) diff --git a/python/sandbox/simple_import/example_02_numpy.metta b/python/sandbox/simple_import/example_02_numpy.metta index edf992a8c..10947773c 100644 --- a/python/sandbox/simple_import/example_02_numpy.metta +++ b/python/sandbox/simple_import/example_02_numpy.metta @@ -1,18 +1,16 @@ -!(import! &self simple_import) +!(bind! np (py-atom numpy)) -!(import_as numpy as np) +!(bind! a1 ( (py-dot np array) (py-atom (py-tuple 1 2 3) ))) +!(bind! a2 ( (py-dot a1 __mul__) 3)) +!(bind! a3 ( (py-dot a1 __add__) a2)) -!(bind! a1 (call_dot np array (ptuple 1 2 3) )) -!(bind! a2 (call_dot a1 __mul__ 3)) -!(bind! a3 (call_dot a1 __add__ a2)) +!(a1) +!(a2) +!(a3) -!(__unwrap a1) -!(__unwrap a2) -!(__unwrap a3) +!(bind! m1 ((py-dot np array) (py-atom (py-list (1 2 3) (py-list 4 4 5) (py-tuple 6 7 8)) ))) +!(bind! linalg (py-atom numpy.linalg)) +!(bind! m1_inv ( (py-dot linalg inv) m1)) -!(bind! m1 (call_dot np array (ptuple (1 2 3) (4 4 5) (6 7 8)) )) -!(import_as numpy.linalg as linalg) -!(bind! m1_inv (call_dot linalg inv m1)) - -!(__unwrap (call_dot np matmul m1 m1_inv)) +!( (py-dot np matmul) m1 m1_inv) diff --git a/python/sandbox/simple_import/example_03_langchain.metta b/python/sandbox/simple_import/example_03_langchain.metta index 1fe219d2d..78a399ea0 100644 --- a/python/sandbox/simple_import/example_03_langchain.metta +++ b/python/sandbox/simple_import/example_03_langchain.metta @@ -1,21 +1,18 @@ -!(import! &self simple_import) +!(bind! ChatOpenAI (py-atom langchain_openai.ChatOpenAI)) +!(bind! ChatPromptTemplate (py-atom langchain_core.prompts.ChatPromptTemplate)) +!(bind! StrOutputParser (py-atom langchain_core.output_parsers.StrOutputParser)) -!(import_from langchain_openai import ChatOpenAI) -!(import_from langchain_core.prompts import ChatPromptTemplate) -!(import_from langchain_core.output_parsers import StrOutputParser) +!(bind! model (ChatOpenAI (Kwargs (temperature 0) (model "gpt-3.5-turbo")))) +!(bind! prompt ( (py-dot ChatPromptTemplate from_template) "tell me a joke about cat")) -!(bind! model (ChatOpenAI (temperature 0) (model "gpt-3.5-turbo"))) +!(bind! chain1 (py-chain prompt model (StrOutputParser) )) -!(bind! prompt (call_dot ChatPromptTemplate from_template "tell me a joke about cat")) +!( (py-dot chain1 invoke) (py-dict)) -!(bind! chain1 (chain prompt model (StrOutputParser) )) +!(bind! prompt2 ( (py-dot ChatPromptTemplate from_messages ) (py-tuple ("system" "You are very funny") ("user" "tell me joke about {foo}")))) -!(__unwrap(call_dot chain1 invoke (pdict))) +!(bind! chain2 (py-chain prompt2 model (StrOutputParser) )) -!(bind! prompt2 (call_dot ChatPromptTemplate from_messages (ptuple ("system" "You are very funny") ("user" "tell me joke about {foo}")))) - -!(bind! chain2 (chain prompt2 model (StrOutputParser) )) - -!(__unwrap(call_dot chain2 invoke (pdict (foo "dogs") ))) +!((py-dot chain2 invoke) (py-dict ("foo" "dogs"))) diff --git a/python/sandbox/simple_import/example_04_numpy_simple_import.metta b/python/sandbox/simple_import/example_04_numpy_simple_import.metta index 646ad158a..70f4c8bf3 100644 --- a/python/sandbox/simple_import/example_04_numpy_simple_import.metta +++ b/python/sandbox/simple_import/example_04_numpy_simple_import.metta @@ -1,14 +1,6 @@ -!(import! &self simple_import) +!(bind! linalg (py-atom numpy.linalg)) +!(bind! numpy (py-atom numpy)) -; with simple "import" it is rather common that we import something -; twice because of submodules in python -; So let's import twice to make sure that it does not cause any problems -!(import numpy) -!(import numpy) -!(import numpy.linalg) - - -!(bind! m1 (call_dot2 numpy random rand 3 3 )) -!(bind! m1_inv (call_dot2 numpy linalg inv m1)) - -!(__unwrap (call_dot numpy matmul m1 m1_inv)) +!(bind! m1 ((py-dot numpy random.rand) 3 3 )) +!(bind! m1_inv ( (py-dot linalg inv) m1)) +!( (py-dot numpy matmul) m1 m1_inv) diff --git a/python/sandbox/simple_import/simple_import.py b/python/sandbox/simple_import/simple_import.py deleted file mode 100644 index 7efba4baa..000000000 --- a/python/sandbox/simple_import/simple_import.py +++ /dev/null @@ -1,147 +0,0 @@ -from hyperon.atoms import OperationAtom, OperationObject, GroundedAtom, ValueAtom, ExpressionAtom, SymbolAtom, ValueObject -from hyperon.ext import register_atoms -import os -import sys - -def groundedatom_to_python_object(a): - obj = a.get_object() - if isinstance(obj, ValueObject): - obj = obj.value - if isinstance(obj, OperationObject): - obj = obj.content - #we need to make __unwrap if needed - if isinstance(obj, PythonCaller): - obj = obj.obj - return obj - -def tuple_to_keyvalue(a): - ac = a.get_children() - if len(ac) != 2: - raise Exception("Syntax error in tuple_to_keyvalue") - return str(ac[0]), groundedatom_to_python_object(ac[1]) - -def atoms_to_args(*atoms): - args = [] - kwargs = {} - for a in atoms: - if isinstance(a, GroundedAtom): - args.append(groundedatom_to_python_object(a)) - elif isinstance(a, ExpressionAtom): - k,v = tuple_to_keyvalue(a) - kwargs[k] = v - else: - raise Exception(f"Unexpected error: {a}, {type(a)}") - return args, kwargs - -class PythonCaller: - def __init__(self, obj): - self.obj = obj - - def __call__(self, *atoms): - args, kwargs = atoms_to_args(*atoms) - return [ValueAtom(PythonCaller(self.obj(*args, **kwargs)))] - -def _import_and_create_operationatom(metta, import_str, obj): - - # we only need these 3 lines to import from the current directory - # TODO fix it somehow differently - current_directory = os.getcwd() - if current_directory not in sys.path: - sys.path.append(current_directory) - - local_scope = {} - exec(import_str, {}, local_scope) - oatom = OperationAtom(obj, PythonCaller(local_scope[obj]), unwrap = False) - metta.register_atom(obj, oatom) - - -def import_from(metta, lib, i, obj): - if str(i) != "import": - raise Exception("bad import syntax") - lib = str(lib) - obj = str(obj) - _import_and_create_operationatom(metta, f"from {lib} import {obj}", obj) - return [] - -def import_as(metta, lib, a, obj): - if str(a) != "as": - raise Exception("bad import syntax") - lib = str(lib) - obj = str(obj) - _import_and_create_operationatom(metta, f"import {lib} as {obj}", obj) - return [] - -def import_simple(metta, lib): - lib = str(lib) - obj = lib.split(".")[0] - _import_and_create_operationatom(metta, f"import {lib}", obj) - return [] - -def call_dot(*atoms): - if len(atoms) < 2: - raise Exception("Syntax error") - obj = groundedatom_to_python_object(atoms[0]) - method = str(atoms[1]) - atoms = atoms[2:] - args, kwargs = atoms_to_args(*atoms) - rez = getattr(obj, method)(*args, **kwargs) - return [ValueAtom(PythonCaller(rez))] - -def call_dot2(*atoms): - if len(atoms) < 3: - raise Exception("Syntax error") - obj = groundedatom_to_python_object(atoms[0]) - method1 = str(atoms[1]) - method2 = str(atoms[2]) - atoms = atoms[3:] - args, kwargs = atoms_to_args(*atoms) - rez = getattr(getattr(obj, method1), method2)(*args, **kwargs) - return [ValueAtom(PythonCaller(rez))] - -def __unwrap(obj): - return obj.obj - -@register_atoms(pass_metta=True) -def my_atoms(metta): - return {'import_from': OperationAtom('import_from', lambda *args: import_from (metta, *args), unwrap = False), - 'import_as': OperationAtom('import_as', lambda *args: import_as (metta, *args), unwrap = False), - 'import': OperationAtom('import', lambda *args: import_simple(metta, *args), unwrap = False)} - -@register_atoms() -def my_atoms2(): - return {'__unwrap': OperationAtom('__unwrap', __unwrap), - "call_dot": OperationAtom("call_dot", call_dot, unwrap = False), - "call_dot2": OperationAtom("call_dot2", call_dot2, unwrap = False)} - -# The functions which are not required for import, but nice for examples - -# convert nested tuples to nested python tuples -def _ptuple(*atoms): - rez = [] - for a in atoms: - if isinstance(a, GroundedAtom): - rez.append(groundedatom_to_python_object(a)) - elif isinstance(a, ExpressionAtom): - rez.append(_ptuple(*a.get_children())) - return tuple(rez) - -def ptuple(*atoms): - return [ValueAtom(_ptuple(*atoms))] - -# convert pair of tuples to python dictionary -def pdict(*atoms): - return [ValueAtom(dict([tuple_to_keyvalue(a) for a in atoms]))] - -# chain python objects with | (syntactic sugar for langchain) -def chain(*atoms): - objects = [groundedatom_to_python_object(a) for a in atoms] - result = objects[0] - for obj in objects[1:]: - result = result | obj - return [ValueAtom(PythonCaller(result))] - -@register_atoms() -def my_atoms3(): - return {"ptuple": OperationAtom("ptuple", ptuple, unwrap = False), - "pdict": OperationAtom("pdict", pdict, unwrap = False), - "chain": OperationAtom("chain", chain, unwrap = False)} From 6d79ccffe9f5de6e733d214f46f54e76367973c3 Mon Sep 17 00:00:00 2001 From: sveta Date: Fri, 19 Apr 2024 12:30:51 +0300 Subject: [PATCH 03/39] changes for Kwargs --- python/hyperon/atoms.py | 9 ++++++++- python/hyperon/stdlib.py | 9 +-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 7d065860a..1c98d9c6c 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -383,7 +383,7 @@ def execute(self, *atoms, res_typ=AtomType.UNDEFINED): except: raise RuntimeError(f"Incorrect kwarg format {kwarg}") try: - kwargs[repr(kwarg[0])] = kwarg[1].get_object().content + kwargs[get_string_value(kwarg[0])] = kwarg[1].get_object().content except: raise NoReduceError() continue @@ -705,3 +705,10 @@ def iterator(self): for r in res: result.append(Bindings(r)) return iter(result) + +def get_string_value(value) -> str: + if not isinstance(value, str): + value = repr(value) + if len(value) > 2 and ("\"" == value[0]) and ("\"" == value[-1]): + return value[1:-1] + return value diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index a35142ab3..fc9f2a437 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -3,7 +3,7 @@ import os from .atoms import ExpressionAtom, E, GroundedAtom, OperationAtom, ValueAtom, NoReduceError, AtomType, MatchableObject, \ - G, S, Atoms + G, S, Atoms, get_string_value from .base import Tokenizer, SExprParser from .ext import register_atoms, register_tokens import hyperonpy as hp @@ -63,13 +63,6 @@ def bool_ops(): r"not": notAtom } -def get_string_value(value) -> str: - if not isinstance(value, str): - value = repr(value) - if len(value) > 2 and ("\"" == value[0]) and ("\"" == value[-1]): - return value[1:-1] - return value - class RegexMatchableObject(MatchableObject): ''' To match atoms with regular expressions''' From 564a23340e0ef1998613845435c4fbc1d96cac00 Mon Sep 17 00:00:00 2001 From: Sergey Rodionov Date: Thu, 25 Apr 2024 10:48:56 +0400 Subject: [PATCH 04/39] update py-tuple py-list and py-dict to work with metta tuples not with list of arguments --- python/hyperon/stdlib.py | 45 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index c7a0febb1..df8329cec 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -3,7 +3,7 @@ import os from .atoms import ExpressionAtom, E, GroundedAtom, OperationAtom, ValueAtom, NoReduceError, AtomType, MatchableObject, \ - G, S, Atoms, ValueObject, OperationObject + G, S, Atoms, ValueObject, OperationObject, GroundedObject, SymbolAtom from .base import Tokenizer, SExprParser from .ext import register_atoms, register_tokens import hyperonpy as hp @@ -213,43 +213,42 @@ def load_ascii_atom(space, name): r"load-ascii": loadAtom } -def groundedatom_to_python_object(a): - obj = a.get_object() - if isinstance(obj, ValueObject): - obj = obj.value - if isinstance(obj, OperationObject): - obj = obj.content - return obj +def try_unwrap_python_object(a, is_symbol_to_str = False): + if isinstance(a, GroundedObject) or isinstance(a, GroundedAtom): + return a.get_object().content + if is_symbol_to_str and isinstance(a, SymbolAtom): + return a.get_name() + return a # convert nested tuples to nested python tuples or lists -def _py_tuple_list(tuple_list, *atoms): +def _py_tuple_list(tuple_list, metta_tuple): rez = [] - for a in atoms: - if isinstance(a, GroundedAtom): - rez.append(groundedatom_to_python_object(a)) - elif isinstance(a, ExpressionAtom): - rez.append(_py_tuple_list(tuple_list, *a.get_children())) + for a in metta_tuple.get_children(): + if isinstance(a, ExpressionAtom): + rez.append(_py_tuple_list(tuple_list, a)) + else: + rez.append(try_unwrap_python_object(a)) return tuple_list(rez) -def py_tuple(*atoms): - return [ValueAtom(_py_tuple_list(tuple, *atoms))] +def py_tuple(metta_tuple): + return [ValueAtom(_py_tuple_list(tuple, metta_tuple))] -def py_list(*atoms): - return [ValueAtom(_py_tuple_list(list, *atoms))] +def py_list(metta_tuple): + return [ValueAtom(_py_tuple_list(list, metta_tuple))] def tuple_to_keyvalue(a): ac = a.get_children() if len(ac) != 2: raise Exception("Syntax error in tuple_to_keyvalue") - return groundedatom_to_python_object(ac[0]), groundedatom_to_python_object(ac[1]) + return try_unwrap_python_object(ac[0], is_symbol_to_str = True), try_unwrap_python_object(ac[1]) # convert pair of tuples to python dictionary -def py_dict(*atoms): - return [ValueAtom(dict([tuple_to_keyvalue(a) for a in atoms]))] +def py_dict(metta_tuple): + return [ValueAtom(dict([tuple_to_keyvalue(a) for a in metta_tuple.get_children()]))] # chain python objects with | (syntactic sugar for langchain) -def py_chain(*atoms): - objects = [groundedatom_to_python_object(a) for a in atoms] +def py_chain(metta_tuple): + objects = [try_unwrap_python_object(a) for a in metta_tuple.get_children()] result = objects[0] for obj in objects[1:]: result = result | obj From 138546994a92aaf783984a2d7e8a85a6ff944fe6 Mon Sep 17 00:00:00 2001 From: Sergey Rodionov Date: Thu, 25 Apr 2024 10:50:10 +0400 Subject: [PATCH 05/39] add unittests for py-dict py-list py-tuple py-chain --- python/tests/test_stdlib.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/python/tests/test_stdlib.py b/python/tests/test_stdlib.py index 7a0d47021..6c7f1f37b 100644 --- a/python/tests/test_stdlib.py +++ b/python/tests/test_stdlib.py @@ -84,6 +84,37 @@ def test_regex(self): self.assertEqual(metta.run('!(intent "Hi")', True), []) + def test_py_list_tuple(self): + metta = MeTTa(env_builder=Environment.test_env()) + self.assertEqual(metta.run('!(py-list ())'), [[ValueAtom( [] )]]) + self.assertEqual(metta.run('!(py-tuple ())'), [[ValueAtom( () )]]) + self.assertEqual(metta.run('!(py-dict ())'), [[ValueAtom( {} )]]) + self.assertEqual(metta.run('!(py-tuple (1 (2 (3 "3")) (py-atom list)))'), [[ValueAtom((1,(2,(3, "3")), list))]]) + self.assertEqual(metta.run('!(py-list (1 2 (4.5 3)))'), [[ValueAtom( [1,2,[4.5,3]] )]]) + self.assertEqual(metta.run('!(py-list (1 2 (py-tuple (3 4))))'), [[ValueAtom( [1,2, (3,4)] )]]) + + self.assertEqual(metta.run('!(py-dict ((a "b") ("b" "c")))'), [[ValueAtom( {"a":"b", "b":"c"} )]]) + + self.assertEqual(str(metta.run('!(py-list (a b c))')[0][0].get_object().content[2]), "c") + + # We need py-chain for langchain, but we test with bitwise operation | (1 | 2 | 3 | 4 = 7) + self.assertEqual(metta.run('!(py-chain (1 2 3 4))'), [[ValueAtom( 7 )]]) + + # test when we except errors (just in case we reset metta after each exception) + self.assertRaises(Exception, metta.run('!(py-dict (("a" "b" "c") ("b" "c")))')) + metta = MeTTa(env_builder=Environment.test_env()) + + self.assertRaises(Exception, metta.run('!(py-dict (("a") ("b" "c")))')) + metta = MeTTa(env_builder=Environment.test_env()) + + self.assertRaises(Exception, metta.run('!(py-dict ("a" "b") ("b" "c"))')) + metta = MeTTa(env_builder=Environment.test_env()) + + self.assertRaises(Exception, metta.run('!(py-list 1 2)')) + metta = MeTTa(env_builder=Environment.test_env()) + + self.assertRaises(Exception, metta.run('!(py-list 1)')) + metta = MeTTa(env_builder=Environment.test_env()) if __name__ == "__main__": unittest.main() From 43d865e3346fd182405542f55ea10e5af47a88d5 Mon Sep 17 00:00:00 2001 From: Sergey Rodionov Date: Thu, 25 Apr 2024 10:50:56 +0400 Subject: [PATCH 06/39] update simple_import sandbox example for new API of py-* functions --- python/sandbox/simple_import/example_02_numpy.metta | 4 ++-- .../sandbox/simple_import/example_03_langchain.metta | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/sandbox/simple_import/example_02_numpy.metta b/python/sandbox/simple_import/example_02_numpy.metta index 10947773c..6d5aa9b5f 100644 --- a/python/sandbox/simple_import/example_02_numpy.metta +++ b/python/sandbox/simple_import/example_02_numpy.metta @@ -1,6 +1,6 @@ !(bind! np (py-atom numpy)) -!(bind! a1 ( (py-dot np array) (py-atom (py-tuple 1 2 3) ))) +!(bind! a1 ( (py-dot np array) (py-atom (py-tuple (1 2 3)) ))) !(bind! a2 ( (py-dot a1 __mul__) 3)) !(bind! a3 ( (py-dot a1 __add__) a2)) @@ -9,7 +9,7 @@ !(a2) !(a3) -!(bind! m1 ((py-dot np array) (py-atom (py-list (1 2 3) (py-list 4 4 5) (py-tuple 6 7 8)) ))) +!(bind! m1 ((py-dot np array) (py-atom (py-list ((1 2 3) (py-list (4 4 5)) (py-tuple (6 7 8))) )))) !(bind! linalg (py-atom numpy.linalg)) !(bind! m1_inv ( (py-dot linalg inv) m1)) diff --git a/python/sandbox/simple_import/example_03_langchain.metta b/python/sandbox/simple_import/example_03_langchain.metta index 78a399ea0..93076827d 100644 --- a/python/sandbox/simple_import/example_03_langchain.metta +++ b/python/sandbox/simple_import/example_03_langchain.metta @@ -6,13 +6,13 @@ !(bind! prompt ( (py-dot ChatPromptTemplate from_template) "tell me a joke about cat")) -!(bind! chain1 (py-chain prompt model (StrOutputParser) )) +!(bind! chain1 (py-chain (prompt model (StrOutputParser)) )) -!( (py-dot chain1 invoke) (py-dict)) +!( (py-dot chain1 invoke) (py-dict ())) -!(bind! prompt2 ( (py-dot ChatPromptTemplate from_messages ) (py-tuple ("system" "You are very funny") ("user" "tell me joke about {foo}")))) +!(bind! prompt2 ( (py-dot ChatPromptTemplate from_messages ) (py-tuple (("system" "You are very funny") ("user" "tell me joke about {foo}"))))) -!(bind! chain2 (py-chain prompt2 model (StrOutputParser) )) +!(bind! chain2 (py-chain (prompt2 model (StrOutputParser)) )) -!((py-dot chain2 invoke) (py-dict ("foo" "dogs"))) +!((py-dot chain2 invoke) (py-dict (("foo" "dogs")))) From c391b76fdce546aace097ab6fe3acc49842ab18b Mon Sep 17 00:00:00 2001 From: Innokenty Date: Thu, 25 Apr 2024 12:55:31 +0300 Subject: [PATCH 07/39] remove _version.py to prevent setuptools from failing --- python/hyperon/__init__.py | 10 +++++----- python/hyperon/_version.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 python/hyperon/_version.py diff --git a/python/hyperon/__init__.py b/python/hyperon/__init__.py index f6f9b7973..c6763cee4 100644 --- a/python/hyperon/__init__.py +++ b/python/hyperon/__init__.py @@ -1,12 +1,12 @@ from .atoms import * from .base import * from .runner import * -from ._version import __version__ as _ver -if _ver is None: +try: + from ._version import __version__ as _ver + __version__ = _ver +except Exception: from pathlib import Path path = Path(__file__).parent / "../VERSION" with path.open() as f: ver = f.read().splitlines()[0].split("'")[1] - __version__ = ver + "+localbuild" -else: - __version__ = _ver \ No newline at end of file + __version__ = ver + "+localbuild" \ No newline at end of file diff --git a/python/hyperon/_version.py b/python/hyperon/_version.py deleted file mode 100644 index c8ffb60db..000000000 --- a/python/hyperon/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = None \ No newline at end of file From 30f9b86a6640127e84872075863462a81f1a83a6 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 25 Apr 2024 14:31:24 +0300 Subject: [PATCH 08/39] Increase version to 0.1.9 --- Cargo.toml | 4 ++-- python/VERSION | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 488539977..2aec01ca2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,11 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.8" +version = "0.1.9" edition = "2021" [workspace.dependencies] -hyperon = { path = "./lib", version = "0.1.8" } +hyperon = { path = "./lib", version = "0.1.9" } regex = "1.5.4" log = "0.4.0" env_logger = "0.8.4" diff --git a/python/VERSION b/python/VERSION index 4ad8ec271..1f52868ad 100644 --- a/python/VERSION +++ b/python/VERSION @@ -1 +1 @@ -'0.1.8' \ No newline at end of file +'0.1.9' From c415151d0974d8013ffe1b960b5e4338fcda465f Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Thu, 25 Apr 2024 18:11:03 +0300 Subject: [PATCH 09/39] rename Atom.get_type -> get_metatype --- c/src/atom.rs | 4 ++-- c/tests/c_space.c | 2 +- python/hyperon/atoms.py | 12 ++++++------ python/hyperonpy.cpp | 2 +- python/sandbox/repl/metta_repl.py | 4 ++-- python/sandbox/resolve/resolve.py | 2 +- python/tests/test_atom.py | 10 +++++----- python/tests/test_custom_space.py | 2 +- python/tests/test_sexparser.py | 4 ++-- repl/src/py_shim.py | 2 +- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/c/src/atom.rs b/c/src/atom.rs index 0c224969c..85f9afb67 100644 --- a/c/src/atom.rs +++ b/c/src/atom.rs @@ -315,13 +315,13 @@ pub extern "C" fn atoms_are_equivalent(a: *const atom_ref_t, b: *const atom_ref_ crate::atom::matcher::atoms_are_equivalent(a, b) } -/// @brief Returns the type of an atom +/// @brief Returns the metatype of an atom /// @ingroup atom_group /// @param[in] atom A pointer to an `atom_t` or an `atom_ref_t` to inspect /// @return An `atom_type_t` indicating the type of `atom` /// #[no_mangle] -pub unsafe extern "C" fn atom_get_type(atom: *const atom_ref_t) -> atom_type_t { +pub unsafe extern "C" fn atom_get_metatype(atom: *const atom_ref_t) -> atom_type_t { match (*atom).borrow() { Atom::Symbol(_) => atom_type_t::SYMBOL, Atom::Variable(_) => atom_type_t::VARIABLE, diff --git a/c/tests/c_space.c b/c/tests/c_space.c index 27aac750d..26ba15b13 100644 --- a/c/tests/c_space.c +++ b/c/tests/c_space.c @@ -12,7 +12,7 @@ typedef struct _atom_list_item { void collect_variable_atoms(atom_ref_t atom, void* vec_ptr) { atom_vec_t* vec = vec_ptr; - if (atom_get_type(&atom) == VARIABLE) { + if (atom_get_metatype(&atom) == VARIABLE) { atom_vec_push(vec, atom_clone(&atom)); } } diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index 7d065860a..c2e2d5644 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -27,9 +27,9 @@ def __repr__(self): """Renders a human-readable text description of the Atom.""" return hp.atom_to_str(self.catom) - def get_type(self): - """Gets the type of the current Atom instance""" - return hp.atom_get_type(self.catom) + def get_metatype(self): + """Gets the kind of the current Atom instance""" + return hp.atom_get_metatype(self.catom) def iterate(self): """Performs a depth-first exhaustive iteration of an Atom and all its children recursively.""" @@ -46,7 +46,7 @@ def match_atom(self, b): @staticmethod def _from_catom(catom): """Constructs an Atom by wrapping a C Atom""" - type = hp.atom_get_type(catom) + type = hp.atom_get_metatype(catom) if type == AtomKind.SYMBOL: return SymbolAtom(catom) elif type == AtomKind.VARIABLE: @@ -190,7 +190,7 @@ def _priv_call_execute_on_grounded_atom(gnd, typ, args): Executes grounded Atoms. """ # ... if hp.atom_to_str(typ) == AtomType.UNDEFINED - res_typ = AtomType.UNDEFINED if hp.atom_get_type(typ) != AtomKind.EXPR \ + res_typ = AtomType.UNDEFINED if hp.atom_get_metatype(typ) != AtomKind.EXPR \ else Atom._from_catom(hp.atom_get_children(typ)[-1]) args = [Atom._from_catom(catom) for catom in args] return gnd.execute(*args, res_typ=res_typ) @@ -214,7 +214,7 @@ def _priv_compare_value_atom(gnd, catom): Private glue for Hyperonpy implementation. Tests for equality between a grounded value atom and another atom """ - if hp.atom_get_type(catom) == AtomKind.GROUNDED: + if hp.atom_get_metatype(catom) == AtomKind.GROUNDED: atom = GroundedAtom(catom) return gnd == atom.get_object() else: diff --git a/python/hyperonpy.cpp b/python/hyperonpy.cpp index 5e75e7f60..9429c3d50 100644 --- a/python/hyperonpy.cpp +++ b/python/hyperonpy.cpp @@ -694,7 +694,7 @@ PYBIND11_MODULE(hyperonpy, m) { m.def("atom_to_str", [](CAtom& atom) { return func_to_string((write_to_buf_func_t)&atom_to_str, atom.ptr()); }, "Convert atom to human readable string"); - m.def("atom_get_type", [](CAtom& atom) { return atom_get_type(atom.ptr()); }, "Get type of the atom"); + m.def("atom_get_metatype", [](CAtom& atom) { return atom_get_metatype(atom.ptr()); }, "Get type of the atom"); m.def("atom_get_name", [](CAtom& atom) { return func_to_string((write_to_buf_func_t)&atom_get_name, atom.ptr()); }, "Get name of the Symbol or Variable atom"); diff --git a/python/sandbox/repl/metta_repl.py b/python/sandbox/repl/metta_repl.py index 170c6261f..9147c7241 100644 --- a/python/sandbox/repl/metta_repl.py +++ b/python/sandbox/repl/metta_repl.py @@ -84,7 +84,7 @@ def resolve_atom(metta, token): # TODO? the problem is that we need to return an operation to make this # work in parent expressions, thus, it is unclear how to return pure # symbols - if atom.get_type() == hp.AtomKind.GROUNDED: + if atom.get_metatype() == hp.AtomKind.GROUNDED: return atom # TODO: borrow atom type to op return OperationAtom( @@ -141,4 +141,4 @@ def main_loop(self): repl = REPL() readline.add_history("!(match &self $ $)") repl.main_loop() - + diff --git a/python/sandbox/resolve/resolve.py b/python/sandbox/resolve/resolve.py index f73ace948..7bf78c53d 100644 --- a/python/sandbox/resolve/resolve.py +++ b/python/sandbox/resolve/resolve.py @@ -29,7 +29,7 @@ def resolve_atom(metta, token): # TODO? the problem is that we need to return an operation to make this # work in parent expressions, thus, it is unclear how to return pure # symbols - if atom.get_type() == hp.AtomKind.GROUNDED: + if atom.get_metatype() == hp.AtomKind.GROUNDED: return atom # TODO: borrow atom type to op return OperationAtom( diff --git a/python/tests/test_atom.py b/python/tests/test_atom.py index 71f86ed20..411156c3f 100644 --- a/python/tests/test_atom.py +++ b/python/tests/test_atom.py @@ -12,7 +12,7 @@ def test_symbol_str(self): self.assertEqual(str(S("a")), "a") def test_symbol_type(self): - self.assertEqual(S("a").get_type(), AtomKind.SYMBOL) + self.assertEqual(S("a").get_metatype(), AtomKind.SYMBOL) def test_symbol_get_symbol(self): self.assertEqual(S("a").get_name(), "a") @@ -28,7 +28,7 @@ def test_variable_str(self): self.assertEqual(str(V("x")), "$x") def test_variable_type(self): - self.assertEqual(V("x").get_type(), AtomKind.VARIABLE) + self.assertEqual(V("x").get_metatype(), AtomKind.VARIABLE) def test_variable_get_name(self): self.assertEqual(V("x").get_name(), "x") @@ -42,7 +42,7 @@ def test_grounded_str(self): self.assertEqual(str(ValueAtom("1.0")), '"1.0"') def test_grounded_type(self): - self.assertEqual(ValueAtom(1.0).get_type(), AtomKind.GROUNDED) + self.assertEqual(ValueAtom(1.0).get_metatype(), AtomKind.GROUNDED) def test_grounded_grounded_type(self): atom = G(GroundedObject(None), S("Float")) @@ -74,7 +74,7 @@ def test_expr_str(self): self.assertEqual(str(E(x2Atom, ValueAtom(1.0))), "(*2 1.0)") def test_expr_type(self): - self.assertEqual(E(x2Atom, ValueAtom(1.0)).get_type(), AtomKind.EXPR) + self.assertEqual(E(x2Atom, ValueAtom(1.0)).get_metatype(), AtomKind.EXPR) def test_expr_get_children(self): self.assertEqual(E(x2Atom, ValueAtom(1.0)).get_children(), @@ -154,7 +154,7 @@ class GroundedNoCopy: class MatchableObjectTest(MatchableObject): def match_(self, atom): - return [{'atom_type': S(atom.get_children()[0].get_type().name)}] + return [{'atom_type': S(atom.get_children()[0].get_metatype().name)}] def MatchableAtomTest(value, type_name=None, atom_id=None): return G(MatchableObjectTest(value, atom_id), AtomType.UNDEFINED) diff --git a/python/tests/test_custom_space.py b/python/tests/test_custom_space.py index e7ff08aa4..b188a7eb2 100644 --- a/python/tests/test_custom_space.py +++ b/python/tests/test_custom_space.py @@ -15,7 +15,7 @@ def __init__(self, unwrap=True): def query(self, query_atom): # Extract only the variables from the query atom - query_vars = list(filter(lambda atom: atom.get_type() == AtomKind.VARIABLE, query_atom.iterate())) + query_vars = list(filter(lambda atom: atom.get_metatype() == AtomKind.VARIABLE, query_atom.iterate())) # Match the query atom against every atom in the space # BindingsSet() creates a binding set with the only matching result diff --git a/python/tests/test_sexparser.py b/python/tests/test_sexparser.py index 996deedf5..a5c6486a7 100644 --- a/python/tests/test_sexparser.py +++ b/python/tests/test_sexparser.py @@ -11,7 +11,7 @@ def testParseToSyntaxNodes(self): parser = SExprParser("(+ one \"one\")") syntax_node = parser.parse_to_syntax_tree() leaf_node_list = syntax_node.unroll() - leaf_node_types = []; + leaf_node_types = [] for node in leaf_node_list: leaf_node_types.append(node.get_type()) @@ -21,7 +21,7 @@ def testParseToSyntaxNodes(self): SyntaxNodeType.WORD_TOKEN, SyntaxNodeType.WHITESPACE, SyntaxNodeType.STRING_TOKEN, - SyntaxNodeType.CLOSE_PAREN]; + SyntaxNodeType.CLOSE_PAREN] self.assertEqual(leaf_node_types, expected_node_types) diff --git a/repl/src/py_shim.py b/repl/src/py_shim.py index 7acd0c52e..addda986e 100644 --- a/repl/src/py_shim.py +++ b/repl/src/py_shim.py @@ -29,7 +29,7 @@ def parse_line(metta, line): return e.args[0] def parse_line_to_syntax_tree(line): - leaf_node_types = []; + leaf_node_types = [] parser = SExprParser(line) while True: syntax_node = parser.parse_to_syntax_tree() From 1f9d888cb5974a5a42f908d80a3e46d685edb654 Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Thu, 25 Apr 2024 18:12:27 +0300 Subject: [PATCH 10/39] comment update --- python/hyperon/atoms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/hyperon/atoms.py b/python/hyperon/atoms.py index c2e2d5644..8d0df492a 100644 --- a/python/hyperon/atoms.py +++ b/python/hyperon/atoms.py @@ -28,7 +28,7 @@ def __repr__(self): return hp.atom_to_str(self.catom) def get_metatype(self): - """Gets the kind of the current Atom instance""" + """Gets the metatype (kind) of the current Atom instance""" return hp.atom_get_metatype(self.catom) def iterate(self): From fc814533c7f6dcc1ac758fefe6d5c8f474fc9ea3 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 25 Apr 2024 15:27:23 +0300 Subject: [PATCH 11/39] Fix warning of using deprecated Node.js 16 in GitHub actions CI Upgrade pypa/cibuildwheel version to 2.17.0 which uses actions/setup-python@v5. Update actions to the new versions which uses latest Node.js. Assign unique name to the artifacts for each platform. Merge artifacts before publishing. It is needed because actions/upload-artifacts@v4 cannot upload artifacts with the same name anymore. --- .github/workflows/common.yml | 4 ++-- .github/workflows/docs.yaml | 4 ++-- .github/workflows/minimal.yml | 4 ++-- .github/workflows/release-python.yml | 20 +++++++++++--------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index 520e3c534..1c3e43070 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust stable uses: actions-rs/toolchain@v1.0.6 @@ -57,7 +57,7 @@ jobs: args: cbindgen - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }} diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 8d4a5347e..69ea7622b 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -9,8 +9,8 @@ jobs: deploy-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-versoin: 3.x - uses: actions/cache@v2 diff --git a/.github/workflows/minimal.yml b/.github/workflows/minimal.yml index d1841e16f..09334d3d2 100644 --- a/.github/workflows/minimal.yml +++ b/.github/workflows/minimal.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust stable uses: actions-rs/toolchain@v1.0.6 @@ -47,7 +47,7 @@ jobs: args: cbindgen - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.7 diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 32e82e4cb..70d745a5f 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -25,10 +25,10 @@ jobs: steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" @@ -40,7 +40,7 @@ jobs: echo "COMMIT_HEAD=${{github.ref_name != '' && github.ref_name || env.GITHUB_SHA}}" | tee -a $GITHUB_ENV - name: Build wheels on ${{ matrix.os }} - uses: pypa/cibuildwheel@v2.13.1 + uses: pypa/cibuildwheel@v2.17.0 env: CIBW_BEFORE_ALL: sh -c "./python/install-hyperonc.sh -u https://github.com/${{github.repository}}.git -r ${{env.COMMIT_HEAD}}" CIBW_SKIP: "*musllinux*" @@ -59,9 +59,9 @@ jobs: file_glob: true - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: python-wheels + name: python-wheels-${{ matrix.os }} path: ./wheelhouse/*.whl publish-test-pypi: @@ -74,9 +74,10 @@ jobs: needs: [build-wheels] if: github.event.action == 'published' steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: python-wheels + pattern: python-wheels-* + merge-multiple: true path: dist - name: Publish package distributions to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 @@ -93,9 +94,10 @@ jobs: needs: [build-wheels] if: github.event.action == 'published' steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: python-wheels + pattern: python-wheels-* + merge-multiple: true path: dist - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 From 45b30fb4ba05852308774ee551c1696b02e8ea00 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 26 Apr 2024 10:01:31 +0300 Subject: [PATCH 12/39] Opt out musllinux platform in pyproject.toml file While musllinux-i686 is not supported by Rust, other musllinux environments can be built but they are disabled to save build time. --- .github/workflows/release-python.yml | 1 - docs/DEVELOPMENT.md | 16 ++++++---------- python/pyproject.toml | 7 ++++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 70d745a5f..b22f04835 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -43,7 +43,6 @@ jobs: uses: pypa/cibuildwheel@v2.17.0 env: CIBW_BEFORE_ALL: sh -c "./python/install-hyperonc.sh -u https://github.com/${{github.repository}}.git -r ${{env.COMMIT_HEAD}}" - CIBW_SKIP: "*musllinux*" with: package-dir: ./python diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 003b82b65..ded8c3eba 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,6 +1,6 @@ # Instructions for developers -## How to check Python release procedure locally +## How to release Python distribution packages locally Python packages are released using [cibuildwheel](https://pypi.org/project/cibuildwheel/). First step is to setup @@ -8,8 +8,11 @@ it. Usually it means setup docker and install the package from PyPi (see [setup instructions](https://cibuildwheel.pypa.io/en/stable/setup/#local)). There are additional preparations to be made before running it. First of all -`libhyperonc` library should be built and installed in a build environment. It -is done by `install-hyperonc.sh` script which is called using +`libhyperonc` library should be built and installed in a build environment. By +default library downloads and install version from the `main` branch of the +`trueagi-io/hyperon-experimental` repository. If one need to use the custom +branch then it is done by passing custom parameters to the +`install-hyperonc.sh` script which is called using [CIBW_BEFORE_ALL](https://cibuildwheel.pypa.io/en/stable/options/#before-all) environment variable: ``` @@ -24,13 +27,6 @@ the Python package is copied into container automatically. Code of the have the code in some repo accessible from the container before starting release. The simplest way is to push the changes in your GitHub repo fork. -Some platform are not supported. Use -[CIBW_SKIP](https://cibuildwheel.pypa.io/en/stable/options/#build-skip) to skip -such platforms: -``` -export CIBW_SKIP="*musllinux*" -``` - Also one can start from building the only platform to quickly check whether release works. This can be done using [CIBW_BUILD](https://cibuildwheel.pypa.io/en/stable/options/#build-skip) diff --git a/python/pyproject.toml b/python/pyproject.toml index 240eb9b9e..b1d923100 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -36,7 +36,8 @@ package-dir = { "hyperon" = "hyperon" } [tool.cibuildwheel] before-all = "sh -c ./python/install-hyperonc.sh" -# no Rust toolchain is available for musllinux-i686 environment -skip = "*-musllinux_i686" +# There is no Rust toolchain is available for musllinux-i686 environment. +# Other musllinux platforms are opted out to decrease the build time. +skip = "*musllinux*" test-requires = ["pytest==7.3.2"] -test-command = "pytest {project}/python/tests" \ No newline at end of file +test-command = "pytest {project}/python/tests" From 9d3addc70d616b2ccc0d2ffafc9d319ecb70a5ce Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 25 Apr 2024 15:27:56 +0300 Subject: [PATCH 13/39] Use Python 3.10 for the MacOsX arm64 environment --- .github/workflows/release-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index b22f04835..264be9e41 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: "3.10" - run: | echo "REF_NAME=${{github.ref_name}}" | tee -a $GITHUB_ENV From 83c60c6a9995f4fd5be9c1f1f0cd0995b094fa6a Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 26 Apr 2024 07:41:45 +0300 Subject: [PATCH 14/39] Document install-hyperonc.sh script parameters --- python/install-hyperonc.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/install-hyperonc.sh b/python/install-hyperonc.sh index 26f0ff03a..d9ac316bf 100755 --- a/python/install-hyperonc.sh +++ b/python/install-hyperonc.sh @@ -12,6 +12,8 @@ while getopts 'u:r:' opt; do ;; ?|h) echo "Usage: $(basename $0) [-u hyperonc_repo_url] [-r hyperonc_revision]" + echo "-u hyperonc_repo_url Git repo URL to get hyperonc source code" + echo "-r hyperonc_revision Revision of hyperonc to get from Git" exit 1 ;; esac From bc2f33d9c22ec6904ec2fe5110d140118c2914c4 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 25 Apr 2024 15:18:48 +0300 Subject: [PATCH 15/39] Add macos-14 to build MacOSX arm64 binaries, update macos-11 to macos-13 Fix permission issue when releasing on MacOSX Apple Silicon. Main environment is used by cibuildwheel on MacOSX Apple Silicon platform. It leads to the insufficient permission issue when installing under /usr/local prefix. Use ~/.local prefix to install to fix the issue. Also add ~/.local prefix to the CMake search paths. --- .github/workflows/release-python.yml | 2 +- python/install-hyperonc.sh | 13 +++++++++---- python/setup.py | 6 ++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 264be9e41..23dd4fd45 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, macos-11] + os: [ubuntu-20.04, macos-13, macos-14] max-parallel: 3 steps: diff --git a/python/install-hyperonc.sh b/python/install-hyperonc.sh index d9ac316bf..65d2ed921 100755 --- a/python/install-hyperonc.sh +++ b/python/install-hyperonc.sh @@ -19,8 +19,8 @@ while getopts 'u:r:' opt; do esac done -echo "hyperonc repository URL $HYPERONC_URL" -echo "hyperonc revision $HYPERONC_REV" +echo "hyperonc repository URL: $HYPERONC_URL" +echo "hyperonc revision: $HYPERONC_REV" # This is to build subunit from Conan on CentOS based manylinux images. if test "$AUDITWHEEL_POLICY" = "manylinux2014"; then @@ -45,8 +45,13 @@ git reset --hard FETCH_HEAD mkdir -p ${HOME}/hyperonc/c/build cd ${HOME}/hyperonc/c/build -# Rust doesn't support building shared libraries under musllinux environment -cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release .. +# Rust doesn't support building shared libraries under musllinux environment. +CMAKE_ARGS="$CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF" +# Local prefix is used to support MacOSX Apple Silicon GitHub actions environment. +CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${HOME}/.local" +CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release" +echo "hyperonc CMake arguments: $CMAKE_ARGS" +cmake $CMAKE_ARGS .. make make check make install diff --git a/python/setup.py b/python/setup.py index cb320db4c..41dc47e71 100644 --- a/python/setup.py +++ b/python/setup.py @@ -38,11 +38,13 @@ def _build_with_cmake(self, ext: CMakeExtension) -> None: extdir = ext_fullpath.parent.resolve() debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug cfg = "Debug" if debug else "Release" + local_prefix = os.path.join(os.environ["HOME"], ".local") cmake_args = [ f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", f"-DPython3_EXECUTABLE={sys.executable}", - f"-DCMAKE_BUILD_TYPE={cfg}" + f"-DCMAKE_BUILD_TYPE={cfg}", + f"-DCMAKE_PREFIX_PATH={local_prefix}" ] build_args = [] # Adding CMake arguments set as environment variable @@ -88,4 +90,4 @@ def version_scheme(*args): use_scm_version={'root': '..', 'version_scheme': version_scheme, 'write_to': 'python/hyperon/_version.py'}, - ) \ No newline at end of file + ) From b5e723537bc999fc7de83debb2408826fcc042ea Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 26 Apr 2024 10:59:09 +0300 Subject: [PATCH 16/39] Document release and test release procedures --- docs/DEVELOPMENT.md | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index ded8c3eba..0ad489bbf 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -38,3 +38,72 @@ export CIBW_BUILD=cp37-manylinux_x86_64 After exporting the variables above one can start release by executing `cibuildwheel` from the `./python` directory of the repo. See [cibuildwheel documentation](https://cibuildwheel.pypa.io/en/stable/) for details. + +## How to update the version + +Usually it is needed after releasing the artifacts or before making a test +release. + +There are three locations to update: +- [/Cargo.toml](/Cargo.toml) file: + - `workspace.package.version` property + - `workspace.dependencies.hyperon.version` property +- [/python/VERSION](/python/VERSION) file + +All three locations should contain the same version. + +## How to release binaries + +Use [Create a new release +link](https://github.com/trueagi-io/hyperon-experimental/releases/new) on the +main page of the GitHub repo. Press `Choose a tag` control and type new tag +which should be in form of `v` (for example if next version is +`0.1.7` then tag is `v0.1.7`). Next version should be identical to versions +which are written in locations mentioned in [How to update the +version](#how-to-update-the-version) instruction. After typing the tag press +`Create new tag on publish`. Now press `Generate release notes` button. It will +automatically fill the `Release title` and `Release description` fields. Tick +`Set as a pre-release` checkbox if needed and press `Publish release` button. +Now you have published new GitHub release and triggered a job to build release +artifacts. + +After release job is finished one need to approve publishing artifacts to the +PyPi repository. Before approving one can download and test Python wheels +built. To check the job status go the `Actions/release-pyhon` page, and select +last workflow run. Links to the archives with the artifacts are located at the +bottom of the page. + +If distribution wheels are good then one can approve the publishing process. At +the top of the workflow run page there are two blocks `Publish to Test PyPi` +and `Publish to PyPi`. First press `Publish to Test PyPi` block approve it and +wait for publishing. It is critical to start from Test PyPi because release +cannot be removed from the PyPi after publishing. + +After release is published check it can be installed executing: +``` +python3 -m pip install --index-url https://test.pypi.org/simple/ hyperon +``` +Check that the latest published version was downloaded and installed. If you +are making a test release then you should not publish it to the PyPi. If it is +a production release then proceed with `Publish to PyPi` block. + +## How to check release job in fork + +First you need to select the test release version. It should contain an +additional version digit after the latest officially released version. Let's +say the latest released version is `0.1.7`. Then the test release version +should be `0.1.7.x` for instance `0.1.7.1`. Start from 1 and increment it after +each release you published successfully. + +Make a separate branch to release the code. It is not necessary but it is +highly recommended to not pollute the main branch of the fork. In order to be +able releasing from the branch one need to temporary make it default branch. It +is done by using GitHub repo `Settings/General/Default branch` control. + +[Update the version](#how-to-update-the-version) in the branch to the test +release version you constructed. Commit and push this change in your test +branch. Now you are ready to make a test release. See [release +binaries instruction](#how-to-release-binaries). + +After testing the release procedure remove the commit with version update from +your branch. And set default branch setting to the previous value. From 7908657579c5c2889f694675613cde1aaeaacfe9 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 25 Apr 2024 11:02:28 +0300 Subject: [PATCH 17/39] Remove dependency on GroundingSpace from the code of the interpreter --- lib/src/metta/interpreter_minimal.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index 54726793d..3e4fa5b53 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -7,7 +7,6 @@ use crate::*; use crate::atom::matcher::*; use crate::space::*; -use crate::space::grounding::*; use crate::metta::*; use std::fmt::{Debug, Display, Formatter}; @@ -163,7 +162,7 @@ impl<'a, T: Space + 'a> SpaceRef<'a> for T {} #[derive(Debug)] struct InterpreterContext<'a, T: SpaceRef<'a>> { space: T, - phantom: PhantomData<&'a GroundingSpace>, + phantom: PhantomData>, } impl<'a, T: SpaceRef<'a>> InterpreterContext<'a, T> { From f37150a27b56ce6770451995e2fcc4e0294c0fe7 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 26 Apr 2024 12:37:29 +0300 Subject: [PATCH 18/39] Remove unused variable --- lib/src/metta/interpreter_minimal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index 3e4fa5b53..d2d2c7f21 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -426,7 +426,7 @@ fn finished_result(atom: Atom, bindings: Bindings, prev: Option>(context: &InterpreterContext<'a, T>, stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: eval, ret: _, finished: _, vars} = stack; + let Stack{ prev, atom: eval, ret: _, finished: _, vars: _} = stack; let query_atom = match_atom!{ eval ~ [_op, query] => query, _ => { @@ -475,7 +475,7 @@ fn eval<'a, T: SpaceRef<'a>>(context: &InterpreterContext<'a, T>, stack: Stack, }, _ if is_embedded_op(&query_atom) => vec![InterpretedAtom(atom_to_stack(query_atom, prev), bindings)], - _ => query(&context.space, prev, query_atom, bindings, vars), + _ => query(&context.space, prev, query_atom, bindings), } } @@ -492,7 +492,7 @@ fn is_variable_op(atom: &Atom) -> bool { } } -fn query<'a, T: SpaceRef<'a>>(space: T, prev: Option>>, atom: Atom, bindings: Bindings, _vars: Variables) -> Vec { +fn query<'a, T: SpaceRef<'a>>(space: T, prev: Option>>, atom: Atom, bindings: Bindings) -> Vec { #[cfg(not(feature = "variable_operation"))] if is_variable_op(&atom) { // TODO: This is a hotfix. Better way of doing this is adding From 13eed209882a98c1f279091495b393c2ab6883a5 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Sat, 27 Apr 2024 07:50:54 +0300 Subject: [PATCH 19/39] Rename method, add documentation --- lib/src/metta/interpreter_minimal.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index d2d2c7f21..c8a7ca7a0 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -34,6 +34,14 @@ macro_rules! match_atom { } }; } + +/// Operation return handler, it is triggered when nested operation is finished +/// and returns its results. First argument gets the reference to the stack +/// which on the top has the frame of the wrapping operation. Last two +/// arguments are the result of the nested operation. Handler returns +/// None when it is not ready to provide new stack (it can happen in case of +/// collapse-bind operation) or new stack with variable bindings to continue +/// execution of the program. type ReturnHandler = fn(Rc>, Atom, Bindings) -> Option<(Stack, Bindings)>; #[derive(Debug, Clone)] @@ -267,7 +275,8 @@ pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterSt pub fn interpret_step<'a, T: Space + 'a>(mut state: InterpreterState<'a, T>) -> InterpreterState<'a, T> { let interpreted_atom = state.pop().unwrap(); log::debug!("interpret_step:\n{}", interpreted_atom); - for result in interpret_root_atom(&state.context, interpreted_atom) { + let InterpretedAtom(stack, bindings) = interpreted_atom; + for result in interpret_stack(&state.context, stack, bindings) { state.push(result); } state @@ -358,12 +367,7 @@ impl Display for Variables { } } -fn interpret_root_atom<'a, T: SpaceRef<'a>>(context: &InterpreterContext<'a, T>, interpreted_atom: InterpretedAtom) -> Vec { - let InterpretedAtom(stack, bindings) = interpreted_atom; - interpret_nested_atom(context, stack, bindings) -} - -fn interpret_nested_atom<'a, T: SpaceRef<'a>>(context: &InterpreterContext<'a, T>, mut stack: Stack, bindings: Bindings) -> Vec { +fn interpret_stack<'a, T: SpaceRef<'a>>(context: &InterpreterContext<'a, T>, mut stack: Stack, bindings: Bindings) -> Vec { if stack.finished { // first executed minimal operation returned error if stack.prev.is_none() { From 8a315c452f1ae3170795df930b2eb205485e0d47 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Sat, 27 Apr 2024 08:53:43 +0300 Subject: [PATCH 20/39] Stop keeping vars in finished stack frames --- lib/src/metta/interpreter_minimal.rs | 31 ++++++++++------------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index c8a7ca7a0..5d8aebf9f 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -68,7 +68,7 @@ impl Stack { fn from_prev_add_vars(prev: Option>>, atom: Atom, ret: ReturnHandler) -> Self { // TODO: vars are introduced in specific locations of the atom thus // in theory it is possible to optimize vars search for eval, unify and chain - let vars = Self::vars(&prev, &atom); + let vars = Self::add_vars(&prev, &atom); Self{ prev, atom, ret, finished: false, vars } } @@ -78,17 +78,7 @@ impl Stack { } fn finished(prev: Option>>, atom: Atom) -> Self { - let vars = Self::vars_copy(&prev); - Self{ prev, atom, ret: no_handler, finished: true, vars } - } - - fn finished_add_vars(prev: Option>>, atom: Atom) -> Self { - let vars = Self::vars(&prev, &atom); - Self{ prev, atom, ret: no_handler, finished: true, vars } - } - - fn finished_with_vars(prev: Option>>, atom: Atom, vars: Variables) -> Self { - Self{ prev, atom, ret: no_handler, finished: true, vars } + Self{ prev, atom, ret: no_handler, finished: true, vars: Variables::new() } } fn len(&self) -> usize { @@ -111,7 +101,7 @@ impl Stack { } } - fn vars(prev: &Option>>, atom: &Atom) -> Variables { + fn add_vars(prev: &Option>>, atom: &Atom) -> Variables { // TODO: nested atoms are visited twice: first time when outer atom // is visited, next time when internal atom is visited. let vars: Variables = atom.iter().filter_type::<&VariableAtom>().cloned().collect(); @@ -515,18 +505,19 @@ fn query<'a, T: SpaceRef<'a>>(space: T, prev: Option>>, atom: results.into_iter() .flat_map(|b| { let res = apply_bindings_to_atom(&atom_x, &b); + let vars = Stack::add_vars(&prev, &res); let stack = if is_function_op(&res) { let call = Stack::from_prev_add_vars(prev.clone(), atom.clone(), call_ret); atom_to_stack(res, Some(Rc::new(RefCell::new(call)))) } else { - Stack::finished_add_vars(prev.clone(), res) + Stack::finished(prev.clone(), res) }; log::debug!("interpreter_minimal::query: b: {}", b); b.merge_v2(&bindings).into_iter().filter_map(move |mut b| { if b.has_loops() { None } else { - b.retain(|v| stack.vars.contains(v)); + b.retain(|v| vars.contains(v)); Some(InterpretedAtom(stack.clone(), b)) } }) @@ -674,7 +665,7 @@ fn collapse_bind_ret(stack: Rc>, atom: Atom, bindings: Bindings) let (_, bindings) = atom_get_atom_bindings(r); vars = vars.union(bindings.vars().cloned().collect()); } - Some((Stack::finished_with_vars(prev, result, vars), bindings)) + Some((Stack::finished(prev, result), bindings)) }, None => None, } @@ -801,13 +792,14 @@ fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { .map(atom_into_atom_bindings) .flat_map(|(atom, b)| { let prev = &prev; + let vars = Stack::add_vars(prev, &atom); b.merge_v2(&bindings).into_iter() .filter_map(move |b| { if b.has_loops() { None } else { - let stack = Stack::finished_add_vars(prev.clone(), atom.clone()); - let b = b.narrow_vars(&stack.vars); + let stack = Stack::finished(prev.clone(), atom.clone()); + let b = b.narrow_vars(&vars); Some(InterpretedAtom(stack, b)) } }) @@ -1071,9 +1063,8 @@ mod tests { let result = superpose_bind(stack, bind!{ b: expr!("B"), d: expr!("D") }); - let expected_vars: Variables = [ "a", "b" ].into_iter().map(VariableAtom::new).collect(); assert_eq!(result, vec![InterpretedAtom( - Stack{ prev: None, atom: expr!("foo" a b), ret: no_handler, finished: true, vars: expected_vars }, + Stack{ prev: None, atom: expr!("foo" a b), ret: no_handler, finished: true, vars: Variables::new() }, bind!{ a: expr!("A"), b: expr!("B") } )]); } From 605927b21d7ab76c3eede19426eac57dcbc31b2b Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Sat, 27 Apr 2024 10:04:40 +0300 Subject: [PATCH 21/39] Stop collecting variables from results of collapse-bind --- lib/src/metta/interpreter_minimal.rs | 44 +++++++++------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index 5d8aebf9f..d93770fe2 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -104,11 +104,10 @@ impl Stack { fn add_vars(prev: &Option>>, atom: &Atom) -> Variables { // TODO: nested atoms are visited twice: first time when outer atom // is visited, next time when internal atom is visited. - let vars: Variables = atom.iter().filter_type::<&VariableAtom>().cloned().collect(); - match (prev, vars.is_empty()) { - (Some(prev), true) => prev.borrow().vars.clone(), - (Some(prev), false) => prev.borrow().vars.clone().union(vars), - (None, _) => vars, + let vars = vars_from_atom(atom); + match prev { + Some(prev) => prev.borrow().vars.clone().insert_all(vars), + None => vars.cloned().collect(), } } } @@ -321,13 +320,17 @@ impl Variables { fn new() -> Self { Self(im::HashSet::new()) } - fn is_empty(&self) -> bool { - self.0.is_empty() + fn insert(&mut self, var: VariableAtom) -> Option { + self.0.insert(var) } - fn union(self, other: Self) -> Self { - Variables(self.0.union(other.0)) + fn insert_all<'a, I: 'a + Iterator>(mut self, it: I) -> Self { + it.for_each(|var| { self.insert(var.clone()); }); + self } } +fn vars_from_atom(atom: &Atom) -> impl Iterator { + atom.iter().filter_type::<&VariableAtom>() +} impl FromIterator for Variables { fn from_iter>(iter: I) -> Self { @@ -654,17 +657,14 @@ fn collapse_bind_ret(stack: Rc>, atom: Atom, bindings: Bindings) finished.children_mut().push(atom_bindings_into_atom(nested, bindings)); } + // all alternatives are evaluated match Rc::into_inner(stack).map(RefCell::into_inner) { Some(stack) => { - let Stack{ prev, atom: collapse, ret: _, finished: _, mut vars } = stack; + let Stack{ prev, atom: collapse, ret: _, finished: _, vars: _ } = stack; let (result, bindings) = match atom_into_array(collapse) { Some([_op, result, bindings]) => (result, atom_into_bindings(bindings)), None => panic!("Unexpected state"), }; - for r in <&ExpressionAtom>::try_from(&result).unwrap().children() { - let (_, bindings) = atom_get_atom_bindings(r); - vars = vars.union(bindings.vars().cloned().collect()); - } Some((Stack::finished(prev, result), bindings)) }, None => None, @@ -675,22 +675,6 @@ fn atom_bindings_into_atom(atom: Atom, bindings: Bindings) -> Atom { Atom::expr([atom, Atom::value(bindings)]) } -fn atom_get_atom_bindings(pair: &Atom) -> (&Atom, &Bindings) { - match atom_as_slice(pair) { - Some([atom, bindings]) => (atom, atom_get_bindings(bindings)), - _ => { - panic!("(Atom Bindings) pair is expected, {} was received", pair) - } - } -} - -fn atom_get_bindings(bindings: &Atom) -> &Bindings { - match bindings.as_gnd::() { - Some(bindings) => bindings, - _ => panic!("Unexpected state: second item cannot be converted to Bindings"), - } -} - fn unify(stack: Stack, bindings: Bindings) -> Vec { let Stack{ prev, atom: unify, ret: _, finished: _, vars } = stack; let (atom, pattern, then, else_) = match atom_as_slice(&unify) { From 247b8005753c3f7fa4eea7437d8097e12d8ca79b Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Sat, 27 Apr 2024 13:10:55 +0300 Subject: [PATCH 22/39] isinstance(a, GroundedObject) was wrong --- python/hyperon/stdlib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index 4a77a8184..1c657cfda 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -207,8 +207,9 @@ def load_ascii_atom(space, name): } def try_unwrap_python_object(a, is_symbol_to_str = False): - if isinstance(a, GroundedObject) or isinstance(a, GroundedAtom): - return a.get_object().content + if isinstance(a, GroundedAtom): + # FIXME? Do we need to unwrap a grounded object if it is not GroundedObject? + return a.get_object().content isinstance(a.get_object(), GroundedObject) else a.get_object() if is_symbol_to_str and isinstance(a, SymbolAtom): return a.get_name() return a From 43dffe21b130ee381d0c4ae47513709083fce001 Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Sat, 27 Apr 2024 13:11:27 +0300 Subject: [PATCH 23/39] Update stdlib.py --- python/hyperon/stdlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index 1c657cfda..fbe0ec749 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -209,7 +209,7 @@ def load_ascii_atom(space, name): def try_unwrap_python_object(a, is_symbol_to_str = False): if isinstance(a, GroundedAtom): # FIXME? Do we need to unwrap a grounded object if it is not GroundedObject? - return a.get_object().content isinstance(a.get_object(), GroundedObject) else a.get_object() + return a.get_object().content if isinstance(a.get_object(), GroundedObject) else a.get_object() if is_symbol_to_str and isinstance(a, SymbolAtom): return a.get_name() return a From 37adfb4f88258ba8fe0e6d9c5d3fc54fdd157c5a Mon Sep 17 00:00:00 2001 From: Sergey Rodionov Date: Sat, 27 Apr 2024 13:20:01 +0300 Subject: [PATCH 24/39] Update python/hyperon/stdlib.py Co-authored-by: Vitaly Bogdanov --- python/hyperon/stdlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index fbe0ec749..2b3dd8875 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -3,7 +3,7 @@ import os from .atoms import ExpressionAtom, E, GroundedAtom, OperationAtom, ValueAtom, NoReduceError, AtomType, MatchableObject, \ - G, S, Atoms, get_string_value, ValueObject, OperationObject, GroundedObject, SymbolAtom + G, S, Atoms, get_string_value, GroundedObject, SymbolAtom from .base import Tokenizer, SExprParser from .ext import register_atoms, register_tokens import hyperonpy as hp From 073527d56820ea850de6b869e6d30e520a605a56 Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Sat, 27 Apr 2024 13:24:27 +0300 Subject: [PATCH 25/39] Update stdlib.py --- python/hyperon/stdlib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/hyperon/stdlib.py b/python/hyperon/stdlib.py index 2b3dd8875..044ee3870 100644 --- a/python/hyperon/stdlib.py +++ b/python/hyperon/stdlib.py @@ -250,7 +250,7 @@ def py_chain(metta_tuple): @register_atoms() def py_funs(): - return {"py-tuple": OperationAtom("py-tuple", py_tuple, unwrap = False), - "py-list": OperationAtom("py-list", py_list, unwrap = False), - "py-dict": OperationAtom("py-dict", py_dict, unwrap = False), - "py-chain": OperationAtom("py-chain", py_chain, unwrap = False)} + return {"py-tuple": OperationAtom("py-tuple", py_tuple, unwrap = False), + "py-list" : OperationAtom("py-list" , py_list , unwrap = False), + "py-dict" : OperationAtom("py-dict" , py_dict , unwrap = False), + "py-chain": OperationAtom("py-chain", py_chain, unwrap = False)} From dd85d3300e296b6498fbb722d279d30dc21d34ca Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Sat, 27 Apr 2024 11:12:27 +0300 Subject: [PATCH 26/39] Optimize variables usage for chain, unify and eval operations --- lib/src/metta/interpreter_minimal.rs | 65 ++++++++++++++++++---------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index d93770fe2..5a2a08147 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -65,10 +65,14 @@ fn no_handler(_stack: Rc>, _atom: Atom, _bindings: Bindings) -> O } impl Stack { + fn from_prev_with_vars(prev: Option>>, atom: Atom, vars: Variables, ret: ReturnHandler) -> Self { + Self{ prev, atom, ret, finished: false, vars } + } + fn from_prev_add_vars(prev: Option>>, atom: Atom, ret: ReturnHandler) -> Self { // TODO: vars are introduced in specific locations of the atom thus // in theory it is possible to optimize vars search for eval, unify and chain - let vars = Self::add_vars(&prev, &atom); + let vars = Self::add_vars_atom(&prev, &atom); Self{ prev, atom, ret, finished: false, vars } } @@ -101,10 +105,12 @@ impl Stack { } } - fn add_vars(prev: &Option>>, atom: &Atom) -> Variables { - // TODO: nested atoms are visited twice: first time when outer atom - // is visited, next time when internal atom is visited. - let vars = vars_from_atom(atom); + #[inline] + fn add_vars_atom(prev: &Option>>, atom: &Atom) -> Variables { + Self::add_vars_it(prev, vars_from_atom(atom)) + } + + fn add_vars_it<'a, I: 'a + Iterator>(prev: &Option>>, vars: I) -> Variables { match prev { Some(prev) => prev.borrow().vars.clone().insert_all(vars), None => vars.cloned().collect(), @@ -508,7 +514,7 @@ fn query<'a, T: SpaceRef<'a>>(space: T, prev: Option>>, atom: results.into_iter() .flat_map(|b| { let res = apply_bindings_to_atom(&atom_x, &b); - let vars = Stack::add_vars(&prev, &res); + let vars = Stack::add_vars_atom(&prev, &res); let stack = if is_function_op(&res) { let call = Stack::from_prev_add_vars(prev.clone(), atom.clone(), call_ret); atom_to_stack(res, Some(Rc::new(RefCell::new(call)))) @@ -537,34 +543,32 @@ fn query<'a, T: SpaceRef<'a>>(space: T, prev: Option>>, atom: fn atom_to_stack(atom: Atom, prev: Option>>) -> Stack { let expr = atom_as_slice(&atom); let result = match expr { - Some([op, ..]) if *op == CHAIN_SYMBOL => { - chain_to_stack(atom, prev) - }, - Some([op, ..]) if *op == FUNCTION_SYMBOL => { - function_to_stack(atom, prev) - }, - Some([op, ..]) if *op == EVAL_SYMBOL - || *op == UNIFY_SYMBOL => { - Stack::from_prev_add_vars(prev, atom, no_handler) - }, - _ => { - Stack::from_prev_keep_vars(prev, atom, no_handler) - }, + Some([op, ..]) if *op == CHAIN_SYMBOL => + chain_to_stack(atom, prev), + Some([op, ..]) if *op == FUNCTION_SYMBOL => + function_to_stack(atom, prev), + Some([op, ..]) if *op == EVAL_SYMBOL => + Stack::from_prev_keep_vars(prev, atom, no_handler), + Some([op, ..]) if *op == UNIFY_SYMBOL => + unify_to_stack(atom, prev), + _ => + Stack::from_prev_keep_vars(prev, atom, no_handler), }; result } fn chain_to_stack(mut atom: Atom, prev: Option>>) -> Stack { let mut nested = Atom::sym("%Nested%"); - let nested_arg = match atom_as_slice_mut(&mut atom) { - Some([_op, nested, Atom::Variable(_var), _templ]) => nested, + let (nested_arg, templ_arg) = match atom_as_slice_mut(&mut atom) { + Some([_op, nested, Atom::Variable(_var), templ]) => (nested, templ), _ => { let error: String = format!("expected: ({} (: Variable) ), found: {}", CHAIN_SYMBOL, atom); return Stack::finished(prev, error_atom(atom, error)); }, }; std::mem::swap(nested_arg, &mut nested); - let cur = Stack::from_prev_add_vars(prev, atom, chain_ret); + let vars = Stack::add_vars_it(&prev, vars_from_atom(templ_arg)); + let cur = Stack::from_prev_with_vars(prev, atom, vars, chain_ret); atom_to_stack(nested, Some(Rc::new(RefCell::new(cur)))) } @@ -675,8 +679,19 @@ fn atom_bindings_into_atom(atom: Atom, bindings: Bindings) -> Atom { Atom::expr([atom, Atom::value(bindings)]) } +fn unify_to_stack(mut atom: Atom, prev: Option>>) -> Stack { + let () = match atom_as_slice_mut(&mut atom) { + Some([_op, _a, _b, _then, _else]) => (), + _ => { + let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, atom); + return Stack::finished(prev, error_atom(atom, error)); + }, + }; + Stack::from_prev_with_vars(prev, atom, Variables::new(), no_handler) +} + fn unify(stack: Stack, bindings: Bindings) -> Vec { - let Stack{ prev, atom: unify, ret: _, finished: _, vars } = stack; + let Stack{ prev, atom: unify, ret: _, finished: _, vars: _ } = stack; let (atom, pattern, then, else_) = match atom_as_slice(&unify) { Some([_op, atom, pattern, then, else_]) => (atom, pattern, then, else_), _ => { @@ -692,10 +707,12 @@ fn unify(stack: Stack, bindings: Bindings) -> Vec { // from car's argument is replaced. let matches: Vec = match_atoms(atom, pattern).collect(); if matches.is_empty() { + let vars = Stack::add_vars_it(&prev, vars_from_atom(else_)); let bindings = bindings.narrow_vars(&vars); let result = apply_bindings_to_atom(else_, &bindings); finished_result(result, bindings, prev) } else { + let vars = Stack::add_vars_it(&prev, vars_from_atom(then)); matches.into_iter() .flat_map(move |b| { let b = b.narrow_vars(&vars); @@ -776,7 +793,7 @@ fn superpose_bind(stack: Stack, bindings: Bindings) -> Vec { .map(atom_into_atom_bindings) .flat_map(|(atom, b)| { let prev = &prev; - let vars = Stack::add_vars(prev, &atom); + let vars = Stack::add_vars_atom(prev, &atom); b.merge_v2(&bindings).into_iter() .filter_map(move |b| { if b.has_loops() { From 8b51f9a6cb3cda38ed737b9335573ae206693271 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Thu, 2 May 2024 13:03:50 +0300 Subject: [PATCH 27/39] Remove unnecessary cloning on applying bindings Replace apply_bindings_to_atom by apply_bindings_to_atom_mut which gets mutable reference to atom. Add apply_bindings_to_atom_move for convenience. --- lib/src/atom/matcher.rs | 26 +++++++++++++++++++------- lib/src/metta/interpreter.rs | 12 ++++++------ lib/src/metta/interpreter_minimal.rs | 25 +++++++++++++------------ lib/src/metta/runner/stdlib.rs | 4 ++-- lib/src/metta/types.rs | 8 ++++---- lib/src/space/grounding.rs | 2 +- lib/src/space/mod.rs | 4 ++-- 7 files changed, 47 insertions(+), 34 deletions(-) diff --git a/lib/src/atom/matcher.rs b/lib/src/atom/matcher.rs index 22c874610..9246ee352 100644 --- a/lib/src/atom/matcher.rs +++ b/lib/src/atom/matcher.rs @@ -1116,6 +1116,13 @@ pub fn match_result_product(prev: MatchResultIter, next: MatchResultIter) -> Mat Box::new(prev.merge(&next).into_iter()) } +/// Applies bindings to atom and return it (see [apply_bindings_to_atom_mut]). +#[inline] +pub fn apply_bindings_to_atom_move(mut atom: Atom, bindings: &Bindings) -> Atom { + apply_bindings_to_atom_mut(&mut atom, bindings); + atom +} + /// Applies bindings to atom. Function replaces all variables in atom by /// corresponding bindings. /// @@ -1123,25 +1130,30 @@ pub fn match_result_product(prev: MatchResultIter, next: MatchResultIter) -> Mat /// /// ``` /// use hyperon::*; -/// use hyperon::atom::matcher::apply_bindings_to_atom; +/// use hyperon::atom::matcher::apply_bindings_to_atom_mut; /// /// let binds = bind!{ y: expr!("Y") }; -/// let atom = apply_bindings_to_atom(&expr!("+" "X" y), &binds); +/// let mut atom = expr!("+" "X" y); +/// apply_bindings_to_atom_mut(&mut atom, &binds); /// /// assert_eq!(atom, expr!("+" "X" "Y")); /// ``` -pub fn apply_bindings_to_atom(atom: &Atom, bindings: &Bindings) -> Atom { - let mut result = atom.clone(); +pub fn apply_bindings_to_atom_mut(atom: &mut Atom, bindings: &Bindings) { + let trace_atom = match log::log_enabled!(log::Level::Trace) { + true => Some(atom.clone()), + false => None, + }; if !bindings.is_empty() { - result.iter_mut().for_each(|atom| match atom { + atom.iter_mut().for_each(|atom| match atom { Atom::Variable(var) => { bindings.resolve(var).map(|value| *atom = value); }, _ => {}, }); } - log::trace!("apply_bindings_to_atom: {} | {} -> {}", atom, bindings, result); - result + if let Some(atom_copy) = trace_atom { + log::trace!("apply_bindings_to_atom: {} | {} -> {}", atom_copy, bindings, atom); + } } /// Applies bindings `from` to the each value from bindings `to`. diff --git a/lib/src/metta/interpreter.rs b/lib/src/metta/interpreter.rs index 12647ad88..2149c1ec4 100644 --- a/lib/src/metta/interpreter.rs +++ b/lib/src/metta/interpreter.rs @@ -250,7 +250,7 @@ impl InterpreterCache { fn insert(&mut self, key: Atom, mut value: Results) { value.iter_mut().for_each(|res| { let vars: HashSet<&VariableAtom> = key.iter().filter_type::<&VariableAtom>().collect(); - res.0 = apply_bindings_to_atom(&res.0, &res.1); + apply_bindings_to_atom_mut(&mut res.0, &res.1); res.1.retain(|v| vars.contains(v)); }); self.0.insert(key, value) @@ -362,7 +362,7 @@ fn interpret_as_type_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a fn cast_atom_to_type_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: InterpretedAtom, typ: Atom) -> StepResult<'a, Results, InterpreterError> { // TODO: implement this via interpreting of the (:cast atom typ) expression - let typ = apply_bindings_to_atom(&typ, input.bindings()); + let typ = apply_bindings_to_atom_move(typ, input.bindings()); let mut results = get_type_bindings(&context.space, input.atom(), &typ); log::debug!("cast_atom_to_type_plan: type check results: {:?}", results); if !results.is_empty() { @@ -373,7 +373,7 @@ fn cast_atom_to_type_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a // should we apply bindings to bindings? let bindings = Bindings::merge(&bindings, &typ_bindings); if let Some(bindings) = bindings { - let atom = apply_bindings_to_atom(&atom, &bindings); + let atom = apply_bindings_to_atom_move(atom, &bindings); Some(InterpretedAtom(atom, bindings)) } else { None @@ -446,7 +446,7 @@ fn interpret_expression_as_type_op<'a, T: SpaceRef<'a>>(context: InterpreterCont plan, OperatorPlan::new(move |results: Results| { make_alternives_plan(arg.clone(), results, move |result| -> NoInputPlan { - let arg_typ = apply_bindings_to_atom(&arg_typ, result.bindings()); + let arg_typ = apply_bindings_to_atom_move(arg_typ.clone(), result.bindings()); Box::new(SequencePlan::new( interpret_as_type_plan(context.clone(), InterpretedAtom(arg.clone(), result.bindings().clone()), @@ -500,7 +500,7 @@ fn insert_reducted_arg_op<'a>(expr: InterpretedAtom, atom_idx: usize, mut arg_va let InterpretedAtom(arg, bindings) = arg; let mut expr_with_arg = expr.atom().clone(); get_expr_mut(&mut expr_with_arg).children_mut()[atom_idx] = arg; - InterpretedAtom(apply_bindings_to_atom(&expr_with_arg, &bindings), bindings) + InterpretedAtom(apply_bindings_to_atom_move(expr_with_arg, &bindings), bindings) }).collect(); log::debug!("insert_reducted_arg_op: result: {:?}", result); StepResult::ret(result) @@ -626,7 +626,7 @@ fn match_op<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: I let results: Vec = query_bindings .drain(0..) .map(|query_binding| { - let result = apply_bindings_to_atom(&Atom::Variable(var_x.clone()), &query_binding); + let result = apply_bindings_to_atom_move(Atom::Variable(var_x.clone()), &query_binding); // TODO: sometimes we apply bindings twice: first time here, // second time when inserting matched argument into nesting // expression. It should be enough doing it only once. diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index 5a2a08147..dd37b617f 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -228,7 +228,7 @@ impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { let InterpretedAtom(stack, bindings) = atom; if stack.atom != EMPTY_SYMBOL { let bindings = bindings.convert_var_equalities_to_bindings(&self.vars); - let atom = apply_bindings_to_atom(&stack.atom, &bindings); + let atom = apply_bindings_to_atom_move(stack.atom, &bindings); self.finished.push(atom); } } else { @@ -513,7 +513,7 @@ fn query<'a, T: SpaceRef<'a>>(space: T, prev: Option>>, atom: results.len(), bindings.len(), results, bindings); results.into_iter() .flat_map(|b| { - let res = apply_bindings_to_atom(&atom_x, &b); + let res = apply_bindings_to_atom_move(atom_x.clone(), &b); let vars = Stack::add_vars_atom(&prev, &res); let stack = if is_function_op(&res) { let call = Stack::from_prev_add_vars(prev.clone(), atom.clone(), call_ret); @@ -593,8 +593,8 @@ fn chain(stack: Stack, bindings: Bindings) -> Vec { } }; let b = Bindings::new().add_var_binding_v2(var, nested).unwrap(); - let result = apply_bindings_to_atom(&templ, &b); - vec![InterpretedAtom(atom_to_stack(result, prev), bindings)] + let templ = apply_bindings_to_atom_move(templ, &b); + vec![InterpretedAtom(atom_to_stack(templ, prev), bindings)] } fn function_to_stack(mut atom: Atom, prev: Option>>) -> Stack { @@ -692,12 +692,12 @@ fn unify_to_stack(mut atom: Atom, prev: Option>>) -> Stack { fn unify(stack: Stack, bindings: Bindings) -> Vec { let Stack{ prev, atom: unify, ret: _, finished: _, vars: _ } = stack; - let (atom, pattern, then, else_) = match atom_as_slice(&unify) { - Some([_op, atom, pattern, then, else_]) => (atom, pattern, then, else_), + let (atom, pattern, then, else_) = match_atom!{ + unify ~ [_op, atom, pattern, then, else_] => (atom, pattern, then, else_), _ => { let error: String = format!("expected: ({} ), found: {}", UNIFY_SYMBOL, unify); return finished_result(error_atom(unify, error), bindings, prev); - }, + } }; // TODO: Should unify() be symmetrical or not. While it is symmetrical then @@ -705,13 +705,14 @@ fn unify(stack: Stack, bindings: Bindings) -> Vec { // priority. Thus interpreter can use any of them further. This sometimes // looks unexpected. For example see `metta_car` unit test where variable // from car's argument is replaced. - let matches: Vec = match_atoms(atom, pattern).collect(); + let matches: Vec = match_atoms(&atom, &pattern).collect(); if matches.is_empty() { - let vars = Stack::add_vars_it(&prev, vars_from_atom(else_)); + let vars = Stack::add_vars_it(&prev, vars_from_atom(&else_)); let bindings = bindings.narrow_vars(&vars); - let result = apply_bindings_to_atom(else_, &bindings); - finished_result(result, bindings, prev) + let else_ = apply_bindings_to_atom_move(else_, &bindings); + finished_result(else_, bindings, prev) } else { + let then = &then; let vars = Stack::add_vars_it(&prev, vars_from_atom(then)); matches.into_iter() .flat_map(move |b| { @@ -721,7 +722,7 @@ fn unify(stack: Stack, bindings: Bindings) -> Vec { if b.has_loops() { None } else { - let then = apply_bindings_to_atom(then, &b); + let then = apply_bindings_to_atom_move(then.clone(), &b); Some(InterpretedAtom(Stack::finished(prev.clone(), then), b)) } }) diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index e2aa62668..48b97783a 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -1234,7 +1234,7 @@ mod non_minimal_only_stdlib { for (pattern, template, external_vars) in cases { let bindings = matcher::match_atoms(atom, &pattern) .map(|b| b.convert_var_equalities_to_bindings(&external_vars)); - let result: Vec = bindings.map(|b| matcher::apply_bindings_to_atom(&template, &b)).collect(); + let result: Vec = bindings.map(|b| matcher::apply_bindings_to_atom_move(template.clone(), &b)).collect(); if !result.is_empty() { return result } @@ -1454,7 +1454,7 @@ mod non_minimal_only_stdlib { let bindings = matcher::match_atoms(&pattern, &atom) .map(|b| b.convert_var_equalities_to_bindings(&external_vars)); - let result = bindings.map(|b| { matcher::apply_bindings_to_atom(&template, &b) }).collect(); + let result = bindings.map(|b| { matcher::apply_bindings_to_atom_move(template.clone(), &b) }).collect(); log::debug!("LetOp::execute: pattern: {}, atom: {}, template: {}, result: {:?}", pattern, atom, template, result); Ok(result) } diff --git a/lib/src/metta/types.rs b/lib/src/metta/types.rs index 40245de2a..caed9cc0c 100644 --- a/lib/src/metta/types.rs +++ b/lib/src/metta/types.rs @@ -19,7 +19,7 @@ //! of `%Undefined%` type can be matched with any type required. use super::*; -use crate::atom::matcher::{Bindings, BindingsSet, apply_bindings_to_atom}; +use crate::atom::matcher::{Bindings, BindingsSet, apply_bindings_to_atom_move}; use crate::space::Space; fn typeof_query(atom: &Atom, typ: &Atom) -> Atom { @@ -39,7 +39,7 @@ fn query_super_types(space: &dyn Space, sub_type: &Atom) -> Vec { let var_x = VariableAtom::new("X").make_unique(); let mut super_types = space.query(&isa_query(&sub_type, &Atom::Variable(var_x.clone()))); let atom_x = Atom::Variable(var_x); - super_types.drain(0..).map(|bindings| { apply_bindings_to_atom(&atom_x, &bindings) }).collect() + super_types.drain(0..).map(|bindings| { apply_bindings_to_atom_move(atom_x.clone(), &bindings) }).collect() } fn add_super_types(space: &dyn Space, sub_types: &mut Vec, from: usize) { @@ -108,7 +108,7 @@ fn query_types(space: &dyn Space, atom: &Atom) -> Vec { let mut types = query_has_type(space, atom, &Atom::Variable(var_x.clone())); let atom_x = Atom::Variable(var_x); let mut types = types.drain(0..).filter_map(|bindings| { - let atom = apply_bindings_to_atom(&atom_x, &bindings); + let atom = apply_bindings_to_atom_move(atom_x.clone(), &bindings); if atom_x == atom { None } else { @@ -286,7 +286,7 @@ fn get_application_types(space: &dyn Space, atom: &Atom, expr: &ExpressionAtom) has_function_types = true; let (expected_arg_types, ret_typ) = get_arg_types(&fn_type); for bindings in check_arg_types(actual_arg_types.as_slice(), meta_arg_types.as_slice(), expected_arg_types, Bindings::new()) { - types.push(apply_bindings_to_atom(&ret_typ, &bindings)); + types.push(apply_bindings_to_atom_move(ret_typ.clone(), &bindings)); } } log::trace!("get_application_types: function application {} types {:?}", atom, types); diff --git a/lib/src/space/grounding.rs b/lib/src/space/grounding.rs index 39a759fc2..b4308e840 100644 --- a/lib/src/space/grounding.rs +++ b/lib/src/space/grounding.rs @@ -253,7 +253,7 @@ impl GroundingSpace { acc } else { acc.drain(0..).flat_map(|prev| -> BindingsSet { - let query = matcher::apply_bindings_to_atom(&query, &prev); + let query = matcher::apply_bindings_to_atom_move(query.clone(), &prev); let mut res = self.query(&query); res.drain(0..) .flat_map(|next| next.merge_v2(&prev)) diff --git a/lib/src/space/mod.rs b/lib/src/space/mod.rs index b760154aa..e6d8f5fcb 100644 --- a/lib/src/space/mod.rs +++ b/lib/src/space/mod.rs @@ -9,7 +9,7 @@ use std::cell::{RefCell, Ref, RefMut}; use crate::common::FlexRef; use crate::atom::*; -use crate::atom::matcher::{BindingsSet, apply_bindings_to_atom}; +use crate::atom::matcher::{BindingsSet, apply_bindings_to_atom_move}; /// Contains information about space modification event. #[derive(Clone, Debug, PartialEq)] @@ -193,7 +193,7 @@ pub trait Space: std::fmt::Debug + std::fmt::Display { /// ``` fn subst(&self, pattern: &Atom, template: &Atom) -> Vec { self.query(pattern).drain(0..) - .map(| bindings | apply_bindings_to_atom(template, &bindings)) + .map(| bindings | apply_bindings_to_atom_move(template.clone(), &bindings)) .collect() } From cb63a1257c8d16de06594d5d659ad63c96981d4a Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 3 May 2024 06:23:50 +0300 Subject: [PATCH 28/39] Remove superfluous lifetime specifier in InterpreterContext --- lib/src/metta/interpreter_minimal.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index dd37b617f..33a99a1f9 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -13,7 +13,6 @@ use std::fmt::{Debug, Display, Formatter}; use std::convert::TryFrom; use std::collections::HashSet; use std::rc::Rc; -use std::marker::PhantomData; use std::fmt::Write; use std::cell::RefCell; @@ -159,27 +158,28 @@ impl Display for InterpretedAtom { } } -pub trait SpaceRef<'a> : Space + 'a {} -impl<'a, T: Space + 'a> SpaceRef<'a> for T {} - #[derive(Debug)] -struct InterpreterContext<'a, T: SpaceRef<'a>> { +struct InterpreterContext { space: T, - phantom: PhantomData>, } -impl<'a, T: SpaceRef<'a>> InterpreterContext<'a, T> { +impl InterpreterContext { fn new(space: T) -> Self { - Self{ space, phantom: PhantomData } + Self{ space } } } +// TODO: This wrapper is for compatibility with interpreter.rs only +pub trait SpaceRef<'a> : Space + 'a {} +impl<'a, T: Space + 'a> SpaceRef<'a> for T {} + #[derive(Debug)] pub struct InterpreterState<'a, T: SpaceRef<'a>> { plan: Vec, finished: Vec, - context: InterpreterContext<'a, T>, + context: InterpreterContext, vars: HashSet, + phantom: std::marker::PhantomData>, } fn atom_as_slice(atom: &Atom) -> Option<&[Atom]> { @@ -204,6 +204,7 @@ impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { finished: results, context: InterpreterContext::new(space), vars: HashSet::new(), + phantom: std::marker::PhantomData, } } @@ -257,6 +258,7 @@ pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterSt finished: vec![], context, vars: expr.iter().filter_type::<&VariableAtom>().cloned().collect(), + phantom: std::marker::PhantomData, } } @@ -366,7 +368,7 @@ impl Display for Variables { } } -fn interpret_stack<'a, T: SpaceRef<'a>>(context: &InterpreterContext<'a, T>, mut stack: Stack, bindings: Bindings) -> Vec { +fn interpret_stack<'a, T: Space>(context: &InterpreterContext, mut stack: Stack, bindings: Bindings) -> Vec { if stack.finished { // first executed minimal operation returned error if stack.prev.is_none() { @@ -428,7 +430,7 @@ fn finished_result(atom: Atom, bindings: Bindings, prev: Option>(context: &InterpreterContext<'a, T>, stack: Stack, bindings: Bindings) -> Vec { +fn eval<'a, T: Space>(context: &InterpreterContext, stack: Stack, bindings: Bindings) -> Vec { let Stack{ prev, atom: eval, ret: _, finished: _, vars: _} = stack; let query_atom = match_atom!{ eval ~ [_op, query] => query, @@ -495,7 +497,7 @@ fn is_variable_op(atom: &Atom) -> bool { } } -fn query<'a, T: SpaceRef<'a>>(space: T, prev: Option>>, atom: Atom, bindings: Bindings) -> Vec { +fn query<'a, T: Space>(space: T, prev: Option>>, atom: Atom, bindings: Bindings) -> Vec { #[cfg(not(feature = "variable_operation"))] if is_variable_op(&atom) { // TODO: This is a hotfix. Better way of doing this is adding @@ -1155,7 +1157,7 @@ mod tests { metta_space(text) } - fn call_interpret<'a, T: SpaceRef<'a>>(space: T, atom: &Atom) -> Vec { + fn call_interpret<'a, T: Space>(space: T, atom: &Atom) -> Vec { let result = interpret(space, atom); assert!(result.is_ok()); result.unwrap() From d8f972a4d2efed498e41ea799b0474d7a51704b5 Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Fri, 3 May 2024 19:28:16 +0300 Subject: [PATCH 29/39] Fix type for add-reduct and fix doc for quote --- lib/src/metta/runner/stdlib.metta | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index a1b767108..430895e01 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -35,10 +35,10 @@ (@param "Atomspace to add atom into") (@param "Atom to add"))) (@return "Unit atom")) -(: add-reduct (-> Space %Undefined% (->))) -(= (add-reduct $dst $atom) (add-atom $dst $atom)) +(: add-reduct (-> hyperon::space::DynSpace %Undefined% (->))) +(= (add-reduct $dst $atom) (add-atom $dst $atom)) -(@doc add-reduct +(@doc quote (@desc "Prevents atom from being reduced") (@params ( (@param "Atom"))) From 26d17800ec260bac246148fbf3015ff1e0789f11 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Mon, 6 May 2024 13:25:43 +0900 Subject: [PATCH 30/39] Changing return type of `include` op atom, to match old `import` op from before module isolation --- lib/src/metta/runner/stdlib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index e2aa62668..019258446 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -195,13 +195,16 @@ impl Grounded for IncludeOp { let program_text = String::from_utf8(program_buf) .map_err(|e| e.to_string())?; let parser = crate::metta::text::OwnedSExprParser::new(program_text); - //QUESTION: do we want to do anything with the result from the `include` operation? - let _eval_result = context.run_inline(|context| { + let eval_result = context.run_inline(|context| { context.push_parser(Box::new(parser)); Ok(()) })?; - unit_result() + //NOTE: Current behavior returns the result of the last sub-eval to match the old + // `import!` before before module isolation. However that means the results prior to + // the last are dropped. I don't know how to fix this or if it's even wrong, but it's + // different from the way "eval-type" APIs work when called from host code, e.g. Rust + Ok(eval_result.into_iter().last().unwrap_or_else(|| vec![])) } fn match_(&self, other: &Atom) -> MatchResultIter { From 93e4fb783c29d3acf0fc24838261d12ce90c5c1d Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Mon, 6 May 2024 13:41:28 +0300 Subject: [PATCH 31/39] fix the return type of modspace --- lib/src/metta/runner/stdlib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index 1be3dc097..72368b37c 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -241,7 +241,7 @@ impl Display for ModSpaceOp { impl Grounded for ModSpaceOp { fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE()]) + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, rust_type_atom::()]) } fn execute(&self, args: &[Atom]) -> Result, ExecError> { From 102838c1648cc8f83c027a78683f8e201531c6d3 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Mon, 6 May 2024 20:19:18 +0900 Subject: [PATCH 32/39] Oops. Forgot to update return type for `include` op with last change --- lib/src/metta/runner/stdlib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index 1be3dc097..48921ce9b 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -172,7 +172,7 @@ impl Display for IncludeOp { impl Grounded for IncludeOp { fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE()]) + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) } fn execute(&self, args: &[Atom]) -> Result, ExecError> { From 0fb01df16d6794db22477c34d582c3d6a5347dac Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Mon, 6 May 2024 23:28:08 +0300 Subject: [PATCH 33/39] %void% -> Empty --- lib/src/metta/runner/stdlib.metta | 2 +- lib/src/metta/runner/stdlib.rs | 8 +++----- lib/src/metta/runner/stdlib_minimal.rs | 10 ++++------ lib/tests/case.rs | 4 ++-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 430895e01..7217e243c 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -50,7 +50,7 @@ (: unify (-> Atom Atom Atom Atom %Undefined%)) (= (unify $a $a $then $else) $then) (= (unify $a $b $then $else) - (case (unify-or-empty $a $b) ((%void% $else))) ) + (case (unify-or-empty $a $b) ((Empty $else))) ) (: unify-or-empty (-> Atom Atom Atom)) (= (unify-or-empty $a $a) unified) (= (unify-or-empty $a $b) (empty)) diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index 3b8335a8c..b10a21fb2 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -18,8 +18,6 @@ use regex::Regex; use super::arithmetics::*; use super::string::*; -pub const VOID_SYMBOL : Atom = sym!("%void%"); - fn unit_result() -> Result, ExecError> { Ok(vec![UNIT_ATOM()]) } @@ -1187,7 +1185,7 @@ mod non_minimal_only_stdlib { Ok(result) if result.is_empty() => { cases.into_iter() .find_map(|(pattern, template, _external_vars)| { - if pattern == VOID_SYMBOL { + if pattern == EMPTY_SYMBOL { Some(template) } else { None @@ -1864,10 +1862,10 @@ mod tests { let case_op = CaseOp::new(space.clone()); assert_eq!(case_op.execute(&mut vec![expr!(("foo")), - expr!(((n "B") n) ("%void%" "D"))]), + expr!(((n "B") n) ("Empty" "D"))]), Ok(vec![Atom::sym("A")])); assert_eq!(case_op.execute(&mut vec![expr!({MatchOp{}} {space} ("B" "C") ("C" "B")), - expr!(((n "C") n) ("%void%" "D"))]), + expr!(((n "C") n) ("Empty" "D"))]), Ok(vec![Atom::sym("D")])); } diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index c64af4637..8573bb4a9 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -16,8 +16,6 @@ use std::convert::TryInto; use super::arithmetics::*; use super::string::*; -pub const VOID_SYMBOL : Atom = sym!("%void%"); - fn unit_result() -> Result, ExecError> { Ok(vec![UNIT_ATOM()]) } @@ -412,7 +410,7 @@ impl Grounded for CaseOp { Ok(results) if results.is_empty() => // TODO: MINIMAL in minimal MeTTa we should use Empty in both // places here and in (case ...) calls in code - vec![switch(VOID_SYMBOL)], + vec![switch(EMPTY_SYMBOL)], Ok(results) => results.into_iter().map(|atom| switch(atom)).collect(), Err(err) => vec![Atom::expr([ERROR_SYMBOL, atom.clone(), Atom::sym(err)])], @@ -633,13 +631,13 @@ mod tests { #[test] fn metta_case_empty() { - let result = run_program("!(case Empty ( (ok ok) (%void% nok) ))"); + let result = run_program("!(case Empty ( (ok ok) (Empty nok) ))"); assert_eq!(result, Ok(vec![vec![expr!("nok")]])); - let result = run_program("!(case (unify (C B) (C B) ok Empty) ( (ok ok) (%void% nok) ))"); + let result = run_program("!(case (unify (C B) (C B) ok Empty) ( (ok ok) (Empty nok) ))"); assert_eq!(result, Ok(vec![vec![expr!("ok")]])); let result = run_program("!(case (unify (B C) (C B) ok nok) ( (ok ok) (nok nok) ))"); assert_eq!(result, Ok(vec![vec![expr!("nok")]])); - let result = run_program("!(case (unify (B C) (C B) ok Empty) ( (ok ok) (%void% nok) ))"); + let result = run_program("!(case (unify (B C) (C B) ok Empty) ( (ok ok) (Empty nok) ))"); assert_eq!(result, Ok(vec![vec![expr!("nok")]])); } diff --git a/lib/tests/case.rs b/lib/tests/case.rs index 8c06219fd..02cfedf99 100644 --- a/lib/tests/case.rs +++ b/lib/tests/case.rs @@ -49,11 +49,11 @@ fn test_case_operation() { (((Rel-P $y) (P $y)) ((Rel-Q $y) (Q $y)))) - ; %void% can be used to capture empty results + ; Empty can be used to capture empty results !(case (match &self ($rel B $x) ($rel $x)) (((Rel-P $y) (P $y)) ((Rel-Q $y) (Q $y)) - (%void% no-match))) + (Empty no-match))) ; a functional example (= (maybe-inc $x) From a9df6020920754b332ad83a2ebb534a483df5d01 Mon Sep 17 00:00:00 2001 From: Alexey Potapov Date: Tue, 7 May 2024 11:39:12 +0300 Subject: [PATCH 34/39] remove comment --- lib/src/metta/runner/stdlib_minimal.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 8573bb4a9..08f6feb63 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -408,8 +408,6 @@ impl Grounded for CaseOp { log::debug!("CaseOp::execute: atom results: {:?}", results); let results = match results { Ok(results) if results.is_empty() => - // TODO: MINIMAL in minimal MeTTa we should use Empty in both - // places here and in (case ...) calls in code vec![switch(EMPTY_SYMBOL)], Ok(results) => results.into_iter().map(|atom| switch(atom)).collect(), From f4e908594fdeb3a89aa677ef404684218aab40f1 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Tue, 7 May 2024 14:11:22 +0300 Subject: [PATCH 35/39] Version to 0.1.10 --- Cargo.toml | 4 ++-- python/VERSION | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2aec01ca2..68e91e6ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,11 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.9" +version = "0.1.10" edition = "2021" [workspace.dependencies] -hyperon = { path = "./lib", version = "0.1.9" } +hyperon = { path = "./lib", version = "0.1.10" } regex = "1.5.4" log = "0.4.0" env_logger = "0.8.4" diff --git a/python/VERSION b/python/VERSION index 1f52868ad..df879447c 100644 --- a/python/VERSION +++ b/python/VERSION @@ -1 +1 @@ -'0.1.9' +'0.1.10' From 682b0b2ec08a759c4c37119c08e6cd36750d0574 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 8 May 2024 11:34:25 +0300 Subject: [PATCH 36/39] Add section on using release from PyPi --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index da5cf2116..a27fb54d6 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,15 @@ If you want to contribute the project please see the [contribution guide](./docs If you find troubles with the installation, see the [Troubleshooting](#troubleshooting) section below. For development related instructions see the [development guide](./docs/DEVELOPMENT.md). -# Prepare environment +# Using the latest release version + +It is the most simple way of getting MeTTa interpreter especially if you are a Python developer. +The following command installs the latest release version from PyPi package repository: +``` +python3 -m pip install hyperon +``` + +# Prepare development environment ## Docker From aa1ee384aad84c0a2f3e0fecf280326066b2083e Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 8 May 2024 11:43:20 +0300 Subject: [PATCH 37/39] Add instruction on running Docker image from Dockerhub --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index a27fb54d6..47377c7d4 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,15 @@ environment. Please keep in mind that resulting image contains temporary build files and takes a lot of a disk space. It is not recommended to distribute it as an image for running MeTTa because of its size. +### Ready to use image + +Run latest docker image from the Dockerhub: +``` +docker run -ti trueagi/hyperon:latest +``` + +### Build image + Build Docker image from a local copy of the repo running: ``` docker build -t trueagi/hyperon . From 19e3ad9bae56dbfd0cdeb5c5599f0311bfcafc0c Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 8 May 2024 11:46:24 +0300 Subject: [PATCH 38/39] Add Docker version requirements --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 47377c7d4..955e32fb6 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ docker run -ti trueagi/hyperon:latest ### Build image +Docker 26.0.0 or greater version is required. + Build Docker image from a local copy of the repo running: ``` docker build -t trueagi/hyperon . From 2ac1c161c76a5ae12c6d090ea3caa73590daffb3 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 8 May 2024 13:19:26 +0300 Subject: [PATCH 39/39] Fix incorrect number of arguments type error constant --- lib/src/metta/runner/stdlib_minimal.metta | 4 +-- lib/src/metta/runner/stdlib_minimal.rs | 35 +++++++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/src/metta/runner/stdlib_minimal.metta b/lib/src/metta/runner/stdlib_minimal.metta index b40442332..0c5061e9f 100644 --- a/lib/src/metta/runner/stdlib_minimal.metta +++ b/lib/src/metta/runner/stdlib_minimal.metta @@ -212,8 +212,8 @@ (eval (match-types $actual-ret-type $ret-type (return ()) (return (Error $atom BadType)) )) - (return (Error $atom BadType)) ))) - (return (Error $atom "Too many arguments")) )) + (return (Error $atom IncorrectNumberOfArguments)) ))) + (return (Error $atom IncorrectNumberOfArguments)) )) (eval (if-decons-expr $args $head $tail (eval (if-decons-expr $arg-types $head-type $tail-types (chain (eval (interpret $head $head-type $space)) $reduced-head diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 08f6feb63..54bcba1f6 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -753,11 +753,6 @@ mod tests { !(eval (interpret (Cons S (Cons Z Nil)) %Undefined% &self)) "); assert_eq!(result, Ok(vec![vec![expr!("Error" ("Cons" "Z" "Nil") "BadType")]])); - let result = run_program(" - (: foo (-> Atom Atom Atom)) - !(eval (interpret (foo A) %Undefined% &self)) - "); - assert_eq!(result, Ok(vec![vec![expr!("Error" ("foo" "A") "BadType")]])); } #[test] @@ -1038,6 +1033,36 @@ mod tests { Ok(vec![vec![expr!("Error" "myAtom" "BadType")]])); } + #[test] + fn test_return_incorrect_number_of_args_error() { + let program1 = " + (: a A) + (: b B) + (: c C) + (: foo (-> A B C)) + (= (foo $a $b) c) + + !(eval (interpret (foo a b) %Undefined% &self)) + "; + + let metta = Metta::new(Some(EnvBuilder::test_env())); + metta.tokenizer().borrow_mut().register_token(Regex::new("id_num").unwrap(), + |_| Atom::gnd(ID_NUM)); + + assert_eq!(metta.run(SExprParser::new(program1)), + Ok(vec![vec![expr!("c")]])); + + let program2 = "!(eval (interpret (foo a) %Undefined% &self))"; + + assert_eq!(metta.run(SExprParser::new(program2)), + Ok(vec![vec![expr!("Error" ("foo" "a") "IncorrectNumberOfArguments")]])); + + let program3 = "!(eval (interpret (foo a b c) %Undefined% &self))"; + + assert_eq!(metta.run(SExprParser::new(program3)), + Ok(vec![vec![expr!("Error" ("foo" "a" "b" "c") "IncorrectNumberOfArguments")]])); + } + #[test] fn use_sealed_to_make_scoped_variable() { assert_eq!(run_program("!(let $x (input $x) (output $x))"), Ok(vec![vec![]]));