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パラメータであるtokenizeComments
をtrue
にします。
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);
}
}
}
}