| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
9.1 タブ区切りファイルの読込み 9.2 状態遷移表の解読 9.3 アセンブリ言語ソース生成
状態遷移表コンパイラの仕掛けとその利便性が分かったところで、いよいよその
コンパイラ本体の作成の説明に入ります。
作成に使用した開発ツールは Borland C++ Compiler 5.5.1 です。また、タブ区
切りのファイルの読み取り用に、bison と flex を使用しました
(7)
。(コンパイルは Visual C++6.0(SP4)でも出来ることを確認しています)
プログラムの構成としては、大きく以下の3つより成っています。この内、タブ 区切りの部分は DLLにしています。
タブ区切りファイルを読み込む
状態遷移表を解読する
解読した結果に基づいて、アセンブリ言語ソースを生成する
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
この部分はDLLとして作成します。bison とflex を使うと結構楽に作れます。
flexでシンボル名やタブなどに分割させて、それが所定の書式に従っているか
を bison に解析させます。flex と bison はこの機能を実装したCソースを生
成してくれるので、あとは得た情報を自分でデータコンテナに格納する部分と、
このDLLのインターフェイスとなる関数群を実装すれば出来上がりです。
タブ区切りファイルの仕様を列挙すると次のようになります。
")で囲まれていてもよい
この仕様を flex と bison に分担させると以下のようになります。
まず、flex の担当。
一方、bison の担当。
となります。これらを flex、bison のソースにすると
`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] | [ ? ] |
状態遷移表はクラスとして実装します。状態遷移表クラスはデータコンテナ
を持ち、外部からの問い合わせに対してそれに応えるメンバ関数も持ちます。
状態遷移表を読み込む処理は、先に作成した `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] | [ ? ] |
今回の状態遷移表コンパイラのコード生成部のクラスにおいて、
CCodeクラスが基底クラスで、CNCode、CFCode
クラスがそれぞれ派生クラスでメーカー別に用意してあります
(8)。CCodeクラスの
GenCode()が Template Method にあたり、その処理は GenHeader()、GenBody()、
GenFooter()、GenIncludeFile() の4メンバ関数を呼んでいるのが主な処理です。
実はこの4メンバ関数は純粋仮想関数で、CCodeを継承した派生クラスで実装し
なくてはいけない関数です。この4メンバ関数をマイコンメーカー、シリーズに
固有のコードを実装すればいいだけです。今後、さらに展開する場合は CNCode
や CFCodeクラスに相当する派生クラスを追加すればよいだけです。CNCodeや
CFCodeは CCodeを介して使われる (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] | [ ? ] |