Freitag, 4. März 2011

Interne DSL mit Clojure

Clojure ist ein Lisp Dialekt. Es läuft in der Java VM. Ein ausführliches Tutorial zu der Sprache findet man hier. Fast alles in Clojure lässt sich in der folgenden Form darstellen:

([Operation/Funktion] <Parameter 1> ... <Parameter n>)

Es gibt weitere sprachliche Konstruktionen. Die meisten sind allerdings "Syntaktischer Zucker" um den Code lesbarer zu machen. Durch die einfache Syntax scheint Clojure sehr geeignet für interne DSLs zu sein. Der Unterschied zwischen API und DSL ist in Clojure nicht sehr groß.

Installation


Clojure besteht im Wesentlichen aus einer JAR-Datei. Diese kann man sich auf der Projektseite herunterladen. Starten lässt sich der Interpreter dann wie folgt:

java -cp C:\Pfad\zu\clojure.jar clojure.main

Um nicht jedes Mal die ganze Zeile wie oben eingeben zu müssen, lohnt es sich ein Startskript zu erstellen. Unter Windows kann das z.B. ein Batch-Skript mit folgendem Inhalt sein.

@ECHO OFF
java -cp C:\Pfad\zu\clojure.jar clojure.main  %*

@ECHO OFF sorgt dafür, dass bei der Ausführung der folgenden Befehle die Kommandozeile nicht mit ausgegeben wird. Das ist wichtig wenn die Ausgabe eines Clojure Skriptes beispielsweise HTML ergibt und in eine Datei umgeleitet werden soll. %* steht für alle Argumente die dem Batch-Skript übergeben wurden. In diesem Beispiel werden also alle Argumente an den Clojure Interpreter übergeben.

Die Fragebogen-DSL


Einen Fragebogen direkt in HTML-Code zu erzeugen bedeutet viel Schreibarbeit, da viel Overhead erstellt werden muss damit gültiges HTML entsteht. Eine Kfz-Umfrage könnte folgendermaßen aussehen:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>Kfz-Umfrage</title>
  </head>
  <body>
    <h1>Kfz-Umfrage</h1>
    <form action="">
      <h2>Welche Marke hat ihr Fahrzeug?</h2>
      <input type="radio" value="Opel" name="Welche Marke hat ihr Fahrzeug?">Opel<br /‌>
      <input type="radio" value="Renault" name="Welche Marke hat ihr Fahrzeug?">Renault<br /‌>
      <input type="radio" value="BMW" name="Welche Marke hat ihr Fahrzeug?">BMW<br /‌>

      <h2>Wie lautet die Modelbezeichnung?</h2>
      <input type="text" name="Wie lautet die Modelbezeichnung?"<br /‌>

      <h2>Welche Ausstattungsmerkmale hat ihr Fahrzeug?</h2>
      <input type="checkbox" value="Klimaanlage" name="Welche Ausstattungsmerkmale hat ihr Fahrzeug?">Klimaanlage<br /‌>
      <input type="checkbox" value="Navigationssystem" name="Welche Ausstattungsmerkmale hat ihr Fahrzeug?">Navigationssystem<br /‌>
      <input type="checkbox" value="ESP" name="Welche Ausstattungsmerkmale hat ihr Fahrzeug?">ESP<br /‌>

      <br /‌>
      <input type="submit" value="Senden">
    </form>
  </body>
</html>

Der größte Teil des Codes kann in jedem Fragebogen verwendet werden und so liegt es nahe diesen zu generieren. Unterscheiden lassen sich solche Fragebögen an den folgenden Dingen:

  • Titel des Fragebogens
  • Fragen des Fragebogens mit ihrem Typ (Textfrage, Einfache Auswahl, Mehrfache Auswahl)
  • Antwortmöglichkeiten zu den einzelnen Fragen

Mit einer entsprechenden internen DSL in Clojure kann man den Fragebogen von oben in der folgenden Form beschreiben.

(Fragebogen "Kfz-Umfrage"
  (Frage Einfachauswahl "Welche Marke hat ihr Fahrzeug?"
    (Antwort "Opel")
    (Antwort "Renault")
    (Antwort "BMW"))
  (Frage Texteingabe "Wie lautet die Modelbezeichnung?")
  (Frage Mehrfachauswahl "Welche Ausstattungsmerkmale hat ihr Fahrzeug?"
    (Antwort "Klimaanlage")
    (Antwort "Navigationssystem")
    (Antwort "ESP")))

Die verwendeten Sprachkonstrukte wie "Fragebogen" oder "Frage" müssen jetzt nur noch definiert werden.

(def doctype "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">
")

(defn Antwort [text]
  text)

(def Einfachauswahl {:Typ :Auswahl, :html "radio"})
(def Mehrfachauswahl {:Typ :Auswahl, :html "checkbox"})
(def Texteingabe {:Typ :Text})

(defmulti Frage (fn [typ frage & antworten] (:Typ typ)))

(defmethod Frage :Auswahl [typ frage & antworten]
  (loop [text (str "<h2>" frage "</h2>"), antworten antworten]
    (if (empty? antworten)
      text
      (recur
        (str
          text
          "<input type=\"" (:html typ) "\""
          " value=\"" (first antworten) "\""
          " name=\"" frage "\" />"
          (first antworten) "<br /‌>")
        (rest antworten)))))

(defmethod Frage :Text [typ, text]
  (str "<h2>" text "</h2>"
    "<input type=\"text\" name=\"" text "\" /><br /‌>"))

(defn Fragebogen [text & fragen]
  (println (str
    (reduce
      str (str
            doctype
            "<html><head><title>"
            text
            "</title></head><body><h1>"
            text
            "</h1><form action=\"\">")
      fragen)
    "<br /‌><input type=\"submit\" value=\"Senden\" /></form></body></html>")))

Speichert man die Definition der DSL in die Datei "Fragebogen.clj" und den Kfz-Fragebogen in die Datei "Kfz-Fragebogen.clj", kann man mit Hilfe der weiter oben angegebenen Batchdatei die HTML-Version des Fragebogens auf die folgende Weiße erzeugen.

clj.bat -i Fragebogen.clj Kfz-Fragebogen.clj > Kfz-Fragebogen.html

Das Symbol ">" sorgt dafür das die Ausgabe des Befehls in die Datei Kfz-Fragebogen.html umgeleitet wird. Nach der Ausführung erhält man den folgenden Fragebogen:

Keine Kommentare:

Kommentar veröffentlichen