import pytest from core.builtin_concepts_ids import BuiltinConcepts from core.concept import Concept from core.sheerka.services.SheerkaExecute import ParserInput from core.sheerka.services.SheerkaRuleManager import FormatAstNode, CompiledCondition from core.tokenizer import Tokenizer, Keywords from core.utils import tokens_are_matching from parsers.BaseCustomGrammarParser import KeywordNotFound, NameNode, SyntaxErrorNode from parsers.BaseParser import UnexpectedEofParsingError from parsers.DefRuleParser import DefRuleParser, DefExecRuleNode, DefFormatRuleNode from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka cmap = { "foo": Concept("foo"), "bar": Concept("bar"), "is a": Concept("x is a y").def_var("x").def_var("y"), "is a question": Concept("x is a y", pre="is_question()").def_var("x").def_var("y"), "a is good": Concept("a is good", pre="is_question()").def_var("a"), "b is good": Concept("b is good", pre="is_question()").def_var("b"), "greetings": Concept("hello a").def_var("a") } class TestDefRuleParser(TestUsingMemoryBasedSheerka): shared_ontology = None @classmethod def setup_class(cls): init_test_ref = cls().init_test(cache_only=False, ontology="#TestDefRuleParser#") sheerka, context, *updated = init_test_ref.with_concepts(*cmap.values(), create_new=True).unpack() for i, concept_name in enumerate(cmap): cmap[concept_name] = updated[i] cls.shared_ontology = sheerka.get_ontology(context) sheerka.pop_ontology(context) def init_parser(self, my_concepts_map=None, **kwargs): if my_concepts_map is None: sheerka, context = self.init_test().unpack() sheerka.add_ontology(context, self.shared_ontology) else: sheerka, context, *updated = self.init_test().with_concepts(*my_concepts_map.values(), **kwargs).unpack() for i, pair in enumerate(my_concepts_map): my_concepts_map[pair] = updated[i] parser = DefRuleParser() return sheerka, context, parser def test_i_cannot_parse_when_parser_input_is_initialized_from_token(self): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput("", list(Tokenizer("init from tokens")))) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME) 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_input_must_be_a_parser_input(self): sheerka, context, parser = self.init_parser() assert parser.parse(context, "not a parser input") is None def test_i_can_parse_simple_exec_rule_definition(self): sheerka, context, parser = self.init_parser() text = "when True then answer('that is true')" res = parser.parse(context, ParserInput(text)) parser_result = res.body parsed = res.body.body assert res.status assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT) assert isinstance(parsed, DefExecRuleNode) assert len(parsed.tokens) == 2 assert tokens_are_matching(parsed.tokens[Keywords.WHEN], Tokenizer("when True")) assert tokens_are_matching(parsed.tokens[Keywords.THEN], Tokenizer("then answer('that is true')")) assert isinstance(parsed.python, list) assert len(parsed.python) == 1 assert isinstance(parsed.python[0], CompiledCondition) assert sheerka.isinstance(parsed.then, BuiltinConcepts.RETURN_VALUE) def test_i_can_parse_simple_format_rule_definition(self): sheerka, context, parser = self.init_parser() text = "when True print hello world!" res = parser.parse(context, ParserInput(text)) parser_result = res.body parsed = res.body.body assert res.status assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT) assert isinstance(parsed, DefFormatRuleNode) assert len(parsed.tokens) == 2 assert tokens_are_matching(parsed.tokens[Keywords.WHEN], Tokenizer("when True")) assert tokens_are_matching(parsed.tokens[Keywords.PRINT], Tokenizer("print hello world!")) assert isinstance(parsed.python, list) assert len(parsed.python) == 1 assert isinstance(parsed.python[0], CompiledCondition) assert isinstance(parsed.print, FormatAstNode) def test_i_can_parse_exec_rule_with_name(self): sheerka, context, parser = self.init_parser() text = "def rule my rule as when True then answer('that is true')" res = parser.parse(context, ParserInput(text)) parser_result = res.body parsed = res.body.body assert res.status assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT) assert isinstance(parsed, DefExecRuleNode) assert parsed.name == NameNode(list(Tokenizer("my rule"))) assert len(parsed.tokens) == 2 assert tokens_are_matching(parsed.tokens[Keywords.WHEN], Tokenizer("when True")) assert tokens_are_matching(parsed.tokens[Keywords.THEN], Tokenizer("then answer('that is true')")) assert isinstance(parsed.python, list) assert len(parsed.python) == 1 assert isinstance(parsed.python[0], CompiledCondition) assert sheerka.isinstance(parsed.then, BuiltinConcepts.RETURN_VALUE) @pytest.mark.skip("Not ready for that") def test_when_is_parsed_in_the_context_of_a_question(self): sheerka, context, parser = self.init_parser() text = "when foo is a bar print hello world" res = parser.parse(context, ParserInput(text)) format_rule = res.body.body rules = format_rule.python assert res.status assert len(rules) == 1 assert isinstance(rules[0], CompiledCondition) assert rules[0].return_value.body.body.get_metadata().pre == "is_question()" @pytest.mark.skip("Not ready for that") def test_when_can_support_multiple_possibilities_when_question_only(self): sheerka, context, parser = self.init_parser() text = "when foo is good print hello world" res = parser.parse(context, ParserInput(text)) format_rule = res.body.body rules = format_rule.python assert res.status assert len(rules) == 2 assert rules[0].return_value.body.body.get_metadata().name == "a is good" assert rules[1].return_value.body.body.get_metadata().name == "b is good" @pytest.mark.parametrize("text, error", [ ("def", [KeywordNotFound(None, keywords=['rule'])]), ("def concept", [KeywordNotFound(None, keywords=['rule'])]), ("def other", [KeywordNotFound(None, keywords=['rule'])]), ("def rule name", [KeywordNotFound(None, keywords=['as'])]), ("def rule complicated long name", [KeywordNotFound(None, keywords=['as'])]), ("hello world", [KeywordNotFound(None, keywords=['when'])]), ("when True", [KeywordNotFound([], keywords=['then', 'print'])]), ("print True", [KeywordNotFound([], keywords=['when'])]), ("when True print 'hello world' then answer('yes')", [SyntaxErrorNode([], message="Cannot have both 'print' and 'then' keywords")]) ]) def test_i_can_detect_not_for_me(self, text, error): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(text)) not_for_me = res.body assert not res.status assert sheerka.isinstance(not_for_me, BuiltinConcepts.NOT_FOR_ME) assert not_for_me.reason == error @pytest.mark.parametrize("text, expected_error", [ ("when x x = False print 'hello world'", BuiltinConcepts.ERROR), ]) def test_i_can_detect_errors(self, text, expected_error): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(text)) assert not res.status assert sheerka.isinstance(res.body, expected_error) @pytest.mark.parametrize("text, error_message", [ ("def rule rule_name as", "While parsing 'when'."), ("def rule rule_name as ", "While parsing 'when'."), ]) def test_i_cannot_parse_when_unexpected_eof(self, text, error_message): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(text)) not_for_me = res.body assert not res.status assert sheerka.isinstance(not_for_me, BuiltinConcepts.NOT_FOR_ME) assert isinstance(not_for_me.reason[0], UnexpectedEofParsingError) assert not_for_me.reason[0].message == error_message def test_rule_name_is_mandatory_when_using_def_rule(self): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput("def rule as when True print 'true'")) error = res.body assert not res.status assert sheerka.isinstance(error, BuiltinConcepts.ERROR) assert isinstance(error.error[0], SyntaxErrorNode) assert error.error[0].message == "Name is mandatory"