[Prev] [Up] [Next] [Index] [Contents]
4 応用ツールの例
4.1 識別子変換ツール
4.2 識別子の重複検査ツール
4.3 識別子の衝突検査ツール
4.4 識別子リンク生成ツール
4.5 関数仕様書雛型生成ツール
4.6 部分評価
4.7 構文に基づく差分抽出ツール

4 応用ツールの例

|Sapid| に含まれる応用ツールは以下の通りである。

以下では、これらのツールについて簡単に説明する。

4.1 識別子変換ツール

chIdentName は、第一引数に変換した識別子の内部識別子を、 第二引数には変換後の識別子を指定すると、該当する識別子を 書き換える。 例えば、図1 の変数 a XXX に変換するためには次のように実行する。

scat hoge.c | chIdentName ##0a XXX | tacs
実行結果を図4 に示す。 StreamCode において、 内部識別子を含む命令は ident と push のみであり、各命令の表現は その識別子そのものである。 そこで、sed を用いて目的の識別子を持つ命令の表現を変換することで 識別子を変換を実現している。


     1	# 1 "hoge.c"
     2	int abs(int);   /* prototype */
     3	
     4	static int      *g;     /* global variable */
     5	
     6	/*
     7	 * main function
     8	 */
     9	void main(int argc, char **argv) {
    10	    int /* aaa */ XXX;
    11	    static char * p /* ppp */;
    12	
    13	    XXX = *g;
    14	    if (XXX > 0) {
    15	        XXX++;
    16	    } else {
    17	        XXX = abs(XXX - 1);
    18	    }
    19	
    20	    printf("%d,%d\n", XXX, *g);
    21	}

図4 識別子変換ツールの実行例

4.2 識別子の重複検査ツール

前節の識別子変換ツールは、指定された識別子に変換するのみで、 その正当性については検証していない。そこで、 識別子が重複して宣言されていないか検査するツールが必要である。 chkIdentDupe は、ブロックの識別子と識別子名を連結させたリストを生成し、 ソートしてから uniq を利用して重複を検査する。もし重複が存在すれば 重複したブロックの識別子と識別子名が出力される。

4.3 識別子の衝突検査ツール

同名の識別子はスコープが異なればそれぞれ宣言しても構わないが、 内側のスコープで同名の変数を再定義することは、バグを潜ませる 要因になる可能性がある。 そこで、chkIdentCol は、同一の名前を持つ識別子が既に宣言されていた場合に、 識別子の内部識別子とその識別子が宣言されているブロックの内部識別子を 出力する。

4.4 識別子リンク生成ツール

プログラム中の識別子の参照から識別子の宣言へHTMLのリンクが 張られていると便利である。 そこで、identLink は、ident と push 命令の表現部分にHTMLの命令を 埋め込んで出力する。

4.5 関数仕様書雛型生成ツール

SapidのmkSpecと同様に関数仕様書を生成することも可能である。 mkSpec.pl は、マクロに関する情報以外は、すべて mkSpec4 に 準拠した出力を行なうツールである。perlのみを使用しているが、 約 300行の記述で実現されている。

4.6 部分評価

|Sapid|の有効な応用例として部分評価が挙げられる。 その部分評価へ足掛かりとして、以下の簡単なツールを用意している。

これらのツールは単機能しか持たないため、変化がなくなるまで各ツールを 繰り返し適用していく必要がある。 例えば、図5 に対しては以下のように実行する必要がある。
  1. streln2const (図6 )
  2. reduce_expr (図7 )
  3. reduce_expr (図8 )
  4. reduce_if (図9 )


     1	int main(void)
     2	{
     3	    if (strlen("12345\n") > 1 + 2) {
     4	        printf("TRUE\n");
     5	    } else {
     6	        printf("FALSE\n");
     7	    }
     8	}

図5 pe.c: 部分評価の入力例


     1	# 1 "pe.c"
     2	int main(void)
     3	{
     4	    if (6 > 1 + 2) {
     5	        printf("TRUE\n");
     6	    } else {
     7	        printf("FALSE\n");
     8	    }
     9	}

図6 pe.c: strlen()の定数化


     1	# 1 "pe.c"
     2	int main(void)
     3	{
     4	    if (6 > 3) {
     5	        printf("TRUE\n");
     6	    } else {
     7	        printf("FALSE\n");
     8	    }
     9	}

図7 pe.c: 定数式の計算


     1	# 1 "pe.c"
     2	int main(void)
     3	{
     4	    if (1) {
     5	        printf("TRUE\n");
     6	    } else {
     7	        printf("FALSE\n");
     8	    }
     9	}

図8 pe.c: 定数式の計算


     1	# 1 "pe.c"
     2	int main(void)
     3	{
     4	    {
     5	        printf("TRUE\n");
     6	    }
     7	}

図9 pe.c: if文の簡略化

定数式の評価では次のようなパターンを探す。

  push <定数1> : <表現1>
  push <定数2> : <表現2>
  op <演算子>  : <表現2>
  
このパターンの各定数を演算子を用いて計算し、以下のようなコードに 置換する。
  push <求めた定数値> : <その表現>
  
ただし、この方式では``x + 1 + 2'' のような式は評価できない。これは、 左結合の規則に従って式が評価されるため、StreamCode中に``1 + 2''に 相当するパターンが表われないためである。この問題を解決するためには、 コンパイラの中間コードに対する最適化と同等の技術を使って等価変換する ことが必要である。

また、strlen() の評価は、関数が strlen() に限定されているにも関わらず 記述が繁雑で簡潔とは言えない。むしろ、このような操作は strlen() 専用とせず、 一般的な関数呼び出しに対して変換可能な仕組にすべきであろう。 さらに、いずれの場合においても、基本的にはある特定パターンを別のパターンへ 変換するため、パターンの変換規則を記述し適用する汎用的な仕組も必要である。

4.7 構文に基づく差分抽出ツール

Cdiff は、ソースプログラムの差分を構文に基づいて調べ、 変更箇所および追加箇所に色を付ける差分抽出ツールである。

このツールでは、二つのソースプログラムの StreamCode を diff を使って比較する。このとき、ed で処理可能な形式で 出力し、変更と追加に関する操作に含まれる StreamCode の 表現に色の情報を付加する。色の情報の記述には HTML を用いている。 この色付きの StreamCode を含む差分を ed に渡すことで 色付きの StreamCode を生成し、ソースプログラムを復元している。


     1	/*
     2	 *  Test Program for |Sapid| (Stream-Oriented Sapid)
     3	 */
     4	
     5	int f(int, char);       /* prototype */
     6	
     7	static int      *g;     /* global variable */
     8	/*
     9	 * main function
    10	 */
    11	void main(int argc, char **argv) {
    12	    int a, /* aaa */ b, aa;
    13	    static char * p /* ppp */;
    14	
    15	    g = &argc;
    16	    a = 1+2 /* xxx */ ; /* yyy */
    17	
    18	    if (a > 0) {
    19	        int a;
    20	        int c;
    21	        b = f(a* 2,/* 3rd argument */ c = 3) ;
    22	        a++;
    23	    } else {
    24	        int c;
    25	        int a = 1 /*111*/;
    26	    }
    27	
    28	    if (0)
    29	        a=(a * b) / 2;
    30	
    31	    p = "hogehoge";
    32	    printf("%s,%c\n", p, *p);
    33	}
    34	
    35	int f(int x, char c) {
    36	    printf("%d, %c\n", x, c);
    37	}

図10 hoge.c: 元のソースプログラム

     1	/*
     2	 *  Test Program for |Sapid| (Stream-Oriented Sapid)
     3	 */
     4	
     5	int f(int, char);       /* prototype */
     6	
     7	static int      *g;     /* global variable */
     8	/*
     9	 * main function
    10	 */
    11	void main(int argc, char **argv) {
    12	    int A, /* aaa */ b, aa;
    13	    static char * p /* ppp */;
    14	
    15	    g = &argc;
    16	    A = 1+3 /* xxx */ ; /* yyy */
    17	
    18	    if (a > 0) {
    19	        int a;
    20	        int c;
    21	        b = f(a* 2,/* 3rd argument */ g = 3) ;
    22	        a++;
    23	    } else {
    24	        int c;
    25	        int a = 1 /*111*/;
    26	        c = a;
    27	    }
    28	
    29	
    30	    A=(A * b) / 2;
    31	
    32	
    33	    if (p != NULL) {
    34	        p = "hogehoge";
    35	    }
    36	    printf("%s,%c\n", p, *p);
    37	}
    38	
    39	int f(int x, char c) {
    40	    printf("%d, %c\n", x, c);
    41	}

図11 hogex.c: 修正したソースプログラム

例えば、図10 から図11 に変更された とすると、図12 のようになる。 なお、赤が変更された箇所、青が追加された箇所を示す。 また、逆の変更の場合には図13 のようになる。


hogex.c
図12 hogex.c: 色付きの修正したソースプログラム


hoge.c
図13 hoge.c: 色付きの元ソースプログラム

この出力では、変更されていない箇所に色が付いたり、追加された箇所が 変更に含まれているなど、直感的におかしいと感じる出力が含まれている。 これは、使用している diff が賢過ぎて、ed で行なう操作の回数を減らすように、 近くに位置する差分の断片をその間も含めて結合してしまったためである。 もし、賢くない diff があれば期待通りの結果が得られるであろう。


[Prev] [Up] [Next] [Index] [Contents]