EclipseでJavaCCを使ってみる

EclipseでJavaCCを使ってみたのでメモ．
今回は，整数のみを受け付けるコンソールを作ってみる．

#1. はじめに
##1.1 実行環境
JavaCC 6.0 ([入手先](https://javacc.java.net/))
Eclipse 4.4.2
Windows 8.1
Java version 1.7

##1.2 JavaCC
Java Compiler Compiler の略.
ざっくり言うと，構文解析をするJavaファイル(Parser)を作成してくれるライブラリ．
JavaCCを使うことで，独自の文法(BNF)を定義でき，その文法でしか入力を受け付けないコンパイラを作ることができる．


##1.3 JJTree
構文解析木を勝手に作ってくれる便利なツール．
基本的にはJJTreeファイル(`.jjt`)に文法を定義する．

##2. 準備
以下の画像のように，
1. libフォルダにダウンロードした`javacc.jar`を置き，パスを通しておく．
2. `test`，`parser`という2つのフォルダを作成．
3. testフォルダに`Main.java`と`test1.jjt`というファイルを作成．
4. プロジェクトフォルダ直下に`build.xml`というファイルを作成．

![image](https://qiita-image-store.s3.amazonaws.com/0/71593/143dd6d2-454a-d712-1335-cf3949152303.png)

これで準備OK．parserフォルダは出力ファイルを入れる用のフォルダなので，今は空のままでOK．

#3. parserの定義(JJTreeファイルの作成)
##3.1 定義
先ほど作成した`test1.jjt`でparserを定義する．
今回は整数のみを受け付けるように定義．

```java:test1.jjt
/** オプション */
options {
	/* parserをfor文などで繰り返し使うならfalseにする */
	STATIC = false;
}

/** Parserクラスの定義 */
PARSER_BEGIN(MyParser)
package parser;
public class MyParser {
    /* ここに，普通のJavaクラスのようにメソッドを定義できるっぽい */
}
PARSER_END(MyParser)
/** ここまでがParserクラスの定義 */
/** 以下は文法の定義 */

/** 無視する文字を定義 */
SKIP:
{
    " " | "\r" | "\t" | "\n"
}

/** 出現するトークンを定義 */
TOKEN:
{
        <INTEGER: (["0"-"9"])>
}

/** Parserの実行
 *  このメソッドをMain.javaから呼び出す
 */
void doParse():
{
	/* ここで使う変数を宣言 */
	Token integer;
}
{
	/* 入力された文字を表示 */
	integer = <INTEGER> {
		System.out.println("inputted: "+integer.image);
	}
}
```

##3.2 メモ
作成中に気が付いたことをメモしておく．
・JJTreeでは正規表現が使える．
・再帰処理はできないっぽい．


#4. JavaCCでParserのJavaコードを作成
##4.1 JJTreeからJavaコードを作成するタスクの定義
上記で定義した`test1.jjt`からJavaファイルを作成する．
そのためのタスクを`build.xml`としてxmlで定義し，antで実行．

```xml:build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="javacc_test" default="javacc" basedir=".">
    
    <!-- javaソースの出力先 -->
    <property name="src" location="src/parser"/>
    
    <!-- javacc.jarを置いているディレクトリ -->
    <property name="javacc.home" location="lib"/>
    
    <!-- jjtファイルの配置場所 -->
    <property name="jjt.file" location="src/test/test1.jjt"/>
    <!-- jjファイルの配置場所 -->
    <property name="jj.file" location="${src}/test1.jj"/>
    
    <target name="javacc">
        <delete dir="${src}" includes="*.*"/>
        
        <!-- jjtreeタスクを実行して、jjtからjjファイルとjavaファイルを作成 -->
        <jjtree target="${jjt.file}"
            javacchome="${javacc.home}"
            outputdirectory="${src}"/>
                 
        <!-- jjファイルからjavaファイルを作成 -->
        <javacc target="${jj.file}"
            javacchome="${javacc.home}"
            outputdirectory="${src}" />
    </target> 
</project>
```

##4.2 タスク実行
Eclipseのパッケージエクスプローラで，`build.xml`を右クリック → `Run As`を選択 → `Ant Build` をクリック．
コンソールにいろいろ表示されるが，最後に

```
BUILD SUCCESSFUL
Total time: 1 second
```
のように出れば成功．

##4.3 実行結果
実行すると，parserフォルダに自動的に必要なファイルが作成される．

![image](https://qiita-image-store.s3.amazonaws.com/0/71593/6750ae75-3ac5-c3fd-1fc1-3b2118d32f70.png)


#5. メインクラス作成
以下のようにメインクラス(`Main.java`)を作成する．
コンソールっぽくしたかったので，exitと入力されるまで何度でも実行できるようにした．
同様の理由で ">" が頭に表示されるようにしてある．

```java:Main.java
package test;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import parser.MyParser;
import parser.ParseException;
import parser.TokenMgrError;

public class Main {
	public static void main(String[] args) throws IOException {
		String inputStr;

		outside_loop: for (;;) {
			inputStr = null;
			System.out.print(">");
			inputStr = new BufferedReader(new InputStreamReader(System.in)).readLine();
			
			if (inputStr.matches("exit" + ".*")) {
				System.out.println("shutdown program");
				break outside_loop;
			}
			else {
				try {
					/* paeserの引数はInputStream型なので，String型から戻す */
					InputStream is = new ByteArrayInputStream(inputStr.getBytes("utf-8"));
					MyParser myParser = new MyParser(is);
					myParser.doParse();
				} catch (TokenMgrError ex) {
					System.out.println("TokenMgrError:" + ex.getMessage());
				} catch (ParseException ex) {
					System.out.println("ParseException:" + ex.getMessage());
				}
			}
		}
	}
}
```

#6. 実行結果
```
>1
inputted: 1
>2
inputted: 2
```

整数はちゃんと受理される．

```
>a
TokenMgrError:Lexical error at line 1, column 1.  Encountered: "a" (97), after : ""
```

アルファベットは受理されず，例外処理となる．

#7. まとめ
JavaCCを使えば簡単にオリジナルのParserを作れる．
JJTreeファイルは，クセがあって慣れるのに時間がかかりそうだが，慣れれば非常に便利．
今後はJavaCCを使ってPrologのParserを作る予定．

#8. 参考
以下のサイトを参考にさせていただきました．
http://symfoware.blog68.fc2.com/blog-entry-1006.html
http://d.hatena.ne.jp/kakkun61/20111222/1324579137