Home -> HSP講座 -> HPI編 No.04

関数を作ろう

スクリプトには、命令コマンドとパラメータ(式)の2つの部分があります。
命令として現れるコマンドには cmdfunc が呼ばれるのは前回までに何度もみました。
一方、パラメータ(式)の中で現れるコマンドに対しては reffunc という関数が使用されます。
reffunc は、関数とシステム変数のキーワードを処理する関数です。
その扱いは cmdfunc と非常に似ていますが、微妙に違う点もあるので、解説します。

今回のソースコードはここにあります:HSP3向けプラグイン「varinfo」リポジトリ


varinfo()関数

GetVarinfo命令では、いちいち配列変数を使うので、結構面倒でした。
今回作る関数 varinfo() は、vartype() などのように関数形式です。
式の中で使用できる方が、使いやすくて良いですよね。


reffuncの書式

今までの cmdfunc() 関数は、簡単に言うと code_next() と switch だけでした。
最後に RUNMODE_RUN を返していますが、これは気にしなくてもいいです。

reffunc() 関数では、引数を囲む丸括弧 ( ) の処理が必要なので、少々長めになります。

※今回はシステム変数は使いませんが、参考のためシステム変数を扱う部分をコメントで表示しています。

引数の cmd は、cmdfunc() のときと同じように、コマンドの code 値です。この値を元に、switch で処理を分岐します。
switch に相当する処理は、ProcFunc, ProcSysvar という別の関数に分けることにしましょう。そのほうがみやすいです。
もう一つの引数 type_res は、返値の型タイプ値をもつ変数へのポインタです (これはHSP側が用意しています)。これに間接参照して、返値の型を設定しておきます。( 設定しなければエラー )
ProcFunc() や ProcSysvar() の返値を、*type_res = ...(); で受け取っていますね。

reffunc は関数やシステム変数を処理した結果の「値」を返すので、「型タイプ値」と「ポインタ」という2つのデータを返却する必要があります。
そのため関数の返値としてポインタを、type_res が指す先の領域に型を書き込む、という手段を用いているわけです。
しかし ProcFunc, ProcSysvar では逆にしています。返値として「型タイプ値」を返し、ppResult という引数が指す領域に「ポインタ」を書き込む、というように。
これはたぶん、こっちのほうがコードが書きやすいと思うためです。僕の好みかもしれませんけれど。

if文の条件式で、typeval というグローバル変数に間接参照しています。(グローバル変数にあるまじき名前だというのは我慢しましょう……。)
type, val はそれぞれ、「次にあるトークン」の type, code 値へのポインタです。
トークンというのは、変数やコマンドの名前、数値リテラル、1つの記号など、スクリプトの中の単語のようなものです。

変数「type」が指す type 値はプラグインの番号だと以前の回にいいましたが、もう少し詳しくいいましょう。
その値は TYPE_... という定数の値か、それ以上です。

// command type
#define TYPE_MARK    0		// 記号(code=文字コード)
#define TYPE_VAR     1		// ユーザー定義変数(code=変数ID)
#define TYPE_STRING  2		// 文字列(code=DSオフセット)
#define TYPE_DNUM    3		// 実数値(code=DSオフセット)
#define TYPE_INUM    4		// 整数値(code=値)
#define TYPE_STRUCT  5		// モジュール変数・構造体(code=minfoID)
#define TYPE_XLABEL  6		// 未使用
#define TYPE_LABEL   7		// ラベル名(code=OTオフセット)
#define TYPE_INTCMD  8		// HSP内部(コア)命令(code=コマンドID)
#define TYPE_EXTCMD  9		// HSP拡張(機種依存)命令(code=コマンドID)
#define TYPE_EXTSYSVAR 10	// HSP拡張(機種依存)システム変数(code=コマンドID)
#define TYPE_CMPCMD  11		// 比較命令(code=コマンドID)
#define TYPE_MODCMD  12		// ユーザー拡張命令・関数(code=コマンドID)
#define TYPE_INTFUNC 13		// HSP内部(コア)関数(code=コマンドID)
#define TYPE_SYSVAR  14		// HSP内部(コア)システム変数(code=コマンドID)
#define TYPE_PROGCMD 15		// プログラム制御命令(code=コマンドID)
#define TYPE_DLLFUNC 16		// 外部DLL拡張命令・関数(code=コマンドID)
#define TYPE_DLLCTRL 17		// 拡張DLLコントロールコマンド(code=コマンドID)
#define TYPE_USERDEF 18		// HSP3拡張プラグインコマンド(code=コマンドID)

#define TYPE_ERROR     (-1)	//
#define TYPE_CALCERROR (-2)	//

引用元はhsp3struct.h。TYPE_XLABEL はコンパイル中にのみ使用されるので、プラグイン側が読み取ることは絶対にありません。

TYPE_MARK から TYPE_LABEL までは、スクリプトに書かれた定数のタイプです。
例えば、スクリプトに「123」と書けば、その部分は TYPE_INUM になります。その code 値は 123。
TYPE_INTCMD 以降は、標準のコマンドです。TYPE_INTCMD に属するコマンドが書いてある場合は、type が TYPE_INTCMD になり、val がそれの code 値になります。
code_**() 系の命令を使うと、HSPがスクリプトの続きを読み、これが更新されます。
※標準命令のコマンドは定義済みです。TYPE_INTCMD 以降はすべて組み込みのキーワード。

if文の条件式では、キーワードの次が記号で、左括弧 '(' かどうかを調べます。
関数なら「 f ( ... ) 」という形式なので、次に '(' が配置されているはずです。
次が '(' ではない場合は、キーワード単体で書かれているということなので、システム変数として扱います。
※システム変数は、( ) や引数のない関数、とみることができます。

次に、関数だった場合の処理です。コマンドが関数形式で書かれている場合、ProcFunc() に cmd を渡して呼び出し、返値の型とポインタを type_res, ppResult で受け取っています。
ProcFunc() の内部は、こんな感じの構成になります。

※ProcSysvar も同じ構成

cmdfunc() から switch の部分だけ抜き出した感じですね。
varinfo_f() 関数は、HSPの varinfo() を処理するための関数です。( 内容は後述 )
pResult にはそっちで書き込みます。
もし関数形式で使用できないキーワードだったら、HSPERR_UNSUPPORTED_FUNCTION エラーが起きます (「サポートされていない機能 (= 関数としての機能) を呼び出した」)。puterror 関数を呼び出すと、HSPがエラー終了するので、それ以降のコードが実行されることはありません。しかしコンパイラはそれを知らないので、「値が返らないコードパスがある」と警告してきます。開発環境の設定で警告を消すのがいいですが、ここでは簡単にダミーの throw を書いておきました。

最後に、引数を取り出し終わった後、右括弧 ')' がくることを確かめます。
括弧の対応がうまくいっていることはコンパイラが(実行前に)確認済みなのですが。
たとえば引数が 3 つ書かれているのに関数が2つしか受け取らなかった場合などは、次が ')' にならず、エラーが起きます。 そのため「関数のパラメータ記述が無効です」ではなく「引数の数が多すぎます」というエラーを出したいのですが、プラグイン側ではエラーを作成できないんですよね。


varinfoの実体を作る

それでは、varinfo_f() 関数を作りましょう。先ほど ProcFunc() の中から呼び出されたやつです。

前回の GetVarinfo 命令も使用できるように、varinfo_st() という関数を作りました。
これの中身は前回の GetVarinfo の処理と同じものです。
上記 cmd.h は dllmain.cpp で include しておきます。

varinfo_f() は、varinfo() 関数を実際に処理する関数です。
返値に型タイプ値の int を、引数に pResult へのポインタを指定されます。
実体は以下の通り。

varinfo 関数を処理する関数

難しいところはありませんね。前回とほぼ同じです。
静的変数 result に返値データを格納し、それへのポインタを渡しています。
返値の型は vtResult に設定しますが、現在は int だけなので、初期値のまま変更していません (つまり変数である必要がない)。


ヘッダファイルを作る

上記のソースと第一回のときのソースを参考にコンパイルして、プラグインを作成できます。
恒例の .as ファイルは次のようになります。

// varinfo - public header

#ifndef IG_VARINFO_HPI_AS
#define IG_VARINFO_HPI_AS

#regcmd "_hsp3hpi_init@4", "varinfo.hpi"
#cmd varinfo 0x000

// 定数
#enum global VARINFO_LEN0 = 0
#enum global VARINFO_LEN1
#enum global VARINFO_LEN2
#enum global VARINFO_LEN3
#enum global VARINFO_LEN4
#enum global VARINFO_FLAG
#enum global VARINFO_MODE
#enum global VARINFO_PTR
#enum global VARINFO_MAX

//######## サンプル・スクリプト ########
#if 1
	// 本当はもっと入念にテストしてください (>_<;
	
	a = 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024
	varinfo a(1), info
	
	mes "len1 : " +   length(a)   +"\t: " + info(VARINFO_LEN1) + "\t: " + varinfo(a(1), VARINFO_LEN1)
	mes "len2 : " +  length2(a)   +"\t: " + info(VARINFO_LEN2) + "\t: " + varinfo(a(1), VARINFO_LEN2)
	mes "len3 : " +  length3(a)   +"\t: " + info(VARINFO_LEN3) + "\t: " + varinfo(a(1), VARINFO_LEN3)
	mes "len4 : " +  length4(a)   +"\t: " + info(VARINFO_LEN4) + "\t: " + varinfo(a(1), VARINFO_LEN4)
	mes "ptr  : " +  varptr(a(1)) +"\t: " + info(VARINFO_PTR ) + "\t: " + varinfo(a(1), VARINFO_PTR )
	mes "flag : " + vartype(a(1)) +"\t: " + info(VARINFO_FLAG) + "\t: " + varinfo(a(1), VARINFO_FLAG)
	mes "mode : " + info(VARINFO_MODE) +"\t: "+ varinfo(a(1), VARINFO_MODE)
	
	stop
	
#endif

BCCの人は _hsp3hpi_init@4 を hsp3hpi_init に変えてください。

varinfoが命令形式でも関数形式でも使えます!!!
これはすごい!!
まさに多重定義!! (違)


おわりに

以上、reffunc の処理方法でした。
方法は一つではありません。これは僕なりの方法ですし、公式のサンプルでは別の方法を使っています。
もっといい方法を思いついたら、教えてくれるとうれしいです。

では、また次回。


by 上大

第三章へ   第五章へ