Alternative Sprachen für die JVM

Auch Cobol und C++ können Java-Programme erzeugen

24.09.1999
Vielfalt gibt es beim Angebot an Java Virtual Machines (JVMs), aber Java schien bisher die einzige Möglichkeit für ihre Programmierung zu sein. Ein Trugschluß: Mittlerweile gibt es Compiler und Interpreter für zahlreiche Programmiersprachen, die als Zielplattform die JVM nutzen. Robert Tolksdorf* gibt einen Überblick über das breite Spektrum an Tools für die Java-Ablaufumgebung.

Mit der Java-Plattform nennt man oft zwei Komponenten in einem Atemzug, die bei genauer Betrachtung aber recht eigenständig sind. Die Java Virtual Machine (JVM) führt Programmcode in binärer Form aus. Die Programmiersprache Java dagegen ist eine objektorientierte Hochsprache, für die ein Compiler Binärcode für den JVM-Prozessor erzeugt.

Die Hardware-Unabhängigkeit der Java-Plattform rührt in erster Linie von der standardisierten Ausführungskomponente JVM her. Sie liegt nicht als "normaler" Mikroprozessor in Hardware vor, sondern wird durch ein Programm simuliert. Der Binärcode - im Java-Jargon "Bytecode" - trifft somit auf jeder realen Hardware-Plattform auf denselben virtuellen Prozessor. Bei einer Portierung der Java-Plattform auf neue Hardware ist lediglich dieser Simulationsteil Hardware-spezifisch zu implementieren.

Die Programmiersprache Java trägt zwar durch plattformunabhängige und standardisierte Bibliotheken zur Portabilität bei. Dies garantiert aber lediglich die Übertragbarkeit des Quellcodes zwischen Java-Compilern. Wäre C++ mit einer standardisierten und über Fenstersysteme hinweg generischen GUI-Bibliothek versehen, könnte auch hier von Plattformunabhängigkeit gesprochen werden.

Die Alternativen zur Java-Programmierung reichen von vollständigen Compilern mit Bytecode-Erzeugung für die JVM über Präcompiler mit Java-Code als Ausgabe bis hin zu reinen Interpretern. Zu diesen "vollständigen" Hochsprachen treten die gerade für Web-Anwendungen wichtigen Skriptsprachen.

Die Implementierung eines Hochsprachen-Compilers umfaßt das Erzeugen und Optimieren von Maschinencode. Scheut man diese anspruchsvolle Aufgabe, kann man stattdessen den Quellcode zu einer anderen Hochsprache hin übersetzen. Ein klassisches Beispiel dafür waren die ersten C++-Compiler. Die von ihnen generierten C-Programme mußte man ihrerseits durch einen C-Compiler schicken, um Binärcode zu erhalten.

In der Java-Welt läßt sich das gleiche Verfahren verwenden. Dabei ist natürlich Java statt C die Zielsprache der Wahl. Der Vorteil der zweistufigen Übersetzung ist eine einfachere Implementierung des Compilers. Nachteilig ist der doppelte Kompilations-Schritt, der den Edit-Compile-Test-Zyklus verlängert.

Diese Variante der Kompilierung ist insbesondere von einem Hersteller bekannt. Die britische Mill Hill & Canterbury Corporation bietet unter http://www.webcom.com/mhc/java. html gleich drei Übersetzer für die Klassiker der imperativen Programmierung an: Pascal, Modula-2 und Oberon-2. Alle drei erzeugen aus den genannten Sprachen Java-Code, der im zweiten Übersetzungsschritt zu ausführbarem Bytecode wird. Das gleiche Verfahren findet bei Net-Rexx (http://www.ibm.com/technology/NetRexx) Verwendung. Net-Rexx ist eine Java-nahe Variante der in der IBM-Welt verbreiteten Sprache Rexx.

Direkte Erzeugung von Bytecode

Im Gegensatz dazu verlassen einige Sprachen den Java-Rahmen für die Programmierung vollständig und implementieren bekannte Hochsprachen neu als vollständige Compiler mit Zielprozessor JVM. Die direkte Erzeugung von Bytecode ist sicherlich der anspruchsvollste, aber auch der beste Weg für JVM-Programmieralternativen. Allerdings muß man hier den Entwicklungsaufwand des Herstellers mit höheren Lizenzkosten bezahlen. In einem sauber gebauten Compiler wird die Code-Erzeugung separat als Backend ausgeführt. Der Austausch des Backends ist damit je nach Zielprozessor möglich. Compiler-Hersteller zögern teilweise noch mit der Entwicklung solcher JVM-Backends, weil das Marktpotential für Konkurrenzsprachen zu Java unklar ist.

Ein Beispiel, das die Migration hin zu Java-Plattformen sicherlich erleichtert, ist der "PERCobol-Compiler" von Synkronix (http://www.synkronix.com). Er implementiert ein Cobol nach ANSI 1985 X3.23b. Zusätzlich ermöglicht er aber den Durchgriff auf Funktionalität der Java-Plattform, beispielsweise zur Applet-Erzeugung, für Netzwerkzugriffe oder zur Beans-Benutzung.

Der frei erhätliche "Small-Eiffel"-Compiler auf http://www.loria.fr/projets/SmallEiffel bevorzugt die JVM sogar als Übersetzungsziel. Während er normalerweise C-Code zur weiteren Übersetzung nach Maschinencode erzeugt, generiert er JVM-Bytecode direkt.

Während Java lange Zeit die einzige Möglichkeit zur Programmierung der JVM war, begann die Entwicklung weiterer Sprachen im Bereich der direkten Assemblerprogrammierung. Dazu beigetragen hat sicher die vollständige Dokumentierung der JVM durch Sun. Die JVM hat als Microprozessor eine Maschinensprache und dementsprechend auch die Möglichkeit zur Assemblerprogrammierung. Der JVM-Prozessor unterscheidet sich von herkömmlichen Chips dadurch, daß er auf die Ausführung objektbasierter Programme spezialisiert ist.

"Jasmin" ist ein Assembler (http://www.cat.nyu.edu/meyerjasmin) für die JVM. Er beherrscht die Maschineninstruktionen der JVM und einen Satz von Direktiven zur Definition von Klassen, Feldern und Methoden. Neben möglicher Geschwindigkeitssteigerung besteht der interessante Vorteil bei der Verwendung von Assembler-Programmierung in der Mischung mit "normalem" Java-Code. Der objektbasierte Rahmen zur Ausführung von Java-Programmen ist mit der JVM auch auf der Prozessorebene vorhanden. Somit können die Java-Standardklassenoder eigene Klassen direkt vom Assembler ohne zusätzlichen Aufwand benutzt werden. Genauso erzeugt ein JVM-Assembler Bytecode-Dateien, die von Java aus wie normale Klassen zu verwenden sind.

Interpreter auf Basis der Java Virtual Machine

Die Implementierung eines Interpreters für eine Programmiersprache ist in der Regel einfacher als das Schreiben eines Compilers. Der Interpreter selber wird üblicherweise als Java-Programm realisiert und läuft als JVM-Code ab. Er liest den Programmtext ein - von Datenträger oder über das Web - und interpretiert ihn.

Während die Programmierung einfacher Interpreter für Lisp, Basic, Logo etc. in Java mittlerweile zum Übungsbetrieb des Informatikstudiums gehört, gehen einige Interpreter auf Java-Basis neue Wege.

"Turtle Tracks" (http://www.ugcs.caltech.edu/~dazuma/turtleindex.html) ist ein Beispiel für die Möglichkeiten, die ein Java-basierter Interpreter bieten kann. Er implementiert die Sprache Logo, eine einfache imperative Sprache, die viel Beachtung im Lernbereich gefunden hat. Der Interpreter beherrscht zunächst den von Logo bekannten Befehlsumfang. Zugleich kann er aber durch LOADPRIMITIVES zur Laufzeit durch weitere Befehle erweitert werden. Diese müssen durch eigene Java-Klassen in bestimmter Form implementiert sein. Durch das dynamische Einbinden von Klassen in Java kann diese Funktionalität über Befehle im Logo-Programm genutzt werden. Damit läßt sich der Sprachumfang des Interpreters dynamisch zur Laufzeit verändern.

In Java implementierte Interpreter sind auch aufgrund ihrer Einbettbarkeit sehr interessant. Sie sind zumeist komplett als ein einziges Java-Objekt in eigene Anwendungen integrierbar. Man könnte so auch verschiedene Interpreter-Objekte gleichzeitig benutzen und Systeme in mehreren Sprachen programmieren. Diese Flexibilität will aber bezahlt sein, denn die Interpreter verstärken zwangsläufig die Geschwindigkeitsnachteile der Java-Plattform.

Skriptsprachen in Java

Skriptsprachen sind Interpretersprachen, die sich auf ein bestimmtes Anwendungsgebiet konzentrieren oder zur nutzerprogrammierbaren Automatisierung von Anwendungen dienen. Ausführungen für die JVM sind aufgrund ihrer Einbettbarkeit in Anwendungen sehr interessant.

Eine der Skriptsprachen mit sehr großer Verbreitung im Web-Umfeld ist Javascript. Die Sprache kann auch außerhalb von Browsern verwendet werden und ist mittlerweile von der europäischen Standardisierungsorganisation ECMA normiert.

Es existieren momentan zwei Implementierungen auf Java-Basis: "Rhino" (http://www.mozilla.org/rhino) und "Fesi" (http:// home.worldcom.ch/jmlugrin/fesi). Beide Interpreter verarbeiten Javascript entsprechend dem ECMA-Standard 262 und sind mit Java implementiert.

Ein Beispiel für eine hochspezialisierte und innovative Skriptsprache ist "WebL" (http://www. research.digital.com/SRC/WebL). Sie wurde in den Forschungszentren von Digital entwickelt, die heute zu Compaq gehören. WebL beherrscht HTML-und XML-Seiten als eingebauten Datentypen und bietet Operatoren darauf an. So läßt sich das Extrahieren aller mit <PRE> markierten Bestandteile einer HTML-Seite mit einem einzigen Audruck (Elem (seite, "pre")) erledigen. WebL ist als Interpreter implementiert, dessen Geschwindigkeitsverlust aber nicht ins Gewicht fällt: Maßgeblich für die Ausführung typischer Programme sind Netzwerkzugriffe auf Web-Server.

Die hier vorgestellten Sprachen für die JVM haben noch an Stabilität und Verbreitung gegenüber Java aufzuholen. Wahrscheinlich werden sie auch nicht tatsächlich in Konkurrenz zu Java treten, sondern komplementär für bestimmte Anwendungen verwendet. Interessant sind die aufgezeigten Möglichkeiten aber allemal.

Eine Liste mit Kurzbeschreibungen und Links zu weit über 100 Sprachen für die JVM finden sich im Internet unter der URL http://www.cs.tu-berlin.de/~tolkvmlanguages. Neben den weit verbreiteten Basic, Smalltalk, C++ oder Cobol gibt es dort eine Reihe von Hinweisen auf Exoten, die vor allem im akademischen Umfeld eingesetzt werden. Dazu zählen beispielsweise Prolog, Oberon oder Eiffel. Bei einem Großteil handelt es sich um experimentelle Implementierungen, aber darunter ist auch eine ansehnliche Liste sehr ausgereifter Pakete. Bislang sind fast alle der Sprachen ohne Lizenzkosten erhältlich.