Skip to content

Instantly share code, notes, and snippets.

@PaperclipBadger
Created May 30, 2018 15:14
Show Gist options
  • Save PaperclipBadger/43f93f36754ae06854869c7f4f144ba8 to your computer and use it in GitHub Desktop.
Save PaperclipBadger/43f93f36754ae06854869c7f4f144ba8 to your computer and use it in GitHub Desktop.
"""
parser_combinators.py
--------------------
Functions that make it easy to create string parsers.
A parser is a function that takes a string ``remaining`` and returns a pair.
If the parser was unsuccessful (the string does not patch the pattern of
characters that that parser accepts), it returns ``(None, remaining)``.
Otherwise, it returns a pair whose first element is the parsing result
(which must not be None) and string remaining after the characters required
for the successful parse have been consumed.
"""
import string
def many(parser):
"""Runs the given parser until it fails, returning a list of results."""
def parser_(remaining):
results = []
result, remaining = parser(remaining)
while result is not None:
results.append(result)
result, remaining = parser(remaining)
return results, remaining
return parser_
def any_(*parsers):
"""Tries parsers in order and returns the first successful result."""
def parser_(remaining):
for parser in parsers:
result, remaining_ = parser(remaining)
if result is not None:
return result, remaining_
else:
return None, remaining
return parser_
def seq(*parsers):
"""Runs parsers in order and succeeds only if they all succeed."""
def parser_(remaining):
for parser in parsers:
result, remaining_ = parser(remaining)
if result is not None:
return result, remaining_
else:
return None, remaining
return parser_
def change_result(parser, f):
"""Runs ``parser`` and if successful, applies ``f`` to the result."""
def parser_(remaining):
result, remaining = parser(remaining)
if result is not None:
result = f(result)
return result, remaining
return parser_
def one_or_more(parser):
"""Like `many`, but the parser must succeed at least once."""
def flatten(result):
[resone, resmany] = result
return resmany.insert(0, resone)
return change_result(seq(parser, many(parser)), flatten)
def char(l):
"""Match exactly ``l``"""
def parser(remaining):
if remaining[0] == l:
return l, remaining[1:]
else:
return None, remaining
return parser
def digit(remaining):
"""Match a character in `string.digits`."""
if remaining[0] in string.digits:
return remaining[0], remaining[1:]
else:
return None, remaining
def letter(remaining):
"""Match a character in `string.ascii_lowercase`."""
if remaining[0] in string.ascii_lowercase:
return remaining[0], remaining[1:]
else:
return None, remaining
def white(remaining):
"""Match a character in `string.whitespace`."""
if remaining[0] in string.whitespace:
return remaining[0], remaining[1:]
else:
return None, remaining
# matches any non-empty sequence of digits and converts it to an int.
integer = change_result(one_or_more(digit), lambda ds: int(ds.join('')))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment