我试图根据py-configparser创建的config对象为每个配置定义一个访问器函数的宏
(defmacro make-config-accessor (config section option)
  ; create an upper case function name then intern
  (let* ((fun-name (intern (string-upcase
                             (str:replace-all "_" "-"
                                              (str:concat "get-" option)))))) 
    `(defun ,fun-name (config)
       (py-configparser:get-option config ,section ,option))))如果将option作为字符串传递给它,但当它是像(car ("db" . "test.db"))这样的对时,表单就会按原样传递并导致错误。如何在不使用option的情况下计算宏中的eval参数。
完整示例:假设我有一个test.ini文件:
[Settings]
db = "test.db"使用py-configparser (您可以使用(ql:quickload "py-configparser")安装它),您可以将配置文件转换为Lisp对象:
(setf *test-config* (py-configparser:make-config))
(py-configparser:read-files *test-config* '("~/test.ini"))这应该是输出:
#S(PY-CONFIGPARSER:CONFIG
   :DEFAULTS #S(PY-CONFIGPARSER::SECTION :NAME "DEFAULT" :OPTIONS NIL)
   :SECTIONS (#S(PY-CONFIGPARSER::SECTION
                 :NAME "Settings"
                 :OPTIONS (("db" . "\"test.db\""))))
   :OPTION-NAME-TRANSFORM-FN #<FUNCTION STRING-DOWNCASE>
   :SECTION-NAME-TRANSFORM-FN #<FUNCTION IDENTITY>)
("~/test.ini")然后,您可以像这样检索db选项:
(py-configparser:get-option *test-config* "Settings" "db")产出:
"\"test.db\""现在,我正在编写一个宏,为每个选项(如db )创建一个函数,比如(get-db *test-config*)应该为我提供相同的输出。
我让它使用上面的make-config-accessor宏,但是当我传递一个像(car ("db" . "test.db"))这样的表单时,我必须使用eval,否则str:concat会失败。
我创建了一个gen-accessors,它遍历配置对象中的每个选项,并为它生成一个访问器:
(defun gen-accessors (config)
  (let ((sections (py-configparser:sections config)))
    (loop for s in sections
       do (loop for i in (py-configparser:items config s)
             do (let* ((o (car i)))
                  (make-config-accessor config s o))))))发布于 2020-07-25 16:23:21
编写宏的第一条规则是:如果您发现自己正在使用eval,那么您几乎肯定犯了一个错误。在这种情况下,您所犯的错误是根本不需要宏:您需要一个函数。
特别是,您可能想要这个函数或类似的东西:
(defun make-config-accessor (section option)
  ;; Make an accessor for OPTION in SECTION with a suitable name
  (let ((fun-name (intern (nsubstitute #\- #\_
                                       (format nil "GET-~A"
                                               (string-upcase option))))))
    (setf (symbol-function fun-name)
          (lambda (config)
            (py-configparser:get-option config section option)))
    fun-name)))然后给出一个合适的配置读取器
(defun read-config (&rest files)
  (py-configparser:read-files (py-configparser:make-config)
                              files))与您的gen-accessors的一个相当简化(不太单一用途的绑定)版本一起使用。
(defun gen-accessors (config)
  (loop for s in (py-configparser:sections config)
        appending (loop for i in (py-configparser:items config s)
                        collect (make-config-accessor s (car i)))))然后,例如,如果/tmp/x.ini包含
[Settings]
db = "test.db"
scrunge = 12然后
 > (gen-accessors (read-config "/tmp/x.ini"))
(get-scrunge get-db)
> (get-scrunge (read-config "/tmp/x.ini"))
"12"您可以使用这样的方法使make-config-accessor的定义更好:
(defun curryr (f &rest trailing-args)
  (lambda (&rest args)
    (declare (dynamic-extent args))
    (apply f (append args trailing-args))))
(defun make-config-accessor (section option)
  ;; Make an accessor for OPTION in SECTION with a suitable name
  (let ((fun-name (intern (nsubstitute #\- #\_
                                       (format nil "GET-~A"
                                               (string-upcase option))))))
    (setf (symbol-function fun-name)
          (curryr #'py-configparser:get-option section option))
    fun-name))当然,并不是每个人都会觉得这样更好。
发布于 2020-07-25 04:16:32
这是一种罕见的情况,在这种情况下,您必须将eval与引用过的宏调用结合使用,并取消引用参数。
(我有一次被这个构造绊倒了,自己叫它eval-over-macro-call。-遵循命名传统let-over-lambda。-实际上它应该命名为eval-over-backquoted-macro-call-with-unquoting。它允许您动态使用宏。Vsevolod Dyomkin也是被它绊倒,独立。我回答了他,因为我是在同一时间或之前偶然发现的。宏--正如您意识到的--不允许对评估进行任意控制。)
但是首先,我生成了一些帮助函数。(您可以使用:str包函数,但我在安装它时遇到了问题。较少的依赖关系更好。就我个人而言,我更喜欢cl-ppcre来代替其他的东西。但是,在你的例子中,你可以摆脱任何依赖关系。
intern会污染您的命名空间。您只希望函数名称空间具有get-函数名条目。但不是变量命名空间。因此,若要只返回符号而不自动执行这些符号,请使用read-from-string。
dotted-list-p函数需要:alexandria包。然而,一个人无论如何都需要它,因为它是最常用的lisp软件包之一(和:cl-ppcre一起),我认为这不算“附加依赖”。
对于dotted-pair-p函数,我不得不进行一些搜索。
dotted-list-to-list转换函数,我自己写的。
如果要为dotted-list使用简单的字符串列表,则可以去掉所有的options函数。
在这种情况下,在宏中,只需使用listp而不是dotted-list-p。使用option而不是(dotted-list-to-list option)。
;; one character replacement
(substitute #\+ #\Space "a simple example")
            replacer find obj
(defun string-to-upper-symbol (str)
  (read-from-string (substitute #\- #\_ (format nil "get-~A" str))))
(ql:quickload :alexandria)
(defun dotted-list-p (x)
  (and (not (alexandria:proper-list-p x))
       (consp x)))
;; correct - but gives nil if empty list (or (null x) ...) would include empty list
(defun dotted-or-empty-list-p (x)
  (or (null x) (dotted-list-p x)))
;; this gives t for empty list and dotted lists
(defun dotted-pair-p (x)
  (and (not (listp (cdr x))) (consp x)))
(defun dotted-list-to-list (dotted-list &optional (acc '()))
  (cond ((null dotted-list) (nreverse acc))
        ((dotted-pair-p dotted-list) (dotted-list-to-list '() (cons (cdr dotted-list) 
                                                                    (cons (car dotted-list) 
                                                                          acc))))
        (t (dotted-list-to-list (cdr dotted-list) (cons (car dotted-list) acc)))))您的宏包含在参数列表config中,但是它从未使用过。
如果您忘了取消引用宏中的config,正确的解决方案是:
(defmacro %make-config-accessor (config section option)
  ; create an upper case function name then intern
  (let* ((fun-name (string-to-upper-symbol option)))
    `(defun ,fun-name (,config)
       (py-configparser:get-option ,config ,section ,option)))))
(defun make-config-accessor (config section option)
  (if (dotted-list-p option)
      (loop for x in (dotted-list-to-list option)
            do (eval `(%make-config-accessor ,config ,section ,x)))
      (%make-config-accessor config section option)))
;; call with
;; (make-config-accessor '<your-config> '<your-section> '("option1" "option2" . "option3"))
;; test for existence
;; #'get-option1
;; #'get-option2
;; #'get-option3在另一种情况下,您不需要配置,正确的解决方案是:
(defmacro %make-config-accessor (section option)
  ; create an upper case function name then intern
  (let* ((fun-name (string-to-upper-symbol option)))
    `(defun ,fun-name (config)
       (py-configparser:get-option config ,section ,option)))))
(defun make-config-accessor (section option)
  (if (dotted-list-p option)
      (loop for x in (dotted-list-to-list option)
            do (eval `(%make-config-accessor ,section ,x)))
      (%make-config-accessor section option)))
;; call with
;; (make-config-accessor '<your-section> '("option1" "option2" . "option3"))
;; test for existence
;; #'get-option1
;; #'get-option2
;; #'get-option3注意,由于您需要一个函数,所以您必须在调用参数config和section时引用它们(它们等待计算,而在函数中,option被求值。
感谢quote和backquote以及unquote和eval,您可以完全控制lisp中的评估级别。
有时,如果想要控制几轮评估,就必须在参数列表中使用更多的quote。
您还可以将助手宏和函数合并到一个宏中。但是,每次调用宏时,都必须使用这个eval-over-backquoted-macro-call取消引用所需的参数。
(defmacro make-config-accessor (section option)
  (if (dotted-list-p option)
      (loop for x in (dotted-list-to-list option)
            do (eval `(make-config-accessor ,section ,x)))
      `(defun ,(string-to-upper-symbol c) (config)
         (py-configparser:get-option config ,section ,option))))
;; call it with
;; (eval `(make-config-accessor <your-section> ,<your-option>))
;; e.g.
;; (eval `(make-config-accessor <your-section> ,'("opt1" "opt2" . "opt3")))
;; test existence with
;; #'get-opt1
;; #'get-opt2
;; #'get-opt3顺便说一句。我不再相信这个"eval是被禁止的“说话了。在这样的情况下--大多数是宏中的评估控件,只有eval才能为这个问题编写额外的迷你解释器.这将是更繁琐(也很可能也更容易出错)。
你没有给出可行的代码。所以我不得不用一些复杂的函数/宏来解决所有这些问题,我写道。
(defmacro q (b c)
  `(defun ,(string-to-upper-symbol c) (a) (list a ,b ,c)))
(defun q-fun (b c)
  (if (dotted-list-p c)
      (loop for x in (dotted-list-to-list c)
            do (eval `(q ,b ,x)))
      (q b c)))
;; (q "b" "c")
;; (q "b" '("d" . "e"))
;; (macroexpand-1 '(q "b" '("d" . "e")))
(defmacro p (b c)
  (if (dotted-list-p c)
      (loop for x in (dotted-list-to-list c)
            do (eval `(p ,b ,x)))
      `(defun ,(string-to-upper-symbol c) (a) (list a ,b ,c))))发布于 2020-07-25 11:06:33
你需要两个层次的评估。
尝试:
(defmacro make-config-accessor (config section option)
  ; create an upper case function name then intern
  `(let* ((fun-name (intern (string-upcase 
                            (str:replace-all "_" "-" (str:concat "get-" ,option)))))) 
     (eval `(defun ,fun-name (config)
              (py-configparser:get-option config ,,section ,,option)))))现在,option是以let*形式计算的。然后,需要使用defun计算返回的eval表单(它总是在全局范围、空词法环境或toplevel中)。
这就是正确运行您的代码所需的所有更改。作为参考,我添加了我在这里运行的全部代码(注意:gen-accessors中有一个变化,我认为您是要使用config而不是*config*)。
(ql:quickload "str")
(ql:quickload "py-configparser")
(defmacro make-config-accessor (config section option)
  ; create an upper case function name then intern
  `(let* ((fun-name (intern (string-upcase 
                              (str:replace-all "_" "-" 
                                               (str:concat "get-" ,option)))))) 
     (eval `(defun ,fun-name (config)
              (py-configparser:get-option config ,,section ,,option)))))
(defun gen-accessors (config)
  (let ((sections (py-configparser:sections config)))
    (loop for s in sections
          do (loop for i in (py-configparser:items config s)
                   do (let* ((o (car i)))
                        (make-config-accessor config s o))))))
(setf *test-config* (py-configparser:make-config))
(py-configparser:read-files *test-config* '("~/Desktop/test.ini"))
(gen-accessors *test-config*)
(get-db *test-config*)https://stackoverflow.com/questions/63082247
复制相似问题