Haben Sie sich auch schon gelegentlich über die Fehlermeldungen von Delphi gewundert? Da verkündet uns dieses Programm zum Beispiel:
";" erwartet
und stellt dann alle weiteren Aktivitäten ein, bis wir den Fehler behoben haben. Woher "weiß" Delphi, was an dieser Stelle "zu erwarten" ist? Wie kann ein Programm überhaupt eine Erwartungshaltung aufbauen? Gehört dazu nicht viel mehr Bewusstsein und Überblick, als wir üblicherweise einer Maschine (oder einem Programm) zuschreiben wollen?
Erstaunlicherweise hat Delphi mit der obigen Fehlermeldung meistens recht. Zwar steht der Cursor im Quelltext in der Regel nicht direkt an der Stelle, an der wir das fehlende ";" einfügen werden, sondern in der folgenden Zeile, aber die Fehlermeldung ist durchaus korrekt: es fehlt wirklich ein Semikolon! Offenbar "versteht" Delphi den Programmtext in einer bestimmten Art und Weise tatsächlich; und anscheinend kann der Compiler auch eine "Erwartungshaltung" aufbauen, aus der heraus er die realen Gegebenheiten unseres Programmtextes "in intelligenter Weise" zu bewerten imstande ist. Der Compiler geht also durch den Programmtext durch und stoppt an genau der Stelle, an der er ihn nicht mehr versteht. An dieser Stelle wird der Text also sinnlos - zum Beispiel, weil das jetzt ein ";" kommen müsste, aber ein anderes Zeichen kommt!
Wie kann ein Programm den Sinn eines Textes erfassen? Alles in uns sträubt sich gegen die Vorstellung, dass eine Maschine (oder ein Programm - ist das ein wesentlicher Unterschied?) imstande sein sollte, den Sinn eines Textes zu erfassen, mithin also "mit Verstand" zu lesen. Das sollte doch uns Menschen vorbehalten bleiben, oder nicht? Trotzdem: Delphi versteht nachgewiesenermaßen in vielen Fällen mehr von Object Pascal als wir. Man ist gut beraten, beim Programmieren die Fehlermeldungen des Compilers sehr aufmerksam zu studieren. Delphi ist im Grunde sogar der beste Experte für Object Pascal, der denkbar ist!
Aber damit ist auch die Grenze des Erkenntnishorizonts von Delphi markiert: während wir neben Pascal-Quelltexten auch "Die Buddenbrocks" von Thomas Mann lesen können, wäre Delphi damit völlig überfordert. Es würde schon in der ersten Zeile eine Fehlermeldung ausgeben, zum Beispiel:
'Unit' erwartet, aber Bezeichner ... gefunden
oder irgend eine andere Meldung, je nach dem, an welcher Stelle des Quelltextes wir den Romantext einfügen würden. In jedem Fall offenbart uns die Fehlermeldung durch ihre offensichtliche Unsinnigkeit, dass Delphi diesen Text "nicht versteht" (wobei man die Anführungszeichen eigentlich auch weglassen kann).
Was aber ist der wesentliche Unterschied zwischen einem Pascal-Quelltext und den "Buddenbrocks"? Es ist die Struktur des Textes. Pascal-Quelltexte müssen nach ganz bestimmten, sehr eng gefassten Regeln aufgebaut sein, während es in der "normalen Sprache" (und erst recht in der von Thomas Mann!) eine viel größere Gestaltungsfreiheit gibt. Somit reicht der Verständnishorizont des Delphi-Compilers also nur bis an den Rand der Sprache Object Pascal; alles was darüber hinaus geht, ist für den Compiler im wahrsten Sinne des Wortes "sinnlos".
Die Menge der Regeln, denen eine Sprache gehorcht, nennt man die Syntax dieser Sprache. Für die natürliche Sprache gehören dazu die üblichen Regeln über Rechtschreibung, Satzzeichen und Grammatik. Ein typisches Merkmal lebender Sprachen ist aber, dass die Regeln oft nicht global gelten. Wer eine Fremdsprache lernt, weiß um die berüchtigten Ausnahmen, wie zum Beispiel die "unregelmäßigen(!) Verben". Solche Regelverstöße verkomplizieren das Erlernen der Sprache und erschweren die umfassende Darstellung der möglichen Sprachstrukturen.
Pascal ist demgegenüber eine künstliche Sprache, deren Syntax nur Regeln ohne Ausnahmen kennt. Aufgrund dieser Regeln kann von jedem beliebigen Text entschieden werden, ob er in "korrektem Pascal" geschrieben ist oder nicht. Eine solche Sprache nennt man eine "formale Sprache".
Wenn man die Regeln einer formalen Sprache kennt, dann kann man in der Tat ohne großen Aufwand eine Erwartungshaltung aufbauen. Als Beispiel betrachten wir in Pascal eine WHILE-Schleife. Umgangssprachlich formuliert hat sie stets die folgende Struktur:
Als erstes kommt das Schlüsselwort "WHILE".
Es folgt eine (Durchführungs-) Bedingung.
Dann kommt das Schlüsselwort "DO".
Es folgt eine Anweisung.
Zum Schluß kommt das Zeichen ";"
Diese Struktur ist bei allen WHILE-Schleifen in allen Pascal-Programmen gleich. Was sich ändert, sind die Inhalte der jeweiligen Bedingung und der Anweisung im Schleifenrumpf. Die Abfolge der 5 oben aufgeführten logischen Sprachelemente bleibt aber stets dieselbe. Wenn Sie es übers Herz bringen, mal für kurze Zeit in Gedanken in die Rolle eines Delphi-Compilers zu schlüpfen, dann können Sie nachvollziehen, wie dieser eine WHILE-Schleife wahrnimmt:
Nehmen wir mal an, Sie hätten den ersten Punkt erfolgreich abgearbeitet und das Schlüsselwort "WHILE" korrekt erkannt. (Es stand zwar "While" da, und nicht "WHILE", aber so tolerant sollten Sie schon konstruiert sein als ordentlicher Pascal-Compiler!) Dann wissen Sie nun ganz genau, dass jetzt eine Bedingung folgen muss. Also können Sie sich für den Fall, dass gleich ein Fehler passiert, schon mal die Fehlermeldung
Bedingung erwartet
vormerken, ohne sie allerdings gleich auszugeben.
Es ist durchaus keine triviale Aufgabe, eine solche Bedingung einzulesen und zu verstehen. Aber wenn Sie das geschafft haben, dann wissen Sie genau, dass nun das Schlüsselwort "DO" folgen muss. Wenn das nächste Zeichen ein "L" ist (weil der vor dem Bildschirm sitzende Spaßvogel "Lirumlarum Löffelstiel" in die Tastatur gehackt hat), dann können Sie nun überzeugend kontern mit
"DO" erwartet.
Nehmen wir mal an, dass auch das "DO" richtig erkannt wurde. Das nun folgende Einlesen und Interpretieren des Schleifenrumpfes ist ziemlich aufwändig: in Pascal gibt es nämlich die Syntaxregel, dass jede einzelne Anweisung auch durch einen Block ersetzt werden kann, also eine Serie von mehreren Anweisungen, die von den Schlüsselwörtern "BEGIN" und "END" eingerahmt ist. Im Vertrauen auf Ihre Leistungsfähigkeit nehmen wir mal an, dass Sie auch diese Aufgabe bravourös meistern und den Schleifenrumpf korrekt einlesen werden.
Wenn nun allerdings ein Zeilenvorschub (LF-CR oder #13#10) folgt, dann -- nein, Sie schlagen noch nicht Alarm, denn Zeilenvorschübe ignoriert der Compiler grundsätzlich! Erst wenn Sie schließlich ein "E" einlesen, weil in der nächsten Zeile steht:
Edit1.Text := 'Compiler machen keine Vehler!';
dann ist es Zeit für die klassische Fehlermeldung:
";" erwartet.
Solange der Kompilierungsvorgang erfolgreich verläuft, "weiß" der Compiler also, was er zu "erwarten" hat, weil dies in den ihm "bekannten" Syntax-Regeln der Sprache Pascal festgelegt ist! In diesem Sinne kann Delphi einen korrekten Pascal-Quelltext tatsächlich verstehen - und diesmal wirklich und absichtlich ohne Anführungszeichen! Der Delphi-Compiler beweist uns nämlich, dass er den Quelltext wirklich verstanden hat, indem er das zu diesem Quelltext gehörende Maschinenprogramm auf die Festplatte schreibt! "Verstehen" heißt: "sinnvoll reagieren können". Und genau das tut der Compiler, wenn er aus Ihrem Quelltext eine Serie von Prozessorbefehlen erzeugt!
Für die Skeptiker, denen der Compiler in dieser Beschreibung zu anthropomorphe Züge trägt, sei noch folgendes klargestellt: es wird nicht etwa behauptet, dass der Compiler die im Quelltext niedergelegten Algorithmen versteht, sondern nur, dass er den ihm vorgelegten Quelltext versteht. Er könnte diesen natürlich nicht selbst schreiben! In kluger Selbstbeschränkung haben die Konstrukteure des Compilers ja sogar darauf verzichtet, ihn das erwartete, aber fehlende Semikolon in Eigenregie hinzufügen zu lassen - und das ist gut so! Warum wohl?
Zur Darstellung der Syntaxregeln einer formalen Sprache haben sich die Mathematiker verschiedene Verfahren ausgedacht. Besonders anschaulich und leicht verständlich sind die Syntax-Diagramme. Wie solche Diagramme beschaffen sind, wollen wir anhand einiger Beispiele klären. Dabei wählen wir eine formale Sprache, die wir schon gut kennen, nämlich Pascal. Sie wissen, dass es in einer Programmiersprache nur wenige sprachlogische Grundstrukturen gibt (nämlich Sequenz, Entscheidung und Wiederholung), und wir haben diese in einem früheren Kapitel schon mit Hilfe von Struktogrammen dargestellt. Hier wollen wir sie nun mit Hilfe der Syntax-Diagramme nochmals veranschaulichen.
Nehmen wir als erstes Beispiel die WHILE-Schleife, deren Pascal-Syntax wir im vorigen Abschnitt schon verbal beschrieben haben. Hier nochmals die Beschreibung, aber nur im Telegrammstil:
"WHILE"
Bedingung
"DO"
Anweisung
";"
In dieser Beschreibung gibt es zwei verschiedene Sorten von "Symbolen":
in Anführungszeichen stehende Zeichenketten
normale Zeichenketten
Von den ersten wissen wir, dass das, was da in den Anführungszeichen steht, genau so, Zeichen für Zeichen, im Programmtext auftauchen muss. Solche Zeichenketten heißen Terminalsymbole. Anstelle des Wortes "Bedingung" oder "Anweisung" wird jedoch in kaum zwei WHILE-Schleifen das gleiche stehen - schließlich haben die Schleifen üblicherweise unterschiedliche Aufgaben zu erledigen! Symbole, die für jeweils spezifische bzw. noch näher zu spezifizierende Inhalte stehen, bezeichnen wir als Nicht-Terminal-Symbole. Wenn wir Terminalsymbole mit einem runden (bzw. abgerundeten) Rahmen umgeben, Nicht-Terminal-Symbole hingegen mit einem rechteckigen, dann kann man die Struktur der WHILE-Schleife auf die folgende naheliegende Art veranschaulichen:
Eine solche Darstellung nennt man ein Syntax-Diagramm: es wird stets längs der Verbindungslinien durchlaufen, wobei wir zu Beginn von links kommen und das Diagramm schließlich nach rechts verlassen. (Bei komplizierteren Syntax-Diagrammen kann die Reise unterwegs durchaus auch mal von rechts nach links gehen - soweit es die eingezeichneten Verbindungslinien eben erlauben.) Alle Verbindungslinien sind als Einbahnstraßen gemeint. Die Richtung, in der sie "offen" sind, ergibt sich beim Durchlaufen aus der Regel, dass scharfe Knicke der Bahn zu vermeiden sind. Gelegentlich werden die Verbindungen zur Verdeutlichung mit Orientierungspfeilen versehen; wir verzichten jedoch hier auf diese kosmetischen Ergänzungen und vertrauen auf das geometrische Einfühlungsvermögen des Lesers. Beim vorliegenden Syntax-Diagramm der WHILE-Schleife ist dies auch kein Problem, weil es ja ohnehin nur einen linearen Weg gibt, auf dem die vorgeschriebenen Elemente von links nach rechts abgearbeitet werden müssen.
Und wo ist denn hier eigentlich die Schleifen-Struktur geblieben? Nun, es ist nötig, dass wir hier zwei Dinge streng auseinanderhalten:
die Interpretation des Programmtextes zur Compilier-Zeit:
Wie oben beschrieben geht der Compiler beim Übersetzen der WHILE-Schleife genau einmal durch den Quelltext hindurch. Für den Übersetzungsprozess ist also tatsächlich das lineare Syntax-Diagramm eine korrekte Repräsentation.
die Abarbeitung der Programmbefehle zur Lauf-Zeit:
Erst zur Laufzeit kommt es gegebenenfalls zu einer eigentlichen "Schleife", weil dann der Schleifenkörper eventuell mehrfach durchlaufen wird.
Syntax-Diagramme repräsentieren also die Struktur der Sprache, in der das Programm geschrieben ist. Sie geben jedoch keine Auskunft über die zeitliche Reihenfolge, in der die einzelnen Befehle zur Laufzeit ausgeführt werden.
Als zweites Beispiel wollen wir uns die Entscheidung in Pascal durch ein Syntax-Diagramm veranschaulichen. Sie beginnt stets mit dem Schlüsselwort "IF", dem eine Bedingung folgt. Danach kommt stets das Schlüsselwort "THEN", gefolgt von einer Anweisung. Möglicherweise ist damit die Entscheidung schon beendet und es folgt der abschließende Strichpunkt. Es kann aber auch das Schlüsselwort "ELSE" kommen und dann eine weitere "Anweisung". Insgesamt erhält man also das folgende Syntax-Diagramm:
Nach der Anweisung des THEN-Teils haben wir nun eine Verzweigung in unserem Diagramm: entweder wir gehen geradeaus zum ";" und verzichten auf einen ELSE-Teil oder wir gehen zum Terminalsymbol "ELSE", dem dann aber notwendigerweise eine Anweisung und der abschließende Strichpunkt folgen muss.
Ein Blick auf die Verhältnisse zur Kompilierzeit und zur Laufzeit zeigt auch hier, dass es sich dabei um zwei durchaus unterschiedliche Aspekte handelt, die deutlich unterschieden werden müssen:
Wenn eine Entscheidung einen ELSE-Teil hat, wird das Syntaxdiagramm zur Kompilierzeit vollständig durchlaufen; zur Laufzeit hingegen wird von den beiden Anweisungsblöcken stets nur einer wirklich abgearbeitet werden.
Fehlt der ELSE-Teil, dann wird der THEN-Teil zur Compilier-Zeit auf jeden Fall komplett "durchgeackert"; ob er aber auch zur Laufzeit ausgeführt wird, darüber wird erst aufgrund der dann aktuellen "Datenlage" entschieden!
Syntax-Diagramme bieten eine sehr elegante Möglichkeit, sich schnell einen Überblick über die Strukturen einer Programmiersprache zu verschaffen. Sie helfen darüber hinaus auch bei der Entscheidung, ob ein gegebener Programmtext "syntaktisch korrekt" ist, d.h. den Sprachregeln entspricht:
In einer formalen Sprache ist ein Text genau dann syntaktisch korrekt, wenn er auf einem zulässigen Weg durch die Syntax-Diagramme dieser Sprache erzeugt werden kann.
So ist zum Beispiel eine IF-Konstruktion ohne ELSE-Teil korrekt, wohingegen ein ";" vor dem ELSE-Teil einen Fehler darstellt: es gibt keine Möglichkeit, das Syntax-Diagramm der Entscheidung so zu durchlaufen, dass nach der Anweisung des THEN-Teils ein ";" kommt und danach noch ein ELSE-Teil folgt!
Aufgaben:
Die Repeat-Schleife
Entwerfen Sie ein Syntax-Diagramm für die REPEAT-Schleife. Sie können zunächst vereinfachend annehmen, dass der Schleifenrumpf wie bei der WHILE-Schleife nur aus genau einer Anweisung besteht. Wenn Sie diese vereinfachte Version erfolgreich erledigt haben, sollten Sie das Diagramm aber erweitern und eine Sequenz von Anweisungen zwischen REPEAT und UNTIL zulassen.
Die folgenden Regeln beschreiben, wie die Wörter der LAL-Sprache aufgebaut sind:
Jedes LAL-Wort beginnt mit einem 'L'.
Wenn nach einem 'L' noch etwas kommt, dann ist es entweder ein LAL-Wort oder ein 'A'.
Nach jedem 'A' kommt ein 'L'.
Zeichnen Sie ein Syntax-Diagramm (oder Syntax-Diagramme?) für die LAL-Wörter.
Prüfen Sie dann, ob 'LAL', 'LALAL', 'LALLAL', 'LLALLAL', 'LLLALAL' und 'LALALA' gültige Wörter der LAL-Sprache sind.