计算传递给用LISP生成函数的宏的参数 [英] Evaluate arguments passed to a macro that generates functions in lisp

查看:20
本文介绍了计算传递给用LISP生成函数的宏的参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个宏,该宏基于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)))))) 
    `(defun ,fun-name (config)
       (py-configparser:get-option config ,section ,option))))

如果将option作为字符串传入,则可以很好地工作,但当它是像(car ("db" . "test.db"))这样的对时就不行了,表单是按原样传递的,会导致错误。如何在不使用eval的情况下计算宏内的option参数。

完整示例:假设我有一个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,LIKE(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))))))

推荐答案

编写宏的第一条规则是:如果您发现自己在使用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))

当然,不是每个人都会觉得这样更好。

这篇关于计算传递给用LISP生成函数的宏的参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆