common-lisphunchentootparenscript

Parenscipt not compiling valid expression?


I have this parenscript macro:

;;;  Parenscript macro for showModal() and close() methods for pop-up dialogs.
;;;Takes the dialog's id, button for opening the dialog's id, and closing button's id.
(defpsmacro open-close-modal-dialog (dialog-id element-id-1 element-id-2 &key open close open-args close-args)
     (let ((dialog (ps-gensym)))
       `(progn
          (setf ,dialog (chain document (get-element-by-id ,dialog-id)))
          (setf (chain document (get-element-by-id ,element-id-1) onclick)
                (lambda (,@open-args)
                  (progn
                    ,@open
                    (funcall (chain ,dialog show-modal)))))
          (setf (chain document (get-element-by-id ,element-id-2) onclick)
                (lambda (,@close-args)
                  (progn
                    ,@close
                    (funcall (chain ,dialog close))))))))

And I'm using it in a Hunchentoot handler like so:

(define-easy-handler (student-name :uri "/student-info") (name)
  (let ((student (student-from-name name)))
    (standard-page (:title "Ashtanga Yoga Osaka | Student Page"
                           :script (ps
                                     (defun init ()
                                       (open-close-modal-dialog "editPassDialog" "getPass" "submitPass"
                                                                       ;; This is the pop-up dialog
                                                                :open (ps(defvar frm (chain document (get-element-by-id "editPass"))))))
                                     (setf (chain window onload) init)))
      ;; Main form
      (:form :action "/edit-student" :method "post" :id "editStudent"
             (:p "Name" (:input :type "text" :name "name" :class "txt" :value (format nil "~A" (name student))))
             (:P "Email" (:input :type "email" :name "email" :class "txt" :value (format nil "~A" (email student))))
             (:p "Passes" (:select :name "passlist" 
                                   (dolist (pass (pass student))
                                     (htm
                                      (:option :id "pass" :value (pass->json pass)
                                               (fmt "~A ~A" (print-month (getf pass :date))
                                                    (print-year (getf pass :date)))))))
                 (:button :type "button" :id "getPass" :class "btn" "Get Pass"))
             (:input :type "hidden" :name "previous" :value nil) ; previous value of the pass
             (:input :type "hidden" :name "old-name" :value name) ; old name of student, used for retrieving the correct instance
             (:p (:input :type "submit" :value "Edit Info" :class "btn")))
      ;; Pop-up dialog for editing passes
      (:dialog :id "editPassDialog"
               (:h1 "Edit Pass")
               (:form :action "#" :method "post" :id "editPass"
                      (:p "Date bought" (:input :type "text" :name "date" :class "txt"))
                      (:p "Type" (:input :type "text" :name "type" :class "txt"))
                      (:p "Amount Paid" (:input :type "text" :name "amt"))
                      (:p (:button :type "button" :class "btn" :id "submitPass" "Edit Pass")))))))

Now when I load the system through Quicklisp, I get this error:

; caught ERROR:
;   during macroexpansion of
;   (PS
;     (DEFUN INIT # ...)
;     (SETF #)).
;   Use *BREAK-ON-SIGNALS* to intercept.
;   
;    The Parenscript form (DEFVAR FRM
;                           (CHAIN DOCUMENT
;                            (GET-ELEMENT-BY-ID
;                             editPass))) cannot be compiled into an expression.

Which is strange, because I can define this form in the REPL:

SHALA-SYS> (macroexpand-1 '(ps (defvar frm (chain document (GET-ELEMENT-BY-ID "editPass")))))
"var frm = document.getElementById('editPass');"

And if I remove the :open and it's arguments, the system loads, then I add :open and args back in and recompile the handler and it compiles without a problem.

Any thoughts?


Solution

  • This will happen if you have both the defpsmacro and the use of the defined macro in the same file. I haven't had the time to dig into parenscript code deep enough to understand what exactly goes wrong with the order of evaluation, but the end result is that, when compiling the file, the macro definition does not exist at the time of the 'compilation' of the ps form.

    As a solution, move your parenscript macros into a separate file and make your other code files depend on it.

    As a side note, the (ps ...) form on the :open keyword argument is unnecessary - the open parameter is already expanded inside parenscript code in your macro. Additionally, ,@ is incorrect at the expansion of open as well - and these two bugs happen to cancel each other.

    (progn
      ,@open
      (funcall (chain ,dialog show-modal)))
    ;; with :open (ps (foo)) expands to
    "ps; foo(); dialog.showModal();"
    ;; and with :open (foo) expands to
    "foo; dialog.showModal();"
    
    (progn
      ,open
      (funcall (chain ,dialog show-modal)))
    ;; would expand :open (ps (foo)) to
    "ps(foo()); dialog.showModal();"
    ;; and :open (foo) to
    "foo(); dialog.showModal();"
    ;; which is what you intended.
    

    Also, funcall is not necessary in that piece of code; you could simply use (chain ,dialog (show-modal)).