re2cメモ その1
字句解析ツールでre2cというのがあるということを知ったので勉強メモ
テスト環境は、Windows上のMinGWで、本家サイト re2c HomeのリンクからWindows用バイナリ(re2c-0.13.5-bin.zip)をダウンロードできるのでこれを使用しました。
基本の書き方は以下のとおり/*!re2c〜*/の中にマッチさせたい正規表現と対応するアクション、設定などを記述し、re2cでこの部分をcのソースに変換する様です。(詳細は本家サイト re2c Homeのオンラインマニュアルを参照)
設定する変数について
- YYCTYPE:解析対象の文字の型
- YYCURSOR:解析対象文字へのカレントポインタ
- YYLIMIT:re2cで使用?
- YYMARKER:re2cで使用?
- YYFILL(n):ファイルからバッファに文字列を読み込んで使用する場合などにバッファにn文字分追加する関数(要自作)、今回は解析文字列はソース中にあって未使用なのでNULLに設定
int scan(void){ /*!re2c 正規表現 {アクション} */ }
サンプル 加減算の字句解析(test01.re)
#include <stdlib.h> #include <stdio.h> #include <string.h> /* re2cに対する設定 */ #define YYCTYPE char #define YYCURSOR yycursor #define YYLIMIT yylimit #define YYMARKER yymarker #define YYFILL(n) NULL /* トークン種別 */ enum {NUMBER = 1, PLUS, MINUS, EOS}; /* re2cが使用する変数 上でdefineしたもの */ char *yycursor; char *yymarker; char *yylimit; int scan(void){ initial: /*!re2c other = .; [0-9]+ {return NUMBER;} "+" {return PLUS; } "-" {return MINUS; } [ \t\v\f]+ {goto initial;} "\n" {goto initial;} "\000" {return EOS;} other { printf("unexpected character\n"); goto initial; } */ } main(){ char *buf = "123+245\n356-456*567"; /* YYCURSORに解析対象文字列の先頭アドレスを設定 */ yycursor = buf; int t; while((t = scan()) != EOS){ printf("%d\n", t); } }
re2cによる変換とコンパイル
~/re2c$ ./re2c -i -o test01.c test01.re ~/re2c$ gcc test01.c
実行結果
~/re2c$ ./a.exe 1 2 1 1 3 1 unexpected character 1
scan関数の戻り値としてトークンの値を返したい場合はアクションにreturn文を記述、戻り値を返さない場合はgoto initialを記述しています。
このソースでは未定義の文字に対してメッセージを表示させるためにotherのアクションを定義しましたが、定義の記述が無い場合は単純にその文字はスキップされるようです。
またre2c用の記述にラベルをつけて分割することで、flexのスタート状態の様なことができるみたいです。
initial: /*!re2c "/*" {goto comment;} */ comment: /*!re2c "*/" {goto initial;} */
作成されたcのソース
scan()部分のみ掲載
int scan(void){ initial: { YYCTYPE yych; if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); yych = *YYCURSOR; switch (yych) { case 0x00: goto yy12; case '\t': case '\v': case '\f': case ' ': goto yy8; case '\n': goto yy10; case '+': goto yy4; case '-': goto yy6; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': goto yy2; default: goto yy14; } yy2: ++YYCURSOR; yych = *YYCURSOR; goto yy19; yy3: {return NUMBER;} yy4: ++YYCURSOR; {return PLUS; } yy6: ++YYCURSOR; {return MINUS; } yy8: ++YYCURSOR; yych = *YYCURSOR; goto yy17; yy9: {goto initial;} yy10: ++YYCURSOR; {goto initial;} yy12: ++YYCURSOR; {return EOS;} yy14: ++YYCURSOR; { printf("unexpected character\n"); goto initial; } yy16: ++YYCURSOR; if (YYLIMIT <= YYCURSOR) YYFILL(1); yych = *YYCURSOR; yy17: switch (yych) { case '\t': case '\v': case '\f': case ' ': goto yy16; default: goto yy9; } yy18: ++YYCURSOR; if (YYLIMIT <= YYCURSOR) YYFILL(1); yych = *YYCURSOR; yy19: switch (yych) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': goto yy18; default: goto yy3; } } }
YYFILL(n)を使用しない場合
YYFILL(n)をNULLに定義したので、無意味なif分が生成されています。これを回避するにはYYFILL(n)の定義を止めて、re2cの定義文中でre2c:yyfill:enableを0に設定すれば良いみたいです。(この場合はYYLIMITの定義もいらない?)
/* #define YYFILL(n) NULL */ /*!re2c re2c:yyfill:enable = 0; */
YYLIMITの意味
YYFILL(n)を呼ぶif文の条件から判断すると、(YYLIMIT - YYCURSOR)で未だ解析していない文字数、YYLIMITは解析対象文字列の最後+1を示しているようですね。