関数ポインタで状態遷移


状態遷移の憂鬱

C で switch 文を使って状態遷移を書いていると「もうだめだぁ」と力尽きることが多い。

関数ポインタ

ググってみると関数ポインタを使うといいらしいのでメモ。

// Parser.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Parser Parser;

struct Parser {
    void (*_worker)(Parser*, int);  // 状態遷移の状態を持つ関数ポインタ
    FILE* _stream;  // 入力ストリーム
    int _number;  // 処理中の番号
    int _buffer_pos;  // バッファ位置
    char _buffer[128];  // バッファ
};

// 状態遷移で使う関数のプロトタイプ
void Parser_first(Parser*, int);
void Parser_comma(Parser*, int);

void die(char const* msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}

/**
 * パーサーの構築
 */
Parser* Parser_new(void) {
    Parser* self = (Parser*) calloc(1, sizeof(Parser));
    if (!self) {
        die("failed to allocate memory");
    }

    // 最初の状態
    self->_worker = Parser_first;

    return self;
}

/**
 * パーサーの破棄
 */
void Parser_delete(Parser* self) {
    if (self) {
        free(self);
    }
}

/**
 * バッファにプッシュする
 */
void Parser_buffer_push(Parser* self, int ch) {
    if (self->_buffer_pos >= sizeof(self->_buffer)-1) {
        die("buffer overflow");
    }
    self->_buffer[self->_buffer_pos++] = ch;
    self->_buffer[self->_buffer_pos] = 0;
}

/**
 * バッファをクリアする
 */
void Parser_buffer_clear(Parser* self) {
    self->_buffer[0] = 0;
    self->_buffer_pos = 0;
}

void Parser_first(Parser* self, int ch) {
    if (ch == ',' || ch == '\n') {
        // 状態を更新
        self->_worker = Parser_comma;

        // debug
        printf("[%s]\n", self->_buffer);

        // バッファをクリア
        Parser_buffer_clear(self);
    } else {
        Parser_buffer_push(self, ch);
    }
}

void Parser_comma(Parser* self, int ch) {
    if (ch == ',' || ch == '\n') {
        // nothing todo
    } else {
        // 状態を更新
        self->_worker = Parser_first;
        Parser_buffer_push(self, ch);
    }
}

/**
 * パースを実行する
 */
void Parser_execute(Parser* self, FILE* stream) {
    // 実行前に状態を初期化
    self->_worker = Parser_first;

    // 実行前にバッファをクリア
    Parser_buffer_clear(self);

    // 処理するストリームをセット
    self->_stream = stream;

    // ストリームをチェック
    if (feof(self->_stream)) {
        return;
    }

    // 実行
    for (size_t i = 0; ; ++i) {
        int ch = fgetc(self->_stream);
        if (ch == EOF || ferror(self->_stream)) {
            return;
        }
        self->_number = i;

        // 状態が勝手に変わってくれるのでラク
        self->_worker(self, ch);
    }
}

int main(void) {
    Parser* parser = Parser_new();
    Parser_execute(parser, stdin);
    Parser_delete(parser);
    return 0;
}

$ clang test.c && ./a.out 
123,223,323
[123]
[223]
[323]
abc,def,ghi
[abc]
[def]
[ghi]