I wrote a Parser module that reads files that are something like .ini for my game (not strictly, however, as I found the following syntax easier to parse, and easier to modify). The following is a bit simplified, and you are welcome to get the source for the game, but hopefully this will help you, or at least provide some insight into an implementation that worked rather nicely for me.
My parser has a few key classes..
The SimpleParser. This drives the parsing. It maintains a map of strings and actions. It reads strings until a certain token is hit (in this example, both { and = tell the parser to call an action, a } tells the parser to stop).
There is also a ParseAction base class, which is simple and looks like this
Code:
class ParseAction {
public:
virtual void getValue(istream& input)=0;
virtual ~ParseAction() { }
};
The Simple parser maintains a map<string, ParseAction*>. When hits a { or =, it looks into the map for the corresponding ParseAction matching the string, if it finds a ParseAction, it calls getValue with the stream it has read from.. so the ParseAction will read the value and then exit.
SimpleParser looks like this.
Code:
class SimpleParser {
public:
void read(); // starts parsing
SimpleParser(std::istream& inputStream); // make parser read from input stream
// action must be dynmically allocated via new
void addAction(const std::string& indentifer, ParseAction* action);
// deletes the actions add with addAction
~SimpleParser();
private:
map<string, ParseAction*> actionMap;
};
You can then subclass ParseAction for specific reading behavior. The simplest and easiest to understand ParseAction implementation is a templated StdRead class, which simply wraps operator >> into a ParseAction.
Code:
template <class T>
class StdRead: public ParseAction {
public:
StdRead(T& object) :
obj(object)
{ }
virtual void getValue(istream& input) {
input >> obj;
}
protected:
T& obj;
};
Now, let's say you want to parse, say, a file for player configuration. Say the file looks like this... the lines can be rearranged in any order.
Code:
left=a
right=d
up=w
down=s
hp=100
mp=15
speed=12
The corresponding player structure might look like this.
Code:
struct player {
char left, right, down up;
string name;
int hp, mp, speed;
};
And the corresponding parser would be like this.
Code:
player p;
SimpleParser parser("knight.cfg");
parser.addAction("left", new StdRead<char>(player.left));
parser.addAction("up", new StdRead<char>(player.up));
parser.addAction("right", new StdRead<char>(player.right));
parser.addAction("down", new StdRead<char>(player.down));
parser.addAction("name", new StdRead<string>(player.name));
parser.addAction("hp", new StdRead<int>(player.hp));
parser.addAction("mp", new StdRead<int>(player.mp));
parser.addAction("speed", new StdRead<int>(player.speed));
parser.read();
By subclassing ParseAction you can extend the behavior in a modular manner, and reuse it. Perhaps you want to be able to nest things.. in that case, you could make a ParseAction that calls a Parser .. or perhaps you might want to know if an action was called.. you could write a subclass CheckedRead, which is like StdRead, except it takes a bool that it sets to true if it's getValue function was called.
I don't know if this has helped you at all (I hope it has though.. ), I didn't handle writing the files, but that is the easier half of the deal.
Here is source documentation from the Parser module of my game, that perhaps provides a bigger picture.
http://bomberlan.sourceforge.net/html/a00219.html