Skip to content

Instantly share code, notes, and snippets.

@drtconway
Created January 19, 2020 01:09
Show Gist options
  • Save drtconway/b40578861314f159a8180181daf5eca4 to your computer and use it in GitHub Desktop.
Save drtconway/b40578861314f159a8180181daf5eca4 to your computer and use it in GitHub Desktop.
Use variadic templates to create composite monadic parsers.
// A cut down abstraction of a parser.
//
template <typename T>
struct parser
{
T v;
explicit parser(T t) : v(t) {}
T eval() { return v; }
};
// A test to see if a type is a parser
//
template <typename T>
struct is_parser_type : std::false_type {};
template <typename T>
struct is_parser_type<parser<T>> : std::true_type {};
// The parser specialization of the bind function from Monad.
//
template <typename U, typename T, typename X>
parser<U> bind(parser<T> p, X x)
{
static_assert(std::is_convertible<X, std::function<parser<U>(T)>>::value);
T t = p.eval();
return x(t);
}
// This is the interesting bit.
//
// We want a variadic function that can take an arbitrary number of parsers of different types,
// use `bind` to apply them in sequence, and if they all succeed, apply a function
// (which is the last agument) to take their result values to yield a new parser.
//
// That is, we want something like the following (using mangled c++/haskell notation):
// parser<T1> -> parser<T2> -> ... -> parser<Tn> -> (T1 -> T2 -> ... -> Tn -> parser<U>) -> parser<U>
//
// We use the fact that the function is not an instantiation of parser to recursively invoke bind,
// effectively rotating the argument list, until we get to the base case which has the function as
// the first argument.
//
// Here's the base case:
//
template <typename V, typename X, typename... Ts>
typename std::enable_if<std::is_convertible<X, std::function<parser<V>(Ts...)>>::value, parser<V>>::type
seq(X x, Ts... ts)
{
return x(ts...);
}
// Here's the recursive case:
//
template <typename V, typename P, typename... Ts>
typename std::enable_if<is_parser_type<P>::value, parser<V>>::type
seq(P p, Ts... ts)
{
using T = typename deconstruct_template<P>::type;
return bind<V>(p, [=](T t) {
return seq<V>(ts..., t);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment