From 51fa9629d0ae22a36527ab904bc00ccf280b1f1d Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sun, 12 Jan 2020 10:28:44 +0100 Subject: [PATCH] Refactored to use cached_asts in Concepts, rather than setting up a value directly --- core/concept.py | 13 +++ core/sheerka.py | 112 +++++++++++++++++-------- evaluators/PythonEvaluator.py | 4 +- parsers/ConceptLexerParser.py | 18 ++-- tests/test_ConceptLexerParser.py | 130 +++++++++++++---------------- tests/test_ConceptNodeEvaluator.py | 6 +- tests/test_PythonEvaluator.py | 2 +- tests/test_sheerka.py | 64 +++++++++++++- tests/test_sheerka_non_reg.py | 74 +++++++--------- 9 files changed, 256 insertions(+), 167 deletions(-) diff --git a/core/concept.py b/core/concept.py index f5efd39..8ee0ae0 100644 --- a/core/concept.py +++ b/core/concept.py @@ -293,3 +293,16 @@ class Property: def __hash__(self): return hash((self.name, self.value)) + + +@dataclass() +class DoNotResolve: + """ + This class is used to that the metadata (or the prop) of the concept must not be evaluated + thru sheerka.execute + + For example, if you want to set a value to the BODY that will not change when + when the concept will be evaluated, + set concept.cached_asts[BODY] to DoNotResolve(value) + """ + value: object diff --git a/core/sheerka.py b/core/sheerka.py index bebc4c6..a3d082d 100644 --- a/core/sheerka.py +++ b/core/sheerka.py @@ -1,5 +1,5 @@ from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, BuiltinUnique -from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW +from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW, DoNotResolve from parsers.BaseParser import BaseParser from sdp.sheerkaDataProvider import SheerkaDataProvider, Event, SheerkaDataProviderDuplicateKeyError import core.utils @@ -551,6 +551,9 @@ class Sheerka(Concept): """ steps = [BuiltinConcepts.BEFORE_PARSING, BuiltinConcepts.PARSING, BuiltinConcepts.AFTER_PARSING] for part_key in ConceptParts: + if part_key in concept.cached_asts: + continue + source = getattr(concept.metadata, part_key.value) if source is None or not isinstance(source, str) or source == "": # the only sources that I am sure to parse are strings @@ -565,6 +568,9 @@ class Sheerka(Concept): sub_context.add_values(return_values=res) for prop in concept.props: + if prop in concept.cached_asts: + continue + value = concept.props[prop].value if value: if isinstance(value, Concept): @@ -604,14 +610,65 @@ class Sheerka(Concept): if concept.metadata.is_evaluated: return concept - def _resolve(return_value, desc, obj): + def _resolve(to_resolve, current_prop, current_concept): + if isinstance(to_resolve, DoNotResolve): + return to_resolve.value + + desc = f"Evaluating {current_prop} (concept={current_concept})" context.log(logger, desc, self.evaluate_concept.__name__) - with context.push(desc=desc, obj=obj) as sub_context: + with context.push(desc=desc, obj=current_concept) as sub_context: sub_context.log_new(logger) - r = self.execute(sub_context, return_value, CONCEPT_EVALUATION_STEPS, logger) - one_r = core.builtin_helpers.expect_one(context, r) - sub_context.add_values(return_values=one_r) - return one_r + + # when it's a concept, evaluate it + if isinstance(to_resolve, Concept): + evaluated = self.evaluate_concept(sub_context, to_resolve) + sub_context.add_values(return_values=evaluated) + if evaluated.key == to_resolve.key: + return evaluated + else: + error = evaluated + + # otherwise, execute all return values to find out what is the value + else: + r = self.execute(sub_context, to_resolve, CONCEPT_EVALUATION_STEPS, logger) + one_r = core.builtin_helpers.expect_one(context, r) + sub_context.add_values(return_values=one_r) + if one_r.status: + return one_r.value + else: + error = one_r.value + + return self.new(BuiltinConcepts.CONCEPT_EVAL_ERROR, + body=error, + concept=concept, + property_name=prop_name) + + def _resolve_list(sheerka, list_to_resolve, current_prop, current_concept): + """When dealing with a list, there are two possibilities""" + # It may be a list of ReturnValueConcept to execute (always the case for metadata) + # or a list of single values (may be the case for properties) + # in this latter case, all values are to be processed one by one and a list should be returned + if len(list_to_resolve) == 0: + return [] + + if sheerka.isinstance(list_to_resolve[0], BuiltinConcepts.RETURN_VALUE): + return _resolve(list_to_resolve, current_prop, current_concept) + + res = [] + for to_resolve in list_to_resolve: + # sanity check + if sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE): + return self.new(BuiltinConcepts.CONCEPT_EVAL_ERROR, + body="Mix between real values and return values", + concept=concept, + property_name=prop_name) + + r = _resolve(to_resolve, current_prop, current_concept) + if sheerka.isinstance(r, BuiltinConcepts.CONCEPT_EVAL_ERROR): + return r + res.append(r) + + return res # WHERE condition should already be validated by the parser. # It's a mandatory condition for the concept before it can be recognized @@ -620,9 +677,7 @@ class Sheerka(Concept): # TODO : Validate the PRE condition # - if len(concept.cached_asts) == 0: - context.log(logger, "concept asts are not initialized. Initializing.", self.evaluate_concept.__name__) - self.initialize_concept_asts(context, concept, logger) + self.initialize_concept_asts(context, concept, logger) # to make sure of the order, it don't use ConceptParts.get_parts() # props must be evaluated first @@ -632,35 +687,24 @@ class Sheerka(Concept): if metadata_to_eval == "props": for prop_name in (p for p in concept.props if p in concept.cached_asts): prop_ast = concept.cached_asts[prop_name] - if isinstance(concept.cached_asts[prop_name], Concept): - context.log( - logger, f"Evaluation prop={prop_name}, value={prop_ast}", self.evaluate_concept.__name__) - with context.push(f"Evaluation property '{prop_name}', value='{prop_ast}'") as sub_context: - sub_context.log_new(logger) - evaluated = self.evaluate_concept(sub_context, prop_ast) - sub_context.add_values(return_values=evaluated) - concept.set_prop(prop_name, evaluated) + + if isinstance(prop_ast, list): + resolved = _resolve_list(context.sheerka, prop_ast, prop_name, None) else: - res = _resolve(prop_ast, f"Evaluating property '{prop_name}'", None) - if res.status: - concept.set_prop(prop_name, res.value) - else: - return self.new(BuiltinConcepts.CONCEPT_EVAL_ERROR, - body=res.value, - concept=concept, - property_name=prop_name) + resolved = _resolve(prop_ast, prop_name, None) + if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR): + return resolved + else: + concept.set_prop(prop_name, resolved) else: part_key = ConceptParts(metadata_to_eval) - if part_key in concept.cached_asts and concept.cached_asts[part_key] is not None: - res = _resolve(concept.cached_asts[part_key], f"Evaluating '{part_key}'", concept) - if res.status: - setattr(concept.metadata, metadata_to_eval, res.value) + metadata_ast = concept.cached_asts[part_key] + resolved = _resolve(metadata_ast, part_key, concept) + if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR): + return resolved else: - return self.new(BuiltinConcepts.CONCEPT_EVAL_ERROR, - body=res.value, - concept=concept, - property_name=part_key) + setattr(concept.metadata, metadata_to_eval, resolved) # # TODO : Validate the POST condition diff --git a/evaluators/PythonEvaluator.py b/evaluators/PythonEvaluator.py index 971077c..4baf886 100644 --- a/evaluators/PythonEvaluator.py +++ b/evaluators/PythonEvaluator.py @@ -107,9 +107,7 @@ class PythonEvaluator(OneReturnValueEvaluator): sub_context.add_values(return_values=evaluated) if evaluated.key == concept.key: - my_locals[name] = evaluated if return_concept else \ - evaluated.body if ConceptParts.BODY in evaluated.cached_asts else \ - evaluated + my_locals[name] = evaluated if return_concept else context.sheerka.value(evaluated) if self.locals: my_locals.update(self.locals) diff --git a/parsers/ConceptLexerParser.py b/parsers/ConceptLexerParser.py index 9e030ba..5d5e9d1 100644 --- a/parsers/ConceptLexerParser.py +++ b/parsers/ConceptLexerParser.py @@ -9,7 +9,7 @@ from dataclasses import field, dataclass from collections import defaultdict from core.builtin_concepts import BuiltinConcepts -from core.concept import Concept +from core.concept import Concept, ConceptParts, DoNotResolve from core.tokenizer import TokenKind, Tokenizer, Token from parsers.BaseParser import BaseParser, Node, ErrorNode import core.utils @@ -702,6 +702,9 @@ class ConceptLexerParser(BaseParser): else: to_match = node.concept + if to_match not in self.concepts_grammars: + return False + return _is_infinite_recursion(ref_concept, self.concepts_grammars[to_match]) if isinstance(node, OrderedChoice): @@ -847,17 +850,17 @@ class ConceptLexerParser(BaseParser): Adds a new entry, makes a list if the property already exists """ - if prop_name not in _concept.props or _concept.props[prop_name].value is None: + if prop_name not in _concept.cached_asts or _concept.cached_asts[prop_name] is None: # new entry - _concept.set_prop(prop_name, value) + _concept.cached_asts[prop_name] = value else: # make a list if there was a value - previous_value = _concept.props[prop_name].value + previous_value = _concept.cached_asts[prop_name] if isinstance(previous_value, list): previous_value.append(value) else: new_value = [previous_value, value] - _concept.set_prop(prop_name, new_value) + _concept.cached_asts[prop_name] = new_value def _look_for_concept_match(_underlying): if isinstance(_underlying.parsing_expression, ConceptMatch): @@ -881,7 +884,7 @@ class ConceptLexerParser(BaseParser): result = self.finalize_concept(sheerka, ref_tpl, concept_match_node.children[0], init_empty_body) _underlying_value_cache[id(concept_match_node)] = result else: - result = _underlying.source + result = DoNotResolve(_underlying.source) return result @@ -898,8 +901,7 @@ class ConceptLexerParser(BaseParser): concept = sheerka.new(key) if init_empty_body and concept.body is None: value = _get_underlying_value(underlying) - concept.metadata.body = value - concept.metadata.is_evaluated = True + concept.cached_asts[ConceptParts.BODY] = value if underlying.parsing_expression.rule_name: _add_prop(concept, underlying.parsing_expression.rule_name, value) diff --git a/tests/test_ConceptLexerParser.py b/tests/test_ConceptLexerParser.py index 2c4aed5..c579a64 100644 --- a/tests/test_ConceptLexerParser.py +++ b/tests/test_ConceptLexerParser.py @@ -1,7 +1,7 @@ import pytest import core.utils from core.builtin_concepts import BuiltinConcepts -from core.concept import Concept +from core.concept import Concept, ConceptParts, DoNotResolve from core.sheerka import Sheerka, ExecutionContext from core.tokenizer import Tokenizer, TokenKind, Token from parsers.ConceptLexerParser import ConceptLexerParser, ConceptNode, Sequence, StrMatch, OrderedChoice, Optional, \ @@ -58,7 +58,20 @@ def get_context(): def get_expected(concept, text=None): - return Concept(name=concept.name, body=text or concept.name).init_key() + c = Concept(name=concept.name) + c.cached_asts[ConceptParts.BODY] = DoNotResolve(text or concept.name) + c.init_key() + return c + + +def cbody(concept): + """cbody stands for compiled body""" + return concept.cached_asts[ConceptParts.BODY] + + +def cprop(concept, prop_name): + """cbody stands for compiled property""" + return concept.cached_asts[prop_name] def init(concepts, grammar): @@ -359,20 +372,15 @@ def test_i_can_use_reference(): assert context.sheerka.isinstance(res[0].value, BuiltinConcepts.PARSER_RESULT) assert res[0].value.body == [("foo", 0, 2, "one two")] concept_found_1 = res[0].value.body[0].concept - assert concept_found_1.metadata.is_evaluated - assert concept_found_1.body == "one two" + assert cbody(concept_found_1) == DoNotResolve("one two") assert res[1].status assert context.sheerka.isinstance(res[1].value, BuiltinConcepts.PARSER_RESULT) assert res[1].value.body == [("bar", 0, 2, "one two")] concept_found_2 = res[1].value.body[0].concept - assert concept_found_2.metadata.is_evaluated # the body and the prop['foo'] are the same concept 'foo' - assert isinstance(concept_found_2.body, Concept) - assert concept_found_2.body.key == "foo" - assert concept_found_2.body.metadata.is_evaluated - assert concept_found_2.body.body == "one two" - assert id(concept_found_2.props["foo"].value) == id(concept_found_2.body) + assert cbody(concept_found_2) == get_expected(foo, "one two") + assert id(cprop(concept_found_2, "foo")) == id(cbody(concept_found_2)) def test_i_can_use_a_reference_with_a_body(): @@ -394,20 +402,15 @@ def test_i_can_use_a_reference_with_a_body(): assert context.sheerka.isinstance(res[0].value, BuiltinConcepts.PARSER_RESULT) assert res[0].value.body == [("foo", 0, 2, "one two")] concept_found_1 = res[0].value.body[0].concept - assert not concept_found_1.metadata.is_evaluated assert concept_found_1.body == "'foo'" assert res[1].status assert context.sheerka.isinstance(res[1].value, BuiltinConcepts.PARSER_RESULT) assert res[1].value.body == [("bar", 0, 2, "one two")] concept_found_2 = res[1].value.body[0].concept - assert concept_found_2.metadata.is_evaluated # the body and the prop['foo'] are the same concept 'foo' - assert isinstance(concept_found_2.body, Concept) - assert concept_found_2.body.key == "foo" - assert not concept_found_2.body.metadata.is_evaluated - assert concept_found_2.body.body == "'foo'" - assert id(concept_found_2.props["foo"].value) == id(concept_found_2.body) + assert cbody(concept_found_2) == foo + assert id(cprop(concept_found_2, "foo")) == id(cbody(concept_found_2)) def test_i_can_use_context_reference_with_multiple_levels(): @@ -429,23 +432,23 @@ def test_i_can_use_context_reference_with_multiple_levels(): assert context.sheerka.isinstance(res[0].value, BuiltinConcepts.PARSER_RESULT) assert res[0].value.body == [("foo", 0, 2, "one two")] concept_found_1 = res[0].value.body[0].concept - assert concept_found_1.body == "one two" - assert concept_found_1.metadata.is_evaluated + assert cbody(concept_found_1) == DoNotResolve("one two") assert res[1].status assert context.sheerka.isinstance(res[1].value, BuiltinConcepts.PARSER_RESULT) assert res[1].value.body == [("bar", 0, 2, "one two")] concept_found_2 = res[1].value.body[0].concept - assert concept_found_2.body == get_expected(foo, "one two") - assert id(concept_found_2.props["foo"].value) == id(concept_found_2.body) + assert cbody(concept_found_2) == get_expected(foo, "one two") + assert id(cprop(concept_found_2, "foo")) == id(cbody(concept_found_2)) assert res[2].status assert context.sheerka.isinstance(res[2].value, BuiltinConcepts.PARSER_RESULT) assert res[2].value.body == [("baz", 0, 2, "one two")] concept_found_3 = res[2].value.body[0].concept expected_foo = get_expected(foo, "one two") - assert concept_found_3.body == get_expected(bar, expected_foo).set_prop("foo", expected_foo) - assert id(concept_found_3.props["bar"].value) == id(concept_found_3.body) + assert cbody(concept_found_3) == get_expected(bar, expected_foo) + assert cprop(concept_found_3, "foo") == expected_foo + assert id(cprop(concept_found_3, "bar")) == id(cbody(concept_found_3)) def test_order_is_not_important_when_using_references(): @@ -476,26 +479,21 @@ def test_i_can_parse_when_reference(): assert res.status assert res.value.body == [("bar", 0, 2, "twenty two")] concept_found = res.value.body[0].concept - assert concept_found.body == "twenty two" - assert concept_found.metadata.is_evaluated - assert concept_found.get_prop("foo") == get_expected(foo, "twenty") - assert concept_found.get_prop("foo").metadata.is_evaluated + assert cbody(concept_found) == DoNotResolve("twenty two") + assert cprop(concept_found, "foo") == get_expected(foo, "twenty") res = parser.parse(context, "thirty one") assert res.status assert res.value.body == [("bar", 0, 2, "thirty one")] concept_found = res.value.body[0].concept - assert concept_found.body == "thirty one" - assert concept_found.metadata.is_evaluated - assert concept_found.get_prop("foo") == get_expected(foo, "thirty") - assert concept_found.get_prop("foo").metadata.is_evaluated + assert cbody(concept_found) == DoNotResolve("thirty one") + assert cprop(concept_found, "foo") == get_expected(foo, "thirty") res = parser.parse(context, "twenty") assert res.status assert res.value.body == [("foo", 0, 0, "twenty")] concept_found = res.value.body[0].concept - assert concept_found.body == "twenty" - assert concept_found.metadata.is_evaluated + assert cbody(concept_found) == DoNotResolve("twenty") def test_i_can_parse_when_reference_has_a_body(): @@ -508,17 +506,14 @@ def test_i_can_parse_when_reference_has_a_body(): assert res.status assert res.value.body == [("bar", 0, 2, "twenty two")] concept_found = res.value.body[0].concept - assert concept_found.body == "twenty two" - assert concept_found.metadata.is_evaluated - assert concept_found.get_prop("foo") == get_expected(foo, "'one'") - assert not concept_found.get_prop("foo").metadata.is_evaluated + assert cbody(concept_found) == DoNotResolve("twenty two") + assert cprop(concept_found, "foo") == foo res = parser.parse(context, "twenty") assert res.status assert res.value.body == [("foo", 0, 0, "twenty")] concept_found = res.value.body[0].concept assert concept_found.body == "'one'" - assert not concept_found.metadata.is_evaluated def test_i_can_parse_multiple_results(): @@ -536,16 +531,14 @@ def test_i_can_parse_multiple_results(): assert context.sheerka.isinstance(res[0].value, BuiltinConcepts.PARSER_RESULT) assert res[0].value.body == [("bar", 0, 2, "one two")] concept_found_0 = res[0].value.body[0].concept - assert concept_found_0.body == "one two" - assert concept_found_0.metadata.is_evaluated + assert cbody(concept_found_0) == DoNotResolve("one two") assert len(concept_found_0.props) == 0 assert res[1].status assert context.sheerka.isinstance(res[1].value, BuiltinConcepts.PARSER_RESULT) assert res[1].value.body == [("foo", 0, 2, "one two")] concept_found_1 = res[1].value.body[0].concept - assert concept_found_1.body == "one two" - assert concept_found_1.metadata.is_evaluated + assert cbody(concept_found_1) == DoNotResolve("one two") assert len(concept_found_1.props) == 0 @@ -617,10 +610,8 @@ def test_i_can_parse_concept_reference_that_is_not_in_grammar(): assert res.status assert res.value.body == [("foo", 0, 2, "twenty two")] concept_found = res.value.body[0].concept - assert concept_found.body == "twenty two" - assert concept_found.metadata.is_evaluated - assert concept_found.get_prop("two") == get_expected(two, "two") - assert concept_found.get_prop("two").metadata.is_evaluated + assert cbody(concept_found) == DoNotResolve("twenty two") + assert cprop(concept_found, "two") == get_expected(two, "two") res = parser.parse(context, "twenty one") assert res.status @@ -638,8 +629,7 @@ def test_i_can_parse_zero_or_more(): assert return_value[0].underlying == u(grammar[foo], 0, 2, [u("one", 0, 0), u("one", 2, 2)]) concept_found = return_value[0].concept - assert concept_found.body == "one one" - assert concept_found.metadata.is_evaluated + assert cbody(concept_found) == DoNotResolve("one one") def test_i_can_parse_sequence_and_zero_or_more(): @@ -1064,8 +1054,8 @@ def test_i_can_get_the_inner_concept_when_possible(): assert context.sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) assert return_value == [("foo", 0, 0, "one")] concept_found = return_value[0].concept - assert concept_found.body == get_expected(one, "one") - assert concept_found.get_prop("one") == concept_found.body + assert cbody(concept_found) == get_expected(one, "one") + assert id(cprop(concept_found, "one")) == id(cbody(concept_found)) def test_i_can_get_the_inner_concept_when_possible_with_rule_name(): @@ -1081,11 +1071,11 @@ def test_i_can_get_the_inner_concept_when_possible_with_rule_name(): assert context.sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) assert return_value == [("foo", 0, 0, "one")] concept_found = return_value[0].concept - assert concept_found.body == get_expected(one, "one") - assert id(concept_found.get_prop("one")) == id(concept_found.body) - assert id(concept_found.get_prop("zero")) == id(concept_found.body) - assert id(concept_found.get_prop("opt")) == id(concept_found.body) - assert id(concept_found.get_prop("seq")) == id(concept_found.body) + assert cbody(concept_found) == get_expected(one, "one") + assert id(cprop(concept_found, "one")) == id(cbody(concept_found)) + assert id(cprop(concept_found, "zero")) == id(cbody(concept_found)) + assert id(cprop(concept_found, "opt")) == id(cbody(concept_found)) + assert id(cprop(concept_found, "seq")) == id(cbody(concept_found)) def test_i_get_multiple_props_when_zero_or_more(): @@ -1098,15 +1088,14 @@ def test_i_get_multiple_props_when_zero_or_more(): assert context.sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) assert return_value == [("foo", 0, 4, "one one one")] concept_found = return_value[0].concept - assert concept_found.body == "one one one" - assert len(concept_found.props) == 1 - assert len(concept_found.get_prop("one")) == 3 - assert concept_found.get_prop("one")[0] == get_expected(one) - assert concept_found.get_prop("one")[1] == get_expected(one) - assert concept_found.get_prop("one")[2] == get_expected(one) - assert id(concept_found.get_prop("one")[0]) != id(concept_found.get_prop("one")[1]) - assert id(concept_found.get_prop("one")[1]) != id(concept_found.get_prop("one")[2]) - assert id(concept_found.get_prop("one")[2]) != id(concept_found.get_prop("one")[0]) + assert cbody(concept_found) == DoNotResolve("one one one") + assert len(concept_found.cached_asts["one"]) == 3 + assert cprop(concept_found, "one")[0] == get_expected(one) + assert cprop(concept_found, "one")[1] == get_expected(one) + assert cprop(concept_found, "one")[2] == get_expected(one) + assert id(cprop(concept_found, "one")[0]) != id(cprop(concept_found, "one")[1]) + assert id(cprop(concept_found, "one")[1]) != id(cprop(concept_found, "one")[2]) + assert id(cprop(concept_found, "one")[2]) != id(cprop(concept_found, "one")[0]) def test_i_get_multiple_props_when_zero_or_more_and_different_values(): @@ -1119,13 +1108,12 @@ def test_i_get_multiple_props_when_zero_or_more_and_different_values(): assert context.sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) assert return_value == [("foo", "one ok un ok uno ok")] concept_found = return_value[0].concept - assert concept_found.get_prop("one")[0] == get_expected(one, "one") - assert concept_found.get_prop("one")[1] == get_expected(one, "un") - assert concept_found.get_prop("one")[2] == get_expected(one, "uno") - assert concept_found.get_prop("seq")[0] == "one ok" - assert concept_found.get_prop("seq")[1] == "un ok" - assert concept_found.get_prop("seq")[2] == "uno ok" - + assert cprop(concept_found, "one")[0] == get_expected(one, "one") + assert cprop(concept_found, "one")[1] == get_expected(one, "un") + assert cprop(concept_found, "one")[2] == get_expected(one, "uno") + assert cprop(concept_found, "seq")[0] == DoNotResolve("one ok") + assert cprop(concept_found, "seq")[1] == DoNotResolve("un ok") + assert cprop(concept_found, "seq")[2] == DoNotResolve("uno ok") # diff --git a/tests/test_ConceptNodeEvaluator.py b/tests/test_ConceptNodeEvaluator.py index 89f81b9..3772345 100644 --- a/tests/test_ConceptNodeEvaluator.py +++ b/tests/test_ConceptNodeEvaluator.py @@ -1,7 +1,7 @@ import pytest from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts -from core.concept import Concept +from core.concept import Concept, ConceptParts, DoNotResolve from core.sheerka import Sheerka, ExecutionContext from evaluators.ConceptNodeEvaluator import ConceptNodeEvaluator from parsers.ConceptLexerParser import ConceptNode, ConceptLexerParser, Sequence, TerminalNode, \ @@ -73,7 +73,7 @@ def test_concept_is_returned_when_only_one_in_the_list(): assert context.sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) assert wrapper.parser == evaluator assert wrapper.source == "foo" - assert return_value == Concept("foo", body="foo").init_key() - assert return_value.metadata.is_evaluated + assert return_value == Concept("foo").init_key() + assert return_value.cached_asts[ConceptParts.BODY] == DoNotResolve("foo") assert result.parents == [ret_val] diff --git a/tests/test_PythonEvaluator.py b/tests/test_PythonEvaluator.py index 0662611..da68168 100644 --- a/tests/test_PythonEvaluator.py +++ b/tests/test_PythonEvaluator.py @@ -84,7 +84,7 @@ def test_i_can_eval_module_with_that_references_concepts(): context = get_context() context.sheerka.add_in_cache(Concept("foo")) - parsed = PythonParser().parse(context, "def a(b):\n return b\na(foo)") + parsed = PythonParser().parse(context, "def a(b):\n return b\na(c:foo:)") evaluated = PythonEvaluator().eval(context, parsed) assert evaluated.status diff --git a/tests/test_sheerka.py b/tests/test_sheerka.py index b6d57d4..c8444d8 100644 --- a/tests/test_sheerka.py +++ b/tests/test_sheerka.py @@ -4,7 +4,7 @@ from os import path import shutil from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UserInputConcept, ConceptAlreadyInSet -from core.concept import Concept, PROPERTIES_TO_SERIALIZE, Property +from core.concept import Concept, PROPERTIES_TO_SERIALIZE, Property, ConceptParts, DoNotResolve from core.sheerka import Sheerka, ExecutionContext from sdp.sheerkaDataProvider import SheerkaDataProvider, Event @@ -528,6 +528,41 @@ def test_i_can_evaluate_a_concept_with_prop(expr, expected): assert evaluated.metadata.is_evaluated +def test_i_can_evaluate_metadata_using_do_not_resolve(): + sheerka = get_sheerka() + concept = Concept("foo") + concept.cached_asts[ConceptParts.BODY] = DoNotResolve("do not resolve") + + evaluated = sheerka.evaluate_concept(get_context(sheerka), concept) + + assert evaluated.body == "do not resolve" + assert evaluated.metadata.is_evaluated + + +def test_i_can_evaluate_property_using_do_not_resolve(): + sheerka = get_sheerka() + concept = Concept("foo").set_prop("a") + concept.cached_asts["a"] = DoNotResolve("do not resolve") + + evaluated = sheerka.evaluate_concept(get_context(sheerka), concept) + + assert evaluated.get_prop("a") == "do not resolve" + assert evaluated.metadata.is_evaluated + + +def test_original_value_is_overridden_when_using_do_no_resolve(): + sheerka = get_sheerka() + concept = Concept("foo", body="original value").set_prop("a", "original value") + concept.cached_asts["a"] = DoNotResolve("do not resolve") + concept.cached_asts[ConceptParts.BODY] = DoNotResolve("do not resolve") + + evaluated = sheerka.evaluate_concept(get_context(sheerka), concept) + + assert evaluated.body == "do not resolve" + assert evaluated.get_prop("a") == "do not resolve" + assert evaluated.metadata.is_evaluated + + def test_props_are_evaluated_before_body(): sheerka = get_sheerka() @@ -615,6 +650,8 @@ def test_i_can_evaluate_concept_when_properties_reference_others_concepts(): concept = Concept("foo", body="a").set_prop("a", "a").init_key() evaluated = sheerka.evaluate_concept(get_context(sheerka), concept) + # first prop a is evaluated to concept_a + # then body is evaluated to prop a -> concept_a assert evaluated.key == concept.key assert evaluated.body == concept_a @@ -647,6 +684,31 @@ def test_i_can_evaluate_concept_when_properties_reference_others_concepts_with_b assert evaluated.body == 3 +def test_i_can_evaluate_concept_when_properties_is_a_concept(): + sheerka = get_sheerka() + concept_a = sheerka.add_in_cache(Concept(name="a", body="'a'").init_key()) + + concept = Concept("foo").set_prop("a", concept_a) + evaluated = sheerka.evaluate_concept(get_context(sheerka), concept) + + assert evaluated.key == concept.key + assert evaluated.get_prop("a") == Concept(name="a", body="a").init_key() + + +def test_i_can_evaluate_when_property_asts_is_a_list(): + sheerka = get_sheerka() + foo = Concept("foo", body="1") + + concept = Concept("to_eval").set_prop("prop") + concept.cached_asts["prop"] = [foo, DoNotResolve("1")] + evaluated = sheerka.evaluate_concept(get_context(sheerka), concept) + + props = evaluated.get_prop("prop") + assert len(props) == 2 + assert props[0] == Concept("foo", body=1).init_key() + assert props[1] == "1" + + def test_i_can_reference_sheerka(): sheerka = get_sheerka() diff --git a/tests/test_sheerka_non_reg.py b/tests/test_sheerka_non_reg.py index a0cb356..393ae6e 100644 --- a/tests/test_sheerka_non_reg.py +++ b/tests/test_sheerka_non_reg.py @@ -413,14 +413,37 @@ def test_i_can_eval_a_mix_with_bnf_and_python(): assert res[0].body == 22 -def test_i_can_eval_a_mix_with_bnf_and_python_when_rule_name(): +@pytest.mark.parametrize("desc, definitions", [ + ("Simple form", [ + "def concept one as 1", + "def concept two as 2", + "def concept twenties from bnf 'twenty' (one|two)=unit as 20 + unit" + ]), + ("When twenty is a concept", [ + "def concept one as 1", + "def concept two as 2", + "def concept twenty as 20", + "def concept twenties from bnf twenty (one|two)=unit as twenty + unit" + ]), + ("When digit is a concept", [ + "def concept one as 1", + "def concept two as 2", + "def concept twenty as 20", + "def concept digit from bnf one|two", + "def concept twenties from bnf twenty digit as twenty + digit" + ]), +]) +def test_i_can_mix_concept_with_python_to_define_numbers(desc, definitions): sheerka = get_sheerka() - sheerka.evaluate_user_input("def concept one as 1") - 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") + for definition in definitions: + sheerka.evaluate_user_input(definition) - assert sheerka.evaluate_user_input("eval twenty one")[0].body == 21 + res = sheerka.evaluate_user_input("twenty one") + assert len(res) == 1 + assert res[0].status + assert sheerka.isinstance(res[0].body, "twenties") + assert res[0].body.body == 21 res = sheerka.evaluate_user_input("twenty one + 1") assert len(res) == 1 @@ -438,47 +461,6 @@ def test_i_can_eval_a_mix_with_bnf_and_python_when_rule_name(): assert res[0].body == 22 -def test_i_can_eval_a_mix_with_bnf_and_python_when_rule_name_2(): - sheerka = get_sheerka() - - sheerka.evaluate_user_input("def concept one as 1") - sheerka.evaluate_user_input("def concept two as 2") - sheerka.evaluate_user_input("def concept twenty as 20") - sheerka.evaluate_user_input("def concept twenties from bnf twenty (one | two)=unit as twenty + unit") - - assert sheerka.evaluate_user_input("eval twenty one")[0].body == 21 - - res = sheerka.evaluate_user_input("twenty one + 1") - assert len(res) == 1 - assert res[0].status - assert res[0].body == 22 - - res = sheerka.evaluate_user_input("twenty one + twenty two") - assert len(res) == 1 - assert res[0].status - assert res[0].body == 43 - - res = sheerka.evaluate_user_input("twenty one + one") - assert len(res) == 1 - assert res[0].status - assert res[0].body == 22 - - -def test_i_can_eval_a_more_complicated_mix_with_bnf_and_python(): - sheerka = get_sheerka() - - sheerka.evaluate_user_input("def concept one as 1") - 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") - - assert sheerka.evaluate_user_input("eval twenty one")[0].body == 21 - - res = sheerka.evaluate_user_input("twenty one + twenty two") - assert len(res) == 1 - assert res[0].status - assert res[0].body == 43 - - def test_i_can_say_that_a_concept_isa_another_concept(): sheerka = get_sheerka() sheerka.evaluate_user_input("def concept foo")