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

Parenscipt not compiling valid expression?

我有这个 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 和 args 并重新编译处理程序,它编译没有问题。

有什么想法吗?

好吧,看来我不(仍然不)理解 defpsmacro 是如何工作的。用包裹在 ps 形式中的普通 defmacro 改变它解决了这个问题。

所以宏代码现在是:

(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 专家可以解释哪里出了问题,我将不胜感激。

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

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

附带说明,:open 关键字参数上的 (ps ...) 形式是不必要的 - 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)).