Anleitung DomainParser

Schon seit ich die ersten Programmiersprachen kennenlernte, hatte ich die Idee zu fachspezifischen Schrachen, Domains Specific Languages genannt.

Das Ziel solcher Sprachen ist die Trennung von fachlicher Aussage und technischer Infrastruktur.

Ich stelle mir vor, dass eine mit einer solchen DSL (Domain Specigic Language) realisierte Applikation stabil gegen Änderungen der fachlichen Logik oder der technischen Infrastruktur ist.

Die Fachlichkeit lässt sich unabhängig von der technischen Infrastruktur in der DSL ändern und die technische Infrastruktur unabhängig von der Fachlichkeit im Laufzeitsystem.

Einerseits kann man die technische Infrastruktur ohne Beeinträchtigung der fachlichen Logik auf andere Plattformen migrieren, solange die Schnittstellen-Spezifikation eingehalten wird. Andererseits haben Änderungen an der Fachlogik keine Auswirken auf die technische Infrastruktur, solange die Fachlogik ausschliesslich auf der spezifizierten Schnittstelle aufsetzt.

Um eine eigene Fachsprache zu entwickeln muss man folgende Schritte abarbeiten:

1. Sprache definieren

2. Parser bauen

3. Übersetzen, wobei dies als Interpreter oder als Compiler erfolgen kann.
Das Compilieren kann in eine spezielle Zwischensprache (Bytecode, Steuertabelle) oder in eine Hochsprache (MDA) erfolgen.

4. Bereitstellen einer Laufzeitumgebung (Klassenbibliothek, Applikationscontainer)

5. Deployen, Testen, iterative Weiterentwicklung

Für den zweiten Schritt, nämlich das Parsen, habe ich diesen kleinen Parser, GenericParser, der sich an die Java-Notation anlehnt, geschrieben.

Eine wesentliche Eigenschaften aller programmiersprachlichen Grammatiken ist die Wohlgeformtheit, das heisst, zu jeder öffnenden Klammer muss es eine entsprechende schliessende Klammer geben.

Dementsprechend kann mein kleiner Parser auch als XML-Ersatz, zumindest soweit es die Datendarstellung angeht verwendet werden.


Anwendungsgebiete

Durch eine Fachsprache will man natürlich ein vorhandenes Problem lösen.

Mit der JSP-Fluss-Steuerung Control-and-Command wird das Problem des Zerfallens des Steuerflusses durch die ereignisbasierte Abarbeitung des Request-Response-Zyklus zwischen Browser und Server gelöst.

Eine andere Anwendungsmöglichkeit ist die Steuerung asynchroner Vorgänge (Web-Services) in einem synchronen Script.

Ich könnte mir vorstellen, dass Client-Server-Applikationen wie allein-stehende Applikationen entwickelt werden könnten, aber verteilt deployt werden.

In GUI-Applikationen könte man Abläufe über spezielle Scripte statt über Status-Merker steuern.


Demos

Die JSP-Fluss-Steuerung Control-and-Command ist das Musterbeispiel einer fachspezifischen Sprache mit Compiler(FlowControlCompiler.java) und Laufzeitsystem(FlowControl.java), denn dafür habe ich den Domain-Parser speziell entwickelt.

Weitere Demos finden sich im Package de.cnc.domainparser.demo in der Klasse DomainParserDemo. Hier sind die Pfadangaben zu den Beispieldateien test.script und test.hierarchicproperties eventuell anzupassen.

Ein Beautifier zum formatierten Ausgeben geparster Unit´s Beautifier.java.

Eine Konfigurationsklasse als Alternative zu Properties-Files oder XML-Files HierarchicProperties.

Codegenerator hierarchische strukturen (nicht fertig)

Die von Markus Völter im Artikel im Javaspektrum 04/2004 (siehe auch das PDF) aufgezeigte Verwendung von textuellen Spezifikationen für die modellgetriebene Entwicklung ist mit meinem Domainparser auch realisierbar.

Aufbau des Domain-Parsers

Mein Domain-Parser lehnt sich an den Aufbau der Sprache Java an.

Die Java-Klasse Unit stellt eine einzulesende Datei dar.
Es ist geplant, darüber eine Struktur Package (ein Verzeichnis)
und darüber wiederum eine Struktur Project (ein Verzeichnisbaum) zu legen.

Eine Unit kann beliebig viele Segmente entahlten.
Ein Segment entspricht einer Deklaration (package, import, variable) oder Klasse, Methode .

Ein Segment wird entweder durch ein Semikolon oder ein BracesArea abgeschlossen.

Segment ; // Beispiel package-Deklaration, Anweisung

oder

... { ... } // Beispiel Klassen- oder Methoden-Deklaration

Ein Segment kann aus beliebig vielen AbstractElementen bestehen.

AbstractElemente können entweder terminal (nicht weiter unterteilbar) oder
nichtterminal (aus weiteren terminalen oder nichtterminalen AbstractElementen bestehend) sein.

Es gibt die nicht-terminalen Elemente
BracesArea (in geschwungene Klammern eingeschlossener Bereich)
BracketArea (in eckige Klammern eingeschlossener Bereich)
CommaSeparatedList (durch Kommas getrennte Liste)
ComplexIdentifier (vorgesehen für die Punktschreibweise mit Members und Operatoren (nicht fertig) )
ElementList (durch Whitespaces getrennte Liste)
ParenthesisArea (in runde Klammern eingeschlossener Bereich)
Segment (durch Semikolon oder BracesArea beendeter Bereich)

Es gibt die terminalen Elemente
BracketComment (Kommentar /*...*/)
HighCommaStringLiteral (String in Hochkommas eingeschlossen)
Identifier (Bezeichner)
InlineComment (Inline-Kommentar //...)
Number (Zahl mit oder ohne Dezimalstellen)
Operator (+ - * / und so weiter)
QuoteStringLiteral (String in Quotes(Apostrophe) eingeschlossen)

Aufbau eines Compilers

Im CnC-Flusssteuerungs-Framework ist der Aufbau eines Compilers als Beispiel vorhanden. Die Klasse Unit liefert einen Syntaxbaum und sorgt für Wohlgeformheit der Quelldatei.

Der Aufbau des Compilers ähnelt dem Vorgehen in einem SAX-Parser und sollte deshalb mit normalem Programmierkenntnissen beherrschbar sein.

Die Methode FlowControlCompiler#resolveLabels() zeigt das Auflösen symbolischer Referenzen, ähnlich einem Link-Vorgang.

Aufbau eines Laufzeitumgebung

Die Klasse FlowControl des CnC-Systems zeigt den Aufbau eines Fluss-Prozessors, der die in einer FlowControlTable abgelegten FlowControlInstructions abarbeitet.

Speichern des Compilates

Das Kompilieren erfolgt in Java-Klassen und nicht in Bytecode.
Die erzeugten Java-Klassen können serialisiert und abgespeichert werden.
Im CnC-JSP-Flusssteuerungs-System dauerte das Kompilieren im Verhältnis zum notwendigen Tomcat-Neustart nur sehr kurze Zeit, so dass ein Caching des Compiliervorganges nicht nötig war.

Einbindung der Expression-Engine

Der CnC-Compiler übersetzt im Gegensatz zu bekannten Programmierspachen keine Ausdrücke, sondern übergibt diese zum Compilieren der Expression-Engine.
Dies hat historische Gründe darin, dass ich die Expression-Engine getrennt vom CnC-System entwicklet habe.
Teilweise sind zum Trennen der beiden Compiler runde Klammern um Ausdrücke erforderlich.

  return ( a + b );
Das ParenthesisArea enthält den zu parsenden Ausdruck.

Die compilierten Ausdrücke werden über die Methode Expression#eval abgearbeitet.

noch zu tun

zuschaltbare Operatoren (nach Länge, längste zuerst sortieren) )

Option zeilenweise (mit schalter case-sensitive)

  begin xxx
    yyy
  end xxx

  if <bool_expression>
    <code_block>
  end if

javadoc

clone-Methoden

#include-Anweisung

#include mit Parametern