diff --git a/advent/days/day05/solution.py b/advent/days/day05/solution.py index 1267ebe..b2103c6 100644 --- a/advent/days/day05/solution.py +++ b/advent/days/day05/solution.py @@ -26,9 +26,9 @@ class Move: frm: int to: int - amount_parser: ClassVar[P[int]] = P.snd(P.string("move "), P.unsigned()) - from_parser: ClassVar[P[int]] = P.snd(P.string(" from "), P.unsigned()) - to_parser: ClassVar[P[int]] = P.snd(P.string(" to "), P.unsigned()) + amount_parser: ClassVar[P[int]] = P.second(P.string("move "), P.unsigned()) + from_parser: ClassVar[P[int]] = P.second(P.string(" from "), P.unsigned()) + to_parser: ClassVar[P[int]] = P.second(P.string(" to "), P.unsigned()) move_parser: ClassVar[P[tuple[int, int, int]]] = P.seq(amount_parser, from_parser, to_parser) @staticmethod diff --git a/advent/parser/parser.py b/advent/parser/parser.py index 146c8c0..9659483 100644 --- a/advent/parser/parser.py +++ b/advent/parser/parser.py @@ -22,15 +22,14 @@ class ParserInput: input: str start: int - def step(self, count: int = 1) -> tuple[Self, str]: - assert count > 0 - if self.start + count > len(self.input): - raise Exception("Not enough chars left in string") - return ParserInput(self.input, self.start - + count), self.input[self.start:self.start + count] + def step(self) -> tuple[Self, str]: + if self.start >= len(self.input): + raise Exception("Already at End of Input") - def is_eof(self) -> bool: - return self.start >= len(self.input) + return ParserInput(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: @@ -162,16 +161,16 @@ class P(Generic[T]): raise Exception("Choose exactly one of exact, min or max") def sep_by(self, sep: P[Any]) -> P[list[T]]: - return P.map2(self, P.snd(sep, self).many(), lambda f, r: [f] + r) + return P.map2(self, P.second(sep, self).many(), lambda f, r: [f] + r) @staticmethod - def snd(p1: P[Any], p2: P[T2]) -> P[T2]: - return p1.bind(lambda _: p2) - - @staticmethod - def fst(p1: P[T1], p2: P[Any]) -> P[T1]: + def first(p1: P[T1], p2: P[Any]) -> P[T1]: return P.map2(p1, p2, lambda v1, _: v1) + @staticmethod + def second(p1: P[Any], p2: P[T2]) -> P[T2]: + return p1.bind(lambda _: p2) + @staticmethod def no_match(p: P[Any]) -> P[tuple[()]]: def inner(parserPos: ParserInput) -> ParserResult[tuple[()]]: @@ -267,7 +266,7 @@ class P(Generic[T]): def sep_seq(*ps: P[Any], sep: P[Any]) -> P[tuple[Any, ...]]: first, *rest = list(ps) return P.map2(first, - reduce(lambda p, x: P.snd(sep, x.bind( + reduce(lambda p, x: P.second(sep, x.bind( lambda a: p.fmap(lambda b: chain([a], b)))), rest[::-1], P.pure(iter([]))), lambda f, r: (f,) + tuple(r)) @@ -310,14 +309,14 @@ class P(Generic[T]): @staticmethod def one_char() -> P[str]: def inner(parserPos: ParserInput) -> ParserResult[str]: - if not parserPos.is_eof(): + if parserPos.has_data(): yield parserPos.step() return P(inner) @staticmethod def eof() -> P[tuple[()]]: def inner(parserPos: ParserInput) -> ParserResult[tuple[()]]: - if parserPos.is_eof(): + if not parserPos.has_data(): yield parserPos, () return P(inner) @@ -366,14 +365,14 @@ class P(Generic[T]): return P.char_func(lambda c: c.isspace()) @staticmethod - def joined(p1: P[str]) -> P[str]: - return p1.many().fmap(lambda cs: ''.join(cs)) + def word(p1: P[str]) -> P[str]: + return P.first(p1.many().fmap(lambda cs: ''.join(cs)), P.no_match(p1)) @staticmethod def unsigned() -> P[int]: - return P.either(P.fst(P.is_decimal(0), P.no_match(P.any_decimal())), - P.map2(P.is_not_decimal(0), P.any_decimal().many(), - lambda f, s: f + ''.join(s)) + return P.either(P.first(P.is_decimal(0), P.no_match(P.any_decimal())), + P.map2(P.is_not_decimal(0), P.word(P.any_decimal()), + lambda f, s: f + s) ).fmap(int) @staticmethod @@ -394,10 +393,10 @@ class P(Generic[T]): return self.between(P.is_char('{'), P.is_char('}')) def trim_left(self) -> P[T]: - return P.snd(WHITE_SPACE, self) + return P.second(WHITE_SPACE, self) def trim_right(self) -> P[T]: - return P.fst(self, WHITE_SPACE) + return P.first(self, WHITE_SPACE) def trim(self) -> P[T]: return self.surround(WHITE_SPACE) diff --git a/advent/parser/test_parser.py b/advent/parser/test_parser.py index 6c5fe07..3a78f4c 100644 --- a/advent/parser/test_parser.py +++ b/advent/parser/test_parser.py @@ -130,7 +130,7 @@ def test_seq_seq(): def test_not(): input = 'a' - parser = P.snd(P.no_match(P.is_char('!')), P.is_char('a')) + parser = P.second(P.no_match(P.is_char('!')), P.is_char('a')) expected = 'a' result = parser.parse(input).get() assert result == expected @@ -170,7 +170,7 @@ def test_seq_eof(): def test_optional(): input = '12' parser = P.seq(P.is_char('1').optional(), P.unsigned()) - expected = [('1', 2), (None, 12), (None, 1)] + expected = [('1', 2), (None, 12)] result = list(parser.parse_multi(input)) assert result == expected @@ -245,3 +245,19 @@ def test_times_lazy_max(): expected = [[], ['a'], ['a', 'a']] result = list(parser.parse_multi(input)) assert result == expected + + +def test_word(): + input = '123' + parser = P.word(P.any_decimal()) + expected = ['123'] + result = list(parser.parse_multi(input)) + assert result == expected + + +def test_word2(): + input = '123a' + parser = P.seq(P.word(P.any_decimal()), P.is_char('a')) + expected = [('123', 'a')] + result = list(parser.parse_multi(input)) + assert result == expected