view paper/chapter/04-interface.tex @ 56:3a8c21a37bf1

interface
author anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp>
date Tue, 02 Feb 2021 14:17:05 +0900
parents 2cbaf041b085
children b1e2bcdd5191
line wrap: on
line source

\chapter{GearsOSのInterfaceの改良}

\section{GearsOSのInterfaceの構文の改良}
GearsOSのInterfaceでは、 従来はDataGearとCodeGearを分離して記述していた。
CodeGearの入出力をDataGearとして列挙する必要があった。
CodeGearの入出力として\texttt{\_\_code()}の間に記述したDataGearの一覧と、Interface上部で記述したDataGearの集合が一致している必要がある。
ソースコード\ref{src:old-stack}はStackのInterfaceの例である。
\lstinputlisting[label=src:old-stack, caption=従来のStack Interface]{src/old-stack.h}

従来の分離している記法の場合、 このDataGearの宣言が一致していないケースが多々発生した。
またInterfaceの入力としてのDataGearではなく、 フィールド変数としてDataGearを使うようなプログラミングスタイルを取ってしまうケースも見られた。
GearsOSでは、 DataGearやフィールド変数をオブジェクトに格納したい場合、 Interface側ではなくImpl側に変数を保存する必要がある。
Interface側に記述してしまう原因は複数考えられる。
GearsOSのプログラミングスタイルに慣れていないことも考えられるが、構文によるところも考えられる。
CodeGearとDataGearはInterfaceの場合は密接な関係性にあるが、 分離して記述してしまうと「DataGearの集合」と「CodeGearの集合」を別個で捉えてしまう。
あくまでInterfaceで定義するCodeGearとDataGearはInterfaceのAPIである。
これをユーザーに強く意識させる必要がある。

golangにもInterfaceの機能が実装されている。
golangの場合はInterfaceは関数の宣言部分のみを記述するルールになっている。
変数名は含まれていても含まなくても問題ない。

\begin{lstlisting}[frame=lrbt,label=src:golang_interface,caption={golangのinterface宣言}]
type geometry interface {
    area() float64
    perim() float64
}
\end{lstlisting}

GearsOSのInterfaceは入力と出力のAPIを定義するものであるので、 golangのInterfaceのように、関数のAPIを並べて記述するほうが簡潔であると考えた。
改良したInterfaceの構文でStackを定義したものをソースコード\ref{src:stack}に示す。
\lstinputlisting[label=src:stack, caption=変更後のStack Interface]{src/stack.h}


従来のInterfaceでは\texttt{<Type, Impl>}という記述があった。
これはジェネリクスの機能を意識して導入された構文である。
\texttt{Impl}キーワードは実装自身の型を示す型変換として使われていた。
しかし基本Interfaceの定義を行う際にGearsOSのシステム上、CodeGearの第一引数は\texttt{Impl}型のポインタが来る。
これはオブジェクト指向言語で言う\texttt{self}に相当するものであり、 自分自身のインスタンスを示すポインタである。
Implキーワードは共通して使用されるために、 宣言部分からは取り外し、デフォルトの型キーワードとして定義した。
\texttt{Type}キーワードは型変数としての利用を意識して導入されていたが、現在までのGearsOSの例題では導入されていなかった。
ジェネリクスとしての型変数の利用の場合は\texttt{T}などの1文字変数がよく使われる。
変更後の構文ではのちのジェネリクス導入のことを踏まえて、\texttt{Type}キーワードは削除した。



構文を変更するには、 GearsOSのビルドシステム上でInterfaceを利用している箇所を修正する必要がある。
Interfaceはgenerate\_stub.plで読み込まれ、 CodeGearと入出力のDataGearの数え上げが行われる。
この処理はInterfaceのパースに相当するものである。
当然ではあるが、パース対象のInterfaceの構文は、変更前の構文にしか対応していない。


\section{Implementの型定義ファイルの導入}
Interfaceを使う言語では、 Interfaceが決まるとこれを実装するクラスや型が生まれる。
GearsOSもInterfaceに対応する実装が存在する。
例えばStack Interfaceの実装はSingleLinkedStackであり、 Queueの実装はSingleLinkedQueueやSynchronizedQueueが存在する。

このSynchronizedQueueはGearsOSではDataGearとして扱われる。
このDataGearの定義は、 Interfaceの定義のように型定義ファイルが存在するわけではなかった。
従来はcontext.hのDataGearの宣言部分に、構造体の形式で表現したものを手で記述していた。(ソースコード\ref{src:singleContext.h})
\lstinputlisting[label=src:singleContext.h, caption=cotnext.hに直接書かれた型定義]{src/singleContext.h}

CbCファイルからはcontext.hをインクルードすることで問題なく型を使うことが可能であるが、 型定義ファイルの存在の有無がInterfaceと実装で異なってしまっていた。
Perlのトランスコンパイラであるgenerate\_stub.plはInterfaceの型定義ファイルをパースしていた。
Implementの型も同様に定義ファイルを作製すれば、generate\_stub.plで型定義を用いた様々な処理が可能となり、ビルドシステムが柔軟な挙動が可能となる。
また型定義は一貫して\texttt{*.h}に記述すれば良くなるため、 プログラマの見通しも良くなる。
本研究では新たにImplementの型定義ファイルを考案する。

GearsOSではすでにInterfaceの型定義ファイルを持っている。
Implementの型定義ファイルも、 Interfaceの型定義ファイルと似たシンタックスにしたい。
Implementの型定義ファイルで持たなければいけないのは、 どのInterfaceを実装しているかの情報である。
この情報は他言語ではInterfaceの実装を持つ型の宣言時に記述するケースと、型名の記述はせずに言語システムが実装しているかどうかを確認するケースが存在する。
Javaでは\texttt{implements}キーワードを用いてどのInterfaceを実装しているかを記述する。\cite{javaimpl}
ソースコード\ref{src:javaimpl}では、\texttt{Pig}クラスは\texttt{Animal} Interfaceを実装している。
\lstinputlisting[label=src:javaimpl, caption=JavaのImplementキーワード]{src/java-interface-implements.java}
golangではInterfaceの実装は特にキーワードを指定せずに、 そのInterfaceで定義しているメソッドを、Implementに相当する構造体がすべて実装しているかどうかでチェックされる。
これはgolangはクラスを持たず、構造体を使ってInterfaceの実装を行う為に、 構造体の定義にどのInterfaceの実装であるかの情報をシンタックス上書けない為である。
GearsOSでは型定義ファイルを持つことができるために、 golangのような実行時チェックは行わず、 Javaに近い形で表現したい。

導入した型定義でSynchronizedQueueを定義したものをソースコード\ref{src:syncqueue}に示す。
大まかな定義方法はInterface定義のものと同様である。
違いとして\texttt{impl}キーワードを導入した。
これはJavaの\texttt{implements}に相当する機能であり、 実装したInterfaceの名前を記述する。
現状のGearsOSではImplが持てるInterfaceは1つのみであるため、\texttt{impl}の後ろにはただ1つの型が書かれる。
型定義の中では独自に定義したCodeGearを書いてもいい。
これはJavaのプライベートメソッドに相当するものである。
特にプライベートメソッドがない場合は、 実装側で所持したい変数定義を記述する。
SynchronizedQueueの例では\texttt{top}などが実装側で所持している変数である。
\lstinputlisting[label=src:syncqueue, caption=SynchronizedQueueの定義ファイル]{src/SynchronizedQueue.h}
従来context.hに直接記述していたすべてのDataGearの定義は、 スクリプトで機械的にInterfaceおよびImplementの型定義ファイルに変換している。

\section{Implementの型をいれたことによる間違ったGearsプログラミング}
Implementの型を導入したが、 GearsOSのプログラミングをするにつれていくつかの間違ったパターンがあることがわかった。
自動生成されるStubCodeGearは、 goto metaから遷移するのが前提であるため、 引数をContextから取り出す必要がある。
Contextから取り出す場合は、 実装しているInterfaceに対応している置き場所からデータを取り出す。
この置き場所は\texttt{data}配列であり、 配列の添え字は\texttt{enum Data}と対応している。
また各CodeGearからgotoする際に、 遷移先のInterfaceに値を書き込みに行く。


Interfaceで定義したCodeGearと対応しているImplementのCodeGearの場合はこのデータの取り出し方で問題はない。
しかしImplementのCodeGearから内部でgotoするCodeGearの場合は事情が異なる。
内部でgotoするCodeGearは、 Javaなどのプライベートメソッドのように使うことを想定している。
このCodeGearのことをprivate CodeGearと呼ぶ。
privateCodeGearにgotoする場合、 goto元のCodeGearからは\texttt{goto meta}経由で遷移する。
goto metaが発行されるとStub Code Gearに遷移するが、現在のシステムではInterfaceから値をとってくることになってしまう。

\section{context.hの自動生成}
GearsOSのContextの定義はcontext.hにある。
ContextはGearsOSの計算で使用されるすべてのCodeGear、 DataGearの情報を持っている。
context.hではDataGearに対応する\texttt{union Data}型の定義も行っている。
Data型はCの共用体であり、 Dataを構成する要素として各DataGearがある。
各DataGearは構造体の形で表現されている。
各DataGear自体の定義もcontext.hのunion Dataの定義の中で行われている。

DataGearの定義はInterfaceファイルで行っていた。
InterfaceファイルはGearsOS用に拡張されたシンタックスのヘッダファイルを使っており、 直接CbCからロードすることができない。
その為従来はプログラマが静的にInterfaceファイルをCbCの文脈に変換し、 context.hに構造体に変換したものを書いていた。
この手法では手書きでの構築のために自由度は高かったが、 GearsOSの例題によっては使わないDataGearも、 context.hから削除しない限りcontextに含んでしまう問題があった。
さらにInterfaceファイルで定義した型をcontext.hに転記し、それをもとにImplの型を考えてCbCファイルを作製する必要があった。
これらをすべてユーザーが行うと、ファイルごとに微妙な差異が発生したりとかなり煩雑な実装を要求されてしまう。
DataGearの定義はInterfaceファイルを作製した段階で決まり、 使用しているDataGear、CodeGearはコンパイル時に確定するはずである。
使用している各Gearがコンパイル時に確定するならば、 コンパイルの直前に実行されるPerlトランスコンパイラでもGearの確定ができるはずである。
ここからcontext.hをコンパイルタイミングでPerlスクリプト経由で生成する手法を考案した。

\subsection{context.hの作製フロー}
GearsCbCからメタ計算を含むCbCファイルに変換するgenerate\_stub.plは各CbCファイルを1つ1つ呼び出していた。
context.hを生成しようとする場合、 プロジェクトで利用する全CbCファイルを扱う必要がある。

Contextの初期化ルーチンを作製するgenerate\_context.plは、その特性上すべてのCbCファイルをロードしていた。
したがってcontext.hを作製する場合はこのスクリプトで行うのが良い。

Perlのモジュールとして\texttt{Gears::Template::Context}を作製した。
xv6プロジェクトの場合は一部ヘッダファイルに含める情報が異なる。

派生モジュールとして\texttt{Gears::Template::Context::XV6}も実装している。
これらのテンプレートモジュールはgenerate\_context.plの実行時のオプションで選択可能とした

\section{メタ計算部分の入れ替え}
GearsOSでは次のCodeGearに移行する前のMetaCodeGearとして、 デフォルトでは\texttt{\_\_code meta}が使われている。
\texttt{\_\_code meta}はcontextに含まれているCodeGearの関数ポインタを、 enumからディスパッチして次のStub CodeGearに継続するものである。

例えばモデル検査をGearsOSで実行する場合、 通常のStub CodeGearのほかに状態の保存などを行う必要がある。
この状態の保存に関する一連の処理は明らかにメタ計算であるので、 ノーマルレベルのCodeGearではない箇所で行いたい。
ノーマルレベル以外のCodeGearで実行する場合は、 通常のコード生成だとStubCodeGearの中で行うことになる。
StubCodeGearは自動生成されてしまうため、 値の取り出し以外のことを行う場合は自分で実装する必要がある。
しかしモデル検査に関する処理は様々なCodeGearの後に行う必要があるため、 すべてのCodeGearのStubを静的に実装するのは煩雑である。

ノーマルレベルのCodeGearの処理の後に、StubCodeGear以外のMeta Code Gearを実行したい。
Stub Code Gearに直ちに遷移してしまう\texttt{\_\_code meta}以外のMeta CodeGearに、 特定のCodeGearの計算が終わったら遷移したい。
このためには、特定のCodeGearの遷移先のMetaCodeGearをユーザーが定義できるAPIが必要となる。
このAPIを実装すると、ユーザーが柔軟にメタ計算を選択することが可能となる。

GearsOSのビルドシステムのAPIとして\texttt{meta.pm}を作製した。
これはPerlのモジュールファイルとして実装した。
meta.pmはPerlで実装されたGearsOSのトランスコンパイラであるgenerate\_stub.plから呼び出される。
meta.pmの中のサブルーチンである\texttt{replaceMeta}に変更対象のCodeGearと変更先のMetaCodeGearへのgotoを記述する。
ユーザーはmeta.pmのPerlファイルをAPIとしてGearsOSのトランスコンパイラにアクセスすることが可能となる。

具体的な使用例をコード\ref{src:metapm}に示す。
meta.pmはサブルーチン\texttt{replaceMeta}が返すリストの中に、特定のパターンで配列を設定する。
各配列の0番目には、goto metaを置換したいCodeGearの名前を示すPerl正規表現リテラルを入れる。
コード\ref{src:metapm}の例では、\texttt{PhilsImpl}が名前に含まれるCodeGearを指定している。
すべてのCodeGearのgotoの先を切り替える場合は\texttt{qr/.*\//}などの正規表現を指定する。

\lstinputlisting[label=src:metapm, caption=meta.pm]{src/meta.pm}

generate\_stub.plはGears CbCファイルの変換時に、 CbCファイルがあるディレクトリにmeta.pmがあるかを確認する。
meta.pmがある場合はモジュールロードを行う。
meta.pmがない場合はmeta Code Gearにgotoするものをデフォルト設定として使う。
各Gode Gearが\texttt{goto文}を呼び出したタイミングでreplaceMetaを呼び出し、 ルールにしたがってgoto文を書き換える。
変換するCodeGearがルールになかった場合は、 デフォルト設定が呼び出される。

\section{別Interfaceからの書き出しを取得する必要があるCodeGear}

従来のMetaCodeGearの生成では、 別のInterfaceからの入力を受け取るCodeGearのStubの生成に問題があった。
具体的なこの問題が発生する例題をソースコード\ref{src:insertTest1}に示す。
\lstinputlisting[label=src:insertTest1, caption=別Interfaceからの書き出しを取得するCodeGearの例]{src/pop2test.cbc}
この例では\texttt{pop2Test}Code Gearから \texttt{stack->pop2}を呼び出し、 継続として\texttt{pop2Test1}を渡している。
\texttt{pop2Test}自体はStackTest Interfaceであり、 \texttt{stack->pop2}の\texttt{stack}はStack Interfaceである。
例題ではStack Interfaceの実装はSingleLinkedStackである。
SingleLinkedStackの\texttt{pop2}の実装をソースコード\ref{src:pop2}に示す。
\lstinputlisting[label=src:pop2, caption=SingleLinkedStackのpop2]{src/pop2.cbc}
pop2はスタックから値を2つ取得するAPIである。
pop2の継続は\texttt{next}であり、 継続先に\texttt{data}と\texttt{data1}を渡している。
data、 data1は引数で受けている\texttt{union Data*}型の変数であり、 それぞれstackの中の値のポインタを代入している。
この操作でstackから値を2つ取得している。


このコードをgenerate\_stub.pl経由でメタ計算を含むコードに変換する。
変換した先のコードを\ref{src:pop2meta}に示す。
\lstinputlisting[label=src:pop2meta, caption=SingleLinkedStackのpop2のメタ計算]{src/pop2meta.cbc}
実際は\texttt{next}は\texttt{goto meta}に変換されてしまう。
data、data1は\texttt{goto meta}の前にポインタ変数\texttt{O\_data}が指す値にそれぞれ書き込まれる。
\texttt{O\_data}はpop2のStub CodeGearである\texttt{pop2SingleLinkedStack\_stub}で作製している。
つまり\texttt{O\_data}はcontext中に含まれているStack Interfaceのデータ保管場所にある変数dataのアドレスである。
\texttt{pop2}のAPIを呼び出すと、 Stack Interface中の\texttt{data}にStackに保存されていたデータのアドレスが書き込まれる。


当初Perlスクリプトが生成した\texttt{pop2Test1}のstub CodeGearはソースコード\ref{src:pop2stub-origin}のものである。
CodeGear間で処理されるデータの流れの概要図を図\ref{fig:stackTest1}に示す。
\lstinputlisting[label=src:pop2stub-origin, caption=生成されたStub]{src/pop2stub-origin.cbc}
\texttt{\_\_code pop2Test}で遷移する先のCodeGearはStackInterfaceであり、 呼び出しているAPIは\texttt{pop2}である。
pop2で取り出したデータは、 上記で確認した通りContext中のStack Interfaceのデータ格納場所に書き込まれる。
しかしソースコード\ref{src:pop2stub-origin}の例では\texttt{Gearef(context, StackTest)}でContext中の\texttt{StackTest} Interfaceのdataの置き場所から値を取得している。
これはInterfaceのImplのCodeGearは、Interfaceから値を取得するというGearsOSのルールの為である。
現状ではpop2でせっかく取り出した値をStubCodeGearで取得できない。

ここで必要となってくるのは、 実装しているInterface以外の呼び出し元のInterfaceからの値の取得である。
今回の例ではStackTest InterfaceではなくStack Interfaceからdata、 data1を取得したい。
どのInterfaceから呼び出されているかは、 コンパイルタイムには確定できるのでPerlのトランスコンパイラでStub Codeを生成したい。

\begin{figure}[h]
  \begin{center}
   \includegraphics[width=130mm]{drawio/stackTest1.pdf}
  \end{center}
  \caption{stackTest1のstubの概要}
  \label{fig:stackTest1}
 \end{figure}

別Interfaceから値を取得するには別の出力があるCodeGearの継続で渡されたCodeGearをまず確定させる。
今回の例では\texttt{pop2Test1}が該当する。
このCodeGearの入力の値と、 出力があるCodeGearの出力を見比べ、 出力をマッピングすれば良い。
Stack Interfaceのpop2はdataとdata1に値を書き込む。
pop2Test1の引数はdata, data1, stackであるので、前2つにpop2の出力を代入したい。

Contextから値を取り出すのはメタ計算であるStub CodeGearで行われる。
別Interfaceから値を取り出そうとする場合、 すでにPerlトランスコンパイラが生成しているStubを書き換えてしまう方法も取れる。
しかしStubCodeGearそのものを、 別Interfaceから値を取り出すように書き換えてはいけない。
これは別Interfaceの継続として渡されるケースと、 次のgoto先として遷移するケースがあるためである。
前者のみの場合は書き換えで問題ないが、 後者のケースで書き換えを行ってしまうとStubで値を取り出す先が異なってしまう。
どのような呼び出し方をしても対応できるようにするには、 Stubを別に別ける必要がある。


GearsOSでは継続として渡す場合や、 次のgoto文で遷移する先のCodeGearはノーマルレベルではenumの番号として表現されていた。
enumが降られるCodeGearは、厳密にはCodeGearそのものではなくStub CodeGearに対して降られる。
StubCodeGearを実装した分だけenumの番号が降られるため、 \texttt{goto meta}で遷移する際にenumの番号さえ合わせれば独自定義のStubに継続させることが可能である。
別Interfaceから値を取り出したいケースの場合、 取り出してくる先のInterfaceと呼び出し元のCodeGearが確定したタイミングで別のStubCodeGearを生成する。
呼び出し元のCodeGearが継続として渡すStubCodeGearのenumを、独自定義したenumに差し替えることでこの問題は解決する。
この機能をPerlのトランスコンパイラである\texttt{generate\_stub.pl}に導入した。

\section{別Interfaceからの書き出しを取得するStubの生成}
別Interfaceからの書き出しを取得する場合、 generate\_stub.plでは次の点をサポートする機能をいれれば実現可能である。

\begin{itemize}
  \item goto先のCodeGearが出力を持つInterfaceでかつ継続で渡しているCodeGearが別Interfaceの場合の検知
  \begin{itemize}
    \item この場合はgotoしている箇所で渡している継続のenumを、新たに作製したstubのenumに差し替える
  \end{itemize}
  \item 継続で実行された場合に別にInterfaceから値をとってこないといけないCodeGear自身
  \begin{itemize}
    \item Stubを別のInterfaceから値をとる実装のものを別に作製する
  \end{itemize}
\end{itemize}


\texttt{generate\_stub.pl}内では変換対象のCbCのソースコードを2度読み込む。
最初の読み込み時に継続の状況を確認し、 2度目の読み込み時に状況を踏まえてコードを生成すれば良い。
初回の読み込み時にInterface経由の\texttt{goto}文があった場合に、別Interfaceからの出力があるかなどの情報を確認したい。

\subsection{初回CbCファイル読み込み時の処理}

Interface経由でのgoto文は\texttt{goto interface->method()}の形式で呼び出される。
ソースコード\ref{src:parsedOutputStub.pl}はこの形式で来ていた行を読み込んだタイミングで実行される処理である。
\lstinputlisting[label=src:parsedOutputStub.pl, caption=goto時に使用するinterfaceの解析]{src/parsedOutputStub.pl}

1行目の正規表現はInterface経由でのgoto文の正規表現パターンである。
変数\texttt{\$instance}はInterfaceのインスタンスである。正規表現パターンでは\texttt{interface->method}の\texttt{->}の前に来ている変数名に紐づけられる。
変数\texttt{\$method}はgoto先のInterfaceのAPIである。正規表現パターンでは\texttt{interface->method}の\texttt{->}の後に来ているAPI名である。
ソースコード\ref{src:insertTest1}の\texttt{pop2Test}では、 \texttt{stack->pop2}の呼び出しをしているため、 \texttt{stack}がインスタンスであり、 \texttt{pop2}がAPIである。
現在解析しているgoto文が含まれているCodeGearの名前は、変数\texttt{\$currentCodeGear}で別途保存している。
連想配列である\texttt{\$codeGearInfo}の中には、 各CodeGearで使われている変数と変数の型などの情報が格納されている。
ソースコード\ref{src:parsedOutputStub.pl}の9行目では、 \texttt{\$codeGearInfo}経由でInterfaceのインスタンスから、具体的にどの型が呼ばれているかを取得する。
\texttt{pop2Test}では、 インスタンス\texttt{stack}に対応する型名は\texttt{Stack}と解析される。

ソースコード\ref{src:parsedOutputStub.pl}の10行目で実行されている\texttt{findExistsOutputDataGear}はgenerate\_stub.pl内の関数である。
これはInterfaceの名前とメソッド名を与えると、 Interfaceの定義ファイルのパース結果から出力の有無を確認する動きをする。
出力がある場合は出力している変数名の一覧を返す。
ソースコード\ref{src:insertTest1}の例では\texttt{pop2}は\texttt{data}と\texttt{data1}を出力している為、 これらがリストとして関数から返される。
出力がない場合は偽値を返すために13行目からのif文から先は動かない。
出力があった場合はgenerate\_stub.plの内部変数に出力する変数名と、 Interfaceの名前の登録を行う。
生成するStubは命名規則が、 \texttt{\_\_code CodeGearStub\_1}のように末尾に\texttt{\_}に続けて数値をいれる。
この数値は変換した回数となるため、 この回数の計算を行う。


27行目で\texttt{\$generateHaveOutputStub}のlist要素に現在のCodeGearの名前と、 出力に関する情報を代入している。
現在のCodeGearの名前を保存しているのは、この後のコード生成部分でenumの番号を切り替える必要があるためである。
ソースコード\ref{src:insertTest1}の例では\texttt{pop2Test}が使うenumを書き換える必要がある為、 ここの\texttt{\$currentCodeGear}はpop2Testとなる。
ここで作製した\texttt{\$outputStubElem}は、返還後のCbCコードを生成しているフェーズで呼びされる。

\subsection{enumの差し替え処理}

ソースコード\ref{src:generatePickNext.pl}の箇所は遷移先のenumをPerlスクリプトで生成し、 GearsOSが実行中にenumをcontextに書き込むコードを生成するフェーズである。
\lstinputlisting[label=src:generatePickNext.pl, caption=Gearefのコード生成部分]{src/generatePickNext.pl}
if文で条件判定をしているが、前者は出力があるケースかどうかのチェックである。
続く条件式はGearsOSのビルドルールとして静的に書いたstubの場合は変更を加えない為に、 静的に書いているかどうかの確認をしている。
変数\texttt{\$pick\_next}で継続先のCodeGearの名前を作製している。
CodeGearの名前は一度目の解析で確認した継続先に\texttt{\_}とカウント数をつけている。
ここで作製したCodeGearの名前を、3行目でcontextに書き込むCbCコードとして生成している。


実際に生成された例題をソースコード\ref{src:replaceenum}に示す。
\lstinputlisting[label=src:replaceenum, caption=enumの番号が差し替えられたCodeGear]{src/replaceenum.cbc}