Ditch the OOP
The member functions probably don’t need to be. You can have the Parser
class provide a few primitive operations (accept character, backtrack, &c.) and implement the rest of the parser as free functions taking a Parser
reference. They can live in the source file (under an anonymous namespace) thus keeping the header nice and minimal.
In this sense, Parser
is really a minimal wrapper around a stream state.
Also, it helps to factor out combinators such as many
, choice
, and so on, to avoid writing error-prone and verbose manual loops.
Sketch
In Parse.h
, your entire public API.
unique_ptr<const Program> parse(istream&);
Your parser state can be entirely private to Parse.cpp
.
struct Parser {
Parser(istream& stream) : stream(stream) {}
bool accept(const char expected) {
char actual;
if (!stream.get(actual)) return false;
if (expected == actual) return true;
stream.seekg(-1, ios::cur);
return false;
}
template<class F, class O>
bool accept_if(F predicate, O output) {
char actual;
if (!stream.get(actual)) return false;
if (predicate(actual)) {
*output++ = actual;
return true;
}
return false;
}
void expect(const char expected) { if (!accept(expected)) throw ...; }
void push_mark() { marks.push_back(stream.tellg()); }
void pop_mark() { stream.seekg(marks.back()); drop_mark(); }
void drop_mark() { marks.pop_back(); }
private:
istream& stream;
vector<istream::pos_type> marks;
};
The public API implementation just forwards to the start production of a grammar with a new parser.
unique_ptr<const Program> parse(istream& stream) {
return parse_program(Parser(stream));
}
Output iterators are a fairly convenient way to produce multiple results.
template<class F, class O>
bool many_if(Parser& parser, F predicate, O output) {
bool success;
while (parser.accept_if(predicate, output)) success = true;
return success;
}
Pointers have the advantage of a conversion to bool.
unique_ptr<const Expression> number(Parser& parser) {
string digits;
auto append = back_inserter(digits);
return many_if(isdigit, append) ? make_unique<Number>(digits) : nullptr;
}
And so on. The real strengths of C++ to leverage when writing parsers are:
- Generic programming
- Iterators
- Streams
Objects, not so much.
Best Answer
GCC was explicitly designed to resist being used as a tools base/library. You need to use Clang for this, or call GCC through commandline.