Minor tweaks on Parser

This commit is contained in:
Ruediger Ludwig 2022-12-08 07:42:26 +01:00
parent 4ddef44cc2
commit 64e3eb063c
4 changed files with 86 additions and 69 deletions

View file

@ -59,7 +59,7 @@ class Crane:
is_9001: bool is_9001: bool
crate_parser: ClassVar[P[str | None]] = P.either( crate_parser: ClassVar[P[str | None]] = P.either(
P.any_char().in_brackets(), P.string(" ").replace(None)) P.one_char().in_brackets(), P.string(" ").replace(None))
crate_row_parser: ClassVar[P[list[str | None]]] = crate_parser.sep_by(P.is_char(' ')) crate_row_parser: ClassVar[P[list[str | None]]] = crate_parser.sep_by(P.is_char(' '))
@staticmethod @staticmethod

View file

@ -121,7 +121,7 @@ class P(Generic[T]):
def many(self) -> P[list[T]]: def many(self) -> P[list[T]]:
return P.either(self.some(), P.pure([])) return P.either(self.some(), P.pure([]))
def satisfies(self, pred: Callable[[T], bool], failtext: str) -> P[T]: def satisfies(self, pred: Callable[[T], bool]) -> P[T]:
return self.bind(lambda v: P.pure(v) if pred(v) else P.fail()) return self.bind(lambda v: P.pure(v) if pred(v) else P.fail())
def optional(self) -> P[T | None]: def optional(self) -> P[T | None]:
@ -264,14 +264,17 @@ class P(Generic[T]):
@staticmethod @staticmethod
def choice(*ps: P[Any]) -> P[Any]: def choice(*ps: P[Any]) -> P[Any]:
return reduce(P.either, ps, P.fail()) def inner(parserPos: ParserInput) -> Iterator[Any]:
for p in ps:
yield from p.func(parserPos)
return P(inner)
@staticmethod @staticmethod
def choice_same(*ps: P[T]) -> P[T]: def choice2(*ps: P[T]) -> P[T]:
return P.choice(*ps) return P.choice(*ps)
@staticmethod @staticmethod
def any_char() -> P[str]: def one_char() -> P[str]:
def inner(parserPos: ParserInput) -> ParserResult[str]: def inner(parserPos: ParserInput) -> ParserResult[str]:
if not parserPos.is_eof(): if not parserPos.is_eof():
yield parserPos.step() yield parserPos.step()
@ -284,56 +287,54 @@ class P(Generic[T]):
yield parserPos, () yield parserPos, ()
return P(inner) return P(inner)
@staticmethod
def char_func(cmp: Callable[[str], bool]) -> P[str]:
return P.one_char().satisfies(cmp)
@staticmethod @staticmethod
def is_char(cmp: str) -> P[str]: def is_char(cmp: str) -> P[str]:
return P.any_char().satisfies(lambda c: c == cmp, f'match {cmp}') return P.char_func(lambda c: c == cmp)
@staticmethod @staticmethod
def is_not_char(s: str) -> P[tuple[()]]: def is_not_char(s: str) -> P[tuple[()]]:
return P.no_match(P.is_char(s)) return P.no_match(P.is_char(s))
@ staticmethod
def char_by_func(cmp: Callable[[str], bool], failtext: str) -> P[str]:
return P.any_char().satisfies(cmp, failtext)
@staticmethod @staticmethod
def string(s: str) -> P[str]: def string(s: str) -> P[str]:
return P.seq(*map(P.is_char, s)).replace(s) return P.seq(*map(P.is_char, s)).replace(s)
@staticmethod @staticmethod
def one_of(s: str) -> P[str]: def one_of(s: str) -> P[str]:
return P.char_by_func(lambda c: c in s, f'one of {s}') return P.char_func(lambda c: c in s)
@staticmethod @staticmethod
def any_decimal() -> P[str]: def any_decimal() -> P[str]:
return P.char_by_func(lambda c: c.isdecimal(), 'decimal') return P.char_func(lambda c: c.isdecimal())
@staticmethod @staticmethod
def is_decimal(num: int) -> P[str]: def is_decimal(num: int) -> P[str]:
return P.any_decimal().bind( return P.any_decimal().satisfies(lambda c: unicodedata.decimal(c) == num)
lambda c: P.pure(c) if unicodedata.decimal(c) == num else P.fail())
@staticmethod @staticmethod
def is_not_decimal(num: int) -> P[str]: def is_not_decimal(num: int) -> P[str]:
return P.any_decimal().bind( return P.any_decimal().satisfies(lambda c: unicodedata.decimal(c) != num)
lambda c: P.pure(c) if unicodedata.decimal(c) != num else P.fail())
@staticmethod @staticmethod
def lower() -> P[str]: def lower() -> P[str]:
return P.char_by_func(lambda c: c.islower(), 'lower') return P.char_func(lambda c: c.islower())
@staticmethod @staticmethod
def upper() -> P[str]: def upper() -> P[str]:
return P.char_by_func(lambda c: c.isupper(), 'upper') return P.char_func(lambda c: c.isupper())
@staticmethod
def space() -> P[str]:
return P.char_func(lambda c: c.isspace())
@staticmethod @staticmethod
def joined(p1: P[str]) -> P[str]: def joined(p1: P[str]) -> P[str]:
return p1.many().fmap(lambda cs: ''.join(cs)) return p1.many().fmap(lambda cs: ''.join(cs))
@ staticmethod
def space() -> P[str]:
return P.char_by_func(lambda c: c.isspace(), 'space')
@staticmethod @staticmethod
def unsigned() -> P[int]: def unsigned() -> P[int]:
return P.either(P.fst(P.is_decimal(0), P.no_match(P.any_decimal())), return P.either(P.fst(P.is_decimal(0), P.no_match(P.any_decimal())),

View file

@ -62,7 +62,7 @@ class Success(Result[S]):
return False return False
def fmap(self, func: Callable[[S], S2]) -> Result[S2]: def fmap(self, func: Callable[[S], S2]) -> Result[S2]:
return Result.of(func(self.value)) # type: ignore return Result.of(func(self.value))
def bind(self, func: Callable[[S], Result[S2]]) -> Result[S2]: def bind(self, func: Callable[[S], Result[S2]]) -> Result[S2]:
return func(self.value) return func(self.value)

View file

@ -112,7 +112,7 @@ def test_choice2():
def test_seq(): def test_seq():
input = '1234' input = '1234'
parser = P.seq(P.any_char(), P.any_char(), P.any_char(), P.any_char()) parser = P.seq(P.one_char(), P.one_char(), P.one_char(), P.one_char())
expected = ('1', '2', '3', '4') expected = ('1', '2', '3', '4')
result = parser.parse(input).get() result = parser.parse(input).get()
assert result == expected assert result == expected
@ -120,7 +120,7 @@ def test_seq():
def test_seq_seq(): def test_seq_seq():
input = '1,2,3,4' input = '1,2,3,4'
digit = P.char_by_func(lambda c: c.isdigit(), "") digit = P.char_func(lambda c: c.isdigit(), )
parser = P.sep_seq(digit, digit, digit, digit, sep=P.is_char(',')) parser = P.sep_seq(digit, digit, digit, digit, sep=P.is_char(','))
expected = ('1', '2', '3', '4') expected = ('1', '2', '3', '4')
@ -165,3 +165,19 @@ def test_seq_eof():
expected = [(['a', 'a'], ())] expected = [(['a', 'a'], ())]
result = list(parser.parse_multi(input)) result = list(parser.parse_multi(input))
assert result == expected assert result == expected
def test_optional():
input = '12'
parser = P.seq(P.is_char('1').optional(), P.unsigned())
expected = [('1', 2), (None, 12), (None, 1)]
result = list(parser.parse_multi(input))
assert result == expected
def test_choice():
input = '1'
parser = P.choice(P.is_char('1'), P.is_char('b'), P.unsigned())
expected = ['1', 1]
result = list(parser.parse_multi(input))
assert result == expected