Fixed #48 : RelationalExpressionParser: Implement relational operator parser
Fixed #49 : ExpressionParser: Implement ExpressionParser Fixed #50 : Implement ReteConditionExprVisitor Fixed #51 : Implement PythonConditionExprVisitor Fixed #52 : SheerkaConceptManager: I can get and set concept property
This commit is contained in:
@@ -5,7 +5,7 @@ from core.builtin_concepts import BuiltinConcepts
|
||||
from core.builtin_helpers import ensure_bnf
|
||||
from core.concept import PROPERTIES_TO_SERIALIZE, Concept, DEFINITION_TYPE_DEF, get_concept_attrs, \
|
||||
DEFINITION_TYPE_BNF
|
||||
from core.global_symbols import NotInit, NotFound
|
||||
from core.global_symbols import NotInit, NotFound, SyaAssociativity
|
||||
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager, NoModificationFound, ForbiddenAttribute, \
|
||||
UnknownAttribute, CannotRemoveMeta, ValueNotFound, ConceptIsReferenced, NoFirstTokenError
|
||||
from parsers.BnfNodeParser import Sequence, StrMatch, ConceptExpression, OrderedChoice, Optional, ZeroOrMore, OneOrMore, \
|
||||
@@ -320,13 +320,13 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
|
||||
def test_i_can_modify_add_a_property(self):
|
||||
sheerka, context, one, foo = self.init_concepts("one", Concept("foo", props={BuiltinConcepts.ISA: {"value"}}))
|
||||
|
||||
res = sheerka.modify_concept(context, foo, to_add={"props": {BuiltinConcepts.ISA: "value2",
|
||||
res = sheerka.modify_concept(context, foo, to_add={"props": {BuiltinConcepts.ISA: {"value2"},
|
||||
BuiltinConcepts.HASA: one}})
|
||||
|
||||
assert res.status
|
||||
assert sheerka.isinstance(res.body, BuiltinConcepts.NEW_CONCEPT)
|
||||
assert res.body.body.get_prop(BuiltinConcepts.ISA) == {"value", "value2"}
|
||||
assert res.body.body.get_prop(BuiltinConcepts.HASA) == {sheerka.new("one")}
|
||||
assert res.body.body.get_prop(BuiltinConcepts.ISA) == {"value2"}
|
||||
assert res.body.body.get_prop(BuiltinConcepts.HASA) == sheerka.new("one")
|
||||
|
||||
def test_i_can_modify_remove_a_property(self):
|
||||
sheerka, context, foo = self.init_concepts(
|
||||
@@ -382,7 +382,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
|
||||
|
||||
to_add = {"meta": {"body": "metadata value"},
|
||||
"variables": {"var_name": "default value"},
|
||||
"props": {BuiltinConcepts.ISA: bar}}
|
||||
"props": {BuiltinConcepts.ISA: {bar}}}
|
||||
|
||||
res = sheerka.modify_concept(context, foo, to_add)
|
||||
new_concept = res.body.body
|
||||
@@ -656,7 +656,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
|
||||
|
||||
to_add = {
|
||||
"meta": {"body": "a body"},
|
||||
"props": {BuiltinConcepts.ISA: "bar"},
|
||||
"props": {BuiltinConcepts.ISA: {"bar"}},
|
||||
"variables": {"c": "value"}
|
||||
}
|
||||
to_remove = {
|
||||
@@ -826,6 +826,40 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
|
||||
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
|
||||
assert sheerka.get_attr(foo, prop) == [bar, baz, qux]
|
||||
|
||||
def test_i_can_get_and_set_property(self):
|
||||
sheerka, context, foo, prop, bar = self.init_concepts("foo", "property", "bar")
|
||||
|
||||
foo_instance = sheerka.new(foo)
|
||||
res = sheerka.set_property(context, foo_instance, prop, bar)
|
||||
assert res.status
|
||||
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
|
||||
assert sheerka.get_property(foo_instance, prop) == bar
|
||||
|
||||
res = sheerka.set_property(context, foo_instance, BuiltinConcepts.ASSOCIATIVITY, SyaAssociativity.Left)
|
||||
assert res.status
|
||||
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
|
||||
assert sheerka.get_property(foo_instance, BuiltinConcepts.ASSOCIATIVITY) == SyaAssociativity.Left
|
||||
|
||||
res = sheerka.set_property(context, foo_instance, sheerka.new(BuiltinConcepts.ASSOCIATIVITY), SyaAssociativity.Right)
|
||||
assert res.status
|
||||
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
|
||||
assert sheerka.get_property(foo_instance, BuiltinConcepts.ASSOCIATIVITY) == SyaAssociativity.Right
|
||||
|
||||
# by default, the concept itself is not modified
|
||||
new_foo = sheerka.new(foo)
|
||||
not_found = sheerka.get_property(new_foo, prop)
|
||||
assert sheerka.isinstance(not_found, BuiltinConcepts.NOT_FOUND)
|
||||
assert not_found.body == {"#concept": new_foo, "#prop": prop}
|
||||
|
||||
# I can modify the concept itself
|
||||
another_foo_instance = sheerka.new(foo)
|
||||
res = sheerka.set_property(context, another_foo_instance, prop, bar, all_concepts=True)
|
||||
assert res.status
|
||||
assert sheerka.get_property(another_foo_instance, prop) == bar
|
||||
|
||||
new_foo = sheerka.new(foo)
|
||||
assert sheerka.get_property(new_foo, prop) == bar
|
||||
|
||||
def test_i_cannot_remove_a_concept_which_has_reference(self):
|
||||
sheerka, context, one, twenty_one = self.init_test().with_concepts(
|
||||
Concept("one"),
|
||||
|
||||
@@ -8,7 +8,7 @@ from core.rule import Rule, ACTION_TYPE_EXEC
|
||||
from core.sheerka.Sheerka import RECOGNIZED_BY_ID, RECOGNIZED_BY_NAME
|
||||
from core.sheerka.services.SheerkaEvaluateRules import SheerkaEvaluateRules, LOW_PRIORITY_RULES, DISABLED_RULES
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
from core.sheerka.services.SheerkaRuleManager import RuleCompiledPredicate, SheerkaRuleManager
|
||||
from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager, CompiledCondition
|
||||
from evaluators.PythonEvaluator import PythonEvaluator, Expando
|
||||
from parsers.PythonParser import PythonParser
|
||||
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
||||
@@ -39,6 +39,7 @@ class TestSheerkaEvaluateRules(TestUsingMemoryBasedSheerka):
|
||||
DISABLED_RULES: [r7]
|
||||
}
|
||||
|
||||
@pytest.mark.skip("Not ready for that")
|
||||
def test_i_can_evaluate_question_concept_rules(self):
|
||||
sheerka, context, concept, r1, r2, r3, r4, r5, r6, r7, r8, r9 = self.init_test().with_concepts(
|
||||
Concept("x equals y", body="x == y", pre="is_question()").def_var("x").def_var("y"),
|
||||
@@ -82,8 +83,8 @@ class TestSheerkaEvaluateRules(TestUsingMemoryBasedSheerka):
|
||||
|
||||
# create fake compiled predicates
|
||||
parser = PythonParser()
|
||||
my_rule.compiled_predicates = [
|
||||
RuleCompiledPredicate("my rule", None, PythonEvaluator.NAME, parser.parse(context, ParserInput(exp)), None)
|
||||
my_rule.compiled_conditions = [
|
||||
CompiledCondition(PythonEvaluator.NAME, parser.parse(context, ParserInput(exp)), set(), set(), None)
|
||||
for exp in predicates]
|
||||
my_rule.metadata.is_compiled = True
|
||||
my_rule.metadata.is_enabled = True
|
||||
@@ -94,6 +95,7 @@ class TestSheerkaEvaluateRules(TestUsingMemoryBasedSheerka):
|
||||
True: [my_rule],
|
||||
}
|
||||
|
||||
@pytest.mark.skip("Not ready for that")
|
||||
def test_i_can_evaluate_rules_when_concepts_are_questions(self):
|
||||
sheerka, context, isa, cat, crocodile, pet, r1, r2, r3 = self.init_test().with_concepts(
|
||||
Concept("x is a y", body="isa(x,y)", pre="is_question()").def_var("x").def_var("y"),
|
||||
@@ -188,6 +190,7 @@ class TestSheerkaEvaluateRules(TestUsingMemoryBasedSheerka):
|
||||
res = service.evaluate_rules(context, [rule], {"__ret": ret}, set())
|
||||
assert res == {True: [rule]}
|
||||
|
||||
@pytest.mark.skip("Not ready for that")
|
||||
def test_i_can_evaluate_concept_rules_when_same_name(self):
|
||||
sheerka, context, g1, g2, rule = self.init_test().with_concepts(
|
||||
Concept("greetings", definition="hello a", definition_type=DEFINITION_TYPE_DEF).def_var("a"),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,6 @@ class TestDefRuleEvaluator(TestUsingMemoryBasedSheerka):
|
||||
is_compiled=True,
|
||||
is_enabled=True)
|
||||
rule = res.body.body
|
||||
assert rule.compiled_predicates is not None
|
||||
assert rule.compiled_conditions is not None
|
||||
assert rule.compiled_action is not None
|
||||
assert rule.rete_disjunctions is not None
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import ast
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
from typing import Union, List
|
||||
|
||||
from core.builtin_concepts import ReturnValueConcept
|
||||
from core.builtin_helpers import CreateObjectIdentifiers
|
||||
@@ -18,7 +17,7 @@ from parsers.FunctionParser import FunctionNode
|
||||
from parsers.PythonParser import PythonNode
|
||||
from parsers.SyaNodeParser import SyaConceptParserHelper
|
||||
from sheerkarete.common import V
|
||||
from sheerkarete.conditions import Condition, AndConditions
|
||||
from sheerkarete.conditions import Condition, AndConditions, NegatedCondition, NegatedConjunctiveConditions
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -933,6 +932,16 @@ class FN:
|
||||
raise NotImplementedError(f"FN, {other=}")
|
||||
|
||||
|
||||
@dataclass()
|
||||
class NEGCOND:
|
||||
condition: str
|
||||
|
||||
|
||||
@dataclass()
|
||||
class NCCOND:
|
||||
conditions: List[str]
|
||||
|
||||
|
||||
comparison_type_mapping = {
|
||||
"EQ": ComparisonType.EQUALS,
|
||||
"NEQ": ComparisonType.NOT_EQUAlS,
|
||||
@@ -1295,10 +1304,10 @@ def resolve_test_concept(concept_map, hint):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def get_rete_conditions(*conditions_as_string):
|
||||
def get_rete_conditions(*conditions):
|
||||
"""
|
||||
Transform a list of string into a list of Condition (Rete conditions)
|
||||
:param conditions_as_string: conditions in the form 'identifier|attribute|value'
|
||||
:param conditions: conditions in the form 'identifier|attribute|value'
|
||||
when one argument starts with "#" it means that it's a variables
|
||||
ex : "#__x_00__|__name__|'__ret'" -> Condition(V('#__x_00__'), '__name__', '__ret')
|
||||
|
||||
@@ -1317,13 +1326,17 @@ def get_rete_conditions(*conditions_as_string):
|
||||
return int(obj)
|
||||
|
||||
res = []
|
||||
for as_string in conditions_as_string:
|
||||
if as_string.startswith("$"):
|
||||
fn_match = re.match(r"(?P<function>\w+)\s?\((?P<args>.+)\)", as_string[1:])
|
||||
as_dict = fn_match.groupdict()
|
||||
pass
|
||||
for cond in conditions:
|
||||
if isinstance(cond, Condition):
|
||||
res.append(cond)
|
||||
elif isinstance(cond, NEGCOND):
|
||||
inner_cond = get_rete_conditions(cond.condition).conditions[0]
|
||||
res.append(NegatedCondition(inner_cond.identifier, inner_cond.attribute, inner_cond.value))
|
||||
elif isinstance(cond, NCCOND):
|
||||
inner_conds = get_rete_conditions(*cond.conditions).conditions
|
||||
res.append(NegatedConjunctiveConditions(*inner_conds))
|
||||
else:
|
||||
parts = as_string.split("|")
|
||||
parts = cond.split("|")
|
||||
identifier = get_value(parts[0])
|
||||
attribute = parts[1]
|
||||
value = get_value(parts[2])
|
||||
|
||||
@@ -3,7 +3,7 @@ import pytest
|
||||
from core.builtin_concepts_ids import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
from core.sheerka.services.SheerkaRuleManager import RuleCompiledPredicate, FormatAstNode
|
||||
from core.sheerka.services.SheerkaRuleManager import FormatAstNode, CompiledCondition
|
||||
from core.tokenizer import Tokenizer, Keywords
|
||||
from core.utils import tokens_are_matching
|
||||
from parsers.BaseCustomGrammarParser import KeywordNotFound, NameNode, SyntaxErrorNode
|
||||
@@ -80,9 +80,9 @@ class TestDefRuleParser(TestUsingMemoryBasedSheerka):
|
||||
assert len(parsed.tokens) == 2
|
||||
assert tokens_are_matching(parsed.tokens[Keywords.WHEN], Tokenizer("when True"))
|
||||
assert tokens_are_matching(parsed.tokens[Keywords.THEN], Tokenizer("then answer('that is true')"))
|
||||
assert isinstance(parsed.when, list)
|
||||
assert len(parsed.when) == 1
|
||||
assert isinstance(parsed.when[0], RuleCompiledPredicate)
|
||||
assert isinstance(parsed.python, list)
|
||||
assert len(parsed.python) == 1
|
||||
assert isinstance(parsed.python[0], CompiledCondition)
|
||||
assert sheerka.isinstance(parsed.then, BuiltinConcepts.RETURN_VALUE)
|
||||
|
||||
def test_i_can_parse_simple_format_rule_definition(self):
|
||||
@@ -100,9 +100,9 @@ class TestDefRuleParser(TestUsingMemoryBasedSheerka):
|
||||
assert len(parsed.tokens) == 2
|
||||
assert tokens_are_matching(parsed.tokens[Keywords.WHEN], Tokenizer("when True"))
|
||||
assert tokens_are_matching(parsed.tokens[Keywords.PRINT], Tokenizer("print hello world!"))
|
||||
assert isinstance(parsed.when, list)
|
||||
assert len(parsed.when) == 1
|
||||
assert isinstance(parsed.when[0], RuleCompiledPredicate)
|
||||
assert isinstance(parsed.python, list)
|
||||
assert len(parsed.python) == 1
|
||||
assert isinstance(parsed.python[0], CompiledCondition)
|
||||
assert isinstance(parsed.print, FormatAstNode)
|
||||
|
||||
def test_i_can_parse_exec_rule_with_name(self):
|
||||
@@ -121,36 +121,38 @@ class TestDefRuleParser(TestUsingMemoryBasedSheerka):
|
||||
assert len(parsed.tokens) == 2
|
||||
assert tokens_are_matching(parsed.tokens[Keywords.WHEN], Tokenizer("when True"))
|
||||
assert tokens_are_matching(parsed.tokens[Keywords.THEN], Tokenizer("then answer('that is true')"))
|
||||
assert isinstance(parsed.when, list)
|
||||
assert len(parsed.when) == 1
|
||||
assert isinstance(parsed.when[0], RuleCompiledPredicate)
|
||||
assert isinstance(parsed.python, list)
|
||||
assert len(parsed.python) == 1
|
||||
assert isinstance(parsed.python[0], CompiledCondition)
|
||||
assert sheerka.isinstance(parsed.then, BuiltinConcepts.RETURN_VALUE)
|
||||
|
||||
@pytest.mark.skip("Not ready for that")
|
||||
def test_when_is_parsed_in_the_context_of_a_question(self):
|
||||
sheerka, context, parser = self.init_parser()
|
||||
|
||||
text = "when foo is a bar print hello world"
|
||||
res = parser.parse(context, ParserInput(text))
|
||||
format_rule = res.body.body
|
||||
rules = format_rule.when
|
||||
rules = format_rule.python
|
||||
|
||||
assert res.status
|
||||
assert len(rules) == 1
|
||||
assert isinstance(rules[0], RuleCompiledPredicate)
|
||||
assert rules[0].predicate.body.body.get_metadata().pre == "is_question()"
|
||||
assert isinstance(rules[0], CompiledCondition)
|
||||
assert rules[0].return_value.body.body.get_metadata().pre == "is_question()"
|
||||
|
||||
@pytest.mark.skip("Not ready for that")
|
||||
def test_when_can_support_multiple_possibilities_when_question_only(self):
|
||||
sheerka, context, parser = self.init_parser()
|
||||
|
||||
text = "when foo is good print hello world"
|
||||
res = parser.parse(context, ParserInput(text))
|
||||
format_rule = res.body.body
|
||||
rules = format_rule.when
|
||||
rules = format_rule.python
|
||||
|
||||
assert res.status
|
||||
assert len(rules) == 2
|
||||
assert rules[0].predicate.body.body.get_metadata().name == "a is good"
|
||||
assert rules[1].predicate.body.body.get_metadata().name == "b is good"
|
||||
assert rules[0].return_value.body.body.get_metadata().name == "a is good"
|
||||
assert rules[1].return_value.body.body.get_metadata().name == "b is good"
|
||||
|
||||
@pytest.mark.parametrize("text, error", [
|
||||
("def", [KeywordNotFound(None, keywords=['rule'])]),
|
||||
@@ -175,7 +177,7 @@ class TestDefRuleParser(TestUsingMemoryBasedSheerka):
|
||||
assert not_for_me.reason == error
|
||||
|
||||
@pytest.mark.parametrize("text, expected_error", [
|
||||
("when x x print 'hello world'", BuiltinConcepts.TOO_MANY_ERRORS),
|
||||
("when x x = False print 'hello world'", BuiltinConcepts.ERROR),
|
||||
|
||||
])
|
||||
def test_i_can_detect_errors(self, text, expected_error):
|
||||
|
||||
@@ -95,10 +95,13 @@ class TestExpressionParser(TestUsingMemoryBasedSheerka):
|
||||
@pytest.mark.parametrize("expression, expected", [
|
||||
("ret.status in ('a', 1 , func())", "new_var in ('a', 1 , func())"),
|
||||
("ret.status not in ('a', 1 , func())", "new_var not in ('a', 1 , func())"),
|
||||
("ret.status == 10", "new_var == 10"),
|
||||
("ret.status == 'a'", "new_var == 'a'"),
|
||||
|
||||
])
|
||||
def test_i_can_rebuild_source(self, expression, expected):
|
||||
sheerka, context, parser, parser_input, error_sink = self.init_parser_with_source(expression)
|
||||
parsed = parser.parse_input(context, parser_input, error_sink)
|
||||
|
||||
assert ComparisonNode.rebuild_source("new_var", parsed.comp, parsed.right.get_source()) == expected
|
||||
new_source = ComparisonNode.rebuild_source("new_var", parsed.comp, parsed.right.get_source())
|
||||
assert new_source == expected
|
||||
|
||||
@@ -10,7 +10,7 @@ from sheerkarete.common import V, WME, ReteToken
|
||||
from sheerkarete.conditions import Condition, NegatedCondition, AndConditions
|
||||
from sheerkarete.join_node import JoinNode
|
||||
from sheerkarete.negative_node import NegativeNode
|
||||
from sheerkarete.network import ReteNetwork, FACT_ID
|
||||
from sheerkarete.network import ReteNetwork, FACT_ID, FACT_NAME, FACT_SELF, FactObj
|
||||
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
||||
from tests.sheerkarete.RuleForTestingRete import RuleForTestingRete
|
||||
|
||||
@@ -247,6 +247,25 @@ class TestReteNetwork(TestUsingMemoryBasedSheerka):
|
||||
assert matches[0].pnode.rules == [rule]
|
||||
assert network.facts == {'f-00000': ret}
|
||||
|
||||
def test_i_can_add_primitive(self):
|
||||
network = ReteNetwork()
|
||||
rule = RuleForTestingRete(AndConditions([Condition(V("a"), FACT_NAME, "a"),
|
||||
Condition(V("a"), FACT_SELF, 10),
|
||||
]))
|
||||
network.add_rule(rule)
|
||||
network.add_obj("a", 10)
|
||||
|
||||
assert network.working_memory == {
|
||||
WME("f-00000", "__name__", "a"),
|
||||
WME("f-00000", "__self__", 10),
|
||||
}
|
||||
|
||||
# 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': FactObj(10)}
|
||||
|
||||
def test_i_can_distinguish_objects_with_different_value(self):
|
||||
network = ReteNetwork()
|
||||
rule = RuleForTestingRete(AndConditions([
|
||||
|
||||
Reference in New Issue
Block a user