Enable Javascript in your browser and then refresh this page, for a much enhanced experience.
extended grammar solution in Clear category for YAML. More Types by juestr
from dataclasses import dataclass
from operator import itemgetter
from typing import Any, Callable, Generic, Iterable, List, Optional, Tuple, TypeVar, Union
# first, a generic parsing framework
S = TypeVar('S', covariant=True)
T = TypeVar('T', covariant=True)
U = TypeVar('U')
class ParseError(Exception):
pass
class Parser(Generic[T]):
"""A generic Parser which returns a value of type T"""
def parse(self, input: str, pos: int) -> Tuple[T, int]:
"""Parse at input[pos:], return T and next position or raise ParseError"""
raise ParseError(input, pos, self, 'abstract parser')
def run(self, input: str) -> T:
"""Exhaustively parse input to T or raise ParseError"""
return (self << eof).parse(input, 0)[0]
def __and__(self, next: "Parser[S]") -> "Parser[Tuple[T, S]]":
return seq(self, next)
def __or__(self, next: "Parser[S]") -> "Parser[Union[T, S]]":
return choice(self, next)
def __lshift__(self, next: "Parser[S]") -> "Parser[T]":
return (self & next) % itemgetter(0)
def __rshift__(self, next: "Parser[S]") -> "Parser[S]":
return (self & next) % itemgetter(1)
def __mod__(self, f: Callable[[T], S]) -> "Parser[S]":
return mapf(self, f)
@dataclass(frozen=True)
class expect(Parser[str]):
e: str
def parse(self, input: str, pos: int) -> Tuple[str, int]:
if input.startswith(self.e, pos):
return self.e, pos + len(self.e)
else:
raise ParseError(input, pos, self, 'unexpected input')
@dataclass(frozen=True)
class char(Parser[str]):
pred: Callable[[str], bool]
def parse(self, input: str, pos: int) -> Tuple[str, int]:
if pos < len(input) and self.pred(input[pos]): # type: ignore
return input[pos], pos + 1
else:
raise ParseError(input, pos, self, 'predicate not matched')
@dataclass(frozen=True)
class _eof(Parser[None]):
def parse(self, input: str, pos: int) -> Tuple[None, int]:
if len(input) == pos:
return None, pos
else:
raise ParseError(input, pos, self, 'unexpected trailing input')
eof = _eof()
@dataclass(frozen=True)
class seq(Parser[Tuple[T, S]]):
left: Parser[T]
right: Parser[S]
def parse(self, input: str, pos: int) -> Tuple[Tuple[T, S], int]:
rl, pos = self.left.parse(input, pos)
rr, pos = self.right.parse(input, pos)
return (rl, rr), pos
@dataclass(frozen=True)
class choice(Parser[Union[T, S]]):
left: Parser[T]
right: Parser[S]
def parse(self, input: str, pos: int) -> Tuple[Union[T, S], int]:
try:
return self.left.parse(input, pos)
except ParseError:
return self.right.parse(input, pos)
@dataclass(frozen=True)
class mapf(Parser[S], Generic[T, S]):
p: Parser[T]
f: Callable[[T], S]
def parse(self, input: str, pos: int) -> Tuple[S, int]:
r, pos = self.p.parse(input, pos)
return self.f(r), pos # type: ignore
@dataclass(frozen=True)
class repeat(Parser[List[T]]):
p: Parser[T]
lower: Optional[int] = None
upper: Optional[int] = None
def parse(self, input: str, pos: int) -> Tuple[List[T], int]:
rs: List[T] = []
while self.upper is None or len(rs) < self.upper:
try:
r, pos = self.p.parse(input, pos)
rs.append(r)
except ParseError:
break
if self.lower is not None and len(rs) < self.lower:
raise ParseError(input, pos, self, 'not enough')
return rs, pos
def repeat1(p: Parser[T]) -> Parser[List[T]]:
return repeat(p, lower=1)
def mkstr(xs: Iterable[str]) -> str:
return ''.join(xs)
def const(c: U) -> Callable[[Any], U]:
return lambda _: c
# and now the YAML grammar
def cleanstr(s):
s = s.strip()
if len(s) > 1 and s[0] == s[-1] in ('"', "'"):
s = s[1:-1]
return s.replace('\\"', '"').replace("\\'", "'")
nl = expect('\n')
linesep = nl | eof
key = repeat1(char(str.isalnum)) % mkstr
nullval = expect('null') % const(None) << linesep
boolval = (expect('false') % const(False) | expect('true') % const(True)) << linesep
intval = repeat1(char(str.isdecimal)) % mkstr % int << linesep
strval = repeat1(char(lambda c: c != '\n')) % mkstr % cleanstr << linesep
someval = expect(': ') >> (nullval | boolval | intval | strval)
noval = (expect(':') & linesep) % const(None)
kvline = repeat(nl) >> key & (someval | noval)
yamldict = repeat(kvline) % dict # type: ignore
top = yamldict << repeat(nl)
yaml = top.run
Nov. 8, 2019
Comments: