5

I'm working on a parser combinator library, and I'd really like my parser to simply be some callable object:

typedef std::function<parse_result(parse_stream)> parser;

Which makes the parser combinators nice, eg:

parser operator &(parser a, parser b) { return both(a,b); }

but I'd like two features:

1) I'd like string literals to get promoted to a parser automatically so you can do things like:

parser option = "<" & regexp("[^+>]+");

2) I'd like a parser to have a name that I can use for error formatting. In the case of the "both" parser above, I could print that I expected a.name() and b.name() for example.

The two options I've tried so far are

  1. a parser class that's callable, this lets me build from strings and std::function instances but a general callable has to be converted to a std::function first and from there to a parser, and C++ won't do two implicit conversions

  2. Inheriting from std::function so I can implicitly convert the functions, but this seems to have a lot of gotchas in terms of only converting callables into a parser.

Does anyone have any thoughts on how to structure this?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
gct
  • 14,100
  • 15
  • 68
  • 107

1 Answers1

3

You don't want a raw typedef of std function; your parser is more than a any std function.

struct parser: std::function<parse_result(parse_stream)>{
  using base = std::function<parse_result(parse_stream)>;
  using base::base;
};

this should permit

parser p = []( parse_stream str ) { return parse_result(7); };

as we use inheriting constructors to expose the raw std::function ctors in parser.

While you can override:

parser operator&(parser a, parser b) { return both(a,b); }

with the typedef version by putting & in the namespace of parse_result or parse_stream, I'd advise against it; there has been chat in the standarizatoin to restrict that kind of template-argument ADL. With a bare parser type, the spot to put such operator overloads is clear.

In addition some types cannot be overloaded outside of the class, like &=. With a struct you can do it in there.

None of this fixes

parser option = "<" & regexp("[^+>]+");

as the problem here is that the right hand side has no idea what the left hand side is doing (unless regexp is a function returing a parser).

First do this:

struct parser: std::function<parse_result(parse_stream)>{
  using base = std::function<parse_result(parse_stream)>;
  parser( char const* str ):
    base( [str=std::string(str)](parse_stream stream)->parse_result { /* do work */ } )
  {}
  parser( char c ):
    base( [c](parse_stream str)->parse_result { /* do work */ } )
  {}
  using base::base;
};

then you can add

namespace parsing {
  // parser definition goes here
  inline namespace literals {
    inline parser operator""_p( char const* str ) { return str; }
  }
}

and using namespace parsing::literals means "hello"_p is a parser that attempts to parse the string "hello".

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Inheriting constructors, that's what I was missing, this is brilliant, thank you! – gct May 04 '18 at 15:04