dbLounge
Einleitung
Der DBP-Präprozessor, bekannt als DBPre, ist ein Makroprozessor, der als Erweiterung für DBP daher kommt und automatisch dein Programm in DBP-Quellcode transformiert, bevor der Compiler ihn bekommt. Er wird als Makroprozessor bezeichnet, weil er die Definition von Makros erlaubt, was Abkürzungen für längere Programmkonstruktionen sind.
Dieses Dokument beschreibt das Verhalten des Präprozessors.

Der DBP-Präprozessor sollte nur in Verbindung mit DBP verwendet werden, da er den Quellcode umformatieren wird.
Einführende Modifikationen
Der Präprozessor vollführt eine Serie von textuellen Transformationen über seiner Eingabe. Diese passieren alle vor jeder weiteren Verarbeitung. Konzeptionell passieren sie alle in einer festen Reihenfolge und die gesamte Datei wird durch eine Transformation geführt, bevor sie die nächste passiert. DBPre führt alle Transformationen aus Geschwindigkeitsgründen gleichzeitig aus.
1. Durchlauf
Die Eingabedatei wird zunächst in den Speicher gelesen und in ihre Zeilen aufgeteilt. Mittels #include eingefügte Dateien sind dabei inbegriffen.
2. Durchlauf
Fortgeführte Zeilen werden zu einer langen Zeile zusammengefügt.
Eine fortgeführte Zeile ist eine Zeile, die mit einem Backslash endet. Optionalerweise ist auch ein Underscore möglich. Der Backslash wird gelöscht und die folgende Zeile wird mit der aktuellen verknüpft. Dabei wird kein Leerzeichen eingefügt, d.h. du kannst jede Zeile überall splitten, sogar in der Mitte eines Wortes. (Es ist allerdings besser lesbar, nur nach abgeschlossenen Wörtern zu splitten)
Wenn ein Leerzeichen zwischen Backslash und Ende der Zeile ist, ist es keine verknüpfte Zeile mehr.
3. Durchlauf
Alle Kommentare werden gelöscht.
Es gibt zwei Arten von Kommentaren. Blockkommentare beginnnen mit Remstart und gehen bis zum nächsten Remend. Blockkommentare lassen sich nicht verschachteln:
Blockkommentare
1
2
3
4
5
6
7
8
remstart
das ist
remstart
ein Kommentar
remend
Text außerhalb des Kommentars
Zeilenkommentare beginnen entweder mit `, mit Rem oder mit // und gehen bis zum Ende der Zeile. Zeilenkommentare lassen sich auch nicht verschachteln, das ist allerdings egal, weil sie sowieso an der selben Stelle enden würden.
Zeilenkommentare
1
2
3
4
` das ist // ein Kommentar
Text außerhalb des Kommentars
Es ist sicher Zeilenkommentare in Blockkommentare zu schreiben oder anders herum.
geschachtelte Kommentare
1
2
3
4
5
6
7
8
9
10
remstart
  Blockkommentar
  // beinhaltet Zeilenkommentar
  mehr Kommentare
remend
außerhalb des Kommentars

` Zeilenkommentar remstart beinhaltet Blockkommentar remend
Doch Vorsicht mit dem Auskommentieren eines Blockkommentars mit einem Zeilenkommentar.
falsch geschachtelt
1
2
3
4
// langer Text... remstart Blockkommentar beginnt huch! das ist kein Kommentar mehr remend
Kommentare werden nicht in Strings erkannt. "remstart blah remend" ist die Stringkonstante <remstart blah remend>, kein leerer String.
4. Durchlauf
Alle In-Place-Ersetzungen werden vorgenommen.
In-Place ersetzt werden die Steuerzeichen in Strings und in Hochkomma gestellte Einzelzeichen.
Steuerzeichen in Strings
1
2
3
4
Print "Das\\nist\\nein\\nTest\\n\\nSie sagte, \\"Wie gehts dir?\\"\\n" Wait key
Würde ausgeben:
Ausgabe
1
2
3
4
5
6
7
8
Das ist ein Test Sie sagte, "Wie gehts dir?"
Vorsicht bei den Zeilenumbrüchen!
Der Printbefehl wird bei erneutem Aufruf jetzt trotzdem in die Zeile, in der "ist" steht weiterschreiben.

Die aktuell implementierten Steuercodes in Strings sind:
Steuercodes in Strings
\\a  
\\n  
\\t  
\\r  
\\b  
\\f  
\\v  
\\"  
\\\\  
audible bell
unix newline
horizontal tab
windows newline
backspace
formfeed
vertical tab
quote
backslash
Nicht alle dieser Steuercodes funktionieren in DBP!

In Hochkomma gestellte Einzelzeichen werden danach durch ihre ASCII-Codes ersetzt, wenn sie nicht in Strings vorkommen.
Zeichenkonstanten
1
2
3
4
5
6
7
For i = 'a' To 'z'
  Print Chr$(i);
Next i

Wait key
Das wird den folgenden String ausgeben:
Ausgabe
1
2
3
abcdefghijklmnopqrstuvwxyz
Mit den Zeichenkonstanten kann selbstverständlich auch gerechnet werden. Sie können überall dort verwendet werden, wo normale Integerwerte zulässig sind. Neben den Zeichen, die man zwischen zwei Hochkommas ohne Probleme setzen kann, gibt es auch hier wieder die Möglichkeit Steuercodes einzufügen.

Hier eine Liste mit den aktuell implementierten Spezialkonstanten:
Steuercodes in Zeichenkonstanten
'\\a'  
'\\n'  
'\\t'  
'\\r'  
'\\b'  
'\\f'  
'\\v'  
audible bell
unix newline
horizontal tab
windows newline
backspace
formfeed
vertical tab
5. Durchlauf
Makros und Inlinefunktionen werden eingesetzt und die Bedingungen ausgewertet.

Ein Makro ist ein Codefragment, welches einen Namen bekommen hat. Wann immer der Name benutzt wird, wird er durch den Inhalt des Makros ersetzt. Es gibt zwei Arten von Makros, die sich größtenteils darin unterscheiden, wie sie aussehen, wenn sie benutzt werden. Objektähnliche Makros ähneln Variablen, wenn sie benutzt werden, funktionsähnliche Makros sehen aus wie Funktionsaufrufe.

Du darfst jede Kombination aus alphanumerischen Zeichen als Makroname benutzen, sogar, wenn es ein DBP-Schlüsselwort ist. DBPre weiß nichts von Schlüsselwörtern.

Objektähnliche Makros
Ein objektähnliches Makro ist ein simpler Bezeichner, der durch ein Codefragment ersetzt wird. Er wird objektähnlich genannt, weil er in dem Code wie ein Datenobjekt aussieht, in dem er benutzt wird. Sie werden allgemein dafür genutzt numerischen Konstanten symbolische Namen zu geben.

Du kannst ein neues Makro mittels der #define-Direktive erstellen. #define wird gefolgt vom Namen des Makros und dann der Zeichenabfolge, für die es eine Abkürzung sein soll.
Zum Beispiel:
Beispiel für ein objektähnliches Makro
1
2
3
#define BUFFER_SIZE 1024
definiert ein Makro mit dem Namen BUFFER_SIZE als eine Abkürzung für die Zeichenkette 1024. Wenn irgendwo danach ein DBP-Statement der Form:
Benutzung
1
2
3
dim Buffer(BUFFER_SIZE) as float
kommt, wird DBPre das erkennen und das Makro BUFFER_SIZE expandieren. DBP wird den Text sehen, als stände dort
Ausgabe
1
2
3
dim Buffer(1024) as float
Als Konvention gilt, dass Makronamen stehts in Großbuchstaben geschrieben werden sollten. Programme sind leichter zu lesen, wenn sofort klar ist, welche Namen Makros sind.

Der Makrokörper endet am Ende der #define-Zeile. Du kannst allerdings die Definition über mehrere Zeilen schreiben, wenn nötig, indem du eine fortgeführte Zeile verwendest (siehe Schritt 2). Wenn das Makro expandiert wird, wird jedoch alles in eine Zeile geschrieben.
Zum Beispiel:
Makrodefinition über mehrere Zeilen
1
2
3
4
5
6
7
8
#define INIT   Set display mode 1280, 1024, 32:_
               Sync on:_
               Sync rate 0:_
               Sync

INIT
wird zu
Ausgabe
1
2
3
Set display mode 1280, 1024, 32: Sync on: Sync rate 0: Sync
Die häufigste sichtbare Konsequenz davon sind überraschende Zeilennummern in den Fehlermeldungen.

Es gibt keine Beschränkungen, was in einen Makrokörper hinein darf, solange es in gültige Präprozessortoken aufgeschlüsselt werden kann. Klammern müssen nicht ausbalanciert sein und der Körper muss kein korrekter DBP-Code sein. (Wenn er es nicht ist, wirst du vermutlich Fehlermeldungen vom DBP-Compiler bekommen, wenn das Makro benutzt wird.)

DBPre scannt das Programm von oben nach unten. Makrodefinitionen treten dann in Kraft, wenn du sie schreibst.
Deshalb wird die Eingabe:
Makrodefinitionen
1
2
3
4
5
foo = X
#define X 4
bar = X
folgende Ausgabe produzieren:
Ausgabe
1
2
3
4
foo = X
bar = 4
Wenn der Präprozessor einen Makronamen expandiert, wird auch diese Definition nach weiteren Makros gescannt.
Zum Beispiel:
Makroexpansion
1
2
3
4
5
#define TABLESIZE BUFSIZE
#define BUFSIZE 1024
TABLESIZE
expandiert zu
BUFSIZE

expandiert zu
1024


TABLESIZE wird zuerst zu BUFSIZE expandiert, danach wird dieses Makro zum Ergebnis 1024 expandiert.
Bemerke, dass BUFSIZE noch nicht definiert war, als TABLESIZE definiert wurde. Das #define für TABLESIZE benutzt genau die Expansion, die du spezifizierst - in diesem Falle BUFSIZE - und prüft nicht weiter, ob es ebenfalls Makronamen beinhaltet. Nur, wenn du TABLESIZE benutzt, wird die Expansion nach weiteren Makronamen gescannt.

Das macht einen Unterschied, wenn sich die Definition von BUFSIZE an einem Punkt der Quelldatei ändert. TABLESIZE wie oben deklariert, wird beim Expandieren immer die Definition von BUFSIZE nutzen, die gerade aktuell ist:
Aktualität in Makroexpansionen
1
2
3
4
5
6
#define BUFSIZE 1020
#define TABLESIZE BUFSIZE
#undef BUFSIZE
#define BUFSIZE 37
Nun wird TABLESIZE (in zwei Durchläufen) zu 37 expandieren.

Wenn eine Makroexpansion ihren eigenen Namen beinhaltet, entweder direkt oder indirekt über andere Makros, wird dieser nicht erneut expandiert, wenn nach weiteren Makros gescannt wird. Das verhindert unendliche Rekursion.
Funktionsähnliche Makros
Es lassen sich auch Makros definieren, die wie ein Funktionsaufruf aussehen. Diese werden funktionsähnliche Makros genannt. Um ein funktionsähnliches Makro zu definieren, benutzt du die selbe #define-Direktive, aber du fügst ein Paar von Klammern direkt hinter den Makronamen.
Zum Beispiel:
einfaches funktionsähnliches Makro
1
2
3
4
#define lang_init() dbp_init() lang_init()
expandiert zu
dbp_init()


Wenn du das Klammernpaar nicht direkt hinter den Makronamen schreibst, wird kein funktionsähnliches Makro definiert, sondern ein objektähnliches, dessen Expansion mit einem Klammernpaar beginnt.
fehlerhafte Makrofunktion
1
2
3
4
#define lang_init () dbp_init() lang_init()
expandiert zu
() dbp_init()()


Die ersten beiden Klammernpaare in dieser Expansion kommen von der Makrodefinition. Das dritte ist das Paar, dass vom Aufruf kommt. Da lang_init ein objektähnliches Makro ist, wird ein solches Paar nicht konsumiert.

Makro-Argumente
Funktionsähnliche Makros können Argumente verlangen, ganz wie echte Funktionen. Um ein Makro zu definieren, dass Argumente nutzt, fügst du Parameter zwischen das Klammernpaar in der Makrodefinition ein. Diese Parameter müssen gültige DBP-Bezeichner sein, mit Kommas und optionalen Leerzeichen getrennt.

Um ein Makro aufzurufen, dass Argumente verlangt, schreibst du das Makro gefolgt von einer Liste von Argumenten in Klammern, durch Kommas getrennt, auf. Die Anzahl der Argumente muss mit der Anzahl der Parameter der Makrodefinition übereinstimmen. Wenn ein Makro expandiert wird, werden alle Parameter in seinem Körper durch die angegebenen Zeichenketten des jeweiligen Argumentes ersetzt. (Du musst nicht alle Parameter des Makrokörpers benutzen.)

Hier ist zum Beispiel ein Makro, dass das Minimum von zwei Werten berechnet:
Minimum
1
2
3
4
5
6
#define min(X, Y)  (((X) < (Y))*((X) - (Y)) + (Y))
  x = min(a, b)
  y = min(1, 2)
  z = min(a + 28, b^2)
Zeile 3 expandiert zu
x = (((a) < (b))*((a) - (b)) + (b))
Zeile 4 expandiert zu
y = (((1) < (2))*((1) - (2)) + (2))
Zeile 5 expandiert zu
z = (((a+28) < (b^2))*((a+28) - (b^2)) + (b^2))

(Hier kann man bereits einige Gefahren von Makroargumenten sehen.)

Führende und nachfolgende Leerzeichen jedes Argumentes werden ignoriert und alle Leerzeichen zwischen den Token eines Argumentes werden auf ein einzelnes Leerzeichen herunterkomprimiert. Klammern eines jeden Argumentes müssen ausbalanciert sein; ein Komma innerhalb dieser Klammerpaare wird das Argument nicht beenden.
Erlaubt ist also:
Kommas in Makroargumenten
1
2
3
4
5
6
7
8
9
10
#define ADD(x,y) (x+y)

dim MyArray(0,1)

MyArray(0,0) = 5
MyArray(0,1) = 6

Print ADD(MyArray(0,0), MyArray(0,1))
Alle Argumente eines Makros sind komplett makroexpandiert, bevor sie in den Makrokörper eingefügt werden. Nach der Ersetzung wird der gesamte Text erneut nach Makros durchsucht, inklusive der Makroargumente. Diese Regel mag merkwürdig erscheinen, doch sie ist sorgfältig designed, damit du nicht darüber nachdenken musst, ob ein Funktionsaufruf in Wirklichkeit ein Makro ist.
Zum Beispiel:
erweiterte Makroargumente
1
2
3
min(min(a,b), c)
wird zuerst expandiert zu
min((((a) < (b))*((a) - (b)) + (b)), c)
und dann zu
((((((a)<(b))*((a)-(b))+(b))) < (c))*(((((a)<(b))*((a)-(b))+(b))) - (c)) + (c))
Du darfst Makroargumente leer lassen; das wird zwar keinen Fehler für den Präprozessor erzeugen, aber viele Makros werden zu ungültigem Code expandieren. Man kann keine Argumente komplett auslassen, wenn ein Makro zwei Argumente erwartet, muss es genau zwei Argumente bekommen.

Makroparameter in Stringliteralen werden nicht durch ihre Argumente ersetzt.
Argumente in Strings
1
2
3
4
#define foo(x) x, "x"
foo(bar)
expandiert zu
bar, "x"
Löschen und Redefinieren von Makros
Wenn ein Makro seinen Sinn verliert, kann es mithilfe der #undef-Direktive gelöscht werden. #undef nimmt ein einziges Argument, den Namen des Makros, welches gelöscht werden soll. Du benutzt dort den reinen Makronamen, sogar wenn das Makro funktionsähnlich ist. Es ist ein Fehler, wenn noch etwas anderes in der Zeile nach dem Makronamen erscheint. #undef hat keinen Effekt, wenn der Name keinem Makro zugeordnet werden kann.
Löschen eines Makros
1
2
3
4
5
6
#define FOO 4
x = FOO    ` expandiert zu x = 4
#undef FOO
x = FOO    ` expandiert zu x = FOO
In dem Moment, wo ein Makro gelöscht wird, kann der Bezeichner durch ein #define neu vergeben werden. Die neue Definition muss nichts mit der alten zu tun haben.

Wenn das Makro schon existiert und neu belegt wird, so wird die neue Definition die alte überschreiben.

Standardmäßig vordefinierte Makros
Alle standardmäßigen Makros starten und enden mit zwei Unterstrichen und heißen aktuell folgendermaßen:
vordefinierte Makros
__LINE__
__FILE__
__TIME__
__DATE__
__TIMEDATE__
__FUNC__
  gibt die Zeilennummer als Integerkonstante zurück
  gibt den Dateinamen als Stringliteral zurück
  gibt die aktuelle Zeit in der Form "22:42:45" als Stringliteral zurück
  gibt das Datum zur Compilezeit als Stringliteral in der Form "08/01/06" zurück
  gibt Zeit und Datum formatiert wie "Tue Aug 01 22:42:45 2006" zurück
  gibt den aktuellen Funktionsnamen als String zurück
Die Makros __LINE__ und __FILE__ eignen sich besonders gut für die Generierung einer Fehlernachricht über eine Inkonsistenz im Programm; die Nachricht kann Datei und Codezeile berichten, wo die Inkonsistenz entdeckt wurde.
Zum Beispiel:
Fehlerreport
1
2
3
Print "Fehler in Zeile", __LINE__, ", Datei ", __FILE__, " gefunden!"
Eine #include-Direktive ändert die Definitionen von __FILE__ und __LINE__ um der aktuellen Datei zu entsprechen. Am Ende dieser Datei, wenn weiter in der übergeordneten Datei gelesen wird, werden die Definitionen wieder zurück gesetzt auf die Werte, die sie hatten bevor die Datei eingefügt wurde (__LINE__ wird noch um 1 erhöht, da es danach eine Zeile nach dem #include weiter geht).

Die aktuelle Zeile und Datei lässt sich mittels der #line-Direktive ändern und wird danach relativ zu dieser Position definiert.
Stringifizierung
Manchmal möchtest du vielleicht ein Makroargument in ein Stringliteral konvertieren. Parameter im Inneren von Stringkonstanten werden bekanntlich nicht ersetzt, aber du kannst den Präprozessoroperator # stattdessen verwenden. Wenn ein Makro(-parameter) mit einem führenden # benutzt wird, ersetzt der Präprozessor es durch seine Definition als Stringliteral. Auch hier wird das Makro erst vollständig expandiert, bevor es ersetzt wird. Diese Operation wird Stringifizierung genannt.

Eine Möglichkeit ein Argument mit umschließenden Text zu erzeugen, ist es ein Makro zu stringifizieren, dass zu vielen Untermakros zerfällt, die dann alle in einen großen String gepackt werden.

Hier ein Beispiel, dass Stringifizierung verwendet:
#-Beispiel
1
2
3
4
5
6
#define WARN_IF(EXPR) _
        if (EXPR) then print "Warnung: ", #EXPR

WARN_IF(x = 0)
expandiert zu
Expansion
1
2
3
if (x = 0) then print "Warnung: ", "x = 0"
Das Argument EXPR wird ein mal in das if-Statement ein gefügt und ein Mal stringifiziert in das Argument für print. Wenn x ein Makro wäre, würde es sowohl im if-Statement, als auch im String expandiert werden.

Alle führenden und nachfolgenden Leerzeichen im zu stringifizierenden Text werden ignoriert. Jede Abfolge von Leerzeichen in der Mitte des Textes wird auf ein einzelnes Leerzeichen herunterkomprimiert. Kommentare werden schon in einem früheren Durchlauf entfernt, d.h. auch diese tauchen nicht in einem stringifizierten Text auf.
Konkatenation
Es ist oft nützlich, zwei Token in eines zu verbinden, wenn Makros expandiert werden. Diese Operation wird Token-Konkatenation genannt. Der ##-Präprozessor-Operator führt Token-Konkatenation durch. Wenn ein Makro expandiert wird, werden die Token links und rechts von jedem ## Operator zu einem einzelnen Token kombiniert, welches die ## und die beiden ehemaligen Token in der Makroexpansion ersetzt. Normalerweise werden beide Token Bezeichner sein, oder eines ist ein Bezeichner und das andere eine Nummer. Wenn sie kombiniert werden, wird ein längerer Bezeichner daraus, doch das ist nicht der einzige, gültige Fall. Es ist auch möglich, zwei Nummern zu konkatenieren (oder eine Nummer und ein Name) in eine Zahl. Beide Token, die durch ## verbunden werden, kommen vom Makrokörper, aber man könnte sie ja auch von Anfang an als ein Token formulieren. Token-Konkatenation wird besonders sinnvoll, wenn ein oder beide Argumente von einem Makroargument kommen. Wenn ein beliebiges Token neben einem ## ein Parameter Name ist, wird zuerst das Argument dieses Parameters eingesetzt, bevor ## ausgeführt wird. Wie bei der Stringifikation wird das tatsächliche Argument zunächst komplett makroexpandiert. Wenn das Argument leer ist, hat ## keinen Effekt.

Nehmen wir an, es soll ein DBP-Programm geschrieben werden, dass so eine Art von statischen Variablen für Funktionen zulässt, also Variablen, die nach einem Funktionsaufruf ihre Gültigkeit behalten.
##-Beispiel
1
2
3
4
5
6
7
8
9
10
#define STATIC(Name) static_ ## __FUNC__ ## _ ## Name

function foo(a$,b$,c$)
  global STATIC(pers) as string
  
  print STATIC(pers)
  STATIC(pers) = a$
endfunction
Man würde die Variable static_foo_pers sicher nicht in einem normalen Programm global deklarieren, was eine Überschneidung unwahrscheinlich macht. Es existiert auch keine andere Funktion mit dem Namen "foo", was bedeutet, dass ein anderes STATIC den Namen nicht überschreiben wird. Man kann diese Idee noch weiter führen, um Variablen zu deklarieren, die nur in einer Datei oder einem Modul global sind.
Bedingungen
Eine Bedingung ist eine Direktive, die den Präprozessor anweist zu entscheiden, ob oder ob nicht ein Stück Code in die finale Datei eingefügt werden soll. Präprozessor-Bedingungen können arithmetische Ausdrücke testen, oder ob ein Name als Makro definiert ist.

Eine Bedingung im DBPre erinnert an ein Statement in DBP, aber es ist wichtig, den Unterschied zwischen ihnen zu verstehen. Die Bedingung eines if-Statements wird während der Laufzeit des Programms getestet. Ihr Zweck ist es, deinem Programm zu erlauben, sich unterschiedlich von Durchlauf zu Durchlauf zu verhalten, abhängig von den Daten, auf denen es gerade operiert. Die Bedingung einer Präprozessordirektive wird getestet, wenn dein Programm compiliert wird. Ihr Zweck ist es, verschiedenen Code zu compilieren, abhängig von der Situation des Programmes bei der Compilierung.

Es gibt zwei generelle Gründe, eine DBPre-Bedingung zu verwenden.

1. Du möchtest die selbe Quelldatei benutzen, um zwei verschiedene Versionen eines Programmes zu compilieren. Eine Version könnte zum Beispiel regelmäßige, zeitaufwändige Konsistenzchecks ihrer Daten durchführen, oder zu Debugzwecken deren Werte ausgeben, die andere nicht.
2. Eine Bedingung, die immer falsch ist, ist eine Möglichkeit Code aus einem Programm auszuschließen, ihn aber als eine Art von Kommentar für zukünftige Referenz zu behalten.

Einfache Programme brauchen für gewöhnlich keine Präprozessorbedingungen.

DBPre-Bedingungen - Syntax
Eine Bedingung für den DBPre-Präprozessor beginnt immer mit einer Bedingungsdirektive: #if, #ifdef oder #ifndef.
#ifdef
Die einfachste Form einer Bedingung ist
#ifdef-Beispiel
1
2
3
4
5
6
7
#ifdef MACRO Quellcode #endif
Der Quellcode wird in die Ausgabedatei für den Compiler eingefügt, wenn und nur wenn MACRO definiert ist.

Der Quellcode innerhalb einer Bedingung kann weitere Präprozessordirektiven enthalten. Sie werden nur dann ausgeführt, wenn die Bedingung erfüllt ist. Du darfst Bedingungen ineinander schachteln, aber sie müssen korrekt geschachtelt sein. Mit anderen Worten, ein #endif bezieht sich immer auf das vorige #ifdef (oder #ifndef oder #if). Es ist weiterhin verboten eine Bedingung in einer Datei zu öffnen und in einer anderen zu beenden.

Auch wenn eine Bedingung nicht erfüllt ist, wird der beinhaltete Quellcode die initialen Transformationen durchlaufen. Deshalb ist es wichtig, dass die Struktur der für den Präprozessor interessanten Befehle trotzdem weiterhin korrekt ist.

Manchmal möchtest du vielleicht Code ausführen, wenn ein Makro nicht definiert ist. Das kannst du mithilfe von #ifndef anstelle von #ifdef tun. Ein gewöhnlicher Nutzen davon ist, Quellcodes nur ein Mal einzufügen.
#if
Die #if-Direktive erlaubt dir, den Wert eines arithmetischen Ausdrucks zu testen, nicht nur die reine Existenz eines Makros.
Seine Syntax ist:
#if-Beispiel
1
2
3
4
5
6
7
#if Ausdruck Quellcode #endif
"Ausdruck" ist ein DBP-Ausdruck vom Typ Float. Er darf Zahlenkonstanten, Zeichenkonstanten, arithmetische Operationen, Klammern, alle Arten von konstanten Makros und den "defined"-Operator enthalten.

Arithmetische Operationen sind im Moment Addition, Subtraktion, Multiplikation, Division, Exponentiation, logisches UND, logisches ODER, logisches NICHT und Vergleiche.

Der Präprozessor wird den Wert des Ausdruckes ausrechnen und zwar mit doppelter Präzision (64 Bit Double Float). Wenn am Ende nicht 0 heraus kommt, gilt die Bedingung als erfüllt und der Quellcode wird eingefügt; sonst nicht.
defined
Der spezielle Operator defined wird benutzt, um in #if- und #elif-Ausdrücken zu testen, ob ein Name als Makro definiert ist. Weiter lässt sich defined auch in konstanten Ausdrücken berechnen (die Ausdrücke mit den eckigen Klammern, siehe weiter unten). Der Ausdruck defined(name) ist genau dann 1, wenn name an der aktuellen Stelle als Makro definiert ist, sonst 0. Das bedeutet, dass #if defined(MACRO) äquivalent zu #ifdef MACRO ist.

defined ist sinnvoll, wenn mehrere Makros auf einmal auf ihre Existenz geprüft werden sollen.
Zum Beispiel:
defined-Beispiel
1
2
3
4
5
6
7
#if defined(DEBUG) or defined(USERINTERVENTION)

Quellcode

#endif
wird genau dann wahr, wenn entweder DEBUG oder USERINTERVENTION als Makros definiert sind.

Wenn der defined-Operator als Resultat einer Makroexpansion auftaucht, wird er von DBPre ganz normal ausgeführt.
#else
Die #else-Direktive kann zu einer Bedingung hinzugefügt werden, um einen alternativen Quelltext einzufügen, wenn die Bedingung scheitert.
Das kann folgendermaßen aussehen:
#else-Beispiel
1
2
3
4
5
6
7
8
9
10
11
#if Ausdruck Quellcode wenn wahr #else Quellcode sonst #endif
Wenn der Ausdruck zu ungleich 0 evaluiert, wird der obige Quellcode eingefügt und der untere überlesen. Wenn der Ausdruck 0 ist, dann passiert das anders herum.

Du kannst #else auch mit #ifdef und #ifndef benutzen.
#elif
Ein oft benutzer Fall von Bedingungen, ist der Test auf mehr als zwei mögliche Alternativen.
Zum Beispiel könntest du folgendes haben:
mehrere Alternativen ohne #elif
1
2
3
4
5
6
7
8
9
10
11
#if X = 1 ... #else #if X = 2 ... #else ... #endif #endif
Eine andere Bedingungsdirektive, #elif, erlaubt das folgendermaßen abgekürzt zu werden:
mehrere Alternativen mit #elif
1
2
3
4
5
6
7
8
9
#if X = 1 ... #elif X = 2 ... #else ... #endif
#elif steht für "else if". Wie #else, kommt es in die Mitte einer Bedingungsgruppe und teilt sie damit; es braucht kein passendes #endif für sich selbst. Wie #if, erlaubt die #elif-Direktive den Test eines Ausdruckes, wird aber nur ausgeführt, wenn die oberen Bedingungen alle fehlgeschlagen sind.
Gelöschter Code
Wenn du einen Teil des Programmes ersetzt oder löschst, aber trotzdem den alten Code für zukünftige Referenzen behalten willst, kannst du ihn oft nicht einfach auskommentieren. Blockkommentare lassen sich nicht verschachteln, also wird der erste Kommentar innerhalb des alten Codes den neuen auskommentieren. Das wahrscheinliche Resultat ist eine Flut an Syntaxfehlern.

Eine Möglichkeit dieses Problem zu verhindern, ist stattdessen die Benutzung von immer-falschen Bedingungen. Zum Beispiel, wenn du #if 0 vor den gelöschten Code schreibst und #endif dahinter. Das funktioniert sogar, wenn der ausgeschaltete Code DBPre-Bedingungsblöcke beinhaltet.
6. Durchlauf
Berechnen von konstanten Ausdrücken.
Im 6. und letzten Durchlauf werden nun noch alle konstanten Ausdrücke berechnet. Konstante Ausdrücke sind all die Ausdrücke, die zwischen eckigen Klammern stehen. Da inzwischen alle Makros und Inlinefunktionen vollständig expandiert sind, dürfen zwischen zwei eckigen Klammern auch wirklich nur noch konstante Ausdrücke stehen, sonst gibt es einen Fehler.
Die konstanten Ausdrücke werden mit doppelter Genauigkeit ausgerechnet, also genauer als DBP das normalerweise tun würde.
Hier ein Beispiel für einen simplen konstanten Ausdruck:
konstanter Ausdruck
1
2
3
print [1+2+3]
erzeugt in der Ausgabedatei
print 6

Konstante Ausdrücke verarbeiten all das, was bei der #if-Direktive auch als Ausdruck gültig ist. Es ist erlaubt, mehrere eckige Klammern ineinander zu schachteln, damit folgendes möglich ist:
verschachtelter Ausdruck
1
2
3
4
5
6
#define PI 3.1415
#define TORAD [PI/180]

print [70*TORAD]
Hier wird auch gleich einer der Vorteile offensichtlich. Normalerweise würde DBP den Ausdruck 3.1415/180.0 stets zur Laufzeit berechnen - und zwar überall, wo er vorkommt. Deshalb könnte man den Ausdruck auch erstmal mit dem Taschenrechner berechnen.
In diesem Fall hat man zwei Nachteile:

Man macht sich mehr Arbeit, als notwenig ist
und
der Code ist schlechter zu lesen, da man der Konstanten 0.01765329252 nicht direkt ansieht, welchen Ausdruck sie darstellt, der Rechnung PI/180 jedoch wesentlich leichter.
Dadurch, dass jetzt alle Makros und Inlinefunktionen vollständig expandiert sind, ist die Nutzung auch komplexer konstanter Ausdrücke mit Inlinefunktionen (funktionsähnliche Makros) kein Problem.