It is one of the rare cases, where you have to use eval
in combination with a backquoted macro call with unquoting the arguments.
(I stumbled over this construct once and called it myself eval-over-macro-call
. - Following the naming tradition let-over-lambda
. - Actually it should be named eval-over-backquoted-macro-call-with-unquoting
. It allows you to use macros dynamically. Vsevolod Dyomkin also stumbled over it, independently. I answered him, because I stumbled over it around the same time or before. Macros - as you realized - don't allow arbitrary control over evaluation.)
But first, I generated some helper functions.
(You can use your :str
package functions, but I had problems to install it. Less dependencies is better. And me, personally, I would prefer cl-ppcre
for replacements etc.
However, in your case, one can get rid of any dependencies.
intern
pollutes your namespace. You want only the function namespace have the get-
function name entries. But not the variable namespace. Therefore, to only return symbols without interning them automatically, use read-from-string
.
The dotted-list-p
function requires :alexandria
package. However, one needs it anyway mostly and since it is one of the most frequently used packages in common lisp shpere (together with :cl-ppcre
) I think that doesn't count as "additional dependency".
For the dotted-pair-p
function, I had to do some searches.
The dotted-list-to-list
converter function, I wrote myself.
You could get rid of all the dotted-list
functions, if you would use simple string lists for options
.
In that case in the macro, simply use listp
instead of dotted-list-p
.
And use option
instead of (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)))))
Your macro contains in arguments list config
which however is never used.
In case you just forgot to unquote the config
in the macro, the correct solution will be:
(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
In the other case, that you don't need config, the correct solution will be:
(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
Note, since you need a function, you have to quote in the call the arguments config
and section
(they wait for evaluation while in the function-round the option
gets evaluated.
Thanks to quote
and backquote
and unquote
and eval
you have full control over evaluation levels in lisp.
Sometimes, one has to use more quote
s in argument list, if one wants to have
control over several rounds of evaluations.
You could also fuse helper-macro and function into one macro.
However, then, every time you call the macro, you have to use this
eval-over-backquoted-macro-call
unquoting the desired argument.
(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
Btw. I don't buy anymore this "eval
is forbidden" talking.
In cases like this - mostly evaluation control in macros, one has to eval
as the only alternative to have to write an extra mini interpreter for this problem ... which would be much more tedious (and very likely also more error prone).
You didn't give workable code. So I had to figure all this out with some tody functions/macros, I wrote.
(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))))
SUBSTITUTE #\_ #\- ...
and STR:CONCAT is either CONCATENATE or FORMAT NIL ... – Rainer Joswigconfig
is used anywhere in the macro. – Sylwester(car '("db" . "test.db"))
, the form is passed as is. – Amumucl-str
because it's just a package with utility functions making it shorter to type. If I was to not use it, I would have make my own version anyway. – Amumu