空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

etolisp 進捗 (14) 〜 macro expansion

で,結局こうなった。context frame とか lexical context とか,正確な定義をしないまま変数名に使っているので,この辺は,間違っていたら,おいおい修正しようと思う。進捗(8) の方針をそのまま実装した。defun は lambda を setf したのとほぼ等価な実装で,defmacro は「ほぼ lamda」を setf したのと等価。実装は lambda の導出クラスという形にした。

(defun foo (...) ...)
(foo ...)

が評価されたときに呼ばれる関数が

auto_ptr<const Value>
ValueLambda::asFunction(RunTime *pE, const Value *pValue) const
{
  RunTime::SymbolTable localTable;
  initContextFrame(pE, pValue, &localTable, cfuncEval());
  {
    AutoContextFrame proxy(*pE, localTable);
    return value()->eval(pE);
  }
}

なのに対して

(demacro bar (...) ...)
(bar ...)

が評価されたときに呼ばれる関数は

auto_ptr<const Value>
ValueMacro::asFunction(RunTime *pE, const Value *pValue) const
{
  RunTime::SymbolTable localTable;
  initContextFrame(pE, pValue, &localTable, cfuncClone());
  auto_ptr<const Value> pExpanded;
  {
    AutoContextFrame proxy(*pE, localTable);
    pExpanded = value()->asCons()->car()->eval(pE);
  }
  return pExpanded->eval(pE);
}

のような感じになった。pE はランタイムのポインタ,pValue は関数の cdr の cons を参照している Value で,nil でない場合は cons であることが保証されている(nil の場合は asCons() で例外が飛ぶ)。value() が Lisp の関数の定義に相当する。localTable がローカル変数用のシンボルテーブルで,initContextFrame() にて,それに変数をセットする。AutoContextFrame が,ランタイムのスタックにシンボルテーブルを push/pop する。lambda はそれ自身を評価する前に引数を評価するから,cfuncEval,一方,macro は展開する前には評価しないので cfuncClone を呼ぶといった雰囲気。内部で仮想関数を呼ぶ実装にしないで,わざわざ関数オブジェクトを渡しているのは,両者の呼び出し方の違いを分かり易くするためで,切実な意味はない*1

みそは進捗(8)の構想に加えて,macro のとき,macro expansion する文脈と,その結果を eval するときの文脈が異なるというところ。今のところ,マクロが呼ばれるたびに毎回展開されるという,悲しいことになっているが,改善は今後の楽しみとしてとっておく。
この辺りは,なんとなく思いうかべたことをダイレクトにコード化できてる感がある。とりあえず ANSI Common LISP (Prentice Hall Series in Artificial Intelligence) の 10 章のコードがちゃんと動くかどうか調べてみよう。

追記:

(defmacro for (var start stop &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))

(for x 1 8 (princ x))

これが動いたのは,けっこう感動。インタプリタの方が私より賢い(笑)。Lisp 難しすぎるぞっ!

*1:本当は「実装の共有には必ずテンプレートを使う」という強力なポリシーを掲げてもいいのかも知れない…