Common Lisp のリーダ・マクロであれこれ

リーダ・マクロの練習。全体的に Clojure を意識してみました。
ブレース({})は普通の丸括弧と同じ。
#{} はハッシュテーブルを作る。シャープとブレースの間に数を入れるとテスト関数を指定できる。
ブラケット([])はベクタを作る。引数は評価されないので #() と同じ。
$ は続く2つのオブジェクトを取り込み、2番目がベクタなら1番目を配列と見なしてアクセス。2番目がリストなら、1番目はハッシュテーブル扱い。後者の場合は gethash に渡している関係で、キーのほうが先に評価されてしまう。要注意
#_ は次に来る式を読み飛ばす。あるいはシャープとバーの間に与えられた数のぶんだけスキップ。
#> は次に来る式を print に渡す。print は受け取ったオブジェクトをそのまま値として返すので、読み飛ばしは起こらない。ただしこれも #_ と同じで、引数を与えるとそのぶんだけ読んで印字する。その場合、最後の値だけが返却される。
最後に、fn は匿名関数を作るマクロ。引数リストの位置にベクタを受け取る。

(defun user-read-left-brace (stream char)
  (declare (ignore char))
  (read-delimited-list #\} stream t))

(set-macro-character #\{ #'user-read-left-brace)

(set-macro-character #\} (get-macro-character #\) nil))



(defun user-sharp-left-brace (stream char num)
  (declare (ignore char))
  (let ((g (gensym)))
    `(let ((,g (make-hash-table :test ,(case num
                                             (0 '#'eq)
                                             (1 '#'eql)
                                             (2 '#'equal)
                                             (3 '#'equalp)
                                             (t '#'eql)))))
       (setf ,@(do ((lst (read-delimited-list #\} stream t)
                         (cddr lst))
                    (acc nil))
                   ((null lst) (nreverse acc))
                   (push `(gethash ,(car lst) ,g) acc)
                   (push (cadr lst) acc)))
       ,g)))

(set-dispatch-macro-character #\# #\{ #'user-sharp-left-brace)



(defun user-read-left-bracket (stream char)
  (declare (ignore char))
  (coerce (read-delimited-list #\] stream t)
          'vector))

(set-macro-character #\[ #'user-read-left-bracket)

(set-macro-character #\] (get-macro-character #\) nil))



(defun user-read-dollar (stream char)
  (declare (ignore char))
  (let ((var (read stream t nil t))
        (arg (read stream t nil t)))
    (etypecase arg
      (vector `(aref ,var ,@(coerce arg 'list)))
      (list   `(gethash ,(car arg) ,var)))))

(set-macro-character #\$ #'user-read-dollar)



(defun user-sharp-underbar (stream char num)
  (declare (ignore char))
  (if num
      (dotimes (i num) (read stream t nil t))
      (read stream t nil t))
  (read stream t nil t))

(set-dispatch-macro-character #\# #\_ #'user-sharp-underbar)



(defun user-sharp-greater-than (stream char num)
  (declare (ignore char))
  (if num
      (let (form)
        (dotimes (i num form)
          (setq form (print (read stream t nil t)))))
      (print (read stream t nil t))))

(set-dispatch-macro-character #\# #\> #'user-sharp-greater-than)



(defmacro fn (argv &body body)
  `#'(lambda ,(coerce argv 'list) ,@body))


使用例:

CL-USER> (print '{a b c})

(A B C) 
(A B C)
CL-USER> (setq v [a b c])
#(A B C)
CL-USER> $v[2]
C
CL-USER> (setq ht #0{'one 1 'two 2})
#<EQ hash-table with 2 entries @ #x2083db22>
CL-USER> $ht{'two}
2
T
CL-USER> (funcall (fn [x &rest y] (list x y))
                  1 2 3)
(1 (2 3))