Knackige Überschrift, was steckt dahinter? Wir warten auf den Architekten für unser Restaurant hier in den Philippinen und so hatte ich etwas Leerlauf, um was total Unnötiges zu programmieren.
Um Java-Programme, beziehungsweise eine Teilmenge der Sprache/Bibliothek, auf dem C64 (oder anderen 8-Bit-Computern) zum Laufen zu bringen, sind mehrere Ansätze denkbar:
- Eine JVM auf dem Heimcomputer, die Bytecode interpretiert. Das würde im Prinzip gehen, es gibt auch Mini-Laufzeitumgebungen für so etwas. Auch Java Smart Card ist ein Stichwort.
- Compiler, die entweder Bytecode oder Source-Code in ein Quellformat umsetzen, was der 65xx versteht.
Ein JVM ist richtig viel Arbeit, und war in 2 Tagen nicht zu schaffen. Und mit http://sourceforge.net/projects/vm02/ gibt es so etwas auch schon für Apple II Computer. Ein (Cross-)Compiler ist deutlicher einfacher. Bytecode in 65xx-Assembler zu übersetzen ist relativ einfach, doch dann müsste ich wieder Assembler anfassen und damit das ganze schnell wird, müsste ich auch einen Code-Optimierer schreiben, denn einfach die Stack-Maschine umzusetzen, führt auch zu keinen Performance-Wundern. Da es für den C64 auch Compiler gibt, etwa für PASCAL (auf der Maschine) oder C (als Cross-Compiler etwa mit CC65), kann man aus Bytecode auch dieses Format generieren. Aber dann hätte ich wieder mit Java-Bytecode arbeiten müssen, was mir auch keine Freunde macht. Am Schnellsten verspricht Resultate eine Code-Transformation von Java nach C. Das Resultat kann dann der http://www.cc65.org/ in Maschinencode umsetzen, und dann bekommt man auch ein paar Optimierung geschenkt.
Das JDK bringt alles mit, um an den AST des Compilers zu kommen, wie schon im Blog hier beschrieben: http://www.tutego.de/blog/javainsel/2012/07/mit-der-internen-compiler-api-auf-den-ast-einer-klasse-zugreifen/. Von den erkannten Elementen (Ausdrücke, Variablenzugriff, Schleife, …) muss man nur C-Code schreiben und fertig ist. Um mir die Sache einfach zu machen vereinfache ich Java jedoch massiv:
- kein float/double/long/boolean
- kein new, keine Klassen, Stellen und sonstiges objektorientiertes “Zeugs”, das dranhängt, wie enum, erweitertes for, nur statische Methoden, keine Ausnahmen, Keine String-Konkatenation
- String-Literale können verwendet werden, allerdings nur von 0x0000 – 0x00FF (256) und eigentlich geht PETSCII nur von 0-191
- Nichts von der Java-Bibliothek
Des weiteren muss sehr “C”-ähnlich programmiert werden:
- Alle lokale Variablen müssen am Anfang einer Methode deklariert sein
- Alle Bezeichner müssen für den C-Compiler gültig sein, keine Unicodes
- Die Reihenfolge muss stimmen, der Umsetzer erzeugt keine Prototypen
- Eine main()-Methode muss etwas zurückgeben in C99, Java macht das nicht, daher nutzt man System.exit(0).
Das auf diese Weise kastrierte Java ist zwar im Prinzip für nix mehr zu gebrauchen, aber für Heimcomputer immer noch akzeptabel und eine nette Spielerei.
Wer bis dahin noch nicht das Interesse verloren hat, kann ein wenig mit dem Compiler spielen; die Source liegen unter https://code.google.com/p/java2c-transcompiler/.
Jetzt brauchen wir Input:
import static j2c.lib.Stdio.printf; // Source: http://skoe.de/wiki/doku.php?id=ckurs:04-abend4 public class Application { public static char istPrimzahl( int n ) { int divisor; int testEnde = n / 2; /* Alle potentiellen Teiler bis zur Mitte testen */ for ( divisor = 3; divisor < testEnde; divisor += 2 ) { /* Mit Rest 0 teilbar? */ if ( n % divisor == 0 ) { /* Ueberprüfung abbrechen, keine Primzahl */ return 0; } } /* Kein Test durchgefallen, ist eine Primzahl */ return 1; } public static void main( String[] args ) { int zahl; /* Von 3 beginnend jede zweite Zahl testen, bis unter 1000 */ for ( zahl = 3; zahl < 1000; zahl += 2 ) { if ( istPrimzahl( zahl ) != 0 ) { printf( "Primzahl: %u\n", zahl ); } } System.exit( 0 ); } }
Für die C-Funktionen (http://www.cc65.org/doc/funcref.html) gibt es eine paar statische Imports und native Platzhalter:
package j2c.lib; // http://www.cplusplus.com/reference/cstdio/ public class Stdio { /** * Print formatted data to stdout. * <code>int printf ( const char * format, ... );</code> * @param format * @param args * @return */ native public static int printf( String format, Object... args ); }
Das setzt der Compiler im Grunde 1:1 so um. Es lohnt sich das Eclipse CDT unter http://download.eclipse.org/tools/cdt/releases/juno zu installieren, damit die syntaktische Hervorhebung funktioniert (den CC65 Compiler einbinden könnten wir hier NICHT). Nach einer CDT-Neuformatierung ergibt sich:
#include <stdio.h> #include <stdlib.h> #include <peekpoke.h> #include <c64.h> #include <conio.h> /* CLASS Application { */ char istPrimzahl(int n) { int divisor; int testEnde = n / 2; for (divisor = 3; divisor < testEnde; divisor += 2) { if (n % divisor == 0) { return 0; } } return 1; } int main(void) { int zahl; for (zahl = 3; zahl < 1000; zahl += 2) { if (istPrimzahl(zahl) != 0) { printf("Primzahl: %u\n", zahl); } } return 0; } /* END CLASS } */
Um das Compilat zu Erzeugen muss nun der cc65 installiert werden. Unter ftp://ftp.musoftware.de/pub/uz/cc65/ lädt man die für Windows etwa die 1.3 MB große EXE und installiert. Die Eintragungen in den Path kann man vornehmen, nach der Installation folgt dann mit dem kleinen Test:
C:\..>cc65
cc65.exe: No input files
Kappt also.
Das Ganze soll im Emulator laufen, hier ist WinVICE gut: http://www.viceteam.org/#download.
Eine Batch-Datei bindet alles zusammen, also Java –> C, Compilieren und im VICE starten:
set JAVA_HOME="C:\Program Files\Java\jdk1.7.0" %JAVA_HOME%\bin\java -cp bin/;%JAVA_HOME%\lib\tools.jar j2c.J2CC65 src/java/Application.java > app.c del app.prg cl65 -o app.prg app.c "C:\Program Files\WinVICE-2.2-x64\x64.exe" app.prg
Und das Ergebnis sieht so aus:
Hat jmd. Lust das weiter zu entwickeln? Schreibt mir eine E-Mail.
Restaurant auf den Philippinen? Das ist doch auch mal einen Blogpost wert…