{"id":89,"date":"2005-02-25T12:15:17","date_gmt":"2005-02-25T10:15:17","guid":{"rendered":"http:\/\/blogs.bl0rg.net\/netzstaub\/?p=89"},"modified":"2005-02-25T12:15:17","modified_gmt":"2005-02-25T10:15:17","slug":"javascriptcompiler-expressions","status":"publish","type":"post","link":"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/02\/25\/javascriptcompiler-expressions\/","title":{"rendered":"Javascriptcompiler: Expressions"},"content":{"rendered":"<p>\nEine Funktionalit&#228;t des Javascript Compilers (&#252;ber den ich <a href=\"http:\/\/blogs.bl0rg.net\/netzstaub\/archives\/000447.html\">hier<\/a> und <a href=\"http:\/\/blogs.bl0rg.net\/netzstaub\/archives\/000461.html\">hier<\/a> schon gebloggt habe) war recht interessant. Und zwar geht es darum, die vollgeklammerten und in Prefix-Syntax geschriebenen arithmetischen Ausdr&#252;cke aus dem Lisp-Javascript-Dialekt zu lesbaren Javascript-Ausdr&#252;cken umzuformen. Also z.B.\n<\/p>\n<p>\n(+ (* 3 4) 9 (- 10 5)) =&gt; (3 * 4) + 9 + (10 &#8211; 5) bzw. 3 * 4 + 9 + 10 &#8211; 5\n<\/p>\n<p>\nMit einem Trivialansatz, bei dem alles geklammert wird, um die korrekte Auswertung zu gew&#228;hrleisten, sieht das Ergebnis richtig unlesbar aus:\n<\/p>\n<p>\n((3) * (4)) + (9) + ((10) &#8211; (5))\n<\/p>\n<p>\nDer bessere Ansatz ist eher, so wenig Klammern wie m&#246;glich einzusetzen, und dazu m&#252;ssen wir die Operatorpr&#228;zedenzen  ber&#252;cksichtigen. Im Javascriptstandard steht jetzt keine klare Tabelle mit der Reihenfolge der Operatoren drin, sondern nur eine BNF-Syntax, aus der man sich das Ganze extrahieren muss. Zum Gl&#252;ck gibt es im Netz eine <a href=\"http:\/\/www.codehouse.com\/javascript\/precedence\/\">Tabelle<\/a> die man verwenden kann. Anstatt die jetzt doof abzutippen werden wir uns dort nur die Reihenfolge der Operatoren abgucken, und diese in Lisp giessen:\n<\/p>\n<pre>(defparameter *op-precedences*\n  '((aref)\n    (* \/ %)\n    (+ -)\n    (&lt;&lt; &gt;&gt;)\n    (&gt;&gt;&gt;)\n    (&lt; &gt; &lt;= &gt;=)\n    (in)\n    (eql == !=)\n    (=== !==)\n    (&#38;)\n    (^)\n    (\\|)\n    (\\&#38;\\&#38; and)\n    (\\|\\| or)\n    (setf)))\n<\/pre>\n<p>\nDiese Liste hat die Operatoren in Reihenfolge abnehmender Pr&#228;zedenz. Wie bei den Specialforms werden wir diese Liste verarbeiten und die einzelnen Operatoren in eine Hashtabelle speichern. Das wollen wir nat&#252;rlich zur Ladezeit machen, also verwenden wir eine EVAL-WHEN Form:\n<\/p>\n<pre>(defvar *op-precedence-hash* (make-hash-table))\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (let ((precedence 1))\n    (dolist (ops *op-precedences*)\n      (dolist (op ops)\n        (setf (gethash op *op-precedence-hash*) precedence))\n      (incf precedence))))\n<\/pre>\n<p>\nJetzt sind in der Hashtabelle *OP-PRECEDENCE-HASH* die Pr&#228;zedenzen der Operatoren gespeichert. Z.B.:\n<\/p>\n<pre>CL-USER&gt; (gethash '&lt; *op-precedence-hash*)\n6\n<\/pre>\n<p>\n&lt; hat also die Pr&#228;zedenz 6. Anhand der Tabelle k&#246;nnen wir auch bestimmen ob ein Ausdruck eine Expression ist (die Funktion OP-FORM-P ist uns gestern schon begegnet):\n<\/p>\n<pre>(defun op-form-p (form)\n  (and (listp form)\n       (not (null (gethash (first form) *op-precedence-hash*)))))\n<\/pre>\n<p>\nLetztendlich wollen wir die Pr&#228;zedenz von einer Expression bestimmen. Da m&#252;ssen wir die Spezialf&#228;lle von Funktionsaufrufen und atomaren Expressions (Strings, Variablen oder Nummern) ber&#252;cksichtigen). Das macht die Funktion OP-FORM-PRECEDENCE, die im bekannten Stil wieder ein grosses COND einsetzt:\n<\/p>\n<pre>(defun op-form-precedence (form)\n  (cond ((op-form-p form)\n         (gethash (first form) *op-precedence-hash*))\n        ((or (atom form)\n             (js-funcall-p form))\n         0)\n        (t (error \"~A is not an expression\" form))))\n<\/pre>\n<p>\nAtomare Expressions (Funktionsaufrufe, Strings, Variablen oder Nummern) haben die niedrigste Pr&#228;zendenz, und binden so am &#8220;St&#228;rksten&#8221;, d.h. man braucht da nie Klammern herum zu packen. Anhand dieser Werkzeugsfunktionen k&#246;nnen wir jetzt den Expression-Compiler angehen, der in Form einer Funktion namens JS-EXPRESSION implementiert ist:\n<\/p>\n<pre>(defun js-expression (form)\n  (if (or (atom form)\n          (js-funcall-p form))\n      (js-compile-single form)\n      (let ((args (cdr form))\n            (name (js-op-name (car form)))\n            (precedence (op-form-precedence form)))\n        (string-join (mapcar #'(lambda (x) (let ((prec (op-form-precedence x)))\n                                             (if (&gt; prec precedence)\n                                                 (klammer (js-expression x))\n                                                 (js-expression x))))\n                             args)\n                     (format nil \" ~A \"  name)))))\n<\/pre>\n<p>\nWenn die Expression &#8220;atomar&#8221; ist, brauchen wir nicht zu klammern, und rufen den &#8220;normalen&#8221; Compiler auf. Wenn aber FORM eine komplexere Expression ist, also eine Expression wo Operatoren zum Einsatz kommen, m&#252;ssen wir die Pr&#228;zedenz des Ausdrucks ermitteln. Das Ergebnis von (OP-FORM-PRECEDENCE FORM) wird an PRECEDENCE gebunden. Wenn jetzt ein Argument der Expression von h&#246;herer Pr&#228;zedenz ist (also loser bindet), m&#252;ssen Klammern eingesetzt werden. Sonst kann der Ausdruck so eingesetzt werden. F&#252;r die Argument einer Expression, die selber wieder Expressions sein m&#252;ssen, wird JS-EXPRESSION rekursiv aufgerufen. Einen kleinen Trick gibt es hier noch, und zwar wird die Funktion JS-OP-NAME eingesetzt um &#8220;Lisp-typische&#8221;-Namen zu &#252;bersetzen, also z.B. EQL nach ==. Zum Schluss noch ein kleines Beispiel:\n<\/p>\n<pre>CL-USER&gt; (js-expression '(+ (* 3 4) 9 (- 10 5)))\n\"3 * 4 + 9 + 10 - 5\"\nCL-USER&gt; (js-expression '(+ (setf a (blorg 2)) (* 4 5 2)))\n\"(a = blorg(2)) + 4 * 5 * 2\"\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Eine Funktionalit&#228;t des Javascript Compilers (&#252;ber den ich hier und hier schon gebloggt habe) war recht interessant. Und zwar geht es darum, die vollgeklammerten und in Prefix-Syntax geschriebenen arithmetischen Ausdr&#252;cke aus dem Lisp-Javascript-Dialekt zu lesbaren Javascript-Ausdr&#252;cken umzuformen. Also z.B. (+ &hellip; <a href=\"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/02\/25\/javascriptcompiler-expressions\/\">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-89","post","type-post","status-publish","format-standard","hentry","category-lispstaub"],"_links":{"self":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/89","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=89"}],"version-history":[{"count":0,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/89\/revisions"}],"wp:attachment":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/media?parent=89"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/categories?post=89"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/tags?post=89"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}