LISP on TeXを作る(評価器編1)

LISP on TeXネタの最終消費作業*1.今回は評価器編.ここが本丸と言いたいところだったが,あまりに分量が多かったため,評価器編は2回に分割する.前編はevalの基本構造,後編は関数適用がメインである.

データ構造と評価の基本構造

再確認になるが,LISP on TeXで用いられるデータはすべて,次の構造をしている.

\@tlabel@hoge{contents}

ここで,制御綴り\@tlabel@hogeは型の情報を示すラベル(型ラベル)である.波括弧の中身はデータ本体である.
\@tlabel@hogeを展開すると,次のトークン列

\@eval@hoge\@apply@hoge\@@apply@hoge

を得ることができる.最初の制御綴りが,型hogeの値に対するevalを実現しているマクロ,後者2つがapplyを実現するためのマクロになっている.

評価器の起動

データ構造がわかったところで,実際にLISP on TeXのオブジェクトがどのように評価されていくのかを示す.まずは,評価器の起動部分である.評価器の起動マクロ\lispevalは,各種初期化処理を実行した後,\@eval@hogeを取得し展開するだけである.コードを示す.

\def\lispeval#1#2{% #1 : \cs -> S-exp, #2 : target register
  \gdef\@temp@write@buffer{}%
    \expandafter\@lisp@expand@environment\@globalenv\@lisp@expand@env@last\@lisp@expand@env@last% 
    \expandafter\@eval#1{}#2%
  \@temp@write@buffer}

\def\@eval#1#2#3#4{
  \begingroup
    \@lisp@expand@environment#3\@lisp@expand@env@last\@lisp@expand@env@last
    \expandafter\@@select@eval#1{#2}{#3}#4%
  \endgroup}

\lispevalの引数#1は制御綴りで,展開するとLISP on TeXのオブジェクトになることが期待される.#2も制御綴りで,\lispeval全体の展開が終了したとき,#2には#1を展開したものの評価結果が格納される.

次に,\lispevalの詳細を説明していく.

まず,\lispevalでは\@temp@write@bufferを空列に初期化する.この制御綴りは,その名の通りトークン出力を行うためのバッファとしての役割を持つ.すべての評価が終わった後,\@temp@write@bufferが展開され,LISP on TeXより出力されたトークンが展開されていく.このような処理にしているのは,むやみなマクロ展開により,LISP on TeXの評価に使うトークンを破壊されないようにするためである*2

次の行は,グローバル環境をTeXのマクロとして展開する.例えば,LISP on TeXの変数\hogeが整数の42に束縛されている*3場合,この行を実行することにより,\@lisp@env@\hoge という制御綴り*4を展開すると\@tlabel@int{42}を得ることができるようになる.

そして,#1を展開して補助マクロ\@evalを展開する.\@evalの引数#1は評価対象の型ラベル,#2はデータ本体,#3は評価中の環境*5のうちグローバル部分との差分,#4は評価結果格納先の制御綴りである.

\@evalは,中身全体を\begingroup...\endgroupで囲んでいる.これは,LISP on TeXのコールスタックの実装になっている.TeXには「ローカルな定義」と「グローバルな定義」という便利な機構があって,「ローカルな定義」の場合,定義したものは現在のグループを抜けると破棄される.局所的に同じ名前の制御綴りを使いたいということの多い言語処理系実装のためにあるといっていい機能である(迫真 最後に\@@select@evalを展開して,各データ型の評価規則へと進む.\@@select@evalは型ラベルを展開して得られる3つの制御綴りのうち1つめ,すなわちeval用のものだけを残し他を捨てるためのマクロである.

各データ型の評価用のマクロは,次のようなコンテキストで展開される.

\@eval@hoge{contents}{env}\target

contentsはデータ本体,envは評価中の環境のうちグローバル部分との差分,\targetは評価結果を格納するための制御綴りである.ここはほとんど\@evalと変わらない.

自己評価型フォームの評価

整数型や文字列型など,ほとんどのデータ型は自己評価型フォームである.すなわち評価すると,環境にかかわらずそれ自身が得られる.これは以前にも説明しているが,実に簡単なコードで実現できる.int型を例にとってコードを示す.

\def\@eval@int#1#2#3{\gdef#3{\@tlabel@int{#1}}}

これは,#3をint型ラベル(\@tlabel@int)を付与した#1で定義することを示す.\def(ローカルな定義)ではなく\gdef(グローバルな定義)にしているのは,この代入をスタックの外へ持ち越すためである.

シンボルの評価

シンボルの評価も,環境を見に行くだけで大したことはない.コードを示す.

\def\@eval@symbol#1#2#3{%
  \expandafter\global\expandafter\let\expandafter#3\csname @lisp@env@\string#1\endcsname
  \ifx#3\relax % not found
    \errmessage{LISP on TeX [evaluation of a symbol]: unbound variable...}%
  \else
    \expandafter\@expand@if@mutable#3#3%
  \fi}

ここでは,\lispevalと\@evalによって,現在の環境で定義されている任意の変数\hogeがマクロ\@lisp@env@\hogeというマクロとして利用可能であることを利用している.すなわち,#3に\@lisp@env@\hogeの中身をグローバルに代入している.次の\ifxは変数が未定義であった場合にエラーメッセージを表示するためのものである.else節?で使われている\@expand@if@mutableは参照型*6を展開するためのものである.あまり気にする必要はない.

(続く)

(補足)環境の表現

環境がどのような構造をしているかについて簡単に述べておく.環境は次のようなトークン列になっている.

\vari{val1}\varii{val2}...

変数と値の列になっていて,この例では\variがval1に,\variiがval2に束縛されていることを示す.

*1:実はいろいろあって拡張はする予定はある.one shot continuationとかGCとか

*2:実際の所,簡単に破壊できるのだが……

*3:よく「束縛」の言葉の使い方を間違えるので,これであっているか自信はない

*4:わかりにくいが\hogeに\stringを適用したトークン列を使うのでこれでひとつの制御綴りになる.

*5:環境の説明は補足として末尾に記載している

*6:LISP on TeXの内部的な型のひとつ.表面からは見えないが\defineMや\letMで導入される.この型はあとから中身を変更可能で,\setB(Schemeのset!相当)を実現するのに使う.