advent-2022-python/advent/parser/parser_input.py
2022-12-12 18:28:16 +01:00

101 lines
2.7 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Iterator, Protocol, Self
AllowedParserInput = str | Iterator[str]
def create_parser_input(input: AllowedParserInput) -> ParserInput:
if isinstance(input, str):
return SimpleParserInput(input, 0)
else:
return IteratorParserInput(StringDispenser(input), 0)
class ParserInput(Protocol):
def step(self) -> tuple[Self, str]:
...
def has_data(self) -> bool:
...
@dataclass(slots=True, frozen=True)
class SimpleParserInput:
input: str
start: int
def step(self) -> tuple[Self, str]:
if self.start >= len(self.input):
raise Exception("Already at End of Input")
return SimpleParserInput(self.input, self.start + 1), self.input[self.start]
def has_data(self) -> bool:
return self.start < len(self.input)
def __repr__(self) -> str:
if self.start == 0:
return f'->[{self.input}]'
if self.start >= len(self.input):
return f'{self.input}'
if self.start < 3:
return f'{self.input[0:self.start-1]}->[{self.input[self.start:]}]'
return f'{self.input[self.start-3:self.start-1]}->[{self.input[self.start:]}]'
@dataclass(slots=True)
class StringDispenser:
lines: Iterator[str]
input: str = field(default="", init=False)
length: int = field(default=0, init=False)
def read_more(self):
try:
part = next(self.lines)
if self.input:
self.input = f"{self.input}\n{part}"
else:
self.input = part
self.length = len(self.input)
return True
except StopIteration:
return False
def get_str(self, pos: int) -> str | None:
assert pos >= 0
if pos < self.length:
return self.input[pos]
elif pos == self.length and pos != 0:
return "\n"
elif self.read_more():
return self.get_str(pos)
else:
return None
def has_more(self, pos: int) -> bool:
assert pos >= 0
if pos <= self.length:
return True
elif self.read_more():
return self.has_more(pos)
else:
return False
@dataclass(slots=True, frozen=True)
class IteratorParserInput:
dispenser: StringDispenser
start: int
def step(self) -> tuple[Self, str]:
char = self.dispenser.get_str(self.start)
if char is None:
raise Exception("Already at End of Input")
return IteratorParserInput(self.dispenser, self.start + 1), char
def has_data(self) -> bool:
return self.dispenser.has_more(self.start)