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

import enum
from src.syntax import Syntax, SyntaxType


# Errors that can happen when parsing
class ParseError(enum.Enum):
    NO_TOKEN = enum.auto()  # pragma: no mutate
    NOT_TOKEN = enum.auto()  # pragma: no mutate
    WRONG_TOKEN = enum.auto()  # pragma: no mutate
    FOUND_STARTTEXT = enum.auto()  # pragma: no mutate
    FOUND_STARTNOTE = enum.auto()  # pragma: no mutate


# Exception thrown when a parse error is encountered
class ParseErrorException(BaseException):
    def __init__(self, error, syntax, expected):
        self.error = error
        self.syntax = syntax
        self.expected = expected

    def __str__(self):
        return (
            "ParseErrorException(error %s, syntax %s, expected %s)"  # pragma: no mutate
            % (  # pragma: no mutate
                self.error,
                self.syntax,
                self.expected,
            )
        )

    def __eq__(self, other):
        return (
            self.error == other.error
            and self.syntax == other.syntax
            and self.expected == other.expected
        )


# Reads a token, possibly of a certain value
def read_token(stream, value):
    s = stream.pop()
    if s is None:
        raise ParseErrorException(ParseError.NO_TOKEN, None, None)
    elif s.type != SyntaxType.TOKEN:
        raise ParseErrorException(ParseError.NOT_TOKEN, s, None)
    elif value is not None and s.value != value:
        raise ParseErrorException(ParseError.WRONG_TOKEN, s, value)
    return s


# Parses a text syntax node
def parse_text(stream):
    buffer = ""
    s = read_token(stream, "StartText")
    location = s.location
    # Parse following tokens
    while True:
        s = read_token(stream, None)
        # Don't allow StartText in text
        if s.value in ["StartText"]:
            raise ParseErrorException(ParseError.FOUND_STARTTEXT, s, None)
        # EndText found, end things
        elif s.value == "EndText":
            break
        else:
            buffer += s.value + " "
    type = SyntaxType.TEXT
    value = buffer[:-1]  # Drop trailing space
    return Syntax(value, location, type)


# Skip a note
def skip_note(stream):
    read_token(stream, "StartNote")
    while True:
        s = read_token(stream, None)
        # Don't allow StartNote in notes
        if s.value in ["StartNote"]:
            raise ParseErrorException(ParseError.FOUND_STARTNOTE, s, None)
        # EndNote found, end things
        elif s.value == "EndNote":
            break
    return None


# Parses tokens
def parse(tokens):
    return tokens