211 lines
9.1 KiB
Python
211 lines
9.1 KiB
Python
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 UnexpectedEofParsingError, UnexpectedTokenParsingError
|
|
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", [UnexpectedEofParsingError("When parsing 'or'")]),
|
|
("one and", [UnexpectedEofParsingError("When parsing 'and'")]),
|
|
("and one", [LeftPartNotFoundError()]),
|
|
("or one", [LeftPartNotFoundError()]),
|
|
("or", [LeftPartNotFoundError(), UnexpectedEofParsingError("When parsing 'or'")]),
|
|
("and", [LeftPartNotFoundError(), UnexpectedEofParsingError("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], UnexpectedTokenParsingError)
|
|
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], UnexpectedTokenParsingError)
|
|
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], UnexpectedTokenParsingError)
|
|
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], UnexpectedTokenParsingError)
|
|
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
|