Enum2String

Beim Programmieren in C++ ist es oft nützlich, die enum-Werte nicht als Zahlen sondern als Symbole mit ihren Namen auszugeben. C++ bietet diese Funktionalität nicht an. Es kann aber mit kleinem Stück Code bewerkstelligt werden. Dieses Code wird nur dann aktiviert, wenn es erforderlich ist – meistens im Entwicklungsstadium und zur Fehlersuche.
Das hier vorgestellte Perl Skript erzeugt eine C++ Klasse, die viele überladene toString Methoden für alle im Projekt deklarierten Enums bietet. Außerdem wird hier kurz auf das nutzliche Tool ctags eingegangen.

Um alle enum-Deklarationen im Code zu finden, wäre das Parsen notwendig. C++ Code manuell (mit Perl) zu parsen, ist abenteuerlich. Es gibt eine viel einfachere und sicherere Mögligkeit – das Tool ctags.

Für jeden, der mit GNU Tool-Chain arbeitet, ist ctags sicherlich ein Begrif – das Tool parst Quellcode und erzeugt daraus Symboltabelle. Der Aufruf ctags -R * parst alle Verzeichnisse rekursiv und erzeugt eine Datei namens tags im Arbeitsverzeichnis. Zu jedem Symbol in der tags-Datei gibt es Information, in welcher Datei es deklariert wurde und mit welchem Regulärem Ausdruck es in der Datei zu finden ist. z.B. hier ist ein Ausschnitt aus der einer tags-Datei:

EnumA inc\enums1.h /^enum EnumA { eA1, eA2, eA3, };$/;" g
eA1 inc\enums1.h /^enum EnumA { eA1, eA2, eA3, };$/;" e enum:EnumA

Die erste Zeile besagt, dass in der Datei inc/enum1.h ein neuer Enum-Typ deklariert wird. Die zweite Zeile bedeutet – “enum eA1 ist ein enum vom Typ EnumA.”

Und tatsächlich, in der Testdatei inc/enums1.h findet man das EnumA:
///@file enums1.h
#ifndef ENUMS1_HG_
#define ENUMS1_HG_
enum
EnumA {
eA1, eA2, eA3, };
enum EnumB
{
eB1, // first enum
eB2, eB3=eA3,
// eBUnused,
/* eBUnused, */eB4
};
typedef enum ECStyle{
eCStyle1=0, eCStyle2=100,
};
typedef enum EStrangeStyle{
eStrangeStyle1, eStrangeStyle2
}EStrangeStyle;
#endif

Die Datei enums1.h ist eine gültige C++ Headerdatei. Ich habe dort vier Enumtypen deklariert. Die unterschiedlichen Schreibweisen habe ich absichtlich gewählt, um zu zeigen, dass manuelles Parsen keine triviale Aufgabe wäre. Diese Möglichkeiten, enums in C++ zu deklarieren, sind nicht hypothetisch. Ich habe sie alle schon im produktiven Code gesehen.
(Die Deklaration von EnumA und EnumB ist das, was man normalerweise in C++ benutzt. Die anderen, … na ja.)
Für Testzwecke habe ich im Verzeichnis inc eine weitere Datei hinterlegt:
///@file enums2.h
#ifndef ENUMS2_HG_
#define ENUMS2_HG_
enum EnumC { eC1, eC2, eC3, };
enum EnumD { eD1, eD2, eD3, };
#endif

Das komplete Perl-Skript samt dem Beispielprojekt findest Du unten, am Ende des Artikels. Hier ist eine kurze Erläuterung.

Das folgende Rreguläre Ausdruck enthält 2 “back-references” – Unterausdrucke, die nach dem Anwenden des Hauptausdrucks referenziert werden können. In Perl würden die reservierten Perl-Variablen $1 und $2 die Inhalte von den fettmarkierten Ausdrücken enthalten.
my $pat="^[^\t]+\t([^\t]+)\t.*enum:([a-zA-Z_][a-zA-Z0-9_]*)";
Zum Beispiel, nach Anwenden des oberen RegExp bei folgender Zeile würden die Perl-Variablen $1 und $2 die fettmarkierten Anteile enthalten:
eA1 inc\enums1.h /^ eA1, eA2, eA3, };$/;" e enum:EnumA

Folgende Schleife schreibt die gefundenen includes und enums in zwei Listen:
while () {
if (my ($m) = m/$pat/){
push @etypes, $2;
push @includes, $1;
}
}

Folgendes Codeschnipsel erzeugt aus beiden Listen, die mehrere gleiche Einträge enthalten können, Unique-Listen:
my %hlp1 = ();
my @uniqenums = grep { ! $hlp1{$_} ++ } @etypes;
my %hlp2 = ();
my @uniqinc = grep { ! $hlp2{$_} ++ } @includes;

Nun habe ich alle notwendigen Informationen für die Codegenerierung – Enum-Typen und Headerdateien.
Es wird eine Utility-Klasse (keine Instanzen möglich/sinnvoll) generiert, die mehrere überladenen statischen toString-Mehtoden hat z.B.:
static const char * toString( EnumA en ) {
switch( en ) {
case eA1: return "eA1";
case eA2: return "eA2";
case eA3: return "eA3";
}
}

Außerdem wird in der Klasse eine Testmethode definiert – static void testEnum2String(), die beim Aufruf alle toString Methoden testet. Enum-Wert und Enum-Symbolname werden auf der Standardausgabe ausgegeben.

Download

Copyright 2010, Valentin Heinitz