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