Fix variable-arity multiplication and so read-float
[jscl.git] / src / compiler / codegen.lisp
1 ;;; compiler-codege.lisp --- Naive Javascript unparser
2
3 ;; copyright (C) 2013 David Vazquez
4
5 ;; JSCL is free software: you can redistribute it and/or
6 ;; modify it under the terms of the GNU General Public License as
7 ;; published by the Free Software Foundation, either version 3 of the
8 ;; License, or (at your option) any later version.
9 ;;
10 ;; JSCL is distributed in the hope that it will be useful, but
11 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 ;; General Public License for more details.
14 ;;
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
17
18 ;;; This code generator takes as input a S-expression representation
19 ;;; of the Javascript AST and generates Javascript code without
20 ;;; redundant syntax constructions like extra parenthesis.
21 ;;;
22 ;;; It is intended to be used with the new compiler. However, it is
23 ;;; quite independent so it has been integrated early in JSCL.
24
25 (/debug "loading compiler-codegen.lisp!")
26
27 (defvar *js-macros* nil)
28 (defmacro define-js-macro (name lambda-list &body body)
29   (let ((form (gensym)))
30     `(push (cons ',name
31                  (lambda (,form)
32                    (block ,name
33                      (destructuring-bind ,lambda-list ,form
34                        ,@body))))
35            *js-macros*)))
36
37 (defun js-macroexpand (js)
38   (if (and (consp js) (assoc (car js) *js-macros*))
39       (let ((expander (cdr (assoc (car js) *js-macros*))))
40         (multiple-value-bind (expansion stop-expand-p)
41             (funcall expander (cdr js))
42           (if stop-expand-p
43               expansion
44               (js-macroexpand expansion))))
45       js))
46
47
48 (defconstant no-comma 12)
49
50 (defvar *js-output* t)
51
52 (defvar *js-pretty-print* t)
53
54 ;;; Two seperate functions are needed for escaping strings:
55 ;;;  One for producing JavaScript string literals (which are singly or
56 ;;;   doubly quoted)
57 ;;;  And one for producing Lisp strings (which are only doubly quoted)
58 ;;;
59 ;;; The same function would suffice for both, but for javascript string
60 ;;; literals it is neater to use either depending on the context, e.g:
61 ;;;  foo's => "foo's"
62 ;;;  "foo" => '"foo"'
63 ;;; which avoids having to escape quotes where possible
64 (defun js-escape-string (string)
65   (let ((index 0)
66         (size (length string))
67         (seen-single-quote nil)
68         (seen-double-quote nil))
69     (flet ((%js-escape-string (string escape-single-quote-p)
70              (let ((output "")
71                    (index 0))
72                (while (< index size)
73                  (let ((ch (char string index)))
74                    (when (char= ch #\\)
75                      (setq output (concat output "\\")))
76                    (when (and escape-single-quote-p (char= ch #\'))
77                      (setq output (concat output "\\")))
78                    (when (char= ch #\newline)
79                      (setq output (concat output "\\"))
80                      (setq ch #\n))
81                    (setq output (concat output (string ch))))
82                  (incf index))
83                output)))
84       ;; First, scan the string for single/double quotes
85       (while (< index size)
86         (let ((ch (char string index)))
87           (when (char= ch #\')
88             (setq seen-single-quote t))
89           (when (char= ch #\")
90             (setq seen-double-quote t)))
91         (incf index))
92       ;; Then pick the appropriate way to escape the quotes
93       (cond
94         ((not seen-single-quote)
95          (concat "'"   (%js-escape-string string nil) "'"))
96         ((not seen-double-quote)
97          (concat "\""  (%js-escape-string string nil) "\""))
98         (t (concat "'" (%js-escape-string string t)   "'"))))))
99
100
101 (defun js-format (fmt &rest args)
102   (apply #'format *js-output* fmt args))
103
104 (defun valid-js-identifier (string-designator)
105   (let ((string (typecase string-designator
106                   (symbol (symbol-name string-designator))
107                   (string string-designator)
108                   (t
109                    (return-from valid-js-identifier (values nil nil))))))
110     (flet ((constitutentp (ch)
111              (or (alphanumericp ch) (member ch '(#\$ #\_)))))
112       (if (and (every #'constitutentp string)
113                (if (plusp (length string))
114                    (not (digit-char-p (char string 0)))
115                    t))
116           (values (format nil "~a" string) t)
117           (values nil nil)))))
118
119 (defun js-identifier (string-designator)
120   (multiple-value-bind (string valid)
121       (valid-js-identifier string-designator)
122     (unless valid
123       (error "~S is not a valid Javascript identifier." string))
124     (js-format "~a" string)))
125
126 (defun js-primary-expr (form)
127   (cond
128     ((numberp form)
129      (if (<= 0 form)
130          (js-format "~a" form)
131          (js-expr `(- ,(abs form)))))
132     ((stringp form)
133      (js-format "~a" (js-escape-string form)))
134     ((symbolp form)
135      (case form
136        (true      (js-format "true"))
137        (false     (js-format "false"))
138        (null      (js-format "null"))
139        (this      (js-format "this"))
140        (undefined (js-format "undefined"))
141        (otherwise
142         (js-identifier form))))
143     (t
144      (error "Unknown Javascript syntax ~S." form))))
145
146 (defun js-vector-initializer (vector)
147   (let ((size (length vector)))
148     (js-format "[")
149     (dotimes (i (1- size))
150       (let ((elt (aref vector i)))
151         (unless (eq elt 'null)
152           (js-expr elt no-comma))
153         (js-format ",")))
154     (when (plusp size)
155       (js-expr (aref vector (1- size)) no-comma))
156     (js-format "]")))
157
158 (defun js-object-initializer (plist)
159   (js-format "{")
160   (do* ((tail plist (cddr tail)))
161        ((null tail))
162     (let ((key (car tail))
163           (value (cadr tail)))
164       (multiple-value-bind (identifier identifier-p) (valid-js-identifier key)
165         (declare (ignore identifier))
166         (if identifier-p
167             (js-identifier key)
168             (js-expr (string key) no-comma)))
169       (js-format ": ")
170       (js-expr value no-comma)
171       (unless (null (cddr tail))
172         (js-format ","))))
173   (js-format "}"))
174
175 (defun js-function (arguments &rest body)
176   (js-format "function(")
177   (when arguments
178     (js-identifier (car arguments))
179     (dolist (arg (cdr arguments))
180       (js-format ",")
181       (js-identifier arg)))
182   (js-format ")")
183   (js-stmt `(group ,@body) t))
184
185 (defun check-lvalue (x)
186   (unless (or (symbolp x)
187               (nth-value 1 (valid-js-identifier x))
188               (and (consp x)
189                    (member (car x) '(get = property))))
190     (error "Bad Javascript lvalue ~S" x)))
191
192 ;;; Process the Javascript AST to reduce some syntax sugar.
193 (defun js-expand-expr (form)
194   (if (consp form)
195       (case (car form)
196         (+
197          (case (length (cdr form))
198            (1 `(unary+ ,(cadr form)))
199            (t (reduce (lambda (x y) `(+ ,x ,y)) (cdr form)))))
200         (-
201          (case (length (cdr form))
202            (1 `(unary- ,(cadr form)))
203            (t (reduce (lambda (x y) `(- ,x ,y)) (cdr form)))))
204         (*
205          (case (length (cdr form))
206            (0 1)
207            (t (reduce (lambda (x y) `(* ,x ,y)) (cdr form)))))
208         ((and or)
209          (reduce (lambda (x y) `(,(car form) ,x ,y)) (cdr form)))
210         ((progn comma)
211          (reduce (lambda (x y) `(comma ,x ,y)) (cdr form) :from-end t))
212         (t
213          (js-macroexpand form)))
214       form))
215
216 (defun js-operator-expression (op args precedence associativity operand-order)
217   (let ((op1 (car args))
218         (op2 (cadr args)))
219     (case op
220       ;; Accessors
221       (property
222        (js-expr (car args) 0)
223        (js-format "[")
224        (js-expr (cadr args) no-comma)
225        (js-format "]"))
226       (get
227        (multiple-value-bind (accessor accessorp)
228            (valid-js-identifier (cadr args))
229          (unless accessorp
230            (error "Invalid accessor ~S" (cadr args)))
231          (js-expr (car args) 0)
232          (js-format ".")
233          (js-identifier accessor)))
234       ;; Function call
235       (call
236        (js-expr (car args) 1)
237        (js-format "(")
238        (when (cdr args)
239          (js-expr (cadr args) no-comma)
240          (dolist (operand (cddr args))
241            (js-format ",")
242            (js-expr operand no-comma)))
243        (js-format ")"))
244       ;; Object syntax
245       (object
246        (js-object-initializer args))
247       ;; Function expressions
248       (function
249        (js-format "(")
250        (apply #'js-function args)
251        (js-format ")"))
252       (t
253        (labels ((low-precedence-p (op-precedence)
254                   (cond
255                     ((> op-precedence precedence))
256                     ((< op-precedence precedence) nil)
257                     (t (not (eq operand-order associativity)))))
258
259                 (%unary-op (operator string operator-precedence operator-associativity post lvalue)
260                   (when (eq op operator)
261                     (when lvalue (check-lvalue op1))
262                     (when (low-precedence-p operator-precedence) (js-format "("))
263                     (cond
264                       (post
265                        (js-expr op1 operator-precedence operator-associativity 'left)
266                        (js-format "~a" string))
267                       (t
268                        (js-format "~a" string)
269                        (js-expr op1 operator-precedence operator-associativity 'right)))
270                     (when (low-precedence-p operator-precedence) (js-format ")"))
271                     (return-from js-operator-expression)))
272
273                 (%binary-op (operator string operator-precedence operator-associativity lvalue)
274                   (when (eq op operator)
275                     (when lvalue (check-lvalue op1))
276                     (when (low-precedence-p operator-precedence) (js-format "("))
277                     (js-expr op1 operator-precedence operator-associativity 'left)
278                     (js-format "~a" string)
279                     (js-expr op2 operator-precedence operator-associativity 'right)
280                     (when (low-precedence-p operator-precedence) (js-format ")"))
281                     (return-from js-operator-expression))))
282
283          (macrolet ((unary-op (operator string precedence associativity &key post lvalue)
284                       `(%unary-op ',operator ',string ',precedence ',associativity ',post ',lvalue))
285                     (binary-op (operator string precedence associativity &key lvalue)
286                       `(%binary-op ',operator ',string ',precedence ',associativity ',lvalue)))
287
288            (unary-op pre++       "++"            2    right :lvalue t)
289            (unary-op pre--       "--"            2    right :lvalue t)
290            (unary-op post++      "++"            2    right :lvalue t :post t)
291            (unary-op post--      "--"            2    right :lvalue t :post t)
292            (unary-op not         "!"             2    right)
293            (unary-op bit-not     "~"             2    right)
294            ;; Note that the leading space is necessary because it
295            ;; could break with post++, for example. TODO: Avoid
296            ;; leading space when it's possible.
297            (unary-op unary+      " +"            2    right)
298            (unary-op unary-      " -"            2    right)
299            (unary-op delete      "delete "       2    right)
300            (unary-op void        "void "         2    right)
301            (unary-op typeof      "typeof "       2    right)
302            (unary-op new         "new "          2    right)
303
304            (binary-op *          "*"             3    left)
305            (binary-op /          "/"             3    left)
306            (binary-op mod        "%"             3    left)
307            (binary-op %          "%"             3    left)
308            (binary-op +          "+"             4    left)
309            (binary-op -          "-"             5    left)
310            (binary-op <<         "<<"            5    left)
311            (binary-op >>         "<<"            5    left)
312            (binary-op >>>        ">>>"           5    left)
313            (binary-op <=         "<="            6    left)
314            (binary-op <          "<"             6    left)
315            (binary-op >          ">"             6    left)
316            (binary-op >=         ">="            6    left)
317            (binary-op instanceof " instanceof "  6    left)
318            (binary-op in         " in "          6    left)
319            (binary-op ==         "=="            7    left)
320            (binary-op !=         "!="            7    left)
321            (binary-op ===        "==="           7    left)
322            (binary-op !==        "!=="           7    left)
323            (binary-op bit-and    "&"             8    left)
324            (binary-op bit-xor    "^"             9    left)
325            (binary-op bit-or     "|"            10    left)
326            (binary-op and        "&&"           11    left)
327            (binary-op or         "||"           12    left)
328            (binary-op =          "="            13    right :lvalue t)
329            (binary-op +=         "+="           13    right :lvalue t)
330            (binary-op incf       "+="           13    right :lvalue t)
331            (binary-op -=         "-="           13    right :lvalue t)
332            (binary-op decf       "-="           13    right :lvalue t)
333            (binary-op *=         "*="           13    right :lvalue t)
334            (binary-op /=         "*="           13    right :lvalue t)
335            (binary-op bit-xor=   "^="           13    right :lvalue t)
336            (binary-op bit-and=   "&="           13    right :lvalue t)
337            (binary-op bit-or=    "|="           13    right :lvalue t)
338            (binary-op <<=        "<<="          13    right :lvalue t)
339            (binary-op >>=        ">>="          13    right :lvalue t)
340            (binary-op >>>=       ">>>="         13    right :lvalue t)
341
342            (binary-op comma      ","            13    right)
343            (binary-op progn      ","            13    right)
344
345            (when (member op '(? if))
346              (when (low-precedence-p 12) (js-format "("))
347              (js-expr (first args) 12 'right 'left)
348              (js-format "?")
349              (js-expr (second args) 12 'right 'right)
350              (js-format ":")
351              (js-expr (third args) 12 'right 'right)
352              (when (low-precedence-p 12) (js-format ")"))
353              (return-from js-operator-expression))
354
355            (error "Unknown operator `~S'" op)))))))
356
357 (defun js-expr (form &optional (precedence 1000) associativity operand-order)
358   (let ((form (js-expand-expr form)))
359     (cond
360       ((or (symbolp form) (numberp form) (stringp form))
361        (js-primary-expr form))
362       ((vectorp form)
363        (js-vector-initializer form))
364       (t
365        (js-operator-expression (car form) (cdr form) precedence associativity operand-order)))))
366
367 (defun js-expand-stmt (form)
368   (cond
369     ((and (consp form) (eq (car form) 'progn))
370      (destructuring-bind (&body body) (cdr form)
371        (cond
372          ((null body)
373           nil)
374          ((null (cdr body))
375           (js-expand-stmt (car body)))
376          (t
377           `(group ,@(cdr form))))))
378     (t
379      (js-macroexpand form))))
380
381 (defun js-end-stmt ()
382   (js-format ";")
383   (when *js-pretty-print*
384     (js-format "~%")))
385
386 (defun js-stmt (form &optional parent)
387   (let ((form (js-expand-stmt form)))
388     (flet ((js-stmt (x) (js-stmt x form)))
389       (cond
390         ((null form)
391          (unless (or (and (consp parent) (eq (car parent) 'group))
392                      (null parent))
393            (js-end-stmt)))
394         ((atom form)
395          (progn
396            (js-expr form)
397            (js-end-stmt)))
398         (t
399          (case (car form)
400            (label
401             (destructuring-bind (label &body body) (cdr form)
402               (js-identifier label)
403               (js-format ":")
404               (js-stmt `(progn ,@body))))
405            (break
406             (destructuring-bind (&optional label) (cdr form)
407               (js-format "break")
408               (when label
409                 (js-format " ")
410                 (js-identifier label))
411               (js-end-stmt)))
412            (return
413              (destructuring-bind (value) (cdr form)
414                (js-format "return ")
415                (js-expr value)
416                (js-end-stmt)))
417            (var
418             (flet ((js-var (spec)
419                      (destructuring-bind (variable &optional initial)
420                          (ensure-list spec)
421                        (js-identifier variable)
422                        (when initial
423                          (js-format "=")
424                          (js-expr initial no-comma)))))
425               (destructuring-bind (var &rest vars) (cdr form)
426                 (js-format "var ")
427                 (js-var var)
428                 (dolist (var vars)
429                   (js-format ",")
430                   (js-var var))
431                 (js-end-stmt))))
432            (if
433             (destructuring-bind (condition true &optional false) (cdr form)
434               (js-format "if (")
435               (js-expr condition)
436               (js-format ") ")
437               (js-stmt true)
438               (when false
439                 (js-format " else ")
440                 (js-stmt false))))
441            (group
442             (let ((in-group-p
443                    (or (null parent)
444                        (and (consp parent) (eq (car parent) 'group)))))
445               (unless  in-group-p (js-format "{"))
446               (mapc #'js-stmt (cdr form))
447               (unless in-group-p (js-format "}"))))
448            (while
449                (destructuring-bind (condition &body body) (cdr form)
450                  (js-format "while (")
451                  (js-expr condition)
452                  (js-format ")")
453                  (js-stmt `(progn ,@body))))
454            (switch
455             (destructuring-bind (value &rest cases) (cdr form)
456               (js-format "switch(")
457               (js-expr value)
458               (js-format "){")
459               (dolist (case cases)
460                 (cond
461                   ((and (consp case) (eq (car case) 'case))
462                    (js-format "case ")
463                    (let ((value (cadr case)))
464                      (unless (or (stringp value) (integerp value))
465                        (error "Non-constant switch case `~S'." value))
466                      (js-expr value))
467                    (js-format ":"))
468                   ((eq case 'default)
469                    (js-format "default:"))
470                   (t
471                    (js-stmt case))))
472               (js-format "}")))
473            (for
474             (destructuring-bind ((start condition step) &body body) (cdr form)
475               (js-format "for (")
476               (js-expr start)
477               (js-format ";")
478               (js-expr condition)
479               (js-format ";")
480               (js-expr step)
481               (js-format ")")
482               (js-stmt `(progn ,@body))))
483            (for-in
484             (destructuring-bind ((x object) &body body) (cdr form)
485               (js-format "for (")
486               (js-identifier x)
487               (js-format " in ")
488               (js-expr object)
489               (js-format ")")
490               (js-stmt `(progn ,@body))))
491            (try
492             (destructuring-bind (&rest body) (cdr form)
493               (js-format "try")
494               (js-stmt `(group ,@body))))
495            (catch
496                (destructuring-bind ((var) &rest body) (cdr form)
497                  (js-format "catch (")
498                  (js-identifier var)
499                  (js-format ")")
500                  (js-stmt `(group ,@body))))
501            (finally
502             (destructuring-bind (&rest body) (cdr form)
503               (js-format "finally")
504               (js-stmt `(group ,@body))))
505            (throw
506                (destructuring-bind (object) (cdr form)
507                  (js-format "throw ")
508                  (js-expr object)
509                  (js-end-stmt)))
510            (t
511             (js-expr form)
512             (js-end-stmt))))))))
513
514 (defun js (&rest stmts)
515   (mapc #'js-stmt stmts)
516   nil)