[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9. 状態遷移表コンパイラの作成

9.1 タブ区切りファイルの読込み  
9.2 状態遷移表の解読  
9.3 アセンブリ言語ソース生成  

状態遷移表コンパイラの仕掛けとその利便性が分かったところで、いよいよその コンパイラ本体の作成の説明に入ります。  作成に使用した開発ツールは Borland C++ Compiler 5.5.1 です。また、タブ区 切りのファイルの読み取り用に、bisonflex を使用しました (7) 。(コンパイルは Visual C++6.0(SP4)でも出来ることを確認しています)

プログラムの構成としては、大きく以下の3つより成っています。この内、タブ 区切りの部分は DLLにしています。

タブ区切りファイルを読み込む
状態遷移表を解読する
解読した結果に基づいて、アセンブリ言語ソースを生成する


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.1 タブ区切りファイルの読込み

この部分はDLLとして作成します。bisonflex を使うと結構楽に作れます。 flexでシンボル名やタブなどに分割させて、それが所定の書式に従っているか を bison に解析させます。flexbison はこの機能を実装したCソースを生 成してくれるので、あとは得た情報を自分でデータコンテナに格納する部分と、 このDLLのインターフェイスとなる関数群を実装すれば出来上がりです。 タブ区切りファイルの仕様を列挙すると次のようになります。

  1. 1ファイルは複数のレコードを持つことができる
  2. 1レコードは複数のフィールドを持つことができ、改行で終わる
  3. フィールドは1のタブで区切られる
  4. フィールドは二重引用符(")で囲まれていてもよい
  5. フィールド中には、改行コード、タブ、二重引用符を含めない

この仕様を flexbison に分担させると以下のようになります。 まず、flex の担当。

  1. フィールドの区切り文字(タブ)を見つける
  2. レコード区切り文字(改行)を見つける
  3. フィールドの認識
  4. 二重引用符の始まりと終わりを認識する

一方、bison の担当。

  1. 1ファイルは複数レコードを持つことができる
  2. 1レコードは複数のフィールドを持つことができ、タブで区切られる
  3. レコードは改行で完結する

となります。これらを flexbison のソースにすると `TsvSplit.l'`TsvSplit.y'になり ます。bisonのソース中には、得た情報をデータコンテナに格納する処理も含ん でいます。push_field() がフィールドを、push_record()でレコードを格納し ています。これらソースから flex`lex.yy.c' を、また bison`y.tab.c'`y.tab.h' を生成します。このうち、`y.tab.c' で定義されてある yyparse() という関数を呼ぶと、ファイルの最後まで指定した規則で処理してくれます。 詳しくは実際のソースを見ていただければ分かる思います。外部に 公開する関数は `TsvSplitDll.h'で宣言しています。これら関数は `TsvSplit.c' に実装しています。`TsvSplit.dll' は以下のコマンドでを生成出来ます。

 
> bison -d -y TsvSplit.y
> flex -l TsvSplit.l
> bcc32 -c -w-par TsvSplit.c
> bcc32 -c -w-aus -w-pia -w-pro y.tab.c
> bcc32 -c -w-aus lex.yy.c
> ilink32 -aa -Tpd c0d32.obj TsvSplit.obj y.tab.obj lex.yy.obj,
   TsvSplit.dll,,import32.lib cw32mt.lib, TsvSplit.def

 
%{
/* TsvSplit.l ---------------------------------------- */
#include <stdio.h>
#include <string.h>
#include "y.tab.h"
%}
%s INSTRING
%%
<INITIAL>"\t"          { /* TAB */
                           return TAB;
                       }
<INITIAL>(\x0d)?(\x0a) { /* new line */
                           return NL;
                       }
<INITIAL>\"            { /* double quotation as start string */
                           BEGIN( INSTRING );
                       }
<INSTRING>\"           {  /* double quotation as end string */
                           BEGIN( INITIAL );
                       }
[^\"\t\r\n]+           { /* symbol */
                           strcpy( yylval.sz, yytext );
                           return SYMBOL;
                       }
.                      ;
%%
int yywrap() { return 1; }

 
%{
/* TsvSplit.y ---------------------------------------- */
#include <stdio.h>
#include <string.h>
#include "TsvSplit.h"

   〜 ( 略 ) 〜

%}
%union{
    char  sz[256];          /* string */
}
%token    <sz> SYMBOL
%token    TAB
%token    NL
%start    s
%%
s               :    record_list
                ;

record_list     :    record
                |    record_list record
                ;

record          :    symbols last_symbol NL {
                         push_record();
                         clear_field_count();
                     }
                ;

symbols         :
                |    symbols set
                ;

set             :    SYMBOL TAB {
                         push_field( $1 );
                         inc_field_count();
                     }
                |    TAB {  /* symbol empty */
                         push_field( "" );
                         inc_field_count();
                     }
                ;

last_symbol     :    {
                         push_field( "" );
                         inc_field_count();
                     }
                |    SYMBOL {
                         push_field( $1 );
                         inc_field_count();
                     }
                ;
%%


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.2 状態遷移表の解読

状態遷移表はクラスとして実装します。状態遷移表クラスはデータコンテナ を持ち、外部からの問い合わせに対してそれに応えるメンバ関数も持ちます。 状態遷移表を読み込む処理は、先に作成した `TsvSplit.dll' に入力ファイル 名を渡すことから始まります。そして1レコードずつ順次獲得して、表開始の キーワードである「START」を見つけます。「START」セルから右方向、下方向 への矩形領域はて遷移表のデータとなるので読み込む必要があります。状態遷 移表を取り込むデータ構造を以下に示します。

 
#include <string>
#include <vector>
typedef vector<string> StringVector;

// 1つのセル情報のデータ構造
typedef struct CELL_ {
    UINT          event_no;        // イベント番号
    UINT          next_state_no;   // 遷移先の状態番号
    StringVector  action_list;     // アクション名のベクタ
} CELL;

// セル情報のベクタ(= 1状態に対して全てのイベントを網羅する)
//     イベント番号がベクタのインデックス
typedef vector<CELL>  CellVector;

// 1状態のデータ構造
typedef struct ONE_STATE_ {
    string      sz_state;         // 状態名
    CellVector  cell_vector;      // セル情報のベクタ
} ONE_STATE;

// 状態遷移表全てのデータ構造
//     状態番号がベクタのインデックス
typedef vector<ONE_STATE> OneStateVector;

STLのコンテナ vector を使用して2次元 配列のイメージで格納しています。読み込んだ状態遷移表情報を外部に公開 するためのメンバなど定義した状態遷移表クラスの定義ファイルを以下に 示します。

 
//////////////////////////////////////////////////////////////////////
// State Transition Table Class
//
class CSTTable {
public:
    explicit CSTTable( const char *psz_fsmname,
                       const char *psz_actionfile );
    ~CSTTable();
    const string &GetActionFileName() const;
    const string &GetFsmName() const;

    // Table operation
    int Read( const char *psz_filename );
    int    GetStateCount() const;
    int GetEventCount() const;

    // State operation
    string GetStateName( int iState ) const;

    // Event operation
    string GetEventName( int iEvent ) const;

    // Cell operation
    int GetNextState( int iEvent, int iState ) const;
    const StringVector * GetActionList( int iEvent, int iState ) const;
    bool IsNopSymbolName( const string &sz_actionname ) const;

private:
    string            m_szActionFile;
    string            m_szFsmName;
    OneStateVector    m_state_vector;
    StringVector      m_eventstring_vector;
    StringVector      m_nop_symbols_vector;
    HINSTANCE         m_hInst;

    int read_cells( );
    int find_start( int *piEvent, int *piState );
    void make_event_vector( int x , char **ppsz, int i_size );
    int push_cell( CellVector &cell_vector,
                   int i_event, const char *psz_string );
    bool is_state_valid( int iState ) const {
        return (   iState >= 0 
                && (unsigned int)iState < m_state_vector.size() ) ;
    }
    bool is_event_valid( int iEvent ) const {
        return (  iEvent >= 0 
                && (unsigned int)iEvent < m_eventstring_vector.size() );
    }
    bool is_digit_string( const char *psz ) const;
};

実装内容については状態番号やイベント番号を渡して遷移に 関する情報を獲得する等の処理がほとんどで、難しい処理はありませんので ソースをご覧下さい。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.3 アセンブリ言語ソース生成

アセンブリ言語ソースの生成アルゴリズムは上に述べたように簡単です。た だ、他メーカー・他シリーズに対応出来るように デザインパターンの Template Method を使っています。Template Method は簡単に言うと基底クラ スで処理の大枠を決めてその詳細は派生クラスに実装させて、インターフェイ スを統一させておくものです。例えば、シリーズA、B、C のマイコンがあれば、 派生クラスを3つ作りますが、使用する側は基底クラスを介して操作することに なるのでどの派生クラス(どのシリーズ)を使おうが操作するコードを変更する 必要がなくなります。

今回の状態遷移表コンパイラのコード生成部のクラスにおいて、 CCodeクラスが基底クラスで、CNCodeCFCode クラスがそれぞれ派生クラスでメーカー別に用意してあります (8)CCodeクラスの GenCode()が Template Method にあたり、その処理は GenHeader()GenBody()GenFooter()GenIncludeFile() の4メンバ関数を呼んでいるのが主な処理です。 実はこの4メンバ関数は純粋仮想関数で、CCodeを継承した派生クラスで実装し なくてはいけない関数です。この4メンバ関数をマイコンメーカー、シリーズに 固有のコードを実装すればいいだけです。今後、さらに展開する場合は CNCodeCFCodeクラスに相当する派生クラスを追加すればよいだけです。CNCodeCFCodeCCodeを介して使われる (CCodeがインターフェイス)ことになるので、 使う側はどの派生クラスであろうと GenCode() を呼ぶだけで、ソースコードを 変更する必要はありません。

簡単な処理でメイン関数を見ればご理解いただけると思います。

 
int
main( int argc, char* argv[] )
{
    CSttcCmdLine    cmdline( argc, argv );

    switch ( cmdline.Parse() ) {
    case RET_CMDLINE_USAGE:
        usage();
        return 1;
    case RET_CMDLINE_HELP:
        help();
        return 1;
    case RET_CMDLINE_VER:
        version();
        return 1;
    case RET_CMDLINE_ERREXIT:
        cerr << cmdline.GetErrorMessage() << endl;
        return 1;
    }

    CSTTable *pTable = new CSTTable( cmdline.GetFsmName(),
                                     cmdline.GetContextName() );
    int i_ret = pTable->Read( cmdline.GetSttName() );
    if ( i_ret ) {
        delete pTable;
        return 1;
    }

    CCode *pCode = 0;
    switch ( cmdline.GetAssembler() ) {
    case NRA78K0:
        pCode = new CNCode;
        break;
    case FASM96:
        pCode = new CFCode;
        break;
    case DSRA74:
        // MITUSHIBI MELPS 740 series. SRA74.exe
        // pCode = new CDCode; :-p
        break;
    default:
        break;
    }

    int i_ret_gen = 1;
    if ( pCode ) {
        i_ret_gen = pCode->GenCode( pTable );
    } else {
        cerr << "not support maker or series." << endl;
    }

    delete pCode;
    delete pTable;

    return ( i_ret_gen ? 1 : 0 );
}

最初の switch文まではコマンドラインを解析しているだけです。状態遷移表ク ラスCSTTableを生成した後の switch文でメーカー毎にコード生成クラスのイン スタンスを生成を振り分けています。switch文の後には 基底クラスポインタか ら GenCode() を呼んでコード生成が行われます。

状態遷移表コンパイラの全てのソースを説明することは出来ませんが、全ソ ースを公開致しますので参考にして下さい。


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Kiyoshi Masumoto on June, 15 2001 using texi2html