Fixed #49 : working
This commit is contained in:
@@ -6,7 +6,7 @@ from typing import Union, Set, List
|
||||
from cache.Cache import Cache
|
||||
from cache.ListIfNeededCache import ListIfNeededCache
|
||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||
from core.builtin_helpers import is_a_question, ensure_evaluated, expect_one
|
||||
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
|
||||
@@ -14,7 +14,7 @@ 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
|
||||
from core.tokenizer import Keywords, TokenKind, Token, IterParser
|
||||
from core.utils import index_tokens, COLORS, get_text_from_tokens, unstr_concept
|
||||
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, FunctionNode
|
||||
@@ -1134,22 +1134,34 @@ class ReteConditionExprVisitor(ExpressionVisitor):
|
||||
self.variables[target] = var_name
|
||||
return var_name
|
||||
|
||||
def init_or_get_variable_from_name(self, node, conditions):
|
||||
def init_or_get_variable_from_name(self, node: VariableNode, conditions):
|
||||
|
||||
if node.attributes:
|
||||
left = node.attributes[:-1]
|
||||
right = [node.attributes[-1]]
|
||||
|
||||
while left:
|
||||
var_name = node.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))
|
||||
|
||||
return V(self.variables[node.name])
|
||||
return V(self.variables[node.name]), node.attributes_str
|
||||
|
||||
def init_or_get_variable_from_attr(self, node, conditions):
|
||||
def init_or_get_variable_from_attr(self, node: VariableNode, conditions):
|
||||
path = f"{node.name}.{node.attributes_str}"
|
||||
if path in self.variables:
|
||||
return self.variables[path]
|
||||
|
||||
root = self.init_or_get_variable_from_name(node, conditions)
|
||||
root, attr = self.init_or_get_variable_from_name(node, conditions)
|
||||
var_name = self.add_variable(path)
|
||||
variable = V(var_name)
|
||||
conditions.append(Condition(root, node.attributes_str, variable))
|
||||
conditions.append(Condition(root, attr, variable))
|
||||
return variable
|
||||
|
||||
def get_conditions(self, expr_node):
|
||||
@@ -1161,9 +1173,9 @@ class ReteConditionExprVisitor(ExpressionVisitor):
|
||||
|
||||
def visit_VariableNode(self, expr_node):
|
||||
conditions = []
|
||||
var_name = self.init_or_get_variable_from_name(expr_node, conditions)
|
||||
var_name, attr = self.init_or_get_variable_from_name(expr_node, conditions)
|
||||
if expr_node.attributes_str is not None:
|
||||
conditions.append(Condition(var_name, expr_node.attributes_str, True))
|
||||
conditions.append(Condition(var_name, attr, True))
|
||||
return conditions
|
||||
|
||||
def visit_AndNode(self, expr_node: AndNode):
|
||||
@@ -1176,29 +1188,74 @@ class ReteConditionExprVisitor(ExpressionVisitor):
|
||||
def visit_ComparisonNode(self, expr_node: ComparisonNode):
|
||||
if isinstance(expr_node.left, VariableNode):
|
||||
conditions = []
|
||||
left = self.init_or_get_variable_from_name(expr_node.left, conditions)
|
||||
attr = expr_node.left.attributes_str or "__self__"
|
||||
right = eval(get_text_from_tokens(expr_node.right.tokens))
|
||||
conditions.append(Condition(left, attr, right))
|
||||
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
|
||||
desc=None,
|
||||
eval_body=False,
|
||||
eval_where=False,
|
||||
is_question=False,
|
||||
expect_success=False,
|
||||
stm=None)
|
||||
res = expect_one(self.context, res)
|
||||
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))
|
||||
return conditions
|
||||
else:
|
||||
raise FailedToCompileError([expr_node])
|
||||
|
||||
def visit_FunctionNode(self, expr_node: FunctionNode):
|
||||
if expr_node.first.value == "recognize(":
|
||||
return self.function_recognize(expr_node.parameters[0].value, expr_node.parameters[1].value)
|
||||
return self.function_recognize_concept(expr_node.parameters[0].value,
|
||||
expr_node.parameters[1].value,
|
||||
[p.value for p in expr_node.parameters[2:]])
|
||||
|
||||
def function_recognize(self, source, target):
|
||||
def function_recognize_concept(self, variable_path, concept_to_recognize, parameters):
|
||||
"""
|
||||
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
|
||||
:return:
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
if not res.status:
|
||||
return FailedToCompileError([f"Unknown concept {concept_as_str}"])
|
||||
|
||||
concept_found = res.body
|
||||
conditions = []
|
||||
body_var = self.init_or_get_variable_from_attr(source, conditions)
|
||||
conditions.append(Condition(body_var, "__is_concept__", True))
|
||||
if isinstance(target, VariableNode):
|
||||
conditions.append(Condition(body_var, "name", target.name))
|
||||
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))
|
||||
else:
|
||||
concept_key, concept_id = unstr_concept(target.value)
|
||||
if concept_id:
|
||||
conditions.append(Condition(body_var, "id", concept_id))
|
||||
elif concept_key:
|
||||
conditions.append(Condition(body_var, "name", concept_key))
|
||||
conditions.append(Condition(variable, "key", concept_found.key))
|
||||
|
||||
return conditions
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import List, Tuple, Union
|
||||
from core.builtin_concepts_ids import BuiltinConcepts
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
from core.tokenizer import Token, TokenKind, Tokenizer, LexerError
|
||||
from core.utils import tokens_are_matching
|
||||
from core.utils import tokens_are_matching, get_text_from_tokens
|
||||
from parsers.BaseNodeParser import UnrecognizedTokensNode
|
||||
from parsers.BaseParser import Node, ParsingError, BaseParser, ErrorSink, UnexpectedTokenParsingError
|
||||
|
||||
@@ -33,7 +33,7 @@ class ParenthesisMismatchError(ParsingError):
|
||||
token: Token
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass(init=False)
|
||||
class ExprNode(Node):
|
||||
"""
|
||||
Base ExprNode
|
||||
@@ -43,6 +43,12 @@ class ExprNode(Node):
|
||||
end: int # index of the last token
|
||||
tokens: List[Token]
|
||||
|
||||
def __init__(self, start: int, end: int, tokens: List[Token]):
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.tokens = tokens
|
||||
self.source = None
|
||||
|
||||
def eval(self, obj):
|
||||
return True
|
||||
|
||||
@@ -61,6 +67,12 @@ class ExprNode(Node):
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end))
|
||||
|
||||
def get_source(self):
|
||||
if self.source is None:
|
||||
self.source = get_text_from_tokens(self.tokens)
|
||||
|
||||
return self.source
|
||||
|
||||
|
||||
class NameExprNode(ExprNode):
|
||||
def __init__(self, start, end, tokens):
|
||||
|
||||
@@ -3,10 +3,13 @@ from __future__ import annotations
|
||||
from itertools import product
|
||||
from typing import TYPE_CHECKING, Generator, Union
|
||||
|
||||
from core.builtin_concepts_ids import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
from core.global_symbols import NotInit
|
||||
from core.rule import Rule, ACTION_TYPE_PRINT
|
||||
from core.utils import as_bag
|
||||
from evaluators.PythonEvaluator import Expando
|
||||
from sheerkapickle.utils import is_primitive
|
||||
from sheerkarete.alpha import AlphaMemory
|
||||
from sheerkarete.beta import ReteNode, BetaMemory
|
||||
from sheerkarete.bind_node import BindNode
|
||||
@@ -413,7 +416,13 @@ class ReteNetwork:
|
||||
else:
|
||||
try:
|
||||
value = getattr(obj, attribute)
|
||||
inner_add_vme(name, fact_id, attribute, value)
|
||||
if (isinstance(value, Concept) and value.key == BuiltinConcepts.SHEERKA or
|
||||
isinstance(value, Expando) and value.get_name() == "sheerka"):
|
||||
value = "__sheerka__"
|
||||
if is_primitive(value):
|
||||
self.add_wme(WME(fact_id, attribute, value))
|
||||
else:
|
||||
inner_add_vme(name, fact_id, attribute, value)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -1085,40 +1085,10 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
|
||||
"#__x_01__|__is_concept__|True",
|
||||
"#__x_01__|name|'greetings'"]
|
||||
),
|
||||
(
|
||||
"recognize by name and variable sheerka",
|
||||
"recognize(__ret.body, greetings, a=sheerka)",
|
||||
"sheerka",
|
||||
["#__x_00__|__name__|'__ret'",
|
||||
"#__x_00__|body|#__x_01__",
|
||||
"#__x_01__|__is_concept__|True",
|
||||
"#__x_01__|name|'greetings'",
|
||||
"#__x_01__|a|'__sheerka__'"]
|
||||
),
|
||||
(
|
||||
"recognize by name and str variable",
|
||||
"recognize(__ret.body, greetings, a='my friend')",
|
||||
"'my friend'",
|
||||
["#__x_00__|__name__|'__ret'",
|
||||
"#__x_00__|body|#__x_01__",
|
||||
"#__x_01__|__is_concept__|True",
|
||||
"#__x_01__|name|'greetings'",
|
||||
"#__x_01__|a|'my friend'"]
|
||||
),
|
||||
(
|
||||
"recognize by name and concept variable",
|
||||
"recognize(__ret.body, greetings, a=foo)",
|
||||
"foo",
|
||||
["#__x_00__|__name__|'__ret'",
|
||||
"#__x_00__|body|#__x_01__",
|
||||
"#__x_01__|__is_concept__|True",
|
||||
"#__x_01__|name|'greetings'",
|
||||
"#__x_01__|a.name|'foo'"]
|
||||
),
|
||||
(
|
||||
"recognize by name and add other conditions (str)",
|
||||
"recognize(__ret.body, greetings) and __ret.body.a == 'my friend'",
|
||||
"foo",
|
||||
"my friend",
|
||||
["#__x_00__|__name__|'__ret'",
|
||||
"#__x_00__|body|#__x_01__",
|
||||
"#__x_01__|__is_concept__|True",
|
||||
@@ -1128,12 +1098,12 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
|
||||
(
|
||||
"recognize by name and add other conditions (sheerka)",
|
||||
"recognize(__ret.body, greetings) and __ret.body.a == sheerka",
|
||||
"foo",
|
||||
"sheerka",
|
||||
["#__x_00__|__name__|'__ret'",
|
||||
"#__x_00__|body|#__x_01__",
|
||||
"#__x_01__|__is_concept__|True",
|
||||
"#__x_01__|name|'greetings'",
|
||||
"#__x_01__|a|'kodjo'"]
|
||||
"#__x_01__|a|'__sheerka__'"]
|
||||
),
|
||||
(
|
||||
"recognize by name and add other conditions (concept)",
|
||||
@@ -1143,8 +1113,21 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
|
||||
"#__x_00__|body|#__x_01__",
|
||||
"#__x_01__|__is_concept__|True",
|
||||
"#__x_01__|name|'greetings'",
|
||||
"#__x_01__|a|'kodjo'"]
|
||||
"#__x_01__|a|#__x_02__",
|
||||
"#__x_02__|__is_concept__|True",
|
||||
"#__x_02__|name|'foo'"]
|
||||
),
|
||||
(
|
||||
"recognize by instance",
|
||||
"recognize(__ret.body, hello sheerka)",
|
||||
"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__"]
|
||||
),
|
||||
|
||||
])
|
||||
def test_i_can_get_rete_conditions_from_recognized(self, test_name, expression, variable_name, expected_as_str):
|
||||
sheerka, context, greetings, foo = self.init_test().with_concepts(
|
||||
@@ -1173,7 +1156,7 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
|
||||
rule.rete_disjunctions = conditions
|
||||
network.add_rule(rule)
|
||||
|
||||
variable = foo if variable_name == "foo" else variable_name
|
||||
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)
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestReteNetwork(TestUsingMemoryBasedSheerka):
|
||||
|
||||
def test_adding_obj_when_requested_attribute_is_not_found(self):
|
||||
"""
|
||||
When a rule with attribute constraint, we only add the requested attributes
|
||||
There is no error when an attribute does not exits
|
||||
"""
|
||||
network = ReteNetwork()
|
||||
ret = ReturnValueConcept("test", True, "value")
|
||||
@@ -218,6 +218,35 @@ class TestReteNetwork(TestUsingMemoryBasedSheerka):
|
||||
assert matches[0].pnode.rules == [rule]
|
||||
assert network.facts == {'f-00000': ret}
|
||||
|
||||
def test_i_can_add_obj_and_match_obj_when_value_is_sheerka(self):
|
||||
sheerka, context, greetings = self.init_concepts(
|
||||
Concept("greetings", definition="hello a", definition_type=DEFINITION_TYPE_DEF).def_var("a")
|
||||
)
|
||||
network = ReteNetwork()
|
||||
rule = RuleForTestingRete(AndConditions([Condition(V("ret"), "__name__", "__ret"),
|
||||
Condition(V("ret"), "body", V("body")),
|
||||
Condition(V("body"), "name", "greetings"),
|
||||
Condition(V("body"), "a", "__sheerka__"),
|
||||
]))
|
||||
network.add_rule(rule)
|
||||
|
||||
hello_concept = sheerka.new(greetings, a=sheerka)
|
||||
ret = ReturnValueConcept("test", True, hello_concept)
|
||||
network.add_obj("__ret", ret)
|
||||
|
||||
assert network.working_memory == {
|
||||
WME("f-00000", "__name__", "__ret"),
|
||||
WME("f-00000", "body", "f-00000.body"),
|
||||
WME("f-00000.body", "name", "greetings"),
|
||||
WME("f-00000.body", "a", "__sheerka__"),
|
||||
}
|
||||
|
||||
# sanity check that the WME produced match the condition
|
||||
matches = list(network.matches)
|
||||
assert len(matches) == 1
|
||||
assert matches[0].pnode.rules == [rule]
|
||||
assert network.facts == {'f-00000': ret}
|
||||
|
||||
def test_i_can_distinguish_objects_with_different_value(self):
|
||||
network = ReteNetwork()
|
||||
rule = RuleForTestingRete(AndConditions([
|
||||
|
||||
Reference in New Issue
Block a user