from dataclasses import dataclass import pytest from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept from core.concept import Concept from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import Tokenizer, TokenKind from parsers.BaseParser import UnexpectedEofNode, UnexpectedTokenErrorNode from parsers.ExpressionParser import PropertyEqualsNode, PropertyEqualsSequenceNode, PropertyContainsNode, AndNode, \ OrNode, NotNode, LambdaNode, IsaNode, NameExprNode, ExpressionParser, LeftPartNotFoundError, TrueifyVisitor from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka @dataclass class Obj: prop_a: object prop_b: object = None prop_c: object = None parent: object = None def n(value): return NameExprNode(Tokenizer(value, yield_eof=False)) class TestExpressionParser(TestUsingMemoryBasedSheerka): def init_parser(self): sheerka, context = self.init_concepts() parser = ExpressionParser() return sheerka, context, parser @pytest.mark.parametrize("expression, expected", [ ("one complicated expression", n("one complicated expression")), # ("function_call(a,b,c)", n("function_call(a,b,c)")), # ("one expression or another expression", OrNode(n("one expression"), n("another expression"))), # ("one expression and another expression", AndNode(n("one expression"), n("another expression"))), # ("one or two or three", OrNode(n("one"), n("two"), n("three"))), # ("one and two and three", AndNode(n("one"), n("two"), n("three"))), # ("one or two and three", OrNode(n("one"), AndNode(n("two"), n("three")))), # ("one and two or three", OrNode(AndNode(n("one"), n("two")), n("three"))), # ("one and (two or three)", AndNode(n("one"), OrNode(n("two"), n("three")))), ]) def test_i_can_parse_expression(self, expression, expected): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(expression)) wrapper = res.body expressions = res.body.body assert res.status assert sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) assert expressions == expected @pytest.mark.parametrize("expression, expected_errors", [ ("one or", [UnexpectedEofNode("When parsing 'or'")]), ("one and", [UnexpectedEofNode("When parsing 'and'")]), ("and one", [LeftPartNotFoundError()]), ("or one", [LeftPartNotFoundError()]), ("or", [LeftPartNotFoundError(), UnexpectedEofNode("When parsing 'or'")]), ("and", [LeftPartNotFoundError(), UnexpectedEofNode("When parsing 'and'")]), ]) def test_i_can_detect_error(self, expression, expected_errors): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(expression)) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR) assert res.body.body == expected_errors def test_i_can_detect_unbalanced_parenthesis(self): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput("(")) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME) assert isinstance(res.body.reason[0], UnexpectedTokenErrorNode) assert res.body.reason[0].token.type == TokenKind.EOF assert res.body.reason[0].expected_tokens == [TokenKind.RPAR] res = parser.parse(context, ParserInput(")")) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME) assert isinstance(res.body.reason[0], UnexpectedTokenErrorNode) assert res.body.reason[0].token.type == TokenKind.RPAR assert res.body.reason[0].expected_tokens == [] res = parser.parse(context, ParserInput("one and two)")) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR) assert isinstance(res.body.body[0], UnexpectedTokenErrorNode) assert res.body.body[0].token.type == TokenKind.RPAR assert res.body.body[0].expected_tokens == [] res = parser.parse(context, ParserInput("one and two)")) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR) assert isinstance(res.body.body[0], UnexpectedTokenErrorNode) assert res.body.body[0].token.type == TokenKind.RPAR assert res.body.body[0].expected_tokens == [] def test_i_can_detect_empty_expression(self): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput("")) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.IS_EMPTY) def test_i_can_test_property_equals(self): node = PropertyEqualsNode("prop_a", "good value") assert node.eval(Obj(prop_a="good value")) assert not node.eval(Obj(prop_a="other value")) def test_i_can_test_property_equals_for_int(self): node = PropertyEqualsNode("prop_a", "1") assert node.eval(Obj(prop_a=1)) assert node.eval(Obj(prop_a="1")) def test_i_can_test_property_equals_sequence(self): node = PropertyEqualsSequenceNode(["prop_b", "prop_a"], ["good parent", "good child"]) assert node.eval(Obj(prop_a="good child", parent=Obj(prop_a="Don't care", prop_b="good parent"))) assert not node.eval(Obj(prop_a="good child", parent=Obj(prop_a="Don't care", prop_b="wrong parent"))) assert not node.eval(Obj(prop_a="good child")) assert not node.eval(Obj(prop_a="wrong child", parent=Obj(prop_a="Don't care", prop_b="good parent"))) def test_i_can_test_property_contains(self): node = PropertyContainsNode("prop_a", "substring") assert node.eval(Obj(prop_a="it contains substring in it")) assert not node.eval(Obj(prop_a="it does not")) def test_i_can_test_property_contains_for_int(self): node = PropertyContainsNode("prop_a", "44") assert node.eval(Obj(prop_a=123445)) assert not node.eval(Obj(prop_a=12435)) def test_i_can_test_and(self): left = PropertyEqualsNode("prop_a", "good a") right = PropertyEqualsNode("prop_b", "good b") other = PropertyEqualsNode("prop_c", "good c") and_node = AndNode(left, right, other) assert and_node.eval(Obj("good a", "good b", "good c")) assert not and_node.eval(Obj("wrong a", "good b", "good c")) assert not and_node.eval(Obj("good a", "wrong b", "good c")) assert not and_node.eval(Obj("good a", "good b", "wrong c")) def test_i_can_test_or(self): left = PropertyEqualsNode("prop_a", "good a") right = PropertyEqualsNode("prop_b", "good b") other = PropertyEqualsNode("prop_c", "good c") or_node = OrNode(left, right, other) assert or_node.eval(Obj("wrong a", "good b", "good c")) assert or_node.eval(Obj("good a", "wrong b", "good c")) assert or_node.eval(Obj("good a", "good b", "wrong c")) assert not or_node.eval(Obj("wrong a", "wrong b", "wrong c")) def test_i_can_test_not(self): node = PropertyEqualsNode("prop_a", "good value") not_node = NotNode(node) assert not not_node.eval(Obj(prop_a="good value")) assert not_node.eval(Obj(prop_a="wrong value")) def test_i_can_test_lambda_node(self): node = LambdaNode(lambda o: o.prop_a + o.prop_b == "ab") assert node.eval(Obj(prop_a="a", prop_b="b")) assert not node.eval(Obj(prop_a="wrong value", prop_b="wrong value")) assert not node.eval(Obj(prop_a="wrong value")) # exception is caught def test_i_can_test_isa_node(self): class_node = IsaNode(Obj) assert class_node.eval(Obj(prop_a="value")) assert not class_node.eval(TestExpressionParser()) concept_node = IsaNode(BuiltinConcepts.RETURN_VALUE) assert concept_node.eval(ReturnValueConcept()) assert concept_node.eval(Concept(name="foo", key=BuiltinConcepts.RETURN_VALUE)) assert not concept_node.eval(Obj) assert not concept_node.eval(Concept()) concept_node2 = IsaNode("foo") assert concept_node2.eval(Concept("foo").init_key()) assert not concept_node2.eval(Obj) assert not concept_node2.eval(Concept()) @pytest.mark.parametrize("expression, to_trueify, to_skip, expected", [ ("a", ["b"], ["a"], "a"), ("b", ["b"], ["a"], "True"), ("a and b", ["b"], ["a"], "a and True"), ("b or a", ["b"], ["a"], "True or a"), ("isinstance(b, str)", ["b"], ["a"], "True"), ("isinstance(b, str) or instance(a, str)", ["b"], ["a"], "True or instance(a, str)"), ("a and b or c", ["b", "c"], ["a"], "a and True or True"), ("a + b or a + c", ["b", "c"], ["a"], "a + b or a + c"), ]) def test_i_can_trueify(self, expression, to_trueify, to_skip, expected): sheerka, context, parser = self.init_parser() expr_node = parser.parse(context, ParserInput(expression)).body.body translated_node = TrueifyVisitor(to_trueify, to_skip).visit(expr_node) assert str(translated_node) == expected