import pytest from core.builtin_concepts import BuiltinConcepts from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import TokenKind from parsers.BaseExpressionParser import AndNode, IsAQuestionVisitor, LeftPartNotFoundError, NotNode, OrNode, \ ParenthesisMismatchError, TrueifyVisitor, compile_disjunctions from parsers.BaseParser import UnexpectedEofParsingError, UnexpectedTokenParsingError from parsers.LogicalOperatorParser import LogicalOperatorParser from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.parsers.parsers_utils import AND, EXPR, NOT, OR, get_expr_node_from_test_node class DoNotCompareStartStopContextManager: def __init__(self): self.original_not_eq = None self.original_and_eq = None self.original_or_eq = None @staticmethod def not_eq(self, other): if not isinstance(other, NotNode): return False return self.node == other.node @staticmethod def and_eq(self, other): if not isinstance(other, AndNode): return False return self.parts == other.parts @staticmethod def or_eq(self, other): if not isinstance(other, OrNode): return False return self.parts == other.parts def __enter__(self): self.original_not_eq = NotNode.__eq__ self.original_and_eq = AndNode.__eq__ self.original_or_eq = OrNode.__eq__ NotNode.__eq__ = self.not_eq AndNode.__eq__ = self.and_eq OrNode.__eq__ = self.or_eq def __exit__(self, *args): NotNode.__eq__ = self.original_not_eq AndNode.__eq__ = self.original_and_eq OrNode.__eq__ = self.original_or_eq class TestLogicalOperatorParser(TestUsingMemoryBasedSheerka): def init_parser(self): sheerka, context = self.init_concepts() parser = LogicalOperatorParser() return sheerka, context, parser @pytest.mark.parametrize("expression, expected", [ ("one complicated expression", EXPR("one complicated expression")), ("function_call(a,b,c)", EXPR("function_call(a,b,c)")), ("one expression or another expression", OR(EXPR("one expression"), EXPR("another expression"))), ("one expression and another expression", AND(EXPR("one expression"), EXPR("another expression"))), ("not one", NOT(EXPR("one"))), ("one and not two", AND(EXPR("one"), NOT(EXPR("two")))), ("not one and two", AND(NOT(EXPR("one")), EXPR("two"))), ("one or not two", OR(EXPR("one"), NOT(EXPR("two")))), ("not one or two", OR(NOT(EXPR("one")), EXPR("two"))), ("one or two or three", OR(EXPR("one"), EXPR("two"), EXPR("three"))), ("one and two and three", AND(EXPR("one"), EXPR("two"), EXPR("three"))), ("one or two and three", OR(EXPR("one"), AND(EXPR("two"), EXPR("three")))), ("one and two or three", OR(AND(EXPR("one"), EXPR("two")), EXPR("three"))), ("one and (two or three)", AND(EXPR("one"), OR(EXPR("two"), EXPR("three")), source="one and (two or three)")), ("not not one", NOT(NOT(EXPR("one")))), ("not (one and two)", NOT(AND(EXPR("one"), EXPR("two")), source="not (one and two)")), ("one 'and' two or three", OR(EXPR("one 'and' two"), EXPR("three"))), ("not ((a or b) and (c or d))", NOT(AND(OR(EXPR("a"), EXPR("b")), OR(EXPR("c"), EXPR("d")), source="(a or b) and (c or d)"), source="not ((a or b) and (c or d))")), ("not ((a and b) or (c and d))", NOT(OR(AND(EXPR("a"), EXPR("b")), AND(EXPR("c"), EXPR("d")), source="(a and b) or (c and d)"), source="not ((a and b) or (c and d))")), ("(one and two)", AND(EXPR("one"), EXPR("two"))), ("(one or two)", OR(EXPR("one"), EXPR("two"))), ("(not one)", NOT(EXPR("one"))), ]) def test_i_can_parse_expression(self, expression, expected): sheerka, context, parser = self.init_parser() expected = get_expr_node_from_test_node(expression, expected) 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", [UnexpectedEofParsingError("while parsing 'or'")]), ("one and", [UnexpectedEofParsingError("while parsing 'and'")]), ("and one", [LeftPartNotFoundError("and", 0)]), ("or one", [LeftPartNotFoundError("or", 0)]), ("or", [LeftPartNotFoundError("or", 0), UnexpectedEofParsingError("while parsing 'or'")]), ("and", [LeftPartNotFoundError("and", 0), UnexpectedEofParsingError("while 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_unexpected_not_error(self): sheerka, context, parser = self.init_parser() expression = "a cat is not a human" res = parser.parse(context, ParserInput(expression)) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR) assert isinstance(res.body.body[0], UnexpectedTokenParsingError) @pytest.mark.parametrize("expression, expected_error, parenthesis_type, index", [ ("(", BuiltinConcepts.NOT_FOR_ME, TokenKind.LPAR, 0), (")", BuiltinConcepts.NOT_FOR_ME, TokenKind.RPAR, 0), ("one and two(", BuiltinConcepts.ERROR, TokenKind.LPAR, 11), ("one (", BuiltinConcepts.NOT_FOR_ME, TokenKind.LPAR, 4), ("one (and", BuiltinConcepts.NOT_FOR_ME, TokenKind.LPAR, 4), ("one and two)", BuiltinConcepts.ERROR, TokenKind.RPAR, 11), ("one )", BuiltinConcepts.ERROR, TokenKind.RPAR, 4), ("one ) and", BuiltinConcepts.ERROR, TokenKind.RPAR, 4), ]) def test_i_can_detect_unbalanced_parenthesis(self, expression, expected_error, parenthesis_type, index): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(expression)) assert not res.status if expected_error == BuiltinConcepts.NOT_FOR_ME: assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME) assert isinstance(res.body.reason[0], ParenthesisMismatchError) assert res.body.reason[0].token.type == parenthesis_type assert res.body.reason[0].token.index == index else: assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR) assert isinstance(res.body.body[0], ParenthesisMismatchError) assert res.body.body[0].token.type == parenthesis_type assert res.body.body[0].token.index == index 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) @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 @pytest.mark.parametrize("expression, expected", [ ("foo", None), ("", None), ("is_question()", True), (" is_question() ", True), ("is_question ( ) ", True), ("is _question()", None), ("is_ question()", None), ("is _ question()", None), ("context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED)", True), ("not is_question()", False), ("not context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED)", False), ("not foo", None), ("not is_question() and not is_question()", False), ("not is_question() and is_question()", False), ("not is_question() and foo", False), ("is_question() and not is_question()", False), ("is_question() and is_question()", True), ("is_question() and foo", True), ("foo and not is_question()", False), ("foo and is_question()", True), ("foo and bar", None), ("not is_question() or not is_question()", False), ("not is_question() or is_question()", True), ("not is_question() or foo", False), ("is_question() or not is_question()", True), ("is_question() or is_question()", True), ("is_question() or foo", True), ("foo or not is_question()", False), ("foo or is_question()", True), ("foo or bar", None), ]) def test_is_a_question(self, expression, expected): sheerka, context, parser = self.init_parser() expr_node = parser.parse(context, ParserInput(expression)).body.body assert IsAQuestionVisitor().visit(expr_node) == expected @pytest.mark.parametrize("expression, expected", [ ("a", [EXPR("a")]), ("a and b and c", [AND(EXPR("a"), EXPR("b"), EXPR("c"))]), ("a or b or c", [EXPR("a"), EXPR("b"), EXPR("c")]), ("a and b or c", [AND(EXPR("a"), EXPR("b")), EXPR("c")]), ("a or b and c", [EXPR("a"), AND(EXPR("b"), EXPR("c"))]), ("(a or b) and c", [AND(EXPR("a"), EXPR("c")), AND(EXPR("b"), EXPR("c"))]), ("a or (b or c) and d", [EXPR("a"), AND(EXPR("b"), EXPR("d")), AND(EXPR("c"), EXPR("d"))]), ("not a", [NOT(EXPR("a"))]), ("not (a and b)", [NOT(AND(EXPR("a"), EXPR("b")))]), ("not (a or b)", [AND(NOT(EXPR("a")), NOT(EXPR("b")))]), ("(a or b) and not (c or d)", [AND(EXPR("a"), NOT(EXPR("c")), NOT(EXPR("d"))), AND(EXPR("b"), NOT(EXPR("c")), NOT(EXPR("d")))]), ("(a or b) or not (c or d)", [EXPR("a"), EXPR("b"), AND(NOT(EXPR("c")), NOT(EXPR("d")))]), ("(a and b) and not (c or d)", [AND(EXPR("a"), EXPR("b"), NOT(EXPR("c")), NOT(EXPR("d")))]), ("(a and b) or not (c or d)", [AND(EXPR("a"), EXPR("b")), AND(NOT(EXPR("c")), NOT(EXPR("d")))]), ("a and (b and c)", [AND(EXPR("a"), EXPR("b"), EXPR("c"))]), ("a or (b or c)", [EXPR("a"), EXPR("b"), EXPR("c")]), ("a and b and not c", [AND(EXPR("a"), EXPR("b"), NOT(EXPR("c")))]), ("not a and b", [AND(NOT(EXPR("a")), EXPR("b"))]), ("not a and not b", [AND(NOT(EXPR("a")), NOT(EXPR("b")))]), ("not a or not b", [NOT(EXPR("a")), NOT(EXPR("b"))]), ]) def test_i_can_compile_disjunction(self, expression, expected): sheerka, context, parser = self.init_parser() resolved_expected = [get_expr_node_from_test_node(expression, e) for e in expected] expr_node = parser.parse(context, ParserInput(expression)).body.body with DoNotCompareStartStopContextManager(): res = compile_disjunctions(expr_node) assert res == resolved_expected