Fixed #131 : Implement ExprToConditions

Fixed #130 : ArithmeticOperatorParser
Fixed #129 : python_wrapper : create_namespace
Fixed #128 : ExpressionParser: Cannot parse func(x) infixed concept 'xxx'
This commit is contained in:
2021-10-13 16:06:57 +02:00
parent a61a1c0d2b
commit 89e1f20975
76 changed files with 5867 additions and 3206 deletions
@@ -0,0 +1,500 @@
import ast
from dataclasses import dataclass
import pytest
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept
from core.sheerka.services.SheerkaEvaluateRules import SheerkaEvaluateRules
from core.sheerka.services.SheerkaExecute import ParserInput
from core.sheerka.services.SheerkaRuleManager import CompiledCondition
from evaluators.PythonEvaluator import PythonEvaluator
from parsers.BaseParser import ErrorSink
from parsers.ExpressionParser import ExpressionParser
from parsers.PythonParser import PythonNode
from sheerkapython.ExprToConditions import ExprToConditionsVisitor
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.core.test_SheerkaRuleManager import PYTHON_EVALUATOR_NAME
@dataclass
class Obj:
value: object
def __eq__(self, other):
return isinstance(other, Obj) and self.value == other.value
def __hash__(self):
return hash(self.value)
cmap = {
"one": Concept("one", body="1"),
"two": Concept("two", body="2"),
"three": Concept("three", body="3"),
"twenties": Concept("twenties", definition="'twenty' (one|two)=unit", body="20 + unit").def_var("unit"),
"equals": Concept("x equals y", pre="is_question()", body="x == y").def_var("x").def_var("y"),
"isa1": Concept("x is a y", pre="is_question()", body="isa(x, y)").def_var("x").def_var("y"),
"isa2": Concept("x is a y", pre="is_question()", body="isinstance(x, y)").def_var("x").def_var("y"),
"isan1": Concept("x is an y", pre="is_question()", body="isa(x , y)").def_var("x").def_var("y"),
"isan2": Concept("x is an y", body="set_isa(x, y)").def_var("x").def_var("y"),
"foo": Concept("foo"),
"bar": Concept("bar"),
"baz": Concept("baz"),
}
class TestExprToConditionsVisitor(TestUsingMemoryBasedSheerka):
shared_ontology = None
@classmethod
def setup_class(cls):
instance = cls()
init_test_helper = instance.init_test(cache_only=False, ontology="#TestExprToConditionsVisitor#")
sheerka, context, *updated = init_test_helper.with_concepts(*cmap.values(), create_new=True).unpack()
for i, concept_name in enumerate(cmap):
cmap[concept_name] = updated[i]
global_context = instance.get_context(sheerka, global_truth=True)
sheerka.set_isa(global_context, cmap["baz"], cmap["foo"])
cls.shared_ontology = sheerka.get_ontology(context)
sheerka.pop_ontology(context)
def initialize_test(self, concepts_map=None):
if concepts_map is None:
sheerka, context = self.init_test().unpack()
sheerka.add_ontology(context, self.shared_ontology)
else:
sheerka, context, *updated = super().init_test().with_concepts(*concepts_map.values(),
create_new=True).unpack()
for i, concept_name in enumerate(concepts_map):
concepts_map[concept_name] = updated[i]
return sheerka, context
@staticmethod
def evaluate_conditions(context, conditions, namespace):
with context.push(BuiltinConcepts.EXEC_CODE, None) as sub_context:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
sub_context.deactivate_push()
evaluation_service = sub_context.sheerka.services[SheerkaEvaluateRules.NAME]
return evaluation_service.evaluate_conditions(sub_context, conditions, namespace)
@staticmethod
def get_conditions_from_expression(context, expression, parser=None):
parser = parser or ExpressionParser(old_style=False)
error_sink = ErrorSink()
parser_input = ParserInput(expression)
parser.reset_parser_input(parser_input, error_sink)
parsed = parser.parse_input(context, parser_input, error_sink)
assert not error_sink.has_error
known_variables = parser.known_variables if hasattr(parser, "known_variables") else None
visitor = ExprToConditionsVisitor(context, known_variables=known_variables)
conditions = visitor.get_conditions(parsed)
return conditions
@staticmethod
def validate_condition(context, expression, condition, e_code, e_objects, e_variables, e_not_variables):
sheerka = context.sheerka
# check what was compiled
if e_code is None:
# manage cases where we only check for variable existence
assert condition.evaluator_type is None
assert condition.return_value is None
else:
ast_ = ast.parse(e_code, "<source>", 'exec' if "\n" in e_code else 'eval')
expected_python_node = PythonNode(e_code, ast_, expression)
assert condition.evaluator_type == PYTHON_EVALUATOR_NAME
assert sheerka.isinstance(condition.return_value, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(condition.return_value) == expected_python_node
# check the objects
if e_objects is not None:
resolved_objects = {k: v.id for k, v in condition.objects.items()}
resolved_expected_objects = {k: cmap[v].id for k, v in e_objects.items()}
assert resolved_objects == resolved_expected_objects
# check that variables detected
if e_variables is not None:
assert condition.variables == e_variables
if e_not_variables is not None:
assert condition.not_variables == e_not_variables
def run_test_cases(self, context, conditions, test_suite):
sheerka = context.sheerka
for test_data in test_suite:
namespace, expected_value = test_data
for k, v in namespace.items():
if isinstance(v, str):
try:
namespace[k] = self.evaluate_from_source(context, v)
except:
pass
res = self.evaluate_conditions(context, conditions, namespace)
value = sheerka.objvalue(res[0].body) if res else False
assert value == expected_value
@staticmethod
def evaluate_condition(context, expression, condition, objects):
with context.push("Testing conditions SheerkaRuleManagerRulesCompilation", expression) as sub_context:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
sub_context.sheerka.add_many_to_short_term_memory(sub_context, objects)
evaluator = PythonEvaluator()
for c in condition.concepts_to_reset:
c.get_hints().is_evaluated = False
return evaluator.eval(sub_context, condition.return_value)
@pytest.mark.parametrize("expression, e_code, e_variables, e_not_variables, test_suite", [
("var", None, {"var"}, set(), [({"var": "value"}, True), ({}, False)]),
("True", "True", set(), set(), []),
("var.value", None, {"var.value"}, set(), []),
("not var", None, set(), {"var"}, [({"var": "value"}, False), ({}, True)]),
("not not var", None, {"var"}, set(), []),
("var and var2 and not var3", None, {"var", "var2"}, {"var3"}, []),
("var and var.value == 3", "var.value == 3", {"var"}, set(), [({"var": Obj(3)}, True)]),
("not v2 and v1.value == 3", "v1.value == 3", {"v1"}, {"v2"}, [({"v1": Obj(3)}, True), ({"v2": 0}, False)]),
("func(var)", "func(var)", {"var"}, set(), []),
("var in []", "var in []", {"var"}, set(), []),
("a + b", "a + b", {"a", "b"}, set(), []),
("foo x", "__o_00__", set(), set(), []), # foo is not a variable
("foo y", "evaluate_question(__o_00__, x=y)", {"y"}, set(), []), # foo is not a variable
("bar y", "call_concept(__o_00__, x=y)", {"y"}, set(), []), # bar is not a variable
])
def test_i_can_parse_and_manage_exists(self, expression, e_code, e_variables, e_not_variables, test_suite):
sheerka, context, foo, bar = self.init_test().with_concepts(
Concept("foo x", pre="is_question()").def_var("x"),
Concept("bar x").def_var("x"),
).unpack()
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert isinstance(conditions[0], CompiledCondition)
condition = conditions[0]
self.validate_condition(context, expression, condition, e_code, None, e_variables, e_not_variables)
self.run_test_cases(context, conditions, test_suite)
@pytest.mark.parametrize("expression, e_code, e_objects, e_variables, test_suite", [
# Concept
("1 equals 1", "evaluate_question(__o_00__)", {"__o_00__": "equals"}, set(), [({}, True)]),
("1 equals 2", "evaluate_question(__o_00__)", {"__o_00__": "equals"}, set(), [({}, False)]),
("one equals one", "evaluate_question(__o_00__)", {"__o_00__": "equals"}, set(), [({}, True)]),
("one equals two", "evaluate_question(__o_00__)", {"__o_00__": "equals"}, set(), [({}, False)]),
("one equals twenty one", "evaluate_question(__o_00__)", {"__o_00__": "equals"}, set(), [({}, False)]),
("self equals 1", "evaluate_question(__o_00__, x=self)", {"__o_00__": "equals"}, {"self"}, [
({"self": 1}, True),
({"self": 2}, False)]),
("x equals 1", "evaluate_question(__o_00__, x=x)", {"__o_00__": "equals"}, {"x"}, [
({"x": 1}, True),
({"x": 2}, False)]),
("one equals self", "evaluate_question(__o_00__, y=self)", {"__o_00__": "equals"}, {"self"}, [
({"self": "one"}, True),
({"self": "two"}, False)]),
("self equals twenty two", "evaluate_question(__o_00__, x=self)", {"__o_00__": "equals"}, {"self"}, [
({"self": "twenty two"}, True),
({"self": "two"}, False)]
),
("x equals 1 and y equals 2",
"evaluate_question(__o_00__, x=x) and evaluate_question(__o_01__, x=y)",
{"__o_00__": "equals", "__o_01__": "equals"},
{"x", "y"},
[({"x": 1, "y": 2}, True), ({"x": "0", "y": "0"}, False)]
),
("x equals y", "__o_00__", {"__o_00__": "equals"}, set(), []),
("func(self) equals twenty one",
"evaluate_question(__o_00__, x=func(self))",
{"__o_00__": "equals"},
{"self"},
[({"self": "twenty one", "func": lambda x: x}, True)]),
("func(self) equals twenty one + 1",
"evaluate_question(__o_01__, x=func(self), y=__o_00__ + 1)",
{"__o_01__": "equals", "__o_00__": "twenties"},
{"self"},
[({"self": "22", "func": lambda x: x}, True)]),
# equality
("a == 10", "a == 10", {}, {"a"}, [({"a": 10}, True), ({"a": 20}, False), ({}, False)]),
("__ret.status == True", "__ret.status == True", {}, {"__ret"}, []),
("self == sheerka", "is_sheerka(self)", {}, {"self"}, [
({"self": "sheerka"}, True),
({"self": "other"}, False),
]),
("self == BuiltinConcepts.TO_DICT", "self == BuiltinConcepts.TO_DICT", {}, {"self"}, [
({"self": "BuiltinConcepts.TO_DICT"}, True),
({"self": "other"}, False),
]),
# other Comparisons
("a + self > 10", "a + self > 10", {}, {"a", "self"}, [
({"a": 10, "self": 1}, True),
({"a": 10, "self": 0}, False),
]),
("10 < one + self", "10 < __o_00__ + self", {"__o_00__": "one"}, {"self"}, [
({"self": 10}, True),
({"self": 1}, False),
]),
("23 < twenty one + self", "23 < __o_00__ + self", {"__o_00__": "twenties"}, {"self"}, [
({"self": 10}, True),
({"self": 1}, False),
]),
("a equals b and c equals d",
"evaluate_question(__o_00__, x=a, y=b) and evaluate_question(__o_01__, x=c, y=d)",
{"__o_00__": "equals", "__o_01__": "equals"},
{"a", "b", "c", "d"},
[]),
# simple expressions
("True", "True", {}, set(), [({}, True)]),
("False", "False", {}, set(), [({}, False)]),
("10 + 5", "10 + 5", {}, set(), [({}, 15)]),
("a + self", "a + self", {}, {"a", "self"}, [({"a": 10, "self": 5}, 15)]),
("a + twenty one", "a + __o_00__", {"__o_00__": "twenties"}, {"a"}, [({"a": 10}, 31)]),
# functions
("isinstance('hello', str)", "isinstance('hello', str)", {}, set(), [({}, True)]),
("isinstance(a, str)", "isinstance(a, str)", {}, {"a"}, [({"a": "an_str"}, True), ({"a": 1}, False)]),
("f(BuiltinConcepts.TO_DICT)", "f(BuiltinConcepts.TO_DICT)", {}, set(), [({"f": lambda x: x}, "__TO_DICT")])
])
def test_i_can_parse(self, expression, e_code, e_objects, e_variables, test_suite):
sheerka, context = self.initialize_test()
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert isinstance(conditions[0], CompiledCondition)
condition = conditions[0]
self.validate_condition(context, expression, condition, e_code, e_objects, e_variables, None)
self.run_test_cases(context, conditions, test_suite)
def test_i_can_force_variable(self):
sheerka, context = self.initialize_test()
parser = ExpressionParser(known_variables={"one"})
conditions = self.get_conditions_from_expression(context, "one < two", parser)
python_source = conditions[0].return_value.body.body.source
assert python_source == "one < __o_00__"
resolved_objects = {k: v.id for k, v in conditions[0].objects.items()}
resolved_expected_objects = {k: cmap[v].id for k, v in {"__o_00__": "two"}.items()}
assert resolved_objects == resolved_expected_objects
def test_i_can_parse_when_variables_are_missing(self):
sheerka, context = self.initialize_test()
expression = "x equals 1"
e_code = "evaluate_question(__o_00__, x=x)"
e_objects = {"__o_00__": "equals"}
e_variables = {"x"}
test_suite = [({}, False), ({"y": 1}, False)]
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert isinstance(conditions[0], CompiledCondition)
condition = conditions[0]
self.validate_condition(context, expression, condition, e_code, e_objects, e_variables, None)
self.run_test_cases(context, conditions, test_suite)
def test_question_concept_is_chosen_other_non_question_concept(self):
sheerka, context = self.initialize_test()
expression = "a is an b"
e_code = "evaluate_question(__o_00__, x=a, y=b)"
e_objects = {"__o_00__": "isan1"}
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert isinstance(conditions[0], CompiledCondition)
condition = conditions[0]
self.validate_condition(context, expression, condition, e_code, e_objects, None, None)
def test_i_can_manage_when_multiple_concepts(self):
sheerka, context = self.initialize_test()
expression = "a is a b"
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 2
condition = conditions[0]
assert isinstance(condition, CompiledCondition)
e_code = "evaluate_question(__o_00__, x=a, y=b)"
e_objects = {"__o_00__": "isa1"}
e_variables = {"a", "b"}
self.validate_condition(context, expression, condition, e_code, e_objects, e_variables, None)
condition = conditions[1]
assert isinstance(condition, CompiledCondition)
e_code = "evaluate_question(__o_01__, x=a, y=b)"
e_objects = {"__o_01__": "isa2"}
e_variables = {"a", "b"}
self.validate_condition(context, expression, condition, e_code, e_objects, e_variables, None)
# testing
namespace = {"a": sheerka.new("foo"), "b": sheerka.new("bar")}
res = self.evaluate_conditions(context, conditions, namespace)
assert len(res) == 2
assert isinstance(res[0].value, bool) and not res[0].value
assert isinstance(res[1].value, bool) and not res[1].value
namespace = {"a": sheerka.new("foo"), "b": sheerka.new("foo")}
res = self.evaluate_conditions(context, conditions, namespace)
assert len(res) == 1
assert isinstance(res[0].value, bool) and res[0].value
namespace = {"a": sheerka.new("baz"), "b": sheerka.new("foo")}
res = self.evaluate_conditions(context, conditions, namespace)
assert len(res) == 1
assert isinstance(res[0].value, bool) and res[0].value
def test_i_can_manage_or_expressions(self):
sheerka, context = self.initialize_test()
expression = "isinstance(self, foo) or self is a bar"
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 3
condition = conditions[0]
assert isinstance(condition, CompiledCondition)
e_code = "isinstance(self, __o_00__)"
e_objects = {"__o_00__": "foo"}
e_variables = {"self"}
self.validate_condition(context, "isinstance(self, foo)", condition, e_code, e_objects, e_variables, None)
condition = conditions[1]
assert isinstance(condition, CompiledCondition)
e_code = "evaluate_question(__o_01__, x=self)"
e_objects = {"__o_01__": "isa1"}
e_variables = {"self"}
self.validate_condition(context, "self is a bar", condition, e_code, e_objects, e_variables, None)
condition = conditions[2]
assert isinstance(condition, CompiledCondition)
e_code = "evaluate_question(__o_02__, x=self)"
e_objects = {"__o_02__": "isa2"}
e_variables = {"self"}
self.validate_condition(context, "self is a bar", condition, e_code, e_objects, e_variables, None)
def test_i_can_manage_multiple_concepts_melt_with_and_expressions(self):
sheerka, context = self.initialize_test()
expression = "isinstance(self, foo) and self is a bar"
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 2
condition = conditions[0]
assert isinstance(condition, CompiledCondition)
e_code = "isinstance(self, __o_00__) and evaluate_question(__o_01__, x=self)"
e_objects = {"__o_00__": "foo", "__o_01__": "isa1"}
e_variables = {"self"}
self.validate_condition(context, expression, condition, e_code, e_objects, e_variables, None)
condition = conditions[1]
assert isinstance(condition, CompiledCondition)
e_code = "isinstance(self, __o_00__) and evaluate_question(__o_02__, x=self)"
e_objects = {"__o_00__": "foo", "__o_02__": "isa2"}
e_variables = {"self"}
self.validate_condition(context, expression, condition, e_code, e_objects, e_variables, None)
@pytest.mark.parametrize("expression, expected", [
("self is a 'foo'", {"x is a y"}),
("set self is a 'foo'", set()),
])
def test_i_can_get_concept_to_reset(self, expression, expected):
"""
When compiled conditions, sometimes there are concepts to reset between two usages
:param expression:
:param expected:
:return:
"""
concepts_map = {
"isa": Concept("x is a y", pre="is_question()").def_var("x").def_var("y"),
"set_isa": Concept("set x is a y").def_var("x").def_var("y"),
}
sheerka, context = self.initialize_test(concepts_map)
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert {c.name for c in conditions[0].concepts_to_reset} == expected
def test_i_can_reset_concepts_when_multiple_levels(self):
"""
When compiled conditions, sometimes there are concepts to reset between two usages
:return:
"""
sheerka, context, is_instance, is_int, is_integer = self.init_concepts(
Concept("x is an instance of y", pre="is_question()", body="isinstance(x, y)").def_var("x").def_var("y"),
Concept("x is a int", pre="is_question()", body="x is an instance of int").def_var("x"),
Concept("x is an integer", pre="is_question()", body="x is a int").def_var("x"),
)
expression = "self is an integer"
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert {c.name for c in conditions[0].concepts_to_reset} == {"x is an instance of y",
"x is a int",
"x is an integer"}
# So I can evaluate multiple times
res = self.evaluate_condition(context, expression, conditions[0], {'self': 10})
assert res.status
assert sheerka.objvalue(res.body)
res = self.evaluate_condition(context, expression, conditions[0], {'self': "string"})
assert res.status
assert not sheerka.objvalue(res.body)
def test_i_can_reset_concepts_when_multiple_levels_and_concept_node(self):
"""
When compiled conditions, sometimes there are concepts to reset between two usages
:return:
"""
# in this example, x + 2 is an int won't be parsed as an ExactNodeConcept, but as a ConceptNode
sheerka, context, is_int, is_integer = self.init_concepts(
Concept("x is a int", pre="is_question()", body="isinstance(x, int)").def_var("x"),
Concept("x is an integer", pre="is_question()", body="x + 2 is a int").def_var("x"),
create_new=True
)
expression = "self is an integer"
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert set(c.name for c in conditions[0].concepts_to_reset) == {"x is a int",
"x is an integer"}
# So I can evaluate multiple times
res = self.evaluate_condition(context, expression, conditions[0], {'self': 10})
assert res.status
assert sheerka.objvalue(res.body)
res = self.evaluate_condition(context, expression, conditions[0], {'self': "string"})
assert not res.status
def test_long_name_concepts_are_not_considered_as_variables(self):
sheerka, context, one, number = self.init_concepts(
"one",
"all numbers",
)
sheerka.set_isa(context, one, number)
expression = "all numbers < 5"
conditions = self.get_conditions_from_expression(context, expression)
assert len(conditions) == 1
assert conditions[0].return_value.body.body.source == '__o_00__ < 5'