bulk of package system, reader, and other refactoring
authorRed Daly <reddaly@gmail.com>
Tue, 24 Jul 2007 04:08:56 +0000 (04:08 +0000)
committerRed Daly <reddaly@gmail.com>
Tue, 24 Jul 2007 04:08:56 +0000 (04:08 +0000)
18 files changed:
docs/internal/architecture.txt [new file with mode: 0644]
docs/internal/notes-and-links.txt [new file with mode: 0644]
parenscript.asd
src/compilation-interface.lisp
src/deprecated-interface.lisp
src/js-macrology.lisp [new file with mode: 0644]
src/js-source-model.lisp [moved from src/source-model.lisp with 73% similarity]
src/js-translation.lisp
src/js-ugly-translation.lisp [new file with mode: 0644]
src/macrology.lisp [deleted file]
src/package.lisp
src/parser.lisp
src/ps-macrology.lisp [new file with mode: 0644]
src/ps-source-model.lisp [new file with mode: 0644]
src/reader.lisp
t/package-system-tests.lisp [new file with mode: 0644]
t/ps-tests.lisp
t/test.lisp

diff --git a/docs/internal/architecture.txt b/docs/internal/architecture.txt
new file mode 100644 (file)
index 0000000..f0c514d
--- /dev/null
@@ -0,0 +1,35 @@
+This document is about the design and architecture of the Parenscript compiler.
+
+Compilation Pipeline:
+
+   user       --> [parenscript text]
+-- reader     --> [parenscript sexp forms]
+-- parser     --> [special forms]
+-- optimizer  --> [(javascript) special forms]
+-- translater --> [javascript text]
+-> user
+
+==reader==
+Parenscript can use either the Lisp reader or the Parenscript reader to read objects from source
+text.  Generally, Parenscript embedded in Lisp will use the Lisp reader, and Parenscript in 
+Parenscript files will use the Parenscript reader.  There are only a few differences between
+the readers:
+#  The Parenscript reader will not obey defined read macros in the Lisp reader
+#  The Parenscript reader understands Parenscript package names as package prefixes and
+does NOT understand Lisp package names as package prefixes.
+#  The Lisp reader does not understand Parenscript package names but does understand
+Lisp package names as symbol prefixes.
+
+==parser==
+Once the source text has been transformed into SEXPs, the parser transforms the SEXPs into
+primitive special-form objects.  This is the stage during which macroexpansion takes place
+and an AST is generated for the program.
+
+==optimizer==
+The compiler then performs optional optimizations on the AST produced by the parser.  The
+result is an AST that produces faster/better code.
+
+==transformer==
+Given an AST, the transformer produces Javascript source text.
+
+*************************************************************************************
diff --git a/docs/internal/notes-and-links.txt b/docs/internal/notes-and-links.txt
new file mode 100644 (file)
index 0000000..0f3506e
--- /dev/null
@@ -0,0 +1,55 @@
+Programming languages were at one point a flourishing research area.  In some areanas, they still are.
+It is difficult to get a programming language "right."  Parenscript is fortunate in that it is 
+modelled after a language to which many people have contributed a great deal over many decades.
+
+Most of the links and notes below refer to ideas about Common Lisp.  Some are about Javascript,
+the target programming language and environment for Parenscript.
+
+
+Strange Javascript Semantics
+===========================================================================
+var x = 1;
+function foo() {
+ if (x == ONE_OR_NOT_ONE) { var x = 3; }
+ return "bleck: " + x;
+};
+foo();
+
+This code returns "bleck: undefined" when ONE_OR_NOT_ONE is 1 or 2.  See
+http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf
+page 37 for an explanation of the semantics of variable scope.
+
+
+var x = 1; function foo(a) { return foo; var foo=5; } foo(3);
+
+=> 'undefined'
+
+var x = 1; function foo(a) { return foo; } foo(3);
+
+=> thee function foo
+
+
+
+
+Reference material
+===========================================================================
+Macro Expansion in Lisp:
+   Common Lisp the Language, 2nd Edition.
+   http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node99.html
+
+File Compilation:
+   http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm
+
+Special forms in Lisp:
+   CLHS
+   http://www.lisp.org/HyperSpec/Body/sec_3-1-2-1-2-1.html
+
+block      let*                  return-from      
+catch      load-time-value       setq             
+eval-when  locally               symbol-macrolet  
+flet       macrolet              tagbody          
+function   multiple-value-call   the              
+go         multiple-value-prog1  throw            
+if         progn                 unwind-protect   
+labels     progv                                  
+let        quote     
index fa56f14..cf03a86 100644 (file)
                (:module :src
                 :components ((:file "package")
                              (:file "utils" :depends-on ("package"))
-                            (:file "source-model" :depends-on ("package" "utils"))
-                            (:file "parser" :depends-on ("source-model"))
+                            (:file "js-source-model" :depends-on ("package" "utils"))
+                            (:file "ps-source-model" :depends-on ("js-source-model"))
+                            (:file "parser" :depends-on ("js-source-model" "ps-source-model"))
                             (:file "deprecated-interface" :depends-on ("parser"))
-                            (:file "macrology" :depends-on ("deprecated-interface"))
-                            (:file "js-translation" :depends-on ("macrology"))
-                            (:file "compilation-interface" :depends-on ("package" "js-translation"))
+                            (:file "js-macrology" :depends-on ("deprecated-interface"))
+                            (:file "ps-macrology" :depends-on ("js-macrology"))
+                            (:file "js-translation" :depends-on ("ps-macrology"))
+;                           (:file "js-ugly-translation" :depends-on ("js-translation"))
+                            (:file "reader" :depends-on ("parser"))
+                            (:file "compilation-interface" :depends-on ("package" "reader" "js-translation")); "js-ugly-translation"))
                             ;; standard library
                              (:module :lib
                                       :components ((:static-file "functional.lisp")
index fa9ba99..4cb04b0 100644 (file)
@@ -17,14 +17,16 @@ to a Javascript string.  Outputs to the stream OUTPUT-STREAM in the language giv
 by OUTPUT-SPEC, pretty printing if PRETTY-PRINT is non-null.
 
 OUTPUT-SPEC must be :javascript at the moment."
-  (declare (ignore pretty-print) (ignore comp-env))
+  (declare (ignore comp-env))
   (when (not (eql :javascript output-spec))
     (error "Unsupported output-spec for translation: ~A" output-spec))
   (when (eql :javascript output-spec)
-    (write-string (string-join
-                  (js-to-statement-strings compiled-expr 0)
-                  (string #\Newline))
-                 output-stream)))
+;    (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 compile-script (script-form
                       &key
@@ -73,9 +75,6 @@ potentially other languages)."
 to the given output stream."
   (setf (comp-env-compiling-toplevel-p comp-env) t)
   (error "NOT IMPLEMENTED."))
-       
-       
-
 
 ;(defun compile-script-file (script-src-file
 ;                          &key
index 74a4183..096d181 100644 (file)
   "Define a ParenScript macro, and store it in the toplevel ParenScript macro environment.
 
 DEPRECATED"
-  `(defscriptmacro ,name ,args ,@body))
\ No newline at end of file
+  `(defscriptmacro ,name ,args ,@body))
+
+;;; dual lisp/parenscript macro balderdash
+;;; TODO: should probably move elsewhere ;;;
+#+nil
+(progn
+(defmacro defmacro/js (name args &body body)
+  "Define a Lisp macro and import it into the ParenScript macro environment."
+  `(progn (defmacro ,name ,args ,@body)
+         (js:import-macros-from-lisp ',name)))
+
+(defmacro defmacro+js (name args &body body)
+  "Define a Lisp macro and a ParenScript macro in their respective
+macro environments. This function should be used when you want to use
+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)))
+
+(defun import-macros-from-lisp (&rest names)
+  "Import the named Lisp macros into the ParenScript macro environment."
+  (dolist (name names)
+    (let ((name name))
+      (undefine-js-special-form name)
+      (setf (get-macro-spec name *script-macro-toplevel*)
+            (cons nil (lambda (&rest args)
+                        (macroexpand `(,name ,@args))))))))
+
+(defmacro js-file (&rest body)
+  `(html
+    (:princ
+     (js ,@body))))
+
+(defmacro js-script (&rest body)
+  `((:script :type "text/javascript")
+    (:princ (format nil "~%// <![CDATA[~%"))
+    (:princ (js ,@body))
+    (:princ (format nil "~%// ]]>~%"))))
+
+(defmacro js-inline (&rest body)
+  `(js-inline* '(progn ,@body)))
+
+(defmacro js-inline* (&rest body)
+  "Just like JS-INLINE except that BODY is evaluated before being
+converted to javascript."
+  `(concatenate 'string "javascript:"
+    (string-join (js-to-statement-strings (compile-script-form (list 'progn ,@body)) 0) " ")))
+)
\ No newline at end of file
diff --git a/src/js-macrology.lisp b/src/js-macrology.lisp
new file mode 100644 (file)
index 0000000..82d4821
--- /dev/null
@@ -0,0 +1,383 @@
+(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 nil       "null")
+(defscriptliteral false     "false")
+(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)))
+
+;;; progn
+(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 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 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 'string-literal)
+                                                   (typep name-expr 'number-literal)))
+                                       (list name-expr (compile-to-expression val))))))
+
+
+(define-script-special-form slot-value (obj slot)
+  (make-instance 'js-slot-value :object (compile-to-expression obj)
+                  :slot (compile-script-form slot)))
+
+;;; 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 #'js-equal))
+      (let ((args-without (remove lhs (op-args rhs)
+                                 :count 1 :test #'js-equal))
+           (args-without-first (remove lhs (op-args rhs)
+                                       :count 1 :end 1
+                                       :test #'js-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 (js-equal args-without one)
+                   (eql (operator rhs) '+))
+              (make-instance 'one-op :pre-p nil :op "++"
+                             :value lhs))
+             ((and (js-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)
+                           '(+ *))
+                    (js-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))
+                   (js-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 setf (&rest args)
+  (let ((assignments (loop for (lhs rhs) on args by #'cddr
+                          for rexpr = (compile-to-expression rhs)
+                          for lexpr = (compile-to-expression lhs)
+                          collect (make-js-test lexpr rexpr))))
+    (if (= (length assignments) 1)
+       (first assignments)
+       (make-instance 'js-block :indent "" :statements assignments))))
+
+(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 return statement)
+(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)
+
+;;; 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 with-slots (slots object &rest body)
+  `(symbol-macrolet ,(mapcar #'(lambda (slot)
+                                `(,slot '(slot-value ,object ',slot)))
+                            slots)
+    ,@body))
+
+(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))
+
+;;; Math library
+(defscriptmacro floor (expr)
+  `(*Math.floor ,expr))
+
+(defscriptmacro random ()
+  `(*Math.random))
+
+(defscriptmacro evenp (num)
+  `(= (% ,num 2) 0))
+
+(defscriptmacro oddp (num)
+  `(= (% ,num 2) 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 js-inline (&rest body)
+  `(script-inline ,@body))
similarity index 73%
rename from src/source-model.lisp
rename to src/js-source-model.lisp
index f91b34e..77946ff 100644 (file)
@@ -3,9 +3,6 @@
 (defgeneric script-equal (compiled-ast-node1 compiled-ast-node2)
   (:documentation "Determines if the AST nodes are equal."))
 
-(defgeneric expression-precedence (expression)
-  (:documentation "Returns the precedence of an enscript-javascript expression"))
-
 ;;; AST node equality
 (defmethod script-equal ((obj1 list) (obj2 list))
   (and (= (length obj1) (length obj2))
                             (slot-value obj2 slot)))
               ',slot-names)))))
 
-;;; js language types
+(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."))
@@ -38,7 +40,7 @@
 (defscriptclass array-literal (expression)
   ((values :initarg :values :accessor array-values)))
 
-(defscriptclass script-aref (expression)
+(defscriptclass js-aref (expression)
   ((array :initarg :array
          :accessor aref-array)
    (index :initarg :index
   (value))
 
 ;;; variables
-(defscriptclass script-variable (expression)
+(defscriptclass js-variable (expression)
   (value))
 
-;;; quote
-(defscriptclass script-quote (expression)
-  ())
-
 ;;; 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*
    (args :initarg :args :accessor m-args)))
 
 ;;; body forms
-(defscriptclass script-body (expression)
-  ((statements :initarg :statements :accessor b-statements)
-   (indent :initarg :indent :initform "" :accessor b-indent)))
+(defscriptclass js-block (expression)
+  ((statements :initarg :statements :accessor block-statements)
+   (indent :initarg :indent :initform "" :accessor block-indent)))
 
-(defmethod initialize-instance :after ((body script-body) &rest initargs)
+(defmethod initialize-instance :after ((block js-block) &rest initargs)
   (declare (ignore initargs))
-  (let* ((statements (b-statements body))
+  (let* ((statements (block-statements block))
         (last (last statements))
         (last-stmt (car last)))
-    (when (typep last-stmt 'script-body)
-      (setf (b-statements body)
+    (when (typep last-stmt 'js-block)
+      (setf (block-statements block)
            (nconc (butlast statements)
-                  (b-statements last-stmt))))))
+                  (block-statements last-stmt))))))
 
-(defscriptclass script-sub-body (script-body)
+(defscriptclass js-sub-block (js-block)
   (statements indent))
 
 ;;; function definition
-(defscriptclass script-lambda (expression)
+(defscriptclass js-lambda (expression)
   ((args :initarg :args :accessor lambda-args)
    (body :initarg :body :accessor lambda-body)))
 
-(defscriptclass script-defun (script-lambda)
+(defscriptclass js-defun (js-lambda)
   ((name :initarg :name :accessor defun-name)))
 
 ;;; object creation
-(defscriptclass script-object (expression)
+(defscriptclass js-object (expression)
   ((slots :initarg :slots
          :accessor o-slots)))
 
-(defscriptclass script-slot-value (expression)
+(defscriptclass js-slot-value (expression)
   ((object :initarg :object
           :accessor sv-object)
    (slot :initarg :slot
         :accessor sv-slot)))
 
 ;;; cond
-(defscriptclass script-cond (expression)
+(defscriptclass js-cond (expression)
   ((tests :initarg :tests
          :accessor cond-tests)
    (bodies :initarg :bodies
           :accessor cond-bodies)))
 
-(defscriptclass script-if (expression)
+(defscriptclass js-if (expression)
   ((test :initarg :test
         :accessor if-test)
    (then :initarg :then
    (else :initarg :else
         :accessor if-else)))
 
-(defmethod initialize-instance :after ((if script-if) &rest initargs)
+(defmethod initialize-instance :after ((if js-if) &rest initargs)
   (declare (ignore initargs))
   (when (and (if-then if)
-            (typep (if-then if) 'script-sub-body))
-    (change-class (if-then if) 'script-body))
+            (typep (if-then if) 'js-sub-block))
+    (change-class (if-then if) 'js-block))
   (when (and (if-else if)
-            (typep (if-else if) 'script-sub-body))
-    (change-class (if-else if) 'script-body)))
+            (typep (if-else if) 'js-sub-block))
+    (change-class (if-else if) 'js-block)))
 
 ;;; switch
-(defscriptclass script-switch (statement)
+(defscriptclass js-switch (statement)
   ((value :initarg :value :accessor case-value)
    (clauses :initarg :clauses :accessor case-clauses)))
 
 ;;; assignment
 
-(defscriptclass script-setf (expression)
+(defscriptclass js-setf (expression)
   ((lhs :initarg :lhs :accessor setf-lhs)
    (rhsides :initarg :rhsides :accessor setf-rhsides)))
 
 ;;; defvar
-(defscriptclass script-defvar (statement)
+(defscriptclass js-defvar (statement)
   ((names :initarg :names :accessor var-names)
    (value :initarg :value :accessor var-value)))
 
 ;;; iteration
-(defscriptclass script-for (statement)
+(defscriptclass js-for (statement)
   ((vars :initarg :vars :accessor for-vars)
    (steps :initarg :steps :accessor for-steps)
    (check :initarg :check :accessor for-check)
    (value :initarg :value :accessor fe-value)
    (body :initarg :body :accessor fe-body)))
 
-(defscriptclass script-while (statement)
+(defscriptclass js-while (statement)
   ((check :initarg :check :accessor while-check)
    (body :initarg :body :accessor while-body)))
 
 ;;; with
-(defscriptclass script-with (statement)
+(defscriptclass js-with (statement)
   ((obj :initarg :obj :accessor with-obj)
    (body :initarg :body :accessor with-body)))
 
 ;;; try-catch
-(defscriptclass script-try (statement)
+(defscriptclass js-try (statement)
   ((body :initarg :body :accessor try-body)
    (catch :initarg :catch :accessor try-catch)
    (finally :initarg :finally :accessor try-finally)))
 
 ;; TODO this may not be the best integrated implementation of
 ;; instanceof into the rest of the code
-(defscriptclass script-instanceof (expression)
+(defscriptclass js-instanceof (expression)
   ((value)
    (type :initarg :type)))
 
-(defmacro define-script-single-op (name &optional (superclass 'expression))
-  (let ((script-name (intern (concatenate 'string "SCRIPT-" (symbol-name name)) #.*package*)))
+(defmacro define-js-single-op (name &optional (superclass 'expression))
+  (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
   `(progn
-    (defscriptclass ,script-name (,superclass)
+    (defscriptclass ,js-name (,superclass)
       (value)))))
 
-(define-script-single-op return statement)
-(define-script-single-op throw statement)
-(define-script-single-op delete)
-(define-script-single-op void)
-(define-script-single-op typeof)
-(define-script-single-op new)
-
-;;; for script-package stuff
-(defscriptclass blank-statement (statement)
-  ()
-  (:documentation "An empty statement that does nothing."))
\ No newline at end of file
+(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
index 1c9b922..0ddf150 100644 (file)
@@ -1,5 +1,4 @@
-(in-package :parenscript)
-
+(in-package :parenscript.javascript)
 
 (defgeneric js-to-strings (expression start-pos)
   (:documentation "Transform an enscript-javascript expression to a string"))
               :start "[ " :end " ]"
               :join-after ",")))
 
-(defmethod js-to-strings ((aref script-aref) start-pos)
+(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)
 ;;; 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:" (symbol-to-js key)))
-                                         (js-to-strings value (+ start-pos 2)))
-                                   (- 80 start-pos 2)
-                                   :start "" :end "" :join-after "")))
-             (- 80 start-pos 2)
-             :start "{ " :end " }"
-             :join-after ","))
+  (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
 
@@ -166,7 +166,7 @@ vice-versa.")
     (#\r . #\Return)
     (#\t . #\Tab)))
 
-(defun lisp-special-char-to-js(lisp-char)
+(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)
@@ -189,9 +189,33 @@ vice-versa.")
            finally (write-char *js-quote-char* escaped)))))
 
 ;;; variables
-(defmethod js-to-strings ((v script-variable) start-form)
+(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))
+  (if parenscript::*enable-package-system*
+      (case *package-prefix-style*
+       (:prefix
+        (cond
+          ((or (eql (symbol-package var-name) (find-package :keyword))
+               (eql (symbol-package var-name) (find-package :parenscript.global)))
+           (symbol-to-js var-name))
+          (t
+           (let ((script-package (symbol-script-package var-name)))
+             (format nil "~A_~A"
+                     (symbol-to-js (script-package-name script-package))
+                     (symbol-to-js var-name))))))
+       (t
+        (symbol-to-js (value var-name))))
+      (symbol-to-js var-name)))
+
+(defmethod js-to-strings ((v js-variable) start-form)
   (declare (ignore start-form))
-  (list (symbol-to-js (value v))))
+  (list (js-translate-symbol v)))
 
 ;;; arithmetic operators
 (defun script-convert-op-name (op)
@@ -254,14 +278,14 @@ vice-versa.")
         (args (dwim-join value-string-lists max-length
                          :start "(" :end ")" :join-after ",")))
     (etypecase (f-function form)
-      (script-lambda
+      (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 script-variable script-aref script-slot-value)
+      ((or js-variable js-aref js-slot-value)
        (dwim-join (list (js-to-strings (f-function form) (+ start-pos 2))
                         args)
                   max-length
@@ -279,11 +303,12 @@ vice-versa.")
     ;; 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 'script-lambda 'number-literal 'script-object 'op-form) :test #'typep)  
+    (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 (symbol-to-js (m-method form))))
+                                   (list (js-translate-symbol (m-method form))))
                              (- 80 start-pos 2)
                              :end "("
                              :separator ""))
@@ -302,32 +327,46 @@ vice-versa.")
              (list ensure-no-newline-before-dot)
              (rest method-and-args)))))
 
-(defmethod js-to-statement-strings ((body script-body) start-pos)
+;;; 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)))
-                    (b-statements body))
+                    (block-statements body))
             (- 80 start-pos 2)
             :join-after ";"
             :append-to-last #'special-append-to-last
-            :start (b-indent body) :collect nil
+            :start (block-indent body) :collect nil
             :end ";"))
 
-(defmethod js-to-strings ((body script-body) start-pos)
+(defmethod js-to-strings ((body js-block) start-pos)
   (dwim-join (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
-                    (b-statements body))
+                    (block-statements body))
             (- 80 start-pos 2)
             :append-to-last #'special-append-to-last
             :join-after ","
-            :start (b-indent body)))
+            :start (block-indent body)))
 
 
-(defmethod js-to-statement-strings ((body script-sub-body) start-pos)
+(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 script-lambda) start-pos)
+(defmethod js-to-strings ((lambda js-lambda) start-pos)
   (let ((fun-header (dwim-join (mapcar #'(lambda (x)
-                                           (list (symbol-to-js x)))
+                                           (list (js-translate-symbol x)))
                                       (lambda-args lambda))
                               (- 80 start-pos 2)
                               :start (function-start-string lambda)
@@ -339,17 +378,17 @@ vice-versa.")
   (: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 script-lambda))
+(defmethod function-start-string ((lambda js-lambda))
   "function (")
 
-(defmethod js-to-statement-strings ((lambda script-lambda) start-pos)
+(defmethod js-to-statement-strings ((lambda js-lambda) start-pos)
   (js-to-strings lambda start-pos))
 
-(defmethod function-start-string ((defun script-defun))
-  (format nil "function ~A(" (symbol-to-js (defun-name defun))))
+(defmethod function-start-string ((defun js-defun))
+  (format nil "function ~A(" (js-translate-symbol (defun-name defun))))
 
 ;;; object creation
-(defmethod js-to-strings ((object script-object) start-pos)
+(defmethod js-to-strings ((object js-object) start-pos)
   (let ((value-string-lists
         (mapcar #'(lambda (slot)
                     (dwim-join (list (js-to-strings (second slot) (+ start-pos 4)))
@@ -364,16 +403,16 @@ this is a lambda or a defun"))
               :white-space "  "
               :collect nil)))
 
-(defmethod js-to-strings ((sv script-slot-value) start-pos)
+(defmethod js-to-strings ((sv js-slot-value) start-pos)
   (append-to-last (js-to-strings (sv-object sv) start-pos)
                   (if (typep (sv-slot sv) 'script-quote)
                       (if (symbolp (value (sv-slot sv)))
-                          (format nil ".~A" (symbol-to-js (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 script-cond) start-pos)
+(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))
@@ -385,7 +424,7 @@ this is a lambda or a defun"))
        :append (js-to-statement-strings (car body) (+ start-pos 2))
        :collect "}"))
 
-(defmethod js-to-statement-strings ((if script-if) start-pos)
+(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 ("
@@ -398,13 +437,13 @@ this is a lambda or a defun"))
                                       (nconc (list "} else {") else-strings (list "}"))
                                       (list "}")))))
 
-(defmethod js-to-strings ((if script-if) start-pos)
+(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 'script-body
-                                                  :statements (b-statements (if-then if))
+                  (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))
@@ -413,8 +452,8 @@ this is a lambda or a defun"))
                             res))
                   (list ":")
                   (if (if-else if)
-                      (let* ((new-else (make-instance 'script-body
-                                                      :statements (b-statements (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))
@@ -426,15 +465,15 @@ this is a lambda or a defun"))
             :white-space "  "))
 
 ;;; setf
-(defmethod js-to-strings ((setf script-setf) start-pos)
+(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 script-defvar) start-pos)
-  (dwim-join (nconc (mapcar #'(lambda (x) (list (symbol-to-js x))) (var-names 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)
@@ -442,9 +481,9 @@ this is a lambda or a defun"))
             :start "var " :end ";"))
 
 ;;; iteration
-(defmethod js-to-statement-strings ((for script-for) start-pos)
+(defmethod js-to-statement-strings ((for js-for) start-pos)
   (let* ((init (dwim-join (mapcar #'(lambda (x)
-                                     (dwim-join (list (list (symbol-to-js (first (var-names 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)
@@ -455,7 +494,7 @@ this is a lambda or a defun"))
         (check (js-to-strings (for-check for) (+ start-pos 2)))
         (steps (dwim-join (mapcar #'(lambda (x var)
                                       (dwim-join
-                                       (list (list (symbol-to-js (first (var-names var))))
+                                       (list (list (js-translate-symbol (first (var-names var))))
                                              (js-to-strings x (- start-pos 2)))
                                        (- 80 start-pos 2)
                                        :join-after " ="))
@@ -472,7 +511,7 @@ this is a lambda or a defun"))
 
 
 (defmethod js-to-statement-strings ((fe for-each) start-pos)
-  (let ((header (dwim-join (list (list (symbol-to-js (fe-name fe)))
+  (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)
@@ -481,7 +520,7 @@ this is a lambda or a defun"))
        (body (js-to-statement-strings (fe-body fe) (+ start-pos 2))))
     (nconc header body (list "}"))))
 
-(defmethod js-to-statement-strings ((while script-while) start-pos)
+(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 ("
@@ -490,7 +529,7 @@ this is a lambda or a defun"))
     (nconc header body (list "}"))))
 
 ;;; with
-(defmethod js-to-statement-strings ((with script-with) start-pos)
+(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 ") {")
@@ -498,7 +537,7 @@ this is a lambda or a defun"))
         (list "}")))
 
 ;;; switch
-(defmethod js-to-statement-strings ((case script-switch) start-pos)
+(defmethod js-to-statement-strings ((case js-switch) start-pos)
   (let ((body   (mapcan #'(lambda (clause)
                     (let ((val (car clause))
                           (body (second clause)))
@@ -520,12 +559,12 @@ this is a lambda or a defun"))
           (list "}"))))
 
 ;;; try-catch
-(defmethod js-to-statement-strings ((try script-try) start-pos)
+(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 (symbol-to-js (first catch))))
+                       (dwim-join (list (list (js-translate-symbol (first catch))))
                                   (- 80 start-pos 2)
                                   :start "} catch ("
                                   :end ") {")
@@ -557,7 +596,7 @@ this is a lambda or a defun"))
 
 
 ;;; TODO instanceof
-(defmethod js-to-strings ((instanceof script-instanceof) start-pos)
+(defmethod js-to-strings ((instanceof js-instanceof) start-pos)
   (dwim-join
    (list (js-to-strings (value instanceof) (+ start-pos 2))
          (list "instanceof")
@@ -570,7 +609,7 @@ this is a lambda or a defun"))
 
 ;;; single operations
 (defmacro define-translate-js-single-op (name &optional (superclass 'expression))
-    (let ((script-name (intern (concatenate 'string "SCRIPT-" (symbol-name name)) #.*package*)))
+    (let ((script-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
       `(defmethod ,(if (eql superclass 'expression)
                        'js-to-strings
                      'js-to-statement-strings)
@@ -585,8 +624,4 @@ this is a lambda or a defun"))
 (define-translate-js-single-op delete)
 (define-translate-js-single-op void)
 (define-translate-js-single-op typeof)
-(define-translate-js-single-op new)
-
-(defmethod js-to-statement-strings ((blank-statement blank-statement) start-pos)
-  (declare (ignore blank-statement) (ignore start-pos))
-  '(";"))
\ No newline at end of file
+(define-translate-js-single-op new)
\ No newline at end of file
diff --git a/src/js-ugly-translation.lisp b/src/js-ugly-translation.lisp
new file mode 100644 (file)
index 0000000..c7d41eb
--- /dev/null
@@ -0,0 +1,118 @@
+(in-package :parenscript)
+
+(defparameter *js-lisp-escaped-chars*
+  '((#\' . #\')
+    (#\\ . #\\)
+    (#\b . #\Backspace)
+    (#\f . #.(code-char 12))
+    (#\n . #\Newline)
+    (#\r . #\Return)
+    (#\t . #\Tab)))
+
+(defparameter *char-escape-table*
+  (let ((hash (make-hash-table)))
+    (dolist (escape-pair *js-lisp-escaped-chars* hash)
+      (setf (gethash (cdr escape-pair) hash) (car escape-pair)))))
+
+(declaim (inline lisp-special-char-to-js-2))
+(defun lisp-special-char-to-js-2 (lisp-char)
+  "Gets the escaped version "
+  (gethash lisp-char *char-escape-table*))
+
+(defgeneric js-translate (ast-node expression-or-statement stream)
+  (:documentation "Translates the given AST node to Javascript.
+expression-or-statement is either the keyword :statement or :expression"))
+
+(defmacro defjstrans (script-class type-spec (node-var stream-var) &body body)
+  "Generates a translate-to-js definition for the special-form class SCRIPT-CLASS
+where type-spec is either :expression or :statement.  STREAM is the output stream
+where we should place the Javascript."
+  (when (not (or (eql :expression type-spec) (eql :statement type-spec)))
+    (error "Invalid type-spec fo DEFJSTRANS form."))
+  `(defmethod js-translate ((,node-var ,script-class) (spec (eql ,type-spec)) ,stream-var)
+    ,@body))
+
+(defjstrans expression :expression (expr stream)
+  (princ (value expr) stream))
+
+(defjstrans expression :statement (expr stream)
+  (princ (value expr) stream))
+
+(defjstrans statement :statement (statement stream)
+  (princ (value statement) stream))
+
+(defmacro dolist+ ((car-var list &key result-form lastp-var) &body body)
+  "Iterates over a list, giving other information in bindings designated
+by the keyword arguments."
+  (let ((sublist-var (gensym)))
+    `(progn
+      (mapl
+       #'(lambda (,sublist-var)
+          (let ((,car-var (car ,sublist-var))
+                ,@(when lastp-var 
+                        (list `(,lastp-var (not (cdr ,sublist-var))))))
+            ,@body))
+       ,list)
+      ,result-form)))
+           
+           
+(defjstrans array-literal :expression (array stream)
+  (write-char #\[ stream)
+  (dolist+ (array-item (array-values array) :lastp-var last?)
+    (js-translate array-item :expression stream)
+    (when (not last?) (princ ",")))
+  (write-char #\] stream))
+
+(defjstrans script-aref :expression (aref stream)
+  (js-translate (aref-array aref) :expression stream)
+  (princ "[")
+  (js-translate (aref-index aref) :expression stream)
+  (princ "]"))
+
+(defjstrans object-literal :expression (obj stream)
+  (princ "{")
+  (dolist+ (obj-pair (object-values obj) :lastp-var last?)
+    (js-translate (car obj-pair) :expression stream)
+    (princ ":")
+    (js-translate (cdr obj-pair) :expression stream)
+    (when (not last?) (princ ",")))
+  (princ "}"))
+
+(defjstrans string-literal :expression (string stream)
+  (declare (inline lisp-special-char-to-js-2))
+  (write-char *js-quote-char*  stream)
+  (loop
+   for char across (value string)
+   for code = (char-code char)
+   for special = (lisp-special-char-to-js-2 char)
+   do
+   (cond
+     (special
+      (write-char #\\ stream)
+      (write-char special stream))
+     ((or (<= code #x1f) (>= code #x80))
+      (format stream "\\u~4,'0x" code))
+     (t (write-char char stream)))
+   finally (write-char *js-quote-char* stream)))
+
+(defjstrans script-variable :expression (var stream)
+  (princ (symbol-to-js (value var)) stream))
+
+(defjstrans op-form :expression (op-form stream)
+  (let ((precedence (expression-precedence op-form)))
+    (flet ((output-op-arg (op-arg)
+            (let ((parens? (>= (expression-precedence op-arg) precedence)))
+              (when parens? (write-char #\())
+              (js-translate op-arg :expression stream)
+              (when parens? (write-char #\))))))
+      (output-op-arg (first (op-args op-form)))
+      (format stream "~A " (operator op-form))
+      (output-op-arg (second (op-args op-form))))))
+
+(defjstrans one-op :expression (one-op stream)
+  (let ((pre? (one-op-pre-p one-op)))
+    (when pre?
+      (princ (one-op one-op) stream))
+    (js-translate (value one-op) :expression stream)
+    (when (not pre?)
+      (princ (one-op one-op) stream))))
\ No newline at end of file
diff --git a/src/macrology.lisp b/src/macrology.lisp
deleted file mode 100644 (file)
index dae9a60..0000000
+++ /dev/null
@@ -1,638 +0,0 @@
-(in-package :parenscript)
-
-;;;; The macrology of the basic Parenscript language.  Special forms and macros in the
-;;;; Parenscript language.
-
-;;; parenscript gensyms
-(defvar *gen-script-name-counter* 0)
-
-(defun gen-script-name-string (&key (prefix "_ps_"))
-  "Generates a unique valid javascript identifier ()"
-  (concatenate 'string
-               prefix (princ-to-string (incf *gen-script-name-counter*))))
-
-(defun gen-script-name (&key (prefix "_ps_"))
-  "Generate a new javascript identifier."
-  (intern (gen-script-name-string :prefix prefix)
-          (find-package :js)))
-
-(defmacro with-unique-js-names (symbols &body body)
-  "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
-
-Each element of SYMBOLS is either a symbol or a list of (symbol
-prefix)."
-  `(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)))))
-                  symbols)
-     ,@body))
-
-(defvar *var-counter* 0)
-
-(defun script-gensym (&optional (name "js"))
-  (intern (format nil "tmp-~A-~A" name (incf *var-counter*)) #.*package*))
-
-;;; 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 nil       "null")
-(defscriptliteral false     "false")
-(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)))
-
-(defscriptmacro list (&rest values)
-  `(array ,@values))
-
-(define-script-special-form aref (array &rest coords)
-  (make-instance 'script-aref
-                :array (compile-to-expression array)
-                :index (mapcar #'compile-to-expression coords)))
-
-
-(defscriptmacro make-array (&rest inits)
-  `(new (*array ,@inits)))
-
-;;; 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)))
-
-;;; progn
-(define-script-special-form progn (&rest body)
-  (make-instance 'script-body
-                :statements (mapcar #'compile-to-statement body)))
-
-(defmethod expression-precedence ((body script-body))
-  (if (= (length (b-statements body)) 1)
-      (expression-precedence (first (b-statements body)))
-      (op-precedence 'comma)))
-
-;;; function definition
-(define-script-special-form lambda (args &rest body)
-  (make-instance 'script-lambda
-                 :args (mapcar #'compile-to-symbol args)
-                 :body (make-instance 'script-body
-                                      :indent "  "
-                                      :statements (mapcar #'compile-to-statement body))))
-
-(define-script-special-form defun (name args &rest body)
-  (make-instance 'script-defun
-                :name (compile-to-symbol name)
-                :args (mapcar #'compile-to-symbol args)
-                :body (make-instance 'script-body
-                                     :indent "  "
-                                     :statements (mapcar #'compile-to-statement body))))
-
-;;; object creation
-(define-script-special-form create (&rest args)
-  (make-instance 'script-object
-                :slots (loop for (name val) on args by #'cddr
-                             collect (let ((name-expr (compile-to-expression name)))
-                                       (assert (or (typep name-expr 'script-variable)
-                                                   (typep name-expr 'string-literal)
-                                                   (typep name-expr 'number-literal)))
-                                       (list name-expr (compile-to-expression val))))))
-
-
-(define-script-special-form slot-value (obj slot)
-  (make-instance 'script-slot-value :object (compile-to-expression obj)
-                  :slot (compile-script-form slot)))
-
-;;; cond
-(define-script-special-form cond (&rest clauses)
-  (make-instance 'script-cond
-                :tests (mapcar (lambda (clause) (compile-to-expression (car clause)))
-                               clauses)
-                :bodies (mapcar (lambda (clause) (compile-to-body (cons 'progn (cdr clause)) :indent "  "))
-                                clauses)))
-
-;;; if
-(define-script-special-form if (test then &optional else)
-  (make-instance 'script-if :test (compile-to-expression test)
-                :then (compile-to-body then :indent "  ")
-                :else (when else
-                        (compile-to-body else :indent "  "))))
-
-(defmethod expression-precedence ((if script-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-body (cons 'progn body) :indent "  "))))
-                        clauses))
-       (check (compile-to-expression value)))
-    (make-instance 'script-switch :value check
-                  :clauses clauses)))
-
-
-(defscriptmacro case (value &rest clauses)
-  (labels ((make-clause (val body more)
-             (cond ((listp val)
-                    (append (mapcar #'list (butlast val))
-                            (make-clause (first (last val)) body more)))
-                   ((member val '(t otherwise))
-                    (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)))
-                              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 #'js-equal))
-      (let ((args-without (remove lhs (op-args rhs)
-                                 :count 1 :test #'js-equal))
-           (args-without-first (remove lhs (op-args rhs)
-                                       :count 1 :end 1
-                                       :test #'js-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 (js-equal args-without one)
-                   (eql (operator rhs) '+))
-              (make-instance 'one-op :pre-p nil :op "++"
-                             :value lhs))
-             ((and (js-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)
-                           '(+ *))
-                    (js-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))
-                   (js-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 'script-setf :lhs lhs :rhsides (list rhs)))))
-      (make-instance 'script-setf :lhs lhs :rhsides (list rhs))))
-
-(define-script-special-form setf (&rest args)
-  (let ((assignments (loop for (lhs rhs) on args by #'cddr
-                          for rexpr = (compile-to-expression rhs)
-                          for lexpr = (compile-to-expression lhs)
-                          collect (make-js-test lexpr rexpr))))
-    (if (= (length assignments) 1)
-       (first assignments)
-       (make-instance 'script-body :indent "" :statements assignments))))
-
-(defmethod expression-precedence ((setf script-setf))
-  (op-precedence '=))
-
-;;; defvar
-(define-script-special-form defvar (name &optional value)
-  (make-instance 'script-defvar :names (list (compile-to-symbol name))
-                :value (when value (compile-to-expression value))))
-
-;;; let
-(define-script-special-form let (decls &rest body)
-  (let ((defvars (mapcar #'(lambda (decl)
-                            (if (atom decl)
-                                 (make-instance 'script-defvar
-                                       :names (list (compile-to-symbol decl))
-                                       :value nil)
-                                 (let ((name (first decl))
-                                       (value (second decl)))
-                                   (make-instance 'script-defvar
-                                                  :names (list (compile-to-symbol name))
-                                                  :value (compile-to-expression value)))))
-                        decls)))
-    (make-instance 'script-sub-body
-                  :indent "  "
-                  :statements (nconc defvars
-                                (mapcar #'compile-to-statement body)))))
-
-;;; 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 'script-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-body (cons 'progn body) :indent "  ")))
-    (make-instance 'script-for
-                  :vars vars
-                  :steps steps
-                  :check check
-                  :body body)))
-
-(defscriptmacro 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)
-  (let ((var (first i-array))
-       (array (second i-array))
-       (arrvar (script-gensym "arr"))
-       (idx (script-gensym "i")))
-    `(let ((,arrvar ,array))
-      (do ((,idx 0 (1+ ,idx)))
-         ((>= ,idx (slot-value ,arrvar 'length)))
-       (let ((,var (aref ,arrvar ,idx)))
-         ,@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-body (cons 'progn body) :indent "  ")))
-
-(define-script-special-form while (check &rest body)
-  (make-instance 'script-while
-                :check (compile-to-expression check)
-                :body (compile-to-body (cons 'progn body) :indent "  ")))
-
-;;; with
-(define-script-special-form with (statement &rest body)
-  (make-instance 'script-with
-                :obj (compile-to-expression statement)
-                :body (compile-to-body (cons 'progn body) :indent "  ")))
-
-
-;;; try-catch
-(define-script-special-form try (body &rest clauses)
-  (let ((body (compile-to-body 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 'script-try
-                  :body body
-                  :catch (when catch (list (compile-to-symbol (caar catch))
-                                           (compile-to-body (cons 'progn (cdr catch))
-                                                               :indent "  ")))
-                  :finally (when finally (compile-to-body (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 'script-instanceof
-                 :value (compile-to-expression value)
-                 :type (compile-to-expression type)))
-
-;;; 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)
-    (format t "~A~%~A~%"
-          (and (compiler-in-situation-p *compilation-environment* :compile-toplevel)
-               (find :compile-toplevel situations))
-          (compiler-in-situation-p *compilation-environment*  :execute)
-           (find :execute situations))
-    (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)))
-          (format t "Form: ~A~%" form)
-          (compile-to-statement form)))))))
-
-;;; script packages
-(define-script-special-form blank-statement ()
-  (make-instance 'blank-statement))
-
-(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)
-         (: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)))))
-    (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"
-  (setf (comp-env-current-package
-        *compilation-environment*)
-       (comp-env-find-package *compilation-environment* package-designator))
-  `(progn))
-
-;;; single operations
-(defmacro define-parse-script-single-op (name &optional (superclass 'expression))
-  (let ((script-name (intern (concatenate 'string "SCRIPT-" (symbol-name name)) #.*package*)))
-    `(define-script-special-form ,name (value)
-       (make-instance ',script-name :value (compile-to-expression value)))
-    ))
-
-(define-parse-script-single-op return statement)
-(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)
-
-;;; 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 with-slots (slots object &rest body)
-  `(symbol-macrolet ,(mapcar #'(lambda (slot)
-                                `(,slot '(slot-value ,object ',slot)))
-                            slots)
-    ,@body))
-
-(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))
-
-;;; macros
-(defmacro with-temp-macro-environment ((var) &body body)
-  `(let* ((,var (make-macro-env-dictionary))
-          (*script-macro-env* (cons ,var *script-macro-env*)))
-    ,@body))
-
-(define-script-special-form macrolet (macros &body body)
-  (with-temp-macro-environment (macro-env-dict)
-    (dolist (macro macros)
-      (destructuring-bind (name arglist &body body)
-          macro
-       (setf (get-macro-spec name macro-env-dict)
-             (cons nil (let ((args (gensym "ps-macrolet-args-")))
-                          (compile nil `(lambda (&rest ,args)
-                                         (destructuring-bind ,arglist
-                                             ,args
-                                           ,@body))))))))
-    (compile-script-form `(progn ,@body))))
-
-(define-script-special-form symbol-macrolet (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))))
-
-(defscriptmacro defmacro (name args &body body)
-  `(lisp (defscriptmacro ,name ,args ,@body) nil))
-
-(defscriptmacro 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 expression)
-  "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
-  the js side for js closures."
-  (unless (listp variables)
-    (setf variables (list variables)))
-  `((lambda ()
-      (let ((new-context (new *object)))
-        ,@(loop for variable in variables
-                do (setf variable (symbol-to-js variable))
-                collect `(setf (slot-value new-context ,variable) (slot-value this ,variable)))
-        (with new-context
-              (return ,expression))))))
-
-;;; Math library
-(defscriptmacro floor (expr)
-  `(*Math.floor ,expr))
-
-(defscriptmacro random ()
-  `(*Math.random))
-
-(defscriptmacro evenp (num)
-  `(= (% ,num 2) 0))
-
-(defscriptmacro oddp (num)
-  `(= (% ,num 2) 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 js-inline (&rest body)
-  `(script-inline ,@body))
-
-;;; dual lisp/parenscript macro balderdash
-;;; TODO: should probably move elsewhere ;;;
-(defmacro defmacro/js (name args &body body)
-  "Define a Lisp macro and import it into the ParenScript macro environment."
-  `(progn (defmacro ,name ,args ,@body)
-         (js:import-macros-from-lisp ',name)))
-
-(defmacro defmacro+js (name args &body body)
-  "Define a Lisp macro and a ParenScript macro in their respective
-macro environments. This function should be used when you want to use
-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)))
-
-(defun import-macros-from-lisp (&rest names)
-  "Import the named Lisp macros into the ParenScript macro environment."
-  (dolist (name names)
-    (let ((name name))
-      (undefine-js-special-form name)
-      (setf (get-macro-spec name *script-macro-toplevel*)
-            (cons nil (lambda (&rest args)
-                        (macroexpand `(,name ,@args))))))))
-
-(defmacro js-file (&rest body)
-  `(html
-    (:princ
-     (js ,@body))))
-
-(defmacro js-script (&rest body)
-  `((:script :type "text/javascript")
-    (:princ (format nil "~%// <![CDATA[~%"))
-    (:princ (js ,@body))
-    (:princ (format nil "~%// ]]>~%"))))
-
-(defmacro js-inline (&rest body)
-  `(js-inline* '(progn ,@body)))
-
-(defmacro js-inline* (&rest body)
-  "Just like JS-INLINE except that BODY is evaluated before being
-converted to javascript."
-  `(concatenate 'string "javascript:"
-    (string-join (js-to-statement-strings (compile-script-form (list 'progn ,@body)) 0) " ")))
index 82b6b20..70b0e7d 100644 (file)
@@ -1,7 +1,118 @@
 (in-package :cl-user)
 
-(defpackage :parenscript
+(defpackage parenscript.javascript
   (:use :common-lisp)
+  (:nicknames javascript ps-js)
+  (:export
+
+   #:new
+   ;; literals
+   #:t
+   #:nil
+   #:this
+   #:false
+   #:undefined
+
+   ;; keywords
+   #:break
+   #:continue
+
+   ;; array literals
+   #:array
+   #:list
+   #:aref
+   #:make-array
+
+   ;; operators
+   #:! #:not #:~
+   #:* #:/ #:%
+   #:+ #:-
+   #:<< #:>>
+   #:>>>
+   #:< #:> #:<= #:>=
+   #:in
+   #:eql #:== #:!= #:=
+   #:=== #:!==
+   #:&
+   #:^
+   #:\|
+   #:\&\& #:and
+   #:\|\| #:or
+   #:>>= #:<<=
+   #:*= #:/= #:%= #:+= #:\&= #:^= #:\|= #:~=
+   #:++ #:--
+   #:1+ #:1-
+   #:incf #:decf
+
+   ;; body forms
+   #:progn
+
+   ;; function definition
+   #:defun
+   #:lambda
+
+   ;; object literals
+   #:create
+   #:slot-value
+   #:with-slots
+
+   ;; macros
+   #:macrolet
+   #:symbol-macrolet
+
+   ;; if
+   #:if
+   #:when
+   #:unless
+
+   ;; single argument statements
+   #:return
+   #:throw
+
+   ;; single argument expressions
+   #:delete
+   #:void
+   #:typeof
+   #:instanceof
+   #:new
+
+   ;; assignment
+   #:setf
+
+   ;; variables
+   #:defvar
+
+   ;; iteration
+   #:for
+   #:doeach
+   #:while
+
+   ;; with
+   #:with
+
+   ;; case
+   #:switch
+   #:case
+   #:default
+
+   ;; try throw catch
+   #:try
+
+   ;; regex literals
+   #:regex
+
+   ;; conditional compilation (IE)
+   #:cc-if
+   
+   ;; 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"))
+
+(defpackage :parenscript
+  (:use :common-lisp :parenscript.javascript)
   (:nicknames :js :ps)
   (:export
    ;; addition js symbols
 
    ;; compiler
    #:compile-script
+   #:compile-parenscript-file
+   #:compile-parenscript-file-to-string
    #:script
    #: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?
    #:css-inline
    #:css-file
 
-   #:compile-parenscript-file
-   #:compile-parenscript-file-to-string
-
    ;; deprecated interface
    #:defjsmacro
    #:js-compile
    #:js-to-statement-strings
    #:js-to-string
    #:js-to-line
-   ))
+   )
+  (:intern 
+   #:define-script-special-form
+   #:defscriptclass
+   #:symbol-to-js
+   #:script-quote
+   #:*package-prefix-style*
+   #:*script-macro-env*
+   #:compile-to-statement
+   #:compile-to-block
+   #:compile-to-symbol
+   #:compile-to-expression
+   #: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
+   #:compile-script-form
+   )
+  )
+
+(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
+   js-equal
+   compile-script-form
+   ) 
+ :parenscript.javascript)
+
+(defpackage parenscript.reader
+  (:nicknames parenscript-reader)
+  (:use :common-lisp :parenscript)
+  (:shadow readtablep
+           readtable-case
+           copy-readtable
+           get-macro-character
+           get-dispatch-macro-character
+           set-macro-character
+           set-dispatch-macro-character
+           make-dispatch-macro-character
+           set-syntax-from-char
+           read-preserving-whitespace
+           read
+           read-from-string
+           read-delimited-list
+           backquote-comma-dot
+           backquote
+           backquote-comma
+           backquote-comma-at
+           
+           *read-eval*
+           *read-base*
+           *read-default-float-format*
+           *read-suppress*
+           *readtable*
+           *read-suppress*
+           *reader-error*
+           *read-suppress*
+           
+           readtable
+           backquote
+           reader-error)
+  (:export
+    read
+    read-from-string
+    read-delimited-list))
+
+(defpackage parenscript.global
+  (:nicknames global)
+  (:documentation "Symbols interned in the global package are serialized in Javascript
+as non-prefixed identifiers."))
+
+(defpackage parenscript.user
+  (:nicknames ps-user paren-user parenscript-user)
+  (:documentation "The default package a user is inside of when compiling code."))
\ No newline at end of file
index de5fcce..f712180 100644 (file)
@@ -2,10 +2,6 @@
 
 ;;;; The mechanisms for defining macros & parsing Parenscript.
 
-(defclass identifier ()
-  ((symbol :accessor id-symbol :initform nil :type symbol))
-  (:documentation ""))
-
 (defclass script-package ()
   ;; configuration slots
   ((name          :accessor script-package-name          :initform nil :initarg :name :type string
@@ -15,7 +11,8 @@
    (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       :initform nil :initarg :exports
+   (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 "")
@@ -59,7 +56,7 @@ about a set of Suavescript code."))
 
 (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.")
+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))
@@ -74,11 +71,18 @@ http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm")
     ))
 
 (defvar *compilation-environment* nil
-  "The active compilation environment.
+  "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.
+  )
+
+(defvar *enable-package-system* t
+  "When NIL, all symbols will function as global symbols.")
 
-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.")
+(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.")
 
 ;;; parenscript packages
 (defun lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
@@ -97,19 +101,85 @@ as arguments.")
 
 (defun find-script-package (name &optional (comp-env *compilation-environment*))
   "Find the script package with the name NAME in the given compilation environment."
-  (find (string name) (comp-env-script-packages comp-env) :test #'equal))
-
+  (typecase name
+    ((or symbol string)
+     (find (string name) (comp-env-script-packages comp-env)
+          :test #'equal :key #'script-package-name))
+    (script-package  name)
+    (t (error "~A has unknown type" name))))
+     
 (defun destroy-script-package (script-package)
   "Disposes of relevant resources when the script package is no longer relevant."
   (when (script-package-exclusive-lisp-package-p script-package)
     (delete-package (script-package-lisp-package script-package))))
 
+(defun script-intern (name script-package)
+  "Returns a Parenscript symbol with the string value STRING interned for the
+given SCRIPT-PACKAGE."
+  (setf script-package (find-script-package script-package))
+  (intern name (script-package-lisp-package script-package)))
+
+(defun script-export (symbols &optional (script-package (comp-env-current-package *compilation-environment*)))
+  "Exports the given symbols in the given script package."
+  (when (symbolp symbols)
+    (setf symbols (list symbols)))
+  ;; TODO check to make sure symbols are each interned under SCRIPT-PACKAGE
+  (mapc #'(lambda (sym)
+           (pushnew sym (script-package-exports script-package)))
+       symbols)
+  t)
+  
+(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"
+  (setf script-package (find-script-package script-package))
+  (let* ((symbol (find-symbol name (script-package-lisp-package script-package)))
+         (exported? (find symbol (script-package-exports script-package))))
+    (values symbol (if exported? :external (when symbol :internal)))))
+
 ;; environmental considerations
+(defgeneric install-standard-script-packages (comp-env)
+  (:documentation "Creates standard script packages and installs them in the current compilation
+environment.")
+  (:method ((comp-env compilation-environment))
+    (list
+     (create-script-package
+      comp-env
+      :name "GLOBAL" :lisp-package :parenscript.global
+      :secondary-lisp-packages '(:keyword))
+     (create-script-package
+      comp-env
+      :name "JAVASCRIPT" :nicknames (list "JS") :lisp-package :parenscript.javascript
+      :secondary-lisp-packages '(:common-lisp))
+     (create-script-package
+      comp-env
+      :name "PARENSCRIPT" :lisp-package :parenscript
+      :used-packages '(:javascript)
+      )
+     (create-script-package
+      comp-env
+      :name "PARENSCRIPT-USER" :lisp-package :parenscript-user
+      :secondary-lisp-packages (list :cl-user)
+      :nicknames '("PS-USER" "PAREN-USER")))))
+
+(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.")
+  (:method ((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))
+
 (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 ((comp-env (make-instance 'compilation-environment)))
-    comp-env))
+  (setup-compilation-environment (make-instance 'compilation-environment)))
 
 (defun create-script-package (comp-env
                              &key name nicknames secondary-lisp-packages used-packages
@@ -221,7 +291,7 @@ ongoing javascript compilation."
 
 (defun funcall-form-p (form)
   (and (listp form)
-       (not (op-form-p form))
+       (not (ps-js::op-form-p form))
        (not (script-special-form-p form))))
 
 (defun method-call-p (form)
@@ -363,6 +433,7 @@ http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm"))
                      (compile-parenscript-form comp-env subform :toplevel-p t))
                  (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))
@@ -375,24 +446,28 @@ http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm"))
               (multiple-value-bind (function warnings-p failure-p)
                   (compile nil `(lambda () ,@body))
                 (declare (ignore warnings-p) (ignore failure-p))
-                `(progn
-                  ,(funcall function)
-                  ,@(when other-situations
-                          (list `(eval-when ,other-situations ,@body))))))))
+                (compile-parenscript-form 
+                 comp-env
+                 `(progn
+                   ,(funcall function)
+                   ,@(when other-situations
+                           (list `(eval-when ,other-situations ,@body))))
+                 :toplevel-p t)))))
          ;; if :compile-toplevel is not in the situation list, return the form
          (t form))))
      (t form))
    (cond ((stringp form)
-         (make-instance 'string-literal :value form))
+         (make-instance 'ps-js::string-literal :value form))
         ((characterp form)
-         (make-instance 'string-literal :value (string form)))
+         (make-instance 'ps-js::string-literal :value (string form)))
         ((numberp form)
-         (make-instance 'number-literal :value form))
-        ((symbolp form) ;; is this the correct behavior?
+         (make-instance 'ps-js::number-literal :value form))
+        ((symbolp form)
+         ;; is this the correct behavior?
          (let ((c-macro (get-script-special-form form)))
            (if c-macro
                (funcall c-macro)
-               (make-instance 'script-variable :value form))))
+               (make-instance 'ps-js::js-variable :value form))))
         ((and (consp form)
               (eql (first form) 'quote))
          (make-instance 'script-quote :value (second form)))
@@ -403,19 +478,19 @@ http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm"))
               (cond (script-form
                      (apply script-form args))
                     
-                    ((op-form-p form)
-                     (make-instance 'op-form
-                                    :operator (script-convert-op-name (compile-to-symbol (first form)))
+                    ((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 'method-call
+                     (make-instance 'ps-js::method-call
                                     :method (compile-to-symbol (first form))
                                     :object (compile-to-expression (second form))
                                     :args (mapcar #'compile-to-expression (cddr form))))
                     
                     ((funcall-form-p form)
-                     (make-instance 'function-call
+                     (make-instance 'ps-js::function-call
                                     :function (compile-to-expression (first form))
                                     :args (mapcar #'compile-to-expression (rest form))))
                     
@@ -429,30 +504,36 @@ http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm"))
 (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 'expression))
+    (assert (typep res 'ps-js::expression))
     res))
 
 (defun compile-to-symbol (form)
-  "Compiles the given Parenscript form and guarantees a symbolic result."
+  "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 'script-variable)
-      (setf res (value res)))
+    (when (typep res 'ps-js::js-variable)
+      (setf res (ps-js::value res)))
     (assert (symbolp res) ()
             "~a is expected to be a symbol, but compiles to ~a. This could be due to ~a being a special form." form res form)
+    (when *enable-package-system*
+      (assert (symbol-script-package res) ()
+             "The symbol ~A::~A has no associated script package." 
+             (package-name (symbol-package res))
+             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 'statement))
+    (assert (typep res 'ps-js::statement))
     res))
 
-(defun compile-to-body (form &key (indent ""))
+(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 'script-body)
-       (progn (setf (b-indent res) indent)
+    (if (typep res 'ps-js::js-block)
+       (progn (setf (ps-js::block-indent res) indent)
               res)
-       (make-instance 'script-body
+       (make-instance 'ps-js::js-block
                       :indent indent
                       :statements (list res)))))
\ No newline at end of file
diff --git a/src/ps-macrology.lisp b/src/ps-macrology.lisp
new file mode 100644 (file)
index 0000000..09280cf
--- /dev/null
@@ -0,0 +1,212 @@
+(in-package :parenscript)
+
+;;;; The macrology of the Parenscript language.  Special forms and macros.
+
+;;; parenscript gensyms
+(defvar *gen-script-name-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 gen-script-name (&key (prefix "_ps_"))
+  "Generate a new javascript identifier."
+  (intern (gen-script-name-string :prefix prefix)
+          (find-package :js)))
+
+(defmacro with-unique-js-names (symbols &body body)
+  "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
+
+Each element of SYMBOLS is either a symbol or a list of (symbol
+prefix)."
+  `(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)))))
+                  symbols)
+     ,@body))
+
+(defvar *var-counter* 0)
+
+(defun script-gensym (&optional (name "js"))
+  (intern (format nil "tmp-~A-~A" name (incf *var-counter*)) #.*package*))
+
+;;; array literals
+(defscriptmacro list (&rest values)
+  `(array ,@values))
+
+(defscriptmacro 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)
+;    (format t "~A~%~A~%"
+;         (and (compiler-in-situation-p *compilation-environment* :compile-toplevel)
+;              (find :compile-toplevel situations))
+;         (compiler-in-situation-p *compilation-environment*  :execute)
+;          (find :execute situations))
+    (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)))
+          (format t "Form: ~A~%" form)
+          (compile-to-statement form)))))))
+
+;;; 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"
+  (setf (comp-env-current-package *compilation-environment*)
+       (find-script-package package-designator *compilation-environment*))
+  `(progn))
+
+(defscriptmacro case (value &rest clauses)
+  (labels ((make-clause (val body more)
+             (cond ((listp val)
+                    (append (mapcar #'list (butlast val))
+                            (make-clause (first (last val)) body more)))
+                   ((member val '(t otherwise))
+                    (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)))
+                              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)))))
+
+;;; iteration
+(defscriptmacro 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)
+  (let ((var (first i-array))
+       (array (second i-array))
+       (arrvar (script-gensym "arr"))
+       (idx (script-gensym "i")))
+    `(let ((,arrvar ,array))
+      (do ((,idx 0 (1+ ,idx)))
+         ((>= ,idx (slot-value ,arrvar 'length)))
+       (let ((,var (aref ,arrvar ,idx)))
+         ,@body)))))
+
+;;; macros
+(defmacro with-temp-macro-environment ((var) &body body)
+  `(let* ((,var (make-macro-env-dictionary))
+          (*script-macro-env* (cons ,var *script-macro-env*)))
+    ,@body))
+
+(define-script-special-form macrolet (macros &body body)
+  (with-temp-macro-environment (macro-env-dict)
+    (dolist (macro macros)
+      (destructuring-bind (name arglist &body body)
+          macro
+       (setf (get-macro-spec name macro-env-dict)
+             (cons nil (let ((args (gensym "ps-macrolet-args-")))
+                          (compile nil `(lambda (&rest ,args)
+                                         (destructuring-bind ,arglist
+                                             ,args
+                                           ,@body))))))))
+    (compile-script-form `(progn ,@body))))
+
+(define-script-special-form symbol-macrolet (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))))
+
+(defscriptmacro defmacro (name args &body body)
+  `(lisp (defscriptmacro ,name ,args ,@body) nil))
+
+(defscriptmacro 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 expression)
+  "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
+  the js side for js closures."
+  (unless (listp variables)
+    (setf variables (list variables)))
+  `((lambda ()
+      (let ((new-context (new *object)))
+        ,@(loop for variable in variables
+                do (setf variable (symbol-to-js variable))
+                collect `(setf (slot-value new-context ,variable) (slot-value this ,variable)))
+        (with new-context
+              (return ,expression))))))
\ No newline at end of file
diff --git a/src/ps-source-model.lisp b/src/ps-source-model.lisp
new file mode 100644 (file)
index 0000000..02c8a34
--- /dev/null
@@ -0,0 +1,6 @@
+(in-package :parenscript)
+
+;;; quote
+(defscriptclass script-quote (ps-js::expression)
+  ())
+
index d137de4..a782dcc 100644 (file)
@@ -1,4 +1,4 @@
-c;; Copyright (C) 2002-2004, Yuji Minejima <ggb01164@nifty.ne.jp>
+;; Copyright (C) 2002-2004, Yuji Minejima <ggb01164@nifty.ne.jp>
 ;; ALL RIGHTS RESERVED.
 ;;
 ;; $Id: reader.lisp,v 1.10 2004/02/20 07:23:42 yuji Exp $
@@ -26,7 +26,14 @@ c;; Copyright (C) 2002-2004, Yuji Minejima <ggb01164@nifty.ne.jp>
 ;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-(in-package parenscript-reader)
+;;;; The ParenScript reader, used for reading Parenscript files and other
+;;;; forms during the Parenscript compilation process.  The main difference
+;;;; between this reader and the standard Lisp reader is that package
+;;;; prefixes are SCRIPT package names rather than Lisp package names.
+
+;;; The main function, READ, will not work unless *compilation-environement*
+;;; is bound to a valid Parenscript COMPILATION-ENVIRONMENT.
+(in-package parenscript.reader)
 
 (defstruct (readtable (:predicate readtablep) (:copier nil))
   (syntax (make-hash-table) :type hash-table)
@@ -372,7 +379,11 @@ c;; Copyright (C) 2002-2004, Yuji Minejima <ggb01164@nifty.ne.jp>
          (float? sign))))))
 
 (defun ensure-external-symbol (name package)
-  (multiple-value-bind (symbol status) (find-script-symbol name package)
+  "Ensures that the symbol with name NAME is external for the given script package PACKAGE.
+Raises a continuable error if NAME is not external in PACKAGE.  Otherwise interns NAME
+in PACKAGE and returns the symbol."
+  (multiple-value-bind (symbol status)
+      (find-script-symbol name package)
     (unless (eq status :external)
       (cerror (if (null status)
                   "Intern and export script symbol ~S in package ~S."
@@ -382,8 +393,6 @@ c;; Copyright (C) 2002-2004, Yuji Minejima <ggb01164@nifty.ne.jp>
       (script-export (setq symbol (script-intern name package)) package))
     symbol))
 
-(defvar *intern-package-prefixes* t)
-
 (defun construct-symbol (lexemes &key uninterned-symbol-wanted)
   (labels ((up (x) (if (listp x) (copy-list x) (list (char-upcase x))))
            (down (x) (if (listp x) (copy-list x) (list (char-downcase x))))
@@ -411,26 +420,19 @@ c;; Copyright (C) 2002-2004, Yuji Minejima <ggb01164@nifty.ne.jp>
     (let* ((pos (position #\: lexemes))
            (external-p (and pos (not (eql (nth (1+ pos) lexemes) #\:))))
            (package (when pos (name (subseq lexemes 0 pos))))
-           (script-package (find-script-package *compilation-environment* package))
+           (script-package (find-script-package package))
            (name (name (subseq lexemes (if pos (+ pos (if external-p 1 2)) 0)))))
       (values (cond
-               (*intern-package-prefixes*
-                (let ((str (if package
-                               (concatenate 'string package ":" name)
-                             name)))
-                             
-                  (if uninterned-symbol-wanted
-                      str
-                    (intern str))))
                (uninterned-symbol-wanted
                 (if package
                     (reader-error)
-                  (make-symbol name)))
+                   (make-symbol name)))
                (external-p
-                (ensure-external-symbol name package))
+                (ensure-external-symbol name script-package))
                (t (script-intern name 
-                                 (or package
-                                     (current-package *compilation-environment*)))))))))
+                                 (or script-package
+                                     (parenscript::comp-env-current-package
+                                     *compilation-environment*)))))))))
 
 (defun read-number-or-symbol (stream c)
   (let ((lexemes (collect-lexemes c stream)))
diff --git a/t/package-system-tests.lisp b/t/package-system-tests.lisp
new file mode 100644 (file)
index 0000000..1e213b0
--- /dev/null
@@ -0,0 +1,43 @@
+(in-package :parenscript-test)
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (def-suite package-system-tests))
+
+(in-suite package-system-tests)
+
+(defpstest simple-variable-prefix ()
+  (progn
+    (defpackage test (:lisp-package :parenscript-test))
+    (defvar the-var))
+   "var test_theVar;")
+
+(defpstest no-global-variable-prefix ()
+  (progn
+    (defvar global::the-var)
+    (defvar global::global))
+   "var theVar; var global;")
+
+(defpstest eval-when-returns-paren-form ()
+  (progn
+    (eval-when (:compile-toplevel)
+      `(global::sort-of-macro-like))
+    global::treat-me-right)
+  "sortOfMacroLike(); treatMeRight;")
+
+(defpstest javascript-operations ()
+  (progn
+    (+ 1 2 3 4)
+    (- 1 2 3 4)
+    nil t this false undefined)
+  "1 + 2 + 3 + 4; 1 - 2 - 3 - 4; null; true; this; false; undefined;")
+
+(defpstest def-keywords ()
+  (progn
+    (defun global::hello-world () (return 5)))
+  "function helloWorld() { return 5; };")
+
+(defpstest ps-js-reserved ()
+  (eval-when (:compile-toplevel)
+    (format nil "~A" (script-package-name (symbol-script-package 'defclass))))
+  "'JAVASCRIPT';")
+
index 0f1a7de..34ab2e3 100644 (file)
@@ -68,7 +68,8 @@ x = 2 + sideEffect() + x + 5;")
             "(function (x) { return x; }) (10).toString()")
 
 (test no-whitespace-before-dot
-  (let* ((str (js:js* '(.to-string ((lambda (x) (return x)) 10))))
+  (let* ((parenscript::*enable-package-system* nil)
+        (str (compile-script '(.to-string ((lambda (x) (return x)) 10))))
          (dot-pos (position #\. str :test #'char=))
          (char-before (elt str (1- dot-pos)))
          (a-parenthesis #\)))
@@ -172,7 +173,8 @@ x = 2 + sideEffect() + x + 5;")
          }")
 
 (test escape-sequences-in-string
-  (let ((escapes `((#\\ . #\\)
+  (let ((parenscript::*enable-package-system* nil)
+       (escapes `((#\\ . #\\)
                    (#\b . #\Backspace)
                    (#\f . ,(code-char 12))
                    ("u000B" . ,(code-char #x000b));;Vertical tab, too uncommon to bother with
index 526d3e5..aa865da 100644 (file)
       (normalize-whitespace str))))))
 
 (defmacro test-ps-js (testname parenscript javascript)
+  (let (
+       ;; (parenscript
+       ;;   `(progn
+       ;; (defpackage parenscript-test
+       ;; (:lisp-package :parenscript-test))
+       ;; ,parenscript)))
+       )
   `(test ,testname ()
     (setf js::*var-counter* 0)
+    
     ;; is-macro expands its argument again when reporting failures, so
     ;; the reported temporary js-variables get wrong if we don't evalute first.
-    (let ((generated-code (compile-script ',parenscript))
+    (let* ((parenscript::*enable-package-system* nil)
+          (generated-code (compile-script ',parenscript))
+          (js-code ,javascript))
+      (is (string= (normalize-js-code generated-code)
+                   (normalize-js-code js-code)))))))
+
+(defmacro defpstest (testname (&key (optimize t) (enable-package-system t)) parenscript javascript)
+  `(test ,testname
+    (setf parenscript::*var-counter* 0)
+    (let* ((parenscript::*enable-package-system* ,enable-package-system)
+          (generated-code (compile-script ',parenscript))
           (js-code ,javascript))
       (is (string= (normalize-js-code generated-code)
                    (normalize-js-code js-code))))))
@@ -44,5 +62,7 @@
   (format t "Running reference tests:~&")
   (run! 'ref-tests)
   (format t "Running other tests:~&")
-  (run! 'ps-tests))
+  (run! 'ps-tests)
+  (format t "Running Package System tests:~&")
+  (run! 'package-system-tests))