added nicer ÜParserInput
This commit is contained in:
parent
5c0183ab93
commit
6a7e34e322
3 changed files with 112 additions and 102 deletions
|
|
@ -8,7 +8,8 @@ I use python 3.11 without any libraries beyond the standard.
|
||||||
|
|
||||||
| Day | Time | Rank | Score | Time | Rank | Score |
|
| Day | Time | Rank | Score | Time | Rank | Score |
|
||||||
| --- | --------- | ----- | ----- | -------- | ----- | ----- |
|
| --- | --------- | ----- | ----- | -------- | ----- | ----- |
|
||||||
| 11 | 00:56:44 | 5414 | 0 | 02:42:24 | 7558 | 0 |
|
| 12 | 01:43:14 | 6571 | 0 | 01:51:15 | 6246 | 0 |
|
||||||
|
| 11 | 00:56:44 | 5414 | 0 | 02:42:24 | 7558 | 0 |
|
||||||
| 10 | 00:38:16 | 7637 | 0 | 01:16:55 | 7961 | 0 |
|
| 10 | 00:38:16 | 7637 | 0 | 01:16:55 | 7961 | 0 |
|
||||||
| 9 | 00:54:18 | 7719 | 0 | 01:07:37 | 4901 | 0 |
|
| 9 | 00:54:18 | 7719 | 0 | 01:07:37 | 4901 | 0 |
|
||||||
| 8 | 00:41:51 | 7831 | 0 | 00:59:27 | 6325 | 0 |
|
| 8 | 00:41:51 | 7831 | 0 | 00:59:27 | 6325 | 0 |
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import Any, Callable, Generic, Iterator, Protocol, Self, TypeVar, overload
|
from typing import Any, Callable, Generic, Iterator, Self, TypeVar, overload
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
|
from advent.parser.parser_input import AllowedParserInput, ParserInput, create_parser_input
|
||||||
|
|
||||||
from .result import Result
|
from .result import Result
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
@ -17,93 +18,6 @@ T5 = TypeVar('T5')
|
||||||
TR = TypeVar('TR')
|
TR = TypeVar('TR')
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
ParserResult = Iterator[tuple[ParserInput, T]]
|
ParserResult = Iterator[tuple[ParserInput, T]]
|
||||||
ParserFunc = Callable[[ParserInput], ParserResult[T]]
|
ParserFunc = Callable[[ParserInput], ParserResult[T]]
|
||||||
|
|
||||||
|
|
@ -112,26 +26,20 @@ class P(Generic[T]):
|
||||||
def __init__(self, func: ParserFunc[T]):
|
def __init__(self, func: ParserFunc[T]):
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def parse(self, input: str | Iterator[str]) -> Result[T]:
|
def parse(self, input: AllowedParserInput) -> Result[T]:
|
||||||
if isinstance(input, str):
|
parser_input = create_parser_input(input)
|
||||||
parser_input = SimpleParserInput(input, 0)
|
|
||||||
else:
|
|
||||||
parser_input = IteratorParserInput(StringDispenser(input), 0)
|
|
||||||
|
|
||||||
all_results = self.func(parser_input)
|
all_results = self.func(parser_input)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_, result = next(all_results)
|
_, result = next(all_results)
|
||||||
return Result.of(result)
|
return Result.of(result)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return Result.fail("No result")
|
return Result.fail("No result")
|
||||||
|
|
||||||
def parse_multi(self, input: str | Iterator[str]) -> Iterator[T]:
|
def parse_multi(self, input: AllowedParserInput) -> Iterator[T]:
|
||||||
if isinstance(input, str):
|
parser_input = create_parser_input(input)
|
||||||
parser_input = SimpleParserInput(input, 0)
|
|
||||||
else:
|
|
||||||
parser_input = IteratorParserInput(StringDispenser(input), 0)
|
|
||||||
|
|
||||||
all_results = self.func(parser_input)
|
all_results = self.func(parser_input)
|
||||||
|
|
||||||
return (v for _, v in all_results)
|
return (v for _, v in all_results)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
101
advent/parser/parser_input.py
Normal file
101
advent/parser/parser_input.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
|
||||||
|
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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue