Implementation eines Konfigurationsparsers mit Hilfe von boost::spirit (Teil 1)
In der folgenden Serie von Artikeln beschaeftigen wir uns mit einer immer wiederkehrenden Aufgabenstellung der meisten C++ Programmierer: Der Entwicklung eines Parsers fuer Konfigurationsdateien. In meinen knapp 15 Jahren Programmierpraxis habe ich mindestens 10 verschiedene Parser fuer beinahe genauso viele verschiedene Konfigurationsformate geschrieben — vom simplen INI-Reader bis zum validierenden XML-Parser war alles dabei. Bezueglich des Aufwands sind die beiden genannten Beispiele entgegengesetzte Extreme. INI Dateien lassen sich mit ein paar Zeilen Code einlesen und schreiben und mit geringem Mehraufwand auch in einen Objektbaum uebersetzen. XML hingegen verlangt dem Programmierer einiges an Disziplin und Schreibarbeit ab, um die umfangreichen DOM- bzw. SAX-Schnittstellen zu implementieren, insbesondere wenn man die komplette Spezifikation erfuellen will und gleichzeitig noch eine Validierung benoetigt.
Beide Formate — INI wie XML — sind ausserdem meines Erachtens fuer die Verwendung als Konfigurationsdateien denkbar ungeeignet. Waehrend XML dem Entwickler zwar alle Moeglichkeiten bietet, die Konfigurationen hierarchisch zu gliedern und beliebige Metadaten dazu abzuspeichern, ist die Verwendung von Begin- und Endtags doch in hoechstem Masse speicherineffizient und redundant. Weiterhin gibt das Format an sich keinerlei Typstruktur vor, weshalb diese entweder zusaetzlich auf das Format aufgesetzt oder vom Benutzer gehandhabt werden muss (dies hat natuerlich Vor- und Nachteile!). Wer nun glaubt, mit INI Dateien besser zu fahren, der wird schnell an Grenzen stossen. Lediglich eine einzige Hierarchieebene ist fuer die meisten Anwendungen zu wenig. Und auch hier gibt es ausser Strings keine vorgegebenen Typen.
Welche Anforderungen muss eine Konfigurationsdatei erfuellen, um moeglichst viele Anwendungsbereiche abzudecken? Fassen wir diese in einem kurzen Brainstorming zusammen:
- Unterstuetzung der wichtigsten atomaren Typen (int, float, bool, string)
- Bereitstellung von Listen und Arrays als zusammengesetzte Datentypen (Listen koennen dabei beliebige Subelemente enthalten, Arrays hingegen sind homogene Felder atomarer Typen)
- Einfuehrung unbegrenzter Hierarchieebenen durch Verwendung von assoziativen Arraystrukturen, die mehrere Variablendeklarationen zusammenfassen und rekursiv definiert werden koennen
Damit haben wir Listen-, Array- und hierarchische Semantik gleichermassen abgedeckt und kommen damit schon sehr nahe an das XML Format heran. Bezueglich der Syntax des Formats habe ich mich an C++ bzw. Python angelehnt und fasse Listen in runden, Arrays in eckigen und assoziative Arrays in geschweiften Klammern zusammen. Ein Beispiel fuer eine solche Konfigurationsdatei ist im folgenden dargestellt:
a = 5; b = 6.5; c = { var1 = "Hallo"; pi = 3.14159; }; d = ( { eins = 1; zwei = 2; drei = 3; }, [1, 2, 3, 4, 5] );
Im naechsten Teil der Serie schreiben wir unter Zuhilfenahme der neuesten Version von boost::spirit (v2.1) einen Parser fuer das soeben definierte Format. Im dritten Teil werden wir eine Objektstruktur fuer die C++ API definieren und diese im letzten Teil mit dem Parser verknuepfen.