命令・関数の作成
HSPにはいろいろな処理を実行する命令や、主に結果を受け取る関数が存在します。
文字や図形を描く命令、サイズを取得する関数等々…。
今回は、この命令や関数のオリジナル版を作ってみると言うものです。
作るといっても、現状の機能で満足してるし、第一、作るのは難しそうだと思うかもしれません。
しかし、ココで言う命令とは、完全な新機能を一から作るというものではなく、
複数の命令や関数を組み合わせた、いわゆるサブルーチンを1つの機能単位として、
ソレをHSPの命令・関数のように呼び出せば利用できるようになるという程度です。
もし、サブルーチンなんて使わない。
サブルーチンのメリットが一切感じられないって方は、読むのが尚早かもしれません。
gosub命令によるサブルーチンを使ってはいるけど、
今回のユーザー定義による新命令・関数は、このgosub命令によるサブルーチンとどう違うのか?
それは、ルーチン内で使用するために渡す数値または文字列データを一時変数に入れて渡さなくても
HSPの数ある命令のようにパラメータ形式で渡すことが可能になり、
HSP3から新登場した関数形式「結果 = 関数(パラメータ)」にも出来るから、
ルーチン結果をそのまま別の変数や別の命令・関数に渡すことも可能になります。
まずは1つ目、命令の定義「DEFineFUNCtion」の略であろう#deffunc命令から説明します。
#deffunc 命令名, パラメータタイプ エイリアス, …
命令名任意のユーザー定義名を指定する。
パラメータタイプ省略可能。呼び出す際にパラメータを付ける場合、そのタイプ(型)を指定する。
エイリアスパラメータ型を指定した場合に必要な別名。
gosub命令の「ジャンプ先ラベル名にあたる部分」を第1パラメータに指定します。 第2パラメータ以降は、呼出時に数値や文字列データを同時に送りたい場合に指定するもので、 送る必要がない場合は当然何も指定する必要はありません。 逆に指定する場合は、呼出元側でも省略はせずに指定する必要があります。(ただし、数値の場合は省略可) パラメータタイプと言うのは、主に変数で使用する型のことであり、現在は上6つのいずれかを指定できます。
タイプ意味
int4バイト整数値(−2,147,483,648〜2,147,483,647)
str文字列
double8バイト実数値
var変数(数値型・文字列型を問わない)
array配列変数(数値型・文字列型を問わない)
localモジュール内実行時のみ生きてい一時変数(グローバルに戻ると消滅)
labelラベル(HSP3.1はパラメータタイプに使用できない)
尚、第2パラメータは型と共に、#define同様「エイリアス(別名)」までが1つのパラメータです。 intstrdoubleというワードは、既に前の章で紹介した関数として存在しているわけですが、 例えばintの場合、「#deffunc newfunc int alias」とパラメータに書くことで、 呼出元で渡した数値をモジュール内ではaliasと言う名で参照することが可能となります。 コレはパラメータがintstrの場合、呼出元が変数ではなく100"Hello"等の定数であったとしても、 モジュール側では付けたエイリアスで受け取って「mes alias」のように使うことが可能ということです。 HSP2では、受け取ったエイリアスは完全に変数として値を書き換えることも出来ましたが、 HSP3からは、厳密には変数のようなものですが、あくまで定数として、 モジュール内で受け取るのみ、書き換えは出来ないものとなっています。 例え変数で渡したとしても、変数の中身(定数)だけが渡されますので、書き換えられません。 モジュール内で書き換えたい場合は、varタイプかarrayタイプを指定しましょう。 varタイプは、変数または、配列変数の1要素を渡すだけに対して、 arrayタイプは、変数または配列変数の先頭(0番目)の要素を渡します。
varタイプ

 1
 2
 3
 4
 5
 6
 7
 8
 9

	goto *main

#deffunc xxx var v
	mes v // 受け取った「abc.2」を表示
	return

*main
	abc = 11, 333, 5555, 77777, 999999
	xxx abc.2
arrayタイプ

 1
 2
 3
 4
 5
 6
 7
 8
 9

	goto *main

#deffunc xxx array v
	mes v // 受け取った「abc.0」を表示
	return

*main
	abc = 11, 333, 5555, 77777, 999999
	xxx abc.2
つまり、varタイプの場合、元の変数が配列で複数要素を持っていても、 モジュール内では要素数1の変数となるので、他の要素にはアクセスすることが出来ないということです。 さて、このパラメータのタイプとエイリアスのセットですが、HSP2では最大8個までしか設定できず、 さらに始めの2つ(第2&第3パラメータ)以外は数値(int)パラメータしか設定できませんでした。 HSP3からは、上限数が撤廃(試してませんが普通に使用する限りは事足りることでしょう)され、 全てのタイプのパラメータ部に上記6種を指定ができるようになりました。 ヘルプには6種ではなく、HSP3.1から新しく追加されたlabelタイプを含めた7種が書かれていますが、 label型変数と言うのは使えるようにはなったものの、#deffunc命令では指定することができません。 goto命令等のジャンプ先ラベルを、以前までのラベル定数による指定以外に、 変数に代入されたラベルワードをジャンプ先ラベルとして指定できるようになりました。 例えば「tmp = *lbl : goto tmp」と書けば、変数tmpに入った*lblラベルへジャンプできるというものです。 現段階で、ラベルを格納した変数を#deffunc命令のパラメータとして渡す場合は、varタイプを使用します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	goto *main

#deffunc jump var lbl
	gosub lbl
	return

*main
	la = *l1, *l2, *l3
	repeat length(la), 1
		jump la(length(la) - cnt) // ラベル型配列の逆順に取り出して移動
	loop
	stop

*l1
	mes "11ラベルです"
	return

*l2
	mes "12ラベルです"
	return

*l3
	mes "13ラベルです"
	return
localタイプを実際に使用しているサンプルは関数側で使用しておりますが簡単に説明すると、 ココで定義したローカル変数は、再帰処理(自分で自分を複数回繰り返し呼び出す)や、 開始時にいちいち0で初期化するのが面倒な時、 使い終わった変数をreturnのタイミングで解放されても問題ないときにはなかなか使えるタイプです。
命令実行の結果かをstatに入れて受け取るサンプルも書いておきましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	value = 10, 8, 5, 6, 2
	op = "+", "-"
	goto *main

#deffunc operation int left, int right, int operator
	if operator : return left - right : else : return left + right

*main
	repeat (length(value) - 1) * 2
		operation value(cnt / 2), value(cnt / 2 + 1), cnt \ 2
		mes str(value(cnt / 2)) + op(cnt \ 2) + value(cnt / 2 + 1) + "=" + stat
	loop
上記は、パラメータ2つを加算して、return命令で合計値をstatに入れるというものなのですが、 命令実行の結果が成功か失敗かを受け取るという場合ならまだしも、 今回のような、内部で処理した結果値をエラートラップ以外の別の処理に使用したい時は、 次に説明するHSP3から使えるようになった関数のユーザー定義の方がスマートです。
HSP3から追加された関数の定義「DEFineCtypeFUNCtion」の略であろう#defcfunc命令を説明します。
受取先 = #defcfunc(関数名, パラメータタイプ エイリアス, …)
受取先関数結果の受取先を指定する。
関数名任意のユーザー定義名を指定する。
パラメータタイプ省略可能。呼び出す際にパラメータを付ける場合、そのタイプ(型)を指定する。
エイリアスパラメータ型を指定した場合に必要な別名。
パラメータの設定方法については#deffunc命令とほぼ同じです。 第1パラメータにgosub命令の「ジャンプ先ラベル名にあたる部分」を指定し、 第2パラメータ以降は受け取る型と、その呼び名を指定できます。 指定できるタイプも#deffunc命令同様にHSP3.1現在はlabelタイプを除くコチラ6種です。 #deffunc命令と異なるのは受け取る部分だけですね。 結果値の送り方は#deffuncの時に説明したreturn命令に設定する方法、 受け取り方は、既に何回も説明したように他の関数と同じ形式「結果 = 関数(パラメータ)」でOKです。 strmid関数はHSP2の命令から関数に生まれ変わりましたが、getstr命令は関数ではなく命令のままです。 今回は、文字列を結果として受け取るこのgetstr命令の関数版を定義してみましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
	goto *main

#defcfunc _getstr var v, int index, int code, local tmp
	getstr tmp, v, index, code
	return tmp

*main
	string = "Mojiretsu Nukidasi Test"
	mes "[" + string + "]"
	mes " ↓"
	mes "[" + _getstr(string, , ' ') + "]"
さて、今回のサンプルスクリプトはいずれも
「初めにmainラベルにジャンプして、すぐ下に定義した命令・関数を実行させない」作りになっていました。
gosub命令によるサブルーチンの場合、ジャンプ先をmainラベル部よりも下に書くことで、
「mainラベルにジャンプしなければならない」という面倒な処理を入れる必要はないのですが、
#deffunc命令、#defcfunc関数は残念ながらそういうわけには行きません。
スクリプトの下部にあっても正常に動作するラベルと違いコンパイル前に処理されるプリプロセッサは、
実際に呼び出している呼出部よりも前に定義されていなければならないのです。
そのため、エラーを出さないためだけにスクリプト前半には書くけど、
通常の上から下へ流れてくる時には実行されないようなスキップする処理を入れているのです。
この章はココまでで終わりますが、
次章では、スクリプトの前半に記述して、スキップする処理を入れずとも勝手にスキップされる方法、
そして、新命令・関数内で一時的に使用している変数に目的の形ではない値が入っていることで
予期せぬ問題が生じてしまう場合の対策について説明したいと思います。