计算传递给用LISP生成函数的宏的参数 [英] Evaluate arguments passed to a macro that generates functions in 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屋!