diff --git a/src/core/sheerka/services/SheerkaRuleManager.py b/src/core/sheerka/services/SheerkaRuleManager.py index 45f9550..cb022d6 100644 --- a/src/core/sheerka/services/SheerkaRuleManager.py +++ b/src/core/sheerka/services/SheerkaRuleManager.py @@ -17,11 +17,12 @@ from core.tokenizer import Keywords, TokenKind, Token, IterParser from core.utils import index_tokens, COLORS, get_text_from_tokens from evaluators.ConceptEvaluator import ConceptEvaluator from evaluators.PythonEvaluator import PythonEvaluator, Expando +from parsers.BaseExpressionParser import AndNode, ExpressionVisitor, VariableNode, ComparisonNode from parsers.BaseNodeParser import SourceCodeWithConceptNode, ConceptNode, SourceCodeNode from parsers.LogicalOperatorParser import LogicalOperatorParser from parsers.PythonParser import PythonNode -from parsers.BaseExpressionParser import AndNode -from sheerkarete.conditions import AndConditions +from sheerkarete.common import V +from sheerkarete.conditions import AndConditions, Condition CONCEPTS_ONLY_PARSERS = ["ExactConcept", "Bnf", "Sya", "Sequence"] @@ -1118,3 +1119,51 @@ class SheerkaRuleManager(BaseService): return return_value.body.body.concept return None + + +class ReteConditionExprVisitor(ExpressionVisitor): + + def __init__(self, context): + self.context = context + self.var_counter = 0 + self.variables = {} + self.res = [] + + def add_variable(self, target): + var_name = f"__x_{self.var_counter:02}__" + self.var_counter += 1 + self.variables[target] = var_name + return var_name + + def init_variable_if_needed(self, node): + if node.name not in self.variables: + var_name = self.add_variable(node.name) + self.res.append(Condition(V(var_name), "__name__", node.name)) + + return V(self.variables[node.name]) + + def get_conditions(self, expr_node): + self.res.clear() + self.var_counter = 0 + self.variables.clear() + + self.visit(expr_node) + return AndConditions(self.res) + + def visit_VariableNode(self, expr_node): + var_name = self.init_variable_if_needed(expr_node) + if expr_node.attributes_str is not None: + self.res.append(Condition(var_name, expr_node.attributes_str, True)) + + def visit_AndNode(self, expr_node: AndNode): + for node in expr_node.parts: + self.visit(node) + + def visit_ComparisonNode(self, expr_node: ComparisonNode): + if isinstance(expr_node.left, VariableNode): + left = self.init_variable_if_needed(expr_node.left) + attr = expr_node.left.attributes_str or "__self__" + right = eval(get_text_from_tokens(expr_node.right.tokens)) + self.res.append(Condition(left, attr, right)) + else: + raise FailedToCompileError(expr_node) diff --git a/tests/core/test_SheerkaRuleManager.py b/tests/core/test_SheerkaRuleManager.py index ccc2424..f850996 100644 --- a/tests/core/test_SheerkaRuleManager.py +++ b/tests/core/test_SheerkaRuleManager.py @@ -7,20 +7,23 @@ from core.concept import Concept, DEFINITION_TYPE_DEF, DoNotResolve from core.global_symbols import RULE_COMPARISON_CONTEXT, NotFound, EVENT_RULE_DELETED from core.rule import Rule, ACTION_TYPE_PRINT, ACTION_TYPE_EXEC from core.sheerka.Sheerka import RECOGNIZED_BY_ID, RECOGNIZED_BY_NAME +from core.sheerka.services.SheerkaExecute import ParserInput from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager, FormatRuleActionParser, \ FormatAstRawText, FormatAstVariable, FormatAstSequence, FormatAstFunction, \ FormatRuleSyntaxError, FormatAstList, UnexpectedEof, FormatAstColor, RuleCompiledPredicate, FormatAstDict, \ FormatAstMulti, \ - PythonCodeEmitter, NoConditionFound, FormatAstNode + PythonCodeEmitter, NoConditionFound, FormatAstNode, ReteConditionExprVisitor from core.sheerka.services.sheerka_service import FailedToCompileError from core.tokenizer import Token, TokenKind from parsers.BaseNodeParser import SourceCodeWithConceptNode, SourceCodeNode +from parsers.BaseParser import ErrorSink +from parsers.ExpressionParser import ExpressionParser from parsers.PythonParser import PythonNode from sheerkarete.common import V from sheerkarete.conditions import Condition, AndConditions from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka -from tests.parsers.parsers_utils import CMV, CC, compare_with_test_object, get_test_obj +from tests.parsers.parsers_utils import CMV, CC, compare_with_test_object, get_test_obj, get_rete_conditions seq = FormatAstSequence raw = FormatAstRawText @@ -1007,6 +1010,39 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \ for k, v in vars(rule).items(): assert getattr(clone, k) == getattr(rule, k) + @pytest.mark.parametrize("expression, expected_as_str", [ + ( + "__ret", + ["#__x_00__|__name__|'__ret'"], + ), + ( + "__ret.status == True", + ["#__x_00__|__name__|'__ret'", "#__x_00__|status|True"], + ), + ( + "__ret.status", + ["#__x_00__|__name__|'__ret'", "#__x_00__|status|True"], + ), + ( + "__ret and __ret.status", + ["#__x_00__|__name__|'__ret'", "#__x_00__|status|True"], + ), + ]) + def test_i_can_get_rete_conditions(self, expression, expected_as_str): + sheerka, context, = self.init_test().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 + class TestSheerkaRuleManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): def test_rules_are_initialized_at_startup(self): diff --git a/tests/parsers/test_ExpressionParser.py b/tests/parsers/test_ExpressionParser.py index a36bac4..28b4ffe 100644 --- a/tests/parsers/test_ExpressionParser.py +++ b/tests/parsers/test_ExpressionParser.py @@ -54,7 +54,8 @@ class TestExpressionParser(TestUsingMemoryBasedSheerka): ("func(var.attr)", FN("func(", ")", [VAR("var.attr")])), ("func(var1.attr1 and var2.attr2)", FN("func(", ")", [AND(VAR("var1.attr1"), VAR("var2.attr2"))])), ("func(var1.attr1 > var2.attr2)", FN("func(", ")", [GT(VAR("var1.attr1"), VAR("var2.attr2"))])), - ("func1(var1) and func2(var2)", AND(FN("func1(", ")", [VAR("var1")]), FN("func2(", (")", 1), [VAR("var2")]))) + ("func1(var1) and func2(var2)", AND(FN("func1(", ")", [VAR("var1")]), FN("func2(", (")", 1), [VAR("var2")]))), + ("__ret", VAR("__ret")), ]) def test_i_can_parse_input(self, expression, expected): sheerka, context, parser, parser_input, error_sink = self.init_parser_with_source(expression)