{"id":88,"date":"2005-02-24T23:39:59","date_gmt":"2005-02-24T21:39:59","guid":{"rendered":"http:\/\/blogs.bl0rg.net\/netzstaub\/?p=88"},"modified":"2005-02-24T23:39:59","modified_gmt":"2005-02-24T21:39:59","slug":"javascriptcompiler","status":"publish","type":"post","link":"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/02\/24\/javascriptcompiler\/","title":{"rendered":"Javascriptcompiler"},"content":{"rendered":"<p>\nVor <a href=\"http:\/\/blogs.bl0rg.net\/netzstaub\/archives\/000447.html\">kurzem<\/a> habe ich kurz erkl&#228;rt, was es mit meinem Javascriptcompiler auf sich hat. Auf Grund erh&#246;htem Idlens bin ich jetzt nicht so schnell vorangekommen wie ich es wollte, aber langsam wird das ganz schon recht brauchbar. Der Compiler funktioniert wie folgt: als Eingabe bekommt er eine Liste von Lisp-artigem Code, der nach Javascript transformiert werden soll. Hier ist es ganz wichtig: das ist kein Lisp was da reinkommt, sondern nur eine Liste, die nach Lisp-Code aussieht, aber eigentlich so eine Art lose definiertes Javascript-Lisp-Dialekt ist. Dieses Dialekt kann man dann relativ einfach in &#8220;richtigem&#8221; Lisp-Code einbetten. Als Ausgabe kommt eine Liste von entweder Strings oder weitere Listen, die menschenlesbaren Javascript sind. Die Listen sind dazu da, um die &#8220;Verschachtelung&#8221; des Codes zu veranschaulichen, so dass der Code auch lesbar indentiert werden kann. Dieser Eintrag wird recht lang, mehr also im Body&#8230;\n<\/p>\n<p><!--more--><\/p>\n<p>\n<strong>Ein kleines Beispiel:<\/strong>\n<\/p>\n<p>\nUnser Javascript-Lisp-Dialekt (ALERT ist z.B. eine Funktion die es in Lisp nicht gibt):\n<\/p>\n<pre>(if (&gt; 3 4) (alert \"3 ist groesser als 4\") (alert \"4 ist groesser als 3\"))\n<\/pre>\n<p>\nWas da rauskommt:\n<\/p>\n<pre>CL-USER&gt; (js-compile '(if (&gt; 3 4) (alert \"3 ist groesser als 4\") (alert \"4 ist groesser als 3\")))\n<\/pre>\n<p>\nergibt\n<\/p>\n<pre>((\"if (3 &gt; 4) {\" \n  (\"alert(\\\"3 ist groesser als 4\\\");\")\n  \"} else {\" \n  (\"alert(\\\"4 ist groesser als 3\\\");\") \"}\"))\n<\/pre>\n<p>\nUnd wenn man es indentiert:\n<\/p>\n<pre>CL-USER&gt; (js-indent *)\n\"if (3 &gt; 4) {\n   alert(\\\"3 ist groesser als 4\\\");\n} else {\n   alert(\\\"4 ist groesser als 3\\\");\n}\n\"\n<\/pre>\n<p>\n<strong>Die Kernfunktion JS-COMPILE<br \/>\n<br \/><\/strong>\n<\/p>\n<p>\nWie ihr schon sehen konntet ist die Kernfunktion von dem Compiler die Funktion JS-COMPILE. Diese Funktion nimmt eine Lisp-Datenstruktur (also eine Nummer, ein String, ein Symbol oder eben eine Liste), und transformiert diese nach Javascript. Bei Nummern und Strings ist das recht einfach, bei Symbolen wenden wir die kleine Voodoofunktion von oben an, bei Listen wirds allerdings ein bisschen komplizierter. Aber erstmal die Funktion:\n<\/p>\n<pre>(defun js-compile (expr)\n  (setf expr (js-expand-form expr))\n  (cond\n    ((null expr) nil)\n    ((symbolp expr) `(,(symbol-to-js expr)))\n    ((stringp expr) `(,(prin1-to-string expr)))\n    ((numberp expr) `(,(prin1-to-string expr)))\n\n    ((not (listp expr))\n     (error \"Unknown atomar expression ~A\" expr))\n\n    (t (js-form expr))))\n<\/pre>\n<p>\nWie man erkennen kann (ich denke das geht auch recht gut f&#252;r Leute die jetzt kein Lisp k&#246;nnen) besteht die Funktion aus einem grossen COND Statement. COND ist sowas wie case in C, eine Art Fallunterscheidung. Hier: Wenn das ein Symbol ist, gib bitte (SYMBOL-TO-JS EXPR) zur&#252;ck, wandle das Symbol also zu eine Javascriptnamen um. Wenn es ein String ist, dann gib einfach das gequotete String zur&#252;ck. Wenn es eine Nummer ist, genauso, und sonst rufe JS-FORM auf. Wer ganz schlau ist hat bestimmt gemerkt, dass wir hier nicht direkt Strings oder Nummern zur&#252;ckgeben, sondern Listen, das erkl&#228;re ich aber sp&#228;ter.\n<\/p>\n<p>\n<strong>Makros in unserem Lisp-Javascript-Dialekt<\/strong>\n<\/p>\n<p>\nEins der Hauptgr&#252;nde f&#252;r den Compiler &#252;berhaupt ist, dass ich in Javascript liebend gerne Lisp-Makros einsetzen will, um mir Tipparbeit zu sparen. Diese Makros m&#252;ssen wir aber beim Compilen aufrufen, das geschieht nicht automatisch. Dazu rufen wir ganz zu Anfang von JS-COMPILE die Funktion JS-EXPAND-FORM auf, die die zu kompilierende Datenstruktur auf Makros untersucht, und diese Makros dann auch auswertet. Um Makros auszuwerten gibt es in Lisp die Funktion MACROEXPAND. Allerdings kann ich diese nicht direkt anwenden, weil viele &#8220;native&#8221; Konstrukte von meinem Lisp-Dialekt (z.B. IF, oder AND) selber als LISP-Makros realisiert sind, und die will ich bloss nicht auswerten, weil da kommen dann unverst&#228;ndliche Compilerinnerein zum Vorschein. Z.B.:\n<\/p>\n<pre>CL-USER&gt; (macroexpand '(and a b))\n(IF A (AND B))\n<\/pre>\n<p>\nUm sowas zu verhindern &#252;berpr&#252;ft die Funktion JS-EXPAND-FORM, ob es sich um ein &#8220;natives&#8221; Konstrukt handelt, oder ob da vielleicht doch ein Makro zum Einsatz kommen k&#246;nnte. Ganz sauber ist das hier nicht, und ich bin mir noch nicht so sicher, ob ich das im Endeffekt auch beibehalten werden. Das Problem ist n&#228;mlich, dass hier zwei semantische Ebenen vermischt werden. Die Datenstruktur, die kompiliert wird, sollte eigentlich reiner Lisp-Javascript-Dialekt sein. Jedoch wird diese Datenstruktur mit Funktionen verarbeitet, die eigentlich auf &#8220;richtigem&#8221; Lisp arbeiten. Da kann es zu dummen Nebeneffekten kommen. Aber nun gut, hier erstmal JS-EXPAND-FORM:\n<\/p>\n<pre>(defun js-expand-form (expr)\n  (cond ((atom expr) expr)\n        ((op-form-p expr) expr)\n        (t (let* ((name (car expr))\n                  (js-form (gethash name *js-forms*)))\n             (if js-form\n                 expr\n                 (macroexpand expr))))))\n<\/pre>\n<p>\nWieder haben wir hier eine grosse Fallunterscheidung. Ist EXPR atomar (also keine Liste), dann gibt es hier kein Makro, das es anzuwenden gilt, und wir geben einfach EXPR zur&#252;ck. Ist EXPR eine Liste, ist aber das erste Element ein Operator (z.B. + oder AND), dann gibt es auch kein anzuwendendes Makro, und EXPR wird zur&#252;ckgegeben. Hier wird also z.B. vermieden, das aus (AND A B) was komisches gemacht wird. Als allerletztes wird noch &#252;berpr&#252;ft, ob es sich hier um eine &#8220;Spezialform&#8221; unserer Lisp-Javascript-Dialektsprache handelt, die auch nicht expandiert werden darf. Alle Spezialformen werden in einer Hashtabelle namens *JS-FORMS* festgehalten. Wenn EXPR auch keine Spezialform ist, wird endlich das b&#246;se MACROEXPAND angewendet.\n<\/p>\n<p>\n<strong>Der &#8220;eigentliche&#8221; Compiler<br \/>\n<br \/><\/strong><br \/>\n<br \/>Jetzt habt ihr die Funktion JS-COMPILE gesehen, und denkt bestimmt: &#8220;die macht ja aber gar nichts&#8221;. Die eigentliche Arbeit wird n&#228;mlich bei dem &#220;bersetzen von Listen geleistet. Diese Rolle &#252;bernimmt die Funktion JS-FORM. Eine Liste kann mehrere Sachen sein. Entweder, sie ist ein arithmetischer (bzw. logicher Ausdruck), eine sogenannte Expression in dem <a href=\"http:\/\/www.ecma-international.org\/publications\/standards\/Ecma-262.htm\">Javascript-Standard<\/a>. Oder sie ist ein Funktionsaufruf (eigentlich auch eine Expression). Oder sie ist eben eine Spezialform, wie z.B. IF, oder eine Variablendeklaration (LET), usw&#8230; Diese Fallunterscheidung macht JS-FORM, und den Code will ich euch nicht weiter vorenthalten:\n<\/p>\n<pre>(defun js-form (expr)\n  (let* ((name (car expr))\n         (args (cdr expr))\n         (js-form (gethash name *js-forms*)))\n    (cond (js-form \n           (let ((*js-form-name* name))\n             (apply js-form args)))\n          ((op-form-p expr)\n           (list (js-expression expr)))\n          (t \n           ;; sonst funktionsaufruf\n           (list (format nil \"~A(~{~A~^, ~})\"\n                         (js-compile-single name)\n                         (mapcar #'js-compile-single args)))))))\n<\/pre>\n<p>\nEine Listen-EXPR sieht immer so aus (NAME ARG1 ARG2 ARG3&#8230;). Den Namen holen wir uns als erstes raus, und gucken in unserer Hashtabelle *JS-FORMS* nach, ob EXPR vielleicht eine Spezialform ist. In dem Fall rufen wir den Code auf, der diese Spezialform verarbeiten kann (wird im n&#228;chsten Abschnitt dokumentiert). Wenn der Ausdruck eine Expression ist, also ein Operator zum Einsatz kommt, dann wird die Funktion JS-EXPRESSION aufgerufen. Im Defaultfall wir daraus einfach ein Funktionsaufruf gemacht. FORMAT ist &#252;brigens das Lisp-&#196;quivalent f&#252;r printf, und ist so eine kleine Programmiersprache f&#252;r sich. Hier ein kleines Beispiel:\n<\/p>\n<pre>CL-USER&gt; (JS-FORM '(print \"Hallo Welt\"))\n<\/pre>\n<p>\nergibt:\n<\/p>\n<pre>(\"print(\\\"Hallo Welt\\\")\")\n<\/pre>\n<p>\n<strong>Spezialformen in unserem Compiler<\/strong>\n<\/p>\n<p>\nUns ist jetzt schon ein paarmal die Hashtabelle *JS-FORMS* begegnet, in der die Spezialformen abgelegt sind. Der Trick ist, dass die Funktion, die eine Spezialform verarbeiten kann, unter dem Namen der Spezialform in der Tabelle abgelegt ist. Begegnet uns eine Spezialform, brauchen wir nur in der Tabelle nachzugucken und die Funktion aufzurufen (genau das macht JS-FORM). In der ersten Version des Compilers war JS-FORM eine riesige Fallunterscheidung, die alle Spezialformen behandeln konnte. Das wurde nach 3 Spezialformen ein bisschen l&#228;stig. Deswegen kam jetzt ein Makro zum Einsatz (wer h&#228;tte es gedacht). Mit dem Makro kann man eine Funktion definieren, die ein Spezialform behandelt, und die Funktion wird automagisch in der Hashtabelle abgelegt. Das Makro heisst DEFJSFORM, und ein Anwendungsbeispiel sieht wie folgt aus:\n<\/p>\n<pre>(defjsform return (arg)\n  (concatenate 'string \"return \" (js-compile-single arg)))\n<\/pre>\n<p>\nHier wird die Spezialform RETURN definiert, die eigentlich nicht viel mehr macht als ihr Argument auszuwerten, und &#8220;return&#8221; noch vorne dranh&#228;ngen. Interessanter ist die Makroexpansion von DEFJSFORM:\n<\/p>\n<pre>CL-USER&gt; (macroexpand '(defjsform return (arg)\n  (concatenate 'string \"return \" (js-compile-single arg))))\n<\/pre>\n<p>\nergibt:\n<\/p>\n<pre>(PROGN (DEFUN JS-RETURN (ARG)\n         (LET ((#:G1618\n                (PROGN (CONCATENATE 'STRING\n                                    \"return \"\n                                    (JS-COMPILE-SINGLE ARG)))))\n           (LIST #:G1618)))\n       (DOLIST (MATCH '(RETURN)) (SETF (GETHASH MATCH *JS-FORMS*) #'JS-RETURN)))\n<\/pre>\n<p>\nHier wird zuerst eine Funktion namens JS-RETURN definiert, die ein Argument nimmt. Dieses Argument wird wie angegeben verarbeitet. Schliesslich wird die Funktion unter &#8216;RETURN in der Tabelle *JS-FORMS* abgelegt. Das DOLIST sieht ein bisschen kompliziert, ist aber dazu da, um eine Verarbeitungsfunktion unter mehreren Namen abzulegen. Zum Beispiel so (weil der Code f&#252;r INCF bzw. DECF ziemlich derselbe ist):\n<\/p>\n<pre>(defjsform (incf-decf incf decf) (form)\n  (concatenate 'string (cdr (assoc *js-form-name* '((incf . \"++\")\n                                                    (decf . \"--\"))))\n               (js-compile-single form)))\n<\/pre>\n<p>\n<strong>Zusammenfassung<br \/>\n<br \/><\/strong><br \/>\n<br \/>Der Compiler ist noch nicht wirklich fertig, aber es fehlt nicht mehr viel. Ich hoffe das dieser kleine Einblick verst&#228;ndlich war (ich beantworte auch gerne Fragen), und gezeigt hat, wie schnell man eigentlich einen Compiler bauen kann. Die ganze l&#228;stige Kleinkramerei mit Lexer und Parser bleibt uns ja zum Gl&#252;ck erspart \ud83d\ude42\n<\/p>\n<p>\nZum Abschluss ein komplizierteres Beispiel:\n<\/p>\n<pre>(jsfunction vis-by-class (class-name state)\n    (if (and (blorg)\n             (bla.foobar (bla.foobar) 1 2))\n        (let ((a-blorg 2)) (setf a-blorg (+ 2 a-blorg)))\n        (progn (setf a (* 1 2 3 4)) 2))\n    (do ((i 0 (incf i))\n         (j n (decf j)))\n        ((null j))\n      (print i)))\n<\/pre>\n<p>\nergibt:\n<\/p>\n<pre>\"function visByClass(className, state) {\n      if (blorg() &#38;&#38; bla.foobar(bla.foobar(), 1, 2)) {\n         {\n            var aBlorg = 2;\n            aBlorg = 2 + aBlorg;\n         }\n      } else {\n         a = 1 * 2 * 3 * 4;\n         2;\n      }\n      for (var i = 0, var j = n; !null(j); i = ++i, j = --j) {\n         print(i);\n      }\n}\n\"\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Vor kurzem habe ich kurz erkl&#228;rt, was es mit meinem Javascriptcompiler auf sich hat. Auf Grund erh&#246;htem Idlens bin ich jetzt nicht so schnell vorangekommen wie ich es wollte, aber langsam wird das ganz schon recht brauchbar. Der Compiler funktioniert &hellip; <a href=\"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/02\/24\/javascriptcompiler\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-88","post","type-post","status-publish","format-standard","hentry","category-lispstaub"],"_links":{"self":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/88","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/comments?post=88"}],"version-history":[{"count":0,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/88\/revisions"}],"wp:attachment":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/media?parent=88"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/categories?post=88"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/tags?post=88"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}