From 71d1b1d1ca2717c2a91d3851efc3d8804ebf60e0 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Thu, 5 Aug 2021 19:07:21 +0200 Subject: [PATCH] Fixed #101 : Implement PLURIAL Fixed #103 : Implement PlurialNodeParser Fixed #104 : Implement dynamic concept Fixed #107 : PrepareEvalxxxEvaluator: context hints are lost on a second evaluation --- sheerka_backup/default.sb | 9 +- src/core/builtin_concepts_ids.py | 3 +- src/core/builtin_helpers.py | 6 +- src/core/concept.py | 4 + src/core/sheerka/Sheerka.py | 43 +++++++- .../sheerka/services/SheerkaConceptManager.py | 5 +- .../services/SheerkaEvaluateConcept.py | 45 +++++--- .../sheerka/services/SheerkaHasAManager.py | 15 ++- .../sheerka/services/SheerkaIsAManager.py | 14 ++- .../sheerka/services/SheerkaPluralManager.py | 100 ++++++++++++++++++ src/core/utils.py | 17 +++ src/evaluators/ExpressionEvaluator.py | 2 +- src/evaluators/PrepareEvalBodyEvaluator.py | 26 +++-- src/evaluators/PrepareEvalCommon.py | 28 +++++ .../PrepareEvalGlobalTruthEvaluator.py | 15 +-- .../PrepareEvalQuestionEvaluator.py | 19 ++-- src/evaluators/PythonEvaluator.py | 6 +- src/parsers/BaseNodeParser.py | 2 +- src/parsers/SequenceNodeParser.py | 40 +++++-- src/sheerkapickle/sheerka_handlers.py | 9 +- tests/core/test_SheerkaConceptManager.py | 8 ++ tests/core/test_SheerkaEvaluateConcept.py | 29 ++++- tests/core/test_SheerkaHasAManager.py | 17 +++ tests/core/test_SheerkaIsAManager.py | 17 ++- tests/core/test_SheerkaPluralManager.py | 76 +++++++++++++ tests/evaluators/test_PrepareEvalCommon.py | 59 +++++++++++ tests/non_reg/test_sheerka_non_reg2.py | 11 ++ tests/non_reg/test_sheerka_non_reg_out.py | 4 +- tests/parsers/test_FunctionParser.py | 12 +++ tests/parsers/test_SequenceNodeParser.py | 21 ++++ tests/sheerkapickle/test_sheerka_handlers.py | 43 ++++---- 31 files changed, 600 insertions(+), 105 deletions(-) create mode 100644 src/core/sheerka/services/SheerkaPluralManager.py create mode 100644 src/evaluators/PrepareEvalCommon.py create mode 100644 tests/core/test_SheerkaPluralManager.py create mode 100644 tests/evaluators/test_PrepareEvalCommon.py diff --git a/sheerka_backup/default.sb b/sheerka_backup/default.sb index 079d232..e7e4593 100644 --- a/sheerka_backup/default.sb +++ b/sheerka_backup/default.sb @@ -53,9 +53,6 @@ woman is a human girl is a female girl is a human - -def concept boys -def concept girls def concept shirt def concept table @@ -75,4 +72,8 @@ def concept what is the x of y pre is_question() where x is an adjective as smar # def concept he ret memory("self is a human and self is a male") -def concept she ret memory("self is a human and self is a female") \ No newline at end of file +def concept she ret memory("self is a human and self is a female") + +# +def concept sky +def concept gender \ No newline at end of file diff --git a/src/core/builtin_concepts_ids.py b/src/core/builtin_concepts_ids.py index a29415f..6558a8d 100644 --- a/src/core/builtin_concepts_ids.py +++ b/src/core/builtin_concepts_ids.py @@ -17,7 +17,7 @@ class BuiltinConcepts: EVAL_UNTIL_SUCCESS_REQUESTED = "__EVAL_UNTIL_SUCCESS_REQUESTED" # PythonEvaluator tries combination until True is found EVAL_QUESTION_REQUESTED = "__EVAL_QUESTION_REQUESTED" # the user input must be treated as question EVAL_GLOBAL_TRUTH_REQUESTED = "__EVAL_GLOBAL_TRUTH_REQUESTED" # the user input is a global truth - VALIDATION_ONLY_REQUESTED = "__VALIDATION_ONLY_REQUESTED" # Validation mode activated. Never evaluate the body + EXPRESSION_ONLY_REQUESTED = "__EXPRESSION_ONLY_REQUESTED" # Do not allow methods with side effect # possible actions during sheerka.execute() or sheerka.evaluate_rules() INIT_SHEERKA = "__INIT_SHEERKA" # @@ -53,6 +53,7 @@ class BuiltinConcepts: # builtin attributes ISA = "__ISA" # when a concept is an instance of another one HASA = "__HASA" # when a concept has/owns another concept + PLURAL = "__PLURAL" # when multiple occurrence of the concept AUTO_EVAL = "__AUTO_EVAL" # when the concept must be auto evaluated # object diff --git a/src/core/builtin_helpers.py b/src/core/builtin_helpers.py index 8103a2d..980b1c5 100644 --- a/src/core/builtin_helpers.py +++ b/src/core/builtin_helpers.py @@ -971,13 +971,13 @@ class CreateObjectIdentifiers: self.identifiers_key = {} @staticmethod - def sanitize(identifier): + def sanitize(identifier, default="0"): if identifier is None: return "" res = "" for c in identifier: - res += c if c.isalnum() else "0" + res += c if c.isalnum() else default return res def get_identifier(self, obj, wrapper): @@ -998,7 +998,7 @@ class CreateObjectIdentifiers: identifier = wrapper + self.sanitize(obj.key or obj.name) if obj.id: - identifier += "__" + obj.id + identifier += "__" + self.sanitize(obj.id, "_") if identifier in self.identifiers_key: self.identifiers_key[identifier] += 1 diff --git a/src/core/concept.py b/src/core/concept.py index 98fcc5f..0337cda 100644 --- a/src/core/concept.py +++ b/src/core/concept.py @@ -171,6 +171,7 @@ class Concept: self._metadata = metadata self._bound_body = bound_body self._compiled = {} # cached ast for the where, pre, post and body parts and variables + self._compiled_context_hints = {} # context hints to use when evaluating compiled self._bnf = None # parsing expression self._original_definition_hash = None # concept hash before any alteration of the metadata self._format = None # how to print the concept @@ -278,6 +279,9 @@ class Concept: def set_compiled(self, compiled): self._compiled = compiled + def get_compiled_context_hints(self): + return self._compiled_context_hints + def get_bnf(self): return self._bnf diff --git a/src/core/sheerka/Sheerka.py b/src/core/sheerka/Sheerka.py index 325c1c0..ea466a3 100644 --- a/src/core/sheerka/Sheerka.py +++ b/src/core/sheerka/Sheerka.py @@ -546,11 +546,12 @@ class Sheerka(Concept): return new_instances(concept, recognized_by, is_instance, is_evaluated) if return_new else concept - def new(self, concept_key, **kwargs): + def new(self, concept_key, allow_dynamic=False, **kwargs): """ Returns an instance of a new concept When the concept is supposed to be unique, returns the same instance :param concept_key: + :param allow_dynamic: :param kwargs: :return: """ @@ -561,7 +562,8 @@ class Sheerka(Concept): else: concept_id = None - template = self.get_by_id(concept_id) if not concept_key else self.get_by_key(concept_key, concept_id) + template = self.get_by_id(concept_id, allow_dynamic) if not concept_key else \ + self.get_by_key(concept_key, concept_id) # manage concept not found if self.isinstance(template, BuiltinConcepts.UNKNOWN_CONCEPT) and \ @@ -610,6 +612,43 @@ class Sheerka(Concept): concept.get_hints().is_evaluated = True # because we have manually set the variables return concept + def new_dynamic(self, concept_or_key, id_suffix, name=None, props=None, attrs=None): + """ + Create a dynamic concept with the given props and attrs + A dynamic concept is a concept that is not declared by 'def concept' but can be deduced from another one + ex: 'boys' can be deduced from 'boy' (given that it's its plural). + dynamic concept are a convenient way not to define all possible concepts + :param concept_or_key: + :param id_suffix: + :param name: new name and key for the concept + :param props: + :param attrs: + :return: + """ + if not isinstance(concept_or_key, Concept): + concept = self.fast_resolve(concept_or_key, return_new=True) + else: + concept = Concept().update_from(concept_or_key, update_value=False) + + if hasattr(concept, "__iter__"): + # TODO: replace exception by the Concept TOO_MANY_SUCCESS and make sure that it is correctly manage + raise NotImplementedError("Too many concepts") + + concept.get_metadata().id = f"{concept.id}-{id_suffix}" + if name: + concept.get_metadata().name = name + concept.get_metadata().key = name + + if props: + for k, v in props.items(): + concept.set_prop(k, v) + + if attrs: + for k, v in attrs.items(): + concept.set_value(k, v) + + return concept + def push_ontology(self, context, name, cache_only=False): try: diff --git a/src/core/sheerka/services/SheerkaConceptManager.py b/src/core/sheerka/services/SheerkaConceptManager.py index 4ddf427..a4cdb35 100644 --- a/src/core/sheerka/services/SheerkaConceptManager.py +++ b/src/core/sheerka/services/SheerkaConceptManager.py @@ -403,7 +403,6 @@ class SheerkaConceptManager(BaseService): ensure_concept(concept) attr = attribute.str_id if isinstance(attribute, Concept) else attribute - old_value = concept.get_value(attr) if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): old_value = self.sheerka.get_by_id(concept.id).get_value(attr) @@ -582,7 +581,9 @@ class SheerkaConceptManager(BaseService): def get_by_hash(self, concept_hash, concept_id=None): return self.internal_get("hash", concept_hash, self.CONCEPTS_BY_HASH_ENTRY, concept_id) - def get_by_id(self, concept_id): + def get_by_id(self, concept_id, allow_dynamic=False): + if allow_dynamic and (index := core.utils.safe_index(concept_id, "-")) > 0: + concept_id = concept_id[:index] return self.internal_get("id", concept_id, self.CONCEPTS_BY_ID_ENTRY, None) def has_id(self, concept_id): diff --git a/src/core/sheerka/services/SheerkaEvaluateConcept.py b/src/core/sheerka/services/SheerkaEvaluateConcept.py index 76bc99e..3ecaaa0 100644 --- a/src/core/sheerka/services/SheerkaEvaluateConcept.py +++ b/src/core/sheerka/services/SheerkaEvaluateConcept.py @@ -123,7 +123,7 @@ class SheerkaEvaluateConcept(BaseService): assert concept_part_source is not None - tokens = [t.str_value for t in Tokenizer(concept_part_source)] + tokens = [t.str_value for t in Tokenizer(concept_part_source, yield_eof=False)] if check_vars: for var_name in (v[0] for v in concept.get_metadata().variables): @@ -486,24 +486,30 @@ class SheerkaEvaluateConcept(BaseService): sub_context.add_values(return_values=ret_val) return ret_val.body + evaluating_concept_part = current_prop in AllConceptParts + path = get_path(context, current_prop) desc = f"Evaluating {path} (concept={current_concept})" with context.push(BuiltinConcepts.EVALUATING_ATTRIBUTE, current_prop, desc=desc, - obj=current_concept) as sub_context: + obj=current_concept if evaluating_concept_part else None) as sub_context: sub_context.add_inputs(path=path) if force_evaluation: sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) if forbid_methods_with_side_effect: - sub_context.protected_hints.add(BuiltinConcepts.VALIDATION_ONLY_REQUESTED) + sub_context.protected_hints.add(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED) if current_prop in (ConceptParts.WHERE, ConceptParts.PRE): sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EVALUATING_PRE_OR_WHERE_CLAUSE) + if current_prop in current_concept.get_compiled_context_hints(): + for hint in current_concept.get_compiled_context_hints()[current_prop]: + sub_context.protected_hints.add(hint) + # when it's a concept, evaluate it if isinstance(to_resolve, Concept) and \ not context.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE): @@ -521,7 +527,7 @@ class SheerkaEvaluateConcept(BaseService): # otherwise, execute all return values to find out what is the value else: # update short term memory with current concept variables - if current_concept: + if evaluating_concept_part: # only update when variables are supposed to be initialized for var in current_concept.get_metadata().variables: value = current_concept.get_value(var[0]) if value != NotInit: @@ -636,8 +642,8 @@ class SheerkaEvaluateConcept(BaseService): sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) if validation_only: - # Never eval the body - sub_context.protected_hints.add(BuiltinConcepts.VALIDATION_ONLY_REQUESTED) + # Never call methods with side effect in this concept or sub concepts + sub_context.protected_hints.add(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED) # auto evaluate commands if context.sheerka.isa(concept, context.sheerka.new(BuiltinConcepts.AUTO_EVAL)): @@ -656,6 +662,8 @@ class SheerkaEvaluateConcept(BaseService): # to make sure of the order, it don't use ConceptParts.get_parts() # variables must be evaluated first, body must be evaluated before where all_metadata_to_eval = metadata or self.compute_metadata_to_eval(sub_context, concept) + if validation_only and ConceptParts.BODY in all_metadata_to_eval: + all_metadata_to_eval.remove(ConceptParts.BODY) for metadata_to_eval in all_metadata_to_eval: if metadata_to_eval == "variables": @@ -671,7 +679,7 @@ class SheerkaEvaluateConcept(BaseService): sub_context, prop_ast, var_name, - None, + concept, True, not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED), w_clause) @@ -680,7 +688,7 @@ class SheerkaEvaluateConcept(BaseService): resolved = self.resolve(sub_context, prop_ast, var_name, - None, + concept, True, not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED), w_clause) @@ -721,7 +729,7 @@ class SheerkaEvaluateConcept(BaseService): if isinstance(resolved, Concept) and not sub_context.sheerka.is_success(resolved): if not (part_key == ConceptParts.BODY and self.sheerka.has_error(context, resolved, body=BuiltinConcepts.METHOD_ACCESS_ERROR) and - sub_context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED)): + sub_context.in_context(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED)): return resolved else: # BuiltinConcepts.METHOD_ACCESS_ERROR is returned only when the access to side effect @@ -751,7 +759,7 @@ class SheerkaEvaluateConcept(BaseService): # if len(concept.get_metadata().variables) == 0: # self.sheerka.om.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept) - if not concept.get_metadata().is_builtin: + if not concept.get_metadata().is_builtin and concept.get_hints().is_evaluated: self.sheerka.register_object(sub_context, concept.name, concept) # manage RET metadata @@ -786,6 +794,7 @@ class SheerkaEvaluateConcept(BaseService): def compute_metadata_to_eval(self, context, concept): to_eval = [] + # variables, body are shortcut for 'variables are already added' and 'body is already added' needed, variables, body = self.get_needed_metadata(concept, ConceptParts.PRE, True, True) to_eval.extend(needed) @@ -799,6 +808,15 @@ class SheerkaEvaluateConcept(BaseService): to_eval.extend(needed) if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED): + if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED): + if not variables: + to_eval.append('variables') + variables = True + + if not body: + to_eval.append(ConceptParts.BODY) + body = True + needed, v, b = self.get_needed_metadata(concept, ConceptParts.RET, not variables, not body) variables |= v body |= b @@ -809,13 +827,6 @@ class SheerkaEvaluateConcept(BaseService): body |= b to_eval.extend(needed) - if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED): - if not variables: - to_eval.append('variables') - - if not body: - to_eval.append(ConceptParts.BODY) - return to_eval def set_auto_eval(self, context, concept): diff --git a/src/core/sheerka/services/SheerkaHasAManager.py b/src/core/sheerka/services/SheerkaHasAManager.py index 524dd51..78cc3b7 100644 --- a/src/core/sheerka/services/SheerkaHasAManager.py +++ b/src/core/sheerka/services/SheerkaHasAManager.py @@ -12,7 +12,7 @@ class SheerkaHasAManager(BaseService): def initialize(self): self.sheerka.bind_service_method(self.NAME, self.set_hasa, True) - self.sheerka.bind_service_method(self.NAME, self.hasa, True) + self.sheerka.bind_service_method(self.NAME, self.hasa, False) def set_hasa(self, context, concept_a, concept_b): """ @@ -26,8 +26,13 @@ class SheerkaHasAManager(BaseService): context.log(f"Setting concept {concept_a} has a {concept_b}", who=self.NAME) ensure_concept(concept_a, concept_b) - if (BuiltinConcepts.HASA in concept_a.get_metadata().props and - concept_b in concept_a.get_metadata().props[BuiltinConcepts.HASA]): + if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): + concept_to_use = self.sheerka.get_by_id(concept_a.id) + else: + concept_to_use = concept_a + + if (BuiltinConcepts.HASA in concept_to_use.get_metadata().props and + concept_b in concept_to_use.get_metadata().props[BuiltinConcepts.HASA]): return self.sheerka.ret( self.NAME, False, @@ -40,7 +45,9 @@ class SheerkaHasAManager(BaseService): if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): to_add = {"props": {BuiltinConcepts.HASA: merged_concepts}} - return self.sheerka.modify_concept(context, concept_a, to_add, modify_source=True) + res = self.sheerka.modify_concept(context, concept_to_use, to_add) + concept_a.set_prop(BuiltinConcepts.HASA, merged_concepts) + return res else: concept_a.set_prop(BuiltinConcepts.HASA, merged_concepts) return self.sheerka.ret(self.NAME, True, concept_a) diff --git a/src/core/sheerka/services/SheerkaIsAManager.py b/src/core/sheerka/services/SheerkaIsAManager.py index e47972c..3966830 100644 --- a/src/core/sheerka/services/SheerkaIsAManager.py +++ b/src/core/sheerka/services/SheerkaIsAManager.py @@ -43,8 +43,13 @@ class SheerkaIsAManager(BaseService): context.log(f"Setting concept {concept} is a {concept_set}", who=self.NAME) core.builtin_helpers.ensure_concept(concept, concept_set) - if BuiltinConcepts.ISA in concept.get_metadata().props and \ - concept_set in concept.get_metadata().props[BuiltinConcepts.ISA]: + if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): + concept_to_use = self.sheerka.get_by_id(concept.id) + else: + concept_to_use = concept + + if BuiltinConcepts.ISA in concept_to_use.get_metadata().props and \ + concept_set in concept_to_use.get_metadata().props[BuiltinConcepts.ISA]: return self.sheerka.ret( self.NAME, False, @@ -56,12 +61,11 @@ class SheerkaIsAManager(BaseService): if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): to_add = {"props": {BuiltinConcepts.ISA: new_concept_set}} - res = self.sheerka.modify_concept(context, concept, to_add, modify_source=True) + res = self.sheerka.modify_concept(context, concept_to_use, to_add) if not res.status: return res - else: - concept = res.body.body + concept.set_prop(BuiltinConcepts.ISA, new_concept_set) res = self.add_concept_to_set(context, concept, concept_set) return res else: diff --git a/src/core/sheerka/services/SheerkaPluralManager.py b/src/core/sheerka/services/SheerkaPluralManager.py new file mode 100644 index 0000000..75a1cb6 --- /dev/null +++ b/src/core/sheerka/services/SheerkaPluralManager.py @@ -0,0 +1,100 @@ +from dataclasses import dataclass + +from cache.Cache import Cache +from core.builtin_concepts import BuiltinConcepts +from core.builtin_helpers import ensure_concept +from core.global_symbols import NotFound +from core.sheerka.services.sheerka_service import BaseService, ServiceObj + + +@dataclass +class KnownPluralObj(ServiceObj): + """ + Order to store + """ + concept: str # id of the concept + plural: str # id of its plural + + +class SheerkaPluralManager(BaseService): + NAME = "PluralManager" + KNOWN_PLURAL_ENTRY = "SheerkaPluralManager:KnownPlural" + + def __init__(self, sheerka): + super().__init__(sheerka, order=22) + + def initialize(self): + cache = Cache().auto_configure(self.KNOWN_PLURAL_ENTRY) + self.sheerka.om.register_cache(self.KNOWN_PLURAL_ENTRY, cache) + + self.sheerka.bind_service_method(self.NAME, self.set_plural, True) + self.sheerka.bind_service_method(self.NAME, self.is_plural, False) + self.sheerka.bind_service_method(self.NAME, self.known_plural, False) + + def set_plural(self, context, concept_a, concept_b): + """ + Set that a concept_a is the plural of concept_b + :param context: + :param concept_a: + :param concept_b: + :return: + """ + + context.log(f"Setting concept {concept_a} as plural of {concept_b}", who=self.NAME) + ensure_concept(concept_a, concept_b) + + if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): + concept_to_use = self.sheerka.get_by_id(concept_a.id) + else: + concept_to_use = concept_a + + if concept_to_use.get_prop(BuiltinConcepts.PLURAL) == concept_b: + return self.sheerka.ret( + self.NAME, + False, + self.sheerka.new(BuiltinConcepts.PROPERTY_ALREADY_DEFINED, + property_value=concept_b, + property_name=BuiltinConcepts.PLURAL, + concept=concept_a)) + + if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): + # update the list of known plurals + known_plural = KnownPluralObj(context.event.get_digest(), concept_b.id, concept_a.id) + self.sheerka.om.put(self.KNOWN_PLURAL_ENTRY, concept_b.id, known_plural) + + # update the concept + to_add = {"props": {BuiltinConcepts.PLURAL: concept_b}} + res = self.sheerka.modify_concept(context, concept_to_use, to_add) + concept_a.set_prop(BuiltinConcepts.PLURAL, concept_b) # do it AFTER calling modify_concept() + return res + else: + concept_a.set_prop(BuiltinConcepts.PLURAL, concept_b) + return self.sheerka.ret(self.NAME, True, concept_a) + + def is_plural(self, concept_a, concept_b=None): + """ + True if concept_a is a plural if concept_b is omitted + True if concept_a is the plural of concept_b if concept_b is given + :param concept_a: + :param concept_b: + :return: + """ + ensure_concept(concept_a) + if concept_b is None: + return BuiltinConcepts.PLURAL in concept_a.get_metadata().props + + ensure_concept(concept_b) + return concept_a.get_prop(BuiltinConcepts.PLURAL) == concept_b + + def known_plural(self, concept): + """ + Return the id of the concept's known plural if any. NotFound otherwise + :param concept: + :return: + """ + ensure_concept(concept) + res = self.sheerka.om.get(self.KNOWN_PLURAL_ENTRY, concept.id) + if res is NotFound: + return NotFound + + return res.plural diff --git a/src/core/utils.py b/src/core/utils.py index 5114608..c045820 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -902,3 +902,20 @@ def get_safe_str_value(obj): return obj.str_id return str(obj) + + +def safe_index(obj, sub_str): + """ + Return -1 is sub_str is not found. + Note that it is usually a bad idea to return -1 as it's a valid index in Python (but not in our case) + :param obj: + :param sub_str: + :return: + """ + if obj is None: + return -1 + + try: + return obj.index(sub_str) + except ValueError: + return -1 diff --git a/src/evaluators/ExpressionEvaluator.py b/src/evaluators/ExpressionEvaluator.py index ddd48c0..4b95c67 100644 --- a/src/evaluators/ExpressionEvaluator.py +++ b/src/evaluators/ExpressionEvaluator.py @@ -36,7 +36,7 @@ class ExpressionEvaluator(OneReturnValueEvaluator): for c in conditions: requested_vars.update(c.variables) namespace = create_namespace(context, self.NAME, requested_vars, set(), {}, True, False) - # TODO: ADD NAMESPACE TO STM + # TODO: ADD NAMESPACE TO ShortTermMemory missing_vars = set() results = rule_evaluator.evaluate_conditions(sub_context, conditions, namespace, missing_vars) diff --git a/src/evaluators/PrepareEvalBodyEvaluator.py b/src/evaluators/PrepareEvalBodyEvaluator.py index a0764c9..2d873d9 100644 --- a/src/evaluators/PrepareEvalBodyEvaluator.py +++ b/src/evaluators/PrepareEvalBodyEvaluator.py @@ -1,8 +1,9 @@ from core.builtin_concepts import BuiltinConcepts from evaluators.BaseEvaluator import OneReturnValueEvaluator +from evaluators.PrepareEvalCommon import PrepareEvalCommon -class PrepareEvalBodyEvaluator(OneReturnValueEvaluator): +class PrepareEvalBodyEvaluator(OneReturnValueEvaluator, PrepareEvalCommon): """ To recognize when the user input is an (body) evaluation """ @@ -11,10 +12,10 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator): def __init__(self, **kwargs): super().__init__(self.NAME, [BuiltinConcepts.BEFORE_PARSING], 90) - self.text = None + self.inner_text = None def reset(self): - self.text = None + self.inner_text = None def matches(self, context, return_value): if not (return_value.status and @@ -26,7 +27,10 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator): if not text.startswith("eval "): return False - self.text = text + self.inner_text = text[5:].strip() + if self.inner_text == "": + return False + return True def eval(self, context, return_value): @@ -34,12 +38,14 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator): new_text_to_parse = sheerka.ret( self.name, - True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.text[5:], user_name=context.event.user_id)) + True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.inner_text, user_name=context.event.user_id)) - root = context.get_parents(lambda ec: ec.action == BuiltinConcepts.PROCESS_INPUT) - root = root[0] if root else context - root.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) - root.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED) - root.protected_hints.add(BuiltinConcepts.RETURN_BODY_REQUESTED) + self.update_context_hints(context, + self.inner_text, + [ + BuiltinConcepts.EVAL_BODY_REQUESTED, + BuiltinConcepts.EVAL_WHERE_REQUESTED, + BuiltinConcepts.RETURN_BODY_REQUESTED + ]) return new_text_to_parse diff --git a/src/evaluators/PrepareEvalCommon.py b/src/evaluators/PrepareEvalCommon.py new file mode 100644 index 0000000..2bfe026 --- /dev/null +++ b/src/evaluators/PrepareEvalCommon.py @@ -0,0 +1,28 @@ +from core.builtin_concepts_ids import BuiltinConcepts + + +class PrepareEvalCommon: + @staticmethod + def update_context_hints(context, source, hints): + """ + + :param context: + :param source: + :param hints: + :return: + """ + root = context.get_parents(lambda ec: ec.action in (BuiltinConcepts.EVALUATING_CONCEPT, + BuiltinConcepts.PROCESS_INPUT)) + root = root[0] if root else context + if root.action == BuiltinConcepts.EVALUATING_CONCEPT: + concept = root.action_context + + if source in concept.variables(): + concept.get_compiled_context_hints()[source] = hints + else: + parsing_context = context.get_parents(lambda ec: ec.action == BuiltinConcepts.PARSING) + concept_part = parsing_context[0].action_context["prop"] + concept.get_compiled_context_hints()[concept_part] = hints + else: + for hint in hints: + root.add_to_protected_hints(hint) diff --git a/src/evaluators/PrepareEvalGlobalTruthEvaluator.py b/src/evaluators/PrepareEvalGlobalTruthEvaluator.py index e747c28..e5d769a 100644 --- a/src/evaluators/PrepareEvalGlobalTruthEvaluator.py +++ b/src/evaluators/PrepareEvalGlobalTruthEvaluator.py @@ -1,8 +1,9 @@ from core.builtin_concepts import BuiltinConcepts from evaluators.BaseEvaluator import OneReturnValueEvaluator +from evaluators.PrepareEvalCommon import PrepareEvalCommon -class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator): +class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator, PrepareEvalCommon): """ To recognize when the user input is a global truth """ @@ -39,11 +40,11 @@ class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator): self.name, True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.inner_text, user_name=context.event.user_id)) - root = context.get_parents(lambda ec: ec.action in (BuiltinConcepts.EVALUATING_CONCEPT, - BuiltinConcepts.PROCESS_INPUT)) - root = root[0] if root else context - root.add_to_protected_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) - root.add_to_protected_hints(BuiltinConcepts.EVAL_BODY_REQUESTED) - root.add_to_protected_hints(BuiltinConcepts.RETURN_BODY_REQUESTED) + self.update_context_hints(context, + self.inner_text, [ + BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED, + BuiltinConcepts.EVAL_BODY_REQUESTED, + BuiltinConcepts.RETURN_BODY_REQUESTED + ]) return new_text_to_parse diff --git a/src/evaluators/PrepareEvalQuestionEvaluator.py b/src/evaluators/PrepareEvalQuestionEvaluator.py index cb101bd..3d6ed45 100644 --- a/src/evaluators/PrepareEvalQuestionEvaluator.py +++ b/src/evaluators/PrepareEvalQuestionEvaluator.py @@ -1,8 +1,9 @@ from core.builtin_concepts import BuiltinConcepts from evaluators.BaseEvaluator import OneReturnValueEvaluator +from evaluators.PrepareEvalCommon import PrepareEvalCommon -class PrepareEvalQuestionEvaluator(OneReturnValueEvaluator): +class PrepareEvalQuestionEvaluator(OneReturnValueEvaluator, PrepareEvalCommon): """ To recognize when the user input is a question """ @@ -39,12 +40,12 @@ class PrepareEvalQuestionEvaluator(OneReturnValueEvaluator): self.name, True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.question, user_name=context.event.user_id)) - root = context.get_parents(lambda ec: ec.action in (BuiltinConcepts.EVALUATING_CONCEPT, - BuiltinConcepts.PROCESS_INPUT)) - root = root[0] if root else context - root.add_to_protected_hints(BuiltinConcepts.EVAL_QUESTION_REQUESTED) - root.add_to_protected_hints(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED) - root.add_to_protected_hints(BuiltinConcepts.EVAL_BODY_REQUESTED) - root.add_to_protected_hints(BuiltinConcepts.RETURN_BODY_REQUESTED) - + self.update_context_hints(context, + self.question, + [ + BuiltinConcepts.EVAL_QUESTION_REQUESTED, + BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED, + BuiltinConcepts.EVAL_BODY_REQUESTED, + BuiltinConcepts.RETURN_BODY_REQUESTED, + ]) return new_text_to_parse diff --git a/src/evaluators/PythonEvaluator.py b/src/evaluators/PythonEvaluator.py index 7fde950..b6aa3dd 100644 --- a/src/evaluators/PythonEvaluator.py +++ b/src/evaluators/PythonEvaluator.py @@ -80,7 +80,7 @@ class PythonEvaluator(OneReturnValueEvaluator): # We need to disable the functions that may alter the state # It's a poor way to have source code security check expression_only = context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED) or \ - context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED) + context.in_context(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED) if not expression_only: attr_under_eval = context.get_parents(lambda ec: ec.action == BuiltinConcepts.EVALUATING_ATTRIBUTE) @@ -94,8 +94,8 @@ class PythonEvaluator(OneReturnValueEvaluator): debugger.debug_var("globals", my_globals) except MethodAccessError as ex: # Quick and dirty, - # When VALIDATION_ONLY_REQUESTED is enabled, it's normal to have some NameError exceptions - if context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED): + # When expression_only, it's normal to have some NameError exceptions + if context.in_context(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED): return sheerka.ret(self.name, False, BuiltinConcepts.METHOD_ACCESS_ERROR, parents=[return_value]) eval_error = PythonEvalError(ex, diff --git a/src/parsers/BaseNodeParser.py b/src/parsers/BaseNodeParser.py index 743ee26..05990fc 100644 --- a/src/parsers/BaseNodeParser.py +++ b/src/parsers/BaseNodeParser.py @@ -306,7 +306,7 @@ class SourceCodeWithConceptNode(LexerNode): super().__init__(9999, -1, None) # why not sys.maxint ? self.first = first_node self.last = last_node - self.nodes = content_nodes or [] + self.nodes = content_nodes or [] # parts of the source code (without the first and the last) self.has_unrecognized = has_unrecognized self._all_nodes = None self.fix_all_pos() diff --git a/src/parsers/SequenceNodeParser.py b/src/parsers/SequenceNodeParser.py index 091de7c..8c488c5 100644 --- a/src/parsers/SequenceNodeParser.py +++ b/src/parsers/SequenceNodeParser.py @@ -4,6 +4,7 @@ from core import builtin_helpers from core.builtin_concepts import BuiltinConcepts from core.builtin_helpers import update_concepts_hints from core.concept import DEFINITION_TYPE_BNF, Concept +from core.global_symbols import NotFound from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import Tokenizer, TokenKind from core.utils import strip_tokens, make_unique @@ -245,13 +246,7 @@ class SequenceNodeParser(BaseNodeParser): if token.type == TokenKind.WHITESPACE: return None - def as_list(a): - if a is None: - return None - - return a if isinstance(a, list) else [a] - - concepts_by_name = as_list(self.sheerka.fast_resolve(token)) + concepts_by_name = self.as_list(self.sheerka.fast_resolve(token)) concepts_by_first_keyword = new_instances(self.sheerka.get_concepts_by_first_token(token, self._is_eligible)) if concepts_by_name is None: @@ -299,6 +294,10 @@ class SequenceNodeParser(BaseNodeParser): concepts = self.get_concepts(token, self._is_eligible) # self.context.log(f"concepts found for {token=}: {concepts}", who=self.name) + + if not concepts: + concepts = self.get_plural(token) + if not concepts: for concept_parser in concept_parser_helpers: concept_parser.eat_unrecognized(token, pos) @@ -454,3 +453,30 @@ class SequenceNodeParser(BaseNodeParser): self.name, False, context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=parser_input.as_text())) + + def get_plural(self, token): + if not token.type == TokenKind.IDENTIFIER: + return None + + if not token.value.endswith("s"): + return None + + concept_name = token.value[:-1] # remove the trailing 's' + concepts = self.as_list(self.sheerka.fast_resolve(concept_name)) + + if concepts is None: + return None + + eligible = [c for c in concepts if self.sheerka.known_plural(c) == NotFound] + if not eligible: + return None + + return [self.sheerka.new_dynamic(c, BuiltinConcepts.PLURAL, name=token.value, props={BuiltinConcepts.PLURAL: c}) + for c in concepts] + + @staticmethod + def as_list(obj): + if obj is None: + return None + + return obj if isinstance(obj, list) else [obj] diff --git a/src/sheerkapickle/sheerka_handlers.py b/src/sheerkapickle/sheerka_handlers.py index ce462f1..3ef871e 100644 --- a/src/sheerkapickle/sheerka_handlers.py +++ b/src/sheerkapickle/sheerka_handlers.py @@ -28,9 +28,9 @@ class ConceptHandler(BaseHandler): ref = default_concept ref_values = default_concept_values else: - ref = sheerka.get_by_id(obj.id) + ref = sheerka.get_by_id(obj.id, allow_dynamic=True) ref_values = ref.values() - data[CONCEPT_ID] = (obj.key, obj.id) + data[CONCEPT_ID] = obj.id # transform metadata for name in CONCEPT_PROPERTIES_TO_SERIALIZE: @@ -51,7 +51,10 @@ class ConceptHandler(BaseHandler): def new(self, data): sheerka = self.sheerka - return sheerka.new(tuple(data[CONCEPT_ID])) if CONCEPT_ID in data else Concept() + if CONCEPT_ID in data: + return sheerka.new((None, data[CONCEPT_ID]), allow_dynamic=True) + else: + return Concept() def restore(self, data, instance): pickler = self.context diff --git a/tests/core/test_SheerkaConceptManager.py b/tests/core/test_SheerkaConceptManager.py index 23a22d4..247bea5 100644 --- a/tests/core/test_SheerkaConceptManager.py +++ b/tests/core/test_SheerkaConceptManager.py @@ -1537,6 +1537,14 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): weights = sheerka.get_weights(BuiltinConcepts.PRECEDENCE, comparison_context=CONCEPT_COMPARISON_CONTEXT) assert weights == {'c:one|1001:': 2, 'c:two|1002:': 1} + def test_i_can_get_a_dynamic_concept_by_id_when_allow_dynamic(self): + sheerka, context, foo = self.init_concepts("foo") + + dynamic_foo = sheerka.new_dynamic(foo, "SUFFIX") + + assert sheerka.isinstance(sheerka.get_by_id(dynamic_foo.id), BuiltinConcepts.UNKNOWN_CONCEPT) + assert sheerka.isinstance(sheerka.get_by_id(dynamic_foo.id, allow_dynamic=True), foo) + class TestSheerkaConceptManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): def test_i_can_add_several_concepts(self): diff --git a/tests/core/test_SheerkaEvaluateConcept.py b/tests/core/test_SheerkaEvaluateConcept.py index 0e9c2ef..9746049 100644 --- a/tests/core/test_SheerkaEvaluateConcept.py +++ b/tests/core/test_SheerkaEvaluateConcept.py @@ -518,6 +518,29 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka): evaluated = sheerka.evaluate_concept(self.get_context(sheerka, False, False), concept) assert evaluated.key == concept.key + def test_i_can_evaluate_context_hint_multiple_times(self): + """ + Previous behaviour (that we want to change) + When def concept foo as global_truth(xxx) pre yyy + the context hint for the global truth is set during the initialisation of the ast + if the body is computed straight away it's ok, + But if the concept is evaluated a second time, the asts is not computed again, so the context hint is not set + :return: + """ + sheerka, context, foo = self.init_concepts( + Concept("foo", body="global_truth(in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED))") + ) + + foo_instance = sheerka.new("foo") + evaluated = sheerka.evaluate_concept(context, foo_instance, eval_body=False) + + assert ConceptParts.BODY in evaluated.get_compiled() + assert evaluated.body == NotInit + assert not evaluated.get_hints().is_evaluated + + evaluated = sheerka.evaluate_concept(context, foo_instance, eval_body=True) # evaluate the body this time + assert isinstance(evaluated.body, bool) and evaluated.body + def test_i_can_apply_intermediate_where_condition_using_python(self): sheerka, context, one_1, one_str, plus = self.init_concepts( Concept("one", body="1"), @@ -818,10 +841,14 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka): (Concept("foo"), False, []), (Concept("foo", pre="pre", post="post", ret="ret", where="where"), False, ["#pre#", "#post#"]), (Concept("foo", pre="pr", post="p", ret="r", where="w"), True, - ["#pre#", "#ret#", "#post#", "variables", "#body#"]), + ["#pre#", "variables", "#body#", "#ret#", "#post#"]), + + (Concept("foo", pre="pre", body="body"), False, ["#pre#"]), + (Concept("foo", pre="pre", body="body"), True, ["#pre#", "variables", "#body#"]), (Concept("foo", pre="a").def_var("a"), False, ["variables", "#pre#"]), (Concept("foo", pre="self"), False, ["#body#", "#pre#"]), (Concept("foo", pre="self + a").def_var("a"), False, ["variables", "#body#", "#pre#"]), + (Concept("foo", pre="self + a", ret="ret").def_var("a"), False, ["variables", "#body#", "#pre#"]), (Concept("foo", pre="self + a", ret="ret").def_var("a"), True, ["variables", "#body#", "#pre#", "#ret#"]), (Concept("foo", body="body"), False, []) diff --git a/tests/core/test_SheerkaHasAManager.py b/tests/core/test_SheerkaHasAManager.py index b330720..7cdbaa8 100644 --- a/tests/core/test_SheerkaHasAManager.py +++ b/tests/core/test_SheerkaHasAManager.py @@ -9,7 +9,10 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka): king_instance = sheerka.new("king") res = sheerka.set_hasa(context, king_instance, kingdom) + assert res.status + assert king_instance.get_prop(BuiltinConcepts.HASA) == {kingdom} + assert sheerka.hasa(king_instance, kingdom) # when global truth is not activated, only the current instance is modified another_king = sheerka.get_by_key("king") @@ -56,3 +59,17 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka): assert res.body.property_name == BuiltinConcepts.HASA assert res.body.property_value == kingdom assert res.body.concept == sheerka.new("king") + + def test_i_can_set_hase_twice_when_global_truth_is_true_the_second_time(self): + sheerka, context, king, kingdom = self.init_concepts("king", "kingdom") + global_truth_context = self.get_context(sheerka, global_truth=True) + + king_instance = sheerka.new("king") + sheerka.set_hasa(context, king_instance, kingdom) + + # set it again with global_truth = True + res = sheerka.set_hasa(global_truth_context, king_instance, kingdom) + + assert res.status + assert sheerka.hasa(king_instance, kingdom) + assert sheerka.hasa(sheerka.new("king"), kingdom) # try another instance diff --git a/tests/core/test_SheerkaIsAManager.py b/tests/core/test_SheerkaIsAManager.py index f5dd3a9..69c2b47 100644 --- a/tests/core/test_SheerkaIsAManager.py +++ b/tests/core/test_SheerkaIsAManager.py @@ -116,7 +116,8 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): context.add_to_private_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) - sheerka.set_isa(context, blue_instance, color) + res = sheerka.set_isa(context, blue_instance, color) + assert res.status assert sheerka.isa(blue_instance, color) assert sheerka.isaset(context, color) assert sheerka.isinset(blue_instance, color) @@ -423,6 +424,20 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): foo = sheerka.get_by_id(foo.id) assert not sheerka.isa(foo, group2) + def test_i_can_set_isa_twice_when_global_truth_is_true_the_second_time(self): + sheerka, context, blue, color = self.init_concepts(Concept("blue"), Concept("color")) + global_truth_context = self.get_context(sheerka, global_truth=True) + + blue_instance = sheerka.new("blue") + sheerka.set_isa(context, blue_instance, color) + + # set it again with global_truth = True + res = sheerka.set_isa(global_truth_context, blue_instance, color) + + assert res.status + assert sheerka.isa(blue_instance, color) + assert sheerka.isa(sheerka.new("blue"), color) # try another instance + class TestSheerkaSetsManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): def test_i_can_add_concept_to_set_and_retrieve_it_in_another_session(self): diff --git a/tests/core/test_SheerkaPluralManager.py b/tests/core/test_SheerkaPluralManager.py new file mode 100644 index 0000000..71bb3dc --- /dev/null +++ b/tests/core/test_SheerkaPluralManager.py @@ -0,0 +1,76 @@ +from core.builtin_concepts_ids import BuiltinConcepts +from core.global_symbols import NotFound +from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka + + +class TestSheerkaPluralManager(TestUsingMemoryBasedSheerka): + def test_i_can_set_plural(self): + sheerka, context, man, men = self.init_concepts("man", "men") + + men_instance = sheerka.new("men") + res = sheerka.set_plural(context, men_instance, man) + + assert res.status + assert men_instance.get_prop(BuiltinConcepts.PLURAL) == man + assert sheerka.is_plural(men_instance) + assert sheerka.is_plural(men_instance, man) + + # global truth is not set + another_instance = sheerka.new("men") + assert another_instance.get_prop(BuiltinConcepts.PLURAL) is None + + def test_i_can_set_plural_when_global_truth_is_set(self): + sheerka, context, man, men = self.init_concepts("man", "men", global_truth=True) + + assert sheerka.known_plural(man) == NotFound + + res = sheerka.set_plural(context, men, man) + + assert res.status + assert sheerka.is_plural(men) + + another_instance = sheerka.new("men") + assert sheerka.is_plural(another_instance) + assert sheerka.known_plural(man) == men.id + + def test_i_cannot_set_plural_twice(self): + sheerka, context, man, men = self.init_concepts("man", "men") + men_instance = sheerka.new("men") + sheerka.set_plural(context, men_instance, man) + + res = sheerka.set_plural(context, men_instance, man) + + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.PROPERTY_ALREADY_DEFINED) + assert res.body.concept == men_instance + assert res.body.property_name == BuiltinConcepts.PLURAL + assert res.body.property_value == man + + def test_i_cannot_set_plural_twice_when_global_truth(self): + sheerka, context, man, men = self.init_concepts("man", "men", global_truth=True) + sheerka.set_plural(context, men, man) + + another_instance = sheerka.new("men") + res = sheerka.set_plural(context, another_instance, man) + + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.PROPERTY_ALREADY_DEFINED) + assert res.body.concept == another_instance + assert res.body.property_name == BuiltinConcepts.PLURAL + assert res.body.property_value == man + + def test_i_can_set_plural_twice_when_global_truth_is_true_the_second_time(self): + sheerka, context, man, men = self.init_concepts("man", "men") + global_truth_context = self.get_context(sheerka, global_truth=True) + + men_instance = sheerka.new("men") + sheerka.set_plural(context, men_instance, man) + + # set it again with global_truth = True + res = sheerka.set_plural(global_truth_context, men_instance, man) + + assert res.status + assert men_instance.get_prop(BuiltinConcepts.PLURAL) == man + + # and it's now true for all newly created context + assert sheerka.is_plural(sheerka.new("men"), man) diff --git a/tests/evaluators/test_PrepareEvalCommon.py b/tests/evaluators/test_PrepareEvalCommon.py new file mode 100644 index 0000000..408e8fd --- /dev/null +++ b/tests/evaluators/test_PrepareEvalCommon.py @@ -0,0 +1,59 @@ +from core.builtin_concepts_ids import BuiltinConcepts +from core.concept import Concept, ConceptParts +from evaluators.PrepareEvalCommon import PrepareEvalCommon +from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka + + +class TestPrepareEvalCommon(TestUsingMemoryBasedSheerka): + def test_i_can_update_the_current_root(self): + sheerka, context = self.init_concepts() + + PrepareEvalCommon.update_context_hints(context, "prop_name", ["to_put_in_context"]) + assert context.in_context("to_put_in_context") + + def test_i_can_update_process_input(self): + sheerka, context = self.init_concepts() + + process_input_context = context.push(BuiltinConcepts.PROCESS_INPUT, "some input", desc=f"some desc") + level1 = process_input_context.push(BuiltinConcepts.TESTING, "some stuff") + level2 = level1.push(BuiltinConcepts.TESTING, "some stuff") # another level for the fun + + PrepareEvalCommon.update_context_hints(level2, "prop_name", ["to_put_in_context"]) + assert not context.in_context("to_put_in_context") + assert process_input_context.in_context("to_put_in_context") + assert not level1.in_context("to_put_in_context") + assert not level2.in_context("to_put_in_context") + + def test_i_can_update_process_when_evaluating_a_concept(self): + sheerka, context, foo = self.init_concepts("foo") + + eval_context = context.push(BuiltinConcepts.EVALUATING_CONCEPT, foo, desc=f"some desc") + parsing_prop_context = eval_context.push(BuiltinConcepts.PARSING, {"prop": ConceptParts.BODY}) + level1 = parsing_prop_context.push(BuiltinConcepts.TESTING, "some stuff") + level2 = level1.push(BuiltinConcepts.TESTING, "some stuff") # another level for the fun + + PrepareEvalCommon.update_context_hints(level2, "prop_name", ["to_put_in_context"]) + assert not context.in_context("to_put_in_context") + assert not eval_context.in_context("to_put_in_context") + assert not parsing_prop_context.in_context("to_put_in_context") + assert not level1.in_context("to_put_in_context") + assert not level2.in_context("to_put_in_context") + assert foo.get_compiled_context_hints() == {ConceptParts.BODY: ["to_put_in_context"]} + + def test_i_can_update_for_the_correct_variable(self): + # when source is the name of the variable, + # use this name, rather than the attribute being parsed + sheerka, context, foo = self.init_concepts(Concept("foo").def_var("var_name")) + + eval_context = context.push(BuiltinConcepts.EVALUATING_CONCEPT, foo, desc=f"some desc") + parsing_prop_context = eval_context.push(BuiltinConcepts.PARSING, {"prop": ConceptParts.BODY}) + level1 = parsing_prop_context.push(BuiltinConcepts.TESTING, "some stuff") + level2 = level1.push(BuiltinConcepts.TESTING, "some stuff") # another level for the fun + + PrepareEvalCommon.update_context_hints(level2, "var_name", ["to_put_in_context"]) + assert not context.in_context("to_put_in_context") + assert not eval_context.in_context("to_put_in_context") + assert not parsing_prop_context.in_context("to_put_in_context") + assert not level1.in_context("to_put_in_context") + assert not level2.in_context("to_put_in_context") + assert foo.get_compiled_context_hints() == {"var_name": ["to_put_in_context"]} diff --git a/tests/non_reg/test_sheerka_non_reg2.py b/tests/non_reg/test_sheerka_non_reg2.py index 651d39b..458522e 100644 --- a/tests/non_reg/test_sheerka_non_reg2.py +++ b/tests/non_reg/test_sheerka_non_reg2.py @@ -44,3 +44,14 @@ class TestSheerkaNonRegMemory2(TestUsingMemoryBasedSheerka): assert res[0].status assert sheerka.isa(sheerka.new("one"), sheerka.new("number")) + + # def test_i_can_define_plural(self): + # init = [ + # "def concept man", + # "def concept men as set_plural(man) ret man auto_eval True", + # ] + # sheerka = self.init_scenario(init) + # + # res = sheerka.evaluate_user_input("men") + # assert res[0].status + diff --git a/tests/non_reg/test_sheerka_non_reg_out.py b/tests/non_reg/test_sheerka_non_reg_out.py index cc9c407..2ad0393 100644 --- a/tests/non_reg/test_sheerka_non_reg_out.py +++ b/tests/non_reg/test_sheerka_non_reg_out.py @@ -151,8 +151,8 @@ __default__ init = [ "def concept one as 1", "def concept two as 2", - "one", - "two" + "eval one", + "eval two" ] sheerka = self.init_scenario(init) capsys.readouterr() diff --git a/tests/parsers/test_FunctionParser.py b/tests/parsers/test_FunctionParser.py index e99cc3a..4503537 100644 --- a/tests/parsers/test_FunctionParser.py +++ b/tests/parsers/test_FunctionParser.py @@ -3,6 +3,7 @@ import pytest from core.builtin_concepts import BuiltinConcepts from core.concept import Concept from core.sheerka.services.SheerkaExecute import ParserInput +from parsers.BaseNodeParser import SourceCodeWithConceptNode from parsers.BaseParser import ErrorSink from parsers.FunctionParser import FunctionParser from parsers.PythonParser import PythonErrorNode @@ -192,6 +193,17 @@ class TestFunctionParser(TestUsingMemoryBasedSheerka): assert expression.python_node is not None assert expression.return_value is not None + def test_i_can_parse_when_the_parameter_is_a_dynamic_concept(self): + sheerka, context, parser = self.init_parser() + + text = "func(ones)" + res = parser.parse(context, ParserInput(text)) + + assert res.status + assert isinstance(res.body.body, SourceCodeWithConceptNode) + assert res.body.body.python_node.source == 'func(__C__ones__1001___PLURAL__C__)' + assert "__C__ones__1001___PLURAL__C__" in res.body.body.python_node.objects + @pytest.mark.parametrize("text, expected_error_type", [ ("one", BuiltinConcepts.NOT_FOR_ME), # no function found ("$*!", BuiltinConcepts.NOT_FOR_ME), # no function found diff --git a/tests/parsers/test_SequenceNodeParser.py b/tests/parsers/test_SequenceNodeParser.py index 5e27934..2c40179 100644 --- a/tests/parsers/test_SequenceNodeParser.py +++ b/tests/parsers/test_SequenceNodeParser.py @@ -229,11 +229,13 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka): sheerka, context, parser = self.init_parser(concepts_map) res = parser.parse(context, ParserInput("a special concept")) + assert res.status lexer_nodes = res.body.body expected_array = compute_expected_array(concepts_map, "a special concept", ["a special concept"]) compare_with_test_object(lexer_nodes, expected_array) res = parser.parse(context, ParserInput("isa")) + assert res.status lexer_nodes = res.body.body expected_array = compute_expected_array(concepts_map, "isa", ["isa"]) compare_with_test_object(lexer_nodes, expected_array) @@ -442,3 +444,22 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka): for node in res.body.body: if hasattr(node, "concept"): assert node.concept.get_hints().use_copy + + def test_i_can_parse_plural(self): + concepts_map = { + "boy": Concept("boy"), + } + + sheerka, context, parser = self.init_parser(concepts_map) + boy = concepts_map['boy'] + + res = parser.parse(context, ParserInput("boys")) + assert res.status + lexer_nodes = res.body.body + assert len(lexer_nodes) == 1 + + concept_found = lexer_nodes[0].concept + assert concept_found.id == f"{boy.id}-{BuiltinConcepts.PLURAL}" + assert concept_found.name == "boys" + assert concept_found.key == "boys" + assert concept_found.get_prop(BuiltinConcepts.PLURAL) == boy diff --git a/tests/sheerkapickle/test_sheerka_handlers.py b/tests/sheerkapickle/test_sheerka_handlers.py index f747826..19c6a04 100644 --- a/tests/sheerkapickle/test_sheerka_handlers.py +++ b/tests/sheerkapickle/test_sheerka_handlers.py @@ -159,7 +159,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka): to_string = sheerkapickle.encode(sheerka, ref_concept) decoded = sheerkapickle.decode(sheerka, to_string) assert decoded == ref_concept - assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": ["my_key", "1001"]}' + assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": "1001"}' # same test, modify a value and check if this modification is correctly saved concept = Concept().update_from(sheerka.get_by_id(ref_concept.id)) @@ -167,26 +167,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka): to_string = sheerkapickle.encode(sheerka, concept) decoded = sheerkapickle.decode(sheerka, to_string) assert decoded == concept - assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": ["my_key", "1001"], "values": [["#body#", {"_sheerka/obj": "core.concept.Concept", "meta.name": "bar"}]]}' - - # def test_i_can_encode_decode_when_variable_is_a_concept(self): - # sheerka = self.get_sheerka() - # - # foo = Concept("foo") - # sheerka.create_new_concept(self.get_context(sheerka), foo) - # - # concept = Concept("my_name") - # sheerka.create_new_concept(self.get_context(sheerka), concept) - # concept.def_var(foo, "a value") - # concept.set_value(foo, "another value") - # concept.get_metadata().full_serialization = True - # - # to_string = sheerkapickle.encode(sheerka, concept) - # decoded = sheerkapickle.decode(sheerka, to_string) - # assert decoded == concept - # assert to_string == '{"_sheerka/obj": "core.concept.Concept", "meta.name": "my_name", "meta.key": "my_name", ' + \ - # '"meta.variables": [[{"_sheerka/obj": "core.concept.Concept", "concept/id": ["foo", "1001"]}, "a value"]], ' + \ - # '"meta.id": "1002", "values": [[{"_sheerka/id": 1}, "another value"]]}' + assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": "1001", "values": [["#body#", {"_sheerka/obj": "core.concept.Concept", "meta.name": "bar"}]]}' def test_i_can_manage_reference_of_the_same_object(self): sheerka = self.get_sheerka() @@ -331,7 +312,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka): def test_i_can_encode_decode_rule(self): sheerka = self.get_sheerka() - rule = Rule("print", "my rule", "True","Hello world") + rule = Rule("print", "my rule", "True", "Hello world") rule.metadata.id = "1" to_string = sheerkapickle.encode(sheerka, rule) @@ -339,3 +320,21 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka): assert to_string == '{"_sheerka/obj": "core.rule.Rule", "rule/id": "1", "name": "my rule", "predicate": "True", "action_type": "print", "action": "Hello world"}' assert decoded == rule + + def test_i_can_encode_decode_dynamic_concept(self): + sheerka, context, foo = self.init_concepts("foo", global_truth=True, create_new=True) + sheerka.set_attr(context, foo, "attr", "attr_value") + sheerka.set_property(context, foo, "prop", "prop_value", all_concepts=True) + + foo_instance = sheerka.new(foo) + dynamic_foo = sheerka.new_dynamic(foo_instance, + "SUFFIX", + "new_name", + props={"new_prop": "value"}, + attrs={"new_attr": "value"}) + + to_string = sheerkapickle.encode(sheerka, dynamic_foo) + decoded = sheerkapickle.decode(sheerka, to_string) + + assert decoded == dynamic_foo + assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": "1001-SUFFIX", "meta.name": "new_name", "meta.key": "new_name", "meta.props": {"prop": "prop_value", "new_prop": "value"}, "meta.id": "1001-SUFFIX", "values": [["new_attr", "value"]]}'