Documents of TEBA

Preg 演習

1 preg.pl について

TEBA のコマンド preg.pl はソースファイル内のパターン検索を行うツールである。 ツールのオプション等は TEBA のマニュアル「TEBAの使い方」で確認できる。その中の次の箇所を確認すること。

また、必要に応じて、preg.pl に関する論文“A Pattern Search Method For Unpreprocessed C Programs based on Tokenized Syntax Trees”も参照すること。

1.1 検索対象の準備

この資料では、GNU Core Utils のソースコードを検索の対象とするので、 以下の手順で、準備をしなさい。

  1. GNU Core Utils のウェブページから、最新のソースコードをダウンロードし、 自分のホームディレクトリの適当な場所に展開しなさい。 (注意: TEBA とは別のディレクトリにすること)
  2. 展開したファイル群のうち、ディレクトリ src に移動し、 ls.c があることを確認しなさい。

どのコマンドのソースファイルが一番長い?

ファイルの行数は wc コマンドで数えられます。 どのソースファイルが一番長いかを調べたいときは、wc コマンドで調べた行数で、 降順で並べ替えをして、その上位を見るようにします。 例えば、次のように実行してみましょう。

wc -l *.c | sort -nr | head

wc の -l オプションは、行数のみを出力させます。 また、sort は並べ替えのコマンドで、-n は先頭の字句を数値と見なすことを意味し、 -r は降順で並べ替えることを意味します。 head は、出力の先頭の数行だけを出力するコマンドです。 これらのコマンドもすべて、GNU Core Utils に含まれています。

1.2 コマンドの確認

以下の手順でコマンドの動作を確認しなさい。

  1. preg.pl -h を実行し、エラーにならないこと、および、 出力されるヘルプを確認しなさい。
  2. ヘルプの説明がTEBAのマニュアルの 2.1 コマンド にあることを確認しなさい。

1.3 switch 文の検索

switch 文を例に検索できることを確認する。以下の手順に従い、preg.pl での検索と grep での検索を行い、その違いを理解しなさい。

  1. ls.c 内の switch 文 を preg.pl で検索しなさい。なお、出力の量が多いので、 次のように出力は less で確認をすること。

preg.pl 'switch (${:EXPR}) ${:STMT}' ls.c | less

  1. grep で switch を検索しなさい。コマンドの例は次に示す。

grep -w switch ls.c | less

なお、オプションの -w の意味は man で確認をすること。 (-w の有無での違いを確認するとよい)

  1. grep より preg.pl で検索することの利点を考えよ。

1.4 preg.pl のオプションの確認

preg.pl の次のオプションについて、2.1 コマンドを参考に、意味を確認せよ。

使用例: preg.pl -v -l3 'switch (${:EXPR}) ${:STMT}' ls.c | less -R

使用例: preg.pl -vb3 'switch (${:EXPR}) ${:STMT}' ls.c | less -R

使用例: preg.pl -av 'switch (${:EXPR}) ${:STMT}' ls.c | less -R

使用例: preg.pl -tv 'switch (${:EXPR}) ${:STMT}' ls.c | less -R

なお、-v オプションは検索結果の範囲を目立たさせるために色を付けるオプションである。 このときは less コマンドには -R オプションをつける必要がある。

1.5 複合的な構造の検索

プログラムの中に、複合文ではなく(つまり、波括弧で囲われることなく)、 if を子として直接持つ for 文が存在する。その for 文を検索しなさい。 検索時に実行したコマンド(引数を含む)を回答しなさい。

2 部分指定による検索

1つの構文要素の一部をパターンで指定することで、その構文要素を検索する方法がある。 この方法は、構文要素をTEBA の字句系列に変換したときに、 その一部をパターンとして記述していることと等しい。 次の例は、main 関数を検索する方法である。

preg.pl -b1 'void ${%begin}main${%end}(){}' ls.c | less

ここで、${%begin}${%end} は、パターンを内部で構文解析して、 字句系列にしたときに、これらに囲まれた範囲を使うという意味になる。 パターンから、この2つの字句を除き、スペースを入れて表記すると次のようになる。

void main() { }

この表記では、main 関数の返り値の型が void で、定義内には何も文が存在しない。 しかし、${%begin}${%end} で囲われた部分を使って検索するので、 その外側の部分の不一致は検索には関係ない。(注意: main 関数は本来 int 型)

この検索がうまくいくのは、${%begin}${%end} の間の字句に B_FR が含まれるからで、関数名以外に main が出現しても(それは許される)、 それには適合しない。実際に検索された字句列は次のように確認するとよい。

preg.pl -t 'void ${%begin}main${%end}(){}' ls.c | less

なお、検索時に -b1 をつけているのは、適合した箇所を含む関数定義そのものを 出力したいので、適合箇所の周囲のブロック(この場合は関数定義になる)を指定している。

2.1 マクロ定義の検索

部分指定により、マクロ定義を出力しなさい。(ヒント: 適当なマクロ定義を書き、 すべてのマクロ定義に共通する部分だけを使う。) 検索時に実行したコマンド(引数を含む)を回答しなさい。

2.2 関数定義の検索

部分指定により、すべての関数定義を出力するパターンを記述しなさい。 (ヒント: 単純かつ適当な関数定義を複数個書き、すべての関数定義に共通する最大の範囲を使う。 引数の有無の違いにも関係しない箇所で、複数の字句が必要。) 実行したコマンド(引数を含む)を回答しなさい。

3 複合的なコマンドの組み合わせ

パターンを検索するときは、複数のパターンを適用して、条件を絞り込むこともある。 例えば、関数定義のうち、内部に ‘#ifndef’ を持つ関数を探したいとき、 この場合、次のように求める。

関数定義は型 FUNCDEF に合致することを利用する(前の問題の答ではない)と、 次のように実行すればよい。

preg.pl -t '${:FUNCDEF}' ls.c | preg.pl -v -b5 -Tuc '${%begin}#ifndef${%end} X' | less -R

なお、-b5 としているのは、関数全体が表示されるようにするためで、 適当に大きな数を指定している。

3.1 演習問題

preg.pl を組み合わせて、switch 文を含む、関数定義を出力させなさい。 -b オプションの数は十分に大きな値にすること。 実行したコマンド(引数を含む)を回答しなさい。

Copyrighted by Atsushi Yoshida. atsu@nanzan-u.ac.jp
Contact: Yoshida Atsushi, atsu@nanzan-u.ac.jp
[Documents of TEBA]