Wegen unkontrollierbarem, auswucherndem Idlen heute nur ein kleiner Einblick in die Konvertierung von Lisp nach Javascript.
Ein nettes Feature bei Lisp sind die Klammern. Ja, die Klammern, diese kleinen Rattenschwänze die die meisten Leute zuerst abturnen, wenn sie Lisp-Code sehen. “Ieeh, das ist ja unlesbar, wie soll man sich da zurechtfinden?”. Das dachte auch John McCarthy bei der Entwicklung von Lisp, und hatte eigentlich fest eingeplant, da später mal eine “normale” Infixe Syntax drüber zu kippen. Doch wichtiger war zuerst überhaupt eine funktionnierende Implementierung, und die benutzte intern wie praktisch jede andere Programmiersprache einen abstrakten Syntaxbaum (AST - Abstract Syntax Tree), das man auf einem “linearen” Medium wie Text einfach durch Postfixausdrücke niederschreiben kann. Bei Lisp waren das die SEXPs (S-Expression, ich glaube das S steht für Syntax). Doch diese SEXPs erwiesen sich als ungemein praktisch, weil sie eigentlich selber Listen sind. Und die Liste ist der primitive Datentyp von Lisp (LISP heisst ja auch nichts anderes als LISt Processing). Das heisst, das man in Lisp ohne grosse Hürden Lisp-Code manipulieren kann. Klingt nach wilden Voodo-Opfer-Parties im dunklen Bayou, und ich will auch nicht verschweigen, dass es manchmal auch so ist. Aber in der Praxis heisst das dann meistens folgendes:
- Fehlt ein Konstrukt in der Sprache, lässt es sich einfach herbeizaubern
- Will man Lisp-Code oder sowas ähnliches transformieren, braucht man keinen Parser und Lexer und Compiler zu schreiben, sondern man wirft einfach ein paar Lisp-Funktionen und noch 2-3 Makros zum abrunden und fertig ist die Software.
Wie das genau funktionniert lässt sich ganz schön anhand des folgenden Beispiels zeigen. Angenommen, man hat eine Webplattform, die in Lisp geschrieben ist. Die Plattform generiert dynamisch Webseiten, die aus HTML bestehen. In den meisten Programmiersprachen macht man das mit einem Wust von “print”-Statements, in LISP schreibt man 1 Makro und brauch nur noch Listen zu generieren. Kleiner Einblick:
Dieses Macro definiert die Spezialform, um ein CMS-internen Link zu erzeugen.
(defmacro cmslink (url &body body)
`(html ((:a :class "cmslink" :href ,url)
,@body)))
Wenn man jetzt aber Javascript noch einführen will, dann wird das ganz schön anstrengend, denn so einfach geformt wie HTML ist javascript nun wieder nicht. Aber auf ganz viele “print”, bzw FORMAT Aufrufe zurückzugreifen, ne, wirklich, das gehört sich nicht. Und den Javascript-Code aufzutrennen in zusätzliche Dateien ist je nachdem auch ein bisschen zuviel Aufwand. Und was ist, wenn Javascript-Code dynamisch generiert werden soll, z.B. um Inhalte aus der Datenbank “effizient” in Javascript zu mappen (anstatt jetzt wilde XML/RPC Orgien in javascript zu veranstalten). Nun, ein komplizierte Sprache ist Javascript nicht wirklich, man könnte doch sowas ähnliches wie Linj bauen. Linj ist eine Art “Compiler”, der eine Lisp-ähnliche Sprache nach lesbarem Java konvertiert. Lisp-ähnlich heisst hier eher sowas wie ein “Subset” von Lisp, was nach Java “umgewandelt” wird. Also nix mit Compilen nach Java-Bytecode, sondern Java-Quellcode, und Quellcode den man noch lesen kann. Der Ansatz ist hier praktisch alle Java-Konstrukte in Lisp nachzumodellieren, bzw. die Lisp-Konstrukte wie DO, DEFUN, usw… nach Java umzuwandeln.
Der erste Schritt ist hier die Variablennennung, die bei Javascript vielleicht noch wichtiger ist, weil Javascript meistens sehr sehr viel auf die schon existierenden Element im DOM-Baum zurückgreift. Problem ist, Lisp (oder zumindest der Lisp-Reader) ist case-insensitive. Wenn ich also foObAr oder FOOBAR oder fooBar oder foobar schreibe, der Lisp-Reader macht daraus immer FOOBAR. Wenn ich jetzt wirklich mit camelCase was machen will, dann muss ich mir da echt Mühe geben, und z.B. |camelCase| schreiben, also überall so kleine hässliche Pipes reinbauen. Das ist auch keinem Entwickler (oder Entwicklerin) zumutbar, da muss was anderes her. Da hab ich mir was ganz ungeniert bei Linj abgeschaut. Bei Linj wird die LISP-übliche Variablen und Funktionsnennung nach Java konvertiert, bzw. andersrum: getFoobar in Java wird da zu get-foobar in Lisp. Die Konstante +MAX-SIZE+ in Lisp wird zu MAXSIZE in Java, WTF?#! in Lisp wird zu WTFQuestionHashBang in Java. Um das zu machen bedarf es ein bisschen Magie, die ich hier mal *hust* erläutern werde. Ach ja, der Code hier ist nicht wirklich fein, nicht wirklich bugfrei, nicht wirklich getestet, aber ich wollte ja heute noch unbedingt etwas posten:
(defvar *special-chars*
'((#\! . "Bang")
(#\? . "What")
(#\# . "Hash")
(#\@ . "At")
(#\% . "Percent")
(#\+ . "Plus")))
(defun string-chars (string)
(coerce string 'list))
(defun constant-string-p (string)
(let ((len (length string))
(constant-chars '(#\+ #\*)))
(and (> len 2)
(member (char string 0) constant-chars)
(member (char string (1- len)) constant-chars))))
(defun symbol-to-js (symbol)
(when (symbolp symbol)
(setf symbol (symbol-name symbol)))
(let (res
(lowercase t)
(all-uppercase nil))
(when (constant-string-p symbol)
(setf all-uppercase t
symbol (subseq symbol 1 (1- (length symbol)))))
(flet ((reschar (c)
(push (if (and lowercase (not all-uppercase))
(char-downcase c)
(char-upcase c)) res)
(setf lowercase t)))
(dotimes (i (length symbol))
(let ((c (char symbol i)))
(case c
(#\- (setf lowercase (not lowercase)))
('(#\! #\? #\+ #\$ #\@ #\# #\%)
(dolist (i (coerce (cdr (assoc c *special-chars*)) 'list))
(reschar i)))
(t (reschar c))))))
(coerce (nreverse res) 'string)))
Als erstes haben wir hier eine Assoziationsliste namens *SPECIAL-CHARS*, die die Zuweisung von “schrägen” Buchstaben in Lispnotation zu ihrem verbosen Bloatäquivalent in Javascript festhält. Eine Assoziationsliste ist so etwas wie ein “Poor Man’s Hash-Table”, in Lisp gibt es da eine Funktion ASSOC mit der man da einfach drinsuchen kann. Weiter geht’s mit der Funktion STRING-CHARS, die ein String zu einer Listen von Buchstaben konvertiert, eigentlich nur da weil mich das Rumgehampel mit COERCE genervt hat, es gibt da aber bestimmt bessere Wege, über ein String zu iterieren. Die Funktion CONSTANT-STRING-P guckt eigentlich nur nach, ob der vorliegende String in Lispnotation sowas wie eine “Lisp-Konstante” ist, also sowas wie +KONSTANTE+ oder *KONSTANTE*. Das -P am Ende der Funktion steht für “Predicate” und heisst, dass CONSTANT-STRING-P irgendwas auf Wahrheit überprüft (in dem Fall ob der String eine Konstante nennt), und T (für Wahr) oder NIL (für Falsch) zurückgibt. So, und SYMBOL-TO-JS ist das Prachtstück, und ich bin gerade zu müde, um diesen Bloatcode zu erklären, gehe deshalb jetzt schlafen, und mache morgen weiter. In der Zeit dürft ihr ja untersuchen, was da genau passiert. Wer ineffiziente Grütze findet darf sie ja gerne behalten, oder vielleicht ein bisschen in die Kommentare unten schmieren (die jetzt wieder gehen müssten, und vor Blinden und Spammern effektiv geschützt sind durch MT Secure Code).
NEINGEIST WAS HERE
Comment by NEINGEIST — February 22, 2005 @ 5:58 am