From e69745adc8e5244ab530692aa94fca18cdcac3dd Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sat, 31 Jul 2021 08:52:00 +0200 Subject: [PATCH] Fixed #100 : SheerkaAdmin: Add builtins() command Fixed #99 : SheerkaQueryManager: I can manage contains predicate when filtering objects Fixed #97 : ERROR: list indices must be integers or slices, not Concept Fixed #96 : SequenceNodeParser: SequenceNodeParser must correctly handle concept definition Fixed #95 : ResolveAmbiguity must not remove concepts that do not require evaluation Fixed #94 : Concepts with the same key are lost when new ontology Fixed #93 : Introduce BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED Fixed #92 : ExpressionParser: Implement compile_disjunctions() Fixed #91 : Implement get_concepts_complexity(context, concepts, concept_parts) Fixed #90 : ResolveAmbiguity : where predicate is not used to resolve ambiguity Fixed #89 : ResolveAmbiguityEvaluator: Concepts embedded in ConceptNode are not resolved Fixed #88: SyaNodeParser: Parse multiple parameters when some of the are not recognized Fixed #87: SyaNodeParser : Parse the multiple parameters --- sheerka_backup/python.sb | 4 +- src/cache/CacheManager.py | 5 +- src/core/builtin_concepts_ids.py | 2 + src/core/builtin_helpers.py | 83 ++++++++-- src/core/concept.py | 13 +- src/core/global_symbols.py | 2 +- src/core/sheerka/ExecutionContext.py | 2 +- src/core/sheerka/Sheerka.py | 81 ++++++++-- src/core/sheerka/SheerkaOntologyManager.py | 2 +- src/core/sheerka/services/SheerkaAdmin.py | 58 +++---- .../services/SheerkaComparisonManager.py | 12 +- .../sheerka/services/SheerkaConceptManager.py | 40 ++--- .../services/SheerkaConceptsAlgebra.py | 7 +- .../sheerka/services/SheerkaDebugManager.py | 35 +++-- src/core/sheerka/services/SheerkaDump.py | 7 +- .../services/SheerkaEvaluateConcept.py | 22 ++- .../sheerka/services/SheerkaEvaluateRules.py | 4 +- .../sheerka/services/SheerkaEventManager.py | 5 +- src/core/sheerka/services/SheerkaExecute.py | 12 +- .../sheerka/services/SheerkaHasAManager.py | 13 +- .../sheerka/services/SheerkaIsAManager.py | 34 +++-- src/core/sheerka/services/SheerkaMemory.py | 93 +++++++----- src/core/sheerka/services/SheerkaOut.py | 2 +- .../sheerka/services/SheerkaQueryManager.py | 20 +-- src/core/sheerka/services/SheerkaQuestion.py | 3 +- .../sheerka/services/SheerkaResultManager.py | 29 ++-- .../sheerka/services/SheerkaRuleManager.py | 14 +- .../services/SheerkaVariableManager.py | 16 +- src/core/utils.py | 54 ++++++- src/evaluators/ExpressionEvaluator.py | 2 +- .../PrepareEvalGlobalTruthEvaluator.py | 49 ++++++ src/evaluators/ResolveAmbiguityEvaluator.py | 34 ++++- src/evaluators/ValidateConceptEvaluator.py | 9 +- src/parsers/BaseExpressionParser.py | 71 ++++++++- src/parsers/BaseNodeParser.py | 6 + src/parsers/BaseParser.py | 20 --- src/parsers/BnfNodeParser.py | 2 +- src/parsers/ExactConceptParser.py | 38 ++++- src/parsers/SequenceNodeParser.py | 18 ++- src/parsers/SyaNodeParser.py | 94 ++++++++++-- src/sheerkapickle/SheerkaPickler.py | 2 + src/sheerkapython/python_wrapper.py | 2 +- tests/BaseTest.py | 24 ++- tests/core/test_SheerkaConceptAlgebra.py | 36 +++-- tests/core/test_SheerkaConceptManager.py | 22 +-- tests/core/test_SheerkaEvaluateConcept.py | 6 +- tests/core/test_SheerkaHasAManager.py | 33 +++- tests/core/test_SheerkaIsAManager.py | 94 ++++++++++-- tests/core/test_SheerkaMemory.py | 9 ++ tests/core/test_SheerkaQueryManager.py | 14 +- tests/core/test_builtin_helpers.py | 22 +++ tests/core/test_sheerka.py | 31 ++++ tests/core/test_sheerka_ontology.py | 44 ++++-- tests/core/test_utils.py | 18 +++ .../test_AddConceptInSetEvaluator.py | 40 ++++- tests/evaluators/test_ExpressionEvaluator.py | 3 +- .../test_PrepareEvalGlobalTruthEvaluator.py | 49 ++++++ .../test_ResolveAmbiguityEvaluator.py | 143 +++++++++++++++++- .../test_ValidateConceptEvaluator.py | 11 -- tests/non_reg/test_sheerka_non_reg.py | 48 +++--- tests/non_reg/test_sheerka_non_reg2.py | 34 ++++- .../test_sheerka_non_reg_file_based.py | 2 +- tests/non_reg/test_sheerka_non_reg_out.py | 39 +++++ .../test_sheerka_non_reg_pipe_functions.py | 2 +- tests/parsers/test_BnfNodeParser.py | 68 +++++---- tests/parsers/test_ExactConceptParser.py | 1 + tests/parsers/test_LogicalOperatorParser.py | 86 ++++++++++- tests/parsers/test_SequenceNodeParser.py | 17 +++ tests/parsers/test_SyaNodeParser.py | 81 ++++++++++ tests/parsers/test_UnrecognizedNodeParser.py | 18 +-- 70 files changed, 1561 insertions(+), 455 deletions(-) create mode 100644 src/evaluators/PrepareEvalGlobalTruthEvaluator.py create mode 100644 tests/evaluators/test_PrepareEvalGlobalTruthEvaluator.py diff --git a/sheerka_backup/python.sb b/sheerka_backup/python.sb index f7515c2..16bb077 100644 --- a/sheerka_backup/python.sb +++ b/sheerka_backup/python.sb @@ -3,4 +3,6 @@ def concept x is a string pre is_question() as isinstance(x, str) def concept x is a integer pre is_question() as isinstance(x, int) def concept x starts with y pre is_question() where x is a string as x.startswith(y) def concept sha256 from bnf r'[a-f0-9]{64}' -def concept sha512 from bnf r'[a-f0-9]{128}' \ No newline at end of file +def concept sha512 from bnf r'[a-f0-9]{128}' + +def concept explain x where isinstance(x, sha256) as get_results_by_digest(x, id=0, depth=2) auto_eval True \ No newline at end of file diff --git a/src/cache/CacheManager.py b/src/cache/CacheManager.py index 276f1d7..8c8093f 100644 --- a/src/cache/CacheManager.py +++ b/src/cache/CacheManager.py @@ -83,18 +83,19 @@ class CacheManager: self.caches[name] = CacheDefinition(cache, use_ref, None, persist) - def add_concept(self, concept): + def add_concept(self, concept, alt_sdp=None): """ We need multiple indexes to retrieve a concept So the new concept is dispatched into multiple caches :param concept: + :param alt_sdp: if not found in self.sdp, look in other repositories :return: """ with self._lock: for name in self.concept_caches: cache_def = self.caches[name] key = cache_def.get_key(concept) - cache_def.cache.put(key, concept) + cache_def.cache.put(key, concept, alt_sdp) self.is_dirty = True diff --git a/src/core/builtin_concepts_ids.py b/src/core/builtin_concepts_ids.py index 2557091..a29415f 100644 --- a/src/core/builtin_concepts_ids.py +++ b/src/core/builtin_concepts_ids.py @@ -16,6 +16,7 @@ class BuiltinConcepts: REDUCE_REQUESTED = "__REDUCE_REQUESTED" # remove meaningless error when possible 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 # possible actions during sheerka.execute() or sheerka.evaluate_rules() @@ -47,6 +48,7 @@ class BuiltinConcepts: EXEC_CODE = "__EXEC_CODE" # to use when executing Python or other language compiled code TESTING = "__TESTING" EVALUATOR_PRE_PROCESS = "__EVALUATOR_PRE_PROCESS" # used modify / tweak behaviour of evaluators + EVALUATING_PRE_OR_WHERE_CLAUSE = "EVALUATING_PRE_OR_WHERE_CLAUSE" # use in is_question() # builtin attributes ISA = "__ISA" # when a concept is an instance of another one diff --git a/src/core/builtin_helpers.py b/src/core/builtin_helpers.py index 81e2158..8103a2d 100644 --- a/src/core/builtin_helpers.py +++ b/src/core/builtin_helpers.py @@ -7,8 +7,10 @@ from core.builtin_concepts import BuiltinConcepts from core.concept import Concept, ConceptParts, DEFINITION_TYPE_BNF, concept_part_value from core.global_symbols import NotInit, NotFound, INIT_AST_PARSERS, DEFAULT_EVALUATORS from core.rule import Rule +from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import Tokenizer, TokenKind from core.utils import as_bag +from parsers.BaseExpressionParser import compile_disjunctions, AndNode from parsers.BaseNodeParser import SourceCodeNode, ConceptNode, UnrecognizedTokensNode, SourceCodeWithConceptNode, \ RuleNode, LexerNode from parsers.BaseParser import ParsingError @@ -205,7 +207,7 @@ def only_successful(context, return_values): def resolve_ambiguity(context, concepts): """ From the list of concepts, elect the one(s) that best suit(s) the context - Use the PRE metadata to choose the correct concepts + Use the PRE and WHERE metadata to choose the correct concepts :param context: :param concepts: :return: @@ -214,8 +216,9 @@ def resolve_ambiguity(context, concepts): # we first sort by condition complexity. The more complex is the PRE condition, the more likely # the concept matches the context by_complexity = {} + parts = [concept_part_value(ConceptParts.PRE), concept_part_value(ConceptParts.WHERE)] for c in concepts: - by_complexity.setdefault(get_condition_complexity(c, concept_part_value(ConceptParts.PRE)), []).append(c) + by_complexity.setdefault(get_concept_complexity(context, c, parts), []).append(c) remaining_concepts = [] for complexity in sorted(by_complexity.keys(), reverse=True): @@ -226,14 +229,14 @@ def resolve_ambiguity(context, concepts): evaluated = context.sheerka.evaluate_concept(context, c, eval_body=False, validation_only=True, - metadata=[ConceptParts.PRE]) + metadata=[ConceptParts.PRE, ConceptParts.WHERE]) if context.sheerka.is_success(evaluated) or evaluated.key == c.key: remaining_concepts.append(c) if len(remaining_concepts) > 0: break # no need to check concept with lower complexity - if len(remaining_concepts) in (0, 1): + if len(remaining_concepts) < 2: return remaining_concepts # they all failed the pre conditions or one champ is found # for concepts with the same condition complexity, we choose the one that has the less number of variables @@ -246,19 +249,58 @@ def resolve_ambiguity(context, concepts): return by_number_of_vars[min(by_number_of_vars.keys())] -def get_condition_complexity(concept, concept_part_str): +def get_condition_complexity(context, condition): + if condition is None or condition.strip() == "": + return 0 + + # # count the number of conjunctions + from parsers.LogicalOperatorParser import LogicalOperatorParser + parser = LogicalOperatorParser() + res = parser.parse(context, ParserInput(condition)) + if not res.status: + return 0 + + disjunctions = compile_disjunctions(res.body.body) + complexity = 0 + for conjunction in disjunctions: + node_complexity = len(conjunction.parts) if isinstance(conjunction, AndNode) else 1 + if node_complexity > complexity: + complexity = node_complexity + + return complexity + + +def get_concept_complexity(context, concept, concepts_parts): """ Need to find a proper algorithm to compute the complexity of a concept metadata So far, the concept is considered as complex if it has concept_part_str (so far with concept_part_str='pre') + :param context: :param concept: - :param concept_part_str: + :param concepts_parts: :return: """ - value = getattr(concept.get_metadata(), concept_part_str) - if value is None or value.strip() == 0: - return 0 + complexity = 0 + for i, parts in enumerate(reversed(concepts_parts)): - return 1 # no real computing as of now + for part in parts.split("|"): + value = getattr(concept.get_metadata(), part) + part_complexity = get_condition_complexity(context, value) + + if part_complexity > 0: + complexity += part_complexity + (i * 100) + + return complexity + + +def get_concepts_complexity(context, concepts, concepts_parts): + """ + compute the complexity of the concepts, relatively to each others + :param context: + :param concepts: concepts + :param concepts_parts: metadata to use to compute the complexity + :return: + """ + return {c.id or c.name: get_concept_complexity(context, c, concepts_parts) for c in concepts} def only_parsers_results(context, return_values): @@ -723,6 +765,27 @@ def set_is_evaluated(concepts, check_nb_variables=False): concepts.get_hints().is_evaluated = True +def update_concepts_hints(concepts, is_evaluated=None, recognized_by=None, is_instance=None): + if concepts is None: + return + + def update_concept_hints(c, _is_evaluated, _recognized_by, _is_instance): + if _is_evaluated is not None: + c.get_hints().is_evaluated = _is_evaluated + + if _recognized_by is not None: + c.get_hints().recognized_by = _recognized_by + + if _is_instance is not None: + c.get_hints().is_instance = _is_instance + + if hasattr(concepts, "__iter__"): + for concept in concepts: + update_concept_hints(concept, is_evaluated, recognized_by, is_instance) + else: + update_concept_hints(concepts, is_evaluated, recognized_by, is_instance) + + def ensure_concept(*concepts): if hasattr(concepts, "__iter__"): for concept in concepts: diff --git a/src/core/concept.py b/src/core/concept.py index d65de01..b89361c 100644 --- a/src/core/concept.py +++ b/src/core/concept.py @@ -44,14 +44,16 @@ def concept_part_value(c): class ConceptHints: is_evaluated: bool = False # True is the concept is evaluated by sheerka.eval_concept() need_validation: bool = False # True if the properties of the concept need to be validated - recognized_by: str = None - use_copy: bool = False + recognized_by: str = None # recognized by its name, by its id or its key + use_copy: bool = False # Do not modify, make a copy first + is_instance: bool = True # False if we think we recognize the definition of a concept, not its usage def copy(self): return ConceptHints(self.is_evaluated, self.need_validation, self.recognized_by, - self.use_copy) + self.use_copy, + self.is_instance) @dataclass @@ -624,6 +626,11 @@ class Concept: return Concept().update_from(self, update_hint=True, update_compiled=True) def get_concept(self): + """ + Used when there is a mix of Concept and ConceptNode + To quickly get the inner concept + :return: + """ return self diff --git a/src/core/global_symbols.py b/src/core/global_symbols.py index 35d8c9b..7cf2c70 100644 --- a/src/core/global_symbols.py +++ b/src/core/global_symbols.py @@ -96,4 +96,4 @@ INIT_AST_PARSERS = ["ExactConcept", INIT_AST_QUESTION_PARSERS = ["Expression"] -DEFAULT_EVALUATORS = ["Python", "Concept", "LexerNode", "ValidateConcept", "ResolveAmbiguity"] +DEFAULT_EVALUATORS = ["Python", "Concept", "LexerNode", "Expression", "ValidateConcept", "ResolveAmbiguity"] diff --git a/src/core/sheerka/ExecutionContext.py b/src/core/sheerka/ExecutionContext.py index b694c0c..eaf0700 100644 --- a/src/core/sheerka/ExecutionContext.py +++ b/src/core/sheerka/ExecutionContext.py @@ -79,7 +79,7 @@ class ExecutionContext: self.private_hints = set() self.protected_hints = set() self.global_hints = set() if global_hints is None else global_hints - self.errors = [] if errors is None else errors # error are global + self.errors = [] if errors is None else errors # errors are global self.inputs = {} # what were the parameters of the execution context self.values = {} # what was produced by the execution context diff --git a/src/core/sheerka/Sheerka.py b/src/core/sheerka/Sheerka.py index f03d4c4..fa4837b 100644 --- a/src/core/sheerka/Sheerka.py +++ b/src/core/sheerka/Sheerka.py @@ -46,6 +46,7 @@ RULES_EXECUTE_STEPS = [ # It indicate which parameter was used to recognize the concept RECOGNIZED_BY_ID = "by_id" RECOGNIZED_BY_NAME = "by_name" +RECOGNIZED_BY_KEY = "by_key" @dataclass @@ -53,9 +54,17 @@ class SheerkaMethod: """ Wrapper to sheerka method, to indicate if it's safe to call """ + name: str + service: str method: object has_side_effect: bool + def __repr__(self): + return self.name + + def __hash__(self): + return hash((self.name, self.service)) + class Sheerka(Concept): """ @@ -111,16 +120,18 @@ class Sheerka(Concept): self.methods_with_context = {"test_using_context"} # only the names, the method is defined in sheerka_methods self.pipe_functions = set() self.sheerka_methods = { - "test": SheerkaMethod(self.test, False), - "test_using_context": SheerkaMethod(self.test_using_context, False), - "test_dict": SheerkaMethod(self.test_dict, False), - "test_error": SheerkaMethod(self.test_error, False), - "is_sheerka": SheerkaMethod(self.is_sheerka, False), - "objvalue": SheerkaMethod(self.objvalue, False), + "test": SheerkaMethod("test", self.name, self.test, False), + "test_using_context": SheerkaMethod("test_using_context", self.name, self.test_using_context, False), + "test_dict": SheerkaMethod("test_dict", self.name, self.test_dict, False), + "test_error": SheerkaMethod("test_error", self.name, self.test_error, False), + "is_sheerka": SheerkaMethod("is_sheerka", self.name, self.is_sheerka, False), + "objvalue": SheerkaMethod("objvalue", self.name, self.objvalue, False), } self.concepts_ids = None + self._global_context_hints = set() # hints to add to every execution contexts before every execution + def __copy__(self): return self @@ -135,9 +146,10 @@ class Sheerka(Concept): def root_folder(self): return self.om.root_folder - def bind_service_method(self, bound_method, has_side_effect, as_name=None, visible=True): + def bind_service_method(self, service_name, bound_method, has_side_effect, as_name=None, visible=True): """ Bind service method to sheerka instance for ease of use ? + :param service_name: :param bound_method: :param has_side_effect: False if the method is safe :param as_name: give another name to the method @@ -157,7 +169,7 @@ class Sheerka(Concept): signature = inspect.signature(bound_method) if len(signature.parameters) > 0 and list(signature.parameters.keys())[0] == "context": self.methods_with_context.add(as_name) - self.sheerka_methods[as_name] = SheerkaMethod(bound_method, has_side_effect) + self.sheerka_methods[as_name] = SheerkaMethod(as_name, service_name, bound_method, has_side_effect) if is_pipe_function: self.pipe_functions.add(as_name) @@ -368,6 +380,9 @@ class Sheerka(Concept): text, desc=f"Evaluating '{text}'") as execution_context: + # adds the global context hints if any + execution_context.global_hints = self._global_context_hints.copy() + user_input = self.ret(self.name, True, self.new(BuiltinConcepts.USER_INPUT, body=text, user_name=user_name)) execution_context.add_inputs(user_input=user_input) @@ -465,17 +480,35 @@ class Sheerka(Concept): return None - def fast_resolve(self, key, return_new=True): - def add_recognized_by(c, _recognized_by): - c.get_hints().recognized_by = _recognized_by + def fast_resolve(self, key, return_new=True, force_instance=False): + """ + Resolve a concept, but do not use get_by_name() or get_by_id() + :param key: + :param return_new: + :param force_instance: When set, return an concept instance, not a concept definition + :return: + """ + + def update_hints(c, rec_by, is_inst, is_eval): + c.get_hints().recognized_by = rec_by + c.get_hints().is_instance = is_inst + if is_eval is None: + c.get_hints().is_evaluated = len(c.get_metadata().variables) > 0 + else: + c.get_hints().is_evaluated = is_eval return c - def new_instances(concepts, _recognized_by): + def new_instances(concepts, rec_by, is_inst, is_eval): if hasattr(concepts, "__iter__"): - return [add_recognized_by(self.new_from_template(c, c.key), _recognized_by) for c in concepts] + return [update_hints(self.new_from_template(c, c.key), rec_by, is_inst, is_eval) for c in + concepts] - return add_recognized_by(self.new_from_template(concepts, concepts.key), _recognized_by) + return update_hints(self.new_from_template(concepts, concepts.key), rec_by, is_inst, is_eval) + # ############## + # PREPROCESS + # ############## + # if the entry is a concept token, use its values. if isinstance(key, Token): if key.type == TokenKind.RULE: # do not recognize rules !!! return None @@ -484,10 +517,15 @@ class Sheerka(Concept): elif isinstance(key, str) and key.startswith("c:"): key = core.utils.unstr_concept(key) + # ############## + # Get the concept(s) + # ############## if isinstance(key, tuple): if key == (None, None): return None + is_instance = force_instance + is_evaluated = not force_instance if key[1]: concept = self.om.get(self.CONCEPTS_BY_ID_ENTRY, key[1]) recognized_by = RECOGNIZED_BY_ID @@ -495,12 +533,15 @@ class Sheerka(Concept): concept = self.om.get(self.CONCEPTS_BY_NAME_ENTRY, key[0]) recognized_by = RECOGNIZED_BY_NAME else: + is_instance = True + is_evaluated = None concept = self.om.get(self.CONCEPTS_BY_NAME_ENTRY, key) recognized_by = RECOGNIZED_BY_NAME if concept is NotFound: return None - return new_instances(concept, recognized_by) if return_new else concept + + return new_instances(concept, recognized_by, is_instance, is_evaluated) if return_new else concept def new(self, concept_key, **kwargs): """ @@ -632,6 +673,16 @@ class Sheerka(Concept): if hasattr(service, "reset_state"): service.reset_state() + def add_to_context(self, concept_key): + concept_key = concept_key.name if isinstance(concept_key, Concept) else concept_key + self._global_context_hints.add(concept_key) + return self.ret(self.name, True, self.new(BuiltinConcepts.SUCCESS)) + + def remove_fom_context(self, concept_key): + concept_key = concept_key.name if isinstance(concept_key, Concept) else concept_key + self._global_context_hints.remove(concept_key) + return self.ret(self.name, True, self.new(BuiltinConcepts.SUCCESS)) + def ret(self, who: str, status: bool, value, parents=None): """ Creates and returns a ReturnValue concept diff --git a/src/core/sheerka/SheerkaOntologyManager.py b/src/core/sheerka/SheerkaOntologyManager.py index 2b986cb..b5b8bbf 100644 --- a/src/core/sheerka/SheerkaOntologyManager.py +++ b/src/core/sheerka/SheerkaOntologyManager.py @@ -300,7 +300,7 @@ class SheerkaOntologyManager: :param concept: :return: """ - return self.current_cache_manager().add_concept(concept) + return self.current_cache_manager().add_concept(concept, self.ontologies[0].alt_sdp) def update_concept(self, old, new): """ diff --git a/src/core/sheerka/services/SheerkaAdmin.py b/src/core/sheerka/services/SheerkaAdmin.py index 9f0f7dd..36631b2 100644 --- a/src/core/sheerka/services/SheerkaAdmin.py +++ b/src/core/sheerka/services/SheerkaAdmin.py @@ -11,7 +11,7 @@ from core.sheerka.services.SheerkaHistoryManager import SheerkaHistoryManager from core.sheerka.services.SheerkaMemory import SheerkaMemory from core.sheerka.services.sheerka_service import BaseService -CONCEPTS_FILE_FULL = "full.sb" +CONCEPTS_FILE_FULL = "full.sb" # .sb for sheerka backup CONCEPTS_FILE_TO_USE = CONCEPTS_FILE_FULL @@ -22,24 +22,24 @@ class SheerkaAdmin(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self.caches_names, False) - self.sheerka.bind_service_method(self.cache, False) - self.sheerka.bind_service_method(self.restore, True) - self.sheerka.bind_service_method(self.concepts, False) - self.sheerka.bind_service_method(self.desc, False) - self.sheerka.bind_service_method(self.desc_evaluators, False) - self.sheerka.bind_service_method(self.desc_parsers, False) - self.sheerka.bind_service_method(self.extended_isinstance, False) - self.sheerka.bind_service_method(self.is_container, False) - self.sheerka.bind_service_method(self.format_rules, False) - self.sheerka.bind_service_method(self.exec_rules, False) - self.sheerka.bind_service_method(self.admin_push_ontology, True, as_name="push_ontology") - self.sheerka.bind_service_method(self.admin_pop_ontology, True, as_name="pop_ontology") - self.sheerka.bind_service_method(self.ontologies, False) - self.sheerka.bind_service_method(self.in_memory, False) - self.sheerka.bind_service_method(self.admin_history, False, as_name="history") - self.sheerka.bind_service_method(self.sdp, False) - self.sheerka.bind_service_method(self.atomic_def, False) + self.sheerka.bind_service_method(self.NAME, self.caches_names, False) + self.sheerka.bind_service_method(self.NAME, self.cache, False) + self.sheerka.bind_service_method(self.NAME, self.restore, True) + self.sheerka.bind_service_method(self.NAME, self.concepts, False) + self.sheerka.bind_service_method(self.NAME, self.builtins, False) + self.sheerka.bind_service_method(self.NAME, self.desc, False) + self.sheerka.bind_service_method(self.NAME, self.desc_evaluators, False) + self.sheerka.bind_service_method(self.NAME, self.desc_parsers, False) + self.sheerka.bind_service_method(self.NAME, self.extended_isinstance, False) + self.sheerka.bind_service_method(self.NAME, self.is_container, False) + self.sheerka.bind_service_method(self.NAME, self.format_rules, False) + self.sheerka.bind_service_method(self.NAME, self.exec_rules, False) + self.sheerka.bind_service_method(self.NAME, self.admin_push_ontology, True, as_name="push_ontology") + self.sheerka.bind_service_method(self.NAME, self.admin_pop_ontology, True, as_name="pop_ontology") + self.sheerka.bind_service_method(self.NAME, self.ontologies, False) + self.sheerka.bind_service_method(self.NAME, self.in_memory, False) + self.sheerka.bind_service_method(self.NAME, self.admin_history, False, as_name="history") + self.sheerka.bind_service_method(self.NAME, self.sdp, False) def caches_names(self): """ @@ -138,9 +138,11 @@ class SheerkaAdmin(BaseService): self.sheerka.save_execution_context = False enable_process_return_values_previous_value = self.sheerka.enable_process_return_values self.sheerka.enable_process_return_values = False + self.sheerka.add_to_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) nb_lines, nb_instructions, nb_lines_in_error, min_time, max_time = restore_from_file(backup_file) + self.sheerka.remove_fom_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) self.sheerka.enable_process_return_values = enable_process_return_values_previous_value self.sheerka.save_execution_context = True self.sheerka.during_restore = False @@ -167,6 +169,10 @@ class SheerkaAdmin(BaseService): concepts = sorted(self.sheerka.om.list(self.sheerka.CONCEPTS_BY_ID_ENTRY), key=lambda item: int(item.id)) return self.sheerka.new(BuiltinConcepts.TO_LIST, body=concepts) + def builtins(self): + builtins = sorted(self.sheerka.sheerka_methods.values(), key=lambda builtin_method: builtin_method.name) + return self.sheerka.new(BuiltinConcepts.TO_LIST, body=builtins) + def desc_evaluators(self): evaluators = {k: sorted(v[0].items(), reverse=True) for k, v in self.sheerka.services[SheerkaExecute.NAME].grouped_evaluators_cache.items()} @@ -241,20 +247,6 @@ class SheerkaAdmin(BaseService): return self.sheerka.isinstance(a, b) - @staticmethod - def atomic_def(a): - """ - Return the 'atomic definition' of a concept - a concept key stripped from its 'var' tokens - >>> assert atomic_def(Concept('a plus b').def_var("a").def_var("b")) == "plus" - >>> assert atomic_def(Concept('x is a y').def_var("x").def_var("y")) == "is a" - :param a: - :return: - """ - ensure_concept(a) - - return a.get_atomic_def() - @staticmethod def is_container(obj): """ diff --git a/src/core/sheerka/services/SheerkaComparisonManager.py b/src/core/sheerka/services/SheerkaComparisonManager.py index 568e4eb..5b62fb7 100644 --- a/src/core/sheerka/services/SheerkaComparisonManager.py +++ b/src/core/sheerka/services/SheerkaComparisonManager.py @@ -47,12 +47,12 @@ class SheerkaComparisonManager(BaseService): cache = Cache().auto_configure(self.RESOLVED_COMPARISON_ENTRY) self.sheerka.om.register_cache(self.RESOLVED_COMPARISON_ENTRY, cache, persist=False) - self.sheerka.bind_service_method(self.set_is_greater_than, True) - self.sheerka.bind_service_method(self.set_is_less_than, True) - self.sheerka.bind_service_method(self.set_is_lesser, True) - self.sheerka.bind_service_method(self.set_is_greatest, True) - self.sheerka.bind_service_method(self.get_partition, False) - self.sheerka.bind_service_method(self.get_weights, False) + self.sheerka.bind_service_method(self.NAME, self.set_is_greater_than, True) + self.sheerka.bind_service_method(self.NAME, self.set_is_less_than, True) + self.sheerka.bind_service_method(self.NAME, self.set_is_lesser, True) + self.sheerka.bind_service_method(self.NAME, self.set_is_greatest, True) + self.sheerka.bind_service_method(self.NAME, self.get_partition, False) + self.sheerka.bind_service_method(self.NAME, self.get_weights, False) @staticmethod def _compute_key(prop_name, comparison_context): diff --git a/src/core/sheerka/services/SheerkaConceptManager.py b/src/core/sheerka/services/SheerkaConceptManager.py index 35cf2a7..0133624 100644 --- a/src/core/sheerka/services/SheerkaConceptManager.py +++ b/src/core/sheerka/services/SheerkaConceptManager.py @@ -113,26 +113,26 @@ class SheerkaConceptManager(BaseService): self.compiled_concepts_by_regex = [] def initialize(self): - self.sheerka.bind_service_method(self.create_new_concept, True) - self.sheerka.bind_service_method(self.modify_concept, True) - self.sheerka.bind_service_method(self.remove_concept, True) - self.sheerka.bind_service_method(self.set_id_if_needed, True) - self.sheerka.bind_service_method(self.set_attr, True) - self.sheerka.bind_service_method(self.get_attr, False) - self.sheerka.bind_service_method(self.smart_get_attr, False) - self.sheerka.bind_service_method(self.set_property, True, as_name="set_prop") - self.sheerka.bind_service_method(self.get_property, False, as_name="get_prop") - self.sheerka.bind_service_method(self.get_by_key, False, visible=False) - self.sheerka.bind_service_method(self.get_by_name, False, visible=False) - self.sheerka.bind_service_method(self.get_by_hash, False, visible=False) - self.sheerka.bind_service_method(self.get_by_id, False, visible=False) - self.sheerka.bind_service_method(self.is_not_a_concept_name, False, visible=False) - self.sheerka.bind_service_method(self.is_a_concept_name, False, visible=False) - self.sheerka.bind_service_method(self.get_concepts_by_first_token, False, visible=False) - self.sheerka.bind_service_method(self.get_concepts_by_first_regex, False, visible=False) - self.sheerka.bind_service_method(self.get_concepts_bnf_definitions, False, visible=False) - self.sheerka.bind_service_method(self.clear_bnf_definition, True, visible=False) - self.sheerka.bind_service_method(self.set_precedence, True) + self.sheerka.bind_service_method(self.NAME, self.create_new_concept, True) + self.sheerka.bind_service_method(self.NAME, self.modify_concept, True) + self.sheerka.bind_service_method(self.NAME, self.remove_concept, True) + self.sheerka.bind_service_method(self.NAME, self.set_id_if_needed, True) + self.sheerka.bind_service_method(self.NAME, self.set_attr, True) + self.sheerka.bind_service_method(self.NAME, self.get_attr, False) + self.sheerka.bind_service_method(self.NAME, self.smart_get_attr, False) + self.sheerka.bind_service_method(self.NAME, self.set_property, True, as_name="set_prop") + self.sheerka.bind_service_method(self.NAME, self.get_property, False, as_name="get_prop") + self.sheerka.bind_service_method(self.NAME, self.get_by_key, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_by_name, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_by_hash, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_by_id, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.is_not_a_concept_name, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.is_a_concept_name, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_concepts_by_first_token, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_concepts_by_first_regex, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_concepts_bnf_definitions, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.clear_bnf_definition, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.set_precedence, True) register_concept_cache = self.sheerka.om.register_concept_cache diff --git a/src/core/sheerka/services/SheerkaConceptsAlgebra.py b/src/core/sheerka/services/SheerkaConceptsAlgebra.py index 5671d67..5db3a9e 100644 --- a/src/core/sheerka/services/SheerkaConceptsAlgebra.py +++ b/src/core/sheerka/services/SheerkaConceptsAlgebra.py @@ -4,7 +4,6 @@ from operator import attrgetter from core.builtin_concepts import BuiltinConcepts from core.builtin_helpers import ensure_concept from core.concept import Concept -from core.sheerka.Sheerka import Sheerka from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager from core.sheerka.services.sheerka_service import BaseService @@ -28,9 +27,9 @@ class SheerkaConceptsAlgebra(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self.cadd, False) - self.sheerka.bind_service_method(self.csub, False) - self.sheerka.bind_service_method(self.recognize, False) + self.sheerka.bind_service_method(self.NAME, self.cadd, False) + self.sheerka.bind_service_method(self.NAME, self.csub, False) + self.sheerka.bind_service_method(self.NAME, self.recognize, False) def cadd(self, context, *concepts): """ diff --git a/src/core/sheerka/services/SheerkaDebugManager.py b/src/core/sheerka/services/SheerkaDebugManager.py index 91e99a5..146eff2 100644 --- a/src/core/sheerka/services/SheerkaDebugManager.py +++ b/src/core/sheerka/services/SheerkaDebugManager.py @@ -320,22 +320,22 @@ class SheerkaDebugManager(BaseService): } def initialize(self): - self.sheerka.bind_service_method(self.set_debug, True) - self.sheerka.bind_service_method(self.inspect, False) - self.sheerka.bind_service_method(self.get_value, False) - self.sheerka.bind_service_method(self.get_debugger, False) - self.sheerka.bind_service_method(self.reset_debug, False) - self.sheerka.bind_service_method(self.set_debug_var, True) - self.sheerka.bind_service_method(self.set_debug_rule, True) - self.sheerka.bind_service_method(self.set_debug_concept, True) - self.sheerka.bind_service_method(self.list_debug_vars, True) - self.sheerka.bind_service_method(self.list_debug_rules, True) - self.sheerka.bind_service_method(self.list_debug_concepts, True) - self.sheerka.bind_service_method(self.register_debug_vars, True, visible=False) - self.sheerka.bind_service_method(self.register_debug_rules, True, visible=False) - self.sheerka.bind_service_method(self.register_debug_concepts, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.set_debug, True) + self.sheerka.bind_service_method(self.NAME, self.inspect, False) + self.sheerka.bind_service_method(self.NAME, self.get_value, False) + self.sheerka.bind_service_method(self.NAME, self.get_debugger, False) + self.sheerka.bind_service_method(self.NAME, self.reset_debug, False) + self.sheerka.bind_service_method(self.NAME, self.set_debug_var, True) + self.sheerka.bind_service_method(self.NAME, self.set_debug_rule, True) + self.sheerka.bind_service_method(self.NAME, self.set_debug_concept, True) + self.sheerka.bind_service_method(self.NAME, self.list_debug_vars, True) + self.sheerka.bind_service_method(self.NAME, self.list_debug_rules, True) + self.sheerka.bind_service_method(self.NAME, self.list_debug_concepts, True) + self.sheerka.bind_service_method(self.NAME, self.register_debug_vars, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.register_debug_rules, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.register_debug_concepts, True, visible=False) - # self.sheerka.bind_service_method(self.get_debug_settings, False, as_name="debug_settings") + # self.sheerka.bind_service_method(self.NAME,self.get_debug_settings, False, as_name="debug_settings") # register what can be registered from parsers.BnfNodeParser import BnfNodeParser from evaluators.DefConceptEvaluator import DefConceptEvaluator @@ -349,8 +349,8 @@ class SheerkaDebugManager(BaseService): self.register_debug_vars(DefConceptEvaluator.NAME, "get_variables", "possible_vars") self.register_debug_vars(PythonEvaluator.NAME, "eval", "globals") self.register_debug_vars(PythonEvaluator.NAME, "eval", "ret") - self.register_debug_vars("Exceptions", PythonEvaluator.NAME+"-eval", "exception") - self.register_debug_vars("Exceptions", PythonEvaluator.NAME+"-eval", "trace") + self.register_debug_vars("Exceptions", PythonEvaluator.NAME + "-eval", "exception") + self.register_debug_vars("Exceptions", PythonEvaluator.NAME + "-eval", "trace") self.register_debug_vars(SyaNodeParser.NAME, "parse", "*") self.register_debug_vars(MultipleSuccessEvaluator.NAME, "matches", "return_values") @@ -919,4 +919,3 @@ class SheerkaDebugManager(BaseService): del res["self"] return res - diff --git a/src/core/sheerka/services/SheerkaDump.py b/src/core/sheerka/services/SheerkaDump.py index bfd1ce1..2e3733f 100644 --- a/src/core/sheerka/services/SheerkaDump.py +++ b/src/core/sheerka/services/SheerkaDump.py @@ -20,8 +20,11 @@ class SheerkaDump(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self.dump_desc, True, "old_desc") # has_side_effect 'cause concept is evaluated - self.sheerka.bind_service_method(self.dump_sdp, False, "dump_sdp") + self.sheerka.bind_service_method(self.NAME, + self.dump_desc, + True, + "old_desc") # has_side_effect 'cause concept is evaluated + self.sheerka.bind_service_method(self.NAME, self.dump_sdp, False, "dump_sdp") def dump_desc(self, *concept_names, eval=False): first = True diff --git a/src/core/sheerka/services/SheerkaEvaluateConcept.py b/src/core/sheerka/services/SheerkaEvaluateConcept.py index ce1dc47..0ec76c3 100644 --- a/src/core/sheerka/services/SheerkaEvaluateConcept.py +++ b/src/core/sheerka/services/SheerkaEvaluateConcept.py @@ -46,10 +46,10 @@ class SheerkaEvaluateConcept(BaseService): self.rule_evaluator = None def initialize(self): - self.sheerka.bind_service_method(self.evaluate_concept, True) - self.sheerka.bind_service_method(self.call_concept, True) - self.sheerka.bind_service_method(self.call_concept, False, as_name="evaluate_question") - self.sheerka.bind_service_method(self.set_auto_eval, True) + self.sheerka.bind_service_method(self.NAME, self.evaluate_concept, True) + self.sheerka.bind_service_method(self.NAME, self.call_concept, True) + self.sheerka.bind_service_method(self.NAME, self.call_concept, False, as_name="evaluate_question") + self.sheerka.bind_service_method(self.NAME, self.set_auto_eval, True) def initialize_deferred(self, context, is_first_time): self.rule_evaluator = self.sheerka.services[SheerkaEvaluateRules.NAME] @@ -499,9 +499,7 @@ class SheerkaEvaluateConcept(BaseService): if current_prop in (ConceptParts.WHERE, ConceptParts.PRE): sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED) - - if current_prop == ConceptParts.WHERE: - sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED) + sub_context.protected_hints.add(BuiltinConcepts.EVALUATING_PRE_OR_WHERE_CLAUSE) # when it's a concept, evaluate it if isinstance(to_resolve, Concept) and \ @@ -642,6 +640,9 @@ class SheerkaEvaluateConcept(BaseService): if context.sheerka.isa(concept, context.sheerka.new(BuiltinConcepts.AUTO_EVAL)): sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) + if context.in_context(BuiltinConcepts.EVALUATING_PRE_OR_WHERE_CLAUSE): + sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED) + sub_context.add_to_short_term_memory(CURRENT_OBJ, concept) try: @@ -764,6 +765,13 @@ class SheerkaEvaluateConcept(BaseService): :param kwargs: :return: """ + if concept.get_hints().use_copy or not concept.get_hints().is_instance: + concept = concept.copy() + concept.get_hints().use_copy = False + concept.get_hints().is_instance = True + concept.get_hints().is_evaluated = False # force evaluation + + # TODO : update the variables before calling the concept evaluated = self.evaluate_concept(context, concept) if self.sheerka.has_error(context, evaluated): diff --git a/src/core/sheerka/services/SheerkaEvaluateRules.py b/src/core/sheerka/services/SheerkaEvaluateRules.py index 607e4fa..6afbae4 100644 --- a/src/core/sheerka/services/SheerkaEvaluateRules.py +++ b/src/core/sheerka/services/SheerkaEvaluateRules.py @@ -18,8 +18,8 @@ class SheerkaEvaluateRules(BaseService): self.network = ReteNetwork() def initialize(self): - self.sheerka.bind_service_method(self.evaluate_format_rules, False, visible=False) - self.sheerka.bind_service_method(self.evaluate_exec_rules, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.evaluate_format_rules, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.evaluate_exec_rules, False, visible=False) self.reset_evaluators() self.sheerka.subscribe(EVENT_RULE_CREATED, self.on_rule_created) self.sheerka.subscribe(EVENT_RULE_DELETED, self.on_rule_deleted) diff --git a/src/core/sheerka/services/SheerkaEventManager.py b/src/core/sheerka/services/SheerkaEventManager.py index 57c1c4e..8b1af14 100644 --- a/src/core/sheerka/services/SheerkaEventManager.py +++ b/src/core/sheerka/services/SheerkaEventManager.py @@ -1,4 +1,5 @@ from threading import RLock + from core.global_symbols import NotFound from core.sheerka.services.sheerka_service import BaseService @@ -16,8 +17,8 @@ class SheerkaEventManager(BaseService): self.subscribers = {} def initialize(self): - self.sheerka.bind_service_method(self.subscribe, True, visible=False) - self.sheerka.bind_service_method(self.publish, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.subscribe, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.publish, True, visible=False) def save_state(self, context): with self._lock: diff --git a/src/core/sheerka/services/SheerkaExecute.py b/src/core/sheerka/services/SheerkaExecute.py index 272185d..c0357e7 100644 --- a/src/core/sheerka/services/SheerkaExecute.py +++ b/src/core/sheerka/services/SheerkaExecute.py @@ -221,12 +221,12 @@ class SheerkaExecute(BaseService): self.rules_eval_service = None def initialize(self): - self.sheerka.bind_service_method(self.execute, True, visible=False) - self.sheerka.bind_service_method(self.execute_rules, True, visible=False) - self.sheerka.bind_service_method(self.parse_unrecognized, False, visible=False) - self.sheerka.bind_service_method(self.parse_function, False, visible=False) - self.sheerka.bind_service_method(self.parse_python, False, visible=False) - self.sheerka.bind_service_method(self.parse_expression, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.execute, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.execute_rules, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.parse_unrecognized, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.parse_function, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.parse_python, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.parse_expression, False, visible=False) self.reset_registered_evaluators() self.reset_registered_parsers() diff --git a/src/core/sheerka/services/SheerkaHasAManager.py b/src/core/sheerka/services/SheerkaHasAManager.py index 74d180a..524dd51 100644 --- a/src/core/sheerka/services/SheerkaHasAManager.py +++ b/src/core/sheerka/services/SheerkaHasAManager.py @@ -11,8 +11,8 @@ class SheerkaHasAManager(BaseService): super().__init__(sheerka, order=22) def initialize(self): - self.sheerka.bind_service_method(self.set_hasa, True) - self.sheerka.bind_service_method(self.hasa, True) + self.sheerka.bind_service_method(self.NAME, self.set_hasa, True) + self.sheerka.bind_service_method(self.NAME, self.hasa, True) def set_hasa(self, context, concept_a, concept_b): """ @@ -37,8 +37,13 @@ class SheerkaHasAManager(BaseService): concept=concept_a)) merged_concepts = merge_sets(concept_a.get_prop(BuiltinConcepts.HASA), {concept_b}) - to_add = {"props": {BuiltinConcepts.HASA: merged_concepts}} - return self.sheerka.modify_concept(context, concept_a, to_add, modify_source=True) + + 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) + else: + concept_a.set_prop(BuiltinConcepts.HASA, merged_concepts) + return self.sheerka.ret(self.NAME, True, concept_a) def hasa(self, concept_a, concept_b): """ diff --git a/src/core/sheerka/services/SheerkaIsAManager.py b/src/core/sheerka/services/SheerkaIsAManager.py index c149d63..e47972c 100644 --- a/src/core/sheerka/services/SheerkaIsAManager.py +++ b/src/core/sheerka/services/SheerkaIsAManager.py @@ -19,12 +19,12 @@ class SheerkaIsAManager(BaseService): super().__init__(sheerka, order=21) def initialize(self): - self.sheerka.bind_service_method(self.set_isa, True) - self.sheerka.bind_service_method(self.get_set_elements, True) # concepts are evaluated - self.sheerka.bind_service_method(self.add_concept_to_set, True) - self.sheerka.bind_service_method(self.isinset, False) - self.sheerka.bind_service_method(self.isa, False) - self.sheerka.bind_service_method(self.isaset, True) # concept is evaluated, need to change the code + self.sheerka.bind_service_method(self.NAME, self.set_isa, True) + self.sheerka.bind_service_method(self.NAME, self.get_set_elements, True) # concepts are evaluated + self.sheerka.bind_service_method(self.NAME, self.add_concept_to_set, True) + self.sheerka.bind_service_method(self.NAME, self.isinset, False) + self.sheerka.bind_service_method(self.NAME, self.isa, False) + self.sheerka.bind_service_method(self.NAME, self.isaset, True) # concept is evaluated, need to change the code cache = SetCache().auto_configure(self.CONCEPTS_GROUPS_ENTRY) self.sheerka.om.register_cache(self.CONCEPTS_GROUPS_ENTRY, cache) @@ -53,16 +53,20 @@ class SheerkaIsAManager(BaseService): # KSI 20200709 add the concept, not its 'id' or 'key' # It will allow conditions handling if concept set has its WHERE or PRE set to something new_concept_set = merge_sets(concept.get_prop(BuiltinConcepts.ISA), {concept_set}) - to_add = {"props": {BuiltinConcepts.ISA: new_concept_set}} - res = self.sheerka.modify_concept(context, concept, to_add, modify_source=True) - if not res.status: + + 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) + if not res.status: + return res + else: + concept = res.body.body + + res = self.add_concept_to_set(context, concept, concept_set) return res else: - concept = res.body.body - - res = self.add_concept_to_set(context, concept, concept_set) - - return res + concept.set_prop(BuiltinConcepts.ISA, new_concept_set) + return self.sheerka.ret(self.NAME, True, concept) def add_concept_to_set(self, context, concept, concept_set): """ @@ -270,7 +274,7 @@ class SheerkaIsAManager(BaseService): sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) errors = [] for element_id in ids: - concept = self.sheerka.fast_resolve((None, element_id)) + concept = self.sheerka.fast_resolve((None, element_id), force_instance=True) if concept: if len(concept.get_metadata().variables) == 0: evaluated = self.sheerka.evaluate_concept(sub_context, concept) diff --git a/src/core/sheerka/services/SheerkaMemory.py b/src/core/sheerka/services/SheerkaMemory.py index b5f270a..3a97ea3 100644 --- a/src/core/sheerka/services/SheerkaMemory.py +++ b/src/core/sheerka/services/SheerkaMemory.py @@ -36,21 +36,23 @@ class SheerkaMemory(BaseService): super().__init__(sheerka, order=13) self.short_term_objects = FastCache() self.registration = {} + self.enable_memory_registration = True def initialize(self): - self.sheerka.bind_service_method(self.get_from_short_term_memory, False, visible=False) - self.sheerka.bind_service_method(self.get_all_short_term_memory, False, visible=False) - self.sheerka.bind_service_method(self.add_to_short_term_memory, True, visible=False) - self.sheerka.bind_service_method(self.remove_context, True, as_name="clear_short_term_memory", visible=False) - self.sheerka.bind_service_method(self.add_to_memory, True) - self.sheerka.bind_service_method(self.add_many_to_short_term_memory, True, visible=False) - self.sheerka.bind_service_method(self.get_from_memory, False) - self.sheerka.bind_service_method(self.get_last_from_memory, False) - self.sheerka.bind_service_method(self.register_object, True, visible=False) - self.sheerka.bind_service_method(self.unregister_object, True, visible=False) - self.sheerka.bind_service_method(self.commit_registered_objects, True, visible=False) - self.sheerka.bind_service_method(self.memory, False) - self.sheerka.bind_service_method(self.mem, False) + self.sheerka.bind_service_method(self.NAME, self.get_from_short_term_memory, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_all_short_term_memory, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.add_to_short_term_memory, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.remove_context, True, as_name="clear_short_term_memory", + visible=False) + self.sheerka.bind_service_method(self.NAME, self.add_to_memory, True) + self.sheerka.bind_service_method(self.NAME, self.add_many_to_short_term_memory, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_from_memory, False) + self.sheerka.bind_service_method(self.NAME, self.get_last_from_memory, False) + self.sheerka.bind_service_method(self.NAME, self.register_object, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.unregister_object, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.commit_registered_objects, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.memory, False) + self.sheerka.bind_service_method(self.NAME, self.mem, False) cache = ListIfNeededCache().auto_configure(self.OBJECTS_ENTRY) self.sheerka.om.register_cache(self.OBJECTS_ENTRY, cache, persist=True, use_ref=True) @@ -187,7 +189,7 @@ class SheerkaMemory(BaseService): :param concept: :return: """ - if self.sheerka.during_initialisation or self.sheerka.during_restore: + if not self.enable_memory_registration or self.sheerka.during_initialisation or self.sheerka.during_restore: return self.registration[key] = concept @@ -220,39 +222,48 @@ class SheerkaMemory(BaseService): :param name: :return: """ - name_to_use = name.name if isinstance(name, Concept) else name - self.unregister_object(context, name_to_use) + self.enable_memory_registration = False + try: + name_to_use = name.name if isinstance(name, Concept) else name + self.unregister_object(context, name_to_use) - # first try direct access - obj = self.get_last_from_memory(context, name_to_use) - if obj is not NotFound: - return obj.obj + # first try direct access + obj = self.get_last_from_memory(context, name_to_use) + if obj is not NotFound: + return obj.obj - all_objects = self.sheerka.om.list(SheerkaMemory.OBJECTS_ENTRY) # not always a list of list - all_objects_copy = [] # to transform into list of list - for obj in all_objects: - if isinstance(obj, list): - all_objects_copy.append(obj.copy()) - else: - all_objects_copy.append([obj]) + # only filter if it's a valid predicate + from parsers.BaseExpressionParser import VariableNode, NameExprNode + parsed_ret_val = self.sheerka.parse_expression(context, name_to_use, auto_compile=False) + if parsed_ret_val.status and not isinstance(parsed_ret_val.body.body, (VariableNode, NameExprNode)): - while len(all_objects_copy) > 0: - current_list = [] - temp = [] - for obj in all_objects_copy: - current_list.append(obj.pop(-1)) - if len(obj) > 0: - temp.append(obj) + all_objects = self.sheerka.om.list(SheerkaMemory.OBJECTS_ENTRY) # not always a list of list + all_objects_copy = [] # to transform into list of list + for obj in all_objects: + if isinstance(obj, list): + all_objects_copy.append(obj.copy()) + else: + all_objects_copy.append([obj]) - all_objects_copy = temp # list constructed with the last item of each item - current_list = sorted(current_list, key=lambda o: o.timestamp, reverse=True) - current_objects = [o.obj for o in current_list] + while len(all_objects_copy) > 0: + current_list = [] + temp = [] + for obj in all_objects_copy: + current_list.append(obj.pop(-1)) + if len(obj) > 0: + temp.append(obj) - res = self.sheerka.filter_objects(context, current_objects, name_to_use) - if len(res) > 0: - return res[0] # only the first, as it should have a better timestamp + all_objects_copy = temp # list constructed with the last item of each item + current_list = sorted(current_list, key=lambda o: o.timestamp, reverse=True) + current_objects = [o.obj for o in current_list] - return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#name": name_to_use}) + res = self.sheerka.filter_objects(context, current_objects, name_to_use) + if len(res) > 0: + return res[0] # only the first, as it should have a better timestamp + + return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#name": name_to_use}) + finally: + self.enable_memory_registration = True def mem(self): keys = sorted([k for k in self.sheerka.om.list(SheerkaMemory.OBJECTS_ENTRY)]) diff --git a/src/core/sheerka/services/SheerkaOut.py b/src/core/sheerka/services/SheerkaOut.py index 2f10e8f..7c505db 100644 --- a/src/core/sheerka/services/SheerkaOut.py +++ b/src/core/sheerka/services/SheerkaOut.py @@ -13,7 +13,7 @@ class SheerkaOut(BaseService): self.out_visitors = [ConsoleVisitor(expand_mode="all_but_first")] def initialize(self): - self.sheerka.bind_service_method(self.process_return_values, False) + self.sheerka.bind_service_method(self.NAME, self.process_return_values, False) self.sheerka.register_debug_vars("Visitor", "create_out_tree", "Exception") self.sheerka.register_debug_vars(SheerkaOut.NAME, "create_out_tree", "out_tree") diff --git a/src/core/sheerka/services/SheerkaQueryManager.py b/src/core/sheerka/services/SheerkaQueryManager.py index dadf891..640ab40 100644 --- a/src/core/sheerka/services/SheerkaQueryManager.py +++ b/src/core/sheerka/services/SheerkaQueryManager.py @@ -26,13 +26,13 @@ class SheerkaQueryManager(BaseService): self.rule_evaluator = None def initialize(self): - self.sheerka.bind_service_method(self.filter_objects, False) - self.sheerka.bind_service_method(self.select_objects, False) - self.sheerka.bind_service_method(self.collect_attributes, False) + self.sheerka.bind_service_method(self.NAME, self.filter_objects, False) + self.sheerka.bind_service_method(self.NAME, self.select_objects, False) + self.sheerka.bind_service_method(self.NAME, self.collect_attributes, False) - self.sheerka.bind_service_method(self.filter_objects, False, as_name="pipe_where") - self.sheerka.bind_service_method(self.select_objects, False, as_name="pipe_select") - self.sheerka.bind_service_method(self.collect_attributes, False, as_name="pipe_props") + self.sheerka.bind_service_method(self.NAME, self.filter_objects, False, as_name="pipe_where") + self.sheerka.bind_service_method(self.NAME, self.select_objects, False, as_name="pipe_select") + self.sheerka.bind_service_method(self.NAME, self.collect_attributes, False, as_name="pipe_props") self.sheerka.register_debug_vars(SheerkaQueryManager.NAME, "filter_objects", "query") @@ -59,12 +59,13 @@ class SheerkaQueryManager(BaseService): if k == "__type": conditions.append(f"get_type(self) == {current_variable_name}") - elif k == "atomic_def": - conditions.append(f"atomic_def(self) == {current_variable_name}") - elif k in ("__self", "_"): conditions.append(f"self == {current_variable_name}") + elif k.endswith("_contains"): + prop_name = k[:-9] + conditions.append(f"{current_variable_name} in self.{prop_name}") + else: conditions.append(f"self.{k} == {current_variable_name}") @@ -91,6 +92,7 @@ class SheerkaQueryManager(BaseService): objects = objects.body debugger.debug_entering(nb_objects=len(objects), predicate=predicate, **kwargs) + local_namespace = {} query_by_kwargs = self.get_query_by_kwargs(local_namespace, **kwargs) diff --git a/src/core/sheerka/services/SheerkaQuestion.py b/src/core/sheerka/services/SheerkaQuestion.py index b8ac4f6..92242fb 100644 --- a/src/core/sheerka/services/SheerkaQuestion.py +++ b/src/core/sheerka/services/SheerkaQuestion.py @@ -1,5 +1,4 @@ from core.builtin_concepts import BuiltinConcepts -from core.concept import Concept from core.sheerka.services.sheerka_service import BaseService @@ -10,7 +9,7 @@ class SheerkaQuestion(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self.is_question, False) + self.sheerka.bind_service_method(self.NAME, self.is_question, False) @staticmethod def is_question(context): diff --git a/src/core/sheerka/services/SheerkaResultManager.py b/src/core/sheerka/services/SheerkaResultManager.py index df8b4ab..cdc269b 100644 --- a/src/core/sheerka/services/SheerkaResultManager.py +++ b/src/core/sheerka/services/SheerkaResultManager.py @@ -31,14 +31,15 @@ class SheerkaResultManager(BaseService): self.state_vars = ["last_created_concept_id", "last_error_event_id"] def initialize(self): - self.sheerka.bind_service_method(self.get_results_by_digest, True) # digest is recorded - self.sheerka.bind_service_method(self.get_results_by_command, True) # digest is recorded - self.sheerka.bind_service_method(self.get_last_results, True) # digest is recorded - self.sheerka.bind_service_method(self.get_results, False) - self.sheerka.bind_service_method(self.get_execution_item, False) - self.sheerka.bind_service_method(self.get_last_return_value, False, as_name="last_ret") - self.sheerka.bind_service_method(self.get_last_created_concept, False, as_name="last_created_concept") - self.sheerka.bind_service_method(self.get_last_error, False, as_name="last_err") + self.sheerka.bind_service_method(self.NAME, self.get_results_by_digest, True) # digest is recorded + self.sheerka.bind_service_method(self.NAME, self.get_results_by_command, True) # digest is recorded + self.sheerka.bind_service_method(self.NAME, self.get_last_results, True) # digest is recorded + self.sheerka.bind_service_method(self.NAME, self.get_results, False) + self.sheerka.bind_service_method(self.NAME, self.get_execution_item, False) + self.sheerka.bind_service_method(self.NAME, self.get_last_return_value, False, as_name="last_ret") + self.sheerka.bind_service_method(self.NAME, self.get_last_created_concept, False, + as_name="last_created_concept") + self.sheerka.bind_service_method(self.NAME, self.get_last_error, False, as_name="last_err") self.sheerka.subscribe(EVENT_USER_INPUT_EVALUATED, self.user_input_evaluated) self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.new_concept_created) @@ -77,6 +78,7 @@ class SheerkaResultManager(BaseService): """ if len(kwargs) == 0: return None + res = [] if "filter" in kwargs: res.append(kwargs["filter"]) @@ -90,6 +92,10 @@ class SheerkaResultManager(BaseService): v = '"' + v.translate(str.maketrans({'"': r'\"'})) + '"' res.append(f"{k} == {v}") predicate = " and ".join(res) + + if predicate.strip() == "": + return None + return compile(ast.parse(predicate, mode="eval"), "", mode="eval") @staticmethod @@ -380,15 +386,16 @@ class SheerkaResultManager(BaseService): folder = os.getenv(SHEERKA_BACKUP_FOLDER) if folder is None: - print(f"{CCM['red']}Cannot backup command. Backup folder (env SHEERKA_BACKUP_FOLDER) is not defined.{CCM['reset']}") + print( + f"{CCM['red']}Cannot backup command. Backup folder (env SHEERKA_BACKUP_FOLDER) is not defined.{CCM['reset']}") return file = os.getenv(SHEERKA_BACKUP_FILE) if file is None: - print(f"{CCM['red']}Cannot backup command. Backup file (env SHEERKA_BACKUP_FILE) is not defined.{CCM['reset']}") + print( + f"{CCM['red']}Cannot backup command. Backup file (env SHEERKA_BACKUP_FILE) is not defined.{CCM['reset']}") return full_path = os.path.join(folder, file) with open(full_path, "a") as f: f.write(f"{execution_context.event.message}\n") - diff --git a/src/core/sheerka/services/SheerkaRuleManager.py b/src/core/sheerka/services/SheerkaRuleManager.py index cd505d2..17391d6 100644 --- a/src/core/sheerka/services/SheerkaRuleManager.py +++ b/src/core/sheerka/services/SheerkaRuleManager.py @@ -82,13 +82,13 @@ class SheerkaRuleManager(BaseService): self.expression_parser = LogicalOperatorParser() def initialize(self): - self.sheerka.bind_service_method(self.create_new_rule, True, visible=False) - self.sheerka.bind_service_method(self.remove_rule, True) - self.sheerka.bind_service_method(self.get_rule_by_id, False) - self.sheerka.bind_service_method(self.get_rule_by_name, False) - self.sheerka.bind_service_method(self.get_format_rules, False, visible=False) - self.sheerka.bind_service_method(self.get_exec_rules, False, visible=False) - self.sheerka.bind_service_method(self.resolve_rule, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.create_new_rule, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.remove_rule, True) + self.sheerka.bind_service_method(self.NAME, self.get_rule_by_id, False) + self.sheerka.bind_service_method(self.NAME, self.get_rule_by_name, False) + self.sheerka.bind_service_method(self.NAME, self.get_format_rules, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.get_exec_rules, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.resolve_rule, False, visible=False) cache = Cache().auto_configure(self.FORMAT_RULE_ENTRY) self.sheerka.om.register_cache(self.FORMAT_RULE_ENTRY, cache, True, True) diff --git a/src/core/sheerka/services/SheerkaVariableManager.py b/src/core/sheerka/services/SheerkaVariableManager.py index f4d5369..17a3893 100644 --- a/src/core/sheerka/services/SheerkaVariableManager.py +++ b/src/core/sheerka/services/SheerkaVariableManager.py @@ -53,14 +53,14 @@ class SheerkaVariableManager(BaseService): } def initialize(self): - self.sheerka.bind_service_method(self.record_var, True, visible=False) - self.sheerka.bind_service_method(self.load_var, False, visible=False) - self.sheerka.bind_service_method(self.record_internal_var, True, visible=False) - self.sheerka.bind_service_method(self.load_internal_var, False, visible=False) - self.sheerka.bind_service_method(self.delete_var, True, visible=False) - self.sheerka.bind_service_method(self.set_var, True) - self.sheerka.bind_service_method(self.get_var, False) - self.sheerka.bind_service_method(self.list_vars, False) + self.sheerka.bind_service_method(self.NAME, self.record_var, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.load_var, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.record_internal_var, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.load_internal_var, False, visible=False) + self.sheerka.bind_service_method(self.NAME, self.delete_var, True, visible=False) + self.sheerka.bind_service_method(self.NAME, self.set_var, True) + self.sheerka.bind_service_method(self.NAME, self.get_var, False) + self.sheerka.bind_service_method(self.NAME, self.list_vars, False) cache = Cache().auto_configure(self.VARIABLES_ENTRY) self.sheerka.om.register_cache(self.VARIABLES_ENTRY, cache, True, True) diff --git a/src/core/utils.py b/src/core/utils.py index 759eb41..5114608 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -327,6 +327,12 @@ def merge_dictionaries(a, b): def merge_sets(a, b): + """ + Merge that can handle None + :param a: + :param b: + :return: + """ if a is None and b is None: return None @@ -494,7 +500,7 @@ def unstr_concept(concept_repr, prefix='c:'): """ if concept_repr is like :c:key:id: return the key and the id - >>> assert unstr_concept("c:key:") == "key" + >>> assert unstr_concept("c:key:") == ("key", None) >>> assert unstr_concept("c:key|id:") == ("key", "id") >>> assert unstr_concept("c:|id:") == ("None", "id") >>> assert unstr_concept("c:key|:") == ("key", "None") @@ -515,28 +521,30 @@ def unstr_concept(concept_repr, prefix='c:'): key = "" while i < length: c = concept_repr[i] + i += 1 if c in (":", "|"): break key += c - i += 1 else: return None, None if c == ":": - return key if key != "" else None, None + return key if key != "" and i == length else None, None - i += 1 - id = "" + c_id = "" while i < length: c = concept_repr[i] + i += 1 if c == ":": break - id += c - i += 1 + c_id += c else: return None, None - return key if key != "" else None, id if id != "" else None + if i != length: + return None, None + + return key if key != "" else None, c_id if c_id != "" else None def encode_concept(t, wrapper="C"): @@ -682,6 +690,36 @@ def flatten(list_of_lists): return functools.reduce(operator.iconcat, list_of_lists, []) +def replace_after(lst: list, start_item, new_items: list): + """ + given a list 'lst', replace all items (starting with 'item') with the new_items + >>> my_new_items =["alpha", "beta", "gamma"] + >>> + >>> my_list1 = ["a", "b", "c", "d"] + >>> replace_after(my_list1, "c", new_items) + >>> assert my_list1 == ["a", "b", "alpha", "beta", "gamma"] + >>> + >>> my_list2 = ["a", "b", "c", "d"] + >>> replace_after(my_list2, "a", new_items) + >>> assert my_list2 == ["alpha", "beta", "gamma"] + >>> # replace_after(lst, "x", new_items) raises a KeyError + + :param lst: + :param start_item: + :param new_items: + :return: + """ + for i, current in enumerate(lst): + if current == start_item: + break + else: + raise KeyError(start_item) + + del lst[i:] + lst.extend(new_items) + return lst + + def get_text_from_tokens(tokens, custom_switcher=None, tracker=None): """ Create the source code, from the list of token diff --git a/src/evaluators/ExpressionEvaluator.py b/src/evaluators/ExpressionEvaluator.py index 0708dfd..ddd48c0 100644 --- a/src/evaluators/ExpressionEvaluator.py +++ b/src/evaluators/ExpressionEvaluator.py @@ -30,7 +30,7 @@ class ExpressionEvaluator(OneReturnValueEvaluator): success = False with context.push(BuiltinConcepts.EXEC_CODE, return_value.value.value.source) as sub_context: - # sub condition is created only to add a namespace + # sub_context is created only to add a namespace requested_vars = set() for c in conditions: diff --git a/src/evaluators/PrepareEvalGlobalTruthEvaluator.py b/src/evaluators/PrepareEvalGlobalTruthEvaluator.py new file mode 100644 index 0000000..e747c28 --- /dev/null +++ b/src/evaluators/PrepareEvalGlobalTruthEvaluator.py @@ -0,0 +1,49 @@ +from core.builtin_concepts import BuiltinConcepts +from evaluators.BaseEvaluator import OneReturnValueEvaluator + + +class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator): + """ + To recognize when the user input is a global truth + """ + + NAME = "PrepareEvalGlobalTruth" + + def __init__(self, **kwargs): + super().__init__(self.NAME, [BuiltinConcepts.BEFORE_PARSING], 90) + self.inner_text = None + + def reset(self): + self.inner_text = None + + def matches(self, context, return_value): + if not (return_value.status and + context.sheerka.isinstance(return_value.body, BuiltinConcepts.USER_INPUT) and + isinstance(return_value.body.body, str)): + return False + + text = return_value.body.body.strip() + if not (text.startswith("global_truth(") and text.endswith(")")): + return False + + self.inner_text = text[13:-1].strip() + if self.inner_text == "": + return False + + return True + + def eval(self, context, return_value): + sheerka = context.sheerka + + new_text_to_parse = sheerka.ret( + 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) + + return new_text_to_parse diff --git a/src/evaluators/ResolveAmbiguityEvaluator.py b/src/evaluators/ResolveAmbiguityEvaluator.py index ed20a54..e6e3f12 100644 --- a/src/evaluators/ResolveAmbiguityEvaluator.py +++ b/src/evaluators/ResolveAmbiguityEvaluator.py @@ -2,6 +2,7 @@ from core.builtin_concepts import BuiltinConcepts from core.builtin_helpers import resolve_ambiguity from core.concept import Concept from evaluators.BaseEvaluator import AllReturnValuesEvaluator +from parsers.BaseNodeParser import ConceptNode class ResolveAmbiguityEvaluator(AllReturnValuesEvaluator): @@ -24,10 +25,12 @@ class ResolveAmbiguityEvaluator(AllReturnValuesEvaluator): # If they share the same source, that means that there are multiple results for one ParserInput self.sources = {} success = False - for ret in [ret for ret in return_values if ret.status]: + for ret in [ret for ret in return_values if + ret.status and context.sheerka.isinstance(ret.body, BuiltinConcepts.PARSER_RESULT)]: source = self.get_source(context, ret) + concept_is_instance = self.get_has_concept_instance(ret) - if source: + if source and concept_is_instance: self.sources.setdefault(source, []).append(ret) if len(self.sources[source]) > 1: @@ -39,17 +42,26 @@ class ResolveAmbiguityEvaluator(AllReturnValuesEvaluator): ret = [] for ret_vals in self.sources.values(): - parser_results = {id(r.body.body): r.body for r in ret_vals} - resolved = resolve_ambiguity(context, [r.body.body for r in ret_vals]) + presults_concepts_map = {id(self.get_concept(r)): r.body for r in ret_vals} + resolved = resolve_ambiguity(context, [self.get_concept(r) for r in ret_vals]) if len(resolved) == 0: ret.append(context.sheerka.ret(self.name, True, BuiltinConcepts.NO_RESULT, parents=ret_vals)) else: if len(resolved) < len(ret_vals): for c in resolved: - ret.append(context.sheerka.ret(self.name, True, parser_results[id(c)], parents=ret_vals)) + ret.append(context.sheerka.ret(self.name, True, presults_concepts_map[id(c)], parents=ret_vals)) return None if len(ret) == 0 else ret + @staticmethod + def get_concept(return_value): + if isinstance(return_value.body.body, Concept): + return return_value.body.body + elif isinstance(return_value.body.body, list) and isinstance(return_value.body.body[0], ConceptNode): + return return_value.body.body[0].concept + + return None + @staticmethod def get_source(context, return_value): """ @@ -60,6 +72,16 @@ class ResolveAmbiguityEvaluator(AllReturnValuesEvaluator): :return: """ if context.sheerka.isinstance(return_value.body, BuiltinConcepts.PARSER_RESULT) and \ - isinstance(return_value.body.body, Concept): + (isinstance(return_value.body.body, Concept) or + (isinstance(return_value.body.body, list) and + len(return_value.body.body) == 1) and + isinstance(return_value.body.body[0], ConceptNode)): return return_value.body.source return None + + def get_has_concept_instance(self, return_value): + concept = self.get_concept(return_value) + if concept is None: + return False + + return concept.get_hints().is_instance diff --git a/src/evaluators/ValidateConceptEvaluator.py b/src/evaluators/ValidateConceptEvaluator.py index 4334f14..f69d3fa 100644 --- a/src/evaluators/ValidateConceptEvaluator.py +++ b/src/evaluators/ValidateConceptEvaluator.py @@ -7,7 +7,7 @@ from parsers.BaseParser import BaseParser class ValidateConceptEvaluator(OneReturnValueEvaluator): """ - To recognize when the user input is a question + Filter the concept that does not meet its pre and where conditions """ NAME = "ValidateConcept" @@ -40,9 +40,12 @@ class ValidateConceptEvaluator(OneReturnValueEvaluator): def eval(self, context, return_value): """ - This evaluator returns None is the concept validates its PRE and POST constraint or if the constraint cannot - be validated + This evaluator returns None if + * the constraint cannot be validated + * the concept is already a copy and it validates its PRE and POST constraint If the constraint can be validated, but fails, an error is returned + If concept was not a copy and the constraint is validated, an evaluated copy is returned + :param context: :param return_value: :return: diff --git a/src/parsers/BaseExpressionParser.py b/src/parsers/BaseExpressionParser.py index c66537f..d5daf90 100644 --- a/src/parsers/BaseExpressionParser.py +++ b/src/parsers/BaseExpressionParser.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from itertools import product from typing import List, Union from core.builtin_concepts_ids import BuiltinConcepts @@ -453,7 +454,7 @@ class BaseExpressionParser(BaseParser): return ret - def parse_input(self, context, parser_input, error_sink): + def parse_input(self, context, parser_input: ParserInput, error_sink: list): raise NotImplementedError def parse_tokens_stop_condition(self, token, parser_input): @@ -666,3 +667,71 @@ class IsAQuestionVisitor(ExpressionVisitor): def is_a_question(self, expr_node): res = self.visit(expr_node) return isinstance(res, bool) and res + + +def compile_disjunctions(expr_node, nest: bool = True): + def get_start_end(items): + start, end = None, None + for item in items: + if start is None or item.start < start: + start = item.start + if end is None or item.end > end: + end = item.end + + return start, end + + def _compile_disjunctions(node, _nest: bool = True): + if isinstance(node, OrNode): + inner = [] + for part in node.parts: + result = _compile_disjunctions(part, _nest=False) + if isinstance(result, tuple): + inner.extend(result) + else: + inner.append(result) + return tuple(inner) + + elif isinstance(node, AndNode): + inner = [] + for ele in node.parts: + if isinstance(ele, NotNode): + t = _compile_disjunctions(ele), + inner.append(t) + else: + inner.append(_compile_disjunctions(ele)) + + temp_res = tuple(product(*inner)) + + all_conjunctions = [] + for conjunctions in temp_res: + # first transform 'a and (b and c)' into 'a and b and c' + conjunctions_to_use = [] + for item in conjunctions: + if isinstance(item, tuple) and isinstance(item[0], AndNode): + conjunctions_to_use.extend(item[0].parts) + elif isinstance(item, AndNode): + conjunctions_to_use.extend(item.parts) + else: + conjunctions_to_use.append(item) + + start, end = get_start_end(conjunctions_to_use) + all_conjunctions.append(AndNode(start, end, expr_node.tokens[start: end + 1], *conjunctions_to_use)) + return tuple(all_conjunctions) + + elif isinstance(node, NotNode): + inner = _compile_disjunctions(node.node) + if len(inner) == 1: + return node, # do not remove the comma, it's a tuple + + temp_res = tuple(NotNode(branch.start, branch.end, [], branch) for branch in inner) + start, end = get_start_end(temp_res) + conjunction = AndNode(start, end, expr_node.tokens[start: end + 1], *temp_res) + return conjunction, # do not remove the comma, it's a tuple + + elif _nest: + return node, # do not remove the comma, it's a tuple + + else: + return node + + return list(_compile_disjunctions(expr_node, nest)) diff --git a/src/parsers/BaseNodeParser.py b/src/parsers/BaseNodeParser.py index adb00ec..743ee26 100644 --- a/src/parsers/BaseNodeParser.py +++ b/src/parsers/BaseNodeParser.py @@ -224,8 +224,14 @@ class ConceptNode(LexerNode): return f'CN({self.concept})' def get_concept(self): + """ + Used when there is a mix of Concept and ConceptNode + To quickly get the inner concept + :return: + """ return self.concept + class SourceCodeNode(LexerNode): """ Returned when some source code (like Python source code is recognized) diff --git a/src/parsers/BaseParser.py b/src/parsers/BaseParser.py index a6dcbe9..92cab2d 100644 --- a/src/parsers/BaseParser.py +++ b/src/parsers/BaseParser.py @@ -250,23 +250,3 @@ class BaseParserInputParser(BaseParser): end -= 1 return start, end - - @staticmethod - def merge_concepts(list_a, b): - if not b: - return list_a - - list_b = b if isinstance(b, list) else [b] - - if not list_a: - return list_b - - by_ids = {c.id for c in list_b} - for c in list_b: - if c.id in by_ids: # and c.metadata.is_evaluated == by_ids[c.id].metadata.is_evaluated: - continue - - list_a.append(c) - by_ids.add(c.id) - - return list_a diff --git a/src/parsers/BnfNodeParser.py b/src/parsers/BnfNodeParser.py index 9658581..3bce7c0 100644 --- a/src/parsers/BnfNodeParser.py +++ b/src/parsers/BnfNodeParser.py @@ -52,7 +52,7 @@ class RegExDef: return flags def serialize(self): - return f"{self.to_match}__!##ZZSEPZZ##!__{self.ignore_case}|{self.multiline}|{self.explicit_flags}" + return f"{self.to_match}__!##ZZSEPZZ##!__{self.ignore_case}|{self.multiline}|{int(self.explicit_flags)}" def deserialize(self, txt): parts = txt.split("__!##ZZSEPZZ##!__") diff --git a/src/parsers/ExactConceptParser.py b/src/parsers/ExactConceptParser.py index 2c78f62..6c6945a 100644 --- a/src/parsers/ExactConceptParser.py +++ b/src/parsers/ExactConceptParser.py @@ -1,6 +1,7 @@ import core.builtin_helpers from core.builtin_concepts import ReturnValueConcept, BuiltinConcepts from core.concept import VARIABLE_PREFIX +from core.sheerka.Sheerka import RECOGNIZED_BY_KEY from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import TokenKind from core.utils import str_concept @@ -43,6 +44,7 @@ class ExactConceptParser(BaseParserInputParser): body = sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=parser_input.as_text(), reason=too_long) return sheerka.ret(self.name, False, body) + # First, get the concept using there keys already_recognized = [] # keep track of the concepts founds for combination in self.combinations(words): @@ -75,11 +77,19 @@ class ExactConceptParser(BaseParserInputParser): value = words[i] concept.def_var_by_index(index, str_concept(value) if isinstance(value, tuple) else value) concept.get_hints().need_validation = True + concept.get_hints().recognized_by = RECOGNIZED_BY_KEY already_recognized.append(concept) + # Also try to recognize concepts by their names by_name = sheerka.fast_resolve(parser_input.as_text()) - core.builtin_helpers.set_is_evaluated(by_name) + if by_name: + core.builtin_helpers.update_concepts_hints(by_name, + # recognized_by=RECOGNIZED_BY_NAME, # keep fast_resolve settings + is_instance=False, + is_evaluated=True) + + # merge the two recognized = self.merge_concepts(already_recognized, by_name) for c in recognized: c.get_hints().use_copy = True @@ -169,6 +179,32 @@ class ExactConceptParser(BaseParserInputParser): res.append(vars[value] if value in vars else value) return tuple(res) + @staticmethod + def merge_concepts(concepts_by_key, concepts_by_name): + """ + Merge concepts when the id is the same and there is no variable + :param concepts_by_key: + :param concepts_by_name: + :return: + """ + if not concepts_by_name: + return concepts_by_key + + concepts_by_name = concepts_by_name if isinstance(concepts_by_name, list) else [concepts_by_name] + + if not concepts_by_key: + return concepts_by_name + + by_ids = {c.id for c in concepts_by_key} + for c in concepts_by_name: + if c.id in by_ids: + continue # keep the 'by_key' version to allow evaluation + + concepts_by_key.append(c) + by_ids.add(c.id) + + return concepts_by_key + def as_return_value(self, context, parser_input, concept): return ReturnValueConcept( self.name, diff --git a/src/parsers/SequenceNodeParser.py b/src/parsers/SequenceNodeParser.py index 49248fc..f1c12e6 100644 --- a/src/parsers/SequenceNodeParser.py +++ b/src/parsers/SequenceNodeParser.py @@ -2,6 +2,7 @@ from dataclasses import dataclass 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.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import Tokenizer, TokenKind @@ -230,7 +231,8 @@ class SequenceNodeParser(BaseNodeParser): :param concept: :return: """ - return len(concept.get_metadata().variables) == 0 and concept.get_metadata().definition_type != DEFINITION_TYPE_BNF + return len(concept.get_metadata().variables) == 0 \ + and concept.get_metadata().definition_type != DEFINITION_TYPE_BNF def get_concepts(self, token, to_keep, custom=None, to_map=None, strip_quotes=False): @@ -245,11 +247,11 @@ class SequenceNodeParser(BaseNodeParser): def as_list(a): if a is None: - return a + return None return a if isinstance(a, list) else [a] - concepts_by_name = as_list(self.sheerka.resolve(token)) + concepts_by_name = 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: @@ -339,11 +341,17 @@ class SequenceNodeParser(BaseNodeParser): It will use the name of the concept, but also its compact form (c::) :return: """ + source = self.parser_input.as_text() - concepts = self.sheerka.resolve(source.strip()) + concepts = self.sheerka.fast_resolve(source.strip()) if concepts is None: return None + update_concepts_hints(concepts, + # recognized_by=RECOGNIZED_BY_NAME, # keep fast_resolve settings + is_instance=False, + is_evaluated=True) + concepts = [concepts] if isinstance(concepts, Concept) else concepts res = [] start, end = self.get_tokens_boundaries(self.parser_input.as_tokens()) @@ -414,6 +422,8 @@ class SequenceNodeParser(BaseNodeParser): sequences = self.get_concepts_sequences() if by_name := self.get_by_name(): + # note that concepts by names must be appended, not prepended + # In case of conflict, we want to keep the one found by get_concepts_sequences() sequences.extend(by_name) parser_helpers = self.get_valid(sequences) diff --git a/src/parsers/SyaNodeParser.py b/src/parsers/SyaNodeParser.py index 75322d0..350c80d 100644 --- a/src/parsers/SyaNodeParser.py +++ b/src/parsers/SyaNodeParser.py @@ -11,7 +11,7 @@ from core.global_symbols import CONCEPT_COMPARISON_CONTEXT, SyaAssociativity from core.sheerka.services.SheerkaComparisonManager import SheerkaComparisonManager from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import Token, TokenKind, Tokenizer -from core.utils import get_n_clones, get_text_from_tokens, NextIdManager +from core.utils import get_n_clones, get_text_from_tokens, NextIdManager, replace_after from parsers.BaseNodeParser import UnrecognizedTokensNode, ConceptNode, SourceCodeNode, \ SourceCodeWithConceptNode, BaseNodeParser, VariableNode from parsers.BaseParser import ParsingError @@ -185,6 +185,19 @@ class SyaConceptParserHelper: if token.type != TokenKind.WHITESPACE: self.expected_parameters_before_first_token += 1 + # remove useless whitespaces (spaces that are between VAR_DEF) + if len(self.expected) > 2: + temp = [self.expected[0]] + for i in range(1, len(self.expected) - 1): + token = self.expected[i] + if (token.type == TokenKind.WHITESPACE and + self.expected[i - 1].type == TokenKind.VAR_DEF and + self.expected[i + 1].type == TokenKind.VAR_DEF): + continue # skip it + temp.append(token) + temp.append(self.expected[-1]) + self.expected = temp + self.eat_token(first_keyword_found) # remove the first token self.tokens.append(first_keyword_found) @@ -496,9 +509,18 @@ class InFixToPostFix: :return: """ if len(self.parameters_list) < parser_helper.expected_parameters_before_first_token: - # The new concept expect some prefix parameters, but there's not enough - parser_helper.error = "Not enough prefix parameters" - return + # There is not enough parameters to fill the new concept + # Try to develop the UnrecognizedTokesNode, to see if it can match + developed_param_list = self.develop_parameter_list(self.parameters_list) + if len(developed_param_list) < parser_helper.expected_parameters_before_first_token: + # The new concept expect some prefix parameters, but there's not enough + parser_helper.error = "Not enough prefix parameters" + return + + # the developed_param_list does the job. Let's replace the previous values + pivot = self.parameters_list[0] + replace_after(self.parameters_list, pivot, developed_param_list) + replace_after(self.out, pivot, developed_param_list) if len(self.parameters_list) > parser_helper.expected_parameters_before_first_token: # There are more parameters than needed by the new concept @@ -530,6 +552,20 @@ class InFixToPostFix: :return: """ + def nb_expected_parameters(expected): + """ + Count the number of successive variables that are expected + :param expected: + :return: + """ + i = 0 + for token in expected: + if token.type == TokenKind.VAR_DEF: + i += 1 + else: + break + return i + # manage parenthesis that didn't find any match if self._is_lpar(self.stack[-1]): self._add_error(ParenthesisMismatchError(self.stack[-1])) @@ -538,6 +574,16 @@ class InFixToPostFix: assert len(self._concepts()) != 0 # sanity check current_concept = self._concepts()[-1] + + if (nb_expected := nb_expected_parameters(current_concept.expected)) > len(self.parameters_list): + # There is not enough parameters in the list to fill the concept + # Try to develop the UnrecognizedTokensNode to see if it can match + developed_param_list = self.develop_parameter_list(self.parameters_list) + if nb_expected == len(developed_param_list): + pivot = self.parameters_list[0] + replace_after(self.parameters_list, pivot, developed_param_list) + replace_after(self.out, pivot, developed_param_list) + while len(current_concept.expected) > 0 and current_concept.expected[0].type == TokenKind.VAR_DEF: # eat everything that was expected if len(self.parameters_list) == 0: @@ -640,13 +686,24 @@ class InFixToPostFix: Helper function that pops the stack and put the item to the output, if needed :return: """ + item = self.stack[-1] # fix the concept is needed if isinstance(item, SyaConceptParserHelper): - # make sure the expected parameters of this item are eaten - if 0 < len(item.expected) <= len(self.parameters_list): - self.manage_parameters() + if len(item.expected) > 0: + # make sure the expected parameters of this item are eaten + if len(item.expected) <= len(self.parameters_list): + self.manage_parameters() + else: + # second chance to match the parameter list when it contains unrecognized token + developed_param_list = self.develop_parameter_list(self.parameters_list) + if len(item.expected) <= len(developed_param_list): + pivot = self.parameters_list[0] + replace_after(self.parameters_list, pivot, developed_param_list) + replace_after(self.out, pivot, developed_param_list) + self.manage_parameters() + item.fix_concept() self.stack.pop() @@ -1119,6 +1176,25 @@ class InFixToPostFix: # clone.forked = self.forked return clone + @staticmethod + def develop_parameter_list(parameter_list): + """ + given a list of parameter (solely from self.parameter_list) + develop UnrecognizedTokensNode parameter that contains whitespaces + :param parameter_list: + :return: + """ + temp = [] + for parameter in parameter_list: + if isinstance(parameter, UnrecognizedTokensNode): + for i, token in [(i, t) for i, t in enumerate(parameter.tokens) if t.type != TokenKind.WHITESPACE]: + temp.append(UnrecognizedTokensNode(parameter.start + i, + parameter.start + i, + [token])) + else: + temp.append(parameter) + return temp + @dataclass() class PostFixToItem: @@ -1428,8 +1504,8 @@ class SyaNodeParser(BaseNodeParser): def _has_sya(items): for item in items: if isinstance(item, SourceCodeWithConceptNode): - if _has_sya(item.nodes): - return True + return _has_sya(item.nodes) + if isinstance(item, SyaConceptParserHelper): return True return False diff --git a/src/sheerkapickle/SheerkaPickler.py b/src/sheerkapickle/SheerkaPickler.py index b0de06e..09ca191 100644 --- a/src/sheerkapickle/SheerkaPickler.py +++ b/src/sheerkapickle/SheerkaPickler.py @@ -41,6 +41,8 @@ class SheerkaPickler: self.to_reduce.append(ToReduce(lambda o: isinstance(o, (BaseParser, BaseEvaluator)), lambda o: o.name)) self.to_reduce.append(ToReduce(lambda o: isinstance(o, ParserInput), lambda o: o.as_text())) self.to_reduce.append(ToReduce(lambda o: isinstance(o, Ontology), lambda o: o.name)) + from core.sheerka.Sheerka import SheerkaMethod + self.to_reduce.append(ToReduce(lambda o: isinstance(o, SheerkaMethod), lambda o: o.name)) def flatten(self, obj): if utils.is_to_discard(obj): diff --git a/src/sheerkapython/python_wrapper.py b/src/sheerkapython/python_wrapper.py index a15cee6..de48e7d 100644 --- a/src/sheerkapython/python_wrapper.py +++ b/src/sheerkapython/python_wrapper.py @@ -155,7 +155,7 @@ def resolve_object(context, who, obj): raise Exception() if (isinstance(obj, str) and obj.startswith("c:")) or isinstance(obj, Token): - concept = context.sheerka.fast_resolve(obj) + concept = context.sheerka.fast_resolve(obj, force_instance=True) if concept is None: return None diff --git a/tests/BaseTest.py b/tests/BaseTest.py index a4ae650..40b73a7 100644 --- a/tests/BaseTest.py +++ b/tests/BaseTest.py @@ -104,7 +104,7 @@ class BaseTest: def get_sheerka(self, **kwargs) -> Sheerka: pass - def get_context(self, sheerka=None, eval_body=False, eval_where=False, message=""): + def get_context(self, sheerka=None, eval_body=False, eval_where=False, global_truth=False, message=""): context = ExecutionContext("test", Event(message=message), sheerka or self.get_sheerka(), @@ -114,20 +114,29 @@ class BaseTest: context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) if eval_where: context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED) + if global_truth: + context.protected_hints.add(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) return context @staticmethod def get_init_test_args(**kwargs): - return {k: v for k, v in kwargs.items() if k in ["cache_only", "ontology", "eval_body", "eval_where"]} + return {k: v for k, v in kwargs.items() if k in ["cache_only", + "ontology", + "eval_body", + "eval_where", + "global_truth"]} @staticmethod def get_with_concepts_args(**kwargs): return {k: v for k, v in kwargs.items() if k in ["create_new"]} - def init_test(self, cache_only=None, ontology=None, eval_body=False, eval_where=False): + def init_test(self, cache_only=None, ontology=None, eval_body=False, eval_where=False, global_truth=False): sheerka = self.get_sheerka(cache_only=cache_only, ontology=ontology) - context = self.get_context(sheerka=sheerka, eval_body=eval_body, eval_where=eval_where) + context = self.get_context(sheerka=sheerka, + eval_body=eval_body, + eval_where=eval_where, + global_truth=global_truth) return InitTestHelper(sheerka, context) @@ -255,14 +264,19 @@ class BaseTest: concept.get_metadata().variables[k] = v return concept - def init_scenario(self, init_expressions): + def init_scenario(self, init_expressions, global_truth=False): sheerka = self.get_sheerka() + if global_truth: + sheerka.add_to_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) for expression in init_expressions: res = sheerka.evaluate_user_input(expression) assert len(res) == 1, f"Failed to execute '{expression}'" assert res[0].status, f"Error while executing '{expression}'" + if global_truth: + sheerka.remove_fom_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) + return sheerka @staticmethod diff --git a/tests/core/test_SheerkaConceptAlgebra.py b/tests/core/test_SheerkaConceptAlgebra.py index 68b482e..d5a1ca1 100644 --- a/tests/core/test_SheerkaConceptAlgebra.py +++ b/tests/core/test_SheerkaConceptAlgebra.py @@ -7,9 +7,9 @@ from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): def test_i_can_add_concepts(self): - sheerka, context, man, human, male, driver, licence, car = self.init_concepts( + sheerka, context, man, human, male, driver, licence, car = self.init_test(global_truth=True).with_concepts( "man", "human", "male", - "driver", "licence", "car") + "driver", "licence", "car").unpack() sheerka.set_isa(context, sheerka.new("man"), human) sheerka.set_isa(context, sheerka.new("man"), male) @@ -23,8 +23,8 @@ class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): BuiltinConcepts.HASA: {car, licence}, } def test_can_add_concepts_when_property_already_exist(self): - sheerka, context, man, human, king, male = self.init_concepts( - "man", "human", "king", "male") + sheerka, context, man, human, king, male = self.init_test(global_truth=True).with_concepts( + "man", "human", "king", "male").unpack() sheerka.set_isa(context, sheerka.new("man"), human) sheerka.set_isa(context, sheerka.new("king"), male) @@ -35,9 +35,9 @@ class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): assert res.get_metadata().props == {BuiltinConcepts.ISA: {male, human}} def test_i_can_subtract_concepts(self): - sheerka, context, foo, bar, isa1, isa2, hasa1, hasa2 = self.init_concepts( + sheerka, context, foo, bar, isa1, isa2, hasa1, hasa2 = self.init_test(global_truth=True).with_concepts( "foo", "bar", - "isa1", "isa2", "has1", "has2") + "isa1", "isa2", "has1", "has2").unpack() new_foo = sheerka.new("foo") sheerka.set_isa(context, new_foo, isa1) @@ -61,9 +61,10 @@ class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): BuiltinConcepts.HASA: {hasa2}, } def test_i_can_recognize_myself_when_using_sdp_repository(self): - sheerka, context, foo, isa1, hasa1, = self.init_test(cache_only=False). \ - with_concepts("foo", "isa1", "has1", create_new=True). \ - unpack() + sheerka, context, foo, isa1, hasa1, = self.init_test(cache_only=False, global_truth=True).with_concepts( + "foo", + "isa1", + "has1", create_new=True).unpack() sheerka.om.commit(context) new_foo = sheerka.new("foo") @@ -74,7 +75,10 @@ class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): assert sheerka.recognize(new_foo, all_scores=True) == [ConceptScore(1, new_foo, new_foo)] def test_i_can_recognize_myself_when_not_using_sdp_repository(self): - sheerka, context, foo, isa1, hasa1, = self.init_concepts("foo", "isa1", "has1") + sheerka, context, foo, isa1, hasa1, = self.init_test(global_truth=True).with_concepts( + "foo", + "isa1", + "has1").unpack() new_foo = sheerka.new("foo") sheerka.set_isa(context, new_foo, isa1) @@ -100,9 +104,9 @@ class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): assert sheerka.recognize(Concept(), all_scores=True) == [] def test_i_can_recognize_multiple_concepts_with_the_proper_score(self): - sheerka, context, foo, bar, isa1, isa2, hasa1, hasa2 = self.init_concepts( + sheerka, context, foo, bar, isa1, isa2, hasa1, hasa2 = self.init_test(global_truth=True).with_concepts( "foo", "bar", - "isa1", "isa2", "has1", "has2") + "isa1", "isa2", "has1", "has2").unpack() new_foo = sheerka.new("foo") sheerka.set_isa(context, new_foo, isa1) @@ -131,9 +135,9 @@ class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): ConceptScore(0.5, sheerka.new("bar"), to_recognize)] def test_i_can_recognize_if_all_scores_is_disabled(self): - sheerka, context, foo, bar, isa1, isa2, hasa1, hasa2 = self.init_concepts( + sheerka, context, foo, bar, isa1, isa2, hasa1, hasa2 = self.init_test(global_truth=True).with_concepts( "foo", "bar", - "isa1", "isa2", "has1", "has2") + "isa1", "isa2", "has1", "has2").unpack() new_foo = sheerka.new("foo") sheerka.set_isa(context, new_foo, isa1) @@ -153,9 +157,9 @@ class TestSheerkaConceptsAlgebra(TestUsingMemoryBasedSheerka): assert res == ConceptScore(1.0, sheerka.new("bar"), to_recognize) def test_i_can_recognize_if_all_scores_is_disabled_but_multiple_high_scores(self): - sheerka, context, foo, bar, isa1, hasa1 = self.init_concepts( + sheerka, context, foo, bar, isa1, hasa1 = self.init_test(global_truth=True).with_concepts( "foo", "bar", - "isa1", "has1") + "isa1", "has1").unpack() new_foo = sheerka.new("foo") sheerka.set_isa(context, new_foo, isa1) diff --git a/tests/core/test_SheerkaConceptManager.py b/tests/core/test_SheerkaConceptManager.py index 1fb54c6..286ec2a 100644 --- a/tests/core/test_SheerkaConceptManager.py +++ b/tests/core/test_SheerkaConceptManager.py @@ -1105,13 +1105,14 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): Concept("hundreds", definition="number hundred"), ) - sheerka.set_isa(context, sheerka.new("one"), number) - sheerka.set_isa(context, sheerka.new("two"), number) - sheerka.set_isa(context, sheerka.new("twenty"), number) - sheerka.set_isa(context, sheerka.new("thirty"), number) - sheerka.set_isa(context, sheerka.new("hundred"), number) - sheerka.set_isa(context, sheerka.new("twenties"), number) - sheerka.set_isa(context, sheerka.new("hundreds"), number) + global_truth_context = self.get_context(sheerka, global_truth=True) + sheerka.set_isa(global_truth_context, sheerka.new("one"), number) + sheerka.set_isa(global_truth_context, sheerka.new("two"), number) + sheerka.set_isa(global_truth_context, sheerka.new("twenty"), number) + sheerka.set_isa(global_truth_context, sheerka.new("thirty"), number) + sheerka.set_isa(global_truth_context, sheerka.new("hundred"), number) + sheerka.set_isa(global_truth_context, sheerka.new("twenties"), number) + sheerka.set_isa(global_truth_context, sheerka.new("hundreds"), number) sheerka.clear_bnf_definition() # reset all the grammar to simulate Sheerka restart @@ -1129,12 +1130,12 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): } def test_i_can_resolve_when_concepts_have_multiple_levels_of_sets(self): - sheerka, context, adjective, color, red, qualified_table = self.init_concepts( + sheerka, context, adjective, color, red, qualified_table = self.init_test(global_truth=True).with_concepts( "adjective", "color", "red", Concept("qualified table", definition="adjective 'table'"), - ) + ).unpack() sheerka.set_isa(context, color, adjective) sheerka.set_isa(context, red, color) @@ -1404,7 +1405,8 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): def test_i_cannot_smart_get_attr_when_attribute_does_not_exist(self): sheerka, context, adjective, color, red, size, table = self.init_concepts("adjective", "color", - Concept("red", body="red").auto_init(), + Concept("red", + body="red").auto_init(), "size", "table", create_new=True) diff --git a/tests/core/test_SheerkaEvaluateConcept.py b/tests/core/test_SheerkaEvaluateConcept.py index 5256568..f5367d4 100644 --- a/tests/core/test_SheerkaEvaluateConcept.py +++ b/tests/core/test_SheerkaEvaluateConcept.py @@ -391,8 +391,8 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka): :return: """ sheerka, context, predicate, foo = self.init_concepts( - Concept("Sometimes True", body="in_context('a')"), - Concept("foo", pre="c:Sometimes True:")) + Concept("Sometimes True", body="in_context('a')", pre="is_question()"), + Concept("foo", pre="Sometimes True")) foo1 = sheerka.new("foo") foo1 = sheerka.evaluate_concept(context, foo1) # 'a' is not in context, so it fails @@ -400,7 +400,7 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka): context2 = self.get_context(sheerka) context2.add_to_protected_hints('a') foo2 = sheerka.new("foo") - foo2 = sheerka.evaluate_concept(context2, foo2) # 'a' in context + new instance of 'Sometimes True' + foo2 = sheerka.evaluate_concept(context2, foo2) # 'a' is now in context assert sheerka.isinstance(foo1, BuiltinConcepts.CONDITION_FAILED) assert sheerka.isinstance(foo2, "foo") diff --git a/tests/core/test_SheerkaHasAManager.py b/tests/core/test_SheerkaHasAManager.py index 8a31352..b330720 100644 --- a/tests/core/test_SheerkaHasAManager.py +++ b/tests/core/test_SheerkaHasAManager.py @@ -7,6 +7,33 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka): def test_i_can_set_hasa(self): sheerka, context, king, kingdom = self.init_concepts("king", "kingdom") + king_instance = sheerka.new("king") + res = sheerka.set_hasa(context, king_instance, kingdom) + assert res.status + + # when global truth is not activated, only the current instance is modified + another_king = sheerka.get_by_key("king") + assert not another_king.get_prop(BuiltinConcepts.HASA) == {kingdom} + assert not sheerka.hasa(another_king, kingdom) + + def test_i_cannot_set_the_same_attribute_twice(self): + sheerka, context, king, kingdom = self.init_concepts("king", "kingdom") + + king_instance = sheerka.new("king") + sheerka.set_hasa(context, king_instance, kingdom) + res = sheerka.set_hasa(context, king_instance, kingdom) + + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.PROPERTY_ALREADY_DEFINED) + assert res.body.property_name == BuiltinConcepts.HASA + assert res.body.property_value == kingdom + assert res.body.concept == king_instance + + def test_i_can_set_hasa_when_global_truth_is_activated(self): + sheerka, context, king, kingdom = self.init_test(global_truth=True).with_concepts( + "king", + "kingdom").unpack() + res = sheerka.set_hasa(context, sheerka.new("king"), kingdom) assert res.status @@ -16,8 +43,10 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka): # check that the definition of the concept has been updated assert sheerka.hasa(sheerka.new("king"), kingdom) - def test_i_cannot_set_the_same_attribute_twice(self): - sheerka, context, king, kingdom = self.init_concepts("king", "kingdom") + def test_i_cannot_set_the_same_attribute_twice_when_global_truth_is_activated(self): + sheerka, context, king, kingdom = self.init_test(global_truth=True).with_concepts( + "king", + "kingdom").unpack() sheerka.set_hasa(context, sheerka.new("king"), kingdom) res = sheerka.set_hasa(context, sheerka.new("king"), kingdom) diff --git a/tests/core/test_SheerkaIsAManager.py b/tests/core/test_SheerkaIsAManager.py index cd80863..f5dd3a9 100644 --- a/tests/core/test_SheerkaIsAManager.py +++ b/tests/core/test_SheerkaIsAManager.py @@ -86,15 +86,46 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): def test_isa(self): sheerka, context, blue, color = self.init_concepts(Concept("blue"), Concept("color")) - assert not sheerka.isa(blue, color) - sheerka.set_isa(context, blue, color) - assert sheerka.isa(blue, color) + blue_instance = sheerka.new("blue") + assert not sheerka.isa(blue_instance, color) + + sheerka.set_isa(context, blue_instance, color) + assert sheerka.isa(blue_instance, color) # isa tests the id of a concept, not it's content another_color_instance_but_with_a_body = sheerka.new(color, body="a body") - assert sheerka.isa(blue, another_color_instance_but_with_a_body) + assert sheerka.isa(blue_instance, another_color_instance_but_with_a_body) + + # isa, when EVAL_GLOBAL_TRUTH_REQUESTED is not activated, only affect the current concept + another_blue_instance = sheerka.new("blue") + assert not sheerka.isa(another_blue_instance, color) + + # when EVAL_GLOBAL_TRUTH_REQUESTED is not activated, color is not a set + assert not sheerka.isinset(blue_instance, color) + assert not sheerka.isaset(context, color) + + def test_isa_global_truth(self): + sheerka, context, blue, color = self.init_concepts(Concept("blue"), Concept("color")) + + blue_instance = sheerka.new("blue") + assert not sheerka.isa(blue_instance, color) + assert not sheerka.isaset(context, color) + assert not sheerka.isinset(blue_instance, color) + + context.add_to_private_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) + + sheerka.set_isa(context, blue_instance, color) + assert sheerka.isa(blue_instance, color) + assert sheerka.isaset(context, color) + assert sheerka.isinset(blue_instance, color) + + # all blue instances are now a color + another_blue_instance = sheerka.new("blue") + assert sheerka.isa(another_blue_instance, color) + assert sheerka.isaset(context, color) + assert sheerka.isinset(another_blue_instance, color) def test_isaset(self): sheerka, context, group, foo = self.init_concepts(Concept("group"), Concept("foo")) @@ -246,13 +277,28 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): foo = sheerka.new(foo.key) # new instance sheerka.set_isa(context, foo, all_foo) + sheerka.set_isa(context, foo, all_bar) + assert foo.get_prop(BuiltinConcepts.ISA) == {all_foo, all_bar} + assert sheerka.isa(foo, all_foo) + assert sheerka.isa(foo, all_bar) + + def test_a_concept_can_be_in_multiple_sets_when_global_truth_is_activated(self): + sheerka, context, foo, all_foo, all_bar = self.init_test(global_truth=True).with_concepts( + Concept("foo"), + Concept("all_foo"), + Concept("all_bar"), + create_new=True).unpack() + + foo = sheerka.new(foo.key) # new instance + sheerka.set_isa(context, foo, all_foo) foo = sheerka.new(foo.key) # new instance sheerka.set_isa(context, foo, all_bar) assert foo.get_prop(BuiltinConcepts.ISA) == {all_foo, all_bar} assert sheerka.isa(foo, all_foo) assert sheerka.isa(foo, all_bar) + assert sheerka.isinset(foo, all_foo) assert sheerka.isinset(foo, all_bar) assert sheerka.isaset(context, all_foo) @@ -270,6 +316,25 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): Concept("baz"), ) + sheerka.set_isa(context, foo, bar) + sheerka.set_isa(context, bar, baz) + + assert sheerka.isa(foo, bar) + assert sheerka.isa(bar, baz) + assert sheerka.isa(foo, baz) + + def test_i_can_manage_isa_transitivity_when_global_truth_is_activated(self): + """ + if foo isa bar and bar isa baz, then foo isa baz + :return: + """ + + sheerka, context, foo, bar, baz = self.init_test(global_truth=True).with_concepts( + Concept("foo"), + Concept("bar"), + Concept("baz"), + ).unpack() + sheerka.set_isa(context, sheerka.new("foo"), bar) sheerka.set_isa(context, sheerka.new("bar"), baz) @@ -278,11 +343,11 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): assert sheerka.isa(sheerka.new("foo"), baz) def test_i_cannot_manage_isa_transitivity_when_using_body(self): - sheerka, context, one, another_one, number = self.init_concepts( + sheerka, context, one, another_one, number = self.init_test(global_truth=True).with_concepts( "one", Concept("another one", body="one"), "number" - ) + ).unpack() sheerka.set_isa(context, sheerka.new("one"), number) @@ -290,7 +355,10 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): assert not sheerka.isa(another_one, number) # Correct this misbehaviour when BuiltinConcepts.IS is implemented def test_concepts_in_group_cache_is_updated(self): - sheerka, context, one, two, number = self.init_concepts("one", "two", "number") + sheerka, context, one, two, number = self.init_test(global_truth=True).with_concepts( + "one", + "two", + "number").unpack() sheerka.set_isa(context, sheerka.new("one"), number) @@ -315,12 +383,12 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka): assert number.id not in sheerka.get_concepts_bnf_definitions() def test_i_can_get_and_set_isa_when_multiple_ontology_layers(self): - sheerka, context, foo, group1, group2 = self.init_concepts( + sheerka, context, foo, group1, group2 = self.init_test(global_truth=True).with_concepts( Concept("foo"), Concept("group1"), Concept("group2"), cache_only=False - ) + ).unpack() sheerka.set_isa(context, foo, group1) @@ -401,10 +469,10 @@ class TestSheerkaSetsManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): } def test_i_can_set_isa(self): - sheerka, context, foo, bar, group = self.init_test().with_concepts("foo", - "bar", - "group", - ).unpack() + sheerka, context, foo, bar, group = self.init_test(global_truth=True).with_concepts("foo", + "bar", + "group", + ).unpack() # nothing was previously in ISA foo = sheerka.new(foo.key) diff --git a/tests/core/test_SheerkaMemory.py b/tests/core/test_SheerkaMemory.py index a7d10f1..b06f146 100644 --- a/tests/core/test_SheerkaMemory.py +++ b/tests/core/test_SheerkaMemory.py @@ -116,6 +116,15 @@ class TestSheerkaMemory(TestUsingMemoryBasedSheerka): assert sheerka.memory(context, "self.name == 'foo'") == foo + def test_i_can_use_memory_when_the_entry_does_not_exist(self): + sheerka, context, foo, bar = self.init_test().with_concepts("foo", "bar", create_new=True).unpack() + + sheerka.add_to_memory(context, "foo", foo) # add at least one element + + res = sheerka.memory(context, "bar") + assert sheerka.isinstance(res, BuiltinConcepts.NOT_FOUND) + assert res.body == {'#name': 'bar'} + def test_i_retrieve_the_last_entry_when_requesting_memory_with_a_query(self): sheerka, context, foo, bar, foo2 = self.init_concepts("foo", "bar", Concept("foo", body="2")) diff --git a/tests/core/test_SheerkaQueryManager.py b/tests/core/test_SheerkaQueryManager.py index 2bdfb70..50d687f 100644 --- a/tests/core/test_SheerkaQueryManager.py +++ b/tests/core/test_SheerkaQueryManager.py @@ -52,22 +52,16 @@ class TestSheerkaQueryManager(TestUsingMemoryBasedSheerka): assert sheerka.filter_objects(context, lst, prop2={"key": "value"}) == [lst[3]] # assert sheerka.filter_objects(context, lst, prop1={1, "v"}) == [lst[2]] set are not supported + # complex properties + assert sheerka.filter_objects(context, lst, prop1_contains="a") == [lst[0], lst[1]] + assert sheerka.filter_objects(context, lst, prop1_contains=1) == [lst[2], lst[3]] + def test_i_can_filter_by_object_type(self): sheerka, context = self.init_test().unpack() lst = [A("a11", "a12"), Concept("foo", body="a").auto_init(), Concept("foo", body="b").auto_init()] assert sheerka.filter_objects(context, lst, __type="foo") == [lst[1], lst[2]] - def test_i_can_filter_on_atomic_def(self): - sheerka, context, isa, plus, isa2 = self.init_concepts( - Concept('x is a y').def_var("x").def_var("y"), - Concept('a plus b').def_var("a").def_var("b"), - Concept('u is a v').def_var("u").def_var("v"), - ) - - lst = [isa, plus, isa2] - assert sheerka.filter_objects(context, lst, atomic_def="is a") == [lst[0], lst[2]] - def test_i_can_filter_on_as_bag_property(self): sheerka, context = self.init_test().unpack() lst = [B("a11", "a12"), B("a21", "a22"), B("a31", "a32")] diff --git a/tests/core/test_builtin_helpers.py b/tests/core/test_builtin_helpers.py index adc677f..ec7e94c 100644 --- a/tests/core/test_builtin_helpers.py +++ b/tests/core/test_builtin_helpers.py @@ -253,6 +253,28 @@ class TestBuiltinHelpers(TestUsingMemoryBasedSheerka): # a second time, now that bar is already evaluated assert core.builtin_helpers.ensure_evaluated(context, bar) == foo + @pytest.mark.parametrize("concept, concepts_parts, expected", [ + (Concept("foo"), ["where"], 0), + (Concept("foo", where="x"), ["where"], 1), + (Concept("foo", where="x and y"), ["where"], 2), + (Concept("foo", where="x or y"), ["where"], 1), + (Concept("foo", where="x or y and z"), ["where"], 2), + (Concept("foo", where="not w"), ["where"], 1), + (Concept("foo", where=""), ["where"], 0), + + (Concept("foo", pre="x"), ["pre", "where"], 101), + (Concept("foo", where="x"), ["pre", "where"], 1), + (Concept("foo", where="x and y", pre="z"), ["pre", "where"], 103), + + (Concept("foo", pre="x"), ["pre|where"], 1), + (Concept("foo", where="x"), ["pre|where"], 1), + (Concept("foo", where="x and y", pre="z"), ["pre|where"], 3), + + ]) + def test_i_can_get_concept_complexity(self, concept, concepts_parts, expected): + context = self.get_context() + assert core.builtin_helpers.get_concept_complexity(context, concept, concepts_parts) == expected + # @pytest.mark.parametrize("return_values", [ # None, # [] diff --git a/tests/core/test_sheerka.py b/tests/core/test_sheerka.py index eb1e9a0..80ad5c3 100644 --- a/tests/core/test_sheerka.py +++ b/tests/core/test_sheerka.py @@ -559,6 +559,37 @@ class TestSheerkaUsingMemoryBasedSheerka(TestUsingMemoryBasedSheerka): ret_val = ReturnValueConcept("Test", True, sheerka.err("an error")) assert sheerka.get_errors(context, ret_val) == [] + def test_i_can_add_and_remove_global_context_hints(self): + sheerka, context = self.init_test().unpack() + + sheerka.add_to_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED) + assert sheerka._global_context_hints == {BuiltinConcepts.EVAL_QUESTION_REQUESTED} + + sheerka.remove_fom_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED) + assert sheerka._global_context_hints == set() + + sheerka.add_to_context(sheerka.new(BuiltinConcepts.EVAL_QUESTION_REQUESTED)) + assert sheerka._global_context_hints == {BuiltinConcepts.EVAL_QUESTION_REQUESTED} + + sheerka.remove_fom_context(sheerka.new(BuiltinConcepts.EVAL_QUESTION_REQUESTED)) + assert sheerka._global_context_hints == set() + + def test_global_context_hints_are_added_to_every_user_input_execution(self): + sheerka, context, foo = self.init_test().with_concepts( + Concept("foo", pre="in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED)") + ).unpack() + + res = sheerka.evaluate_user_input("eval foo", "testing_user") + assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONDITION_FAILED) # sanity check + + sheerka.add_to_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) + res = sheerka.evaluate_user_input("eval foo", "testing_user") + assert sheerka.isinstance(res[0].value, "foo") + + sheerka.remove_fom_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) + res = sheerka.evaluate_user_input("eval foo", "testing_user") + assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONDITION_FAILED) + class TestSheerkaUsingFileBasedSheerka(TestUsingFileBasedSheerka): diff --git a/tests/core/test_sheerka_ontology.py b/tests/core/test_sheerka_ontology.py index 6c8f178..5fa9093 100644 --- a/tests/core/test_sheerka_ontology.py +++ b/tests/core/test_sheerka_ontology.py @@ -842,11 +842,9 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka): sheerka, context, foo = self.init_concepts("foo", cache_only=False) manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only) - cache = Cache(default=lambda sdp, key: sdp.get("by_id", key), - extend_exists=lambda sdp, key: sdp.get("by_id", key)) + cache = Cache().auto_configure("by_id") manager.register_concept_cache("by_id", cache, lambda obj: obj.id, use_ref=True) - cache = ListIfNeededCache(default=lambda sdp, key: sdp.get("by_key", key), - extend_exists=lambda sdp, key: sdp.get("by_key", key)) + cache = ListIfNeededCache().auto_configure("by_key") manager.register_concept_cache("by_key", cache, lambda obj: obj.key, use_ref=True) manager.freeze() @@ -863,11 +861,9 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka): sheerka, context, foo = self.init_concepts("foo", cache_only=False) manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only) - cache = Cache(default=lambda sdp, key: sdp.get("by_id", key), - extend_exists=lambda sdp, key: sdp.get("by_id", key)) + cache = Cache().auto_configure("by_id") manager.register_concept_cache("by_id", cache, lambda obj: obj.id, use_ref=True) - cache = ListIfNeededCache(default=lambda sdp, key: sdp.get("by_key", key), - extend_exists=lambda sdp, key: sdp.get("by_key", key)) + cache = ListIfNeededCache().auto_configure("by_key") manager.register_concept_cache("by_key", cache, lambda obj: obj.key, use_ref=True) manager.freeze() @@ -887,15 +883,33 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka): assert list(manager.ontologies[0].cache_manager.sdp.state.data.keys()) == ['by_id', 'by_key'] assert manager.ontologies[1].cache_manager.sdp.state.data == {} + def test_i_can_add_the_concepts_with_the_same_key_from_different_layers(self): + sheerka, context, foo_1, foo_2 = self.init_concepts( + Concept("foo x", body="x + 1").def_var("x"), + Concept("foo x", body="x + 2").def_var("x"), + cache_only=False) + + manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only) + cache = ListIfNeededCache().auto_configure("by_key") + manager.register_concept_cache("by_key", cache, lambda obj: obj.key, use_ref=True) + manager.freeze() + + manager.add_concept(foo_1) + manager.commit(context) + + manager.push_ontology("new ontology") + manager.add_concept(foo_2) + manager.commit(context) + + assert manager.current_sdp().get("by_key", foo_1.key) == [foo_1, foo_2] + def test_i_can_update_concept_in_default_layer(self): sheerka, context, foo = self.init_concepts("foo", cache_only=False) manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only) - cache = Cache(default=lambda sdp, key: sdp.get("by_id", key), - extend_exists=lambda sdp, key: sdp.get("by_id", key)) + cache = Cache().auto_configure("by_id") manager.register_concept_cache("by_id", cache, lambda obj: obj.id, use_ref=True) - cache = ListIfNeededCache(default=lambda sdp, key: sdp.get("by_key", key), - extend_exists=lambda sdp, key: sdp.get("by_key", key)) + cache = ListIfNeededCache().auto_configure("by_key") manager.register_concept_cache("by_key", cache, lambda obj: obj.key, use_ref=True) manager.freeze() @@ -953,11 +967,9 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka): sheerka, context, foo = self.init_concepts("foo", cache_only=False) manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only) - cache = Cache(default=lambda sdp, key: sdp.get("by_id", key), - extend_exists=lambda sdp, key: sdp.get("by_id", key)) + cache = Cache().auto_configure("by_id") manager.register_concept_cache("by_id", cache, lambda obj: obj.id, use_ref=True) - cache = ListIfNeededCache(default=lambda sdp, key: sdp.get("by_key", key), - extend_exists=lambda sdp, key: sdp.get("by_key", key)) + cache = ListIfNeededCache().auto_configure("by_key") manager.register_concept_cache("by_key", cache, lambda obj: obj.key, use_ref=True) manager.freeze() diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py index a753a83..a898514 100644 --- a/tests/core/test_utils.py +++ b/tests/core/test_utils.py @@ -193,6 +193,8 @@ def test_i_can_escape(): ("c:|id:", None, "id"), ("c:key|:", "key", None), ("c:key|id:x", None, None), + ("c:one: plus c:two:", None, None), + ("c:one|id: plus c:two:", None, None), ]) def test_i_can_unstr_concept(text, expected_key, expected_id): k, i = core.utils.unstr_concept(text) @@ -494,3 +496,19 @@ def test_sheerka_hasattr_get_attr(): assert not core.utils.sheerka_hasattr(concept, "b") with pytest.raises(AttributeError): core.utils.sheerka_getattr(concept, "b") + + +def test_i_can_replace_after(): + my_new_items = ["alpha", "beta", "gamma"] + + my_list1 = ["a", "b", "c", "d"] + core.utils.replace_after(my_list1, "c", my_new_items) + assert my_list1 == ["a", "b", "alpha", "beta", "gamma"] + + my_list2 = ["a", "b", "c", "d"] + core.utils.replace_after(my_list2, "a", my_new_items) + assert my_list2 == ["alpha", "beta", "gamma"] + + with pytest.raises(KeyError): + my_list3 = ["a", "b", "c", "d"] + core.utils.replace_after(my_list3, "x", my_new_items) diff --git a/tests/evaluators/test_AddConceptInSetEvaluator.py b/tests/evaluators/test_AddConceptInSetEvaluator.py index 7ae6640..be72bee 100644 --- a/tests/evaluators/test_AddConceptInSetEvaluator.py +++ b/tests/evaluators/test_AddConceptInSetEvaluator.py @@ -1,10 +1,10 @@ import pytest + from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts from core.concept import Concept from core.tokenizer import Tokenizer from evaluators.AddConceptInSetEvaluator import AddConceptInSetEvaluator from parsers.DefConceptParser import IsaConceptNode, NameNode - from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka @@ -51,7 +51,20 @@ class TestAddConceptInSetEvaluator(TestUsingMemoryBasedSheerka): assert res.value.body == "bar" def test_i_can_add_concept_to_a_set_of_concept(self): - sheerka, context, foo, bar = self.init_test().with_concepts("foo", "bar", create_new=True).unpack() + sheerka, context, foo, bar = self.init_test().with_concepts("foo", "bar").unpack() + ret_val = get_isa_ret_val("foo", "bar") + + res = AddConceptInSetEvaluator().eval(context, ret_val) + foo = res.body # get the created instance + + assert res.status + assert context.sheerka.isinstance(res.value, foo.key) + assert context.sheerka.isa(foo, bar) + + assert foo.get_prop(BuiltinConcepts.ISA) == {bar} + + def test_i_can_add_concept_to_a_set_of_concept_when_global_truth_is_activated(self): + sheerka, context, foo, bar = self.init_test(global_truth=True).with_concepts("foo", "bar").unpack() ret_val = get_isa_ret_val("foo", "bar") res = AddConceptInSetEvaluator().eval(context, ret_val) @@ -65,19 +78,18 @@ class TestAddConceptInSetEvaluator(TestUsingMemoryBasedSheerka): assert foo.get_prop(BuiltinConcepts.ISA) == {bar} - def test_i_can_add_bnf_concept_to_a_set_of_concept(self): + def test_i_can_add_bnf_concept_to_a_set_of_concept_when_global_truth_is_activated(self): """ This test is the reason why I have started the whole eval on demand stuff Sheerka tries to evaluate the body but it can't (as a and b are not defined) So 'foo' cannot be put is set :return: """ - sheerka, context, one, two, foo, bar = self.init_test().with_concepts( + sheerka, context, one, two, foo, bar = self.init_test(global_truth=True).with_concepts( "one", "two", Concept("foo", definition="(one|two)=a 'plus' (one|two)=b", body="a + b").def_var("a").def_var("b"), - "bar", - create_new=True).unpack() + "bar").unpack() ret_val = get_isa_ret_val("foo", "bar") res = AddConceptInSetEvaluator().eval(context, ret_val) @@ -103,11 +115,25 @@ class TestAddConceptInSetEvaluator(TestUsingMemoryBasedSheerka): ret_val = get_isa_ret_val("foo", "bar") res = AddConceptInSetEvaluator().eval(context, ret_val) + assert res.status + assert context.sheerka.isinstance(res.value, foo.key) + + def test_i_can_add_concept_with_a_body_to_a_set_of_concept_when_global_truth_is_activated(self): + context = self.get_context(global_truth=True) + foo = Concept("foo", body="1") + context.sheerka.create_new_concept(context, foo) + + bar = Concept("bar") + context.sheerka.create_new_concept(context, bar) + + ret_val = get_isa_ret_val("foo", "bar") + res = AddConceptInSetEvaluator().eval(context, ret_val) + assert res.status assert context.sheerka.isinstance(res.value, BuiltinConcepts.SUCCESS) def test_i_cannot_add_the_same_concept_twice(self): - sheerka, context, foo, bar = self.init_test().with_concepts("foo", "bar", create_new=True).unpack() + sheerka, context, foo, bar = self.init_test(global_truth=True).with_concepts("foo", "bar").unpack() ret_val = get_isa_ret_val("foo", "bar") AddConceptInSetEvaluator().eval(context, ret_val) diff --git a/tests/evaluators/test_ExpressionEvaluator.py b/tests/evaluators/test_ExpressionEvaluator.py index 5f93c01..6de0076 100644 --- a/tests/evaluators/test_ExpressionEvaluator.py +++ b/tests/evaluators/test_ExpressionEvaluator.py @@ -43,7 +43,8 @@ class TestExpressionEvaluator(TestUsingMemoryBasedSheerka): assert not res.body # second time - sheerka.set_isa(context, one, number) + global_truth_context = self.get_context(sheerka, global_truth=True) + sheerka.set_isa(global_truth_context, one, number) parsed_return_value = ExpressionParser().parse(context, ParserInput("one is a number")) res = evaluator.eval(context, parsed_return_value) assert res.status diff --git a/tests/evaluators/test_PrepareEvalGlobalTruthEvaluator.py b/tests/evaluators/test_PrepareEvalGlobalTruthEvaluator.py new file mode 100644 index 0000000..e4bb54b --- /dev/null +++ b/tests/evaluators/test_PrepareEvalGlobalTruthEvaluator.py @@ -0,0 +1,49 @@ +import pytest + +from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UserInputConcept +from core.concept import Concept +from evaluators.PrepareEvalGlobalTruthEvaluator import PrepareEvalGlobalTruthEvaluator + +from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka + +r = ReturnValueConcept + + +class TestPrepareEvalGlobalTruthEvaluator(TestUsingMemoryBasedSheerka): + + @pytest.mark.parametrize("ret_val, expected", [ + (r("name", True, UserInputConcept("global_truth(1 + 1)")), True), + (r("name", True, UserInputConcept(" global_truth(1 + 1) ")), True), + (r("name", True, UserInputConcept("global_truth()")), False), + (r("name", True, UserInputConcept("1+1")), False), + (r("name", True, UserInputConcept("")), False), + (r("name", True, UserInputConcept("global_truth(")), False), + (r("name", True, UserInputConcept(")")), False), + (r("name", True, UserInputConcept([])), False), + (r("name", True, Concept("foo")), False), + (r("name", True, "not a concept"), False), + (r("name", False, UserInputConcept("global_truth(1 + 1)")), False), + ]) + def test_i_can_match(self, ret_val, expected): + context = self.get_context() + assert PrepareEvalGlobalTruthEvaluator().matches(context, ret_val) == expected + + @pytest.mark.parametrize("ret_val, expected", [ + (r("name", True, UserInputConcept("global_truth(1 + 1)")), "1 + 1"), + (r("name", True, UserInputConcept(" global_truth( 1 + 1 ) ")), "1 + 1"), + ]) + def test_i_can_eval(self, ret_val, expected): + context = self.get_context() + sheerka = context.sheerka + + prepare_evaluator = PrepareEvalGlobalTruthEvaluator() + prepare_evaluator.matches(context, ret_val) + res = prepare_evaluator.eval(context, ret_val) + + assert res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.USER_INPUT) + assert res.body.body == expected + + assert BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED in context.protected_hints + assert BuiltinConcepts.EVAL_BODY_REQUESTED in context.protected_hints + assert BuiltinConcepts.RETURN_BODY_REQUESTED in context.protected_hints diff --git a/tests/evaluators/test_ResolveAmbiguityEvaluator.py b/tests/evaluators/test_ResolveAmbiguityEvaluator.py index 1b16baa..638717c 100644 --- a/tests/evaluators/test_ResolveAmbiguityEvaluator.py +++ b/tests/evaluators/test_ResolveAmbiguityEvaluator.py @@ -1,12 +1,15 @@ import pytest + from core.builtin_concepts import BuiltinConcepts from core.concept import Concept +from core.sheerka.services.SheerkaExecute import ParserInput +from evaluators.BaseEvaluator import BaseEvaluator from evaluators.ResolveAmbiguityEvaluator import ResolveAmbiguityEvaluator - +from parsers.ExactConceptParser import ExactConceptParser +from parsers.SyaNodeParser import SyaNodeParser from tests.BaseTest import BaseTest from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka - pretval = BaseTest.pretval @@ -21,6 +24,37 @@ class TestResolveAmbiguityEvaluator(TestUsingMemoryBasedSheerka): context = self.get_context() assert ResolveAmbiguityEvaluator().matches(context, return_values) == expected + def test_i_can_match_when_the_input_comes_from_an_evaluator(self): + ret_val1 = pretval(Concept("foo"), source="source", parser=BaseEvaluator.get_name("evaluator")) + ret_val2 = pretval(Concept("bar"), source="source", parser=BaseEvaluator.get_name("evaluator")) + return_values = [ret_val1, ret_val2] + + context = self.get_context() + assert ResolveAmbiguityEvaluator().matches(context, return_values) + + def test_i_can_match_when_concept_nodes(self): + sheerka, context, a, foo_1, foo_2 = self.init_concepts( + Concept("a"), + Concept("foo x").def_var("x"), + Concept("foo y").def_var("y"), + create_new=True + ) + + return_values = SyaNodeParser().parse(context, ParserInput("foo a")) + assert ResolveAmbiguityEvaluator().matches(context, return_values) + + def test_i_can_match_when_concept_node_mixed_with_concept(self): + sheerka, context, a, foo_1, foo_a = self.init_concepts( + Concept("a"), + Concept("foo x").def_var("x"), + Concept("foo a"), + create_new=True + ) + + sya_return_value = SyaNodeParser().parse(context, ParserInput("foo a")) + + assert ResolveAmbiguityEvaluator().matches(context, [sya_return_value, pretval(foo_a, source="foo a")]) + def test_i_can_manage_when_no_source(self): context = self.get_context() return_values = [BaseTest.retval(Concept("foo"))] @@ -53,6 +87,44 @@ class TestResolveAmbiguityEvaluator(TestUsingMemoryBasedSheerka): return_values[2], ] + @pytest.mark.parametrize("concepts, expected", [ + ([Concept("c1"), Concept("c2", where="True")], "c2"), + ([Concept("c1"), Concept("c2", pre="True")], "c2"), + ([Concept("c1"), Concept("c2", where="False")], "c1"), + ([Concept("c1"), Concept("c2", pre="False")], "c1"), + ([Concept("c1", pre="True"), Concept("c2", where="True")], "c1"), + ([Concept("c1", pre="False"), Concept("c2", where="True")], "c2"), + ([Concept("c1", pre="False"), Concept("c2", where="False")], BuiltinConcepts.NO_RESULT), + ]) + def test_i_can_eval_2(self, concepts, expected): + context = self.get_context() + return_values = [self.pretval(c, source="foo") for c in concepts] + + evaluator = ResolveAmbiguityEvaluator() + evaluator.matches(context, return_values) + res = evaluator.eval(context, return_values) + + assert len(res) == 1 + if expected != BuiltinConcepts.NO_RESULT: + selected_concept = res[0].body.body + assert selected_concept.name == expected + else: + assert res[0].body == BuiltinConcepts.NO_RESULT + + @pytest.mark.parametrize("concepts", [ + [Concept("c1"), Concept("c2")], + [Concept("c1", pre="True"), Concept("c2", pre="True")], + ]) + def test_i_can_eval_when_same_complexity(self, concepts): + context = self.get_context() + return_values = [self.pretval(c, "foo") for c in concepts] + + evaluator = ResolveAmbiguityEvaluator() + evaluator.matches(context, return_values) + res = evaluator.eval(context, return_values) + + assert res is None + def test_i_can_eval_all_fail(self): context = self.get_context() return_values = [ @@ -91,3 +163,70 @@ class TestResolveAmbiguityEvaluator(TestUsingMemoryBasedSheerka): res = evaluator.eval(context, return_values) assert res is None + + def test_i_can_eval_to_the_simplest_concept(self): + """ + def concept foo x where x + def concept foo a + + When the input is foo a, 'foo x' must be discarded as concept 'foo a' is more accurate + :return: + """ + + sheerka, context, a, foo_1, foo_a = self.init_concepts( + Concept("a"), + Concept("foo x").def_var("x"), + Concept("foo a"), + create_new=True + ) + + sya_return_value = SyaNodeParser().parse(context, ParserInput("foo a")) + exact_concept_return_value = pretval(foo_a, source="foo a") + return_values = [sya_return_value, exact_concept_return_value] + evaluator = ResolveAmbiguityEvaluator() + + evaluator.matches(context, return_values) + res = evaluator.eval(context, return_values) + + assert len(res) == 1 + assert res[0].who == evaluator.name + assert res[0].body == exact_concept_return_value.body + assert res[0].parents == return_values + + def test_i_can_eval_to_the_simplest_concept_when_concept_nodes(self): + """ + Same explanation than test_i_can_eval_to_the_simplest_concept() + :return: + """ + sheerka, context, a, b, foo_1, foo_2 = self.init_concepts( + Concept("a"), + Concept("b"), + Concept("foo x y").def_var("x").def_var("y"), + Concept("foo a x").def_var("x"), + create_new=True + ) + + return_values = SyaNodeParser().parse(context, ParserInput("foo a b")) + evaluator = ResolveAmbiguityEvaluator() + + evaluator.matches(context, return_values) + res = evaluator.eval(context, return_values) + + assert len(res) == 1 + assert res[0].who == evaluator.name + assert sheerka.isinstance(res[0].body.body[0].concept, foo_2) + + def test_i_can_eval_when_non_instance_concepts(self): + sheerka, context, foo_1, foo_2 = self.init_concepts( + Concept("foo x", body="1", pre="True").def_var("x"), + Concept("foo x", body="2", pre="True").def_var("x"), + create_new=True + ) + + exact_parser_return_values = ExactConceptParser().parse(context, ParserInput("c:foo x:")) + evaluator = ResolveAmbiguityEvaluator() + + evaluator.matches(context, exact_parser_return_values) + res = evaluator.eval(context, exact_parser_return_values) + + assert res is None # means that there is nothing to resolve as the concepts are not instances diff --git a/tests/evaluators/test_ValidateConceptEvaluator.py b/tests/evaluators/test_ValidateConceptEvaluator.py index 87f06e9..9daaae6 100644 --- a/tests/evaluators/test_ValidateConceptEvaluator.py +++ b/tests/evaluators/test_ValidateConceptEvaluator.py @@ -185,14 +185,3 @@ class TestValidateConceptEvaluator(TestUsingMemoryBasedSheerka): assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.FILTERED) assert res.body.body == ret_val.body.body - - def test_i_can_manage_infinite_recursion(self): - sheerka, context, a_and_b = self.init_concepts( - Concept("a and b", where="is_question()", body="a and b").def_var("a").def_var("b"), - create_new=True) - evaluator = ValidateConceptEvaluator() - - ret_val = pr_ret_val(a_and_b) - res = evaluator.eval(context, ret_val) - - assert res is None # infinite recursion detected, res is None to drop the validator diff --git a/tests/non_reg/test_sheerka_non_reg.py b/tests/non_reg/test_sheerka_non_reg.py index 3634f69..2b6bd86 100644 --- a/tests/non_reg/test_sheerka_non_reg.py +++ b/tests/non_reg/test_sheerka_non_reg.py @@ -362,6 +362,7 @@ as: ]) def test_i_can_mix_concept_with_python_to_define_numbers(self, desc, definitions): sheerka = self.get_sheerka() + sheerka.add_to_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) for definition in definitions: sheerka.evaluate_user_input(definition) @@ -396,6 +397,7 @@ as: assert len(res) == 1 assert res[0].status assert res[0].body == 23 + sheerka.remove_fom_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) def test_i_can_mix_bnf_and_isa(self): """ @@ -406,8 +408,8 @@ as: sheerka.evaluate_user_input("def concept one as 1") sheerka.evaluate_user_input("def concept two as 2") sheerka.evaluate_user_input("def concept number") - sheerka.evaluate_user_input("set_isa(one, number)") - sheerka.evaluate_user_input("set_isa(two, number)") + sheerka.evaluate_user_input("global_truth(set_isa(one, number))") + sheerka.evaluate_user_input("global_truth(set_isa(two, number))") sheerka.evaluate_user_input("def concept twenties from bnf 'twenty' number as 20 + number") res = sheerka.evaluate_user_input("twenty one") @@ -443,6 +445,7 @@ as: def test_i_can_mix_bnf_and_isa_2(self): sheerka = self.get_sheerka() + sheerka.add_to_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) init = [ "def concept one as 1", @@ -460,6 +463,7 @@ as: assert len(res) == 1 assert res[0].status assert res[0].body == 21 + sheerka.remove_fom_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) def test_i_can_use_concepts_defined_with_from(self): sheerka = self.get_sheerka() @@ -565,7 +569,7 @@ as: "def concept plus_one from bnf number=n1 'plus_one' as n1 + 1", ] - sheerka = self.init_scenario(definitions) + sheerka = self.init_scenario(definitions, global_truth=True) res = sheerka.evaluate_user_input("eval two plus_one") assert len(res) == 1 @@ -642,8 +646,6 @@ as: assert res[0].body == 21 def test_i_can_use_where_in_bnf(self): - sheerka, context = self.init_test().unpack() - init = [ "def concept one as 1", "def concept two as 2", @@ -656,8 +658,8 @@ as: "def concept twenties from bnf twenty number where number <= 2 as twenty + number" ] - for exp in init: - sheerka.evaluate_user_input(exp) + sheerka = self.init_scenario(init, global_truth=True) + context = self.get_context(sheerka) res = sheerka.evaluate_user_input("twenty one") assert len(res) == 1 @@ -765,7 +767,7 @@ as: sheerka.evaluate_user_input("def concept two as 2") sheerka.evaluate_user_input("def concept twenties from bnf 'twenty' (one|two)=unit as 20 + unit") - res = sheerka.evaluate_user_input("set_isa(twenties, number)") + res = sheerka.evaluate_user_input("global_truth(set_isa(twenties, number))") assert len(res) == 1 assert res[0].status @@ -946,7 +948,7 @@ as: "set_isa(twenties, number)", ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) # simulate that sheerka was stopped and restarted sheerka.clear_bnf_definition() @@ -967,8 +969,8 @@ as: res = sheerka.evaluate_user_input("set_isa(last_created_concept(), number)") assert res[0].status - assert sheerka.isinstance(res[0].body, BuiltinConcepts.SUCCESS) - assert sheerka.isa(sheerka.new("one"), sheerka.new("number")) + assert sheerka.isinstance(res[0].body, "one") + assert sheerka.isa(res[0].body, sheerka.new("number")) def test_i_can_evaluate_sya_and_ret_concepts(self): init = [ @@ -1001,7 +1003,7 @@ as: # Since command is a __COMMAND, the body is auto evaluated # and we return the body, not the concept - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("command") assert res[0].status assert res[0].body == "Executed !" @@ -1016,7 +1018,7 @@ as: "def concept x is a y as set_isa(x,y)", ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("question(one is a number)") # automatically evaluated assert len(res) == 1 assert res[0].status @@ -1042,7 +1044,7 @@ as: # So the first one should be picked. # the second concept 'one' 's value is an integer, to make sure that it won't be rejected because of its type - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("def concept x plus y where x is a number as x + y") assert len(res) == 1 assert res[0].status @@ -1065,7 +1067,7 @@ as: "set_is_greater_than(BuiltinConcepts.PRECEDENCE, c:is_a:, c:q:, 'Sya')", ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("one is a number ?") # automatically evaluated assert len(res) == 1 assert res[0].status @@ -1133,7 +1135,7 @@ as: res = sheerka.evaluate_user_input("twenty one") assert len(res) > 1 # not recognized - sheerka.evaluate_user_input("set_isa(one, number)") + sheerka.evaluate_user_input("global_truth(set_isa(one, number))") res = sheerka.evaluate_user_input("twenty one") assert len(res) == 1 assert res[0].status @@ -1252,9 +1254,9 @@ as: "def concept two", "def concept number", "def concept nb times from bnf number 'times'", - "set_isa(two, number)", + "set_isa(two, number)", # defined after 'nb times' ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("two times") @@ -1270,7 +1272,7 @@ as: "def concept cars", "def concept quantify x from bnf number x as set_attr(x, 'qty', number) ret x" ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("eval two cars") @@ -1287,7 +1289,7 @@ as: "def concept cars", "def concept quantify x from bnf number=n1 x as set_attr(x, 'qty', n1) ret x" ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("eval two cars") @@ -1317,7 +1319,7 @@ as: "def concept she ret memory('isa(self, female)')", "girl" ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) context = self.get_context(sheerka) res = sheerka.evaluate_user_input("set_attr(she, 'my_attr', 'my value')") @@ -1337,7 +1339,7 @@ as: "def concept x attribute y equals z as set_attr(x, y, z)", "girl" ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) context = self.get_context(sheerka) res = sheerka.evaluate_user_input("eval she attribute 'my_attr' equals 'my value'") @@ -1392,7 +1394,7 @@ as: "def concept qualify x from bnf adjective x as set_attr(x, c:adjective:, adjective) ret x", "eval a red short", ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("what is the color of the short ?") assert len(res) == 1 diff --git a/tests/non_reg/test_sheerka_non_reg2.py b/tests/non_reg/test_sheerka_non_reg2.py index fce91bf..651d39b 100644 --- a/tests/non_reg/test_sheerka_non_reg2.py +++ b/tests/non_reg/test_sheerka_non_reg2.py @@ -13,6 +13,34 @@ class TestSheerkaNonRegMemory2(TestUsingMemoryBasedSheerka): res = sheerka.evaluate_user_input("question(foo is a foo)") - # assert len(res) == 1 - # assert res[0].status - # assert res[0].value + assert len(res) == 1 + assert res[0].status + assert res[0].value # value is True since 'x is a foo' was chosen + + def test_i_can_select_the_correct_concept_vs_bnf(self): + init = [ + "def concept one as 1", + "def concept two as 2", + "def concept twenties from bnf 'twenty' (one|two)=unit as 20 + unit", + "def concept twenty two as 'specific occurrence'", + ] + sheerka = self.init_scenario(init) + + res = sheerka.evaluate_user_input("eval twenty one") + assert res[0].value == 21 + + res = sheerka.evaluate_user_input("eval twenty two") + assert res[0].value == "specific occurrence" # thanks to ExactConceptParser + + def test_i_can_use_global_truth_when_calling_a_concept(self): + init = [ + "def concept one", + "def concept number", + "def concept x is a y as set_isa(x, y)", + ] + sheerka = self.init_scenario(init) + + res = sheerka.evaluate_user_input("global_truth(one is a number)") + + assert res[0].status + assert sheerka.isa(sheerka.new("one"), sheerka.new("number")) diff --git a/tests/non_reg/test_sheerka_non_reg_file_based.py b/tests/non_reg/test_sheerka_non_reg_file_based.py index fea1db0..5294a5d 100644 --- a/tests/non_reg/test_sheerka_non_reg_file_based.py +++ b/tests/non_reg/test_sheerka_non_reg_file_based.py @@ -85,7 +85,7 @@ class TestSheerkaNonRegFile(TestUsingFileBasedSheerka): "set_isa(thirty, number)", "def concept thirties from bnf thirty number where number < 10 as thirty + number", "set_isa(thirties, number)", - ]) + ], global_truth=True) sheerka = self.new_sheerka_instance() diff --git a/tests/non_reg/test_sheerka_non_reg_out.py b/tests/non_reg/test_sheerka_non_reg_out.py index 2012475..edb3f07 100644 --- a/tests/non_reg/test_sheerka_non_reg_out.py +++ b/tests/non_reg/test_sheerka_non_reg_out.py @@ -53,6 +53,45 @@ post : None ret : None vars : [] props : {} +""" + + def test_i_can_display_multiple_concepts_description_when_concept_definition(self, capsys): + init = [ + "def concept foo as 1", + "def concept foo as 2 where True", + ] + sheerka = self.init_scenario(init) + capsys.readouterr() + + sheerka.enable_process_return_values = True + sheerka.evaluate_user_input("desc(c:foo:)") + captured = capsys.readouterr() + assert captured.out == """id : 1001 +name : foo +key : foo +definition: None +type : None +hash : 16f7fbb8bc509b8c652edaf3d0c0457d15a37f0a862fbe03fa357b0c77249c46 +body : 1 +where : None +pre : None +post : None +ret : None +vars : [] +props : {} +id : 1002 +name : foo +key : foo +definition: None +type : None +hash : e8dd1af1b6bc0eca0fb4a87a6fabb16655caa4b7a6ea9dbbd1f887757e6caf89 +body : 2 +where : True +pre : None +post : None +ret : None +vars : [] +props : {} """ def test_i_can_describe_a_rule(self, capsys): diff --git a/tests/non_reg/test_sheerka_non_reg_pipe_functions.py b/tests/non_reg/test_sheerka_non_reg_pipe_functions.py index 595723b..260a311 100644 --- a/tests/non_reg/test_sheerka_non_reg_pipe_functions.py +++ b/tests/non_reg/test_sheerka_non_reg_pipe_functions.py @@ -35,7 +35,7 @@ class TestSheerkaNonRegPipeFunctions(TestUsingMemoryBasedSheerka): "add_to_memory('x', [one])" ] - sheerka = self.init_scenario(init) + sheerka = self.init_scenario(init, global_truth=True) res = sheerka.evaluate_user_input("x | where('isa(self, number)')") assert len(res) == 1 diff --git a/tests/parsers/test_BnfNodeParser.py b/tests/parsers/test_BnfNodeParser.py index b08893b..b6f13d2 100644 --- a/tests/parsers/test_BnfNodeParser.py +++ b/tests/parsers/test_BnfNodeParser.py @@ -98,22 +98,24 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka): @classmethod def setup_class(cls): - init_test_helper = cls().init_test(cache_only=False, ontology="#TestBnfNodeParser#") + test_instance = cls() + init_test_helper = test_instance.init_test(cache_only=False, ontology="#TestBnfNodeParser#") sheerka, context, *updated = init_test_helper.with_concepts(*cmap.values(), create_new=True).unpack() for i, concept_name in enumerate(cmap): cmap[concept_name] = updated[i] # end of initialisation + global_truth_context = test_instance.get_context(sheerka, global_truth=True) sheerka = TestBnfNodeParser.sheerka - sheerka.set_isa(context, cmap["one"], cmap["number"]) - sheerka.set_isa(context, cmap["two"], cmap["number"]) - sheerka.set_isa(context, cmap["three"], cmap["number"]) - sheerka.set_isa(context, cmap["four"], cmap["number"]) - sheerka.set_isa(context, cmap["thirty"], cmap["number"]) - sheerka.set_isa(context, cmap["forty"], cmap["number"]) - sheerka.set_isa(context, cmap["fifty"], cmap["number"]) - sheerka.set_isa(context, cmap["one hundred"], cmap["number"]) - sheerka.set_isa(context, cmap["hundreds"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["one"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["two"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["three"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["four"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["thirty"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["forty"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["fifty"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["one hundred"], cmap["number"]) + sheerka.set_isa(global_truth_context, cmap["hundreds"], cmap["number"]) # Pay attention. 'twenties (t1 and t2) are not set as 'number' @@ -122,28 +124,28 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka): where="number < 10", body="thirty + number").def_var("thirty").def_var("number")) cmap["thirties"] = sheerka.create_new_concept(context, thirties).body.body - sheerka.set_isa(context, sheerka.new("thirties"), sheerka.new("number")) + sheerka.set_isa(global_truth_context, sheerka.new("thirties"), sheerka.new("number")) forties = cls.update_bnf(context, Concept("forties", definition="forty number", where="number < 10", body="forty + number").def_var("forty").def_var("number")) cmap["forties"] = sheerka.create_new_concept(context, forties).body.body - sheerka.set_isa(context, sheerka.new("forties"), sheerka.new("number")) + sheerka.set_isa(global_truth_context, sheerka.new("forties"), sheerka.new("number")) fifties = cls.update_bnf(context, Concept("fifties", definition="fifty number", where="number < 10", body="fifty + number").def_var("fifty").def_var("number")) cmap["fifties"] = sheerka.create_new_concept(context, fifties).body.body - sheerka.set_isa(context, sheerka.new("fifties"), sheerka.new("number")) + sheerka.set_isa(global_truth_context, sheerka.new("fifties"), sheerka.new("number")) thousands = cls.update_bnf(context, Concept("thousands", definition="number 'thousand'", where="number < 999", body="number * 1000").def_var("number")) cmap["thousands"] = sheerka.create_new_concept(context, thousands).body.body - sheerka.set_isa(context, sheerka.new("thousands"), sheerka.new("number")) + sheerka.set_isa(global_truth_context, sheerka.new("thousands"), sheerka.new("number")) cls.shared_ontology = sheerka.get_ontology(context) sheerka.pop_ontology(context) @@ -1217,10 +1219,11 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka): "twenties": self.bnf_concept("twenties", Sequence(ConceptExpression("twenty"), ConceptExpression("number"))) } sheerka, context, parser = self.init_parser(my_map) + global_truth_context = self.get_context(sheerka, global_truth=True) parser.context = context parser.sheerka = sheerka - sheerka.set_isa(context, sheerka.new("one"), my_map["number"]) - sheerka.set_isa(context, sheerka.new("twenty"), my_map["number"]) + sheerka.set_isa(global_truth_context, sheerka.new("one"), my_map["number"]) + sheerka.set_isa(global_truth_context, sheerka.new("twenty"), my_map["number"]) parser.concepts_grammars.clear() # make sure parsing expression is created from scratch @@ -1251,9 +1254,10 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka): sheerka, context, parser = self.init_parser(my_map, singleton=True) parser.context = context parser.sheerka = sheerka - sheerka.set_isa(context, sheerka.new("one"), my_map["number"]) - sheerka.set_isa(context, sheerka.new("two"), my_map["number"]) - sheerka.set_isa(context, sheerka.new("hundreds"), my_map["number"]) + global_truth_context = self.get_context(sheerka, global_truth=True) + sheerka.set_isa(global_truth_context, sheerka.new("one"), my_map["number"]) + sheerka.set_isa(global_truth_context, sheerka.new("two"), my_map["number"]) + sheerka.set_isa(global_truth_context, sheerka.new("hundreds"), my_map["number"]) parser.concepts_grammars.clear() # make sure parsing expression is created from scratch parsing_expression = parser.get_parsing_expression(context, my_map["hundreds"]) @@ -1281,9 +1285,10 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka): sheerka, context, parser = self.init_parser(my_map, singleton=True) parser.context = context parser.sheerka = sheerka - sheerka.set_isa(context, sheerka.new("one"), my_map["number"]) - sheerka.set_isa(context, sheerka.new("twenty"), my_map["number"]) - sheerka.set_isa(context, sheerka.new("twenties"), my_map["number"]) # <- twenties is also a number + global_truth_context = self.get_context(sheerka, global_truth=True) + sheerka.set_isa(global_truth_context, sheerka.new("one"), my_map["number"]) + sheerka.set_isa(global_truth_context, sheerka.new("twenty"), my_map["number"]) + sheerka.set_isa(global_truth_context, sheerka.new("twenties"), my_map["number"]) # <- twenties is also a number parser.concepts_grammars.clear() # make sure parsing expression is created from scratch @@ -1900,15 +1905,16 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka): } sheerka, context, parser = self.init_parser(my_map, init_from_sheerka=True, create_new=True) - sheerka.set_isa(context, my_map["light_red"], my_map["adjective"]) - sheerka.set_isa(context, my_map["dark_red"], my_map["adjective"]) - sheerka.set_isa(context, my_map["light_red"], my_map["color"]) - sheerka.set_isa(context, my_map["dark_red"], my_map["color"]) - sheerka.set_isa(context, my_map["light_red"], my_map["red colors"]) - sheerka.set_isa(context, my_map["dark_red"], my_map["red colors"]) - sheerka.set_isa(context, my_map["color"], my_map["adjective"]) - sheerka.set_isa(context, my_map["red colors"], my_map["color"]) - sheerka.set_isa(context, my_map["red colors"], my_map["adjective"]) + global_truth_context = self.get_context(sheerka, global_truth=True) + sheerka.set_isa(global_truth_context, my_map["light_red"], my_map["adjective"]) + sheerka.set_isa(global_truth_context, my_map["dark_red"], my_map["adjective"]) + sheerka.set_isa(global_truth_context, my_map["light_red"], my_map["color"]) + sheerka.set_isa(global_truth_context, my_map["dark_red"], my_map["color"]) + sheerka.set_isa(global_truth_context, my_map["light_red"], my_map["red colors"]) + sheerka.set_isa(global_truth_context, my_map["dark_red"], my_map["red colors"]) + sheerka.set_isa(global_truth_context, my_map["color"], my_map["adjective"]) + sheerka.set_isa(global_truth_context, my_map["red colors"], my_map["color"]) + sheerka.set_isa(global_truth_context, my_map["red colors"], my_map["adjective"]) text = "light red table" diff --git a/tests/parsers/test_ExactConceptParser.py b/tests/parsers/test_ExactConceptParser.py index 7bdd8ff..ef83f0f 100644 --- a/tests/parsers/test_ExactConceptParser.py +++ b/tests/parsers/test_ExactConceptParser.py @@ -144,6 +144,7 @@ class TestExactConceptParser(TestUsingMemoryBasedSheerka): assert concept_found == foo assert not concept_found.get_hints().need_validation assert concept_found.get_hints().is_evaluated + assert not concept_found.get_hints().is_instance def test_i_can_parse_concept_with_concept_tokens(self): sheerka, context, one, two, plus = self.init_concepts( diff --git a/tests/parsers/test_LogicalOperatorParser.py b/tests/parsers/test_LogicalOperatorParser.py index 99d57ec..c80192c 100644 --- a/tests/parsers/test_LogicalOperatorParser.py +++ b/tests/parsers/test_LogicalOperatorParser.py @@ -4,7 +4,7 @@ from core.builtin_concepts import BuiltinConcepts from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import TokenKind from parsers.BaseExpressionParser import TrueifyVisitor, IsAQuestionVisitor, LeftPartNotFoundError, \ - ParenthesisMismatchError + ParenthesisMismatchError, compile_disjunctions, NotNode, AndNode, OrNode from parsers.BaseParser import UnexpectedEofParsingError, UnexpectedTokenParsingError from parsers.LogicalOperatorParser import LogicalOperatorParser from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka @@ -12,6 +12,46 @@ from tests.parsers.parsers_utils import EXPR, OR, AND, NOT, \ get_expr_node_from_test_node +class DoNotCompareStartStopContextManager: + def __init__(self): + self.original_not_eq = None + self.original_and_eq = None + self.original_or_eq = None + + @staticmethod + def not_eq(self, other): + if not isinstance(other, NotNode): + return False + return self.node == other.node + + @staticmethod + def and_eq(self, other): + if not isinstance(other, AndNode): + return False + + return self.parts == other.parts + + @staticmethod + def or_eq(self, other): + if not isinstance(other, OrNode): + return False + + return self.parts == other.parts + + def __enter__(self): + self.original_not_eq = NotNode.__eq__ + self.original_and_eq = AndNode.__eq__ + self.original_or_eq = OrNode.__eq__ + NotNode.__eq__ = self.not_eq + AndNode.__eq__ = self.and_eq + OrNode.__eq__ = self.or_eq + + def __exit__(self, *args): + NotNode.__eq__ = self.original_not_eq + AndNode.__eq__ = self.original_and_eq + OrNode.__eq__ = self.original_or_eq + + class TestLogicalOperatorParser(TestUsingMemoryBasedSheerka): def init_parser(self): @@ -175,6 +215,50 @@ class TestLogicalOperatorParser(TestUsingMemoryBasedSheerka): assert IsAQuestionVisitor().visit(expr_node) == expected + @pytest.mark.parametrize("expression, expected", [ + ("a", [EXPR("a")]), + ("a and b and c", [AND(EXPR("a"), EXPR("b"), EXPR("c"))]), + ("a or b or c", [EXPR("a"), + EXPR("b"), + EXPR("c")]), + ("a and b or c", [AND(EXPR("a"), EXPR("b")), EXPR("c")]), + ("a or b and c", [EXPR("a"), + AND(EXPR("b"), EXPR("c"))]), + ("(a or b) and c", [AND(EXPR("a"), EXPR("c")), + AND(EXPR("b"), EXPR("c"))]), + ("a or (b or c) and d", [EXPR("a"), + AND(EXPR("b"), EXPR("d")), + AND(EXPR("c"), EXPR("d"))]), + + ("not a", [NOT(EXPR("a"))]), + ("not (a and b)", [NOT(AND(EXPR("a"), EXPR("b")))]), + ("not (a or b)", [AND(NOT(EXPR("a")), NOT(EXPR("b")))]), + + ("(a or b) and not (c or d)", [AND(EXPR("a"), NOT(EXPR("c")), NOT(EXPR("d"))), + AND(EXPR("b"), NOT(EXPR("c")), NOT(EXPR("d")))]), + ("(a or b) or not (c or d)", [EXPR("a"), + EXPR("b"), + AND(NOT(EXPR("c")), NOT(EXPR("d")))]), + + ("(a and b) and not (c or d)", [AND(EXPR("a"), EXPR("b"), NOT(EXPR("c")), NOT(EXPR("d")))]), + ("(a and b) or not (c or d)", [AND(EXPR("a"), EXPR("b")), + AND(NOT(EXPR("c")), NOT(EXPR("d")))]), + + ("a and (b and c)", [AND(EXPR("a"), EXPR("b"), EXPR("c"))]), + ("a or (b or c)", [EXPR("a"), + EXPR("b"), + EXPR("c")]), + ]) + def test_i_can_compile_disjunction(self, expression, expected): + sheerka, context, parser = self.init_parser() + resolved_expected = [get_expr_node_from_test_node(expression, e) for e in expected] + + expr_node = parser.parse(context, ParserInput(expression)).body.body + + with DoNotCompareStartStopContextManager(): + res = compile_disjunctions(expr_node) + assert res == resolved_expected + # @pytest.mark.parametrize("expression, expected", [ # ("foo", "foo"), # ("one two", "one two"), diff --git a/tests/parsers/test_SequenceNodeParser.py b/tests/parsers/test_SequenceNodeParser.py index f657946..96ee2e5 100644 --- a/tests/parsers/test_SequenceNodeParser.py +++ b/tests/parsers/test_SequenceNodeParser.py @@ -258,6 +258,23 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka): assert sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) compare_with_test_object(lexer_nodes, expected_array) + @pytest.mark.parametrize("concept", [ + Concept("foo x", body="1").def_var("x"), + Concept("foo"), + ]) + def test_i_can_parse_token_concepts(self, concept): + concepts_map = { + "foo": concept, + } + + sheerka, context, parser = self.init_parser(concepts_map, create_new=True, use_sheerka=True) + res = parser.parse(context, ParserInput(f"c:{concept.name}:")) + + assert res.status + concept_found = res.body.body[0].concept + assert concept_found.get_hints().is_evaluated + assert not concept_found.get_hints().is_instance + @pytest.mark.parametrize("text", [ "foo", f"foo one", diff --git a/tests/parsers/test_SyaNodeParser.py b/tests/parsers/test_SyaNodeParser.py index 98f110c..ce7de9a 100644 --- a/tests/parsers/test_SyaNodeParser.py +++ b/tests/parsers/test_SyaNodeParser.py @@ -709,6 +709,65 @@ class TestSyaNodeParser(TestUsingMemoryBasedSheerka): self.compare_results(res, expected_sequences, cmap, expression) + @pytest.mark.parametrize("expression, expected", [ + ("suffixed2 a b", ['a', 'b', "suffixed2"]), + ("suffixed3 a b c", ['a', 'b', 'c', "suffixed3"]), + ("a b prefixed2", ['a', 'b', "prefixed2"]), + ("a b c prefixed3", ['a', 'b', 'c', "prefixed3"]), + ("start2 a b stop", ['a', 'b', "start2"]), + ("start3 a b c stop", ['a', 'b', 'c', "start3"]), + ]) + def test_i_can_post_fix_when_multiple_parameters_are_expected(self, expression, expected): + concepts_map = { + "a": Concept("a"), + "b": Concept("b"), + "c": Concept("c"), + "suffixed2": Concept("suffixed2 x y").def_var("x").def_var("y"), + "suffixed3": Concept("suffixed3 x y z").def_var("x").def_var("y").def_var("z"), + "prefixed2": Concept("x y prefixed2").def_var("x").def_var("y"), + "prefixed3": Concept("x y z prefixed3").def_var("x").def_var("y").def_var("z"), + "start2": Concept("start2 x y stop").def_var("x").def_var("y"), + "start3": Concept("start3 x y z stop").def_var("x").def_var("y").def_var("z"), + } + sheerka, context, parser = self.init_parser(concepts_map, None) + + res = parser.infix_to_postfix(context, ParserInput(expression)) + expected_array = compute_expected_array(concepts_map, expression, expected) + + assert len(res) == 1 + transformed_out = get_test_obj(res[0].out, expected_array) + assert transformed_out == expected_array + + @pytest.mark.parametrize("expression, expected", [ + ("suffixed3 x y z", ['x', 'y', 'z', "suffixed3"]), + ("suffixed3 a y z", ['a', 'y', 'z', "suffixed3"]), + ("suffixed3 x a z", ['x ', 'a', ' z', "suffixed3"]), # this one was not managed by the second chance + ("suffixed3 x y a", ['x', 'y', 'a', "suffixed3"]), + ("x y z prefixed3", ['x', 'y', 'z', "prefixed3"]), + ("a y z prefixed3", ['a', 'y', 'z', "prefixed3"]), + ("x a z prefixed3", ['x ', 'a', ' z', "prefixed3"]), + ("x y a prefixed3", ['x', 'y', 'a', "prefixed3"]), + ("start3 x y z stop", ['x', 'y', 'z', "start3"]), + ("start3 a y z stop", ['a', 'y', 'z', "start3"]), + ("start3 x a z stop", ['x ', 'a', ' z ', "start3"]), + ("start3 x y a stop", ['x', 'y', 'a', "start3"]), + ]) + def test_i_can_post_fix_when_multiple_parameters_are_expected_but_unrecognized_tokens(self, expression, expected): + concepts_map = { + "a": Concept("a"), + "suffixed3": Concept("suffixed3 x y z").def_var("x").def_var("y").def_var("z"), + "prefixed3": Concept("x y z prefixed3").def_var("x").def_var("y").def_var("z"), + "start3": Concept("start3 x y z stop").def_var("x").def_var("y").def_var("z"), + } + sheerka, context, parser = self.init_parser(concepts_map, None) + + res = parser.infix_to_postfix(context, ParserInput(expression)) + expected_array = compute_expected_array(concepts_map, expression, expected) + + assert len(res) == 1 + transformed_out = get_test_obj(res[0].out, expected_array) + assert transformed_out == expected_array + @pytest.mark.parametrize("expression, expected", [ ("(", ("(", 0)), ("one plus ( 1 + ", ("(", 4)), @@ -1080,6 +1139,28 @@ class TestSyaNodeParser(TestUsingMemoryBasedSheerka): assert isinstance(concept_plus_b[0].body.body, PythonNode) assert concept_suffixed_a == cmap["two"] + @pytest.mark.parametrize("text, expected", [ + ("suffixed3 a b c", [("x", "a"), ("y", "b"), ("z", "c")]), + ("a b c prefixed3", [("x", "a"), ("y", "b"), ("z", "c")]), + ("start3 a b c stop", [("x", "a"), ("y", "b"), ("z", "c")]), + ]) + def test_i_can_parse_when_multiple_parameters_are_expected(self, text, expected): + concepts_map = { + "a": Concept("a"), + "b": Concept("b"), + "c": Concept("c"), + "suffixed3": Concept("suffixed3 x y z").def_var("x").def_var("y").def_var("z"), + "prefixed3": Concept("x y z prefixed3").def_var("x").def_var("y").def_var("z"), + "start3": Concept("start3 x y z stop").def_var("x").def_var("y").def_var("z"), + } + sheerka, context, parser = self.init_parser(concepts_map, None) + + res = parser.parse(context, ParserInput(text)) + lexer_nodes = res.body.body + + assert res.status + assert lexer_nodes[0].concept.get_metadata().variables == expected + @pytest.mark.parametrize("text", [ "function(suffixed one)", "function(one plus two mult three)", diff --git a/tests/parsers/test_UnrecognizedNodeParser.py b/tests/parsers/test_UnrecognizedNodeParser.py index 08ccfd3..9692c14 100644 --- a/tests/parsers/test_UnrecognizedNodeParser.py +++ b/tests/parsers/test_UnrecognizedNodeParser.py @@ -433,6 +433,10 @@ class TestUnrecognizedNodeParser(TestUsingMemoryBasedSheerka): compare_with_test_object(actual_nodes, expected_array) def test_i_can_parse_when_multiple_atom_and_sya(self): + """ + Testing that the ambiguity between hello_atom and hello_sya is correctly managed + :return: + """ sheerka, context, parser = self.init_parser() expression = "two hello one three" nodes = get_input_nodes_from(concepts_map, expression, @@ -440,25 +444,15 @@ class TestUnrecognizedNodeParser(TestUsingMemoryBasedSheerka): parser_input = ParserResultConcept("parsers.xxx", source=expression, value=nodes) res = parser.parse(context, parser_input) - assert len(res) == 2 - assert res[0].status - assert res[1].status + assert res.status - actual_nodes0 = res[0].body.body + actual_nodes0 = res.body.body expected_0 = compute_expected_array(concepts_map, expression, [ CN("two", start=0, end=0), CN("hello_atom", source="hello one", start=2, end=4), CN("three", start=6, end=6)]) compare_with_test_object(actual_nodes0, expected_0) - actual_nodes1 = res[1].body.body - expected_1 = compute_expected_array(concepts_map, expression, [ - CN("two", start=0, end=0), - CNC("hello_sya", "hello one", start=2, end=4, a="one"), - CN("three", start=6, end=6)], - exclude_body=True) - compare_with_test_object(actual_nodes1, expected_1) - def test_i_can_parse_when_multiple_sya_concepts(self): sheerka, context, parser = self.init_parser() expression = "greetings two"