1. トップ
  2. HSP講座
  3. プリプロセッサ命令編
  4. >ここ [
    1. ←前
    2. |
    3. 次→
    ]

ユーザ定義命令

今回は、非常に便利な「ユーザ定義命令」についてです。

  1. 銀の弓矢の背に乗って
  2. ピリオド、ではなくカンマ
  3. あれはあそこにあるよ
  4. 関数形式 〜 c 〜
  5. 最終実行部隊、オンイグ・ジット、参★上!
  6. おわりに
	goto *main
	
#deffunc HelloWorld
	mes "Hello, world!"
	return
	
*main
	mes "HelloWorld 命令を呼び出します。"
	HelloWorld		// こんな命令ないだろ!
	stop

上大はもしかしたら馬鹿なのかもしれません。
HSPに HelloWorld 命令なんてありませんよねー。あるわけありませんよねー。
だから、こんなスクリプト動くわけがないですよねー。

って動いたぁ!?

とまぁ小芝居はいいとして(爆)、ちょっと説明を。
HSPには、HSP自身を拡張するとも言うべき機能が付いています。それが、ユーザ定義命令です。
その名の通り、ユーザ(=使用者)が定義(=作成)する命令な訳です。
作成するには #deffunc 命令を使用します。
これによって定義された命令は、#deffunc より下で、普通の命令と同じように使うことができます。
いや、便利……なのかな。

なぜ #deffunc を上に書いているのか?
#deffuncなどが定義したユーザ定義なルーチン(一連の処理を行う小さなプログラム)は、
定義より下でないと呼び出すことができません。
ただし、HSP3.2β4 以降では、この制限がなくなっています。やったね☆

ユーザ定義命令を呼び出すと、#deffunc の次の行に移動します。このジャンプは、gosubと同じ、サブルーチン・ジャンプなので、return命令で、呼び出し元に戻ることができます。
上の例でいうと、HelloWorld は、画面に "Hello, world!" と表示する命令です。しょぼいですね。

なお、#deffunc というのは *ラベル などと同じように、スクリプトにつけた印でしかないので、上から普通に実行されてしまいます。しかしこれはいろいろな理由でヤバいので、goto などで飛ばし、呼び出されたとき以外には実行されないようにしなければいけません。


銀の弓矢の背に乗って

#deffunc で定義できるユーザ定義命令。パラメータがないと、命令っぽくなくて、どことなくしょぼい。

	goto *main
	
#deffunc Message str string
	mes string
	return
	
*main
	mes "Message 命令を呼び出します。"
	Message "Hello, world!"
	stop

さっきの HelloWorld 命令では、"Hello, world!" という文字列しか表示できない、全くもって使い道の分からない命令でしたが、今回の Message 命令は mes 命令と同じく、第一パラメータに、画面に表示したい文字列を渡すことができます。
これで、汎用的な命令になりました。
まぁ、車輪の再発明とかなんとかごにょごにょ。

引数付きの命令を定義するには、#deffunc で、命令の名前の後に「種類 エイリアス名」という順番に、識別子(=名前)を書きます。

「種類」には受け取る引数の種類を書きます。型の名前と同じ str, double, int と、変数を受け取る var 、配列を受け取る array を使うことができます。

エイリアス(alias)というのは「別名」という意味で、「エイリアス名」には、与えられた引数に付ける名前を指定します。スクリプトの中で引数を使うときには、この名前で参照します。
命令を定義する時点では、何が引数に与えられるか分からないので、それにとりあえず名前をつけて扱おう、ということ。

str 引数には、純粋に文字列しか渡すことができません。そのため、「Message 256」とint型の値を渡しても、エラーが発生します。これはメリットともデメリットとも言えますが……。
解決方法は後述。


ピリオド、ではなくカンマ

上で引数付き命令の定義の仕方が分かりましたが、パラメータが2つ以上ある場合はどう書けばいいのかわかりません。
簡単なんですけどね。2つ目以降は、カンマ , で区切ります。

	goto *main

#deffunc Message str string, int pos_x, int pos_y
	pos pos_x, pos_y
	mes string
	return
	
*main
	mes "Message 命令を呼び出します。"
	Message "(40, 40) Hello, world!", 40, 40
	stop

今回の Message は、ただの mes の下位互換じゃなくて、表示する位置まで指定できるようになりました。
……便利とは言い難いですけれど。


あれはあそこにあるよ

命令にパラメータを渡す方法は、一般的に、二種類あります。
1つ目が値渡し(call by value)で、引数に指定した値と等しい値を、命令に渡します。……まぁ普通のことですね。
2つ目は参照渡し(call by reference)といい、1つ目とは違います。これは、指定したを渡すのではなく、指定した変数自体を渡します (これは、参照渡しの中でも特に変数渡しと呼ばれるものです; HSPにはこれしかありません)。

	goto *main

#deffunc StrMulti var result, str sSrc, int times
	result = ""
	
	if ( times <= 0 ) { return }	// 0回反復の場合は完了、負数回の反復は失敗
	
	repeat times
		result += sSrc
	loop
	return
	
*main
	mes "StrMulti 命令を使って、文字列を反復します。"
	StrMulti sRet, " + X", 10
	mes "X * 10 = 0"+ sRet
	stop

引数 result は参照渡しになっているため、与えられた変数と全く同じものです。この命令は、与えられた変数を直接操作しているのです。
HSPで参照渡しを行うには、「var」または「array」を引数の種類に指定します。
ところで、var と array の違いは、意外とややこしいことになっています。
分かりやすいのは array で、これは変数をまるごと1つ渡す引数です。皆さんがイメージしている通り動くと思います。
一方、var というのは、変数を渡すのですが、特に「配列要素」を渡します。つまり、v(0) とか v(1, 1) とかの、添字まで含めたものを、引数に渡しているのです。例えば:

	goto *main

#deffunc assign var x, int n
	x = n
	return
	
*main
	dim arr, 10		// 配列変数
	repeat 10
		assign arr(cnt), cnt
	loop
	
	stop

こうした場合、引数 x は arr(cnt) と同一のものになります。ただの arr ではないことに注意してください。
……ということは、配列変数じゃなかったら使えないのか、というと、そうでもありません。

	assign m, 13		// 配列じゃない変数 m を var に渡す

これは可能です。m に 13 が代入されます。
しかし何故、m は配列変数じゃないのに、配列の要素を渡せるのか?
実は、これも「配列要素」を渡しています。そもそもHSPの変数はすべて配列変数で、m は m(0) と同じものとして扱われているので、単に m と書いても配列要素を渡していることになるのです。

また、var 引数のエイリアスは配列ではないので、添字をつけることができません。エラー(12: 配列・関数として使用できない型です)になります。

なお、参照渡しをより正確に説明すると、これは「値(value)ではなく値の位置(pointer)を与える」引数の渡し方なのです。HSPに「値の位置」という概念は直接は存在しませんが、“値の入れ物”たる変数は値への位置を持つ存在なので、変数を与える行為が、HSPにおける参照渡しとなります。


関数形式 〜 c 〜

HSP3.0 で導入された「関数」も、ユーザ定義することが可能です。
#defcfunc を使います。

	goto *main
	
#defcfunc HelloWorld
	return "Hello, world!"
	
*main
	mes {"
		多言語に対応できるように、文字列を関数から受け取ります。この関数は、
		Hello, world の文字列を、外部のファイルから読み込んでくれるんだそうです。
	"}
	mes "「"+ HelloWorld() +"」"
	mes "\n……ってこの大嘘つき関数め!"
	stop

大丈夫ですか、「{" 〜 "}」の書き方と、関数について。
忘れていたら復讐しましょう。………… 説明がヘタな上大に ([悲鳴])。

#defcfunc#deffunc とほぼ同じです。関数の戻り値は return の引数にする、ということくらいでしょうか。

	goto *main
	
#defcfunc GetNiceMessage int number
	switch ( number )
		case 0: return "Zero was invented in India."
		case 1: return "One for all, all for one."
		case 2: return "One stone can strike down two birds."
		case 3: return "The whisper becomes super if three people gather."
		case 4: return "In time multiply six by four."
		default:
			return ""
	swend
	
*main
	mes "GetMessage 関数から文字列を貰う。\n"
	repeat 6
		mes "#"+ cnt +": "+ GetNiceMessage(cnt)
	loop
	stop

※引数付きバージョン。名言というかなんというか……。英語力が絶望的……。

	goto *main

#defcfunc StrMul str sSrc, int times
	result = ""
	
	if ( times <= 0 ) { return }	// 0回反復の場合は完了、負数回の反復は失敗
	
	repeat times
		result += sSrc				// 遅い、こんな方法はダメ
	loop
	return result
	
*main
	mes "StrMul 命令を使って、文字列を反復します。"
	mes "X * 10 = 0"+ StrMul(" + X", 10)
	stop

※複数の引数つきバージョン。文字列反復は関数の方が自然ですね。

参照渡しも命令と同様です。


最終実行部隊、オンイグ・ジット、参★上!

というわけで、ユーザ定義命令最後は、onexit です。
これは、スクリプトの実行が終了するときに呼び出される命令を作ります。

	goto *main

#deffunc FinallyGreet onexit
	mes "さよ〜なら〜〜。"
	return
	
*main
	// 処理開始
	numberLeftArgument = -2
	numberLeftArgument ++
	numberLeftArgument = abs(numberLeftArgument)
	
	numberRightArgument = 99
	numberRightArgument /= numberRightArgument
	numberRightArgument ++
	
	mes ""+ numberLeftArgument +" + "+ numberRightArgument +" = "+ ( numberLeftArgument + numberRightArgument )
	
	// 完了を示す
	mes "高度な処理が完了しました。"
	stop

※ちなみに、Argument = 実引数、Greet = 挨拶する。

変数名があまりに冗長だと見づらいという絶好のサンプルです (おい)。

ウィンドウが表示された後は何もしませんが、終了させると一瞬「さよ〜なら〜〜。」という文字列が画面に表示されたのが分かりましたか? (わかりにくいかもしれません)

ちなみに、onexit の命令( クリーンアップ命令 )は、普通に呼び出すこもできますが、引数は設定できません。
また、終了時に実行されるときは、dialog などの実行を止める命令が無視されます。注意してください。


おわりに

実は今回は二本立てです。次のモジュール編に続きます!