import pytest from core.builtin_concepts import BuiltinConcepts from core.concept import Concept from core.sheerka.services.SheerkaExecute import ParserInput from parsers.BaseNodeParser import SourceCodeWithConceptNode from parsers.BaseParser import ErrorSink from parsers.FunctionParser import FunctionParser from parsers.PythonParser import PythonErrorNode from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.parsers.parsers_utils import compute_expected_array, SCN, SCWC, CN, UTN, CNC, RN, FN, get_test_obj, \ get_expr_node_from_test_node cmap = { "one": Concept("one"), "two": Concept("two"), "twenties": Concept("twenties", definition="'twenty' (one|two)=unit").def_var("unit"), "plus": Concept("a plus b").def_var("a").def_var("b"), } class TestFunctionParser(TestUsingMemoryBasedSheerka): shared_ontology = None @classmethod def setup_class(cls): init_test_helper = cls().init_test(cache_only=False, ontology="#TestFunctionParser#") sheerka, context, *updated = init_test_helper.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 = FunctionParser() return sheerka, context, parser def init_parser_with_source(self, source): sheerka, context, parser = self.init_parser() error_sink = ErrorSink() parser_input = ParserInput(source) parser.reset_parser_input(parser_input, error_sink) return sheerka, context, parser, parser_input, error_sink 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() parser.parse(context, "not a parser input") is None def test_i_cannot_parse_when_not_a_function(self): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput("not a function")) assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME) @pytest.mark.parametrize("expression, expected", [ ("func()", FN("func(", ")", [])), ("concept(one)", FN("concept(", ")", ["one"])), ("func(one)", FN("func(", ")", ["one"])), ("func(a long two, 'three', ;:$*)", FN("func(", ")", ["a long two, ", "'three', ", ";:$*"])), ("func(func1(one), two, func2(func3(), func4(three)))", FN("func(", (")", 4), [ (FN("func1(", ")", ["one"]), ", "), "two, ", (FN("func2(", (")", 3), [ (FN("func3(", (")", 1), []), ", "), (FN("func4(", (")", 2), ["three"]), None), ]), None) ])), ("func(r:|1:)", FN("func(", ")", ["r:|1:"])) ]) def test_i_can_parse_function(self, expression, expected): sheerka, context, parser, parser_input, error_sink = self.init_parser_with_source(expression) expected = get_expr_node_from_test_node(expression, expected) parsed = parser.parse_input(context, parser_input, error_sink) assert not error_sink.has_error assert parsed == expected @pytest.mark.parametrize("text, expected", [ ("func()", SCN("func()")), (" func()", SCN("func()")), ("func(one)", SCWC("func(", ")", CN("one"))), ("func(one, unknown, two)", SCWC("func(", ")", CN("one"), ", ", UTN("unknown"), (", ", 1), CN("two"))), ("func(one, twenty two)", SCWC("func(", ")", "one", ", ", CN("twenties", "twenty two"))), ("func(one plus two, three)", SCWC("func(", ")", CNC("plus", a="one", b="two"), ", ", UTN("three"))), ("func(func1(one), two)", SCWC("func(", (")", 1), SCWC("func1(", ")", "one"), ", ", "two")) ]) def test_i_can_parse(self, text, expected): sheerka, context, parser = self.init_parser() resolved_expected = compute_expected_array(cmap, text, [expected])[0] res = parser.parse(context, ParserInput(text)) parser_result = res.body expression = res.body.body assert res.status assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT) transformed_expression = get_test_obj(expression, resolved_expected) assert transformed_expression == resolved_expected assert expression.python_node is not None assert expression.return_value is not None def test_i_can_parse_when_multiple_results_when_requested(self): # the previous output was # [ # SCWC("func(", ")", "one", ", ", "twenty ", "two"), # SCWC("func(", ")", "one", ", ", CN("twenties", "twenty two")) # ] # But the first one is now filtered out, as it's not a valid python function call sheerka, context, parser = self.init_parser() parser.longest_concepts_only = False text = "func(one, twenty two)" expected = [SCWC("func(", ")", "one", ", ", CN("twenties", "twenty two"))] resolved_expected = compute_expected_array(cmap, text, expected) results = parser.parse(context, ParserInput(text)) assert len(results) == 2 res = results[0] assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR) assert len(res.body.body) == 1 assert (res.body.body[0], PythonErrorNode) res = results[1] parser_result = res.body expressions = res.body.body assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT) transformed_expressions = get_test_obj(expressions, resolved_expected[0]) assert transformed_expressions == resolved_expected[0] def test_i_can_parse_when_the_parameter_is_not_a_concept(self): """ It's not a concept, but it can be a valid short term memory object :return: """ sheerka, context, parser = self.init_parser() text = "func(unknown_concept)" res = parser.parse(context, ParserInput(text)) expected = [SCWC("func(", ")", "unknown_concept")] resolved_expected = compute_expected_array(cmap, text, expected) assert res.status parsed = res.body.body transformed_parsed = get_test_obj([parsed], resolved_expected) assert transformed_parsed == resolved_expected def test_i_can_parse_when_the_concept_is_not_found(self): """ We do not check yet if it's a valid concept If you find a cheap way to do so, simply remove this test :return: """ sheerka, context, parser = self.init_parser() text = "func(c:|xxx:)" res = parser.parse(context, ParserInput(text)) assert res.status def test_i_can_parse_when_rules(self): sheerka, context, parser = self.init_parser() text = "func(r:|1:)" expected = SCWC("func(", ")", RN("1")) resolved_expected = compute_expected_array(cmap, text, [expected])[0] res = parser.parse(context, ParserInput(text)) parser_result = res.body expression = res.body.body transformed_expression = get_test_obj(expression, resolved_expected) assert res.status assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT) assert transformed_expression == resolved_expected assert expression.python_node is not None assert expression.return_value is not None def test_i_can_parse_when_the_parameter_is_a_dynamic_concept(self): sheerka, context, parser = self.init_parser() text = "func(ones)" res = parser.parse(context, ParserInput(text)) assert res.status assert isinstance(res.body.body, SourceCodeWithConceptNode) assert res.body.body.python_node.source == 'func(__C__ones__1001___PLURAL__C__)' assert "__C__ones__1001___PLURAL__C__" in res.body.body.python_node.objects @pytest.mark.parametrize("text, expected_error_type", [ ("one", BuiltinConcepts.NOT_FOR_ME), # no function found ("$*!", BuiltinConcepts.NOT_FOR_ME), # no function found ("func(", BuiltinConcepts.ERROR), # function found, but incomplete ("func(one", BuiltinConcepts.ERROR), # function found, but incomplete ("func(one, two, ", BuiltinConcepts.ERROR), # function found, but incomplete ("func(one) and func(two)", BuiltinConcepts.ERROR), # to many function ("one func(one)", BuiltinConcepts.NOT_FOR_ME), # function not found ! (as it is not the first) ("func(a=b, c)", BuiltinConcepts.ERROR), # function found, but cannot be parsed ("func(one two)", BuiltinConcepts.ERROR), # function found, but cannot be parsed ]) def test_i_cannot_parse(self, text, expected_error_type): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput(text)) assert not res.status assert sheerka.isinstance(res.body, expected_error_type) @pytest.mark.parametrize("sequence, expected", [ (None, None), ([["a"]], [["a"]]), ([["a"], ["b", "c"]], [["a"]]), ([["b", "c"], ["a"]], [["a"]]), ([["b", "c"], ["a"], ["d", "e"], ["f"]], [["a"], ["f"]]), ]) def test_i_can_get_the_longest_concept_sequence(self, sequence, expected): assert FunctionParser.get_longest_concepts(sequence) == expected def test_concepts_found_are_fully_initialized(self): sheerka, context, parser = self.init_parser() res = parser.parse(context, ParserInput("func(one plus three)")) concept = res.body.body.nodes[0].concept assert res.status assert isinstance(concept.get_compiled()["a"], Concept) # three is not recognized, # so it will be transformed into list of ReturnValueConcept that indicate how to recognized it assert isinstance(concept.get_compiled()["b"], list) for item in concept.get_compiled()["b"]: assert sheerka.isinstance(item, BuiltinConcepts.RETURN_VALUE)