Newer
Older
NewLang / tests / test_syntax.py
# SPDX-License-Identifier: LGPL-2.1-only
# Copyright 2022 Jookia <contact@jookia.org>

from hypothesis import given, assume
from hypothesis.strategies import (
    booleans,
    characters,
    composite,
    integers,
    lists,
    one_of,
    sampled_from,
    text,
)

from src.syntax import Syntax, SyntaxLocation, SyntaxStream, SyntaxType

# Keywords recognized by the language
keywords = [
    "NewLang",
    "Done",
    "Set",
    "To",
    "EndSet",
    "If",
    "Then",
    "Else",
    "EndIf",
    "StartNote",
    "EndNote",
    "StartText",
    "EndText",
]


# Draws a random token location
@composite
def draw_token_location(draw):
    line = draw(integers())
    column = draw(integers())
    filename = draw(text())
    return SyntaxLocation(line, column, filename)


# Draws a random syntax type
@composite
def draw_syntax_type(draw):
    return draw(sampled_from(list(SyntaxType)))


# Draws a token syntax value
@composite
def draw_syntax_token(draw):
    value = draw(draw_token_classified())
    location = draw(draw_token_location())
    type = SyntaxType.TOKEN
    return Syntax(value.value, location, type)


# Draws a text syntax value
@composite
def draw_syntax_text(draw):
    value = draw(text())
    location = draw(draw_token_location())
    type = SyntaxType.TEXT
    return Syntax(value, location, type)


# Draws a random syntax
@composite
def draw_syntax_random(draw):
    strategies = [
        draw_syntax_token(),
        draw_syntax_text(),
    ]
    return draw(one_of(strategies))


# Test syntax getters
@given(text(), draw_token_location(), draw_syntax_type())
def test_syntax_syntax_getters(value, location, type):
    # Use text as a somewhat random value
    test = Syntax(value, location, type)
    assert test.value == value
    assert test.location == location
    assert test.type == type


# Test syntax equals
@given(draw_syntax_random(), draw_syntax_random())
def test_syntax_syntax_equality(syntax1, syntax2):
    equals = (
        syntax1.type == syntax2.type
        and syntax1.value == syntax2.value
        and syntax1.location == syntax2.location
    )
    assert (syntax1 == syntax2) == equals


# Draws a random token
@composite
def draw_token_random(draw):
    value = draw(text())
    location = draw(draw_token_location())
    return Syntax(value, location, SyntaxType.TOKEN)


# Draws an unknown token
@composite
def draw_token_unknown(draw):
    reserved = " \n\t"
    token = draw(draw_token_random())
    chars = characters(blacklist_characters=reserved)
    value = draw(text(alphabet=chars, min_size=1))
    assume(value not in ["True", "False"])
    assume(value not in keywords)
    assume(value[0:2] != "#!")
    return Syntax(value, token.location, SyntaxType.TOKEN)


# Draws a space token
@composite
def draw_token_space(draw):
    space = " \t"
    token = draw(draw_token_random())
    value = draw(sampled_from(space))
    return Syntax(value, token.location, SyntaxType.TOKEN)


# Draws a new line token
@composite
def draw_token_newline(draw):
    token = draw(draw_token_random())
    value = "\n"
    return Syntax(value, token.location, SyntaxType.TOKEN)


# Draws a bool token
@composite
def draw_token_bool(draw):
    token = draw(draw_token_random())
    if draw(booleans()):
        value = "True"
    else:
        value = "False"
    return Syntax(value, token.location, SyntaxType.TOKEN)


# Draws a keyword token
@composite
def draw_token_keyword(draw):
    token = draw(draw_token_random())
    value = draw(sampled_from(keywords))
    return Syntax(value, token.location, SyntaxType.TOKEN)


# Draws a shebang token
@composite
def draw_token_shebang(draw):
    token = draw(draw_token_random())
    value = "#!" + draw(text())
    return Syntax(value, token.location, SyntaxType.TOKEN)


# Draws a classified token
@composite
def draw_token_classified(draw):
    strategies = [
        draw_token_unknown(),
        draw_token_space(),
        draw_token_newline(),
        draw_token_bool(),
        draw_token_keyword(),
        draw_token_shebang(),
    ]
    token = draw(one_of(strategies))
    return token


# Test location getters
@given(integers(), integers(), text())
def test_syntax_location_getters(line, column, filename):
    test = SyntaxLocation(line, column, filename)
    assert test.line == line
    assert test.column == column
    assert test.file == filename


# Test location equals
@given(draw_token_location(), draw_token_location())
def test_syntax_location_equality(location1, location2):
    equals = (
        location1.line == location2.line
        and location1.column == location2.column
        and location1.file == location2.file
    )
    assert (location1 == location2) == equals


# Test token getters
@given(text(), draw_token_location())
def test_syntax_token_getters(value, location):
    test = Syntax(value, location, SyntaxType.TOKEN)
    assert test.value == value
    assert test.location == location


# Test token equals
@given(draw_token_random(), draw_token_random())
def test_syntax_token_equality(token1, token2):
    equals = token1.value == token2.value and token1.location == token2.location
    assert (token1 == token2) == equals


# Tests that a syntax stream reads items correctly
# We expect the following behaviour:
# - All items are popped in order
# - None is returned at the end of the stream
@given(lists(draw_syntax_random()))
def test_syntax_syntax_stream(nodes):
    stream = SyntaxStream(nodes.copy())
    read = []
    node = stream.pop()
    while node is not None:
        read.append(node)
        node = stream.pop()
    assert read == nodes
    assert stream.pop() is None