Naive Javascript code generator
authorDavid Vázquez <davazp@gmail.com>
Wed, 19 Jun 2013 02:40:05 +0000 (04:40 +0200)
committerDavid Vázquez <davazp@gmail.com>
Wed, 19 Jun 2013 02:40:05 +0000 (04:40 +0200)
experimental/codegen.lisp [new file with mode: 0644]

diff --git a/experimental/codegen.lisp b/experimental/codegen.lisp
new file mode 100644 (file)
index 0000000..4b38eb4
--- /dev/null
@@ -0,0 +1,359 @@
+;;; Naive Javascript code generator
+;;;
+;;; This code generator takes as input a S-expression representation
+;;; of the Javascript AST and generates Javascript code without
+;;; redundant syntax constructions like extra parenthesis.
+;;;
+;;; It is intended to be used with the new compiler. However, it is
+;;; quite independent so it has been integrated early in JSCL.
+
+(defun ensure-list (x)
+  (if (listp x)
+      x
+      (list x)))
+
+(defvar *js-output* t)
+
+(defun js-format (fmt &rest args)
+  (apply #'format *js-output* fmt args))
+
+(defun valid-js-identifier (string-designator)
+  (let ((string (typecase string-designator
+                  (symbol (string-downcase (symbol-name string-designator)))
+                  (string string-designator)
+                  (t
+                   (return-from valid-js-identifier (values nil nil))))))
+    (flet ((constitutentp (ch)
+             (or (alphanumericp ch) (member ch '(#\$ #\_)))))
+      (if (and (every #'constitutentp string)
+               (if (plusp (length string))
+                   (not (digit-char-p (char string 0)))
+                   t))
+          (values (format nil "~a" string) t)
+          (values nil nil)))))
+
+(defun js-identifier (string-designator)
+  (multiple-value-bind (string valid)
+      (valid-js-identifier string-designator)
+    (unless valid
+      (error "~S is not a valid Javascript identifier." string))
+    (js-format "~a" string)))
+
+(defun js-primary-expr (form)
+  (cond
+    ((numberp form)
+     (js-format "~a" form))
+    ((stringp form)
+     (js-format "'~a'" form))
+    ((symbolp form)
+     (case form
+       (true  (js-format "true"))
+       (false (js-format "false"))
+       (null  (js-format "null"))
+       (this  (js-format "this"))
+       (otherwise
+        (js-identifier form))))
+    (t
+     (error "Unknown Javascript syntax ~S." form))))
+
+(defun js-vector-initializer (vector)
+  (let ((size (length vector)))
+    (js-format "[")
+    (dotimes (i (1- size))
+      (let ((elt (aref vector i)))
+        (unless (eq elt 'null)
+          (js-expr elt))
+        (js-format ",")))
+    (when (plusp size)
+      (js-expr (aref vector (1- size))))
+    (js-format "]")))
+
+(defun js-object-initializer (plist)
+  (js-format "{")
+  (do* ((tail plist (cddr tail)))
+       ((null tail))
+    (let ((key (car tail))
+          (value (cadr tail)))
+      (multiple-value-bind (identifier identifier-p) (valid-js-identifier key)
+        (declare (ignore identifier))
+        (if identifier-p
+            (js-identifier key)
+            (js-expr (string key))))
+      (js-format ": ")
+      (js-expr value)
+      (unless (null (cddr tail))
+        (js-format ","))))
+  (js-format "}"))
+
+(defun js-function (arguments &rest body)
+  (js-format "function(")
+  (when arguments
+    (js-identifier (car arguments))
+    (dolist (arg (cdr arguments))
+      (js-format ",")
+      (js-identifier arg)))
+  (js-format ")")
+  (js-stmt `(group ,@body)))
+
+(defun check-lvalue (x)
+  (unless (or (symbolp x)
+              (nth-value 1 (valid-js-identifier x))
+              (and (consp x)
+                   (member (car x) '(get =))))
+    (error "Bad Javascript lvalue ~S" x)))
+
+;;; Process the Javascript AST to reduce some syntax sugar.
+(defun js-expand-expr (form)
+  (if (consp form)
+      (case (car form)
+        (+
+         (case (length (cdr form))
+           (1 `(unitary+ ,(cadr form)))
+           (t (reduce (lambda (x y) `(+ ,x ,y)) (cdr form)))))
+        (-
+         (case (length (cdr form))
+           (1 `(unitary- ,(cadr form)))
+           (t (reduce (lambda (x y) `(- ,x ,y)) (cdr form)))))
+        (t form))
+      form))
+
+(defvar *js-expression-precedence* 1000)
+
+(defun js-operator-expression (op args)
+  (macrolet (;; Format an expression optionally wrapped with
+             ;; parenthesis if the precedence rules require it.
+             (with-precedence (level &body body)
+               (let ((g!parens (gensym))
+                     (g!level (gensym)))
+                 `(let* ((,g!level ,level)
+                         (,g!parens (> ,g!level *js-expression-precedence*))
+                         (*js-expression-precedence* ,g!level))
+                    (when ,g!parens (js-format "("))
+                    (progn ,@body)
+                    (when ,g!parens (js-format ")"))))))
+    (let ((arity (length args))
+          (op1 (car args))
+          (op2 (cadr args))
+          (op3 (cadr args)))
+      (case op
+        (+
+         (cond
+           ((= arity 1)
+            (with-precedence 1
+              (js-format "+")
+              (js-expr op1)))
+           (t
+            (with-precedence 3
+              (js-expr op1)
+              (js-format "+")
+              (js-expr op2)))))
+        (-
+         (cond
+           ((cdr args)
+            (with-precedence 3
+              (js-expr op1)
+              (js-format "-")
+              (js-expr op2)))
+           (t
+            (with-precedence 1
+              (js-format "-")
+              (js-expr op1)))))
+        ((progn comma)
+         (with-precedence 14
+           (js-expr (car args))
+           (dolist (operand (cdr args))
+             (let ((*js-output* t))
+               (js-format ",")
+               (js-expr operand)))))
+        ;; Function call
+        (call
+         (js-expr (car args))
+         (js-format "(")
+         (when (cdr args)
+           (with-precedence 13
+             (js-expr (cadr args))
+             (dolist (operand (cddr args))
+               (let ((*js-output* t))
+                 (js-format ",")
+                 (js-expr operand)))))
+         (js-format ")"))
+        ;; Accessors
+        (get
+         (multiple-value-bind (identifier identifierp)
+             (valid-js-identifier (car args))
+           (multiple-value-bind (accessor accessorp)
+               (valid-js-identifier (cadr args))
+             (cond
+               ((and identifierp accessorp)
+                (js-identifier identifier)
+                (js-format ".")
+                (js-identifier accessor))
+               (t
+                (js-expr (car args))
+                (js-format "[")
+                (js-expr (cadr args))
+                (js-format "]"))))))
+        ;; Object syntax
+        (object
+         (js-object-initializer args))
+        ;; Function expressions
+        (function
+         (js-format "(")
+         (apply #'js-function args)
+         (js-format ")"))
+        (t
+         (flet ((unary-op (operator string precedence &key post lvalue)
+                  (when (member op (ensure-list operator))
+                    (with-precedence precedence
+                      (when lvalue (check-lvalue op1))
+                      (cond
+                        (post
+                         (js-expr op1)
+                         (js-format string))
+                        (t
+                         (js-format string)
+                         (js-expr op1))))
+                    (return-from js-operator-expression)))
+                (binary-op (operator string precedence &key lvalue)
+                  (when (member op (ensure-list operator))
+                    (when lvalue (check-lvalue op1))
+                    (with-precedence precedence
+                      (js-expr op1)
+                      (js-format string)
+                      (js-expr op2))
+                    (return-from js-operator-expression))))
+
+           (unary-op 'pre++     "++"    1 :lvalue t)
+           (unary-op 'pre--     "--"    1 :lvalue t)
+           (unary-op 'post++    "++"    1 :lvalue t :post t)
+           (unary-op 'post--    "--"    1 :lvalue t :post t)
+           (unary-op 'not--     "!"     1)
+
+           (unary-op 'unitary+  "+"     1)
+           (unary-op 'unitary-  "-"     1)
+
+           (unary-op 'delete    "delete "       1)
+           (unary-op 'delete    "void "         1)
+           (unary-op 'delete    "typeof "       1)
+
+           (binary-op '*        "*"     2)
+           (binary-op '/        "/"     2)
+           (binary-op '(mod %)  "%"     2)
+
+           (binary-op '+        "+"     3)
+           (binary-op '-        "-"     3)
+
+           (binary-op '<<       "<<"    4)
+           (binary-op '>>       "<<"    4)
+           (binary-op '>>>      ">>>"   4)
+           (binary-op '<=       "<="    5)
+           (binary-op '<        "<"     5)
+           (binary-op '>        ">"     5)
+           (binary-op '>=       ">="    5)
+
+           (binary-op 'instanceof " instanceof " 5)
+           (binary-op 'in         " in "         5)
+
+           (binary-op '==       "=="    6)
+           (binary-op '!=       "!="    6)
+           (binary-op '===      "==="   6)
+           (binary-op '!==      "!=="   6)
+
+           (binary-op 'bit-and  "&"     7)
+           (binary-op 'bit-xor  "^"     8)
+           (binary-op 'bit-or   "|"     9)
+           (binary-op 'and      "&&"    10)
+           (binary-op 'or       "||"    11)
+
+           (binary-op '=         "="    13      :lvalue t)
+           (binary-op '(+= incf) "+="   13      :lvalue t)
+           (binary-op '(-= decf) "-="   13      :lvalue t)
+           (binary-op '*=       "*="    13      :lvalue t)
+           (binary-op '/=       "*="    13      :lvalue t)
+           (binary-op 'bit-xor= "^="    13      :lvalue t)
+           (binary-op 'bit-and= "&="    13      :lvalue t)
+           (binary-op 'bit-or=  "|="    13      :lvalue t)
+           (binary-op '<<=      "<<="   13      :lvalue t)
+           (binary-op '>>=      ">>="   13      :lvalue t)
+           (binary-op '>>>=     ">>>="  13      :lvalue t)
+
+           (when (member op '(? if))
+             (with-precedence 12
+               (js-expr op1)
+               (js-format "?")
+               (js-expr op2)
+               (js-format ":")
+               (js-expr op3)))))))))
+
+
+(defun js-expr (form)
+  (let ((form (js-expand-expr form)))
+    (cond
+      ((or (symbolp form) (numberp form) (stringp form))
+       (js-primary-expr form))
+      ((vectorp form)
+       (js-vector-initializer form))
+      (t
+       (js-operator-expression (car form) (cdr form))))))
+
+
+(defun js-stmt (form)
+  (if (atom form)
+      (progn
+        (js-expr form)
+        (js-format ";"))
+      (case (car form)
+        (label
+         (js-identifier (cadr form))
+         (js-format ":")
+         (js-stmt `(progn ,@(cddr form))))
+        (break
+         (js-format "break ")
+         (js-identifier (second form))
+         (js-format ";"))
+        (return
+          (js-format "return ")
+          (js-expr (cadr form))
+          (js-format ";"))
+        (var
+         (destructuring-bind (var &rest vars) (cdr form)
+           (js-format "var ")
+           (js-identifier var)
+           (dolist (var vars)
+             (js-format ",")
+             (js-identifier var))
+           (js-format ";")))
+        (if
+         (destructuring-bind (condition true &optional false)
+             (cdr form)
+           (js-format "if (")
+           (js-expr condition)
+           (js-format ") ")
+           (js-stmt true)
+           (when false
+             (js-format " else ")
+             (js-stmt false))))
+        (group
+         (js-format "{")
+         (mapc #'js-stmt (cdr form))
+         (js-format "}"))
+        (progn
+          (cond
+            ((null (cdr form))
+             (js-format ";"))
+            ((null (cddr form))
+             (js-stmt (cadr form)))
+            (t
+             (js-stmt `(group ,@(cdr form))))))
+        (while
+            (destructuring-bind (condition &body body) (cdr form)
+              (js-format "while (")
+              (js-expr condition)
+              (js-format ")")
+              (js-stmt `(group ,@body))))
+        (t
+         (js-expr form)
+         (js-format ";")))))
+
+(defun js (&rest stmts)
+  (mapc #'js-stmt stmts))