diff --git a/src/parse.py b/src/parse.py index 396f63b..c102911 100644 --- a/src/parse.py +++ b/src/parse.py @@ -34,6 +34,7 @@ PARSE_TEXT = enum.auto() # pragma: no mutate PARSE_BOOL = enum.auto() # pragma: no mutate PARSE_REFERENCE = enum.auto() # pragma: no mutate + PARSE_VALUE = enum.auto() # pragma: no mutate # Context used for parse error exception @@ -188,6 +189,21 @@ raise ParseErrorException(ParseError.RESERVED_NAME, t, None, context) return Reference(t.value) + # Parses a value + def parse_value(self, stream, parent_context): + context = ParseContext(ParseTask.PARSE_VALUE, stream.peek(), parent_context) + t = stream.peek() + if t is None: + raise ParseErrorException(ParseError.NO_TOKEN, None, None, context) + elif t.value in ["True", "False"]: + return self.parse_bool(stream, context) + elif t.value == "StartText": + return self.parse_text(stream, context) + elif t.value in reserved_names: + raise ParseErrorException(ParseError.RESERVED_NAME, t, None, context) + else: + return self.parse_reference(stream, context) + # Parses tokens def parse(tokens, context): diff --git a/tests/parse/test_value.py b/tests/parse/test_value.py new file mode 100644 index 0000000..745a137 --- /dev/null +++ b/tests/parse/test_value.py @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: LGPL-2.1-only +# Copyright 2022 Jookia + +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 ParserValueMock(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 + + +# 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) + + +# 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(ParserValueMock().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(ParserValueMock().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 +@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 +@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)