JFlex のコンパイル後のファイルを読んでみた。

## JFlex とは？
今回は、Lucene というオープンソースの全文検索ライブラリを読もうと思ったら、意味不明な Javaファイルがあって、調べたら JFlex で書かれていたことが判明したのですが、その JFlex のファイルに関する 情報が無かったので記事にしました。どこかで同じように、JFlex の謎ファイルを見つけた人の一助になればと思います。

JFlex とは、パーサーで言うところの lexer を作ってくれるライブラリで、作った lexer で文字列をブロックに分けたりなどのことを自分でカスタマイズできます。
使い方は、flex ファイルという特別なファイルを作って、それを JFlex の jar にかませると、lexer が実装された Java ファイルが出力されるようになっている。
噛み砕いて説明すると、

１：[JFlex の jar をダウンロード](https://www.jflex.de/download.html)
２：flex ファイルを作成する。（今回は[作ってあるもの](https://inst.eecs.berkeley.edu/~cs164/sp05/support/jflex/examples/byaccj/calc.flex)を拝借しました。）ここから、下のコマンドを叩けば、Java ファイルが出力されます。

```
java -jar jflex-full-1.8.2.jar -d output ./calc.flex
```

※ 今回の calc.flex は、[同じディレクトリ](https://inst.eecs.berkeley.edu/~cs164/sp05/support/jflex/examples/byaccj) にある Parser.java などを使わないと動きません。

今回は、この `java -jar jflex-full-1.8.2.jar` 以下のコマンドを叩くと吐き出される Yylex.java の中身を見ていきたいと思います。
また、今回見ていくファイルは、少し古いバージョンの JFlex がはいていた [このファイル](https://inst.eecs.berkeley.edu/~cs164/sp05/support/jflex/examples/byaccj/Yylex.java) になります。時間があったら、最新バージョンのものも見て、差分をまとめたいと思います。

## JFlex ファイルの中身

Yylex.java のファイルを見るにあたって、ファイルの主な機能は、

１：Unicode の対応表などの変数を作る機能 (ZZ_CMAP, ZZ_ROWMAP, ZZ_TRANS, ZZ_ATTRIBUTE, ZZ_ACTION)
２：ファイルの読み込みの機能 (zzRefill など)
３：lexer の機能 (yylex)

になります。
ここでは、ZZ_CMAP, ZZ_TRANS, ZZ_ROWMAP, ZZ_ATTRIBUTE, ZZ_ACTION の ５つの変数の説明をします。

##### ZZ_CMAP

```java
  private static final char [] ZZ_CMAP = {
     0,  0,  0,  0,  0,  0,  0,  0,  7,  6,  3,  0,  0,  4,  0,  0, 
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 
     6,  0,  0,  0,  0,  0,  0,  0,  5,  5,  5,  5,  0,  5,  2,  5, 
     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,  0, 
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5,  0, 
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
  };
```

ここでは、ZZ_CMAP という Unicode と それに対応するラベルを 決めています。
flex ファイルでは、

```java
NUM = [0-9]+ ("." [0-9]+)?
NL  = \n | \r | \r\n

"+" | 
"-" | 
"*" | 
"/" | 
"^" | 
"(" | 
")"    { return (int) yycharat(0); }

\b     { System.err.println("Sorry, backspace doesn't work"); }

/* error fallback */
[^]    { System.err.println("Error: unexpected character '"+yytext()+"'"); return -1; }

```

のように、中で使う文字列を定義しましたが、その中身が 1つ1つ ラベル付けされています。
上の例で見ると、正規表現で使われている 0〜9 の数字は 1でラベル付けされ（Unicode の 48〜58番目）、正規表現で使われている「.」は 2でラベル付けされていて（Unicode の 46番目）・・・のような感じで、今回は 7 の back space まで定義されています。

簡単にまとめると、下のような変数定義になります。

- １ → 数字
- ２ → .
- ３ → 改行
- ４ → < return >
- ５ → / - + * ( )
- ６ → スペース
- ７ → バックスペース

##### ZZ_TRANS

```java
  private static final String ZZ_TRANS_PACKED_0 =
    "\1\2\1\3\1\2\1\4\1\5\1\6\1\7\1\10"+
    "\11\0\1\3\1\11\10\0\1\4\12\0\1\7\2\0"+
    "\1\12\6\0";
  /*
    1 2 1 3 4 5 6 7 
	-1 -1 -1 -1 -1 -1 -1 -1 
	-1 2 8 -1 -1 -1 -1 -1 
	-1 -1 -1 3 -1 -1 -1 -1 
	-1 -1 -1 -1 -1 -1 6 -1 
	-1 9 -1 -1 -1 -1 -1 -1 
  */
```

元の変数と、それを関数に入れた後の結果を表示しています。
後の説明で触れますが、DFA ([決定性有限オートマトン](https://www.krrk0.com/deterministic-finite-automaton/)) の対応表になっていて、この表をもとに lexer する際に 前回の状態を反映します。

##### ZZ_ROWMAP

```java
  private static final String ZZ_ROWMAP_PACKED_0 =
    "\0\0\0\10\0\20\0\10\0\30\0\10\0\40\0\10"+
    "\0\50\0\50";
  // 0 8 16 8 24 8 32 8 40 40
```

上の ZZ_TRANS は今の状態（lexしている文字）と前の状態を反映した今の状態がの２つの状態を示しています。0〜7で示されるのが現在進行形で lex している 状態で、今回の ZZ_ROWMAP では 前の何の状態を反映したかを 8の倍数で示しています。

例えば、最初の状態（ZZ_ROWMAPは0から始まる）で ZZ_CMAP でいうところの 1 (数字) が入力されると、まずは ROWMAP (前回の状態) + ZZCMAP(現在 lexしている文字) の対応表で ZZ_TRANS[1] つまり 2が出力されます。
この2が前回の状態として、次の lex をする際に、ROWMAP に入力されます。なので次も ZZ_CMAP の 1 (数字) が入力されると、次には ROWMAP = 16 + ZZ_CMAP = 1 の対応表で ZZ_TRANS[17] つまり 2が出力されます。
ここで例えば 次に 「.」 つまり 2 が入力されると、ROWMAP = 16 + ZZ_CMAP = 2 の対応表で ZZ_TRANS[18] つまり 8 が出力されます。

この 8と対応する ZZ_ROWMAP は 40 になりますが、ZZ_TRANS の 40以降は 1つ目が 9 で それ以外は -1（無効）になるので、数字 → 「.」 の後は 数字しか受け付けないことが分かります。

上のコードは、[コード](https://inst.eecs.berkeley.edu/~cs164/sp05/support/jflex/examples/byaccj/Yylex.java)の yylex の部分を説明したものになります。

##### ZZ_ATTRIBUTE

```java
	private static final String ZZ_ATTRIBUTE_PACKED_0 =
	"\1\0\1\11\1\1\1\11\1\1\1\11\1\1\1\11"+
	"\1\0\1\1";
    //	0 9 1 9 1 9 1 9 0 1 
```

元の変数と、それを関数に入れた後の結果を表示しています。
これは、上の ZZ_TRANS の結果を判別するための対応表になっています。
基本的には、9 (ZZ_ROWMAPが0の場合は、ZZ_CMAP で言う 0 / 2 / / 3 / 5 / 7 => 文字列など | . | 改行 | "/ - + * ( )" | バックスペース ) であれば 結果の判定をするようになっています。結果の判定に飛ぶのは、他には EOF にあたった時です。

##### ZZ_ACTION

```java
	private static final String ZZ_ACTION_PACKED_0 =
	"\1\0\1\1\1\2\2\3\1\4\1\5\1\6\1\0"+
	"\1\2";
    // 0 1 2 3 3 4 5 6 0 2 
```

最後に、結果の判定をするための対応表になっています。

---

ファイルの読み込みである zzReFill は比較的簡単な実装になっているので飛ばします。
yylex の説明は、上の DFA が分かれば、このくらい軽量のファイルであれば分かると思います。実際に[コードを読んで](https://inst.eecs.berkeley.edu/~cs164/sp05/support/jflex/examples/byaccj/Yylex.java) 内容の理解を深めてもらえればと思います。

再度になりますが、謎の Javaファイル の ZZ_CMAP やら ZZ_TRANS が分からない人のためになればと思います。最初は、自分も意味が分からなかった。