From 6a8011ec12aa7f3feb871c8b18d4b07b46cbad7e Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Thu, 18 Mar 2021 18:18:19 +0100 Subject: [PATCH] Fixed #49 : working --- .../sheerka/services/SheerkaRuleManager.py | 38 +++++-- tests/core/test_SheerkaRuleManager.py | 103 +++++++++++++++++- tests/parsers/parsers_utils.py | 17 ++- 3 files changed, 144 insertions(+), 14 deletions(-) diff --git a/src/core/sheerka/services/SheerkaRuleManager.py b/src/core/sheerka/services/SheerkaRuleManager.py index 84bc77f..9148652 100644 --- a/src/core/sheerka/services/SheerkaRuleManager.py +++ b/src/core/sheerka/services/SheerkaRuleManager.py @@ -1123,12 +1123,22 @@ class SheerkaRuleManager(BaseService): class ReteConditionExprVisitor(ExpressionVisitor): + """ + From an ExprNode, construct the list of Rete condition that can be used in the ReteNetwork + """ def __init__(self, context): self.context = context self.var_counter = 0 self.variables = {} + def get_conditions(self, expr_node): + self.var_counter = 0 + self.variables.clear() + + conditions = self.visit(expr_node) + return [AndConditions(conditions)] + def add_variable(self, target): var_name = f"__x_{self.var_counter:02}__" self.var_counter += 1 @@ -1165,17 +1175,25 @@ class ReteConditionExprVisitor(ExpressionVisitor): conditions.append(Condition(root, attr, variable)) return variable - def get_conditions(self, expr_node): - self.var_counter = 0 - self.variables.clear() + def visit_VariableNode(self, expr_node: VariableNode): + if expr_node.attributes_str is None and not expr_node.name.startswith("__"): + # try to recognize a concept + res = evaluate(self.context, + expr_node.name, + evaluators=CONDITIONS_VISITOR_EVALUATORS, + desc=None, + eval_body=True, + eval_where=False, + is_question=False, + expect_success=False, + stm=None) + res = expect_one(self.context, res) + if res.status and isinstance(res.value, Concept): + return self.recognize_concept(["__ret", "body"], res.value, {}) - conditions = self.visit(expr_node) - return [AndConditions(conditions)] - - def visit_VariableNode(self, expr_node): conditions = [] var_name, attr = self.init_or_get_variable_from_name(expr_node.unpack(), conditions) - if expr_node.attributes_str is not None: + if attr: conditions.append(Condition(var_name, attr, True)) return conditions @@ -1277,3 +1295,7 @@ class ReteConditionExprVisitor(ExpressionVisitor): conditions.extend(res) else: conditions.append(Condition(left, attr, value)) + + +class PythonConditionExprVisitor(ExpressionVisitor): + pass diff --git a/tests/core/test_SheerkaRuleManager.py b/tests/core/test_SheerkaRuleManager.py index b550a6f..7061b3b 100644 --- a/tests/core/test_SheerkaRuleManager.py +++ b/tests/core/test_SheerkaRuleManager.py @@ -1152,7 +1152,7 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \ ), ]) - def test_i_can_get_rete_conditions_from_recognized(self, test_name, expression, variable_name, expected_as_str): + def test_i_can_get_rete_using_recognized_function(self, test_name, expression, variable_name, expected_as_str): sheerka, context, greetings, foo = self.init_test().with_concepts( Concept("greetings", definition="hello a", definition_type=DEFINITION_TYPE_DEF).def_var("a"), Concept("foo"), @@ -1185,6 +1185,107 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \ matches = list(network.matches) assert len(matches) == 1 + @pytest.mark.parametrize("expression, variable_name, expected_as_str", [ + ( + "greetings", + None, + ["#__x_00__|__name__|'__ret'", + "#__x_00__|body|#__x_01__", + "#__x_01__|__is_concept__|True", + "#__x_01__|name|'greetings'"] + ), + ( + "hello foo", + "foo", + ["#__x_00__|__name__|'__ret'", + "#__x_00__|body|#__x_01__", + "#__x_01__|__is_concept__|True", + "#__x_01__|key|'hello __var__0'", + "#__x_01__|a|#__x_02__", + "#__x_02__|__is_concept__|True", + "#__x_02__|key|'foo'", + ] + ), + ]) + def test_i_can_get_rete_when_a_concept_is_recognized(self, expression, variable_name, expected_as_str): + sheerka, context, greetings, foo = self.init_test().with_concepts( + Concept("greetings", definition="hello a", definition_type=DEFINITION_TYPE_DEF).def_var("a"), + Concept("foo"), + ).unpack() + parser = ExpressionParser() + expected = get_rete_conditions(*expected_as_str) + + error_sink = ErrorSink() + parser_input = ParserInput(expression) + parser.reset_parser_input(parser_input, error_sink) + parsed = parser.parse_input(context, parser_input, error_sink) + + visitor = ReteConditionExprVisitor(context) + conditions = visitor.get_conditions(parsed) + + assert conditions == [expected] + + # check against a Rete network + network = ReteNetwork() + rule = Rule("test", expression, None) + rule.metadata.id = 9999 + rule.metadata.is_compiled = True + rule.metadata.is_enabled = True + rule.rete_disjunctions = conditions + network.add_rule(rule) + + variable = foo if variable_name == "foo" else sheerka if variable_name == "sheerka" else variable_name + to_recognize = sheerka.new_from_template(greetings, greetings.key, a=variable) + network.add_obj("__ret", ReturnValueConcept("Test", True, to_recognize)) + matches = list(network.matches) + assert len(matches) == 1 + + @pytest.mark.parametrize("expression, expected_as_str", [ + ( + "eval(__ret.body, 'foo' starts with 'f')", + ["#__x_00__|__name__|'__ret'", + "#__x_00__|body|#__x_01__", + "#__x_01__|__is_concept__|True", + "#__x_01__|key|'__var__0 starts with __var__1'", + "#__x_01__|x|'foo'", + "#__x_01__|y|'f'", + "$eval(__x_01__, a, b, c)"] + ), + ]) + def test_i_can_get_rete_conditions_using_eval_function(self, expression, expected_as_str): + sheerka, context, start_with = self.init_test().with_concepts( + Concept("x starts with y", + pre="is_question", + body="x.startswith(y)", + where="isinstance(x, str)").def_var("x").def_var("y"), + ).unpack() + + parser = ExpressionParser() + expected = get_rete_conditions(*expected_as_str) + + error_sink = ErrorSink() + parser_input = ParserInput(expression) + parser.reset_parser_input(parser_input, error_sink) + parsed = parser.parse_input(context, parser_input, error_sink) + + visitor = ReteConditionExprVisitor(context) + conditions = visitor.get_conditions(parsed) + + assert conditions == [expected] + + # check against a Rete network + network = ReteNetwork() + rule = Rule("test", expression, None) + rule.metadata.id = 9999 + rule.metadata.is_compiled = True + rule.metadata.is_enabled = True + rule.rete_disjunctions = conditions + network.add_rule(rule) + + to_recognize = sheerka.new_from_template(start_with, start_with.key, x="foo", y="f") + network.add_obj("__ret", ReturnValueConcept("Test", True, to_recognize)) + matches = list(network.matches) + assert len(matches) == 1 class TestSheerkaRuleManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): def test_rules_are_initialized_at_startup(self): diff --git a/tests/parsers/parsers_utils.py b/tests/parsers/parsers_utils.py index 0a24ce2..2341508 100644 --- a/tests/parsers/parsers_utils.py +++ b/tests/parsers/parsers_utils.py @@ -1,4 +1,5 @@ import ast +import re from dataclasses import dataclass from typing import Union @@ -1317,12 +1318,18 @@ def get_rete_conditions(*conditions_as_string): res = [] for as_string in conditions_as_string: - parts = as_string.split("|") - identifier = get_value(parts[0]) - attribute = parts[1] - value = get_value(parts[2]) + if as_string.startswith("$"): + fn_match = re.match(r"(?P\w+)\s?\((?P.+)\)", as_string[1:]) + as_dict = fn_match.groupdict() + pass + else: + parts = as_string.split("|") + identifier = get_value(parts[0]) + attribute = parts[1] + value = get_value(parts[2]) + + res.append(Condition(identifier, attribute, value)) - res.append(Condition(identifier, attribute, value)) return AndConditions(res)