本連載では第一線の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
ブロックチェーン、

