diff --git a/src/parsita/parsers/_alternative.py b/src/parsita/parsers/_alternative.py index 9846703..862b634 100644 --- a/src/parsita/parsers/_alternative.py +++ b/src/parsita/parsers/_alternative.py @@ -12,7 +12,7 @@ def __init__(self, parser: Parser[Input, Output], *parsers: Parser[Input, Output super().__init__() self.parsers = (parser,) + tuple(parsers) - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): for parser in self.parsers: status = parser.cached_consume(state, reader) if isinstance(status, Continue): @@ -54,7 +54,7 @@ def __init__(self, parser: Parser[Input, Output], *parsers: Parser[Input, Output super().__init__() self.parsers = (parser,) + tuple(parsers) - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): longest_success: Optional[Continue] = None for parser in self.parsers: status = parser.cached_consume(state, reader) diff --git a/src/parsita/parsers/_any.py b/src/parsita/parsers/_any.py index aea42c8..87504b4 100644 --- a/src/parsita/parsers/_any.py +++ b/src/parsita/parsers/_any.py @@ -18,7 +18,7 @@ class AnyParser(Generic[Input], Parser[Input, Input]): def __init__(self): super().__init__() - def consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, Input]]: + def consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Continue[Input, Input]]: if reader.finished: state.register_failure("anything", reader) return None diff --git a/src/parsita/parsers/_base.py b/src/parsita/parsers/_base.py index 3feca95..4a20f36 100644 --- a/src/parsita/parsers/_base.py +++ b/src/parsita/parsers/_base.py @@ -3,7 +3,7 @@ __all__ = ["Parser", "completely_parse_reader"] from types import MethodType -from typing import Any, Generic, Optional, Sequence +from typing import Any, Generic, List, Optional, Sequence from .. import options from ..state import Continue, Failure, Input, Output, ParseError, Reader, Result, State, Success @@ -49,7 +49,7 @@ class Parser(Generic[Input, Output]): def __init__(self): self.parse = MethodType(options.parse_method, self) - def cached_consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, Output]]: + def cached_consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Continue[Input, Output]]: """Match this parser at the given location. This is a concrete wrapper around ``consume``. This method implements @@ -88,7 +88,7 @@ def cached_consume(self, state: State, reader: Reader[Input]) -> Optional[Contin return result - def consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, Output]]: + def consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Continue[Input, Output]]: """Abstract method for matching this parser at the given location. This is the central method of every parser combinator. @@ -152,7 +152,7 @@ def __or__(self, other) -> Parser: from ._alternative import AlternativeParser other = self.handle_other(other) - parsers = [] + parsers: List[Parser] = [] if isinstance(self, AlternativeParser) and not self.protected: parsers.extend(self.parsers) else: @@ -228,7 +228,7 @@ def completely_parse_reader(parser: Parser[Input, Output], reader: Reader[Input] """ from ._end_of_source import eof - state = State() + state: State[Input] = State() status = (parser << eof).cached_consume(state, reader) if isinstance(status, Continue): diff --git a/src/parsita/parsers/_conversion.py b/src/parsita/parsers/_conversion.py index ab4c847..3480f61 100644 --- a/src/parsita/parsers/_conversion.py +++ b/src/parsita/parsers/_conversion.py @@ -14,7 +14,7 @@ def __init__(self, parser: Parser[Input, Output], converter: Callable[[Output], self.parser = parser self.converter = converter - def consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, Convert]]: + def consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Continue[Input, Convert]]: status = self.parser.cached_consume(state, reader) if isinstance(status, Continue): @@ -27,12 +27,12 @@ def __repr__(self): class TransformationParser(Generic[Input, Output, Convert], Parser[Input, Convert]): - def __init__(self, parser: Parser[Input, Output], transformer: Callable[[Output], Parser[Output, Convert]]): + def __init__(self, parser: Parser[Input, Output], transformer: Callable[[Output], Parser[Input, Convert]]): super().__init__() self.parser = parser self.transformer = transformer - def consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, Convert]]: + def consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Continue[Input, Convert]]: status = self.parser.cached_consume(state, reader) if isinstance(status, Continue): diff --git a/src/parsita/parsers/_debug.py b/src/parsita/parsers/_debug.py index 9848a6e..77d1780 100644 --- a/src/parsita/parsers/_debug.py +++ b/src/parsita/parsers/_debug.py @@ -20,7 +20,7 @@ def __init__( self.callback = callback self._parser_string = repr(parser) - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): if self.verbose: print(f"""Evaluating token {reader.next_token()} using parser {self._parser_string}""") diff --git a/src/parsita/parsers/_end_of_source.py b/src/parsita/parsers/_end_of_source.py index b294df9..9e8c2a6 100644 --- a/src/parsita/parsers/_end_of_source.py +++ b/src/parsita/parsers/_end_of_source.py @@ -10,7 +10,7 @@ class EndOfSourceParser(Generic[Input], Parser[Input, None]): def __init__(self): super().__init__() - def consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, None]]: + def consume(self, state: State[Input], reader: Reader[Input]) -> Optional[Continue[Input, None]]: if reader.finished: return Continue(reader, None) else: diff --git a/src/parsita/parsers/_literal.py b/src/parsita/parsers/_literal.py index 6966653..0ccc8f2 100644 --- a/src/parsita/parsers/_literal.py +++ b/src/parsita/parsers/_literal.py @@ -12,7 +12,7 @@ def __init__(self, pattern: Sequence[Input]): super().__init__() self.pattern = pattern - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): remainder = reader for elem in self.pattern: if remainder.finished: @@ -36,7 +36,7 @@ def __init__(self, pattern: str, whitespace: Optional[Parser[str, None]] = None) self.whitespace = whitespace self.pattern = pattern - def consume(self, state: State, reader: StringReader): + def consume(self, state: State[str], reader: StringReader): if self.whitespace is not None: status = self.whitespace.cached_consume(state, reader) reader = status.remainder diff --git a/src/parsita/parsers/_optional.py b/src/parsita/parsers/_optional.py index 051c956..6bcdab9 100644 --- a/src/parsita/parsers/_optional.py +++ b/src/parsita/parsers/_optional.py @@ -12,7 +12,7 @@ def __init__(self, parser: Parser[Input, Output]): super().__init__() self.parser = parser - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): status = self.parser.cached_consume(state, reader) if isinstance(status, Continue): diff --git a/src/parsita/parsers/_predicate.py b/src/parsita/parsers/_predicate.py index da4c6ae..281b326 100644 --- a/src/parsita/parsers/_predicate.py +++ b/src/parsita/parsers/_predicate.py @@ -13,7 +13,7 @@ def __init__(self, parser: Parser[Input, Output], predicate: Callable[[Output], self.predicate = predicate self.description = description - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): remainder = reader status = self.parser.cached_consume(state, remainder) if isinstance(status, Continue): diff --git a/src/parsita/parsers/_regex.py b/src/parsita/parsers/_regex.py index 1b34298..3fbce42 100644 --- a/src/parsita/parsers/_regex.py +++ b/src/parsita/parsers/_regex.py @@ -15,7 +15,7 @@ def __init__(self, pattern: str, whitespace: Optional[Parser[str, None]] = None) self.whitespace = whitespace self.pattern = re.compile(pattern) - def consume(self, state: State, reader: StringReader): + def consume(self, state: State[str], reader: StringReader): if self.whitespace is not None: status = self.whitespace.cached_consume(state, reader) reader = status.remainder diff --git a/src/parsita/parsers/_repeated.py b/src/parsita/parsers/_repeated.py index 5d849bc..c2a5c6f 100644 --- a/src/parsita/parsers/_repeated.py +++ b/src/parsita/parsers/_repeated.py @@ -1,6 +1,6 @@ __all__ = ["RepeatedOnceParser", "rep1", "RepeatedParser", "rep"] -from typing import Generic, Optional, Sequence, Union +from typing import Generic, List, Optional, Sequence, Union from ..state import Continue, Input, Output, Reader, RecursionError, State from ._base import Parser @@ -12,7 +12,7 @@ def __init__(self, parser: Parser[Input, Output]): super().__init__() self.parser = parser - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): status = self.parser.cached_consume(state, reader) if status is None: @@ -58,8 +58,8 @@ def __init__(self, parser: Parser[Input, Output], *, min: int = 0, max: Optional self.min = min self.max = max - def consume(self, state: State, reader: Reader[Input]): - output = [] + def consume(self, state: State[Input], reader: Reader[Input]): + output: List[Output] = [] remainder = reader while self.max is None or len(output) < self.max: diff --git a/src/parsita/parsers/_repeated_seperated.py b/src/parsita/parsers/_repeated_seperated.py index b284d60..9239577 100644 --- a/src/parsita/parsers/_repeated_seperated.py +++ b/src/parsita/parsers/_repeated_seperated.py @@ -22,7 +22,7 @@ def __init__( self.min = min self.max = max - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): status = self.parser.cached_consume(state, reader) if not isinstance(status, Continue): @@ -98,7 +98,7 @@ def __init__(self, parser: Parser[Input, Output], separator: Parser[Input, Any]) self.parser = parser self.separator = separator - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): status = self.parser.cached_consume(state, reader) if status is None: diff --git a/src/parsita/parsers/_sequential.py b/src/parsita/parsers/_sequential.py index 48deb05..3f08e8e 100644 --- a/src/parsita/parsers/_sequential.py +++ b/src/parsita/parsers/_sequential.py @@ -11,7 +11,7 @@ def __init__(self, parser: Parser[Input, Any], *parsers: Parser[Input, Any]): super().__init__() self.parsers = (parser,) + tuple(parsers) - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): output = [] remainder = reader @@ -39,7 +39,7 @@ def __init__(self, left: Parser[Input, Any], right: Parser[Input, Output]): self.left = left self.right = right - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): status = self.left.cached_consume(state, reader) if isinstance(status, Continue): return self.right.cached_consume(state, status.remainder) @@ -56,7 +56,7 @@ def __init__(self, left: Parser[Input, Output], right: Parser[Input, Any]): self.left = left self.right = right - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): status1 = self.left.cached_consume(state, reader) if isinstance(status1, Continue): status2 = self.right.cached_consume(state, status1.remainder) diff --git a/src/parsita/parsers/_success.py b/src/parsita/parsers/_success.py index bf497dc..266eec3 100644 --- a/src/parsita/parsers/_success.py +++ b/src/parsita/parsers/_success.py @@ -11,7 +11,7 @@ def __init__(self, value: Output): super().__init__() self.value = value - def consume(self, state: State, reader: Reader[Input]) -> Continue[Input, None]: + def consume(self, state: State[Input], reader: Reader[Input]) -> Continue[Input, Output]: return Continue(reader, self.value) def __repr__(self): @@ -36,7 +36,7 @@ def __init__(self, expected: str): super().__init__() self.expected = expected - def consume(self, state: State, reader: Reader[Input]) -> None: + def consume(self, state: State[Input], reader: Reader[Input]) -> None: state.register_failure(self.expected, reader) return None diff --git a/src/parsita/parsers/_until.py b/src/parsita/parsers/_until.py index 60f57e1..8fca720 100644 --- a/src/parsita/parsers/_until.py +++ b/src/parsita/parsers/_until.py @@ -12,7 +12,7 @@ def __init__(self, parser: Parser[Input, Any]): super().__init__() self.parser = parser - def consume(self, state: State, reader: Reader[Input]): + def consume(self, state: State[Input], reader: Reader[Input]): start_position = reader.position while True: status = self.parser.cached_consume(state, reader) diff --git a/src/parsita/state/_reader.py b/src/parsita/state/_reader.py index ffb6c0a..6e9d9ad 100644 --- a/src/parsita/state/_reader.py +++ b/src/parsita/state/_reader.py @@ -25,6 +25,8 @@ class Reader(Generic[Input]): source (Sequence[Input]): The full source being read. """ + # Despite what mypy says, these cannot be converted to properties because + # they will break the dataclass attributes of the subclasses. first: Input rest: Reader[Input] position: int diff --git a/src/parsita/state/_result.py b/src/parsita/state/_result.py index 616b575..2397b82 100644 --- a/src/parsita/state/_result.py +++ b/src/parsita/state/_result.py @@ -11,8 +11,9 @@ # Reexport Returns Result types Result = result.Result[Output, ParseError] Success = result.Success -Failure = result.Failure if TYPE_CHECKING: # This object fails in isinstance # Result does too, but that cannot be fixed without breaking eager type annotations Failure = result.Failure[ParseError] +else: + Failure = result.Failure diff --git a/src/parsita/state/_state.py b/src/parsita/state/_state.py index c5e7203..c42b402 100644 --- a/src/parsita/state/_state.py +++ b/src/parsita/state/_state.py @@ -14,11 +14,11 @@ Output = TypeVar("Output") -class State: +class State(Generic[Input]): def __init__(self): self.farthest: Optional[Reader[Any]] = None self.expected: List[str] = [] - self.memo: Dict[Tuple[Parser[Any, Any], int], Optional[Continue[Any, Any]]] = {} + self.memo: Dict[Tuple[Parser[Input, Any], int], Optional[Continue[Input, Any]]] = {} def register_failure(self, expected: str, reader: Reader[Any]): if self.farthest is None or self.farthest.position < reader.position: