renaming and refactoring
authorRed Daly <reddaly@gmail.com>
Fri, 20 Jul 2007 00:34:31 +0000 (00:34 +0000)
committerRed Daly <reddaly@gmail.com>
Fri, 20 Jul 2007 00:34:31 +0000 (00:34 +0000)
Changed the names of many functions and macros to get rid of the
symbols with "js" in them--its not Javascript, it's Parenscript!  Most
of those symbols were renamed with "script" replacing "js".

Also changed the main compilation interfaces to use the function
COMPILE-SCRIPT and the macro SCRIPT from JS-COMPILE and JS
respectively.

Additionally, the first steps of the package system are included (with
the addition of a the SCRIPT-PACKAGE and COMPILATION-ENVIRONMENT
classes).  These are integrated into the current compiler, though they
probably break a few "traditional" serialization methods, specifically
because macro and special form names are stored as symbols rather than
strings and EQL comparisons are used rather than STRING-EQUAL
comparisons of the strings.

I have also split parser.lisp into parser.lisp and macrology.lisp.
Parser.lisp contains mechanisms for parsing Parenscript given an input
s-expression while macrology.lisp contains language definitions that
make use of the parsing mechanisms.

All tests now pass, though the documentation has gone slightly out of
date with the symbol renaming.  This will be fixed shortly.  More
tests and functionality need to be added to make the current
Parenscript compatable with the older semantics (as dicussed,
comparing macro/special form names based on their string values is the
main thing).

19 files changed:
parenscript.asd
src/compilation-interface.lisp [new file with mode: 0644]
src/compile-js.lisp [deleted file]
src/defgenerics.lisp [deleted file]
src/deprecated-interface.lisp [new file with mode: 0644]
src/js-translation.lisp
src/lib/css.lisp [moved from src/css.lisp with 97% similarity]
src/lib/functional.lisp
src/lib/js-html.lisp [moved from src/js-html.lisp with 94% similarity]
src/lib/js-utils.lisp [moved from src/js-utils.lisp with 87% similarity]
src/macrology.lisp [new file with mode: 0644]
src/package.lisp
src/parse-lambda-list.lisp [new file with mode: 0644]
src/parser.lisp
src/reader.lisp [new file with mode: 0644]
src/source-model.lisp
t/ps-tests.lisp
t/test-package.lisp
t/test.lisp

index c400037..fa56f14 100644 (file)
   :version "0"
   :maintainer "Vladimir Sedach <vsedach@gmail.com>"
   :licence "BSD"
-  :description "js - javascript compiler"
+  :description "Parenscript is a lispy language that compiles to Javascript."
   :components ((:static-file "parenscript.asd")
                (:module :src
                 :components ((:file "package")
                              (:file "utils" :depends-on ("package"))
-                             (:file "defgenerics" :depends-on ("package"))
-                            (:file "source-model" :depends-on ("package" "utils" "defgenerics"))
+                            (:file "source-model" :depends-on ("package" "utils"))
                             (:file "parser" :depends-on ("source-model"))
-                            (:file "js-translation" :depends-on ("parser"))
-                             (:file "js-html" :depends-on ("package" "js-translation" "utils"))
-                             (:file "css" :depends-on ("package" "utils"))
-                             (:file "compile-js" :depends-on ("package" "js-translation"))
-                             (:file "js-utils" :depends-on ("package" "js-translation"))
+                            (: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"))
+                            ;; standard library
                              (:module :lib
-                                      :components ((:static-file "functional.lisp")))))))
+                                      :components ((:static-file "functional.lisp")
+                                                  (:file "js-html")
+                                                  (:file "css"    )
+                                                  (:file "js-utils"))
+                                     :depends-on ("compilation-interface")))))
+  :depends-on ())
 
 (defmethod asdf:perform :after ((op asdf:load-op) (system (eql (asdf:find-system :parenscript)))) 
   (pushnew :parenscript cl:*features*))
diff --git a/src/compilation-interface.lisp b/src/compilation-interface.lisp
new file mode 100644 (file)
index 0000000..2420cb2
--- /dev/null
@@ -0,0 +1,139 @@
+(in-package :parenscript)
+
+(defmacro with-new-compilation-environment ((var) &body body)
+  `(let* ((,var (make-basic-compilation-environment))
+         (*compilation-environment* ,var))
+    ,@body))
+    
+
+(defun translate-ast (compiled-expr
+                     &key
+                     (comp-env *compilation-environment*)
+                     (output-stream *standard-output*)
+                     (output-spec :javascript)
+                     (pretty-print t))
+  "Translates a compiled Parenscript program (compiled with COMPILE-PAREN-FORM)
+to a Javascript string.  Outputs to the stream OUTPUT-STREAM in the language given
+by OUTPUT-SPEC, pretty printing if PRETTY-PRINT is non-null.
+
+OUTPUT-SPEC must be :javascript at the moment."
+  (declare (ignore pretty-print) (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)))
+
+(defun compile-script (script-form
+                      &key
+                      (output-spec :javascript)
+                      (pretty-print t)
+                      (output-stream nil)
+                      (comp-env (make-basic-compilation-environment)))
+  "Compiles the Parenscript form SCRIPT-FORM into the language specified by OUTPUT-SPEC.
+Non-null PRETTY-PRINT values result in a pretty-printed output code.  If OUTPUT-STREAM
+is NIL, then the result is a string; otherwise code is output to the OUTPUT-STREAM stream.
+COMP-ENV is the compilation environment in which to compile the form.
+
+This is the main function used by Parenscript users to compile their code to Javascript (and
+potentially other languages)."
+  (macrolet ((with-output-stream ((var) &body body)
+              `(if (null output-stream)
+                (with-output-to-string (,var)
+                  ,@body)
+                (let ((,var output-stream))
+                  ,@body))))
+    (with-output-stream (stream)
+      (let ((*compilation-environment* comp-env))
+       (translate-ast (compile-script-form script-form :comp-env comp-env)
+                      :comp-env comp-env
+                      :output-stream stream
+                      :output-spec output-spec
+                      :pretty-print pretty-print)))))
+
+;;; SEXPs -> Javascript string functionality
+(defmacro script (&body body)
+  "A macro that returns a Javascript string of the supplied Parenscript forms."
+  `(js* '(progn ,@body)))
+
+(defmacro script* (&body body)
+  "Return the javascript string representing BODY.
+
+Body is evaluated."
+  `(compile-script (progn ,@body)))
+
+;; DEPRECATED
+(defmacro js (&body body)
+  "A macro that returns a javascript string of the supplied Parenscript forms."
+  `(script ,@body))
+
+(defmacro js* (&body body)
+  `(script* ,@body))
+
+(defun js-to-string (expr)
+  "Given an AST node, compiles it to a Javascript string."
+  (string-join
+   (js-to-statement-strings (compile-script-form expr) 0)
+   (string #\Newline)))
+
+(defun js-to-line (expr)
+  "Given an AST node, compiles it to a Javascript string."
+  (string-join
+   (js-to-statement-strings (compile-script-form expr) 0) " "))
+
+(defun compile-parenscript-file-to-string (source-file
+                                          &key
+                                          (log-stream nil)
+                                          (comment nil)
+                                          (eval-forms-p nil))
+  "Compile SOURCE-FILE (a parenscript file) to a javascript string. (in-package ...) forms
+behave as expected and all other forms are evaluated according to the value of
+EVAL-FORMS-P. If the result of the evaluation is not nil then it's compiled with
+js:js* and written to the output."
+  (with-output-to-string (output)
+    (with-open-file (input source-file :direction :input)
+      (flet ((read-form ()
+               (read input nil))
+             (log-message (&rest args)
+               (when log-stream
+                 (apply #'format log-stream args))))
+        (let ((*package* *package*))
+          (loop for form = (read-form)
+                while form do
+                (if (or (not (listp form))
+                        (not (eq (car form) 'cl:in-package)))
+                    (progn
+                      (log-message "Processing form:~%~S~%" form)
+                      (when comment
+                        (princ "/*" output)
+                        (print form output)
+                        (terpri output)
+                        (princ "*/" output)
+                        (terpri output))
+                      (when eval-forms-p
+                        (setf form (eval form)))
+                      (log-message "After evaluation:~%~S~%" form)
+                      (when form
+                        (let ((compiled (js:js* form)))
+                          (log-message "Compiled into:~%~A~%~%" compiled)
+                          (write-string compiled output)
+                          (terpri output)
+                          (terpri output))))
+                    (when (and (listp form)
+                               (eq (car form) 'cl:in-package))
+                      (log-message "Setting package to: ~S~%" (cadr form))
+                      (setf *package* (find-package (cadr form)))))))))))
+
+(defun compile-parenscript-file (source-file &rest args &key destination-file &allow-other-keys)
+  "Compile SOURCE-FILE (a parenscript file) to a javascript file with
+compile-parenscript-file-to-string. When DESTINATION-FILE is omitted,
+then it will be named the same as SOURCE-FILE but with js extension."
+  (setf args (copy-list args))
+  (remf args :destination-file)
+  (unless destination-file
+    (setf destination-file (merge-pathnames (make-pathname :type "js")
+                                            source-file)))
+  (with-open-file (output destination-file :if-exists :supersede :direction :output)
+    (write-string (apply #'compile-parenscript-file-to-string source-file args) output)))
diff --git a/src/compile-js.lisp b/src/compile-js.lisp
deleted file mode 100644 (file)
index f0a2cb9..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-(in-package :parenscript)
-
-(defun compile-parenscript-file-to-string (source-file &key
-                                                       (log-stream nil)
-                                                       (comment nil)
-                                                       (eval-forms-p nil))
-  "Compile SOURCE-FILE (a parenscript file) to a javascript string. (in-package ...) forms
-behave as expected and all other forms are evaluated according to the value of
-EVAL-FORMS-P. If the result of the evaluation is not nil then it's compiled with
-js:js* and written to the output."
-  (with-output-to-string (output)
-    (with-open-file (input source-file :direction :input)
-      (flet ((read-form ()
-               (read input nil))
-             (log-message (&rest args)
-               (when log-stream
-                 (apply #'format log-stream args))))
-        (let ((*package* *package*))
-          (loop for form = (read-form)
-                while form do
-                (if (or (not (listp form))
-                        (not (eq (car form) 'cl:in-package)))
-                    (progn
-                      (log-message "Processing form:~%~S~%" form)
-                      (when comment
-                        (princ "/*" output)
-                        (print form output)
-                        (terpri output)
-                        (princ "*/" output)
-                        (terpri output))
-                      (when eval-forms-p
-                        (setf form (eval form)))
-                      (log-message "After evaluation:~%~S~%" form)
-                      (when form
-                        (let ((compiled (js:js* form)))
-                          (log-message "Compiled into:~%~A~%~%" compiled)
-                          (write-string compiled output)
-                          (terpri output)
-                          (terpri output))))
-                    (when (and (listp form)
-                               (eq (car form) 'cl:in-package))
-                      (log-message "Setting package to: ~S~%" (cadr form))
-                      (setf *package* (find-package (cadr form)))))))))))
-
-(defun compile-parenscript-file (source-file &rest args &key destination-file &allow-other-keys)
-  "Compile SOURCE-FILE (a parenscript file) to a javascript file with
-compile-parenscript-file-to-string. When DESTINATION-FILE is omitted,
-then it will be named the same as SOURCE-FILE but with js extension."
-  (setf args (copy-list args))
-  (remf args :destination-file)
-  (unless destination-file
-    (setf destination-file (merge-pathnames (make-pathname :type "js")
-                                            source-file)))
-  (with-open-file (output destination-file :if-exists :supersede :direction :output)
-    (write-string (apply #'compile-parenscript-file-to-string source-file args) output)))
diff --git a/src/defgenerics.lisp b/src/defgenerics.lisp
deleted file mode 100644 (file)
index 409ebea..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-(in-package :parenscript)
-
-(defgeneric js-equal (obj1 obj2)
-  (:documentation "Determine if two enscript-javascript statements are equivalent"))
-
-(defgeneric js-to-strings (expression start-pos)
-  (:documentation "Transform an enscript-javascript expression to a string"))
-
-(defgeneric js-to-statement-strings (code-fragment start-pos)
-  (:documentation "Transform an enscript-javascript code fragment to a string"))
-
-(defgeneric expression-precedence (expression)
-  (:documentation "Returns the precedence of an enscript-javascript expression"))
-
-(defgeneric function-start-string (function)
-  (:documentation "Returns the string that starts the function - this varies according to whether
-this is a lambda or a defun"))
diff --git a/src/deprecated-interface.lisp b/src/deprecated-interface.lisp
new file mode 100644 (file)
index 0000000..74a4183
--- /dev/null
@@ -0,0 +1,19 @@
+(in-package :parenscript)
+
+;;; DEPRECATED INTERFACE ;;;
+(defun js-equal (a b) (script-equal a b))
+
+(defun js-compile (form)
+  (compile-script form :output-spec :javascript))
+
+(defun js-compile-list (form)
+  (compile-script form :output-spec :javascript))
+
+(defun js-gensym (&rest args)
+  (apply #'script-gensym args))
+
+(defmacro defjsmacro (name args &rest body)
+  "Define a ParenScript macro, and store it in the toplevel ParenScript macro environment.
+
+DEPRECATED"
+  `(defscriptmacro ,name ,args ,@body))
\ No newline at end of file
index 817d19e..1c9b922 100644 (file)
@@ -1,5 +1,12 @@
 (in-package :parenscript)
 
+
+(defgeneric js-to-strings (expression start-pos)
+  (:documentation "Transform an enscript-javascript expression to a string"))
+
+(defgeneric js-to-statement-strings (code-fragment start-pos)
+  (:documentation "Transform an enscript-javascript code fragment to a string"))
+
 ;;; indenter
 
 (defun special-append-to-last (form elt)
               :start "[ " :end " ]"
               :join-after ",")))
 
-(defmethod js-to-strings ((aref js-aref) start-pos)
+(defmethod js-to-strings ((aref script-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)
@@ -182,12 +189,12 @@ vice-versa.")
            finally (write-char *js-quote-char* escaped)))))
 
 ;;; variables
-(defmethod js-to-strings ((v js-variable) start-form)
+(defmethod js-to-strings ((v script-variable) start-form)
   (declare (ignore start-form))
   (list (symbol-to-js (value v))))
 
 ;;; arithmetic operators
-(defun js-convert-op-name (op)
+(defun script-convert-op-name (op)
   (case op
     (and '\&\&)
     (or '\|\|)
@@ -198,7 +205,7 @@ vice-versa.")
 
 (defun op-form-p (form)
   (and (listp form)
-       (not (js-special-form-p form))
+       (not (script-special-form-p form))
        (not (null (op-precedence (first form))))))
 
 (defun klammer (string-list)
@@ -247,14 +254,14 @@ vice-versa.")
         (args (dwim-join value-string-lists max-length
                          :start "(" :end ")" :join-after ",")))
     (etypecase (f-function form)
-      (js-lambda
+      (script-lambda
        (dwim-join (list (append (dwim-join (list (js-to-strings (f-function form) (+ start-pos 2)))
                                            max-length
                                            :start "(" :end ")" :separator "")
                                 args))
                   max-length
                   :separator ""))
-      ((or js-variable js-aref js-slot-value)
+      ((or script-variable script-aref script-slot-value)
        (dwim-join (list (js-to-strings (f-function form) (+ start-pos 2))
                         args)
                   max-length
@@ -272,7 +279,7 @@ 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 'js-lambda 'number-literal 'js-object 'op-form) :test #'typep)  
+    (when (member (m-object form) (list 'script-lambda 'number-literal 'script-object 'op-form) :test #'typep)  
       (push "(" object)
       (nconc object (list ")")))
     (let* ((fname (dwim-join (list object
@@ -295,30 +302,30 @@ vice-versa.")
              (list ensure-no-newline-before-dot)
              (rest method-and-args)))))
 
-(defmethod js-to-statement-strings ((body js-body) start-pos)
+(defmethod js-to-statement-strings ((body script-body) start-pos)
   (dwim-join (mapcar #'(lambda (x) (js-to-statement-strings x (+ start-pos 2)))
-                    (b-stmts body))
+                    (b-statements body))
             (- 80 start-pos 2)
             :join-after ";"
             :append-to-last #'special-append-to-last
             :start (b-indent body) :collect nil
             :end ";"))
 
-(defmethod js-to-strings ((body js-body) start-pos)
+(defmethod js-to-strings ((body script-body) start-pos)
   (dwim-join (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
-                    (b-stmts body))
+                    (b-statements body))
             (- 80 start-pos 2)
             :append-to-last #'special-append-to-last
             :join-after ","
             :start (b-indent body)))
 
 
-(defmethod js-to-statement-strings ((body js-sub-body) start-pos)
+(defmethod js-to-statement-strings ((body script-sub-body) start-pos)
   (declare (ignore start-pos))
   (nconc (list "{") (call-next-method) (list "}")))
 
 ;;; function definition
-(defmethod js-to-strings ((lambda js-lambda) start-pos)
+(defmethod js-to-strings ((lambda script-lambda) start-pos)
   (let ((fun-header (dwim-join (mapcar #'(lambda (x)
                                            (list (symbol-to-js x)))
                                       (lambda-args lambda))
@@ -328,17 +335,21 @@ vice-versa.")
        (fun-body (js-to-statement-strings (lambda-body lambda) (+ start-pos 2))))
     (nconc fun-header fun-body (list "}"))))
 
-(defmethod function-start-string ((lambda js-lambda))
+(defgeneric function-start-string (function)
+  (:documentation "Returns the string that starts the function - this varies according to whether
+this is a lambda or a defun"))
+
+(defmethod function-start-string ((lambda script-lambda))
   "function (")
 
-(defmethod js-to-statement-strings ((lambda js-lambda) start-pos)
+(defmethod js-to-statement-strings ((lambda script-lambda) start-pos)
   (js-to-strings lambda start-pos))
 
-(defmethod function-start-string ((defun js-defun))
+(defmethod function-start-string ((defun script-defun))
   (format nil "function ~A(" (symbol-to-js (defun-name defun))))
 
 ;;; object creation
-(defmethod js-to-strings ((object js-object) start-pos)
+(defmethod js-to-strings ((object script-object) start-pos)
   (let ((value-string-lists
         (mapcar #'(lambda (slot)
                     (dwim-join (list (js-to-strings (second slot) (+ start-pos 4)))
@@ -353,16 +364,16 @@ vice-versa.")
               :white-space "  "
               :collect nil)))
 
-(defmethod js-to-strings ((sv js-slot-value) start-pos)
+(defmethod js-to-strings ((sv script-slot-value) start-pos)
   (append-to-last (js-to-strings (sv-object sv) start-pos)
-                  (if (typep (sv-slot sv) 'js-quote)
+                  (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" (first (js-to-strings (sv-slot sv) 0))))
                       (format nil "[~A]" (first (js-to-strings (sv-slot sv) 0))))))
 
 ;;; cond
-(defmethod js-to-statement-strings ((cond js-cond) start-pos)
+(defmethod js-to-statement-strings ((cond script-cond) start-pos)
   (loop :for body :on (cond-bodies cond)
        :for first = (eq body (cond-bodies cond))
        :for last = (not (cdr body))
@@ -374,7 +385,7 @@ vice-versa.")
        :append (js-to-statement-strings (car body) (+ start-pos 2))
        :collect "}"))
 
-(defmethod js-to-statement-strings ((if js-if) start-pos)
+(defmethod js-to-statement-strings ((if script-if) start-pos)
   (let ((if-strings (dwim-join (list (js-to-strings (if-test if) 0))
                               (- 80 start-pos 2)
                               :start "if ("
@@ -387,13 +398,13 @@ vice-versa.")
                                       (nconc (list "} else {") else-strings (list "}"))
                                       (list "}")))))
 
-(defmethod js-to-strings ((if js-if) start-pos)
+(defmethod js-to-strings ((if script-if) start-pos)
   (assert (typep (if-then if) 'expression))
   (when (if-else if)
     (assert (typep (if-else if) 'expression)))
   (dwim-join (list (append-to-last (js-to-strings (if-test if) start-pos) " ?")
-                  (let* ((new-then (make-instance 'js-body
-                                                  :stmts (b-stmts (if-then if))
+                  (let* ((new-then (make-instance 'script-body
+                                                  :statements (b-statements (if-then if))
                                                   :indent ""))
                          (res (js-to-strings new-then start-pos)))
                     (if (>= (expression-precedence (if-then if))
@@ -402,8 +413,8 @@ vice-versa.")
                             res))
                   (list ":")
                   (if (if-else if)
-                      (let* ((new-else (make-instance 'js-body
-                                                      :stmts (b-stmts (if-else if))
+                      (let* ((new-else (make-instance 'script-body
+                                                      :statements (b-statements (if-else if))
                                                       :indent ""))
                              (res (js-to-strings new-else start-pos)))
                         (if (>= (expression-precedence (if-else if))
@@ -415,14 +426,14 @@ vice-versa.")
             :white-space "  "))
 
 ;;; setf
-(defmethod js-to-strings ((setf js-setf) start-pos)
+(defmethod js-to-strings ((setf script-setf) start-pos)
   (dwim-join (cons (js-to-strings (setf-lhs setf) start-pos)
                   (mapcar #'(lambda (x) (js-to-strings x start-pos)) (setf-rhsides setf)))
             (- 80 start-pos 2)
             :join-after " ="))
 
 ;;; defvar
-(defmethod js-to-statement-strings ((defvar js-defvar) start-pos)
+(defmethod js-to-statement-strings ((defvar script-defvar) start-pos)
   (dwim-join (nconc (mapcar #'(lambda (x) (list (symbol-to-js x))) (var-names defvar))
                    (when (var-value defvar)
                      (list (js-to-strings (var-value defvar) start-pos))))
@@ -431,7 +442,7 @@ vice-versa.")
             :start "var " :end ";"))
 
 ;;; iteration
-(defmethod js-to-statement-strings ((for js-for) start-pos)
+(defmethod js-to-statement-strings ((for script-for) start-pos)
   (let* ((init (dwim-join (mapcar #'(lambda (x)
                                      (dwim-join (list (list (symbol-to-js (first (var-names x))))
                                                       (js-to-strings (var-value x)
@@ -470,7 +481,7 @@ vice-versa.")
        (body (js-to-statement-strings (fe-body fe) (+ start-pos 2))))
     (nconc header body (list "}"))))
 
-(defmethod js-to-statement-strings ((while js-while) start-pos)
+(defmethod js-to-statement-strings ((while script-while) start-pos)
   (let ((header (dwim-join (list (js-to-strings (while-check while) (+ start-pos 2)))
                           (- 80 start-pos 2)
                           :start "while ("
@@ -479,7 +490,7 @@ vice-versa.")
     (nconc header body (list "}"))))
 
 ;;; with
-(defmethod js-to-statement-strings ((with js-with) start-pos)
+(defmethod js-to-statement-strings ((with script-with) start-pos)
   (nconc (dwim-join (list (js-to-strings (with-obj with) (+ start-pos 2)))
                    (- 80 start-pos 2)
                    :start "with (" :end ") {")
@@ -487,7 +498,7 @@ vice-versa.")
         (list "}")))
 
 ;;; switch
-(defmethod js-to-statement-strings ((case js-switch) start-pos)
+(defmethod js-to-statement-strings ((case script-switch) start-pos)
   (let ((body   (mapcan #'(lambda (clause)
                     (let ((val (car clause))
                           (body (second clause)))
@@ -509,7 +520,7 @@ vice-versa.")
           (list "}"))))
 
 ;;; try-catch
-(defmethod js-to-statement-strings ((try js-try) start-pos)
+(defmethod js-to-statement-strings ((try script-try) start-pos)
   (let* ((catch (try-catch try))
         (finally (try-finally try))
         (catch-list (when catch
@@ -546,7 +557,7 @@ vice-versa.")
 
 
 ;;; TODO instanceof
-(defmethod js-to-strings ((instanceof js-instanceof) start-pos)
+(defmethod js-to-strings ((instanceof script-instanceof) start-pos)
   (dwim-join
    (list (js-to-strings (value instanceof) (+ start-pos 2))
          (list "instanceof")
@@ -559,11 +570,11 @@ vice-versa.")
 
 ;;; single operations
 (defmacro define-translate-js-single-op (name &optional (superclass 'expression))
-    (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
+    (let ((script-name (intern (concatenate 'string "SCRIPT-" (symbol-name name)) #.*package*)))
       `(defmethod ,(if (eql superclass 'expression)
                        'js-to-strings
                      'js-to-statement-strings)
-         ((,name ,js-name) start-pos)
+         ((,name ,script-name) start-pos)
          (dwim-join (list (js-to-strings (value ,name) (+ start-pos 2)))
                     (- 80 start-pos 2)
                     :start ,(concatenate 'string (string-downcase (symbol-name name)) " ")
@@ -575,3 +586,7 @@ vice-versa.")
 (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
similarity index 97%
rename from src/css.lisp
rename to src/lib/css.lisp
index 9ef4a85..5fd470a 100644 (file)
@@ -41,7 +41,7 @@
               ";"))
 
 (defmacro css-inline (&rest propvals)
-  `(js::css-inline-func ,propvals))
+  `(parenscript::css-inline-func ,propvals))
 
 (defmacro css-file (&rest rules)
   `(html
index 5c1dfb2..134b141 100644 (file)
@@ -1,5 +1,7 @@
 (in-package :js)
 
+;;;; this file might be a little dated
+
 ;; This file contains JS code and is meant to be compiled and included
 ;; into the host environment in one way or another
 
similarity index 94%
rename from src/js-html.lisp
rename to src/lib/js-html.lisp
index 5939efc..33bb241 100644 (file)
@@ -71,8 +71,8 @@
       (map nil #'handle-form forms))
     (cons '+ (optimize-string-list (nreverse res)))))
 
-(define-js-special-form html (&rest forms)
-  (js-compile (process-html-forms forms)))
+(define-script-special-form html (&rest forms)
+  (compile-script-form (process-html-forms forms)))
 
 (defun process-css-forms(proplist)
   (optimize-string-list (butlast
@@ -83,5 +83,5 @@
                                     ";")))))
 
 
-(define-js-special-form css-inline (&rest forms)
-  (js-compile (cons '+ (process-css-forms forms))))
+(define-script-special-form css-inline (&rest forms)
+  (compile-script-form (cons '+ (process-css-forms forms))))
similarity index 87%
rename from src/js-utils.lisp
rename to src/lib/js-utils.lisp
index c9010c3..4e79ed6 100644 (file)
@@ -3,13 +3,13 @@
 ;;; Handy utilities for doing common tasks found in many web browser
 ;;; JavaScript implementations
 
-(defjsmacro do-set-timeout ((timeout) &body body)
+(defscriptmacro do-set-timeout ((timeout) &body body)
   `(set-timeout (lambda () ,@body) ,timeout))
 
 ;;; Arithmetic
 
 (defmacro def-js-maths (&rest mathdefs)
-  `(progn ,@(mapcar (lambda (def) (cons 'defjsmacro def)) mathdefs)))
+  `(progn ,@(mapcar (lambda (def) (cons 'defscriptmacro def)) mathdefs)))
 
 (def-js-maths
     (min (&rest nums) `(*math.min ,@nums))
@@ -34,5 +34,5 @@
 
 ;;; Exception handling
 
-(defjsmacro ignore-errors (&body body)
+(defscriptmacro ignore-errors (&body body)
   `(try (progn ,@body) (:catch (e))))
diff --git a/src/macrology.lisp b/src/macrology.lisp
new file mode 100644 (file)
index 0000000..f5b12a3
--- /dev/null
@@ -0,0 +1,609 @@
+(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)))
+
+;;; 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 98f7392..82b6b20 100644 (file)
@@ -2,7 +2,7 @@
 
 (defpackage :parenscript
   (:use :common-lisp)
-  (:nicknames :js)
+  (:nicknames :js :ps)
   (:export
    ;; addition js symbols
    #:new
@@ -98,6 +98,7 @@
    #:with
 
    ;; case
+   #:switch
    #:case
    #:default
 
    #:html
 
    ;; compiler
-   #:js-compile
-   #:js
-   #:js*
-   #:js-inline
-   #:js-inline*
-   #:js-file
-   #:js-script
-   #:js-to-strings
-   #:js-to-statement-strings
-   #:js-to-string
-   #:js-to-line
-   #:defjsmacro
+   #:compile-script
+   #:script
+   #:with-new-compilation-environment ; tentative
+   #:with-compilation-environment     ; tentative
+   
+   ;; for parenscript macro definition within lisp
+   #:defscriptmacro #:defpsmacro ; should we use one or the other of these?
    #:defmacro/js
    #:defmacro+js
    #:import-macros-from-lisp
 
    #:compile-parenscript-file
    #:compile-parenscript-file-to-string
+
+   ;; deprecated interface
+   #:defjsmacro
+   #:js-compile
+   #:js ; replaced by #:script
+   #:js*
+   #:js-inline
+   #:js-inline*
+   #:js-file
+   #:js-script
+   #:js-to-strings
+   #:js-to-statement-strings
+   #:js-to-string
+   #:js-to-line
    ))
diff --git a/src/parse-lambda-list.lisp b/src/parse-lambda-list.lisp
new file mode 100644 (file)
index 0000000..59aa829
--- /dev/null
@@ -0,0 +1,222 @@
+(in-package parenscript)
+;;;; This software was taken from the SBCL system.  there are very few 
+;;;; changes, and one SBCL-specific thing left (sb-c::collect
+
+;;; if you have found this on google, THIS IS NOT AN SBCL SOURCE FILE
+
+;;; Break something like a lambda list (but not necessarily actually a
+;;; lambda list, e.g. the representation of argument types which is
+;;; used within an FTYPE specification) into its component parts. We
+;;; return twelve values:
+;;;  1. a list of the required args;
+;;;  2. a list of the &OPTIONAL arg specs;
+;;;  3. true if a &REST arg was specified;
+;;;  4. the &REST arg;
+;;;  5. true if &KEY args are present;
+;;;  6. a list of the &KEY arg specs;
+;;;  7. true if &ALLOW-OTHER-KEYS was specified.;
+;;;  8. true if any &AUX is present (new in SBCL vs. CMU CL);
+;;;  9. a list of the &AUX specifiers;
+;;; 10. true if a &MORE arg was specified;
+;;; 11. the &MORE context var;
+;;; 12. the &MORE count var;
+;;; 13. true if any lambda list keyword is present (only for
+;;;     PARSE-LAMBDA-LIST-LIKE-THING).
+;;;
+;;; The top level lambda list syntax is checked for validity, but the
+;;; arg specifiers are just passed through untouched. If something is
+;;; wrong, we use COMPILER-ERROR, aborting compilation to the last
+;;; recovery point.
+(eval-when (#-sb-xc :compile-toplevel :load-toplevel :execute)
+  (defun collect-list-expander (n-value n-tail forms)
+    (let ((n-res (gensym)))
+      `(progn
+       ,@(mapcar (lambda (form)
+                   `(let ((,n-res (cons ,form nil)))
+                     (cond (,n-tail
+                            (setf (cdr ,n-tail) ,n-res)
+                            (setq ,n-tail ,n-res))
+                           (t
+                            (setq ,n-tail ,n-res  ,n-value ,n-res)))))
+                 forms)
+       ,n-value))))
+  
+(defmacro collect (collections &body body)
+  (let ((macros ())
+       (binds ()))
+    (dolist (spec collections)
+                                       ;      (unless (proper-list-of-length-p spec 1 3)
+                                       ;        (error "malformed collection specifier: ~S" spec))
+      (let* ((name (first spec))
+            (default (second spec))
+            (kind (or (third spec) 'collect))
+            (n-value (gensym (concatenate 'string
+                                          (symbol-name name)
+                                          "-N-VALUE-"))))
+       (push `(,n-value ,default) binds)
+       (if (eq kind 'collect)
+           (let ((n-tail (gensym (concatenate 'string
+                                              (symbol-name name)
+                                              "-N-TAIL-"))))
+             (if default
+                 (push `(,n-tail (last ,n-value)) binds)
+                 (push n-tail binds))
+               (push `(,name (&rest args)
+                       (collect-list-expander ',n-value ',n-tail args))
+                     macros))
+           (push `(,name (&rest args)
+                   (collect-normal-expander ',n-value ',kind args))
+                 macros))))
+    `(macrolet ,macros (let* ,(nreverse binds) ,@body))))
+  
+(defparameter *lambda-list-keywords*
+ '(&allow-other-keys &aux &body &environment &key &optional &rest &whole))
+
+(defun style-warn (&rest args) (apply #'format t args))
+
+
+(defun parse-lambda-list-like-thing (list)
+ (collect ((required)
+            (optional)
+            (keys)
+            (aux))
+    (let ((restp nil)
+          (rest nil)
+          (morep nil)
+          (more-context nil)
+          (more-count nil)
+          (keyp nil)
+          (auxp nil)
+          (allowp nil)
+          (state :required))
+      (declare (type (member :allow-other-keys :aux
+                             :key
+                             :more-context :more-count
+                             :optional
+                             :post-more :post-rest
+                             :required :rest)
+                     state))
+      (dolist (arg list)
+        (if (member arg *lambda-list-keywords*)
+            (case arg
+              (&optional
+               (unless (eq state :required)
+                 (format t "misplaced &OPTIONAL in lambda list: ~S"
+                                 list))
+               (setq state :optional))
+              (&rest
+               (unless (member state '(:required :optional))
+                 (format t "misplaced &REST in lambda list: ~S" list))
+               (setq state :rest))
+              (&more
+               (unless (member state '(:required :optional))
+                 (format t "misplaced &MORE in lambda list: ~S" list))
+               (setq morep t
+                     state :more-context))
+              (&key
+               (unless (member state
+                               '(:required :optional :post-rest :post-more))
+                 (format t "misplaced &KEY in lambda list: ~S" list))
+               #-sb-xc-host
+               (when (optional)
+                 (format t
+                  "&OPTIONAL and &KEY found in the same lambda list: ~S" list))
+               (setq keyp t
+                     state :key))
+              (&allow-other-keys
+               (unless (eq state ':key)
+                 (format t "misplaced &ALLOW-OTHER-KEYS in ~
+                                  lambda list: ~S"
+                                 list))
+               (setq allowp t
+                     state :allow-other-keys))
+              (&aux
+               (when (member state '(:rest :more-context :more-count))
+                 (format t "misplaced &AUX in lambda list: ~S" list))
+               (when auxp
+                 (format t "multiple &AUX in lambda list: ~S" list))
+               (setq auxp t
+                     state :aux))
+              (t (format t "unknown LAMBDA-LIST-KEYWORD in lambda list: ~S." arg)))
+            (progn
+              (when (symbolp arg)
+                (let ((name (symbol-name arg)))
+                  (when (and (plusp (length name))
+                             (char= (char name 0) #\&))
+                    (style-warn
+                     "suspicious variable in lambda list: ~S." arg))))
+              (case state
+                (:required (required arg))
+                (:optional (optional arg))
+                (:rest
+                 (setq restp t
+                       rest arg
+                       state :post-rest))
+                (:more-context
+                 (setq more-context arg
+                       state :more-count))
+                (:more-count
+                 (setq more-count arg
+                       state :post-more))
+                (:key (keys arg))
+                (:aux (aux arg))
+                (t
+                 (format t "found garbage in lambda list when expecting ~
+                                  a keyword: ~S"
+                                 arg))))))
+      (when (eq state :rest)
+        (format t "&REST without rest variable"))
+
+      (values (required) (optional) restp rest keyp (keys) allowp auxp (aux)
+              morep more-context more-count
+              (not (eq state :required))))))
+
+;;; like PARSE-LAMBDA-LIST-LIKE-THING, except our LAMBDA-LIST argument
+;;; really *is* a lambda list, not just a "lambda-list-like thing", so
+;;; can barf on things which're illegal as arguments in lambda lists
+;;; even if they could conceivably be legal in not-quite-a-lambda-list
+;;; weirdosities
+(defun parse-lambda-list (lambda-list)
+
+  ;; Classify parameters without checking their validity individually.
+  (multiple-value-bind (required optional restp rest keyp keys allowp auxp aux
+                        morep more-context more-count)
+      (parse-lambda-list-like-thing lambda-list)
+
+    ;; Check validity of parameters.
+    (flet ((need-symbol (x why)
+             (unless (or (symbolp x) t)
+               (format t "~A is not a symbol: ~S" why x))))
+      (dolist (i required)
+        (need-symbol i "Required argument"))
+      (dolist (i optional)
+        (typecase i
+          (symbol)
+          (cons
+           (destructuring-bind (var &optional init-form supplied-p) i
+             (declare (ignore init-form supplied-p))
+             (need-symbol var "&OPTIONAL parameter name")))
+          (t
+           (format t "&OPTIONAL parameter is not a symbol or cons: ~S"
+                           i))))
+      (when restp
+        (need-symbol rest "&REST argument"))
+      (when keyp
+        (dolist (i keys)
+          (typecase i
+            (symbol)
+            (cons
+             (destructuring-bind (var-or-kv &optional init-form supplied-p) i
+               (declare (ignore init-form supplied-p))
+               (if (consp var-or-kv)
+                   (destructuring-bind (keyword-name var) var-or-kv
+                     (declare (ignore keyword-name))
+                     (need-symbol var "&KEY parameter name"))
+                   (need-symbol var-or-kv "&KEY parameter name"))))
+            (t
+             (format t "&KEY parameter is not a symbol or cons: ~S"
+                             i))))))
+
+    ;; Voila.
+    (values required optional restp rest keyp keys allowp auxp aux
+            morep more-context more-count)))
dissimilarity index 89%
index d196b75..bccae90 100644 (file)
-(in-package :parenscript)
-
-;;; special forms
-
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (defvar *js-special-forms* (make-hash-table :test 'equal)
-        "A hash-table containing functions that implement ParenScript
-special forms, indexed by name (a string).")
-
-  (defun undefine-js-special-form (name)
-    (when (gethash (symbol-name name) *js-special-forms*)
-      (warn "Redefining ParenScript special form ~S" name)
-      (remhash (symbol-name name) *js-special-forms*))))
-
-(defmacro define-js-special-form (name lambda-list &rest body)
-  "Define a special form NAME. Arguments are destructured according to
-LAMBDA-LIST. The resulting JS language types are appended to the
-ongoing javascript compilation."
-  (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*))
-        (arglist (gensym "ps-arglist-")))
-    `(eval-when (:compile-toplevel :load-toplevel :execute)
-      (defun ,js-name (&rest ,arglist)
-        (destructuring-bind ,lambda-list
-            ,arglist
-          ,@body))
-      (setf (gethash ,(symbol-name name) *js-special-forms*) #',js-name))))
-
-(defun js-special-form-p (form)
-  (and (consp form)
-       (symbolp (car form))
-       (gethash (symbol-name (car form)) *js-special-forms*)))
-
-(defun js-get-special-form (name)
-  (when (symbolp name)
-    (gethash (symbol-name name) *js-special-forms*)))
-
-;;; macro expansion
-
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (defun make-macro-env-dictionary ()
-    (make-hash-table :test 'equal))
-  
-  (defvar *js-macro-toplevel* (make-macro-env-dictionary)
-    "Toplevel macro environment dictionary. Key is symbol-name of the macro, value is (symbol-macro-p . expansion-function).")
-  (defvar *js-macro-env* (list *js-macro-toplevel*)
-    "Current macro environment."))
-
-(defmacro get-macro-spec (name env-dict)
-  `(gethash (symbol-name ,name) ,env-dict))
-
-(defun lookup-macro-spec (name &optional (environment *js-macro-env*))
-  (when (symbolp name)
-    (do ((env environment (cdr env)))
-        ((null env) nil)
-      (let ((val (get-macro-spec name (car env))))
-        (when val
-          (return-from lookup-macro-spec
-            (values val (or (cdr env)
-                            (list *js-macro-toplevel*)))))))))
-
-(defun symbol-macro-p (name &optional (environment *js-macro-env*))
-  (and (symbolp name) (car (lookup-macro-spec name environment))))
-
-(defun macro-p (name &optional (environment *js-macro-env*))
-  (and (symbolp name) (let ((macro-spec (lookup-macro-spec name environment)))
-                        (and macro-spec (not (car macro-spec))))))
-
-(defun lookup-macro-expansion-function (name &optional (environment *js-macro-env*))
-  "Lookup NAME in the given macro expansion environment (which
-defaults to the current macro environment). Returns the expansion
-function and the parent macro environment of the macro."
-  (multiple-value-bind (macro-spec parent-env)
-      (lookup-macro-spec name environment)
-    (values (cdr macro-spec) parent-env)))
-
-(defmacro defjsmacro (name args &rest body)
-  "Define a ParenScript macro, and store it in the toplevel ParenScript macro environment."
-  (let ((lambda-list (gensym "ps-lambda-list-"))
-        (body (if (stringp (first body)) (rest body) body))) ;; drop docstring
-    (undefine-js-special-form name)
-    `(setf (get-macro-spec ',name *js-macro-toplevel*)
-      (cons nil (lambda (&rest ,lambda-list)
-                  (destructuring-bind ,args
-                      ,lambda-list
-                    ,@body))))))
-
-(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)
-          (js:defjsmacro ,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 *js-macro-toplevel*)
-            (cons nil (lambda (&rest args)
-                        (macroexpand `(,name ,@args))))))))
-
-(defun js-expand-form (expr)
-  (if (consp expr)
-      (let ((op (car expr))
-            (args (cdr expr)))
-        (cond ((equal op 'quote) expr)
-              ((macro-p op) (multiple-value-bind (expansion-function macro-env)
-                                (lookup-macro-expansion-function op)
-                              (js-expand-form (let ((*js-macro-env* macro-env))
-                                                (apply expansion-function args)))))
-              (t expr)))
-      (cond ((js-special-form-p expr) expr)
-            ((symbol-macro-p expr) (multiple-value-bind (expansion-function macro-env)
-                                       (lookup-macro-expansion-function expr)
-                                     (js-expand-form (let ((*js-macro-env* macro-env))
-                                                       (funcall expansion-function)))))
-            (t expr))))
-
-(defvar *gen-js-name-counter* 0)
-
-(defun gen-js-name-string (&key (prefix "_ps_"))
-  "Generates a unique valid javascript identifier ()"
-  (concatenate 'string
-               prefix (princ-to-string (incf *gen-js-name-counter*))))
-
-(defun gen-js-name (&key (prefix "_ps_"))
-  "Generate a new javascript identifier."
-  (intern (gen-js-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-js-name :prefix ,prefix))
-                          `(,symbol (gen-js-name)))))
-                  symbols)
-     ,@body))
-
-(defjsmacro 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))))))
-
-(defvar *var-counter* 0)
-
-(defun js-gensym (&optional (name "js"))
-  (intern (format nil "tmp-~A-~A" name (incf *var-counter*)) #.*package*))
-
-;;; reserved Javascript keywords
-
-(defvar *reserved-javascript-keywords*
-  '("abstract" "else" "instanceof" "switch" "boolean" "enum" "int" "synchronized"
-    "break" "export" "interface" "this" "byte" "extends" "long" "throw" "case"
-    "native" "throws" "catch" "final" "new" "transient" "char" "finally" "float"
-    "package" "try" "const" "for" "private" "typeof" "continue" "function"
-    "protected" "var" "debugger" "goto" "public" "void" "default" "if" "return"
-    "volatile" "delete" "implements" "short" "while" "do" "import" "static" "with"
-    "double" "in" "super" "class"))
-
-(defun reserved-identifier-p (id-string)
-  (find id-string *reserved-javascript-keywords* :test #'string-equal))
-
-(defmethod initialize-instance :after ((var js-variable) &rest initargs)
-  (declare (ignore initargs))
-  (when (reserved-identifier-p (slot-value var 'value))
-    (warn "~a is a reserved Javascript keyword and should not be used as a variable or function name." (slot-value var 'value))))
-
-;;; literals
-
-(defmacro defjsliteral (name string)
-  "Define a Javascript literal that will expand to STRING."
-  `(define-js-special-form ,name () (make-instance 'expression :value ,string)))
-
-(defjsliteral this      "this")
-(defjsliteral t         "true")
-(defjsliteral nil       "null")
-(defjsliteral false     "false")
-(defjsliteral undefined "undefined")
-
-(defmacro defjskeyword (name string)
-  "Define a Javascript keyword that will expand to STRING."
-  `(define-js-special-form ,name () (make-instance 'statement :value ,string)))
-
-(defjskeyword break    "break")
-(defjskeyword continue "continue")
-
-;;; array literals
-
-(define-js-special-form array (&rest values)
-  (make-instance 'array-literal
-                :values (mapcar #'js-compile-to-expression values)))
-
-(defjsmacro list (&rest values)
-  `(array ,@values))
-
-(define-js-special-form aref (array &rest coords)
-  (make-instance 'js-aref
-                :array (js-compile-to-expression array)
-                :index (mapcar #'js-compile-to-expression coords)))
-
-
-(defjsmacro make-array (&rest inits)
-  `(new (*array ,@inits)))
-
-;;; object literals (maps and hash-tables)
-
-(define-js-special-form {} (&rest values)
-  (make-instance 'object-literal
-                 :values (loop
-                            for (key value) on values by #'cddr
-                            collect (cons key (js-compile-to-expression value)))))
-
-;;; operators
-(define-js-special-form ++ (x)
-  (make-instance 'one-op :pre-p nil :op "++"
-                :value (js-compile-to-expression x)))
-
-(define-js-special-form -- (x)
-  (make-instance 'one-op :pre-p nil :op "--"
-                :value (js-compile-to-expression x)))
-
-(define-js-special-form incf (x &optional (delta 1))
-  (if (eql delta 1)
-      (make-instance 'one-op :pre-p t :op "++"
-                     :value (js-compile-to-expression x))
-      (make-instance 'op-form
-                     :operator '+=
-                     :args (mapcar #'js-compile-to-expression
-                                   (list x delta )))))
-
-(define-js-special-form decf (x &optional (delta 1))
-  (if (eql delta 1)
-      (make-instance 'one-op :pre-p t :op "--"
-                     :value (js-compile-to-expression x))
-      (make-instance 'op-form
-                     :operator '-=
-                     :args (mapcar #'js-compile-to-expression
-                                   (list x delta )))))
-
-(define-js-special-form - (first &rest rest)
-  (if (null rest)
-      (make-instance 'one-op
-                     :pre-p t
-                     :op "-"
-                     :value (js-compile-to-expression first))
-      (make-instance 'op-form
-                     :operator '-
-                     :args (mapcar #'js-compile-to-expression
-                                   (cons first rest)))))
-
-(define-js-special-form not (x)
-  (let ((value (js-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-js-special-form ~ (x)
-  (let ((expr (js-compile-to-expression x)))
-    (make-instance 'one-op :pre-p t :op "~" :value expr)))
-
-;;; function calls
-
-(defun funcall-form-p (form)
-  (and (listp form)
-       (not (op-form-p form))
-       (not (js-special-form-p form))))
-
-(defun method-call-p (form)
-  (and (funcall-form-p form)
-       (symbolp (first form))
-       (eql (char (symbol-name (first form)) 0) #\.)))
-
-;;; progn
-
-(define-js-special-form progn (&rest body)
-  (make-instance 'js-body
-                :stmts (mapcar #'js-compile-to-statement body)))
-
-(defmethod expression-precedence ((body js-body))
-  (if (= (length (b-stmts body)) 1)
-      (expression-precedence (first (b-stmts body)))
-      (op-precedence 'comma)))
-
-;;; function definition
-(define-js-special-form lambda (args &rest body)
-  (make-instance 'js-lambda
-                 :args (mapcar #'js-compile-to-symbol args)
-                 :body (make-instance 'js-body
-                                      :indent "  "
-                                      :stmts (mapcar #'js-compile-to-statement body))))
-
-(define-js-special-form defun (name args &rest body)
-  (make-instance 'js-defun
-                :name (js-compile-to-symbol name)
-                :args (mapcar #'js-compile-to-symbol args)
-                :body (make-instance 'js-body
-                                     :indent "  "
-                                     :stmts (mapcar #'js-compile-to-statement body))))
-
-;;; object creation
-(define-js-special-form create (&rest args)
-  (make-instance 'js-object
-                :slots (loop for (name val) on args by #'cddr
-                             collect (let ((name-expr (js-compile-to-expression name)))
-                                       (assert (or (typep name-expr 'js-variable)
-                                                   (typep name-expr 'string-literal)
-                                                   (typep name-expr 'number-literal)))
-                                       (list name-expr (js-compile-to-expression val))))))
-
-
-(define-js-special-form slot-value (obj slot)
-  (make-instance 'js-slot-value :object (js-compile-to-expression obj)
-                  :slot (js-compile slot)))
-
-;;; cond
-(define-js-special-form cond (&rest clauses)
-  (make-instance 'js-cond
-                :tests (mapcar (lambda (clause) (js-compile-to-expression (car clause)))
-                               clauses)
-                :bodies (mapcar (lambda (clause) (js-compile-to-body (cons 'progn (cdr clause)) :indent "  "))
-                                clauses)))
-
-;;; if
-(define-js-special-form if (test then &optional else)
-  (make-instance 'js-if :test (js-compile-to-expression test)
-                :then (js-compile-to-body then :indent "  ")
-                :else (when else
-                        (js-compile-to-body else :indent "  "))))
-
-(defmethod expression-precedence ((if js-if))
-  (op-precedence 'if))
-
-;;; switch
-(define-js-special-form switch (value &rest clauses)
-  (let ((clauses (mapcar #'(lambda (clause)
-                            (let ((val (first clause))
-                                  (body (cdr clause)))
-                              (list (if (eql val 'default)
-                                        'default
-                                        (js-compile-to-expression val))
-                                    (js-compile-to-body (cons 'progn body) :indent "  "))))
-                        clauses))
-       (check (js-compile-to-expression value)))
-    (make-instance 'js-switch :value check
-                  :clauses clauses)))
-
-
-(defjsmacro 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 'js-setf :lhs lhs :rhsides (list rhs)))))
-      (make-instance 'js-setf :lhs lhs :rhsides (list rhs))))
-
-(define-js-special-form setf (&rest args)
-  (let ((assignments (loop for (lhs rhs) on args by #'cddr
-                          for rexpr = (js-compile-to-expression rhs)
-                          for lexpr = (js-compile-to-expression lhs)
-                          collect (make-js-test lexpr rexpr))))
-    (if (= (length assignments) 1)
-       (first assignments)
-       (make-instance 'js-body :indent "" :stmts assignments))))
-
-(defmethod expression-precedence ((setf js-setf))
-  (op-precedence '=))
-
-;;; defvar
-(define-js-special-form defvar (name &optional value)
-  (make-instance 'js-defvar :names (list (js-compile-to-symbol name))
-                :value (when value (js-compile-to-expression value))))
-
-;;; let
-(define-js-special-form let (decls &rest body)
-  (let ((defvars (mapcar #'(lambda (decl)
-                            (if (atom decl)
-                                 (make-instance 'js-defvar
-                                       :names (list (js-compile-to-symbol decl))
-                                       :value nil)
-                                 (let ((name (first decl))
-                                       (value (second decl)))
-                                   (make-instance 'js-defvar
-                                                  :names (list (js-compile-to-symbol name))
-                                                  :value (js-compile-to-expression value)))))
-                        decls)))
-    (make-instance 'js-sub-body
-                  :indent "  "
-                  :stmts (nconc defvars
-                                (mapcar #'js-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 'js-defvar :names (list (js-compile-to-symbol var))
-                              :value (js-compile-to-expression init))))
-
-(defun make-for-steps (decls)
-  (loop for decl in decls
-       when (= (length decl) 3)
-       collect (js-compile-to-expression (third decl))))
-
-(define-js-special-form do (decls termination &rest body)
-  (let ((vars (make-for-vars decls))
-       (steps (make-for-steps decls))
-       (check (js-compile-to-expression (list 'not (first termination))))
-       (body (js-compile-to-body (cons 'progn body) :indent "  ")))
-    (make-instance 'js-for
-                  :vars vars
-                  :steps steps
-                  :check check
-                  :body body)))
-
-(defjsmacro dotimes (iter &rest body)
-  (let ((var (first iter))
-        (times (second iter)))
-  `(do ((,var 0 (1+ ,var)))
-       ((>= ,var ,times))
-     ,@body)))
-
-(defjsmacro dolist (i-array &rest body)
-  (let ((var (first i-array))
-       (array (second i-array))
-       (arrvar (js-gensym "arr"))
-       (idx (js-gensym "i")))
-    `(let ((,arrvar ,array))
-      (do ((,idx 0 (1+ ,idx)))
-         ((>= ,idx (slot-value ,arrvar 'length)))
-       (let ((,var (aref ,arrvar ,idx)))
-         ,@body)))))
-
-(define-js-special-form doeach (decl &rest body)
-  (make-instance 'for-each :name (js-compile-to-symbol (first decl))
-                :value (js-compile-to-expression (second decl))
-                :body (js-compile-to-body (cons 'progn body) :indent "  ")))
-
-(define-js-special-form while (check &rest body)
-  (make-instance 'js-while
-                :check (js-compile-to-expression check)
-                :body (js-compile-to-body (cons 'progn body) :indent "  ")))
-
-;;; with
-
-(define-js-special-form with (statement &rest body)
-  (make-instance 'js-with
-                :obj (js-compile-to-expression statement)
-                :body (js-compile-to-body (cons 'progn body) :indent "  ")))
-
-;;; try-catch
-(define-js-special-form try (body &rest clauses)
-  (let ((body (js-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 'js-try
-                  :body body
-                  :catch (when catch (list (js-compile-to-symbol (caar catch))
-                                           (js-compile-to-body (cons 'progn (cdr catch))
-                                                               :indent "  ")))
-                  :finally (when finally (js-compile-to-body (cons 'progn finally)
-                                                             :indent "  ")))))
-;;; regex
-(define-js-special-form regex (regex)
-  (make-instance 'regex :value (string regex)))
-
-;;; TODO instanceof
-(define-js-special-form instanceof (value type)
-  (make-instance 'js-instanceof
-                 :value (js-compile-to-expression value)
-                 :type (js-compile-to-expression type)))
-
-;;; single operations
-(defmacro define-parse-js-single-op (name &optional (superclass 'expression))
-  (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
-    `(define-js-special-form ,name (value)
-       (make-instance ',js-name :value (js-compile-to-expression value)))
-    ))
-
-(define-parse-js-single-op return statement)
-(define-parse-js-single-op throw statement)
-(define-parse-js-single-op delete)
-(define-parse-js-single-op void)
-(define-parse-js-single-op typeof)
-(define-parse-js-single-op new)
-
-;;; conditional compilation
-(define-js-special-form cc-if (test &rest body)
-  (make-instance 'cc-if :test test
-                :body (mapcar #'js-compile body)))
-
-;;; standard macros
-(defjsmacro with-slots (slots object &rest body)
-  `(symbol-macrolet ,(mapcar #'(lambda (slot)
-                                `(,slot '(slot-value ,object ',slot)))
-                            slots)
-    ,@body))
-
-(defjsmacro when (test &rest body)
-  `(if ,test (progn ,@body)))
-
-(defjsmacro unless (test &rest body)
-  `(if (not ,test) (progn ,@body)))
-
-(defjsmacro 1- (form)
-  `(- ,form 1))
-
-(defjsmacro 1+ (form)
-  `(+ ,form 1))
-
-;;; macros
-(defmacro with-temp-macro-environment ((var) &body body)
-  `(let* ((,var (make-macro-env-dictionary))
-          (*js-macro-env* (cons ,var *js-macro-env*)))
-    ,@body))
-
-(define-js-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))))))))
-    (js-compile `(progn ,@body))))
-
-(define-js-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))))))
-    (js-compile `(progn ,@body))))
-
-(defjsmacro defmacro (name args &body body)
-  `(lisp (defjsmacro ,name ,args ,@body) nil))
-
-(defjsmacro 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)))
-
-;;; Math library
-(defjsmacro floor (expr)
-  `(*Math.floor ,expr))
-
-(defjsmacro random ()
-  `(*Math.random))
-
-(defjsmacro evenp (num)
-  `(= (% ,num 2) 0))
-
-(defjsmacro oddp (num)
-  `(= (% ,num 2) 1))
-
-;;; helper macros
-(define-js-special-form js (&rest body)
-  (make-instance 'string-literal
-                :value (string-join (js-to-statement-strings
-                                     (js-compile (cons 'progn body)) 0) " ")))
-
-(define-js-special-form js-inline (&rest body)
-  (make-instance 'string-literal
-                :value (concatenate
-                        'string
-                        "javascript:"
-                        (string-join (js-to-statement-strings
-                                      (js-compile (cons 'progn body)) 0) " "))))
-
-;;;; compiler interface ;;;;
-(defun js-compile (form)
-  (setf form (js-expand-form form))
-  (cond ((stringp form)
-        (make-instance 'string-literal :value form))
-        ((characterp form)
-        (make-instance 'string-literal :value (string form)))
-       ((numberp form)
-        (make-instance 'number-literal :value form))
-       ((symbolp form)
-        (let ((c-macro (js-get-special-form form)))
-          (if c-macro
-              (funcall c-macro)
-              (make-instance 'js-variable :value form))))
-       ((and (consp form)
-             (eql (first form) 'quote))
-        (make-instance 'js-quote :value (second form)))
-       ((consp form)
-        (js-compile-list form))
-       (t (error "Unknown atomar expression ~S" form))))
-
-(defun js-compile-list (form)
-  (let* ((name (car form))
-        (args (cdr form))
-        (js-form (js-get-special-form name)))
-    (cond (js-form
-          (apply js-form args))
-
-         ((op-form-p form)
-          (make-instance 'op-form
-                         :operator (js-convert-op-name (js-compile-to-symbol (first form)))
-                         :args (mapcar #'js-compile-to-expression (rest form))))
-
-         ((method-call-p form)
-          (make-instance 'method-call
-                         :method (js-compile-to-symbol (first form))
-                         :object (js-compile-to-expression (second form))
-                         :args (mapcar #'js-compile-to-expression (cddr form))))
-
-         ((funcall-form-p form)
-          (make-instance 'function-call
-                         :function (js-compile-to-expression (first form))
-                         :args (mapcar #'js-compile-to-expression (rest form))))
-
-         (t (error "Unknown form ~S" form)))))
-
-(defun js-compile-to-expression (form)
-  (let ((res (js-compile form)))
-    (assert (typep res 'expression))
-    res))
-
-(defun js-compile-to-symbol (form)
-  (let ((res (js-compile form)))
-    (when (typep res 'js-variable)
-      (setf res (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)
-    res))
-
-(defun js-compile-to-statement (form)
-  (let ((res (js-compile form)))
-    (assert (typep res 'statement))
-    res))
-
-(defun js-compile-to-body (form &key (indent ""))
-  (let ((res (js-compile-to-statement form)))
-    (if (typep res 'js-body)
-       (progn (setf (b-indent res) indent)
-              res)
-       (make-instance 'js-body
-                      :indent indent
-                      :stmts (list res)))))
-
-(defmacro js (&rest body)
-  `(js* '(progn ,@body)))
-
-(defmacro js* (&rest body)
-  "Return the javascript string representing BODY.
-
-Body is evaluated."
-  `(string-join
-    (js-to-statement-strings (js-compile (list 'progn ,@body)) 0)
-    (string #\Newline)))
-
-(defun js-to-string (expr)
-  (string-join
-   (js-to-statement-strings (js-compile expr) 0)
-   (string #\Newline)))
-
-(defun js-to-line (expr)
-  (string-join
-   (js-to-statement-strings (js-compile expr) 0) " "))
-
-(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 (js-compile (list 'progn ,@body)) 0) " ")))
-
-
+(in-package :parenscript)
+
+;;;; 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
+                 :documentation "Canonical name of the package (a String).")
+   (nicknames     :accessor script-package-nicknames     :initform nil :initarg :nicknames
+                 :documentation "List of nicknames for the package (as strings).")
+   (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
+                 :documentation "List of exported identifiers.")
+   (used-packages :accessor script-package-used-packages :initform nil :initarg :used-packages
+                 :documentation "")
+   (documentation :accessor script-package-documentation :initform nil :initarg :documentation)
+   (compilation-env :accessor script-package-comp-env    :initform nil :initarg :comp-env)
+   (locked?       :accessor script-package-locked? :initform nil :initarg :locked?
+                 :documentation "t if redefinition of top-level symbols is disallowed.")
+   ;; internal use slots
+   (exclusive-lisp-package-p
+    :initform nil :initarg :exclusive-lisp-package?
+    :accessor script-package-exclusive-lisp-package-p
+    :documentation "t if the lisp package is an anonymous package created exclusively for
+                    the script package.")
+   (toplevel-identifiers :accessor script-package-toplevel-ids :initarg :toplevel-ids
+                         :initform nil)
+   (macro-table   :accessor script-package-macro-table
+                  :initform (make-hash-table :test #'eql)
+                  :documentation "This package's macro environment, set up as a hash table
+                                  from symbols to macro functions")
+   (special-form-table :accessor script-package-special-form-table
+                      :initform (make-hash-table :test #'equal)
+                      :documentation "Holds special form macros for the package.
+                                       Probably not used except for built-in packages."))
+  (:documentation "A Parenscript package is a lisp object that holds information
+about a set of Suavescript code."))
+
+(defclass compilation-environment ()
+  ((script-packages :accessor comp-env-script-packages :initform nil :initarg :packages
+                   :documentation "List of packages defined in this environment.")
+   (current-package :accessor comp-env-current-package :initform nil :initarg :current-package
+                   :documentation "Current in-package.")
+   (lisp-to-script-package-table
+    :accessor comp-env-lisp-to-script-package-table :initform (make-hash-table)
+    :documentation   "Maps a lisp package to a script package."))
+  (:documentation ""))
+
+(defvar *compilation-environment* nil
+  "The active compilation environment.
+
+Right now all code assumes that *compilation-environment* is accurately bound to the
+current compilation environment--even some functions that take the compilation environment
+as arguments.")
+
+;;; parenscript packages
+(defun lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
+  "Gets a script package corresponding to the given Lisp package."
+  (gethash lisp-package (comp-env-lisp-to-script-package-table comp-env)))
+
+(defsetf lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
+    (script-package)
+  "Sets the script package corresponding to the given Lisp package."
+  `(setf (gethash ,lisp-package (comp-env-lisp-to-script-package-table ,comp-env))
+    ,script-package))
+
+(defun symbol-script-package (symbol &optional (comp-env *compilation-environment*))
+  "Gets the Parenscript package associated with a Lisp symbol."
+  (lisp-to-script-package (symbol-package symbol) comp-env))
+
+(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))
+
+(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))))
+
+;; environmental considerations
+(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))
+
+(defun create-script-package (comp-env
+                             &key name nicknames secondary-lisp-packages used-packages
+                             lisp-package exports documentation)
+  "Creates a script package in the given compilation environment"
+  (labels ((normalize (string-like) (string string-like)))
+    (let*  ((explicit-lisp-package-p (not (null lisp-package)))
+           (lisp-package
+            (or (and explicit-lisp-package-p (find-package lisp-package))
+                (make-package (gensym (string name))))))
+      (labels ((package-intern (string-like)
+                (intern (normalize string-like) lisp-package)))
+       (let ((script-package
+              (make-instance 'script-package
+                             :name (normalize name)
+                             :comp-env comp-env
+                             :nicknames (mapcar #'normalize nicknames)
+                             :lisp-package (find-package lisp-package)
+                             :secondary-lisp-packages (mapcar #'find-package secondary-lisp-packages)
+                             :exclusive-lisp-package? (not explicit-lisp-package-p)
+                             :exports (mapcar #'package-intern exports)
+                             :used-packages (mapcar #'(lambda (script-package-designator)
+                                                        (find-script-package
+                                                         script-package-designator comp-env))
+                                                    used-packages)
+                             :documentation documentation)))
+         (push script-package (comp-env-script-packages comp-env)))))))
+
+(defmethod initialize-instance :after ((package script-package) &key)
+  (assert (script-package-comp-env package))
+  (assert (script-package-lisp-package package))
+  (let ((lisp-packages (cons (script-package-lisp-package package)
+                       (script-package-secondary-lisp-packages package))))
+    (dolist (lisp-package lisp-packages)
+      (when (lisp-to-script-package lisp-package (script-package-comp-env package))
+       (error "Lisp package already has corresponding script package: ~A" (package-name lisp-package)))
+      (setf (lisp-to-script-package lisp-package (script-package-comp-env package))
+           package))))
+
+(defgeneric comp-env-find-package (comp-env package-designator)
+  (:documentation "Finds the script package named by PACKAGE-DESIGNATOR in the current 
+compilation environment. PACKAGE-DESIGNATOR is a string or symbol.")
+  (:method ((comp-env compilation-environment) (name string))
+    (find name (comp-env-script-packages comp-env)
+         :key #'script-package-name :test #'equal))
+  (:method ((comp-env compilation-environment) (package-designator symbol))
+    (comp-env-find-package comp-env (string package-designator))))
+
+;; TODO loop through all defined macros and add them to the script package's
+;; macro environment
+;        (labels ((name-member (name)
+;                   (eql (script-package-lisp-package script-package) (symbol-package name)))
+;                 (import-macro (name function)
+;                   (when (name-member name)
+;                     (setf (gethash name (script-package-macro-table script-package))
+;                           function)))
+;                 (import-special-form (name function)
+;                   (when (name-member name)
+;                     (setf (gethash name (script-package-special-form-table script-package))
+;                           function))))
+;        (maphash #'import-special-form *toplevel-special-forms*)
+;        (maphash #'import-special-form *toplevel-special-forms*)
+
+;(defgeneric comp-env-select-package (comp-env script-package)
+;  (:documentation "")
+;  (:method ((comp-env compilation-environment) (package script-package))
+;    (setf (comp-env-current-package 
+  
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (defvar *toplevel-special-forms* (make-hash-table)
+    "A hash-table containing functions that implement Parenscript special forms,
+indexed by name (as symbols)")
+  
+  (defun undefine-script-special-form (name)
+    "Undefines the special form with the given name (name is a symbol)."
+    (declare (type symbol name))
+    (when (gethash name *toplevel-special-forms*)
+      (remhash name *toplevel-special-forms*))))
+
+(defmacro define-script-special-form (name lambda-list &rest body)
+  "Define a special form NAME. Arguments are destructured according to
+LAMBDA-LIST. The resulting Parenscript language types are appended to the
+ongoing javascript compilation."
+  (declare (type symbol name))
+  (let ((script-name 
+        (intern (format nil "PAREN-~A" (symbol-name name))
+                (find-package :parenscript)))
+       (arglist (gensym "ps-arglist-")))
+    `(eval-when (:compile-toplevel :load-toplevel :execute)
+      (defun ,script-name (&rest ,arglist)
+       (destructuring-bind ,lambda-list
+           ,arglist
+         ,@body))
+      (setf (gethash (quote ,name) *toplevel-special-forms*) #',script-name))))
+
+(defun get-script-special-form (name)
+  "Returns the special form function corresponding to the given name."
+; (declare (type symbol name))
+  (when (symbolp name)
+    (gethash name *toplevel-special-forms*)))
+
+;;; sexp form predicates
+(defun script-special-form-p (form)
+  "Returns T if FORM is a special form and NIL otherwise."
+  (and (consp form)
+       (symbolp (car form))
+       (gethash (car form) *toplevel-special-forms*)))
+
+(defun funcall-form-p (form)
+  (and (listp form)
+       (not (op-form-p form))
+       (not (script-special-form-p form))))
+
+(defun method-call-p (form)
+  (and (funcall-form-p form)
+       (symbolp (first form))
+       (eql (char (symbol-name (first form)) 0) #\.)))
+
+;;; macro expansion
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (defun make-macro-env-dictionary ()
+    "Creates a standard macro dictionary."
+    (make-hash-table))
+  (defvar *script-macro-toplevel* (make-macro-env-dictionary)
+    "Toplevel macro environment dictionary. Key is symbol-name of the macro, value
+is (symbol-macro-p . expansion-function).")
+  (defvar *script-macro-env* (list *script-macro-toplevel*) ;(list nil)
+    "Current macro environment."))
+
+(defmacro get-macro-spec (name env-dict)
+  "Retrieves the macro spec of the given name with the given environment dictionary.
+SPEC is of the form (symbol-macro-op expansion-function)."
+  `(gethash ,name ,env-dict))
+
+(defun lookup-macro-spec (name &optional (environment *script-macro-env*))
+  "Looks up the macro spec associated with NAME in the given environment.  A
+macro spec is of the form (symbol-macro-p function). Returns two values:
+the SPEC and the parent macro environment.
+
+NAME must be a symbol."
+  (when (symbolp name)
+    (do ((env environment (cdr env)))
+        ((null env) nil)
+      (let ((val (get-macro-spec name (car env))))
+        (when val
+          (return-from lookup-macro-spec
+            (values val (or (cdr env)
+                            (list *script-macro-toplevel*)))))))))
+
+(defun script-symbol-macro-p (name &optional (environment *script-macro-env*))
+  "True if there is a Parenscript symbol macro named by the symbol NAME."
+  (and (symbolp name) (car (lookup-macro-spec name environment))))
+
+(defun script-macro-p (name &optional (environment *script-macro-env*))
+  "True if there is a Parenscript macro named by the symbol NAME."
+  (and (symbolp name)
+       (let ((macro-spec (lookup-macro-spec name environment)))
+        (and macro-spec (not (car macro-spec))))))
+
+(defun lookup-macro-expansion-function (name &optional (environment *script-macro-env*))
+  "Lookup NAME in the given macro expansion environment (which
+defaults to the current macro environment). Returns the expansion
+function and the parent macro environment of the macro."
+  (multiple-value-bind (macro-spec parent-env)
+      (lookup-macro-spec name environment)
+    (values (cdr macro-spec) parent-env)))
+
+(defmacro defscriptmacro (name args &body body)
+  "Define a ParenScript macro, and store it in the toplevel ParenScript
+macro environment."
+  (let ((lambda-list (gensym "ps-lambda-list-"))
+        (body (if (stringp (first body)) (rest body) body))) ;; drop docstring
+    (undefine-script-special-form name)
+    `(setf (get-macro-spec ',name *script-macro-toplevel*)
+      (cons nil (lambda (&rest ,lambda-list)
+                  (destructuring-bind ,args
+                      ,lambda-list
+                    ,@body))))))
+
+(defmacro defpsmacro (name args &body body)
+  `(defscriptmacro (,name ,args ,@body)))
+
+(defun expand-script-form (expr)
+  "Expands a Parenscript form down to special forms."
+  (if (consp expr)
+      (let ((op (car expr))
+            (args (cdr expr)))
+        (cond ((equal op 'quote) expr) ;; leave quotes alone
+              ((script-macro-p op) ;; recursively expand parenscript macros in parent env.
+              (multiple-value-bind (expansion-function macro-env)
+                  (lookup-macro-expansion-function op)
+                (expand-script-form (let ((*script-macro-env* macro-env))
+                                     (apply expansion-function args)))))
+              (t expr)))
+      ;; not a cons
+      (cond ((script-special-form-p expr)
+            ;; leave special forms alone (expanded during compile)
+            expr) 
+            ((script-symbol-macro-p expr)
+            ;; recursively expand symbol macros in parent env.
+            (multiple-value-bind (expansion-function macro-env)
+                (lookup-macro-expansion-function expr)
+              (expand-script-form (let ((*script-macro-env* macro-env))
+                                   (funcall expansion-function)))))
+           ;; leave anything else alone
+            (t expr))))
+
+;;;; compiler interface ;;;;
+(defgeneric compile-parenscript-form (compilation-environment form)
+  (:documentation "Compiles FORM, which is a ParenScript form, into a pre-text
+compilation object (the AST root).  Subsequently TRANSLATE-AST can be called
+to convert the result to Javascript."))
+
+(defmethod compile-parenscript-form ((comp-env compilation-environment) form)
+  (setf form (expand-script-form form))
+  (cond ((stringp form)
+        (make-instance 'string-literal :value form))
+        ((characterp form)
+        (make-instance 'string-literal :value (string form)))
+       ((numberp form)
+        (make-instance '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))))
+       ((and (consp form)
+             (eql (first form) 'quote))
+        (make-instance 'script-quote :value (second form)))
+       ((consp form)
+        (let* ((name (car form))
+               (args (cdr form))
+               (script-form (get-script-special-form name)))
+          (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)))
+                                :args (mapcar #'compile-to-expression (rest form))))
+                
+                ((method-call-p form)
+                 (make-instance '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
+                                :function (compile-to-expression (first form))
+                                :args (mapcar #'compile-to-expression (rest form))))
+                
+                (t (error "Unknown form ~S" form)))))
+       (t (error "Unknown atomar expression ~S" form))))
+
+(defun compile-script-form (form &key (comp-env *compilation-environment*))
+  "Compiles a Parenscript form to an AST node."
+  (compile-parenscript-form *compilation-environment* form ))
+
+(defun compile-to-expression (form)
+  "Compiles the given Parenscript form and guarantees the result is an expression."
+  (let ((res (compile-script-form form)))
+    (assert (typep res 'expression))
+    res))
+
+(defun compile-to-symbol (form)
+  "Compiles the given Parenscript form and guarantees a symbolic result."
+  (let ((res (compile-script-form form)))
+    (when (typep res 'script-variable)
+      (setf res (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)
+    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))
+    res))
+
+(defun compile-to-body (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)
+              res)
+       (make-instance 'script-body
+                      :indent indent
+                      :statements (list res)))))
\ No newline at end of file
diff --git a/src/reader.lisp b/src/reader.lisp
new file mode 100644 (file)
index 0000000..d137de4
--- /dev/null
@@ -0,0 +1,809 @@
+c;; 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 $
+;; 
+;; Redistribution and use in source and binary forms, with or without
+;; modification, are permitted provided that the following conditions
+;; are met:
+;; 
+;;  * Redistributions of source code must retain the above copyright
+;;    notice, this list of conditions and the following disclaimer.
+;;  * Redistributions in binary form must reproduce the above copyright
+;;    notice, this list of conditions and the following disclaimer in
+;;    the documentation and/or other materials provided with the
+;;    distribution.
+;; 
+;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+;; (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)
+
+(defstruct (readtable (:predicate readtablep) (:copier nil))
+  (syntax (make-hash-table) :type hash-table)
+  (case :upcase :type (member :upcase :downcase :preserve :invert)))
+
+(defvar *read-base* '10)
+(defvar *read-default-float-format* 'single-float)
+(defvar *read-eval* 't)
+(defvar *read-suppress* 'nil)
+(defvar *readtable*)
+
+
+(defvar *sharp-equal-alist* nil)
+(defvar *consing-dot-allowed* nil)
+(defvar *consing-dot* (gensym))
+(defvar *preserve-whitespace-p* nil)
+(defvar *input-stream* nil)
+(defvar *backquote-level* 0)
+(defvar *dispatch-macro-char* nil)
+(defvar *standard-readtable*)
+
+(define-condition reader-error (parse-error)
+  ((format-control :reader reader-error-format-control :initarg :format-control)
+   (format-arguments :reader reader-error-format-arguments
+                    :initarg :format-arguments)))
+
+(define-condition invalid-character-error (reader-error)
+  ((character :type character :reader invalid-character-error-character
+              :initarg :character))
+  (:report
+   (lambda (condition stream)
+     (format stream "Invalid character ~S is read."
+             (invalid-character-error-character condition)))))
+
+(defun reader-error (&optional format-control &rest format-arguments)
+  (error 'reader-error
+         :format-control format-control :format-arguments format-arguments))
+
+(defun copy-readtable (&optional (from-readtable *readtable*) to-readtable)
+  (flet ((copy-syntax (src)
+           (let ((new (make-hash-table)))
+             (maphash
+              #'(lambda (k v)
+                  (let ((plist (copy-list v)))
+                    (setf (gethash k new) plist)
+                    (when (getf plist :dispatch-table)
+                      (let ((hash (make-hash-table)))
+                        (maphash #'(lambda (k v) (setf (gethash k hash) v))
+                                 (getf plist :dispatch-table))
+                        (setf (getf plist :dispatch-table) hash)))))
+              src)
+             new)))
+    (let ((from (or from-readtable *standard-readtable*)))
+      (if to-readtable
+          (prog1 to-readtable
+            (setf (readtable-syntax to-readtable)
+                  (copy-syntax (readtable-syntax from)))
+            (setf (readtable-case to-readtable) (readtable-case from)))
+          (make-readtable :syntax (copy-syntax (readtable-syntax from))
+                          :case (readtable-case from))))))
+
+(defun syntax-type (char &optional (readtable *readtable*))
+  (let ((plist (gethash char (readtable-syntax readtable))))
+    (getf plist :syntax :constituent)))
+
+(defun get-macro-character (char &optional (readtable *readtable*))
+  (unless readtable (setq readtable *standard-readtable*))
+  (let ((plist (gethash char (readtable-syntax readtable))))
+    (case (syntax-type char readtable)
+      (:terminating-macro-char (values (getf plist :macro-function) nil))
+      (:non-terminating-macro-char (values (getf plist :macro-function) t))
+      (t (values nil nil)))))
+
+(defun set-macro-character (char new-function
+                            &optional non-terminating-p (readtable *readtable*))
+  (check-type char character)
+;  (check-type new-function function-designator)
+  (when (null readtable)
+    (error "Standard readtable must not be changed."))
+  (let ((plist (gethash char (readtable-syntax readtable))))
+    (setf (getf plist :syntax) (if non-terminating-p
+                                   :non-terminating-macro-char
+                                   :terminating-macro-char)
+          (getf plist :macro-function) new-function
+          (gethash char (readtable-syntax readtable)) plist))
+  t)
+
+(defun get-dispatch-macro-character (disp-char sub-char
+                                     &optional (readtable *readtable*))
+  (unless readtable (setq readtable *standard-readtable*))
+  (unless (eq (get-macro-character disp-char readtable)
+              'dispatch-macro-character)
+    (error "~S is not a dispatching macro character." disp-char))
+  (let* ((syntax-table (readtable-syntax readtable))
+         (dispatch-table (getf (gethash disp-char syntax-table) :dispatch-table))
+         (sub-char (char-upcase sub-char)))
+    (multiple-value-bind (value present-p) (gethash sub-char dispatch-table)
+      (cond
+        ((digit-char-p sub-char 10) nil)
+        (present-p value)
+        (t
+         #'(lambda (stream sub-char number)
+             (declare (ignore stream number))
+             (reader-error "No dispatch function defined for ~S." sub-char)))))))
+
+(defun set-dispatch-macro-character (disp-char sub-char new-function
+                                     &optional (readtable *readtable*))
+  (when (null readtable) (error "Standard readtable must not be changed."))
+  (unless (eq (get-macro-character disp-char readtable)
+              'dispatch-macro-character)
+    (error "~S is not a dispatch character." disp-char))
+  (let* ((syntax-table (readtable-syntax readtable))
+         (dispatch-table (getf (gethash disp-char syntax-table) :dispatch-table))
+         (sub-char (char-upcase sub-char)))
+    (setf (gethash sub-char dispatch-table) new-function)
+    t))
+
+(defun make-dispatch-macro-character (char &optional non-terminating-p
+                                      (readtable *readtable*))
+  (when (null readtable) (error "Standard readtable must not be changed."))
+  (set-macro-character char 'dispatch-macro-character
+                       non-terminating-p readtable)
+  
+  (setf (getf (gethash char (readtable-syntax readtable)) :dispatch-table)
+        (make-hash-table))
+  t)
+
+(defun dispatch-macro-character (stream char)
+  (let ((n (when (digit-char-p (peek-char nil stream t nil t) 10)
+             (loop
+              with n = 0
+              for digit = (read-char stream t nil t)
+              do (setq n (+ (* n 10) (digit-char-p digit 10)))
+              while (digit-char-p (peek-char nil stream t nil t) 10)
+              finally (return n))))
+        (*dispatch-macro-char* char)
+        (sub-char (char-upcase (read-char stream t nil t))))
+    (funcall (get-dispatch-macro-character char sub-char) stream sub-char n)))
+
+(defun set-syntax-from-char (to-char from-char
+                             &optional (to-readtable *readtable*)
+                             (from-readtable *standard-readtable*))
+  (check-type to-char character)
+  (check-type from-char character)
+  (check-type to-readtable readtable)
+  (unless from-readtable (setq from-readtable *standard-readtable*))
+  (check-type from-readtable readtable)
+  (let ((plist (copy-list (gethash from-char
+                                   (readtable-syntax from-readtable)))))
+    (when (getf plist :dispatch-table)
+      (let ((hash (make-hash-table)))
+        (maphash #'(lambda (k v) (setf (gethash k hash) v))
+                 (getf plist :dispatch-table))
+        (setf (getf plist :dispatch-table) hash)))
+    (setf (gethash to-char (readtable-syntax to-readtable)) plist)
+    t))
+
+;; (defmacro with-standard-io-syntax (&rest forms)
+;;   `(let ((*package* (find-package "CL-USER"))
+;;          (*print-array* t)
+;;          (*print-base* 10)
+;;          (*print-case* :upcase)
+;;          (*print-circle* nil)
+;;          (*print-escape* t)
+;;          (*print-gensym* t)
+;;          (*print-length* nil)
+;;          (*print-level* nil)
+;;          (*print-lines* nil)
+;;          (*print-miser-width* nil)
+;;          ;;(*print-pprint-dispatch* *standard-print-pprint-dispatch*)
+;;          (*print-pretty* nil)
+;;          (*print-radix* nil)
+;;          (*print-readably* t)
+;;          (*print-right-margin* nil)
+;;          (*read-base* 10)
+;;          (*read-default-float-format* 'single-float)
+;;          (*read-eval* t)
+;;          (*read-suppress* nil)
+;;          (*readtable* (copy-readtable nil)))
+;;     ,@forms))
+
+
+(defun read-preserving-whitespace (&optional (input-stream *standard-input*)
+                                   (eof-error-p t) eof-value recursive-p)
+  (let ((*preserve-whitespace-p* (if recursive-p *preserve-whitespace-p* t)))
+    (declare (special *preserve-whitespace-p*))
+    (read-lisp-object input-stream eof-error-p eof-value recursive-p)))
+
+(defun read (&optional (input-stream *standard-input*)
+             (eof-error-p t) eof-value recursive-p)
+  (let ((*preserve-whitespace-p* (when recursive-p *preserve-whitespace-p*)))
+    (declare (special *preserve-whitespace-p*))
+    (read-lisp-object input-stream eof-error-p eof-value recursive-p)))
+
+(defun read-from-string (string &optional (eof-error-p t) eof-value
+                         &key (start 0) end preserve-whitespace)
+  (let ((index nil))
+    (values (with-input-from-string (stream string :index index
+                                            :start start :end end)
+              (funcall (if preserve-whitespace
+                           #'read-preserving-whitespace
+                           #'read)
+                       stream eof-error-p eof-value))
+            index)))
+
+(defun make-str (chars)
+  (make-array (length chars) :element-type 'character :initial-contents chars))
+
+(defun read-list (char &optional (stream *standard-input*) recursive-p
+                  &key allow-consing-dot)
+  (let ((*sharp-equal-alist* (when recursive-p *sharp-equal-alist*))
+        (*consing-dot-allowed* allow-consing-dot)
+        c stack values)
+    (loop
+     (setq c (peek-char t stream t nil t))
+     (when (char= char c)
+       (when (eq (first stack) *consing-dot*)
+         (error "Nothing appears after . in list."))
+       (read-char stream t nil t)
+       (if (eq (second stack) *consing-dot*)
+           (return (nreconc (cddr stack) (first stack)))
+           (return (nreverse stack))))
+     (when (setq values (multiple-value-list (lisp-object? stream t nil t)))
+       (if (eq (second stack) *consing-dot*)
+           (error "More than one object follows . in list.")
+           (push (car values) stack))))))
+
+(defun read-delimited-list (char &optional (stream *standard-input*) recursive-p)
+  (let ((list (read-list char stream recursive-p)))
+    (unless *read-suppress* list)))
+
+(defun lisp-object? (stream eof-error-p eof-value recursive-p)
+  (loop
+   (let* ((c (read-char stream eof-error-p eof-value recursive-p)))
+     (when (and (not eof-error-p) (eq c eof-value)) (return eof-value))
+     (ecase (syntax-type c)
+       (:invalid (error 'invalid-character-error :character c))
+       (:whitespace 'skip)
+       ((:single-escape :multiple-escape :constituent)
+        (return (read-number-or-symbol stream c)))
+       ((:terminating-macro-char :non-terminating-macro-char)
+        (return (funcall (get-macro-character c) stream c)))))))
+
+(defun read-lisp-object (stream eof-error-p eof-value recursive-p)
+  (let ((*sharp-equal-alist* (when recursive-p *sharp-equal-alist*)))
+    (loop
+     (let ((values (multiple-value-list (lisp-object? stream
+                                                      eof-error-p eof-value
+                                                      recursive-p))))
+       (when values (return (unless *read-suppress* (car values))))))))
+
+(defun read-ch () (read-char *input-stream* nil nil t))
+(defun read-ch-or-die () (read-char *input-stream* t nil t))
+(defun unread-ch (c) (unread-char c *input-stream*))
+
+(defun collect-escaped-lexemes (c)
+  (ecase (syntax-type c)
+    (:invalid (error 'invalid-character-error :character c))
+    (:multiple-escape nil)
+    (:single-escape (cons (read-ch-or-die)
+                          (collect-escaped-lexemes (read-ch-or-die))))
+    ((:constituent
+      :whitespace :terminating-macro-char :non-terminating-macro-char)
+     (cons c (collect-escaped-lexemes (read-ch-or-die))))))
+
+(defun collect-lexemes (c &optional (stream *input-stream*))
+  (let ((*input-stream* stream))
+    (when c
+      (ecase (syntax-type c)
+        (:invalid (error 'invalid-character-error :character c))
+        (:whitespace (when *preserve-whitespace-p* (unread-ch c)))
+        (:terminating-macro-char (unread-ch c))
+        (:multiple-escape (cons (collect-escaped-lexemes (read-ch-or-die))
+                                (collect-lexemes (read-ch))))
+        (:single-escape (cons (list (read-ch-or-die))
+                              (collect-lexemes (read-ch))))
+        ((:constituent :non-terminating-macro-char)
+         (cons c (collect-lexemes (read-ch))))))))
+
+;; integer  ::= [sign] decimal-digit+ decimal-point
+;;            | [sign] digit+
+;; ratio    ::= [sign] {digit}+ slash {digit}+
+;; float    ::= [sign] {decimal-digit}* decimal-point {decimal-digit}+ [exponent]
+;;            | [sign] {decimal-digit}+ [decimal-point {decimal-digit}*] exponent
+;; exponent ::= exponent-marker [sign] {digit}+
+
+(defun construct-number (chars)
+  (labels ((sign ()
+             (let ((c (and chars (car chars))))
+               (cond
+                 ((eql c #\-) (pop chars) -1)
+                 ((eql c #\+) (pop chars) +1)
+                 (t +1))))
+           (digit* (&optional (base *read-base*))
+             (let ((pos (or (position-if-not #'(lambda (d) (digit-char-p d base))
+                                             chars)
+                            (length chars))))
+               (prog1 (subseq chars 0 pos)
+                 (setq chars (subseq chars pos)))))
+           (int? (sign digits &optional (base *read-base*))
+             (when (and digits
+                        (every #'(lambda (d) (digit-char-p d base)) digits))
+               (* sign (reduce #'(lambda (a b) (+ (* base a) b))
+                               (mapcar #'(lambda (d) (digit-char-p d base))
+                                       digits)))))
+           (float? (sign)
+             (let* ((int (digit* 10))
+                    (fraction (when (eql (car chars) #\.)
+                                (pop chars) (digit* 10)))
+                    (exp-marker (when (and chars
+                                           (find (char-upcase (car chars))
+                                                 '(#\D #\E #\F #\L #\S)))
+                                  (char-upcase (pop chars))))
+                    (exp-sign (and exp-marker (sign)))
+                    (exp-digits (and exp-sign (digit*))))
+               (when (and (null chars)
+                          (or fraction (and int exp-marker exp-digits)))
+                 (float (* (int? sign (append int fraction) 10)
+                           (expt 10 (- (or (int? exp-sign exp-digits 10) 0)
+                                       (length fraction))))
+                        (ecase (or exp-marker *read-default-float-format*)
+                          (#\E                1.0e0)
+                          ((#\D double-float) 1.0d0)
+                          ((#\F single-float) 1.0f0)
+                          ((#\L long-float)   1.0l0)
+                          ((#\S short-float)  1.0s0)))))))
+    (let ((sign (sign))
+          pos numerator denominator)
+      (when chars
+        (or
+         ;; [sign] digit+
+         (int? sign chars)
+         ;; [sign] decimal-digit+ decimal-point
+         (and (eql (car (last chars)) #\.) (int? sign (butlast chars) 10))
+         ;; [sign] {digit}+ slash {digit}+
+         (and (setq pos (position #\/ chars))
+              (setq numerator (int? sign (subseq chars 0 pos)))
+              (setq denominator (int? 1 (subseq chars (1+ pos))))
+              (not (zerop denominator))
+              (/ numerator denominator))
+         ;; [sign] {decimal-digit}* decimal-point {decimal-digit}+ [exponent]
+         ;; [sign] {decimal-digit}+ [decimal-point {decimal-digit}*] exponent
+         (float? sign))))))
+
+(defun ensure-external-symbol (name package)
+  (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."
+                  "Export script symbol ~S in package ~S.")
+              "There is no external symbol by the name of ~S in script package ~S."
+              name package)
+      (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))))
+           (chars (lexemes)
+             (ecase (readtable-case *readtable*)
+               (:upcase (mapcan #'up lexemes))
+               (:downcase (mapcan #'down lexemes))
+               (:invert
+                (let ((unescaped (remove-if-not #'alpha-char-p
+                                                (remove-if #'listp lexemes))))
+                  (mapcan (cond
+                            ((every #'upper-case-p unescaped) #'down)
+                            ((every #'lower-case-p unescaped) #'up)
+                            (t #'(lambda (x)
+                                   (if (listp x) (copy-list x) (list x)))))
+                          lexemes)))
+               (:preserve (mapcan #'(lambda (x)
+                                      (if (listp x) (copy-list x) (list x)))
+                                  lexemes))))
+           (name (lexemes)
+             (when (and (find #\: lexemes) t)
+;;                        (not *intern-package-prefixes*))
+               (error "Too many package markers."))
+             (make-str (chars lexemes))))
+    (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))
+           (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)))
+               (external-p
+                (ensure-external-symbol name package))
+               (t (script-intern name 
+                                 (or package
+                                     (current-package *compilation-environment*)))))))))
+
+(defun read-number-or-symbol (stream c)
+  (let ((lexemes (collect-lexemes c stream)))
+    (assert lexemes)
+    (unless *read-suppress*
+      (cond
+        ((and lexemes (every #'(lambda (x) (eql x #\.)) lexemes))
+         (when (rest lexemes)
+           (reader-error "Tokens consisting of only dots are invalid."))
+         (when (not *consing-dot-allowed*)
+           (reader-error "Consing dot is not allowed."))
+         *consing-dot*)
+        (t 
+         (or (and (every #'characterp lexemes) (construct-number lexemes))
+             (construct-symbol lexemes)))))))
+
+
+;; backquote
+(defmacro define-constant (name value &optional doc)
+  `(defconstant ,name (if (boundp ',name) (symbol-value ',name) ,value)
+     ,@(when doc (list doc))))
+(define-constant backquote (gensym))
+(define-constant backquote-comma (gensym))
+(define-constant backquote-comma-at (gensym))
+(define-constant backquote-comma-dot (gensym))
+
+(defun backquoted-expression-type (exp)
+  (if (atom exp)
+      :normal
+      (cond
+        ((eq (first exp) backquote-comma) :comma)
+        ((eq (first exp) backquote-comma-at) :comma-at)
+        ((eq (first exp) backquote-comma-dot) :comma-dot)
+        (t :normal))))
+
+(defmacro backquote (object)
+  (if (atom object)
+      (if (simple-vector-p object)
+          (list 'apply #'vector (list backquote (concatenate 'list object)))
+          (list 'quote object))
+      (let* ((list (copy-list object))
+             (last (loop for x = list then (cdr x)
+                         until (or (atom (cdr x))
+                                   (find (cadr x) (list backquote
+                                                        backquote-comma
+                                                        backquote-comma-at
+                                                        backquote-comma-dot)))
+                         finally (return (prog1 (cdr x) (setf (cdr x) nil)))))
+             (types (mapcar #'backquoted-expression-type list)))
+        (append
+         (cons (if (notany #'(lambda (x) (eq x :comma-at)) types) 'nconc 'append)
+               (mapcar #'(lambda (x)
+                           (ecase (backquoted-expression-type x)
+                             (:normal (list 'list (list 'backquote x)))
+                             (:comma (list 'list x))
+                             ((:comma-at :comma-dot) x)))
+                       list))
+         (list (ecase (backquoted-expression-type last)
+                 (:normal (list 'quote last))
+                 (:comma last)
+                 (:comma-at (error ",@ after dot"))
+                 (:comma-dot (error ",. after dot"))))))))
+
+(defmacro backquote-comma (obj) obj)
+(setf (macro-function backquote) (macro-function 'backquote))
+(setf (macro-function backquote-comma) (macro-function 'backquote-comma))
+(setf (macro-function backquote-comma-at) (macro-function 'backquote-comma))
+(setf (macro-function backquote-comma-dot) (macro-function 'backquote-comma))
+
+
+(defun read-comma-form (stream c)
+  (declare (ignore c))
+  (unless (> *backquote-level* 0)
+    (error "Comma must be used in a backquoted expression."))
+  (let ((*backquote-level* (1- *backquote-level*)))
+    (case (peek-char t stream t nil t)
+      (#\@ (read-char stream t nil t)
+           (list backquote-comma-at (read stream t nil t)))
+      (#\. (read-char stream t nil t)
+           (list backquote-comma-dot (read stream t nil t)))
+      (t (list backquote-comma (read stream t nil t))))))
+
+(defun read-backquoted-expression (stream c)
+  (declare (ignore c))
+  (let ((*backquote-level* (1+ *backquote-level*)))
+    (list backquote (read stream t nil t))))
+
+
+(defun sharp-backslash (stream sub-char n)
+  (declare (ignore n))
+  (let* ((lexemes (collect-lexemes sub-char stream))
+         (str (make-str (mapcan #'(lambda (x)
+                                    (if (listp x) (copy-list x) (list x)))
+                                lexemes))))
+    (unless *read-suppress*
+      (cond
+        ((= 1 (length str)) (char str 0))
+        ((name-char str))
+        (t (reader-error "Unrecognized character name: ~S" str))))))
+
+(defun sharp-single-quote (stream sub-char n)
+  (declare (ignore sub-char n))
+  `(function ,(read stream t nil t)))
+
+(defun sharp-left-parenthesis (stream sub-char n)
+  (declare (ignore sub-char))
+  (let ((list (read-delimited-list #\) stream t)))
+    (unless *read-suppress*
+      (when (and n (> (length list) n))
+        (reader-error "vector is longer than specified length #~A*~A."
+                      n list))
+      (apply #'vector
+             (if (and n (< (length list) n))
+                 (append list (make-list (- n (length list))
+                                         :initial-element (car (last list))))
+                 list)))))
+
+(defun sharp-asterisk (stream sub-char n)
+  (declare (ignore sub-char))
+  (let* ((*input-stream* stream)
+         (lexemes (collect-lexemes (read-ch)))
+         (bits (mapcar #'(lambda (d)
+                           (unless (characterp d)
+                             (error "Binary digit must be given"))
+                           (digit-char-p d 2)) lexemes)))
+    (unless *read-suppress*
+      (unless (every #'(lambda (d) (digit-char-p d 2)) lexemes)
+        (reader-error "Illegal bit vector format."))
+      (when (and n (> (length bits) n))
+        (reader-error "Bit vector is longer than specified length #~A*~A."
+                      n (make-str lexemes)))
+      (when (and n (> n 0) (zerop (length bits)))
+        (reader-error
+         "At least one bit must be given for non-zero #* bit-vectors."))
+      (make-array (or n (length bits)) :element-type 'bit
+                  :initial-contents
+                  (if (and n (< (length bits) n))
+                      (append bits
+                              (make-list (- n (length bits))
+                                         :initial-element (car (last bits))))
+                      bits)))))
+
+(defun sharp-colon (stream sub-char n)
+  (declare (ignore sub-char n))
+  (let* ((*input-stream* stream)
+         (lexemes (collect-lexemes (read-ch))))
+    (unless *read-suppress*
+      (construct-symbol lexemes :uninterned-symbol-wanted t))))
+
+(defun sharp-dot (stream sub-char n)
+  (declare (ignore sub-char n))
+  (let ((object (read stream t nil t)))
+    (unless *read-suppress*
+      (unless *read-eval*
+        (reader-error "Attempt to read #. while *READ-EVAL* is bound to NIL."))
+      (eval object))))
+
+(defun sharp-b (stream sub-char n)
+  (declare (ignore n))
+  (sharp-r stream sub-char 2))
+
+(defun sharp-o (stream sub-char n)
+  (declare (ignore n))
+  (sharp-r stream sub-char 8))
+
+(defun sharp-x (stream sub-char n)
+  (declare (ignore n))
+  (sharp-r stream sub-char 16))
+
+(defun sharp-r (stream sub-char n)
+  (cond
+    (*read-suppress* (read stream t nil t))
+    ((not n) (reader-error "Radix missing in #R."))
+    ((not (<= 2 n 36)) (reader-error "Illegal radix for #R: ~D." n))
+    (t (let ((rational (let ((*read-base* n)) (read stream t nil t))))
+         (unless (typep rational 'rational)
+           (reader-error "#~A (base ~D) value is not a rational: ~S."
+                         sub-char n rational))
+         rational))))
+
+
+(defun sharp-c (stream sub-char n)
+  (declare (ignore sub-char n))
+  (let ((pair (read stream t nil t)))
+    (unless *read-suppress*
+      (unless (and (listp pair) (= (length pair) 2))
+        (reader-error "Illegal complex number format: #C~S" pair))
+      (complex (first pair) (second pair)))))
+
+(defun sharp-a (stream sub-char rank)
+  (declare (ignore sub-char))
+  (cond
+    (*read-suppress* (read stream t nil t))
+    ((null rank)
+     (reader-error "Rank for #A notation is missing."))
+    (t (let* ((contents (read stream t nil t))
+              (dimensions (loop repeat rank
+                                for x = contents then (first x)
+                                collect (length x))))
+         (make-array dimensions :initial-contents contents)))))
+
+
+(defun find-default-constructor (name)
+  (declare (ignore name)))
+    
+(defun sharp-s (stream sub-char n)
+  (declare (ignore sub-char n))
+  (let ((structure-spec (read stream t nil t)))
+    (unless *read-suppress*
+      (unless (listp structure-spec)
+        (reader-error "Non list follows #S."))
+      (unless (symbolp (first structure-spec))
+        (reader-error "Structure type is not a symbol: ~S" (car structure-spec)))
+      (let* ((name (first structure-spec))
+             (plist (loop
+                     for list on (rest structure-spec) by #'cddr
+                     append (list (intern (string (first list)) "KEYWORD")
+                                  (second list))))
+             (class (find-class name nil)))
+        (unless (typep class 'structure-class)
+          (reader-error "~S is not a defined structure type." name))
+        (let ((constructor (find-default-constructor name)))
+          (apply constructor plist))))))
+
+(defun sharp-p (stream sub-char n)
+  (declare (ignore sub-char n))
+  (let ((namestring (read stream t nil t)))
+    (unless *read-suppress* (parse-namestring namestring))))
+
+(defun container-subst (new old tree
+                        &optional (done (make-hash-table :test 'eq)))
+  (cond
+    ((eq tree old) new)
+    ((gethash tree done) tree)
+    (t (setf (gethash tree done) t)
+       (typecase tree
+         (null nil)
+         (cons (setf (car tree) (container-subst new old (car tree) done)
+                     (cdr tree) (container-subst new old (cdr tree) done))
+               tree)
+         (array (loop for i below (array-total-size tree)
+                      do (setf (row-major-aref tree i)
+                               (container-subst new old
+                                                (row-major-aref tree i) done)))
+                tree)
+         (t tree)))))
+
+(defun sharp-equal (stream sub-char n)
+  (declare (ignore sub-char))
+  (if *read-suppress*
+      (values)
+      (let* ((this (gensym))
+             (object (let ((*sharp-equal-alist* (acons n this
+                                                       *sharp-equal-alist*)))
+                       (read stream t nil t)))
+             (assoc (assoc n *sharp-equal-alist*)))
+        (when (null n)
+          (reader-error "Missing label number for #=."))
+        (when assoc
+          (reader-error "#~D= is already defined." n))
+        (setq *sharp-equal-alist* (acons n object *sharp-equal-alist*))
+        (when (eq object this)
+          (reader-error "need to tag something more than just #~D#." n))
+        (container-subst object this object))))
+
+(defun sharp-sharp (stream sub-char n)
+  (declare (ignore sub-char stream))
+  (unless *read-suppress*
+    (unless n (reader-error "Label is missing for ##."))
+    (let ((assoc (assoc n *sharp-equal-alist*)))
+      (unless assoc
+        (reader-error "No object labeld ~D is defined." n))
+      (cdr assoc))))
+
+(defun featurep (x)
+  (if (atom x)
+      (member x *features*)
+      (ecase (first x)
+        (:not (not (featurep (second x))))
+        (:and (every #'featurep (rest x)))
+        (:or (some #'featurep (rest x))))))
+
+(defun read-feature-test (stream)
+  (let ((*package* (or (find-package "KEYWORD")
+                       (error "KEYWORD package not found."))))
+    (read stream t nil t)))
+
+(defun sharp-plus (stream sub-char n)
+  (declare (ignore sub-char n))
+  (if (featurep (read-feature-test stream))
+      (read stream t nil t)
+      (let ((*read-suppress* t)) (read stream t nil t) (values))))
+
+(defun sharp-minus (stream sub-char n)
+  (declare (ignore sub-char n))
+  (if (not (featurep (read-feature-test stream)))
+      (read stream t nil t)
+      (let ((*read-suppress* t)) (read stream t nil t) (values))))
+
+(defun sharp-vertical-bar (stream sub-char n)
+  (declare (ignore sub-char n))
+  (loop for c = (read-char stream t nil t)
+        if (and (char= c #\#) (char= (read-char stream t nil t) #\|))
+        do (sharp-vertical-bar stream #\| nil)
+        until (and (char= c #\|) (char= (read-char stream t nil t) #\#)))
+  (values))
+
+
+(defvar *standard-syntax-table*
+  (let ((table (make-hash-table)))
+    (mapc #'(lambda (x)
+              (let ((syntax (first x))
+                    (chars (rest x)))
+                (dolist (c chars)
+                  (setf (gethash c table) `(:syntax ,syntax)))))
+          '((:whitespace #\Tab #\Newline #\Linefeed #\Page #\Return #\Space)
+            (:single-escape #\\)
+            (:multiple-escape #\|)))
+    table))
+
+(setq *standard-readtable* (make-readtable :syntax *standard-syntax-table*))
+
+(set-macro-character #\` 'read-backquoted-expression nil *standard-readtable*)
+(set-macro-character #\, 'read-comma-form nil *standard-readtable*)
+
+(set-macro-character #\( #'(lambda (stream char)
+                             (declare (ignore char))
+                             (read-list #\) stream t :allow-consing-dot t))
+                     nil *standard-readtable*)
+
+(set-macro-character #\) #'(lambda (stream char)
+                             (declare (ignore stream char))
+                             (error "Unmatched close parenthesis."))
+                     nil *standard-readtable*)
+
+(set-macro-character #\' #'(lambda (stream char)
+                             (declare (ignore char))
+                             `(quote ,(read stream t nil t)))
+                     nil *standard-readtable*)
+
+(set-macro-character #\; #'(lambda (stream char)
+                             (declare (ignore char))
+                             (loop
+                              for c = (read-char stream nil nil t)
+                              until (or (null c) (eql c #\Newline)))
+                             (values))
+                     nil *standard-readtable*)
+
+(set-macro-character #\" #'(lambda (stream char)
+                             (declare (ignore char))
+                             (loop
+                              for c = (read-char stream t nil t)
+                              until (char= c #\")
+                              if (eq :single-escape (syntax-type c))
+                              collect (read-char stream t nil t) into chars
+                              else
+                              collect c into chars
+                              finally
+                              (return (make-array (length chars)
+                                                  :element-type 'character
+                                                  :initial-contents chars))))
+                     nil *standard-readtable*)
+                                   
+
+(make-dispatch-macro-character #\# t *standard-readtable*)
+(mapc
+ #'(lambda (pair)
+     (set-dispatch-macro-character #\# (first pair) (second pair)
+                                   *standard-readtable*))
+ '((#\\ sharp-backslash) (#\' sharp-single-quote) (#\( sharp-left-parenthesis)
+   (#\* sharp-asterisk) (#\: sharp-colon) (#\. sharp-dot) (#\b sharp-b)
+   (#\o sharp-o) (#\x sharp-x) (#\r sharp-r) (#\c sharp-c) (#\a sharp-a)
+   (#\s sharp-s) (#\p sharp-p) (#\= sharp-equal) (#\# sharp-sharp)
+   (#\+ sharp-plus) (#\- sharp-minus) (#\| sharp-vertical-bar)))
+
+(setq *readtable* (copy-readtable nil))
index e71eb5c..f91b34e 100644 (file)
@@ -1,21 +1,27 @@
 (in-package :parenscript)
 
+(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 js-equal ((obj1 list) (obj2 list))
+(defmethod script-equal ((obj1 list) (obj2 list))
   (and (= (length obj1) (length obj2))
-       (every #'js-equal obj1 obj2)))
+       (every #'script-equal obj1 obj2)))
 
-(defmethod js-equal ((obj1 t) (obj2 t))
+(defmethod script-equal ((obj1 t) (obj2 t))
   (equal obj1 obj2))
 
-(defmacro defjsclass (name superclasses slots &rest class-options)
+(defmacro defscriptclass (name superclasses slots &rest class-options)
   (let ((slot-names (mapcar #'(lambda (slot) (if (atom slot) slot (first slot))) slots)))
     `(progn
       (defclass ,name ,superclasses
        ,slots ,@class-options)
-      (defmethod js-equal ((obj1 ,name) (obj2 ,name))
+      (defmethod script-equal ((obj1 ,name) (obj2 ,name))
        (every #'(lambda (slot)
-                  (js-equal (slot-value obj1 slot)
+                  (script-equal (slot-value obj1 slot)
                             (slot-value obj2 slot)))
               ',slot-names)))))
 
   (:documentation "A Javascript entity with a value."))
 
 ;;; array literals
-(defjsclass array-literal (expression)
+(defscriptclass array-literal (expression)
   ((values :initarg :values :accessor array-values)))
 
-(defjsclass js-aref (expression)
+(defscriptclass script-aref (expression)
   ((array :initarg :array
          :accessor aref-array)
    (index :initarg :index
          :accessor aref-index)))
 
 ;;; object literals (maps and hash-tables)
-(defjsclass object-literal (expression)
+(defscriptclass object-literal (expression)
   ((values :initarg :values :accessor object-values)))
 
 ;;; string literals
-(defjsclass string-literal (expression)
+(defscriptclass string-literal (expression)
   (value))
 
 
 ;;; number literals
-(defjsclass number-literal (expression)
+(defscriptclass number-literal (expression)
   (value))
 
 ;;; variables
-(defjsclass js-variable (expression)
+(defscriptclass script-variable (expression)
   (value))
 
 ;;; quote
-(defjsclass js-quote (expression)
+(defscriptclass script-quote (expression)
   ())
 
 ;;; operators
-(defjsclass op-form (expression)
+(defscriptclass op-form (expression)
   ((operator :initarg :operator :accessor operator)
    (args :initarg :args :accessor op-args)))
 
                  op)
              *op-precedence-hash*)))
 
-(defjsclass one-op (expression)
+(defscriptclass one-op (expression)
   ((pre-p :initarg :pre-p
          :initform nil
          :accessor one-op-pre-p)
        :accessor one-op)))
 
 ;;; function calls
-(defjsclass function-call (expression)
+(defscriptclass function-call (expression)
   ((function :initarg :function :accessor f-function)
    (args :initarg :args :accessor f-args)))
 
-(defjsclass method-call (expression)
+(defscriptclass method-call (expression)
   ((method :initarg :method :accessor m-method)
    (object :initarg :object :accessor m-object)
    (args :initarg :args :accessor m-args)))
 
 ;;; body forms
-(defjsclass js-body (expression)
-  ((stmts :initarg :stmts :accessor b-stmts)
+(defscriptclass script-body (expression)
+  ((statements :initarg :statements :accessor b-statements)
    (indent :initarg :indent :initform "" :accessor b-indent)))
 
-(defmethod initialize-instance :after ((body js-body) &rest initargs)
+(defmethod initialize-instance :after ((body script-body) &rest initargs)
   (declare (ignore initargs))
-  (let* ((stmts (b-stmts body))
-        (last (last stmts))
+  (let* ((statements (b-statements body))
+        (last (last statements))
         (last-stmt (car last)))
-    (when (typep last-stmt 'js-body)
-      (setf (b-stmts body)
-           (nconc (butlast stmts)
-                  (b-stmts last-stmt))))))
+    (when (typep last-stmt 'script-body)
+      (setf (b-statements body)
+           (nconc (butlast statements)
+                  (b-statements last-stmt))))))
 
-(defjsclass js-sub-body (js-body)
-  (stmts indent))
+(defscriptclass script-sub-body (script-body)
+  (statements indent))
 
 ;;; function definition
-(defjsclass js-lambda (expression)
+(defscriptclass script-lambda (expression)
   ((args :initarg :args :accessor lambda-args)
    (body :initarg :body :accessor lambda-body)))
 
-(defjsclass js-defun (js-lambda)
+(defscriptclass script-defun (script-lambda)
   ((name :initarg :name :accessor defun-name)))
 
 ;;; object creation
-(defjsclass js-object (expression)
+(defscriptclass script-object (expression)
   ((slots :initarg :slots
          :accessor o-slots)))
 
-(defjsclass js-slot-value (expression)
+(defscriptclass script-slot-value (expression)
   ((object :initarg :object
           :accessor sv-object)
    (slot :initarg :slot
         :accessor sv-slot)))
 
 ;;; cond
-(defjsclass js-cond (expression)
+(defscriptclass script-cond (expression)
   ((tests :initarg :tests
          :accessor cond-tests)
    (bodies :initarg :bodies
           :accessor cond-bodies)))
 
-(defjsclass js-if (expression)
+(defscriptclass script-if (expression)
   ((test :initarg :test
         :accessor if-test)
    (then :initarg :then
    (else :initarg :else
         :accessor if-else)))
 
-(defmethod initialize-instance :after ((if js-if) &rest initargs)
+(defmethod initialize-instance :after ((if script-if) &rest initargs)
   (declare (ignore initargs))
   (when (and (if-then if)
-            (typep (if-then if) 'js-sub-body))
-    (change-class (if-then if) 'js-body))
+            (typep (if-then if) 'script-sub-body))
+    (change-class (if-then if) 'script-body))
   (when (and (if-else if)
-            (typep (if-else if) 'js-sub-body))
-    (change-class (if-else if) 'js-body)))
+            (typep (if-else if) 'script-sub-body))
+    (change-class (if-else if) 'script-body)))
 
 ;;; switch
-(defjsclass js-switch (statement)
+(defscriptclass script-switch (statement)
   ((value :initarg :value :accessor case-value)
    (clauses :initarg :clauses :accessor case-clauses)))
 
 ;;; assignment
 
-(defjsclass js-setf (expression)
+(defscriptclass script-setf (expression)
   ((lhs :initarg :lhs :accessor setf-lhs)
    (rhsides :initarg :rhsides :accessor setf-rhsides)))
 
 ;;; defvar
-(defjsclass js-defvar (statement)
+(defscriptclass script-defvar (statement)
   ((names :initarg :names :accessor var-names)
    (value :initarg :value :accessor var-value)))
 
 ;;; iteration
-(defjsclass js-for (statement)
+(defscriptclass script-for (statement)
   ((vars :initarg :vars :accessor for-vars)
    (steps :initarg :steps :accessor for-steps)
    (check :initarg :check :accessor for-check)
    (body :initarg :body :accessor for-body)))
 
-(defjsclass for-each (statement)
+(defscriptclass for-each (statement)
   ((name :initarg :name :accessor fe-name)
    (value :initarg :value :accessor fe-value)
    (body :initarg :body :accessor fe-body)))
 
-(defjsclass js-while (statement)
+(defscriptclass script-while (statement)
   ((check :initarg :check :accessor while-check)
    (body :initarg :body :accessor while-body)))
 
 ;;; with
-(defjsclass js-with (statement)
+(defscriptclass script-with (statement)
   ((obj :initarg :obj :accessor with-obj)
    (body :initarg :body :accessor with-body)))
 
 ;;; try-catch
-(defjsclass js-try (statement)
+(defscriptclass script-try (statement)
   ((body :initarg :body :accessor try-body)
    (catch :initarg :catch :accessor try-catch)
    (finally :initarg :finally :accessor try-finally)))
 
 ;;; regular expressions
-(defjsclass regex (expression)
+(defscriptclass regex (expression)
   (value))
 
 ;;; conditional compilation
-(defjsclass cc-if ()
+(defscriptclass cc-if ()
   ((test :initarg :test :accessor cc-if-test)
    (body :initarg :body :accessor cc-if-body)))
 
 ;; TODO this may not be the best integrated implementation of
 ;; instanceof into the rest of the code
-(defjsclass js-instanceof (expression)
+(defscriptclass script-instanceof (expression)
   ((value)
    (type :initarg :type)))
 
-(defmacro define-js-single-op (name &optional (superclass 'expression))
-  (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
+(defmacro define-script-single-op (name &optional (superclass 'expression))
+  (let ((script-name (intern (concatenate 'string "SCRIPT-" (symbol-name name)) #.*package*)))
   `(progn
-    (defjsclass ,js-name (,superclass)
+    (defscriptclass ,script-name (,superclass)
       (value)))))
 
-(define-js-single-op return statement)
-(define-js-single-op throw statement)
-(define-js-single-op delete)
-(define-js-single-op void)
-(define-js-single-op typeof)
-(define-js-single-op new)
+(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
index 1b34ab1..0f1a7de 100644 (file)
@@ -184,12 +184,12 @@ x = 2 + sideEffect() + x + 5;")
                    ("u0080" . ,(code-char 128)) ;;Character over 127. Actually valid, parenscript escapes them to be sure.
                    ("uABCD" . ,(code-char #xabcd)))));; Really above ascii.
     (loop for (js-escape . lisp-char) in escapes
-          for generated = (js-to-string `(let ((x , (format nil "hello~ahi" lisp-char)))))
-          for wanted = (format nil "{
+         for generated = (compile-script `(let ((x , (format nil "hello~ahi" lisp-char)))))
+         for wanted = (format nil "{
   var x = 'hello\\~ahi';
 }" js-escape)
-          do (is (string= generated wanted)))))
-
+         do (is (string= generated wanted)))))
+  
 (test-ps-js complicated-symbol-name1
   grid-rows[foo].bar
   "gridRows[foo].bar")
index bbac824..2f3c450 100644 (file)
@@ -1,6 +1,7 @@
 (in-package :cl-user)
 
-(defpackage :js-test
+(defpackage :parenscript-test
+  (:nicknames :js-test)
   (:use :common-lisp :js :5am)
   (:shadowing-import-from :js :!)
   (:export #:run-tests
index 3fd24db..526d3e5 100644 (file)
@@ -35,7 +35,7 @@
     (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 (js-to-string ',parenscript))
+    (let ((generated-code (compile-script ',parenscript))
           (js-code ,javascript))
       (is (string= (normalize-js-code generated-code)
                    (normalize-js-code js-code))))))