LoginSignup
12
9

More than 5 years have passed since last update.

Javaのソースコードをトークン化する

Last updated at Posted at 2016-02-22

Javaのソースコードからトークンを取得してみます。トークンとは、ソースコードから空白やコメントを除去した、識別子名や括弧、数値などのことです。

下はJavaのHelloWorldを出力するソースコードです。

System.out.println("hello world");

このソースコードをトークン化すると次のようになります。

System
.
out
.
println
(
"hello world"
)
;

同時に、トークンの種類(識別子名か文字列リテラルかなど)も特定できます。

ライブラリを導入する

トークン化にはEclipse JDTを使います。Eclipse JDTはEclipseで使われているJavaのコンパイラーです。

Mavenではこのサイト

Gradleで導入するにはdependenciesに以下の文を追加してください。

compile 'org.eclipse.jdt:org.eclipse.jdt.core:3.12.3'

Scannerの使い方

org.eclipse.jdt.core.ToolFactory.createScannerメソッドでIScannerインスタンスを生成し、ソースコードをトークン化します。

解析対象のソースコードは、setSourceメソッドに文字配列(char[])の形で渡します。次に、getNextToken() メソッドで、順次トークンを取得していきます。

あとは下のソースコードを見てください。

import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class JavaTokenizer {
    /**
     * プログラムのエントリポイント
     */
    public static void main(String[] args) throws IOException, InvalidInputException {
        Path sourcePath = Paths.get("src/main/java/JavaTokenizer.java");
        String source = new String(Files.readAllBytes(sourcePath));
        IScanner scanner = ToolFactory.createScanner(true, false, true, "1.9");
        scanner.setSource(source.toCharArray());
        System.out.println("token|start| end |line | ");
        int tokenType;
        while ((tokenType = scanner.getNextToken()) != ITerminalSymbols.TokenNameEOF) {
            int start = scanner.getCurrentTokenStartPosition();
            int end = scanner.getCurrentTokenEndPosition();
            int line = scanner.getLineNumber(start);
            String token = new String(scanner.getCurrentTokenSource());
            String tokenDesc = String.format("%4d |%4d |%4d |%4d | %s", tokenType,
                    start, end, line, token);
            System.out.println(tokenDesc);
        }
    }
}

getNextTokenメソッドを呼ぶことで、次のトークンに現在の状態を遷移します。

このプログラムの出力例は下のようになります。

token|start| end |line | 
 191 |   0 |   5 |   1 | import
   5 |   7 |   9 |   1 | org
   6 |  10 |  10 |   1 | .
   5 |  11 |  17 |   1 | eclipse
   6 |  18 |  18 |   1 | .
   5 |  19 |  21 |   1 | jdt
   6 |  22 |  22 |   1 | .
   5 |  23 |  26 |   1 | core
   6 |  27 |  27 |   1 | .
   5 |  28 |  38 |   1 | ToolFactory
  64 |  39 |  39 |   1 | ;

ToolFactory.createScannerのパラメータ

コメントをトークン化する

コメントは普通トークンとして扱いませんが、時にはトークンとして見たいときがあるでしょう。そんなときは第1パラメータであるtokenizeCommentstrueにします。

IScanner scanner = ToolFactory.createScanner(true, false, true, "1.9");

すると、コメントがトークンとして認識されます。

1003 | 318 | 352 |  10 | /**
     * プログラムのエントリポイント
     */

空白をトークン化する

空白は普通トークンとして扱いませんが、時にはトークンとして見たいときがあるでしょう。
そんなときは、第2パラメータをtrueに変更するとトークンとして取得できます。

IScanner scanner = ToolFactory.createScanner(false, true, true, "1.9");

出力は以下のようになります。

1000 | 222 | 223 |   5 | 

ソースコードのバージョンを指定する

上の例では第4パラメータを1.9にしています。これを変更すると、解析するソースコードのバージョンを変更できます。
ちなみに指定しない場合、Java1.3がデフォルトで設定されます。

PMDを用いる

Eclipse JDT を用いたトークン化を説明しましたが、Eclipse JDT はもとはコンパイラーなので非常にJARファイルのサイズが大きいです。また、ソースコード解析に使わない機能も多く、ライブラリ自体の理解もしにくいです。

そのため、ソースコード解析を目的としたライブラリPMDを使うことを検討しましょう。PMDとは、ソースコードを解析してバグやバグっぽい箇所を特定するツールです。以下にトークン化をするプログラムの実装例を示します。(net.sourceforge.pmd:pmd-java:5.4.1をライブラリとして追加してください)

import net.sourceforge.pmd.lang.ast.JavaCharStream;
import net.sourceforge.pmd.lang.java.ast.JavaParserTokenManager;
import net.sourceforge.pmd.lang.java.ast.Token;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaTokenizer {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("src/main/java/JavaTokenizer.java");
        try (BufferedReader br = Files.newBufferedReader(path)) {
            JavaCharStream javaCharStream = new JavaCharStream(br);
            JavaParserTokenManager javaParserTokenManager = new JavaParserTokenManager(javaCharStream);
            Token token;
            while ((token = javaParserTokenManager.getNextToken()).kind > 0) {
                String desc = String.format("%4d:%-4d %s", token.beginLine, token.beginColumn, token.image);
                System.out.println(desc);
            }
        }
    }
}
12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9