静的解析を行うプログラムを書いてみる
静的解析を行うプログラムやツールは世の中にたくさんありますが、
本節で解析の対象とするソースコードは次に示すspell.です。
my $name = "foo";
my $namae = "bar";nameは英語辞書に載っている正しい英単語なので警告を出さず、namaeは
Compiler::Lexerでトークン列に分割する
まず、
use Compiler::Lexer;
my $filename = 'spell.pl';
# ソースコードの文字列を読み込む
open my $fh, '<', $filename
or die "Cannot open $filename: $!";
my $src = do { local $/; <$fh> };
# トークナイザのインスタンスを作る
my $lexer = Compiler::Lexer->new($filename);
# トークナイザでソースコード文字列をトークン列に分割する
my $tokens = $lexer->tokenize($src);実際にトークン列を見てみる
上記のプログラムを実行してspell.を解析すると、
[
bless( {
data => "my", …①
has_warnings => 0,
kind => 3,
line => 1, …②
name => "VarDecl", …③
stype => 0,
type => 59 …④
}, 'Compiler::Lexer::Token' ),
bless( {
data => "\$name",
has_warnings => 0,
kind => 24,
line => 1,
name => "LocalVar",
stype => 0,
type => 187
}, 'Compiler::Lexer::Token' ),
(中略)
bless( {
data => ";",
has_warnings => 0,
kind => 21,
line => 2,
name => "SemiColon",
stype => 0,
type => 103
}, 'Compiler::Lexer::Token' )
]静的解析を行うにあたってはこのトークン列を適宜見ながら処理する必要があります。
1つ目のトークンを見ると、data => "my"という記述からソースコードのmyに対応していることがわかります。また、name => "VarDecl"および④のtype => 59という記述から変数宣言のトークンであることtypeの持つ数値が何を表しているかはCompiler::Lexer::TokenTypeに記述されています)、line=> 1という記述から1行目に位置していることがわかります。以上をまとめると、
トークンの情報に基づいて機能を実装する
今回のゴールはnameがLocalVarであるトークンに注目すればよいことがわかります。したがって、LocalVarのトークンのみを処理し、
$lexer->tokenize()で取得できるトークン列は配列リファレンスなので、forによって走査できます。したがってトークン列の配列リファレンスを先頭から末尾にかけて走査していき、nameの値がLocalVarだったときだけdataの値に対してスペルチェックを行います
(前のプログラムの続き)
use Lingua::Ispell qw/spellcheck/;
for my $token (@$tokens) {
if ($token->{name} eq 'LocalVar') {
my $val = $token->{data};
my $val_name = substr($val, 1); # シジルの削除
if (spellcheck($val_name)) {
print "[Maybe Typo] '$val' at $filename " .
"line $token->{line}\n";
}
}
}今回はスペルチェックのためのモジュールとしてLingua::Ispellを利用しました。Lingua::Ispellが提供するspellcheck()という関数は、
上記のプログラムを実行すると、
[Maybe Typo] '$namae' at spell.pl line 22行目の$namaeのミススペリングをうまく検出できていることがわかります。
以上、
まとめ
静的解析の利点や、
また、
次回の執筆者は今回取り上げたCompiler::LexerやCompiler::Parserの作者である五嶋壮晃さんで、
