import pytest from common.global_symbols import NotInit from core.concept import DefinitionType from helpers import get_parser_input from parsers.ConceptDefinitionParser import ConceptDefinition, ConceptDefinitionParser from parsers.parser_utils import ParsingError, UnexpectedEof, UnexpectedToken from parsers.tokenizer import Keywords, Token, TokenKind class TestConceptDefinitionParser: @pytest.fixture() def parser(self): return ConceptDefinitionParser() @pytest.mark.parametrize("text", [ "", " "]) def test_i_can_detect_empty_input(self, parser, text): pi = get_parser_input(text) res = parser.parse(pi) assert res is None assert parser.error_sink == [UnexpectedEof(Keywords.DEF, None)] def test_must_start_with_def_keyword(self, parser): pi = get_parser_input("hello") res = parser.parse(pi) assert res is None assert parser.error_sink == [UnexpectedToken(Token(TokenKind.IDENTIFIER, "hello", 0, 1, 1), Keywords.DEF)] @pytest.mark.parametrize("text, expected", [ ("def concept hello", ConceptDefinition(name="hello")), ("def concept hello ", ConceptDefinition(name="hello")), ("def concept a + b", ConceptDefinition(name="a + b")), ("def concept a+b", ConceptDefinition(name="a + b")), ("def concept 'a+b'+c", ConceptDefinition(name="'a+b' + c")), ('def concept "a+b"+c', ConceptDefinition(name="a+b + c")), ('def concept "as if"', ConceptDefinition(name="as if")), ("def concept 'as if'", ConceptDefinition(name="'as if'")), ("def concept 'as' \"if\"", ConceptDefinition(name="'as' if")), ('def concept \'as\' "if"', ConceptDefinition(name="'as' if")), ]) def test_i_can_parse_def_concept_name(self, parser, text, expected): pi = get_parser_input(text) actual = parser.parse(pi) assert actual == expected def test_concept_name_is_mandatory(self, parser): pi = get_parser_input("def concept as foo") actual = parser.parse(pi) assert len(parser.error_sink) == 1 assert isinstance(parser.error_sink[0], ParsingError) assert parser.error_sink[0].message == "Name is mandatory." assert actual is None def test_new_line_is_not_allowed_in_concept_name(self, parser): pi = get_parser_input("def concept complicated \n name as foo") actual = parser.parse(pi) assert len(parser.error_sink) == 1 assert isinstance(parser.error_sink[0], ParsingError) assert parser.error_sink[0].message == "Newlines are not allowed in name." assert actual is None @pytest.mark.parametrize("text, part", [ ("def concept foo as where True", "as"), ("def concept foo where as 1 + 1", "where"), ("def concept foo pre as 1 + 1", "pre"), ("def concept foo post as 1 + 1", "post"), ("def concept foo ret as 1 + 1", "ret"), ]) def test_empty_declarations_are_not_allowed(self, parser, text, part): pi = get_parser_input(text) actual = parser.parse(pi) assert actual is None assert len(parser.error_sink) == 1 assert isinstance(parser.error_sink[0], ParsingError) assert parser.error_sink[0].message == f"Empty '{part}' declaration." def test_empty_parts_are_not_initialized(self, parser): pi = get_parser_input("def concept foo") actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.body is "" assert actual.where is "" assert actual.pre is "" assert actual.post is "" assert actual.ret is "" def test_i_can_manage_all_parts(self, parser): concept_def = "def concept foo" concept_def += " where my where clause" concept_def += " pre my pre clause" concept_def += " as my body" concept_def += " ret my return value" concept_def += " post my post condition" pi = get_parser_input(concept_def) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.body == "my body" assert actual.where == "my where clause" assert actual.pre == "my pre clause" assert actual.post == "my post condition" assert actual.ret == "my return value" @pytest.mark.parametrize("body", [ "c:#1001: is an int", "c:one: is an int", "'one' is an int", '"one" is an in', ]) def test_i_can_manage_special_tokens_in_part(self, parser, body): text = f"def concept foo as {body}" pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.body == body @pytest.mark.parametrize("text, expected_type, expected_definition, ", [ ("def concept foo from def 'hello world'", DefinitionType.DEFAULT, "'hello world'"), ("def concept foo from 'hello world'", DefinitionType.DEFAULT, "'hello world'"), ("def concept foo from bnf my bnf definition", DefinitionType.BNF, "my bnf definition"), ]) def test_i_can_set_concept_definition(self, parser, text, expected_type, expected_definition): pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.definition_type == expected_type assert actual.definition == expected_definition @pytest.mark.parametrize("text", [ "def concept foo from where True", "def concept foo from bnf where True", "def concept foo from def where True", "def concept foo from bnf", "def concept foo from def ", ]) def test_empy_definition_are_not_allowed(self, parser, text): pi = get_parser_input(text) actual = parser.parse(pi) assert actual is None assert parser.error_sink[0].message == "Empty 'from' declaration." def test_i_can_parse_multiline_definition(self, parser): text = """ def concept add one to a as def func(x): return x+1 func(a) """ pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.body == "def func(x):\n return x+1\nfunc(a)" def test_i_can_parse_indention_mode(self, parser): text = """ def concept add one to a as: def func(x): return x+1 func(a) """ pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.body == "def func(x):\n return x+1\nfunc(a)" def test_i_can_detect_invalid_indentation(self, parser): text = """ def concept add one to a as: def func(x): return x+1 func(a) """ pi = get_parser_input(text) actual = parser.parse(pi) assert actual is None assert len(parser.error_sink) > 0 def test_i_can_can_use_colon_to_protect_keywords(self, parser): text = """ def concept today as: from datetime import date today = date.today() from: give me the date ! """ pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.body == "from datetime import date\ntoday = date.today()" assert actual.definition == "give me the date !" def test_i_can_parse_bnf_concept_with_regex(self, parser): text = "def concept sha512 from bnf number | r'[a-f0-9]+' | (number r'[a-f0-9]+')+" pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.definition == "number | r'[a-f0-9]+' | (number r'[a-f0-9]+')+" @pytest.mark.parametrize("text, expected", [ ("def concept foo auto_eval True", True), ("def concept foo auto_eval true", True), ("def concept foo auto_eval False", False), ("def concept foo auto_eval false", False), ]) def test_i_can_parse_auto_eval(self, parser, text, expected): pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.auto_eval == expected def test_auto_eval_is_set_to_false_by_default(self, parser): pi = get_parser_input("def concept foo") actual = parser.parse(pi) assert actual.auto_eval is False def test_empty_auto_eval_is_not_allowed(self, parser): pi = get_parser_input("def concept foo auto_eval as 1") actual = parser.parse(pi) assert actual is None assert parser.error_sink[0].message == "Empty 'auto_eval' declaration." def test_i_cannot_parse_wrong_value(self, parser): pi = get_parser_input("def concept foo auto_eval wrong_value") actual = parser.parse(pi) assert actual is None assert parser.error_sink[0].message == "Invalid 'auto_eval' declaration (wrong_value is not recognized)" @pytest.mark.parametrize("text, expected", [ ("def concept foo def_var var", [("var", NotInit)]), ("def concept foo def_var var1 def_var var2", [("var1", NotInit), ("var2", NotInit)]), ("def concept foo def_var var1 var2", [("var1", NotInit), ("var2", NotInit)]), ("def concept foo def_var var1, var2", [("var1", NotInit), ("var2", NotInit)]), ("def concept foo def_var var1=10", [("var1", 10)]), ("def concept foo def_var var1 = 10", [("var1", 10)]), ("def concept foo def_var var1 = 'hello'", [("var1", "'hello'")]), ("def concept foo def_var var1 = hello", [("var1", "hello")]), ("def concept foo def_var var1, var2 = 10", [("var1", NotInit), ("var2", 10)]), ("def concept foo def_var var1='hello', var2 = 10", [("var1", "'hello'"), ("var2", 10)]), ("def concept foo def_var var1='hello' var2 = 10", [("var1", "'hello'"), ("var2", 10)]), ]) def test_i_can_parse_variable_definitions(self, parser, text, expected): pi = get_parser_input(text) actual = parser.parse(pi) assert isinstance(actual, ConceptDefinition) assert actual.def_var == expected def test_empty_def_var_is_not_allowed(self, parser): pi = get_parser_input("def concept foo def_var as 1") actual = parser.parse(pi) assert actual is None assert parser.error_sink[0].message == "Empty 'def_var' declaration."