Skip to content

Instantly share code, notes, and snippets.

@vain0x
Last active September 16, 2017 08:27
Show Gist options
  • Save vain0x/535119376e5bdc878b94 to your computer and use it in GitHub Desktop.
Save vain0x/535119376e5bdc878b94 to your computer and use it in GitHub Desktop.

HSP3の処理系の実装の要点メモ

注意

  • ランタイムの話はすべて wingui 版のみ。

  • 要加筆:

    • マクロ展開の話
    • 特殊マクロとタグスタックの話
    • TYPE_* の話、内蔵プラグインの話
    • コンパイル時の解析順序の話
      • デバッガでトレースしたほうが早い
    • ax format の話
      • 公式ドキュメントが充実しているので後回し
  • 独自書式から Markdown 形式に形式的に変換したので変なところが多々ある。

  • コンパイラとランタイムの話を分けたい。

実行の流れ

初期化

  1. ランタイムのエントリーポイント関数 (main.cpp/WinMain) が、ランタイムの初期化関数(hsp3win.cpp/hsp3win_init)を呼ぶ。
  2. 初期化関数により、OLEやコモンコントロールの初期化が行われる。クラス Hsp3 のインスタンスが作られ、Hsp3::Reset により、ax ファイルの読み込み、組み込み型の登録 (HspVarCoreResetVartype)、HSPCTX の初期化 (code_resetctx)、静的変数のint初期化、などが行われる。次に、カレントディレクトリとコマンドラインの設定が行われる。wingui 特有の、comobj, variant 型が登録され (HspVarCoreRegisterType)、DLLFUNC, DLLCTRL, EXTCMD, EXTFUNC プラグインが登録される (hsp3typeinit_**)。

実行

  1. 初期化関数がエラーを返さなければ、ランタイムの実行関数(hsp3win_exec)を呼ぶ (hsp3win.cpp)。その中で DebugWindow を開き、命令実行メイン(code_execcmd)を呼ぶ。
  2. スタックを初期化してから、ループを始める。
  3. コードセグメントの先頭 (type, val) に対応する命令(cmdfunc)を実行し、返された実行モード(RUNMODE)を HSPCTX::msgfunc で処理する。ただし、return の場合は cmdfunc_return を呼ぶ。これは stop や各種割り込み、assert (ついでにいうと logmes も) の処理に関わる。
  4. 無事に終了すると関数を終える。

破棄

  1. onexit が設定されていれば、それを endsession として実行(code_execcmd2)する。
  2. ランタイムの破棄関数(hsp3win_bye)が呼ばれる。
  3. Hsp3 のインスタンスが破棄されてデストラクタが走る。コードの終了処理(code_termfunc)が呼ばれて、struct 型の各静的変数が解体され、次に onexit 指定の各ユーザ定義コマンドが呼ばれ、そして各プラグインの終了関数(HspVarProc::termfunc)が呼ばれる。次に Hsp3::Dispose により ax データが破棄される。終了関数(code_bye)が呼ばれ、一時変数とHspVarProcの破棄(HspVarCoreBye)、いくつかの文字列バッファの解放が行われる。
  4. そしてタイマー、DebugWindowが解放され、システム関連(OLE、コモンコントロール)の uninitialize が行われる。
  5. 終了コードを返してプロセスが終わる。

なお、#func onexit が呼ばれるのは TYPE_DLLCMD の termfunc の時点。

変数

HSP上の値は、(vartype, PDAT) の組み合わせで管理される。

  • vartype:

    • 型を表す int (id) で、型タイプ値という。HSP から vartype で取得できるもの。
    • 変数では flag という名前であることが多い。
    • 組み込み型の型タイプ値は、「HSPVAR_FLAG_*」として定義されている (hsp3struct.h)。
    • HSPVAR_FLAG_COMSTRUCT (comobj) という名前に注意。HSPVAR_FLAG_VARIANT はない。
  • PDAT:

    • 「(PDAT*) 型」の形で用いられる。これを「実体ポインタ」という。
    • PDAT 自体は void* の typedef だが特に意味はなく、型によって任意のデータ型の代わりとして扱われる。
      • struct PDAT { PDAT() = delete; }; と定義したほうが自然な気もする。
    • void* の「任意のデータポインタ型から暗黙変換されうる」という性質を嫌っての strong typedef と思われる。しかし reinterpret_cast が多発するのが良くない。
    • 変数(PVal)からは、HspVarProc::GetPtr 関数を利用して取得する。
    • 例:
      • str: PDAT* = char* (実際に格納されている文字配列 (の先頭要素へのポインタ))
      • int: PDAT* = int*
      • com: PDAT* = IUnknown**

変数の実体

変数は、それぞれ1つの PVal 構造体で管理される。

静的変数は ctx->mem_var[] に全て配列され、ローカル変数は prmstack の一部として配置される (後述)。

各型について「テンポラリ変数」(式中で一時的に値を格納するために使用される変数)が1つ存在する (後述)。 プラグイン側で PVal を生成することも可能である (ex: call.hpi, var_multi.hpi)。

	//	PVAL structure
	//
	typedef struct
	{
		//	Memory Data structure (2.5 compatible)
		//
		short	flag;		// type of val
		short	mode;		// mode (0=normal/1=clone/2=alloced)
		int		len[5];		// length of array 4byte align (dim)
		int		size;		// size of Val
		char	*pt;		// ptr to array

		//	Memory Data structure (3.0 compatible)
		//
		void	*master;			// Master pointer for data
		unsigned short	support;	// Support Flag
		short	arraycnt;			// Array Set Count
		int		offset;				// Array Data Offset
		int		arraymul;			// Array Multiple Value 
	} PVal;
  • flag:
    • 変数の型タイプ値 (HSPVAR_FLAG_*) が格納される。
  • mode:
    • 変数が利用するデータの種類を示す HSPVAR_MODE_* の値が格納される。
    • NONE: 未使用
    • MALLOC: 所有のバッファ
    • CLONE: 外部のバッファへの(弱)参照
  • len:
    • [0] は1要素あたりの「大きさ」(例:str型の「バッファサイズ」など)。
    • [1]~[4]はそれぞれ配列の長さ。
    • len[1] は基本的に1以上、[0] および [2..4] は0以上である。
    • プラグイン拡張型では長さ 0 の配列変数を作ることもできる。
  • size:
    • pt が指すバッファの大きさ。
    • 型を定義するファイルの中で、「GetVarSize」などの関数で与えられる。
  • pt:
    • この変数が持つ「バッファ」へのポインタを示す。ほとんどの型で、ここに変数データを保存する。
    • mode = MALLOC かつ組み込み型なら、STRBUF を利用して確保されている。
    • バッファは、変数の総要素数の分だけの配列として使用する。つまり、型の基本データが int ならば、
    • pt はデータ的に int* で、「int 型の配列の先頭」へのポインタとなる。
    • pt 自体を、要素[0]の実体ポインタ(PDAT*)として扱ってよい。
    • 「バッファ」という語が、pt の持つバッファを指すのか、あるいはそれからさらに参照できる他のバッファを指すのかは、
      • 文脈によるので注意。この文章では、「実体バッファ」と言えば PVal::pt のバッファである。
  • master:
    • 変数データを保存するための、もう一つのメンバ。使い方は HspVarProc の自由。
    • int, double 型などは使用しない。初期化もしない。
    • str, comobj 型が使用している。
    • dup, dupptr 命令などでクローンを生成するとき、master は放置されてしまうので注意する。というかバグ?
  • support:
    • HspVarProc::support の値が格納されている。HSPVAR_SUPPORT_* のビットフラグ。
    • この変数が「テンポラリ変数」なら HSPVAR_SUPPORT_TEMPVAR も持つ。

arraycnt, offset, arraymul:

  • 添字情報を示すメンバ。「配列の扱い」で詳述。

なお、全ての変数(PVal)は配列変数である。 生成直後の変数は、int 型の1次元1要素の配列 (int[1]) (値は 0) として初期化される。

PVal* を受け取って処理をする関数が多くあるため、HSP上のデータを扱うには PVal を使うのが楽なのである。

変数操作関数

  • hspvar_core.cpp の HspVarCore* 関数群について

確保

  • HspVarCoreClear: 変数を単体(要素数1)で確保する。内部的には、もっとも基本的な関数。
  • HspVarCoreClearTemp: テンポラリ変数を確保する
  • HspVarCoreDim: 固定長ストレージ型(HSPVAR_SUPPORT_STORAGE) (intなど) の配列の確保
  • HspVarCoreDimFlex: 可変長ストレージ型(HSPVAR_SUPPORT_FLEXSTORAGE) (strなど) の配列の確保
  • HspVarCoreRedim: 既に配列となっている変数の要素数を変更(拡張)する。
  • HspVarCoreDup : 変数を他の変数のクローンとして確保する
    • dup 命令の実体
    • dup a, bdupptr a, varptr(b), varsize(b), vartype(b) に等しいことが分かる。
  • HspVarCoreDupPtr:
    • dupptr 命令の実体
    • PVal::master, PVal::support がうまく処理されてないっぽい?

解放

PVal* に対して HspVarProc::Free を呼ぶと、すべての要素が解放される。

  • 呼び出し後、mode が HSPVAR_MODE_DISABLE となる。
  • PVal* 自体のバッファはそれぞれの方法(delete や free など)で解放する。静的変数なら (HSPCTXの一部なので) 解放の必要はなく、ローカル変数・モジュール変数なら prmstack の一部なのでそれの解放を待つ。

配列の扱い

PVal 構造体のメンバを見ても分かるとおり、すべての変数は配列である。

HSPVAR_SUPPORT_FLEXARRAY, HSPVAR_SUPPORT_ARRAYOBJ のいずれかをサポート している型は、常に変数は配列であるものとして、処理しなければならない。

配列用のバッファの確保

  • PVal::len[1..4] を設定した後に HspVarProc::Alloc を呼んで確保する。

変数型の実装

HspVarProc 構造体

変数型の具体的な動作は、HspVarProc 構造体で定められる。

	//		command execute core function
	//
	typedef struct
	{
		//		データフィールド
		//
		short flag;							// 型タイプ値 (親アプリケーションで設定されます)
		short aftertype;					// 演算後のタイプ値
		short version;						// 型タイプランタイムバージョン(0x100 = 1.0)
		unsigned short support;				// サポート状況フラグ(HSPVAR_SUPPORT_*)
		short basesize;						// 1つのデータが使用するサイズ(byte) / 可変長の時は-1
		short opt;							// (未使用)

		char *vartype_name;					// 型タイプ名文字列へのポインタ
		char *user;							// ユーザーデータ(未使用)

		//		システム参照・型変換用
		//
		void *(*Cnv)( const void *buffer, int flag );
		void *(*CnvCustom)( const void *buffer, int flag );
		PDAT *(*GetPtr)( PVal *pval );

		void *(*ArrayObjectRead)( PVal *pval, int *mptype );// 配列要素の指定 (連想配列/読み出し)
		void (*ArrayObject)( PVal *pval );							// 配列要素の指定 (連想配列/書き込み準備)
		void (*ObjectWrite)( PVal *pval, void *data, int type );		// HSPVAR_SUPPORT_NOCONVERT指定時の代入
		void (*ObjectMethod)( PVal *pval );							// 変数に対するメソッドの指定

		void (*Alloc)( PVal *pval, const PVal *pval2 );		// 変数メモリを確保する
		void (*Free)( PVal *pval );						// 変数メモリを解放する

		int (*GetSize)( const PDAT *pdat );			// 要素が使用するメモリサイズを返す(可変長のため)
		int (*GetUsing)( const PDAT *pdat );			// 要素が使用中であるかを返す(varuse関数用)

		//		変数バッファ(バイナリ)のポインタとサイズを返す
		//		(要素が可変長(str)の場合は該当する1配列バイナリのみ)
		//		(要素が固定長(int,double)の場合は全配列バイナリ)
		//		(サイズはメモリ確保サイズを返す)
		void *(*GetBlockSize)( PVal *pval, PDAT *pdat, int *size );

		//		バイナリデータ用にメモリブロックを確保する
		//		(要素が可変長(str)の場合にブロックサイズを強制的に確保する)
		//		(固定長の場合は何もしない)
		void (*AllocBlock)( PVal *pval, PDAT *pdat, int size );

		//		代入用関数(型の一致が保障されます)
		//
		void (*Set)( PVal *pval, PDAT *pdat, const void *in );

		//		演算用関数(型の一致が保障されます)
		//
		void (*AddI)( PDAT *pval, const void *val );
		void (*SubI)( PDAT *pval, const void *val );
		void (*MulI)( PDAT *pval, const void *val );
		void (*DivI)( PDAT *pval, const void *val );
		void (*ModI)( PDAT *pval, const void *val );

		void (*AndI)( PDAT *pval, const void *val );
		void (*OrI)( PDAT *pval, const void *val );
		void (*XorI)( PDAT *pval, const void *val );

		void (*EqI)( PDAT *pval, const void *val );
		void (*NeI)( PDAT *pval, const void *val );
		void (*GtI)( PDAT *pval, const void *val );
		void (*LtI)( PDAT *pval, const void *val );
		void (*GtEqI)( PDAT *pval, const void *val );
		void (*LtEqI)( PDAT *pval, const void *val );

		void (*RrI)( PDAT *pval, const void *val );
		void (*LrI)( PDAT *pval, const void *val );
	} HspVarProc;
  • basesize:

    • この型の「1要素」が使用するバッファサイズ (例:sizeof(int), sizeof(double))。
    • 可変長なら負数(-1)。
    • pt で1要素が占めるサイズとは限らない。
  • Alloc:

    • PVal::mode = MODE_ALLOC なら、変数のメモリを確保する。
    • pval == pval2 となる場合があるので注意する。
  • Free:

    • PVal::mode = MODE_ALLOC なら、変数のバッファを解放する。
  • AllocBlock:

    • (HSPVAR_SUPPORT_FLEXSTORAGE)
    • バッファサイズを指定サイズ以上確保する。
    • 命令 memexpand の実体。
  • Cnv, CnvCustom:

    • 型変換を実行する関数。int 関数などの実体。
    • プラグイン拡張型でも、組み込み型のみに対応しておけばよい。
  • GetPtr:

    • 変数(の offset で指定した要素)に対応する実体ポインタを返す。
  • GetSize:

    • 変数(の offset で指定した要素)に対応するデータサイズを返す (バッファのうち、使用中の部分のサイズ)。
    • 固定幅(HSPVAR_SUPPORT_STORAGE)の型の場合は、HspVarProc::basesize の値が返されるだろう。
    • 例: str 型なら strlen + 1 の値を返す。
  • GetBlockSize:

    • (HSPVAR_SUPPORT_FLEXSTORAGE):
      • 変数(の offset で指定した要素)に対応する「バッファサイズ」を返す。
    • else:
      • 変数が確保したバッファサイズ (配列全体) を返す。PVal::size の値と一致するはず。
    • デバッグウィンドウの「メモリダンプ」では、この関数から得たポインタとサイズが表示される。
  • GetUsing:

    • (HSPVAR_SUPPORT_VARUSE)
    • 変数(の offset で指定した要素)が有効か否かを返す。
  • ArrayObjectRead:

    • (HSPVAR_SUPPORT_ARRAYOBJ)
    • 連想配列として、offset の値をもとに要素の値を返す。
    • 変数が添字括弧 ( ) つきで右辺値として参照されたときに呼ばれる。
    • これが呼ばれた時点では、添字は解析されていない。
      • そのため、この関数の実行の最初に ArrayObject を呼び出しておくとよい。
  • ArrayObject:

    • (HSPVAR_SUPPORT_ARRAYOBJ)
    • 変数に添字括弧 ( ) つきで参照されるときに呼ばれる。
    • 括弧内の引数列を、関数の引数と同様に取り出して、処理する。それを元に、PVal::offset に値を設定する。
  • ObjectWrite:

    • (HSPVAR_SUPPORT_NOCONVERT)
    • 添字括弧 ( ) つきの場合、変数要素に代入するときの処理を行う。Set と違い、右辺の型が左辺と異なることがある。
    • 添字括弧なしの場合は、要素ではなく、変数自体への破壊代入と解釈する。
  • ObjectMethod:

    • 変数に対するメソッド呼び出し ( mcall, -> ) の処理をする。(ex: comobj)
  • Set:

    • 要素への代入を処理する。右辺は、この型に合わせて変換されているので、型不一致はない。
    • pdat は代入先の要素の実体ポインタ。
    • 右辺の const void* は、const PDAT* のことである。 AddI, 他:
    • それぞれ対応する二項演算を処理する。両辺の値を演算し、その結果を左辺の変数に書きこむ。
    • 演算後に型が変わる場合、その型を HspVarProc::aftertype に書きこむ。
    • 右辺の const void* は、const PDAT* のことである。
  • 新規作成する場合は、組み込み型のコードを参考にするのが良い。

変数がどのようにデータを保存するかは、型の実装 (HspVarProc) に依存する。

  • HspVarProc が PVal::pt, master をどう使用するかを、HSP側は知らない、ということ。
    • PVal::pt の値が第一要素の実体ポインタとして使える、という例外はあるけど。
  • ちなみに、上の方で「実体型」だの何だのと書いたが、それは組み込み型に関して実装の仕様が
    • 統一的になっているから言えるのであって、実際にはどんな使い方をしてもよい。

例えば、int 型はC形式の配列として実体を確保しているが (int[]とメモリ構造が等しいという意味)、 str 型は特殊な方法で文字列データを保存している。

型がサポートする動作

HSPVAR_SUPPORT_* それぞれについてのそこそこ詳細な解説。

  • HSPVAR_SUPPORT_STORAGE: // 固定長ストレージサポート
    • 1要素あたりのデータサイズが、内容に関わらず一定であることを示す。
    • なお、「FlexValue」を実体型とする struct 型もこのサポートフラグを持つが、
    • これは FlexValue 構造体の大きさが種類や内容によらず一定なためである。
    • HspVarProc::GetBlockSize で FlexValue::size の値を取得することが可能だが、
    • その値は、要素によって可変である。
    • 対して、str 型はこのサポートフラグを持たない。
    • このことから、このフラグを持つ条件は、HspVarProc::GetSize が一定値を返すことだと推測される
    • ( str 型は GetSize で ((文字列長) + 1) の値を返し、これは内容によって可変である )。
    • 例: int, double; struct
  • HSPVAR_SUPPORT_FLEXSTORAGE: // 可変長ストレージサポート
    • 1要素あたりのデータサイズが、内容によって異なることを示す。
    • 使用中のサイズは HspVarProc::GetSize、確保されたサイズは HspVarProc::GetBlockSize で取得する。
    • 例: str
  • HSPVAR_SUPPORT_FIXEDARRAY: // 配列サポート
    • 配列として扱えるが、要素の自動拡張には対応しないことを示す。
  • HSPVAR_SUPPORT_FLEXARRAY: // 可変長配列サポート
    • 配列として扱えて、要素の自動拡張にも対応することを示す。
    • 例えば、a がこの型の要素数 3 の配列であるとき、 a(5) = 0 とアクセスすれば、これは自動拡張されて、要素数 6 の配列となる。
    • もしこの型が HSPVAR_SUPPORT_FIXEDARRAY だったら、「配列の要素が無効です」というエラーが出る。
  • HSPVAR_SUPPORT_ARRAYOBJ: // 連想配列サポート
    • 連想配列として扱えることを示す。
    • 言い換えると、配列として扱えて、その添字の処理も型が独自に行うことを示す。
    • HspVarProc::ArrayObject, HspVarProc::ArrayRead の実装が必須である。
    • これが自動拡張を行えるか否かは、HSPVAR_SUPPORT_FLEXARRAY に対応するか否かによって決まる。
    • 例: a = x, y (x, y は同一の連想配列型, a は未定義変数) とした場合、x の型が自動拡張に対応していないと
      • 「配列の要素が無効です」のエラーを生じることになる。
  • HSPVAR_SUPPORT_FLEXSIZE: // 要素ごとのデータが可変長
    • 使われていないので不明。
  • HSPVAR_SUPPORT_NOCONVERT: // 代入時の型変換を無効にする
    • 代入時、可能なら HspVarProc::ObjectWrite 関数を呼ぶことを示す。
    • 左辺がこの型の配列で、添字つきで参照されている場合、HspVarProc::Set の代わりに
    • ObjectWrite が呼ばれる。
    • Set を呼ぶ場合は、左辺と右辺の型が異なるなら、両辺の型を合わせるために、右辺を型変換するが、
    • ObjectWrite なら型変換をせずに実行出来る (そのため No Convert フラグという名前になっている)。
    • なお、左辺がこのフラグを持つ型であっても、 添字リスト ( ) が付いていなければ、通常通り右辺を型変換し、Set を呼ぶ。
  • HSPVAR_SUPPORT_VARUSE: // varuse関数のチェックを有効にする
    • この型の値が、varuse 関数に対応していることを示す。
    • HspVarProc::GetUsing 関数の実装が必須である。varuse の実体はこの関数である。
    • 要素が「無効」である状態が存在する場合は、このフラグを立てておく方が良い。
    • そうでなくても、varuse がエラー起こさないためにどの型もこれを立ててくれた方が嬉しいとかなんとか。
  • HSPVAR_SUPPORT_TEMPVAR: // テンポラリ変数として使用する
    • HspVarProc::support ではなく、PVal::support の方にのみ使用される。
    • 変数が「テンポラリ変数」であることを示す。
    • 詳しくはその項を参照。
  • HSPVAR_SUPPORT_USER1: // ユーザーフラグ1
  • HSPVAR_SUPPORT_USER2: // ユーザーフラグ2
    • ユーザが勝手に使って良いらしい。

型の登録

変数型をHSPに登録するには、HspVarCoreRegisterType( int flag, HSPVAR_COREFUNC func ) を呼ぶ。 このとき flag に -1 を指定すると、内部で HspVarCoreAddType が呼ばれ、新しい型が生成される。

HSP内部が型ごとに割り当てるデータは、{ 型タイプ値, HspVarProc, テンポラリ変数(mpval) } の3つ。 HspVarCoreAddType が呼ばれると、これらが新たな型のために準備される。

型タイプ値とテンポラリ変数は自動で確保し、HspVarProc は、関数 (HSPVAR_COREFUNC) を呼んで初期化する。

  • typedef void (* HSPVAR_COREFUNC) (HspVarProc *); 初期化関数は受け取った HspVarProc に諸関数や型名を代入し、また HspVarProc::flag から自身の型タイプ値を取得する。
  • 組み込み型は HSPVAR_FLAG_INT などの定数があるが、プラグイン定義の型はそれがないため、
  • グローバル変数などに自身の型タイプを代入して記憶する。
  • 参照: HspVarInt_Init (hspvar_int.cpp)
    • int 型の初期化関数

なお、組み込み型は HspVarCoreResetVartype で追加される。

HspVarProcの実体

HspVarProc の動的配列であるグローバル変数 hspvarproc が持つ。

extern HspVarProc *hspvarproc;

初めは HspVarCoreInit で組み込み型の分(TYPE_USERDEF個)確保され、拡張プラグイン型のぶんは動的に拡張される。

この配列の要素番号は型タイプ値と対応する。要素 [0] は使用されない。

型の置換

HspVarCoreRegisterType の flag に有効な型タイプ値、例えば HSPVAR_FLAG_INT などを指定すると、その型の HspVarProc を(事実上)置換できる。

型各論

label

ラベル(コード上の位置)を保持する型。 実体は HSPVAR_LABEL (typedef unsigned char*)。

str

文字列を管理する型。別名、文字列型。 実体は (char*) である。 なお、str 型に限り、実体ポインタ (PDAT*) も (char*) なので注意すべし!

実体 (HSPVAR_MODE_MALLOC) なら、この (char*) は STRBUF ポインタである。

  • sbAlloc で確保し、sbFree で解放する (解放権限は HspVarProc::Free が持つ)。 また、それのオプション (sbGetOption) は、この char* へのポインタ(char**)となっている。

実体ポインタは、内部関数 GetFlexBufPtr により得られる。第一要素のポインタは PVal::pt が、第二要素以降の ポインタは、(char*) の配列となっている PVal::master が持つ。

クローン (HSPVAR_MODE_CLONE) なら、この (char*) は外部から与えられる。 bufsize は拡張できない。バッファサイズも不明 ((strlen + 1) と仮定して計算される)。

  • やや複雑な型のサンプルとして適している。

double, int

それぞれ、実数値、整数値を管理する型。別名、実数型、整数型。 実体は double, int 、実体ポインタ PDAT* は double*, int* である。

  • 単純な型のサンプルとして適している。

struct

概要

モジュールクラスの実体を管理する型。通称、モジュール変数型。あるいはインスタンス型。 実体は FlexValue 、実体ポインタは FlexValue* である。 疑似クラス機能である「モジュール変数」機能のインスタンスを表す。

使い方

newmod で生成する。 変数とともに管理され、その変数の寿命切れや delmod されたときに、破棄される。

なお、実行終了時に struct 型の静的変数はすべてランタイムによって破棄される。

STRUCTDAT構造体

modcmd と同じように、STRUCTDAT 1つと複数の STRUCTPRM で、構造体の情報、すなわち メンバの情報が保存されている。

STRUCTDAT は、HSPCTX::mem_finfo[STRUCTPRM::subid] にある。

STRUCTPRM は (メンバ変数の数 + 1) 存在し、一つ目の STRUCTPRM は mptype == MPTYPE_STRUCTTAG になっている。 ※STRUCTDATが構造体情報を表すかどうかを判定するため?

    • STRUCTDAT
      • index // STRUCTDAT_INDEX_* の値
      • subid // STRUCTPRM_SUBID_* の値
      • prmindex // STRUCTPRM の先頭の index
      • prmmax // 使用する STRUCTPRM の数
      • nameidx // 名称文字列 (DSオフセット)
      • size // 引数全体が使用するサイズ
      • otindex // 位置の OT-index
      • funcflag // STRUCTDAT_FUNCFLAG_* の値 (bits-flag)
    • STRUCTPRM
      • mptype
      • MPTYPE_* の値が入る。
      • subid
      • module の STRUCTDAT に属する場合、その subid。
      • modcmd の場合、STRUCTPRM_SUBID_* 。
      • offset
      • 引数の場合、prmstack のオフセット値。
      • mptype == MPTYPE_STRUCTTAG の場合、構築関数の finfo-index 。
      • 構築関数がなければ -1 。

構築関数(modinit)、解体関数(modterm)

構築関数は、その finfo-index が

  • HSPCTX::mem_minfo[ moduleのSTRUCTDAT::prmindex ]::offset に入っている。存在しなければ -1。

解体関数は、その finfo-index が

  • moduleのSTRUCTDAT::otindex に入っている。存在しなければ 0。

FlexValue構造体

struct 型の値を管理するために使われる構造体。

    • FlexValue
      • type // ( PVal::mode 的なもの )
      • myid // 固有ID (未使用)
      • customid // 構造体の種類 ( クラスの STRUCTTAG を持つ minfo-index )
      • clonetype // typeID for clone ( 後述 )
      • size // ptr が確保しているサイズ(bytes)
      • ptr // 値へのポインタ

type は、このインスタンスの状態として以下の値のどれかを保持する。PVal::mode と同様。

  • FLEXVAL_TYPE_NONE: 未使用

  • FLEXVAL_TYPE_ALLOC: 実体

  • FLEXVAL_TYPE_CLONE: クローン (弱参照)

  • ptr:

    • prmstack 形式で、メンバ変数の値が保存されている。
    • STRUCTPRM の mptype は、すべてコンパイラによって local が指定されている。
  • clonetype:

    • 次の3箇所で使用されている。 0. code_setvs で明示的に 0 が代入されている。 0. HspVarProc::Alloc で、他のメンバと同様に memcpy で 0-fill される。 0. clonetype == 0 のときのみ、デストラクタ(modterm)を実行する。
    • コメントアウトされたコードの中にもいくつかあるため、仕様の変遷によって 不必要になったメンバだと思われる。

FlexValue インスタンスは newmod により生成され、HspVarProc::Free により削除される。

newmod の処理

コードを読んでも流れが分かりにくいのでまとめた。

  1. コードから変数を受け取り、code_newstruct で作成されるインスタンスの格納先 APTR を取得する。
  2. code_getstruct で、生成されるモジュールクラス(STRUCTDAT*)を取得する (newmod の第2引数)。
  3. code_setvs で FlexValue を生成し、それをインスタンス変数の指定位置(APTR)に「代入」する。
  4. code_expandstruct で、先程の FlexValue に指定されていたバッファ(prmstack)を初期化する。
  • local の位置に PVal の実体が確保される、など。
  1. 最後に、あればコンストラクタ(modinit)を呼び出す。

comobj, variant

詳しくは知らない。win32gui/ で定義される。

式の評価(eval)

コードから「値」(引数値、代入文両辺など)を取得するときの処理。 取得する際に、左辺値か右辺値か分かっている必要がある。

  • コードから式の値を取得するには、次の関数を使う。
    • 右辺値:
      • code_get
    • 左辺値:
      • code_getpval: 配列変数
      • code_getva: 配列要素

左辺値

HSPにおいて、左辺値となりうる値は変数しか存在しない。

単独変数なら code_getva、配列変数なら code_getpval を使う。変数の PVal* (左辺値) が得られる。

  • 次のコードの種類によって処理が分岐し、種類ごとの方法で変数のデータを取りに行く。詳しくは関数本体。

右辺値

code_get と、それの wrapper (code_geti など; 型ごとにキャストやエラーチェックを挟む) を利用する。

(EXFLG_1 | EXFLG_2) フラグを持つコードの直前までが一連の式である。それらを 連続して取り出すことで、式から1つの値を取り出す。取り出された式の値は、 テンポラリ変数 mpval が保持している。

式はコード中に逆ポーランド法で保存されているので、スタックを利用して演算する。 値が現れればスタックに積み、演算子が現れればスタック上の値に対して演算を行う。

変数が現れた場合、その code_checkarray または code_checkarray_obj で、その直後に添字括弧が続くかどうかを 調べ、続けばそれを即座に取得・処理する。そして、変数の値がスタックに積む。

  • コマンドが現れた場合、TYPEINFO::reffunc を呼び、その返値をスタックに積む。

テンポラリ変数(mpval; temporary variable)

各型について、テンポラリ変数と呼ばれる変数(PVal)が1つ存在する。 テンポラリ変数は PVal::support に HSPVAR_SUPPORT_TEMPVAR を持つ (このフラグは初期化後に追加される)。 代入でない二項演算を行う際(code_calcop)と、code_get で得られた値を運ぶために使用する。

実体

実体は、HspVarProc と同じ数の要素を持つPValの配列 mem_pval (hspvar_core.h で extern 宣言)である。 int のものを除き、最初に使用される直前に初期化される。

  • 初期化は HspVarCoreClearTemp を利用する。
  • int はコード実行の開始直前に初期化されている。code_execcmd (hsp3code.cpp) 参照。
  • 型が増えたら mem_pval を拡張している。HspVarCoreResetVartype (hspvar_core.cpp) 参照。

操作

型に対応するテンポラリ変数は、HspVarCoreGetPVal でそのポインタが得られる。

  • hsp3code.cpp の中では、ファイルスコープ変数 mpval_int が int 型のテンポラリ変数へのポインタを持つ。

テンポラリ変数への値の代入は HspVarProc::Set を利用する (ObjectWrite は呼ばれない)。

  • pdat に pval->pt を使う。

代入でない二項演算

左辺の型に対応するテンポラリ変数を mpval とし、左辺の値をそれに代入する。 mpval を左辺として HspVarProc::AddI などの関数を呼ぶ。 その後、mpval の値をスタックに積む。

  • この演算によって型が変わる場合も、その結果はこの型のテンポラリ変数に格納される。
  • 例: 比較演算 == で、値は int 型 (0 か 1) になる。
  • そのため、mpval->pt がこの型が取り得ない値をとることがあるので注意する (Free のときなど)。
  • 参照: code_calcop, calcprm (hsp3code.cpp)

右辺値の運搬

code_get は、終盤で、スタック上に1つだけ残った値を mpval に代入してから、関数を終了している。 ここは代入操作のみ。

スタック

計算に用いるスタックは、モジュール stack.cpp, stack.h で定義されている。マクロが複数あるので注意。

StackInit, StackTerm でモジュールの初期化・破棄である。それぞれ、コード実行の開始前・終了後に行う。

実体

スタックフレームは構造体 STMDATA で、その動的配列 mem_stm がスタックの本体である。 要素数は stm_cur 。

	//	STMDATA structure
	//
	typedef struct
	{
		//	Memory Data structure
		//
		short type;
		short mode;
		char *ptr;
		int ival;
		char itemp[STM_STRSIZE_DEFAULT-4];		// data area peding
	} STMDATA;

スタックフレームが持つデータの場所は、その大きさによって異なる。ただし、データの場所は一貫して ptr が指す。

  • 値のデータ量が一定サイズ (STM_STRSIZE_DEFAULT = 64) 以下の場合:

    • mode = STMMODE_SELF となり、ptr が &ival を指す。値は ival と itemp の領域に保存される。
  • それより大きなサイズの場合:

    • mode = STMMODE_ALLOC となり、ptr が malloc で確保したヒープを指す。値はそのヒープに保存される。
    • ヒープは必要なサイズの分だけ確保される。
    • STRBUF ではなく普通に malloc で確保している。
  • スタックフレームは StackAlloc で初期化される。

  • スタックフレームのポインタ(STMDATA*)を pp とすると、実体データへのポインタは STM_GETPTR(pp) で表す。

  • ptr は実体ポインタなので、(char*) というより (PDAT*) である。(void*) でないのは、これ経由で値を書き込むため。

  • 値の種類は type が示す。以下の2通りである。 0. 型タイプ値(HSPVAR_FLAG_*)

    • 単に、HSP上で扱われる値を保存するフレームであることを示す。
    • code_get で Push/Pop される。
    1. 定数 TYPE_EX_* の値
    • #define TYPE_EX_SUBROUTINE 0x100 // gosub用のスタックタイプ
    • #define TYPE_EX_CUSTOMFUNC 0x101 // deffunc呼び出し用のスタックタイプ
    • hsp3struct.h
    • ユーザ定義命令・関数を呼び出すときに、その prmstack を保存する領域としてスタックフレームが積まれる。
    • つまり STMDATA::ptr ⇔ prmstack となる。呼び出し終了後に破棄される。

StackPush, StackPop でそれぞれ値のプッシュ・ポップを行う。

命令/関数の呼び出し

EXFLG_1 がある、文頭のコードで TYPEINFO::cmdfunc を呼ぶ。 この関数ポインタが指す関数の内容によって、プラグインごとに異なる処理が行われる。

反復(repeat-loop)

配列 hspctx->mem_loop にループのデータ LOOPDAT が格納されている。 一番要素番号の大きいものが最内周ループ。

  • hsp3code.cpp の中では、関数的マクロ GETLOP( looplev ) -> LOOPDAT* で取得する。

ユーザ定義命令/関数の実行

ユーザ定義命令/関数は TYPE_MODCMD で、それぞれ cmdfunc_custom, reffunc_custom にて実行される。

どちらも実行の本体は code_callfunc だが、prmstack を用意する以外は gosub とほぼ同じ。

返値の受け渡し

return コマンドの実行時に、cmdfunc_return_setval が呼ばれ、 返値の型に対応するシステム変数に返値が格納される。 また、返値の型は hsp3code.cpp のファイルスコープ変数 funcres が持つ。

関数呼び出しの終了時、reffunc_custom で、funcres の値(返値の型)に対応するシステム変数から返値を読み出し、それを返値とする。

構造体パラメータ

  • #module, #deffunc の話。
  • #dllfunc, #comfunc については詳しくないので省略。

基本データ

STRUCTDAT

STRUCTDAT は ctx->mem_finfo[] に全て格納されている。これはAXファイルにある FINFO セグメントのコピーである。

	// function,module specific data
	typedef struct STRUCTDAT {
		short	index;				// base LIBDAT index
		short	subid;				// struct index
		int		prmindex;			// STRUCTPRM index(MINFO)
		int		prmmax;				// number of STRUCTPRM
		int		nameidx;			// name index (DS)
		int		size;				// struct size (stack)
		int		otindex;			// OT index(Module) / cleanup flag(Dll)
		union {
		void	*proc;				// proc address
		int		funcflag;			// function flags(Module)
		};
	} STRUCTDAT;

STRUCTDAT 1つが ( モジュールクラス, ユーザ定義命令・関数, Dll関数 ) のどれか1つに対応する。

それが持つデータは、STRUCTPRM の(部分)配列として保存される。 ctx->mem_minfo[ STRUCTDAT::prmindex ] を先頭として、STRUCTDAT::prmmax 個の STRUCTPRM を持つ。

  • STRUCTPRM 列の中身
    • モジュールクラス:
      • [1] MPTYPE_STRUCTTAG
      • [...] メンバ変数
    • ユーザ定義命令・関数 or Dll関数:
      • [1] thismod:
        • #modinit -> MPTYPE_IMODULEVAR
        • #modterm -> MPTYPE_TMODULEVAR
        • #modfunc -> MPTYPE_MODULEVAR
      • [...] 仮引数

STRUCTPRM

STRUCTPRM は ctx->mem_minfo[] に全て格納されている。

	typedef struct STRUCTPRM {
		short	mptype;				// Parameter type
		short	subid;				// struct index
		int		offset;				// offset from top
	} STRUCTPRM;

連続するいくつかの STRUCTPRM が、1つの STRUCTDAT に従属する。 subid が所属する STRUCTDAT の index を表す。

  • STRUCTPRM_SUBID_STACK:
    • ユーザ定義命令(#deffunc, #modfunc など)の引数を表す
  • STRUCTPRM_SUBID_STID:
    • Dll命令(#func, #comfunc など)の引数を表す
  • STRUCTPRM_SUBID_DLL:
  • STRUCTPRM_SUBID_DLLINIT:
  • STRUCTPRM_SUBID_OLDDLL:
  • STRUCTPRM_SUBID_OLDDLLINIT:
  • STRUCTPRM_SUBID_COMOBJ:
    • 未確認

prmstack (parameter stack; prmstk)

STRUCTDAT が持つデータ (メンバ変数や実引数) の実体。あるいはその形式。 STRUCTPRM に対応するデータが敷き詰められた構造となっている。

  • 例:

    • 仮引数列: int, int, str, double, local
    • prmstakc のレイアウト: struct { int; int; char*; double; PVal; }
      • 68 bytes
  • 参照: MPTYPE_* と size の対応 → PutStructParam [token.cpp]

  • 参照: MPTYPE_* とデータ型の対応 → code_expandstruct [hsp3code.cpp]

  • 参照: MPTYPE_* Dll関数用 prmstk → code_expand_next [win32gui/hsp3extlib.cpp]

いくつかの MPTYPE_* について

  • MPTYPE_MODULEVAR, MPTYPE_IMODULEVAR, MPTYPE_TMODULEVAR:
    • いわゆる thismod となるような引数の実体。
		//	Var Data for Module Function
		typedef struct MPModVarData {
			short subid;
			short magic;
			PVal	*pval;
			APTR	aptr;
		} MPModVarData;

subid で STRUCTDAT が得られるのが特徴。magic は定数 MODVAR_MAGICCODE が格納される。

  • init, term の場合、ファイルスコープ・グローバル変数 modvar_init が与えられる (hsp3code.cpp)。

    • newmod, delmod の時に、この変数に thismod の情報が設定される。
    • コードから変数を得られないため。
  • MPTYPE_SINGLEVAR, MPTYPE_ARRAY:

    • 参照渡し引数、var, array の実体。
		//	Var Data for Multi Parameter
		typedef struct MPVarData {
			PVal	*pval;
			APTR	aptr;
		} MPVarData;

配列として参照されるたび、pval->offset に aptr が設定されるようになっている。

  • MPTYPE_LOCALSTRING:

    • 取得した文字列を確保できるだけのバッファを新たに確保し、そのバッファへのポインタが prmstack に書き込まれる。
  • MPTYPE_LOCALVAR:

    • prmstack 上の領域に、直接 PVal が展開されている (そのため sizeof(PVal) を占める)。
    • この空の PVal を、HspVarProc::Alloc などで動的に初期化する。
    • この引数は、モジュールクラスのメンバ変数としても利用される。
    • プラグインで PVal を自作するときの参考になる。
    • これを引数から初期化するようにしたバージョンの仮引数タイプがあれば any 引数の実装となるのに。

引数データとしての運用

最新の prmstack は ctx->prmstack が持つ。

  • ユーザ定義命令・関数の呼び出し
    • cmdfunc_custom, reffunc_custom:
      • コマンドとしての処理
  • 命令部分へのプログラムジャンプ
    • code_callfunc:
      • ここでスタック上に (HSPROUTINE と) prmstack のための領域が確保される。
    • code_expandstruct:
      • コードからデータを読み込み、prmstack にデータを書き込む処理。
  • return 時
    • cmdfunc_return:
    • customstack_delete:
      • prmstack を解体する。
      • 文字列(LOCALSTRING)用バッファの解放、local 変数の解体。

モジュールインスタンスとしての運用

FlexValue を参照。 このとき、最初1つの MPTYPE_STRUCTTAG を除き、全て MPTYPE_LOCALVAR (メンバ変数) である。

STRUCTDAT生成過程

  • from:
    • GenerateCodePP_deffunc0
    • GenerateCodePP_func
    • GenerateCodePP_usecom
    • GenerateCodePP_comfunc
    • GenerateCodePP_struct
  1. PutStructStart
  • STRUCTDAT の生成の準備を行う。
  1. 必要な回数だけ PutStructParam または PutStructParamTag を呼ぶ。
  • 後者はモジュールクラスが最初に一回使用するのみ。他はすべて前者。
  • STRUCTPRM を生成し、データに追加する。
  1. PutStructEnd, or PutStructEndDll
  • STRUCTDAT の生成を完了する。2.で生成した STRUCTPRM を所有する。

反復処理

反復関係命令は次の通り。いずれも TYPE_PROGCMD。

  • repeat, foreach
  • loop
  • break, continue

概要

HSPでの反復処理を行う命令群。

コード生成時の細工

CodeGenerate フェイズで、上記の命令群には細工がなされる。

なお、コード生成中は replev でループの深さを管理している。

反復開始命令の repeat, foreach が来ると、その後に仮ラベルを配置する。この 仮ラベル( -1で登録されている )の OT-index を、repend[replev] に設定しておく。

  • foreach の場合は、さらに eachchk 命令(仮称)と、同じ仮ラベルを出力する。

反復終端命令 loop が来ると、開始時の仮ラベルを修整し、現在位置(loopの位置)に 書き換える。

このようにして、反復開始命令の時点で終端を知ることができるようにしている。 また、その他の反復命令 break, continue の第一引数にも、この仮ラベルが挿入さ れている。

実行時

実行時は、ループの深さを HSPCTX::looplev が、ループの情報を HSPCTX::mem_loop が管理している。後者は LOOPDAT 構造体の配列 [ネストの最大数] となっている。

  • LOOPDAT:
    • time: 反復する回数 or 無限ループを表す定数( ETRLOOP )
    • cnt: 現在の cnt
    • step: 未使用
    • pt: repeat の位置

mem_loop[ looplev ] が、現在使用されている LOOPDAT となる。hsp3code.cpp の中 なら GETLOP マクロでも取得できる。

プラグイン

内蔵プラグインについて。

STRBUF モジュール

  • strbuf.cpp, strbuf.h

概要

曰く「おおらかなメモリ管理を行うモジュール」。

文字列(str)型のバッファを管理するのにのみ使える関数もあるが、一般のバッファとし ても使用できるし、実際使用されている。

  • 各型の PVal::pt など。

strbuf-ptr

sbAlloc で確保、取得できるバッファへのポインタを、strbuf-ptr と呼ぶことにする。

メモリ管理に使われる STRBUF 構造体は、以下の構成となっている。

  • STRBUF
      • STRINF // 文字列バッファの情報
      • flag // 使用中か否か
      • exflag // (未使用)
      • intptr // 内部 STRBUF へのポインタ
      • size // 確保サイズ
      • ptr // 文字列バッファへのポインタ
      • extptr // 外部 STRBUF へのポインタ
      • opt // ユーザが使えるポインタ。sbGetOption で得る。
      • data [64] // バッファ

内部 STRBUF は、STRINF を包含している STRBUF のこと。

確保サイズ(size)が64バイトを超える場合、新しく STRBUF を、data が必要なサイズになるように(いわゆる可変長構造体として)確保し、extptr にそのポインタを保存する。それを外部 STRBUF と呼ぶ。

strbuf-ptr は、次のどちらかを指す。この値は STRINF::ptr に保存される。いずれにせよ、これは必ず指定されたサイズのバッファを指す。

  • 要求されるバッファサイズが 64 バイト以下なら、内部 STRBUF の data
  • 要求されるバッファサイズが 64 バイトより多いなら、外部 STRBUF の data

strbuf-ptr から sizeof(STRINF) を減算すると、親 STRBUF へのポインタとなる。

  • sbGetSTRINF を使う。

なお、外部 STRBUF の STRINF 部分は、元の STRBUF の STRINF 部分のコピーである。

  • STRINF::flag:
    • STRINF_FLAG_USEINT: 内部 STRBUF を使うとき、つまり 64 バイト以下のとき
    • STRING_FLAG_USEEXT: 外部 STRBUF を使うとき

STRBUFの管理

外部STRBUFの場合

所有元の STRBUF が解放されるときに、malloc や free によって確保・解放される。

内部STRBUFの場合

STRBUF はすべて、SLOT::mem に、大量の配列として確保される。確保される個数は 変数 slot_len を参照し、これは SLOT が増えるたびに 1.8 倍になる。

  • 初期値は STRBUF_BLOCK_DEFAULT。

  • SLOT 構造体

      • mem // STRBUF の動的配列
      • len // mem の要素数

SLOT 構造体は動的配列として確保され、mem_sb がそれへのポインタを持つ。 それの要素数は str_blockcur が保存する。

  • SLOT の拡張を行うのは、内部関数 BlockPtrPrepare のみ、と実装されている。

新しい STRBUF が欲しい場合は、この mem のうち、使用されていない、すなわち

  • STRBUF::inf.flag が STRINF_FLAG_NONE のものを探し、それへのポインタを返す。

ただし、STRBUF を求めるたびに線形探索するのでは非効率である。そのため実際は、初期化されている STRBUF は、extptr メンバに ( 配列 mem において ) その前の要素である STRBUF へのポインタを持つ、という連結リスト構造になっている。 変数 freelist が、この未使用の STRBUF 連結リストの末尾へのポインタを持つ。

新たな STRBUF を取得する内部関数 BlockEntry は freelist が NULL でない場合、それの指すポインタ値を返却し、freelist を freelist->inf.extptr に書き換え、連結リストを進める。最後の STRBUF の extptr は NULL なので、freelist が NULL になる場合というのは、SLOT::mem にある STRBUF を使い切ったということを表すため、BlockPtrPrepare を呼び出して、新たに STRBUF を大量確保するのである。

また、STRBUF が解放されたときは、それを初期化し、freelist に連結する。そのた め、解放済み STRBUF が SLOT の中に無駄に残る、ということはなく、使い回せる。

ちなみに、extptr に次の要素の STRBUF * を、freelist に先頭へのポインタを、と いう直感的な方法になっていないのは、初期化時の利便性を取った実装と思われる。 それぞれの STRBUF について、freelist の値を extptr に格納して、freelist を、 その STRBUF へのポインタに書き換える、というループにすれば、特に一時変数は いらないし、freelistも、ループ終了時には末尾の要素を指すため、ループ外でする 処理もない。

  • freelist の最初の値は nullptr なので、先頭の STRBUF の extptr は nullptr となる。

関数

sb***() 関数が公開関数。

sbInit

strbuf モジュールの初期化。

sbBye

strbuf モジュールの後始末。 確保したメモリをすべて解放する。 sbInit 1回につき、1回しか呼んではいけない。

sbAlloc( size )

大きさが size バイトのメモリを確保する。ただし、最低 64B は確保する。 内部関数 BlockAlloc を呼ぶ。 戻り値は strbuf-ptr。

  • malloc 的な関数。

sbAllocClear( size )

sbAlloc と同様。確保したバッファは 0 でクリアされる。

sbExpand( strbuf-ptr, size )

メモリの拡張を行う。内部関数 BlockRealloc を呼ぶ。 size は新たに確保するバッファの、総サイズ。拡張したい分のサイズ、ではない。 size が既に確保されているサイズ以下の場合、何もしない。 戻り値は strbuf-ptr。

  • realloc 的な関数。

sbFree( strbuf-ptr )

メモリの解放を行う。内部関数 BlockFree を呼ぶ。

  • STRINF::ptr が引数の strbuf-ptr と同一かどうかを確認して、strbuf-ptr でないポインタは解放しないようにしているようだ。

  • nullptr を渡してはいけない。

  • free 的な関数。

sbAdd( &strbuf-ptr, sptr, size )

sbStrAdd( &strbuf-ptr, sptr )

文字列バッファに文字列 sptr を連結する。sbStrAdd では、長さは strlen で 取得する。 strbuf-ptr をポインタ渡しするのは、自動拡張によって strbuf-ptr が変わるかも しれないため。

sbCopy( &strbuf-ptr, sptr, size )

sbStrCopy( &strbuf-ptr, sptr )

文字列バッファに文字列 sptr を複写する。

sbGetOption( strbuf-ptr )

文字列バッファのユーザ定義データへのポインタを得る。 戻り値は STRBUF::inf.opt という自由に使用されるポインタ。

sbSetOption( strbuf-ptr, option )

文字列バッファのユーザ定義データにポインタを設定する。

  • STRBUF::inf.opt の値を option とする。内容はなんでもいい。 このポインタの取得には sbGetOption を使う。

CLabel クラス (コンパイラ)

概要

CLabel は、hspcmp で、識別子のリストを管理するクラス。内部で1つ作られる。

label.h に宣言が、label.cpp に実装がある。

識別子番号

すべての識別子には識別番号(ID)が振られている。これは、識別子を登録する Regist の戻り値として得られるが、Search で後から検索することもできる。

mem_lab の添字と同値である。

識別子データの構造体 LABOBJ

LABOBJ は、識別子1つのデータを保持する構造体。 CLabel の mem_lab メンバが、これへのポインタの配列となっている。

  • LABOBJ
      • flag
      • 通常は 1、undef されると -1 になる
      • type
      • 識別子の種類を表す整数値
      • preprocing : LAB_TYPE_* 定数の値
      • compiling : TYPE_* 定数の値
      • opt
      • 識別子に対応付けられた整数値
      • preprocing : 識別子の種類によって異なる
      • compiling : code値(val)が格納される
      • eternal
      • 永続スコープか否か ( global 指定のマクロなどが真値をとる )
      • ref
      • 参照される回数
      • 参照されない識別子を最適化で削除するためと思われる。
      • hash
      • 識別子を表現する文字列のハッシュ値
      • name
      • 識別子を表現する文字列
      • *data
      • 識別子に対応づけられたC形式文字列
      • *data2
      • 識別子に対応づけられたバイナリデータ (バイト列)
      • *rel
      • struct LABREL へのポインタ
      • 依存ID
        • *link
      • 連結リストの次の要素へのポインタ
        • rel_id
      • related id

type, opt はプリプロセス中とコンパイル中で値の意味が異なる。

  • rel:
    • 未使用の #func や モジュールを削除するための連結リスト

識別子タイプ

プリプロセス中に登録される識別子の type 値は、LAB_TYPE_* である。

LAB_TYPE_PPVAL

  • #enum, #constで定義される定数。
  • 値は、int なら opt に、double なら data2 に格納される。

LAB_TYPE_PPMAC

  • #defineで定義されるマクロ。
  • opt: 引数の数と ctype フラグ (PRM_MASK, PRM_FLAG_CTYPE)
  • data: 置換後の文字列
  • data2: デフォルト引数データ(MACDEF)

LAB_TYPE_PPMODFUNC

  • #deffunc, #defcfuncで定義される命令・関数。
  • #modfunc, #modcfunc, #modinit, #modterm もこれに該当する。
  • opt, data, data2: 未使用

LAB_TYPE_PPDLLFUNC

  • #func, #module などで定義される識別子、モジュールのメンバ変数の識別子。
  • 一緒になっているのは、プリプロセス中にこれらを区別する必要がないからか。

LAB_TYPE_PPINTMAC

  • 内部マクロ。#cmdで定義される識別子や標準命令に使われる。
  • 例: mesmes@hsp
  • LAB_TYPE_PPMACとの違いは、プリプロセス行のときはマクロ展開しないという点。

LAB_TYPE_PPEX_*

  • hsc3_getsym用 (エディタの色分けに使われる)。

LAB_TYPE_COMVAR

  • #usecomで定義されるインターフェース名。

LAB_TYPE_PP_PREMODFUNC

  • プロトタイプ宣言されたユーザー定義関数
  • プロトタイプ宣言は未実装なので、使われることはない。

識別子バッファ(symbol buffer)

LABOBJ の name や data、data2 が指すバッファは、CLabel 側が確保しており、 そこに必要なデータが複写されている。

このバッファを symbol buffer と呼び、メンバ symblock にそこへのポインタの 配列がある。0 から順番に使われていき、現在使われているモノは symbol で参照 でき、使われている個数は curblock にある。その symbol の中で使われている サイズは symcur で参照できる。

すべての symbol buffer のサイズは一律で、メンバ maxsymbol にある。

依存リスト(rel)

  • LABOBJ::rel は、未使用の #func やモジュールを削除するために使われる。

モジュール名の識別子から、そのモジュール内の全部のモジュールコマンドを、 連結リストで結んでいる。

プリプロセス中、ユーザ定義命令・関数の定義がある場合は、それを、定義がある モジュール名の識別子の rel に連結する。

また、modcmd が定義以外の場面で登場する場合、則ち呼び出しがある場合、参照 カウンタ ref を増加させる。

コンパイル後、モジュールの識別子にある rel を走査し、すべての modcmd の参照 カウンタが 0 の場合に、そのモジュールを削除する。

なお、モジュール内で #func の関数を呼んだときは、呼び出し回数を増やすのでは なく、連結リストにモジュールのラベルを追加する。これは、未使用モジュールだけ から呼ばれた #func 関数も削除されるように、ということだと推測される。

公開メンバ関数

Regist

識別子を登録する。その識別子に対応するIDを返す。

RegistList

識別子を、識別子リストから一括して登録する。

  • 識別子リストとは、hspcmd.cpp にあるようなもので、"$code type ident" の形式。
    • codeに16進数でcode値が、typeに10進数でtype値が、identには識別子が入る。

RegistList2

識別子を、識別子リストから、ident@module 形式ではなく ident だけでいいように なる内部マクロとして、一括して登録する。

  • 例: mes@hspmes で登録する、などに使われる。

Search, SearchLocal

識別子から、それに対応するIDを検索する。存在しない場合は -1 を返す。

GetOpt, SetOpt

GetData, SetData

GetData2, SetData2

識別子に対応付けられたデータをそれぞれ取得(Get)、設定(Set)する関数。 第一引数に id をとる。

AddReference

識別子の参照回数を増やす、つまり参照カウンタを1増加させる。

  • 減らす方の関数はない。

GetReference

識別子の参照回数を取得する。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment