JFlex と jacc を同時に使う


JFlex と jacc を併用するとき、JFlexで生成される yylexメソッド は、トークン (終端記号) の情報を戻り値とし、それを Jacc が利用します。 トークンが 1文字の場合は、通常、戻り値は文字コードそのものです。トーク ンが2文字以上の場合は、Jacc で %tokenにより宣言されたマクロ を使用します。

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

implement されるクラス (CalcTokens) は jacc が生成するクラスで、 %tokenにより宣言されたマクロが定義されています。

また、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}+)?

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

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

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

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

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

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

生成規則の中で、トークン(終端記号)として文字リテラル ('+', '-'など) と %tokenで宣言したマクロを用いることができます。


// 属性値の型
%semantic double

%token NUMBER
  /* 演算子の優先順位 */
%left  '+' '-'
%left  '*' '/'

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

   /* $$は左辺の属性値、$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