flex
Flex ist eine in der Funktionalität erweiterte GNU-Version von
lex. Mit flex kann man in einfacher Weise endliche Automaten
beschreiben. Dieser wird hier zur lexikalischen Analyse
(Tokenisierung) einer ddl-Datei eingesetzt.
Erste Aufgabe des Programmierers ist es, mit Hilfe regulärer Ausdrücke die Token zu definieren, die erkannt werden sollen. Flex übersetzt dies in einen C/C++-Quellcode, der entsprechend die einzelnen Token-Arten (wie Schlüsselwörter, Bezeichner, Konstanten) durch int-Werte repräsentiert.
Der Programmierer kann nun dem jeweils erkannten Token zusätzlich zum Typ noch einen Wert zuweisen, so hat "1234" etwa den Typ Integer-Konstante und den Wert 1234. Der Programmierer muß diese Übersetzung von der als Token erkannten Zeichenkette in einen Wert als C/C++-Code zur Verfügung stellen.
Flex baut diesen Code entsprechend in den endlichen Automaten ein, der dann in der Form benutzt wird, daß ihm eine Eingabedatei übergeben wird und er eine Funktion zur Verfügung stellt, über die nacheinander die in der Datei enthaltenen Token mit Typ und Wert abgefragt werden können.
bison
Bison ist eine in der Funktionalität erweiterte GNU-Version von
yacc. Mit bison kann man in einfacher Weise Parser (Programme zur
Erkennung von kontextfreien Grammatiken) beschreiben. Dieser wird hier zur
strukturellen Analyse einer ddl-Datei eingesetzt. Dabei wird eine
Datenstruktur aufgebaut, die dem Baum der kontextfreien Grammatik
entspricht. Der Baum wird durch einen Baum von Klassenobjekten
repräsentiert, die über Memberfunktionen die
semantische Analyse rekursiv vornehmen können. Ziel ist es
dabei insbesondere, einen C++-Quellcode zu erzeugen, der die Domain
repräsentiert und zur Erzeugung eines Domain-spezifischen Editors und
Simulators verwendet werden kann.
Der Programmierer muß die verwendeten Token-Typen (Terminale) und die Regeln der kontextfreien Grammatik angeben. Die flex-Schnittstellenfunktion wird verwendet um die Terminale und deren zugehörige Werte einzulesen. Der Programmierer muß nun in C/C++-Code angeben, wie bei Anwendung einer KFG-Regel dem entstehenden Nichtterminal ein Wert zugeordnet werden soll. In unserem Fall wird dabei normalerweise mehrere Bäume zu einem mit einer neuen Wurzel zusammengefaßt.
Flex übersetzt diese Spezifikation des Programmierers in einen
C/C++-Quelltext, der eine Funktion zur Verfügung stellt, mit dem der
Wert des Startsymbols der Grammatik herausgefunden wird.
In unserem Fall wird dadurch der komplette Syntaxbaum des ddl-Programms
aufgebaut. Wie oben erwähnt besitzt dieser schon selbst die
Fähigkeit zur Semantikanalyse.
Das Hauptprogramm muß also "nur" die von bison zur Verfügung
gestellte Funktion aufrufen und danach die Wurzel des Syntaxbaums
anweisen, die Semantikanalyse durchzuführen.
Dateien
Folgende Dateien werden vom Programmierer bearbeitet:
Deklarations-Datei | Implementations-Datei | Aufgabe |
---|---|---|
Makefile | Automatische Übersetzung des DDL-Compilers | |
Src/ddl_main.cc | C++-Hauptprogramm, das den Parsevorgang und die Semantische Analyse auslöst | |
Includes/ddl_common.hh | Deklarationen, die sowohl im DDL-Compiler als auch im Simulator zur Verfügung stehen müssen | |
Includes/komp_tools.hh | Deklarationen, die im Simulator
zur Verfügung stehen müssen; in den vom DDL-Compiler
erzeugten C++-Code wird automatisch ein #include auf
diese Datei eingefügt
| |
Includes/error.hh | Src/error.cc | Fehler-Behandlung |
Includes/cstring.hh | Src/cstring.cc | String-Klasse, die statt der C++-Strings (char*) benutzt wird |
Includes/ddl_ds.hh | Datenstrukturen, die vom DDL-Compiler benutzt werden. | |
Includes/ddl_nodes.hh | Src/ddl_nodes.cc | Datenstrukturen des DDL-Compilers für den Syntax-Baum |
Src/ddl_lexer.l | flex-Quellcode | |
Src/ddl_parser.y | bison-Quellcode |
Makefile
Die Benutzung eines Makefiles für normale C++-Projekte wird
als bekannt vorausgesetzt.
Zusätzlich müssen die Regeln für die Übersetzung des flex- und des bison-Quellcodes angegeben werden:
[...] .y.cc: $(YACC) $(YFLAGS) -o$@ $< .l.cc: $(LEX) $(LFLAGS) -o$@ $< [...] $(DDLSRCDIR)/ddl_lexer.o: $(DDLSRCDIR)/ddl_lexer.cc \\ $(DDLSRCDIR)/ddl_parser.cc.h \\ $(DDLINCLUDEDIR)/ddl_nodes.hh [...] $(DDLSRCDIR)/ddl_lexer.cc: $(DDLSRCDIR)/ddl_lexer.l $(DDLSRCDIR)/ddl_parser.o: $(DDLSRCDIR)/ddl_parser.cc [...] $(DDLSRCDIR)/ddl_parser.cc: $(DDLSRCDIR)/ddl_parser.y $(DDLSRCDIR)/ddl_parser.cc.h: $(DDLSRCDIR)/ddl_parser.y $(YACC) $(YFLAGS) -o$(DDLSRCDIR)/ddl_parser.cc $(DDLSRCDIR)/ddl_parser.y [...]
Als Besonderheit fällt hier die Datei ddl_parser.cc.h
auf. Diese wird von bison automatisch erstellt und wird als
#include
in die flex-Quellcode-Datei eingefügt.
In bison werden symbolische Namen für Token vereinbart, die für
den erzeugten C++-Code in int-Werte übersetzt werden.
Gleichzeitig erzeugt bison die Datei ddl_parser.cc.h
, welche
die symbolischen Namen und ihre int-Übersetzungen als
#define
s enthält. Damit können im flex-Quellcode die
selben Namen verwendet werden und es ist sichergestellt, daß die
Überstzung konsisten ist.
Da die #include
-Anweisung textuell in den von flex erzeugten
C++-Code übernommen wird, besteht die Abhängigkeit erst bei der
Erzeugung der .o-Datei, also der Anwendung des C++-Compilers,
nicht schon bei der Erzeugung des C++-Codes durch flex.