B2J - BlitzBasic zu Java Crosscompiler

Kommentare anzeigen Worklog abonnieren

Worklogs B2J - BlitzBasic zu Java Crosscompiler

Es lebt doch noch

Dienstag, 12. August 2014 von DAK
Da es scheinbar doch Interesse an diesem Projekt gibt, und gerade Uni-Ferien sind, habe ich es wieder mal raus geholt und was daran gemacht. Hab dabei überraschend schnell die Probleme gelöst gehabt, an denen ich beim letzten Mal gehangen bin.

Was jetzt schon alles funktioniert:

Syntax-Elemente

If/Else If/Else/EndIf
While/Wend
Repeat/Until (Forever fehlt noch)
For/Step/Next
Function/End Function
And/Or (Not buggt noch)
Variablen-Autodeklaration mit passenden Typen
Variablen-Autocast
Funktionsaufrufe mit Klammern (ohne Klammern fehlt noch)

Befehle
Print
Locate
Input
Delay
Millisecs (zählt im Moment noch Millisekunden seit 1.1.1970 statt seit dem letzten Boot)
Sin
Cos
Tan
Graphics
Cls
Line
Rect
Oval
Text
Color
Flip
LoadFont
SetFont
KeyDown (KeyDown und Keyhit erkennnen momentan nur ESC und die Cursor-Tasten, da ich jede Taste einzeln mappen muss)
KeyHit
Exit (Sollte End heißen, bin ich gerade drauf gekommen)
chr
asc

Abweichungen zu BlitzBasic
Im Moment gibt es nur BackBuffer-Rendering. Das heißt, nach Graphics ist nicht der FrontBuffer als Renderziel gesetzt, sondern der BackBuffer.

Fehlende Syntaxelemente
Einzeiler-Ifs
Repeat/Forever
Funktionsaufrufe ohne Klammern
Dim-Arrays
Blitz-Arrays
Types
Goto/Gosub


Es gibt auch eine Demo, die dem momentanen Zustand wiederspiegelt. Das Ding ist nur zu Demozwecken gedacht, nicht für echte Projekte, dafür ist es noch nicht stabil genug. Auch sollte B2J bitte nicht dazu verwendet werden, Java zu lernen, da der erstellte Code echt grauenhaft zu lesen ist. Er tut was er soll, ist dabei aber echt nicht schön.

Wie man den Compiler verwendet und was man dafür benötigt findet sich in der Readme im Download.

Ich habe auch ein neues Demoprojekt erstellt, nämlich einen kleinen Pong-Klon.

user posted image

Man kann also zumindest ein paar Kleinigkeiten schon damit machen.

Edit: Den generierten Code kann man sich gerne anschauen, nur nicht als Vorbild nehmen^^

Es werde Bild!

Montag, 28. Oktober 2013 von DAK
Es hat sich seit dem letzten Post doch so einiges getan.

Zuerst mal das Wichtigste: der Compiler funktioniert soweit (und das ganz gut) und erzeugt Javacode und fertige JARs, was heißt, er kann schon lauffähigen Code erzeugen.

Bislang fehlen noch einige Syntaxelemente (For, Repeat, Select, Default-Werte bei Funktionen, Funktionen ohne Klammern, explizite Casts, Arrays), die nicht fehlen, weil ich sie nicht hinkriege, sondern weil ich bislang noch nicht dazu gekommen bin. Ich habe mich als erstes darauf konzentriert, quasi gerade voraus zu gehen, und so schnell als möglich was Sichtbares zu bekommen.

Der Großteil des BB-Wirrsinns ist ist auch schon drinnen, sowas wie Autocast und Variablen, die aus dem nichts entstehen.

So jetzt zu dem Interessanten. Ich habe nämlich begonnen, die Standardlib zusammenzubasteln. Ist natürlich noch nicht viel, gerade mal 11 Befehle, aber es kann schon was, vor allem, weil ich mit der Grafik begonnen habe (nur für dich, Eingeproggt Wink ).

Dazu erzeuge ich ein JFrame als Fenster in dem ein JPanel die Grafik selbst darstellt. JPanels funktionieren leider ziemlich anders als BB, weswegen ich z.B. einen Pseudo-Backbuffer einbauen hab müssen, welcher aber ganz gut geht, und schon wunderbar augenkrebsverursachendes Geblinke auf den Bildschirm werfen kann.

Wer es testen will, einen Download gibt es hier.

Ich habe übrigens Inlinejava und Inlinejavafunction rausgelassen, das wird stattdessen per Extern gemacht.
So ein Extern-Feld schaut so aus:
Code: [AUSKLAPPEN]
extern
   import b2jlibs.ConsoleLib;
   ConsoleLib.print(text$)
   ConsoleLib.input$(text$)
   ConsoleLib.locate(x%,y%)
   
   import b2jlibs.basics.TimeAndRandom
   TimeAndRandom.delay(ms%)
   
   import b2jlibs.graphics.Graphics
   Graphics.cls()
   Graphics.graphics(w%,h%,bit%,mode%)
   Graphics.line(x1%,y1%,x2%,y2%)
   Graphics.rect(x%,y%,w%,h%)
   Graphics.text(x%,y%,text$)
   Graphics.color(r%,g%,b%)
   Graphics.flip()
endextern


Es wird also die Java-Klasse, die man verwenden will, importiert, und alle Funktionen, die man braucht, deklariert. Wichtig hierbei: Da Blitz ja nicht objektorientiert ist, müssen sämtliche so verwendeten Funktionen in Java static sein, und dürfen nur die Variablentypen Int, Float und String verwenden, da Blitz ja mit dem Rest nicht umgehen kann.

Edit: Noch ein Bild:
user posted image

Zurückrudern und wieder an den Start

Donnerstag, 24. Oktober 2013 von DAK
Da die Designentscheidungen vom vorigen Post ja wirklich nicht gut angekommen sind, habe ich mich entschieden, sie fallen zu lassen, und stattdessen von vorne zu beginnen. (Mein alter selbstgebastelter Compiler hätte wirkliche Schwierigkeiten mit den meisten von den Punkten gehabt).

Für die neue Version habe ich mir mal auf die Schnelle JavaCC beigebracht, was ist ein Parser-/Compilergenerator. Das Ding ist unglaublich mächtig, nur leider auch alles andere als simpel und einfach. Statt dass ich z.B. die Rechnungen und Zuweisungen annähernd ohne Bearbeitung von Blitz nach Java übernehmen kann, muss ich das Parsen jetzt selber machen. Das ist auch das, womit ich mich die letzten Tage über hauptsächlich beschäftigt habe, dafür zu schauen, dass sämtliche Rechnungen und Zuweisungen korrekt von Blitz nach Java übernommen werden. Das funktioniert jetzt auch schon ganz gut.

Was ich bislang funktionabel habe:
-Zuweisungen und Bedingungen werden korrekt nach Java übernommen
-Autocasting zwischen String, Float und Int genau wie in Blitz, dazu noch den Autocast nach Boolean, wo es notwendig ist, um in Java den gleichen Effekt zu haben, wie in Blitz
-If und While funktionieren


Dazu noch eine weitere Überlegung, zu der ich gerne Feedback hätte:
-Sind die Befehle inlinejava/inlinejavafunction sinnvoll?
Java und Blitz direkt miteinander interagieren zu lassen, finde ich ein wenig gefährlich, vor allem, was Parametertypen von Funktionen angeht. Mit einem unüberprüftem inlinejava/inlinejavafunction könnte man z.B. eine Funktion definieren, die als Parameter z.B. ein Objekt vom Typ Scanner will. Wenn man jetzt direkt aus Blitz die Funktion aufruft, und dann dort einen falschen Parameter eingibt, kann der Parser nicht mehr automatisch casten (so kann man z.B. beim besten Willen nicht von int auf InetAddress casten, nur als Beispiel), und das "alles geht", was man in Blitz gewohnt ist, ist nicht mehr gewährleistet.
Als Alternative würde sich ein "extern" wie in BlitzMax anbieten. Man schreibt seine Java-Funktionen/-Klassen in ein normales Java-File, und muss im Blitz nur noch mittels extern den Header der Funktion reinschreiben.

Designentscheidungen

Montag, 21. Oktober 2013 von DAK
Ich weiß, der Post kommt schnell nach dem Letzten, aber ich hab ein wenig darüber nachgedacht, wie genau ich den Compiler mache, und hätte gerne ein paar Rückmeldungen darüber, was ihr dazu denkt.



-Implizites Casting, ja oder nein?
Ich finde das implizite Castverhalten von Blitz ein wenig mühsam, und denke, dass es mehr Fehler verursacht, als es beseitigt.
Beispiel:
BlitzBasic: [AUSKLAPPEN]
Local x% = 5 ;ist int, wird also nicht gecastet
Local y$ = 3 ;ist int, sollte aber String sein, wird also auf "3" gecastet
Local z% = x+y ;x ist int, y ist String, das heißt, x wird automatisch zu "5" gecastet. "5" + "3" = "53".
;z% ist aber ein int, also wird wieder auf (int) 53 gecastet


Das automatische "Hochcasten" von Int->Float->String tut Java auch, ist auch ansich kein Problem, das Problem kommt erst dann, wenn man erwartet, dass 5+"3" = 8, wenn man also erwartet, dass weil das Ergebnis ein int ist, automatisch auf int gecastet wird.
Java würde an dieser Stelle sich beschweren, und einen expliziten Cast von String auf int erwarten.

Ich tendiere dazu, das implizite Runtercasten wegzulassen. Was meint ihr?



-Klammern bei Funktionen müssen sein
Das steht schon recht fest, dass ich das so behalten werde.
Zur Erläuterung:
In BB geht Folgendes:
BlitzBasic: [AUSKLAPPEN]
Print "test"

In Java braucht man die Klammern unbedingt:
BlitzBasic: [AUSKLAPPEN]
Print("test")

Ich denke, das werde ich fix so übernehmen, wie es in Java ist, da es in Blitz doch eher Probleme macht, weil:
BlitzBasic: [AUSKLAPPEN]
Print "test" ;Geht
x = Sin y ;Geht nicht
eigeneFunktion ;Geht, solange es keinen Parameter gibt

Das heißt:
-Ein Statement ohne irgendwas (einfach nur den Namen einer Funktion) funktioniert als Funktion.
-Eine Funktion lässt sich ohne Funktionsklammern aufrufen, solange es keinen Rückgabewert gibt

Wirklich schlimm wird es dann in Kombination damit:
BlitzBasic: [AUSKLAPPEN]
Print(MilliSecs) ;Gibt 0
Print(MilliSecs()) ;Gibt momentane Systemzeit in ms
MilliSecs = 5
Print(MilliSecs) ;Gibt 5


Dadurch, dass MilliSecs keine Klammern hat, wird es, nicht wie erwartet, als Funktion aufgerufen und der Rückgabewert zurückgegeben, sondern als Variable aufgerufen, und der Wert der Variable (uninitialisiert = 0) zurückgegeben.



-Variablen müssen initialisiert werden
In Blitz ist eine Variable da, sobald man sie verwendet. Beispiel:
BlitzBasic: [AUSKLAPPEN]
x = 5
Print(x)


Das hat wieder seine Tücken, vor allem sobald man sich vertippt:
BlitzBasic: [AUSKLAPPEN]
testvar = 5
Print(tetsvar)

Statt dass der Compiler hier einen Fehler wirft, und sagt, diese Variable gibt es gar nicht, lässt er es einfach durchgehen, und man hat dann einfach ein falsches Rechenergebnis. Bei komplexeren Algos kann dass dann wirklich schwer zu finden sein.

Variablen werden also wohl (außer es gibt großen Gegenspruch dagegen) einzeln initialisiert werden müssen:
BlitzBasic: [AUSKLAPPEN]
Local testvar% = 5
Print(testvar)

Dabei gilt wie im Standard-Blitz $ => String, # => Float, % oder nichts => Int.

Was haltet ihr von den Entscheidungen / Ideen?

Inlinejava und was sich unter der Haube tut.

Montag, 21. Oktober 2013 von DAK
So, bin jetzt etwas weiter gekommen.

Das Wichtigste, was neu dazu gekommen ist, sind includes und inline-Java, da ich beides brauche, um die Standardbibliothek einzubauen.

Was ich ja nicht wirklich mag, sind "magische Befehle", also Befehle, der Compiler ermöglicht, ohne dass ein User ähnliches selber einbauen kann, weswegen ich mich entschieden habe, die Standardbibliothek in einem include-File mit Inlinejava zu schreiben. Aber zum Inline-Java später noch mehr, davor noch etwas über die Innereien des Compilers.

[quote=Addi]- Was für einen Parser (recursive descent, oder die bottom up variante) benutzt du und
schreibst du diesen selbst oder hast du ihn generieren lassen.[/quote]
Diese Frage habe ich gefürchtet. Ich verwende keinen richtigen Parser per se (im Sinne von dass ich zuerst das Ganze zerlege, und es dann nach dem Parsen wieder zusammensetze), sondern ich ersetze zeilenbasiert die Syntax von Blitz durch die Syntax von Java.
Optimiert wird dabei nicht, da es nachher sowieso durch den Java-Compiler geworfen wird, und der viel besser optimiert, als ich es je könnte.

Da macht es sich wohl bestraft, dass ich bislang nur einen Compiler geschrieben habe, und der für einen Assembler war Wink

Was ich allerdings noch mache, ist ich habe verschiedene "Scopes", in die geschrieben wird, und zwar drei verschiedene: Main, Global und Function.

Um das zu verdeutlichen mal folgendes BB-Codesegment:
BlitzBasic: [AUSKLAPPEN]
Local x = 5
Function test(x)
Print("X = "+x)
End Function
Global y = 2
If (x=y+3) Then
x = 3
EndIf


In Blitz ist dieses Zusammenwürfeln von Hauptfunktion (das "normale" Programm außerhalb von Funktionen), Funktionen und Deklarationen von Globalen Variablen erlaubt und voll ok.
In Java geht das leider nicht.
Wird der Codeteil von oben in Java übersetzt, dann würde das so ausschauen müssen:

Code: [AUSKLAPPEN]
public class test {
   static int y;
   public void main(String args[]) {
      int x = 5;
      y = 2;
      if (x==y+3) {
         x = 3;
      }
   }
   public static void test(int x) {
      print("X = "+x);
   }
}


Das heißt:
-Die Hauptfunktion in Java ist tatsächlich eine Funktion
-Globale Variablen sowie andere Funktionen müssen außerhalb der Hauptfunktion sein

Das heißt, je nach Scope, werden Zeilen wo anders hingeschrieben. Der Scope wird dabei automatisch je nach Schlüsselwort umgeschalten.

Jetzt wieder zum Inline-Java. Da gibt es nämlich zwei verschiedene Varianten:
-inlinejava / endinlinejava
und
-inlinejavafunction / endinlinejavafunction

Alles zwischen inlinejava und endinlinejava wir 1:1 in den Java-Code übernommen. Dabei wird der momentane Scope nicht geändert. Damit kann man z.B. in der Hauptfunktion ein paar Zeilen Java einfügen, wenn man z.B. eine Java-Funktion verwenden will, die es in Blitz so nicht gibt.

Dazu gibt es noch inlinejavafunction (und das dazupassende endinlinejavafunction). Dabei wird wie beim inlinejava-Befehl alles dazwischen direkt in den Java-Code übernommen, allerdings auch der Scope auf Function gesetzt. Es wird aber nicht automatisch ein Funktionsheader erstellt.

Ein Beispiel dazu aus der lib.bb, in der die Standardbibliothek entsteht:
Code: [AUSKLAPPEN]
inlinejavafunction
    public static void print(Object o) {
        System.out.println(""+o);
    }
endinlinejavafunction


Ein inlinejavaglobal halte ich für unnötig, da man die für die Funktion benötigten globalen Variablen auch direkt vor der Funktion initialisieren kann. Beispiel hierfür (auch aus der lib.bb):
Code: [AUSKLAPPEN]
inlinejavafunction
private static Scanner _bbInstreamScanner = new Scanner(System.in);
    public static String input(String s) {
        System.out.println(""+s);
        try{
            return _bbInstreamScanner.nextLine();
        } catch(Exception e) {
            System.exit(1);
        }
        return "";
    }
endinlinejavafunction


Mir ist gerade beim Schreiben noch aufgefallen, dass ein inlinejavaimport, mit dem man weitere Java-Klassen importieren kann, doch sehr nützlich wäre, wird also noch implementiert.


Explizite Casts sind jetzt auch drinnen. Was mir noch etwas schwieriger wird, ist das Implizite Casten von Blitz. Beispiel:

BlitzBasic: [AUSKLAPPEN]
Local x$ = "5"
Local y% = 3
Local z# = y+x


Dabei werden Strings, Ints und Floats automatisch umgecastet, so dass sie passen. In Java gibt es den Autocast in diesem Sinne nur, wenn keine Information verloren geht, das heißt von int nach float nach String, aber nicht in die andere Richtung. Da werde ich noch alle impliziten Casts auf Explizite umwandeln müssen.

Auch die Logik-Befehle sind inzwischen drin, sowie die Variablenvergleiche. Stringvergleiche kommen noch.

Weiteres Todo:
-Syntax
--Repeat/Select (Fehlen nicht, weil sie so schwer sind, sondern nur weil ich noch nicht dazu gekommen bin, und ich sie selbst so selten verwende)
--Stringvergleiche
--Implizite Casts

-Standardbibliothek portieren

Erster Anfang

Samstag, 19. Oktober 2013 von DAK
Es kennt ja jeder das alte Lied mit BlitzBasic. Es war für viele hier (wie auch für mich) die erste Sprache, mit der man begonnen hat, weil sie so schön einfach zu schreiben ist, und man trotzdem so viel damit machen kann. Aber dadurch, dass es seit mehreren Jahren keine Weiterentwicklung mehr gibt, wird Blitz leider immer inkompatibler mit modernen Windows-Versionen. Dazu kommt, dass Blitz ja wirklich nur auf Windows rennt.

Also habe ich mir gedacht, ich bastel mal einen BlitzBasic zu Java Crosscompiler. Was der tut, ist, er kompiliert normalen Blitz-Code zu lauffähigem (wenn auch nicht gerade schönem) Java-Code um. Damit hat man dann Blitz-Einfachheit auf der JVM. Der Code wird dann auch gleich mittels javac (falls vorhanden) kompiliert und per jar zu einem netten jar-File verpackt.
Dadurch ist das Programm nicht nur auf Windows, sondern auch auf allen anderen wichtigen Plattformen lauffähig.

Bislang hab ich es so weit, dass der größere Teil der Syntax in lauffähigen Code übersetzt wird.
Ein Beispiel dazu:

BlitzBasic: [AUSKLAPPEN]
While (True)
Local name$ = getName()
begruessen(name);
Wend

Function getName$()
Return Input("Wie heisst du? ")
End Function

Function begruessen(name$)
Print("Hallo "+name)
End Function


wird zu

Code: [AUSKLAPPEN]
//Created using David Krywult's amazing B2J-Compiler
import java.util.Scanner;
public class test {
   public static void main(String[] args) {
         while (true) {
            String name = getname();
            begruessen(name);
         }
         
         
   }
   public static void print(Object o) {System.out.println(""+o);}
   private static Scanner _bbInstreamScanner = new Scanner(System.in);
   public static String input(String s) {System.out.println(""+s);try{return _bbInstreamScanner.nextLine();}catch(Exception e){System.exit(1);return "";}}
   public static String getname() {
      return input("Wie heisst du? ");
   }
   public static int begruessen(String name) {
      print("Hallo "+name);
      return 0;
   }
}


Was dann zuerst in eine .java übersetzt und dannin eine .jar gepackt wird, die man dann ausführen kann.

Wer das Ganze ausprobieren möchte, kann sich eine Techdemo hier runterladen.

Nächste Sachen am Todo:
-Rest der Syntax implementieren
--Repeat
--Variablenvergleiche (= wird zu ==, <> zu !=) (inzwischen erledigt)
--And, Not, Or implementieren
--Stringvergleiche

-Inline-Java ermöglichen
-Imports folgen

-Standardbibliothek portieren (noch ohne 2D-Grafik, die kommt später dann, wenn sonst alles geht)


Besteht Interesse an dem Projekt?