状態遷移表を作成する
サンプルとして
「ソースコード(テキストファイル)から ブロックコメント:/*から*/まで と ラインコメント://から改行まで を抽出し出力する」
を与えられた要件/要求とします。
この要求を状態遷移で実現するにあたり思いつく状態としては、"コメントの外","ブロックコメントの中","ラインコメントの中"……ほかにも'/'の直後はコメントの中か外か定まっていないので"/の直後"なんて状態も必要になりそうです。
そしてイベントはファイルから'/','*',改行,その他 の各文字を読み込んだ、の4種でいいかな。
プリンタ用紙に鉛筆で丸と矢印を描いては消してできあがった状態遷移図がコレ。
状態遷移図を基に、遷移に伴うアクションを加えて転記した状態遷移表がコレです。
状態遷移表からひな型コードを生成する
この状態遷移表からコードを起こすわけですが、状態遷移の実装については『StateパターンでCSVを読む』を書きました。デザイン・パターンの一つ:Stateによる実装です。今回の実装はC、継承も仮想関数も使えないという利き腕を封じられた条件なので戦術を大きく変えにゃならんです。
状態遷移の実装は要するに「(1)現状態 と (2)受理したイベント の組」に対応する「(3)アクション と (4)遷移先(新たな状態)」を引き当てることに他なりません。ならば上記(1)~(4)の並びをレコードとし、そのレコード列(=状態遷移表)から「(1)現状態 と (2)受理したイベント の組」に一致するレコードを探し出して「(3)アクション を実行して (4)新たな状態 に遷移」すればいい。
状態遷移表からひな型コードの生成には使い慣れた「T4-template」を用います。テンプレートに記述するのは enum S { 状態... } と enum E { イベント... } そして状態遷移表: transition(状態,イベント,遷移先...) を並べたものです。アクションは状態とイベントから適当な名前を与えた関数(当然中身からっぽ)を吐かせます。遷移先は複数個(4つまで)書けるようにしました。というのも、実際の設計ではアクションの結果次第で遷移先が異なるケースがままあるのです。例えば「初期状態で開始イベントがきたらチャネルをオープンして受信待ちに遷移する」場合、チャネルのオープンに失敗したら初期状態に戻りたい、こんなケースに対応すべく、アクションが返した値nに応じて遷移先そのnを指定できるように。
コメント抽出(ExtractComment)を行う状態遷移表テンプレート:state_transition.ttはこんなやつです。
<#@ output encoding="shift_jis" #> <#@ template debug="false" hostspecific="true" language="C#" #> <# string prefix = this.Host.ResolveParameterValue("","","prefix"); int slen = 0; foreach( string s in Enum.GetNames(typeof(S)) ) if ( slen < s.Length ) slen = s.Length; int elen = 0; foreach( string e in Enum.GetNames(typeof(E)) ) if ( elen < e.Length ) elen = e.Length; #> <#@ include file="state_transition_.tt" #> <#+ static Tuple<S,E,S[]> transition(S s, E e, params S[] n) { return Tuple.Create(s,e,n); } // 状態を列挙 enum S { OUT_COMMENT, SECOND_LEAD, IN_BLOCKCOMMENT, IN_LINECOMMENT, SECOND_TRAIL, }; // イベントを列挙 enum E { STAR, SLASH, LF, OTHER }; // 以下に書かれた状態遷移表に基づいてコードを生成する Tuple<S,E,S[]>[] _transition_ = { // 状態, イベント,次の状態... transition(S.OUT_COMMENT, E.STAR, S.OUT_COMMENT), transition(S.OUT_COMMENT, E.SLASH, S.SECOND_LEAD), transition(S.OUT_COMMENT, E.LF, S.OUT_COMMENT), transition(S.OUT_COMMENT, E.OTHER, S.OUT_COMMENT), transition(S.SECOND_LEAD, E.STAR, S.IN_BLOCKCOMMENT), transition(S.SECOND_LEAD, E.SLASH, S.IN_BLOCKCOMMENT), transition(S.SECOND_LEAD, E.LF, S.OUT_COMMENT), transition(S.SECOND_LEAD, E.OTHER, S.OUT_COMMENT), transition(S.IN_BLOCKCOMMENT, E.STAR, S.SECOND_TRAIL), transition(S.IN_BLOCKCOMMENT, E.SLASH, S.IN_BLOCKCOMMENT), transition(S.IN_BLOCKCOMMENT, E.LF, S.IN_BLOCKCOMMENT), transition(S.IN_BLOCKCOMMENT, E.OTHER, S.IN_BLOCKCOMMENT), transition(S.IN_LINECOMMENT, E.STAR, S.IN_LINECOMMENT), transition(S.IN_LINECOMMENT, E.SLASH, S.IN_LINECOMMENT), transition(S.IN_LINECOMMENT, E.LF, S.OUT_COMMENT), transition(S.IN_LINECOMMENT, E.OTHER, S.IN_LINECOMMENT), transition(S.SECOND_TRAIL, E.STAR, S.SECOND_TRAIL), transition(S.SECOND_TRAIL, E.SLASH, S.OUT_COMMENT), transition(S.SECOND_TRAIL, E.LF, S.IN_BLOCKCOMMENT), transition(S.SECOND_TRAIL, E.OTHER, S.IN_BLOCKCOMMENT), }; #>
サンプルファイルのディレクトリ:state_transitonに上記テンプレートと生成バッチ:state_transiton.batを用意しました。コマンドラインから state_transition ec すると、ひな型コード:ec_state_transition.h/.c が生成されます。Cは名前空間を持たないのでシンボルの衝突回避のためプレフィクス(ここではec)を付けられるようにしました。
#ifndef EC_STATE_TRANSITION_H_ #define EC_STATE_TRANSITION_H_ /* ----------------------------------------------------- * 状態遷移 ec * ----------------------------------------------------- */ #include <stdbool.h> /* from C99 : defines bool, true, false */ #include "statemachine_defs.h" /* * 状態を列挙 */ enum ec_state_code_t { EC_S_OUT_COMMENT, EC_S_SECOND_LEAD, EC_S_IN_BLOCKCOMMENT, EC_S_IN_LINECOMMENT, EC_S_SECOND_TRAIL, }; typedef enum ec_state_code_t ec_state_code; /* * イベントを列挙 */ enum ec_event_code_t { EC_E_STAR, EC_E_SLASH, EC_E_LF, EC_E_OTHER, }; typedef enum ec_event_code_t ec_event_code; /* * イベントおよび付随するデータ */ struct ec_event_t { ec_event_code event_code_; /* your ftuff here */ }; typedef struct ec_event_t ec_event; /* * アクションの実行に必要なデータ */ struct ec_context_t { int dummy; /* you may remove this */ /* your stuff here */ }; typedef struct ec_context_t ec_context; /* ---------------- */ #ifdef __cplusplus extern "C" { #endif event_code ec_event_get_event_code(const event* ev); void ec_event_set_event_code(event* ev, event_code evc); void ec_event_make(ec_event* ev, void* arg, ec_state_code st); bool ec_context_setup(ec_context* ctx, void* arg); void ec_context_teardown(ec_context* ctx); transition* ec_state_transition_table(void); const char* ec_state_code_to_c_str(state_code st); const char* ec_event_code_to_c_str(event_code ev); #ifdef __cplusplus } #endif #endif
#include <assert.h> #include <stddef.h> #include "ec_state_transition.h" /* ----------------------- * state * ----------------------- */ // stcに対応する文字列を返す const char* ec_state_code_to_c_str(state_code stc) { const char* result; switch ( stc ) { case EC_S_OUT_COMMENT : result = "EC_S_OUT_COMMENT"; break; case EC_S_SECOND_LEAD : result = "EC_S_SECOND_LEAD"; break; case EC_S_IN_BLOCKCOMMENT : result = "EC_S_IN_BLOCKCOMMENT"; break; case EC_S_IN_LINECOMMENT : result = "EC_S_IN_LINECOMMENT"; break; case EC_S_SECOND_TRAIL : result = "EC_S_SECOND_TRAIL"; break; default : result = "ec_S_BAD_STATE"; break; } return result; } /* ----------------------- * event * ----------------------- */ // ev内のevent_codeを返す event_code ec_event_get_event_code(const event* ev) { return ((const ec_event*)ev)->event_code_; } // ev内にevent_codeを設定する void ec_event_set_event_code(event* ev, event_code evc) { ((ec_event*)ev)->event_code_ = evc; } // event_codeに対応した文字列を返す const char* ec_event_code_to_c_str(event_code ev) { const char* result; switch ( ev ) { case EC_E_STAR : result = "EC_E_STAR"; break; case EC_E_SLASH : result = "EC_E_SLASH"; break; case EC_E_LF : result = "EC_E_LF"; break; case EC_E_OTHER : result = "EC_E_OTHER"; break; default : result = "EC_E_BAD_EVENT"; break; } return result; } // argおよびstを基に、ev内にevent_codeおよび付帯情報を設定する void ec_event_make(ec_event* ev, void* arg, ec_state_code st) { /* your stuff here */ } /* ----------------------- * context * ----------------------- */ // contextを初期化する。成功/失敗時にtrue/falseを返す。 bool ec_context_setup(ec_context* ctx, void* arg) { /* your stuff here */ return true; } // contextを解体する。 void ec_context_teardown(ec_context* ctx) { /* do nothing */ } /* ----------------------- * action * ----------------------- */ /* ec action for state:OUT_COMMENT event:STAR */ static int ec_action_S0_E0(const event* ev, context* ctx) { int result = 0; #ifndef STATEMACHINE_TEST /* your stuff here */ #endif return result; } ... 中略 ... /* ----------------------- * state_transition_table * ----------------------- */ transition* ec_state_transition_table(void) { static transition table[] = { { EC_S_OUT_COMMENT , EC_E_STAR , &ec_action_S0_E0, { EC_S_OUT_COMMENT, }}, { EC_S_OUT_COMMENT , EC_E_SLASH, &ec_action_S0_E1, { EC_S_SECOND_LEAD, }}, { EC_S_OUT_COMMENT , EC_E_LF , &ec_action_S0_E2, { EC_S_OUT_COMMENT, }}, { EC_S_OUT_COMMENT , EC_E_OTHER, &ec_action_S0_E3, { EC_S_OUT_COMMENT, }}, { EC_S_SECOND_LEAD , EC_E_STAR , &ec_action_S1_E0, { EC_S_IN_BLOCKCOMMENT, }}, { EC_S_SECOND_LEAD , EC_E_SLASH, &ec_action_S1_E1, { EC_S_IN_BLOCKCOMMENT, }}, { EC_S_SECOND_LEAD , EC_E_LF , &ec_action_S1_E2, { EC_S_OUT_COMMENT, }}, { EC_S_SECOND_LEAD , EC_E_OTHER, &ec_action_S1_E3, { EC_S_OUT_COMMENT, }}, { EC_S_IN_BLOCKCOMMENT, EC_E_STAR , &ec_action_S2_E0, { EC_S_SECOND_TRAIL, }}, { EC_S_IN_BLOCKCOMMENT, EC_E_SLASH, &ec_action_S2_E1, { EC_S_IN_BLOCKCOMMENT, }}, { EC_S_IN_BLOCKCOMMENT, EC_E_LF , &ec_action_S2_E2, { EC_S_IN_BLOCKCOMMENT, }}, { EC_S_IN_BLOCKCOMMENT, EC_E_OTHER, &ec_action_S2_E3, { EC_S_IN_BLOCKCOMMENT, }}, { EC_S_IN_LINECOMMENT , EC_E_STAR , &ec_action_S3_E0, { EC_S_IN_LINECOMMENT, }}, { EC_S_IN_LINECOMMENT , EC_E_SLASH, &ec_action_S3_E1, { EC_S_IN_LINECOMMENT, }}, { EC_S_IN_LINECOMMENT , EC_E_LF , &ec_action_S3_E2, { EC_S_OUT_COMMENT, }}, { EC_S_IN_LINECOMMENT , EC_E_OTHER, &ec_action_S3_E3, { EC_S_IN_LINECOMMENT, }}, { EC_S_SECOND_TRAIL , EC_E_STAR , &ec_action_S4_E0, { EC_S_SECOND_TRAIL, }}, { EC_S_SECOND_TRAIL , EC_E_SLASH, &ec_action_S4_E1, { EC_S_OUT_COMMENT, }}, { EC_S_SECOND_TRAIL , EC_E_LF , &ec_action_S4_E2, { EC_S_IN_BLOCKCOMMENT, }}, { EC_S_SECOND_TRAIL , EC_E_OTHER, &ec_action_S4_E3, { EC_S_IN_BLOCKCOMMENT, }}, { -1, -1, NULL, { -1 }} /* DO NOT FORGET THIS TERMINATOR! */ }; return table; }