単純なマーク付けがなされた文字列の解析 #10
前回示した状態遷移図をそのまま素直にコードに落としてみる。
#include <stdio.h> #include <stdarg.h> void eprintf(char const *format, ...); void neprintf(char const *format, ...); void eprintf(char const *format, ...) { va_list ap; fputs("error: ", stderr); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fputc('\n', stderr); } void neprintf(char const *format, ...) { va_list ap; fputc('\n', stderr); va_start(ap, format); eprintf(format, ap); va_end(ap); } int main(void) { enum {START, RESTART, STRING, HEAD, BODY, ACCEPT, ERROR} state = START; do { int c = getchar(); switch (state) { case START: switch (c) { case '(': state = HEAD; break; case ')': eprintf("illegal right parenthesis"); state = ERROR; break; case EOF: eprintf("unexpected EOF"); state = ERROR; break; default: state = STRING; printf("string [%c", c); break; } break; case RESTART: switch (c) { case '(': state = HEAD; break; case ')': eprintf("illegal right parenthesis"); state = ERROR; break; case EOF: state = ACCEPT; break; default: state = STRING; printf("string [%c", c); break; } break; case STRING: switch (c) { case '(': puts("]"); state = HEAD; break; case ')': neprintf("illegal right parenthesis"); state = ERROR; break; case EOF: puts("]"); state = ACCEPT; break; default: putchar(c); break; } break; case HEAD: switch (c) { case '(': eprintf("illegal left parenthesis"); state = ERROR; break; case ')': eprintf("null string between parentheses"); state = ERROR; break; case EOF: eprintf("unexpected EOF"); state = ERROR; break; default: state = BODY; printf("marked-string [%c", c); break; } break; case BODY: switch (c) { case '(': neprintf("illegal left parenthesis"); state = ERROR; break; case ')': puts("]"); state = RESTART; break; case EOF: neprintf("unexpected EOF"); state = ERROR; break; default: putchar(c); break; } break; default: eprintf("\nstrange state: %d", state); state = ERROR; break; } } while (state != ACCEPT && state != ERROR); return 0; }
二重のswitchによる状態stateと入力cの組み合わせで、
行うべきアクションと次に遷移すべき状態を定義している。
状態を表すenum型には状態遷移図に描いていなかったエラー状態errorも列挙してある。
このエラー状態になった時の理由についての示唆を出力するようにした。
どの状態のときにどの入力が来るとどういう理由でエラーになるかが分かっているからだ。
今までだと単にsyntax errorになっていたものが、
$ echo -n "()" | ./parser error: null string between parentheses $ echo -n | ./parser error: unexpected EOF $ echo -n "(Ab)ra(cad)((" | ./parser marked-string [Ab] string [ra] marked-string [cad] error: illegal left parenthesis $ echo -n "(Ab)ra(cad))" | ./parser marked-string [Ab] string [ra] marked-string [cad] error: illegal right parenthesis $ echo -n "(Ab" | ./parser marked-string [Ab error: unexpected EOF
のように理由つきのエラー表示となる。
エラー表示には基本的に自前のeprintf関数を使うが、
string状態やmarked-string-body-body状態でエラーになった場合は、文字列出力の途中であるので、
見やすくなるようにエラー表示前に改行を出力するneprintf関数を使う。