単純なマーク付けがなされた文字列の解析 #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はまだ入力を受けていない状態で、
SUCCESSIONINITIAL状態でstringかmarked-stringかを一回受け取った後の状態である。
これを区別することで空入力をエラーとすることができる。
INITIALで一度入力を受ければ、BEGIN(SUCCESSION)によりSUCCESSIONに移行する。
エラー処理については空入力以外は両開始状態で共通にできる。
stringとmarked-stringの違いは単に括弧で括られているかどうかだけである。
今までと違い、一回のアクションで文字列全体をひとまとめで受け取って処理する。
marked-stringの場合のprintfの怪しげな処理は、
一緒にマッチしている前後の括弧を表示させないためである。
yylengにはマッチした文字列yytextの長さが入っているので、
yytext+1yytextの二文字目から、yyleng-2で二文字分短い長さの文字列を表示している。

細かな状態遷移など考える必要はなく、ずいぶん簡潔になった。
エラーとなる規則を抜けのないようにリストアップしているか心配だが、
テストデータではそれなりに上手くいっているようだ。