3
\$\begingroup\$

This is an implementation of a general Purpose Parser/Matcher that can operate on any input stream of objects and will return all possible combinations.

In this case I do not value performance that much, but if you find something major, tell me.

This does by design no optimizations before parsing to be easily editable.

from __future__ import annotations

from typing import Iterator, TypeVar, Iterable, Generic, List

T = TypeVar('T')


class Rewindable(Iterator[T]):
    """
    A Rewindable Wrapper around an iterator the allows for 'rewinding'
    the iterator by reloading an old state
    """

    def __init__(self, iterator: Iterable[T]):
        self._iterator = iter(iterator)
        self._index = 0
        self._cache = []

    def __next__(self) -> T:
        self._index += 1
        while self._index > len(self._cache):
            self._cache.append(next(self._iterator))
        return self._cache[self._index - 1]

    def get_state(self) -> object:
        """Returns an object indicating the current position
        that can be reloaded via `load_state`"""
        return self._index

    def load_state(self, state: object):
        """Reloads a state returned by `get_state`"""
        assert isinstance(state, int)
        self._index = state


IN = TypeVar('IN')
OUT = TypeVar('OUT')


class Parser(Generic[IN, OUT]):
    """
    Base class for Parser which match pattern in an rewindable
    input stream and return all possible matches
    """

    def parse(self, in_stream: Rewindable[IN]) -> Iterator[Iterator[OUT]]:
        """
        Matches the pattern in the Rewindable stream `in_stream`
        Returns an Iterator over all possible matches (also iterator)
        The implementation must insure that `in_stream` is reset
        after StopIteration is thrown
        """
        raise NotImplementedError


class DirectParser(Parser):
    """
    A Parser that matches the value `expected` and yields `result` in response
    """

    def __init__(self, expected: IN, result: OUT):
        self.expected = expected
        self.result = result

    def __repr__(self):
        return f"<{self.expected!r}>"

    def parse(self, in_stream: Rewindable[IN]) -> Iterator[Iterator[OUT]]:
        state = in_stream.get_state()
        try:
            n = next(in_stream)
        except StopIteration:
            return
        if n == self.expected:
            yield iter((self.result,))
        in_stream.load_state(state)


class ORParser(Parser[IN, OUT]):
    """
    A Parser that matches any Parser of `sub_parser` and returns all possible results of each one
    """

    def __init__(self, sub_parser: List[Parser[IN, OUT]]):
        self._sub_parser = list(sub_parser)

    def __repr__(self):
        return '(' + '|'.join(repr(sp) for sp in self._sub_parser) + ')'

    def parse(self, in_stream: Rewindable[IN]) -> Iterator[Iterator[OUT]]:
        for sp in self._sub_parser:
            yield from sp.parse(in_stream)


class ANDParser(Parser[IN, OUT]):
    """
    A  Parser that matches the Parsers of `sub_parser` and returns all possible combinations
    """

    def __init__(self, sub_parser: List[Parser[IN, OUT]]):
        self._sub_parser = list(sub_parser)

    def __repr__(self):
        return '(' + ' '.join(repr(sp) for sp in self._sub_parser) + ')'

    def parse(self, in_stream: Rewindable[IN]) -> Iterator[Iterator[OUT]]:
        iterator_stack = [self._sub_parser[0].parse(in_stream)]
        try:
            result = [tuple(next(iterator_stack[0]))]
        except StopIteration:
            return
        skip = False
        while len(iterator_stack) > 0:
            if not skip:
                if len(iterator_stack) == len(self._sub_parser):
                    assert len(result) == len(self._sub_parser)
                    yield (v for t in result for v in t)
                else:
                    iterator_stack.append(self._sub_parser[len(iterator_stack)].parse(in_stream))
                    result.append(())
            try:
                result[-1] = tuple(next(iterator_stack[-1]))
                skip = False
            except StopIteration:
                skip = True
                result.pop()
                iterator_stack.pop()


class RepeatingNGParser(Parser[IN, OUT]):
    """
    A Parser that matches `min_count` to `max_count` repetitions of Parser `parser`
    and returns all possible combinations (Non Greedy, matches as few repetitions as possible first
    `max_count` can be `None` to indicate that the repetition can be infinite
    """

    def __init__(self, parser: Parser[IN, OUT], min_count: int, max_count: int = None):
        self.parser = parser
        self.min_count = min_count
        self.max_count = max_count

    def parse(self, in_stream: Rewindable[IN]) -> Iterator[Iterator[OUT]]:
        count = self.min_count
        if count == 0:  # Special case Optional matching
            yield iter(())
            count = 1
        while self.max_count is None or self.max_count > count:
            iterator_stack = [self.parser.parse(in_stream)]
            try:
                result = [tuple(next(iterator_stack[0]))]
            except StopIteration:
                return
            skip = False
            any_result = False
            while len(iterator_stack) > 0:
                if not skip:
                    if len(iterator_stack) >= count:
                        assert len(result) == len(iterator_stack)
                        any_result = True
                        yield (v for t in result for v in t)
                    else:
                        iterator_stack.append(self.parser.parse(in_stream))
                        result.append(())
                try:
                    result[-1] = tuple(next(iterator_stack[-1]))
                    skip = False
                except StopIteration:
                    skip = True
                    result.pop()
                    iterator_stack.pop()
            count += 1
            if not any_result:
                return
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.