本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはPerlで長年Webサービス開発に携わっているマコピーこと谷脇真琴さんで、
本稿のサンプルコードは、
日常化するプログラムの静的解析
昨今のプログラミング現場では、
静的解析とは、
- ソースコード内の情報を多く使うことができる
- コメントや関数の位置、
ステートメントの前後関係を解析に使える - 一般的に動的解析に比べて安全かつ高速
- プログラムの実行には必須のリソースが必要ない
- プログラミング言語の機能を超えた解析が可能
- コメントを用いたアノテーションなどを用いてコード生成に用いる
本稿では、Perl::Tidy
の使い方を紹介したあと、
本稿は、
- Perl 5.
30. 0 - PPI 1.
270 - Perl::Tidy 20200619
- Git::Repository 1.
324 - Git 2.
26. 2
Perlの静的解析モジュールの使い方
Perlには、perl
コマンド以外で解析することは困難である」perl
コマンド以外でも実用的に静的解析を行えるモジュールがCPANには存在します。
本節ではその中でも、PPI
とPerl::Tidy
を紹介します。
抽象構文木が利用できるPPI
PPI
は、Perl::Critic
で使われています。
PPI
でソースコードを解析すると、PPI
の用語では、PDOM
と呼んでいます。PDOM
は、
PDOM
に変換すると、
ソースコードを解析する
PPI
を用いてPerlのソースコードを解析する方法を解説します。
今回は、PDOM
に変換します。次のコードでは、$var
変数にテキストを挿入しています。
my $var = "Lorem ipsum dolor sit amet";
PPI
でソースコードを読み込むにはいくつかの方法がありますが、
use PPI;
my $source = <<'EOL';
my $var = "Lorem ipsum dolor sit amet";
EOL
my $doc = PPI::Document->new(\$source);
PPI
で文字列のソースコードを読み込むには、
このソースコードのPDOM
を見てみます。人の目で見てわかりやすく出力するために、PDOM
の構造を表示するPPI::Dumper
を使用します。
use PPI::Dumper;
my $dumper = PPI::Dumper->new($doc);
$dumper->print;
1つ前のコードでPPI::Document
を用いて解析したPDOM
を$doc
に入れて、PPI::Dumper
に渡しています。そして、print
メソッド用いて標準出力へ出力しています。
このコードを実行すると、
PPI::Document
PPI::Statement::Variable
PPI::Token::Word 'my'
PPI::Token::Whitespace ' '
PPI::Token::Symbol '$var'
PPI::Token::Whitespace ' '
PPI::Token::Operator '='
PPI::Token::Whitespace ' '
PPI::Token::Quote::Double '"Lorem ipsum dolor sit
amet"'
PPI::Token::Structure ';'
PPI::Token::Whitespace '\n'
PPI::Dumper
は木構造をインデントで表現します。PPI::Document
が、PPI::Document
は、PPI::Element
を持ちます。PPI::Statement::Variable
やPPI::Token::Word
いったPDOM
は、PPI::Element
を継承しています。
上記の出力を見ると、PPI::Document
は、PPI::Statement::Variable
クラスのPDOM
を1個だけ持ちます。変数の宣言は、PPI::Statement::Variable
クラスで表現されます。PPI::Statement::Variable
を含むPPI::Statement
から始まるクラスは、
PPI::Statement::Variable
も複数のPDOM
を持って入れ子構造を作っています。PPI::Statement::Variable
が持つPDOM
は、PPI::Token
から始まるクラスです。PPI::Token
から始まるクラスは、my
キーワードはPPI::Token::Word
、PPI::Token::Whitespace
、PPI::Token::Symbol
です。
解析した結果をソースコードに戻す
先ほど見たように、PDOM
には、PPI
のユースケースとして、
生成したPDOM
から、
say $doc->content;
すると、
PPI::Element
クラスには、content
メソッドが定義されています。また、PPI::Statement::Variable
などのクラスにもcontent
メソッドが定義されており、
ソースコード上の位置を出力する
もとのソースコードに戻すために、PDOM
にはファイル内での行と列の情報も入っています。
ここでは、PPI::Statement::Variable
のファイル上の位置を取り出してみます。先ほどのPPI::Dumper
の結果から、PPI::Statement::Variable
はPPI::Document
の先頭にあるので、first_
メソッドで取り出します。そのうえで、PPI::Element
のメソッドであるline_
およびcolumn_
メソッドで、
my $statement = $doc->first_element;
say $statement->line_number;
say $statement->column_number;
実行すると、
では、PPI::Statement::Variable
のelements
メソッドで字句のリストを取得して、line_
とcolumn_
メソッドで行番号と列番号を表示し、content
メソッドで内容を表示します。
for my $elem ($statement->elements) {
say "line=" . $elem->line_number .
", column=" . $elem->column_number .
", content=\"" . $elem->content . "\"";
}
実行結果を示します。
line=1, column=1, content="my" line=1, column=3, content=" " line=1, column=4, content="$var" line=1, column=8, content=" " line=1, column=9, content="=" line=1, column=10, content=" " line=1, column=11, content=""Lorem ipsum dolor sit amet"" line=1, column=39, content=";"
line
はすべて1のままですが、column
は文字列内の位置によって変わっています。
ソースコード内で宣言されている変数を列挙する
PPI
の簡単な使用例として、
次のソースコードを解析します。if
文で構造が作られている複雑なソースコードです。
my $v1 = "Lorem ipsum dolor sit amet";
if ($var) {
my $v2 = " consectetur adipiscing elit";
$v1 .= $v2;
}
say $v1;
上記のソースコード内で宣言されている変数名を列挙します。$source
に上記のソースコードが格納されているとすると、
my $doc = PPI::Document->new(\$source);
my $vars = $doc->find("PPI::Statement::Variable");
my @vnames = map { $_->variables } @$vars;
say "@vnames";
このプログラムを実行すると、$v1 $v2
と出力されます。ソースコード中で宣言された変数の名前が抜き出せました。
PPI::Document
に定義されているfind
メソッドは、PPI
のクラス名を持っている木構造から検索して列挙します。上のコードでは、PPI::Statement::Variable
クラスを指定しています。
PPI::Statement::Variable
クラスにはvariables
メソッドが定義されていて、my ($v1, $v2) = ...
と一度に複数の変数名が宣言された場合は、
このプログラムを応用すると、
- ソースコード中の変数がプロジェクトの命名規則にのっとったものになっているか
- 未使用の変数がないか
- Perl 5.
24. 0で廃止された、 keysなどの組込み関数にリファレンスを渡せる機能が使われていないか
コード整形ツールPerl::Tidy
Perl::Tidy
は、Perl::Tidy
は静的解析を行い、
Perl::Tidyをモジュールとして使う
多くの場合Perl::Tidy
はperltidy
コマンドで使われますが、Perl::Tidy
をuse
したうえで、Perl::Tidy::perltidy
関数を使います。
次の例では、prefilter
と、postfilter
を指定しています。
use Perl::Tidy;
my $source = '...';
my $destination = '';
Perl::Tidy::perltidy(
argv => undef,
source => \$source,
destination => \$destination,
prefilter => sub { ... },
postfilter => sub { ... },
);
特定のコメントが現れた次の行は整形しない
Perl::Tidy
の静的解析の結果は、Perl::Tidy
の挙動をカスタマイズする際に使用できます。ここでは、Perl::Tidy
の具体的なカスタマイズ例を紹介します。
perltidy
コマンドを通常どおり使用していると、
たとえば、
my $map = {
key => [{
"bar" => "bazz",
}, {
"foo" => "boo",
}],
};
ハッシュリファレンスの入れ子構造の場合、 前項で述べた しかし、 このコードでは、 <続きの 2022年8月24日発売Perl::Tidy
で整形すると、my $map = {
key => [
{
"bar" => "bazz",
},
{
"foo" => "boo",
}
],
};
Perl::Tidy
のデフォルト設定だと、Perl::Tidy
をカスタマイズし、Perl::Tidy::perltidy
関数には、formatter
オプションで独自の整形を行うオブジェクトを渡せます。オブジェクトには、Perl::Tidy::Formatter
が用いられます。通常の動作から少しだけ変えたい場合は、Perl::Tidy::Formatter
の挙動を少しだけ変えるとよさそうです。Perl::Tidy::Formatter
は、perltidy
関数内部で初期化されるPerl::Tidy::Formatter
が持つ、write_
の挙動を直接上書きして手間を省きます。Perl::Tidy
のドキュメントには、write_
関数は1行ごとに呼び出されると記述されています。引数として、*Perl::Tidy::Formatter::write_line = sub {
my ($obj, $line_of_tokens) = @_;
if ($ignore_lines > 0) {
$ignore_lines--;
$line_of_tokens->{_line_type} = "POD";
}
if (
$line_of_tokens->{_line_type} eq "CODE" &&
$line_of_tokens->{_rtoken_type}[0] eq "#" &&
$line_of_tokens->{_rtokens}[0] =~
/# ignore (\d+) lines?/
) {
$ignore_lines = $1;
}
}
# ignore <数字> lines
形式のコメントを検知して、$ignore_
変数に保存しています。$ignore_
変数は関数の外にあるため、$line_
は渡された行の種別を示します。通常のPerlソースコードであればCODE
が入りますが、POD
、__
より下の行であればEND
が入ります。Perl::Tidy::Formatter
はCODE
以外ではコードの整形を行わないので、POD
に上書きして整形をスキップします。Perl::Tidy
はとても複雑なモジュールで、Perl::Tidy
のドキュメントを参照してカスタマイズするとよいでしょう。本誌最新号をチェック!
WEB+DB PRESS Vol.130
B5判/
定価1,628円
ISBN978-4-297-13000-8
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
いまはじめるFlutter
iOS/
作って学ぶWeb3
ブロックチェーン、