静的解析を行うプログラムを書いてみる
静的解析を行うプログラムやツールは世の中にたくさんありますが、
本節で解析の対象とするソースコードは次に示す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 2
2行目の$namae
のミススペリングをうまく検出できていることがわかります。
以上、
まとめ
静的解析の利点や、
また、
次回の執筆者は今回取り上げたCompiler::LexerやCompiler::Parserの作者である五嶋壮晃さんで、