Wie wird aus einem BASIC-Programm Maschinensprache?
Zur Zeit der Erfindung von BASIC, Mitte der 1960er Jahre, waren Computer groß genug, um den Nutzern ausreichende Ressourcen für Anwendungen, Programmierung und Number Crunching zur Verfügung zu stellen. Mainframe-Systeme von IBM, Univac, General Electric und anderen sowie die neuen Minicomputer von DEC, Data General oder Hewlett-Packard brachten ausreichen RAM-Speicher und Massenspeicher in Form von Festplatten für diese Zwecke mit. Der General Electric GE-265 (eine Verbindung zweier kleinerer GE-Computer), auf dem am Dartmouth-College ab 1964 BASIC lief, hatte beispielsweise einen bis zu 16 Kilo-Worte großen Magnetkernspeicher (ein Datenwort ist in diesem Fall 20 Bit groß) und verfügte über ein Festplatten-, Lochstreifen- und Magnetband-Massenspeicher. Eine solch üppige Ausstattung ließ den Betrieb eines Timesharing-Betriebssystems und eines BASIC-Compilers für bis zu 300 Nutzer gleichzeitig zu.
Damit ein BASIC-Programm läuft, muss es allerdings zuerst in Maschinensprache übersetzt werden – dazu gab es zur Zeit des Dartmouth-BASIC nur die Option es zu kompilieren, was auf derartig großen Maschinen weder ein Zeit- noch ein Speicherproblem darstellte. Als BASIC in den folgenden Jahrzehnten auf immer kleineren Maschinen zum Laufen gebracht werden sollte, mussten dafür alledings alternative Möglichkeiten gefunden werden.
Grundsätzlich gilt: Digitalcomputer können nur Programme in der Maschinensprache ihres Mikroprozessors ausführen. Diese Maschinensprache, oder ihre erste Abstrakten, die 1947 eingeführte Assembler-Sprache, ist jedoch vergleichsweise umständlich zu programmieren und eignet sich vor allem kaum für problemnahe Lösungswege – stets muss der Programmierer sich fragen, wie die Maschine die Aufgabe bewältigen würde und sie dann genau so umsetzen. Aus diesem Grund wurden Mitte der 1950er Jahre erste höhere Programmiersprachen entwickelt, die für spezielle Anwendungen, wie den Einsatz für wissenschaftliche Zwecke oder in der Wirtschaft, angepasst waren. BASIC besaß als Lehrsprache eine solche Spezialausrichtung, was Art und Umfang seines Befehlssatzes sowie die Syntax seinerProgrammierung bedingte. Und wie die vorherigen Sprachen (Fortran, Cobol, Algol u.a.) war BASIC eine Compilersprache.
Compiler
Compiler sind Übersetzer, die die Sourcecodes in der Hochsprache vor der Ausführung vollständig in Programme in Maschinensprache transformieren. Hierfür muss das fertige Programm als Textdatei vorliegen, die vom Compiler eingelesen wird und dann in mehreren Schritten verarbeitet und übersetzt wird: Zuerst wird der Sourcecode auf seine lexikalische Richtigkeit überprüft: Sind alle Befehlswörter korrekt geschrieben und alle Parameter korrekt angegeben? Falls hierbei Fehler gefunden werden, wird der Übersetzungsprozess mit einer Error-Ausgabe abgebrochen. Ist der Code korrekt, wird er im zweiten Durchlauf auf größere syntaktische Strukturen überprüft – etwa FOR-NEXT-Schleifen, GOTO/GOSUB-Sprünge usw. Hierbei werden Tabellen mit Sprungadressen und Variablenwerten angelegt, die im dritten Schritt genutzt werden. Dann nämlich wird der Code in Maschinensprache übersetzt, eventuell eingebundene Library-Routinen verlinkt und eine ausführbare Datei (Objektcode) gespeichert, die der User schließlich starten kann.
Das Dartmouth-BASIC hat bis zur 7. Version (1979) einen Compiler mit nur einem Durchlauf genutzt (einzig für das Anlegen einer Tabelle mit Sprungzielen für GOTO/GOSUB-Anweisungen kam noch ein Durchlauf hinzu), weil die Sprache sehr einfach aufgebaut war und wenig Befehle besaß. Für die Nutzer hat sich das Dartmouth-BASIC allerdings nicht wie ein üblicher Compiler verhalten: Weder musste zuerst der BASIC-Sourcecode manuell gespeichert noch später der ausführbare Objektcode geladen werden; dies übernahm das Betriebssystem automatisch nach Eingabe des RUN-Befehls. So entstand für die Nutzer der Eindruck von Interaktivität, die sich mit späteren Interpreter-BASIC-Dialekten vergleichen ließ und dies bei der vollen Ausführungsgeschwindigkeit eines Maschinenspracheprogramms. Erst nach der Blütezeit von BASIC wurden für diese Sprache wieder vermehrt Compiler genutzt – weil BASIC bei Plattformen, wie dem Atari ST, dem Commodore Amiga oder in PCs nicht mehr im ROM eingebaut war.
B-Code
Der Nachfolger des Dartmouth-BASIC, das 1984 erschienene True-BASIC, nutzte eine besondere Form von Compiler, die eigentlich ein Zwischending aus Vorabübersetzung und Echtzeitausführung von BASIC-Programmen darstellte: Der BASIC-Sourcecode wird hierbei zunächst in eine Zwischensprache übersetzt, den so genannten „Byte-Code“. Diese Zwischensprache wird dann von einer virtuellen Maschine in die Maschinensprache des Mikroprozessors übersetzt. Ein wichtiger Vorteil hiervon ist die Plattform-Unabhängigkeit des BASIC-Dialektes: Für jeden Computer wird zunächst derselbe Bytecode erzeugt und erst der Bytecode-Interpreter ist auf die jeweilige Hardware angepasst. Diese Form der Zwischenübersetzung ist aus anderen Programmiersprachen wie Pascal (P-Code) oder Lisp bekannt und wird heute vor allem für Java genutzt, um plattformunabhängige Softwareentwicklung zu ermöglichen.
Allerdings waren es nicht die BASIC-Erfinder Thomas Kurtz und John Kemeny, die als erste B-Code eingesetzt haben. 1970 führt die Firma DEC das Prinzip für ihren Dialekt BASIC-PLUS ein. Ein Grund hierfür ist, dass bei der schrittweisen Übersetzung von Sourcecode in B-Code (der bei DEC PPCode hieß) sofort eine Richtigkeitsprüfung durchgeführt und Fehler ausgegeben werden können – und nicht erst am Ende des Kompilierungsprozesses. Auch war es mit dieser Lösung möglich, laufende BASIC-Programme zu unterbrechen und sich sofort den Programmcode anzeigen zu lassen, weil der B-Code/PPCode zur Laufzeit ausgeführt wurde. Diese Interaktivität wurde allerdings durch Geschwindigkeitseinbußen erkauft – immerhin benötigte die Zwischenübersetzung zusätzliche Zeit.
Das B-Code-Prinzip findet sich dann später auch bei Heimcomputer-BASIC-Dialekten, wie den ab 1976 erschienen Tiny-BASIC-Varianten. Hier wurde die „Tiny BASIC Immediate Language“, wie der B-Code in diesem Fall hieß, vor allem zur Vereinfachung für Hobby-Sprachentwickler eingesetzt, die ihr Tiny-BASIC unabhängig damit von der Maschine entwickel konnten. Zusätzlich ermöglichten diese Varianten sehr kleine BASIC-Interpreter; Tiny-BASICs waren oft nur 2-3 Kilobyte groß, was den geringen RAM-Speichergroßen der frühen Heimcomputer sehr entgegen kam. Ein etwas aus der Zeit gefallener Einsatz von B-Code findet sich beim TI-99/4. Hier wird das BASIC-Programm zunächst in die Zwischensprache „Graphics Programming Language“ übersetzt, die so heißt, weil allein der Grafikchip des Computers auf das 16 Kilobyte große RAM zugreifen kann. Eine Notlösung, der ohnehin sehr skurril gestalteten Architektur des TI-99/4, die die berüchtigte Langsamkeit des TI-BASIC zur Folge hatte.
Interpreter
Heimcomputer besaßen zumeist ein im ROM implementiertes BASIC, das als Interpreter realisiert war. Interpreter benötigen weder eine gespeicherte Datei mit dem Sourcecode, noch erzeugen sie eine ausführbare Datei. Alle Übersetzungsprozesse finden im RAM des Computer statt. Dies hat den Nachteil, dass interpretierte BASIC-Programme im Vergleich zu kompilierten nur sehr langsam ausgeführt werden. Denn bei jedem Programmstart findet die Übersetzung Zeile für Zeile aufs Neue statt. Im Gegenzug dazu benötigt die Programmiersprache aber keine schnellen externen Massenspeicher, die für Heimcomputer zusätzlich hätten angeschafft werden müssen, und ist vollständig interaktiv.
BASIC-Befehle werden in einem Editor eingegeben, der zugleich die Betriebssystem-Eingabeshell ist, die ebenfalls von BASIC aus gesteuert wird (RUN, LOAD, SAVE, BYE usw.). Nach dem Drücken von RETURN übersetzt der Interpreter den Befehl entweder sofort (wenn er im Direktmodus eingegeben wurde) oder legt ihn im Speicher ab, falls am Zeilenanfang eine Zeilennummer steht. Das im zweiten Fall gespeicherte Programm kann dann mit RUN aufgerufen werden und wird Zeile für Zeile auf Korrektheit überprüft, auf eventuell vorhandene Informationen, die zwischengespeichert werden müssen (Variablenwerte, Sprungziele), gescannt und dann durch eine dem Befehl zugehörigen Maschinenspracheroutine im Speicher ausgeführt.
Solche BASIC-Interpreter wurden zuerst von Hewlett-Packard ab 1968 für ihre HP-2000-Computerreihe entwickelt. Hierbei stand vor allem die Möglichkeit, den Computer auch erstmals außerhalb von Mehrplatz/Timesharing-Umgebungen zur Programmierung einsetzen zu können, im Mittelpunkt. Ein Jahr später folgte zunächst Data General mit ihrem System Nova 3 dem Interpreter-Prinzip – ebenfalls, um den Computer ohne Festplatte programmieren zu können.
Compreter und Interpiler
Zwei Nachteile von „reinen“ Interpretern sind, dass erstens das Speichern des Sourcecodes im RAM sehr viel Platz einnimmt (denn jedes BASIC-Schlüsselwort besteht ja aus 2 (TO), 6 (RETURN) oder – je nach Abhängigkeit von Dialekt – noch mehr ASCII-Zeichen, von denen jedes 1 Byte RAM benötigen. Und zweitens müssen diese Schlüsselwörter bei jedem RUN erneut auf ihre Schreibung gecheckt werden, was den Interpreter langsamer noch als macht. Eine Lösung hierfür hat 1973 abermals Hewlett-Packard für seinen „BASIC-Computer“ HP 9830 entwickelt: Nachdem am Ende einer Eingabezeile die Execute“-Taste gedrückt wird, wird jedes erkannte BASIC-Schlüsselwort in ein 1 Byte großes „Token“ übersetzt. Dies kann man sich als eine eindeutige Abkürzung des jeweiligen Schlüsselwortes vorstelle. Dieses Token wird dann im RAM abgelegt, was den Speicherplatzbedarf stark reduziert. Soll die BASIC-Programmzeile später noch einmal angezeigt werden, dann findet einfach eine Rückübersetzung des Tokens in das BASIC-Schlüsselwort statt. Mit 1 Byte großen Tokens lassen sich auf diese Weise 256 BASIC-Schlüsselwörter abkürzen/kodieren.
Diese Tokenisierung brachte bei HP BASIC und einigen nachfolgenden Dialekten noch einen weiteren Vorteil: Das Überprüfen der Eingabe konnte sofort mit einer Fehlerangabe quittiert werden, falls etwas nicht stimmte. Dies erkennt man zum Beispiel beim Atari-BASIC oder beim TI-BASIC. BASIC-Übersetzer mit Tokenisierung wurden von einigen auch „Compreter“ genannt, weil sie ein Zwischending aus Compiler (Vorabübersetzung) und Interpreter (Direktausführung) darstellten. In einem Funkschau-Beitrag von 1980 taucht neben diesem Begriff auch „Interpiler“ auf, mit welchem die oben genannte Variante des Kompilierens mit B-Code und virtueller Maschine bezeichnet wurde. Der Einsatz tokensierender BASIC-Interpreter in den allermeisten 8-Bit-Heimcomputern hatte schon wegen der kleinen RAM-Speicher nahegelegen. Daher hat Micro-Soft schon 1974 dieses Prinzip in seinem Altair-8800-BASIC und später auch in all seinen anderen Dialekten verwendet.
Wahlverwandschaften
An BASIC-Übersetzern zeigt sich die Bandbreite von Möglichkeiten aus einem Programm in Hochsprache ein lauffähiges Maschinenspracheprogramm zu generieren. Die Wahl des Übersetzungsprinzips hing oft mit den Anforderungen an Hardware (Peripherie) und Speichergröße zusammen – und sie fand stets als Trade-Off zwischen Programmgröße, Ausführungsgeschwindigkeit und Interaktivität statt. Auch hierin liegt ein Aspekt der enormen Variantenvielfalt von BASIC-Dialekten, die zu manchmal skurrilen, vor allem aber von den Vorstellungen der BASIC-Erfinder abweichenden Lösungen führte.
Während in der Spätzeit von BASIC vor allem Compiler dominiert haben, gibt es heutige BASIC-Dialekte vor allem wieder als Interpreter: Bei Chipmunk-BASIC, BBC-BASIC oder Small-BASIC wiegt die Interaktivität wichtiger als die Übersetzungs- und Ausführungszeit, zumal heutige Computer so schnell sind, dass der Unterschied verschwindend gering ist. Die BASIC-Dialekte der Vergangenheit zeigen oft auch in ihren Befehlssätzen diese unterschiedlichen Ansätze. Will man ein Programm von einer Plattform auf eine andere übertragen, muss dies mitbedacht werden. Wie Menschen im Unterschied zu Maschinen BASIC-Dialekte übersetzen, wird in der nächsten Folge dieser Kolumne besprochen.
(Zuerst erschienen in RETURN, Ausgabe 63.)




Pingback: BASIC übersetzen Teil 2 | SimulationsRaum