Technik der Übersetzung

Technik der Übersetzung

Inhalt


1 Der Übersetzer

Topic:.Java2C.Translator.

pStyle=std

.

1.1 Einfache Umsetzung geht nicht

Topic:.Java2C.Translator..

pStyle=std

Java und C++ scheinen sehr ähnlich zu sein. Eine Überlegung nach dem Schema: "." gegen "->" ersetzen, weil alles Zeiger sind, geht nur für eine manuelle Konvertierung. Die restlichen Unterschiede sind also von Hand anzupassen. Das geht aber nicht für einen ordentlichen Durchlauf: Testen und Korrigieren in Java, Implementieren in C. Jegliche manuelle Nacharbeit muss unterbleiben, sonst ist diese Bearbeitungsschleife zu groß und damit fehlerträchtig.

1.2 Parsen des Java-Quelltextes

Topic:.Java2C.Translator..

pStyle=std

Damit verbleibt nur die Möglichkeit, den Java-Quelltext mit einem Parser vollkommen auseinanderzunehmen und danach mit einem Generator wieder zusammenzusetzen. Damit ist die Sprachähnlichkeit von Java und C++ nicht mehr ein wesentliches Merkmal. Generieren lässt sich jegliche Zielsprache. Auch aus anderen Gründen ist es daher geboten, auf C als Zielsprache zu setzen.

Als Parser wird der ZBNF-Parser eingesetzt. Dieser ist sehr flexibel konfigurierbar: Die Syntax mit zugehöriger Semantik wird in einem Textfile vorgegeben: ../sf/Java2C/syntax/Java2C.zbnf, der Parser arbeitet bezüglich der Syntax interpretierend. Das hilft in der Entwicklung, da syntaktische Sachverhalte von Java recht schnell untersucht und angepasst werden können. Es gibt immerhin mehrere Wege der Fassung von Java in eine taugliche Syntax und in Java durchaus komplexe syntaktische Zusammenhänge. Der ZBNF-Parser ist naturgemäß langsamer als andere Parser wie yacc. DerGeschwindigkeitsnachteil wirkt sich aber nicht aus für mittelgroße Projekte. Die Übersetzungszeiten überschreiten keine Sekunden. Eine Verwendung eines anderen Parsers ist aber für den Zeitpunkt möglich und sinnvoll, zu dem der Gesamtumfang und die konkrete Fassung der Syntax feststeht. Jedenfalls gibt es eine klare Schnittstelle zwischen dem Parserergebnis und der Generierung der C- und H-Files.

1.3 Generieren der C-und H-Files

Topic:.Java2C.Translator..

pStyle=std

Aus dem Parserergebnis heraus werden die C- und H-Files mit Algorithmen direkt in Java generiert. Somit kann man alle Sonderfälle ausprogrammieren. Beispielsweise sollen Instanzen der Form

 final Type obj = new Type(arg);

innerhalb der umgebenden Klasse als embedded Data und nicht als Zeiger auf eine mit new angelegte Struktur realisiert werden. Das ist funktional exakt, weil auch in Java das obj nicht geändert werden kann und der referenzierende Typ mit dem instanziiertem Typ übereinstimmt. Dann muss aber obj als Identifier einer Instanz behandelt werden, beim Zugriff also obj. geschrieben werden und nicht obj-> wie sonst bei Referenzen.

Grob gesehen ist für jedes syntaktische Konstrukt von Java eine Methode des Generators notwendig, der die notwendige Syntax in C erzeugt. Dabei sollte straigth-forward gelten, keine allzu komplizierten Algorithmen.

Wichtig ist, dass Identifier in Tabellen über das gesamte zu übersetzende Package gehalten werden müssen, damit bei der Generierung die Art des Identifiers und dessen Zusammenhang (wo definiert, welches #include notwendig usw.) in C richtig ausgeführt wird.

Java-Sources als javadoc:

1.3.1 Liste von lokalen Bezeichnern

Topic:.Java2C.Translator...

pStyle=std

In Java_Quelltext ist ein lokaler Bezeichner nicht von einem Bezeichner eines Elementes der Klasse unterscheidbar, wenn nicht explizit this. davorsteht. Letzreres ist nicht voraussetzbar. Bei einer Übertragung nach C++ würde man hierbei keine Schwierigkeiten haben, da C++ das selbe Verhalten hat. In C muss man jedoch vor einen Bezeichner eines Elementes der Klasse den Zeiger auf die Klassenstruktur ythis-> davorschreiben. Um zu unterscheiden, ob ein Bezeichner lokal ist oder nicht, werden alle lokalen Bezeichner in einer Liste geführt:

 TreeMap<String, String> localIdents

Diese Liste wird als Aufrufargument den jeweiligen Generiermethoden weitergegeben, also nicht als Klassenelement gehalten. Wichtig ist, dass Variablendefinitionen in einem BlockStatement nur innerhalb dieses gelten. Es muss daher jeweils eine eigene Kopie dieser Liste localIdents innerhalb von BlockStatemens angelegt werden und nicht das Original verändert, wenn sich in dem Statement-Block lokale Variablendefinitionen befinden.

1.3.2 Liste von Typebezeichnern

Topic:.Java2C.Translator...

pStyle=std

Typebezeichner werden beim Parsen aufgrund ihrer Stellung in der Java-Syntax erkannt. Typbezeichner können aber sein:

  • skalare Basistypen: int, float usw.

  • Typen, die als embedded eingesetzt werden sollen wegen final TYPE name = new TYPE();

  • Typen, die Referenzen darstellen.

Letztere Typen sollen bei der Definition als Zeiger mit Vorwärtsdeklaration generiert werden:

 struct TYPE_t* name;

Bei den skalaren Basistypen ist gegebenenfalls eine Übersetzung notwendig. int wird zu int32 (da in Java immer 32 bit breit) usw.

Daher werden erkannte Typen in einer Liste typeIdentifiers auf Klassenebene GenerateClass geführt.

1.4 Derzeitiger Stand der Dinge

Topic:.Java2C.Translator..

pStyle=std

März 2008 Das gezeigte Beispiel dient als Vorlage. Dieses ist geparst und bis hinunter zu Expressions und Assignments generiert. Der ersichtliche Umfang des Ergebnisses PID_controller.h und PID_controller.c ist aber noch nicht sehr hoch. Mehr steht derzeit auch nicht in der Quelle PID_controller.java. Die zweite Quelle MainController.java ist noch nicht getestet. Hier muss das Beispiel noch wesentlich erweitert werden, um den bisher vorhandenen Translator zu testen. Die Arbeit muss also noch fortgesetzt werden. Letzlich muss das Beispiel alles umfassen, was in Java für Embedded so alles denkbar ist.

Welche syntaktischen Möglichkeiten von Java in der fast embedded Welt notwendig sind und wie sie syntaktisch zu fassen sind, muss ebenfalls noch endgültig geklärt werden.

Als Runtime-Umgebung soll CRuntimeJavalike verwendet werden. Diese Laufzeitroutinen umfassen direkt in C programmiert die wichtigsten Bestandteile aus java.lang und java.util. Im Beispiel soll insbesondere gezeigt werden, dass man in der fast embedded Welt ganz gut java.util.List gebrauchen kann und wie das umgesetzt ist. Auch die Garbage Collection ist ein Thema.

August 2008 Mittlerweile sind zwei Versionen bei Sourceforge herausgekommen, die dritte ist in Arbeit:

  • Das Beispiel ist erweitert. Alles was in Java vorliegt, ist in C++ compiliert und getestet.

  • In der Version 0.82 auf Sourceforge ist der Garbage Collector enthalten. Dem GC-Prinzip ist eine wesentliche Bedeutung zuzumessen, auch wenn in der embedded Welt die wichtigen Daten nur statisch instanziiert werden/sollten.

  • Es hat sich erwiesen, dass ein einfaches und einheitliches Log- bzw. Meldeausgabe-System sowohl auf der Java-Seite als in C (und allgemein verwendbar auch in C++) notwendig ist. Es ist hier etwas anprogrammiert, aber noch verbesserungsfähig.

  • Die Version 0.82 bei Sourceforge ist noch unzureichend kommentiert (javadoc). Eine verbesserte Version gegenüber sf-0.82 fast ohne inhaltliche ist Änderungen ist ../sf/Java2C_080618.zip.

  • Die Version ../sf/Java2C_080810.zip enthält eine Umarbeitung bei Typdefinitionen mit der Lösung des Problems der Erkennung innerer Typen. Die Typdefinitionsüberarbeitung ist im Hinblick auf String-Verarbeitung erfolgt. Die String-Verarbeitung selbst ist noch in Arbeit.

  • Die Versionen ab ../sf/Java2C_080905.zip bis ../sf/Java2C_080913.zip enthalten die Implementierung der Stringverarbeitung und einige andere Dinge. Prinzipiell gelöst ist die Generierung von verschiedenen C-Routinennamen für parametersensitive (überladene) Methoden aus der CRuntimeJavalike. Das ist notwendig für die Stringverarbeitung, dort tritt gehäuft so etwas wie indexOf(verschiedenes) auf. Die Generierung von eindeutigen C-Routinennamen aus übersetzenten Java-Sources ist noch nicht erfolgt, aber dringend notwendig. Motor dafür ist die Java2C-Übersetzung der Quelle javasrc:_org/vishia/util/StringPart. Das lässt sich auch in C gut verwenden, kann/sollte auch in das Beispiel eingebaut werden.

  • Für den Garbage Collector wurde finalize() generiert. Es hat sich eine Lücke herausgestellt: Blöcke werden zwar freigegeben, wenn sie nicht mehr von außen referenziert werden. Aber eine Referenz, die sie selbst nach außen haben, spiegelt sich in den damit referenzierten Blöcken als Rückwärtsreferenz wieder. Dann bleiben die Rückwärtsreferenzen stehen, was ggf. zum Verklemmer führt. Das generierte finalize() setzt alle Referenzen des betreffenden Objektes auf null, damit werden die Rückwärtsreferenzen in den referenzierten Objekten gelöscht. Die Referenzen sind beim Generieren bekannt. Der Garbage Collector ruft vor der Freigabe das finalize auf. Hier ist das erstemal ein dynamisches call (dynamisches Binden) produktiv verwendet. Das ist das erstemal in der Version ../sf/Java2C_080908.zip implementiert.

    Insgesamt muss der Garbage Collector noch etwas verbessert werden, insbesondere für Reportzwecke. Er scheint zu funktionieren, aber ganz sicher sollte man nie sein. Deshalb beobachten. Was fehlt, ist eine Reportausgabe über belegte Blöcke und eine Ausgabe, wann von wem belegt und freigegeben wurde. Dann kann dann leicht verifiziert werden, ob das stimmt.

    Für das finalize() und für Zwecke des GC-Reports/Debugging wurden einige Strukturen im GC geändert. So kann man von einem ObjectJc jetzt den zugehörigen BlockHeapBlockJc debugmäßig erreichen (es gibt in ObjectJc einen Pointer dahin), anders herum auch: Aus BlockHeapBlockJc das zugehörige ObjectJc, um finalize zu rufen.

Sprung nach Anfang Oktober 2009

Selbstverständlich ist dieser Text seit August 2008 nicht mehr gepflegt worden, aber dafür ist viel passiert. Der entscheidende Schub war der Einsatz von Java2C in einer professionellen Lösung: Die javasrc:_org/vishia/util/MsgDispatcher sollte in C++ implementiert werden, den Test der inneren Algorithmen habe ich in Java realisiert. Das Debuggen unter Eclipse ist einfacher, in Java kann man schneller zusätzlichen Testcode einbringen, Fehler beispielsweise in der Indizierung von Arrays oder Positionen in Strings kann man auf sich zurollen lassen, eine von Java erzeugte Exception gibt dann schon die notwendige klare Auskunft über Fehler. Das ist alles ein Stück besser als in C++ oder C, wo man bei einem komplexen Algorithmus bitte alles sorgfältig durchdebuggen sollte, weil ein sonst nicht erkannter Fehler alles durcheinanderbringt. Nun lag schon getesteter Java-code vor, der in C händisch nachzuprogrammieren wäre :-( Der Java2C-Translator war bereits in der Lage mit Strings umzugehen, aber noch nicht voll getestet. Interfaces waren angearbeitet. Also warum nicht den Java-code dem Java2C vorgeben und schauen was raus kommt. Es kam lesbarer C-Code raus. Der Arbeitsaufwand vorhandene Probleme im Java2C nachzuspüren schien einfacher als die Zeit für manuelles C-Programmieren zu opfern. Die String-Verarbeitung musste sicher funktionieren, jedenfalls bezüglich dem, was der MessageDispatcher braucht. Das war Anfang 2009. Detailverbesserungen gibt das dann immer im Laufe der Zeit.

Die Darstellung der Strukturen mit UML war ebenfalls ein entscheidender Sprung. Das Programm wurde zu komplex, schlecht durchschaubar. Das Motto Hauptsache funkt reicht nicht. Umstrukturieren und Dokumentieren!

Die Version 0.87 hat dann erstmalig vollständig das Vererben und Implementieren von Interfaces enthalten. Eine Funktionalität, die für embedded Systeme gar nicht so oft gebraucht wird. Aber wenn man in embedded sonst in C++ arbeitet, nutzt man solche Dinge auch.

Bereits im Januar 2009 hat sich ein Problem herausgestellt: Partielle Übersetzung. Ein Teil des nach C übersetzen Codes ist bereits in Libraries eingebunden (konkret war das javasrc:_org/vishia/util/StringPart), ein anderer Teil gehört zu einem darauf aufbauenden Modul und ist daher in C in einer anderen Library anzusiedeln, konkert der MessageDispatcher. Die Idee mit den stc-Files war schon da, aber es gab Differenzen in der Anordnung der Files und Erkennung der richtigen stc-Files. Daher ist zur Version 0.90 die Packagezuordnung überarbeitet und nun endgültig gelöst.

Beim Test zeigt sich die eine oder andere Seite. Die String-Verkettung sah in C nicht schön aus, funktioniert zwar, aber.... Die Verkettung insgesamt musste auf temporäre Zwischenzeiger aufbauen, weil sonst überladene Methoden nicht richtig funktionieren, Seiteneffekte wenn die zuvor gerufenen Methoden einer Verkettung nicht nur einfache Zugriffe enthalten. Damit war die Stringverarbeitung auch überarbeitungswürdig. Da mittlerweile ein Package java2C.test entstanden ist, neben dem PositionControl-Example, war alles ganz gut testbar.

Was jetzt ansteht ist die Einführung des Multithreadings. Es ist ja alles schon überlegt und vorgetestet, aber noch nicht in Java2C so eingebunden, das es Andere auch nutzen können. Damit muss aber nicht der Übersetzer selbst erweitert werden, sondern die CRuntimeJavalike muss ordentlich angepasst werden. Das ist demnächst zu tun.