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 UnexpectedEof, 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", [UnexpectedEof("When parsing 'or'")]), ("one and", [UnexpectedEof("When parsing 'and'")]), ("and one", [LeftPartNotFoundError()]), ("or one", [LeftPartNotFoundError()]), ("or", [LeftPartNotFoundError(), UnexpectedEof("When parsing 'or'")]), ("and", [LeftPartNotFoundError(), UnexpectedEof("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.ERROR) assert isinstance(res.body.body[0], UnexpectedTokenErrorNode) assert res.body.body[0].token.type == TokenKind.EOF assert res.body.body[0].expected_tokens == [TokenKind.RPAR] res = parser.parse(context, ParserInput(")")) 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 == [] 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