文字列を生成する際にはもっぱらformat関数を使っていますが,毎回(format nil "hoge~A" fuga)と書くのがめんどくさいので,#!"hoge{fuga}"と書けるようにリーダマクロにチャレンジしてみました.
(defun make-format-args (lst)
(let ((fmt-lst nil)
(args nil))
(dolist (obj lst)
(if (characterp obj)
(push
(cond
((char= obj #\~) "~~")
((char= obj #\\) "\\")
(T (string obj)))
fmt-lst)
(progn
(push "~A" fmt-lst)
(push (car obj) args))))
(append
(list (apply #'concatenate 'string (reverse fmt-lst)))
(reverse args))))
(defun emb-str (stream ch1 ch2)
(declare (ignore ch1 ch2))
(let ((str (read stream)))
(unless (stringp str) (error "object isn't string"))
(append '(format nil)
(make-format-args
(let ((*readtable* (copy-readtable)))
(set-macro-character #\} (get-macro-character #\)))
(with-input-from-string (in str)
(loop :while (listen in)
:for ch = (read-char in)
:collect (if (char= ch #\{)
(read-delimited-list #\} in)
ch))))))))
(set-dispatch-macro-character #\# #\! #'emb-str)
;;;test
>(defparameter hoge 3)
>#!"hoge={hoge}"
"hoge=3"
>'#!"hoge={hoge}"
(FORMAT NIL "hoge=~A" HOGE)
>#!"hoge * hoge = {(funcall #'(lambda (x) (* x x)) hoge)}"
"hoge * hoge = 9"
問題は多々あれど(エスケープ,終了判定等),普段の使い方からするとあまり気にならなさそう.困ることが起きてから直せばいいか.
まぁ,プログラムうんぬんよりも先に,ブログへのコードの張り付けをどうにかしようと思います.