diff --git a/Makefile b/Makefile index c23de71..f6a6834 100644 --- a/Makefile +++ b/Makefile @@ -14,3 +14,4 @@ clean: rm -rf tests/prof find . -name '.pytest_cache' -exec rm -rf {} + find . -name '__pycache__' -exec rm -rf {} + + find . -name 'debug.txt' -exec rm -rf {} + diff --git a/_concepts.txt b/_concepts.txt index 53049b4..2f01974 100644 --- a/_concepts.txt +++ b/_concepts.txt @@ -82,3 +82,5 @@ last_created_concept() is number def concept history as history() def concept plus from a plus b as a + b def concept mult from a mult b as a * b +def concept explain as get_results() | filter("id == 0") | recurse(2) +def concept explain last as get_last_results() | filter("id == 0") | recurse(2) diff --git a/_concepts_lite.txt b/_concepts_lite.txt new file mode 100644 index 0000000..47688a2 --- /dev/null +++ b/_concepts_lite.txt @@ -0,0 +1,5 @@ +def concept one as 1 +def concept two as 2 +def concept plus from a plus b as a + b +def concept explain as get_results() | filter("id == 0") | recurse(2) +def concept explain last as get_last_results() | filter("id == 0") | recurse(2) \ No newline at end of file diff --git a/src/core/sheerka/services/SheerkaAdmin.py b/src/core/sheerka/services/SheerkaAdmin.py index 5df245b..73a91fa 100644 --- a/src/core/sheerka/services/SheerkaAdmin.py +++ b/src/core/sheerka/services/SheerkaAdmin.py @@ -3,7 +3,8 @@ import time from core.builtin_concepts import BuiltinConcepts from core.sheerka.services.sheerka_service import BaseService -CONCEPTS_FILE = "_concepts.txt" +CONCEPTS_FILE = "_concepts_lite.txt" +CONCEPTS_FILE_ALL_CONCEPTS = "_concepts.txt" class SheerkaAdmin(BaseService): @@ -37,15 +38,19 @@ class SheerkaAdmin(BaseService): return self.sheerka.cache_manager.caches[name].cache.copy() - def restore(self): + def restore(self, concept_file=CONCEPTS_FILE): """ Restore the state with all previous valid concept definitions :return: """ + + if concept_file == "full": + concept_file = CONCEPTS_FILE_ALL_CONCEPTS + try: start = time.time_ns() self.sheerka.during_restore = True - with open(CONCEPTS_FILE, "r") as f: + with open(concept_file, "r") as f: for line in f.readlines(): line = line.strip() if line == "" or line.startswith("#"): diff --git a/src/core/sheerka/services/SheerkaEvaluateConcept.py b/src/core/sheerka/services/SheerkaEvaluateConcept.py index 4049128..5518db1 100644 --- a/src/core/sheerka/services/SheerkaEvaluateConcept.py +++ b/src/core/sheerka/services/SheerkaEvaluateConcept.py @@ -43,6 +43,16 @@ class SheerkaEvaluateConcept(BaseService): return None + @staticmethod + def apply_ret(concept): + """ + Check if a concept has its RET part defined + If True, returns it + :param concept: + :return: + """ + return concept.get_value(ConceptParts.RET) if ConceptParts.RET in concept.values else concept + def manage_infinite_recursion(self, context): """ We look for the fist parent that has a body that means something @@ -189,7 +199,7 @@ class SheerkaEvaluateConcept(BaseService): evaluated = self.evaluate_concept(sub_context, to_resolve) sub_context.add_values(return_values=evaluated) if evaluated.key == to_resolve.key: - return evaluated + return self.apply_ret(evaluated) else: error = evaluated @@ -324,9 +334,9 @@ class SheerkaEvaluateConcept(BaseService): concept.init_key() # only does it if needed concept.metadata.is_evaluated = "body" in all_metadata_to_eval - # # update the cache for concepts with no variable - # if len(concept.metadata.variables) == 0: - # self.sheerka.cache_manager.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept) + # update the cache for concepts with no variable + if len(concept.metadata.variables) == 0: + self.sheerka.cache_manager.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept) return concept diff --git a/src/core/sheerka/services/SheerkaFilter.py b/src/core/sheerka/services/SheerkaFilter.py index cc6e57e..d9ad59f 100644 --- a/src/core/sheerka/services/SheerkaFilter.py +++ b/src/core/sheerka/services/SheerkaFilter.py @@ -434,11 +434,22 @@ class SheerkaFilter(BaseService): pass def pipe_inspect(self, iterable, path, when=None): - compiled = self.get_compiled("inspect", path) + """ + Follow the path + :param iterable: + :param path: + :param when: + :return: + """ + # quick and dirty implementation as it does not handle dictionaries items for item in iterable: try: - context = {} if is_primitive(item) else as_bag(item) - context["self"] = item - yield eval(compiled, context) + props = path.split(".") + for prop in props: + compiled = self.get_compiled("inspect", prop) + context = {} if is_primitive(item) else as_bag(item) + item = eval(compiled, context) + + yield item except Exception as ex: yield ex diff --git a/src/core/utils.py b/src/core/utils.py index e6e6a9f..f1416cf 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -6,6 +6,16 @@ import re from core.tokenizer import TokenKind +def my_debug(*args): + with open("debug.txt", "a") as f: + for arg in args: + if isinstance(arg, list): + for item in arg: + f.write(f"{item}\n") + else: + f.write(f"{arg}\n") + + def sysarg_to_string(argv): """ Transform a list of strings into a single string diff --git a/src/evaluators/AddConceptEvaluator.py b/src/evaluators/AddConceptEvaluator.py index 64aa3b6..260b769 100644 --- a/src/evaluators/AddConceptEvaluator.py +++ b/src/evaluators/AddConceptEvaluator.py @@ -83,11 +83,12 @@ class AddConceptEvaluator(OneReturnValueEvaluator): props_found.add(p) # add variables by order of appearance when possible - for token in def_concept_node.name.tokens: - if token.value in props_found: - concept.def_var(token.value, None) + for name_part in name_to_use: + if name_part in props_found: + concept.def_var(name_part, None) # add the remaining properties + # They mainly come from BNF definition for p in props_found: if p not in concept.values: concept.def_var(p, None) @@ -112,7 +113,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator): @staticmethod def get_name_to_use(node): source = node.definition if node.definition_type == DEFINITION_TYPE_DEF else node.name - return [part.value for part in core.utils.strip_tokens(source.tokens, True)] + return [part.str_value for part in core.utils.strip_tokens(source.tokens, True)] @staticmethod def get_variables(sheerka, ret_value, concept_name): diff --git a/src/parsers/BaseNodeParser.py b/src/parsers/BaseNodeParser.py index 768bd9f..5a99a4d 100644 --- a/src/parsers/BaseNodeParser.py +++ b/src/parsers/BaseNodeParser.py @@ -196,6 +196,19 @@ class ConceptNode(LexerNode): clone = ConceptNode(self.concept, self.start, self.end, self.tokens, self.source, self.underlying) return clone + def as_bag(self): + """ + Creates a dictionary with the useful properties of the ConceptNode + see Concept.as_bag() for extra informations + """ + bag = {} + for k, v in self.__dict__.items(): + bag[k] = v + + # if isinstance(self.concept, Concept): + # bag["compiled"] = self.concept.compiled + return bag + class SourceCodeNode(LexerNode): """ @@ -528,7 +541,7 @@ class CNC(CN): to_compare = {k: v for k, v in other.concept.compiled.items() if k != ConceptParts.BODY} else: to_compare = other.concept.compiled - if self.compiled == to_compare: # expanded form to ease the debug + if self.compiled == to_compare: # expanded form to ease the debug return True else: return False diff --git a/src/parsers/BnfNodeParser.py b/src/parsers/BnfNodeParser.py index affa1da..244d78c 100644 --- a/src/parsers/BnfNodeParser.py +++ b/src/parsers/BnfNodeParser.py @@ -23,14 +23,6 @@ from parsers.BaseParser import BaseParser PARSERS = ["AtomNode", "SyaNode", "Python"] -# def debug(obj): -# with open("debug.txt", "a") as f: -# f.write(f"{obj}\n") - -def debug(obj): - pass - - @dataclass class ParsingContext: """ @@ -185,7 +177,6 @@ class ParsingExpression: return hash((self.rule_name, self.elements)) def parse(self, parser): - debug(self) # TODO : add memoization return self._parse(parser) @@ -590,7 +581,6 @@ class StrMatch(Match): parser_helper.next_token(self.skip_white_space) return node - debug(f"Failed to match {self}. {token=}") return None @@ -811,7 +801,6 @@ class BnfConceptParserHelper: self.token = self.parser.parser_input.tokens[self.pos] # parse - debug(f"parsing {parsing_expression} against '{self.parser.parser_input.text}'") node = parsing_expression.parse(self) if isinstance(node, MultiNode): diff --git a/tests/core/test_SheerkaFilter.py b/tests/core/test_SheerkaFilter.py index de98f88..9ea4315 100644 --- a/tests/core/test_SheerkaFilter.py +++ b/tests/core/test_SheerkaFilter.py @@ -17,7 +17,7 @@ class Obj: @dataclass class ObjWithAsBag: prop1: str - prop2: str + prop2: object def as_bag(self): return { @@ -227,3 +227,7 @@ class TestSheerkaFilter(TestUsingMemoryBasedSheerka): res = lst | Pipe(filter_service.pipe_inspect)("second_prop") assert list(res) == ["b", "d"] + + lst = [ObjWithAsBag("a", ObjWithAsBag("b", ObjWithAsBag("c", "d")))] + res = lst | Pipe(filter_service.pipe_inspect)("second_prop.second_prop.second_prop") + assert list(res) == ["d"] diff --git a/tests/evaluators/test_AddConceptEvaluator.py b/tests/evaluators/test_AddConceptEvaluator.py index 9226cb2..9d523e5 100644 --- a/tests/evaluators/test_AddConceptEvaluator.py +++ b/tests/evaluators/test_AddConceptEvaluator.py @@ -66,6 +66,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka): def_concept.post = self.get_concept_part(post) if ret: def_concept.ret = self.get_concept_part(ret) + if bnf_def: def_concept.definition = bnf_def def_concept.definition_type = DEFINITION_TYPE_BNF @@ -174,6 +175,15 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka): assert AddConceptEvaluator.get_variables(context.sheerka, ret_val, ["a", "b"]) == ["a"] + def test_i_can_get_variables_when_keywords(self): + sheerka, context = self.init_concepts() + + def_concept = self.get_def_concept("condition pre").value.value + name_to_use = AddConceptEvaluator.get_name_to_use(def_concept) + concept_part = self.get_concept_part("pre") + + assert AddConceptEvaluator.get_variables(context.sheerka, concept_part, name_to_use) == ["pre"] + def test_i_cannot_get_variables_from_python_node_when_name_has_only_one_token(self): ret_val = self.get_concept_part("isinstance(a, str)") context = self.get_context() diff --git a/tests/evaluators/test_RetEvaluator.py b/tests/evaluators/test_RetEvaluator.py index f247345..2862c08 100644 --- a/tests/evaluators/test_RetEvaluator.py +++ b/tests/evaluators/test_RetEvaluator.py @@ -44,3 +44,4 @@ class TestRetEvaluator(TestUsingMemoryBasedSheerka): res = RetEvaluator().eval(context, ret_value) assert res.status assert sheerka.isinstance(res.body, expected) + diff --git a/tests/non_reg/test_sheerka_non_reg.py b/tests/non_reg/test_sheerka_non_reg.py index 19ee9ba..dd21d53 100644 --- a/tests/non_reg/test_sheerka_non_reg.py +++ b/tests/non_reg/test_sheerka_non_reg.py @@ -1,6 +1,6 @@ import pytest from core.builtin_concepts import BuiltinConcepts -from core.concept import Concept, PROPERTIES_TO_SERIALIZE, simplec, CMV, NotInit +from core.concept import Concept, PROPERTIES_TO_SERIALIZE, simplec, CMV, NotInit, CC from evaluators.MutipleSameSuccessEvaluator import MultipleSameSuccessEvaluator from evaluators.PythonEvaluator import PythonEvalError from parsers.BaseNodeParser import SyaAssociativity @@ -976,6 +976,28 @@ as: assert res[0].status assert sheerka.isa(sheerka.new("one"), sheerka.new("number")) + def test_i_can_evaluate_sya_and_ret_concepts(self): + init = [ + "def concept one as 1", + "def concept plus from a plus b as a + b", + "def concept the a ret a" + ] + + sheerka = self.init_scenario(init) + the = sheerka.get_by_name("the a") + + # res = sheerka.evaluate_user_input("one plus the one") + # assert res[0].status + # plus = res[0].body + # assert isinstance(plus, Concept) + # assert plus.name == "plus" + # assert plus.compiled["a"] == sheerka.new("one") + # assert plus.compiled["b"] == CC(the, a=sheerka.new("one")) + + res = sheerka.evaluate_user_input("eval one plus the one") + assert res[0].status + assert res[0].body == 2 + class TestSheerkaNonRegFile(TestUsingFileBasedSheerka): def test_i_can_def_several_concepts(self):