Files
Sheerka-Old/tests/parsers/test_DefaultParser.py
T
2020-05-25 18:09:12 +02:00

373 lines
14 KiB
Python

import ast
from dataclasses import dataclass
import pytest
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts, ReturnValueConcept
from core.concept import DEFINITION_TYPE_BNF, DEFINITION_TYPE_DEF
from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import Keywords, Tokenizer, LexerError
from parsers.BnfNodeParser import OrderedChoice, ConceptExpression, StrMatch
from parsers.BnfParser import BnfParser
from parsers.DefaultParser import DefaultParser, NameNode, SyntaxErrorNode, CannotHandleErrorNode, IsaConceptNode
from parsers.DefaultParser import UnexpectedTokenErrorNode, DefConceptNode
from parsers.PythonParser import PythonParser, PythonNode
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
def get_def_concept(name, where=None, pre=None, post=None, body=None, definition=None, bnf_def=None):
def_concept = DefConceptNode([], name=NameNode(list(Tokenizer(name))))
if body:
def_concept.body = get_concept_part(body)
if where:
def_concept.where = get_concept_part(where)
if pre:
def_concept.pre = get_concept_part(pre)
if post:
def_concept.post = get_concept_part(post)
if bnf_def:
def_concept.definition = ReturnValueConcept(
"parsers.Bnf",
True,
bnf_def)
def_concept.definition_type = DEFINITION_TYPE_BNF
if definition:
def_concept.definition = NameNode(list(Tokenizer(definition)))
def_concept.definition_type = DEFINITION_TYPE_DEF
return def_concept
def get_concept_part(part):
if isinstance(part, str):
node = PythonNode(part.strip(), ast.parse(part.strip(), mode="eval"))
return ReturnValueConcept(
who="parsers.Default",
status=True,
value=ParserResultConcept(
source=part,
parser=PythonParser(),
value=node))
if isinstance(part, PN):
node = PythonNode(part.source.strip(), ast.parse(part.source.strip(), mode=part.mode))
return ReturnValueConcept(
who="parsers.Default",
status=True,
value=ParserResultConcept(
source=part.source,
parser=PythonParser(),
value=node))
if isinstance(part, PythonNode):
return ReturnValueConcept(
who="parsers.Default",
status=True,
value=ParserResultConcept(
source=part.source,
parser=PythonParser(),
value=part))
if isinstance(part, ReturnValueConcept):
return part
@dataclass
class PN:
source: str # parser result source
mode: str # compilation mode
class TestDefaultParser(TestUsingMemoryBasedSheerka):
def init_parser(self, *concepts):
sheerka, concept, *updated = self.init_concepts(*concepts, singleton=True)
parser = DefaultParser()
return sheerka, concept, parser, *updated
@pytest.mark.parametrize("text, expected", [
("def concept hello", get_def_concept(name="hello")),
("def concept hello ", get_def_concept(name="hello")),
("def concept a + b", get_def_concept(name="a + b")),
("def concept a+b", get_def_concept(name="a + b")),
("def concept 'a+b'+c", get_def_concept(name="'a+b' + c")),
("def concept 'as if'", get_def_concept(name="'as if'")),
("def concept 'as' if", get_def_concept(name="'as if'")),
("def concept hello as 'hello'", get_def_concept(name="hello", body="'hello'")),
("def concept hello as 1", get_def_concept(name="hello", body="1")),
("def concept hello as 1 + 1", get_def_concept(name="hello", body="1 + 1")),
])
def test_i_can_parse_def_concept(self, text, expected):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
node = res.value.value
assert res.status
assert res.who == parser.name
assert res.value.source == text
assert isinstance(res.value, ParserResultConcept)
assert node == expected
def test_i_can_parse_complex_def_concept_statement(self):
text = """def concept a plus b
where a,b
pre isinstance(a, int) and isinstance(b, float)
post isinstance(res, int)
as res = a + b
"""
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
expected_concept = get_def_concept(
name="a plus b",
where="a,b\n",
pre="isinstance(a, int) and isinstance(b, float)\n",
post="isinstance(res, int)\n",
body=PN("res = a + b\n ", "exec")
)
assert res.status
assert isinstance(return_value, ParserResultConcept)
assert return_value.value == expected_concept
def test_i_can_have_mutilines_declarations(self):
text = """
def concept add one to a as
def func(x):
return x+1
func(a)
"""
expected_concept = get_def_concept(
name="add one to a ",
body=PN("def func(x):\n return x+1\nfunc(a)\n", "exec")
)
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert res.status
assert isinstance(return_value, ParserResultConcept)
assert return_value.value == expected_concept
def test_i_can_use_colon_to_use_indentation(self):
text = """
def concept add one to a as:
def func(x):
return x+1
func(a)"""
expected_concept = get_def_concept(
name="add one to a ",
body=PythonNode(
"def func(x):\n return x+1\nfunc(a)",
ast.parse("def func(x):\n return x+1\nfunc(a)", mode="exec"))
)
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert res.status
assert isinstance(return_value, ParserResultConcept)
assert return_value.value == expected_concept
def test_indentation_is_mandatory_after_a_colon(self):
text = """
def concept add one to a as:
def func(x):
return x+1
func(a)
"""
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert not res.status
assert isinstance(return_value, ParserResultConcept)
assert isinstance(return_value.value[0], SyntaxErrorNode)
assert return_value.value[0].message == "Indentation not found."
def test_indentation_is_not_allowed_if_the_colon_is_missing(self):
text = """
def concept add one to a as
def func(x):
return x+1
func(a)
"""
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert not res.status
assert context.sheerka.isinstance(return_value, BuiltinConcepts.TOO_MANY_ERRORS)
def test_name_is_mandatory(self):
text = "def concept as 'hello'"
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert not res.status
assert isinstance(return_value, ParserResultConcept)
assert isinstance(return_value.value[0], SyntaxErrorNode)
assert return_value.value[0].message == "Name is mandatory"
def test_concept_keyword_is_mandatory_but_the_concept_is_recognized(self):
text = "def hello as a where b pre c post d"
expected_concept = get_def_concept(name="hello", body="a ", where="b ", pre="c ", post="d")
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert not res.status
assert isinstance(return_value, ParserResultConcept)
assert isinstance(return_value.value[0], UnexpectedTokenErrorNode)
assert return_value.value[0].message == "Syntax error."
assert return_value.value[0].expected_tokens == [Keywords.CONCEPT]
assert return_value.try_parsed == expected_concept
@pytest.mark.parametrize("text", [
"def concept hello where 1+",
"def concept hello pre 1+",
"def concept hello post 1+",
"def concept hello as 1+"
])
def test_i_can_detect_error_in_declaration(self, text):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert not res.status
assert sheerka.isinstance(return_value, BuiltinConcepts.TOO_MANY_ERRORS)
def test_new_line_is_not_allowed_in_the_name(self):
text = "def concept hello \n my friend as 'hello'"
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
return_value = res.value
assert not res.status
assert return_value.value == [SyntaxErrorNode([], "Newline are not allowed in name.")]
def test_i_can_parse_def_concept_from_bnf(self):
text = "def concept name from bnf a_concept | 'a_string' as __definition[0]"
sheerka, context, parser, a_concept = self.init_parser("a_concept")
res = parser.parse(context, ParserInput(text))
node = res.value.value
definition = OrderedChoice(ConceptExpression(a_concept, rule_name="a_concept"), StrMatch("a_string"))
parser_result = ParserResultConcept(BnfParser(), "a_concept | 'a_string'", None, definition, definition)
expected = get_def_concept(name="name", body="__definition[0]", bnf_def=parser_result)
assert res.status
assert res.who == parser.name
assert res.value.source == text
assert isinstance(res.value, ParserResultConcept)
assert node == expected
def test_i_can_parse_def_concept_where_bnf_references_itself(self):
text = "def concept name from bnf 'a' + name?"
sheerka, context, parser, a_concept = self.init_parser("a_concept")
parser.parse(context, ParserInput(text))
assert not parser.has_error
@pytest.mark.parametrize("text", [
"def concept name from bnf as here is my body",
"def concept name from def as here is my body",
"def concept name from as here is my body"
])
def test_i_can_detect_empty_bnf_declaration(self, text):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
assert not res.status
assert res.value.value[0] == SyntaxErrorNode([], "Empty declaration")
@pytest.mark.parametrize("text", [
"def concept addition from a plus b as a + b",
"def concept addition from def a plus b as a + b"])
def test_i_can_def_concept_from_definition(self, text):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
expected = get_def_concept("addition", definition="a plus b", body="a + b")
node = res.value.value
assert res.status
assert res.who == parser.name
assert res.value.source == text
assert isinstance(res.value, ParserResultConcept)
assert node == expected
def test_i_can_detect_not_for_me(self):
text = "hello world"
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
assert not res.status
assert context.sheerka.isinstance(res.value, BuiltinConcepts.NOT_FOR_ME)
assert isinstance(res.value.body[0], CannotHandleErrorNode)
def test_i_can_parse_is_a(self):
text = "the name of my 'concept' isa the name of the set"
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
expected = IsaConceptNode([],
concept=NameNode(list(Tokenizer("the name of my 'concept'"))),
set=NameNode(list(Tokenizer("the name of the set"))))
assert res.status
assert res.who == parser.name
assert res.value.source == text
assert isinstance(res.value, ParserResultConcept)
assert res.value.value == expected
@pytest.mark.parametrize("text", [
"concept",
"isa number",
"name isa",
"def",
"def concept_name"
])
def test_i_cannot_parse_invalid_entries(self, text):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
assert not res.status
assert isinstance(res.body, ParserResultConcept)
assert isinstance(res.body.body[0], UnexpectedTokenErrorNode)
@pytest.mark.parametrize("text, error_msg, error_text", [
("'name", "Missing Trailing quote", "'name"),
("foo isa 'name", "Missing Trailing quote", "'name"),
("def concept 'name", "Missing Trailing quote", "'name"),
("def concept name as 'body", "Missing Trailing quote", "'body"),
("def concept name from bnf 'expression", "Missing Trailing quote", "'expression"),
("def concept c::", "Concept identifiers not found", ""),
])
def test_i_cannot_parse_when_tokenizer_fails(self, text, error_msg, error_text):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR)
assert isinstance(res.body.body[0], LexerError)
assert res.body.body[0].message == error_msg
assert res.body.body[0].text == error_text
def test_i_cannot_parse_bnf_definition_referencing_unknown_concept(self):
text = "def concept name from bnf unknown"
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
assert not res.status
assert context.sheerka.isinstance(res.value, BuiltinConcepts.UNKNOWN_CONCEPT)
assert res.value.body == ("key", "unknown")