Reworked printing implementation to get rid of dwim-join and gratuitious consing.
authorVladimir Sedach <vsedach@gmail.com>
Wed, 15 Aug 2007 01:07:08 +0000 (01:07 +0000)
committerVladimir Sedach <vsedach@gmail.com>
Wed, 15 Aug 2007 01:07:08 +0000 (01:07 +0000)
Now everything gets written to a stream.

src/compilation-interface.lisp
src/js-macrology.lisp
src/js-translation.lisp

dissimilarity index 81%
index 7780eec..3c17258 100644 (file)
@@ -1,44 +1,17 @@
-(in-package :parenscript)
-
-(defun translate-ast (compiled-expr &key (output-stream *standard-output*) (output-spec :javascript) (pretty-print t))
-  "Translates a compiled Parenscript program (compiled with COMPILE-PAREN-FORM)
-to a Javascript string.  Outputs to the stream OUTPUT-STREAM in the language given
-by OUTPUT-SPEC, pretty printing if PRETTY-PRINT is non-null.
-
-OUTPUT-SPEC must be :javascript at the moment."
-  (when (not (eql :javascript output-spec))
-    (error "Unsupported output-spec for translation: ~A" output-spec))
-  (write-string (string-join (ps-print compiled-expr 0)
-                             (string #\Newline))
-                output-stream))
-
-(defun compile-script (script-form &key (output-spec :javascript) (pretty-print t) (output-stream nil) (toplevel-p t))
-  "Compiles the Parenscript form SCRIPT-FORM into the language specified by OUTPUT-SPEC.
-Non-null PRETTY-PRINT values result in a pretty-printed output code.  If OUTPUT-STREAM
-is NIL, then the result is a string; otherwise code is output to the OUTPUT-STREAM stream.
-
-This is the main function used by Parenscript users to compile their code to Javascript (and
-potentially other languages)."
-  (macrolet ((with-output-stream ((var) &body body)
-              `(if (null output-stream)
-                (with-output-to-string (,var)
-                  ,@body)
-                (let ((,var output-stream))
-                  ,@body))))
-    (with-output-stream (stream)
-      (translate-ast (compile-parenscript-form script-form)
-                     :output-stream stream
-                     :output-spec output-spec
-                     :pretty-print pretty-print))))
-
-(defun ps-to-string (expr)
-  (string-join (ps-print (compile-parenscript-form expr) 0) (string #\Newline)))
-
-(defmacro ps (&body body)
-  "A macro that returns a Javascript string of the supplied Parenscript forms."
-  `(ps* '(progn ,@body)))
-
-(defun ps* (&rest body)
-  "Return the javascript string representing BODY.
-Body is evaluated."
-  (compile-script `(progn ,@body)))
+(in-package :parenscript)
+
+(defun compile-script (script-form &key (output-stream nil))
+  "Compiles the Parenscript form SCRIPT-FORM into Javascript.
+Non-null PRETTY-PRINT values result in a pretty-printed output code.
+If OUTPUT-STREAM is NIL, then the result is a string; otherwise code
+is output to the OUTPUT-STREAM stream."
+  (parenscript-print (compile-parenscript-form script-form) output-stream))
+
+(defmacro ps (&body body)
+  "A macro that returns a Javascript string of the supplied Parenscript forms."
+  `(ps* '(progn ,@body)))
+
+(defun ps* (&rest body)
+  "Return the javascript string representing BODY.
+Body is evaluated."
+  (compile-script `(progn ,@body)))
index 4bdecf5..c67e009 100644 (file)
 (define-ps-special-form ~ (expecting x)
   (list 'unary-operator "~" (compile-parenscript-form x :expecting :expressin) :prefix t))
 
-(defun flatten-progns (body)
-  (unless (null body)
+(defun flatten-blocks (body)
+  (when body
     (if (and (listp (car body))
-             (eql 'progn (caar body)))
-        (append (cdar body) (flatten-progns (cdr body)))
-        (cons (car body) (flatten-progns (cdr body))))))
+             (eql 'js-block (caar body)))
+        (append (third (car body)) (flatten-blocks (cdr body)))
+        (cons (car body) (flatten-blocks (cdr body))))))
 
 (define-ps-special-form progn (expecting &rest body)
   (list 'js-block
         (if (eql expecting :statement) t nil)
-        (remove nil (mapcar (lambda (form)
-                              (compile-parenscript-form form :expecting :statement))
-                            (flatten-progns body)))))
+        (flatten-blocks (remove nil (mapcar (lambda (form)
+                                              (compile-parenscript-form form :expecting :statement))
+                                            body)))))
 
 ;;; function definition
 (define-ps-special-form %js-lambda (expecting args &rest body)
                            (destructuring-bind (test &rest body)
                                clause
                              (list (compile-parenscript-form test :expecting :expression)
-                                   (mapcar (lambda (form) (compile-parenscript-form form :expecting :statement))
-                                           body))))
+                                   (compile-parenscript-form `(progn ,@body)))))
                          clauses)))
 
 (define-ps-special-form if (expecting test then &optional else)
   (let ((catch (cdr (assoc :catch clauses)))
         (finally (cdr (assoc :finally clauses))))
     (assert (not (cdar catch)) nil "Sorry, currently only simple catch forms are supported.")
+    (assert (or catch finally) ()
+            "Try form should have either a catch or a finally clause or both.")
     (list 'js-try (compile-parenscript-form `(progn ,form))
           :catch (when catch (list (compile-parenscript-form (caar catch) :expecting :symbol)
                                    (compile-parenscript-form `(progn ,@(cdr catch)))))
 (defpsmacro 1+ (form)
   `(+ ,form 1))
 
-;;; helper macros
+;;; inlining macros
 (define-ps-special-form js (expecting &rest body)
-  (string-join (ps-print (compile-parenscript-form `(progn ,@body)) 0) " "))
+  (parenscript-print (compile-parenscript-form `(progn ,@body))))
 
 (define-ps-special-form ps-inline (expecting &rest body)
   (concatenate 'string
                "javascript:"
-               (string-join (reduce #'append (mapcar (lambda (form)
-                                                       (ps-print (compile-parenscript-form form :expecting :statement)
-                                                                 0))
-                                                     body))
-                            ";")
+               (reduce (lambda (str1 str2)
+                         (concatenate 'string str1 ";" str2))
+                       (mapcar (lambda (form)
+                                 (parenscript-print (compile-parenscript-form form :expecting :statement)))
+                               body))
                ";"))
 
dissimilarity index 79%
index b4aacd0..65ce5e2 100644 (file)
-(in-package :parenscript)
-
-(defgeneric ps-print% (special-form-name special-form-args %start-pos%))
-
-(defmacro defprinter (special-form content-args &body body)
-  "Given a special-form name and a destructuring lambda-list for its
-arguments, defines a printer for that form using the given body."
-  (let ((sf (gensym))
-        (sf-args (gensym)))
-    `(defmethod ps-print% ((,sf (eql ',special-form)) ,sf-args %start-pos%)
-      (declare (ignore ,sf))
-      (destructuring-bind ,content-args
-          ,sf-args
-        ,@body))))
-
-(defvar %start-pos%)
-
-(defgeneric ps-print (compiled-form %start-pos%))
-
-(defmethod ps-print ((compiled-form cons) %start-pos%)
-  "Prints the given compiled ParenScript form starting at the given
-indent position."
-  (ps-print% (car compiled-form) (cdr compiled-form) %start-pos%))
-
-;;; string literals
-(defvar *js-quote-char* #\'
-  "Specifies which character JS should use for delimiting strings.
-
-This variable is useful when have to embed some javascript code
-in an html attribute delimited by #\\\" as opposed to #\\', or
-vice-versa.")
-
-(defparameter *js-lisp-escaped-chars*
-  '((#\' . #\')
-    (#\\ . #\\)
-    (#\b . #\Backspace)
-    (#\f . #.(code-char 12))
-    (#\n . #\Newline)
-    (#\r . #\Return)
-    (#\t . #\Tab)))
-
-(defmethod ps-print ((string string) %start-pos%)
-  (flet ((lisp-special-char-to-js (lisp-char)
-           (car (rassoc lisp-char *js-lisp-escaped-chars*))))
-    (list (with-output-to-string (escaped)
-            (write-char *js-quote-char* escaped)
-            (loop for char across string
-                  for code = (char-code char)
-                  for special = (lisp-special-char-to-js char)
-                  do (cond
-                       (special
-                        (write-char #\\ escaped)
-                        (write-char special escaped))
-                       ((or (<= code #x1f) (>= code #x80))
-                        (format escaped "\\u~4,'0x" code))
-                       (t (write-char char escaped)))
-                  finally (write-char *js-quote-char* escaped))))))
-
-(defmethod ps-print ((number number) %start-pos%)
-  (list (format nil (if (integerp number) "~S" "~F") number)))
-
-;;; expression and operator precedence rules
-
-(defun expression-precedence (expr)
-  (if (consp expr)
-      (case (car expr)
-        (js-block (if (= (length (cdr expr)) 1)
-                      (expression-precedence (first (cdr expr)))
-                      (op-precedence 'comma)))
-        (js-expression-if (op-precedence 'js-expression-if))
-        (js-assign (op-precedence '=))
-        (operator (op-precedence (second expr)))
-        (otherwise 0))
-      0))
-
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (defparameter *op-precedence-hash* (make-hash-table :test #'equal))
-
-  ;;; generate the operator precedences from *OP-PRECEDENCES*
-  (let ((precedence 1))
-    (dolist (ops '((js-aref)
-                   (js-slot-value)
-                   (! not ~)
-                   (* / %)
-                   (+ -)
-                   (<< >>)
-                   (>>>)
-                   (< > <= >=)
-                   (in js-expression-if)
-                   (eql == != =)
-                   (=== !==)
-                   (&)
-                   (^)
-                   (\|)
-                   (\&\& and)
-                   (\|\| or)
-                   (js-assign *= /= %= += -= <<= >>= >>>= \&= ^= \|=)
-                   (comma)))
-      (dolist (op ops)
-        (let ((op-name (symbol-name op)))
-          (setf (gethash op-name *op-precedence-hash*) precedence)))
-      (incf precedence)))
-
-  (defun op-precedence (op)
-    (gethash (if (symbolp op)
-                 (symbol-name op)
-                 op)
-             *op-precedence-hash*)))
-
-;;; indenter
-
-(defmacro max-length () '(- 80 %start-pos% 2))
-
-(defun ps-print-indent (ps-form)
-  (ps-print ps-form (+ %start-pos% 2)))
-
-(defun special-append-to-last (form elt)
-  (flet ((special-append (form elt)
-          (let ((len (length form)))
-            (if (and (> len 0)
-                      (string= (char form (1- len)) elt))
-                form
-                (concatenate 'string form elt)))))
-    (cond ((stringp form)
-          (special-append form elt))
-         ((consp form)
-          (let ((last (last form)))
-            (if (stringp (car last))
-                (rplaca last (special-append (car last) elt))
-                (append-to-last (car last) elt))
-          form))
-         (t (error "Wrong argument type to indent appender: ~S" form)))))
-
-(defun dwim-join (value-string-lists max-length
-                 &key (start "")
-                       end
-                      (join-before "")
-                       join-after
-                      (white-space (make-string (length start) :initial-element #\Space))
-                       (separator " ")
-                 (append-to-last #'append-to-last)
-                 (collect t))
-    #+nil
-    (format t "value-string-lists: ~S~%" value-string-lists)
-
-    ;;; collect single value-string-lists until the line is full
-
-    (do* ((string-lists value-string-lists (cdr string-lists))
-         (string-list (car string-lists) (car string-lists))
-         (cur-elt start)
-          (is-first t nil)
-         (cur-empty t)
-         (res nil))
-        ((null string-lists)
-         (unless cur-empty
-           (push cur-elt res))
-         (if (null res)
-             (list (concatenate 'string start end))
-             (progn
-               (when end
-                 (setf (first res)
-                       (funcall append-to-last (first res) end)))
-               (nreverse res))))
-      #+nil
-      (format t "string-list: ~S~%" string-list)
-
-      (when join-after
-       (unless (null (cdr string-lists))
-         (funcall append-to-last string-list join-after)))
-
-      (if (and collect (= (length string-list) 1))
-         (progn
-           #+nil
-           (format t "cur-elt: ~S line-length ~D, max-length ~D, string: ~S~%"
-                   cur-elt
-                   (+ (length (first string-list))
-                      (length cur-elt))
-                   max-length
-                   (first string-list))
-           (if (or cur-empty
-                   (< (+ (length (first string-list))
-                         (length cur-elt)) max-length))
-               (setf cur-elt
-                     (concatenate 'string cur-elt
-                                  (if (or is-first (and cur-empty (string= join-before "")))
-                                        "" (concatenate 'string separator join-before))
-                                  (first string-list))
-                     cur-empty nil)
-               (progn
-                 (push cur-elt res)
-                 (setf cur-elt (concatenate 'string white-space
-                                            join-before (first string-list))
-                       cur-empty nil))))
-
-         (progn
-           (unless cur-empty
-             (push cur-elt res)
-             (setf cur-elt white-space
-                   cur-empty t))
-           (setf res (nconc (nreverse
-                             (cons (concatenate 'string
-                                                cur-elt
-                                                 (if (null res)
-                                                    "" join-before)
-                                                (first string-list))
-                                   (mapcar #'(lambda (x) (concatenate 'string white-space x))
-                                           (cdr string-list))))
-                             res))
-           (setf cur-elt white-space cur-empty t)))))
-
-(defprinter script-quote (val)
-  (if (null val)
-      (list "null")
-      (error "Cannot translate quoted value ~S to javascript" val)))
-
-(defprinter js-literal (str)
-  (list str))
-
-(defprinter js-keyword (str)
-  (list str))
-
-;;; array literals
-
-(defprinter array-literal (&rest initial-contents)
-  (let ((initial-contents-strings (mapcar #'ps-print-indent initial-contents)))
-    (dwim-join initial-contents-strings (max-length)
-              :start "[ " :end " ]"
-              :join-after ",")))
-
-(defprinter js-aref (array coords)
-  (dwim-join (cons (ps-print array %start-pos%)
-                  (mapcar (lambda (x) (dwim-join (list (ps-print-indent x))
-                                                  (max-length)
-                                                  :start "[" :end "]"))
-                          coords))
-            (max-length)
-             :white-space "  "
-             :separator ""))
-
-(defprinter object-literal (&rest arrows)
-  (dwim-join (loop for (key . value) in arrows appending
-                   (list (dwim-join (list (list (format nil "~A:" (js-translate-symbol key)))
-                                          (ps-print-indent value))
-                                    (max-length)
-                                    :start "" :end "" :join-after "")))
-             (max-length)
-             :start "{ " :end " }"
-             :join-after ","))
-
-(defprinter js-variable (var)
-  (list (js-translate-symbol var)))
-
-;;; arithmetic operators
-(defun script-convert-op-name (op)
-  (case op
-    (and '\&\&)
-    (or '\|\|)
-    (not '!)
-    (eql '\=\=)
-    (=   '\=\=)
-    (t op)))
-
-(defun parenthesize (string-list)
-  (prepend-to-first string-list "(")
-  (append-to-last string-list ")")
-  string-list)
-
-(defprinter operator (op args)
-  (let* ((precedence (op-precedence op))
-        (arg-strings (mapcar (lambda (arg)
-                                (let ((arg-strings (ps-print-indent arg)))
-                                  (if (>= (expression-precedence arg) precedence)
-                                      (parenthesize arg-strings)
-                                      arg-strings)))
-                              args))
-        (op-string (format nil "~A " op)))
-    (dwim-join arg-strings (max-length) :join-before op-string)))
-
-(defprinter unary-operator (op arg &key prefix)
-  (let ((arg-string (ps-print arg %start-pos%)))
-    (when (eql 'operator (car arg))
-      (setf arg-string (parenthesize arg-string)))
-    (if prefix
-        (prepend-to-first arg-string op)
-        (append-to-last arg-string op))))
-
-;;; function and method calls
-(defprinter js-funcall (fun-designator args)
-  (let* ((arg-strings (mapcar #'ps-print-indent args))
-        (args (dwim-join arg-strings (max-length)
-                         :start "(" :end ")" :join-after ",")))
-    (cond ((eql 'js-lambda (car fun-designator))
-           (dwim-join (list (append (dwim-join (list (ps-print-indent fun-designator))
-                                               (max-length)
-                                               :start "(" :end ")" :separator "")
-                                    args))
-                      (max-length)
-                      :separator ""))
-          ((member (car fun-designator) '(js-variable js-aref js-slot-value))
-           (dwim-join (list (ps-print-indent fun-designator) args)
-                      (max-length)
-                      :separator ""))
-          ((eql 'js-funcall (car fun-designator))
-           ;; TODO it adds superfluous newlines after each ()
-           ;; and it's nearly the same as the js-lambda case above
-           (dwim-join (list (append (dwim-join (list (ps-print-indent fun-designator))
-                                               (max-length) :separator "")
-                                    args))
-                      (max-length) :separator "")))))
-
-(defprinter js-method-call (method object args)
-  (let ((printed-object (ps-print object (+ %start-pos% 2))))
-    ;; TODO: this may not be the best way to add ()'s around lambdas
-    ;; probably there is or should be a more general solution working
-    ;; in other situations involving lambda's
-    (when (or (numberp object) (and (consp object) (member (car object) '(js-lambda js-object operator))))
-      (setf printed-object (append (list "(") printed-object (list ")"))))
-    (let* ((fname (dwim-join (list printed-object (list (js-translate-symbol method)))
-                             (max-length)
-                             :end "("
-                             :separator ""))
-           (butlast (butlast fname))
-           (last (car (last fname)))
-           (method-and-args (dwim-join (mapcar #'ps-print-indent args)
-                                       (max-length)
-                                       :start last
-                                       :end ")"
-                                       :join-after ","))
-           (ensure-no-newline-before-dot (concatenate 'string
-                                                      (car (last butlast))
-                                                      (first method-and-args))))
-      (append (butlast butlast) (list ensure-no-newline-before-dot) (cdr method-and-args)))))
-
-(defprinter js-block (statement-p statements)
-  (dwim-join (mapcar #'ps-print-indent statements)
-            (max-length)
-            :join-after (if statement-p ";" ",")
-            :append-to-last #'special-append-to-last
-            :start (if statement-p "    " "")
-             :collect nil
-            :end (if statement-p ";" "")))
-
-(defprinter js-lambda (args body)
-  (print-fun-def nil args body %start-pos%))
-
-(defprinter js-defun (name args body)
-  (print-fun-def name args body %start-pos%))
-
-(defun print-fun-def (name args body %start-pos%)
-  (let ((fun-header (dwim-join (mapcar (lambda (x) (list (js-translate-symbol x)))
-                                      args)
-                              (max-length)
-                              :start (format nil "function ~:[~;~A~](" name (js-translate-symbol name))
-                               :join-after ","
-                              :end ") {"))
-       (fun-body (ps-print-indent body)))
-    (append fun-header fun-body (list "}"))))
-
-;;; object creation
-(defprinter js-object (slot-defs)
-  (let ((value-string-lists (mapcar (lambda (slot)
-                                      (let* ((slot-name (first slot))
-                                             (slot-string-name
-                                              (if (and (listp slot-name) (eql 'script-quote (car slot-name)))
-                                                  (format nil "~A" (if (symbolp (second slot-name))
-                                                                       (js-translate-symbol (second slot-name))
-                                                                       (car (ps-print slot-name 0))))
-                                                  (car (ps-print slot-name 0)))))
-                                        (dwim-join (list (ps-print (second slot) (+ %start-pos% 4)))
-                                                   (max-length)
-                                                   :start (concatenate 'string slot-string-name  " : ")
-                                                   :white-space "    ")))
-                                    slot-defs)))
-    (dwim-join value-string-lists (max-length)
-              :start "{ "
-              :end " }"
-              :join-after ", "
-              :white-space "  "
-              :collect nil)))
-
-(defprinter js-slot-value (obj slot)
-  (append-to-last (if (and (listp obj) (eql 'js-variable (car obj)))
-                      (ps-print obj %start-pos%)
-                      (list (format nil "~A" (ps-print obj %start-pos%))))
-                  (if (and (listp slot) (eql 'script-quote (car slot)))
-                      (format nil ".~A" (if (symbolp (second slot))
-                                            (js-translate-symbol (second slot))
-                                            (first (ps-print slot 0))))
-                      (format nil "[~A]" (first (ps-print slot 0))))))
-
-;;; cond
-(defprinter js-cond (clauses)
-  (loop for (test body-forms) in clauses
-        for start = "if (" then "else if ("
-        append (if (string= test "true")
-                   '("else {")
-                   (dwim-join (list (ps-print test 0)) (max-length)
-                              :start start :end ") {"))
-        append (mapcar #'ps-print-indent body-forms)
-        collect "}"))
-
-(defprinter js-statement-if (test then else)
-  (let ((if-strings (dwim-join (list (ps-print test 0))
-                              (- 80 %start-pos% 2)
-                              :start "if ("
-                              :end ") {"))
-       (then-strings (ps-print-indent then))
-       (else-strings (when else
-                       (ps-print-indent else))))
-    (append if-strings then-strings (if else-strings
-                                        (append (list "} else {") else-strings (list "}"))
-                                        (list "}")))))
-
-(defprinter js-expression-if (test then else)
-  (dwim-join (list (append-to-last (ps-print test %start-pos%) " ?")
-                  (let ((then-string (ps-print then %start-pos%)))
-                    (if (>= (expression-precedence then) (op-precedence 'js-expression-if))
-                         (parenthesize then-string)
-                         then-string))
-                  (list ":")
-                  (if else
-                      (let ((else-string (ps-print else %start-pos%)))
-                        (if (>= (expression-precedence else) (op-precedence 'js-expression-if))
-                            (parenthesize else-string)
-                            else-string))
-                      (list "undefined")))
-            (max-length)
-            :white-space "  "))
-
-(defprinter js-assign (lhs rhs)
-  (dwim-join (list (ps-print lhs %start-pos%) (ps-print rhs %start-pos%))
-            (max-length)
-            :join-after " ="))
-
-(defprinter js-defvar (var-name &rest var-value)
-  (dwim-join (append (list (list (js-translate-symbol var-name)))
-                     (when var-value
-                       (list (ps-print (car var-value) %start-pos%))))
-            (max-length)
-            :join-after " ="
-            :start "var " :end ";"))
-
-;;; iteration
-(defprinter js-for (vars steps test body-block)
-  (let* ((init (dwim-join (mapcar (lambda (var-form)
-                                    (dwim-join (list (list (js-translate-symbol (car var-form)))
-                                                     (ps-print-indent (cdr var-form)))
-                                               (max-length)
-                                               :join-after " ="))
-                                 vars)
-                         (max-length)
-                         :start "var " :join-after ","))
-        (test-string (ps-print-indent test))
-        (step-strings (dwim-join (mapcar (lambda (x var-form)
-                                            (dwim-join
-                                             (list (list (js-translate-symbol (car var-form)))
-                                                   (ps-print x (- %start-pos% 2)))
-                                             (max-length)
-                                             :join-after " ="))
-                                          steps
-                                          vars)
-                                  (max-length)
-                                  :join-after ","))
-        (header (dwim-join (list init test-string step-strings)
-                           (max-length)
-                           :start "for (" :end ") {"
-                           :join-after ";"))
-        (body (ps-print-indent body-block)))
-    (append header body (list "}"))))
-
-(defprinter js-for-each (var object body-block)
-  (let ((header (dwim-join (list (list (js-translate-symbol var))
-                                (list "in")
-                                (ps-print-indent object))
-                          (max-length)
-                          :start "for (var "
-                          :end ") {"))
-       (body (ps-print-indent body-block)))
-    (append header body (list "}"))))
-
-(defprinter js-while (test body-block)
-  (let ((header-strings (dwim-join (list (ps-print-indent test))
-                          (max-length)
-                          :start "while ("
-                          :end ") {"))
-       (body-strings (ps-print-indent body-block)))
-    (append header-strings body-strings (list "}"))))
-
-(defprinter js-with (expression body-block)
-  (append (dwim-join (list (ps-print-indent expression))
-                     (max-length)
-                     :start "with (" :end ") {")
-          (ps-print-indent body-block)
-          (list "}")))
-
-(defprinter js-switch (test clauses)
-  (let ((body-strings (mapcar (lambda (clause)
-                                (let ((val (first clause))
-                                      (body-block (second clause)))
-                                  (dwim-join (list (if (eql val 'default)
-                                                       (list "")
-                                                       (ps-print-indent val))
-                                                   (ps-print-indent body-block))
-                                             (max-length)
-                                             :start (if (eql val 'default) "  default" "  case ")
-                                             :white-space "   "
-                                             :join-after ":")))
-                              clauses)))
-    (append (dwim-join (list (ps-print-indent test))
-                       (max-length)
-                       :start "switch (" :end ") {")
-            (reduce #'append body-strings)
-            (list "}"))))
-
-(defprinter js-try (body &key catch finally)
-  (let ((catch-strings (when catch
-                      (append (dwim-join (list (list (js-translate-symbol (first catch))))
-                                         (max-length)
-                                         :start "} catch ("
-                                         :end ") {")
-                              (ps-print-indent (second catch)))))
-        (finally-strings (when finally
-                           (append (list "} finally {")
-                                   (ps-print-indent finally)))))
-    (append (list "try {")
-            (ps-print-indent body)
-            catch-strings
-            finally-strings
-            (list "}"))))
-
-;;; regex
-(defprinter js-regex (regex)
-  (flet ((first-slash-p (string)
-           (and (> (length string) 0) (eql (char string 0) '#\/))))
-    (let ((slash (unless (first-slash-p regex) "/")))
-      (list (format nil (concatenate 'string slash "~A" slash) regex)))))
-
-(defprinter js-return (value)
-  (let ((printed-value (ps-print value 0)))
-    (cons (concatenate 'string "return " (car printed-value)) (cdr printed-value))))
-
-;;; conditional compilation
-(defprinter cc-if (test body-forms)
-  (append (list (format nil "/*@if ~A" test))
-          (mapcar (lambda (x) (ps-print x %start-pos%)) body-forms)
-          (list "@end @*/")))
-
-;;; TODO instanceof
-(defprinter js-instanceof (value type)
-  (dwim-join (list (ps-print-indent value)
-                   (list "instanceof")
-                   (ps-print-indent type))
-             (max-length)
-             :start "("
-             :end ")"
-             :white-space "  "))
-
-(defprinter js-named-operator (op value)
-  (dwim-join (list (ps-print-indent value))
-             (max-length)
-             :start (concatenate 'string (string-downcase (symbol-name op)) " ")
-             :white-space "  "))
+(in-package :parenscript)
+
+(defvar *ps-output-stream*)
+
+(defmethod parenscript-print (ps-form &optional *ps-output-stream*)
+  (flet ((print-ps (form)
+           (let ((*standard-output* *ps-output-stream*))
+             (ps-print form))))
+    (if *ps-output-stream*
+        (print-ps ps-form)
+        (with-output-to-string (*ps-output-stream*)
+          (print-ps ps-form)))))
+
+(defgeneric ps-print% (special-form-name special-form-args))
+
+(defmacro defprinter (special-form content-args &body body)
+  "Given a special-form name and a destructuring lambda-list for its
+arguments, defines a printer for that form using the given body."
+  (let ((sf (gensym))
+        (sf-args (gensym)))
+    `(defmethod ps-print% ((,sf (eql ',special-form)) ,sf-args)
+      (declare (ignore ,sf))
+      (destructuring-bind ,content-args
+          ,sf-args
+        ,@body))))
+
+(defgeneric ps-print (compiled-form))
+
+(defmethod ps-print ((compiled-form cons))
+  "Prints the given compiled ParenScript form starting at the given
+indent position."
+  (ps-print% (car compiled-form) (cdr compiled-form)))
+
+;;; string literals
+(defvar *js-quote-char* #\'
+  "Specifies which character JS should use for delimiting strings.
+
+This variable is useful when have to embed some javascript code
+in an html attribute delimited by #\\\" as opposed to #\\', or
+vice-versa.")
+
+(defparameter *js-lisp-escaped-chars*
+  '((#\' . #\')
+    (#\\ . #\\)
+    (#\b . #\Backspace)
+    (#\f . #.(code-char 12))
+    (#\n . #\Newline)
+    (#\r . #\Return)
+    (#\t . #\Tab)))
+
+(defmethod ps-print ((string string))
+  (flet ((lisp-special-char-to-js (lisp-char)
+           (car (rassoc lisp-char *js-lisp-escaped-chars*))))
+    (write-char *js-quote-char*)
+    (loop for char across string
+          for code = (char-code char)
+          for special = (lisp-special-char-to-js char)
+          do (cond (special (write-char #\\)
+                            (write-char special))
+                   ((or (<= code #x1f) (>= code #x80))
+                    (format *ps-output-stream* "\\u~4,'0x" code))
+                   (t (write-char char)))
+          finally (write-char *js-quote-char*))))
+
+(defmethod ps-print ((number number))
+  (format *ps-output-stream* (if (integerp number) "~S" "~F") number))
+
+;;; expression and operator precedence rules
+
+(defun expression-precedence (expr)
+  (if (consp expr)
+      (case (car expr)
+        (js-block (if (= (length (cdr expr)) 1)
+                      (expression-precedence (first (cdr expr)))
+                      (op-precedence 'comma)))
+        (js-expression-if (op-precedence 'js-expression-if))
+        (js-assign (op-precedence '=))
+        (operator (op-precedence (second expr)))
+        (otherwise 0))
+      0))
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (defparameter *op-precedence-hash* (make-hash-table :test #'equal))
+
+  ;;; generate the operator precedences from *OP-PRECEDENCES*
+  (let ((precedence 1))
+    (dolist (ops '((js-aref)
+                   (js-slot-value)
+                   (! not ~)
+                   (* / %)
+                   (+ -)
+                   (<< >>)
+                   (>>>)
+                   (< > <= >=)
+                   (in js-expression-if)
+                   (eql == != =)
+                   (=== !==)
+                   (&)
+                   (^)
+                   (\|)
+                   (\&\& and)
+                   (\|\| or)
+                   (js-assign *= /= %= += -= <<= >>= >>>= \&= ^= \|=)
+                   (comma)))
+      (dolist (op ops)
+        (let ((op-name (symbol-name op)))
+          (setf (gethash op-name *op-precedence-hash*) precedence)))
+      (incf precedence)))
+
+  (defun op-precedence (op)
+    (gethash (if (symbolp op)
+                 (symbol-name op)
+                 op)
+             *op-precedence-hash*)))
+
+(defprinter script-quote (val)
+  (if (null val)
+      (write-string "null")
+      (error "Cannot translate quoted value ~S to javascript" val)))
+
+(defprinter js-literal (str)
+  (write-string str))
+
+(defprinter js-keyword (str)
+  (write-string str))
+
+(defun print-comma-list (ps-forms)
+  (loop for (form . rest) on ps-forms
+        with after = ", "
+        unless rest do (setf after "")
+        doing (progn (ps-print form)
+                     (write-string after))))
+
+(defprinter array-literal (&rest initial-contents)
+  (write-char #\[)
+  (print-comma-list initial-contents)
+  (write-char #\]))
+
+(defprinter js-aref (array indices)
+  (ps-print array)
+  (loop for idx in indices do
+        (progn (write-char #\[)
+               (ps-print idx)
+               (write-char #\]))))
+
+(defprinter object-literal (&rest slot-definitions)
+  (write-char #\{)
+  (loop for ((key . value) . rest) on slot-definitions
+        with after = ", "
+        unless rest do (setf after "")
+        doing (progn (format *ps-output-stream* "~A: " (js-translate-symbol key))
+                     (ps-print value)
+                     (write-string after)))
+  (write-string " }"))
+
+(defprinter js-variable (var)
+  (write-string (js-translate-symbol var)))
+
+;;; arithmetic operators
+(defun script-convert-op-name (op)
+  (case op
+    (and '\&\&)
+    (or '\|\|)
+    (not '!)
+    (eql '\=\=)
+    (=   '\=\=)
+    (t op)))
+
+(defun parenthesize-print (ps-form)
+  (write-char #\()
+  (ps-print ps-form)
+  (write-char #\)))
+
+(defprinter operator (op args)
+  (loop for (arg . rest) on args
+        with precedence = (op-precedence op)
+        with op-string = (format nil " ~A " op)
+        unless rest do (setf op-string "")
+        do (progn (if (>= (expression-precedence arg) precedence)
+                      (parenthesize-print arg)
+                      (ps-print arg))
+                  (write-string op-string))))
+
+(defprinter unary-operator (op arg &key prefix)
+  (when prefix
+    (write-string op))
+  (if (eql 'operator (car arg))
+      (parenthesize-print arg)
+      (ps-print arg))
+  (unless prefix
+    (write-string op)))
+
+;;; function and method calls
+(defprinter js-funcall (fun-designator args)
+  (cond ((member (car fun-designator) '(js-variable js-aref js-slot-value))
+         (ps-print fun-designator))
+        ((eql 'js-lambda (car fun-designator))
+         (write-char #\()
+         (ps-print fun-designator)
+         (write-char #\)))
+        ((eql 'js-funcall (car fun-designator))
+         (ps-print fun-designator)))
+  (write-char #\()
+  (print-comma-list args)
+  (write-char #\)))
+
+(defprinter js-method-call (method object args)
+  ;; TODO: this may not be the best way to add ()'s around lambdas
+  ;; probably there is or should be a more general solution working
+  ;; in other situations involving lambda's
+  (if (or (numberp object) (and (consp object) (member (car object) '(js-lambda js-object operator js-expression-if js-block))))
+      (parenthesize-print object)
+      (ps-print object))
+  (write-string (js-translate-symbol method))
+  (write-char #\()
+  (print-comma-list args)
+  (write-char #\)))
+
+(defprinter js-block (statement-p statements)
+  (loop for (statement . rest) on statements
+        with indent = (if statement-p "    " "")
+        with after = (if statement-p
+                         ";
+"
+                         ", ")
+        unless rest do (setf after (if statement-p after ""))
+        do (progn (write-string indent)
+                  (ps-print statement)
+                  (write-string after))))
+
+(defprinter js-lambda (args body)
+  (print-fun-def nil args body))
+
+(defprinter js-defun (name args body)
+  (print-fun-def name args body))
+
+(defun print-fun-def (name args body)
+  (format *ps-output-stream* "function ~:[~;~A~](" name (js-translate-symbol name))
+  (loop for (arg . rest) on args
+        with after = ", "
+        unless rest do (setf after "")
+        do (progn (write-string (js-translate-symbol arg))
+                  (write-string after))
+        finally (write-string ") {"))
+  (fresh-line)
+  (ps-print body)
+  (write-char #\}))
+
+;;; object creation
+(defprinter js-object (slot-defs)
+  (write-string "{ ")
+  (loop for ((slot-name slot-value) . rest) on slot-defs
+        with after = ", "
+        unless rest do (setf after "")
+        do (progn (if (and (listp slot-name) (eql 'script-quote (car slot-name)) (symbolp (second slot-name)))
+                      (write-string (js-translate-symbol (second slot-name)))
+                      (ps-print slot-name))
+                  (write-string " : ")
+                  (ps-print slot-value)
+                  (write-string after)))
+  (write-string " }"))
+
+(defprinter js-slot-value (obj slot)
+  (if (and (listp obj) (member (car obj) '(js-block js-expression-if)))
+      (parenthesize-print obj)
+      (ps-print obj))
+  (if (and (listp slot) (eql 'script-quote (car slot)))
+      (progn (write-char #\.)
+             (if (symbolp (second slot))
+                 (write-string (js-translate-symbol (second slot)))
+                 (ps-print slot)))
+      (progn (write-char #\[)
+             (ps-print slot)
+             (write-char #\]))))
+
+;;; cond
+(defprinter js-cond (clauses)
+  (loop for (test body-block) in clauses
+        for start = "if (" then "else if ("
+        do (progn (if (string= test "true")
+                      (progn (write-string "else {")
+                             (fresh-line))
+                      (progn (ps-print test)
+                             (write-string ") {")
+                             (fresh-line)))
+                  (ps-print body-block)
+                  (write-char #\}))))
+
+(defprinter js-statement-if (test then else)
+  (write-string "if (")
+  (ps-print test)
+  (write-string ") {")
+  (fresh-line)
+  (ps-print then)
+  (fresh-line)
+  (when else
+      (write-string "} else {")
+      (fresh-line)
+      (ps-print else))
+  (write-char #\}))
+
+(defprinter js-expression-if (test then else)
+  (ps-print test)
+  (write-string " ? ")
+  (if (>= (expression-precedence then) (op-precedence 'js-expression-if))
+      (parenthesize-print then)
+      (ps-print then))
+  (write-string " : ")
+  (if else
+      (if (>= (expression-precedence else) (op-precedence 'js-expression-if))
+          (parenthesize-print else)
+          (ps-print else))
+      (write-string "undefined")))
+
+(defprinter js-assign (lhs rhs)
+  (ps-print lhs)
+  (write-string " = ")
+  (ps-print rhs))
+
+(defprinter js-defvar (var-name &rest var-value)
+  (write-string "var ")
+  (write-string (js-translate-symbol var-name))
+  (when var-value
+    (write-string " = ")
+    (ps-print (car var-value))))
+
+;;; iteration
+(defprinter js-for (vars steps test body-block)
+  (write-string "for (")
+  (loop for ((var-name . var-init) . rest) on vars
+        for decl = "var " then ""
+        with after = ", "
+        unless rest do (setf after "")
+        do (progn (write-string decl)
+                  (write-string (js-translate-symbol var-name))
+                  (write-string " = ")
+                  (ps-print var-init)
+                  (write-string after)))
+  (write-string "; ")
+  (ps-print test)
+  (write-string "; ")
+  (loop for ((var-name . var-init) . rest) on vars
+        for step in steps
+        with after = ", "
+        unless rest do (setf after "")
+        do (progn (write-string (js-translate-symbol var-name))
+                  (write-string " = ")
+                  (ps-print step)
+                  (write-string after)))
+  (write-string ") {")
+  (fresh-line)
+  (ps-print body-block)
+  (write-char #\}))
+
+(defprinter js-for-each (var object body-block)
+  (write-string "for (var ")
+  (write-string (js-translate-symbol var))
+  (write-string " in ")
+  (ps-print object)
+  (write-string ") {")
+  (fresh-line)
+  (ps-print body-block)
+  (write-char #\}))
+
+(defprinter js-while (test body-block)
+  (write-string "while (")
+  (ps-print test)
+  (write-string ") {")
+  (fresh-line)
+  (ps-print body-block)
+  (write-char #\}))
+
+(defprinter js-with (expression body-block)
+  (write-string "with (")
+  (ps-print expression)
+  (write-string ") {")
+  (fresh-line)
+  (ps-print body-block)
+  (write-char #\}))
+
+(defprinter js-switch (test clauses)
+  (write-string "switch (")
+  (ps-print test)
+  (write-string ") {")
+  (fresh-line)
+  (loop for (val body-block) in clauses
+        do (if (eql val 'default)
+               (progn (write-string "default: ")
+                      (ps-print body-block))
+               (progn (write-string "case ")
+                      (ps-print val)
+                      (write-char #\:)
+                      (fresh-line)
+                      (ps-print body-block))))
+  (write-char #\}))
+
+(defprinter js-try (body &key catch finally)
+  (write-string "try {")
+  (fresh-line)
+  (ps-print body)
+  (when catch
+    (write-string "} catch (")
+    (write-string (js-translate-symbol (first catch)))
+    (write-string ") {")
+    (ps-print (second catch)))
+  (when finally
+    (write-string "} finally {")
+    (ps-print finally))
+  (write-char #\}))
+
+;;; regex
+(defprinter js-regex (regex)
+  (flet ((first-slash-p (string)
+           (and (> (length string) 0) (char= (char string 0) #\/))))
+    (let ((slash (unless (first-slash-p regex) "/")))
+      (format *ps-output-stream* (concatenate 'string slash "~A" slash) regex))))
+
+(defprinter js-return (value)
+  (write-sequence "return " *ps-output-stream*)
+  (ps-print value))
+
+;;; conditional compilation
+(defprinter cc-if (test body-forms)
+  (write-string "/*@if ")
+  (ps-print test)
+  (fresh-line)
+  (dolist (form body-forms)
+    (ps-print form))
+  (fresh-line)
+  (write-string "@end @*/"))
+
+(defprinter js-instanceof (value type)
+  (write-char #\()
+  (ps-print value)
+  (write-string " instanceof ")
+  (ps-print type)
+  (write-char #\)))
+
+(defprinter js-named-operator (op value)
+  (format *ps-output-stream* "~(~A~) " op)
+  (ps-print value))