読者です 読者をやめる 読者になる 読者になる

一文字マクロ文字

common lisp sbcl read macro

パターンマッチがある関数型言語でよく見かける_変数*1sbclでも使えないかと少し試してみた。
結局満足出来る結果は得られなかったが、作成したものの一部をメモして残しておく。

;; 以下は、マクロ文字候補文字xが
;; シンボル名の一部として解釈できない文脈で現れた場合にだけ、
;; マクロ文字として扱うための定義群
;;
;;  x       -> マクロ文字
;;  xYYY    -> シンボル
;;  YYYxZZZ -> シンボル
;;  x"YYY"  -> マクロ文字 + 文字列

;; マクロ文字候補xが、シンボルの頭文字ならそのシンボルを、そうでないなら(intern x)を返す
;; 関数名はてきとう
(defun read-xYYY-symbol (x stream)
  (let ((*readtable* (copy-readtable)))    ; xを普通の文字として、再度読み込んでみる
    (set-syntax-from-char x x *readtable*)
    (unread-char x stream)
    (read stream)))

;; マクロ文字#\!の定義
(set-macro-character #\!
  (lambda (stream char)
    (let ((sym (read-xYYY-symbol char stream)))
      (if (eq sym (intern "!"))
          "ビックリ"
        sym)))
  t) ; 第二引数(non-terminating-p)にtを渡すと、#\!がシンボル名の途中に現れた場合、シンボル名の一部として解釈されるようになる


;; 動作例
> !
--> "ビックリ"

> '!abc
--> !ABC

> 'abc!def
--> ABC!DEF

> '!
--> "ビックリ"
;; ついでに_の定義例
(set-macro-character #\_
  (lambda (stream char)
    (let ((sym (read-xYYY-symbol char stream)))
      (if (eq sym (intern "_"))
          (gensym) 
        sym)))
  t)

> (destructuring-bind (_ (a b _)) '(1 (2 3 4))
    (+ a b))
;; 一応動くが、sbclの場合、警告が出力される
; in: LAMBDA NIL
;     (LET* ((#:G652 (CAR #:WHOLE654))
;            (A (CAR #:REQUIRED-656))
;            (B (CAR (CDR #:REQUIRED-656)))
;            (#:G653 (CAR (CDR #))))
;       (+ A B))
; 
; caught STYLE-WARNING:
;   The variable #:G652 is defined but never used.
; 
; caught STYLE-WARNING:
;   The variable #:G653 is defined but never used.
; 
; compilation unit finished
;   caught 2 STYLE-WARNING conditions
--> 5

こういうこと(一文字のマクロの定義)をしたい場合、大抵はdefine-symbol-macroを使えば良い。
必要なのは、マクロ展開よりも早い段階で展開されるマクロ(つまりリードマクロ)が欲しい時くらい?

*1:ワイルドカードと呼ぶらしい