refactor into transformers
This commit is contained in:
parent
e8a0aa8a64
commit
23529950e9
18 changed files with 330 additions and 150 deletions
|
@ -1,4 +1,4 @@
|
|||
|
||||
from .parser import Parser, just, regex, end
|
||||
from .result import Result, ParseError
|
||||
from .forward import FwDeclaration
|
||||
from .forward import Declare
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]]
|
||||
inner: "Transformer[P]"
|
||||
|
||||
def __init__(self, inner: Callable[[str, int], Result[P]]):
|
||||
self.inner = inner
|
||||
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(self, stream: str):
|
||||
(result, _rest) = self.and_then(end()).parse_part(stream)
|
||||
(value, _end) = result
|
||||
return value
|
||||
def and_(self, other: "Parser[T]"):
|
||||
return Parser(AndTransform(self.inner, other.inner))
|
||||
|
||||
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 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])
|
||||
|
|
1
src/pyalibert/py.typed
Normal file
1
src/pyalibert/py.typed
Normal file
|
@ -0,0 +1 @@
|
|||
# Marker file for PEP 561. The mypy package uses inline types.
|
|
@ -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)
|
||||
|
|
11
src/pyalibert/transformer.py
Normal file
11
src/pyalibert/transformer.py
Normal file
|
@ -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.")
|
13
src/pyalibert/transformers/__init__.py
Normal file
13
src/pyalibert/transformers/__init__.py
Normal file
|
@ -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
|
23
src/pyalibert/transformers/and_.py
Normal file
23
src/pyalibert/transformers/and_.py
Normal file
|
@ -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)
|
15
src/pyalibert/transformers/end.py
Normal file
15
src/pyalibert/transformers/end.py
Normal file
|
@ -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("<end>"))
|
19
src/pyalibert/transformers/just.py
Normal file
19
src/pyalibert/transformers/just.py
Normal file
|
@ -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]))
|
||||
|
24
src/pyalibert/transformers/list.py
Normal file
24
src/pyalibert/transformers/list.py
Normal file
|
@ -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)
|
21
src/pyalibert/transformers/map.py
Normal file
21
src/pyalibert/transformers/map.py
Normal file
|
@ -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)
|
18
src/pyalibert/transformers/option.py
Normal file
18
src/pyalibert/transformers/option.py
Normal file
|
@ -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)
|
23
src/pyalibert/transformers/or_.py
Normal file
23
src/pyalibert/transformers/or_.py
Normal file
|
@ -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))
|
19
src/pyalibert/transformers/regex.py
Normal file
19
src/pyalibert/transformers/regex.py
Normal file
|
@ -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"<matching '{self.pattern}'>"))
|
||||
else:
|
||||
match = do_match[0]
|
||||
return Success(match, at_index + len(match))
|
35
src/pyalibert/transformers/sep_list.py
Normal file
35
src/pyalibert/transformers/sep_list.py
Normal file
|
@ -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
|
18
src/pyalibert/transformers/value.py
Normal file
18
src/pyalibert/transformers/value.py
Normal file
|
@ -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)
|
5
src/pyalibert/utils.py
Normal file
5
src/pyalibert/utils.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
class TypeInfo(Generic[T]):
|
||||
pass
|
Loading…
Add table
Add a link
Reference in a new issue