健全なマクロ展開 - 構文オブジェクト (その8)

今度は syntax-case の構文オブジェクトが局所マクロの再帰定義をどう扱うかを調べます。 例にするのは、 syntax-rules に free-identifier=? の真似ごとをさせるマクロとします。

(let-syntax
 ((freeid=? (syntax-rules ()
   ((_ a b)
    (let-syntax
     ((test (syntax-rules (a) ((_ a) #t) ((_ _) #f))))
     (test b)))))
  (foo? (syntax-rules () ((_ tester a) (tester foo a)))))
 ((lambda (foo) (foo? freeid=? foo)) 'FOO))
; => ((lambda (foo.3) #f) 'FOO)

syntax-rules マクロを低レベル・マクロに変換した結果を展開に使います。 展開器は、 全部のシンボルに let-syntax:E1:M0 のように構文環境フレーム E1 とマーク M0 をくっつけてから展開を始めます。

(let-syntax:E1:M0
 ((freeid=?:E1:M0 (lambda:E1:M0 (x:E1:M0)
   (syntax-quasiquote:E1:M0
    (let-syntax:E1:M0 ((test:E1:M0 (lambda:E1:M0 (y:E1:M0)
     (if:E1:M0 (free-identifier=?:E1:M0
      (syntax-quote:E1:M0 ,(syntax-cadr:E1:M0 x:E1:M0))
      (syntax-cadr:E1:M0 y:E1:M0)) #t #f))))
     (test:E1:M0 ,(syntax-caddr:E1:M0 x:E1:M0))))))
  (foo?:E1:M0 (lambda (x:E1:M0)
   (syntax-quasiquote:E1:M0
    (,(syntax-cadr:E1:M0 x:E1:M0) foo:E1:M0 ,(syntax-caddr:E1:M0 x:E1:M0))))))
 ((lambda:E1:M0 (foo:E1:M0) (foo?:E1:M0 freeid=?:E1:M0 foo:E1:M0)) 'FOO))
E1 ((let-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) ...)

今回は let-syntax 構文なので、 マクロ変換手続きに含まれる識別子に freeid=? と foo? を含む構文環境フレーム E2 がくっつきません。 E2 は let-syntax の本体の識別子にだけくっつきます。 なお、 E3 は freeid=? のλ構文の展開が、 E4 は foo? のそれが追加したものです。

(let-syntax
 ((freeid=? (lambda (x.1)
   `(,'let-syntax:E3:E1:M0 ((,'test:E3:E1:M0 (,'lambda:E3:E1:M0 (,'y:E3:E1:M0)
     (,'if:E3:E1:M0 (,'free-identifier=?:E3:E1:M0
      (,'syntax-quote:E3:E1:M0 ,(syntax-cadr x.1))
      (,'syntax-cadr:E3:E1:M0 ,'y:E3:E1:M0)) #t #f))))
     (,'test:E3:E1:M0 ,(syntax-caddr x.1)))))
  (foo? (lambda (x.2) `(,(syntax-cadr x.2) ,'foo:E4:E1:M0 ,(syntax-caddr x.2)))))
 ((lambda:E2:E1:M0 (foo:E2:E1:M0) (foo?:E2:E1:M0 freeid=?:E2:E1:M0 foo:E2:E1:M0)) 'FOO))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((freeid=?:M0 macro <proc>) (foo?:M0 macro <proc>))
E1 ((let-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) ...)

λ構文を展開して、 本体の識別子が foo?:E5:E2:E1:M0 等になります。

(let-syntax
 ((freeid=? (lambda (x.1) 省略 ))
  (foo? (lambda (x.2) 省略 )))
  ((lambda (foo.3) (foo?:E5:E2:E1:M0 freeid=?:E5:E2:E1:M0 foo:E5:E2:E1:M0)) 'FOO))
E5 ((foo:M0 subst foo.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((freeid=?:M0 macro <proc>) (foo?:M0 macro <proc>))
E1 ((let-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) ...)

局所マクロ foo?:M0 と新しいマーク M6 を使って、 マクロを展開します。

(let-syntax
 ((freeid=? (lambda (x.1) 省略 ))
  (foo? (lambda (x.2) 省略 )))
  ((lambda (foo.3) (freeid=?:E5:E2:E1:M0 foo:M6:E4:E1:M0 foo:E5:E2:E1:M0)) 'FOO))
E5 ((foo:M0 subst foo.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((freeid=?:M0 macro <proc>) (foo?:M0 macro <proc>))
E1 ((let-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) ...)

さらに、 局所マクロ freeid=?:M0 と新しいマーク M7 を使って、 マクロを展開します。

(let-syntax
 ((freeid=? (lambda (x.1) 省略 ))
  (foo? (lambda (x.2) 省略 )))
  ((lambda (foo.3)
    (let-syntax:M7:E3:E1:M0 ((test:M7:E3:E1:M0 (lambda:M7:E3:E1:M0 (y:M7:E3:E1:M0)
     (if:M7:E3:E1:M0 (free-identifier=?:M7:E3:E1:M0
      (syntax-quote:M7:E3:E1:M0 foo:M6:E4:E1:M0)
      (syntax-cadr:M7:E3:E1:M0 y:M7:E3:E1:M0)) #t #f))))
     (test:M7:E3:E1:M0 foo:E5:E2:E1:M0))) 'FOO))
E5 ((foo:M0 subst foo.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((freeid=?:M0 macro <proc>) (foo?:M0 macro <proc>))
E1 ((let-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) ...)

識別子 let-syntax:M7:E3:E1:M0 の意味を、 E1 から見つけます。 これの意味は let-syntax 構文のキーワードです。 新しい構文環境フレーム E8 を作り、 識別子 test:M7:M0 をマクロに意味束縛します。 この例のように、 局所マクロを再帰定義すると、 構文環境中のマークのリストが伸びていきます。

(let-syntax
 ((freeid=?:E1:M0 (lambda (x.1) 省略 ))
  (foo?:E1:M0 (lambda (x.2) 省略 )))
  ((lambda (foo.3)
    (let-syntax
     ((test (lambda (y.4)
       (if (free-identifier=? (quote foo:M6:E4:E1:M0) (syntax-cadr y.4)) #t #f))))
     (test:E8:M7:E3:E1:M0 foo:E8:E5:E2:E1:M0))) 'FOO))
E9 ((y:M7:M0 subst y.4))
E8 ((test:M7:M0 macro <proc>))
E5 ((foo:M0 subst foo.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((freeid=?:M0 macro <proc>) (foo?:M0 macro <proc>))
E1 ((let-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) ...)

識別子 test:E8:M7:E3:E1:M0 の意味を、 test:M7:M0 を E8 から、 test:M0 を E3 E1 からと順に探します。 見つかった意味は、 局所マクロ test:M7:M0 のキーワードです。 この局所マクロの展開を新しいマーク Ma で始めます。

(let-syntax
 ((freeid=?:E1:M0 (lambda (x.1) 省略 ))
  (foo?:E1:M0 (lambda (x.2) 省略 )))
  ((lambda (foo.3)
    (let-syntax
     ((test (lambda (y.4)
       (if (free-identifier=? (quote foo:M6:E4:E1:M0) (syntax-cadr y.4)) #t #f))))
     (test:Ma:E8:M7:E3:E1:M0 foo:Ma:E8:E5:E2:E1:M0))) 'FOO))
E9 ((y:M7:M0 subst y.4))
E8 ((test:M7:M0 macro <proc>))
E5 ((foo:M0 subst foo.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((freeid=?:M0 macro <proc>) (foo?:M0 macro <proc>))
E1 ((let-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) ...)

free-identifier=? アプリケーションで、 最初の識別子 foo:M6:E4:E1:M0 の意味は foo への変数置換、 次の識別子 foo:Ma:E8:E5:E2:E1:M0 の意味は foo.3 への変数置換がそれぞれ見つかります。 意味が一致しないので、 free-identifier=? アプリケーションの評価値は偽です。 それで、 マクロ変換結果も偽になります。

(free-identifier=? (quote foo:M6:E4:E1:M0) (syntax-cadr y.4)) ;=> #f
;                         foo:M6:E4:E1:M0   foo:Ma:E8:E5:E2:E1:M0
;                         E4 |- foo:M0      E8 |- foo:M0
;                         E1 |- foo:M0      E5 |- foo:M0
;                         (subst foo)       (subst foo.3)

内側の let-syntax は #f に展開されて、 外側の let-syntax の展開が終わります。

((lambda (foo.3) #f) 'FOO)

この例のように、 局所マクロの再帰定義であっても、 マクロが挿入する識別子を自動的に判別して変換世代を区別できるようになっています。