Implementation einer domainspezifischen Sprache mit Askemos®/BALL

logo

Programmierung 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.

Wozu DSL?

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.

Beispielimplementation einer DSL für Askemos/fiXml

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.

Beispielaufgabe

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
}

Parserdefinition

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.

Tokendefinitionen

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.

Grammatikdefinition

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.

Beispielcode

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:

  1. den Namen des Schlüsselwortes (eine Folge von alphanumerischen Zeichen)
  2. 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.

Einbindung in Askmeos/BALL

Compilieren

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.

Registrierung als Converter

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)))
)

Anwendung

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)