Minor tweaks on Parser
This commit is contained in:
parent
4ddef44cc2
commit
64e3eb063c
4 changed files with 86 additions and 69 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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())),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue