Inhalt
Topic:.ZbnfJava.
Das Parsen über ZBNF ist in Java programmiert. Die Javadoc findet sich unter javadoc:_org/vishia/zbnf/ZbnfParser bzw. mit private und src-Angaben unter javadoc-src:_org/vishia/zbnf/ZbnfParser.
Im folgenden Text sind einige TODO notiert als Hinweis für mögliche, empfehlenswerte oder bevorstehende Weiterentwicklungen. Diese sollen abwärtskompatibel ausgeführt werden, um bereits jetzt umfangreich bestehende Anwendungen nicht überarbeiten zu müssen.
Die Programmbeispiele sind der javadoc-src:_org/vishia/zbnf/Zbnf2Xml-Anwendung entnommen und für diese Darstellung etwas vereinfacht worden. Die genannte Quelle kann als Muster benutzt werden.
Topic:.ZbnfJava.
Topic:.ZbnfJava.JavaParserInvoke.
import org.vishia.zbnf.ZbnfParser; ZbnfParser parser = new ZbnfParser(report);
Der Konstruktor übernimmt eine Instanz des javadoc-src:_org/vishia/mainCmd/Report-Interfaces. Eine Instanziierung dieses Interface ist beispielsweise mit javadoc-src:_org/vishia/mainCmd/MainCmd gegeben, oder mit javadoc-src:_org/vishia/mainCmd/MainCmdWin. Das Report-Interface regelt die Ausgabe von Zwischenergebnissen, die gegebenenfalls für eine Fehlersuche interessant sein könnten. Immerhin kann die zu parsende Syntax und Eingabetexte recht komplex sein. Der Report ist aber nicht unbedingt notwendig, soll nur ergänzend helfen. Derzeit kann kein null-Zeiger übergeben werden (TODO: Auch ohne Report-Angabe).
String sFileSyntax = "syntax.zbnf"; try { spSyntax = new StringPartFromFileLines(new File(sFileSyntax), 20000, "encoding=", null); } catch(Exception exception) { writeError("file read error:" + sFileSyntax); bOk = false; } if(bOk) { try { parser.setSyntax(spSyntax); parser.setSkippingComment("/*", "*/", true); parser.reportSyntax(this); } catch (ParseException exception) { writeError("Parser Syntax reading error: " + exception.getMessage()); bOk = false; } }
Die Syntax muss dem ZbnfParser in Form eines javadoc-src:_org/vishia/util/StringPart übergeben werden. Ein StringPart ist eine Erweiterung des String-Konzeptes von Java und enthält selbst Methoden für einfache Parsing-Aktionen. Das Einlesen und Konvertieren der Syntax wird ausschließlich mit den Möglichkeiten des StringPart erledigt. Im Gegensatz zum ZbnfParser arbeitet das Parsen unter Nutzung der StringPart-Klasse nicht interpretativ sondern ist ausprogrammiert und damit schneller.
Die Klasse javadoc-src:_org/vishia/util/StringPartFromFileLines ist von StringPart abgeleitet. Sie ist dafür gedacht, einen StringPart je nach Verarbeitungsfortschritt aus einem textuellen File zu speisen. (TODO: Bisher hat die Puffergröße immer ausgereicht, einen gesamten File auf einmal zu speichern. Die Funktion des Nachschiebens von Inhalt ist hier noch nicht realisiert. Sie wurde von mir schonmal unter C++ implementiert, zu DOS-Zeiten als Nachschiebe-Puffer wirklich notwendig waren. Beim Nachschieben muss man Positionen korrigieren, das muss sorgfältig getestet werden.)
Eine wichtige gegebenenfalls notwendige Funktionalität der Klasse StringPartFromFileLines ist aber die Feststellung der Zeichencodierung
(encoding). In Files werden meist Zeichen mit 8 Bits gespeichert, daher muss man verschiedene Encodings berücksichtigen. Die
Erkennung geschieht jedoch nicht automatisch, das ist grundsätzlich unsicher. Vorgesehen ist, dass in der ersten Zeile, die
selbst entweder Standard-ASCII (US-ASCII) oder UTF-16 ist, die Zeichencodierung im Klartext steht. Wie im Beispiel angegeben
leitet "encoding="
diese Angabe ein. Ohne Encoding-Angabe wird der Fileinhalt in der System-Zeichencodierung gelesen.
Falls die Syntaxvorschrift selbst Syntaxfehler enthält, werden diese wie im Beispiel vorgegeben angezeigt. Dabei kann die message in der ParseException mehrere Zeilen umfassen.
Topic:.ZbnfJava..
Über Aufruf einiger set-Methoden kann der Parser konfiguriert werden. Teilweise sind dieselben Konfigurationsmöglichkeiten
auch in den Steuerbefehlen innerhalb einer Syntaxvorschrift formulierbar. Das sind also zwei Möglichkeiten, um das Selbe zu
erreichen. Die Steuerbefehle wirken innerhalb des Aufrufes von setSyntax()
. Erfolgen die Aufrufe der set-Methoden in Java danach, dann werden die Einstellungen in der Syntaxvorschrift überschrieben.
Topic:.ZbnfJava...
Wie beim Steuerbefehl in der Syntaxvorschrift
$setLinemode.
kann dem Zeichen \n
(Hexa 0a
) seine Rolle als Whitespace zu- oder aberkannt werden. Damit wird ein Zeilenumbruch nicht als Whitespace erkannt. Die
setLinemode(boolean);
-Methode kann den Zeilenmodus einschalten und ausschalten. Defaultmäßig ist er ausgeschaltet (Zeilenumbruch wirkt als Whitespace).
Zum Problem des carrige return: Bei Windows-Texten wird traditionell eine Zeile mit \r\d
(hexa 0d0a
) abgeschlossen. Damit, dass \r
als Whitespace erkannt wird, wird es also am Zeilenende ignoriert, stört also nicht. Auch nicht im Linemode, da das \n
maßgebend ist und vorhanden ist. Bei Macintosh-Texten, die nur \r
als Zeilentrennung verwenden, funktioniert das aber nicht mehr. Dann muss man mit setWhiteSpaces(" \t\f\n")
nachhelfen und so das \r
aus der Menge der Whitespaces entfernen.
Topic:.ZbnfJava...
Mit
setWhiteSpaces(String);
wird festgelegt, welche Zeichen als Whitespaces gelten. Damit können beispielsweise Tabulatoren ausgeschaltet werden. Die
Methode überschreibt vorige Festlegungen von setLinemode()
, da hier unabhängig von vorigen Einstellungen festgelegt wird, ob \n
dabei ist. Die Defaulteinstellung ist " \t\r\f\n"
, also das Leerzeichen, Tabulator, form feed (Hexa 0c
), carrige return (Hexa 0d
) und line feed (hexa 0a
).
Topic:.ZbnfJava...
Mit
setSkippingComment(String commentStart, String commentEnd, boolean bStoreComment);
wird festgelegt, welche Zeichenkettenpaare als Kommentierung verstanden werden und ob Kommentierungen eigenständig im ParserStrore gespeichert werden sollen. Das entspricht der Angabe von
$comment=/*...*/,
in der Syntaxvorschrift (/*
und */
stehen hier als Beispiel, man kann eben Kommentare auch anderes kennzeichnen).
Mit
setSkippingEndlineComment(String commentStart, boolean bStoreComment);
wird selbiges für die Kommentierung bis zum Zeileneende festgelegt, wie bei der Angabe von
$endlineComment=//,
Defaultmäßig sind diese Zeichen wie im Beispiel anggeben. Mit Übergabe einer leeren Zeichenkette kann man die Erkennung von Kommentar ausschalten.
TODO: Bisher wurden die Kommentare nicht im ParserStore gespeichert, es bestand keine Notwendigkeit. Beim Java2C-Translator und beim Parsen von Headerfiles aus C Cheader.zbnf werden javadoc-Kommentare in der Syntax spezifisch berücksichtigt. Das Feature des automatischen Speicherns an sich überlesener Kommentare ist also ggf. nicht notwendig und bisher noch nicht implementiert, ist aber relativ einfach nachzurüsten.
Topic:.ZbnfJava...
Es gibt Fälle, bei denen Informationen weder in der Syntaxvorschrift formuliert werden können noch im zu parsenden Eingangstext stehen, sondern anderweitig beim Aufruf bekannt sind. Beispielsweise ist dass der Filename und Path des zu parsenden Textes. Diese Informationen können beim Aufruf des Parsens als Zusatzargument übergeben werden und erscheinen damit für die Auswertung, zum Beispiel im erzeugten XML-tree, wie ein geparster Bestandteil.
boolean parse(StringPart spToParse, List<String> additionalInfo);
Dabei ist in der List jeweils abwechselnd der erste String ein Semantik-Bezeichner, der folgende String ist der zugehörige Inhalt. Diese Informationen werden als erste Child-Items des Toplevel-Items gespeichert.
Topic:.ZbnfJava..
Eine Instanz des ZbnfParser mit einer Syntax versehen ist in der Lage, beliebig oft verwendet zu werden. Als Input muss dem ZbnfParser wiederum ein StringPart gegenben werden:
StringPart spToParse = null; if(bOk) { try { spToParse = new StringPartFromFile(new File(sFileIn)); } catch(Exception exception) { writeError("file read error:" + sFileIn); bOk = false; } }
Der Aufruf des Parsens selbst erzeugt keine Exception bei einem Parserfehler, sondern wird mit false-Rückkehr beantwortet.
Die Exception im Beispiel ist also ein allgemeiner Auffänger. Im Beispiel ist auch zu sehen, dass das Exception-Handling-Konzept
(try-catch) mit einem bOk-Konzept verwoben ist: Der Gesamtablauf wird gesteuert von einer boolean-Variable bOk
. Solange diese true
bleibt, geht es weiter. Damit sind zwei Ebenen voneinander getrennt und das Programm ist übersichtlicher: Gesamtablauf mit
bOk, Details mit Exceptionhandling. Es wäre aber auch denkbar, entweder alles in ein großes try zu packen oder zwei try-Ebenen zu installieren und bei einem ein catch ein weiteres throw auszulösen.
if(bOk) { try { bOk = parser.parse(spToParse, additionalSemantic); if(!bOk) { report.writeError(parser.getSyntaxErrorReport()); parser.reportStore(report); } } catch(Exception exception) { writeError("any exception while parsing:" + exception.getMessage()); report.report("any exception while parsing", exception); parser.reportStore(report); bOk = false; } }
Bei einem Syntaxfehler des zu parsenden Textes wird mit der oben gezeigten Methode getSyntaxErrorReport()
ein englischer Klartext ausgegeben, der neben der Fehlerstelle im Eingangstext einige Hinweise zum Syntaxfehler enthält.
Ein Syntaxfehler in der Syntaxvorschrift ist eher nicht zu erwartet, da diese einmalig erstellt und dann richtig sein sollte.
Ein Syntaxfehler im Eingangstext ist dagegen ein Normalfall. Das sind die zu übersetzenden Daten und sicherlich zunächst mit
Schreibfehlern, Verwechslungen oder vergessenen Strukturierungselementen versehen. Daher ist die Syntaxfehleranzeige für den
Benutzer wesentlich.
Topic:.ZbnfJava.JavaParserEvaluate.
Parserergebnisse lassen sich in Java auf zweierlei Arten auswerten:
manuelles steppen durch das Parser-Ergebnis. Diese Variante ist geeignet, wenn das Parser-Ergebnis eher sehr umfangreich ist und mittels spezieller Java-Programmierung in einen Output verwandelt werden soll.
Aufruf von setOutput(Object, ZbnfParseResultItem, Report)
in class:_org/vishia/zbnf/ZbnfJavaOutput. Dabei wird das Parser-Ergebnis insgesamt an die gegebene Instanz appliziert. Diese Instanz muss in passender Weise zur Syntax
Elemente enthalten, die das Parser-Ergebnis dann in Java-angemessener Weise speichert. Diese Variante ist geeignet, wenn der
Inhalt des zu parsenden Textes eher übersichtlich ist, etwa ein ini-File oder ähnliches.
Topic:.ZbnfJava.JavaParserEvaluate.ZbnfParseResultItem.
Wenn der Parsing-Prozess erfolgreich durchlaufen wurde, dann ist in der ZbnfParser-Instanz das Parsing-Ergebnis gespeichert. Für den Zugriff ist insgesamt das interface:_org/vishia/zbnf/ZbnfParseResultItem vorgesehen. Dieses Interface ist jeweils mit einem Ergebnis-Eintrag (Item) verbunden. Mit dem Aufruf
ZbnfParseResultItem firstItem = myParser.getFirstParseResult();
wird das Toplevel-ResultItem zurückgeliefert. Es gibt ähnlich wie bei XML genau 1 Toplevel-Item.
Die Items sind in einem Baum (tree) strukturiert. Der Baum entspricht der Syntaxvorschrift. Alle erkannten Elemente einer Einzel-Synaxvorschrift werden in einer Ebene. Passt die Syntax der Syntaxvorschrift zu einer Syntaxkomponente, dann sind die erkannten Elemente dieser Syntaxkomponente eine untergeordnete Ebene, ein child.
In der Beipielsyntaxvorschrift
einkaufsliste::=Einkaufszettel \\n <![=]*?> \\n { <position> \\n }. position::=<#?@menge> [<?@einheit>Stck|x|] <$?text()>.
sind die Elemente, die innerhalb position
erkannt werden, ein child von einkaufsliste
. Diese Anordnung ist wie im Xml, wenn man Zbnf2Xml konvertiert.
Topic:.ZbnfJava.JavaParserEvaluate.ZbnfParseResultItem..
Hat man das toplevel-Element mit
ZbnfParseResultItem firstItem = myParser.getFirstParseResult();
ermittelt, dann gibt es mehrere Möglichkeiten, in die Tiefe des Baums einzudringen. Als Methode von javadoc:org/vishia/zbnf/_ZbnfParseResultItem gibt es die Möglichkeit, mit
ZbnfParseResultItem item = firstItem.firstChild();
das erste Element der nachfolgenden Ebene zu erreichen. Mit diesem Element kann man dann alle Elemente der selben Ebene durchwandern:
item = item.next();
Dieser Aufruf iteriert durch die gegebene Ebene. Um wieder eine Ebene tiefer zu kommen, muss man wieder item.firstChild()
aufrufen. Die next()
-Methode hat aber die Eigenschaft, nicht zu testen, ob eine Child-Ebene verlassen wird. Sie liefert das Folgeelement der Elternebene
am letzten child. Nur am Ende des gesamten Parser-Ergebnisses wird null
zurückgegeben. Die next()
-Methode ist dafür gedacht, das Folgeelement zu ermitteln, wenn in der Syntaxvorschrift geregelt ist, dass es ein solches
gibt.
Es gibt noch eine weiter Herangehensweisen, die in einem allgemein gehaltenen Interface javadoc:_org/vishia/util/SortedTree, von dem javadoc:org/vishia/zbnf/_ZbnfParseResultItem abgeleitet ist, definiert sind:
ZbnfParseResultItem item = itemParent.getChild("name")
sucht das erste Element der untergeordnten Ebene mit der Semantik "name". Diese Methode ist hilfreich, wenn in der Syntaxvorschrift
geregelt ist, dass es ein solches Element geben könnte. Aber es kann beispielsweise in einer Wiederholung mit mehreren Optionen
oder optional an verschiedenen Stellen auftreten. Wird das Element nicht gefunden, dann wird null
zurückgeliefert. Wird diese Methode aufgerufen, dann wird über alle children erst einmal ein Index erstellt. Das ist ein
TreeMap, der die Semantik als key enthält, und eine Liste von Elementen, die dieser Semantik entsprechen. Es können auch mehrere
sein. Das Erstellen dieser TreeMap wird also nur realisiert, wenn ein solcher Zugriff oder einer der weiteren nachfolgend
genannten erfolgt. Das bedeutet zunächst einen kleinen Rechenzeitaufwand zur Bildung dieses TreeMap mit zugehörigen LinkedList,
natürlich im Mikrosekundenbereich an einem Standard-PC. Aber dieser Rechenzeitaufwand macht sich bezahlt beim Folgezugriff,
der dann auf die vorhandene TreeMap mit den LinkedList zugreifen kann. Das ist auch der Fall bei folgendem Befehl:
Iterator<ZbnfParseResultItem> iter = getChildren("name");
Diese Methode liefert den java.utilIterator auf alle Items der angegebenen Semantik.
Der Aufruf von
Iterator<ZbnfParseResultItem> iter = getChildren();
liefert dagegen einen Iterator durch alle untergeordneten Elemente, ohne zuvor ein TreeMap gebildet zu haben. Das ist nicht notwendig, weil hier keine semantische Sortierung verlangt ist.
Damit hat man einige Möglichkeiten der Evaluierung des Parser-Ergebnisses. Es muss nich dargestellt werden, dass in einem Baum Elemente wiederum children haben können, die selben Methoden also rekursiv angewendet werden können.
Lezlich sollen auch die alten, mittlerweile deprecated- Zugriffe dargestellt werden:
ZbnfParseResultItem item = next(parent);
lieferte das nächste Item innerhalb des child und null
am Ende des Childs (im Gegensatz zum parameterlosen next()
), das ist aber durch die Möglichkeit des getChildren("name")
verbessert.
ZbnfParseResultItem item = nextSkipIntoComponent(parent);
war fast das gleiche wie firstChild()
, nur das in firstChild()
nicht mehr getestet wird, ob das parent
überhaupt ein child enthält. Weis man das, ist die Anwendung einfacher. Das ist der häufigere Fall. Ist es unbekannt, sollte
man getChildren()
verwenden.
Topic:.ZbnfJava.JavaParserEvaluate.ZbnfParseResultItem..
Mit der Methode getSemantic()
wird die Semantic des Items als String ausgegeben. Das ist die Semantik, wie sie in der Syntaxvorschrift zu dem gefundenen
Abschnitt angegeben wurde. Der Aufruf von getSemantic()
erübrigt sich natürlich, wenn man mit getChild("Semantic") Elemente einer bestimmten Semantik gesucht hat.
Im interface:org/vishia/zbnf/_ZbnfParseResultItem sind die Methoden getParsedFloat()
, getParsedInteger()
usw. definiert. Ist der Semantik syntaktisch ein Float, Integer usw. zugeordnet, dann liefern die Methoden den Inhalt des
Input-Textes, der zu diesem Abschnitt gehört und im ParseResultItem gespeichert ist. Ein falscher Aufruf liefert den Wert
0. Man kann auch testen, ob das Item ein float, integer usw. beinhaltet, mittels Aufruf von isFloat()
usw. das wird sich aber in den meisten Fällen erübrigen. Ein float ist allgemein eine Fließkommazahl und wird in Java mit
einem double repräsentiert. Ein Integer wird als allgemeine Ganzzahl mit dem Java-long, also im Zahlenbereich bis +/-8000000000000000000
repräsentiert. Für die Weiterverarbeitung kann der Anwender ein entsprechendes cast nach float, int, short usw. ausführen,
wenn die Größenordnung der Zahl das zulässt.
Der Aufruf von getParsedString()
liefert den dem Item zugeordneten String, falls das Item einen geparsten String repräsentiert, ansonsten null. Dagegen liefert
getParsedText()
immer einen String, und zwar genau der Input-Text des Abschnittes, unabhängig von der Art des Items und einschließlich aller
gegebenenfalls überlesenen Kommentare und Leerzeichen, die diesem Abschnitt zugeordnet sind.
Topic:.ZbnfJava.ZbnfJavaOutput.
Es ist möglich, mit Aufruf einer Methode das Ergebnis des Parse-Prozesses insgesamt in eine Java-Instanz und entsprechenden Sub-Instanzen abzulegen. Das ist dann zweckmäßig, wenn nicht erst eine Betrachtung einzelner Parse-Ergebnisse erfolgen soll sondern der gesamte Inhalt so wie geparst zur Weiterverarbeitung gleichzeitig anstehen soll. Das ist vergleichbar mit dem DOM-Modell in XML gegenüber dem SAX-Modell. Diese Arbeitsweise ist jedenfalls geeeignet, wenn es um überschaubare Daten, etwa aus einem ini-File geht.
Der Anwender muss hierzu eine class-Struktur bereitstellen, die den Semantik-Angaben in der Syntaxvorschrift des ZBNF-Parsers folgt. Eine Instanz dieser class und das Parser-Ergebnis werden dann der statischen methode ZbnfJavaOutput.setContent(java.lang.Class, java.lang.Object, org.vishia.zbnf.ZbnfParseResultItem) übergeben. Die Zuordnung des Parser-Ergebnisses zu den einzelnen Elementen, Sub-classes und deren Elementen erfolgt unter Nutzung der Reflection.
Die entsprechende Java-class ist class:_org/vishia/zbnf/ZbnfJavaOutput
private
-view: class-src:_org/vishia/zbnf/ZbnfJavaOutput
Topic:.ZbnfJava.ZbnfJavaOutput..
Ist ein Parser-Ergebnis-Element mit einer bestimmten Semantik vorhanden, gibt es dafür aber weder ein Field an entsprechender
Stelle noch wird eine Methode gefunden, dann wird im Standardfall eine IllegalArgumentException
erzeugt mit einem entsprechend langen Hinweistext. Die Nichtübereinstimmung kann gegebenenfalls ein Versionskonflikt oder
bei der Programmentwicklung ein Schreibfehler sein. Würde man diessen Fehler ignorieren, dann kann es unerkannte Datenverluste
mit Folgefehlern geben, die erst im Test zu finden sind.
Dieses Standardverhalten kann man abschalten mit ZbnfJavaOutput.setWeakErrors(boolean). Das Abschalten macht dann Sinn, wenn während der Programmentwicklung noch nicht alles abgestimmt ist. Wird keine Exception
erzeugt, dann wird nicht abgebrochen, weitere Werte werden dann gegebenenfalls zugewiesen, auf die man sich zunächst konzentrieren
möchte. Die methode setContent(...)
liefert als Ergebnis entweder null
, wenn es keinen Fehler gab, oder einen gegebenfalls mehrzeiligen Fehlertext.
Topic:.ZbnfJava.ZbnfJavaOutput..
Grundsätzlich gibt es zwei Möglichkeiten, die Parser-Ergebnisse zuzuordnen:
a) Es müssen public-Fields mit dem Namen, der der Semantic im ZBNF entspricht, vorhanden sein. Die Attribute werden mit den Parser-Ergebissen gesetzt.
b) Es müssen public-Methoden mit dem Namen get_semantic
vorhanden sein, die mit den Parser-Ergebnissen als Parameter aufgerufen werden.
Die zweite Möglichkeit entspricht eher der Idee der Kapselung in der ObjectOrientierten Programmierung und bietet weitgehende Möglichkeiten der Verarbeitung der Werte. Die Variante a) ist aber mit weniger Aufwand programmierbar und soll daher zunächst betrachtet werden.
Topic:.ZbnfJava.ZbnfJavaOutput...
Der Name der Fields soll der Bezeichnung der Semantic entsprechen. Dabei wird aber die in Java übliche Kleinschreibung des Namens berücksichtigt.
Ist der erste Buchstabe der Semantik ein Großbuchstabe, muss der Name des Fields dennoch mit einem Kleinbuchstaben anfangen.
Aufgrund der Möglichkeit der Erzeugung eines XML-Baumes als Parser-Output und der daran angelegten Möglichkeit der Schreibweise der Semantik gibt es noch einige Besonderheiten:
Der Semantik-Bezeichner vor dem /
ist der Name einer aufnehmenden Instanz, in die dann entsprechend dem Teil des Semantik-Bezeichner nach dem /
eingeschrieben wird. Damit ist auch hier ähnlich wie in XML die Bündelung von Daten möglich.
Ein @
am Anfang eines Bezeichners, der in XML zum Erzeugen eines Attributes statt eines Elementes führt, wird hier ignoriert.
Ein -
innerhalb eines Bezeichners wird in einen Unterstrich _
gewandelt. Das -
innerhalb von Bezeichnern ist in XML eine übliche Schreibweise, die aber nicht in Java-Bezeichnern funktioniert.
Alle anderen Zeichen müssen in der Syntax so angegeben sein, dass es gültige Java-Bezeichner sind. Ansonsten ist eine Zuordnung nicht möglich.
Konflikte der Doppelnennung beispielsweise an-example
und an_example
sind im ZBNF-Syntaxscript zu vermeiden und könnten auch anderweitig weniger günstig sein.
Topic:.ZbnfJava.ZbnfJavaOutput...
Die Fields müssen public
sein. Sonst ist ein Zugriff darauf über Reflection nicht möglich. Der Reflection-Zugriff erfolgt von außen, auch hier greift
eine private
oder protected
-Kapselung und würde diesen Zugriff verbieten. Es stellt sich die Frage: Öffnet nicht eine ..public-Deklaration alle Türen für eine unkalkulierbare Änderung der Werte außerhalb des eigenen Programmcodes.
Die eine Hälfte der Antwort ist: Nein. Denn: Die Instanz, die die Werte aus dem Parser-Ergebnis aufnimmt, kann und sollte selbst gekapselt sein. Eine Änderung
der Werte einer beliebigen global bekannten Instanz wäre möglich aufgrund der public
-Definition ihrer Attribute, nicht aber die Änderung der konkret gegebenfalls nur private
bekannten Instanz, die der Methode ZbnfJavaOutput.setContent(java.lang.Class, java.lang.Object, org.vishia.zbnf.ZbnfParseResultItem) ganz privat und incognito mitgeteilt wird. Also, kein Aufbrechen einer gewünschten Kapselung.
Die zweite Hältfe der Antwort ist: Softwarepflege ist erschwert. Bei einer Umbenennung oder funktionalen Änderung von Attributen, die private
sind, braucht nur der bekannte Kontext beachtet zu werden. Bei public
-Attributen ist es grundsätzlich nicht vorhersagbar, wo diese Attribute in anderer Software noch verwendet werden. Sie werden
verwendet, als Semantik-Bezeichner in ZBNF-Scripts. Das muss beachtet werden und spricht gegebenfalls gegen die Attribute-Variante.
Topic:.ZbnfJava.ZbnfJavaOutput...
Für einfache Semantiken muss der Typ der Attribute mit der Syntax des zugehörigen Elementes in der ZBNF übereinstimmen. Wird
zum Beispiel geparst: <#?value>
, dann ist das Parser-Ergebnis an dieser Stelle mit der Semantik value
bezeichnet ein Integer-Wert. Der Parser speichert einen long-Wert, aber die Zuweisung zu einer int
- oder float
-Variablen wird ebenfalls unterstüzt. Alle nicht numerischen Werte werden als java.lang.String
gespeichert.
Ist die Semantik einer Syntaxkomponente zugeordnet, dann muss das Parser-Ergebnis der Syntaxkomponente in die Instanz mit dem Namen der Semantik abgelegt werden können. Ist beispielsweise im ZBNF-Script notiert:
BillOfMaterial::= ...<position>... position::= <#?amount> <#?code> <16*?description> <*\r\n?value> .
dann muss es ein Attribut position
geben. Dessen Typ muss aber die in position
geparsten Elemente aufnehmen können. Der Typ von position
muss also speziell passend sein. Allerdings kann dieser Typ nicht nur über Attribute zuordenbar sein, sondern auch über Methoden,
es kann sich auch um einen Interfacetyp handeln. Der Typ muss public
definiert sein, an beliebiger Stelle im Java-Code.
Wenn ein entsprechendes Attribute vorhanden, aber mit null
besetzt ist, dann wird der Typ des Attributes als Instanz erzeugt und zugeordnet, bevor die Weiterverarbeitung erfolgt. Das
geht nicht bei Interface-Typen und ist nicht passend wenn aus anderen Gründen ein abgeleiteter Typ benötigt wird. Will man
das, so muss man Methoden für die Parser-Ergebniszuordnung wählen. Die Zuordnung zu Attributen ist die einfache Variante.
Topic:.ZbnfJava.ZbnfJavaOutput...
Insbesondere für Wiederholungs-Konstrukte in der ZBNF {...}
sollen einem Attribute mehrere Exemplare von Parser-Ergebnissen zuordenbar sein. Das ist möglich, wenn das Attribute eine
java.util.List
ist. Unterstützt wird hierbei eine java.util.LinkedList
und java.util.ArrayList
. Ist das Attribut vom Typ List
aber mit null
vorbesetzt, dann wird bei der ersten Benutzung eine LinkedList
intialisiert. Braucht man eine ArrayList
, dann muss man vorher initialisieren.
Bei jedem Parser-Ergebnis, das diesem Attribut zugehordent werden soll, wird dann ein Element der List
erzeugt und an die List
angehangen (List.add(Object)
). Der Typ des Elementes richtet sich dabei nach dem generischem Typ der List
. Hier gilt gleiches wie bei einfachen Attributen: Ist ein Interfacetyp angegeben oder ein abgeleiteter Typ notwendig, dann
geht das nicht mit der einfacheren Attribut-Variante, sondern es muss eine Parser-Ergebniszuordnung über Methoden erfolgen.
Allerdings, für viele Fälle ist der einfache Weg gangbar und schneller ausprogrammierbar.
Topic:.ZbnfJava.ZbnfJavaOutput...
Das Beispiel BillOfMaterial
ist der Veröffentlichung bei Sourceforge http://www.sf.net/projects/ZBNF entnommen. Die Syntax dieser Stückliste ist definiert in examples:_billOfMaterial.zbnf. Die class class:_org/vishia/zbnf/example/billOfMaterial/BillOfMaterialZbnf_Fields ist diejenige, die als primäre Instanz zur Aufnahme der Daten bereitgestellt wird. Dort finden sich entsprechend dem Toplevel-Syntaxprescript
die Variablen order
, position
und date
. Alle drei Variable sind Referenzen zu classes, die die Variablen entsprechend der Sub-Syntax enthalten, beziehungsweise
im Fall order
die Variablen, die aufgrund der Semantic-Angabe notwendig sind.
Das Field position
ist als List<BillOfMaterialData.Position>
angelegt. Folglich wird bei jedem Auftreten einer Syntaxkomponente mit der Semantic position
eine Instanz von BillOfMaterialData_Fields.Position
erzeugt, in die Liste eingehängt und mit den Inhalten der Syntaxkomponente gefüllt.
Die class-Typen sind an anderer Stelle definiert, in class:_org/vishia/zbnf/example/billOfMaterial/BillOfMaterialData_Fields, enthalten aber dort die der Semantic entsprechenden weiteren Fields.
Topic:.ZbnfJava.ZbnfJavaOutput..
Im Gegensatz der Zuordnung zu Fields ist die Zuordnung mit Methoden mehr programmieraufwändig, schafft aber Möglichkeiten der Verarbeitung und Strukturierung der Daten unmittelbar bei der Zuordnung aber unabhängig von der ZBNF-Syntax.
Topic:.ZbnfJava.ZbnfJavaOutput...
Um Parser-Ergebnisse, die nicht Komponenten sind, zuzuordnen, sind set_semantic(type)
- oder add_---
Methoden erforderlich. Im Namen ist genau die Semantik des Parser-Ergebniselementes anzugeben, einschließlich Groß- oder Kleinschreibung.
Der Argument-type
der Methode muss dem Datentyp des Parser-Ergebniselementes entsprechen, wobei hier Varianten möglich sind:
Ein numerischer Parser-Ergebnis-Wert kann wahlweise im Format int, long, double oder float übernommen werden.
Eine argumentlose Methode wird ebenfalls akzeptiert. Hierbei wird kein Wert gespeichert, sondern nur bei Auftretens des Parserergebnisses diese Methode gerufen.
Alle anderen Nicht-Komponenten-Werte werden als String
übergeben.
Um Komponenten als Parser-Ergebnisse zuzuordnen, wird wie folgt vorgegangen:
Zunächst wird eine new_semantic()
-Methode mit der Semantik der Komponente aufgesucht und gerufen. Der Rückgabewert ist eine Instanz, in die der Inhalt der
Komponente eingeschrieben wird. In der new_semantic()
-Methode kann der Anwender entweder eine neue Instanz erzeugen (also tatsächlich ein new
aufrufen), oder eine bestehende bereits angelegte Instanz zurückliefern. Es ist auch möglich, Instanzen wiederzuverwenden.
In dieser Methode kann also eine bestehende Instanz initialisiert zurückgegeben werden, die das Parser-Ergebnis der Komponente
nur temporär aufnimmt.
Das Einschreiben des Inhaltes der Komponente erfolgt in die gelieferte Instanz. Als Typ wird der Return-Typ der new_
-Methode verwendet. Das Einschreiben kann entweder mit Attributen oder Methoden oder gemischt erfolgen.
Nachdem die Inhalte in die mit new_semantic()
gelieferte Instanz eingeschrieben worden sind, wird eine methode set_semantic(Type)
oder add_semantic(Type)
aufgerufen. Die Methode muss als Parameter den Typ akzeptieren, der bei new_semantic()
geliefert wurde. add_
oder set_
wird beides aktzeptiert, der Anwender hat hier einen Bezeichnungs-Gestaltungsspielraum. add_
suggeriert eher einen Containertyp.
Topic:.ZbnfJava.ZbnfJavaOutput...
Die Methoden lassen sich über Interfaces auch aus der class ZbnfJavaOutput ansprechen. Damit ist eine größtmögliche Trennung (Dependency break) zwischen der Zbnf-Syntax und implementierenden Klassen für die Daten möglich, entsprechend den Regeln der ObjektOrientierten Programmierung.
Im Test lassen sich in den Methoden Breakpoints setzen, Zusatzanweisungen schreiben oder eine Überwachung mit Aspektorientierter
Programmierung unterbringen. Damit ist leichter nachvollziehbar und testbar, welche Daten über das ZbnfJavaOutput
erzeugt werden.
In den Methoden können Parser-Ergebnisse entweder weiter verarbeitet oder anders strukturiert werden.
Beispiel: ZBNF-Komponenten sollen letzlich in einer Map
abrufbereit sein, sortiert nach einem Schlüssel, der einem Element der Komponente entspricht. Das ist mit wenig Aufwand in
der add_semantic(...)
-Methode realisierbar, da die Instanz alle Informationen enthält und beliebige Elemente der Anwenderklasse angesprochen werden
können.
Beispiel: Die Anordnung der Parser-Ergebnisse soll ganz anders erfolgen, in Anwenderklassen, die unabhängig vom Parser geschrieben
worden sind und auch in anderen Kontexten benötigt werden. Hier können die Parser-Ergebnisse komponentenweise erst nur zwischensgepeichert
werden. Dazu kann man auch die einfache Zuordnung zu Fields verwenden. Die add_semantic(...)
-Methode holt dann diese zwischengespeicherten Ergebnisse aus der temporären Instanz und ordnet sie so an, wie sie letztlich
benötigt werden.
Topic:.ZbnfJava.ZbnfJavaOutput...
Es werden Interfaces für die jeweiligen einzelnen Syntaxvorschriften erstellt, die dort erzeugte Semantik muss entgegengenommen werden. Diese Interfaces müssen also direkt dem ZBNF-Script folgen. Inhaltlich, wie die Daten zu verarbeiten sind, wird dabei nichts festgelegt.
Es wird das Konzept der inhaltlichen Verarbeitung der Daten unabhängig von den Interfaces und dem ZBNF-Script erstellt bzw. betrachtet.
Die Interfaces werden formell der Implementierung zugeführt. Arbeitet man beispielsweise unter Eclipse, dann ist das unter
Zuhilfenahme der IDE-Bedieneigenschaften sehr einfach: implements Interface
an die implementierende class
anhängen und über quick fix die Methoden zunächst leer erzeugen lassen.
Die implementierenden Methoden werden mit Inhalten gefüllt und getestet. Man hat dabei alle Gestaltungsfreiheiten unabhängig vom ZBNF-Script.
Topic:.ZbnfJava.ZbnfJavaOutput...
Das Beispiel BillOfMaterial
der Veröffentlichung bei Sourceforge http://www.sf.net/projects/ZBNF enthält auch eine Methoden-Variante, konsequenterweise mit Nutzung von Interfaces. Die Syntax dieser Stückliste ist definiert
in examples:_billOfMaterial.zbnf. Das interface:_org/vishia/zbnf/example/billOfMaterial/BillOfMaterial_Zbnf_ifc enthält als Rahmen alle Interfaces zur Ablage von Informationen aus ZBNF-Komponenten. Die primäre Instanz implementiert interface:_org/vishia/zbnf/example/billOfMaterial/BillOfMaterial_Zbnf_ifc.ZbnfStore_BillOfMaterial und ist instanziiert als class:_org/vishia/zbnf/example/billOfMaterial/BillofMaterialData_Methods. Die Attribute sind hier private, sihe auch javadoc-src:_org/vishia/zbnf/example/billOfMaterial/BillofMaterialData_Methods.
Topic:.ZbnfJava.ZbnfJavaOutput..
In bestimmten Fällen sollen die Positionen des Parser-Inputs bekannt sein, weil in der Verarbeitung darauf bezug genommen werden soll. Beispielsweise wenn ein Formular geparst wird, und eine Anordnung in der Zeile wesentlich ist. Die Input-Position wird vom Parser gespeichert, wenn TODO
Um diese Input-Position im Java-Output zu speichern, gibt es sowohl für die Speicherung in Fields als auch mit Methoden folgende Möglichkeiten:
Ein Field inputColumn_
speichert die Position der jeweiligen Komponente in der Instanz der Komponente.
Ein Field inputColumn_semantic
speichert die Position eines Elementes.
Eine Methode set_inputColumn_(int)
dient dem Speichern der Position der jeweiligen Komponente in der Instanz der Komponente.
Eine Methode set_inputColumn_semantic(int)
dient dem Speichern der Position eines Elementes.
Die Regel ist: Wird ein solches Field oder eine solche Methode gefunden, wird die Position gespeichert, ansonsten nicht. Es erfolgt keine Fehlermeldung, wenn eine Position vorhanden ist aber nicht eines dieser Elemente gefunden wird.
Topic:.ZbnfJava.ZbnfJavaOutput..
Eine [<?semantic> option1| option2...]
ist eine ZBNF-Komponente. Sie hat die Besonderheit, dass der geparste Text ihr zusätzlich zugewiesen wird, neben Details
der optionalen Syntaxbestandteile. Dieser geparste Text wird in eine String-Variable geschrieben oder einer set_sematic(String)
oder add_sematic(String)
-Methode übergeben, wenn diese vorhanden ist. Das kann zusätzlich zur Übergabe des Inhalt der Komponente oder alternativ erfolgen.
Die Übergabe als String ist dann wesentlich, insbesondere dann wenn feste Bezeichnungen als Option syntaktisch zulässig sind.
Beispielsweise [<?unit> second | millisec | ms | µs | ms ]
.
Topic:.ZbnfJava.ZbnfJavaOutput..
Um ein erfolgreich geparstes Ergebnis abzulegen, ist folgende Aufrufreihenfolge günstig:
ZbnfJavaOutput javaOutput = new ZbnfJavaOutput(report); javaOutput.setMethodsOnly(true); javaOutput.setContent(Destination.class, destination, zbnfParser.getFirstParseResult());
Es wird eine Instanz angelegt. Es können bestimmte Einstellungen vorgenommen werden. Dann wird die Kern-Routine aufgerufen.
Hierbei ist Destination
die class
oder das interface
, in der die Elemente zur Aufnahme des Toplevel-Parserergebnisses definiert sind. destination
ist eine zugehörige Instanz. zbnfParser
ist die class:_org/vishia/zbnf/ZbnfParser-Instanz. Alle Exceptions entstehen dann, wenn etwas mit den aufnehmenden classes oder Intanzen nicht stimmt und sind Programmierfehler.
D.h. ein spezielles Abfangen der Exceptions ist nicht zweckmäßig und notwendig.
Man kann ohne Anlegen einer eigenen Instanz arbeiten, in dem statische Methoden aufgerufen werden. Es stehen mehrere Varianten zur Verfügung. Hierbei wird das Anlegen einer Instanz intern ausgeführt.
Die class:_org/vishia/zbnf/ZbnfJavaOutput enthält auch den Aufruf des gesamten Parsers einschließlich Ablage des Ergebnisses in eine Anwenderinstanz. Dann hat man alles mit einer Befehlszeile. Das kann zweckmäßig sein beispielsweise für Konfigurationsfiles:
String sError = ZbnfJavaOutput.parseFileAndFillJavaObject(destination.getClass(), destination, fileCfg, fileSyntax, report, 0); if(sError != null) { throw new ParseException("Error reading config file" + sError, 0); }
Diese Methode ist so gebaut, dass keine Exceptions erzeugt werden sondern ein Fehlertext zurückgeliefert wird. Dieser ist
null
, wenn kein Fehler auftrat. Ein Fehler kann sowohl in der Syntax des Parsers vorhanden sein, beim Parsen entstehen (das ist
erwartbar, der Input-Text kann falsch sein) oder bei der Ablage der Daten. Alle drei Fälle werden hier in der Weiterverarbeitung
nicht unterschieden. Zweckmäßig ist eine Fehleranzeige für den Anwender, für Eingabetextfehler. Das in der Syntax oder bei
der Ablage Fehler entstehen können,ist vorher durch Test zu minimieren. Eine Restwahrscheinlichkeit wird dann ebenfalls angezeigt.
Um ein erfolgreich geparstes Ergebnis abzulegen, ist folgende Aufrufreihenfolge günstig:
ZbnfJavaOutput javaOutput = new ZbnfJavaOutput(report); javaOutput.setMethodsOnly(true); javaOutput.setContent(Destination.class, destination, zbnfParser.getFirstParseResult());
Es wird eine Instanz angelegt. Es können bestimmte Einstellungen vorgenommen werden. Dann wird die Kern-Routine aufgerufen.
Hierbei ist Destination
die class
oder das interface
, in der die Elemente zur Aufnahme des Toplevel-Parserergebnisses definiert sind. destination
ist eine zugehörige Instanz. zbnfParser
ist die class:_org/vishia/zbnf/ZbnfParser-Instanz. Alle Exceptions entstehen dann, wenn etwas mit den aufnehmenden classes oder Intanzen nicht stimmt und sind Programmierfehler.
D.h. ein spezielles Abfangen der Exceptions ist nicht zweckmäßig und notwendig.
Man kann ohne Anlegen einer eigenen Instanz arbeiten, in dem statische Methoden aufgerufen werden. Es stehen mehrere Varianten zur Verfügung. Hierbei wird das Anlegen einer Instanz intern ausgeführt.
Die class:_org/vishia/zbnf/ZbnfJavaOutput enthält auch den Aufruf des gesamten Parsers einschließlich Ablage des Ergebnisses in eine Anwenderinstanz. Dann hat man alles mit einer Befehlszeile. Das kann zweckmäßig sein beispielsweise für Konfigurationsfiles:
String sError = ZbnfJavaOutput.parseFileAndFillJavaObject(destination.getClass(), destination, fileCfg, fileSyntax, report, 0); if(sError != null) { throw new ParseException("Error reading config file" + sError, 0); }
Diese Methode ist so gebaut, dass keine Exceptions erzeugt werden sondern ein Fehlertext zurückgeliefert wird. Dieser ist
null
, wenn kein Fehler auftrat. Ein Fehler kann sowohl in der Syntax des Parsers vorhanden sein, beim Parsen entstehen (das ist
erwartbar, der Input-Text kann falsch sein) oder bei der Ablage der Daten. Alle drei Fälle werden hier in der Weiterverarbeitung
nicht unterschieden. Zweckmäßig ist eine Fehleranzeige für den Anwender, für Eingabetextfehler. Das in der Syntax oder bei
der Ablage Fehler entstehen können,ist vorher durch Test zu minimieren. Eine Restwahrscheinlichkeit wird dann ebenfalls angezeigt.