From 8a866880bc85471dcd011692491383b7895b1308 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sun, 30 Aug 2020 20:31:06 +0200 Subject: [PATCH] Added is_lesser and is_greatest in SheerkaComparison --- _concepts.txt | 16 +- src/core/builtin_concepts.py | 13 +- .../services/SheerkaComparisonManager.py | 185 ++++++++++++++-- .../services/SheerkaCreateNewConcept.py | 2 +- .../sheerka/services/SheerkaModifyConcept.py | 2 +- src/evaluators/PythonEvaluator.py | 2 + src/parsers/SyaNodeParser.py | 9 +- tests/core/test_SheerkaComparisonManager.py | 198 +++++++++++++++++- tests/core/test_SheerkaCreateNewConcept.py | 4 +- tests/core/test_SheerkaModifyConcept.py | 2 +- tests/non_reg/test_sheerka_non_reg.py | 11 +- 11 files changed, 393 insertions(+), 51 deletions(-) diff --git a/_concepts.txt b/_concepts.txt index b664de3..6ae9331 100644 --- a/_concepts.txt +++ b/_concepts.txt @@ -84,10 +84,10 @@ def concept plus from a plus b as a + b def concept minus from a minus b as a - b def concept multiplied from a multiplied by b as a * b def concept divided from a divided by b as a * b -set_is_greater_than(BuiltinConcepts.PRECEDENCE, multiplied, plus) -set_is_greater_than(BuiltinConcepts.PRECEDENCE, divided, plus) -set_is_greater_than(BuiltinConcepts.PRECEDENCE, multiplied, minus) -set_is_greater_than(BuiltinConcepts.PRECEDENCE, divided, minus) +set_is_greater_than(__PRECEDENCE, multiplied, plus) +set_is_greater_than(__PRECEDENCE, divided, plus) +set_is_greater_than(__PRECEDENCE, multiplied, minus) +set_is_greater_than(__PRECEDENCE, divided, minus) def concept explain as get_results() | filter("id == 0") | recurse(2) def concept explain last as get_last_results() | filter("id == 0") | recurse(2) def concept explain x as get_results() | filter(f"id == {x}") | recurse(3) where x @@ -95,9 +95,11 @@ def concept explain x '--recurse' y as get_results() | filter(f"id == {x}") | re set_isa(c:explain:, __COMMAND) set_isa(c:explain last:, __COMMAND) set_isa(c:explain x:, __COMMAND) -def concept precedence a > precedence b as set_is_greater_than(BuiltinConcepts.PRECEDENCE, a, b) +def concept precedence a > precedence b as set_is_greater_than(__PRECEDENCE, a, b) set_isa(c:precedence a > precedence b:, __COMMAND) def concept x is a command as set_isa(x, __COMMAND) set_isa(c:x is a command:, __COMMAND) -def concept q from q ? as question(q) pre in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED) -def concept x is a 'concept' as isinstance(x, Concept) pre in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED) \ No newline at end of file +def concept q from q ? as question(q) pre is_question() +set_is_lesser(__PRECEDENCE, q) +def concept x is a 'concept' as isinstance(x, Concept) pre is_question() +def concept x is a y as isa(x,y) pre is_question() \ No newline at end of file diff --git a/src/core/builtin_concepts.py b/src/core/builtin_concepts.py index 8a20fd1..8ff5a8b 100644 --- a/src/core/builtin_concepts.py +++ b/src/core/builtin_concepts.py @@ -72,7 +72,7 @@ class BuiltinConcepts(Enum): IS_EMPTY = "is empty" # when a set is empty NO_RESULT = "no result" # no return value returned INVALID_RETURN_VALUE = "invalid return value" # the return value of an evaluator is not correct - CONCEPT_ALREADY_DEFINED = "concept already defined" # when you try to add the same concept twice + ALREADY_DEFINED = "already defined" # when you try to add the same object twice (a concept or whatever) NOP = "no operation" # no operation concept. Does nothing CONCEPT_EVAL_ERROR = "concept evaluation error" # cannot evaluate a property or metadata of a concept ENUMERATION = "enum" # represents a list or a set @@ -91,6 +91,8 @@ class BuiltinConcepts(Enum): FORMAT_INSTRUCTIONS = "format instructions" # to express how to print the concept NOT_IMPLEMENTED = "not implemented" # instead of raise an error PYTHON_SECURITY_ERROR = "security error" # when trying to execute statement when only expression is allowed + INVALID_LESSER_OPERATION = "Invalid lesser operation" + INVALID_GREATEST_OPERATION = "Invalid greatest operation" NODE = "node" GENERIC_NODE = "generic node" @@ -152,6 +154,9 @@ BuiltinUnique = [ BuiltinConcepts.ISA, BuiltinConcepts.COMMAND, + + BuiltinConcepts.INVALID_LESSER_OPERATION, + BuiltinConcepts.INVALID_GREATEST_OPERATION, ] BuiltinErrors = [str(e) for e in { @@ -164,14 +169,16 @@ BuiltinErrors = [str(e) for e in { BuiltinConcepts.TOO_MANY_ERRORS, BuiltinConcepts.MULTIPLE_ERRORS, BuiltinConcepts.INVALID_RETURN_VALUE, - BuiltinConcepts.CONCEPT_ALREADY_DEFINED, + BuiltinConcepts.ALREADY_DEFINED, BuiltinConcepts.CONCEPT_EVAL_ERROR, BuiltinConcepts.CONCEPT_ALREADY_IN_SET, BuiltinConcepts.NOT_A_SET, BuiltinConcepts.CONDITION_FAILED, BuiltinConcepts.CHICKEN_AND_EGG, BuiltinConcepts.NOT_INITIALIZED, - BuiltinConcepts.NOT_FOUND + BuiltinConcepts.NOT_FOUND, + BuiltinConcepts.INVALID_LESSER_OPERATION, + BuiltinConcepts.INVALID_GREATEST_OPERATION, }] """ diff --git a/src/core/sheerka/services/SheerkaComparisonManager.py b/src/core/sheerka/services/SheerkaComparisonManager.py index 7453671..99c1ff7 100644 --- a/src/core/sheerka/services/SheerkaComparisonManager.py +++ b/src/core/sheerka/services/SheerkaComparisonManager.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from cache.Cache import Cache from cache.ListCache import ListCache from core.builtin_concepts import BuiltinConcepts -from core.concept import ensure_concept +from core.concept import ensure_concept, Concept from core.sheerka.services.sheerka_service import ServiceObj, BaseService @@ -27,15 +27,30 @@ class SheerkaComparisonManager(BaseService): COMPARISON_ENTRY = "ComparisonManager:Comparison" RESOLVED_COMPARISON_ENTRY = "ComparisonManager:Resolved_Comparison" + # to use an initialisation value for attributes that will make use of computed weights + # the lesser and the greatest weight will be given relatively to this value + DEFAULT_COMPARISON_VALUE = 1 + def __init__(self, sheerka): super().__init__(sheerka) @staticmethod def _compute_key(prop_name, comparison_context): - return f"{prop_name}|{comparison_context}" + """ + Key to use to store the comparisons + :param prop_name: + :param comparison_context: + :return: + """ + if isinstance(prop_name, Concept): + prefix = prop_name.key if prop_name.metadata.is_builtin else prop_name.id + else: + prefix = prop_name + + return f"{prefix}|{comparison_context}" @staticmethod - def _compute_weights(comparison_objs): + def _get_weights(comparison_objs): """ For every element in greater_than_s, give it a weight if weight(a) > weight(b) it means that a > b @@ -45,8 +60,8 @@ class SheerkaComparisonManager(BaseService): values = {} for comparison_obj in comparison_objs: - values[comparison_obj.a] = 1 - values[comparison_obj.b] = 1 + values[comparison_obj.a] = SheerkaComparisonManager.DEFAULT_COMPARISON_VALUE + values[comparison_obj.b] = SheerkaComparisonManager.DEFAULT_COMPARISON_VALUE for _ in range(len(comparison_objs)): for comparison_obj in comparison_objs: @@ -57,6 +72,54 @@ class SheerkaComparisonManager(BaseService): return values + def _compute_weights(self, comparison_objs, lesser_objs_ids=None, greatest_objs_ids=None): + """ + + :param comparison_objs: + :return: + """ + + def is_not_in_objs(obj, objs_ids): + return obj.op in ("<", ">") and obj.a not in objs_ids and obj.b not in objs_ids + + def is_in_objs(obj, objs_ids): + return obj.op in ("<", ">") and (obj.a in objs_ids or obj.b in objs_ids) + + lesser_objs_ids = lesser_objs_ids or {co.a for co in comparison_objs if co.op == "<<"} + greatest_objs_ids = greatest_objs_ids or {co.a for co in comparison_objs if co.op == ">>"} + + default_weight = SheerkaComparisonManager.DEFAULT_COMPARISON_VALUE + + # get the weights for all the lesser + lesser_objs = [co for co in comparison_objs if is_in_objs(co, lesser_objs_ids)] + lesser_objs_weights = self._get_weights(lesser_objs) + + # rearrange the weight to have the highest equals to DEFAULT_COMPARISON_VALUE - 1 + highest_weight = len(lesser_objs_weights) + for co_id in lesser_objs_weights: + lesser_objs_weights[co_id] = lesser_objs_weights[co_id] - highest_weight + default_weight - 1 + for concept_id in lesser_objs_ids: + if concept_id not in lesser_objs_weights: + lesser_objs_weights[concept_id] = default_weight - 1 + + # get the weights for concepts that are not lesser or greatest + in_between_objs = [o for o in comparison_objs if is_not_in_objs(o, lesser_objs_ids | greatest_objs_ids)] + in_between_weights = self._get_weights(in_between_objs) + + # get the weights for all the greatest + greatest_objs = [co for co in comparison_objs if is_in_objs(co, greatest_objs_ids)] + greatest_objs_weights = self._get_weights(greatest_objs) + + # rearrange the weight to have the lowest equals to DEFAULT_COMPARISON_VALUE + 1 + highest_weight = max(default_weight, len(in_between_weights)) + for co_id in greatest_objs_weights: + greatest_objs_weights[co_id] = greatest_objs_weights[co_id] + highest_weight + for concept_id in greatest_objs_ids: + if concept_id not in greatest_objs_weights: + greatest_objs_weights[concept_id] = highest_weight + 1 + + return {**lesser_objs_weights, **in_between_weights, **greatest_objs_weights} + @staticmethod def _get_partition(weighted_concepts): @@ -65,21 +128,52 @@ class SheerkaComparisonManager(BaseService): res.setdefault(v, []).append(k) return res - def _inner_add_comparison(self, comparison_obj): + def _add_comparison(self, comparison_obj): key = self._compute_key(comparison_obj.property, comparison_obj.context) previous = self.sheerka.cache_manager.get(self.COMPARISON_ENTRY, key) - new = previous.copy() if previous else [] + + for co in new: + if co.property == comparison_obj.property and \ + co.a == comparison_obj.a and \ + co.b == comparison_obj.b and \ + co.op == comparison_obj.op and \ + co.context == comparison_obj.context: + return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.ALREADY_DEFINED)) + new.append(comparison_obj) + lesser_objs_ids = {co.a for co in new if co.op == "<<"} + greatest_objs_ids = {co.a for co in new if co.op == ">>"} + + # check if it is a valid operation regarding other lesser and greatest concepts + if comparison_obj.op in ("<", ">"): + a_is_lesser = comparison_obj.a in lesser_objs_ids + b_is_lesser = comparison_obj.b in lesser_objs_ids + if a_is_lesser != b_is_lesser: # XOR operation + return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.INVALID_LESSER_OPERATION)) + + a_is_greatest = comparison_obj.a in greatest_objs_ids + b_is_greatest = comparison_obj.b in greatest_objs_ids + if a_is_greatest != b_is_greatest: # XOR operation + return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.INVALID_GREATEST_OPERATION)) + + if comparison_obj.op == "<<" and comparison_obj.a in greatest_objs_ids: + return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.INVALID_GREATEST_OPERATION)) + + if comparison_obj.op == ">>" and comparison_obj.a in lesser_objs_ids: + return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.INVALID_LESSER_OPERATION)) + cycles = self.detect_cycles(new) if cycles: concepts_in_cycle = [self.sheerka.get_by_id(c) for c in cycles] chicken_an_egg = self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_in_cycle) return self.sheerka.ret(self.NAME, False, chicken_an_egg) - self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, self._compute_weights(new)) self.sheerka.cache_manager.put(self.COMPARISON_ENTRY, key, comparison_obj) + self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, self._compute_weights(new, + lesser_objs_ids, + greatest_objs_ids)) return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS)) @@ -92,6 +186,8 @@ class SheerkaComparisonManager(BaseService): 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_concepts_weights, False) @@ -110,7 +206,7 @@ class SheerkaComparisonManager(BaseService): event_digest = context.event.get_digest() comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, ">", comparison_context) - return self._inner_add_comparison(comparison_obj) + return self._add_comparison(comparison_obj) def set_is_less_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"): """ @@ -127,7 +223,66 @@ class SheerkaComparisonManager(BaseService): event_digest = context.event.get_digest() comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, "<", comparison_context) - return self._inner_add_comparison(comparison_obj) + return self._add_comparison(comparison_obj) + + def set_is_lesser(self, context, prop_name, concept, comparison_context="#"): + """ + Records that the concept is less than any other concept if no direct comparison is given + + * A lesser concept has a weight smaller than any other concept that is not a lesser + * When two concepts are lesser, you can compare (using set_is_less_than or set_is_greater_than) + * If a concept is lesser, you cannot compare it with a non lesser concept + * All lesser concepts that have no comparison directive are greater than the others (and share the same weight) + :param context: + :param prop_name: + :param concept: + :param comparison_context: + :return: + """ + context.log(f"Setting concept {concept} is lesser", who=self.NAME) + ensure_concept(concept) + + event_digest = context.event.get_digest() + comparison_obj = ComparisonObj(event_digest, prop_name, concept.id, None, "<<", comparison_context) + return self._add_comparison(comparison_obj) + + def set_is_greatest(self, context, prop_name, concept, comparison_context="#"): + """ + Records that the concept is greater than any other concept if no direct comparison is given + + * A greatest concept has a weight bigger than any other concept that is not a greatest + * When two concepts are greatest, you can compare them (using set_is_less_than or set_is_greater_than) + * If a concept is greatest, you cannot compare it with a non greatest concept + * All greatest concepts that have no comparison directive are less than the others (and share the same weight) + :param context: + :param prop_name: + :param concept: + :param comparison_context: + :return: + """ + context.log(f"Setting concept {concept} is greatest", who=self.NAME) + ensure_concept(concept) + + event_digest = context.event.get_digest() + comparison_obj = ComparisonObj(event_digest, prop_name, concept.id, None, ">>", comparison_context) + return self._add_comparison(comparison_obj) + + def set_are_equivalent(self, context, prop_name, concept_a, concept_b, comparison_context="#"): + """ + Records that two concepts have the same weight + + * You cannot set the weight + :param context: + :param prop_name: + :param concept_a: + :param concept_b: + :param comparison_context: + :return: + """ + pass + + def set_are_equiv(self, context, prop_name, concept_a, concept_b, comparison_context="#"): + pass def get_partition(self, prop_name, comparison_context="#"): """ @@ -141,21 +296,21 @@ class SheerkaComparisonManager(BaseService): return self._get_partition(weighted_concept) def get_concepts_weights(self, prop_name, comparison_context="#"): - weighted_concept = self.sheerka.cache_manager.get( + weighted_concepts = self.sheerka.cache_manager.get( self.RESOLVED_COMPARISON_ENTRY, self._compute_key(prop_name, comparison_context)) - if weighted_concept is None: + if weighted_concepts is None: key = self._compute_key(prop_name, comparison_context) entries = self.sheerka.cache_manager.get(self.COMPARISON_ENTRY, key) if entries is None: return {} else: - weighted_concept = self._compute_weights(entries) - self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, weighted_concept) + weighted_concepts = self._compute_weights(entries) + self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, weighted_concepts) - return weighted_concept + return weighted_concepts @staticmethod def detect_cycles(comparison_objs): diff --git a/src/core/sheerka/services/SheerkaCreateNewConcept.py b/src/core/sheerka/services/SheerkaCreateNewConcept.py index 30dc820..fcfd74b 100644 --- a/src/core/sheerka/services/SheerkaCreateNewConcept.py +++ b/src/core/sheerka/services/SheerkaCreateNewConcept.py @@ -45,7 +45,7 @@ class SheerkaCreateNewConcept(BaseService): return sheerka.ret( self.NAME, False, - sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_DEFINED, body=concept), + sheerka.new(BuiltinConcepts.ALREADY_DEFINED, body=concept), error.args[0]) # set id before saving in db diff --git a/src/core/sheerka/services/SheerkaModifyConcept.py b/src/core/sheerka/services/SheerkaModifyConcept.py index 43f0fba..d85ed37 100644 --- a/src/core/sheerka/services/SheerkaModifyConcept.py +++ b/src/core/sheerka/services/SheerkaModifyConcept.py @@ -34,7 +34,7 @@ class SheerkaModifyConcept(BaseService): return self.sheerka.ret( self.NAME, False, self.sheerka.new( - BuiltinConcepts.CONCEPT_ALREADY_DEFINED, + BuiltinConcepts.ALREADY_DEFINED, body=concept)) old_references = self.sheerka.cache_manager.get(self.sheerka.CONCEPTS_REFERENCES_ENTRY, concept.id) diff --git a/src/evaluators/PythonEvaluator.py b/src/evaluators/PythonEvaluator.py index 98325a3..837bec7 100644 --- a/src/evaluators/PythonEvaluator.py +++ b/src/evaluators/PythonEvaluator.py @@ -37,6 +37,8 @@ class Expando: for k, v in bag.items(): setattr(self, k, v) + def __repr__(self): + return f"{dir(self)}" @dataclass class PythonEvalError: diff --git a/src/parsers/SyaNodeParser.py b/src/parsers/SyaNodeParser.py index 2a37e9f..daf7530 100644 --- a/src/parsers/SyaNodeParser.py +++ b/src/parsers/SyaNodeParser.py @@ -6,6 +6,7 @@ from typing import List from core import builtin_helpers from core.builtin_concepts import BuiltinConcepts from core.concept import Concept, DEFINITION_TYPE_BNF +from core.sheerka.services.SheerkaComparisonManager import SheerkaComparisonManager from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import Token, TokenKind, Tokenizer from parsers.BaseNodeParser import UnrecognizedTokensNode, ConceptNode, SourceCodeNode, SyaAssociativity, \ @@ -77,7 +78,7 @@ class SyaConceptDef: It gives the precedence and the associativity for the concept """ concept: Concept - precedence: int = 0 + precedence: int = SheerkaComparisonManager.DEFAULT_COMPARISON_VALUE associativity: SyaAssociativity = SyaAssociativity.Right @@ -541,11 +542,6 @@ class InFixToPostFix: if stack.associativity == SyaAssociativity.No and current.associativity == SyaAssociativity.No: self._add_error(NoneAssociativeSequenceErrorNode(current.concept, stack_head.start, concept_node.start)) - if not current.precedence: - # precedence is not set (None or zero) - # Do not apply any rule - return False - if current.associativity == SyaAssociativity.Left and current.precedence <= stack.precedence: return True @@ -947,6 +943,7 @@ class SyaNodeParser(BaseNodeParser): def _get_sya_concept_def(parser, concept): sya_concept_def = SyaConceptDef(concept) if concept.id in parser.sya_definitions: + # Manage when precedence and associativity are given in the unit tests sya_def = parser.sya_definitions.get(concept.id) if sya_def[0] is not None: sya_concept_def.precedence = sya_def[0] diff --git a/tests/core/test_SheerkaComparisonManager.py b/tests/core/test_SheerkaComparisonManager.py index fbc38cd..3af7a9e 100644 --- a/tests/core/test_SheerkaComparisonManager.py +++ b/tests/core/test_SheerkaComparisonManager.py @@ -6,6 +6,22 @@ from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka class TestSheerkaGreaterThanManager(TestUsingMemoryBasedSheerka): + + @staticmethod + def execution_definition(context, service, concepts_map, definition): + if ">>" in definition: + a = concepts_map[definition.split(">>")[0].strip()] + return service.set_is_greatest(context, "prop_name", a) + if "<<" in definition: + a = concepts_map[definition.split("<<")[0].strip()] + return service.set_is_lesser(context, "prop_name", a) + if ">" in definition: + a, b = [concepts_map[e.strip()] for e in definition.split(">")] + return service.set_is_greater_than(context, "prop_name", a, b) + if "<" in definition: + a, b = [concepts_map[e.strip()] for e in definition.split("<")] + return service.set_is_less_than(context, "prop_name", a, b) + def test_i_can_add_a_is_greater_than(self): sheerka, context, one, two = self.init_concepts("one", "two", cache_only=False) service = sheerka.services[SheerkaComparisonManager.NAME] @@ -83,22 +99,34 @@ class TestSheerkaGreaterThanManager(TestUsingMemoryBasedSheerka): (["one < two"], {'1001': 1, '1002': 2}), (["three > two", "one < two"], {'1001': 1, '1002': 2, '1003': 3}), (["three > one", "one < two"], {'1001': 1, '1002': 2, '1003': 2}), + (["three >>"], {'1003': 2}), + (["one <<"], {'1001': 0}), ]) def test_i_can_get_concept_weight(self, entries, expected): sheerka, context, *concepts = self.init_concepts("one", "two", "three") service = sheerka.services[SheerkaComparisonManager.NAME] - concepts_map = dict(zip(["one", "two", "three", "four", "five", "six"], concepts)) + concepts_map = dict(zip(["one", "two", "three"], concepts)) for entry in entries: - if ">" in entry: - a, b = [concepts_map[e.strip()] for e in entry.split(">")] - service.set_is_greater_than(context, "prop_name", a, b) - else: - a, b = [concepts_map[e.strip()] for e in entry.split("<")] - service.set_is_less_than(context, "prop_name", a, b) + self.execution_definition(context, service, concepts_map, entry) assert service.get_concepts_weights("prop_name") == expected + def test_i_can_get_concept_weight_when_no_comparison_is_defined(self): + sheerka, context = self.init_concepts() + service = sheerka.services[SheerkaComparisonManager.NAME] + + assert service.get_concepts_weights("prop_name") == {} + + def test_i_can_recover_from_deleted_weight(self): + sheerka, context, one = self.init_concepts("one") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_lesser(context, "prop_name", one) + sheerka.cache_manager.clear(service.RESOLVED_COMPARISON_ENTRY) + + assert service.get_concepts_weights("prop_name") == {"1001": 0} + def test_i_can_get_partition(self): sheerka, context, one, two, three = self.init_concepts("one", "two", "three") service = sheerka.services[SheerkaComparisonManager.NAME] @@ -134,9 +162,6 @@ class TestSheerkaGreaterThanManager(TestUsingMemoryBasedSheerka): service.set_is_greater_than(context, "prop_name", four, three) service.set_is_greater_than(context, "prop_name", five, two) - res = service.set_is_greater_than(context, "prop_name", two, one) - assert res.status - res = service.set_is_greater_than(context, "prop_name", one, five) assert not res.status @@ -157,3 +182,156 @@ class TestSheerkaGreaterThanManager(TestUsingMemoryBasedSheerka): sheerka, context, one, two = self.init_concepts("one", "two") res = sheerka.set_is_greater_than(context, "prop_name", two, one) assert res.status + + def test_a_lesser_concept_has_the_lowest_weight(self): + sheerka, context, one, two, three = self.init_concepts("one", "two", "three", cache_only=False) + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_lesser(context, "prop_name", one) + assert service.get_concepts_weights("prop_name") == {"1001": 0} # DEFAULT_COMPARISON_VALUE - 1 + + sheerka.set_is_greater_than(context, "prop_name", three, two) + assert service.get_concepts_weights("prop_name") == {"1001": 0, "1002": 1, "1003": 2} + + # I can commit + sheerka.cache_manager.commit(context) + in_db = sheerka.sdp.get(SheerkaComparisonManager.COMPARISON_ENTRY, "prop_name|#") + assert in_db == [ + ComparisonObj(context.event.get_digest(), "prop_name", one.id, None, "<<", "#"), + ComparisonObj(context.event.get_digest(), "prop_name", three.id, two.id, ">", "#") + ] + + def test_i_can_define_an_order_for_lesser_concepts(self): + sheerka, context, lesser, less_l, even_more_l = self.init_concepts("lesser", "less_l", "even_less_l") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_lesser(context, "prop_name", lesser) + service.set_is_lesser(context, "prop_name", less_l) + service.set_is_lesser(context, "prop_name", even_more_l) + + sheerka.set_is_less_than(context, "prop_name", less_l, lesser) + sheerka.set_is_greater_than(context, "prop_name", less_l, even_more_l) + assert service.get_concepts_weights("prop_name") == {"1001": 0, "1002": -1, "1003": -2} + + def test_i_cannot_define_less_than_a_lesser_if_not_a_lesser_itself(self): + """ + minus_one cannot be defined as less that one is one is defined as lesser + unless minus_one is defined as lesser itself + :return: + """ + sheerka, context, lesser, foo = self.init_concepts("lesser", "foo") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_lesser(context, "prop_name", lesser) + + res = sheerka.set_is_less_than(context, "prop_name", foo, lesser) + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.INVALID_LESSER_OPERATION) + + res = sheerka.set_is_greater_than(context, "prop_name", foo, lesser) + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.INVALID_LESSER_OPERATION) + + def test_a_greatest_concept_has_the_highest_weight(self): + sheerka, context, one, two, three = self.init_concepts("one", "two", "three") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_greatest(context, "prop_name", three) + assert service.get_concepts_weights("prop_name") == {"1003": 2} # DEFAULT_COMPARISON_VALUE + 1 + + sheerka.set_is_greater_than(context, "prop_name", two, one) + assert service.get_concepts_weights("prop_name") == {"1001": 1, "1002": 2, "1003": 3} + + def test_i_cannot_define_greater_than_a_greatest_if_not_a_greater_itself(self): + sheerka, context, greatest, foo = self.init_concepts("greatest", "foo") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_greatest(context, "prop_name", greatest) + + res = sheerka.set_is_less_than(context, "prop_name", foo, greatest) + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.INVALID_GREATEST_OPERATION) + + res = sheerka.set_is_greater_than(context, "prop_name", foo, greatest) + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.INVALID_GREATEST_OPERATION) + + @pytest.mark.parametrize("definitions, expected", [ + (["foo >>", "foo <<"], BuiltinConcepts.INVALID_GREATEST_OPERATION), + (["foo <<", "foo >>"], BuiltinConcepts.INVALID_LESSER_OPERATION), + ]) + def test_i_cannot_define_a_concept_as_lesser_and_greatest_at_the_same_time(self, definitions, expected): + sheerka, context, foo = self.init_concepts("foo") + service = sheerka.services[SheerkaComparisonManager.NAME] + concepts_map = {"foo": foo} + + self.execution_definition(context, service, concepts_map, definitions[0]) + res = self.execution_definition(context, service, concepts_map, definitions[1]) + assert not res.status + assert sheerka.isinstance(res.body, expected) + + def test_i_can_define_an_order_for_greatest_concepts(self): + sheerka, context, greatest, more_g, even_more_g = self.init_concepts("greatest", "more_g", "even_more_g") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_greatest(context, "prop_name", greatest) + service.set_is_greatest(context, "prop_name", more_g) + service.set_is_greatest(context, "prop_name", even_more_g) + + sheerka.set_is_less_than(context, "prop_name", greatest, more_g) + sheerka.set_is_greater_than(context, "prop_name", even_more_g, more_g) + assert service.get_concepts_weights("prop_name") == {"1001": 2, "1002": 3, "1003": 4} + + def test_i_can_mix_all_comparisons(self): + sheerka, context, one, two, three, four, five, six, three_and_half = self.init_concepts( + "one", "two", "three", "four", "five", "six", "two_and_half") + service = sheerka.services[SheerkaComparisonManager.NAME] + + service.set_is_lesser(context, "prop_name", one) + service.set_is_lesser(context, "prop_name", two) + sheerka.set_is_less_than(context, "prop_name", one, two) + + service.set_is_greatest(context, "prop_name", five) + service.set_is_greatest(context, "prop_name", six) + sheerka.set_is_greater_than(context, "prop_name", six, five) + + sheerka.set_is_less_than(context, "prop_name", three, four) + + assert service.get_concepts_weights("prop_name") == { + "1001": -1, + "1002": 0, + "1003": 1, + "1004": 2, + "1005": 3, + "1006": 4 + } + + sheerka.set_is_less_than(context, "prop_name", three_and_half, four) + sheerka.set_is_greater_than(context, "prop_name", three_and_half, three) + + assert service.get_concepts_weights("prop_name") == { + "1001": -1, + "1002": 0, + "1003": 1, + "1007": 2, + "1004": 3, + "1005": 4, + "1006": 5 + } + + @pytest.mark.parametrize("definition", [ + "one >>", + "one <<", + "one > two", + "one < two", + ]) + def test_i_cannot_add_the_same_definition_twice(self, definition): + sheerka, context, *concepts = self.init_concepts("one", "two") + service = sheerka.services[SheerkaComparisonManager.NAME] + concepts_map = dict(zip(["one", "two"], concepts)) + + self.execution_definition(context, service, concepts_map, definition) + res = self.execution_definition(context, service, concepts_map, definition) + + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.ALREADY_DEFINED) diff --git a/tests/core/test_SheerkaCreateNewConcept.py b/tests/core/test_SheerkaCreateNewConcept.py index 8931c8f..c829a52 100644 --- a/tests/core/test_SheerkaCreateNewConcept.py +++ b/tests/core/test_SheerkaCreateNewConcept.py @@ -99,7 +99,7 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka): res = sheerka.create_new_concept(self.get_context(sheerka), concept) assert not res.status - assert sheerka.isinstance(res.value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED) + assert sheerka.isinstance(res.value, BuiltinConcepts.ALREADY_DEFINED) assert res.value.body == concept def test_i_can_get_a_newly_created_concept(self): @@ -244,7 +244,7 @@ class TestSheerkaCreateNewConceptFileBased(TestUsingFileBasedSheerka): res = sheerka.create_new_concept(context, concept) assert not res.status - assert sheerka.isinstance(res.value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED) + assert sheerka.isinstance(res.value, BuiltinConcepts.ALREADY_DEFINED) assert res.value.body == concept def test_new_entry_does_not_override_the_previous_ones(self): diff --git a/tests/core/test_SheerkaModifyConcept.py b/tests/core/test_SheerkaModifyConcept.py index d95a673..2411c29 100644 --- a/tests/core/test_SheerkaModifyConcept.py +++ b/tests/core/test_SheerkaModifyConcept.py @@ -73,7 +73,7 @@ class TestSheerkaModifyConcept(TestUsingMemoryBasedSheerka): res = sheerka.modify_concept(context, foo) assert not res.status - assert sheerka.isinstance(res.body, BuiltinConcepts.CONCEPT_ALREADY_DEFINED) + assert sheerka.isinstance(res.body, BuiltinConcepts.ALREADY_DEFINED) def test_i_can_modify_a_concept_that_is_in_a_list(self): sheerka, context, foo1, foo2 = self.init_concepts( diff --git a/tests/non_reg/test_sheerka_non_reg.py b/tests/non_reg/test_sheerka_non_reg.py index dc9a7b2..2b6a2be 100644 --- a/tests/non_reg/test_sheerka_non_reg.py +++ b/tests/non_reg/test_sheerka_non_reg.py @@ -167,7 +167,7 @@ as: assert len(res) == 1 assert not res[0].status - assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED) + assert sheerka.isinstance(res[0].value, BuiltinConcepts.ALREADY_DEFINED) @pytest.mark.parametrize("text", [ "", @@ -891,16 +891,17 @@ as: assert sheerka.get_concepts_weights("some_prop") == {'1001': 1, '1002': 2, '1003': 3, '1004': 4} def test_i_can_evaluate_expression_when_using_token_concept(self): - sheerka, context, one, two, plus = self.init_concepts( + sheerka, context, one, two, three, is_less_than = self.init_concepts( Concept("one", body="1"), Concept("two", body="2"), + Concept("three", body="3"), self.from_def_concept("<", "a < b", ["a", "b"], body="set_is_less_than('some_prop', a, b)") ) expression = "c:one: < c:two:" res = sheerka.evaluate_user_input(expression) assert res[0].status - assert res[0].body == CMV(plus, a="c:one:", b="c:two:") + assert res[0].body == CMV(is_less_than, a="c:one:", b="c:two:") assert res[0].body.a == NotInit # concept is not evaluated assert res[0].body.b == NotInit # concept is not evaluated @@ -911,11 +912,11 @@ as: assert sheerka.get_concepts_weights("some_prop") == {'1001': 1, '1002': 2} # it now also works using the concepts names - expression = "eval one < two" + expression = "eval two < three" res = sheerka.evaluate_user_input(expression) assert res[0].status assert sheerka.isinstance(res[0].body, BuiltinConcepts.SUCCESS) - assert sheerka.get_concepts_weights("some_prop") == {'1001': 1, '1002': 2} + assert sheerka.get_concepts_weights("some_prop") == {'1001': 1, '1002': 2, '1003': 3} def test_i_can_detect_multiple_errors_when_evaluating_a_concept(self): sheerka, context, foo, plus_one = self.init_concepts(