Fixed #49 : working

This commit is contained in:
2021-03-18 18:18:19 +01:00
parent e1a0b2f46e
commit 6a8011ec12
3 changed files with 144 additions and 14 deletions
@@ -1123,12 +1123,22 @@ class SheerkaRuleManager(BaseService):
class ReteConditionExprVisitor(ExpressionVisitor): class ReteConditionExprVisitor(ExpressionVisitor):
"""
From an ExprNode, construct the list of Rete condition that can be used in the ReteNetwork
"""
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
self.var_counter = 0 self.var_counter = 0
self.variables = {} 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): def add_variable(self, target):
var_name = f"__x_{self.var_counter:02}__" var_name = f"__x_{self.var_counter:02}__"
self.var_counter += 1 self.var_counter += 1
@@ -1165,17 +1175,25 @@ class ReteConditionExprVisitor(ExpressionVisitor):
conditions.append(Condition(root, attr, variable)) conditions.append(Condition(root, attr, variable))
return variable return variable
def get_conditions(self, expr_node): def visit_VariableNode(self, expr_node: VariableNode):
self.var_counter = 0 if expr_node.attributes_str is None and not expr_node.name.startswith("__"):
self.variables.clear() # 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 = [] conditions = []
var_name, attr = self.init_or_get_variable_from_name(expr_node.unpack(), 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)) conditions.append(Condition(var_name, attr, True))
return conditions return conditions
@@ -1277,3 +1295,7 @@ class ReteConditionExprVisitor(ExpressionVisitor):
conditions.extend(res) conditions.extend(res)
else: else:
conditions.append(Condition(left, attr, value)) conditions.append(Condition(left, attr, value))
class PythonConditionExprVisitor(ExpressionVisitor):
pass
+102 -1
View File
@@ -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( sheerka, context, greetings, foo = self.init_test().with_concepts(
Concept("greetings", definition="hello a", definition_type=DEFINITION_TYPE_DEF).def_var("a"), Concept("greetings", definition="hello a", definition_type=DEFINITION_TYPE_DEF).def_var("a"),
Concept("foo"), Concept("foo"),
@@ -1185,6 +1185,107 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
matches = list(network.matches) matches = list(network.matches)
assert len(matches) == 1 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): class TestSheerkaRuleManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
def test_rules_are_initialized_at_startup(self): def test_rules_are_initialized_at_startup(self):
+12 -5
View File
@@ -1,4 +1,5 @@
import ast import ast
import re
from dataclasses import dataclass from dataclasses import dataclass
from typing import Union from typing import Union
@@ -1317,12 +1318,18 @@ def get_rete_conditions(*conditions_as_string):
res = [] res = []
for as_string in conditions_as_string: for as_string in conditions_as_string:
parts = as_string.split("|") if as_string.startswith("$"):
identifier = get_value(parts[0]) fn_match = re.match(r"(?P<function>\w+)\s?\((?P<args>.+)\)", as_string[1:])
attribute = parts[1] as_dict = fn_match.groupdict()
value = get_value(parts[2]) 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) return AndConditions(res)