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

import enum

from hypothesis import assume
from hypothesis.strategies import composite, just, one_of

from src.parse import ParseContext, ParseError, ParseErrorException, ParseTask, Parser
from tests.parse.templates import template_parse_valid, template_parse_invalid
from tests.test_token import (
    draw_token_keyword,
    draw_token_unknown,
    static_token_by_value,
)


# Values indicating what a parser did
class ParserMockAction(enum.Enum):
    PARSE_BOOL = enum.auto()
    PARSE_TEXT = enum.auto()
    PARSE_REFERENCE = enum.auto()


# Dummy Parser for testing value parsing
# Instead of actually parsing values just return a static value to show what
# the parser would normally do
class ParserValueMockValid(Parser):
    def parse_bool(self, stream, context):
        stream.pop()
        return ParserMockAction.PARSE_BOOL

    def parse_text(self, stream, context):
        stream.pop()
        return ParserMockAction.PARSE_TEXT

    def parse_reference(self, stream, context):
        stream.pop()
        return ParserMockAction.PARSE_REFERENCE


# Dummy Parser for testing error propagation
# Uses parser mock values as errors
class ParserValueMockError(Parser):
    def parse_bool(self, stream, context):
        raise ParseErrorException(ParserMockAction.PARSE_BOOL, None, None, context)

    def parse_text(self, stream, context):
        raise ParseErrorException(ParserMockAction.PARSE_TEXT, None, None, context)

    def parse_reference(self, stream, context):
        raise ParseErrorException(ParserMockAction.PARSE_REFERENCE, None, None, context)


# Generates a strategy for a valid word and parser action
def token_and_action(word, action):
    return just(([static_token_by_value(word)], action))


# Draws tokens for values based on literals
@composite
def draw_token_value_literal(draw):
    strategies = [
        token_and_action("True", ParserMockAction.PARSE_BOOL),
        token_and_action("False", ParserMockAction.PARSE_BOOL),
        token_and_action("StartText", ParserMockAction.PARSE_TEXT),
    ]
    return draw(one_of(strategies))


# Draws tokens to make a value based on a reference
@composite
def draw_token_value_reference(draw):
    token = draw(draw_token_unknown())
    return ([token], ParserMockAction.PARSE_REFERENCE)


# Draws tokens for a valid value
@composite
def draw_token_value_valid(draw):
    strategies = [
        draw_token_value_literal(),
        draw_token_value_reference(),
    ]
    return draw(one_of(strategies))


# Tests parsing a literal value
# We expect the following behaviour:
# - parse_value parses a Bool if it sees True or False
# - parse_value parses a Text if it sees StartText
# template_parse_valid provides general parsing properties
@template_parse_valid(ParserValueMockValid().parse_value, draw_token_value_literal())
def test_parse_value_literal():
    pass


# Tests parsing a reference value
# We expect the following behaviour:
# - parse_value parses a Reference if it sees an unknown value
# template_parse_valid provides general parsing properties
@template_parse_valid(ParserValueMockValid().parse_value, draw_token_value_reference())
def test_parse_value_reference():
    pass


# Tests parsing a keyword as a value fails
# We expect the following behaviour:
# - Error if a keyword is encountered
# - Have ParseError.RESERVED_NAME as the exception code
# - Have ParseTask.PARSE_VALUE as the context's parse task
@template_parse_invalid(Parser().parse_value)
def test_parse_value_invalid_name(draw, parent_context):
    token = draw(draw_token_keyword())
    assume(token.value != "StartText")
    context = ParseContext(ParseTask.PARSE_VALUE, token, parent_context)
    error = ParseErrorException(ParseError.RESERVED_NAME, token, None, context)
    return ([token], error)


# Tests parsing empty value
# We expect the following behaviour:
# - Have ParseError.NO_TOKEN as the exception code
# - Have ParseTask.PARSE_VALUE as the context's parse task
@template_parse_invalid(Parser().parse_value)
def test_parse_value_invalid_empty(draw, parent_context):
    context = ParseContext(ParseTask.PARSE_VALUE, None, parent_context)
    error = ParseErrorException(ParseError.NO_TOKEN, None, None, context)
    return ([], error)


# Tests parse_value error propagation
# We expect the following behaviour:
# - Errors from parsing are propagated and have the correct context
# - Have ParseTask.PARSE_VALUE as the context's parse task
@template_parse_invalid(ParserValueMockError().parse_value)
def test_parse_value_error_propagation(draw, parent_context):
    (tokens, action) = draw(draw_token_value_valid())
    context = ParseContext(ParseTask.PARSE_VALUE, tokens[0], parent_context)
    error = ParseErrorException(action, None, None, context)
    return (tokens, error)