XMLを用いた状態遷移 part-2
はじめに
前作XMLを用いた状態遷移では状態遷移表をXMLで記述し、それをXMLパーサで解析して状態遷移表のふるまいを動的に再現すると言う試みを紹介しました。
今回はここからさらに発展させ、XMLで記述された状態遷移表に基づいて、そこに表現されたとおりに振る舞うC++/Javaのソースコードを生成することを考えます。つまりはSMCのXML版です。SMCでは構文解析/字句解析にyacc/lexを用いていましたが、XMLで記述することで、yacc/lexから解放されました。
前回用いた例”(門番のいる)ゲート”の状態遷移表を再度示します。
現在の状態 | イベント | 次の状態 | アクション |
---|---|---|---|
Locked | Coin | Unlocked | Unlock |
Pass | Locked | Alarm | |
Unlocked | Coin | Unlocked | ThankYou |
Pass | Locked | Lock |
- ゲートがLocked状態のとき:
- Coinイベントが発生したら、Unlocked状態に遷移してUnlockアクションを起こす。
- Passイベントが発生したら、閉じているゲートを無理矢理誰かが通過したことに対しAlarmアクションを起こす
- ゲートがUnlocked状態のとき:
- Passイベントが発生したら、Locked状態に戻してLockアクションを起こす
- Coinイベントが発生したら、余分なお金を頂いた事に礼を言う(ThankYouアクションを起こす)。
状態遷移表のXMLによる表現
この状態遷移表のXMLによる記述は前作から少しばかり拡張されています。
文法
まず、状態遷移表をXMLで表現するための文法をDTD(Document Type Definition)で定義します。
これは今後いかなる状態遷移表をXMLで記述する場合でも再利用できます。
- fsm.dtd : 状態遷移表の文法
-
<!-- Document Type Definition for 'Finite State Machine' --> <!ELEMENT FSM (Note?, State*) > <!ATTLIST FSM name NMTOKEN #REQUIRED> <!ATTLIST FSM initial NMTOKEN #REQUIRED> <!ATTLIST FSM terminal NMTOKEN #IMPLIED> <!ATTLIST FSM package NMTOKEN #IMPLIED> <!ATTLIST FSM header NMTOKEN #IMPLIED> <!ELEMENT State (Note?, Event*) > <!ATTLIST State name NMTOKEN #REQUIRED> <!ATTLIST State enter NMTOKEN #IMPLIED> <!ATTLIST State exit NMTOKEN #IMPLIED> <!ELEMENT Event (Note?) > <!ATTLIST Event name NMTOKEN #REQUIRED> <!ATTLIST Event transition NMTOKEN #IMPLIED> <!ATTLIST Event action NMTOKEN #IMPLIED> <!ELEMENT Note (#PCDATA)>
-
<!ELEMENT FSM (Note?, State*) >
<FSM>タグはその要素として0または1個の<Note>タグに続いて<State>タグを0個以上含みます。<Note>タグには適当なコメントを記述できます。
-
<!ATTLIST FSM name NMTOKEN #REQUIRED> <!ATTLIST FSM initial NMTOKEN #REQUIRED> <!ATTLIST FSM terminal NMTOKEN #IMPLIED> <!ATTLIST FSM package NMTOKEN #IMPLIED> <!ATTLIST FSM header NMTOKEN #IMPLIED>
<FSM>タグはname(名前),initial(初期状態),terminal(停止状態)、そしてpackage(パッケージ名)とheader(ヘッダディレクトリ)を属性に持ちます。name,initialは必須属性です。packageは生成するJava-packageを指定します。たとえば
name=”Gate” package=”jp.co.s34.fsm” とすると、生成されるコードはpackage
jp.co.s34.fsm.Gate に置かれます。なお、生成されるJavaコードは Context.java,
State.java, Fsm.java の3本です。また、C++コードはFSM名.h および FSM名.cpp
です。宣言/実装はすべて namespace FSM名 {…} で囲まれます。
headerは生成するC++コードの宣言部(ヘッダ)のディレクトリを指定します。たとえば name=”Gate”
header=”fsm” とすると、生成される実装部 fsm.cpp は #include “fsm/Gate.h”
します。 -
<!ELEMENT State (Note?, Event*) >
<State>タグはその要素として0または1個の<Note>タグに続いて<Event>タグを0個以上含みます。<Note>タグには適当なコメントを記述できます。
-
<!ATTLIST State name NMTOKEN #REQUIRED> <!ATTLIST State enter NMTOKEN #IMPLIED> <!ATTLIST State exit NMTOKEN #IMPLIED>
<State>タグはname(名前),enter(入場動作)およびexit(退場動作)を属性に持ちます。nameは必須属性です。
-
<!ELEMENT Event (Note?) >
<Event>タグはその要素として0または1個の<Note>タグを含みます。
-
<!ATTLIST Event name NMTOKEN #REQUIRED> <!ATTLIST Event transition NMTOKEN "none"> <!ATTLIST Event action NMTOKEN "none">
<EventM>タグはname(名前),transition(次の状態),action(アクション)を属性に持ちます。nameは必須属性です。transitionを省略すると、そのイベントは状態の遷移を起こさず、入場動作/退場動作も行ないません。
文法に基づいた状態遷移表
では、この文法に基づいてゲートの状態遷移表をXMLで記述します。
- gate.xml : ゲートの状態遷移表
-
<?xml version="1.0" encoding="Shift_JIS"?> <?xml-stylesheet type="text/xsl" href="fsm.xsl"?> <!DOCTYPE FSM SYSTEM "fsm.dtd"> <FSM name="Gate" initial="Locked" package="jp.co.s34.fsm" header="fsm"> <Note>門番のいるゲート</Note> <State name="Locked" enter="EnterLocked" exit="ExitLocked"> <Note>ゲートが閉じている</Note> <Event name="Coin" action="Unlock" transition="Unlocked"> <Note>ゲートを開ける</Note> </Event> <Event name="Pass" action="Alarm"> <Note>不法侵入</Note> </Event> </State> <State name="Unocked" enter="EnterUnlocked" exit="ExitUnlocked"> <Note>ゲートが開いている</Note> <Event name="Coin" action="ThankYou"> <Note>チップとして受け取る</Note> </Event> <Event name="Pass" action="Lock" transition="Locked"> <Note>ゲートを閉じる</Note> </Event> </State> </FSM>
XSLスタイルシートを用意しました。
- fsm.xsl : スタイルシート
-
<?xml version="1.0" encoding="Shift_JIS"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl" > <xsl:template match="/"> <html> <header> <TITLE>XMLによる状態遷移表</TITLE> </header> <body> <hr/> <xsl:for-each select="FSM"> <h1><xsl:value-of select="@name"/> の状態遷移</h1> <p><i><xsl:value-of select="Note"/></i></p> <table> <tr> <th align="left">初期状態</th> <td><font color="blue"><xsl:value-of select="@initial"/></font></td> </tr> <tr> <th align="left">停止状態</th> <td><font color="blue"><xsl:value-of select="@terminal"/></font></td> </tr> </table> <ul>Java source<p/> <font color="gray"> <li><xsl:value-of select="@package"/>.<xsl:value-of select="@name"/>.Context.java</li> <li><xsl:value-of select="@package"/>.<xsl:value-of select="@name"/>.State.java</li> <li><xsl:value-of select="@package"/>.<xsl:value-of select="@name"/>.FSM.java</li> </font> </ul> <ul>C++ source<p/> <font color="gray"> <li><xsl:value-of select="@name"/>.h</li> <li><xsl:value-of select="@name"/>.cpp</li> </font> </ul> <ol> <xsl:for-each select="State"> <li>状態 : <font color="blue"><xsl:value-of select="@name"/></font> <p><i><xsl:value-of select="Note"/></i></p> <p> <ul> <li>入場動作 : <font color="red"><xsl:value-of select="@enter"/></font></li> <li>退場動作 : <font color="red"><xsl:value-of select="@exit"/></font></li> </ul> </p> <p> <table border="2"> <tr> <th>イベント</th> <th>アクション</th> <th>遷移先</th> <th>説明</th> </tr> <xsl:for-each select="Event" > <tr> <td><font color="green"><xsl:value-of select="@name"/></font></td> <td><font color="red"><xsl:value-of select="@action"/></font></td> <td><font color="blue"><xsl:value-of select="@transition"/></font></td> <td><i><xsl:value-of select="Note"/></i></td> </tr> </xsl:for-each> </table> </p> </li> </xsl:for-each> </ol> <p/> <p>※ 遷移先が空欄である場合、状態の変化がなく、入場/退場動作も行わないことを意味します</p> <hr/> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet>
XML/XSLに対応したブラウザであれば、次のように整形されたドキュメントが表示されます。
- スタイルシートによる整形(一部)
生成コードの駆動メカニズム
状態遷移表のとおりに振る舞うコードの生成に、”STATEパターン”と呼ばれるデザイン・パターンを用います。STATEパターンによる状態遷移表のからくりは以下のとおりです。
- Context
まず、状態遷移表に現われるすべての
“red”>アクション(入場動作/退場動作を含む)を抽象メソッド(純粋仮想関数)として定義したクラスContextを用意します。(引数
FSM については後述)class Context { public: virtual void Lock(FSM*) =0; virutal void Unlock(FSM*) =0; ... };
Contextに定義されたアクションが実際のコードの中でどうふるまうかは状態遷移表には書かれておらず、利用者がこのContextから導出した派生クラスで再定義します。
- State
状態遷移表に現われるすべてのイベントをメソッドとするクラスStateを定義します。
class State { public: virtual void Coin(FSM*); virtual void Pass(FSM*); ... };
そして状態遷移表に現われる状態ひとつひとつをStateから導出します
class LockedState : public State { public: virtual void Coin(FSM*); virtual void Pass(FSM*); ... }; class UnockedState : public State { public: virtual void Coin(FSM*); virtual void Pass(FSM*); ... };
このように定義すれば、たとえばメソッドLockedState::Coin()はLocked状態においてCoinというイベントが発生したときの振る舞いに対応づける事ができます。メソッドの引数FSMにコンテキストを取得するメソッド
Context* getContext()、そして次の状態を設定するメソッド void setState(State*)
を用意しておけば、状態とイベントの組に対応するアクションの実行と次の状態への遷移をコード化できます。void 状態State::イベント(FSM* fsm) { fsm->getContext()->アクション(fsm); fsm->setState(次の状態); }
- FSM
コンテキストおよび現在の状態を内包するくらすFSMを定義します。FSMには状態遷移表に現われる全イベントをメソッドとして定義し、また、全状態をstaticメンバとして内包します。
class FSM { Context* context_; // コンテキスト State* state_; // 現在の状態 static LockedState Locked_; static UnockedState Unocked_; public: void Coin(); void Pass(); ... };
状態遷移表の各イベントに対応したメソッドでは、現在の状態を表す変数 state_ の同名のメソッドを呼び出します。状態とイベントの組に対応した振る舞いのため、状態State::イベント()に委譲するわけです。
void FSM::イベント() { state_->イベント(this); }
C++
生成されたコード
- Gate.h 宣言部
-
#ifndef __fsm_Gate_h__ #define __fsm_Gate_h__ namespace Gate { class FSM; class Context { public: virtual void error(const char* state_name, const char* event_name) =0; virtual void Alarm(FSM* fsm) =0; virtual void EnterLocked(FSM* fsm) =0; virtual void EnterUnlocked(FSM* fsm) =0; virtual void ExitLocked(FSM* fsm) =0; virtual void ExitUnlocked(FSM* fsm) =0; virtual void Lock(FSM* fsm) =0; virtual void ThankYou(FSM* fsm) =0; virtual void Unlock(FSM* fsm) =0; }; class State { public: virtual const char* getName() const =0; virtual void enter(FSM* fsm); virtual void exit(FSM* fsm); virtual void Coin(FSM* fsm); virtual void Pass(FSM* fsm); }; class LockedState : public State { public: virtual const char* getName() const; virtual void enter(FSM* fsm); virtual void exit(FSM* fsm); virtual void Coin(FSM* fsm); virtual void Pass(FSM* fsm); }; class UnlockedState : public State { public: virtual const char* getName() const; virtual void enter(FSM* fsm); virtual void exit(FSM* fsm); virtual void Coin(FSM* fsm); virtual void Pass(FSM* fsm); }; class FSM { Context* context_; State* state_; State* newstate_; State* terminal_; static LockedState Locked_; static UnlockedState Unlocked_; public: FSM() { state_ = &Locked_; terminal_ = 0; } bool isTerminated() const { return state_ == terminal_; } void setContext(Context* context) { context_ = context; } Context* getContext() const { return context_; } void setNewState(State* state) { newstate_ = state; } State* getNewState() { return newstate_; } void setState(State* state) { state_ = state; } void setState() { state_ = newstate_; } State* getState() { return state_; } static State* Locked() { return &Locked_; } static State* Unlocked() { return &Unlocked_; } void Coin() { state_->Coin(this); } void Pass() { state_->Pass(this); } }; } #endif
- Gate.cpp 実装部
-
#include "fsm/Gate.h" namespace Gate { void State::enter(FSM* fsm) {} void State::exit(FSM* fsm) {} void State::Coin(FSM* fsm) { fsm->getContext()->error(getName(), "Coin"); } void State::Pass(FSM* fsm) { fsm->getContext()->error(getName(), "Pass"); } const char* LockedState::getName() const { return "Locked"; } void LockedState::enter(FSM* fsm) { fsm->getContext()->EnterLocked(fsm); } void LockedState::exit(FSM* fsm) { fsm->getContext()->ExitLocked(fsm); } void LockedState::Coin(FSM* fsm) { Context* context = fsm->getContext(); fsm->setNewState(fsm->Unlocked()); context->Unlock(fsm); exit(fsm); fsm->setState(); fsm->getState()->enter(fsm); } void LockedState::Pass(FSM* fsm) { Context* context = fsm->getContext(); fsm->setNewState(this); context->Alarm(fsm); if ( fsm->getNewState() != this ) { exit(fsm); fsm->setState(); fsm->getState()->enter(fsm); } else { fsm->setState(); } } LockedState FSM::Locked_; const char* UnlockedState::getName() const { return "Unlocked"; } void UnlockedState::enter(FSM* fsm) { fsm->getContext()->EnterUnlocked(fsm); } void UnlockedState::exit(FSM* fsm) { fsm->getContext()->ExitUnlocked(fsm); } void UnlockedState::Coin(FSM* fsm) { Context* context = fsm->getContext(); fsm->setNewState(this); context->ThankYou(fsm); if ( fsm->getNewState() != this ) { exit(fsm); fsm->setState(); fsm->getState()->enter(fsm); } else { fsm->setState(); } } void UnlockedState::Pass(FSM* fsm) { Context* context = fsm->getContext(); fsm->setNewState(fsm->Locked()); context->Lock(fsm); exit(fsm); fsm->setState(); fsm->getState()->enter(fsm); } UnlockedState FSM::Unlocked_; }
利用者コード
- main.cpp
-
#include <iostream> #include <string> #include "fsm/Gate.h" using namespace std; class GateContext : public Gate::Context { public: virtual void error(const char* state_name, const char* event_name) { cout << "error state=" << state_name << " event=" << event_name << endl; } virtual void Alarm(Gate::FSM* fsm) { cout << "金払え!!" << endl; } virtual void EnterLocked(Gate::FSM*) { cout << "ゲートが閉まりました" << endl; } virtual void EnterUnlocked(Gate::FSM*) { cout << "ゲートが開きました" << endl; } virtual void ExitLocked(Gate::FSM*) { cout << "閉じた状態を終わります" << endl; } virtual void ExitUnlocked(Gate::FSM*) { cout << "開いた状態を終わります" << endl; } virtual void Lock(Gate::FSM*) { cout << "ゲートを閉めます" << endl; } virtual void ThankYou(Gate::FSM*) { cout << "ありがとうございます" << endl; } virtual void Unlock(Gate::FSM*) { cout << "ゲートを開けます" << endl; } }; int main() { GateContext context; Gate::FSM fsm; fsm.setContext(&context); while ( true ) { cout << fsm.getState()->getName() << endl; cout << " enter an event :"; string event_name; cin >> event_name; if ( event_name == "exit" ) break; else if ( event_name == "Coin" ) fsm.Coin(); else if ( event_name == "Pass" ) fsm.Pass(); else continue; } return 0; }
Java
生成されたコード
- Context.java
-
package jp.co.s34.fsm.Gate; public interface Context { void error(String state_name, String event_name); void Alarm(FSM fsm); void EnterLocked(FSM fsm); void EnterUnlocked(FSM fsm); void ExitLocked(FSM fsm); void ExitUnlocked(FSM fsm); void Lock(FSM fsm); void ThankYou(FSM fsm); void Unlock(FSM fsm); }
- State.java
-
package jp.co.s34.fsm.Gate; public abstract class State { abstract public String getName(); public void enter(FSM fsm) {} public void exit(FSM fsm) {} public void Coin(FSM fsm) { fsm.getContext().error(getName(), "Coin"); } public void Pass(FSM fsm) { fsm.getContext().error(getName(), "Pass"); } }
- Fsm.java
-
package jp.co.s34.fsm.Gate; class LockedState extends State { public String getName() { return "Locked"; } public void enter(FSM fsm) { fsm.getContext().EnterLocked(fsm); } public void exit(FSM fsm) { fsm.getContext().ExitLocked(fsm); } public void Coin(FSM fsm) { Context context = fsm.getContext(); fsm.setNewState(fsm.Unlocked()); context.Unlock(fsm); exit(fsm); fsm.setState(); fsm.getState().enter(fsm); } public void Pass(FSM fsm) { Context context = fsm.getContext(); fsm.setNewState(this); context.Alarm(fsm); if ( fsm.getNewState() != this ) { exit(fsm); fsm.setState(); fsm.getState().enter(fsm); } else { fsm.setState(); } } } class UnlockedState extends State { public String getName() { return "Unlocked"; } public void enter(FSM fsm) { fsm.getContext().EnterUnlocked(fsm); } public void exit(FSM fsm) { fsm.getContext().ExitUnlocked(fsm); } public void Coin(FSM fsm) { Context context = fsm.getContext(); fsm.setNewState(this); context.ThankYou(fsm); if ( fsm.getNewState() != this ) { exit(fsm); fsm.setState(); fsm.getState().enter(fsm); } else { fsm.setState(); } } public void Pass(FSM fsm) { Context context = fsm.getContext(); fsm.setNewState(fsm.Locked()); context.Lock(fsm); exit(fsm); fsm.setState(); fsm.getState().enter(fsm); } } public class FSM { Context context_; State state_; State terminal_; State newstate_; private final static LockedState Locked_ = new LockedState(); private final static UnlockedState Unlocked_ = new UnlockedState(); public FSM() { state_ = Locked_; terminal_ = null; } public boolean isTerminated() { return state_ == terminal_; } public void setContext(Context context) { context_ = context; } public Context getContext() { return context_; } public void setNewState(State state) { newstate_ = state; } public State getNewState() { return newstate_; } public void setState(State state) { state_ = state; } public void setState() { state_ = newstate_; } public State getState() { return state_; } public static State Locked() { return Locked_; } public static State Unlocked() { return Unlocked_; } public void Coin() { state_.Coin(this); } public void Pass() { state_.Pass(this); } }
利用者コード
- Gate.java
-
import jp.co.s34.fsm.Gate.*; import java.io.*; public class Gate implements Context { public void error(String state_name, String event_name) { System.out.println("error state=" + state_name + " event=" + event_name); } public void Alarm(FSM fsm) { System.out.println("金払え!!"); } public void EnterLocked(FSM fsm) { System.out.println("ゲートが閉まりました"); } public void EnterUnlocked(FSM fsm) { System.out.println("ゲートが開きました"); } public void ExitLocked(FSM fsm) { System.out.println("閉じた状態を終わります"); } public void ExitUnlocked(FSM fsm) { System.out.println("開いた状態を終わります"); } public void Lock(FSM fsm) { System.out.println("ゲートを閉めます"); } public void ThankYou(FSM fsm) { System.out.println("ありがとうございます"); } public void Unlock(FSM fsm) { System.out.println("ゲートを開けます"); } public static void main(String[] arg) throws Exception { Context context = new Gate(); FSM fsm = new FSM(); fsm.setContext(context); BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); while ( true ) { System.out.println(fsm.getState().getName()); System.out.print("enter an event :"); String event_name = input.readLine(); if ( event_name.equals("exit") ) break; else if ( event_name.equals("Coin") ) fsm.Coin(); else if ( event_name.equals("Pass") ) fsm.Pass(); else continue; } } }
イベント駆動型XMLパーサによるコード生成
アプリケーションの実装にはIBM alphaWorks XML4C(XML Paeser for C++) 3.0.1
が提供するイベント駆動型パーサを使いました。
XML上のエレメント<Event>/<State>/<FSM>それぞれが持つ属性およびそれらの子エレメントを内包する3種の
struct を定義します。
struct Event { std::string name; // 名前 std::string action; // アクション std::string transition; // 次の状態 ... }; typedef std::set<Event> Events; struct State { std::string name; // 名前 std::string enter; // 入場動作 std::string exit; // 退場動作 Events events; // Eventの集合 ... }; typedef std::set<State> States; struct FSM { std::string name; // 名前 std::string initial; // 初期状態 std::string terminal; // 停止状態 std::string package; // package名(Java) std::string header; // ヘッダディレクトリ(C++) States states; // Stateの集合 };
パーサがタグの開始/終了を検出した時点でハンドラのメソッドstartElement()/endElement()が呼び出されますから、そこでタグ名に応じて上記
struct を組み立てます。
このようにして構築された struct は状態遷移表のすべての情報を包含していますから、“生成コードの駆動メカニズム”で解説したとおりのコードをファイルに書き出せばできあがりです。
- fsm.cpp
-
/* * Finite State Machine code generator * using XML4C/SAXParser (IBM XML Parser for C++ 3.0.1) */ #include <iostream> #include <fstream> #include <set> #include <string> #include <util/PlatformUtils.hpp> // PlatformUtils #include <parsers/SAXParser.hpp> // SAXParser #include <sax/HandlerBase.hpp> // HandlerBase #include <sax/SaxParseException.hpp> // SaxParseException #define LN "\n" /* * UNICODEからstringへの変換 */ inline std::string transcode(const XMLCh* xmlch) { return xmlch ? XMLString::transcode(xmlch) : ""; } typedef std::set<std::string> Names; /* * Event */ struct Event { std::string name; // 名前 std::string action; // アクション std::string transition; // 次の状態 Event() {} Event(const std::string& n) : name(n) {} Event(const std::string& n, const std::string& a, const std::string& t) : name(n), action(a), transition(t) {} }; inline bool operator<(const Event& x, const Event& y) { return x.name < y.name; } typedef std::set<Event> Events; /* * State */ struct State { std::string name; // 名前 std::string enter; // 入場動作 std::string exit; // 退場動作 Events events; // Eventの集合 State() {} State(const std::string& n) : name(n) {} }; inline bool operator<(const State& x, const State& y) { return x.name < y.name; } typedef std::set<State> States; /* * FSM */ struct FSM { std::string name; // 名前 std::string initial; // 初期状態 std::string terminal; // 停止状態 std::string package; // package名(Java) std::string header; // ヘッダディレクトリ(C++) States states; // Stateの集合 Names state_names; Names event_names; Names action_names; Names transition_names; }; /* * エラーメッセージの出力 */ std::ostream& operator<<(std::ostream& stream, const SAXParseException& exception) { return stream << "public-id=" << transcode(exception.getPublicId()) << " system-id=" << transcode(exception.getSystemId()) << " line=" << exception.getLineNumber() << " column=" << exception.getColumnNumber() << " : " << transcode(exception.getMessage()); } using namespace std; /* * XMLからFSMを構築するためのハンドラ */ class Handler : public HandlerBase { State current_state; FSM& fsm; public: Handler(FSM& f) : fsm(f) {} // DocumentHandler // エレメント(タグ)の開始 virtual void startElement(const XMLCh* const tag_name, AttributeList& attrs) { std::string tag = transcode(tag_name); std::string tmp; // <FSM ...> if ( tag == "FSM" ) { fsm.name = transcode(attrs.getValue(L"name")); fsm.initial = transcode(attrs.getValue(L"initial")); fsm.terminal = transcode(attrs.getValue(L"terminal")); fsm.package = transcode(attrs.getValue(L"package")); fsm.header = transcode(attrs.getValue(L"header")); } else // <State ...> if ( tag == "State" ) { current_state.name = tmp = transcode(attrs.getValue(L"name")); if ( !tmp.empty() ) fsm.state_names.insert(tmp); current_state.enter = tmp = transcode(attrs.getValue(L"enter")); if ( !tmp.empty() ) fsm.action_names.insert(tmp); current_state.exit = tmp = transcode(attrs.getValue(L"exit")); if ( !tmp.empty() ) fsm.action_names.insert(tmp); current_state.events.clear(); } else // <Event ...> if ( tag == "Event" ) { Event event; event.name = tmp = transcode(attrs.getValue(L"name")); if ( !tmp.empty() ) fsm.event_names.insert(tmp); event.action = tmp = transcode(attrs.getValue(L"action")); if ( !tmp.empty() ) fsm.action_names.insert(tmp); event.transition = tmp = transcode(attrs.getValue(L"transition")); if ( !tmp.empty() ) fsm.transition_names.insert(tmp); current_state.events.insert(event); } } // エレメント(タグ)の終了 virtual void endElement(const XMLCh* const tag_name) { std::string tag = transcode(tag_name); // </State> if ( tag == "State" ) { fsm.states.insert(current_state); } } // ErrorHandler virtual void warning(const SAXParseException& exception) { cout << "warning: " << exception << endl; } virtual void error(const SAXParseException& exception) { cout << "error: " << exception << endl; } virtual void fatalError(const SAXParseException& exception) { cout << "fatal: " << exception << endl; } }; /* * ----- main ----- */ int main(int argc, char* argv[]) { FSM fsm; Events::const_iterator eit; States::const_iterator sit; Names::const_iterator nit; /* * 初期化とFSMの構築 */ try { XMLPlatformUtils::Initialize(); SAXParser parser; Handler handler(fsm); parser.setDoValidation(true); // DTDによる検証を行う parser.setDocumentHandler(&handler); // ハンドラ設定 parser.setErrorHandler(&handler); // 同上(エラー用) parser.parse(argv[1]); } catch ( const XMLException& er ) { cout << er.getMessage() << endl; } /* * 初期状態は定義されているか? */ if ( fsm.state_names.find(fsm.initial) == fsm.state_names.end() ) { cerr << "invalid initial : " << fsm.initial << endl; return 1; } /* * 停止状態は定義されているか? */ if ( !fsm.terminal.empty() && fsm.state_names.find(fsm.terminal) == fsm.state_names.end() ) { cerr << "invalid terminal : " << fsm.terminal << endl; return 1; } /* * 遷移先は定義されているか? */ for ( nit = fsm.transition_names.begin(); nit != fsm.transition_names.end(); ++nit ) { if ( fsm.state_names.find(*nit) == fsm.state_names.end() ) { cerr << "invalid transition : " << *nit << endl; return 1; } } ofstream fout; #include "cppgen.inc" /* C++ コード生成部 */ #include "javagen.inc" /* Java コード生成部 */ return 0; }
- cppgen.inc C++ コード生成部
-
/* * C++ コードの生成 */ fout.open((fsm.name + ".h").c_str()); /* * 宣言部 (ヘッダ) */ if ( fsm.header.empty() ) { fout << "#ifndef __" << fsm.name << "_h__" LN << "#define __" << fsm.name << "_h__" LN LN; } else { fout << "#ifndef __" << fsm.header << '_' << fsm.name << "_h__" LN << "#define __" << fsm.header << '_' << fsm.name << "_h__" LN LN; } fout << "namespace " << fsm.name << " {" LN LN; /* * Context 宣言 */ fout << "class FSM;" LN LN "class Context {" LN "public:" LN " virtual void error(const char* state_name, const char* event_name) =0;" LN; for ( nit = fsm.action_names.begin(); nit != fsm.action_names.end(); ++nit ) { fout << " virtual void " << *nit << "(FSM* fsm) =0;" LN; } fout << "};" LN LN; /* * State(base) 宣言 */ fout << "class State {" LN "public:" LN << " virtual const char* getName() const =0;" LN << " virtual void enter(FSM* fsm);" LN << " virtual void exit(FSM* fsm);" LN; for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) { fout << " virtual void " << *nit << "(FSM* fsm);" LN; } fout << "};" LN LN; /* * State 宣言 */ for ( sit = fsm.states.begin(); sit != fsm.states.end(); ++sit) { fout << "class " << sit->name << "State : public State {" LN "public:" LN << " virtual const char* getName() const;" LN; if ( !sit->enter.empty() ) { fout << " virtual void enter(FSM* fsm);" LN; } if ( !sit->exit.empty() ) { fout << " virtual void exit(FSM* fsm);" LN; } for ( eit = sit->events.begin(); eit != sit->events.end(); ++eit ) { fout << " virtual void " << eit->name << "(FSM* fsm);" LN; } fout << "};" LN LN; } /* * FSM 宣言 */ fout << "class FSM {" LN " Context* context_;" LN " State* state_;" LN " State* newstate_;" LN " State* terminal_;" LN; for ( nit = fsm.state_names.begin(); nit != fsm.state_names.end(); ++nit ) { fout << " static " << *nit << "State " << *nit << "_;" LN; } fout << "public:" LN " FSM() { state_ = &" << fsm.initial << "_; "; if ( fsm.terminal.empty() ) { fout << "terminal_ = 0; }" LN; } else { fout << "terminal_ = &" << fsm.terminal << "_; }" LN; } fout << " bool isTerminated() const { return state_ == terminal_; }" LN LN << " void setContext(Context* context) { context_ = context; }" LN << " Context* getContext() const { return context_; }" LN LN << " void setNewState(State* state) { newstate_ = state; }" LN << " State* getNewState() { return newstate_; }" LN LN << " void setState(State* state) { state_ = state; }" LN << " void setState() { state_ = newstate_; }" LN << " State* getState() { return state_; }" LN LN; for ( nit = fsm.state_names.begin(); nit != fsm.state_names.end(); ++nit ) { fout << " static State* " << *nit << "() { return &" << *nit << "_; }" LN; } fout << LN; for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) { fout << " void " << *nit << "() { state_->" << *nit << "(this); }" LN; } fout << "};" LN LN; fout << "}" LN LN; fout << "#endif" LN; fout.close(); /* * 実装部 */ fout.open((fsm.name + ".cpp").c_str()); if ( fsm.header.empty() ) { fout << "#include \"" << fsm.name << ".h\"" LN LN; } else { fout << "#include \"" << fsm.header << '/' << fsm.name << ".h\"" LN LN; } fout << "namespace " << fsm.name << " {" LN LN; /* * Context 実装 */ // 実装すべきものはなにもない。 /* * State(base) 実装 */ fout << "void State::enter(FSM* fsm) {}" LN "void State::exit(FSM* fsm) {}" LN; for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) { fout << "void State::" << *nit << "(FSM* fsm) { fsm->getContext()->error(getName(), \"" << *nit << "\"); }" LN; } fout << LN; /* * State 実装 */ for ( sit = fsm.states.begin(); sit != fsm.states.end(); ++sit) { fout << "const char* " << sit->name << "State::getName() const {" " return \"" << sit->name << "\"; }" LN; if ( !sit->enter.empty() ) { fout << "void " << sit->name << "State::enter(FSM* fsm) { fsm->getContext()->" << sit->enter << "(fsm); }" LN; } if ( !sit->exit.empty() ) { fout << "void " << sit->name << "State::exit(FSM* fsm) { fsm->getContext()->" << sit->exit << "(fsm); }" LN; } fout << LN; for ( eit = sit->events.begin(); eit != sit->events.end(); ++eit ) { fout << "void " << sit->name << "State::" << eit->name << "(FSM* fsm) {" LN " Context* context = fsm->getContext();" LN; if ( eit->transition.empty() ) { fout << " fsm->setNewState(this);" LN; } else { fout << " fsm->setNewState(fsm->" << eit->transition << "());" LN; } if ( !eit->action.empty() ) { fout << " context->" << eit->action << "(fsm);" LN; } States::const_iterator tit = eit->transition.empty() ? sit : fsm.states.find(State(eit->transition)); if ( eit->transition.empty() ) { fout << " if ( fsm->getNewState() != this ) {" LN " exit(fsm);" LN " fsm->setState();" LN " fsm->getState()->enter(fsm);" LN " } else {" LN " fsm->setState();" LN " }" LN; } else { if ( !sit->exit.empty() ) { fout << " exit(fsm);" LN; } fout << " fsm->setState();" LN; if ( !tit->enter.empty() ) { fout << " fsm->getState()->enter(fsm);" LN; } } fout << "}" LN LN; } fout << sit->name << "State FSM::" << sit->name << "_;" LN LN; } fout << "}" LN; fout.close();
- javagen.inc Java コード生成部
-
/* * Java コードの生成 */ /* * Context.java */ fout.open("Context.java"); if ( fsm.package.empty() ) { fout << "package " << fsm.name << ";" LN LN; } else { fout << "package " << fsm.package << '.' << fsm.name << ";" LN LN; } fout << "public interface Context {" LN << " void error(String state_name, String event_name);" LN ; for ( nit = fsm.action_names.begin(); nit != fsm.action_names.end(); ++nit ) { fout << " void " << *nit << "(FSM fsm);" LN; } fout << "}" LN LN; fout.close(); /* * State.java */ fout.open("State.java"); if ( fsm.package.empty() ) { fout << "package " << fsm.name << ";" LN LN; } else { fout << "package " << fsm.package << '.' << fsm.name << ";" LN LN; } /* * State(base) */ fout << "public abstract class State {" LN " abstract public String getName();" LN " public void enter(FSM fsm) {}" LN " public void exit(FSM fsm) {}" LN; for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) { fout << " public void " << *nit << "(FSM fsm) { fsm.getContext().error(getName(), \"" << *nit << "\"); }" LN; } fout << "}" LN LN; fout.close(); /* * FSM.java */ fout.open("FSM.java"); if ( fsm.package.empty() ) { fout << "package " << fsm.name << ";" LN LN; } else { fout << "package " << fsm.package << '.' << fsm.name << ";" LN LN; } /* * State */ for ( sit = fsm.states.begin(); sit != fsm.states.end(); ++sit) { fout << "class " << sit->name << "State extends State {" LN " public String getName() { return \"" << sit->name << "\"; }" LN; if ( !sit->enter.empty() ) { fout << " public void enter(FSM fsm) { fsm.getContext()." << sit->enter << "(fsm); }" LN; } if ( !sit->exit.empty() ) { fout << " public void exit(FSM fsm) { fsm.getContext()." << sit->exit << "(fsm); }" LN; } for ( eit = sit->events.begin(); eit != sit->events.end(); ++eit ) { fout << " public void " << eit->name << "(FSM fsm) {" LN " Context context = fsm.getContext();" LN; if ( eit->transition.empty() ) { fout << " fsm.setNewState(this);" LN; } else { fout << " fsm.setNewState(fsm." << eit->transition << "());" LN; } if ( !eit->action.empty() ) { fout << " context." << eit->action << "(fsm);" LN; } States::const_iterator tit = eit->transition.empty() ? sit : fsm.states.find(State(eit->transition)); if ( eit->transition.empty() ) { fout << " if ( fsm.getNewState() != this ) {" LN " exit(fsm);" LN " fsm.setState();" LN " fsm.getState().enter(fsm);" LN " } else {" LN " fsm.setState();" LN " }" LN; } else { if ( !sit->exit.empty() ) { fout << " exit(fsm);" LN; } fout << " fsm.setState();" LN; if ( !tit->enter.empty() ) { fout << " fsm.getState().enter(fsm);" LN; } } fout << " }" LN; } fout << "}" LN LN; } /* * FSM */ fout << "public class FSM {" LN " Context context_;" LN " State state_;" LN " State terminal_;" LN " State newstate_;" LN; for ( nit = fsm.state_names.begin(); nit != fsm.state_names.end(); ++nit ) { fout << " private final static " << *nit << "State " << *nit << "_ = new " << *nit << "State();" LN; } fout << LN; fout << " public FSM() { state_ = " << fsm.initial << "_; "; if ( fsm.terminal.empty() ) { fout << "terminal_ = null; }" LN; } else { fout << "terminal_ = " << fsm.terminal << "State_; }" LN; } fout << " public boolean isTerminated() { return state_ == terminal_; }" LN LN " public void setContext(Context context) { context_ = context; }" LN " public Context getContext() { return context_; }" LN LN " public void setNewState(State state) { newstate_ = state; }" LN " public State getNewState() { return newstate_; }" LN LN " public void setState(State state) { state_ = state; }" LN " public void setState() { state_ = newstate_; }" LN " public State getState() { return state_; }" LN; for ( nit = fsm.state_names.begin(); nit != fsm.state_names.end(); ++nit ) { fout << " public static State " << *nit << "() { return " << *nit << "_; }" LN; } fout << LN; for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) { fout << " public void " << *nit << "() { state_." << *nit << "(this); }" LN; } fout << "}" LN LN; fout.close();
※サンプルを含む全ソースコードはここ。