|Sapidverb|+ では、ソースプログラムを文字列の書換えによって処理できる特殊な形式に変換する。この形式に変換したものを ``StreamCode''と呼ぶ。StreamCodeは、ソースプログラムについての以下の情報から構成される。
現在の|Sapid|の実装では、StreamCodeはスタックに基づいた形式のコードに変換され、スタックマシンとしてインタープリタを実装できる。スタック基づいた形式を採用した理由は、コードの理解やインタープリタの実装が容易であることである。しかし、他の形式であっても構わず、変更操作に適していれば他の形式を採用することもありうる。以下では、現在の StreamCode の仕様について説明する。
StreamCode は以下の構文から成る命令の列である。ただし、(、)、<、>、*、?、_ はメタ文字である。また、_ は一つの空白を表わす。
<オペレータ>(_<オペランド>)*(_: <表現>)?これらの命令は一つの構文要素に対応して一つまたは二つ定義されている。また、インタープリタを作ることのみが目的ではなく、変数名の書換えや最適化などプログラムを書き換えることも目的としているため、複合ブロックやステートメントなどのように、操作的な意味を持たない構文要素についても該当する命令が用意されている。
block, call, decl, decr, fbody, file, func,ident, init, jump, label, list, member, nop, op, proto, push, qual, return, spec, stmt各オペレータについて以下に説明する。
- block
- ブロック
ブロックの区切を示し、オペランドは次のように定義されている。<区切の種類> <ブロック識別子>区切の種類は以下の通りである。
- comp
- 複合ブロックの開始
- if
- if文の開始
- else
- else文の開始
- while
- while文の開始
- do
- do-while文の開始
- switch
- switch文の開始
- for
- for文の開始
- init
- for文の初期化式の終了
- cond
- 条件式の終了
- succ
- for文のsuccessorの終了
- end
- ブロックの終了
- call
- 関数呼び出し
関数呼び出しを表わし、オペランドとして引数の数を取る。呼び出される関数は、あらかじめスタックに関数へのポインタを積むことで指定しなければならない。- decl
- 宣言
変数や関数などの宣言の終わりを表わし、オペランドは存在しない。宣言の始まりを表わす命令は存在しないが、宣言では必ず型指定子から始まるため、型指定子specの存在によって始まりを知ることができる。- decr
- 宣言子
宣言を構成する宣言子を表わし、オペランドは存在しない。宣言子の始まりを表わす命令は存在しないが、宣言子では必ず型修飾子または識別子から始まるため、それらのの存在によって始まりを知ることができる。- fbody
- 関数本体
引数と単一の複合ブロックからなる関数本体の終りを表わし、オペランドは存在しない。関数本体の開始位置は定義される関数の識別子を定義する ident 命令の直後である。- file
- ファイル
ファイルの開始と終了を表わし、オペランドは次の二種類である。<ファイル名>, endファイル名が指定されたときは、そのファイルの開始を表わし、endが指定されたときはファイルの終了を表わす。- func
- 関数定義
関数の定義を表わし、オペランドは次の二種類である。<関数識別子>, end関数識別子は関数を区別するための識別子で、関数の開始を示す。また、endが指定れたときは関数定義の終了を示す。- ident
- 識別子
プログラムに出現する識別子を表わし、オペランドは、この識別子を区別するための内部的な識別子を指定する。識別子のプログラム中における名前は、識別子の表現であるため、次節で説明する「表現」の中に含まれる。- init
- 初期化式
変数宣言子に含まれる初期化式を表わし、オペランドは存在しない。この命令の前には一つの式に相当する命令列が存在しなければならない。- jump
- ジャンプ文
break, continue, goto 命令を表わし、goto 命令にはオペランドとしてラベルの内部的な識別子を指定する。- label
- ラベル文
ラベルを表わし、オペランドには内部的な識別子を指定する。switch文のcaseラベルやdeafultも、これに含まれる。- list
- リスト式
変数の宣言における初期化式に現われる式のリストを表わす。- member
- メンバ
構造体の定義におけるメンバのリストを表わす。- nop
- 空式
何も計算しない式を表わす。空文を構成する式である場合と、for文において省略された条件式である場合がある。- op
- 演算子
演算子を表わし、オペランドには演算子そのものを指定する。なお、括弧や三項演算子の場合は、それを構成する記号の一つが用いられる。- proto
- プロトタイプ宣言の引数部
プロトタイプ宣言の引数部分を表わし、オペランドには引数の数を指定する。この命令の直前には、関数識別子と指定した引数の数だけの識別子の宣言の系列が存在しなければならない。- push
- 値
変数の参照や即値の参照を表わし、オペランドは次の三種類である。<識別子>, &<識別子>, <定数値>変数識別子は、プログラムに含まれる識別子を区別するための内部的な識別子である。また、識別子の直前に ``&'' を付加した場合には左辺値を意味する。識別子が関数識別子の場合は、その関数へのポインタを意味する。- qual
- 型修飾子
ポインタや配列を表わす修飾子を表わし、オペランドには型指定子を表わす記号を指定する。- return
- リターン命令
リターン命令を表し、オペランドには返値の数を指定する。具体的には0 または 1 である。- spec
- 型指定子
宣言の型を表わし、オペランドには型名やストーレジを一つ指定する。複数の型やストーレジが必要な場合には、この命令を連続させる。- stmt
- ステートメント
式文の終わりを表わし、オペランドは存在しない。
表現は、各命令に対応した構文要素が、ソースプログラム中でどのようなテキストになっているかを表わす。表現が付加される命令は、各命令のうち単体の構文要素を表わす命令か構文要素の終わりを表わす命令のみである。
また、表現には、その構文要素を構成する構文要素に対応した表現部分は含まれず、「穴」になっている。現時点では記号 ``_#'' で表現する。例えば、式 ``1 + 2-'' に対して次のコードが生成される。 begin{quote} begin{verbatim} push 1 : "1" push 2 : "2" op + : "_# + _#" end{verbatim} end{quote} 元の表現はスタックを利用することで再構成できる。 すなわち、各表現に穴があればスタックから表現を取り出し埋め、 穴がなくなった時点でその表現をスタックに積んでいく。 このとき、穴は最後尾から埋める。 もし正しい命令系列であれば、 最終的にスタックにはファイル全体の表現のみが積まれている。 この表現方式により、Sapidのように位置情報を保持する必要がなく、 表現の変更に対しても該当箇所以外に影響を及ぼさない。 なお、 StreamCode では構文要素の終わりを表わす命令が多く存在しているが、 このようなスタックを利用した表現形式を採用したためである。 また、穴を表現する記号に ``verb+_#+'' を用いているが、これは見易さのためで、 実用化するためには、ソースプログラムには含まれない記号を使用するか、 コメントや文字列定数などに穴と同じ記号列が生じないことを保証する必要がある。 subsection{変換例} 図ref{fig:input}のプログラムを入力した場合の SapidExpandOnlyLaTeX{ StreamCodeを図ref{fig:StreamCode1},ref{fig:StreamCode2}に示す。 なお、StreamCode が長いため二つに分割して示しているが、本来は 一つの命令系列である。 begin{figure} footnotesize SapidSourceInput{Examples/hoge.c} caption{入力プログラム} label{fig:input} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/hoge.scode-1} caption{StreamCode: 前半} label{fig:StreamCode1} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/hoge.scode-2} caption{StreamCode: 後半} label{fig:StreamCode2} end{figure} } SapidExpandOnlyHTML{ StreamCodeを図ref{fig:StreamCdoe}に示す。 begin{figure} footnotesize SapidSourceInput{Examples/hoge.c} caption{入力プログラム} label{fig:input} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/hoge.scode} caption{StreamCode} label{fig:StreamCode} end{figure} } section{システム構成} 現在のverb+|+Sapidverb+|+では、二つの基本コマンドが用意されている。 一つは StreamCode を生成する``scat''である。このコマンドは 引数に指定されたソースプログラムを解析してStreamCodeを生成する。 また、ソースプログラムが指定されなかった場合には、標準入力から ソースプログラムを読み込んで解析する。 もう一つは、元のソースプログラムを再構成する``tacs''である。 このコマンドは標準入力からStreamCodeを読み込み再構成する。 また、オプションに ``{tt -v}'' を指定すると、各識別子と ブロックの直前に、それらの内部識別子を含むコメントが付加される。 これは、操作の対象を指定する際に用いる。 図ref{fig:v}に図ref{fig:input}を内部識別子付きソースプログラムに変換した 例を示す。 このソースプログラムは次のように実行した結果である。 begin{quote} verb+scat hoge.c | tacs -v+ end{quote} なお、現時点では前処理については一切対処していないため、 前処理後のソースプログラムが再構成される。 begin{figure} footnotesize SapidSourceInput{Examples/hoge.c-v} caption{内部識別子付きソースプログラム} label{fig:v} end{figure} section{応用ツールの例} verb+|+Sapidverb+|+ に含まれる応用ツールは以下の通りである。 begin{itemize} item 識別子変換ツール(chIdentName) item 識別子の重複検査ツール(chkIdentDupe) item 識別子の衝突検査ツール(chkIdentCol) item 識別子リンク生成ツール(identLink) item 関数仕様書雛型生成ツール(mkSpec.pl) item 部分評価ツール(reduceExpr, reduceIf) item 構文に基づく差分抽出ツール(stxDiff) end{itemize} 以下では、これらのツールについて簡単に説明する。 subsection{識別子変換ツール} chIdentName は、第一引数に変換した識別子の内部識別子を、 第二引数には変換後の識別子を指定すると、該当する識別子を 書き換える。 例えば、図ref{fig:input}の変数 {tt a} を {tt XXX} に変換するためには次のように実行する。 begin{quote} verb+scat hoge.c | chIdentName ##0a XXX | tacs+ end{quote} 実行結果を図ref{fig:chIdentNameEx}に示す。 StreamCode において、 内部識別子を含む命令は ident と push のみであり、各命令の表現は その識別子そのものである。 そこで、sed を用いて目的の識別子を持つ命令の表現を変換することで 識別子を変換を実現している。 begin{figure} footnotesize SapidSourceInput{Examples/hoge.XXX.c} caption{識別子変換ツールの実行例} label{fig:chIdentNameEx} end{figure} subsection{識別子の重複検査ツール} 前節の識別子変換ツールは、指定された識別子に変換するのみで、 その正当性については検証していない。そこで、 識別子が重複して宣言されていないか検査するツールが必要である。 chkIdentDupe は、ブロックの識別子と識別子名を連結させたリストを生成し、 ソートしてから uniq を利用して重複を検査する。もし重複が存在すれば 重複したブロックの識別子と識別子名が出力される。 subsection{識別子の衝突検査ツール} 同名の識別子はスコープが異なればそれぞれ宣言しても構わないが、 内側のスコープで同名の変数を再定義することは、バグを潜ませる 要因になる可能性がある。 そこで、chkIdentCol は、同一の名前を持つ識別子が既に宣言されていた場合に、 識別子の内部識別子とその識別子が宣言されているブロックの内部識別子を 出力する。 subsection{識別子リンク生成ツール} プログラム中の識別子の参照から識別子の宣言へHTMLのリンクが 張られていると便利である。 そこで、identLink は、ident と push 命令の表現部分にHTMLの命令を 埋め込んで出力する。 subsection{関数仕様書雛型生成ツール} SapidのmkSpecと同様に関数仕様書を生成することも可能である。 mkSpec.pl は、マクロに関する情報以外は、すべて mkSpec4 に 準拠した出力を行なうツールである。perlのみを使用しているが、 約 300行の記述で実現されている。 subsection{部分評価} verb+|+Sapidverb+|+の有効な応用例として部分評価が挙げられる。 その部分評価へ足掛かりとして、以下の簡単なツールを用意している。 begin{itemize} item 定数式を評価して定数値に置換する(reduceExpr) item 条件式が定数値のif文から実行されない節またはif文全体を削除する (reduceIf) item 引数が定数文字列である strlen() を定数に置換する (strlen2const) end{itemize} これらのツールは単機能しか持たないため、変化がなくなるまで各ツールを 繰り返し適用していく必要がある。 例えば、図ref{fig:pe}に対しては以下のように実行する必要がある。 begin{enumerate} item {tt streln2const} (図ref{fig:pe1}) item {tt reduce_expr} (図ref{fig:pe2}) item {tt reduce_expr} (図ref{fig:pe3}) item {tt reduce_if} (図ref{fig:pe4}) end{enumerate} begin{figure} footnotesize SapidSourceInput{Examples/pe.c} caption{pe.c: 部分評価の入力例} label{fig:pe} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/pe1.c} caption{pe.c: strlen()の定数化} label{fig:pe1} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/pe2.c} caption{pe.c: 定数式の計算} label{fig:pe2} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/pe3.c} caption{pe.c: 定数式の計算} label{fig:pe3} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/pe4.c} caption{pe.c: if文の簡略化} label{fig:pe4} end{figure} 定数式の評価では次のようなパターンを探す。 begin{quote} begin{verbatim} push <定数1> : <表現1> push <定数2> : <表現2> op <演算子> : <表現2> end{verbatim} end{quote} このパターンの各定数を演算子を用いて計算し、以下のようなコードに 置換する。 begin{quote} begin{verbatim} push <求めた定数値> : <その表現> end{verbatim} end{quote} ただし、この方式では``x + 1 + 2'' のような式は評価できない。これは、 左結合の規則に従って式が評価されるため、StreamCode中に``1 + 2''に 相当するパターンが表われないためである。この問題を解決するためには、 コンパイラの中間コードに対する最適化と同等の技術を使って等価変換する ことが必要である。 また、strlen() の評価は、関数が strlen() に限定されているにも関わらず 記述が繁雑で簡潔とは言えない。むしろ、このような操作は strlen() 専用とせず、 一般的な関数呼び出しに対して変換可能な仕組にすべきであろう。 さらに、いずれの場合においても、基本的にはある特定パターンを別のパターンへ 変換するため、パターンの変換規則を記述し適用する汎用的な仕組も必要である。 subsection{構文に基づく差分抽出ツール} stxDiff は、ソースプログラムの差分を構文に基づいて調べ、 変更箇所および追加箇所に色を付ける差分抽出ツールである。 このツールでは、二つのソースプログラムの StreamCode を diff を使って比較する。このとき、ed で処理可能な形式で 出力し、変更と追加に関する操作に含まれる StreamCode の 表現に色の情報を付加する。色の情報の記述には HTML を用いている。 この色付きの StreamCode を含む差分を ed に渡すことで 色付きの StreamCode を生成し、ソースプログラムを復元している。 begin{figure} footnotesize SapidSourceInput{Examples/stxDiff-1.c} caption{hoge.c: 元のソースプログラム} label{fig:stxDiff1} end{figure} begin{figure} footnotesize SapidSourceInput{Examples/stxDiff-2.c} caption{hogex.c: 修正したソースプログラム} label{fig:stxDiff2} end{figure} 例えば、図ref{fig:stxDiff1}から図ref{fig:stxDiff2}に変更された とすると、図ref{fig:stxDiff3}のようになる。 なお、赤が変更された箇所、青が追加された箇所を示す。 また、逆の変更の場合には図ref{fig:stxDiff4}のようになる。 begin{figure} footnotesize SapidExpandOnlyLaTeX{ SapidSourceInput{Examples/stxDiff-2.C} } SapidExpandOnlyRawHTML{ <A HREF="stxDiff-2.C.html"><TT>hogex.c</TT></A> } caption{hogex.c: 色付きの修正したソースプログラム} label{fig:stxDiff3} end{figure} begin{figure} footnotesize SapidExpandOnlyLaTeX{ SapidSourceInput{Examples/stxDiff-1.C} } SapidExpandOnlyRawHTML{ <A HREF="stxDiff-1.C.html"><TT>hoge.c</TT></A> } caption{hoge.c: 色付きの元ソースプログラム} label{fig:stxDiff4} end{figure} この出力では、変更されていない箇所に色が付いたり、追加された箇所が 変更に含まれているなど、直感的におかしいと感じる出力が含まれている。 これは、使用している diff が賢過ぎて、ed で行なう操作の回数を減らすように、 近くに位置する差分の断片をその間も含めて結合してしまったためである。 もし、賢くない diff があれば期待通りの結果が得られるであろう。 section{今後の課題} 現在のverb+|+Sapidverb+|+では、StreamCodeを提供するのみで、 そのStreamCodeを取り扱う環境を提供していない。よって、 StreamCodeに対する操作環境を用意する必要がある。 特にパターン変換を簡潔に記述できるソフトウェア操作言語の構築が必要である。 さらには、依存解析のように抽象構文木の方が実現しやすい操作もあるため、 StreamCodeを抽象構文木に見せるビューを構築する必要もある。 抽象構文木を基本形とするSapidに比べて利点はあるが、以下のような欠点も 存在し、今後の課題として残されている。 begin{itemize} item 実用規模のソースプログラムを対象としたときに、StreamCodeの 量が膨大になる。 item 各式に対する型の情報など派生的な情報はあらかじめStreamCodeに 含めることができず、常に計算しなければならない。 item 構文要素ごとにテキストを分割しているため、複数の構文要素にまたがる マクロを扱うことが難しい。 end{itemize} end{document}