Fixed #49 : working

This commit is contained in:
2021-03-18 11:49:48 +01:00
parent 27bc6c4ba1
commit 36515aebb7
5 changed files with 153 additions and 63 deletions
+81 -24
View File
@@ -6,7 +6,7 @@ from typing import Union, Set, List
from cache.Cache import Cache from cache.Cache import Cache
from cache.ListIfNeededCache import ListIfNeededCache from cache.ListIfNeededCache import ListIfNeededCache
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept 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.concept import Concept
from core.global_symbols import EVENT_RULE_PRECEDENCE_MODIFIED, RULE_COMPARISON_CONTEXT, NotFound, ErrorObj, \ 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
@@ -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.Sheerka import RECOGNIZED_BY_NAME, RECOGNIZED_BY_ID
from core.sheerka.services.sheerka_service import BaseService, FailedToCompileError from core.sheerka.services.sheerka_service import BaseService, FailedToCompileError
from core.tokenizer import Keywords, TokenKind, Token, IterParser 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.ConceptEvaluator import ConceptEvaluator
from evaluators.PythonEvaluator import PythonEvaluator, Expando from evaluators.PythonEvaluator import PythonEvaluator, Expando
from parsers.BaseExpressionParser import AndNode, ExpressionVisitor, VariableNode, ComparisonNode, FunctionNode from parsers.BaseExpressionParser import AndNode, ExpressionVisitor, VariableNode, ComparisonNode, FunctionNode
@@ -1134,22 +1134,34 @@ class ReteConditionExprVisitor(ExpressionVisitor):
self.variables[target] = var_name self.variables[target] = var_name
return 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: if node.name not in self.variables:
var_name = self.add_variable(node.name) var_name = self.add_variable(node.name)
conditions.append(Condition(V(var_name), "__name__", 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}" path = f"{node.name}.{node.attributes_str}"
if path in self.variables: if path in self.variables:
return self.variables[path] 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) var_name = self.add_variable(path)
variable = V(var_name) variable = V(var_name)
conditions.append(Condition(root, node.attributes_str, variable)) conditions.append(Condition(root, attr, variable))
return variable return variable
def get_conditions(self, expr_node): def get_conditions(self, expr_node):
@@ -1161,9 +1173,9 @@ class ReteConditionExprVisitor(ExpressionVisitor):
def visit_VariableNode(self, expr_node): def visit_VariableNode(self, expr_node):
conditions = [] 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: 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 return conditions
def visit_AndNode(self, expr_node: AndNode): def visit_AndNode(self, expr_node: AndNode):
@@ -1176,29 +1188,74 @@ class ReteConditionExprVisitor(ExpressionVisitor):
def visit_ComparisonNode(self, expr_node: ComparisonNode): def visit_ComparisonNode(self, expr_node: ComparisonNode):
if isinstance(expr_node.left, VariableNode): if isinstance(expr_node.left, VariableNode):
conditions = [] conditions = []
left = self.init_or_get_variable_from_name(expr_node.left, conditions) left, attr = 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)) res = evaluate(self.context,
conditions.append(Condition(left, attr, right)) 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 return conditions
else: else:
raise FailedToCompileError([expr_node]) raise FailedToCompileError([expr_node])
def visit_FunctionNode(self, expr_node: FunctionNode): def visit_FunctionNode(self, expr_node: FunctionNode):
if expr_node.first.value == "recognize(": 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 = [] conditions = []
body_var = self.init_or_get_variable_from_attr(source, conditions) variable = self.init_or_get_variable_from_attr(variable_path, conditions)
conditions.append(Condition(body_var, "__is_concept__", True)) conditions.append(Condition(variable, "__is_concept__", True))
if isinstance(target, VariableNode):
conditions.append(Condition(body_var, "name", target.name)) 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: else:
concept_key, concept_id = unstr_concept(target.value) conditions.append(Condition(variable, "key", concept_found.key))
if concept_id:
conditions.append(Condition(body_var, "id", concept_id))
elif concept_key:
conditions.append(Condition(body_var, "name", concept_key))
return conditions return conditions
+14 -2
View File
@@ -4,7 +4,7 @@ from typing import List, Tuple, Union
from core.builtin_concepts_ids import BuiltinConcepts from core.builtin_concepts_ids import BuiltinConcepts
from core.sheerka.services.SheerkaExecute import ParserInput from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import Token, TokenKind, Tokenizer, LexerError 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.BaseNodeParser import UnrecognizedTokensNode
from parsers.BaseParser import Node, ParsingError, BaseParser, ErrorSink, UnexpectedTokenParsingError from parsers.BaseParser import Node, ParsingError, BaseParser, ErrorSink, UnexpectedTokenParsingError
@@ -33,7 +33,7 @@ class ParenthesisMismatchError(ParsingError):
token: Token token: Token
@dataclass @dataclass(init=False)
class ExprNode(Node): class ExprNode(Node):
""" """
Base ExprNode Base ExprNode
@@ -43,6 +43,12 @@ class ExprNode(Node):
end: int # index of the last token end: int # index of the last token
tokens: List[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): def eval(self, obj):
return True return True
@@ -61,6 +67,12 @@ class ExprNode(Node):
def __hash__(self): def __hash__(self):
return hash((self.start, self.end)) 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): class NameExprNode(ExprNode):
def __init__(self, start, end, tokens): def __init__(self, start, end, tokens):
+10 -1
View File
@@ -3,10 +3,13 @@ from __future__ import annotations
from itertools import product from itertools import product
from typing import TYPE_CHECKING, Generator, Union from typing import TYPE_CHECKING, Generator, Union
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept from core.concept import Concept
from core.global_symbols import NotInit from core.global_symbols import NotInit
from core.rule import Rule, ACTION_TYPE_PRINT from core.rule import Rule, ACTION_TYPE_PRINT
from core.utils import as_bag from core.utils import as_bag
from evaluators.PythonEvaluator import Expando
from sheerkapickle.utils import is_primitive
from sheerkarete.alpha import AlphaMemory from sheerkarete.alpha import AlphaMemory
from sheerkarete.beta import ReteNode, BetaMemory from sheerkarete.beta import ReteNode, BetaMemory
from sheerkarete.bind_node import BindNode from sheerkarete.bind_node import BindNode
@@ -413,7 +416,13 @@ class ReteNetwork:
else: else:
try: try:
value = getattr(obj, attribute) 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: except AttributeError:
pass pass
+18 -35
View File
@@ -1085,40 +1085,10 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
"#__x_01__|__is_concept__|True", "#__x_01__|__is_concept__|True",
"#__x_01__|name|'greetings'"] "#__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 by name and add other conditions (str)",
"recognize(__ret.body, greetings) and __ret.body.a == 'my friend'", "recognize(__ret.body, greetings) and __ret.body.a == 'my friend'",
"foo", "my friend",
["#__x_00__|__name__|'__ret'", ["#__x_00__|__name__|'__ret'",
"#__x_00__|body|#__x_01__", "#__x_00__|body|#__x_01__",
"#__x_01__|__is_concept__|True", "#__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 by name and add other conditions (sheerka)",
"recognize(__ret.body, greetings) and __ret.body.a == sheerka", "recognize(__ret.body, greetings) and __ret.body.a == sheerka",
"foo", "sheerka",
["#__x_00__|__name__|'__ret'", ["#__x_00__|__name__|'__ret'",
"#__x_00__|body|#__x_01__", "#__x_00__|body|#__x_01__",
"#__x_01__|__is_concept__|True", "#__x_01__|__is_concept__|True",
"#__x_01__|name|'greetings'", "#__x_01__|name|'greetings'",
"#__x_01__|a|'kodjo'"] "#__x_01__|a|'__sheerka__'"]
), ),
( (
"recognize by name and add other conditions (concept)", "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_00__|body|#__x_01__",
"#__x_01__|__is_concept__|True", "#__x_01__|__is_concept__|True",
"#__x_01__|name|'greetings'", "#__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): 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( 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 rule.rete_disjunctions = conditions
network.add_rule(rule) 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) to_recognize = sheerka.new_from_template(greetings, greetings.key, a=variable)
network.add_obj("__ret", ReturnValueConcept("Test", True, to_recognize)) network.add_obj("__ret", ReturnValueConcept("Test", True, to_recognize))
matches = list(network.matches) matches = list(network.matches)
+30 -1
View File
@@ -134,7 +134,7 @@ class TestReteNetwork(TestUsingMemoryBasedSheerka):
def test_adding_obj_when_requested_attribute_is_not_found(self): 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() network = ReteNetwork()
ret = ReturnValueConcept("test", True, "value") ret = ReturnValueConcept("test", True, "value")
@@ -218,6 +218,35 @@ class TestReteNetwork(TestUsingMemoryBasedSheerka):
assert matches[0].pnode.rules == [rule] assert matches[0].pnode.rules == [rule]
assert network.facts == {'f-00000': ret} 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): def test_i_can_distinguish_objects_with_different_value(self):
network = ReteNetwork() network = ReteNetwork()
rule = RuleForTestingRete(AndConditions([ rule = RuleForTestingRete(AndConditions([