From a683d4cd42264e8ce78561db7eeb957a647ea828 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sat, 21 Dec 2019 22:02:07 +0100 Subject: [PATCH] Added concept 'isa' other_concept functionality --- core/builtin_concepts.py | 23 +++++++ core/sheerka.py | 36 +++++++++- core/tokenizer.py | 1 + docs/blog.rst | 62 ++++++++++++++++- evaluators/AddConceptEvaluator.py | 2 +- evaluators/AddConceptInSetEvaluator.py | 70 +++++++++++++++++++ parsers/DefaultParser.py | 37 +++++++++- sdp/sheerkaDataProvider.py | 7 +- tests/test_AddConceptInSetEvaluator.py | 94 ++++++++++++++++++++++++++ tests/test_DefaultParser.py | 77 ++++++++++++++------- tests/test_sheerka.py | 70 ++++++++++++++++++- tests/test_sheerkaDataProvider.py | 15 ++-- tests/test_sheerka_non_reg.py | 56 ++++++++------- 13 files changed, 489 insertions(+), 61 deletions(-) create mode 100644 evaluators/AddConceptInSetEvaluator.py create mode 100644 tests/test_AddConceptInSetEvaluator.py diff --git a/core/builtin_concepts.py b/core/builtin_concepts.py index 1abe77c..73a9edf 100644 --- a/core/builtin_concepts.py +++ b/core/builtin_concepts.py @@ -45,6 +45,7 @@ class BuiltinConcepts(Enum): ENUMERATION = "enum" # represents a list or a set LIST = "list" # represents a list CANNOT_RESOLVE_VALUE_ERROR = "value cannot be resolved" # don't know how to find concept value + CONCEPT_ALREADY_IN_SET = "concept already in set" NODE = "node" GENERIC_NODE = "generic node" @@ -68,6 +69,7 @@ BuiltinErrors = [str(e) for e in { BuiltinConcepts.CONCEPT_ALREADY_DEFINED, BuiltinConcepts.CONCEPT_EVAL_ERROR, BuiltinConcepts.CANNOT_RESOLVE_VALUE_ERROR, + BuiltinConcepts.CONCEPT_ALREADY_IN_SET, }] """ @@ -320,3 +322,24 @@ class ListConcept(Concept): def __contains__(self, item): return item in self.body + + +class ConceptAlreadyInSet(Concept): + def __init__(self, concept=None, concept_set=None): + super().__init__(BuiltinConcepts.CONCEPT_ALREADY_IN_SET, + True, + False, + BuiltinConcepts.CONCEPT_ALREADY_IN_SET, + concept) + self.set_prop("concept_set", concept_set) + + def __repr__(self): + return f"ConceptAlreadyInSet(concept={self.concept}, concept_set={self.concept_set})" + + @property + def concept(self): + return self.body + + @property + def concept_set(self): + return self.props["concept_set"].value diff --git a/core/sheerka.py b/core/sheerka.py index 572d53e..7e3cac3 100644 --- a/core/sheerka.py +++ b/core/sheerka.py @@ -15,6 +15,7 @@ CONCEPT_EVALUATION_STEPS = [ BuiltinConcepts.BEFORE_EVALUATION, BuiltinConcepts.EVALUATION, BuiltinConcepts.AFTER_EVALUATION] + CONCEPT_LEXER_PARSER_CLASS = "parsers.ConceptLexerParser.ConceptLexerParser" DEBUG_TAB_SIZE = 4 @@ -353,14 +354,17 @@ class Sheerka(Concept): obj.metadata.id = self.sdp.get_next_key(self.BUILTIN_CONCEPTS_KEYS if is_builtin else self.USER_CONCEPTS_KEYS) self.log.debug(f"Setting id '{obj.metadata.id}' to concept '{obj.metadata.name}'.") - def create_new_concept(self, context, concept: Concept): + def create_new_concept(self, context, concept: Concept, logger=None): """ Adds a new concept to the system :param context: :param concept: DefConceptNode + :param logger :return: digest of the new concept """ + logger = logger or self.log + concept.init_key() concepts_definitions = None init_ret_value = None @@ -393,6 +397,7 @@ class Sheerka(Concept): if concepts_definitions is not None: self.sdp.set(context.event_digest, self.CONCEPTS_DEFINITIONS_ENTRY, concepts_definitions, use_ref=True) except SheerkaDataProviderDuplicateKeyError as error: + context.log_error(logger, "Failed to create a new concept.", who=self.create_new_concept.__name__) return self.ret(self.create_new_concept.__name__, False, ErrorConcept(error), error.args[0]) # Updates the caches @@ -404,6 +409,35 @@ class Sheerka(Concept): ret = self.ret(self.create_new_concept.__name__, True, self.new(BuiltinConcepts.NEW_CONCEPT, body=concept)) return ret + def add_concept_to_set(self, context, concept, concept_set, logger=None): + """ + Add an entry in sdp to tell that concept isa concept_set + :param context: + :param concept: + :param concept_set: + :param logger: + :return: + """ + logger = logger or self.log + + context.log(logger, f"Adding concept {concept} to set {concept_set}", who=self.add_concept_to_set.__name__) + + assert concept.id + assert concept_set.id + + try: + ret = self.sdp.add_unique(context.event_digest, "All_" + str(concept_set.id), concept.id) + if ret == (None, None): # concept already in set + return self.ret( + self.add_concept_to_set.__name__, + False, + self.new(BuiltinConcepts.CONCEPT_ALREADY_IN_SET, body=concept, concept_set=concept_set)) + else: + return self.ret(self.add_concept_to_set.__name__, True, self.new(BuiltinConcepts.SUCCESS)) + except Exception as error: + context.log_error(logger, "Failed to add to set.", who=self.add_concept_to_set.__name__) + return self.ret(self.create_new_concept.__name__, False, ErrorConcept(error), error.args[0]) + def initialize_concept_asts(self, context, concept: Concept, logger=None): """ Updates the codes of the newly created concept diff --git a/core/tokenizer.py b/core/tokenizer.py index e1eeed5..8f292e0 100644 --- a/core/tokenizer.py +++ b/core/tokenizer.py @@ -86,6 +86,7 @@ class Keywords(Enum): WHERE = "where" PRE = "pre" POST = "post" + ISA = "isa" class Tokenizer: diff --git a/docs/blog.rst b/docs/blog.rst index 387e581..7354c2e 100644 --- a/docs/blog.rst +++ b/docs/blog.rst @@ -81,8 +81,8 @@ So, you could call the concept by They will produce the strings "hello kodjo" or "hello my friend" -About versioning -"""""""""""""""" +About versionning of the information +""""""""""""""""""""""""""""""""""""" As I said previously, I mimic how git_ versions its objects. :: @@ -597,4 +597,60 @@ Like in regular expressions, you will also find For those who doesn't know that BNF stands for, please have a look at the bnf_ wikipedia page. -I guess that I will need a complete chapter to explain how you retrieve what was parsed \ No newline at end of file +I guess that I will need a complete chapter to explain how you retrieve what was parsed + +2019-12-21 +********** + +Implementing Inheritance +"""""""""""""""""""""""" + +Except that it is not inheritance, at least the way it is seen in modern programing languages. + +I think that I should first express what I am trying to do. I guess that it will help me +have a better understanding myself. + +:: + + def concept one as 1 + def concept two as 2 + one is a number + two is a number + +When I enter :code:`one`, the result should be :code:`1` + +But I should be able to express other concepts by using + +:: + + def concept a plus b where a is a number and b is a number as a + b + +Just by reading what I have just written, we can see that 'is a' has two separate meanings. +In the first usage, it's an affirmation, in the latter one, it's a question. + +Should we consider them as the same concept, with two usages, or as two separate concepts, +which are somehow linked ? + +As of now, there is only one usage to all concepts, which is the property 'BODY', but I have +prepared the property 'PRE' which can be used for that. + +I am a little bit making a digression. The original subject was on how I can express that a +concept is an element of another concept. We may focus on the implementation later. + +So saying that 'one' is a 'number' means that there is a set called 'number' +in which 'one' belong. + +The simple implementation will be to create an entry 'all_number' in sdp and to add 'one' in it. +The two issue that I foresee are: + +* What about infinite sets ? (my set 'number' can never be completed if I put the item one by one) +* What if the same name refers to different set (I don't have any example in mind, but I guess that synonyms of sets do exist) + + +For the two questions, I will first try the simple implementations and see there I go from there. ie : + +* on the top of the entry all_numbers which lists the known numbers, you can define concepts :code:`is a number` + that can be also used to detect the the concept is part of the set +* the entry in sdp will not be all_number, but all_id_of_number. I will use the concept id instead of its name + + diff --git a/evaluators/AddConceptEvaluator.py b/evaluators/AddConceptEvaluator.py index 044a60d..0ba0140 100644 --- a/evaluators/AddConceptEvaluator.py +++ b/evaluators/AddConceptEvaluator.py @@ -87,7 +87,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator): sheerka.is_success(def_concept_node.definition): concept.bnf = def_concept_node.definition.value.value - ret = sheerka.create_new_concept(context, concept) + ret = sheerka.create_new_concept(context, concept, self.verbose_log) if not ret.status: error_cause = sheerka.value(ret.body) context.log(self.log, f"Failed to add concept '{concept.name}'. Reason: {error_cause}", self.name) diff --git a/evaluators/AddConceptInSetEvaluator.py b/evaluators/AddConceptInSetEvaluator.py new file mode 100644 index 0000000..792de67 --- /dev/null +++ b/evaluators/AddConceptInSetEvaluator.py @@ -0,0 +1,70 @@ +import core.builtin_helpers +from core.builtin_concepts import ParserResultConcept, BuiltinConcepts +from evaluators.BaseEvaluator import OneReturnValueEvaluator +from parsers.DefaultParser import IsaConceptNode + +ALL_STEPS = [ + BuiltinConcepts.BEFORE_PARSING, + BuiltinConcepts.PARSING, + BuiltinConcepts.EVALUATION, + BuiltinConcepts.AFTER_EVALUATION +] + + +class AddConceptInSetEvaluator(OneReturnValueEvaluator): + """ + Tells that a concept is a part of a set + """ + NAME = "AddConceptInSet" + + def __init__(self): + super().__init__(self.NAME, 50) + + def matches(self, context, return_value): + return return_value.status and \ + isinstance(return_value.value, ParserResultConcept) and \ + isinstance(return_value.value.value, IsaConceptNode) + + def eval(self, context, return_value): + + def _resolve(name_node): + ret_val = sheerka.ret( + self.name, + True, + sheerka.new(BuiltinConcepts.USER_INPUT, body=name_node.tokens, user_name="N/A")) + sub_context = context.push(desc=f"Recognizing '{name_node}'") + r = sheerka.execute(sub_context, ret_val, ALL_STEPS, self.verbose_log) + return core.builtin_helpers.expect_one(context, r) + + isa_node = return_value.value.value + sheerka = context.sheerka + context.log(self.log, f"Adding a concept {isa_node.concept} to set {isa_node.set}", self.name) + + # Try to recognize the concept + res = _resolve(isa_node.concept) + if not res.status: + return sheerka.ret( + self.name, + False, + sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=str(isa_node.concept)), + parents=[return_value]) + concept = res.value + + res = _resolve(isa_node.set) + if not res.status: + return sheerka.ret( + self.name, + False, + sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=str(isa_node.set)), + parents=[return_value]) + concept_set = res.value + + res = sheerka.add_concept_to_set(context, concept, concept_set, self.verbose_log) + if not res.status: + context.log(self.log, f"Failed. Reason: {sheerka.value(res.body)}.", self.name) + else: + context.log(self.log, f"Concept added.", self.name) + + return res + + diff --git a/parsers/DefaultParser.py b/parsers/DefaultParser.py index 9145b95..8e8e0ff 100644 --- a/parsers/DefaultParser.py +++ b/parsers/DefaultParser.py @@ -96,6 +96,12 @@ class DefConceptNode(DefaultParserNode): return asts +@dataclass() +class IsaConceptNode(DefaultParserNode): + concept: NameNode = NotInitializedNode() + set: NameNode = NotInitializedNode() + + class DefaultParser(BaseParser): """ Parse sheerka specific grammar (like def concept) @@ -220,7 +226,7 @@ class DefaultParser(BaseParser): self.context.log(self.verbose_log, "Keyword DEF found.", self.name) return self.parse_def_concept(token) else: - return self.add_error(CannotHandleErrorNode([], self.text)) + return self.parse_isa_concept() def parse_def_concept(self, def_token): """ @@ -257,6 +263,35 @@ class DefaultParser(BaseParser): return concept_found + def parse_isa_concept(self): + concept_name = self.parse_concept_name() + if isinstance(concept_name, DefaultParserErrorNode): + return concept_name + + keyword = [] + token = self.get_token() + if token.value != Keywords.ISA: + return self.add_error(CannotHandleErrorNode([token], "")) + keyword.append(token) + self.next_token() + + set_name = self.parse_concept_name() + return IsaConceptNode(keyword, concept_name, set_name) + + def parse_concept_name(self): + tokens = [] + token = self.get_token() + + while not (token.type == TokenKind.EOF or token.type == TokenKind.KEYWORD): + tokens.append(token) + self.next_token() + token = self.get_token() + + if len(tokens) == 0: + return self.add_error(UnexpectedTokenErrorNode([token], "Unexpected token", [])) + else: + return NameNode(tokens) + def regroup_tokens_by_parts(self, keywords_tokens): def_concept_parts = [Keywords.CONCEPT, Keywords.FROM, Keywords.AS, Keywords.WHERE, Keywords.PRE, Keywords.POST] diff --git a/sdp/sheerkaDataProvider.py b/sdp/sheerkaDataProvider.py index b4bea3d..85320f0 100644 --- a/sdp/sheerkaDataProvider.py +++ b/sdp/sheerkaDataProvider.py @@ -407,12 +407,15 @@ class SheerkaDataProvider: state.date = datetime.now() if entry not in state.data: state.data[entry] = {obj} + already_exist = False else: - state.data[entry].add(obj) + already_exist = obj in state.data[entry] + if not already_exist: + state.data[entry].add(obj) new_snapshot = self.save_state(state) self.set_snapshot(new_snapshot) - return entry, None + return (None if already_exist else entry), None def set(self, event_digest, entry, obj, use_ref=False): """ diff --git a/tests/test_AddConceptInSetEvaluator.py b/tests/test_AddConceptInSetEvaluator.py new file mode 100644 index 0000000..9615a2e --- /dev/null +++ b/tests/test_AddConceptInSetEvaluator.py @@ -0,0 +1,94 @@ +import pytest + +from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts +from core.concept import Concept +from core.sheerka import Sheerka, ExecutionContext +from core.tokenizer import Tokenizer +from evaluators.AddConceptInSetEvaluator import AddConceptInSetEvaluator +from parsers.DefaultParser import IsaConceptNode, NameNode + + +def get_context(): + sheerka = Sheerka(skip_builtins_in_db=True) + sheerka.initialize("mem://") + return ExecutionContext("test", "xxx", sheerka) + + +def get_ret_val(concept_name, concept_set_name): + n1 = NameNode(list(Tokenizer(concept_name))) + n2 = NameNode(list(Tokenizer(concept_set_name))) + + return ReturnValueConcept("some_name", True, ParserResultConcept(value=IsaConceptNode([], n1, n2))) + + +@pytest.mark.parametrize("ret_val, expected", [ + (ReturnValueConcept("some_name", True, ParserResultConcept(value=IsaConceptNode([]))), True), + (ReturnValueConcept("some_name", False, ParserResultConcept(value=IsaConceptNode([]))), False), + (ReturnValueConcept("some_name", True, "not a ParserResultConcept"), False), + (ReturnValueConcept("some_name", True, ParserResultConcept()), False), +]) +def test_i_can_match(ret_val, expected): + context = get_context() + assert AddConceptInSetEvaluator().matches(context, ret_val) == expected + + +def test_i_cannot_add_if_the_concept_does_not_exists(): + context = get_context() + + ret_val = get_ret_val("foo", "bar") + res = AddConceptInSetEvaluator().eval(context, ret_val) + + assert not res.status + assert context.sheerka.isinstance(res.value, BuiltinConcepts.UNKNOWN_CONCEPT) + assert res.value.body == "foo" + + +def test_i_cannot_add_if_the_set_does_not_exists(): + context = get_context() + foo = Concept("foo") + context.sheerka.set_id_if_needed(foo, False) + context.sheerka.add_in_cache(foo) + + ret_val = get_ret_val("foo", "bar") + res = AddConceptInSetEvaluator().eval(context, ret_val) + + assert not res.status + assert context.sheerka.isinstance(res.value, BuiltinConcepts.UNKNOWN_CONCEPT) + assert res.value.body == "bar" + + +def test_i_can_add_concept_to_a_set_of_concept(): + context = get_context() + foo = Concept("foo") + context.sheerka.set_id_if_needed(foo, False) + context.sheerka.add_in_cache(foo) + + bar = Concept("bar") + context.sheerka.set_id_if_needed(bar, False) + context.sheerka.add_in_cache(bar) + + ret_val = get_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(): + context = get_context() + foo = Concept("foo") + context.sheerka.set_id_if_needed(foo, False) + context.sheerka.add_in_cache(foo) + + bar = Concept("bar") + context.sheerka.set_id_if_needed(bar, False) + context.sheerka.add_in_cache(bar) + + ret_val = get_ret_val("foo", "bar") + AddConceptInSetEvaluator().eval(context, ret_val) + res = AddConceptInSetEvaluator().eval(context, ret_val) + + assert not res.status + assert context.sheerka.isinstance(res.value, BuiltinConcepts.CONCEPT_ALREADY_IN_SET) + assert res.value.concept == foo + assert res.value.concept_set == bar diff --git a/tests/test_DefaultParser.py b/tests/test_DefaultParser.py index d3bdecd..091ac66 100644 --- a/tests/test_DefaultParser.py +++ b/tests/test_DefaultParser.py @@ -6,7 +6,7 @@ from core.sheerka import Sheerka, ExecutionContext from parsers.ConceptLexerParser import OrderedChoice, StrMatch, ConceptMatch from parsers.PythonParser import PythonParser, PythonNode from core.tokenizer import Keywords, Tokenizer -from parsers.DefaultParser import DefaultParser, NameNode, SyntaxErrorNode, CannotHandleErrorNode +from parsers.DefaultParser import DefaultParser, NameNode, SyntaxErrorNode, CannotHandleErrorNode, IsaConceptNode from parsers.DefaultParser import UnexpectedTokenErrorNode, DefConceptNode from parsers.BnfParser import BnfParser @@ -55,24 +55,24 @@ from parsers.BnfParser import BnfParser # return left_as_string == right_as_string # -def get_concept(name, where=None, pre=None, post=None, body=None, definition=None): - concept = DefConceptNode([], name=NameNode(list(Tokenizer(name)))) +def get_def_concept(name, where=None, pre=None, post=None, body=None, definition=None): + def_concept = DefConceptNode([], name=NameNode(list(Tokenizer(name)))) if body: - concept.body = get_concept_part(body) + def_concept.body = get_concept_part(body) if where: - concept.where = get_concept_part(where) + def_concept.where = get_concept_part(where) if pre: - concept.pre = get_concept_part(pre) + def_concept.pre = get_concept_part(pre) if post: - concept.post = get_concept_part(post) + def_concept.post = get_concept_part(post) if definition: - concept.definition = ReturnValueConcept( + def_concept.definition = ReturnValueConcept( "parsers.Bnf", True, definition) - return concept + return def_concept def get_context(): @@ -145,16 +145,16 @@ def get_concept_part(part): @pytest.mark.parametrize("text, expected", [ - ("def concept hello", get_concept(name="hello")), - ("def concept hello ", get_concept(name="hello")), - ("def concept a + b", get_concept(name="a + b")), - ("def concept a+b", get_concept(name="a + b")), - ("def concept 'a+b'+c", get_concept(name="'a+b' + c")), - ("def concept 'as if'", get_concept(name="'as if'")), - ("def concept 'as' if", get_concept(name="'as if'")), - ("def concept hello as 'hello'", get_concept(name="hello", body="'hello'")), - ("def concept hello as 1", get_concept(name="hello", body="1")), - ("def concept hello as 1 + 1", get_concept(name="hello", body="1 + 1")), + ("def concept hello", get_def_concept(name="hello")), + ("def concept hello ", get_def_concept(name="hello")), + ("def concept a + b", get_def_concept(name="a + b")), + ("def concept a+b", get_def_concept(name="a + b")), + ("def concept 'a+b'+c", get_def_concept(name="'a+b' + c")), + ("def concept 'as if'", get_def_concept(name="'as if'")), + ("def concept 'as' if", get_def_concept(name="'as if'")), + ("def concept hello as 'hello'", get_def_concept(name="hello", body="'hello'")), + ("def concept hello as 1", get_def_concept(name="hello", body="1")), + ("def concept hello as 1 + 1", get_def_concept(name="hello", body="1 + 1")), ]) def test_i_can_parse_def_concept(text, expected): parser = DefaultParser() @@ -178,7 +178,7 @@ as res = a + b parser = DefaultParser() res = parser.parse(get_context(), text) return_value = res.value - expected_concept = get_concept( + expected_concept = get_def_concept( name="a plus b", where="a,b", pre="isinstance(a, int) and isinstance(b, float)", @@ -199,7 +199,7 @@ def func(x): func(a) """ - expected_concept = get_concept( + expected_concept = get_def_concept( name="add one to a ", body=PythonNode( "def func(x):\n return x+1\nfunc(a)", @@ -223,7 +223,7 @@ def concept add one to a as: func(a) """ - expected_concept = get_concept( + expected_concept = get_def_concept( name="add one to a ", body=PythonNode( "def func(x):\n return x+1\nfunc(a)", @@ -292,7 +292,7 @@ def test_name_is_mandatory(): def test_concept_keyword_is_mandatory_but_the_concept_is_recognized(): text = "def hello as a where b pre c post d" - expected_concept = get_concept(name="hello", body="a", where="b", pre="c", post="d") + expected_concept = get_def_concept(name="hello", body="a", where="b", pre="c", post="d") parser = DefaultParser() res = parser.parse(get_context(), text) return_value = res.value @@ -342,7 +342,7 @@ def test_i_can_parse_def_concept_from_regex(): node = res.value.value definition = OrderedChoice(ConceptMatch("a_concept"), StrMatch("a_string")) parser_result = ParserResultConcept(BnfParser(), "a_concept | 'a_string'", definition, definition) - expected = get_concept(name="name", body="__definition[0]", definition=parser_result) + expected = get_def_concept(name="name", body="__definition[0]", definition=parser_result) assert res.status assert res.who == parser.name @@ -370,3 +370,32 @@ def test_i_can_detect_not_for_me(): assert not res.status assert context.sheerka.isinstance(res.value, BuiltinConcepts.NOT_FOR_ME) assert isinstance(res.value.body[0], CannotHandleErrorNode) + + +def test_i_can_parse_is_a(): + parser = DefaultParser() + text = "the name of my 'concept' isa the name of the set" + res = parser.parse(get_context(), text) + expected = IsaConceptNode([], + concept=NameNode(list(Tokenizer("the name of my 'concept'"))), + set=NameNode(list(Tokenizer("the name of the set")))) + + assert res.status + assert res.who == parser.name + assert res.value.source == text + assert isinstance(res.value, ParserResultConcept) + assert res.value.value == expected + + +@pytest.mark.parametrize("text", [ + "concept", + "isa number", + "name isa", +]) +def test_i_cannot_parse_invalid_entries(text): + parser = DefaultParser() + res = parser.parse(get_context(), text) + + assert not res.status + assert isinstance(res.body, ParserResultConcept) + assert isinstance(res.body.body[0], UnexpectedTokenErrorNode) diff --git a/tests/test_sheerka.py b/tests/test_sheerka.py index b6b8807..26307a2 100644 --- a/tests/test_sheerka.py +++ b/tests/test_sheerka.py @@ -3,7 +3,7 @@ import os from os import path import shutil -from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UserInputConcept +from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UserInputConcept, ConceptAlreadyInSet from core.concept import Concept, PROPERTIES_TO_SERIALIZE, Property from core.sheerka import Sheerka, ExecutionContext from evaluators.MutipleSameSuccessEvaluator import MultipleSameSuccessEvaluator @@ -600,3 +600,71 @@ def test_builtin_error_concept_are_errors(): # only test a random one, it will be the same for the others sheerka = get_sheerka() assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS)) + + +def test_i_can_add_concept_to_set(): + sheerka = get_sheerka(False, False) + + foo = Concept("foo") + sheerka.set_id_if_needed(foo, False) + + all_foos = Concept("all_foos") + sheerka.set_id_if_needed(all_foos, False) + + context = get_context(sheerka) + res = sheerka.add_concept_to_set(context, foo, all_foos) + + assert res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) + + all_entries = get_sheerka(False, False).sdp.get("All_" + all_foos.id, None, False) + assert len(all_entries) == 1 + assert foo.id in all_entries + + +def test_i_can_add_several_concepts_to_set(): + sheerka = get_sheerka(False, False) + + foo1 = Concept("foo1") + sheerka.set_id_if_needed(foo1, False) + + foo2 = Concept("foo1") + sheerka.set_id_if_needed(foo2, False) + + all_foos = Concept("all_foos") + sheerka.set_id_if_needed(all_foos, False) + + context = get_context(sheerka) + sheerka.add_concept_to_set(context, foo1, all_foos) + res = sheerka.add_concept_to_set(context, foo2, all_foos) + + assert res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) + + all_entries = get_sheerka(False, False).sdp.get("All_" + all_foos.id, None, False) + assert len(all_entries) == 2 + assert foo1.id in all_entries + assert foo2.id in all_entries + + +def test_i_cannot_add_the_same_concept_twice_in_a_set(): + sheerka = get_sheerka() + + foo = Concept("foo") + sheerka.set_id_if_needed(foo, False) + + all_foos = Concept("all_foos") + sheerka.set_id_if_needed(all_foos, False) + + context = get_context(sheerka) + sheerka.add_concept_to_set(context, foo, all_foos) + res = sheerka.add_concept_to_set(context, foo, all_foos) + + assert not res.status + assert res.body == ConceptAlreadyInSet(foo, all_foos) + + all_entries = sheerka.sdp.get("All_" + all_foos.id, None, False) + assert len(all_entries) == 1 + assert foo.id in all_entries + + diff --git a/tests/test_sheerkaDataProvider.py b/tests/test_sheerkaDataProvider.py index 60846ad..f22c4b8 100644 --- a/tests/test_sheerkaDataProvider.py +++ b/tests/test_sheerkaDataProvider.py @@ -836,15 +836,20 @@ def test_i_can_add_a_dictionary_as_a_reference(root): ]) def test_i_can_add_unique(root): sdp = SheerkaDataProvider(root) - sdp.add_unique(evt_digest, "entry", ObjNoKey(1, "foo")) - sdp.add_unique(evt_digest, "entry", ObjNoKey(1, "foo")) - sdp.add_unique(evt_digest, "entry", ObjNoKey(2, "bar")) + entry, key = sdp.add_unique(evt_digest, "entry", ObjNoKey(1, "foo")) + assert (entry, key) == ("entry", None) + + entry, key = sdp.add_unique(evt_digest, "entry", ObjNoKey(1, "foo")) + assert (entry, key) == (None, None) + entry, key = sdp.add_unique(evt_digest, "entry", ObjNoKey(2, "bar")) + assert (entry, key) == ("entry", None) + + entry, key = sdp.add_unique(evt_digest, "entry", ObjNoKey(2, "bar")) + assert (entry, key) == (None, None) state = sdp.load_state(sdp.get_snapshot()) assert state.data == {"entry": {ObjNoKey(1, "foo"), ObjNoKey(2, "bar")}} - assert entry == "entry" - assert key is None @pytest.mark.parametrize("root", [ diff --git a/tests/test_sheerka_non_reg.py b/tests/test_sheerka_non_reg.py index 6bea14b..b75cb39 100644 --- a/tests/test_sheerka_non_reg.py +++ b/tests/test_sheerka_non_reg.py @@ -30,6 +30,32 @@ def init_test(): os.chdir(current_pwd) +def get_sheerka(use_dict=True, skip_builtins_in_db=True): + root = "mem://" if use_dict else root_folder + sheerka = Sheerka(skip_builtins_in_db=skip_builtins_in_db) + sheerka.initialize(root) + + return sheerka + + +def get_context(sheerka): + return ExecutionContext("test", "xxx", sheerka) + + +def get_default_concept(): + concept = Concept( + name="a + b", + where="isinstance(a, int) and isinstance(b, int)", + pre="isinstance(a, int) and isinstance(b, int)", + post="isinstance(res, int)", + body="def func(x,y):\n return x+y\nfunc(a,b)", + desc="specific description") + concept.set_prop("a", "value1") + concept.set_prop("b", "value2") + + return concept + + @pytest.mark.parametrize("text, expected", [ ("1 + 1", 2), ("sheerka.test()", 'I have access to Sheerka !') @@ -382,27 +408,11 @@ def test_i_can_eval_bnf_definitions_from_separate_instances(): assert sheerka.isinstance(res[0].value, concept_b) -def get_sheerka(use_dict=True, skip_builtins_in_db=True): - root = "mem://" if use_dict else root_folder - sheerka = Sheerka(skip_builtins_in_db=skip_builtins_in_db) - sheerka.initialize(root) +def test_i_can_say_that_a_concept_isa_another_concept(): + sheerka = get_sheerka() + sheerka.evaluate_user_input("def concept foo") + sheerka.evaluate_user_input("def concept bar") - return sheerka - - -def get_context(sheerka): - return ExecutionContext("test", "xxx", sheerka) - - -def get_default_concept(): - concept = Concept( - name="a + b", - where="isinstance(a, int) and isinstance(b, int)", - pre="isinstance(a, int) and isinstance(b, int)", - post="isinstance(res, int)", - body="def func(x,y):\n return x+y\nfunc(a,b)", - desc="specific description") - concept.set_prop("a", "value1") - concept.set_prop("b", "value2") - - return concept + res = sheerka.evaluate_user_input("foo isa bar") + assert res[0].status + assert sheerka.isinstance(res[0].body, BuiltinConcepts.SUCCESS)