読者です 読者をやめる 読者になる 読者になる

初代Masteries

きっとモヒカンにもなれないお前たちに告げる!!!

Compiler::Lexerで遊んでみた.

perl

Compiler::Lexerというモジュールがあります.
これは, mixiの@goccy54さんがgperlという「速度を重視したPerlの実装」を開発した際に作った, Perlの字句解析器をモジュール化したものです. たぶん.

gperlについては, 去年のYAPC::Asia 2012で「Perlと出会い、Perlを作る」という発表をされています.

ちなみに, mixi社内ではCompiler::Lexerを利用したCompiler::Tools::CopyPasteDetectorというコピペ検出ツールが活用されているそうです.
これについては, mixi Engineers' Blogの「続・技術的負債の把握と改善を促すために」に詳しく書かれています.

...というわけで, 今回はCompiler::Lexerを導入して, Perl5のコードを字句解析してみました.

インストール

$ git clone git://github.com/goccy/p5-Compiler-Lexer.git
$ cd p5-Compiler-Lexer
$ perl Makefile.pl
$ make
$ make install

基本的に, READMEの通りにコマンドを打ち込んでいけばインストールができますが, 'make test'で行われるテストは常に失敗します.
これについては,

...とつぶやいた3時間後くらいに@tokuhiromさんからpull requestが送られていて, この修正を適用すればテストが成功するようになります.

問題があるのはテストそのもの(t/Lexer.t)なので, Compiler::Lexerには問題はないようです.

遊ぶ!

というわけで, Compiler::Lexerを使って遊んでみましょう.

use Compiler::Lexer;
use Data::Dumper;

my $filename = $ARGV[0];
open(my $fh, "<", $filename) or die("Error");
my $code = do { local $/; <$fh> };
my $lexer = Compiler::Lexer->new($filename);
my $tokens = $lexer->tokenize($code);
print Dumper $tokens;
print Dumper $lexer->get_groups_by_syntax_level($$tokens, Compiler::Lexer::SyntaxType::T_Stmt);
print Dumper $lexer->get_used_modules($code);

READMEに, こんな感じのサンプルコードがあったのでこれを使ってみます.
引数としてPerlスクリプトのファイル名を受け取って, そのスクリプトを字句解析してくれるようです.

use strict;
use warnings;

my $word = "hello!\n";
for my $i (1..100) {
    print $word;
}

「hello!」という文字列を100回出力する, このようなスクリプトを字句解析してみます.
すると...

$VAR1 = \[
            bless( {
                     'kind' => 3,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'UseDecl',
                     'data' => 'use',
                     'type' => 87,
                     'line' => 1
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'UsedName',
                     'data' => 'strict',
                     'type' => 88,
                     'line' => 1
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 26,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'SemiColon',
                     'data' => ';',
                     'type' => 99,
                     'line' => 1
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 3,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'UseDecl',
                     'data' => 'use',
                     'type' => 87,
                     'line' => 2
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'UsedName',
                     'data' => 'warnings',
                     'type' => 88,
                     'line' => 2
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 26,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'SemiColon',
                     'data' => ';',
                     'type' => 99,
                     'line' => 2
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 3,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'VarDecl',
                     'data' => 'my',
                     'type' => 57,
                     'line' => 4
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'LocalVar',
                     'data' => '$word',
                     'type' => 176,
                     'line' => 4
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 2,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'Assign',
                     'data' => '=',
                     'type' => 60,
                     'line' => 4
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'RawString',
                     'data' => 'hello!\\n',
                     'type' => 164,
                     'line' => 4
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 26,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'SemiColon',
                     'data' => ';',
                     'type' => 99,
                     'line' => 4
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 22,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'ForStmt',
                     'data' => 'for',
                     'type' => 125,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 3,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'VarDecl',
                     'data' => 'my',
                     'type' => 57,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'LocalVar',
                     'data' => '$i',
                     'type' => 176,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 27,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'LeftParenthesis',
                     'data' => '(',
                     'type' => 100,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'Int',
                     'data' => '1',
                     'type' => 161,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 1,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'Slice',
                     'data' => '..',
                     'type' => 54,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'Int',
                     'data' => '100',
                     'type' => 161,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 27,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'RightParenthesis',
                     'data' => ')',
                     'type' => 101,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 27,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'LeftBrace',
                     'data' => '{',
                     'type' => 102,
                     'line' => 5
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 4,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'BuiltinFunc',
                     'data' => 'print',
                     'type' => 64,
                     'line' => 6
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 21,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'Var',
                     'data' => '$word',
                     'type' => 157,
                     'line' => 6
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 26,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'SemiColon',
                     'data' => ';',
                     'type' => 99,
                     'line' => 6
                   }, 'Compiler::Lexer::Token' ),
            bless( {
                     'kind' => 27,
                     'has_warnings' => 0,
                     'stype' => 0,
                     'name' => 'RightBrace',
                     'data' => '}',
                     'type' => 103,
                     'line' => 7
                   }, 'Compiler::Lexer::Token' )
          ];
$VAR1 = \[
            {
              'token_num' => 3,
              'has_warnings' => 0,
              'end_line' => 1,
              'src' => ' use strict ;',
              'start_line' => 1,
              'indent' => 0,
              'block_id' => 0
            },
            {
              'token_num' => 3,
              'has_warnings' => 0,
              'end_line' => 2,
              'src' => ' use warnings ;',
              'start_line' => 2,
              'indent' => 0,
              'block_id' => 0
            },
            {
              'token_num' => 5,
              'has_warnings' => 0,
              'end_line' => 4,
              'src' => ' my $word = \'hello!\\n\' ;',
              'start_line' => 4,
              'indent' => 0,
              'block_id' => 0
            },
            {
              'token_num' => 13,
              'has_warnings' => 1,
              'end_line' => 7,
              'src' => ' for my $i ( 1 .. 100 ) { print $word ; }',
              'start_line' => 5,
              'indent' => 0,
              'block_id' => 0
            },
            {
              'token_num' => 3,
              'has_warnings' => 1,
              'end_line' => 6,
              'src' => ' print $word ;',
              'start_line' => 6,
              'indent' => 1,
              'block_id' => 1
            }
          ];
$VAR1 = [
          {
            'args' => '',
            'name' => 'strict'
          },
          {
            'args' => '',
            'name' => 'warnings'
          }
        ];

...出力結果はとても長いですが, 3種類の出力が得られていることがわかります.
まず, 最初の'$lexer->tokenize($code)'では, スクリプトを字句解析した結果をトークン化して取得できるようです.
次の'$lexer->get_groups_by_syntax_level($$tokens, Compiler::Lexer::SyntaxType::T_Stmt)'では, トークンをシンタックスのレベル単位(今回は'Compiler::Lexer::SyntaxType::T_Stmt')でグループ化して表示できるようです.
最後の'$lexer->get_used_modules($code)'では, 使用しているモジュール・プラグマの一覧を取得できるようです.

まとめ

...とまあ, こんな感じでPerlの字句解析がお手軽にできるので, 「ほえー, こんな感じに字句解析できるのかー...」と, 眺めてみるのもいいかもしれません.

ちなみに, 現在取り組んでいる修論では, Perlスクリプトを字句解析したり抽象構文木を構築したりとかをやらないといけないので, Compiler::Lexerを有効に活用させて頂く所存であります!