Implementation eines Konfigurationsparsers mit Hilfe von boost::spirit (Teil 2b)
Im letzten Teil dieser Serie haben wir bereits einen Grossteil der Grammatik fuer unseren Parser definiert. Es fehlen nur noch die Definitionen der rekursiven Datenstrukturen Gruppe und Liste sowie des Arrays. Zunaechst wollen wir uns die Regel fuer den Array ansehen:
arrayDefinition = lit('[') >> -( double_ % ',' | long_ % ',' | bool_ % ',' | string % ',' ) > ']' ;
Gemaess unserer Syntaxspezifikation wird der Array durch eine oeffnende eckige Klammer eingeleitet, gefolgt von beliebig vielen kommagetrennten Werten vom Typ double, long, bool oder string. Die schliessende eckige Klammer darf nie fehlen, daher kann der Parser auch sofort abbrechen, wenn keines der inneren Elemente mehr matcht und kein “]” folgt. Dies wird durch das einzelne “>” erreicht. Der Prozent Operator hinter einer Regel (X % C) bedeutet so viel wie “ein oder mehrere Produktionen vom Typ X, jeweils getrennt durch das Token C”. Das “-” (das fuer “optional” steht) sorgt dafuer, dass auch leere Arrays erlaubt sind.
Diese Regel beinhaltet noch eine weitere Besonderheit, die vielleicht auf den ersten Blick nicht ganz offensichtlich ist. Haetten wir fuer den inneren Teil geschrieben
( double | long_ | bool_ | string) % ','
oder noch kuerzer
atom % ','
so haetten wir zwar (mehr oder weniger) Zeichen gespart, aber die Semantik dieser Regel waere nicht mehr dieselbe. Der Array ist so definiert, dass er nur Elemente ein und desselben Typs enthalten darf. Wenn das erste Element ein String ist, so muessen alle weiteren Elemente auch Strings sein. Diese Eigenschaft ist aber nur garantiert, wenn wir den Listenoperator auf jede Unterregel fuer sich anwenden und erst die daraus resultierenden Regeln verodern.
Die Regel fuer die Liste sieht sehr aehnlich aus, muss aber zusaetzlich Rekursion erlauben. Im Gegensatz zum Array darf sie ausserdem gleichzeitig Elemente unterschiedlicher Typen enthalten:
listDefinition = lit('(') >> -( ( groupDefinition | listDefinition | arrayDefinition | atom ) % ',' ) > ')' ;
Wie schon beim Array besprochen muss der Listenoperator bei der inneren Regel nun aussen stehen, um Listeneintraege unterschiedlichen Typs zuzulassen. Erlaubte Produktionen innerhalb der Liste sind die Array- und Gruppendefinition, die Listendefinition (Rekursion!) sowie Elemente vom Typ atom.
Die Gruppendefinition ist die letzte Produktion, die uns noch fehlt. Sie ist im Wesentlichen der Liste aehnlich, enthaelt aber nicht nur eine Menge von Objektdefinitionen sondern vielmehr eine Liste von Variablenzuweisungen und bildet damit die Semantik eines Dictionaries (Key-Value-Map) in unserem Konfigurationsformat ab:
groupDefinition = lit('{') >> *variableAssignment > '}' ;
Innerhalb der geschweiften Klammern erlauben wir also eine beliebige Anzahl von Produktionen vom Typ “variableAssignment”, die wir bereits im Teil 2a definiert hatten.
Im naechsten Teil der Serie werden wir eine Objektstruktur definieren, die es uns erlaubt, Konfigurationsdateien geeignet abzubilden. Dabei werden wir insbesondere Wert darauf legen, dass der Zugriff auf die Konfigurationswerte moeglichst einfach und flexibel ist.