342 lines
11 KiB
Python
342 lines
11 KiB
Python
import pytest
|
|
import ast
|
|
|
|
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts, ReturnValueConcept
|
|
from core.sheerka import Sheerka, ExecutionContext
|
|
from parsers.ConceptLexerParser import OrderedChoice, StrMatch, ConceptExpression
|
|
from parsers.PythonParser import PythonParser, PythonNode
|
|
from core.tokenizer import Keywords, Tokenizer, LexerError
|
|
from parsers.DefaultParser import DefaultParser, NameNode, SyntaxErrorNode, CannotHandleErrorNode, IsaConceptNode
|
|
from parsers.DefaultParser import UnexpectedTokenErrorNode, DefConceptNode
|
|
from parsers.BnfParser import BnfParser
|
|
|
|
from sdp.sheerkaDataProvider import Event
|
|
|
|
|
|
def get_def_concept(name, where=None, pre=None, post=None, body=None, definition=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 definition:
|
|
def_concept.definition = ReturnValueConcept(
|
|
"parsers.Bnf",
|
|
True,
|
|
definition)
|
|
|
|
return def_concept
|
|
|
|
|
|
def get_context():
|
|
sheerka = Sheerka(skip_builtins_in_db=True)
|
|
sheerka.initialize("mem://")
|
|
return ExecutionContext("test", Event(), sheerka)
|
|
|
|
|
|
def get_concept_part(part):
|
|
if isinstance(part, str):
|
|
node = PythonNode(part, ast.parse(part, mode="eval"))
|
|
return ReturnValueConcept(
|
|
who="parsers.Default",
|
|
status=True,
|
|
value=ParserResultConcept(
|
|
source=part,
|
|
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
|
|
|
|
|
|
@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(text, expected):
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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():
|
|
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
|
|
"""
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), text)
|
|
return_value = res.value
|
|
expected_concept = get_def_concept(
|
|
name="a plus b",
|
|
where="a,b",
|
|
pre="isinstance(a, int) and isinstance(b, float)",
|
|
post="isinstance(res, int)",
|
|
body=PythonNode("res = a + b", ast.parse("res = a + b", mode="exec"))
|
|
)
|
|
|
|
assert res.status
|
|
assert isinstance(return_value, ParserResultConcept)
|
|
assert return_value.value == expected_concept
|
|
|
|
|
|
def test_i_can_have_mutilines_declarations():
|
|
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"))
|
|
)
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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():
|
|
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"))
|
|
)
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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():
|
|
text = """
|
|
def concept add one to a as:
|
|
def func(x):
|
|
return x+1
|
|
func(a)
|
|
"""
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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():
|
|
text = """
|
|
def concept add one to a as
|
|
def func(x):
|
|
return x+1
|
|
func(a)
|
|
"""
|
|
context = get_context()
|
|
sheerka = context.sheerka
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(context, text)
|
|
return_value = res.value
|
|
|
|
assert not res.status
|
|
assert isinstance(return_value, ParserResultConcept)
|
|
assert sheerka.isinstance(return_value.value[0], BuiltinConcepts.TOO_MANY_ERRORS)
|
|
|
|
|
|
def test_name_is_mandatory():
|
|
text = "def concept as 'hello'"
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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():
|
|
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")
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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(text):
|
|
context = get_context()
|
|
sheerka = context.sheerka
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(context, text)
|
|
return_value = res.value
|
|
|
|
assert not res.status
|
|
assert isinstance(return_value, ParserResultConcept)
|
|
assert sheerka.isinstance(return_value.value[0], BuiltinConcepts.TOO_MANY_ERRORS)
|
|
|
|
|
|
def test_new_line_is_not_allowed_in_the_name():
|
|
text = "def concept hello \n my friend as 'hello'"
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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_regex():
|
|
text = "def concept name from bnf a_concept | 'a_string' as __definition[0]"
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), text)
|
|
node = res.value.value
|
|
definition = OrderedChoice(ConceptExpression("a_concept"), StrMatch("a_string"))
|
|
parser_result = ParserResultConcept(BnfParser(), "a_concept | 'a_string'", definition, definition)
|
|
expected = get_def_concept(name="name", body="__definition[0]", definition=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_detect_empty_bnf_declaration():
|
|
text = "def concept name from bnf as __definition[0]"
|
|
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), text)
|
|
|
|
assert not res.status
|
|
assert res.value.value[0] == SyntaxErrorNode([], "Empty declaration")
|
|
|
|
|
|
def test_i_can_detect_not_for_me():
|
|
text = "hello world"
|
|
context = get_context()
|
|
parser = DefaultParser()
|
|
res = parser.parse(context, 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():
|
|
parser = DefaultParser()
|
|
text = "the name of my 'concept' isa the name of the set"
|
|
res = parser.parse(get_context(), 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(text):
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), 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 name not found", ""),
|
|
])
|
|
def test_i_cannot_parse_when_tokenizer_fails(text, error_msg, error_text):
|
|
parser = DefaultParser()
|
|
res = parser.parse(get_context(), text)
|
|
|
|
assert not res.status
|
|
assert isinstance(res.body, ParserResultConcept)
|
|
assert isinstance(res.body.body[0], LexerError)
|
|
assert res.body.body[0].message == error_msg
|
|
assert res.body.body[0].text == error_text
|