Big refactoring of the ParenScript compiler.
authorVladimir Sedach <vsedach@gmail.com>
Sun, 12 Aug 2007 01:19:48 +0000 (01:19 +0000)
committerVladimir Sedach <vsedach@gmail.com>
Sun, 12 Aug 2007 01:19:48 +0000 (01:19 +0000)
14 files changed:
parenscript.asd
src/builtin-packages.lisp [deleted file]
src/compilation-interface.lisp
src/deprecated-interface.lisp
src/js-macrology.lisp
src/js-source-model.lisp [deleted file]
src/js-translation.lisp
src/lib/js-html.lisp
src/lib/js-utils.lisp
src/namespace.lisp
src/package.lisp
src/parser.lisp
src/ps-macrology.lisp
src/ps-source-model.lisp [deleted file]

index 2b90631..434b010 100644 (file)
                (:module :src
                 :components ((:file "package")
                              (:file "utils" :depends-on ("package"))
-                            (:file "js-source-model" :depends-on ("package" "utils"))
-                            (:file "ps-source-model" :depends-on ("js-source-model"))
-                             (:file "namespace" :depends-on ("js-source-model" "ps-source-model"))
-                            (:file "parser" :depends-on ("js-source-model" "ps-source-model" "namespace"))
-                            (:file "builtin-packages" :depends-on ("parser"))
-                            (:file "deprecated-interface" :depends-on ("parser"))
-                            (:file "parse-lambda-list" :depends-on ("package"))
-                            (:file "js-macrology" :depends-on ("deprecated-interface"))
+                             (:file "namespace" :depends-on ("package"))
+                             (:file "parse-lambda-list" :depends-on ("package"))
+                            (:file "parser" :depends-on ("namespace"))
+                            (:file "js-macrology" :depends-on ("parser"))
                             (:file "ps-macrology" :depends-on ("js-macrology" "parse-lambda-list"))
                             (:file "js-translation" :depends-on ("ps-macrology"))
 ;                           (:file "js-ugly-translation" :depends-on ("js-translation"))
-                            (:file "compilation-interface" :depends-on ("package" "js-translation" "builtin-packages")); "js-ugly-translation"))
+                            (:file "compilation-interface" :depends-on ("package" "js-translation")); "js-ugly-translation"))
+                             (:file "deprecated-interface" :depends-on ("compilation-interface"))
                             ;; standard library
                              (:module :lib
                                       :components ((:static-file "functional.lisp")
diff --git a/src/builtin-packages.lisp b/src/builtin-packages.lisp
deleted file mode 100644 (file)
index 9efa9d7..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-(in-package :parenscript)
-
-(defmethod setup-compilation-environment ((comp-env compilation-environment))
-  (install-standard-script-packages comp-env)
-  (setf (comp-env-current-package comp-env)
-       (find-script-package :parenscript-user comp-env))
-  comp-env)
-
-(defparameter *javascript-exports*
-  (append
-   nil
-   cl-user::*shared-symbols-ps-js*))
-
-(defparameter *parenscript-exports*
-  (append
-   *javascript-exports*
-   cl-user::*parenscript-lang-exports*
-   nil
-   ))
-
-(defgeneric install-standard-script-packages (comp-env)
-  (:documentation "Creates standard script packages and installs them in the current compilation
-environment."))
-
-(defmethod install-standard-script-packages ((comp-env compilation-environment))
-  (list
-   (create-script-package
-    comp-env
-    :name "KEYWORD" :lisp-package :keyword)
-   (create-script-package
-    comp-env
-    :name "GLOBAL" :lisp-package :parenscript.global)
-   ;; symbols in the parenscript, javascript and parenscript-user packages are non-prefixed
-   (create-script-package
-    comp-env
-    :name "JAVASCRIPT" :prefix "" :nicknames (list "JS") :lisp-package :parenscript.javascript
-    :exports *javascript-exports*
-    :secondary-lisp-packages '(:common-lisp))
-   (create-script-package
-    comp-env
-    :name "PARENSCRIPT" :prefix "" :lisp-package :parenscript
-    :exports *parenscript-exports*
-    :used-packages '(:javascript)
-    )
-   (create-script-package
-    comp-env
-    :name "PARENSCRIPT-USER" :prefix "" :lisp-package :parenscript-user
-    :secondary-lisp-packages (list :cl-user)
-    :used-packages '("PARENSCRIPT")
-    :nicknames '("PS-USER" "PAREN-USER"))
-   (create-script-package
-    comp-env
-    :name "PS_GS" :lisp-package :parenscript.ps-gensyms)
-   (create-script-package
-    comp-env
-    :name "UNINTERNED" :prefix "")))
\ No newline at end of file
dissimilarity index 64%
index ec1dce6..7780eec 100644 (file)
-(in-package :parenscript)
-
-(defmacro with-new-compilation-environment ((var) &body body)
-  `(let* ((,var (make-basic-compilation-environment))
-         (*compilation-environment* ,var))
-    ,@body))
-    
-(defun translate-ast (compiled-expr
-                     &key
-                     (comp-env *compilation-environment*)
-                     (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."
-  (declare (ignore comp-env))
-  (when (not (eql :javascript output-spec))
-    (error "Unsupported output-spec for translation: ~A" output-spec))
-  (when (eql :javascript output-spec)
-;    (if (not pretty-print)
-;      (js-translate compiled-expr :statement output-stream)
-       (write-string (string-join
-                      (ps-js::js-to-statement-strings compiled-expr 0)
-                      (string #\Newline))
-                     output-stream)))
-
-(defun non-nil-comp-env ()
-  "Returns a sane compilation environment.  Either the one currently bound or a new
-one."
-  (or *compilation-environment*
-      (make-basic-compilation-environment)))
-
-
-(defun compile-script (script-form
-                      &key
-                      (output-spec :javascript)
-                      (pretty-print t)
-                      (output-stream nil)
-                      (toplevel-p t)
-                      (comp-env (non-nil-comp-env)))
-  "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.
-COMP-ENV is the compilation environment in which to compile the form.
-
-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))))
-    ;; we might want to bind this rather than set it
-    (setf (comp-env-compiling-toplevel-p comp-env) toplevel-p)
-    (with-output-stream (stream)
-      (let* ((*compilation-environment* comp-env)
-            (compiled (let ((first-result (compile-parenscript-form comp-env script-form)))
-                         (if (not toplevel-p)
-                             first-result
-                             (progn
-                               (setf (comp-env-compiling-toplevel-p comp-env) nil)
-                               (compile-parenscript-form comp-env first-result))))))
-       (translate-ast
-        compiled
-;       (compile-script-form script-form :comp-env comp-env)
-        :comp-env comp-env
-        :output-stream stream
-        :output-spec output-spec
-        :pretty-print pretty-print)))))
-
-(defun ps-to-string (expr)
-  "Given an AST node, compiles it to a Javascript string."
-  (string-join
-   (ps-js::js-to-statement-strings (compile-script-form expr) 0)
-   (string #\Newline)))
-
-;;; SEXPs -> Javascript string functionality
-(defmacro script (&body body)
-  "A macro that returns a Javascript string of the supplied Parenscript forms."
-  `(script* '(progn ,@body)))
-
-(defun script* (&rest body)
-  "Return the javascript string representing BODY.
-Body is evaluated."
-  (compile-script `(progn ,@body)))
-
-;;; Handy synonyms
-(defmacro ps (&body body)
-  `(script ,@body))
-
-(defmacro ps* (&body body)
-  `(script* ,@body))
-
-(defmacro js (&body body)
-  `(script ,@body))
-
-(defmacro js* (&body body)
-  `(script* ,@body))
+(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)))
index a40a419..ac18e8e 100644 (file)
@@ -28,7 +28,7 @@
 
 (defmacro defjsmacro (&rest args)
   (warn-deprecated 'defjsmacro 'defpsmacro)
-  `(defscriptmacro ,@args))
+  `(defpsmacro ,@args))
 
 (defmacro js-file (&rest body)
   (warn-deprecated 'js-file)
 
 (defmacro gen-js-name (&rest args)
   (warn-deprecated 'gen-js-name 'gen-ps-name)
-  `(gen-ps-name ,@args))
+  `(ps-gensym ,@args))
 
-(defmacro gen-js-name-string (&rest args)
-  (warn-deprecated 'gen-js-name-string 'gen-script-name-string)
-  `(gen-script-name-string ,@args))
+(defmacro js (&rest args)
+  `(ps ,@args))
+
+(defmacro js* (&rest args)
+  `(ps ,@args))
\ No newline at end of file
dissimilarity index 90%
index 53ed9e1..4bdecf5 100644 (file)
-(in-package :parenscript.javascript)
-
-;;;; The macrology of the basic Javascript-in-SEXPs language.  Special forms and macros.
-
-;;; literals
-(defmacro defscriptliteral (name string)
-  "Define a Javascript literal that will expand to STRING."
-  `(define-script-special-form ,name () (make-instance 'expression :value ,string)))
-
-(defscriptliteral this      "this")
-(defscriptliteral t         "true")
-(defscriptliteral true      "true")
-(defscriptliteral false     "false")
-(defscriptliteral f         "false")
-(defscriptliteral nil       "null")
-(defscriptliteral undefined "undefined")
-
-(defmacro defscriptkeyword (name string)
-  "Define a Javascript keyword that will expand to STRING."
-  `(define-script-special-form ,name () (make-instance 'statement :value ,string)))
-
-(defscriptkeyword break    "break")
-(defscriptkeyword continue "continue")
-
-;;; array literals
-(define-script-special-form array (&rest values)
-  (make-instance 'array-literal
-                :values (mapcar #'compile-to-expression values)))
-
-(define-script-special-form aref (array &rest coords)
-  (make-instance 'js-aref
-                :array (compile-to-expression array)
-                :index (mapcar #'compile-to-expression coords)))
-
-
-;;; object literals (maps and hash-tables)
-(define-script-special-form {} (&rest values)
-  (make-instance 'object-literal
-                 :values (loop
-                            for (key value) on values by #'cddr
-                            collect (cons key (compile-to-expression value)))))
-
-;;; operators
-(define-script-special-form ++ (x)
-  (make-instance 'one-op :pre-p nil :op "++"
-                :value (compile-to-expression x)))
-
-(define-script-special-form -- (x)
-  (make-instance 'one-op :pre-p nil :op "--"
-                :value (compile-to-expression x)))
-
-(define-script-special-form incf (x &optional (delta 1))
-  (if (eql delta 1)
-      (make-instance 'one-op :pre-p t :op "++"
-                     :value (compile-to-expression x))
-      (make-instance 'op-form
-                     :operator '+=
-                     :args (mapcar #'compile-to-expression
-                                   (list x delta )))))
-
-(define-script-special-form decf (x &optional (delta 1))
-  (if (eql delta 1)
-      (make-instance 'one-op :pre-p t :op "--"
-                     :value (compile-to-expression x))
-      (make-instance 'op-form
-                     :operator '-=
-                     :args (mapcar #'compile-to-expression
-                                   (list x delta )))))
-
-(define-script-special-form - (first &rest rest)
-  (if (null rest)
-      (make-instance 'one-op
-                     :pre-p t
-                     :op "-"
-                     :value (compile-to-expression first))
-      (make-instance 'op-form
-                     :operator '-
-                     :args (mapcar #'compile-to-expression
-                                   (cons first rest)))))
-
-(define-script-special-form not (x)
-  (let ((value (compile-to-expression x)))
-    (if (and (typep value 'op-form)
-            (= (length (op-args value)) 2))
-       (let ((new-op (case (operator value)
-                       (== '!=)
-                       (< '>=)
-                       (> '<=)
-                       (<= '>)
-                       (>= '<)
-                       (!= '==)
-                       (=== '!==)
-                       (!== '===)
-                       (t nil))))
-         (if new-op
-             (make-instance 'op-form :operator new-op
-                            :args (op-args value))
-             (make-instance 'one-op :pre-p t :op "!"
-                           :value value)))
-       (make-instance 'one-op :pre-p t :op "!"
-                      :value value))))
-
-(define-script-special-form ~ (x)
-  (let ((expr (compile-to-expression x)))
-    (make-instance 'one-op :pre-p t :op "~" :value expr)))
-
-(define-script-special-form progn (&rest body)
-  (make-instance 'js-block :statements (mapcar #'compile-to-statement body)))
-
-(defmethod expression-precedence ((body js-block))
-  (if (= (length (block-statements body)) 1)
-      (expression-precedence (first (block-statements body)))
-      (op-precedence 'comma)))
-
-;;; function definition
-(define-script-special-form %js-lambda (args &rest body)
-  (make-instance 'js-lambda
-                 :args (mapcar #'compile-to-symbol args)
-                 :body (make-instance 'js-block
-                                      :indent "  "
-                                      :statements (mapcar #'compile-to-statement body))))
-
-(define-script-special-form %js-defun (name args &rest body)
-  (make-instance 'js-defun
-                :name (compile-to-symbol name)
-                :args (mapcar #'compile-to-symbol args)
-                :body (make-instance 'js-block
-                                     :indent "  "
-                                     :statements (mapcar #'compile-to-statement body))))
-
-;;; object creation
-(define-script-special-form create (&rest args)
-  (make-instance 'js-object
-                :slots (loop for (name val) on args by #'cddr
-                             collect (let ((name-expr (compile-to-expression name)))
-                                       (assert (or (typep name-expr 'js-variable)
-                                                   (typep name-expr 'script-quote)
-                                                   (typep name-expr 'string-literal)
-                                                   (typep name-expr 'number-literal)))
-                                       (list name-expr (compile-to-expression val))))))
-
-
-(define-script-special-form %js-slot-value (obj slot)
-  (if (ps::expand-script-form slot)
-      (make-instance 'js-slot-value
-                    :object (compile-to-expression obj)
-                     :slot (compile-script-form slot))
-      (compile-to-expression obj)))
-
-;;; cond
-(define-script-special-form cond (&rest clauses)
-  (make-instance 'js-cond
-                :tests (mapcar (lambda (clause) (compile-to-expression (car clause)))
-                               clauses)
-                :bodies (mapcar (lambda (clause) (compile-to-block (cons 'progn (cdr clause)) :indent "  "))
-                                clauses)))
-
-;;; if
-(define-script-special-form if (test then &optional else)
-  (make-instance 'js-if :test (compile-to-expression test)
-                :then (compile-to-block then :indent "  ")
-                :else (when else
-                        (compile-to-block else :indent "  "))))
-
-(defmethod expression-precedence ((if js-if))
-  (op-precedence 'if))
-
-;;; switch
-(define-script-special-form switch (value &rest clauses)
-  (let ((clauses (mapcar #'(lambda (clause)
-                            (let ((val (first clause))
-                                  (body (cdr clause)))
-                              (list (if (eql val 'default)
-                                        'default
-                                        (compile-to-expression val))
-                                    (compile-to-block (cons 'progn body) :indent "  "))))
-                        clauses))
-       (check (compile-to-expression value)))
-    (make-instance 'js-switch :value check
-                  :clauses clauses)))
-
-
-;;; assignment
-(defun assignment-op (op)
-  (case op
-    (+ '+=)
-    (~ '~=)
-    (\& '\&=)
-    (\| '\|=)
-    (- '-=)
-    (* '*=)
-    (% '%=)
-    (>> '>>=)
-    (^  '^=)
-    (<< '<<=)
-    (>>> '>>>=)
-    (/   '/=)
-    (t   nil)))
-
-(defun make-js-test (lhs rhs)
-  (if (and (typep rhs 'op-form)
-          (member lhs (op-args rhs) :test #'script-equal))
-      (let ((args-without (remove lhs (op-args rhs)
-                                 :count 1 :test #'script-equal))
-           (args-without-first (remove lhs (op-args rhs)
-                                       :count 1 :end 1
-                                       :test #'script-equal))
-           (one (list (make-instance 'number-literal :value 1))))
-       #+nil
-       (format t "OPERATOR: ~S, ARGS-WITHOUT: ~S, ARGS-WITHOUT-FIRST ~S~%"
-               (operator rhs)
-               args-without
-               args-without-first)
-       (cond ((and (script-equal args-without one)
-                   (eql (operator rhs) '+))
-              (make-instance 'one-op :pre-p nil :op "++"
-                             :value lhs))
-             ((and (script-equal args-without-first one)
-                   (eql (operator rhs) '-))
-              (make-instance 'one-op :pre-p nil :op "--"
-                             :value lhs))
-             ((and (assignment-op (operator rhs))
-                   (member (operator rhs)
-                           '(+ *))
-                    (script-equal lhs (first (op-args rhs))))
-              (make-instance 'op-form
-                             :operator (assignment-op (operator rhs))
-                             :args (list lhs (make-instance 'op-form
-                                                            :operator (operator rhs)
-                                                            :args args-without-first))))
-             ((and (assignment-op (operator rhs))
-                   (script-equal (first (op-args rhs)) lhs))
-              (make-instance 'op-form
-                             :operator (assignment-op (operator rhs))
-                             :args (list lhs (make-instance 'op-form
-                                                            :operator (operator rhs)
-                                                            :args (cdr (op-args rhs))))))
-             (t (make-instance 'js-setf :lhs lhs :rhsides (list rhs)))))
-      (make-instance 'js-setf :lhs lhs :rhsides (list rhs))))
-
-(define-script-special-form setf1% (lhs rhs)
-  (make-js-test (compile-to-expression lhs) (compile-to-expression rhs)))
-
-(defmethod expression-precedence ((setf js-setf))
-  (op-precedence '=))
-
-;;; defvar
-(define-script-special-form defvar (name &optional value)
-  (make-instance 'js-defvar :names (list (compile-to-symbol name))
-                :value (when value (compile-to-expression value))))
-
-;;; iteration
-(defun make-for-vars (decls)
-  (loop for decl in decls
-       for var = (if (atom decl) decl (first decl))
-       for init = (if (atom decl) nil (second decl))
-       collect (make-instance 'js-defvar :names (list (compile-to-symbol var))
-                              :value (compile-to-expression init))))
-
-(defun make-for-steps (decls)
-  (loop for decl in decls
-       when (= (length decl) 3)
-       collect (compile-to-expression (third decl))))
-
-(define-script-special-form do (decls termination &rest body)
-  (let ((vars (make-for-vars decls))
-       (steps (make-for-steps decls))
-       (check (compile-to-expression (list 'not (first termination))))
-       (body (compile-to-block (cons 'progn body) :indent "  ")))
-    (make-instance 'js-for
-                  :vars vars
-                  :steps steps
-                  :check check
-                  :body body)))
-
-(define-script-special-form doeach (decl &rest body)
-  (make-instance 'for-each :name (compile-to-symbol (first decl))
-                :value (compile-to-expression (second decl))
-                :body (compile-to-block (cons 'progn body) :indent "  ")))
-
-(define-script-special-form while (check &rest body)
-  (make-instance 'js-while
-                :check (compile-to-expression check)
-                :body (compile-to-block (cons 'progn body) :indent "  ")))
-
-;;; with
-(define-script-special-form with (statement &rest body)
-  (make-instance 'js-with
-                :obj (compile-to-expression statement)
-                :body (compile-to-block (cons 'progn body) :indent "  ")))
-
-
-;;; try-catch
-(define-script-special-form try (body &rest clauses)
-  (let ((body (compile-to-block body :indent "  "))
-       (catch (cdr (assoc :catch clauses)))
-       (finally (cdr (assoc :finally clauses))))
-    (assert (not (cdar catch)) nil "Sorry, currently only simple catch forms are supported.")
-    (make-instance 'js-try
-                  :body body
-                  :catch (when catch (list (compile-to-symbol (caar catch))
-                                           (compile-to-block (cons 'progn (cdr catch))
-                                                               :indent "  ")))
-                  :finally (when finally (compile-to-block (cons 'progn finally)
-                                                             :indent "  ")))))
-;;; regex
-(define-script-special-form regex (regex)
-  (make-instance 'regex :value (string regex)))
-
-;;; TODO instanceof
-(define-script-special-form instanceof (value type)
-  (make-instance 'js-instanceof
-                 :value (compile-to-expression value)
-                 :type (compile-to-expression type)))
-
-;;; single operations
-(defmacro define-parse-script-single-op (name &optional (superclass 'expression))
-  (let ((script-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
-    `(define-script-special-form ,name (value)
-       (make-instance ',script-name :value (compile-to-expression value)))
-    ))
-
-(define-parse-script-single-op throw statement)
-(define-parse-script-single-op delete)
-(define-parse-script-single-op void)
-(define-parse-script-single-op typeof)
-(define-parse-script-single-op new)
-
-(define-script-special-form return (&optional value)
-  (make-instance 'js-return :value (compile-to-expression value)))
-
-;;; conditional compilation
-(define-script-special-form cc-if (test &rest body)
-  (make-instance 'cc-if :test test
-                :body (mapcar #'compile-script-form body)))
-
-;;; standard macros
-(defscriptmacro when (test &rest body)
-  `(if ,test (progn ,@body)))
-
-(defscriptmacro unless (test &rest body)
-  `(if (not ,test) (progn ,@body)))
-
-(defscriptmacro 1- (form)
-  `(- ,form 1))
-
-(defscriptmacro 1+ (form)
-  `(+ ,form 1))
-
-;;; helper macros
-(define-script-special-form js (&rest body)
-  (make-instance 'string-literal
-                :value (string-join (js-to-statement-strings
-                                     (compile-script-form (cons 'progn body)) 0) " ")))
-
-(define-script-special-form script-inline (&rest body)
-  (make-instance 'string-literal
-                :value (concatenate
-                        'string
-                        "javascript:"
-                        (string-join (js-to-statement-strings
-                                      (compile-script-form (cons 'progn body)) 0) " "))))
-(defscriptmacro parenscript::js-inline (&rest body)
-  `(script-inline ,@body))
+(in-package :parenscript)
+
+;;;; The macrology of the basic Javascript-in-SEXPs language.  Special forms and macros.
+
+;;; literals
+(defmacro defpsliteral (name string)
+  `(define-ps-special-form ,name (expecting) (list 'js-literal ,string)))
+
+(defpsliteral this      "this")
+(defpsliteral t         "true")
+(defpsliteral true      "true")
+(defpsliteral false     "false")
+(defpsliteral f         "false")
+(defpsliteral nil       "null")
+(defpsliteral undefined "undefined")
+
+(defmacro defpskeyword (name string)
+  `(define-ps-special-form ,name (expecting) (list 'js-keyword ,string)))
+
+(defpskeyword break    "break")
+(defpskeyword continue "continue")
+
+(define-ps-special-form array (expecting &rest values)
+  (cons 'array-literal (mapcar (lambda (form) (compile-parenscript-form form :expecting :expression))
+                               values)))
+
+(define-ps-special-form aref (expecting array &rest coords)
+  (list 'js-aref (compile-parenscript-form array :expecting :expression)
+        (mapcar (lambda (form)
+                  (compile-parenscript-form form :expecting :expression))
+                coords)))
+
+(define-ps-special-form {} (expecting &rest arrows)
+  (cons 'object-literal (loop for (key value) on arrows by #'cddr
+                              collect (cons key (compile-parenscript-form value :expecting :expression)))))
+
+;;; operators
+(define-ps-special-form incf (expecting x &optional (delta 1))
+  (if (equal delta 1)
+      (list 'unary-operator "++" (compile-parenscript-form x :expecting :expression) :prefix t)
+      (list 'operator '+= (list (compile-parenscript-form x :expecting :expression)
+                                (compile-parenscript-form delta :expecting :expression)))))
+
+(define-ps-special-form decf (expecting x &optional (delta 1))
+  (if (equal delta 1)
+      (list 'unary-operator "--" (compile-parenscript-form x :expecting :expression) :prefix t)
+      (list 'operator '-= (list (compile-parenscript-form x :expecting :expression)
+                                (compile-parenscript-form delta :expecting :expression)))))
+
+(define-ps-special-form - (expecting first &rest rest)
+  (if (null rest)
+      (list 'unary-operator "-" (compile-parenscript-form first :expecting :expression) :prefix t)
+      (list 'operator '- (mapcar (lambda (val) (compile-parenscript-form val :expecting :expression))
+                                 (cons first rest)))))
+
+(define-ps-special-form not (expecting x)
+  (let ((form (compile-parenscript-form x :expecting :expression))
+        (not-op nil))
+    (if (and (eql (first form) 'operator)
+            (= (length (third form)) 2)
+             (setf not-op (case (second form)
+                            (== '!=)
+                            (< '>=)
+                            (> '<=)
+                            (<= '>)
+                            (>= '<)
+                            (!= '==)
+                            (=== '!==)
+                            (!== '===)
+                            (t nil))))
+        (list 'operator not-op (third form))
+        (list 'unary-operator "!" form :prefix t))))
+
+(define-ps-special-form ~ (expecting x)
+  (list 'unary-operator "~" (compile-parenscript-form x :expecting :expressin) :prefix t))
+
+(defun flatten-progns (body)
+  (unless (null body)
+    (if (and (listp (car body))
+             (eql 'progn (caar body)))
+        (append (cdar body) (flatten-progns (cdr body)))
+        (cons (car body) (flatten-progns (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)))))
+
+;;; function definition
+(define-ps-special-form %js-lambda (expecting args &rest body)
+  (list 'js-lambda (mapcar (lambda (arg)
+                             (compile-parenscript-form arg :expecting :symbol))
+                           args)
+        (compile-parenscript-form `(progn ,@body))))
+
+(define-ps-special-form %js-defun (expecting name args &rest body)
+  (list 'js-defun (compile-parenscript-form name :expecting :symbol)
+        (mapcar (lambda (val) (compile-parenscript-form val :expecting :symbol)) args)
+       (compile-parenscript-form `(progn ,@body))))
+
+;;; object creation
+(define-ps-special-form create (expecting &rest args)
+  (list 'js-object (loop for (name val) on args by #'cddr collecting
+                         (let ((name-expr (compile-parenscript-form name :expecting :expression)))
+                           (assert (or (stringp name-expr)
+                                       (numberp name-expr)
+                                       (and (listp name-expr)
+                                            (or (eql 'js-variable (car name-expr))
+                                                (eql 'script-quote (car name-expr)))))
+                                   ()
+                                   "Slot ~s is not one of js-variable, keyword, string or number." name-expr)
+                           (list name-expr (compile-parenscript-form val :expecting :expression))))))
+
+(define-ps-special-form %js-slot-value (expecting obj slot)
+  (if (ps::ps-macroexpand slot)
+      (list 'js-slot-value (compile-parenscript-form obj :expecting :expression) (compile-parenscript-form slot))
+      (compile-parenscript-form obj :expecting :expression)))
+
+(define-ps-special-form cond (expecting &rest clauses)
+  (list 'js-cond (mapcar (lambda (clause)
+                           (destructuring-bind (test &rest body)
+                               clause
+                             (list (compile-parenscript-form test :expecting :expression)
+                                   (mapcar (lambda (form) (compile-parenscript-form form :expecting :statement))
+                                           body))))
+                         clauses)))
+
+(define-ps-special-form if (expecting test then &optional else)
+  (ecase expecting
+    (:statement (list 'js-statement-if (compile-parenscript-form test :expecting :expression)
+                      (compile-parenscript-form `(progn ,then))
+                      (when else (compile-parenscript-form `(progn ,else)))))
+    (:expression (list 'js-expression-if (compile-parenscript-form test :expecting :expression)
+                       (compile-parenscript-form then :expecting :expression)
+                       (when else (compile-parenscript-form else :expecting :expression))))))
+
+(define-ps-special-form switch (expecting test-expr &rest clauses)
+  (let ((clauses (mapcar (lambda (clause)
+                            (let ((val (car clause))
+                                  (body (cdr clause)))
+                              (list (if (eql val 'default)
+                                        'default
+                                        (compile-parenscript-form val :expecting :expression))
+                                     (compile-parenscript-form `(progn ,@body)))))
+                        clauses))
+       (expr (compile-parenscript-form test-expr :expecting :expression)))
+    (list 'js-switch expr clauses)))
+
+;;; assignment
+(defun assignment-op (op)
+  (case op
+    (+ '+=)
+    (~ '~=)
+    (\& '\&=)
+    (\| '\|=)
+    (- '-=)
+    (* '*=)
+    (% '%=)
+    (>> '>>=)
+    (^  '^=)
+    (<< '<<=)
+    (>>> '>>>=)
+    (/   '/=)
+    (t   nil)))
+
+(defun smart-setf (lhs rhs)
+  (if (and (listp rhs)
+           (eql 'operator (car rhs))
+          (member lhs (third rhs) :test #'equalp))
+      (let ((args-without (remove lhs (third rhs) :count 1 :test #'equalp))
+           (args-without-first (remove lhs (third rhs) :count 1 :end 1 :test #'equalp)))
+       (cond ((and (equal (car args-without) 1) (eql (second rhs) '+))
+              (list 'unary-operator "++" lhs :prefix nil))
+             ((and (equal (second args-without-first) 1) (eql (second rhs) '-))
+              (list 'unary-operator "--" lhs :prefix nil))
+             ((and (assignment-op (second rhs))
+                   (member (second rhs) '(+ *))
+                    (equalp lhs (first (third rhs))))
+              (list 'operator (assignment-op (second rhs))
+                     (list lhs (list 'operator (second rhs) args-without-first))))
+             ((and (assignment-op (second rhs)) (equalp (first (third rhs)) lhs))
+              (list 'operator (assignment-op (second rhs))
+                     (list lhs (list 'operator (second rhs) (cdr (third rhs))))))
+             (t (list 'js-assign lhs rhs))))
+      (list 'js-assign lhs rhs)))
+
+(define-ps-special-form setf1% (expecting lhs rhs)
+  (smart-setf (compile-parenscript-form lhs :expecting :expression) (compile-parenscript-form rhs :expecting :expression)))
+
+(define-ps-special-form defvar (expecting name &rest value)
+  (append (list 'js-defvar (compile-parenscript-form name :expecting :symbol))
+          (when value
+            (assert (= (length value) 1) () "Wrong number of arguments to defvar: ~s" `(defvar ,name ,@value))
+           (list (compile-parenscript-form (car value) :expecting :expression)))))
+
+;;; iteration
+(defun make-for-vars (decls)
+  (loop for decl in decls
+       for var = (if (atom decl) decl (first decl))
+       for init-value = (if (atom decl) nil (second decl))
+       collect (cons (compile-parenscript-form var :expecting :symbol) (compile-parenscript-form init-value))))
+
+(defun make-for-steps (decls)
+  (loop for decl in decls
+       when (= (length decl) 3)
+       collect (compile-parenscript-form (third decl) :expecting :expression)))
+
+(define-ps-special-form do (expecting decls termination-test &rest body)
+  (let ((vars (make-for-vars decls))
+       (steps (make-for-steps decls))
+       (test (compile-parenscript-form `(not ,(first termination-test)) :expecting :expression))
+       (body (compile-parenscript-form `(progn ,@body))))
+    (list 'js-for vars steps test body)))
+
+(define-ps-special-form doeach (expecting decl &rest body)
+  (list 'js-for-each
+        (compile-parenscript-form (first decl) :expecting :symbol)
+        (compile-parenscript-form (second decl) :expecting :expression)
+       (compile-parenscript-form `(progn ,@body))))
+
+(define-ps-special-form while (expecting test &rest body)
+  (list 'js-while (compile-parenscript-form test :expecting :expression)
+                  (compile-parenscript-form `(progn ,@body))))
+
+(define-ps-special-form with (expecting expression &rest body)
+  (list 'js-with (compile-parenscript-form expression :expecting :expression)
+                (compile-parenscript-form `(progn ,@body))))
+
+(define-ps-special-form try (expecting form &rest clauses)
+  (let ((catch (cdr (assoc :catch clauses)))
+        (finally (cdr (assoc :finally clauses))))
+    (assert (not (cdar catch)) nil "Sorry, currently only simple catch forms are supported.")
+    (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)))))
+          :finally (when finally (compile-parenscript-form `(progn ,@finally))))))
+
+(define-ps-special-form regex (expecting regex)
+  (list 'js-regex (string regex)))
+
+;;; TODO instanceof
+(define-ps-special-form instanceof (expecting value type)
+  (list 'js-instanceof (compile-parenscript-form value :expecting :expression)
+        (compile-parenscript-form type :expecting :expression)))
+
+;;; single operations
+(mapcar (lambda (op) (eval `(define-ps-special-form ,op (expecting value)
+                             (list 'js-named-operator ',op (compile-parenscript-form value)))))
+        '(throw delete void typeof new))
+
+(define-ps-special-form return (expecting &optional value)
+  (list 'js-return (compile-parenscript-form value :expecting :expression)))
+
+;;; conditional compilation
+(define-ps-special-form cc-if (expecting test &rest body)
+  (list 'cc-if test (mapcar #'compile-parenscript-form body)))
+
+;;; standard macros
+(defpsmacro when (test &rest body)
+  `(if ,test (progn ,@body)))
+
+(defpsmacro unless (test &rest body)
+  `(if (not ,test) (progn ,@body)))
+
+(defpsmacro 1- (form)
+  `(- ,form 1))
+
+(defpsmacro 1+ (form)
+  `(+ ,form 1))
+
+;;; helper macros
+(define-ps-special-form js (expecting &rest body)
+  (string-join (ps-print (compile-parenscript-form `(progn ,@body)) 0) " "))
+
+(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))
+                            ";")
+               ";"))
+
diff --git a/src/js-source-model.lisp b/src/js-source-model.lisp
deleted file mode 100644 (file)
index 77946ff..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-(in-package :parenscript)
-
-(defgeneric script-equal (compiled-ast-node1 compiled-ast-node2)
-  (:documentation "Determines if the AST nodes are equal."))
-
-;;; AST node equality
-(defmethod script-equal ((obj1 list) (obj2 list))
-  (and (= (length obj1) (length obj2))
-       (every #'script-equal obj1 obj2)))
-
-(defmethod script-equal ((obj1 t) (obj2 t))
-  (equal obj1 obj2))
-
-(defmacro defscriptclass (name superclasses slots &rest class-options)
-  (let ((slot-names (mapcar #'(lambda (slot) (if (atom slot) slot (first slot))) slots)))
-    `(progn
-      (defclass ,name ,superclasses
-       ,slots ,@class-options)
-      (defmethod script-equal ((obj1 ,name) (obj2 ,name))
-       (every #'(lambda (slot)
-                  (script-equal (slot-value obj1 slot)
-                            (slot-value obj2 slot)))
-              ',slot-names)))))
-
-(in-package :parenscript.javascript)
-
-(defgeneric expression-precedence (expression)
-  (:documentation "Returns the precedence of an enscript-javascript expression"))
-
-;;;; define Javascript language types
-(defclass statement ()
-  ((value :initarg :value :accessor value :initform nil))
-  (:documentation "A Javascript entity without a value."))
-
-(defclass expression (statement)
-  ()
-  (:documentation "A Javascript entity with a value."))
-
-;;; array literals
-(defscriptclass array-literal (expression)
-  ((values :initarg :values :accessor array-values)))
-
-(defscriptclass js-aref (expression)
-  ((array :initarg :array
-         :accessor aref-array)
-   (index :initarg :index
-         :accessor aref-index)))
-
-;;; object literals (maps and hash-tables)
-(defscriptclass object-literal (expression)
-  ((values :initarg :values :accessor object-values)))
-
-;;; string literals
-(defscriptclass string-literal (expression)
-  (value))
-
-
-;;; number literals
-(defscriptclass number-literal (expression)
-  (value))
-
-;;; variables
-(defscriptclass js-variable (expression)
-  (value))
-
-;;; operators
-(defscriptclass op-form (expression)
-  ((operator :initarg :operator :accessor operator)
-   (args :initarg :args :accessor op-args)))
-
-(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 '((aref)
-                   (slot-value)
-                   (! not ~)
-                   (* / %)
-                   (+ -)
-                   (<< >>)
-                   (>>>)
-                   (< > <= >=)
-                   (in if)
-                   (eql == != =)
-                   (=== !==)
-                   (&)
-                   (^)
-                   (\|)
-                   (\&\& and)
-                   (\|\| or)
-                   (setf *= /= %= += -= <<= >>= >>>= \&= ^= \|=)
-                   (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*)))
-
-(defscriptclass one-op (expression)
-  ((pre-p :initarg :pre-p
-         :initform nil
-         :accessor one-op-pre-p)
-   (op :initarg :op
-       :accessor one-op)))
-
-;;; function calls
-(defscriptclass function-call (expression)
-  ((function :initarg :function :accessor f-function)
-   (args :initarg :args :accessor f-args)))
-
-(defscriptclass method-call (expression)
-  ((method :initarg :method :accessor m-method)
-   (object :initarg :object :accessor m-object)
-   (args :initarg :args :accessor m-args)))
-
-;;; body forms
-(defscriptclass js-block (expression)
-  ((statements :initarg :statements :accessor block-statements)
-   (indent :initarg :indent :initform "" :accessor block-indent)))
-
-(defmethod initialize-instance :after ((block js-block) &rest initargs)
-  (declare (ignore initargs))
-  (let* ((statements (block-statements block))
-        (last (last statements))
-        (last-stmt (car last)))
-    (when (typep last-stmt 'js-block)
-      (setf (block-statements block)
-           (nconc (butlast statements)
-                  (block-statements last-stmt))))))
-
-(defscriptclass js-sub-block (js-block)
-  (statements indent))
-
-;;; function definition
-(defscriptclass js-lambda (expression)
-  ((args :initarg :args :accessor lambda-args)
-   (body :initarg :body :accessor lambda-body)))
-
-(defscriptclass js-defun (js-lambda)
-  ((name :initarg :name :accessor defun-name)))
-
-;;; object creation
-(defscriptclass js-object (expression)
-  ((slots :initarg :slots
-         :accessor o-slots)))
-
-(defscriptclass js-slot-value (expression)
-  ((object :initarg :object
-          :accessor sv-object)
-   (slot :initarg :slot
-        :accessor sv-slot)))
-
-;;; cond
-(defscriptclass js-cond (expression)
-  ((tests :initarg :tests
-         :accessor cond-tests)
-   (bodies :initarg :bodies
-          :accessor cond-bodies)))
-
-(defscriptclass js-if (expression)
-  ((test :initarg :test
-        :accessor if-test)
-   (then :initarg :then
-        :accessor if-then)
-   (else :initarg :else
-        :accessor if-else)))
-
-(defmethod initialize-instance :after ((if js-if) &rest initargs)
-  (declare (ignore initargs))
-  (when (and (if-then if)
-            (typep (if-then if) 'js-sub-block))
-    (change-class (if-then if) 'js-block))
-  (when (and (if-else if)
-            (typep (if-else if) 'js-sub-block))
-    (change-class (if-else if) 'js-block)))
-
-;;; switch
-(defscriptclass js-switch (statement)
-  ((value :initarg :value :accessor case-value)
-   (clauses :initarg :clauses :accessor case-clauses)))
-
-;;; assignment
-
-(defscriptclass js-setf (expression)
-  ((lhs :initarg :lhs :accessor setf-lhs)
-   (rhsides :initarg :rhsides :accessor setf-rhsides)))
-
-;;; defvar
-(defscriptclass js-defvar (statement)
-  ((names :initarg :names :accessor var-names)
-   (value :initarg :value :accessor var-value)))
-
-;;; iteration
-(defscriptclass js-for (statement)
-  ((vars :initarg :vars :accessor for-vars)
-   (steps :initarg :steps :accessor for-steps)
-   (check :initarg :check :accessor for-check)
-   (body :initarg :body :accessor for-body)))
-
-(defscriptclass for-each (statement)
-  ((name :initarg :name :accessor fe-name)
-   (value :initarg :value :accessor fe-value)
-   (body :initarg :body :accessor fe-body)))
-
-(defscriptclass js-while (statement)
-  ((check :initarg :check :accessor while-check)
-   (body :initarg :body :accessor while-body)))
-
-;;; with
-(defscriptclass js-with (statement)
-  ((obj :initarg :obj :accessor with-obj)
-   (body :initarg :body :accessor with-body)))
-
-;;; try-catch
-(defscriptclass js-try (statement)
-  ((body :initarg :body :accessor try-body)
-   (catch :initarg :catch :accessor try-catch)
-   (finally :initarg :finally :accessor try-finally)))
-
-;;; regular expressions
-(defscriptclass regex (expression)
-  (value))
-
-;;; conditional compilation
-(defscriptclass cc-if ()
-  ((test :initarg :test :accessor cc-if-test)
-   (body :initarg :body :accessor cc-if-body)))
-
-;; TODO this may not be the best integrated implementation of
-;; instanceof into the rest of the code
-(defscriptclass js-instanceof (expression)
-  ((value)
-   (type :initarg :type)))
-
-(defmacro define-js-single-op (name &optional (superclass 'expression))
-  (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
-  `(progn
-    (defscriptclass ,js-name (,superclass)
-      (value)))))
-
-(define-js-single-op return statement)
-(define-js-single-op throw statement)
-(define-js-single-op delete)
-(define-js-single-op void)
-(define-js-single-op typeof)
-(define-js-single-op new)
\ No newline at end of file
dissimilarity index 77%
index bff0828..b160690 100644 (file)
-(in-package :parenscript.javascript)
-
-(defgeneric js-to-strings (expression start-pos)
-  (:documentation "Transform an enscript-javascript expression to a string"))
-
-(defgeneric js-to-statement-strings (code-fragment start-pos)
-  (:documentation "Transform an enscript-javascript code fragment to a string"))
-
-;;; indenter
-
-(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 "unsupported form ~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 line 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)))))
-
-(defmethod js-to-strings ((expression expression) start-pos)
-  (declare (ignore start-pos))
-  (list (princ-to-string (value expression))))
-
-(defmethod js-to-statement-strings ((expression expression) start-pos)
-  (js-to-strings expression start-pos))
-
-(defmethod js-to-statement-strings ((statement statement) start-pos)
-  (declare (ignore start-pos))
-  (list (princ-to-string (value statement))))
-
-(defmethod js-to-strings ((expression script-quote) start-pos)
-  (declare (ignore start-pos))
-  (list
-   (if (eql nil (value expression))
-       "null"
-       (case (value expression)
-        (t (error "Cannot translate quoted value ~S to javascript" (value expression)))))))
-
-;;; array literals
-
-(defmethod js-to-strings ((array array-literal) start-pos)
-  (let ((value-string-lists
-        (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
-                (array-values array)))
-       (max-length (- 80 start-pos 2)))
-    (dwim-join value-string-lists max-length
-              :start "[ " :end " ]"
-              :join-after ",")))
-
-(defmethod js-to-strings ((aref js-aref) start-pos)
-  (dwim-join (cons (js-to-strings (aref-array aref) start-pos)
-                  (mapcar #'(lambda (x) (dwim-join (list (js-to-strings x (+ start-pos 2)))
-                                                   (- 80 start-pos 2)
-                                                   :start "[" :end "]"))
-                          (aref-index aref)))
-            (- 80 start-pos 2) :separator ""
-            :white-space "  "))
-
-;;; object literals (maps and hash-tables)
-
-(defmethod js-to-strings ((obj object-literal) start-pos)
-  (dwim-join
-   (loop
-    for (key . value) in (object-values obj)
-    append (list
-           (dwim-join (list (list (format nil "~A:" (js-translate-symbol key)))
-                            (js-to-strings value (+ start-pos 2)))
-                      (- 80 start-pos 2)
-                      :start "" :end "" :join-after "")))
-   (- 80 start-pos 2)
-   :start "{ " :end " }"
-   :join-after ","))
-
-;;; string literals
-
-(defvar *js-quote-char* #\'
-  "Specifies which character JS sholud use for delimiting strings.
-
-This variable is usefull 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)))
-
-(defun lisp-special-char-to-js (lisp-char)
-    (car (rassoc lisp-char *js-lisp-escaped-chars*)))
-
-(defmethod js-to-strings ((string string-literal) start-pos)
-  (declare (ignore start-pos)
-           (inline lisp-special-char-to-js))
-  (list (with-output-to-string (escaped)
-          (write-char *js-quote-char*  escaped)
-          (loop
-           for char across (value 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)))))
-
-;;; variables
-(defgeneric js-translate-symbol (var)
-  (:documentation "Given a JS-VARIABLE returns an output
-JavaScript version of it as a string."))
-
-(defmethod js-translate-symbol ((var js-variable))
-  (js-translate-symbol (value var)))
-
-(defmethod js-translate-symbol ((var-name symbol))
-  (ps::js-translate-symbol-contextually var-name (ps::symbol-script-package var-name) ps::*compilation-environment*))
-
-(defmethod js-to-strings ((v js-variable) start-form)
-  (declare (ignore start-form))
-  (list (js-translate-symbol v)))
-
-;;; arithmetic operators
-(defun script-convert-op-name (op)
-  (case op
-    (and '\&\&)
-    (or '\|\|)
-    (not '!)
-    (eql '\=\=)
-    (=   '\=\=)
-    (t op)))
-
-(defun op-form-p (form)
-  (and (listp form)
-       (not (script-special-form-p form))
-       (not (null (op-precedence (first form))))))
-
-(defun klammer (string-list)
-  (prepend-to-first string-list "(")
-  (append-to-last string-list ")")
-  string-list)
-
-(defmethod expression-precedence ((expression expression))
-  0)
-
-(defmethod expression-precedence ((form op-form))
-  (op-precedence (operator form)))
-
-(defmethod js-to-strings ((form op-form) start-pos)
-  (let* ((precedence (expression-precedence form))
-        (value-string-lists
-         (mapcar #'(lambda (x)
-                     (let ((string-list (js-to-strings x (+ start-pos 2))))
-                       (if (>= (expression-precedence x) precedence)
-                           (klammer string-list)
-                           string-list)))
-                 (op-args form)))
-        (max-length (- 80 start-pos 2))
-        (op-string (format nil "~A " (operator form))))
-    (dwim-join value-string-lists max-length :join-before op-string)    
-    ))
-
-(defmethod js-to-strings ((one-op one-op) start-pos)
-  (let* ((value (value one-op))
-        (value-strings (js-to-strings value start-pos)))
-    (when (typep value 'op-form)
-      (setf value-strings (klammer value-strings)))
-    (if (one-op-pre-p one-op)
-      (prepend-to-first value-strings
-                       (one-op one-op))
-      (append-to-last value-strings
-                     (one-op one-op)))))
-
-;;; function calls
-
-(defmethod js-to-strings ((form function-call) start-pos)
-  (let* ((value-string-lists
-         (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
-                 (f-args form)))
-        (max-length (- 80 start-pos 2))
-        (args (dwim-join value-string-lists max-length
-                         :start "(" :end ")" :join-after ",")))
-    (etypecase (f-function form)
-      (js-lambda
-       (dwim-join (list (append (dwim-join (list (js-to-strings (f-function form) (+ start-pos 2)))
-                                           max-length
-                                           :start "(" :end ")" :separator "")
-                                args))
-                  max-length
-                  :separator ""))
-      ((or js-variable js-aref js-slot-value)
-       (dwim-join (list (js-to-strings (f-function form) (+ start-pos 2))
-                        args)
-                  max-length
-                  :separator ""))
-      (function-call
-       ;; 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 (js-to-strings (f-function form) (+ start-pos 2)))
-                                           max-length :separator "")
-                                args))
-                  max-length :separator "")))))
-
-(defmethod js-to-strings ((form method-call) start-pos)
-  (let ((object (js-to-strings (m-object form) (+ 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 (member (m-object form) (list 'js-lambda 'number-literal 'js-object 'op-form)
-                 :test #'typep)  
-      (push "(" object)
-      (nconc object (list ")")))
-    (let* ((fname (dwim-join (list object
-                                   (list (js-translate-symbol (m-method form))))
-                             (- 80 start-pos 2)
-                             :end "("
-                             :separator ""))
-           (butlast (butlast fname))
-           (last (car (last fname)))
-           (method-and-args (dwim-join (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
-                                               (m-args form))
-                                       (- 80 start-pos 2)
-                                       :start last
-                                       :end ")"
-                                       :join-after ","))
-           (ensure-no-newline-before-dot (concatenate 'string
-                                                      (car (last butlast))
-                                                      (first method-and-args))))
-      (nconc (butlast butlast)
-             (list ensure-no-newline-before-dot)
-             (rest method-and-args)))))
-
-;;; optimization that gets rid of nested blocks, which have no meaningful effect
-;;; in javascript
-(defgeneric expanded-subblocks (block)
-  (:method (block)
-    (list block))
-  (:method ((block js-block))
-    (mapcan #'expanded-subblocks (block-statements block))))
-
-(defun consolidate-subblocks (block)
-  (setf (block-statements block) (expanded-subblocks block))
-  block)
-
-
-(defmethod js-to-statement-strings ((body js-block) start-pos)
-  (consolidate-subblocks body)
-  (dwim-join (mapcar #'(lambda (x) (js-to-statement-strings x (+ start-pos 2)))
-                    (block-statements body))
-            (- 80 start-pos 2)
-            :join-after ";"
-            :append-to-last #'special-append-to-last
-            :start (block-indent body) :collect nil
-            :end ";"))
-
-(defmethod js-to-strings ((body js-block) start-pos)
-  (dwim-join (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
-                    (block-statements body))
-            (- 80 start-pos 2)
-            :append-to-last #'special-append-to-last
-            :join-after ","
-            :start (block-indent body)))
-
-
-(defmethod js-to-statement-strings ((body js-sub-block) start-pos)
-  (declare (ignore start-pos))
-  (nconc (list "{") (call-next-method) (list "}")))
-
-;;; function definition
-(defmethod js-to-strings ((lambda js-lambda) start-pos)
-  (let ((fun-header (dwim-join (mapcar #'(lambda (x)
-                                           (list (js-translate-symbol x)))
-                                      (lambda-args lambda))
-                              (- 80 start-pos 2)
-                              :start (function-start-string lambda)
-                              :end ") {" :join-after ","))
-       (fun-body (js-to-statement-strings (lambda-body lambda) (+ start-pos 2))))
-    (nconc fun-header fun-body (list "}"))))
-
-(defgeneric function-start-string (function)
-  (:documentation "Returns the string that starts the function - this varies according to whether
-this is a lambda or a defun"))
-
-(defmethod function-start-string ((lambda js-lambda))
-  "function (")
-
-(defmethod js-to-statement-strings ((lambda js-lambda) start-pos)
-  (js-to-strings lambda start-pos))
-
-(defmethod function-start-string ((defun js-defun))
-  (format nil "function ~A(" (js-translate-symbol (defun-name defun))))
-
-;;; object creation
-(defmethod js-to-strings ((object js-object) start-pos)
-  (let ((value-string-lists
-        (mapcar #'(lambda (slot)
-                    (let* ((slot-name (first slot))
-                           (slot-string-name
-                           (if (typep slot-name 'script-quote)
-                               (if (symbolp (value slot-name))
-                                   (format nil "~A" (js-translate-symbol (value slot-name)))
-                                   (format nil "~A" (first (js-to-strings slot-name 0))))
-                               (car (js-to-strings slot-name 0)))))
-                      (dwim-join (list (js-to-strings (second slot) (+ start-pos 4)))
-                                 (- 80 start-pos 2)
-                                 :start (concatenate 'string slot-string-name  " : ")
-                                 :white-space "    ")))
-                (o-slots object)))
-       (max-length (- 80 start-pos 2)))
-    (dwim-join value-string-lists max-length
-              :start "{ "
-              :end " }"
-              :join-after ", "
-              :white-space "  "
-              :collect nil)))
-
-(defmethod js-to-strings ((sv js-slot-value) start-pos)
-  (append-to-last (if (typep (sv-object sv) 'js-variable)
-                      (js-to-strings (sv-object sv) start-pos)
-                      (list (format nil "~A" (js-to-strings (sv-object sv) start-pos))))
-                  (if (typep (sv-slot sv) 'script-quote)
-                      (if (symbolp (value (sv-slot sv)))
-                          (format nil ".~A" (js-translate-symbol (value (sv-slot sv))))
-                          (format nil ".~A" (first (js-to-strings (sv-slot sv) 0))))
-                      (format nil "[~A]" (first (js-to-strings (sv-slot sv) 0))))))
-
-;;; cond
-(defmethod js-to-statement-strings ((cond js-cond) start-pos)
-  (loop :for body :on (cond-bodies cond)
-       :for first = (eq body (cond-bodies cond))
-       :for last = (not (cdr body))
-       :for test :in (cond-tests cond)
-       :append (if (and last (not first) (string= (value test) "true"))
-                   '("else {")
-                   (dwim-join (list (js-to-strings test 0)) (- 80 start-pos 2)
-                              :start (if first "if (" "else if (") :end ") {"))
-       :append (js-to-statement-strings (car body) (+ start-pos 2))
-       :collect "}"))
-
-(defmethod js-to-statement-strings ((if js-if) start-pos)
-  (let ((if-strings (dwim-join (list (js-to-strings (if-test if) 0))
-                              (- 80 start-pos 2)
-                              :start "if ("
-                              :end ") {"))
-       (then-strings (js-to-statement-strings (if-then if) (+ start-pos 2)))
-       (else-strings (when (if-else if)
-                       (js-to-statement-strings (if-else if)
-                                                (+ start-pos 2)))))
-    (nconc if-strings then-strings (if else-strings
-                                      (nconc (list "} else {") else-strings (list "}"))
-                                      (list "}")))))
-
-(defmethod js-to-strings ((if js-if) start-pos)
-  (assert (typep (if-then if) 'expression))
-  (when (if-else if)
-    (assert (typep (if-else if) 'expression)))
-  (dwim-join (list (append-to-last (js-to-strings (if-test if) start-pos) " ?")
-                  (let* ((new-then (make-instance 'js-block
-                                                  :statements (block-statements (if-then if))
-                                                  :indent ""))
-                         (res (js-to-strings new-then start-pos)))
-                    (if (>= (expression-precedence (if-then if))
-                            (expression-precedence if))
-                            (klammer res)
-                            res))
-                  (list ":")
-                  (if (if-else if)
-                      (let* ((new-else (make-instance 'js-block
-                                                      :statements (block-statements (if-else if))
-                                                      :indent ""))
-                             (res (js-to-strings new-else start-pos)))
-                        (if (>= (expression-precedence (if-else if))
-                                (expression-precedence if))
-                            (klammer res)
-                            res))
-                      (list "undefined")))
-            (- 80 start-pos 2)
-            :white-space "  "))
-
-;;; setf
-(defmethod js-to-strings ((setf js-setf) start-pos)
-  (dwim-join (cons (js-to-strings (setf-lhs setf) start-pos)
-                  (mapcar #'(lambda (x) (js-to-strings x start-pos)) (setf-rhsides setf)))
-            (- 80 start-pos 2)
-            :join-after " ="))
-
-;;; defvar
-(defmethod js-to-statement-strings ((defvar js-defvar) start-pos)
-  (dwim-join (nconc (mapcar #'(lambda (x) (list (js-translate-symbol x))) (var-names defvar))
-                   (when (var-value defvar)
-                     (list (js-to-strings (var-value defvar) start-pos))))
-            (- 80 start-pos 2)
-            :join-after " ="
-            :start "var " :end ";"))
-
-;;; iteration
-(defmethod js-to-statement-strings ((for js-for) start-pos)
-  (let* ((init (dwim-join (mapcar #'(lambda (x)
-                                     (dwim-join (list (list (js-translate-symbol (first (var-names x))))
-                                                      (js-to-strings (var-value x)
-                                                                     (+ start-pos 2)))
-                                                (- 80 start-pos 2)
-                                                :join-after " ="))
-                                 (for-vars for))
-                         (- 80 start-pos 2)
-                         :start "var " :join-after ","))
-        (check (js-to-strings (for-check for) (+ start-pos 2)))
-        (steps (dwim-join (mapcar #'(lambda (x var)
-                                      (dwim-join
-                                       (list (list (js-translate-symbol (first (var-names var))))
-                                             (js-to-strings x (- start-pos 2)))
-                                       (- 80 start-pos 2)
-                                       :join-after " ="))
-                                  (for-steps for)
-                                  (for-vars for))
-                          (- 80 start-pos 2)
-                          :join-after ","))
-        (header (dwim-join (list init check steps)
-                           (- 80 start-pos 2)
-                           :start "for (" :end ") {"
-                           :join-after ";"))
-        (body (js-to-statement-strings (for-body for) (+ start-pos 2))))
-    (nconc header body (list "}"))))
-
-
-(defmethod js-to-statement-strings ((fe for-each) start-pos)
-  (let ((header (dwim-join (list (list (js-translate-symbol (fe-name fe)))
-                                (list "in")
-                                (js-to-strings (fe-value fe) (+ start-pos 2)))
-                          (- 80 start-pos 2)
-                          :start "for (var "
-                          :end ") {"))
-       (body (js-to-statement-strings (fe-body fe) (+ start-pos 2))))
-    (nconc header body (list "}"))))
-
-(defmethod js-to-statement-strings ((while js-while) start-pos)
-  (let ((header (dwim-join (list (js-to-strings (while-check while) (+ start-pos 2)))
-                          (- 80 start-pos 2)
-                          :start "while ("
-                          :end ") {"))
-       (body (js-to-statement-strings (while-body while) (+ start-pos 2))))
-    (nconc header body (list "}"))))
-
-;;; with
-(defmethod js-to-statement-strings ((with js-with) start-pos)
-  (nconc (dwim-join (list (js-to-strings (with-obj with) (+ start-pos 2)))
-                   (- 80 start-pos 2)
-                   :start "with (" :end ") {")
-        (js-to-statement-strings (with-body with) (+ start-pos 2))
-        (list "}")))
-
-;;; switch
-(defmethod js-to-statement-strings ((case js-switch) start-pos)
-  (let ((body   (mapcan #'(lambda (clause)
-                    (let ((val (car clause))
-                          (body (second clause)))
-                      (dwim-join (list (if (eql val 'default)
-                                           (list "")
-                                           (js-to-strings val (+ start-pos 2)))
-                                       (js-to-statement-strings body (+ start-pos 2)))
-                                 (- 80 start-pos 2)
-                                 :start (if (eql val 'default) "  default" "  case ")
-                                 :white-space "   "
-                                 :join-after ":"))) (case-clauses case))))
-    (nconc (dwim-join (list (js-to-strings (case-value case) (+ start-pos 2)))
-                   (- 80 start-pos 2)
-                   :start "switch (" :end ") {")
-          body
-          (list "}"))))
-
-;;; try-catch
-(defmethod js-to-statement-strings ((try js-try) start-pos)
-  (let* ((catch (try-catch try))
-        (finally (try-finally try))
-        (catch-list (when catch
-                      (nconc
-                       (dwim-join (list (list (js-translate-symbol (first catch))))
-                                  (- 80 start-pos 2)
-                                  :start "} catch ("
-                                  :end ") {")
-                       (js-to-statement-strings (second catch) (+ start-pos 2)))))
-        (finally-list (when finally
-                        (nconc (list "} finally {")
-                               (js-to-statement-strings finally (+ start-pos 2))))))
-    (nconc (list "try {")
-          (js-to-statement-strings (try-body try) (+ start-pos 2))
-          catch-list
-          finally-list
-          (list "}"))))
-
-;;; regex
-(defun first-slash-p (string)
-  (and (> (length string) 0)
-       (eq (char string 0) '#\/)))
-
-(defmethod js-to-strings ((regex regex) start-pos)
-   (declare (ignore start-pos))
-   (let ((slash (if (first-slash-p (value regex)) nil "/")))
-     (list (format nil (concatenate 'string slash "~A" slash) (value regex)))))
-
-;;; conditional compilation
-(defmethod js-to-statement-strings ((cc cc-if) start-pos)
-  (nconc (list (format nil "/*@if ~A" (cc-if-test cc)))
-        (mapcan #'(lambda (x) (js-to-strings x start-pos)) (cc-if-body cc))
-        (list "@end @*/")))
-
-
-;;; TODO instanceof
-(defmethod js-to-strings ((instanceof js-instanceof) start-pos)
-  (dwim-join
-   (list (js-to-strings (value instanceof) (+ start-pos 2))
-         (list "instanceof")
-         (js-to-strings (slot-value instanceof 'type) (+ start-pos 2)))
-   (- 80 start-pos 2)
-   :start "("
-   :end ")"
-   :white-space
-   "  "))
-
-;;; single operations
-(defmacro define-translate-js-single-op (name &optional (superclass 'expression))
-    (let ((script-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
-      `(defmethod ,(if (eql superclass 'expression)
-                       'js-to-strings
-                      'js-to-statement-strings)
-       ((,name ,script-name) start-pos)
-       (dwim-join (list (js-to-strings (value ,name) (+ start-pos 2)))
-        (- 80 start-pos 2)
-        :start ,(concatenate 'string (string-downcase (symbol-name name)) " ")
-        :white-space "  "))))
-
-(define-translate-js-single-op return statement)
-(define-translate-js-single-op throw statement)
-(define-translate-js-single-op delete)
-(define-translate-js-single-op void)
-(define-translate-js-single-op typeof)
-(define-translate-js-single-op new)
\ No newline at end of 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 (eql 'js-variable (car obj))
+                      (ps-print obj %start-pos%)
+                      (list (format nil "~A" (ps-print obj %start-pos%))))
+                  (if (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 "  "))
index 33bb241..8f9920b 100644 (file)
       (map nil #'handle-form forms))
     (cons '+ (optimize-string-list (nreverse res)))))
 
-(define-script-special-form html (&rest forms)
-  (compile-script-form (process-html-forms forms)))
+(define-ps-special-form html (expecting &rest forms)
+  (compile-parenscript-form (process-html-forms forms)))
 
-(defun process-css-forms(proplist)
+(defun process-css-forms (proplist)
   (optimize-string-list (butlast
                          (loop for propval on proplist by #'cddr appending
-                              (list (string-downcase ( symbol-name (first propval)))
+                              (list (string-downcase (symbol-name (first propval)))
                                     ":"
                                     (second propval)
                                     ";")))))
 
 
-(define-script-special-form css-inline (&rest forms)
-  (compile-script-form (cons '+ (process-css-forms forms))))
+(define-ps-special-form css-inline (expecting &rest forms)
+  (compile-parenscript-form (cons '+ (process-css-forms forms))))
index 4065933..5644972 100644 (file)
@@ -3,13 +3,13 @@
 ;;; Handy utilities for doing common tasks found in many web browser
 ;;; JavaScript implementations
 
-(defscriptmacro do-set-timeout ((timeout) &body body)
+(defpsmacro do-set-timeout ((timeout) &body body)
   `(set-timeout (lambda () ,@body) ,timeout))
 
 ;;; Arithmetic
 
 (defmacro def-js-maths (&rest mathdefs)
-  `(progn ,@(mapcar (lambda (def) (cons 'defscriptmacro def)) mathdefs)))
+  `(progn ,@(mapcar (lambda (def) (cons 'defpsmacro def)) mathdefs)))
 
 (def-js-maths
     (min (&rest nums) `(*math.min ,@nums))
@@ -34,7 +34,7 @@
 
 ;;; Exception handling
 
-(defscriptmacro ignore-errors (&body body)
+(defpsmacro ignore-errors (&body body)
   `(try (progn ,@body) (:catch (e))))
 
 ;;; Misc
dissimilarity index 96%
index e3aeb7d..5562743 100644 (file)
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;; ParenScript package system
-
-(in-package :parenscript)
-
-(defclass script-package ()
-  ;; configuration slots
-  ((name          :accessor script-package-name          :initform nil :initarg :name :type string
-                 :documentation "Canonical name of the package (a String).")
-   (nicknames     :accessor script-package-nicknames     :initform nil :initarg :nicknames
-                 :documentation "List of nicknames for the package (as strings).")
-   (prefix        :accessor script-package-prefix        :initform nil :initarg :prefix :type string
-                  :documentation "The prefix string that will be used when translating the symbols in the current package to Javascript.")
-   (lisp-package  :accessor script-package-lisp-package  :initform nil :initarg :lisp-package)
-   (secondary-lisp-packages :accessor script-package-secondary-lisp-packages :initform nil
-                           :initarg :secondary-lisp-packages)
-   (exports       :accessor script-package-exports       :initarg :exports
-                 :initform nil;(make-hash-table :test #'equal)
-                 :documentation "List of exported identifiers.")
-   (used-packages :accessor script-package-used-packages :initform nil :initarg :used-packages
-                 :documentation "")
-   (documentation :accessor script-package-documentation :initform nil :initarg :documentation)
-   (compilation-env :accessor script-package-comp-env    :initform nil :initarg :comp-env)
-   (locked?       :accessor script-package-locked? :initform nil :initarg :locked?
-                 :documentation "t if redefinition of top-level symbols is disallowed.")
-   ;; internal use slots
-   (symbol-table :accessor script-package-symbol-table :initform nil :initarg :symbol-table
-                :documentation "Contains symbols when there is no lisp package for this package.")
-   )
-  (:documentation "A Parenscript package is a lisp object that holds information
-about a set of code.
-
-"))
-
-(defmethod print-object ((sp script-package) stream)
-  (format stream "#<SCRIPT-PACKAGE ~s>" (script-package-name sp)))
-
-(defclass compilation-environment ()
-  ((script-packages :accessor comp-env-script-packages :initform nil :initarg :packages
-                   :documentation "List of packages defined in this environment.")
-   (current-package :accessor comp-env-current-package :initform nil :initarg :current-package
-                   :documentation "Current in-package.")
-   (lisp-to-script-package-table
-    :accessor comp-env-lisp-to-script-package-table :initform (make-hash-table)
-    :documentation   "Maps a lisp package to a script package.")
-   (compiling-toplevel-p 
-    :accessor comp-env-compiling-toplevel-p :initform nil :initarg :processing-toplevel-p
-    :documentation "T if the environment is currently processing toplevel forms.")
-   (symbol-table :accessor symbol-to-script-package :initform (make-hash-table)
-                :documentation "Maps symbols to script packages.  Used for only the
-symbols in script packages that do not have a primary lisp package."))
-  (:documentation ""))
-
-(defgeneric symbol-script-package (symbol)
-  (:documentation "Gets the Parenscript package associated with a Lisp/Parenscript symbol."))
-
-(defvar *warn-ps-package* nil
-  "If true, warns when ParenScript attempts to compile symbols that
-don't have an associated ParenScript package.")
-
-(defun lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
-  "Gets a script package corresponding to the given Lisp package."
-  (gethash lisp-package (comp-env-lisp-to-script-package-table comp-env)))
-
-(defsetf lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
-    (script-package)
-  "Sets the script package corresponding to the given Lisp package."
-  `(setf (gethash ,lisp-package (comp-env-lisp-to-script-package-table ,comp-env))
-    ,script-package))
-
-(defmethod symbol-script-package ((symbol symbol))
-  (if (symbol-package symbol)
-      (or (lisp-to-script-package (symbol-package symbol) *compilation-environment*)
-          (progn (when *warn-ps-package*
-                   (warn 'simple-style-warning
-                         :format-control "~s is a symbol with lisp package ~s, which has no corresponding ParenScript package.
-Defaulting to :parenscript-user."
-                         :format-arguments (list symbol (symbol-package symbol))))
-                 (find-script-package "PARENSCRIPT-USER" (make-basic-compilation-environment))))
-      (find-script-package "UNINTERNED" *compilation-environment*)))
-
-(defun find-script-package (name &optional (comp-env *compilation-environment*))
-  "Find the script package with the name NAME in the given compilation environment."
-  (typecase name
-    ((or symbol string)
-     (find-if #'(lambda (script-package)
-                 (find (string name)
-                       (cons (script-package-name script-package)
-                             (script-package-nicknames script-package))
-                       :test #'equal))
-             (comp-env-script-packages comp-env)))
-    (script-package name)
-    (t (error "~A has unknown type" name))))
-
-(defun script-intern (name script-package-name)
-  "Returns a Parenscript symbol with the string value STRING interned for the
-given SCRIPT-PACKAGE."
-  (declare (type string name))
-  (let ((script-package (find-script-package script-package-name)))
-    (flet ((find-exported-symbol (name script-package)
-             (let ((res
-                    (find name (script-package-exports script-package)
-                          :key #'(lambda (exported-symbol) (string exported-symbol))
-                          :test #'equal)))
-               res)))
-      (let ((res
-             (or
-              (some #'(lambda (used-package)
-                        (find-exported-symbol name used-package))
-                    (script-package-used-packages script-package))
-              (if (script-package-lisp-package script-package)
-                  (intern name (script-package-lisp-package script-package))
-                  (progn
-                    (let ((sym (intern-without-package name)))
-                      (setf (gethash name (script-package-symbol-table script-package))
-                            sym)
-                      (setf (gethash sym (symbol-to-script-package (script-package-comp-env script-package)))
-                            script-package)
-                      sym))))))
-        (declare (type symbol res))
-        res))))
-
-(defun find-script-symbol (name script-package)
-  "Finds the symbol with name NAME in the script package SCRIPT-PACKAGE.  NAME is a
-string and SCRIPT-PACKAGE is a package designator.  If NAME does not specify a symbol of
-script-package, returns nil.  Otherwise returns 2 values:
-1.  the symbol
-2.  :external if the symbol is external.  :internal if the symbol is internal. NIL if
-the symbol is not interned in the package."
-  (setf script-package (find-script-package script-package))
-  (let (symbol interned-p)
-
-    (if (script-package-lisp-package script-package)
-       (multiple-value-bind (lisp-symbol lisp-status)
-           (find-symbol name (script-package-lisp-package script-package))
-         (setf symbol lisp-symbol)
-         (setf interned-p (and lisp-status t)))
-       (multiple-value-bind (sym sym-found-p)
-           (gethash name (script-package-symbol-table script-package))
-         (setf symbol sym)
-         (setf interned-p sym-found-p)))
-    (let ((exported? (member symbol (script-package-exports script-package))))
-      (values symbol
-             (if exported? :external (if interned-p :internal nil))))))
-
-(defun script-export (symbols
-                     &optional (script-package (comp-env-current-package *compilation-environment*)))
-  "Exports the given symbols in the given script package."
-  (when (not (listp symbols)) (setf symbols (list symbols)))
-  (setf script-package (find-script-package script-package))
-  (let ((symbols-not-in-package
-        (remove-if #'(lambda (symbol)
-                       (declare (type symbol symbol))
-                       (eql symbol (find-script-symbol (string symbol) script-package)))
-                   symbols)))
-    (when symbols-not-in-package
-      (error "Invalid exports.  The following symbols are not interned in the package ~A:~%~A"
-            (script-package-name script-package) symbols-not-in-package)))
-  (mapc #'(lambda (symbol)
-           (pushnew symbol (script-package-exports script-package)))
-       symbols)
-  t)
-
-(defun use-script-package (packages-to-use
-                          &optional (into-package (comp-env-current-package *compilation-environment*)))
-  "use-script-package causes INTO-PACKAGE to inherit all the external symbols of packages-to-use. 
-The inherited symbols become accessible as internal symbols of package."
-  (when (not (listp packages-to-use)) (setf packages-to-use (list packages-to-use)))
-  (setf packages-to-use (mapcar #'find-script-package packages-to-use))
-  (setf into-package (find-script-package into-package))
-
-  (let ((all-used-symbols (apply #'append (mapcar #'script-package-exports packages-to-use))))
-    (mapc #'(lambda (used-symbol)
-             (let ((symbol-same-name (find-script-symbol (string used-symbol) into-package)))
-               (when (not (or (null symbol-same-name)
-                              (eql symbol-same-name used-symbol)))
-                 (error "Import of symbol ~A into package ~A conflicts with interned symbol ~A"
-                        used-symbol (script-package-name into-package) symbol-same-name))))
-         all-used-symbols))
-  (setf (script-package-used-packages into-package)
-       (append (script-package-used-packages into-package) packages-to-use)))
-
-(defun intern-without-package (name)
-  (macrolet ((with-temp-package ((var) &body body)
-              (let ((result-var (gensym)))
-              `(let* ((,var (make-package ',(gensym)))
-                      (,result-var (progn ,@body)))
-                (delete-package ,var)
-                ,result-var))))
-    (with-temp-package (package)
-      (let ((sym (intern name package)))
-       (unintern sym package)
-       sym))))
-
-(defun create-script-package (comp-env
-                             &key name nicknames prefix secondary-lisp-packages used-packages
-                             lisp-package exports documentation)
-  "Creates a script package in the given compilation environment"
-  (when (and lisp-package (not (find-package lisp-package)))
-    (error "Package ~A does not exists" lisp-package))
-  (let* ((script-package
-         (make-instance 'script-package
-                        :name (string name)
-                        :comp-env comp-env
-                         :prefix prefix
-                        :nicknames (mapcar #'string nicknames)
-                        :lisp-package (when lisp-package (find-package lisp-package))
-                        :secondary-lisp-packages (mapcar #'find-package secondary-lisp-packages)
-                        :documentation documentation)))
-    (use-script-package used-packages script-package)
-    (labels ((package-intern (string-like)
-              (script-intern (string string-like) script-package)))
-      (script-export (mapcar #'package-intern exports) script-package))
-    (push script-package (comp-env-script-packages comp-env))
-    script-package))
-
-(defmethod initialize-instance :after ((package script-package) &key)
-  (assert (script-package-comp-env package))
-  (when (null (script-package-lisp-package package))
-    (setf (script-package-symbol-table package)
-         (make-hash-table :test #'equal)))
-  (let ((lisp-packages
-        (remove-if #'null
-                   (cons (script-package-lisp-package package)
-                         (script-package-secondary-lisp-packages package)))))
-    (dolist (lisp-package lisp-packages)
-      (when (lisp-to-script-package lisp-package (script-package-comp-env package))
-       (error "Lisp package already has corresponding script package: ~A" (package-name lisp-package)))
-      (setf (lisp-to-script-package lisp-package (script-package-comp-env package))
-           package))))
-
-(defgeneric comp-env-find-package (comp-env package-designator)
-  (:documentation "Finds the script package named by PACKAGE-DESIGNATOR in the current 
-compilation environment. PACKAGE-DESIGNATOR is a string or symbol.")
-  (:method ((comp-env compilation-environment) (name string))
-    (find name (comp-env-script-packages comp-env)
-         :key #'script-package-name :test #'equal))
-  (:method ((comp-env compilation-environment) (package-designator symbol))
-    (comp-env-find-package comp-env (string package-designator))))
-
-;; TODO loop through all defined macros and add them to the script package's
-;; macro environment
-;        (labels ((name-member (name)
-;                   (eql (script-package-lisp-package script-package) (symbol-package name)))
-;                 (import-macro (name function)
-;                   (when (name-member name)
-;                     (setf (gethash name (script-package-macro-table script-package))
-;                           function)))
-;                 (import-special-form (name function)
-;                   (when (name-member name)
-;                     (setf (gethash name (script-package-special-form-table script-package))
-;                           function))))
-;        (maphash #'import-special-form *toplevel-special-forms*)
-;        (maphash #'import-special-form *toplevel-special-forms*)
-
-;(defgeneric comp-env-select-package (comp-env script-package)
-;  (:documentation "")
-;  (:method ((comp-env compilation-environment) (package script-package))
-;    (setf (comp-env-current-package 
-
-
-(defvar *enable-package-system* nil)
-
-;;; Interface for reading in identifier
-
-(defgeneric lisp-symbol-to-ps-identifier (symbol context &optional compilation-environment) 
-  (:documentation "Context is one of :special-form, :macro or nil."))
-
-(defmethod lisp-symbol-to-ps-identifier ((symbol symbol) (context (eql :special-form)) &optional comp-ev)
-  (declare (ignore context comp-ev))
-  (symbol-name symbol))
-
-(defmethod lisp-symbol-to-ps-identifier ((symbol symbol) (context (eql :macro)) &optional comp-ev)
-  (declare (ignore context comp-ev))
-  symbol)
-
-(defmethod lisp-symbol-to-ps-identifier :around ((symbol symbol) context &optional comp-ev)
-  (declare (ignore context comp-ev))
-  (if *enable-package-system*
-      (call-next-method)
-      (symbol-name symbol)))
-
-;;; Symbol obfuscation (this should really go somewhere else)
-(defvar *obfuscate-standard-identifiers* nil)
-
-(defparameter *obfuscation-table* (make-hash-table))
-
-(defun obfuscated-symbol (symbol)
-  (or (gethash symbol *obfuscation-table*)
-      (setf (gethash symbol *obfuscation-table*) (string (gensym)))))
-
-;;; Interface for printing identifiers
-
-(defvar *package-prefix-style* :prefix
-  "Determines how package symbols are serialized to JavaScript identifiers.  NIL for
-no prefixes.  :prefix to prefix variables with something like packagename_identifier.")
-
-(defgeneric js-translate-symbol-contextually (symbol package env)
-  (:documentation "Translates a symbol to a string in the given environment & package
-and for the given symbol."))
-
-(defmethod js-translate-symbol-contextually ((symbol symbol) (package ps::script-package) (env ps::compilation-environment))
-  (cond ((member (ps::script-package-lisp-package package) (mapcar #'find-package '(:keyword :parenscript.global)))
-         (symbol-to-js symbol))
-        (*obfuscate-standard-identifiers* (obfuscated-symbol symbol))
-        (t (if (and *enable-package-system* (eql *package-prefix-style* :prefix))
-               (format nil "~A~A"
-                       (or (ps::script-package-prefix package) (concatenate 'string (ps::script-package-name package) "_"))
-                       (symbol-to-js symbol))
-               (symbol-to-js symbol)))))
-
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; ParenScript namespace system
+
+(in-package :parenscript)
+
+(defun lisp-symbol-to-ps-identifier (symbol context)
+  (case context
+    (:special-form (symbol-name symbol))
+    (:macro symbol)
+    (otherwise (symbol-name symbol))))
+
+;;; Symbol obfuscation
+(defvar *obfuscate-identifiers* nil)
+
+(defparameter *obfuscation-table* (make-hash-table))
+
+(defun obfuscated-symbol (symbol)
+  (or (gethash symbol *obfuscation-table*)
+      (setf (gethash symbol *obfuscation-table*) (string (gensym)))))
+
+;;; Interface for printing identifiers
+
+(defvar *package-prefix-style* :prefix
+  "Determines how package symbols are serialized to JavaScript identifiers.  NIL for
+no prefixes.  :prefix to prefix variables with something like packagename_identifier.")
+
+(defvar *package-prefix-table* (make-hash-table))
+
+(defmacro ps-package-prefix (package)
+  "Place for storing a string to be prefixed to any symbols in the
+designated package when translating ParenScript code."
+  `(gethash (find-package ,package) *package-prefix-table*))
+
+(defun js-translate-symbol (symbol)
+  (cond (*obfuscate-identifiers* (obfuscated-symbol symbol))
+        ((and (eql *package-prefix-style* :prefix) (ps-package-prefix (symbol-package symbol)))
+         (format nil "~A~A" (ps-package-prefix (symbol-package symbol)) (symbol-to-js symbol)))
+        (t (symbol-to-js symbol))))
+
index a3002c0..ca9d6f0 100644 (file)
     "Symbols exported from both the Parenscript and Javascript packages 
 that are also valid as Parenscript symbols for the corresponding script packages."))
 
-(defpackage parenscript.javascript
-  (:use :common-lisp)
-  (:nicknames javascript ps-js)
-  #.(cons :export *shared-symbols-ps-js*)
-  (:export
-   ;; function definition
-   #:%js-defun
-   #:%js-lambda
-   #:%js-slot-value
-   ;; translate
-   #:js-to-strings
-   #:js-to-statement-strings
-   )
-  (:documentation "The package used to define Javascript special forms.  Most of Parenscript
-is defined as macros on top of Javascript special forms"))
-
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (defparameter *parenscript-lang-exports*
     (append 
      *shared-symbols-ps-js*
      '(
-       ;; package system
-       #:defpackage
-       #:in-package
-
        ;; function definition
        #:defun
        #:lambda
@@ -146,8 +126,6 @@ is defined as macros on top of Javascript special forms"))
        #:with-slots
        #:slot-value
 
-       ;; eval-when
-       #:eval-when
        ;; macros
        #:macrolet
        #:symbol-macrolet
@@ -190,44 +168,26 @@ part of the Parenscript language.  These should be exported within
 both the Lisp package and the script package for Parenscript."))
 
 (defpackage :parenscript
-  (:use :common-lisp :parenscript.javascript)
+  (:use :common-lisp)
   (:nicknames :js :ps)
   #.(cons :export *shared-symbols-ps-js*)
   #.(cons :export *parenscript-lang-exports*)
   (:export
    ;; compiler
    #:compile-script
-   #:script
-   #:script*
    #:ps
    #:ps*
-   #:js
-   #:js*
-   #:with-new-compilation-environment ; tentative
-   #:with-compilation-environment     ; tentative
-   #:*compilation-environment*
-   
-   ;; package system
-   #:find-script-package
-   #:script-intern
-   #:script-export
-   #:find-script-symbol
-   #:comp-env-current-package
-   #:symbol-script-package
-   #:script-package-name
    
    ;; for parenscript macro definition within lisp
-   #:defscriptmacro
-   #:defpsmacro ; should we use one or the other of these?
+   #:defpsmacro
    #:defmacro/ps
    #:defmacro+ps
    #:import-macros-from-lisp
    
    ;; gensym
-   #:with-unique-ps-names
-   #:gen-script-name
-   #:gen-script-name-string
-   #:gen-ps-name
+   #:ps-gensym
+   #:with-ps-gensyms
+   #:*ps-gensym-counter*
 
    ;; deprecated interface
    #:gen-js-name
@@ -240,46 +200,7 @@ both the Lisp package and the script package for Parenscript."))
    #:js-file
    #:js-script
    #:js-to-statement-strings
+   #:js
+   #:js*
    ))
 
-(in-package :parenscript)
-(import 
- '(defscriptclass
-   define-script-special-form
-   defscriptmacro
-   symbol-to-js
-   script-quote
-   *package-prefix-style*
-   *script-macro-env*
-   compile-to-statement
-   compile-to-block
-   compile-to-symbol
-   compile-to-expression
-   symbol-script-package
-   script-package-name
-   list-join
-   list-to-string
-   append-to-last
-   prepend-to-first
-   string-join
-   val-to-string
-   string-split
-   script-special-form-p
-   make-macro-env-dictionary
-   script-equal
-   compile-script-form
-   ) 
- :parenscript.javascript)
-
-(defpackage parenscript.global
-  (:nicknames "GLOBAL")
-  (:documentation "Symbols interned in the global package are serialized in Javascript
-as non-prefixed identifiers."))
-
-(defpackage parenscript.user
-  (:use :parenscript)
-  (:nicknames ps-user parenscript-user)
-  (:documentation "The default package a user is inside of when compiling code."))
-
-(defpackage parenscript.non-prefixed (:nicknames ps.non-prefixed))
-(defpackage parenscript.ps-gensyms)
index 1af7853..cbfc22b 100644 (file)
@@ -1,34 +1,19 @@
 (in-package :parenscript)
 
 ;;;; The mechanisms for defining macros & parsing Parenscript.
-(defgeneric compiler-in-situation-p (comp-env situation)
-  (:documentation "Returns true when the compiler is considered 'in' the situation
-given by SITUATION, which is one of :compile-toplevel :execute.")
-  (:method ((comp-env compilation-environment) situation)
-    (cond
-      ((eql situation :compile-toplevel) (processing-toplevel-p comp-env))
-      ((eql situation :execute) (not (processing-toplevel-p comp-env)))
-      (t nil))))
-
-(defgeneric processing-toplevel-p (comp-env)
-  (:documentation "T if we are compiling TOPLEVEL forms, as in 
-http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm")
-  (:method ((comp-env compilation-environment))
-    (comp-env-compiling-toplevel-p comp-env)
-    ))
-
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (defvar *toplevel-special-forms* (make-hash-table :test #'equal)
     "A hash-table containing functions that implement Parenscript special forms,
 indexed by name (as symbols)")
-  (defun undefine-script-special-form (name)
+  (defun undefine-ps-special-form (name)
     "Undefines the special form with the given name (name is a symbol)."
     (remhash (lisp-symbol-to-ps-identifier name :special-form) *toplevel-special-forms*)))
 
-(defmacro define-script-special-form (name lambda-list &rest body)
-  "Define a special form NAME. Arguments are destructured according to
-LAMBDA-LIST. The resulting Parenscript language types are appended to the
-ongoing javascript compilation."
+(defmacro define-ps-special-form (name lambda-list &rest body)
+  "Define a special form NAME. The first argument given to the special
+form is a keyword indicating whether the form is expected to produce
+an :expression or a :statement. The resulting Parenscript language
+types are appended to the ongoing javascript compilation."
   (let ((arglist (gensym "ps-arglist-")))
     `(setf (gethash (lisp-symbol-to-ps-identifier ',name :special-form) *toplevel-special-forms*)
       (lambda (&rest ,arglist)
@@ -36,21 +21,25 @@ ongoing javascript compilation."
             ,arglist
           ,@body)))))
 
-(defun get-script-special-form (name)
+(defun get-ps-special-form (name)
   "Returns the special form function corresponding to the given name."
   (gethash (lisp-symbol-to-ps-identifier name :special-form) *toplevel-special-forms*))
 
-;;; sexp form predicates
-(defun script-special-form-p (form)
-  "Returns T if FORM is a special form and NIL otherwise."
+;;; ParenScript form predicates
+(defun ps-special-form-p (form)
   (and (consp form)
        (symbolp (car form))
-       (get-script-special-form (car form))))
+       (get-ps-special-form (car form))))
+
+(defun op-form-p (form)
+  (and (listp form)
+       (not (ps-special-form-p form))
+       (not (null (op-precedence (first form))))))
 
 (defun funcall-form-p (form)
   (and (listp form)
-       (not (ps-js::op-form-p form))
-       (not (script-special-form-p form))))
+       (not (op-form-p form))
+       (not (ps-special-form-p form))))
 
 (defun method-call-p (form)
   (and (funcall-form-p form)
@@ -119,7 +108,7 @@ function and the parent macro environment of the macro."
   (defun define-script-macro% (name args body &key symbol-macro-p)
     (let ((lambda-list (gensym "ps-lambda-list-"))
          (body (if (and (cdr body) (stringp (first body))) (rest body) body))) ;; drop docstring
-      (undefine-script-special-form name)
+      (undefine-ps-special-form name)
       (setf (get-macro-spec name *script-macro-toplevel*)
            (cons symbol-macro-p (compile nil `(lambda (&rest ,lambda-list)
                                                (destructuring-bind ,args
@@ -127,7 +116,7 @@ function and the parent macro environment of the macro."
                                                  ,@body)))))
       nil)))
 
-(defmacro defscriptmacro (name args &body body)
+(defmacro defpsmacro (name args &body body)
   "Define a ParenScript macro, and store it in the toplevel ParenScript
 macro environment."
   `(define-script-macro% ',name ',args ',body :symbol-macro-p nil))
@@ -159,237 +148,134 @@ the same macro in both Lisp and ParenScript, but the 'macroexpand' of
 that macro in Lisp makes the Lisp macro unsuitable to be imported into
 the ParenScript macro environment."
   `(progn (defmacro ,name ,args ,@body)
-          (defscriptmacro ,name ,args ,@body)))
-
-(defmacro defpsmacro (&rest args)
-  `(defscriptmacro ,@args))
-
-(defun expand-script-form (expr)
-  "Expands a Parenscript form until it reaches a special form.  Returns 2 values:
-1. the expanded form.
-2. whether the form was expanded."
-  (if (consp expr)
-      (let ((op (car expr))
-            (args (cdr expr)))
+          (defpsmacro ,name ,args ,@body)))
+
+(defun ps-macroexpand (form)
+  "Recursively macroexpands ParenScript macros and symbol-macros in
+the given ParenScript form. Returns two values: the expanded form, and
+whether any expansion was performed on the form or not."
+  (if (consp form)
+      (let ((op (car form))
+            (args (cdr form)))
         (cond ((equal op 'quote)
               (values 
-               (if (equalp '(nil) args) nil expr) ;; leave quotes alone, unless it's a quoted nil
+               (if (equalp '(nil) args) nil form) ;; leave quotes alone, unless it's a quoted nil
                nil))
               ((script-macro-p op) ;; recursively expand parenscript macros in parent env.
               (multiple-value-bind (expansion-function macro-env)
                   (lookup-macro-expansion-function op)
                 (values
-                 (expand-script-form (let ((*script-macro-env* macro-env))
+                 (ps-macroexpand (let ((*script-macro-env* macro-env))
                                        (apply expansion-function args)))
                  t)))
-              ((script-special-form-p expr)
-               (values expr nil))
-              (t (values expr nil))))
-      (cond ((script-symbol-macro-p expr)
+              (t (values form nil))))
+      (cond ((script-symbol-macro-p form)
             ;; recursively expand symbol macros in parent env.
             (multiple-value-bind (expansion-function macro-env)
-                (lookup-macro-expansion-function expr)
+                (lookup-macro-expansion-function form)
               (values
-               (expand-script-form (let ((*script-macro-env* macro-env))
+               (ps-macroexpand (let ((*script-macro-env* macro-env))
                                      (funcall expansion-function)))
                t)))
            ;; leave anything else alone
-            (t (values expr nil)))))
-
-(defun process-eval-when-args (args)
-  "(eval-when form-language? (situation*) form*) - returns 3 values: 
-form-language, a list of situations, and a list of body forms"
-  (let* ((rest args)
-        (form-language
-         (when (not (listp (first rest)))
-           (setf rest (rest args))
-           (first args)))
-        (situations (first rest))
-        (body (rest rest)))
-    (when (and (find :compile-toplevel situations) (find :execute situations))
-      (error "Cannot use EVAL-WHEN to execute COMPILE-TOPLEVEL and EXECUTE code simultaneously."))
-    (when (null form-language)
-      (setf form-language
-           (cond
-             ((find :compile-toplevel situations) :lisp)
-             ((find :execute situations)          :parenscript))))
-    (values form-language situations body)))
-  
-;;;; compiler interface ;;;;
-(defgeneric compile-parenscript-form (compilation-environment form)
-  (:documentation "Compiles FORM, which is a ParenScript form.
-If toplevel-p is NIL, the result is a compilation object (the AST root).
-Subsequently TRANSLATE-AST can be called to convert the result to Javascript.
-
-If the compiler is in the COMPILE-TOPLEVEL stage, then the result will
-be a Parenscript form (after it has been processed according to semantics
-like those of Lisp's COMPILE-FILE). See
-http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm"))
-
-(defgeneric compile-toplevel-parenscript-form (comp-env form)
-  (:documentation "Compiles a parenscript form in the given compilation environment
-when the environment is in the :compile-toplevel situation.  Returns a form to be
-compiled in place of the original form upon exiting the :compile-toplevel situation."))
-
-(defmethod compile-toplevel-parenscript-form ((comp-env compilation-environment) form)
-  (cond
-    ((not (listp form)) form)
-    ;; process each clause of a progn as a toplevel form
-    ((eql 'progn (car form))
-     `(progn
-       ,@(mapcar #'(lambda (subform)
-                    (compile-parenscript-form comp-env subform))
-                (rest form))))
-    ;; TODO process macrolets, symbol-macrolets, and file inclusions
-
-    ;; process eval-when.  evaluates in :COMPILE-TOPLEVEL situation and returns
-    ;; the resultant form.  for :EXECUTE situation it returns 
-    ((eql 'eval-when (car form))
-     (multiple-value-bind (body-language situations body)
-        (process-eval-when-args (rest form))
-       (cond
-        ((find :compile-toplevel situations)
-         (when (eql body-language :lisp)
-           (let ((other-situations (remove :compile-toplevel situations)))
-             (multiple-value-bind (function warnings-p failure-p)
-                 (compile nil `(lambda () ,@body))
-               (declare (ignore warnings-p) (ignore failure-p))
-               (compile-parenscript-form 
-                comp-env
-                `(progn
-                  ,(funcall function)
-                  ,@(when other-situations
-                          (list `(eval-when ,other-situations ,@body)))))))))
-        ;; if :compile-toplevel is not in the situation list, return the form
-        (t form))))
-    (t form)))
-
-
-(defmethod compile-parenscript-form :around ((comp-env compilation-environment) form)
-  (multiple-value-bind (expanded-form expanded-p)
-      (expand-script-form form)
-    (cond
-      (expanded-p
-       (compile-parenscript-form comp-env expanded-form))
-      ((comp-env-compiling-toplevel-p comp-env)
-       (compile-toplevel-parenscript-form comp-env form))
-      (t (call-next-method)))))
-
-(defmethod compile-parenscript-form ((comp-env compilation-environment) (form string))
-  (make-instance 'ps-js::string-literal :value form))
-
-(defmethod compile-parenscript-form ((comp-env compilation-environment) (form character))
-  (compile-parenscript-form comp-env (string form)))
-
-(defmethod compile-parenscript-form ((comp-env compilation-environment) (form number))
-  (make-instance 'ps-js::number-literal :value form))
-
-(defmethod compile-parenscript-form ((comp-env compilation-environment) (form symbol))
+            (t (values form nil)))))
+
+;;;; compiler interface
+(defgeneric compile-parenscript-form (form &key expecting)
+  (:documentation "Compiles a ParenScript form to the intermediate
+ParenScript representation. :expecting determines whether the form is
+compiled to an :expression (the default), a :statement, or a
+:symbol."))
+
+(defmethod compile-parenscript-form :around (form &key expecting)
+  (if (eql expecting :symbol)
+      (compile-to-symbol form)
+      (multiple-value-bind (expanded-form expanded-p)
+          (ps-macroexpand form)
+        (if expanded-p
+            (compile-parenscript-form expanded-form)
+            (call-next-method)))))
+
+(defun compile-to-symbol (form)
+  "Compiles the given Parenscript form and guarantees that the
+resultant symbol has an associated script-package. Raises an error if
+the form cannot be compiled to a symbol."
+  (let ((exp (compile-parenscript-form form)))
+    (when (or (eql (first exp) 'js-variable)
+              (eql (first exp) 'script-quote))
+      (setf exp (second exp)))
+    (assert (symbolp exp) ()
+            "~a is expected to be a symbol, but compiles to ~a (the ParenScript output for ~a alone is \"~a\"). This could be due to ~a being a special form." form exp form (ps* form) form)
+    exp))
+
+(defmethod compile-parenscript-form (form &key expecting)
+  (declare (ignore expecting))
+  (error "The object ~S cannot be compiled by ParenScript." form))
+
+(defmethod compile-parenscript-form ((form number) &key expecting)
+  (declare (ignore expecting))
+  form)
+
+(defmethod compile-parenscript-form ((form string) &key expecting)
+  (declare (ignore expecting))
+  form)
+
+(defmethod compile-parenscript-form ((form character) &key expecting)
+  (declare (ignore expecting))
+  (compile-parenscript-form (string form)))
+
+(defmethod compile-parenscript-form ((symbol symbol) &key expecting)
+  (declare (ignore expecting))
   ;; is this the correct behavior?
-  (let ((c-macro (get-script-special-form form)))
-    (cond
-      (c-macro (funcall c-macro))
-      ;; the following emulates the lisp behavior that a keyword is bound to itself
-      ;; see http://clhs.lisp.se/Body/t_kwd.htm
-      ((keywordp form) (compile-parenscript-form comp-env `(quote ,form)))
-      (t (make-instance 'ps-js::js-variable :value form)))))
-
-(defun compile-function-argument-forms (forms)
+  (let ((special-symbol (get-ps-special-form symbol)))
+    (cond (special-symbol (funcall special-symbol :symbol))
+          ;; the following emulates the lisp behavior that a keyword is bound to itself
+          ;; see http://clhs.lisp.se/Body/t_kwd.htm
+          ((keywordp symbol) (compile-parenscript-form `(quote ,symbol)))
+          (t (list 'js-variable symbol)))))
+
+(defun compile-function-argument-forms (arg-forms)
   "Compiles a bunch of Parenscript forms from a funcall form to an effective set of
 Javascript arguments.  The only extra processing this does is makes :keyword arguments
 into a single options argument via CREATE."
   (flet ((keyword-arg (arg)
           "If the given compiled expression is supposed to be a keyword argument, returns
 the keyword for it."
-          (when (typep arg 'script-quote) (ps-js::value arg))))
-  (let ((expressions (mapcar #'compile-to-expression forms)))
-
-    (do ((effective-expressions nil)
-        (expressions-subl expressions))
-
-       ((not expressions-subl)
-        (nreverse effective-expressions))
-      
-      (let ((arg-expr (first expressions-subl)))
-       (if (keyword-arg arg-expr)
-           (progn
-             (when (oddp (length expressions-subl))
-               (error "Odd number of keyword arguments: ~A." forms))
-             (push
-              (make-instance 'ps-js::js-object
-                             :slots
-                             (loop for (name val) on expressions-subl by #'cddr
-                                   collect (list name val)))
-              effective-expressions)
-             (setf expressions-subl nil))
-           (progn
-             (push arg-expr effective-expressions)
-             (setf expressions-subl (rest expressions-subl)))))))))
-
-(defmethod compile-parenscript-form ((comp-env compilation-environment) (form cons))
+          (when (and (listp arg) (eql (first arg) 'script-quote)) (second arg))))
+    (let ((compiled-args (mapcar #'compile-parenscript-form arg-forms)))
+      (do ((effective-expressions nil)
+           (expressions-subl compiled-args))
+          ((not expressions-subl) (reverse effective-expressions))
+        (let ((arg-expr (first expressions-subl)))
+          (if (keyword-arg arg-expr)
+              (progn (when (oddp (length expressions-subl))
+                       (error "Odd number of keyword arguments: ~A." arg-forms))
+                     (push (list 'js-object (loop for (name val) on expressions-subl by #'cddr
+                                                  collect (list name val)))
+                           effective-expressions)
+                     (setf expressions-subl nil))
+              (progn (push arg-expr effective-expressions)
+                     (setf expressions-subl (rest expressions-subl)))))))))
+
+(defmethod compile-parenscript-form ((form cons) &key (expecting :statement))
   (let* ((name (car form))
-        (args (cdr form))
-        (script-form (when (symbolp name) (get-script-special-form name))))
-    (cond
-      ((eql name 'quote)       (make-instance 'script-quote :value (first args)))
-      (script-form             (apply script-form args))
-      ((ps-js::op-form-p form)
-       (make-instance 'ps-js::op-form
-                     :operator (ps-js::script-convert-op-name (compile-to-symbol (first form)))
-                     :args (mapcar #'compile-to-expression (rest form))))
-      ((method-call-p form)
-       (make-instance 'ps-js::method-call
-                     :method (compile-to-symbol name)
-                     :object (compile-to-expression (first args))
-                     :args (compile-function-argument-forms (rest args))))
-      ((funcall-form-p form)
-       (make-instance 'ps-js::function-call
-                     :function (compile-to-expression name)
-                     :args (compile-function-argument-forms args)))
-      (t (error "Unknown form ~S" form)))))
-
-(defun compile-script-form (form &key (comp-env *compilation-environment*))
-  "Compiles a Parenscript form to an AST node."
-  (compile-parenscript-form comp-env form))
-
-(defun compile-to-expression (form)
-  "Compiles the given Parenscript form and guarantees the result is an expression."
-  (let ((res (compile-script-form form)))
-    (assert (typep res 'ps-js::expression) ()
-            "Error: ~s was expected to compile to a ParenScript expression, but instead compiled to ~s, which has type ~s"
-            form res (type-of res))
-    res))
+        (args (cdr form)))
+    (cond ((eql name 'quote)
+           (assert (= 1 (length args)) () "Wrong number of arguments to quote: ~s" args)
+           (list 'script-quote (first args)))
+          ((ps-special-form-p form) (apply (get-ps-special-form name) (cons expecting args)))
+          ((op-form-p form)
+           (list 'operator
+                 (script-convert-op-name (compile-parenscript-form (first form) :expecting :symbol))
+                 (mapcar (lambda (form) (compile-parenscript-form form :expecting :expression)) (rest form))))
+          ((method-call-p form)
+           (list 'js-method-call
+                 (compile-parenscript-form name :expecting :symbol)
+                 (compile-parenscript-form (first args) :expecting :expression)
+                 (compile-function-argument-forms (rest args))))
+          ((funcall-form-p form)
+           (list 'js-funcall
+                 (compile-parenscript-form name :expecting :expression)
+                 (compile-function-argument-forms args)))
+          (t (error "Cannot compile ~S to a ParenScript form." form)))))
 
-(defun compile-to-symbol (form)
-  "Compiles the given Parenscript form and guarantees a symbolic result.  This
-also guarantees that the symbol has an associated script-package."
-  (let ((res (compile-script-form form)))
-    (when (typep res 'ps-js::js-variable)
-      (setf res (ps-js::value res)))
-    (when (typep res 'ps-js::script-quote)
-      (setf res (ps-js::value res)))
-    (assert (symbolp res) ()
-            "~a is expected to be a symbol, but compiles to ~a (the ParenScript output for ~a alone is \"~a\"). This could be due to ~a being a special form." form res form (ps::ps* form) form)
-    (unless (symbol-script-package res)
-      (when *warn-ps-package*
-        (warn 'simple-style-warning
-              :format-control "The symbol ~A::~A has no associated script package."
-              :format-arguments (list (if (symbol-package res) (package-name (symbol-package res)) "ANONYMOUS-PACKAGE")
-                                      res))))
-    res))
-
-(defun compile-to-statement (form)
-  "Compiles the given Parenscript form and guarantees the result is a statement."
-  (let ((res (compile-script-form form)))
-    (assert (typep res 'ps-js::statement))
-    res))
-
-(defun compile-to-block (form &key (indent ""))
-  "Compiles the given Parenscript form and guarantees the result is of type SCRIPT-BODY"
-  (let ((res (compile-to-statement form)))
-    (if (typep res 'ps-js::js-block)
-       (progn (setf (ps-js::block-indent res) indent)
-              res)
-       (make-instance 'ps-js::js-block
-                      :indent indent
-                      :statements (list res)))))
\ No newline at end of file
index f5ceb23..6608e89 100644 (file)
@@ -3,83 +3,45 @@
 ;;;; The macrology of the Parenscript language.  Special forms and macros.
 
 ;;; parenscript gensyms
-(defvar *gen-script-name-counter* 0)
+(defvar *ps-gensym-counter* 0)
 
-(defun gen-script-name-string (&key (prefix "_js_"))
-  "Generates a unique valid javascript identifier ()"
-  (concatenate 'string
-               prefix (princ-to-string (incf *gen-script-name-counter*))))
+(defun ps-gensym (&optional (prefix "_js"))
+  (make-symbol (format nil "~A-~A" prefix (incf *ps-gensym-counter*))))
 
-(defun gen-script-name (&key (prefix "_ps_"))
-  "Generate a new javascript identifier."
-  (intern (gen-script-name-string :prefix prefix)
-          (find-package :parenscript.ps-gensyms)))
-
-(defmacro gen-ps-name (&rest args)
-  `(gen-script-name ,@args))
-
-(defmacro with-unique-ps-names (symbols &body body)
-  "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
+(defmacro with-ps-gensyms (symbols &body body)
+  "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers.
 
 Each element of SYMBOLS is either a symbol or a list of (symbol
-prefix)."
+gensym-prefix-string)."
   `(let* ,(mapcar (lambda (symbol)
                     (destructuring-bind (symbol &optional prefix)
                         (if (consp symbol)
                             symbol
                             (list symbol))
                       (if prefix
-                          `(,symbol (gen-script-name :prefix ,prefix))
-                          `(,symbol (gen-script-name)))))
+                          `(,symbol (ps-gensym ,prefix))
+                          `(,symbol (ps-gensym)))))
                   symbols)
      ,@body))
 
-(defvar *var-counter* 0)
-
-(defun script-gensym (&optional (name "js"))
-  (intern (format nil "tmp-~A-~A" name (incf *var-counter*)) #.*package*))
-
-(defscriptmacro defaultf (place value)
+(defpsmacro defaultf (place value)
   `(setf ,place (or (and (=== undefined ,place) ,value)
                 ,place)))
 
 ;;; array literals
-(defscriptmacro list (&rest values)
+(defpsmacro list (&rest values)
   `(array ,@values))
 
-(defscriptmacro make-array (&rest inits)
+(defpsmacro make-array (&rest inits)
   `(new (*array ,@inits)))
 
-;;; eval-when
-(define-script-special-form eval-when (&rest args)
-  "(eval-when form-language? (situation*) form*)
-
-The given forms are evaluated only during the given SITUATION in the specified 
-FORM-LANGUAGE (either :lisp or :parenscript, def, defaulting to :lisp during
--toplevel and :parenscript during :execute). The accepted SITUATIONS are :execute,
-:scan-toplevel. :scan-toplevel is the phase of compilation when function definitions 
-and the like are being added to the compilation environment. :execute is the phase when
-the code is being evaluated by a Javascript engine."
-  (multiple-value-bind (body-language situations subforms)
-      (process-eval-when-args args)
-    (cond
-      ((and (compiler-in-situation-p *compilation-environment* :compile-toplevel)
-           (find :compile-toplevel situations))
-       (error "Should never be processing eval-when :COMPILE-TOPLEVEL forms from here."))
-
-      ((and (compiler-in-situation-p *compilation-environment*  :execute)
-           (find :execute situations))
-       (when (eql body-language :parenscript)
-        (let ((form `(progn ,@subforms)))
-          (compile-to-statement form)))))))
-
 ;;; slot access
-(defscriptmacro slot-value (obj &rest slots)
+(defpsmacro slot-value (obj &rest slots)
   (if (null (rest slots))
       `(%js-slot-value ,obj ,(first slots))
       `(slot-value (slot-value ,obj ,(first slots)) ,@(rest slots))))
 
-(defscriptmacro with-slots (slots object &rest body)
+(defpsmacro with-slots (slots object &rest body)
   (flet ((slot-var (slot) (if (listp slot) (first slot) slot))
         (slot-symbol (slot) (if (listp slot) (second slot) slot)))
     `(symbol-macrolet ,(mapcar #'(lambda (slot)
@@ -87,46 +49,7 @@ the code is being evaluated by a Javascript engine."
                               slots)
       ,@body)))
 
-;;; script packages
-(defscriptmacro defpackage (name &rest options)
-  "Defines a Parenscript package."
-  (labels ((opt-name (opt) (if (listp opt) (car opt) opt)))
-  (let ((nicknames nil) (lisp-package nil) (secondary-lisp-packages nil)
-       (exports nil) (used-packages nil) (documentation nil))
-    (dolist (opt options)
-      (case (opt-name opt)
-       (:lisp-package (setf lisp-package (second opt)))
-       (:nicknames (setf nicknames (rest opt)))
-       (:secondary-lisp-packages secondary-lisp-packages t)
-       (:export (setf exports (rest opt)))
-       (:use (setf used-packages (rest opt)))
-       (:documentation (setf documentation (second opt)))
-       (t (error "Unknown option in DEFPACKAGE: ~A" (opt-name opt)))))
-    (create-script-package
-     *compilation-environment*
-     :name name
-     :nicknames nicknames
-     :secondary-lisp-packages secondary-lisp-packages
-     :used-packages used-packages
-     :lisp-package lisp-package
-     :exports exports
-     :documentation documentation)))
-  `(progn))
-
-(defscriptmacro in-package (package-designator)
-  "Changes the current script package in the parenscript compilation environment.  This mostly
-affects the reader and how it interns non-prefixed symbols"
-  (let ((script-package
-        (find-script-package package-designator *compilation-environment*)))
-    (when (null script-package)
-      (error "~A does not designate any script package.  Available script package: ~A"
-            package-designator
-            (mapcar #'script-package-name (comp-env-script-packages *compilation-environment*))))
-    (setf (comp-env-current-package *compilation-environment*)
-         script-package)
-    `(progn)))
-
-(defscriptmacro case (value &rest clauses)
+(defpsmacro case (value &rest clauses)
   (labels ((make-clause (val body more)
              (cond ((listp val)
                     (append (mapcar #'list (butlast val))
@@ -135,46 +58,35 @@ affects the reader and how it interns non-prefixed symbols"
                     (make-clause 'default body more))
                    (more `((,val ,@body break)))
                    (t `((,val ,@body))))))
-    `(switch ,value ,@(mapcon #'(lambda (x)
-                                  (make-clause (car (first x))
-                                               (cdr (first x))
-                                               (rest x)))
+    `(switch ,value ,@(mapcon (lambda (clause)
+                                (make-clause (car (first clause))
+                                             (cdr (first clause))
+                                             (rest clause)))
                               clauses))))
 
-;;; let
-(define-script-special-form let (decls &rest body)
-  (let ((defvars (mapcar #'(lambda (decl)
-                            (if (atom decl)
-                                 (make-instance 'ps-js::js-defvar
-                                       :names (list (compile-to-symbol decl))
-                                       :value nil)
-                                 (let ((name (first decl))
-                                       (value (second decl)))
-                                   (make-instance 'ps-js::js-defvar
-                                                  :names (list (compile-to-symbol name))
-                                                  :value (compile-to-expression value)))))
-                        decls)))
-    (make-instance 'ps-js::js-sub-block
-                  :indent "  "
-                  :statements (nconc defvars
-                                (mapcar #'compile-to-statement body)))))
+(define-ps-special-form let (expecting bindings &rest body)
+  (let ((defvars (mapcar (lambda (binding) (if (atom binding)
+                                               `(defvar ,binding)
+                                               `(defvar ,@binding)))
+                         bindings)))
+    (compile-parenscript-form `(progn ,@defvars ,@body))))
 
 ;;; iteration
-(defscriptmacro dotimes (iter &rest body)
+(defpsmacro dotimes (iter &rest body)
   (let ((var (first iter))
         (times (second iter)))
   `(do ((,var 0 (1+ ,var)))
        ((>= ,var ,times))
      ,@body)))
 
-(defscriptmacro dolist (i-array &rest body)
+(defpsmacro dolist (i-array &rest body)
   (let ((var (first i-array))
        (array (second i-array))
-       (arrvar (script-gensym "arr"))
-       (idx (script-gensym "i")))
+       (arrvar (ps-gensym "tmp-arr"))
+       (idx (ps-gensym "tmp-i")))
     `(let ((,arrvar ,array))
       (do ((,idx 0 (1+ ,idx)))
-         ((>= ,idx (slot-value ,arrvar 'global::length)))
+         ((>= ,idx (slot-value ,arrvar 'length)))
        (let ((,var (aref ,arrvar ,idx)))
          ,@body)))))
 
@@ -184,7 +96,7 @@ affects the reader and how it interns non-prefixed symbols"
           (*script-macro-env* (cons ,var *script-macro-env*)))
     ,@body))
 
-(define-script-special-form macrolet (macros &body body)
+(define-ps-special-form macrolet (expecting macros &body body)
   (with-temp-macro-environment (macro-env-dict)
     (dolist (macro macros)
       (destructuring-bind (name arglist &body body)
@@ -195,33 +107,33 @@ affects the reader and how it interns non-prefixed symbols"
                                          (destructuring-bind ,arglist
                                              ,args
                                            ,@body))))))))
-    (compile-script-form `(progn ,@body))))
+    (compile-parenscript-form `(progn ,@body))))
 
-(define-script-special-form symbol-macrolet (symbol-macros &body body)
+(define-ps-special-form symbol-macrolet (expecting symbol-macros &body body)
   (with-temp-macro-environment (macro-env-dict)
     (dolist (macro symbol-macros)
       (destructuring-bind (name &body expansion)
           macro
        (setf (get-macro-spec name macro-env-dict)
              (cons t (compile nil `(lambda () ,@expansion))))))
-    (compile-script-form `(progn ,@body))))
+    (compile-parenscript-form `(progn ,@body))))
 
-(define-script-special-form defmacro (name args &body body)
+(define-ps-special-form defmacro (expecting name args &body body)
   (define-script-macro% name args body :symbol-macro-p nil)
-  (compile-script-form '(progn)))
+  nil)
 
-(define-script-special-form define-symbol-macro (name &body body)
+(define-ps-special-form define-symbol-macro (expecting name &body body)
   (define-script-macro% name () body :symbol-macro-p t)
-  (compile-script-form '(progn)))
+  nil)
 
-(defscriptmacro lisp (&body forms)
+(defpsmacro lisp (&body forms)
   "Evaluates the given forms in Common Lisp at ParenScript
 macro-expansion time. The value of the last form is treated as a
 ParenScript expression and is inserted into the generated Javascript
 \(use nil for no-op)."
   (eval (cons 'progn forms)))
 
-(defscriptmacro rebind (variables &body body)
+(defpsmacro rebind (variables &body body)
   "Creates a new js lexical environment and copies the given
 variable(s) there. Executes the body in the new environment. This
 has the same effect as a new (let () ...) form in lisp but works on
@@ -348,7 +260,7 @@ the given lambda-list and body."
                  effective-body)))
        (values effective-args effective-body)))))
 
-(ps:defscriptmacro defun (name lambda-list &body body)
+(defpsmacro defun (name lambda-list &body body)
   "An extended defun macro that allows cool things like keyword arguments.
 lambda-list::=
  (var* 
@@ -362,7 +274,7 @@ lambda-list::=
                      "(defun ~s ~s ...) needs to have a symbol or (setf symbol) for a name." name lambda-list)
              `(defun-setf ,name ,lambda-list ,@body))))
 
-(ps:defscriptmacro defun-normal (name lambda-list &body body)
+(defpsmacro defun-normal (name lambda-list &body body)
   (multiple-value-bind (effective-args effective-body)
       (parse-extended-function lambda-list body name)
     `(%js-defun ,name ,effective-args
@@ -370,7 +282,7 @@ lambda-list::=
 
 (defvar *defun-setf-name-prefix* "__setf_")
 
-(ps:defscriptmacro defun-setf (setf-name lambda-list &body body)
+(defpsmacro defun-setf (setf-name lambda-list &body body)
   (let ((mangled-function-name (intern (concatenate 'string *defun-setf-name-prefix* (symbol-name (second setf-name)))
                                        (symbol-package (second setf-name))))
         (function-args (cdr (ordered-set-difference lambda-list lambda-list-keywords))))
@@ -378,7 +290,7 @@ lambda-list::=
               `(,',mangled-function-name ,store-var ,@(list ,@function-args)))
             (defun ,mangled-function-name ,lambda-list ,@body))))
 
-(ps:defscriptmacro lambda (lambda-list &body body)
+(defpsmacro lambda (lambda-list &body body)
   "An extended defun macro that allows cool things like keyword arguments.
 lambda-list::=
  (var* 
@@ -391,15 +303,15 @@ lambda-list::=
     `(%js-lambda ,effective-args
       ,@effective-body)))
 
-(ps:defscriptmacro defsetf-long (access-fn lambda-list (store-var) form)
+(defpsmacro defsetf-long (access-fn lambda-list (store-var) form)
   (setf (get-macro-spec access-fn *script-setf-expanders*)
         (compile nil
                  (let ((var-bindings (ordered-set-difference lambda-list lambda-list-keywords)))
                    `(lambda (access-fn-args store-form)
                      (destructuring-bind ,lambda-list
                                access-fn-args
-                       (let* ((,store-var (ps:gen-ps-name))
-                              (gensymed-names (loop repeat ,(length var-bindings) collecting (ps:gen-ps-name)))
+                       (let* ((,store-var (ps-gensym))
+                              (gensymed-names (loop repeat ,(length var-bindings) collecting (ps-gensym)))
                               (gensymed-arg-bindings (mapcar #'list gensymed-names (list ,@var-bindings))))
                          (destructuring-bind ,var-bindings
                              gensymed-names
@@ -408,7 +320,7 @@ lambda-list::=
                              ,,form))))))))
   nil)
 
-(ps:defscriptmacro defsetf-short (access-fn update-fn &optional docstring)
+(defpsmacro defsetf-short (access-fn update-fn &optional docstring)
   (declare (ignore docstring))
   (setf (get-macro-spec access-fn *script-setf-expanders*)
         (lambda (access-fn-args store-form)
@@ -422,10 +334,10 @@ lambda-list::=
   (flet ((process-setf-clause (place value-form)
            (if (and (listp place) (get-macro-spec (car place) *script-setf-expanders*))
                (funcall (get-macro-spec (car place) *script-setf-expanders*) (cdr place) value-form)
-               (let ((exp-place (expand-script-form place)))
+               (let ((exp-place (ps-macroexpand place)))
                  (if (and (listp exp-place) (get-macro-spec (car exp-place) *script-setf-expanders*))
                      (funcall (get-macro-spec (car exp-place) *script-setf-expanders*) (cdr exp-place) value-form)
-                     `(parenscript.javascript::setf1% ,exp-place ,value-form))))))
+                     `(setf1% ,exp-place ,value-form))))))
     (assert (evenp (length args)) ()
             "~s does not have an even number of arguments." (cons 'setf args))
     `(progn ,@(loop for (place value) on args by #'cddr collect (process-setf-clause place value)))))
diff --git a/src/ps-source-model.lisp b/src/ps-source-model.lisp
deleted file mode 100644 (file)
index f7c4f1c..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-(in-package :parenscript)
-
-;;; quote
-(defscriptclass script-quote (ps-js::expression)
-  ())
-
-;;; Compilation environment stuff
-
-(defvar *compilation-environment* nil
-  "The active compilation environment."
-;; Right now all code assumes that *compilation-environment* is accurately bound to the
-;; current compilation environment--even some functions that take the compilation environment
-;; as arguments.
-  )
-
-;; environmental considerations
-(defgeneric setup-compilation-environment (comp-env)
-  (:documentation "Sets up a basic compilation environment prepared for a language user.
-This should do things like define packages and set the current package.
-
-Returns the compilation-environment."))
-
-(defun make-basic-compilation-environment ()
-  "Creates a compilation environment object from scratch.  Fills it in with the default
-script packages (parenscript, global, and parenscript-user)."
-  (let ((*compilation-environment* (make-instance 'compilation-environment)))
-    (setup-compilation-environment *compilation-environment*)))
\ No newline at end of file