vishia: SBNF - Semantic Backus-Naur-Format


Das SBNF-Format - Aufbau eines Syntaxausdruckes

Das BNF, Backus-Naur-Format (=>Wikipedia) ist enstanden mit der Entwicklung von Algol anfangs der 60-ger Jahre und stellte einen wichtigen Meilenstein in der Entwicklung der Softwaretechnologie dar. Mit BNF war es das erste mal möglich, die Syntax von Programmanweisungen exakt zu beschreiben.

BNF hat eine vielfältige Weiterentwicklung genommen, ausschlagsgebend war es unter anderem für die Entwicklung der Programmiersprache PASCAL in den 80-gern in der Form EBNF. Bekannt ist die Syntaxschreibweise ähnlich BNF beispielsweise in der Angabe der Syntax von Kommandozeilenparamtern, am bekanntesten sind dabei die Optionsklammern [...] und in diesem Sinne gängige Praxis.

Das BNF ist nicht eindeutig standardisiert, es sind verschiedene Varianten in Gebrauch. Dieses Format wird auch eher zur dokumentatischen Beschreibung von Syntax verwendet. Kursivschrift und ähnliche Auszeichnungen in textuellen Beschreibungen sind zwar gut leserlich für die Dokumentation einer Syntax, aber kaum automatisiert verwertbar. In BNF fehlt der semantische Aspekt. Die Semantik wird zumeist anhand der eindeutig beschriebenen Syntax verbal erläutert. Für die automatisierte Verarbeitung ist aber insbesondere die Semantik wichtig, um zu erkennen, was eine Information (eine Zahl, eine Zeichenkette) in einem bestimmten Kontext bedeutet.

Die SBNF erweitert die BNF um den semantischen Aspekt. Dazu ist die Syntax der SBNF eindeutig definiert und um einige Aspekete erweitert.

Die Stärke der SBNF besteht letztlich darin, automatisiert verarbeitet zu werden, und zwar sowohl für das Parsen von Texten im freiem (aber syntaktisch beschreibbaren) Format für die Verarbeitung innerhalb eines Java - Programmes, als auch zur Konvertierung solcher frei formatierter Texte nach XML, um die damit gewonnenen Informationen der vielfältigen Möglichkeiten der Behandlung in XML zuzuführen.

Grundform einer Syntaxdefinition in SBNF

syntaxident::=syntaxprescript.

syntaxident ist der Bezeichner, der als Symbol für die definierte Syntax in anderen Syntaxvorschriften verwendet werden kann. syntaxprescript ist die gesamte Syntaxvorschrift. Der Punkt bezeichnet das Ende. In den folgenden Darstellungen der Syntax, die nicht SBNF selbst sind, wird kursive Monospaced-Schrift verwendet, wenn an dieser Stelle ein Bezeichner (wie in C oder Java Variablenbezeichner) steht, kursive Proportionalschrift wird für ein Nicht-Terminal-Symbol, eine Syntaxkomponente verwendet, die einen komplexeren Aufbau hat. Alle anderen Zeichen werden so notiert, wie sie geschrieben werden müssen, in nichtkursiver Monospaced-Schrift.

In SBNF selbst geschrieben würde die oben angegeben Syntax wie folgt aussehen:

SBNF::=<$?syntaxident>::=<syntaxprescript>\.

Das bedeutet: Eine SBNF besteht aus einem Bezeichner, der semantisch einen "syntaxident" darstellt, gefolgt ohne Leerzeichen von 3 zusammenhängen Zeichen ':' ':' und '='. Darauf folgt ein komplexer Ausdruck, der mit dem Bezeichner und gleichzeitig der Semantik "syntaxprescript" an anderer Stelle definiert ist. Der Abschluss bildet das Zeichen '.'.

Terminalsymbole

Terminalsymbole sind diejenigen Zeichen der Syntaxvorschrift, die genauso erwartet werden wie vorgeschrieben. Das sind die Schlüsselwörter und -zeichen der Texterkennung. In der BNF gibt es die Variante, bei der die Terminalsymbole in Anführungszeichen geschrieben werden. Das ist in SBNF nicht so, Terminalsymbole werden direkt notiert. Der Konflikt mit denjenigen Zeichen [ ] | { } < > ? . die für die Syntaxsteuerung selbst benötigt werden, wird mit einem "\"als Umschaltzeichen gelöst. Wird die "[" als Terminalsymbol benötigt, ist also "\[" zu schreiben usw. Das "\" ist wie in Stringliteralen in Java und C/C++ üblich auch Einleitungszeichen für die Steuerzeichen \n \r \b \t \f mit der Bedeutung Newline (0x0a), Carrige Return (0x0d), Backspace (0x08), Tabulator (0x09), Formfeed (0x0c). Diese Zeichen außer Backspace dienen häufig zur Gliederung von formatgebundenen Texten und sind daher wichtig als Terminalsymbole. Der Backslash selbst wird mit "\\" codiert. Die Folge "\s" steht für ein Whitespace-Zeichen in der Zeile, aber nicht für einen Zeilenumbruch (wie in Regular Expressions unter Unix). Ein "\" gefolgt von einem Leerzeichen steht für ein einzelnes Leerzeichen als Terminalsymbol.

Beliebige Unicode-Zeichen werden mit \#xxxx codiert, wobei xxxx die 4-stellige Hexa-Codierung der UFT16-Tabelle darstellt. Diese Art der Darstellung kann als sicher bezeichnet werden, wenn es darum geht, Zeichen außerhalb des 7bit breiten Standard-ASCII (US-ASCII) zu codieren. Direkt notierte Zeichen außerhalb des 7-bit-ASCII werden dann richtig erkannt, wenn die Codierung beim Einlesen innerhalb der Verarbeitung in Java der gewünschten Codierung entspricht. Man kann sich aber nicht darauf verlassen, dass auch ein Texteditor genau die selbe Codierung hat. Daher muss es entweder Vereinbarungen geben, beispielsweise Benutzung des unter Windows üblichen Zeichensatzes ISO-8859-1, oder man vermeidet die direkte Benutzung der Zeichen außerhalb des 7bit breiten Standard-ASCII und umschreibt diese.

Beispiele:

Eine Zahl = <#> Es wird die Zeichenfolge "Eine", gefolgt von "Zahl", gefolgt von einem "=" erwartet. Zwischen diesen Symbolen dürfen Whitespaces stehen, auch Zeilenumbrüche, wenn beim Parsen Whitespace aktiviert ist. Ansonsten darf kein Leerzeichen dazwischenstehen (!). <#> steht syntaktisch für eine positive Ganzzahl.
Eine\ Zahl\ = <#> Es wird eine Zeichenfolge "Eine Zahl =" erwartet, und zwar mit genau einem Leerzeichen dazwischen, unabhängig davon ob Whitespace aktiviert ist oder nicht. Danach muss die Zahl folgen, und zwar mit einem Abstand von beliebig vielen Whitespaces, oder keinem Zwischenraum, je nach dem Whitespace-Mode.
Eine\sZahl\s=\s<#>\s\n Hier wird Whitespace als syntaktisches Element vorgeschrieben. Eine Zeilenumbruch darf aber nicht vorkommen. Nach der Zahl muss aber, nach möglichen Whitespaces, ein Umbruch kommen. Das ist unabhängig von der Wahl parsen mit oder ohne Whitespaces.
\[ <#> \] Hier muss eine Zahl zwischen [ ] stehen.
   

Whitespaces und Kommentare in der Syntaxvorschrift

Für das Einlesen der Syntaxvorschrift gilt in jedem Fall eine Whitespace-Erkennung: Zwischen den Syntaxelementen können beliebige Leerzeichen oder Zeilenumbrüche stehen, oder auch keine, ohne dass das eine Auswirkung auf die Syntaxvorschrift selbst hat.

Es ist eine Kommentierung möglich. Kommentare werden mit zwei aufeinanderfolgenden Zeichen ## außerhalb einer Angabe in < > und außerhalb der Umschreibung mit \# eingeleitet und gelten bis zum Zeilenende. In < > hat das # die beschriebene Bedeutung und darf nicht mit \# umschrieben werden.

Whitespace- und Kommentar-Verarbeitung

Wie schon im Beispiel angedeutet gibt es beim Parsen zwei verschiedene Modis:

Das Ignorieren von Whitespaces macht für viele Parsing-Aufgaben Sinn, es hängt ab von der Aufgabenstellung und der dementsprechenden Gestaltung der Inputfiles. Für festformatierte Texte ist allerdings das Nichtignorieren der Whitespaces unter Umständen die richtige Wahl. Insbesondere dann, wenn Daten zeilenorientiert abgelegt sind.

Wird ohne Whitespace-Ignorieren geparsed, dann müssen alle zulässigen Leerzeichen, Zeilenumbrüche, Tabulatoren in der Syntaxvorschrift explizit erwähnt werden. Dazu können die Umschreibungen "\ " "\t" "\n" "\s" benutzt werden, wie oben dargestellt.

Wird mit Whitespace-Ignorieren geparsed, dann wird immer zuerst getestet, ob der Input mit der Syntaxvorschrift übereinstimmt, bevor Whitespaces oder Kommentare überlesen werden. Nur wenn dies nicht der Fall ist, wird der anstehende Input als Whitespace oder Kommentar überlesen, und zwar entweder nur Whitespaces oder nur Kommentare oder nur sogenannte Endline-Kommentare. Danach wird immer wieder zunächst die Übereinstimmung mit der Syntaxvorschrift getestet, bevor mit Überlesen von Whitespaces oder Kommentaren fortgesetzt wird. Das hat den Vorteil, dass Whitespaces oder Kommentare auch in der Syntaxvorschrift ausgewertet werden können. Sie werden nur dann ignoriert, wenn sie nicht ausgewertet wurden. Damit ist es möglich, das bestimmte Informationen, die in den Kommentaren stehen, dennoch erkannt werden, obwohl Kommentare im allgemeinen überlesen werden. Beispiel dafür ist die Erkennung von Javadoc-like Informationen. Diese Eigenschaft wird im Parsen von Headerfiles mit CHeader.sbnf ausgenutzt.

Die Einstellung, welche Whitespace-Zeichen und welche Kommentare überlesen werden, ist am Kopf der Syntaxvorschrift mit den Steuervariablen $Whitespaces, $CommentString und $EndlineCommentString einstellbar. Defaultmäßig sind hier die Konventionen eingestellt, die für Java und C++ gelten, das heißt

$Whitespaces=\ \r\n\t.
$CommentString=/*?*/.
$EndlineCommentString=//.

Beim $Commentstring wird vor dem ? die einleitende und nach dem ? die ausleitende Zeichenkette für Kommentare angegeben. Wird beispielsweise ein Kommentar in (?...?) geschrieben, dann ist in diesem Fall zu notieren:

$CommentString=(\??\?).

Alternative ...|...

Die Alternative war das einzige Syntax-Steuerzeichen der alten BNF aus den 60-ger Jahren. Diese baute nur auf Alternativen und die Rekursion auf. Die Alternative ist kaum mehr nötig um zu definieren:

ZifferUngleichNull::=1|2|3|4|5|6|7|8|9.
Ziffer::=0 | <ZifferUngleichNull>.
Ziffernfolge::= <Ziffer> | <Ziffer><Ziffernfolge>.
PositiveZahl::= <ZifferUngleichNull><Ziffernfolge>.

wie in Lehrbeispielen oft angebracht wird. Diese Aufgabe wird bei der Konvertierung einer Zahl mit einem fest programmierten Algorithmus wesentlich schneller und letztlich einfacher erledigt mit if(digit >'0' && digit <='9').

Die Alternative ist für höhere Dinge notwendig, wie beispielsweise:

Kunde::=<Privatkunde>|<Firmenkunde>.

wobei sich hinter der Definition von "Privatekunde" und "Firmenkunde" eine komplexe Syntax, die eigenständig definiert ist, verbirgt. Auch mit Terminalsymbolen ist der Aufbau von Alternativen sinnvoll:

Anrede::= Herr | Frau | Fräulein | Mr\. | Mrs\. .

Der antiquieren Ausdruck "Fräulein" ist nur für das Beispiel gedacht. Der Punkt hinter Mr und Mrs muss mit einem \ eingeleitet werden.

Optionen [...]

Einfache Optionen werden wie in der BNF üblich mit [ ] geklammert. Dabei darf das in Klammern stehende vorkommen oder nicht.

Interessant ist, dass Optionen dieser Art auch mitten zwischen Terminalsymbolen vorkommen können. Beispielsweise gab es einen Reportfile, in dem das Wort Telegramm ohne zweites 'e' geschrieben wurde: Telgramm. In einer späteren Version war das 'e' da. Offensichtlich ein Verschreiber, der später korrigiert wurde. Um beide Varianten zu parsen, wurde in der Syntaxvorschrift

Tel[e]gramm

verlangt. Das sind genau genommen drei verschiedene syntaktische Elemente: Das Terminalsymbol 'Tel', eine Option, innen mit 'e' als Terminalsymbol, und das Terminalsymbol 'gramm'.

Auswahl-Option [ ... | ... | ... ] und [ ... | ... | ]

Die Auswahl-Option ist eine Kombination von Auswahl mit | und der Optionsklammerung. In der Praxis kommt es häufig vor, das eine Alternative genutzt werden muss. Um das auszudrücken, ist bei Auftreten von Alternativen innerhalb einer Optionsklammerung vorgesehen, dass die leere Option nur dann zugelassen ist, wenn sie definitiv angegeben ist. Das erfolgt, in dem am Ende "|]" notiert ist.

Auswahl-Option mit rechtsbündigem Test [ |... | ... ]

Es ist vorgesehen, dass während der Syntaxprüfung zuerst die Fortsetzung nach der Optionsklammer geprüft wird, nur wenn diese Syntax im aktuellem Zweig nicht passt, dann werden die Optionen getestet. Das ist beispielsweise in folgendem Konstrukt sinnvoll:

[|-] <value>

Value soll im Beispiel unter anderem eine Zahl, auch negativ, darstellen: value::=<#-?number>|<$?ident>. Wenn im Input eine Folge "-123" auftritt, dann soll nicht das negative Vorzeichen getrennt erkannt werden, sondern diese Folge als negative Zahl gelesen werden. Mit der Schreibweise [-]<value> würde man auf jeden Fall das negative Vorzeichen extra erkennnen. Syntaktisch ist das zwar egal, da aber eine Semantik dahinterliegt, kann der Unterschied schon bedeutungsvoll sein. Ein anderes Beispiel dafür ist:

coplexlyNumberString::=[|<#?leftPart>\.][|<#?middlePart>\.]<#?rightPart>.

Bei einer Angabe "123.456" würde man nach dieser Schreibweise semantisch den middle- und den RightPart bekommen. Bei der einfachen Optionsschreibweise würde man, da der Rightpart pflicht ist, einen Syntaxfehler bekommen, da die Zeichenkette als left- und middlePart verarbeitet wird. In diesem Fall könnte zwar streng formalsyntaktisch auch [<#?leftPart>\.][<#?middlePart>\.]<#?rightPart>\.. zulässig sein, es funktioniert so aber nicht. Selbstverständlich ist <#?leftPart>[.\<#?middlePart>][\.<#?rightPart>]. zulässig, hier wird die Eingabe optional mit fehlendem middle- oder Rightpart akzeptiert.

Die Auswahl-Option ist eine Kombination von Auswahl mit | und der Optionsklammerung. In der Praxis kommt es häufig vor, das eine Alternative genutzt werden muss. Um das auszudrücken, ist bei Auftreten von Alternativen innerhalb einer Optionsklammerung vorgesehen, dass die leere Option nur dann zugelassen ist, wenn sie definitiv angegeben ist. Das erfolgt, in dem am Ende eine Folge von '|]' steht.

Wiederholung { ... ? ... }

Im Gegensatz zum Verständnis der Wiederholung der meisten EBNF- Ausprägungen ist hier der mindestens einmalige Durchlauf Pflicht. Ist es gewünscht, dass die Wiederholung auch leer sein kann, so kann sie als Option angegeben werden:

[{...}]

Es gibt einen zweiten wichtigen Unterschied: Das Fragezeichen als Einleitung eines Rückwärtszweiges. Zwischen '?' und '}' steht die Syntax, nach deren Auftreten eine weitere Wiederholung Pflicht ist, und die bei einer Wiederholung Pflicht ist. Das ist der häufig in der Praxis auftretende Fall, dass zwischen Aufzählungen ein bestimmtes Trennzeichen steht, aber nicht am Ende. Beispielsweise kann die in C bekannte enum-Definition wie folgt notiert werden:

enumDefinition::=\{ {<$name> [= <wert>] ? , } \}.

Zur enumDefinition gehört die '{' und '}' als Terminalsymbol, hier mit dem '\' davor angegeben. Zwischen den Klammern muss wenigstens eine Definition stehen. Es können mehrere stehen, aber mit Komma getrennt.

Nichtteminalsymbole - Komponenten <syntax?semantik>

Die einfache Form von Nichtterminalsymbolen wird wie in BNF üblich in spitzen Klammern geschrieben:

<Syntaxbezeichner>

Die Syntax an dieser Stelle ist ein komplexer Ausdruck sein, der eigenständig definiert ist:

Syntaxbezeichner::=Syntax.

Aus Sicht der Syntaxbeschreibung ist der Ausdruck "Nichtterminalsymbol" richtig, auch die Bezeichnung Metamorphem ist gebräuchlich. Aus Sicht des damit geparsten Textes kann aber nicht von einem Symbol geredet werden, es ist ein komplexer Inhalt, der diesem Nichtterminalsymbol entspricht. Für den geparsten Inhalt wird daher der Ausdruck Komponente in der Bedeutung: Teil eines Ganzen, was aber wieder aus mehreren Komponenten bestehen kann, gebraucht.

Die SBNF zeichnet sich damit aus, dass nicht nur die Syntax definiert wird, sondern auch semantische Angaben formell mit definiert werden. Die Semantikbezeichnung folgt ohne weitere Angaben der Bezeichnung der Syntax. Allerding ist bei der Syntaxdefinition auch eine abweichende Semantik angegebbar. Insbesondere ist es möglich, eine spezielle Semantik zu unterdrücken mittels Angabe:

Syntaxbezeichner::=<?>Syntax.

In diesem Fall ist die Definition in einer Syntaxkomponente identisch mit einer direkt hingeschriebenen Definition (inline).

Für eine Komponente ist es möglich, unabhängig von der Syntaxbezeichnung eine abweichende Semantik anzugeben:

<Syntaxbezeichner?Semantikbezeichner>

Die Syntax muss dem Syntaxbezeichner gehorchen, aber im parser-Ergebnis wird an dieser Stelle der Semantikbezeichner für die Auswertung abgelegt. Beispielsweise besteht ein Text hintereinander aus zwei Adressen, Rechnungsadresse und Lieferadresse. Beide haben prinzipiell den gleichen Aufbau. Unterschieden werden sie mit dem davorstehenden Schlüsselwort. Deren Syntax ist in SBNF wie folgt angebbar.

{Rechnungsadresse: <Adresse?Rechnungsadresse> | Lieferadresse: <Adresse?Lieferadresse>}

In diesem Beispiel muss mindestens eine der Adressen angegeben sein, welche, ist dann in der Ausgabe abfragbar.

Wird eine Semantik bei der Verwendung einer Komponente angegeben, dann ist diese gültig auch bei anderer Festlegung der Semantik bei der Definition der Syntaxkomponente. Bei folgender Angabe

<Syntaxbezeichner?>

wird zwar die Syntax geprüft, aber keine Semantik-Information erzeugt, da die Semantik eine leere Zeichenkette ist. Damit ist die Wirkung an dieser Stelle identisch mit der direkt hingeschriebenen Definition (inline).

Der Semantikbezeichner ist die gesamte Zeichenkette bis zu dem abschließendem ">". In der SBNF gibt es keine Regeln für die Semantikbezeichner, aber die Auswertung muss damit zurecht kommen. Wird über SBNF ein XML-File erzeugt, dann muss die Semantikbezeichnung den Regeln für Tagnamen in XML folgen. Zusätzlich ist es möglich, mit führendem "@" ein Attribut zu bestimmen. Außerdem gibt es die Möglichkeit, einen Pfad mit "/" getrennt anzugeben, und "." für die Bezeichnung des Textes zu einem Element, adäquat zu "text()". Es gibt einige Zeichen mit Sonderbedeutungen unmittelbar folgend nach dem einleitendem "?". Diese sind in den Abschnitten "Innere Syntax von erkannten Zeichenketten" und "Transformation einer Semantik in eine andere Komponente" angegeben.

Spezielle Nichtterminalsymbole / Syntaxkomponenten: Bezeichner, Zahlen, ... <#...?>, <*...?...>

Folgende Angaben sind typisch für SBNF:

<#?Zahl>  <$?Bezeichner>  <*|*/?Zeichenkette>  <![=]*?RegularExpression>

Bezeichner und Zahlen sind programmtechnisch wesentlich einfacher zu beherrschen als mit einem formellen Parsen, die Konvertierungsroutinen liegen fest. Die Syntax ist bekannt. Innerhalb von Nichtterminal-Symbolen in der Syntax kann eine bestimmte Menge dieser syntaktischen Einheiten angegeben werden, mit Rückgriff auf deren fest programmierte Syntax und Konvertierung. Folgende Angaben dürfen als Syntaxbezeichnung einer Komponente zwischen "<...?>" stehen:

<Angabe> Bedeutung
$ Ein Bezeichner, so wie er in den bekannten Programmiersprachen Java, C gebräuchlich ist: Bestehend aus Buchstaben A bis Z und a bis z, den Ziffern und dem Unterstrich, aber nicht beginnend mit einer Ziffer.
$AddChars Der Bezeichner kann die angegebenen Zeichen zusätzlich enthalten, beispielsweise '$-' wenn auch das Zeichen '-' Bestandteil eines Bezeichners sein darf. Das ist beispielsweise bei XML-Bezeichnern der Fall. Will man damit beispielsweise einen Vornamen damit parsen, dann kann man von der Tatsache, dass auch Ziffern darinnen akzeptiert werden, gegebenenfalls absehen, da das ansonsten keine Widersprüche erzeugt. Um solche Konstruktionen wie "Hans-Dieter" richtig zu erkennen, ist die Angabe von '$-' ebenfalls die richtige Wahl, ebenfalls für die Zulassung von Umlauten. Beispiel: <$-ÄÖÜäöüß?Name>.
# Steht für eine Ziffernfolge aus 0..9, die in eine positive Zahl konvertiert wird. Die Ziffernfolge darf nicht mit 0 beginnen oder darf nur aus der 0 bestehen.
#- Ein negatives Vorzeichen als führendes Zeichen wird akzeptiert, es wird in eine positive oder negative Zahl konvertiert.
#0 Führende Nullen werden akzeptiert
#-0 Ein negatives Vorzeichen und führende Nullen werden akzeptiert.
#f Stehr für eine Float-Zahl in der Java- oder C-üblichen Schreibweise. Intern wird eine Zahl im double-Format erzeugt.
#fiFactor Steht für eine Float-Zahl, die während des Parsens mit dem angegebenem Faktor multipliziert wird und als Integer abgespeichert wird. Faktor muss eine positive Integerzahl sein. Mit dieser Variante ist es möglich, in Scripten mit Dezimalpunkt getrennte Zahlen als Integer weiterzuverarbeiten. Beispiel: Eine Angabe "56.34" wird mit <#fi100?Wert> in den Wert 5634 gewandelt.
#x Eine hexadezimale Zeichenfolge wird erwartet. Die Hexaziffern a..f dürfen groß oder kleingeschrieben sein. Führende Nullen dürfen enthalten sein. Einleitende Zeichen wie das '0x' bei den Programmiersprachen C, Java gehört nicht dazu. Um eine Zahl wahlweise als Dezimal- oder Hexazahl zu parsen, kann man schreiben: [0x<#x?number>|<#?number>]. Wird eine Zeichenfolge '0x' ohne Hexaziffern danach vorgefunden, dann wird die 0 als Ziffer 0 erkannt. Das x bleibt danach übrig, so dass sich ein Syntaxfehler in der weiteren Folge ergibt.
*endchars

Zeichen bis ausschließlich eines der hier angegebenen Zeichen. Die Zeichen unter endchars können beispielsweise auch ein \n für das Zeilenende sein. Wird keines der Zeichen aus endchars gefunden, dann gilt der Syntaxtest als nicht bestanden. Wird eines der terminierenden Zeichen direkt gefunden, dann ist das kein Syntaxfehler, es wird aber kein semantischer Eintrag abgelegt.

Beispiel: <*\r\n?CharsUntilLineend>: Alle Zeichen bis Zeilenende, genau bis eines der Zeichen 0x0d (umschrieben mit "\r") oder 0x0a (umschrieben mit "\n").

Beispiel: <*!\??CharsUntilExclamationOrQuestionMark>: Das das "?" als Trennzeichen zur Semantik benutzt wird, wird es bei Angabe als eines der Endzeichen mit "\?" umschrieben. Es wird die Zeichenkette bis auschließlich einem "?" oder einem "!" eingelesen.

Achtung: Dieses Konstrukt steht an sich im Widerspruch zum Gedanken des Parsens: Hier wird nicht auf Übereinstimmung geprüft sondern bis zur Nichtübereinstimmung gesucht. Es muss aufgepasst werden, dass die endchars passend angegeben werden. Ein typischer Fehler ist es, nur bis zu einem Leerzeichen zu suchen. Im Quelltext gibt es aber kein Leerzeichen nach der zu erkennenden Folge, sondern beispielsweise einen Zeilenumbruch, gefolgt sofort von einem anderen Quellteil am Anfang der neuen Zeile. Alles dies bis irgendwann ein Leerzeichen kommt wird dann dem <*\ ?semantic>-Konstrukt zugeordnet. Aber damit gibt es einen nachfolgenden Syntaxfehler.

Da die Folgen *| *"" *'' Sonderbedeutungen haben, können diese Zeichen nicht als erste Zeichen in endchars aufgeführt werden. Entweder man dreht dann die Reihenfolge, oder man kann mit \| usw. umschreiben.

*""endchars

Zeichen bis ausschließlich eines der hier angegebenen Zeichen. Wird dabei ein Anführungszeichen erkannt, dann werden alle Zeichen bis zum Ende der Anführung nicht als endchar getestet. Die Umschreibung mit \" in der Anführung wird richtig erkannt, also nicht als Ende der Anführung. Wird ein Zeichen aus endchars außerhalb der Anführung nicht gefunden, dann gilt der Syntaxtest als nicht bestanden. Wird eines der terminierenden Zeichen direkt gefunden, dann ist das kein Syntaxfehler, es wird aber kein semantischer Eintrag abgelegt.

Beispiel: <*""&?CharsUntilAmpersand>: Alle Zeichen bis zum Kaufmanns-Und, aber nicht ein & innerhalb einer Anführung, also beispielsweise bis

xyz-Zeichen "da ist ein & drin" noch was &

ausschließlich dem letzten "&".

*|str|str

Zeichen bis ausschließlich eines der hier angegebenen Zeichenketten. Die Zeichenketten, hier mit str bezeichnet, stehen jeweils nach einem Trennzeichen "|". Wird keine der terminierenden Zeichenketten aus endchars gefunden, dann gilt der Syntaxtest als nicht bestanden. Wird eines der terminierenden Zeichenketten direkt gefunden, dann ist das kein Syntaxfehler, es wird aber kein semantischer Eintrag abgelegt.

Beispiel: <*|*/|@?description>: Eine Zeichenkette bis ausschließlich "*/" oder ausschließlich "@" wird eingelesen und als "description" interpretiert.

*<<endchars

Alle Zeichen bis ausschließlich eines der am weitesten hinten auftretenden Zeichen aus endchars.

Das ist eine Sonderform, die nur bei relativ kurzen Eingabetexten sinnvoll anwendbar ist, aber auch insbesondere bei Verwendung der Inneren Syntax. Ein wichtiges Anwendungsgebiet ist das Separieren von Pfadangaben und Filenamen, wie im folgenden Beispiel gezeigt:

something::= <* \r\n),?!testFilePath>  ##Innere Syntax für testFilePath, 
                                       ##alle Zeichen bis whitespace oder ) ,

 
testFilePath::=[<*<</\\:?path>[/|:|\\]]<*?name>.

Im Beispiel werden zuerst alle Zeichen bis einem Whitespace oder Klammer zu oder Komma eingelesen und für eine innere Syntax testFilePath bereitgestellt. In der inneren Syntax wird dann bis zum am weitestens rechts der gesamten Eingabe vorkommenden Zeichen \ / oder : der Path erkannt. Wird keines solcher Zeichen gefunden, dann ist keine path-Angabe vorhanden. Nach erkannter Path-Angabe muss das vorgefundene Trennzeichen muss überlesen werden. Alle Zeichen bis zum Ende gelten dann als Name.

*{indentchars}
|str|str

Zeichen bis ausschließlich eines der hier angegebenen Zeichenketten str. Es wird eine Einrückung berücksichtigt: Jeweils bei einer neuen Zeile werden die Zeichen indentchars überlesen, allerdings nur bis zu der Spalten-Position des Textes, bei dem die Zeichenkette insgesamt beginnt. Endet die Angabe mit einem Leerzeichen, dann werden Leerzeichen auch bis über die Spaltenposition hinweg ignoriert, andere Zeichen aber nicht. Diese Variante ist beispielsweise geeignet um Blockkommentare, wie sie in C- oder Java-Quelltexten üblich sind, zu verarbeiten. Ist beispielsweise folgender Text vorhanden:

   /** Das ist kommentar zu
     * * irgendeiner Methode
        Zeile beginnt weiter rechts
     Zeile beginnt weiter links
     */

und dieser wird mit der Syntaxvorschrift /** <*{ * }|*/?comment>*/ geparsed, dann enthält das Ergebnis die Zeichenkette

Das ist kommentar zu
* irgendeiner Methode
Zeile beginnt weiter rechts
Zeile beginnt weiter links

Zu beachten ist, dass eine Zeile mit einer geringeren Einrückung richtig erkannt wird. Eine weitere Einrückung wird genau dann nicht weggelassen (entgegen obenstehendem Beispiel), wenn als letztes Zeichen zwischen {...} kein Leerzeichen angegeben wird. Damit ist insbesondere die Arbeit mit einer Wikipedia-like-Textformatierung möglich. Dazu folgender Hinweis: Bei der Konvertierung nach XML wird die Wikipedia-like-Textformatierung dann umgesetzt, wenn als Semantik "p+" als letztes Element angegeben wird. Die Wikipedia-like-Textformatierung ist keine Leistung der SBNF, sondern im SBNF-XML-Umsetzer enthalten.

"" Es wird eine Anführung erwartet. Geparsed werden alle Zeichen innerhalb einer Anführung einschließlich der umschließenden Anführungszeichen. Die Umschreibung mit \" in der Anführung wird richtig erkannt, ebenso wird die Umschreibung von \n \t usw. erkannt und als Sonderzeichen geparsed. Gültig ist, wenn im zu parsenden Text beispielsweise "Das ist ein \"Zitat\"." gefunden wird. Nicht gültig ist, wenn der Text an dieser Stelle nicht mit einem Anführungszeichen beginnt oder das schließende Anführungszeichen fehlt. Die Anführungszeichen sind diejenigen des Standard-ASCII (hexa 0x22), kein Zeichen aus erweiterten Codetabellen. Alle Zeilenumbrüche Leerzeichen usw. werden unverändert als Text in der Anführung verarbeitet. Soll eine Sonderzeichenerkennung in der Anführung nicht erfolgen, beispielsweise ein Text wie "Das ist ein Backslash: \" zurückgegeben werden, dann ist diese Kennzeichnung nicht zu verwenden, statt dessen reguläre Ausdrücke.
'' Es wird eine Anführung in Hochkommata (ASCII=0x27) erwartet. Die Umschreibung mit \ ist ebenfalls aktiv.
! Damit wird eine Angabe Regulärer Ausdrücke eingeleitet, siehe folgendes Kapitel.

Reguläre Ausdrücke <!...?>

Reguläre Ausdrücke an sich sind bereits eine Möglichkeit, Texte zu parsen. Dabei kann angegeben werden, welche Teile der Regulären Ausdrücke Variablen zugewiesen werden, die dann für die Auswertung zur Verfügung stehen. Die Sprache Perl hat den Ruf, solche Verarbeitungen besonders gut zu unterstützen.

Jedoch sind solche Ausdrücke, wenn sie komplexer sind, schwer lesbar und für eine Syntaxbeschreibung nicht gut tauglich.

In des SBNF werden solche Regulären Ausdrücke unterstützt, die in Suchzeichenketten im aus UNIX bekannten grep angegeben werden können. In der Implementierung des SBNF-Parsers bei vishia wird genau das unterstützt, was in Standard-Java mit der Methode java..util.regex.Matcher.lookingAt() unterstützt wird. Dabei ist in der SBNF folgende Schreibweise notwendig:

<!Regex?Semantik>

Als Regex ist beispielsweise folgendes angebbar (Auszug, vollständig siehe Beschreinbung von java.util.regex.Pattern):

Regex Bedeutung
. Ein einzelner Punkt steht für ein beliebiges Zeichen.
.* Ein beliebiges Zeichen kann angegeben werden, das ganze aber beliebig oft, von 1 bis beliebig. Das * steht für die Häufigkeit der Folge. Das bedeutet aber, das alles an Text kommen darf, bis zum Ende. Hier kommt allerdings die Begrenzung in SBNF in der Form (Beispiel) <32?.*?Max32Chars> angegeben zum Ausdruck, außerhalb des Regulären Ausdruckes wird eine Maximalanzahl angegeben.
.+ Wie .*, es muss aber mindestens 1 Zeichen stehen, bei * auch kein Zeichen.
[abc] Eines der zwischen den [] genannten Zeichen
[abc]* Beliebig viele der zwischen den [] genannten Zeichen. Das ist wieder das bei .* genannte Muster. * gibt die Anzahl an. Davor steht die Auswahl an Zeichen auf jeder Position.
[abc]+ Adäquat .+, es muss mindestens 1 der genannten Zeichen vorkommen.
[a-c] Eines der Zeichen in dem in [] genannten Bereich, im Beispiel zwischen a und c.
[A-Za-z] Es können auch mehrere Bereiche angegeben werden. Hier sind alle Buchstaben gemeint.
[A-Za-z]+ Das ist ein Wort bestehend aus Groß- und Kleinbuchstaben frei gemischt.
[A-Z][a-z]* Zuerst muss ein Grossbuchstabe kommen, danach beliebig viele Kleinbuchstaben.
muss Das ist die Folge von ganz bestimmen Buchstaben (hier 'muss')
Me.er Meyer oder Meier, aber auch Mexer, oder MeAer oder Me!er, der Punkt steht für jedes beliebige Zeichen.
Me[yi]er Meyer oder Meier
\\S Jedes Zeichen, was kein Whitespace ist. Hinweis: Das Doppel-Backslash muss hier doppelt geschrieben werden.
\\s Jedes Whitespace, dazu zählen Leerzeichen, Tabulator, Linefeed (Hexa 0a) und Carrige Return (Hexa 0d).
\\w Ein Wort
\\W Alles außerhalb eines Wortes
   

Um einen regulären Ausdruck in der Syntaxvorschrift zu notieren, ist folgendes zu schreiben:

<!regex?semantic>

Beispiel:

<!\\W?Zeichen_zwischen_Worten>

 

Innere Syntax von erkannten Zeichenketten <syntax?!innerSyntax>

Eine geparste Zeichenkette insbesondere mit <*endchars?...> oder <*|endstr?...> kann gegebenfalls ein innere Syntax enthalten. Der äußere Syntaxtest erkennt zunächst die gesamte Zeichenkette bis zu bestimmten Ende-Zeichen oder Ende-Zeichenketten unabhängig von ihrem eigentlichen Inhalt. Eine Subsyntax-Prüfung kann nach Erkennung der Gesamt-Zeichenkette eine Syntaxerkennung und Semantik-Umsetzung deren Inhaltes ausführen. Dabei muss folgendes notiert werden:

<...?!syntax>

Hier wird keine Semantik angegeben, sondern nach dem Ausrufezeichen der Bezeichner der Syntaxvorschrift zum inneren parsen der erkannten Zeichenkette. Das parsen wird allerdings erst dann erledigt, wenn das äußere Parsen abgeschlossen ist.

Semantikangaben

Das "S" in "SBNF" steht für Semantik. Ein Haupt-Anwendungsgebiet eines SBNF-Parsers ist die Umwandlung von frei aber syntaktisch fassbaren Texten in XML. Die Semantik bestimmt dabei den Aufbau des als Ergebnis erzeugten XML-Baumes (-Files). Die Gliederung in Syntaxkomponenten bestimmt die Baumstruktur, die angegebene Semantik bestimmt die Tagnamen der Elemente.

Beispielsweise erzeugt folgende Syntax:

syntax::= { <kopf> { <data> } } -end-.
kopf::= idx = <#?index>.
data::= value = <#?wert>.

mit folgenden Daten:

idx=1 value=5 value=6 idx=123 value=7 value=23 -end-

folgenden XML-Baum:

<syntax>
  <kopf><index int="1" /></kopf>
  <data><wert int="5" /></data>
  <data><wert int="6" /></data>
  <kopf><index int="123" /></kopf>
  <data><wert int="7" /></data>
  <data><wert int="23" /></data>
</syntax>

An diesem Beispiel ist an sich auch schön sichtbar, was das Wesen von XML ist. Die Datenquelle vor dem parsen ist zwar wesentlich kürzer, benötigt aber die Information über die Syntax mit Semantik, um die Daten auch verwerten zu können. XML enthält in den Daten gleichzeitig die Semantik. SBNF ist das Bindeglied zwischen beliebigen Daten und XML.

Semantikangaben zu Syntaxkomponenten, Unterdrückung der Semantik

Eine Syntaxkomponente der Form <Name> hat eine bezeichnungsidentische Semantik. Die Semantik wird als Resultat des Parsers abgelegt. Modifikationen der Semantik sind möglich:

Die letzte Variante ist recht wichtig, wenn komplexe syntaktische Zusammenhänge beschrieben werden. Nicht in jedem Fall ist eine Syntaxkomponente von semantischer Bedeutung, oft ist es nur eine Gliederung der Syntax. Ein Beispiel dafür stammt aus der Syntaxvorschrift zur Konvertierung von Headerfiles (C-Programmierung:

structDefinition::=struct [<$?typetagident>] \{ { <structContent?> } \} <$?name>;.
classDefinition::=class [<$?typetagident>] \{ { [ <classContent> | <structContent> ] } \} <$?name>;.

structContent::=<?>
   [ <unionDefinition>
   | <structDefinition>
   | <attribute>
   | <defineDefinition>
   | <structContentInsideCondition>
   ].

In diesem Beispiel ist <structContent> zwar eine Syntaxkomponente, aber nur damit die Syntax übersichtlich geschrieben werden kann, auch weil dieser Baustein zweimal verwendet wird. <structContent> ist daher nicht semantisch von Bedeutung, die Inhalte unterhalb <structContent> sind es aber. Hier wird die Syntax von <structContent> auf zweierlei Art ausgeschaltet:

Die hier dargestellten Semantikregeln gelten gleicherweise auch für die speziellen Syntaxkomponenten wie <#?Semantik> oder <!regex?Semantik>

Zusätzliche Semantikangaben im Verlauf einer Syntaxvorschrift

Immer wenn eine Folge

 <?semantic>

beim Parsen erfolgreich durchlaufen wird, dann wird ein Semantik-Element erzeugt. Das kann sinnvoll mit Terminalsymbolen verbunden werden, beispielsweise:

Das ist das Haus vom [Nikolaus<?Nikolaus>|Weihnachtsmann].

In diesem Fall wird in der Variante 'Nikolaus' die Semantikinformation 'Nikolaus' erzeugt, beim 'Weihnachtsmann' wird aber nichts erzeugt.

Zusätzliche Semantikangaben am Anfang einer Syntaxvorschrift

Ein spezialisierter Fall liegt vor, wenn die Folge

[<?semantic> .... oder ::=<?semantic> ... oder {<?semantic>....

direkt am Anfang einer (Teil-) Syntaxvorschrift ohne Whiteblank dazwischen steht. Hier gilt ebenfalls, das eine Semantik-Information erzeugt wird, in diesem Fall immer, wenn dieser gesamte Syntaxteil erfolgreich absolviert wurde. Zusätzlich wird in diese Semantik noch die Information eingeschrieben, welche Alternative und die wievielte Wiederholung dabei durchlaufen wurde. Ein typischer Fall der Anwendung ist

[<?whichOption> a | b |]

Hier wird bei Zutreffen von 'a' der Wert 1 als Auswahl vermerkt, bei 'b' der Wert 2 und bei keinem Zutreffen der Wert 0. Damit ist für die Auswertung erkennbar, welche Auswahl getroffen wurde.

Das obige Beispiel der XML-Konvertierung ist folgend mit etwas geändertem Inhalt und Syntax/Semantik angegegen. In der Syntax wurde ein <?dataBlock> eingefügt. Die Komponenten <data> und <info> erzeugen aber dafür kein eigenes Element:

syntax::= { <kopf> {<?dataBlock> <data?> | <info?> } } -end- .
kopf::= idx = <#?index>.
info::= info = <""?info>.
data::= value = <#?wert>.

damit wird folgender XML-Baum erzeugt:

<syntax>
  <kopf><index int="1" /></kopf>
	<dataBlock repetition="1" alternative="1"><wert int="5" /></dataBlock>
  <dataBlock repetition="2" alternative="1"><wert int="6" /></dataBlock>
  <dataBlock repetition="3" alternative="2"><info string="null" src="&quot;Index1&quot;" /></dataBlock>
  <kopf><index int="123" /></kopf>
  <dataBlock repetition="1" alternative="1"><wert int="7" /></dataBlock>
  <dataBlock repetition="2" alternative="1"><wert int="23" /></dataBlock>
</syntax>

Transformation einer Semantik in eine andere Komponente

Ein interessanter und gar nicht allzu seltener Fall ist folgender:

Aus rein syntaktischer Sicht wird notiert:

syntax::=<KopfInfo> [ <Variante1> | <Variante2> | <Variante3> ]

Die Kopfinfo soll aber semantisch jeweils in den Varianten gespeichert werden. Möglicherweise wird man daher schreiben:

syntax::=[ <Variante1> | <Variante2> | <Variante3> ]
....
Variante1::= <KopfInfo> <RestVariante1>.
Variante2::= <KopfInfo> <RestVariante2>.
Variante3::= <KopfInfo> <RestVariante3>.

Nur dann erscheint die Kopfinfo der Variante zugehörig, deutlich zu sehen beispielsweise bei der Elementzuordnung im XML. Diese semantische Zuordnung kann bedeutend sein. In der letzten Form ist aber zum einen im übergeordnetem Prescript nicht mehr sichtbar, dass es da eine gemeinsame Kopfinformation gibt, zum anderen gibt es wieder den Rechenzeit- und Reportfileauswerte-Aufwand.

Das hat aber den Nachteil, dass die Kopfinfo wiederholt geparsed wird, wenn die entsprechende Variante nicht zutrifft. Wenn die Kopfinformation umfänglich ist, dann ist das ein durchaus als erheblicher Aufwand zu bezeichnen, bezogen auf die Rechenzeit oder wenn gegebenenfalls der Reportfile analysiert werden soll.

Um das parsen zu vereinfachen und semantisch die gleiche Aussage zu bekommen, kann man stattdessen schreiben:

<KopfInfo?-@> [ <Variante1?+@> | <Variante2?+@> | <Variante3?+Variante3> ]

Dann wird die Semantikinformation der Kopfinfo erstmal zwischengeparkt und dann jeweil dort hinterlegt, wo nach dem "?", das die Semantikbezeichnung einleitet, ein "+" steht. Das Zeichen "@" wird hier als Kürzel der Semantik benutzt und bedeutet, dass die Semantik identisch mit dem Namen der Syntax der Syntaxkomponente ist, also so wie es auch im ausgeschriebenen Beispiel der Variante3 ist. Bei der Schreibweise <Variante1?+> wird keine Semantik erzeugt.

Wird das Semantikparken mit "?-" mehrfach innerhalb einer Syntaxvorschrift verwendet, dann wird anhängend geparkt. Wird das "?+" mehrfach verwendet, dann wird der Inhalt mehrfach eingefügt. Da das oft auch zu Widersprüchen führen kann, sollten zu dessen Vermeidung kleinere Syntaxvorschriften als Sub-Syntax verwendet werden, was insgesamt zur Übersichtssteigerung sowieso empfehlenswert ist.

Das SBNF-Script - - Zeichenkette / Datei mit Syntaxinformationen

Ein SBNF-Script ist eine Zeichenkette, die gegebenenfalls mehrere SBNF-Ausdrücke und zusätzliche Definitionen (Werte) enthält. Ein SBNF-Script kann typisch als Datei vorliegen und wird dann als Zeichenkette vor dem Parsen eingelesen, oder wird in Java-Programmen direkt als Zeichenkette vorgegeben.

Beispiel für den Inhalt einer Datei eines SBNF-Scriptes ist:

<?SBNF-www.vishia.de version="1.0" encoding="iso-8859-1"?>
$setLinemode.


einkaufsliste::=Einkaufszettel \n
<![=]*?> \n
{ <position> \n }.

position::=<#?@menge> [<?@einheit>Stck|x|] <$?text()>.

In dieser Datei wird zu Anfang die Codierung und danach als Wert der "Zeilenmodus" festgelegt. Der erste SBNF-Ausdruck "einkaufsliste" ist der main-Ausdruck, mit ihm beginnt das Parsen. Der zweite Ausdruck "position" ist eine Syntaxkomponente, die intern aufgerufen wird.

Festlegung der Zeichencodierung der SBNF-Script-Datei

Es gibt mindestens zwei gleichberechtigte Zeichencodierungen (englisch: encoding) in Dateien: Das bei Windows übliche 8-bit-Zeichenformat genormt nach ISO-8859-1, und UTF-8. Erläuterungen zu Zeichenformaten siehe Wikipedia-ASCII. Das UTF-16-Format spielt in Codierungen in Dateien kaum eine Rolle, es hat sich dort nie durchgesetzt und ist vom UTF-8 verdrängt worden. Dennoch sollte es benutzt werden können. Grundsätzlich soll der SBNF-Parser auch Codierungen in fremden Sprachen beherrschen. - Damit besteht die Notwendigkeit, mehrere Zeichencodierungen zu beherrschen.

Das Problem der Zeichencodierungsangabe wurde im XML-Standard gelöst: In der Kopfzeile wird die Codierung angegeben. Da die Kopfzeile ausschließlich mit dem Zeichenvorrat geschrieben wird, die dem ASCII (7 bit) entspricht, dieser Teil der Codierung ist in allen 8-bit-Zeichencodierungen identisch, kann diese Kopfzeile ohne Kenntnis der Codierung auf jeden Fall ausgewertet werden. Um auch UTF-16 zu beherrschen, wird der Inhalt der Kopfzeile mit diesem Format eingelesen, wenn die Kopfinformation beim Einlesen im 8-Bit-Format fehlerhaft ist.

Diese Lösung ist für SBNF übertragen worden. Die Kopfzeile einer SBNF-Script-Datei kann wie im Beispiel gezeigt mit "<?SBNF-www.vishia.de" beginnen, soll nach dem Text "encoding=" in Anführungszeichen den Namen des Zeichensatzes enthalten. Der Eintrag muss mit "?>" abschließen.

Für den Fall, dass ein SBNF-Script nicht aus einer Datei gelesen wird sondern von einem Java-Programm vorgegeben wird, braucht es diese Encoding-Angabe und die Startzeile nicht. Innerhalb Java sind in Strings alle Inhalte in UTF-16 codiert.

Festlegung der Zeichencodierung der zu parsenden Datei

Grundsätzlich gilt, dass zu dem Inhalt einer zu parsenden Datei keine Angaben gemacht werden können außer durch das SBNF-Script selbst. Damit kann es sinnvoll sein, dass die Zeichencodierung einer zu parsenden Datei im SBNF-Script festgelegt ist. Das erfolgt mittels Angabe des Defines im SBNF-Script (Beispiel):

$InputEncoding=iso-8859-1.

Es kann aber auch der Fall eintreten, dass die Codierung nicht Im SBNF-Script festgelegt werden kann oder soll. Dann gibt es zwei weitere Möglichkeiten:

Die letzte Variante ermöglicht eine Speicherung der Information in verschiedenen Zeichencodierungen, ohne dass das SBNF-Script daraufhin angepasst werden muss, eine wichtige Möglichkeit. Es ist dabei voraussetzbar, dass die Codierung in der ersten Zeile der Datei enthalten ist, diese erste Zeile muss allgemein eine Schlüsselzeile sein, die ausschließlich im ASCII (7 bit) codiert ist. Mit welchem Schlüsselstring der Codierungsstring eingeleitet wird, kann dagegen nicht vorausgesetzt werden. Daher gibt es in der SBNF die Möglichkeit, dieses Schlüsselstring festzulegen:

$InputEncodingKeyword="charset=".

Das Schlüsselwort wird in Anführungszeichen eingegeben. Nach diesem Schlüsselstring wird die erste Zeile durchsucht. Eine Codierung kann danach mit oder ohne Anführungszeichen stehen, beides wird akzeptiert. Eine Codierungsangabe in Anführungszeichen wird bis zum Ausführungszeichen akzeptiert, eine Codierungsangabe ohne Anführungszeichen wird als Identifier (Buchtstaben, Ziffern, Unterstrich) mit dem Zusatzzeichen "-" akzeptiert.

Ist die Codierungsangabe keinem verarbeitbarem Zeichensatz zuweisbar, dann wird das Parsen mit einer Fehlermeldung abgebrochen. Das gilt sowohl für das SBNF-Script als auch für die Input-Datei.

Behandlung von Leerzeichen und Kommentar zwischen den SBNF-Ausdrücken und Definitionen

Leerzeichen und Zeilenumbrüche zwischen SBNF-Ausdrücken und Definitionen werden als whitespaces überlesen. Kommentare mit ## eingeleitet gelten bis zum Zeilenende und werden ebenfalls überlesen. Das ist das gleiche Verfahren wie innerhalb von SBNF-Ausdrücken.

Ein whitespace zwischen SBNF-Ausdrücken hat keine Bedeutung. Demgegenüber kann es innnerhalb eines SBNF-Ausdruckes zur Aufforderung zum Überlesen von Whitespaces im Input-String dienen, wie im Abschnitt "WhitespaceParsing" beschrieben.

Übersicht über alle Definitionsmöglichkeiten

 

Ablauf des Parsen im Java-Parser

Es gibt einen Java-Parser vishia.StringScan.Parser. Dieser realisiert das Parsen nach der hier vorgestellten SBNF. Dem Parser wird eine Syntaxvorschrift zunächst zugeordnet. Diese wird zunächst selbst geparsed und dann in eine baumorientierte interne Abbildung umgefort. Danach ist der Parser in der Lage, für beliebige Inputs einen Output zu erzeugen. Der Output wird im Parser zwischengespeichert und kann schrittweise ausgelesen werden. Bei einem erneueten Aufruf des parsen wird der Output überschrieben.

Beim Parsen wird der Quelltext des Eingabestreams von seinem Beginn an eingelesen und mit der SBNF-Syntaxvorschrift verglichen. Wenn die Syntaxvorschrift Alternativen zulässt, dann werden die Wege (Syntaxpath) der einzelnen Alternativen in der Reihenfolge der Notation in der SBNF-Syntaxvorschrift durchgegangen. Wenn der Input nicht zur Syntax passt, dann wird jeweils an den unmittelbar zurückliegenden Verzweigungspunkt von Alternativen in der SBNF zurückgegangen und ein weiterer möglicher Weg getestet. Ist kein Weg mehr vorhanden, dann wird jeweils an dem weiter zurückliegenden Punkt erneut weitere Alternativen getestet. Das geht so weiter bis zum ersten Verzweigungspunkt. Wird dabei kein möglicher Weg gefunden, dann ist das Parsen insgesamt nicht erfolgreich. Als Fehlerstelle wird dabei diejenige Position angezeigt, bei der der Parser am weitesten vorangekommen ist. Zur Fehlerstelle sind die möglichen Syntaxvarianten als String zur Fehlerangabe abrufbar. Die davor ermittelten erfolgreich geparsden Einheiten sind ebenfalls abrufbar, so dass der Syntaxfehler möglichst gut erkannt werden kann (besser als bei den gängigen C-Compilern).

Bei Optionen kann wie oben dargestellt in der Syntax der SBNF angegeben werden, ob zuerst ihr Zutreffen oder das Nicht-Zutreffen getestet werden soll. Das ist in einigen Konstruktionen nützlich. Die Option stellt einen Verzweigungspunkt der Alternativen dar.

Anwenden der SBNF

Die SBNF ist neben einer rein textuellen Beschreibung für die syntaktisch - semantische Zusammenhänge für das unmittelbare Parsen von Texten geeignet, die maschinentechnisch weiterverarbeitet werden sollen. Insbesondere ist auch eine Konvertierung nach XML möglich.

Dabei kann folgendes benutzt werden:

Für den ersten Fall sind die Java-Sources des Parsers einschließlich der Java-Doc (private, source) hilfreich, wobei genaugenommen das compilierte jar-Archiv auch ausreicht. Der zweite Fall benötigt lediglich einen Kommandozeilen-Aufruf entweder des kommandozeilenorientierten Übersetzers (für batch-Abläufe)

java -cp JavaVishia.jar vishia.stringScan.SBNF2Xml -sSyntax.sbnf -iInputfile.txt -xXmlOutput.xml

oder der menügeführten Bedienung

java -cp JavaVishia.jar vishia.stringScan.SBNFgui

Die XML-Konvertierung baut XML-Tags entsprechend der angegebenen Semantik auf. Das Toplevel-Element hat den Namen der ersten ( Haupt-) Syntaxvorschrift im SBNF-File.

Anwendungsbeispiel: Parsen von C-Headerfiles

Headerfiles enthalten die Beschreibung von Datenstrukturen für die C- und C++-Programmierung. C und C++ sind weit verbreitet. Interessant ist es oftmals, Dokumentationen der Datenstrukturen zu erhalten, beispielsweise zur Beschreibung des Datenformates auf einem Übertragungsweg. Enthalten die Headerfiles passend zusätzlichen Kommentar, dann ist es möglich, die Headerfiles alleinig als Quelle für diese Dokumentation heranzuziehen beziehungsweise von vornherein darauf zu orientieren, dass die Headerfiles Dokumentation in der gewünschten Art für diese Zwecke enthalten. Das Vorgehen gleicht dabei dem Prinzip von Javadoc: Dokumentation der Klassen ist in den Java-Quellfiles enthalten und wird über Javadoc.exe extrahiert. Dabei gibt es immer Konsistenz zwischen den aktuellen Versionen der Software-Quellen und der daraus erzeugten Dokumentation, es ist kein Nachpflegen notwendig.

Für Headerfiles kann man für diesen Zweck doxygen verwenden. Doxygen ist beispielsweise auch in der Lage, XML zu erzeugen.

Interessant ist es aber auch, Headerfiles als Quelle zu verwenden, um daraus ein Java-File zu erzeugen. Damit kann beispielsweise direkt aus Java Datenstrukturen (über ein byte[]) beschrieben werden, die genau zu den Headerfiles passen. Damit bekommt man eine einfache Möglichkeit, Datenstrukturen, die in C oder C++ verarbeitet oder erzeugt wurden, auch mit Java zu erzeugen oder verarbeiten. Der Austausch der Daten erfolgt beispielsweise über Binärfiles (Datenimages) oder via Socketkommunikation. Im folgenden ist ein Weg gezeigt

Das ganze erfordert also 'nur' zwei Aufrufe von Standardtools per Kommandozeile und zwei zugehörige Scriptfiles:

Im SBNF-File wird bestimmt, wie der XML-File auszusehen hat. Daher ist es nicht vordergründig, eine bestimmte Syntax anzugeben, sondern die Syntaxformulierung richtet sich nach der für den Zweck erforderlichen Semantik. Für eine anderweitige Auswertung des Headerfiles könnte man gegebenenfalls eine etwas andere Syntaxformulierung gebrauchen können.

 

Es gibt unter JavaSrc/vishia/XmlBinCoding eine Konvertierung von Headerfiles der C-Programmentwicklung nach XML und ein passendes XSL-Script um daraus eine Java-Source herzustellen, die XmlBinCoding erweitert, damit das byteweise Setzen von Daten ermöglicht, beispielsweise um Binärfiles zu erzeugen, die in C weiter verarbeitet werden sollen. Das könnte eine wichtige Anwendung sein.

Eine andere Anwendung wäre die Vorgabe von Stimulifiles in textueller Form (manuell erstellbar) für Tests, die ein Java-Teststimulator verarbeitet.

***** www.vishia.de/java 2006-06-15==>home