def parse_bool(s: str) -> ParseResult[bool]:
- return alt(
- value(True, tag("True")),
- value(False, tag("False")),
+ return parser_map_err(
+ lambda e: ParseError("Expected boolean 'True' or 'False'", e.at),
+ alt(
+ value(True, tag("True")),
+ value(False, tag("False")),
+ ),
)(s)
def parse_int(s: str) -> ParseResult[int]:
- return parser_map(int, recognize(many_count(ascii_digit, min_n=1)))(s)
+ return parser_map_err(
+ lambda e: ParseError("Expected integer literal", e.at),
+ parser_map(int, recognize(many_count(ascii_digit, min_n=1))),
+ )(s)
def multispace0(s: str) -> ParseResult[str]:
return recognize(seq(tag("#"), many_count(none_of("\n"))))(s)
-# def parse_empty_line(s: str) -> ParseResult[None]:
-# return (None, s) if s.startswith("\n") else ParseError("temp", "temp")
-
-
def spaced[T](parser: Parser[T]) -> Parser[T]:
return delimited(multispace0, parser, multispace0)
def parse_varname(s: str) -> ParseResult[str]:
varstart = "_" + char_range("a", "z") + char_range("A", "Z")
vartail = varstart + char_range("0", "9")
- return recognize(seq(one_of(varstart), many_count(one_of(vartail))))(s)
+ return parser_map_err(
+ lambda e: ParseError("Expected color identifier", e.at),
+ recognize(seq(one_of(varstart), many_count(one_of(vartail)))),
+ )(s)
type Color = tuple[int, int, int] | str
def parse_color(s: str) -> ParseResult[Color]:
- return cast(
- ParseResult[Color],
- alt(
- parser_map(
- tuple,
- many(parse_int, 3, 3, spaced(tag(","))),
- ),
- parse_varname,
- )(s),
+ cut_comma = spaced(
+ cut(lookahead_parser(tag(","), seq(multispace0, parse_int)))
)
+ try:
+ return cast(
+ ParseResult[Color],
+ alt(
+ parser_map(
+ tuple,
+ many(parse_int, 3, 3, cut_comma),
+ ),
+ parse_varname,
+ )(s),
+ )
+ except ParseError as e:
+ return e
def parse_color_pair(s: str) -> ParseResult[ColorPair]:
return spaced(
delimited(
- tag('"'), many(pair(color_prefix, cut(noncolor_str))), tag('"')
+ tag('"'),
+ many(pair(color_prefix, cut(noncolor_str))),
+ parser_map_err(
+ lambda e: ParseError("Expected color prefix or '\"'", e.at),
+ tag('"'),
+ ),
)
)(s)
def parse(self, s: str) -> ParseResult[T]: ...
def default(self) -> U:
- raise ConfigException(
- f"Value {self.__name} not provided, "
- + "and no default value exists"
- )
+ raise ConfigException(f"Value {self.__name} not provided")
@abstractmethod
def merge(self, vals: list[T]) -> U: ...
parser_map(lambda _: None, parse_comment),
*(
preceeded(
- seq(tag(name), multispace0, cut(tag("=")), multispace0),
+ seq(tag(name), multispace0, tag("="), multispace0),
parser_map(
(lambda name: lambda res: (name, res))(name),
cut(terminated(field.parse, multispace0)),
) -> Parser[dict[str, Any]]:
fields = {key: cls(key) for key, cls in fields_raw.items()}
parse_line = nonempty_parser(
- cut(terminated(line_parser(fields), alt(tag("\n"), eof_parser())))
+ cut(
+ terminated(
+ line_parser(fields),
+ parser_map_err(
+ lambda e: ParseError("Expected newline or EOF", e.at),
+ alt(tag("\n"), eof_parser()),
+ ),
+ )
+ )
)
def inner(s: str) -> ParseResult[dict[str, Any]]:
def parser_complete[T](
p: Parser[T],
- msg: str = "Complete parser error: leftover characters",
) -> Parser[T]:
- def inner(res: tuple[T, str]) -> ParseResult[T]:
- if len(res[1]) != 0:
- raise ParseError(msg, res[1])
- return res
-
- return lambda s: error_map(inner, p(s))
+ return terminated(p, eof_parser())
def recognize[T](p: Parser[T]) -> Parser[str]:
return inner
-def tag(tag: str, msg: str | None = None) -> Parser[str]:
- if msg is None:
- msg = f"Expected tag {repr(tag)}"
+def tag(tag: str) -> Parser[str]:
return lambda s: (
(s[: len(tag)], s[len(tag) :]) # noqa E203
if s.startswith(tag)
- else ParseError(msg, s)
+ else ParseError(f"Expected tag {repr(tag)}", s)
)
-def char(msg: str | None = None) -> Parser[str]:
- if msg is None:
- msg = f"Expected char {repr(tag)}"
- return lambda s: (s[0], s[1:]) if len(s) > 0 else ParseError(msg, s)
+def char(s: str) -> ParseResult[str]:
+ return (s[0], s[1:]) if len(s) > 0 else ParseError("Early EOF", s)
def null_parser(s: str) -> ParseResult[str]:
return inner
-def eof_parser(msg: str = "Expected end of file") -> Parser[str]:
- return lambda s: ("", "") if len(s) == 0 else ParseError(msg, s)
+def eof_parser() -> Parser[str]:
+ return lambda s: (
+ ("", "") if len(s) == 0 else ParseError("Expected EOF", s)
+ )
-def nonempty_parser[T](p: Parser[T], msg: str = "Expected non end of file"):
- return lambda s: p(s) if len(s) > 0 else ParseError(msg, s)
+def nonempty_parser[T](p: Parser[T]) -> Parser[T]:
+ return lambda s: p(s) if len(s) > 0 else ParseError("Early EOF", s)
def value[T, V](val: V, p: Parser[T]) -> Parser[V]:
return parser_map(lambda _: val, p)
-def alt[T](
- *choices: Parser[T], msg: str = "None of the following was met:"
-) -> Parser[T]:
+def alt[T](*choices: Parser[T]) -> Parser[T]:
def inner(s: str) -> ParseResult[T]:
acc: list[ParseError] = []
for e in map(lambda p: p(s), choices):
if not isinstance(e, ParseError):
return e
acc.append(e)
- return ParseError(msg, s, acc)
+ return ParseError("Expected any of the following to match:", s, acc)
return inner
min_n: int = 0,
max_n: int | None = None,
sep: Parser[Any] = null_parser,
- msg: Callable[[int], str] | None = None,
) -> Parser[R]:
"""
Repeatedly call the p parser, folding the results using f, with an acc
Returns error if and only if min_n iterations are not reached
"""
- if msg is None:
- msg = (
- lambda count: f"Expected at least {min_n} elements, got {count}, after error:"
- )
-
# no clean way to do this with lambdas i could figure out :<
def inner(s: str) -> ParseResult[R]:
curr_s = s
acc = acc_init()
count: int = 0
curr_p: Parser[T] = p
- err: list[ParseError] | None = None
while max_n is None or count < max_n:
nxt: ParseResult[T] = curr_p(curr_s)
if isinstance(nxt, ParseError):
- err = [nxt]
+ if count < min_n:
+ return nxt
break
if count == 0:
curr_p = preceeded(sep, p)
count += 1
acc = f(acc, nxt[0])
curr_s = nxt[1]
- return (
- (acc, curr_s) if count >= min_n else ParseError(msg(count), s, err)
- )
+ return (acc, curr_s)
return inner
min_n: int = 0,
max_n: int | None = None,
sep: Parser[Any] = null_parser,
- msg: Callable[[int], str] | None = None,
) -> Parser[list[T]]:
return fold(
parser_map(lambda e: [e], p),
min_n,
max_n,
sep,
- msg,
)
min_n: int = 0,
max_n: int | None = None,
sep: Parser[Any] = null_parser,
- msg: Callable[[int], str] | None = None,
) -> Parser[int]:
- return fold(value(1, p), int.__add__, lambda: 0, min_n, max_n, sep, msg)
+ return fold(value(1, p), int.__add__, lambda: 0, min_n, max_n, sep)
def seq[T](*parsers: Parser[T]) -> Parser[str]:
def none_of(chars: str) -> Parser[str]:
return lambda s: (
- char()(s)
+ char(s)
if isinstance(one_of(chars)(s), ParseError)
else ParseError(f"Expected any character except {repr(chars)}", s)
)