JFlex と jacc を同時に使う


JFlex と jacc を併用する場合には、 JFlex のソースファイル(calc.jflex)には次のような %implements オプションを入れておきます。

implement されるクラス (CalcTokens) は jacc が生成するクラスで、NUMBEREOLなどの JFlex で使用する終端記号を表すマクロが定義されています。

また、jacc はデフォルトで lexer というフィールドの nextToken, getToken, getSemanticという メソッドを利用するので、これらのメソッドを定義しておきます。


/* import宣言はここに書く */
import java.io.IOException;

%%

// 生成するクラスの名前
%class CalcLexer
// 生成するクラスが実装するインタフェース
%implements CalcTokens
// 生成するメソッドの戻り値型
%int
%unicode
%line
%column

%{
  /* Jaccとのインタフェースのためのコード */
  int token;
  double yylval;

  int nextToken() {
    try {
      return token = yylex();
    } catch (IOException e) {
      return token = ENDINPUT;
    }
  }

  int getToken() {
    return token; 
  }

  double getSemantic() {
    return yylval;
  }
%}

ws     = [ \t\f]
digit  = [0-9]
number = {digit}+(\.{digit}+)?(E[+\-]?{digit}+)?

%%

  \r|\n|\r\n                     { return EOL; } 
  {ws}+                          { /* 無視する */ }
  {number}                       { yylval = Double.parseDouble(yytext()); return NUMBER; }
  [+\-\*\/\(\)\=]                { return (int)(yytext().charAt(0)); }
/* error fallback */
  .                              { throw new Error("不正な文字です <"+ yytext()+">"); }  

動作の中に return 文を入れておくと、 その式の値が JFlex の生成するyylex メソッドの戻り値になります。 yylex メソッドは呼び出されるたびに、次のトークンの情報を返します。

終端記号の “種類”(NUMBER, EOLなど)を yylex メソッドの値として返し、値(“属性”)を yylval というフィールドに代入していることに注意します。

一般に正規表現にマッチした文字は、 yytext() というメソッドで得ることができます。

jacc のソースファイル (Calc.jacc)の方は、 生成するクラスのコンストラクタを定義し、 lexer というフィールドに JFlex が生成するクラスのインスタンス が代入されるようにしておきます。

%next, %get, %semanticなどのオプションは、 JFlex の方で jacc のデフォルトの名前のメソッドを用意するので指定する必要はありません。


// 属性値の型
%semantic double

%token NUMBER EOL
%left  '+' '-'
%left  '*' '/'

%%
   /* 文法記述とそれに対する動作(還元時に実行されるプログラム) */
   /* 通常の BNFの → の代わりに : を書く。各BNFは ; で区切る。 */
   /* 最初に start symbolを書く */
input :   /* 空 */
      | input line EOL    {}
      ;

   /* $$は左辺の属性値、$nは右辺の n番目の文法要素の属性値を表す。 */
line  :  /* 空 */     { System.exit(0); }
      |  expr         { System.out.printf("%g%n", $1); }
      ;

expr  : expr '+' expr	{ $$ = $1 + $3; }
      | expr '-' expr	{ $$ = $1 - $3; }
      | expr '*' expr	{ $$ = $1 * $3; }
      | expr '/' expr	{ $$ = $1 / $3; }
      | '(' expr ')'    { $$ = $2; }
      | NUMBER          { $$ = $1; }
      ;

%%

  /* フィールドやメソッドの定義 */

  CalcParser(CalcLexer l) {
      lexer = l;  
  }
  
  private CalcLexer lexer;

  private void yyerror(String msg) {
      System.out.println("エラー: " + msg);
      System.exit(1);
  }

  public static void main(String[] args) {
      CalcLexer lexer = new CalcLexer(System.in);
      CalcParser calc = new CalcParser(lexer);
      lexer.nextToken();    
      calc.parse();    // parse the input
  }

Java ソースファイルはそれぞれ次のコマンドで生成します。

  java -jar jacc.jar MyCalc.jacc
  java -jar JFlex.jar MyCalc.jflex

すると、ソースファイルの名前に Parser, Token を後づけしたク ラスファイル(この場合 CalcToken, CalcParser)も生成されます。

あとはこの 3つの Java ソースファイルをまとめてコンパイルします。

  javac CalcParser.java

コンパイラのホームページ
Koji Kagawa