renamingとR6RSライブラリ

moshのようなR6RS scheme実装の内部は大きく2つのパートに分けることができる。

  • core言語 - バイトコードコンパイラC++で書かれた評価器。R6RSのサブセットだけを実行できる。mosh -5で起動できる。
  • expander - ライブラリの読み込みやdefine-syntaxやlet-syntaxによるマクロを展開する。

...mosh以外のR6RS実装ではここまでハッキリと分割されていないかもしれない。他にはLarcenyのERR5RS等も同じように実装されている。
現在のmoshにはpsyntax-moshとnmoshの2つの実装が有って、この2つはcore言語が共通でexpanderが異なる。
nmoshはSRFI-72のリファレンス実装に由来するexpanderを使っていて、今はこれを置き換えるexpanderを目下制作している。

expanderの役割

(nmoshのような実装では、)expanderの役割は、本質的に、リネームとなる。つまり、

;; ライブラリ(A)はproc手続きをexportする
(library (A)
         (export proc)
         (import (rnrs))
(define (proc) 'A))

(library (B)
         (export proc)
         (import (rnrs))
(define (proc) 'B))

;; ライブラリ(A)のprocをprocAという名前でimportする
(import (rename (A) (proc procA))
        (rename (B) (proc procB)))

(procA)
(procB)           

のようなプログラムを、

(define (proc~0) 'A)
(define (proc~1) 'B)
(proc~0)
(proc~1)

のように変換する。
上のプログラムと下のプログラムの違いは、

  • library宣言が消えた
  • (A)のprocはproc~0にリネームされた
  • (B)のprocはproc~1にリネームされた
  • procAはproc~0に、procBはproc~1にリネームされた

よって、R6RSのexpanderを実装するためには、このような変換を行うプログラムを書くためにはどうすれば良いのかを考えないといけない。
これは本質的にはα変換なので、α変換を実装するのと同じことをすれば良い。
簡単には、

  • 新たな名前を定義する = 環境を拡張する構文を全部認識する
    • lambda、let*1、letrec、let-syntax、letrec-syntax、define、define-syntax、ライブラリのimportを全部走査して、"定義されている名前のリスト" = 環境を作る
  • 名前の変換ルールを構築する
  • 名前の変換ルールを適用する
  • (マクロを適用し、もしdatum→syntaxが使われたらdatumに変換ルールを適用する)
  • 利用しているライブラリとプログラムを全部繋ぎ合わせる

というステップを踏むことになる。

*1:マクロを使えばletはlambdaで実装できるから、letは実は実装しなくても良い。ただ、コンパイラ的に都合が悪いのでここのリストには入っている。