リーダーマクロ使わないで"[]"を"()"として利用できるか — #:g1

Posted 2010-10-27 13:04:00 GMT

ふと、リーダーマクロを使わないで

(with-[] (list [1 2 3 4] [[5] 6 7 8]))
;⇒ ((1 2 3 4) ((5) 6 7 8))
みたいなことができないものかなあと思ったので挑戦してみることにしました。
Common Lisp的には、"["も"]"も括弧としての意味はないので、例えば
(with-[] (list [1 2 3 4] [[5] 6 7 8]))
と書けば、
"(" "with-[]" "(" "list" "[1" "2" "3" "4]" "[[5]" "6" "7" "8]" ")" ")"
という腑分けになります。
これを単純に文字列置換で、"[]"->"()"してやれば良いんじゃないかということで、
;; utility
(defmacro aprogn (&body body)
  `(let (it)
     (setq ,@(mapcan (curry #'list 'it)
                     body))
     it))

(defmacro with-[]% (&body form) (aprogn (write-to-string form) (ppcre:regex-replace-all "\\[" it "(") (ppcre:regex-replace-all "\\]" it ")") (read-from-string it) `(progn ,@it)))

と書いてみました。
実行
(with-[]%
  (let [[x 1] (y 2) (z 3)]
   (list '[[[[[[[[[[8]]]]]]]]]]
         [list x y z]
         "[foo]")))
;⇒ (((((((((((8)))))))))) (1 2 3) "(foo)")
なるほど、文字列置換だけにリテラルの中身まで置換されてしまうのですね。

ということで、面倒なのでやっぱりリーダーに手を入れてみます。
(defmacro with-[] (&body form)
  (let ((*readtable* (copy-readtable)))
    (set-macro-character #\[ 
                         (lambda (stream char)
                           (declare (ignore char))
                           (read-delimited-list #\] stream 'T)))
    (set-macro-character #\] (get-macro-character #\)))
    ;; 
    (aprogn
      (write-to-string form)
      (read-from-string it)
      `(progn ,@it))))
実行
(with-[]
  (let [[x 1] (y 2) (z 3)]
       (list '[[[[[[[[[[x z y]]]]]]]]]]
             [list x y z]
             "[1 2 3]")))
;⇒ (((((((((((X Z Y)))))))))) (1 2 3) "[1 2 3]")
結局リーダーをプログラミングした方が楽でした。
ちなみに、バッククォート記法は残念ながら使えません…。

comments powered by Disqus