単純なマーク付けがなされた文字列の解析 #15
前回まではflex用のソースに状態遷移表をそのままに記述することで解析器を作っていた。
しかし、わざわざ人間が状態遷移表まで考えた上でそれを明示的に記述するなど、
せっかくの解析器自動生成系がもったいなさすぎで泣いている。
もう少し自然に素直に書いてみる。
%{ #include <stdio.h> #ifdef __STRICT_ANSI__ int fileno(FILE *); #endif void yyerror(char const *msg); void yyerror(char const *msg) { fprintf(stderr, "error: %s\n", msg); } %} %option main nounput noinput %s SUCCESSION %% <INITIAL>{ [^()]+ { printf("string [%s]\n", yytext); BEGIN(SUCCESSION); } \([^()]+\) { printf("marked-string [%.*s]\n", yyleng - 2, yytext + 1); BEGIN(SUCCESSION); } <<EOF>> { yyerror("unexpected EOF"); return 1; } } <SUCCESSION>{ [^()]+ { printf("string [%s]\n", yytext); } \([^()]+\) { printf("marked-string [%.*s]\n", yyleng - 2, yytext + 1); } <<EOF>> { return 0; } } <INITIAL,SUCCESSION>{ \(\) { yyerror("null string between parentheses"); return 1; } \([^)]*\( { yyerror("illegal left parenthesis"); return 1; } \([^)]* { yyerror("unexpected EOF"); return 1; } \) { yyerror("illegal right parenthesis"); return 1; } }
デフォルト開始状態INITIAL
以外にSUCCESSION
を定義する。
INITIAL
はまだ入力を受けていない状態で、
SUCCESSION
はINITIAL
状態でstringかmarked-stringかを一回受け取った後の状態である。
これを区別することで空入力をエラーとすることができる。
INITIAL
で一度入力を受ければ、BEGIN(SUCCESSION)
によりSUCCESSION
に移行する。
エラー処理については空入力以外は両開始状態で共通にできる。
stringとmarked-stringの違いは単に括弧で括られているかどうかだけである。
今までと違い、一回のアクションで文字列全体をひとまとめで受け取って処理する。
marked-stringの場合のprintf
の怪しげな処理は、
一緒にマッチしている前後の括弧を表示させないためである。
yyleng
にはマッチした文字列yytext
の長さが入っているので、
yytext+1
でyytext
の二文字目から、yyleng-2
で二文字分短い長さの文字列を表示している。
細かな状態遷移など考える必要はなく、ずいぶん簡潔になった。
エラーとなる規則を抜けのないようにリストアップしているか心配だが、
テストデータではそれなりに上手くいっているようだ。