syntax-rulesの中間表現を考える会

これすっげぇ難しい。。

中間表現の概要と必要性

自前のSchemeフロントエンドを実装する上ではsyntax-rulesの実装はどうしても避けて通れない。syntax-rulesは一応Scheme標準(R6RSとかR7RS)的には唯一のマクロ機構なので、これが無いとマクロが書けない。
今回は↓のような構成を考えてみた。

... ちょっとクールな図が思いつかなかったので非常に解りづらいが、1つのsyntax-rulesルールから、

  1. 環境(グローバル、{テンプレート、パターン}xN)
  2. パタン中間表現 xN
  3. テンプレート中間表現 xN

を生成する。環境には負値のインデックスを振り、vectorの参照一発でlookupできるように配慮する。(テンプレートやパタンに負の正確数が表われた場合はエスケープの必要がある。というわけで、-1や-2、正値はエスケープしなくても良いようにオフセットしている。)
そもそも以前見た( http://d.hatena.ne.jp/mjt/20150728/p1 )ように、その辺のsyntax-rules実装の大半は明示的な中間言語を持たず、syntax-rulesを直接古典的マクロに変換したり(NMosh)、ランタイムでパタンを解釈している。
設計目標としては:

  • 可能な限りO(1)アルゴリズムを使う。普通だな!
  • 可能な限りソースコードに表われないシンボルをinternしない。NMoshも含め、Scheme処理系によってはシンボルGCが存在しないこともあるので、マクロシステムは可能な限りシンボルをinternしないように配慮する方がメモリ効率に優れる。
  • シリアライズ可能なオブジェクトのみを使用する = write可能なオブジェクトのみでIRを構成する = ハッシュテーブルを使わない。殆どのSchemeでハッシュテーブルはwrite可能でない。

というわけで、IRは全てvectorで表現する。

環境の構造


IRには当該syntax-rulesの環境が出力される。環境は普通のSchemeの環境フレームと同じようなものだが、

  • index
  • 種別 = Identifier | パタン変数 | 即値 | IR
  • ライブラリ名 (Identifierのみ)
  • ソースシンボル名
  • body (IRのみ)

の組が1エントリとなる。更に、環境全体を1つのベクタで表現すれば、indexは先頭のオフセットだけ記録すれば良いことになる。
(実際にIRに記録されるものは、syntax-rulesの記述全体で有効な環境と、clause(パタン/テンプレートの組)のみで有効な環境の2つに分かれることになる。)

パタン / テンプレート要素表現

ここが未だできていない。
マクロを実際に使用した際に挿入されるコードの単位がパタン/テンプレート要素といえる。

(※ 図では#(5 2 1 ...) となっているが、↓のようにMLISTは 4 を当てているので、正しくは#(4 2 1 ...)。)
IRの図で赤い箱になっているところがパタン/テンプレートの要素クラスを表わしていて、この要素クラスが結構な種類に及ぶ。たとえば、ここではクラスMLISTのパタン要素を表わしている。
MLISTは ... を含むリストで、

  1. ... より前の要素
  2. ... となっている要素(図では黄色)
  3. ... より後の要素

の3コンポーネントで表現される。このとき、vector3つでそれぞれの要素を表現するのではなく、要素の数を別途記録することでvectorを1つに抑えている。このようにしてvectorの数をケチることによって、ライブラリをロードした際のメモリ消費を抑えることができる。。。
実際には、今までの図に出てきていないようなクラスが当然存在する。

  • パタン要素クラス
 1 :             (p0 . p1) =>   PAIR[ p0 : p1 ]
 2 :            (p0 p1 p2) =>   LIST[ p0 p1 p2     ]
 3 :        (p0 p1 p2 . p) =>  DLIST[ p0 p1 p2 : p ]
 4 :     (p0 p1 ... p2 p3) =>  MLIST[ p0 : p1 : p2 p3     ]
 5 : (p0 p1 ... p2 p3 . p) => MDLIST[ p0 : p1 : p2 p3 : p ]

 6 :           #(p0 p1 p2) =>    VEC[ p0 p1 p2 ]
 7 :    #(p0 p1 ... p2 p3) =>   MVEC[ p0 : p1 : p2 p3 ]

MLISTはmeta listの略(適当に付けた)。DLISTはdotted listの略。DLISTの最終要素を空リストにすればLISTと等価になるけど、実際問題として殆どのsyntax-rulesマクロはDLISTを使わないのでクラスを分割してその分容量をケチっている。

  • テンプレート要素クラス
 1 :        (a0 . a1) =>    PAIR[ a0 : a1 ]
 2 :          (a0 a1) =>    LIST[ a0 a1 ]
 3 :     (a0 a1 . a2) =>   DLIST[ a0 a1 : a2 ]
 4 :           a0 ... =>  SPLICE[ a0 ]    ;; Single element
 5 : SPPAIR     ;; (a0 . a1) ...
 6 : SPLIST     ;; (a0 a1) ...
 7 : SPDLIST    ;; (a0 a1 . a2) ...

 8 :           #(a0 a1) =>   VEC[ a0 a1 ]           ;; Fixed length vector
 9 : #(a0 a1 ... a2 a3) =>  MVEC[ a0 : a1 : a2 a3 ] ;; Variable length vector
10 : SPVEC      ;; #(a0 a1) ...
11 : SPMVEC     ;; #(a0 a1 ... a2 a3) ...

... テンプレート要素クラスは考え中。SPLICEは現在展開中のリストまたはベクタにspliceする形でコードを挿入する。パタンは1エントリ内に複数のellipsisを含むことは無いが、テンプレートの方では普通に ... を複数含むケースが存在できるため、専用の配慮が必要になる。たぶんMVECも独立させる必要は無いな。。
例えば、テンプレート

(a b ... c ...)

は、

%root =   LIST[a %1 %2]
   %1 = SPLICE[b]
   %2 = SPLICE[c]

のようにエンコードされる。正直もうちょっと良いエンコードが有るんじゃないかという気がしてならない。