alexpanderを読む

expanderのインターフェースを検討するために、既存のexpanderを改造してライブラリ対応にしてみることにした。
nmoshのbootstrapにはR5RSが必要なので、alexpanderを使用している。

alexpanderは、マクロを使用せずにR5RSのexpander(要するにsyntax-rulesとかquasiquote等)を実装していて、これがnmoshのexpanderをexpandするのに十分な機能を持っている。
今回は、これを改造してR6RSのライブラリシステムを乗せてみることにする。

ロードと実行

まずは、素のalexpanderをロードしてみる。nmoshのREPLにはloadがあるのでそれを使用する。

oku@cage ~/alexpander
$ nmosh
nmosh top program
nmosh> (load "alexpander.scm")
#<unspecified>

ここでロードするalexpanderはnmoshのものでないといけない。元の配布( http://petrofsky.org/src/alexpander.scm )は余計な定義があるので失敗する。
試しにalexpander自身をexpandしてみる。alexpanderはこのために使用できるexpand-program手続きを定義する。

nmosh> (import (mosh pp) (yuni util files))
nmosh> (pp (expand-program (file->sexp-list "alexpander.scm")))
((define _eqv?_17 eqv?)
 (define _cons_31 cons)
 (define _append_32 append)
 (define _list_33 list)
 (define _vector_34 vector)
 (define _list->vector_35 list->vector)
 (define _map_36 map)
 (define
   ERROR
   (lambda e (error "err from alexpander" e)))
 (define
   sid?
   (lambda
     (sexp)
     ((lambda (x) (if x x (renamed-sid? sexp)))
      (symbol? sexp))))

alexpanderの要素

alexpanderのコア言語はbuiltinと呼ばれ、(begin define define-syntax if lambda quote set! delay let-syntax syntax-rules)の10定義が存在する。
expanderの実装する言語要素は"null-environment"と呼ばれる。これはR5RSがそう呼んでいるからで、nullといっても一切定義が無いというわけではない。null-environmentは純粋にsyntax-rulesで書かれているか、alexpanderの内部構造を利用して書かれていることも有る。
alexpanderにおける名前空間storeと呼ばれる。library対応にあたっては、このstoreの入出力がキモとなる。

nmosh> (import (mosh pp))
nmosh> (pp builtins-store)
((syntax-rules . syntax-rules) ;; builtinsは単純なシンボル→シンボルマップとなる
 (let-syntax . let-syntax)
  :
nmosh> (pp null-store)
((quasiquote
   (syntax-rules
     ()
     ((_ template) (let () (qq template () template))))
   ((qq . 39) (let . 30)))
 (36 . #(map 36))
 (35 . #(list->vector 35)) ;; rename後の名前はintegerになる
 (34 . #(vector 34))
 (33 . #(list 33))
 (32 . #(append 32))
 (31 . #(cons 31))
 (37
  (syntax-rules
    ()
    ((_ template) (let () (qq template () template))))
  ((qq . 39) (let . 30)))
 :

コア言語はbuiltins-storeに格納され、R5RS null environmentはnull-storeに格納される。これらは、現状のalexpanderではexpandの際にimplicitにimportされる。
これらのフォーマットは、alexpander.scmに説明がある

;; env: ((id . local-location) ...)
;; store: ((location . val) ...)
;; location: toplevel-location | local-location ;; a.k.a. symloc and intloc.
;; toplevel-location: symbol
;; local-location: integer
;; val: variable | syntax | code
;; variable: #(toplevel-location) | #(symbol local-location)
;; code: (output) ; output is the expanded code for an expression.
;; syntax: builtin | transformer
;; builtin: symbol
;; transformer: (synrules env)
;; synrules: the unaltered sexp of the syntax-rules form.

alexpanderはR5RSなので、transformerにはsyntax-rulesしか存在しない。

ライブラリをexpandするために

プログラムのexpandとライブラリのexpandで異なるのは、ライブラリのexpandではマクロのexpand結果も必要という点になる。プログラムをexpandする場合は、そのプログラム中で定義されたマクロは捨ててしまって構わない。alexpanderでこれを実現するためには、expand後のstoreの内容をキャプチャする必要がある。alexpanderは、builtins-storeからnull-storeを作るために既にこれを行っているので、これを行うAPIを提供すれば良い。

;; null-prog is the preamble that defines all the standard macros that
;; are in the null-store. (The "null-" name prefix was chosen to
;; correspond to the name of r5rs's null-environment procedure, even
;; though the null-store is far from empty.)
(define null-prog
  '((define-syntax letrec-syntax
      (let-syntax ((let-syntax let-syntax) (define-syntax define-syntax))
        (syntax-rules ()
         ((_ ((kw init) ...) . body)
         (let-syntax ()
         (define-syntax kw init) ... (let-syntax () . body))))))
- snip -
;; ここのexpand-top-level-formsでbuiltins-storeをimportしてnull-stuffを作る。
(define null-stuff (expand-top-level-forms null-prog builtins-store 0 list))
(define null-output (car null-stuff))
(define null-store (cadr null-stuff))
(define null-loc-n (caddr null-stuff))