101 lines
2.7 KiB
Python
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)
|