Implementation einer domainspezifischen Sprache mit Askemos®/BALL |
Die Definition und Progammierung der verteilten virtuelle Maschine des Askemos, erfolgt auf sehr abstraktem Niveau. Sie bietet Continuations, mehrere, unabhängige Wertebereiche so zum Beispiel für Datenwerte, Berechtigungen, urheberrechtliche Informationen u.a. Dadurch ist sie sehr flexibel und allgemeingültig, jedoch hat erkauft man sich diese Vorteile dadurch, daß Operationen zunächst in einer sehr detailierten Notation erfolgen müssen.
Charakteristisch für die Programmierung auf diesem Niveau ist, daß oft nur wenige der möglichen syntaktischen Konstrukte sinnvoll sind. Wobei diese häufig fest bestimmte Elemente wiederholen. Auch scheinbar einfache und in der praktischen Anwendung oft benötigte Operationen erfordern häufig, im Vergleich zu komplexen Vorgängen, unverhältnismäßig hohen Schreibaufwand. Auf der anderen Seite ist diese Darstellung nicht problemorientiert und es fehlen oft Validierungsschritte sowohl des Programmes als auch der vom Programm produzierten Ergebnisse (Dokumente).
Die Programmierung auf diesem Niveau erfordert eine hohe Disziplin, anderenfalls sind fertige Programme für andere Programmierer kaum lesbar. Einer solchen Situation begegnet man in der Informatik durch die Entwicklung von Sprachen, die auf das jeweilige Anwendungsgebiet spezialisiert sind, sogenannte domain specific languages (DSL).
Dieses Dokument beschreibt die zur Implementation einer domainspezifischen Sprache notwendigen Schritte.
DSL sind spezifische Sprachen, um bestimmte Aufgaben zu erfassen. Im Rahmen der Spezialisierung eignet sich der Mensch eine problembezogene Fachsprache an, damit fachliche Inhalte effizienter transportierbar sind als durch die Umgangsprache. So kommunizieren z.B. Betriebswirte oder Informatiker oft untereinander in Begriffen, die einem Arzt oder Rechtsanwalt kaum zu vermitteln sind - und umgekehrt. In gleicher Weise gibt es spezialisierte Sprachen innerhalb der Web-Programmierung, zum Beispiel CSS für die graphische Gestaltung, JavaScript zur dynamischen Veränderung von Inhalten oder XSLT zur deklarativen Beschreibung komlexer Strukturtransformationen.
In Askemos/fiXml-Applikationen ist es beispielsweise oftmals notwendig, sich auf einen nutzerbezogenen Zustand zu beziehen, dessen Werte in Formularfeldern zur Bearbeitung zugänglich gemacht werden sollen.
Typischerweise wird man dazu für jeden Wert eine Variable anlegen, diese
in HTML-input
-Elementen zum Browser übertragen
und letztendlich die entsprechenden Felder der Antwort wieder den
Variablen zuordnen. Die Umsetzung erfordert einen
relativ hohen Programmieraufwand, der ein festes (syntaktisches) Muster
wiederholt -- ein typisches Problem, das durch
Einsatz einer DSL effizient gestaltet werden kann.
Anmerkung: Diese Anleitung bezieht sich auf Askemos version 0.8. Für zukünftige Versionen können bestimmte Beschränkungen aufgehoben sein. Bitte überprüfen Sie die ggf. Ihre Version.
der im vorigen Abschnitt genannte, typische Einsatz von DSL führt i.d.R. auf relativ umfangreiche Grammatiken. Zur Demonstation ist ein anderer Einsatzfall, die Datenkonvertierung, geeigneter.
Es soll eine Konvertierung aus einer XML-Darstellung von Zahlungsverkehrsdaten in das DTAUS-Format, beziehungsweise der Import von DTAUS-Daten in das XML-Format implementiert werden.
Im Zusammenhang mit Zahlungsverkehrsdaten ist der Testplan von besonderer Bedeutung. Zur Verifikation wird zunächst ein unter der GPL verfügbarer DTAUS-Konverter Die zu implementierende Sprache ist damit definiert: die in der zugehörigen Dokmumentation nur anhand des folgenden Beispiels beschriebene "Steuerdatei".
# dtaus -dtaus -d sample.txt -c sample.ctl -b sample.doc -o sample.sik BEGIN { Art LK Name Martin Schulze Konto 123545 BLZ 2004002 Ausfuehrung 24.12.2001 Euro } { Transaktion Einzug Name Martha Schulze Konto 1234567 BLZ 47110815 Betrag 30.00 Zweck Gebühr Wohnheimnetz Text Anschluß u. 11+12.97 } { Transaktion Einzug Name Misia Schulze Konto 1234567 BLZ 47110815 Betrag 20.00 Zweck Gebühr Wohnheimnetz Text 11+12.97 }
Mit dem Askemos/BALL-Code ist der LALR(1)-Parsergenerator
von Dominique Boucher und die
pcre-Bibliothek verbunden.
Mit dem Makro pcre-lalr-parser
konstruiert man
aus diesen einen Parser.
Erster Parameter ist eine Liste zur Beschreibung der Token,
alle weitern Parameter beschreiben die Grammatik.
Der erste Parameter ist eine Liste von Listen, welche jeweils ein Token beschreiben. Das erste Element der Liste enthält jeweils einen regulären Ausdruck in Perl-Syntax, auf welchen das Token passen muß. Wenn die Liste nur diesen regulären Ausdruck enhält, dann wird das Token ignoriert. Für irrelevante Token wie Leerzeichen oder Kommentare ist dies oft nützlich. Anderenfalls muß das zweite Element ein Symbol sein, welches das Token bezeichnet. Dieses Symbol muß in der Liste der Token im zweiten Parameter mit aufgeführt werden.
Einfache Token liefern als semantischen Wert ihr Symbol.
Sollen andere semantische Werte erkannt werden, so sind diese im regulären Ausdruck mit runden Klammern als Teilausdruck zu markieren. Der semantische Wert des Tokens ist dann die Liste der auf die Teilausdrücke abgebildenten Zeichenfolge.
Der zweite und weitere Parameter des Makros
pcre-lalr-parser
werden unverändert an den
LALR(1)-Parsergenerator weiter gereicht, dort findet sich ggf.
weiterführende Dokumentation.
Der vom Parsergenerator verwendete Algorithmus stammt aus dem yacc-Compiler. Auch die Eingabesyntax erinnert daran.
Die Regeln der Grammatik werden durch jeweils eine Liste
beschrieben. Das erste Element enthält den Namen der Regel
(Zwischensymbol) es folgen Produktionen.
Diese bestehen entweder nur aus einer Liste von Zwischensymbolen
und terminalen Token, dann wird die Produktion akzeptiert,
produziert jedoch lediglich #f
als Wert,
oder es folgt ein :
und ein Scheme-Ausdruck,
welcher das Ergebnis der Produktion berechnet.
Wie bei yacc sind in diesem Ausdruck
die semantischen Werte der einzelnen
Elemente der rechten Seite (Produktion) der Regel an die
Symbole $1
, $2
etc. gebunden.
Es ist sehr zu empfehlen,
den von pcre-lalr-parser
generierten Parser an eine Toplevelvariable zu binden.
Der Makro leistet Einiges an Arbeit, was sich in seinem
Laufzeitverhalten und Speicherverbrauch niederschlägt.
(define dtaus-parser (pcre-lalr-parser (
Zunächst werden Leer- und Steuerzeichen ignoriert:
("[[:space:][:cntrl:]\n\r]+")
Kommentare beginnen mit einem Doppelkreuz reichen bis zum Zeilenende. Auch diese werden hier ignoriert. (Damit enthält das Zielformat nicht mehr genug Informationen um die Eingangsdaten wieder herzustellen. Es ist daher nicht mehr für die dauerhafte Speicherung geeignet.)
("#.*\r?\n")
Es folgen einige Tokendefinitionen, welche keine semantischen Werte transportieren:
("BEGIN" BEGIN) ("END" END) ("{" stago) ("}" etag)
Das letzte Token sind Schlüsselwort/Wert-Paare. Dieses liefert zwei semantische Werte:
getrennt durch Leerzeichen oder Tabulatoren den Rest der Zeile als Wert. Der reguläre Ausdruck enthält deshalb zwei Teilausdrücke (Klammern).
wir definieren:
("([[:alpha:][:alnum:]]*)[ \t]*([^\r\n]*)?\r?\n" key) )
Der Tokeniser ist damit vollständig. Es folgt die Liste der zulässigen Token nach Präzedenz geordnet. An dieser Stelle können ggf. noch Assoziativregeln mit angegeben werden. (siehe Dokumentation des Parsergenerators.)
(BEGIN END stago etag key)
Die übrigen Parameter definieren die Regeln der Grammatik. Mit Aktionen (Ausdrücke nach dem Doppelpunkt) konstruieren wir hier im Interesse der Einfachheit eine SXML-Repräsentation.
Eine DTAUS-Datei besteht aus einem Kopf, Transaktionen und einer Zusammenfassung am Ende.
(dtaus (head transactions tail) : (cons 'dtaus (append $1 $3 (list (cons 'transactions $2)))))
Der Kopf ist ein durch BEGIN markierter und mit geschweiften Klammern (stago, etag) eingefaßter Block von Schlüssel-Wert-Paaren. Die Zusammenfassung (im obigen Beispiel nicht vorhanden) ist mit END markiert.
(head (BEGIN stago keys etag) : $3) (tail (END stago keys etag) : $3)
Wenigstens eine Transaktion, ebenfalls ein Block in geschweiften Klammern, die jeweils wenigstens ein Schlüssel-Wert-Paar enthalten.
(transactions () : '() (transaction transactions) : (cons $1 $2)) (transaction (stago keys etag) : (cons 'transaction $2)) (keys () : '()
Die Aktion hier ist etwas komplizierter: die Liste der zulässigen Schlüssel steht in der Assoziativliste dtaus-trans. Verschiedene Schlüssel haben Aliasnamen. Die werden über diese Liste auf den internen Namen abgebildet. Textdaten werden intern grundsätzlich als UTF-8 gespeichert, DTAUS verwendet jedoch ISO8859-15, mithin ist noch eine Konvetierung mittels iconv nötig:
(key keys) : (cons (let ((x (assoc (car $1) dtaus-trans))) (if x (list (cdr x) (if (equal? (cadr $1) "") (car $1) (iconv "UTF-8" "ISO8859-15" (cadr $1)))) (error "Tag '~a' not defined" (car $1)))) $2))) )
Der auf diese Weise definierte Parser transformiert einem String in der Syntax der Steuerdatei des dtaus-Programms eine SXML-Repräsentation. Zur weiteren Verwendung muß diese noch mit der sxml-Funktion in die interne Darstellung umgewandelt werden.
Die Implementation LALR(1)-Parsergenerator ist leider noch nicht in multihtreaded-Umgebungen einsetzbar. Aufgrund des Laufzeitverhaltens des Makros ist es aber sowieso zu empfehlen, den Parser bereits zur Compilezeit zu generieren. Dazu wird der Code im Quellcode von Askemos/BALL abgelegt.
Das Verzeichnis policy
ist für solche Erweiterungen
vorgesehen, für eigene Erweiterungen durch Anwender wird empfohlen
nach diesem Muster ein eigenes Verzeichnis anzulegen.
Beachten Sie jedoch unbedingt die Lizenzbestimmungen,
wenn Sie Code gegen das Basissystem linken.
Letzlich ist noch für den Aufruf zu sorgen. DTAUS ist ein Datenformat, es liegt also nahe, dieses im MIME-Konvertierung zu registrieren:
(register-mime-converter! "text/xml" ;; Zielformat "application/x-dtaus-control" ;; Quellformat (lambda (str) (sxml (dtaus-parser str))) )
Da das Datenvormat nun registriert ist,
werden Dateien, die als MIME-Type "application/x-dtaus-control"
abgelegt werden, im Kontexten, die XML-Knoten liefern
(z. B. von fetch
im NamespaceDSSSL)
automatisch konvertiert.
Diese Transformation kann bei bedarf auch explizit angeforert werden und liefert dann ein XML-Element in der internen Darstellung:
((mime-cast "text/xml" "application/x-dtaus-control") input-string-im-dtaus-control-format)