単純なマーク付けがなされた文字列の解析 #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関数を使う。