ttlogo.jpg Freie TextTransformer Projekte
Start
Text2HTML
Wikipedia
Yacc2TT
Delphi-Parser
Java-Parser
C-Präprozessor
C-Parser
HTML4
Nützliches
MIME-Parser
Spamfilter
Weitere Beispiele
Freie Komponenten
  Minimal Website   Impressum

c_pp


Mit c_pp.ttp ist ein TextTransformer-Projekt benannt, das einen C-Präprozessor nachbildet. Mit c_pp lassen sich C++-Dateien in die vorverarbeitete Form bringen, wie der Compiler sie "sieht": Präprozessor-Direktiven sind darin entfernt: Include-Dateien werden eingeschlossen, Definitionen werden ersetzt, nicht definierte Bereiche werden ausgelassen und Makros werden aufgelöst. Im Unterschied zu den existierenden Präprozessoren, die der Kompilierung von C++-Code vorausgeschaltet sind, wird mit c_pp nicht nur intermediär die vorverarbeitete Tokenfolge erzeugt, sondern ein vollständiger Text.


Zum Namen "c_pp"

Der Name "c_pp" steht für C-Präprozessor. Durch den Unterstrich wird der Name von einem ebenfalls existierenden Cplusplus-Parser mit dem Namen "Cpp" unterschieden.


Geschichte des c_pp-Projekts

Die ursprüngliche Version dieses C-Präprozessors wurde entwickelt, um die Übersetzung einer in C++ verfassten Firmensoftware nach Java vorzubereiten. Es war also nicht das Ziel einen allgemeingültigen Präprozessor zu erzeugen, der mit allen Tricks einer Präprozessor Meta-Programmierung zurechtkommt. Die Zielsetzung war vielmehr pragmatisch: Aus der endlichen Anzahl von Dateien sollten die Präprozessor-Direktiven in einer Weise ersetzt werden, die den Sinn dieser Direktiven bewahrte.

  • für definierte Konstanten wurden "echte" C++ Konstanten in den Code eingefügt
  • etliche Makros wurden nicht aufgelöst, sondern durch Funktionen ersetzt
  • Kommentare wurden im Code belassen
  • Header der System- und Bibliotheks-Dateien wurden nicht eingeschlossen. Ihr Inhalt sollte direkt durch Java-Analoga substituiert werden.
  • für jeden Firmen-Header wurde ein entsprechender vorverarbeiteter Header erzeugt und die Include-Anweisungen für diese Header wurden deshalb in den Quellcode-Dateien belassen.

Diese, auf die betreffende Firmensoftware zugeschnittenen Spezialbehandlungen wurden aus dem hier veröffentlichten c_pp-Projekt entfernt. Es ist aber leicht möglich, entsprechende Spezialbehandlungen für andere Übersetzungsprojekte erneut einzufügen.


Anwendungsmöglichkeiten von c_pp

Neben der eben geschilderten Aufgabe des c_pp-Projkts die Übersetzung von C++-Code in andere Programmiersprachen vorzubereiten, sind noch weitere Anwendungsmöglichkeiten denkbar. Beispielsweise könnte es dazu benutzt werden zu testen, ob Präprozessoranweisungen tatsächlich den Code erzeugen, den man erwartet. Tatsächlich gibt es hier so viele Fallstricke, dass ihnen im Gnu Präprozessor Manual ein langer Abschnitt gewidmet wurde:

http://gcc.gnu.org/onlinedocs/cpp/Macro-Pitfalls.html#Macro-Pitfalls

Auch korrekt geschriebene Anweisungen haben den Nachteil, dass sie nur schwer zu debuggen sind. Scott Meyers gibt daher gleich im ersten Kapitel in seines viel beachteten Buchs: "Effective c++" den Rat: "prefer the compiler to the preprocessor". Eine weitere denkbare Anwendungsmöglichkeit für das c_pp-Projekt wäre es also, C++-Dateien real vorzuverarbeiten, d.h. sie in neue Dateien mit aufgelösten Präprozessoranweisungen umzuformen.



Anmerkungen zum Aufbau des Projekts und zur Standard-Konformität


c_pp ist nahezu eine standardkonforme Implementierung der geforderten C99/C++ Vorprozessorfunktionalität. Die Abweichungen werden in den folgenden Anmerkungen erörtert, die wie die ausgezeichnete Einführung in den C Vorprozessor gegliedert sind:

http://gcc.gnu.org/onlinedocs/cpp/

Erste Verarbeitung


1. Lesen der Eingabedatei

Die Datei wird kontinuierlich gelesen, ohne sie zunächst in Zeilen umzubrechen, wie das andere Präprozessoren tun. Leerzeichen, Tabulatoren und Kommentare werden ignoriert


2. Trigraph-Sequenzen

Trigraph-Sequenzen werden nicht ersetzt.

3. Zeilenverlängerungen

Backslashes `\' an Zeilenenden und nachfolgende Leerzeichen werden ausgelassen.


4. Kommentare

Alle Kommentare werden mit der Produktion "comment" durch einzelne Leerzeichen ersetzt. "comment" ist in c_pp als Einschluss-Produktion gesetzt. Dies ist eine spezielle TextTransformer-Funktion, die es erlaubt, Kommentare etc. einfach zu behandeln.

"Extrem verwirrende" Tricks, wie die Aufsplittung von `/*', `*/', und `//' in mehrere Zeilen durch eine Zeilenverlängerung, werden von c_pp nicht korrekt behandelt.


5. Zeilenenden

In TextTransformer-Projekten umfassen reguläre Ausdrücke für die auszulassenden Zeichen oft auch Zeilenenden. In der C-Präprozessor Grammatik haben Zeilenenden aber eine wichtige Rolle, so dass ihre möglichen Vorkommen explizit aufgeführt werden.


Zerlegung in Token

Gemäß dem 1999 C Standard können Bezeichner u.U. Buchstaben enthalten, die nicht zu den ASCII-Zeichenmenge gehören. c_pp kann solche Bezeichner nicht verarbeiten.



Header Dateien

Include Syntax

Eingeschlossene Benutzer- oder System-Header, wie

`#include "FILE"'

oder

`#include <FILE>'

werden beide durch den Ausdruck

PD_INCLUDE ::= #\s*include\s*("([^"]+)"|<([^>]+)>)

erkannt. Der erste Unterausdruck dieses Ausdrucks - in TextTransformer-Schreibweise: 'xState.str(1)' - liefert die einzuschließende Datei 'FILE'.


Include Operation


Immer, wenn c_pp eine Include-Anweisung findet, wird die Funktion

'scan_include_file'

aufgerufen. In dieser Funktion wird die Datei mit 'load_file' geladen und dann mit der Produktion 'header' auf die gleiche Art verarbeitet wie die ursprüngliche Datei. D.h. der vorverarbeitete Text der eingeschlossenen Datei wird an den Text angefügt, der aus der ursprünglichen Datei schon generiert wurde. Nach vollständiger Verarbeitung der eingeschlossenen Datei wird mit der Bearbeitung der einschließenden Datei fortgesetzt. Gibt es in der eingeschlossenen Datei ebenfalls include-Direktiven, so wird das Einschlussverfahren auf höherer Ebene analog ausgeführt. Ein Integer-Parameter für die aktuelle Ebene wird beim Aufruf von 'header' inkrementiert.

Ob eine Datei wirklich eingeschlossen werden soll, wird durch die Funktion 'ReallyInclude' gesteuert. Bei dem eingangs erwähnten Übersetzer von C++ nach Java wurden z.B. nur die unmittelbar zum Quelltext gehöriger Header-Datei eingeschlossen.


Suchpfad

In c_pp wird zwischen System- und Benutzer-Headern nicht unterschieden. Nach dem Header wird in beiden Fällen gleichermaßen in einer Verzeichnisliste gesucht, die im vector

m_vIncludeDirs

gespeichert ist. Diese Liste kann beim Aufruf des Projekts als Konfigurations-Parameter übergeben werden. Je nach dem, wie das TextTransformer-Projekt ausgeführt wird, ist der Konfigurations-Parameter in den Projektoptionen (für die Arbeitsoberfläche der IDE), im Transformation-Manager oder als Kommandozeilen-Parameter zu setzen.
Im Konfigurations-String ist jedes Verzeichnis in eine Zeile zu schreiben. Z.B.

D:\Tetra\Projects\Divers\Cpp
C:\Programme\Borland\CBuilder6\Include

Diese Liste wird vor dem Strart des c_pp Präprozessors mit der Produktion 'IncludePaths' geparst, um den m_vIncludeDirs-vector zu füllen.

  (
    SKIP {{ AddIncludeDir(trim_copy(xState.str())); }}
    ( EOL | EOF )
  )*

Der Unterparser wird in der Init-Funktion aufgerufen:

IncludePaths(ConfigParam());

Außerdem wird mit

m_vIncludeDirs.push_back(SourceRoot());

das Wurzelverzeichnis des Quelltextes als Include-Pfad gesetzt.


Once-Only Header

Die Namen bereits verarbeiteter Header werden in der map

m_mHeaderPaths

gespeichert. Der Präprozessor ließe sich dadurch beschleunigen, dass Dateien die bereits in dieser Liste enthalten sind, nicht nochmals geparst werden. Dies Verfahren ist jedoch nicht völlig korrekt, da sich zwischen zwei Einschlüssen der gleichen Datei die Menge der definierten Ausdrücke geändert haben kann. Die gleiche Datei kann daher bei nochmaliger verarbeitung ein anderes Resultat ergeben.



Makros

Makros sind Abkürzungen für Code-Fragmente, die durch die Präprozessor-Direktive '#define' definiert werden. Funktionsartige Makros enthalten Klammern und mögliche Argumente, während objektartige Makros einfache Bezeichner sind.

Makro-Definitionen werden in der Produktion 'definition' geparst. Sie beginnt mit dem Token

PD_DEFINE ::= #\s*define

Für objekt-artige Makros folgt hierauf ein einfacher Bezeichner 'ID'. Folgt hingegen ein Token

MACRO_DEF_BEGIN ::= (\w+)\(

so handelt es sich um eine funktionsartige Makrodefinition.

Entdeckt c_pp ein aufgerufenes Makro, so wird es expandiert: Makro Argumente werden ausgewertet, Argumente mit vorangestelltem '#' werden als String ausgegeben und Token können durch '##' verkettet werden. Variadische Makros (Variadic Macros) werden nicht unterstützt.
Mit undef können Makrodefinitionen wieder entfernt werden und makros können umdefiniert werden. (in c_pp erfolgt keine Warnmeldung, wenn die Neudefinition verschieden ist von der ursprünglichen.)


Die Expansion des Makros erfolgt ähnlich, wie von Kaiser auf macro_expansion_process.html beschrieben.


Conditionals

Die einfachste Art einer bedingten Gruppe ist:

#ifdef MACRO

CONTROLLED TEXT

#endif /* MACRO */

CONTROLLED TEXT wird vom Präprozessor genau dann ausgegeben, wenn MACRO definiert ist. Innerhalb von CONTROLLED TEXT können weitere Präprozessor Direktiven vorkommen. Sie werden nur dann ausgeführt, wenn die bedingung zutrifft. Bedingte Gruppierungen können ineinander verschachtelt werden.



Diagnostics

Line Control


Pragmas


Other Directives

Preprocessor Output

Links

Ein C-Präprozessor in Pascal von Dr. Hans-Peter Diettrich
http://members.aol.com/vbdis/

wave - Standard konforme Implementation der vorgeschriebenen C99/C++ C-Präprozessor Funktionalität in C++ von Hartmut Kaiser
http://www.boost.org/libs/wave/index.html



 to the top