Parenscipt没有编译有效的表达式?

时间:2015-02-06 09:43:03

标签: common-lisp hunchentoot parenscript

我有这个parenscript宏:

;;;  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))))))))

我在像这样的Hunchentoot处理程序中使用它:

(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")))))))

现在,当我通过Quicklisp加载系统时,我收到此错误:

; 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.

这很奇怪,因为我可以在REPL中定义这个表单:

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

如果我删除:open及其参数,系统加载,然后我添加:open并重新编译处理程序并重新编译处理程序,它编译没有问题。

有什么想法吗?

2 个答案:

答案 0 :(得分:3)

如果同时存在defpsmacro和在同一文件中使用定义的宏,则会发生这种情况。我没有时间深入挖掘parenscript代码,以了解评估顺序究竟出现了什么问题,但最终结果是,在编译文件时,宏定义在''时不存在ps形式的汇编。

作为解决方案,将您的parenscript宏移动到一个单独的文件中,并使其他代码文件依赖于它。

作为旁注,(ps ...)关键字参数上的:open表单是不必要的 - open参数已在 parenscript代码中扩展 。此外,,@的扩展open也不正确 - 这两个错误恰好相互抵消。

(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.

此外,在该段代码中不需要funcall;你可以简单地使用(chain ,dialog (show-modal))

答案 1 :(得分:0)

好吧所以我似乎没有(仍然没有)理解defpsmacro是如何工作的。使用defmacro形式包裹的普通ps进行更改可以解决问题。

所以宏代码现在是:

(ps (defmacro 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)))))))))

如果任何 ParenScript 专家可以解释错误的原因我会非常感激。