diff --git a/src/pyalibert/__init__.py b/src/pyalibert/__init__.py index 2899a23..0db4195 100644 --- a/src/pyalibert/__init__.py +++ b/src/pyalibert/__init__.py @@ -1,4 +1,4 @@ from .parser import Parser, just, regex, end from .result import Result, ParseError -from .forward import FwDeclaration +from .forward import Declare diff --git a/src/pyalibert/forward.py b/src/pyalibert/forward.py index fd59dd0..9e2e7a7 100644 --- a/src/pyalibert/forward.py +++ b/src/pyalibert/forward.py @@ -1,23 +1,39 @@ -from typing import Generic, TypeVar +from typing import Generic, Optional, TypeVar + +from .transformer import Transformer from .result import Result from .parser import Parser P = TypeVar("P") -class FwDeclaration(Generic[P]): - parser: None | Parser[P] +class FutureTransform(Transformer[P]): + actual: Optional[Transformer[P]] + def __init__(self) -> None: + self.actual = None + + def define(self, actual: Transformer[P]): + if self.actual is not None: + raise Exception("Redefinition of parser.") + self.actual = actual + + def parse(self, stream: str, at_index: int) -> Result[P]: + if self.actual is None: + raise Exception("Using forwarded definition of parser without defining first.") + return self.actual.parse(stream, at_index) + + +P = TypeVar("P") +class Declare(Generic[P]): + parser: Parser[P] + transform: FutureTransform[P] def __init__(self) -> None: - self.parser = None + transform = FutureTransform[P]() + self.parser = Parser(transform) + self.transform = transform def p(self): - def inner(stream: str, index: int) -> Result[P]: - if self.parser is None: - raise Exception("Using forwarded definition of parser without defining first.") - return self.parser.inner(stream, index) - return Parser(inner) + return self.parser def define(self, parser: Parser[P]): - if self.parser is not None: - raise Exception("Redefinition of parser.") - self.parser = parser + self.transform.define(parser.inner) diff --git a/src/pyalibert/parser.py b/src/pyalibert/parser.py index fce3065..b382217 100644 --- a/src/pyalibert/parser.py +++ b/src/pyalibert/parser.py @@ -1,165 +1,81 @@ -from typing import Callable, Generic, TypeVar, Union -from .result import Result, Success, Failure, ParseError -import re +from dataclasses import dataclass +from typing import Callable, Generic, TypeVar + +from .result import ParseError, Failure +from .transformer import Transformer +from .transformers import ( + EndTransform, + JustTransform, + RegexTransform, + AndTransform, + OrTransform, + ListTransform, + SepListTransform, + OptionTransform, + MapTransform, + ValueTransform +) def regex(pattern: str): - def inner(stream: str, index: int) -> Result[str]: - do_match = re.match(pattern, stream[index:]) - if do_match is None: - return Result.failure(index, set(f"matching /{pattern}/")) - else: - match = do_match[0] - return Result.success(match, index + len(match)) - return Parser(inner) - -def just(text: str): - length = len(text) - def inner(stream: str, index: int) -> Result[str]: - end = index + length - if stream[index:end] == text: - return Result.success(text, end) - else: - return Result.failure(index, set([text])) - return Parser(inner) + return Parser(RegexTransform(pattern)) -def end() -> "Parser[None]": - def inner(stream: str, index: int): - if stream[index:] == "": - return Result.success(None, index) - else: - return Result.failure(index, set()) - return Parser(inner) +def just(word: str): + return Parser(JustTransform(word)) + + +def end(): + return Parser(EndTransform()) P = TypeVar("P") -O = TypeVar("O") T = TypeVar("T") - +O = TypeVar("O") +@dataclass class Parser(Generic[P]): - inner: Callable[[str, int], Result[P]] - - def __init__(self, inner: Callable[[str, int], Result[P]]): - self.inner = inner + inner: "Transformer[P]" - def parse(self, stream: str): - (result, _rest) = self.and_then(end()).parse_part(stream) - (value, _end) = result - return value + def parse(self, stream: str) -> P: + parser = self.and_(end()).map(lambda value : value[0]) + parsed = parser.inner.parse(stream, 0) + if isinstance(parsed, Failure): + raise ParseError(parsed, stream) + return parsed.value - def parse_part(self, stream: str): - result = self.inner(stream, 0) - if isinstance(result.actual, Success): - rest = stream[result.actual.next_index:] - return (result.actual.value, rest) - else: - raise ParseError(result.actual, stream) + def and_(self, other: "Parser[T]"): + return Parser(AndTransform(self.inner, other.inner)) - def map(self, transform: Callable[[P], O]) -> "Parser[O]": - def inner(stream: str, index: int): - result = self.inner(stream, 0) - if isinstance(result.actual, Success): - mapped = transform(result.actual.value) - return Result.success(mapped, result.actual.next_index) - return result - return Parser(inner) - - def and_then(self, other: "Parser[T]"): - def inner(stream: str, index: int) -> Result[tuple[P, T]]: - result = self.inner(stream, index) - if isinstance(result.actual, Failure): - return Result(result.actual) - value_left = result.actual.value - next_index = result.actual.next_index - - result = other.inner(stream, next_index) - if isinstance(result.actual, Failure): - return Result(result.actual) - value_right = result.actual.value - next_index = result.actual.next_index - - return Result.success((value_left, value_right), next_index) - return Parser(inner) - - def or_else(self, other: "Parser[T]"): - def inner(stream: str, index: int) -> Result[Union[P, T]]: - result_left = self.inner(stream, index) - if isinstance(result_left.actual, Success): - return Result.success(result_left.actual.value, result_left.actual.next_index) - result_right = other.inner(stream, index) - if isinstance(result_right.actual, Success): - return Result.success(result_right.actual.value, result_right.actual.next_index) - return Result.failure(index, result_left.actual.expected.union(result_right.actual.expected)) - return Parser(inner) - - def repeat(self): - def inner(stream: str, index: int) -> Result[list[P]]: - values = list[P]() - while True: - result = self.inner(stream, index) - if isinstance(result.actual, Failure): - break - values.append(result.actual.value) - if result.actual.next_index == index: - raise Exception("Parsing empty patterns repeatedly.") - index = result.actual.next_index - return Result.success(values, index) - return Parser(inner) + def or_(self, other: "Parser[T]"): + return Parser(OrTransform(self.inner, other.inner)) def or_not(self): - def inner(stream: str, index: int) -> Result[Union[P, None]]: - result = self.inner(stream, index) - if isinstance(result.actual, Failure): - return Result.success(None, index) - return Result.success(result.actual.value, result.actual.next_index) - return Parser(inner) + return Parser(OptionTransform(self.inner)) - def value(self, value: T) -> "Parser[T]": - return self.map(lambda _: value) + def repeat(self): + return Parser(ListTransform(self.inner)) - def sep_by(self, other: "Parser[T]"): - parser = self.or_not().and_then(other.and_then(self).repeat()) - def mapping(value: tuple[P | None, list[tuple[T, P]]]): - (first, rest) = value - if first is None: return list[P]() - return [first, *(value for (_sep, value) in rest)] - mapped = parser.map(mapping) - return mapped + def map(self, transform: Callable[[P], O]): + return Parser(MapTransform(self.inner, transform)) - # def skip_until(self, other: "Parser[T]"): - # def inner(stream: str, index: int) -> Result[tuple[P, str, T]]: - # pass - # return Parser(inner) + def set(self, value: T) -> "Parser[T]": + return Parser(ValueTransform(self.inner, value)) + + def sep_by(self, sep: "Parser[T]"): + return Parser(SepListTransform(self.inner, sep.inner)) # | def __or__(self, other: "Parser[T]"): - return self.or_else(other) + return self.or_(other) # & def __and__(self, other: "Parser[T]"): - return self.and_then(other) + return self.and_(other) # >> def __rshift__(self, other: "Parser[T]"): - return self.and_then(other).map(lambda v: v[1]) + return self.and_(other).map(lambda v: v[1]) # << def __lshift__(self, other: "Parser[T]"): - return self.and_then(other).map(lambda v: v[0]) - -P = TypeVar("P") -class Transformer(Generic[P]): - def parse(self, stream: str, at_index: int) -> Result[P]: - raise Exception("Abstract method.") - -L = TypeVar("L") -R = TypeVar("R") -class AndParser(Transformer[tuple[L, R]]): - def __init__(self, left: Transformer[L], right: Transformer[R]): - self.left = left - self.right = right - - def parse(self, stream: str, at_index: int) -> Result[tuple[L, R]]: - result_left = self.left.parse(stream, at_index) - if result_left.actual + return self.and_(other).map(lambda v: v[0]) diff --git a/src/pyalibert/py.typed b/src/pyalibert/py.typed new file mode 100644 index 0000000..2c85c45 --- /dev/null +++ b/src/pyalibert/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. The mypy package uses inline types. \ No newline at end of file diff --git a/src/pyalibert/result.py b/src/pyalibert/result.py index edf00ba..1dee550 100644 --- a/src/pyalibert/result.py +++ b/src/pyalibert/result.py @@ -1,8 +1,11 @@ from dataclasses import dataclass from typing import Union, Generic, TypeVar +from .utils import TypeInfo + P = TypeVar("P") +T = TypeVar("T") @dataclass class Success(Generic[P]): value: P @@ -13,7 +16,7 @@ class Success(Generic[P]): class Failure: at_index: int expected: set[str] - depth: int | None + depth: int | None = None P = TypeVar("P") @@ -33,5 +36,5 @@ class ParseError(BaseException): Parsing failed at position {self.failure.at_index} of stream : ${fail_section} Expected one of: {failure.expected} -""" +""".strip() super().__init__(message) diff --git a/src/pyalibert/transformer.py b/src/pyalibert/transformer.py new file mode 100644 index 0000000..d744d45 --- /dev/null +++ b/src/pyalibert/transformer.py @@ -0,0 +1,11 @@ +from abc import abstractmethod +from typing import Generic, TypeVar + +from .result import Result + + +P = TypeVar("P") +class Transformer(Generic[P]): + @abstractmethod + def parse(self, stream: str, at_index: int) -> Result[P]: + raise Exception("Abstract method.") diff --git a/src/pyalibert/transformers/__init__.py b/src/pyalibert/transformers/__init__.py new file mode 100644 index 0000000..1705940 --- /dev/null +++ b/src/pyalibert/transformers/__init__.py @@ -0,0 +1,13 @@ + +from .end import EndTransform +from .just import JustTransform +from .regex import RegexTransform + +from .and_ import AndTransform +from .or_ import OrTransform +from .list import ListTransform +from .sep_list import SepListTransform +from .option import OptionTransform + +from .map import MapTransform +from .value import ValueTransform \ No newline at end of file diff --git a/src/pyalibert/transformers/and_.py b/src/pyalibert/transformers/and_.py new file mode 100644 index 0000000..4b7dca2 --- /dev/null +++ b/src/pyalibert/transformers/and_.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from typing import TypeVar + +from ..result import Result, Success, Failure +from ..transformer import Transformer + + +L = TypeVar("L") +R = TypeVar("R") +@dataclass +class AndTransform(Transformer[tuple[L, R]]): + def __init__(self, left: Transformer[L], right: Transformer[R]): + self.left = left + self.right = right + + def parse(self, stream: str, at_index: int) -> Result[tuple[L, R]]: + result_left = self.left.parse(stream, at_index) + if isinstance(result_left, Failure): + return result_left + result_right = self.right.parse(stream, result_left.next_index) + if isinstance(result_right, Failure): + return result_right + return Success((result_left.value, result_right.value), result_right.next_index) diff --git a/src/pyalibert/transformers/end.py b/src/pyalibert/transformers/end.py new file mode 100644 index 0000000..cbab0bf --- /dev/null +++ b/src/pyalibert/transformers/end.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + +from ..result import Result, Success, Failure +from ..transformer import Transformer + + +@dataclass +class EndTransform(Transformer[None]): + def __init__(self) -> None: + pass + + def parse(self, stream: str, at_index: int) -> Result[None]: + if len(stream) == at_index: + return Success(None, at_index) + return Failure(at_index, set("")) diff --git a/src/pyalibert/transformers/just.py b/src/pyalibert/transformers/just.py new file mode 100644 index 0000000..b0b462b --- /dev/null +++ b/src/pyalibert/transformers/just.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass + +from ..result import Result, Success, Failure +from ..transformer import Transformer + + +@dataclass +class JustTransform(Transformer[str]): + def __init__(self, word: str): + self.word = word + self.word_len = len(self.word) + + def parse(self, stream: str, at_index: int) -> Result[str]: + end_index = at_index + self.word_len + prefix = stream[at_index:end_index] + if prefix == self.word: + return Success(self.word, end_index) + return Failure(at_index, set([self.word])) + diff --git a/src/pyalibert/transformers/list.py b/src/pyalibert/transformers/list.py new file mode 100644 index 0000000..b9e870c --- /dev/null +++ b/src/pyalibert/transformers/list.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass +from typing import TypeVar + +from ..result import Result, Success, Failure +from ..transformer import Transformer + + +T = TypeVar("T") +@dataclass +class ListTransform(Transformer[list[T]]): + def __init__(self, item: Transformer[T]): + self.item = item + + def parse(self, stream: str, at_index: int) -> Result[list[T]]: + values = list[T]() + while True: + result = self.item.parse(stream, at_index) + if isinstance(result, Failure): + break + values.append(result.value) + if result.next_index == at_index: + raise Exception("Parsing empty patterns repeatedly.") + at_index = result.next_index + return Success(values, at_index) diff --git a/src/pyalibert/transformers/map.py b/src/pyalibert/transformers/map.py new file mode 100644 index 0000000..37b0e03 --- /dev/null +++ b/src/pyalibert/transformers/map.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass +from typing import Callable, TypeVar + +from ..result import Result, Success, Failure +from ..transformer import Transformer + + +I = TypeVar("I") +O = TypeVar("O") +@dataclass +class MapTransform(Transformer[O]): + def __init__(self, value: Transformer[I], transform: Callable[[I], O]): + self.value = value + self.transform = transform + + def parse(self, stream: str, at_index: int) -> Result[O]: + result = self.value.parse(stream, at_index) + if isinstance(result, Failure): + return result + transformed = self.transform(result.value) + return Success(transformed, result.next_index) diff --git a/src/pyalibert/transformers/option.py b/src/pyalibert/transformers/option.py new file mode 100644 index 0000000..679fced --- /dev/null +++ b/src/pyalibert/transformers/option.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass +from typing import Optional, TypeVar + +from ..result import Result, Success +from ..transformer import Transformer + + +T = TypeVar("T") +@dataclass +class OptionTransform(Transformer[Optional[T]]): + def __init__(self, value: Transformer[T]): + self.value = value + + def parse(self, stream: str, at_index: int) -> Result[Optional[T]]: + result = self.value.parse(stream, at_index) + if isinstance(result, Success): + return Success(result.value, result.next_index) + return Success(None, at_index) diff --git a/src/pyalibert/transformers/or_.py b/src/pyalibert/transformers/or_.py new file mode 100644 index 0000000..91ec158 --- /dev/null +++ b/src/pyalibert/transformers/or_.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from typing import TypeVar, Union + +from ..result import Result, Success, Failure +from ..transformer import Transformer + + +L = TypeVar("L") +R = TypeVar("R") +@dataclass +class OrTransform(Transformer[Union[L, R]]): + def __init__(self, left: Transformer[L], right: Transformer[R]): + self.left = left + self.right = right + + def parse(self, stream: str, at_index: int) -> Result[Union[L, R]]: + result_left = self.left.parse(stream, at_index) + if isinstance(result_left, Success): + return Success(result_left.value, result_left.next_index) + result_right = self.right.parse(stream, at_index) + if isinstance(result_right, Success): + return Success(result_right.value, result_right.next_index) + return Failure(at_index, result_left.expected.union(result_right.expected)) diff --git a/src/pyalibert/transformers/regex.py b/src/pyalibert/transformers/regex.py new file mode 100644 index 0000000..f55cbb2 --- /dev/null +++ b/src/pyalibert/transformers/regex.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass +import re + +from ..result import Result, Success, Failure +from ..transformer import Transformer + + +@dataclass +class RegexTransform(Transformer[str]): + def __init__(self, pattern: str): + self.pattern = pattern + + def parse(self, stream: str, at_index: int) -> Result[str]: + do_match = re.match(self.pattern, stream[at_index:]) + if do_match is None: + return Failure(at_index, set(f"")) + else: + match = do_match[0] + return Success(match, at_index + len(match)) diff --git a/src/pyalibert/transformers/sep_list.py b/src/pyalibert/transformers/sep_list.py new file mode 100644 index 0000000..4c1619a --- /dev/null +++ b/src/pyalibert/transformers/sep_list.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass +from typing import Optional, TypeVar + +from ..result import Result +from ..transformer import Transformer + +from .map import MapTransform +from .option import OptionTransform +from .and_ import AndTransform +from .list import ListTransform + + +T = TypeVar("T") +S = TypeVar("S") +@dataclass +class SepListTransform(Transformer[list[T]]): + def __init__(self, items: Transformer[T], sep: Transformer[S]): + self.items = items + self.sep = sep + first_transformer = OptionTransform(items) + rest_transformer = ListTransform(AndTransform(sep, items)) + total_transformer = AndTransform(first_transformer, rest_transformer) + self.actual = MapTransform(total_transformer,SepListTransform.mapping) + + def parse(self, stream: str, at_index: int) -> Result[list[T]]: + return self.actual.parse(stream, at_index) + + @staticmethod + def mapping(value: tuple[Optional[T], list[tuple[S, T]]]): + items = list[T]() + if value[0] is not None: + items.append(value[0]) + for (_sep, item) in value[1]: + items.append(item) + return items diff --git a/src/pyalibert/transformers/value.py b/src/pyalibert/transformers/value.py new file mode 100644 index 0000000..724e88b --- /dev/null +++ b/src/pyalibert/transformers/value.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass +from typing import TypeVar + +from ..result import Result +from ..transformer import Transformer + +from .map import MapTransform + + +I = TypeVar("I") +T = TypeVar("T") +@dataclass +class ValueTransform(Transformer[T]): + def __init__(self, ignored: Transformer[I], value: T): + self.actual = MapTransform(ignored, lambda _: value) + + def parse(self, stream: str, at_index: int) -> Result[T]: + return self.actual.parse(stream, at_index) diff --git a/src/pyalibert/utils.py b/src/pyalibert/utils.py new file mode 100644 index 0000000..5e1d15c --- /dev/null +++ b/src/pyalibert/utils.py @@ -0,0 +1,5 @@ +from typing import Generic, TypeVar + +T = TypeVar("T") +class TypeInfo(Generic[T]): + pass