Skip to content

Commit

Permalink
Add more parser combinators
Browse files Browse the repository at this point in the history
  • Loading branch information
neshkeev committed Jul 24, 2023
1 parent 5b387f7 commit 9511a2e
Showing 1 changed file with 86 additions and 6 deletions.
92 changes: 86 additions & 6 deletions src/pgpc/parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from copy import copy

from pgpc.scanner import Scanner, Position, ELEMENT
from typing import TypeVar, Generic, Callable, Any, Sequence
from typing import TypeVar, Generic, Callable, Any, Sequence, List
from functools import wraps

V = TypeVar('V')
Expand All @@ -23,9 +23,49 @@ def __with_scanner(scanner: Scanner) -> U:

return Parser(__with_scanner)

def __call__(self, scanner: Scanner, *args, **kwargs):
def __call__(self, scanner: Scanner, *args, **kwargs) -> V:
return self.__parser(scanner)

def alt(self, parser: 'Parser[U]') -> 'Parser[V | U]':
def __with_scanner(scanner: Scanner) -> V:
try:
scanner.mark()
return self.__parser(scanner)
except ValueError:
scanner.reset()
return parser(scanner)

return Parser(__with_scanner)

def map(self, mapper: Callable[[V], U]) -> 'Parser[U]':
def __with_scanner(scanner: Scanner) -> U:
result = self.__parser(scanner)
return mapper(result)

return Parser(__with_scanner)

def replace_with(self, parser: 'Parser[U]') -> 'Parser[U]':
return self.replace_lazy(lambda: parser)

def replace_lazy(self, parser: Callable[[], 'Parser[U]']) -> 'Parser[U]':
def __with_scanner(scanner: Scanner) -> U:
self.__parser(scanner)
value = parser()(scanner)
return value

return Parser(__with_scanner)

def drain_next(self, parser: 'Parser[V]') -> 'Parser[V]':
def __with_scanner(scanner: Scanner) -> V:
value = self.__parser(scanner)
parser(scanner)
return value

return Parser(__with_scanner)

def __or__(self, other: 'Parser[U]') -> 'Parser[V | U]':
return self.alt(other)


def topology(parser_builder) -> Callable[..., Parser[V]]:
@wraps(parser_builder)
Expand All @@ -46,8 +86,8 @@ def __with_scanner(scanner: Scanner) -> V:
return wrapper


def satisfy(predicate: Callable[[str], bool]) -> Parser[str]:
def __with_scanner(scanner: Scanner) -> str:
def satisfy(predicate: Callable[[V], bool]) -> Parser[V]:
def __with_scanner(scanner: Scanner) -> V:
result = scanner.advance_if(predicate)
if not result:
raise Parser.ParserError(f"Unexpected '{scanner.current}' at {scanner.pos}")
Expand All @@ -60,10 +100,51 @@ def any_char() -> Parser[str]:
return satisfy(lambda x: True)


def char(c: str) -> Parser[str]:
def char(c: V) -> Parser[V]:
return satisfy(lambda x: c == x)


def opt(parser: Parser[V]) -> Parser[V | None]:
def __with_scanner(scanner: Scanner) -> V | None:
try:
scanner.mark()
return parser(scanner)
except ValueError:
scanner.reset()
return None

return Parser(__with_scanner)


def many(parser: Parser[V]) -> Parser[List[V]]:
def __with_scanner(scanner: Scanner) -> List[V]:
result = []
try:
while True:
element = parser(scanner)
result.append(element)
except ValueError:
return result

return Parser(__with_scanner)


def ws() -> Parser[str]:
return satisfy(lambda x: type(x) is str and x.isspace())


def letter() -> Parser[str]:
return satisfy(lambda x: type(x) is str and x.isalpha())


def digit() -> Parser[str]:
return satisfy(lambda x: type(x) is str and x.isdigit())


def alphanum() -> Parser[str]:
return satisfy(lambda x: type(x) is str and x.isalnum())


def content() -> Parser[Sequence[ELEMENT]]:
def __with_scanner(scanner: Scanner) -> Sequence[ELEMENT]:
return scanner.content
Expand All @@ -76,4 +157,3 @@ def __with_scanner(scanner: Scanner) -> Position:
return copy(scanner.pos)

return Parser(__with_scanner)

0 comments on commit 9511a2e

Please sign in to comment.