Cleaned up macro-defining code, fixed handling of special forms in script-expand...
[clinton/parenscript.git] / src / parser.lisp
index 10ed65b..7fe9d97 100644 (file)
@@ -2,13 +2,9 @@
 
 ;;;; The mechanisms for defining macros & parsing Parenscript.
 
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (defvar *enable-package-system* t
-    "When NIL, all symbols will function as global symbols."))
-  
 (eval-when (:compile-toplevel :load-toplevel)
   (defun macro-name-hash-function ()
-    (if *enable-package-system* #'eql #'equal)))
+    #'eql))
 
 (defclass script-package ()
   ;; configuration slots
@@ -16,6 +12,8 @@
                  :documentation "Canonical name of the package (a String).")
    (nicknames     :accessor script-package-nicknames     :initform nil :initarg :nicknames
                  :documentation "List of nicknames for the package (as strings).")
+   (prefix        :accessor script-package-prefix        :initform nil :initarg :prefix :type string
+                  :documentation "The prefix string that will be used when translating the symbols in the current package to Javascript.")
    (lisp-package  :accessor script-package-lisp-package  :initform nil :initarg :lisp-package)
    (secondary-lisp-packages :accessor script-package-secondary-lisp-packages :initform nil
                            :initarg :secondary-lisp-packages)
    (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."))
+   (symbol-table :accessor script-package-symbol-table :initform nil :initarg :symbol-table
+                :documentation "Contains symbols when there is no lisp package for this package.")
    )
   (:documentation "A Parenscript package is a lisp object that holds information
 about a set of code.
 
 "))
 
+(defmethod print-object ((sp script-package) stream)
+  (format stream "#<SCRIPT-PACKAGE ~s>" (script-package-name sp)))
+
 (defclass compilation-environment ()
   ((script-packages :accessor comp-env-script-packages :initform nil :initarg :packages
                    :documentation "List of packages defined in this environment.")
    (current-package :accessor comp-env-current-package :initform nil :initarg :current-package
                    :documentation "Current in-package.")
-
    (lisp-to-script-package-table
     :accessor comp-env-lisp-to-script-package-table :initform (make-hash-table)
     :documentation   "Maps a lisp package to a script package.")
    (compiling-toplevel-p 
     :accessor comp-env-compiling-toplevel-p :initform nil :initarg :processing-toplevel-p
-    :documentation "T if the environment is currently processing toplevel forms."))
+    :documentation "T if the environment is currently processing toplevel forms.")
+   (symbol-table :accessor symbol-to-script-package :initform (make-hash-table)
+                :documentation "Maps symbols to script packages.  Used for only the
+symbols in script packages that do not have a primary lisp package."))
   (:documentation ""))
 
+(defgeneric symbol-script-package (symbol)
+  (:documentation "Gets the Parenscript package associated with a Lisp/Parenscript symbol."))
+
 (defgeneric compiler-in-situation-p (comp-env situation)
   (:documentation "Returns true when the compiler is considered 'in' the situation
 given by SITUATION, which is one of :compile-toplevel :execute.")
@@ -91,6 +84,10 @@ http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm")
   "Determines how package symbols are serialized to JavaScript identifiers.  NIL for
 no prefixes.  :prefix to prefix variables with something like packagename_identifier.")
 
+(defvar *warn-ps-package* nil
+  "If true, warns when ParenScript attempts to compile symbols that
+don't have an associated ParenScript package.")
+
 ;;; parenscript packages
 (defun lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
   "Gets a script package corresponding to the given Lisp package."
@@ -102,9 +99,16 @@ no prefixes.  :prefix to prefix variables with something like packagename_identi
   `(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))
+(defmethod symbol-script-package ((symbol symbol))
+  (if (symbol-package symbol)
+      (or (lisp-to-script-package (symbol-package symbol) *compilation-environment*)
+          (progn (when *warn-ps-package*
+                   (warn 'simple-style-warning
+                         :format-control "~s is a symbol with lisp package ~s, which has no corresponding ParenScript package.
+Defaulting to :parenscript-user."
+                         :format-arguments (list symbol (symbol-package symbol))))
+                 (find-script-package "PARENSCRIPT-USER" (make-basic-compilation-environment))))
+      (find-script-package "UNINTERNED" *compilation-environment*)))
 
 (defun find-script-package (name &optional (comp-env *compilation-environment*))
   "Find the script package with the name NAME in the given compilation environment."
@@ -116,54 +120,65 @@ no prefixes.  :prefix to prefix variables with something like packagename_identi
                              (script-package-nicknames script-package))
                        :test #'equal))
              (comp-env-script-packages comp-env)))
-    (script-package  name)
+    (script-package name)
     (t (error "~A has unknown type" name))))
      
-(defun destroy-script-package (script-package)
-  "Disposes of relevant resources when the script package is no longer relevant."
-  (when (script-package-exclusive-lisp-package-p script-package)
-    (delete-package (script-package-lisp-package script-package))))
-
-(defun script-intern (name script-package)
+(defun script-intern (name script-package-name)
   "Returns a Parenscript symbol with the string value STRING interned for the
 given SCRIPT-PACKAGE."
-  (setf script-package (find-script-package script-package))
-  (flet ((find-exported-symbol (name script-package)
-          (let ((res
-                 (find name (script-package-exports script-package)
-                       :key #'(lambda (exported-symbol) (string exported-symbol))
-                       :test #'equal)))
-;           (format t "Searching for exported symbol ~A in ~A: ~A~%" 
-;                   name (script-package-name script-package) res)
-            res)))
-    (let ((res
-          (or
-           (some #'(lambda (used-package)
-                     (find-exported-symbol name used-package))
-                 (script-package-used-packages script-package))
-           (intern name (script-package-lisp-package script-package)))))
-      (declare (type symbol res))
-      res)))
-
+  (declare (type string name))
+  (let ((script-package (find-script-package script-package-name)))
+    (flet ((find-exported-symbol (name script-package)
+             (let ((res
+                    (find name (script-package-exports script-package)
+                          :key #'(lambda (exported-symbol) (string exported-symbol))
+                          :test #'equal)))
+               res)))
+      (let ((res
+             (or
+              (some #'(lambda (used-package)
+                        (find-exported-symbol name used-package))
+                    (script-package-used-packages script-package))
+              (if (script-package-lisp-package script-package)
+                  (intern name (script-package-lisp-package script-package))
+                  (progn
+                    (let ((sym (intern-without-package name)))
+                      (setf (gethash name (script-package-symbol-table script-package))
+                            sym)
+                      (setf (gethash sym (symbol-to-script-package (script-package-comp-env script-package)))
+                            script-package)
+                      sym))))))
+        (declare (type symbol res))
+        res))))
 
 (defun find-script-symbol (name script-package)
   "Finds the symbol with name NAME in the script package SCRIPT-PACKAGE.  NAME is a
 string and SCRIPT-PACKAGE is a package designator.  If NAME does not specify a symbol of
 script-package, returns nil.  Otherwise returns 2 values:
 1.  the symbol
-2.  :external if the symbol is external.  :internal if the symbol is internal"
+2.  :external if the symbol is external.  :internal if the symbol is internal. NIL if
+the symbol is not interned in the package."
   (setf script-package (find-script-package script-package))
-  (let* ((symbol (find-symbol name (script-package-lisp-package script-package)))
-         (exported? (find symbol (script-package-exports script-package))))
-    (values symbol (if exported? :external (when symbol :internal)))))
+  (let (symbol interned-p)
+
+    (if (script-package-lisp-package script-package)
+       (multiple-value-bind (lisp-symbol lisp-status)
+           (find-symbol name (script-package-lisp-package script-package))
+         (setf symbol lisp-symbol)
+         (setf interned-p (and lisp-status t)))
+       (multiple-value-bind (sym sym-found-p)
+           (gethash name (script-package-symbol-table script-package))
+         (setf symbol sym)
+         (setf interned-p sym-found-p)))
+    (let ((exported? (member symbol (script-package-exports script-package))))
+      (values symbol
+             (if exported? :external (if interned-p :internal nil))))))
 
 (defun script-export (symbols
                      &optional (script-package (comp-env-current-package *compilation-environment*)))
   "Exports the given symbols in the given script package."
   (when (not (listp symbols)) (setf symbols (list symbols)))
   (setf script-package (find-script-package script-package))
-;  (format t "Exporting symbols ~A in package ~A~%"
-;        symbols (script-package-name script-package))
   (let ((symbols-not-in-package
         (remove-if #'(lambda (symbol)
                        (declare (type symbol symbol))
@@ -215,36 +230,51 @@ script packages (parenscript, global, and parenscript-user)."
   (let ((*compilation-environment* (make-instance 'compilation-environment)))
     (setup-compilation-environment *compilation-environment*)))
 
+(defun intern-without-package (name)
+  (macrolet ((with-temp-package ((var) &body body)
+              (let ((result-var (gensym)))
+              `(let* ((,var (make-package ',(gensym)))
+                      (,result-var (progn ,@body)))
+                (delete-package ,var)
+                ,result-var))))
+    (with-temp-package (package)
+      (let ((sym (intern name package)))
+       (unintern sym package)
+       sym))))
+
+                
+
 (defun create-script-package (comp-env
-                             &key name nicknames secondary-lisp-packages used-packages
+                             &key name nicknames prefix secondary-lisp-packages used-packages
                              lisp-package exports documentation)
   "Creates a script package in the given compilation environment"
-  (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))))))
-    (let ((script-package
-          (make-instance 'script-package
-                         :name (string name)
-                         :comp-env comp-env
-                         :nicknames (mapcar #'string nicknames)
-                         :lisp-package (find-package lisp-package)
-                         :secondary-lisp-packages (mapcar #'find-package secondary-lisp-packages)
-                         :exclusive-lisp-package? (not explicit-lisp-package-p)
-                         :documentation documentation)))
-       (use-script-package used-packages script-package)
-;      (format t "CSP exports for ~A: ~A~%" (script-package-name script-package) exports)
-       (labels ((package-intern (string-like)
-                  (script-intern (string string-like) script-package)))
-         (script-export (mapcar #'package-intern exports) script-package))
-       (push script-package (comp-env-script-packages comp-env))
-       script-package)))
+  (when (and lisp-package (not (find-package lisp-package)))
+    (error "Package ~A does not exists" lisp-package))
+  (let* ((script-package
+         (make-instance 'script-package
+                        :name (string name)
+                        :comp-env comp-env
+                         :prefix prefix
+                        :nicknames (mapcar #'string nicknames)
+                        :lisp-package (when lisp-package (find-package lisp-package))
+                        :secondary-lisp-packages (mapcar #'find-package secondary-lisp-packages)
+                        :documentation documentation)))
+    (use-script-package used-packages script-package)
+    (labels ((package-intern (string-like)
+              (script-intern (string string-like) script-package)))
+      (script-export (mapcar #'package-intern exports) script-package))
+    (push script-package (comp-env-script-packages comp-env))
+    script-package))
 
 (defmethod initialize-instance :after ((package script-package) &key)
   (assert (script-package-comp-env package))
-  (assert (script-package-lisp-package package))
-  (let ((lisp-packages (cons (script-package-lisp-package package)
-                       (script-package-secondary-lisp-packages package))))
+  (when (null (script-package-lisp-package package))
+    (setf (script-package-symbol-table package)
+         (make-hash-table :test #'equal)))
+  (let ((lisp-packages
+        (remove-if #'null
+                   (cons (script-package-lisp-package package)
+                         (script-package-secondary-lisp-packages package)))))
     (dolist (lisp-package lisp-packages)
       (when (lisp-to-script-package lisp-package (script-package-comp-env package))
        (error "Lisp package already has corresponding script package: ~A" (package-name lisp-package)))
@@ -309,17 +339,8 @@ ongoing javascript compilation."
 
 (defun get-script-special-form (name)
   "Returns the special form function corresponding to the given name."
-; (declare (type symbol name))
-  (cond
-    (*enable-package-system*
-     (when (symbolp name)
-       (gethash name *toplevel-special-forms*)))
-    (t
-     (when (symbolp name)
-       (maphash #'(lambda (macro-name value)
-                   (when (equal (string macro-name) (string name))
-                     (return-from get-script-special-form value)))
-               *toplevel-special-forms*)))))
+  (when (symbolp name)
+    (gethash name *toplevel-special-forms*)))
 
 ;;; sexp form predicates
 (defun script-special-form-p (form)
@@ -342,24 +363,21 @@ ongoing javascript compilation."
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (defun make-macro-env-dictionary ()
     "Creates a standard macro dictionary."
-    (make-hash-table  :test (macro-name-hash-function)))
+    (make-hash-table :test (macro-name-hash-function)))
   (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).")
+    "Toplevel macro environment dictionary. Key is the symbol of the
+macro, value is (symbol-macro-p . expansion-function).")
   (defvar *script-macro-env* (list *script-macro-toplevel*) ;(list nil)
     "Current macro environment.")
+
+  (defvar *script-setf-expanders* (make-macro-env-dictionary)
+    "Setf expander dictionary. Key is the symbol of the access
+function of the place, value is an expansion function that takes the
+arguments of the access functions as a first value and the form to be
+stored as the second value.")
   
   (defun find-macro-spec (name env-dict)
-    (if *enable-package-system*
-       (gethash name env-dict)
-       (with-hash-table-iterator (next-entry env-dict)
-         (loop
-          (multiple-value-bind (exists? macro-name spec)
-              (next-entry)
-            (if exists?
-                (when (equal (string macro-name) (string name))
-                  (return spec))
-                (return nil)))))))
+    (gethash name env-dict))
   (defsetf find-macro-spec (name env-dict)
       (spec)
     `(setf (gethash ,name ,env-dict) ,spec)))
@@ -367,12 +385,12 @@ is (symbol-macro-p . expansion-function).")
 
 (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)."
+SPEC is of the form (symbol-macro-p . expansion-function)."
   `(find-macro-spec ,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:
+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."
@@ -403,17 +421,26 @@ function and the parent macro environment of the macro."
       (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."
+(defun define-script-macro% (name args body &key symbol-macro-p)
   (let ((lambda-list (gensym "ps-lambda-list-"))
         (body (if (and (cdr body) (stringp (first body))) (rest body) body))) ;; drop docstring
     (undefine-script-special-form name)
-    `(setf (get-macro-spec ',name *script-macro-toplevel*)
-      (cons nil (lambda (&rest ,lambda-list)
-                  (destructuring-bind ,args
-                      ,lambda-list
-                    ,@body))))))
+    (setf (get-macro-spec name *script-macro-toplevel*)
+          (cons symbol-macro-p (compile nil `(lambda (&rest ,lambda-list)
+                                   (destructuring-bind ,args
+                                       ,lambda-list
+                                     ,@body)))))
+    nil))
+
+(defmacro defscriptmacro (name args &body body)
+  "Define a ParenScript macro, and store it in the toplevel ParenScript
+macro environment."
+  (define-script-macro% name args body :symbol-macro-p nil))
+
+(defmacro define-script-symbol-macro (name &body body)
+  "Define a ParenScript symbol macro, and store it in the toplevel ParenScript
+macro environment.  BODY is a Lisp form that should return a ParenScript form."
+  (define-script-macro% name () body :symbol-macro-p t))
 
 (defun import-macros-from-lisp (&rest names)
   "Import the named Lisp macros into the ParenScript macro
@@ -421,18 +448,14 @@ environment. When the imported macro is macroexpanded by ParenScript,
 it is first fully macroexpanded in the Lisp macro environment, and
 then that expansion is further expanded by ParenScript."
   (dolist (name names)
-    (let ((name name))
-      (undefine-script-special-form name)
-      (setf (get-macro-spec name *script-macro-toplevel*)
-            (cons nil (lambda (&rest args)
-                        (macroexpand `(,name ,@args))))))))
+    (define-script-macro% name '(&rest args) (list `(common-lisp:macroexpand `(,',name ,@args))) :symbol-macro-p nil)))
 
-(defmacro defmacro/js (name args &body body)
+(defmacro defmacro/ps (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)))
+         (ps:import-macros-from-lisp ',name)))
 
-(defmacro defmacro+js (name args &body body)
+(defmacro defmacro+ps (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
@@ -445,29 +468,36 @@ the ParenScript macro environment."
   `(defscriptmacro ,@args))
 
 (defun expand-script-form (expr)
-  "Expands a Parenscript form down to special forms."
+  "Expands a Parenscript form until it reaches a special form.  Returns 2 values:
+1. the expanded form.
+2. whether the form was expanded."
   (if (consp expr)
       (let ((op (car expr))
             (args (cdr expr)))
-        (cond ((equal op 'quote) expr) ;; leave quotes alone
+        (cond ((equal op 'quote)
+              (values 
+               (if (equalp '(nil) args) nil expr) ;; leave quotes alone, unless it's a quoted nil
+               nil))
               ((script-macro-p op) ;; recursively expand parenscript macros in parent env.
               (multiple-value-bind (expansion-function macro-env)
                   (lookup-macro-expansion-function op)
-                (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)
+                (values
+                 (expand-script-form (let ((*script-macro-env* macro-env))
+                                       (apply expansion-function args)))
+                 t)))
+              ((script-special-form-p expr)
+               (values expr nil))
+              (t (values expr nil))))
+      (cond ((script-symbol-macro-p expr)
             ;; 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)))))
+              (values
+               (expand-script-form (let ((*script-macro-env* macro-env))
+                                     (funcall expansion-function)))
+               t)))
            ;; leave anything else alone
-            (t expr))))
+            (t (values expr nil)))))
 
 (defun process-eval-when-args (args)
   "(eval-when form-language? (situation*) form*) - returns 3 values: 
@@ -489,7 +519,7 @@ form-language, a list of situations, and a list of body forms"
     (values form-language situations body)))
   
 ;;;; compiler interface ;;;;
-(defgeneric compile-parenscript-form (compilation-environment form &key toplevel-p)
+(defgeneric compile-parenscript-form (compilation-environment form)
   (:documentation "Compiles FORM, which is a ParenScript form.
 If toplevel-p is NIL, the result is a compilation object (the AST root).
 Subsequently TRANSLATE-AST can be called to convert the result to Javascript.
@@ -499,88 +529,131 @@ be a Parenscript form (after it has been processed according to semantics
 like those of Lisp's COMPILE-FILE). See
 http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm"))
 
-(defmethod compile-parenscript-form ((comp-env compilation-environment) form &key toplevel-p)
-  (setf form (expand-script-form form))
-  ;; ensures proper compilation environment TOPLEVEL-P slot value
-  (setf (comp-env-compiling-toplevel-p comp-env) toplevel-p)
-  (if
-   toplevel-p
-   (cond
-     ((not (listp form)) form)
-     ;; process each clause of a progn as a toplevel form
-     ((eql 'progn (car form))
-      `(progn
-       ,@(mapcar #'(lambda (subform)
-                     (compile-parenscript-form comp-env subform :toplevel-p t))
-                 (rest form))))
-     ;; TODO process macrolets, symbol-macrolets, and file inclusions
-     
-     ;; process eval-when.  evaluates in :COMPILE-TOPLEVEL situation and returns
-     ;; the resultant form.  for :EXECUTE situation it returns 
-     ((eql 'eval-when (car form))
-      (multiple-value-bind (body-language situations body)
-         (process-eval-when-args (rest form))
-       (cond
-         ((find :compile-toplevel situations)
-          (when (eql body-language :lisp)
-            (let ((other-situations (remove :compile-toplevel situations)))
-              (multiple-value-bind (function warnings-p failure-p)
-                  (compile nil `(lambda () ,@body))
-                (declare (ignore warnings-p) (ignore failure-p))
-                (compile-parenscript-form 
-                 comp-env
-                 `(progn
-                   ,(funcall function)
-                   ,@(when other-situations
-                           (list `(eval-when ,other-situations ,@body))))
-                 :toplevel-p t)))))
-         ;; if :compile-toplevel is not in the situation list, return the form
-         (t form))))
-     (t form))
-   (cond ((stringp form)
-         (make-instance 'ps-js::string-literal :value form))
-        ((characterp form)
-         (make-instance 'ps-js::string-literal :value (string form)))
-        ((numberp form)
-         (make-instance 'ps-js::number-literal :value form))
-        ((symbolp form)
-         ;; is this the correct behavior?
-         (let ((c-macro (get-script-special-form form)))
-           (if c-macro
-               (funcall c-macro)
-               (make-instance 'ps-js::js-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))
-                    
-                    ((ps-js::op-form-p form)
-                     (make-instance 'ps-js::op-form
-                                    :operator (ps-js::script-convert-op-name (compile-to-symbol (first form)))
-                                    :args (mapcar #'compile-to-expression (rest form))))
-                    
-                    ((method-call-p form)
-                     (make-instance 'ps-js::method-call
-                                    :method (compile-to-symbol (first form))
-                                    :object (compile-to-expression (second form))
-                                    :args (mapcar #'compile-to-expression (cddr form))))
-                    
-                    ((funcall-form-p form)
-                     (make-instance 'ps-js::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)))))
+(defgeneric compile-toplevel-parenscript-form (comp-env form)
+  (:documentation "Compiles a parenscript form in the given compilation environment
+when the environment is in the :compile-toplevel situation.  Returns a form to be
+compiled in place of the original form upon exiting the :compile-toplevel situation."))
+
+(defmethod compile-toplevel-parenscript-form ((comp-env compilation-environment) form)
+  (cond
+    ((not (listp form)) form)
+    ;; process each clause of a progn as a toplevel form
+    ((eql 'progn (car form))
+     `(progn
+       ,@(mapcar #'(lambda (subform)
+                    (compile-parenscript-form comp-env subform))
+                (rest form))))
+    ;; TODO process macrolets, symbol-macrolets, and file inclusions
+
+    ;; process eval-when.  evaluates in :COMPILE-TOPLEVEL situation and returns
+    ;; the resultant form.  for :EXECUTE situation it returns 
+    ((eql 'eval-when (car form))
+     (multiple-value-bind (body-language situations body)
+        (process-eval-when-args (rest form))
+       (cond
+        ((find :compile-toplevel situations)
+         (when (eql body-language :lisp)
+           (let ((other-situations (remove :compile-toplevel situations)))
+             (multiple-value-bind (function warnings-p failure-p)
+                 (compile nil `(lambda () ,@body))
+               (declare (ignore warnings-p) (ignore failure-p))
+               (compile-parenscript-form 
+                comp-env
+                `(progn
+                  ,(funcall function)
+                  ,@(when other-situations
+                          (list `(eval-when ,other-situations ,@body)))))))))
+        ;; if :compile-toplevel is not in the situation list, return the form
+        (t form))))
+    (t form)))
+
+
+(defmethod compile-parenscript-form :around ((comp-env compilation-environment) form)
+  (multiple-value-bind (expanded-form expanded-p)
+      (expand-script-form form)
+    (cond
+      (expanded-p
+       (compile-parenscript-form comp-env expanded-form))
+      ((comp-env-compiling-toplevel-p comp-env)
+       (compile-toplevel-parenscript-form comp-env form))
+      (t (call-next-method)))))
+
+(defmethod compile-parenscript-form ((comp-env compilation-environment) (form string))
+  (make-instance 'ps-js::string-literal :value form))
+
+(defmethod compile-parenscript-form ((comp-env compilation-environment) (form character))
+  (compile-parenscript-form comp-env (string form)))
+
+(defmethod compile-parenscript-form ((comp-env compilation-environment) (form number))
+  (make-instance 'ps-js::number-literal :value form))
+
+(defmethod compile-parenscript-form ((comp-env compilation-environment) (form symbol))
+  ;; is this the correct behavior?
+  (let ((c-macro (get-script-special-form form)))
+    (cond
+      (c-macro (funcall c-macro))
+      ;; the following emulates the lisp behavior that a keyword is bound to itself
+      ;; see http://clhs.lisp.se/Body/t_kwd.htm
+      ((keywordp form) (compile-parenscript-form comp-env `(quote ,form)))
+      (t (make-instance 'ps-js::js-variable :value form)))))
+
+(defun compile-function-argument-forms (forms)
+  "Compiles a bunch of Parenscript forms from a funcall form to an effective set of
+Javascript arguments.  The only extra processing this does is makes :keyword arguments
+into a single options argument via CREATE."
+  (flet ((keyword-arg (arg)
+          "If the given compiled expression is supposed to be a keyword argument, returns
+the keyword for it."
+          (when (typep arg 'script-quote) (ps-js::value arg))))
+  (let ((expressions (mapcar #'compile-to-expression forms)))
+
+    (do ((effective-expressions nil)
+        (expressions-subl expressions))
+
+       ((not expressions-subl)
+        (nreverse effective-expressions))
+      
+      (let ((arg-expr (first expressions-subl)))
+       (if (keyword-arg arg-expr)
+           (progn
+             (when (oddp (length expressions-subl))
+               (error "Odd number of keyword arguments."))
+             (push
+              (make-instance 'ps-js::js-object
+                             :slots
+                             (loop for (name val) on expressions-subl by #'cddr
+                                   collect (list name val)))
+              effective-expressions)
+             (setf expressions-subl nil))
+           (progn
+             (push arg-expr effective-expressions)
+             (setf expressions-subl (rest expressions-subl)))))))))
+
+(defmethod compile-parenscript-form ((comp-env compilation-environment) (form cons))
+  (let* ((name (car form))
+        (args (cdr form))
+        (script-form (when (symbolp name) (get-script-special-form name))))
+    (cond
+      ((eql name 'quote)       (make-instance 'script-quote :value (first args)))
+      (script-form             (apply script-form args))
+      ((ps-js::op-form-p form)
+       (make-instance 'ps-js::op-form
+                     :operator (ps-js::script-convert-op-name (compile-to-symbol (first form)))
+                     :args (mapcar #'compile-to-expression (rest form))))
+      ((method-call-p form)
+       (make-instance 'ps-js::method-call
+                     :method (compile-to-symbol name)
+                     :object (compile-to-expression (first args))
+                     :args (compile-function-argument-forms (rest args))))
+      ((funcall-form-p form)
+       (make-instance 'ps-js::function-call
+                     :function (compile-to-expression name)
+                     :args (compile-function-argument-forms args)))
+      (t (error "Unknown form ~S" form)))))
 
 (defun compile-script-form (form &key (comp-env *compilation-environment*))
   "Compiles a Parenscript form to an AST node."
-  (compile-parenscript-form comp-env form ))
+  (compile-parenscript-form comp-env form))
 
 (defun compile-to-expression (form)
   "Compiles the given Parenscript form and guarantees the result is an expression."
@@ -594,13 +667,16 @@ also guarantees that the symbol has an associated script-package."
   (let ((res (compile-script-form form)))
     (when (typep res 'ps-js::js-variable)
       (setf res (ps-js::value res)))
+    (when (typep res 'ps-js::script-quote)
+      (setf res (ps-js::value res)))
     (assert (symbolp res) ()
-            "~a is expected to be a symbol, but compiles to ~a. This could be due to ~a being a special form." form res form)
-    (when *enable-package-system*
-      (assert (symbol-script-package res) ()
-             "The symbol ~A::~A has no associated script package." 
-             (package-name (symbol-package res))
-             res))
+            "~a is expected to be a symbol, but compiles to ~a (the ParenScript output for ~a alone is \"~a\"). This could be due to ~a being a special form." form res form (ps::ps* form) form)
+    (unless (symbol-script-package res)
+      (when *warn-ps-package*
+        (warn 'simple-style-warning
+              :format-control "The symbol ~A::~A has no associated script package."
+              :format-arguments (list (if (symbol-package res) (package-name (symbol-package res)) "ANONYMOUS-PACKAGE")
+                                      res))))
     res))
 
 (defun compile-to-statement (form)