1 ;;; compiler-codege.lisp --- Naive Javascript unparser
3 ;; copyright (C) 2013 David Vazquez
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.
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.
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/>.
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.
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.
25 (/debug "loading compiler-codegen.lisp!")
27 (defconstant no-comma 12)
29 (defvar *js-output* t)
31 ;;; Two seperate functions are needed for escaping strings:
32 ;;; One for producing JavaScript string literals (which are singly or
34 ;;; And one for producing Lisp strings (which are only doubly quoted)
36 ;;; The same function would suffice for both, but for javascript string
37 ;;; literals it is neater to use either depending on the context, e.g:
40 ;;; which avoids having to escape quotes where possible
41 (defun js-escape-string (string)
43 (size (length string))
44 (seen-single-quote nil)
45 (seen-double-quote nil))
46 (flet ((%js-escape-string (string escape-single-quote-p)
50 (let ((ch (char string index)))
52 (setq output (concat output "\\")))
53 (when (and escape-single-quote-p (char= ch #\'))
54 (setq output (concat output "\\")))
55 (when (char= ch #\newline)
56 (setq output (concat output "\\"))
58 (setq output (concat output (string ch))))
61 ;; First, scan the string for single/double quotes
63 (let ((ch (char string index)))
65 (setq seen-single-quote t))
67 (setq seen-double-quote t)))
69 ;; Then pick the appropriate way to escape the quotes
71 ((not seen-single-quote)
72 (concat "'" (%js-escape-string string nil) "'"))
73 ((not seen-double-quote)
74 (concat "\"" (%js-escape-string string nil) "\""))
75 (t (concat "'" (%js-escape-string string t) "'"))))))
78 (defun js-format (fmt &rest args)
79 (apply #'format *js-output* fmt args))
81 (defun valid-js-identifier (string-designator)
82 (let ((string (typecase string-designator
83 (symbol (symbol-name string-designator))
84 (string string-designator)
86 (return-from valid-js-identifier (values nil nil))))))
87 (flet ((constitutentp (ch)
88 (or (alphanumericp ch) (member ch '(#\$ #\_)))))
89 (if (and (every #'constitutentp string)
90 (if (plusp (length string))
91 (not (digit-char-p (char string 0)))
93 (values (format nil "~a" string) t)
96 (defun js-identifier (string-designator)
97 (multiple-value-bind (string valid)
98 (valid-js-identifier string-designator)
100 (error "~S is not a valid Javascript identifier." string))
101 (js-format "~a" string)))
103 (defun js-primary-expr (form)
107 (js-format "~a" form)
108 (js-expr `(- ,(abs form)))))
110 (js-format "~a" (js-escape-string form)))
113 (true (js-format "true"))
114 (false (js-format "false"))
115 (null (js-format "null"))
116 (this (js-format "this"))
117 (undefined (js-format "undefined"))
119 (js-identifier form))))
121 (error "Unknown Javascript syntax ~S." form))))
123 (defun js-vector-initializer (vector)
124 (let ((size (length vector)))
126 (dotimes (i (1- size))
127 (let ((elt (aref vector i)))
128 (unless (eq elt 'null)
129 (js-expr elt no-comma))
132 (js-expr (aref vector (1- size)) no-comma))
135 (defun js-object-initializer (plist)
137 (do* ((tail plist (cddr tail)))
139 (let ((key (car tail))
141 (multiple-value-bind (identifier identifier-p) (valid-js-identifier key)
142 (declare (ignore identifier))
145 (js-expr (string key) no-comma)))
147 (js-expr value no-comma)
148 (unless (null (cddr tail))
152 (defun js-function (arguments &rest body)
153 (js-format "function(")
155 (js-identifier (car arguments))
156 (dolist (arg (cdr arguments))
158 (js-identifier arg)))
160 (js-stmt `(group ,@body) t))
162 (defun check-lvalue (x)
163 (unless (or (symbolp x)
164 (nth-value 1 (valid-js-identifier x))
166 (member (car x) '(get = property))))
167 (error "Bad Javascript lvalue ~S" x)))
169 ;;; Process the Javascript AST to reduce some syntax sugar.
170 (defun js-expand-expr (form)
174 (case (length (cdr form))
175 (1 `(unary+ ,(cadr form)))
176 (t (reduce (lambda (x y) `(+ ,x ,y)) (cdr form)))))
178 (case (length (cdr form))
179 (1 `(unary- ,(cadr form)))
180 (t (reduce (lambda (x y) `(- ,x ,y)) (cdr form)))))
182 (reduce (lambda (x y) `(comma ,x ,y)) (cdr form) :from-end t))
186 (defun js-operator-expression (op args precedence associativity operand-order)
187 (let ((op1 (car args))
190 ;; Transactional compatible operator
192 (js-format "~a" (apply #'code args)))
195 (js-expr (car args) 0)
197 (js-expr (cadr args) no-comma)
200 (multiple-value-bind (accessor accessorp)
201 (valid-js-identifier (cadr args))
203 (error "Invalid accessor ~S" (cadr args)))
204 (js-expr (car args) 0)
206 (js-identifier accessor)))
209 (js-expr (car args) 1)
212 (js-expr (cadr args) no-comma)
213 (dolist (operand (cddr args))
215 (js-expr operand no-comma)))
219 (js-object-initializer args))
220 ;; Function expressions
223 (apply #'js-function args)
226 (labels ((low-precedence-p (op-precedence)
228 ((> op-precedence precedence))
229 ((< op-precedence precedence) nil)
230 (t (not (eq operand-order associativity)))))
232 (%unary-op (operator string operator-precedence operator-associativity post lvalue)
233 (when (eq op operator)
234 (when lvalue (check-lvalue op1))
235 (when (low-precedence-p operator-precedence) (js-format "("))
238 (js-expr op1 operator-precedence operator-associativity 'left)
239 (js-format "~a" string))
241 (js-format "~a" string)
242 (js-expr op1 operator-precedence operator-associativity 'right)))
243 (when (low-precedence-p operator-precedence) (js-format ")"))
244 (return-from js-operator-expression)))
246 (%binary-op (operator string operator-precedence operator-associativity lvalue)
247 (when (eq op operator)
248 (when lvalue (check-lvalue op1))
249 (when (low-precedence-p operator-precedence) (js-format "("))
250 (js-expr op1 operator-precedence operator-associativity 'left)
251 (js-format "~a" string)
252 (js-expr op2 operator-precedence operator-associativity 'right)
253 (when (low-precedence-p operator-precedence) (js-format ")"))
254 (return-from js-operator-expression))))
256 (macrolet ((unary-op (operator string precedence associativity &key post lvalue)
257 `(%unary-op ',operator ',string ',precedence ',associativity ',post ',lvalue))
258 (binary-op (operator string precedence associativity &key lvalue)
259 `(%binary-op ',operator ',string ',precedence ',associativity ',lvalue)))
261 (unary-op pre++ "++" 2 right :lvalue t)
262 (unary-op pre-- "--" 2 right :lvalue t)
263 (unary-op post++ "++" 2 right :lvalue t :post t)
264 (unary-op post-- "--" 2 right :lvalue t :post t)
265 (unary-op not "!" 2 right)
266 (unary-op bit-not "~" 2 right)
267 ;; Note that the leading space is necessary because it
268 ;; could break with post++, for example. TODO: Avoid
269 ;; leading space when it's possible.
270 (unary-op unary+ " +" 2 right)
271 (unary-op unary- " -" 2 right)
272 (unary-op delete "delete " 2 right)
273 (unary-op void "void " 2 right)
274 (unary-op typeof "typeof " 2 right)
275 (unary-op new "new " 2 right)
277 (binary-op * "*" 3 left)
278 (binary-op / "/" 3 left)
279 (binary-op mod "%" 3 left)
280 (binary-op % "%" 3 left)
281 (binary-op + "+" 4 left)
282 (binary-op - "-" 5 left)
283 (binary-op << "<<" 5 left)
284 (binary-op >> "<<" 5 left)
285 (binary-op >>> ">>>" 5 left)
286 (binary-op <= "<=" 6 left)
287 (binary-op < "<" 6 left)
288 (binary-op > ">" 6 left)
289 (binary-op >= ">=" 6 left)
290 (binary-op instanceof " instanceof " 6 left)
291 (binary-op in " in " 6 left)
292 (binary-op == "==" 7 left)
293 (binary-op != "!=" 7 left)
294 (binary-op === "===" 7 left)
295 (binary-op !== "!==" 7 left)
296 (binary-op bit-and "&" 8 left)
297 (binary-op bit-xor "^" 9 left)
298 (binary-op bit-or "|" 10 left)
299 (binary-op and "&&" 11 left)
300 (binary-op or "||" 12 left)
301 (binary-op = "=" 13 right :lvalue t)
302 (binary-op += "+=" 13 right :lvalue t)
303 (binary-op incf "+=" 13 right :lvalue t)
304 (binary-op -= "-=" 13 right :lvalue t)
305 (binary-op decf "-=" 13 right :lvalue t)
306 (binary-op *= "*=" 13 right :lvalue t)
307 (binary-op /= "*=" 13 right :lvalue t)
308 (binary-op bit-xor= "^=" 13 right :lvalue t)
309 (binary-op bit-and= "&=" 13 right :lvalue t)
310 (binary-op bit-or= "|=" 13 right :lvalue t)
311 (binary-op <<= "<<=" 13 right :lvalue t)
312 (binary-op >>= ">>=" 13 right :lvalue t)
313 (binary-op >>>= ">>>=" 13 right :lvalue t)
315 (binary-op comma "," 13 right)
316 (binary-op progn "," 13 right)
318 (when (member op '(? if))
319 (when (low-precedence-p 12) (js-format "("))
320 (js-expr (first args) 12 'right 'left)
322 (js-expr (second args) 12 'right 'right)
324 (js-expr (third args) 12 'right 'right)
325 (when (low-precedence-p 12) (js-format ")"))
326 (return-from js-operator-expression))
328 (error "Unknown operator `~S'" op)))))))
330 (defun js-expr (form &optional (precedence 1000) associativity operand-order)
331 (let ((form (js-expand-expr form)))
333 ((or (symbolp form) (numberp form) (stringp form))
334 (js-primary-expr form))
336 (js-vector-initializer form))
338 (js-operator-expression (car form) (cdr form) precedence associativity operand-order)))))
340 (defun js-expand-stmt (form)
342 ((and (consp form) (eq (car form) 'progn))
343 (destructuring-bind (&body body) (cdr form)
348 (js-expand-stmt (car body)))
350 `(group ,@(cdr form))))))
354 (defun js-stmt (form &optional parent)
355 (let ((form (js-expand-stmt form)))
356 (flet ((js-stmt (x) (js-stmt x form)))
359 (unless (or (and (consp parent) (eq (car parent) 'group))
369 (js-format "~a" (apply #'code (cdr form))))
371 (destructuring-bind (label &body body) (cdr form)
372 (js-identifier label)
374 (js-stmt `(progn ,@body))))
376 (destructuring-bind (&optional label) (cdr form)
380 (js-identifier label))
383 (destructuring-bind (value) (cdr form)
384 (js-format "return ")
388 (flet ((js-var (spec)
389 (destructuring-bind (variable &optional initial)
391 (js-identifier variable)
394 (js-expr initial no-comma)))))
395 (destructuring-bind (var &rest vars) (cdr form)
403 (destructuring-bind (condition true &optional false) (cdr form)
414 (and (consp parent) (eq (car parent) 'group)))))
415 (unless in-group-p (js-format "{"))
416 (mapc #'js-stmt (cdr form))
417 (unless in-group-p (js-format "}"))))
419 (destructuring-bind (condition &body body) (cdr form)
420 (js-format "while (")
423 (js-stmt `(progn ,@body))))
425 (destructuring-bind (value &rest cases) (cdr form)
426 (js-format "switch(")
430 (destructuring-bind (x &body body) case
432 (js-format "default: ")
434 (unless (or (stringp x) (numberp x))
435 (error "Non-constant switch case `~S'." (car cases)))
439 (mapc #'js-stmt body)))
442 (destructuring-bind ((start condition step) &body body) (cdr form)
450 (js-stmt `(progn ,@body))))
452 (destructuring-bind ((x object) &body body) (cdr form)
458 (js-stmt `(progn ,@body))))
460 (destructuring-bind (&rest body) (cdr form)
462 (js-stmt `(group ,@body))))
464 (destructuring-bind ((var) &rest body) (cdr form)
465 (js-format "catch (")
468 (js-stmt `(group ,@body))))
470 (destructuring-bind (&rest body) (cdr form)
471 (js-format "finally")
472 (js-stmt `(group ,@body))))
474 (destructuring-bind (object) (cdr form)
480 (js-format ";"))))))))
482 (defun js (&rest stmts)
483 (mapc #'js-stmt stmts)