Fixed problems with expressions being compiled to statements inside expression progns...
authorVladimir Sedach <vsedach@gmail.com>
Sun, 11 Nov 2007 18:10:33 +0000 (18:10 +0000)
committerVladimir Sedach <vsedach@gmail.com>
Sun, 11 Nov 2007 18:10:33 +0000 (18:10 +0000)
src/compiler.lisp
src/js-macrology.lisp
src/printer.lisp
src/ps-macrology.lisp
t/ps-tests.lisp

index 5c1e297..81ab624 100644 (file)
@@ -1,6 +1,5 @@
 (in-package :parenscript)
 
-;;;; The mechanisms for parsing Parenscript.
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (defvar *toplevel-special-forms* (make-hash-table :test #'equal)
     "A hash-table containing functions that implement Parenscript special forms,
@@ -25,6 +24,16 @@ types are appended to the ongoing javascript compilation."
   "Returns the special form function corresponding to the given name."
   (gethash (lisp-symbol-to-ps-identifier name :special-form) *toplevel-special-forms*))
 
+(defvar *enclosing-lexical-block-declarations* ()
+  "This special variable is expected to be bound to a fresh list by
+special forms that introduce a new JavaScript lexical block (currently
+function definitions and lambdas). Enclosed special forms are expected
+to push variable declarations onto the list when the variables
+declaration cannot be made by the enclosed form (for example, a
+(x,y,z) expression progn). It is then the responsibility of the
+enclosing special form to introduce the variable bindings in its
+lexical block.")
+
 ;;; ParenScript form predicates
 (defun ps-special-form-p (form)
   (and (consp form)
@@ -177,6 +186,7 @@ compiled to an :expression (the default), a :statement, or a
 :symbol."))
 
 (defmethod compile-parenscript-form :around (form &key expecting)
+  (assert (if expecting (member expecting '(:expression :statement :symbol)) t))
   (if (eql expecting :symbol)
       (compile-to-symbol form)
       (multiple-value-bind (expanded-form expanded-p)
index 39c84bc..c8866d0 100644 (file)
@@ -84,7 +84,7 @@
 
 (define-ps-special-form ~ (expecting x)
   (declare (ignore expecting))
-  (list 'unary-operator "~" (compile-parenscript-form x :expecting :expressin) :prefix t))
+  (list 'unary-operator "~" (compile-parenscript-form x :expecting :expression) :prefix t))
 
 (defun flatten-blocks (body)
   (when body
@@ -97,9 +97,9 @@
   (if (and (eql expecting :expression) (= 1 (length body)))
       (compile-parenscript-form (car body) :expecting :expression)
       (list 'js-block
-            (if (eql expecting :statement) t nil)
+            expecting
             (let* ((block (mapcar (lambda (form)
-                                    (compile-parenscript-form form :expecting :statement))
+                                    (compile-parenscript-form form :expecting expecting))
                                   body))
                    (clean-block (remove nil block))
                    (flat-block (flatten-blocks clean-block))
               reachable-block))))
 
 ;;; function definition
+(defun compile-function-definition (args body)
+  (list (mapcar (lambda (arg) (compile-parenscript-form arg :expecting :symbol)) args)
+        (let ((*enclosing-lexical-block-declarations* ()))
+          ;; the first compilation will produce a list of variables we need to declare in the function body
+          (compile-parenscript-form `(progn ,@body) :expecting :statement)
+          ;; now declare and compile
+          (compile-parenscript-form `(progn ,@(loop for var in *enclosing-lexical-block-declarations* collect `(defvar ,var))
+                                      ,@body) :expecting :statement))))
+
 (define-ps-special-form %js-lambda (expecting args &rest body)
   (declare (ignore expecting))
-  (list 'js-lambda (mapcar (lambda (arg)
-                             (compile-parenscript-form arg :expecting :symbol))
-                           args)
-        (compile-parenscript-form `(progn ,@body))))
+  (cons 'js-lambda (compile-function-definition args body)))
 
 (define-ps-special-form %js-defun (expecting name args &rest body)
   (declare (ignore expecting))
-  (list 'js-defun name
-        (mapcar (lambda (val) (compile-parenscript-form val :expecting :symbol)) args)
-       (compile-parenscript-form `(progn ,@body))))
+  (append (list 'js-defun name) (compile-function-definition args body)))
 
 ;;; object creation
 (define-ps-special-form create (expecting &rest args)
                                 (destructuring-bind (test &rest body)
                                     clause
                                   (list (compile-parenscript-form test :expecting :expression)
-                                        (compile-parenscript-form `(progn ,@body)))))
+                                        (compile-parenscript-form `(progn ,@body) :expecting :statement))))
                               clauses)))
     (:expression (make-cond-clauses-into-nested-ifs clauses))))
 
   (let ((clauses (mapcar (lambda (clause)
                             (let ((val (car clause))
                                   (body (cdr clause)))
-                              (list (if (eql val 'default)
+                              (cons (if (eql val 'default)
                                         'default
                                         (compile-parenscript-form val :expecting :expression))
-                                     (compile-parenscript-form `(progn ,@body)))))
+                                     (mapcar (lambda (statement) (compile-parenscript-form statement :expecting :statement))
+                                             body))))
                         clauses))
        (expr (compile-parenscript-form test-expr :expecting :expression)))
     (list 'js-switch expr clauses)))
index ac5ac52..e6feedd 100644 (file)
@@ -203,26 +203,28 @@ vice-versa.")
 (defprinter js-method-call (method object args)
   ;; TODO: this may not be the best way to add ()'s around lambdas
   ;; probably there is or should be a more general solution working
-  ;; in other situations involving lambda's
+  ;; in other situations involving lambdas
   (if (or (numberp object) (and (consp object) (member (car object) '(js-lambda js-object operator js-expression-if))))
       (parenthesize-print object)
       (ps-print object))
   (psw (js-translate-symbol method))
   (psw #\() (print-comma-delimited-list args) (psw #\)))
 
-(defprinter js-block (statement-p statements)
-  (if statement-p
-      (progn (psw #\{)
-             (incf *indent-level*)
-             (dolist (statement statements)
-               (newline-and-indent) (ps-print statement) (psw #\;))
-             (decf *indent-level*)
-             (newline-and-indent)
-             (psw #\}))
-      (progn (psw #\()
-             (loop for (statement . remaining) on statements do
-                   (ps-print statement) (when remaining (psw ", ")))
-             (psw #\)))))
+(defprinter js-block (block-type statements)
+  (case block-type
+    (:statement
+     (psw #\{)
+     (incf *indent-level*)
+     (dolist (statement statements)
+       (newline-and-indent) (ps-print statement) (psw #\;))
+     (decf *indent-level*)
+     (newline-and-indent)
+     (psw #\}))
+    (:expression
+     (psw #\()
+     (loop for (statement . remaining) on statements do
+           (ps-print statement) (when remaining (psw ", ")))
+     (psw #\)))))
 
 (defprinter js-lambda (args body)
   (print-fun-def nil args body))
@@ -334,16 +336,16 @@ vice-versa.")
                         (psw #\;)))
            (decf *indent-level*)))
     (psw "switch (") (ps-print test) (psw ") {")
-    (loop for (val body-block) in clauses
-          for body-statements = (third body-block)
+    (loop for (val . statements) in clauses
           do (progn (newline-and-indent)
                     (if (eql val 'default)
                         (progn (psw "default: ")
-                               (print-body-statements body-statements))
+                               (print-body-statements statements))
                         (progn (psw "case ")
                                (ps-print val)
                                (psw #\:)
-                               (print-body-statements body-statements)))))
+                               (print-body-statements statements)))))
+    (newline-and-indent)
     (psw #\})))
 
 (defprinter js-try (body-block &key catch finally)
index 71b5ccb..5d2d29b 100644 (file)
@@ -71,12 +71,18 @@ gensym-prefix-string)."
                               clauses))))
 
 (define-ps-special-form let (expecting bindings &rest body)
-  (declare (ignore expecting))
-  (let ((defvars (mapcar (lambda (binding) (if (atom binding)
-                                               `(defvar ,binding)
-                                               `(defvar ,@binding)))
-                         bindings)))
-    (compile-parenscript-form `(progn ,@defvars ,@body))))
+  (ecase expecting
+    (:statement
+     (let ((defvars (mapcar (lambda (binding) (if (atom binding)
+                                                  `(defvar ,binding)
+                                                  `(defvar ,@binding)))
+                            bindings)))
+       (compile-parenscript-form `(progn ,@defvars ,@body) :expecting :statement)))
+    (:expression
+     (let ((declared-variables (mapcar (lambda (binding) (if (atom binding) binding (car binding))) bindings))
+           (variable-assignments (loop for b in bindings when (listp b) collect (cons 'setf b))))
+       (setf *enclosing-lexical-block-declarations* (append declared-variables *enclosing-lexical-block-declarations*))
+       (compile-parenscript-form `(progn ,@variable-assignments ,@body) :expecting :expression)))))
 
 ;;; iteration
 (defpsmacro dotimes (iter &rest body)
@@ -278,12 +284,12 @@ lambda-list::=
   [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]] 
   [&aux {var | (var [init-form])}*])"
   (if (symbolp name)
-      `(defun-normal ,name ,lambda-list ,@body)
+      `(defun-function ,name ,lambda-list ,@body)
       (progn (assert (and (= (length name) 2) (eql 'setf (car name))) ()
                      "(defun ~s ~s ...) needs to have a symbol or (setf symbol) for a name." name lambda-list)
              `(defun-setf ,name ,lambda-list ,@body))))
 
-(defpsmacro defun-normal (name lambda-list &body body)
+(defpsmacro defun-function (name lambda-list &body body)
   (multiple-value-bind (effective-args effective-body)
       (parse-extended-function lambda-list body name)
     `(%js-defun ,name ,effective-args
index 7a0a1d8..570ec38 100644 (file)
@@ -441,3 +441,16 @@ x = 2 + sideEffect() + x + 5;")
   (progn (define-symbol-macro tst-sym-macro 2)
          tst-sym-macro)
   "2;")
+
+(test-ps-js expression-progn
+  (defun f () (return (progn (foo) (if x 1 2))))
+  "function f() {
+    return (foo(), x ? 1 : 2);
+}")
+
+(test-ps-js let-decl-in-expression
+  (defun f (x) (return (if x 1 (let ((foo x)) foo))))
+  "function f(x) {
+    var foo;
+    return x ? 1 : (foo = x, foo);
+}")