From c4399d631c1c8b22500588c42a147c5b75aa3476 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Tue, 7 Jul 2020 11:34:40 +0200 Subject: [PATCH] Added RET keyword --- src/core/builtin_concepts.py | 2 + src/core/builtin_helpers.py | 11 ++-- src/core/concept.py | 6 ++- src/core/profiling.py | 4 +- src/core/sheerka/Sheerka.py | 3 +- src/core/sheerka/services/SheerkaDump.py | 50 +++++++------------ .../services/SheerkaEvaluateConcept.py | 44 +++++++++++----- src/core/sheerka/services/SheerkaExecute.py | 4 +- src/core/tokenizer.py | 1 + src/evaluators/AddConceptEvaluator.py | 2 +- src/evaluators/PythonEvaluator.py | 6 ++- src/evaluators/RetEvaluator.py | 40 +++++++++++++++ src/parsers/DefaultParser.py | 12 ++++- src/parsers/UnrecognizedNodeParser.py | 2 +- src/sheerkapickle/SheerkaPickler.py | 15 ++++-- tests/core/test_concept.py | 2 + tests/evaluators/test_AddConceptEvaluator.py | 13 +++-- tests/evaluators/test_PythonEvaluator.py | 38 +++++++++++--- tests/evaluators/test_RetEvaluator.py | 46 +++++++++++++++++ tests/non_reg/test_sheerka_non_reg.py | 4 +- tests/parsers/test_DefaultParser.py | 27 ++++++---- 21 files changed, 245 insertions(+), 87 deletions(-) create mode 100644 src/evaluators/RetEvaluator.py create mode 100644 tests/evaluators/test_RetEvaluator.py diff --git a/src/core/builtin_concepts.py b/src/core/builtin_concepts.py index d509a44..0286b53 100644 --- a/src/core/builtin_concepts.py +++ b/src/core/builtin_concepts.py @@ -68,6 +68,7 @@ class BuiltinConcepts(Enum): EVAL_WHERE_REQUESTED = "eval where requested" # to evaluate the where clause CONCEPT_VALUE_REQUESTED = "concept value requested" # returns the body of the concept instead of the concept itself REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible + EVAL_SUCCESS_REQUESTED = "Try to find a successful evaluation" # PyhtonEvaluator tries combination until True is found NOT_A_SET = "not a set" # the concept has no entry in sets WHERE_CLAUSE_FAILED = "where clause failed" # failed to validate where clause during evaluation CHICKEN_AND_EGG = "chicken and egg" # infinite recursion when declaring concept @@ -78,6 +79,7 @@ class BuiltinConcepts(Enum): NOT_INITIALIZED = "not initialized" NOT_FOUND = "not found" # when the wanted resource is not found FORMAT_INSTRUCTIONS = "format instructions" # to express how to print the concept + NOT_IMPLEMENTED = "not implemented" # instead of raise an error NODE = "node" GENERIC_NODE = "generic node" diff --git a/src/core/builtin_helpers.py b/src/core/builtin_helpers.py index 8072749..7f7d931 100644 --- a/src/core/builtin_helpers.py +++ b/src/core/builtin_helpers.py @@ -314,11 +314,12 @@ def get_lexer_nodes(return_values, start, tokens): return lexer_nodes -def ensure_evaluated(context, concept): +def ensure_evaluated(context, concept, eval_body=True): """ Evaluate a concept is not already evaluated :param context: :param concept: + :param eval_body: :return: """ if concept.metadata.is_evaluated: @@ -326,11 +327,15 @@ def ensure_evaluated(context, concept): # do not try to evaluate concept that are not fully initialized for var in concept.metadata.variables: - if var[0] not in concept.values or concept.get_value(var[0]) == NotInit: + # to code + if var[1] is None and \ + var[0] not in concept.compiled and \ + (var[0] not in concept.values or concept.get_value(var[0]) == NotInit): return concept with context.push(BuiltinConcepts.EVALUATE_CONCEPT, concept, desc=f"Evaluating concept {concept}") as sub_context: - sub_context.local_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) + if eval_body: + sub_context.local_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) evaluated = context.sheerka.evaluate_concept(sub_context, concept) sub_context.add_values(return_values=evaluated) diff --git a/src/core/concept.py b/src/core/concept.py index c73c57b..2770994 100644 --- a/src/core/concept.py +++ b/src/core/concept.py @@ -12,7 +12,7 @@ from core.tokenizer import Tokenizer, TokenKind PROPERTIES_FOR_DIGEST = ("name", "key", "definition", "definition_type", "is_builtin", "is_unique", - "where", "pre", "post", "body", + "where", "pre", "post", "body", "ret", "desc", "props", "variables") PROPERTIES_TO_SERIALIZE = PROPERTIES_FOR_DIGEST + tuple(["id"]) PROPERTIES_FOR_NEW = ("where", "pre", "post", "body", "desc") @@ -40,6 +40,7 @@ class ConceptParts(Enum): PRE = "pre" POST = "post" BODY = "body" + RET = "ret" @staticmethod def get_parts(): @@ -56,6 +57,7 @@ class ConceptMetadata: where: str # condition to recognize variables in name pre: str # list of pre conditions before calling the main function post: str # list of post conditions after calling the main function + ret: str # variable to return when a concept is recognized definition: str # regex used to define the concept definition_type: str # definition can be done with something else than regex desc: str # possible description for the concept @@ -82,6 +84,7 @@ class Concept: where=None, pre=None, post=None, + ret=None, definition=None, definition_type=None, desc=None, @@ -98,6 +101,7 @@ class Concept: where, pre, post, + ret, definition, definition_type, desc, diff --git a/src/core/profiling.py b/src/core/profiling.py index f4cdccc..fe9ba5c 100644 --- a/src/core/profiling.py +++ b/src/core/profiling.py @@ -1,5 +1,5 @@ # ############################ -# from github: nealtodd/decorator.py +# from github gist: nealtodd/decorator.py (https://gist.github.com/nealtodd/2489618) # ############################ import pstats @@ -8,7 +8,7 @@ from cProfile import Profile def profile(sort_args=None, print_args=None): sort_args = sort_args or ['cumulative'] - print_args = print_args or [10] + print_args = print_args or [20] profiler = Profile() def decorator(fn): diff --git a/src/core/sheerka/Sheerka.py b/src/core/sheerka/Sheerka.py index dd771ef..dcb749c 100644 --- a/src/core/sheerka/Sheerka.py +++ b/src/core/sheerka/Sheerka.py @@ -365,6 +365,7 @@ class Sheerka(Concept): self.sdp.reset() self.locals = {} + # @profile() def evaluate_user_input(self, text: str, user_name="kodjo"): """ Note to KSI: If you try to add execution context to this function, @@ -676,7 +677,7 @@ class Sheerka(Concept): return self.new(BuiltinConcepts.UNKNOWN_PROPERTY, body=k, concept=concept) # TODO : add the concept to the list of known concepts (self.instances) - concept.metadata.is_evaluated = True + concept.metadata.is_evaluated = True # because we have manually set the variables return concept def ret(self, who: str, status: bool, value, message=None, parents=None): diff --git a/src/core/sheerka/services/SheerkaDump.py b/src/core/sheerka/services/SheerkaDump.py index bb75dd1..e1a5b76 100644 --- a/src/core/sheerka/services/SheerkaDump.py +++ b/src/core/sheerka/services/SheerkaDump.py @@ -22,7 +22,7 @@ class SheerkaDump(BaseService): def initialize(self): self.sheerka.bind_service_method(self.dump_desc, "desc") - self.sheerka.bind_service_method(self.dump_state, "state") + self.sheerka.bind_service_method(self.dump_sdp, "dump_sdp") def dump_desc(self, *concept_names, eval=False): first = True @@ -47,17 +47,24 @@ class SheerkaDump(BaseService): if not first: self.sheerka.log.info("") - self.sheerka.log.info(f"name : {c.name}") - self.sheerka.log.info(f"key : {c.key}") - self.sheerka.log.info(f"bnf : {c.metadata.definition}") - self.sheerka.log.info(f"body : {c.metadata.body}") - self.sheerka.log.info(f"where : {c.metadata.where}") + self.sheerka.log.info(f"name : {c.name}") + self.sheerka.log.info(f"key : {c.key}") + self.sheerka.log.info(f"definition : {c.metadata.definition}") + self.sheerka.log.info(f"type : {c.metadata.definition_type}") + self.sheerka.log.info(f"body : {c.metadata.body}") + self.sheerka.log.info(f"where : {c.metadata.where}") + self.sheerka.log.info(f"pre : {c.metadata.pre}") + self.sheerka.log.info(f"post : {c.metadata.post}") + self.sheerka.log.info(f"ret : {c.metadata.ret}") + self.sheerka.log.info(f"vars : {c.metadata.variables}") + self.sheerka.log.info(f"props : {c.metadata.props}") if eval: self.sheerka.log.info(f"value : {value}") - for v in c.values: - self.sheerka.log.info(f"{v}: {c.get_value(v)}") + if c.values: + for v in c.values: + self.sheerka.log.info(f"{v}: {c.get_value(v)}") else: - self.sheerka.log.info("No property") + self.sheerka.log.info("No variable") self.sheerka.log.info(f"digest : {c.get_origin()}") @@ -67,30 +74,7 @@ class SheerkaDump(BaseService): first = False - # def dump_history(self, page=20, start=0): - # count = 0 - # resolved_page = page if page > 0 else 50 - # page_count = 0 - # - # while count < page if page > 0 else True: - # history = self.sheerka.history(resolved_page, start + page_count * resolved_page) - # try: - # h = next(history) - # except StopIteration: - # break - # - # while True: - # try: - # if h.result: - # self.sheerka.log.info(h) - # count += 1 - # h = next(history) - # except StopIteration: - # break - # - # page_count += 1 - - def dump_state(self): + def dump_sdp(self): snapshot = self.sheerka.sdp.get_snapshot(SheerkaDataProvider.HeadFile) state = self.sheerka.sdp.load_state(snapshot) self.sheerka.log.info(get_pp().pformat(state.data)) diff --git a/src/core/sheerka/services/SheerkaEvaluateConcept.py b/src/core/sheerka/services/SheerkaEvaluateConcept.py index ed18458..4049128 100644 --- a/src/core/sheerka/services/SheerkaEvaluateConcept.py +++ b/src/core/sheerka/services/SheerkaEvaluateConcept.py @@ -145,10 +145,12 @@ class SheerkaEvaluateConcept(BaseService): if self.sheerka.has_id(concept.id): self.sheerka.get_by_id(concept.id).compiled = concept.compiled - def resolve(self, context, to_resolve, current_prop, current_concept, force_evaluation): + def resolve(self, context, to_resolve, current_prop, current_concept, force_evaluation, expect_success): def get_path(context_, prop_name): - prefix = context_.path if hasattr(context_, "path") else "" + concept_name = f'"{context_.action_context.name}"' if isinstance(context_.action_context, Concept) \ + else "'N/A'" + prefix = context_.path if hasattr(context_, "path") else concept_name value = prop_name.name if isinstance(current_prop, ConceptParts) else prop_name return prefix + "." + value @@ -178,6 +180,9 @@ class SheerkaEvaluateConcept(BaseService): if force_evaluation: sub_context.local_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) + if expect_success: + sub_context.local_hints.add(BuiltinConcepts.EVAL_SUCCESS_REQUESTED) + # when it's a concept, evaluate it if isinstance(to_resolve, Concept) and \ not context.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE): @@ -206,7 +211,7 @@ class SheerkaEvaluateConcept(BaseService): concept=current_concept, property_name=current_prop) - def resolve_list(self, context, list_to_resolve, current_prop, current_concept, force_evaluation): + def resolve_list(self, context, list_to_resolve, current_prop, current_concept, force_evaluation, expect_success): """When dealing with a list, there are two possibilities""" # It may be a list of ReturnValueConcept to execute (always the case for metadata) # or a list of single values (may be the case for properties) @@ -215,7 +220,12 @@ class SheerkaEvaluateConcept(BaseService): return [] if self.sheerka.isinstance(list_to_resolve[0], BuiltinConcepts.RETURN_VALUE): - return self.resolve(context, list_to_resolve, current_prop, current_concept, force_evaluation) + return self.resolve(context, + list_to_resolve, + current_prop, + current_concept, + force_evaluation, + expect_success) res = [] for to_resolve in list_to_resolve: @@ -226,7 +236,7 @@ class SheerkaEvaluateConcept(BaseService): concept=current_concept, property_name=current_prop) - r = self.resolve(context, to_resolve, current_prop, current_concept, force_evaluation) + r = self.resolve(context, to_resolve, current_prop, current_concept, force_evaluation, expect_success) if self.sheerka.isinstance(r, BuiltinConcepts.CONCEPT_EVAL_ERROR): return r res.append(r) @@ -258,10 +268,10 @@ class SheerkaEvaluateConcept(BaseService): if isinstance(prop_ast, list): # Do not send the current concept for the properties - resolved = self.resolve_list(context, prop_ast, var_name, None, True) + resolved = self.resolve_list(context, prop_ast, var_name, None, True, False) else: # Do not send the current concept for the properties - resolved = self.resolve(context, prop_ast, var_name, None, True) + resolved = self.resolve(context, prop_ast, var_name, None, True, False) if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved): resolved.set_value("concept", concept) # since current concept was not sent @@ -278,10 +288,20 @@ class SheerkaEvaluateConcept(BaseService): if part_key in concept.compiled and concept.compiled[part_key] is not None: metadata_ast = concept.compiled[part_key] - # if part_key is PRE, POST or WHERE, the concepts need to be evaluated - # otherwise the predicates cannot be resolved + # if part_key is PRE, POST or WHERE, the concept need to be evaluated + # if we want the predicates to be resolved => so force_eval = True + # otherwise no need to force force_concept_eval = False if part_key == ConceptParts.BODY else True - resolved = self.resolve(context, metadata_ast, part_key, concept, force_concept_eval) + + # when resolving predicate (where or pre), we need to make sure that PythonEvaluator + # will try every possibilities before returning False + expect_success = part_key in (ConceptParts.WHERE, ConceptParts.PRE) + resolved = self.resolve(context, + metadata_ast, + part_key, + concept, + force_concept_eval, + expect_success) if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved): return resolved else: @@ -312,9 +332,9 @@ class SheerkaEvaluateConcept(BaseService): def choose_metadata_to_eval(self, context, concept): if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED): - return ["pre", "post", "variables", "body", "where"] + return ["pre", "post", "variables", "body", "where", "ret"] - metadata = ["pre", "post"] + metadata = ["pre", "post", "ret"] if context.in_context(BuiltinConcepts.EVAL_WHERE_REQUESTED) or concept.metadata.need_validation: needed = self.needed_metadata(concept, ConceptParts.WHERE) for e in needed: diff --git a/src/core/sheerka/services/SheerkaExecute.py b/src/core/sheerka/services/SheerkaExecute.py index aa122db..56c4a7e 100644 --- a/src/core/sheerka/services/SheerkaExecute.py +++ b/src/core/sheerka/services/SheerkaExecute.py @@ -326,13 +326,13 @@ class SheerkaExecute(BaseService): result = evaluator.eval(sub_context, item) if result is None: # match() was successful but nothing was done in eval - # most of the time, it's because checks made in eval were unsuccessful + # most of the time, it's because extra checks are unsuccessful debug_result.append({"input": item, "return_value": None}) continue if id(result) == id(item): # eval was successful, but we don't want to alter the processing flow - debug_result.append({"input": item, "return_value": item}) + debug_result.append({"input": item, "return_value": result}) continue # otherwise, item will be removed and replaced by result diff --git a/src/core/tokenizer.py b/src/core/tokenizer.py index 2b9eb46..dbdff91 100644 --- a/src/core/tokenizer.py +++ b/src/core/tokenizer.py @@ -128,6 +128,7 @@ class Keywords(Enum): PRE = "pre" POST = "post" ISA = "isa" + RET = "ret" class Tokenizer: diff --git a/src/evaluators/AddConceptEvaluator.py b/src/evaluators/AddConceptEvaluator.py index 741300c..64aa3b6 100644 --- a/src/evaluators/AddConceptEvaluator.py +++ b/src/evaluators/AddConceptEvaluator.py @@ -59,7 +59,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator): concept.metadata.definition_type = def_concept_node.definition_type name_to_use = self.get_name_to_use(def_concept_node) - for prop in ("definition", "where", "pre", "post", "body"): + for prop in ("definition", "where", "pre", "post", "body", "ret"): part_ret_val = getattr(def_concept_node, prop) diff --git a/src/evaluators/PythonEvaluator.py b/src/evaluators/PythonEvaluator.py index ac13891..9307648 100644 --- a/src/evaluators/PythonEvaluator.py +++ b/src/evaluators/PythonEvaluator.py @@ -81,6 +81,7 @@ class PythonEvaluator(OneReturnValueEvaluator): concepts_entries = None evaluated = BuiltinConcepts.NOT_INITIALIZED errors = [] + expect_success = BuiltinConcepts.EVAL_SUCCESS_REQUESTED in context.local_hints for globals_ in all_possible_globals: try: # eval @@ -92,7 +93,8 @@ class PythonEvaluator(OneReturnValueEvaluator): context.log("Evaluating using 'exec'.", self.name) evaluated = self.exec_with_return(node.ast_, globals_, sheerka.locals) - break # in this first version, we stop once a success is found + if not expect_success or evaluated: + break # in this first version, we stop once a success is found except Exception as ex: if concepts_entries is None: concepts_entries = self.get_concepts_entries_from_globals(my_globals) @@ -246,7 +248,7 @@ class PythonEvaluator(OneReturnValueEvaluator): # make the product the rest as cartesian product res = [fixed_values] for k, v in concepts_with_body.items(): - res = core.utils.dict_product(res, [{k: context.sheerka.objvalue(v)}, {k: v}]) + res = core.utils.dict_product(res, [{k: v}, {k: context.sheerka.objvalue(v)}]) return res diff --git a/src/evaluators/RetEvaluator.py b/src/evaluators/RetEvaluator.py new file mode 100644 index 0000000..8fd916b --- /dev/null +++ b/src/evaluators/RetEvaluator.py @@ -0,0 +1,40 @@ +from core.builtin_concepts import BuiltinConcepts +from core.builtin_helpers import ensure_evaluated +from core.concept import Concept, ConceptParts +from evaluators.BaseEvaluator import OneReturnValueEvaluator + + +class RetEvaluator(OneReturnValueEvaluator): + """ + The evaluator transform the a concept, using the ret value + """ + + NAME = "Ret" + + def __init__(self): + super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 10) + + def matches(self, context, return_value): + return return_value.status and \ + isinstance(return_value.value, Concept) and \ + return_value.value.metadata.ret is not None + + def eval(self, context, return_value): + sheerka = context.sheerka + concept = return_value.value + context.log(f"Processing ret value for concept {concept}.", self.name) + + if not concept.metadata.is_evaluated: + evaluated = ensure_evaluated(context, concept) + if evaluated.key != concept.key: + context.log(f"Failed to evaluate concept '{concept}'") + return None + ret = evaluated.get_value(ConceptParts.RET) + else: + ret = concept.get_value(ConceptParts.RET) + + if isinstance(ret, Concept) and sheerka.is_known(ret): + return sheerka.ret(self.name, True, ret, parents=[return_value]) + + context.log(f"ret '{ret}' is not a concept!") + return None diff --git a/src/parsers/DefaultParser.py b/src/parsers/DefaultParser.py index f91ff6f..d8d683b 100644 --- a/src/parsers/DefaultParser.py +++ b/src/parsers/DefaultParser.py @@ -84,6 +84,7 @@ class DefConceptNode(DefaultParserNode): pre: ReturnValueConcept = NotInitializedNode() post: ReturnValueConcept = NotInitializedNode() body: ReturnValueConcept = NotInitializedNode() + ret: ReturnValueConcept = NotInitializedNode() definition: ReturnValueConcept = NotInitializedNode() definition_type: str = None @@ -245,6 +246,7 @@ class DefaultParser(BaseParser): concept_found.pre = asts_found_by_parts[Keywords.PRE] concept_found.post = asts_found_by_parts[Keywords.POST] concept_found.body = asts_found_by_parts[Keywords.AS] + concept_found.ret = asts_found_by_parts[Keywords.RET] return concept_found @@ -279,7 +281,13 @@ class DefaultParser(BaseParser): def regroup_tokens_by_parts(self, keywords_tokens): - def_concept_parts = [Keywords.CONCEPT, Keywords.FROM, Keywords.AS, Keywords.WHERE, Keywords.PRE, Keywords.POST] + def_concept_parts = [Keywords.CONCEPT, + Keywords.FROM, + Keywords.AS, + Keywords.WHERE, + Keywords.PRE, + Keywords.POST, + Keywords.RET] # tokens found, when trying to recognize the parts tokens_found_by_parts = { @@ -289,6 +297,7 @@ class DefaultParser(BaseParser): Keywords.WHERE: None, Keywords.PRE: None, Keywords.POST: None, + Keywords.RET: None, } current_part = Keywords.CONCEPT token = self.parser_input.token @@ -386,6 +395,7 @@ class DefaultParser(BaseParser): Keywords.WHERE: NotInitializedNode(), Keywords.PRE: NotInitializedNode(), Keywords.POST: NotInitializedNode(), + Keywords.RET: NotInitializedNode() } for keyword in tokens_found_by_parts: diff --git a/src/parsers/UnrecognizedNodeParser.py b/src/parsers/UnrecognizedNodeParser.py index 0d1f397..731d9bb 100644 --- a/src/parsers/UnrecognizedNodeParser.py +++ b/src/parsers/UnrecognizedNodeParser.py @@ -67,7 +67,7 @@ class UnrecognizedNodeParser(BaseParser): has_unrecognized = True # never trust source code not. I may be an invalid source code else: # cannot happen as of today :-) - raise NotImplementedError() + raise NotImplementedError(f"Node is {type(node)}, which is not supported yet") # concept with UnrecognizedToken in their properties is considered as fatal error if self.has_error: diff --git a/src/sheerkapickle/SheerkaPickler.py b/src/sheerkapickle/SheerkaPickler.py index 9b75d89..ff081ec 100644 --- a/src/sheerkapickle/SheerkaPickler.py +++ b/src/sheerkapickle/SheerkaPickler.py @@ -130,8 +130,15 @@ class SheerkaPickler: return None def exist(self, obj): - for k, v in self.ids.items(): - if k == id(obj): - return True, v + try: + v = self.ids[id(obj)] + return True, v + except KeyError: + return False, None - return False, None + # def exist(self, obj): + # for k, v in self.ids.items(): + # if k == id(obj): + # return True, v + # + # return False, None diff --git a/tests/core/test_concept.py b/tests/core/test_concept.py index 5521d6c..4ca6c77 100644 --- a/tests/core/test_concept.py +++ b/tests/core/test_concept.py @@ -69,6 +69,7 @@ def test_i_can_serialize(): where="definition of the where", pre="definition of the pre", post="definition of the post", + ret="concept to return", definition="bnf definition", definition_type="def type", desc="this this the desc", @@ -88,6 +89,7 @@ def test_i_can_serialize(): 'name': 'concept_name', 'post': 'definition of the post', 'pre': 'definition of the pre', + 'ret': "concept to return", 'props': {}, 'variables': [('a', "10"), ('b', None)], 'where': 'definition of the where' diff --git a/tests/evaluators/test_AddConceptEvaluator.py b/tests/evaluators/test_AddConceptEvaluator.py index 8bff3ed..9226cb2 100644 --- a/tests/evaluators/test_AddConceptEvaluator.py +++ b/tests/evaluators/test_AddConceptEvaluator.py @@ -1,6 +1,6 @@ import ast -import pytest +import pytest from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts from core.concept import VARIABLE_PREFIX, Concept, DEFINITION_TYPE_BNF, DEFINITION_TYPE_DEF from core.tokenizer import Tokenizer @@ -52,7 +52,8 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka): ) ) - def get_def_concept(self, name, where=None, pre=None, post=None, body=None, definition=None, bnf_def=None): + def get_def_concept(self, name, where=None, pre=None, post=None, body=None, ret=None, + definition=None, bnf_def=None): def_concept = DefConceptNode([], name=NameNode(list(Tokenizer(name)))) if body: @@ -63,6 +64,8 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka): def_concept.pre = self.get_concept_part(pre) if post: 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 @@ -91,7 +94,8 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka): bnf_def=self.get_return_value("hello a", Sequence(StrMatch("hello"), StrMatch("a"))), where="isinstance(a, str )", pre="a is not None", - body="print('hello' + a)") + body="print('hello' + a)", + ret="a") evaluated = AddConceptEvaluator().eval(context, def_concept_return_value) @@ -103,8 +107,9 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka): assert created_concept.metadata.key == "hello __var__0" assert created_concept.metadata.where == "isinstance(a, str )" assert created_concept.metadata.pre == "a is not None" - assert created_concept.metadata.post is None + assert created_concept.metadata.post is None # test that NotInitialized is mapped into None assert created_concept.metadata.body == "print('hello' + a)" + assert created_concept.metadata.ret == "a" assert created_concept.metadata.definition == "hello a" assert created_concept.metadata.definition_type == "bnf" diff --git a/tests/evaluators/test_PythonEvaluator.py b/tests/evaluators/test_PythonEvaluator.py index 4e9ef3f..cb09a15 100644 --- a/tests/evaluators/test_PythonEvaluator.py +++ b/tests/evaluators/test_PythonEvaluator.py @@ -112,7 +112,7 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka): evaluated = PythonEvaluator().eval(context, parsed) assert evaluated.status - assert evaluated.value == 2 + assert evaluated.value == CB("foo", 2) def test_i_can_eval_concept_token(self): context = self.get_context() @@ -126,13 +126,35 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka): assert evaluated.status assert evaluated.value == "foo" + def test_i_can_eval_when_expect_success(self): + context = self.get_context() + context.sheerka.add_in_cache(Concept("foo", body="2")) + + parsed = PythonParser().parse(context, ParserInput("foo==2")) + python_evaluator = PythonEvaluator() + + evaluated = python_evaluator.eval(context, parsed) + assert evaluated.status + assert not evaluated.value # the first test is between Concept(foo) and int(2) + + context.local_hints.add(BuiltinConcepts.EVAL_SUCCESS_REQUESTED) + evaluated = python_evaluator.eval(context, parsed) + assert evaluated.status + assert evaluated.value # we test until we compare foo.body and 2 + + parsed = PythonParser().parse(context, ParserInput("foo==3")) + evaluated = python_evaluator.eval(context, parsed) + assert evaluated.status + assert not evaluated.value # neither foo or foo.body ==3 + def test_i_can_call_function_with_complex_concepts(self): sheerka, context, plus, mult = self.init_concepts( self.from_def_concept("plus", "a plus b", ["a", "b"]), self.from_def_concept("mult", "a mult b", ["a", "b"]), ) - parsed = PythonParser().parse(context, ParserInput("set_is_greater_than(BuiltinConcepts.PRECEDENCE, mult, plus)")) + parsed = PythonParser().parse(context, + ParserInput("set_is_greater_than(BuiltinConcepts.PRECEDENCE, mult, plus)")) python_evaluator = PythonEvaluator() evaluated = python_evaluator.eval(context, parsed) @@ -164,8 +186,8 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka): all_globals = python_evaluator.get_all_possible_globals(context, my_globals) assert len(all_globals) == 2 - assert all_globals[0]["foo"] == 'foo' - assert all_globals[1]["foo"] == CB(foo, "foo") # body is evaluated + assert all_globals[0]["foo"] == CB(foo, "foo") + assert all_globals[1]["foo"] == 'foo' # body is evaluated def test_i_can_detect_one_error(self): sheerka, context, foo = self.init_concepts("foo") @@ -194,11 +216,11 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka): error0 = evaluated.body.body[0] assert isinstance(error0, PythonEvalError) assert isinstance(error0.error, TypeError) - assert error0.error.args[0] == 'can only concatenate str (not "int") to str' - assert error0.concepts == {'foo': 'string'} + assert error0.error.args[0] == "unsupported operand type(s) for +: 'Concept' and 'int'" + assert error0.concepts == {'foo': CB(foo, 'string')} error1 = evaluated.body.body[1] assert isinstance(error1, PythonEvalError) assert isinstance(error1.error, TypeError) - assert error1.error.args[0] == "unsupported operand type(s) for +: 'Concept' and 'int'" - assert error1.concepts == {'foo': CB(foo, 'string')} + assert error1.error.args[0] == 'can only concatenate str (not "int") to str' + assert error1.concepts == {'foo': 'string'} diff --git a/tests/evaluators/test_RetEvaluator.py b/tests/evaluators/test_RetEvaluator.py new file mode 100644 index 0000000..f247345 --- /dev/null +++ b/tests/evaluators/test_RetEvaluator.py @@ -0,0 +1,46 @@ +import pytest +from core.builtin_concepts import ReturnValueConcept +from core.concept import Concept +from evaluators.RetEvaluator import RetEvaluator + +from tests.BaseTest import BaseTest +from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka + + +class TestRetEvaluator(TestUsingMemoryBasedSheerka): + + @pytest.mark.parametrize("ret_val, expected", [ + (ReturnValueConcept("who", True, Concept("foo", ret="bar")), True), + (ReturnValueConcept("who", False, Concept("foo", ret="bar")), False), + (ReturnValueConcept("who", True, Concept("foo")), False), + (BaseTest.pretval(Concept("foo", ret="bar")), False), + (ReturnValueConcept("who", True, "not even a concept"), False), + ]) + def test_i_can_match(self, ret_val, expected): + context = self.get_context() + assert RetEvaluator().matches(context, ret_val) == expected + + def test_i_can_evaluate_fully_initialized(self): + sheerka, context, foo, the_concept = self.init_concepts("foo", Concept("the concept", ret="a").def_var("a")) + + instance = sheerka.new("the concept") + instance.set_value("a", sheerka.new("foo")) + ret_value = self.tretval(sheerka, instance) + + res = RetEvaluator().eval(context, ret_value) + assert res.status + assert sheerka.isinstance(res.body, "foo") + + @pytest.mark.parametrize("instance, expected", [ + (Concept("with ret", ret="a").def_var("a", "foo"), "foo"), + (Concept("with ret", ret="a", body="10").def_var("a", "foo"), "foo"), + (Concept("with ret", ret="a").def_var("a", "bar"), "bar"), + ]) + def test_i_can_evaluate(self, instance, expected): + sheerka, context, foo, bar = self.init_concepts("foo", Concept("bar", body="10")) + + ret_value = self.tretval(sheerka, instance) + + 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 dd69db7..19ee9ba 100644 --- a/tests/non_reg/test_sheerka_non_reg.py +++ b/tests/non_reg/test_sheerka_non_reg.py @@ -929,12 +929,12 @@ as: error0 = res[0].body.body.body[0] assert isinstance(error0, PythonEvalError) assert isinstance(error0.error, TypeError) - assert error0.error.args[0] == 'can only concatenate str (not "int") to str' + assert error0.error.args[0] == "unsupported operand type(s) for +: 'Concept' and 'int'" error1 = res[0].body.body.body[1] assert isinstance(error1, PythonEvalError) assert isinstance(error1.error, TypeError) - assert error1.error.args[0] == "unsupported operand type(s) for +: 'Concept' and 'int'" + assert error1.error.args[0] == 'can only concatenate str (not "int") to str' def test_i_can_evaluate_bnf_concept_defined_with_group_after_restart(self): """ diff --git a/tests/parsers/test_DefaultParser.py b/tests/parsers/test_DefaultParser.py index 723721b..0035ad7 100644 --- a/tests/parsers/test_DefaultParser.py +++ b/tests/parsers/test_DefaultParser.py @@ -15,7 +15,7 @@ from parsers.PythonParser import PythonParser, PythonNode from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka -def get_def_concept(name, where=None, pre=None, post=None, body=None, definition=None, bnf_def=None): +def get_def_concept(name, where=None, pre=None, post=None, body=None, definition=None, bnf_def=None, ret=None): def_concept = DefConceptNode([], name=NameNode(list(Tokenizer(name)))) if body: @@ -26,6 +26,8 @@ def get_def_concept(name, where=None, pre=None, post=None, body=None, definition def_concept.pre = get_concept_part(pre) if post: def_concept.post = get_concept_part(post) + if ret: + def_concept.ret = get_concept_part(ret) if bnf_def: def_concept.definition = ReturnValueConcept( "parsers.Bnf", @@ -75,6 +77,9 @@ def get_concept_part(part): @dataclass class PN: + """ + Python Node + """ source: str # parser result source mode: str # compilation mode @@ -110,21 +115,23 @@ class TestDefaultParser(TestUsingMemoryBasedSheerka): assert node == expected def test_i_can_parse_complex_def_concept_statement(self): - text = """def concept a plus b + text = """def concept a mult b where a,b -pre isinstance(a, int) and isinstance(b, float) -post isinstance(res, int) -as res = a + b - """ +pre isinstance(b, int) +post isinstance(res, a) +as res = a * b +ret a if isinstance(a, Concept) else self +""" sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(text)) return_value = res.value expected_concept = get_def_concept( - name="a plus b", + name="a mult b", where="a,b\n", - pre="isinstance(a, int) and isinstance(b, float)\n", - post="isinstance(res, int)\n", - body=PN("res = a + b\n ", "exec") + pre="isinstance(b, int)\n", + post="isinstance(res, a)\n", + body=PN("res = a * b\n", "exec"), + ret="a if isinstance(a, Concept) else self\n" ) assert res.status