diff --git a/src/core/sheerka/services/SheerkaRuleManager.py b/src/core/sheerka/services/SheerkaRuleManager.py index 21642f7..84bc77f 100644 --- a/src/core/sheerka/services/SheerkaRuleManager.py +++ b/src/core/sheerka/services/SheerkaRuleManager.py @@ -9,7 +9,7 @@ from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept from core.builtin_helpers import is_a_question, ensure_evaluated, expect_one, evaluate from core.concept import Concept from core.global_symbols import EVENT_RULE_PRECEDENCE_MODIFIED, RULE_COMPARISON_CONTEXT, NotFound, ErrorObj, \ - EVENT_RULE_CREATED, EVENT_RULE_DELETED + EVENT_RULE_CREATED, EVENT_RULE_DELETED, NotInit from core.rule import Rule, ACTION_TYPE_PRINT from core.sheerka.Sheerka import RECOGNIZED_BY_NAME, RECOGNIZED_BY_ID from core.sheerka.services.sheerka_service import BaseService, FailedToCompileError @@ -25,6 +25,7 @@ from sheerkarete.common import V from sheerkarete.conditions import AndConditions, Condition CONCEPTS_ONLY_PARSERS = ["ExactConcept", "Bnf", "Sya", "Sequence"] +CONDITIONS_VISITOR_EVALUATORS = ["Python", "Concept"] identifier_regex = re.compile(r"[\w _.]+") @@ -1134,31 +1135,31 @@ class ReteConditionExprVisitor(ExpressionVisitor): self.variables[target] = var_name return var_name - def init_or_get_variable_from_name(self, node: VariableNode, conditions): + def init_or_get_variable_from_name(self, variable_path: List[str], conditions): - if node.attributes: - left = node.attributes[:-1] - right = [node.attributes[-1]] + if len(variable_path) > 1: + left = variable_path[:-1] + right = [variable_path[-1]] while left: - var_name = node.name + "." + ".".join(left) + var_name = ".".join(left) if var_name in self.variables: return V(self.variables[var_name]), ".".join(right) right.insert(0, left.pop()) - if node.name not in self.variables: - var_name = self.add_variable(node.name) - conditions.append(Condition(V(var_name), "__name__", node.name)) + if variable_path[0] not in self.variables: + var_name = self.add_variable(variable_path[0]) + conditions.append(Condition(V(var_name), "__name__", variable_path[0])) - return V(self.variables[node.name]), node.attributes_str + return V(self.variables[variable_path[0]]), ".".join(variable_path[1:]) - def init_or_get_variable_from_attr(self, node: VariableNode, conditions): - path = f"{node.name}.{node.attributes_str}" + def init_or_get_variable_from_attr(self, variable_path: List[str], conditions): + path = ".".join(variable_path) if path in self.variables: return self.variables[path] - root, attr = self.init_or_get_variable_from_name(node, conditions) + root, attr = self.init_or_get_variable_from_name(variable_path, conditions) var_name = self.add_variable(path) variable = V(var_name) conditions.append(Condition(root, attr, variable)) @@ -1173,7 +1174,7 @@ class ReteConditionExprVisitor(ExpressionVisitor): def visit_VariableNode(self, expr_node): conditions = [] - var_name, attr = self.init_or_get_variable_from_name(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: conditions.append(Condition(var_name, attr, True)) return conditions @@ -1188,11 +1189,9 @@ class ReteConditionExprVisitor(ExpressionVisitor): def visit_ComparisonNode(self, expr_node: ComparisonNode): if isinstance(expr_node.left, VariableNode): conditions = [] - left, attr = self.init_or_get_variable_from_name(expr_node.left, conditions) - res = evaluate(self.context, expr_node.right.get_source(), - evaluators="all", # TODO: all is too much + evaluators=CONDITIONS_VISITOR_EVALUATORS, desc=None, eval_body=False, eval_where=False, @@ -1203,59 +1202,78 @@ class ReteConditionExprVisitor(ExpressionVisitor): if not res.status: return FailedToCompileError([f"Cannot recognize '{expr_node.right.get_source()}'"]) - value = res.value - if (isinstance(value, Expando) and value.get_name() == "sheerka" or - isinstance(value, Concept) and value.id == self.context.sheerka.id): - conditions.append(Condition(left, attr, "__sheerka__")) - else: - conditions.append(Condition(left, attr, res.value)) + self.add_to_condition(expr_node.left.unpack(), res.value, conditions) return conditions else: raise FailedToCompileError([expr_node]) def visit_FunctionNode(self, expr_node: FunctionNode): if expr_node.first.value == "recognize(": - return self.function_recognize_concept(expr_node.parameters[0].value, - expr_node.parameters[1].value, - [p.value for p in expr_node.parameters[2:]]) + if not isinstance(expr_node.parameters[0].value, VariableNode): + return FailedToCompileError([f"Cannot recognize '{expr_node.parameters[0].value}'"]) - def function_recognize_concept(self, variable_path, concept_to_recognize, parameters): + return self.recognize_concept(expr_node.parameters[0].value.unpack(), + expr_node.parameters[1].value, + {}) + + def recognize_concept(self, variable_path, concept_to_recognize, concept_variables: dict): """ Creates Rete conditions to recognize a concept :param variable_path: variable holding the information :param concept_to_recognize: concept to recognize - :param parameters: concept variables values + :param concept_variables: concept variables values :return: """ - concept_as_str = concept_to_recognize.get_source() - if not concept_as_str: - return FailedToCompileError([f"Missing concept in for {variable_path}"]) + if not isinstance(concept_to_recognize, Concept): + concept_as_str = concept_to_recognize.get_source() + if not concept_as_str: + return FailedToCompileError([f"Missing concept in for {variable_path}"]) - res = evaluate(self.context, - concept_as_str, - evaluators="all", # TODO: all is too much - desc=None, - eval_body=False, - eval_where=False, - is_question=False, - expect_success=False, - stm=None) - res = expect_one(self.context, res) + res = evaluate(self.context, + concept_as_str, + 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 not res.status: - return FailedToCompileError([f"Unknown concept {concept_as_str}"]) + if not res.status: + return FailedToCompileError([f"Unknown concept {concept_as_str}"]) + concept = res.body + else: + concept = concept_to_recognize - concept_found = res.body conditions = [] variable = self.init_or_get_variable_from_attr(variable_path, conditions) conditions.append(Condition(variable, "__is_concept__", True)) - if concept_found.get_hint(BuiltinConcepts.RECOGNIZED_BY) == RECOGNIZED_BY_NAME: - conditions.append(Condition(variable, "name", concept_found.name)) - elif concept_found.get_hint(BuiltinConcepts.RECOGNIZED_BY) == RECOGNIZED_BY_ID: - conditions.append(Condition(variable, "id", concept_found.id)) + if concept.get_hint(BuiltinConcepts.RECOGNIZED_BY) == RECOGNIZED_BY_NAME: + conditions.append(Condition(variable, "name", concept.name)) + elif concept.get_hint(BuiltinConcepts.RECOGNIZED_BY) == RECOGNIZED_BY_ID: + conditions.append(Condition(variable, "id", concept.id)) else: - conditions.append(Condition(variable, "key", concept_found.key)) + conditions.append(Condition(variable, "key", concept.key)) + concept_variables.update({k: v for k, v in concept.variables().items() if v is not NotInit}) + + for var_name, var_value in concept_variables.items(): + new_var_path = variable_path.copy() + new_var_path.append(var_name) + self.add_to_condition(new_var_path, var_value, conditions) return conditions + + def add_to_condition(self, var_path, value, conditions): + left, attr = self.init_or_get_variable_from_name(var_path, conditions) + + if (isinstance(value, Expando) and value.get_name() == "sheerka" or + isinstance(value, Concept) and value.id == self.context.sheerka.id): + conditions.append(Condition(left, attr, "__sheerka__")) + elif isinstance(value, Concept): + res = self.recognize_concept(var_path, value, {}) + conditions.extend(res) + else: + conditions.append(Condition(left, attr, value)) diff --git a/src/parsers/BaseExpressionParser.py b/src/parsers/BaseExpressionParser.py index 394b0b1..71e6881 100644 --- a/src/parsers/BaseExpressionParser.py +++ b/src/parsers/BaseExpressionParser.py @@ -287,6 +287,9 @@ class VariableNode(ExprNode): else: return self.name + def unpack(self): + return [self.name] + self.attributes + @dataclass class ComparisonNode(ExprNode): diff --git a/tests/core/test_SheerkaRuleManager.py b/tests/core/test_SheerkaRuleManager.py index 3875db9..b550a6f 100644 --- a/tests/core/test_SheerkaRuleManager.py +++ b/tests/core/test_SheerkaRuleManager.py @@ -1115,17 +1115,40 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \ "#__x_01__|name|'greetings'", "#__x_01__|a|#__x_02__", "#__x_02__|__is_concept__|True", - "#__x_02__|name|'foo'"] + "#__x_02__|key|'foo'"] ), ( "recognize by instance", "recognize(__ret.body, hello sheerka)", + "sheerka", + ["#__x_00__|__name__|'__ret'", + "#__x_00__|body|#__x_01__", + "#__x_01__|__is_concept__|True", + "#__x_01__|key|'hello __var__0'", + "#__x_01__|a|'__sheerka__'"] + ), + ( + "recognize by instance", + "recognize(__ret.body, hello 'my friend')", + "my friend", + ["#__x_00__|__name__|'__ret'", + "#__x_00__|body|#__x_01__", + "#__x_01__|__is_concept__|True", + "#__x_01__|key|'hello __var__0'", + "#__x_01__|a|'my friend'"] + ), + ( + "recognize by instance", + "recognize(__ret.body, 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|__sheerka__"] + "#__x_01__|a|#__x_02__", + "#__x_02__|__is_concept__|True", + "#__x_02__|key|'foo'", + ] ), ])