サブルーチン
難しそうな名前です…。
自分も初めてプログラム(小学1〜2年位に初めて触ったMSX2)に取り組んだ時、
この言葉に悩まされた記憶があります。
書いてある説明を読んでも良く理解できない…というか読んだほうが分からなくなる。
自分の苦い経験を思い出したので、全く初めて取り組む人が同じ様な道を歩まぬよう
この章はルーチンの説明から入り、サブルーチン命令の説明で終了します。
さて、プログラムの流れと言うのは今までで既に説明したように、
(手続型の場合は)基本的に書かれたプログラムの先頭から順に実行されますが、
常に一方通行と言うわけではなく、コチラで紹介したように任意の位置へ処理を移すことが出来ます。
処理を順番ではなく、あちこちに飛ばせるは大変便利ですが、
無駄に多用しすぎると、問題が発生した時や、バージョンアップしようとプログラムを見た時に
処理の流れが分かりにくくて修正に時間が掛かり、苦労してしまいます。
…かと言って、ほとんど使わない場合、汎用性に欠けたプログラムとなってしまいます。
似たような処理があったとしても、もう1パターンを用意するか、
既存の処理にif命令等で制御して使いまわす形となります。
どちらをするにしても次回のバージョンアップ以降で、
修正のし忘れや、影響範囲の調査漏れによりプログラム欠陥の1つになりがちなので、
それぞれの処理機能毎の大きさには分けて行きたいものです。
この各機能毎の塊をルーチンと呼びます。
ルーチン内ではさらに細かい機能(処理)が幾つもあるわけですが、
ルーチンA内の機能Xを、ルーチンBでも使いたい場合があったとします。
ルーチンBから機能Xを使う場合、このルーチンAに処理を飛ばすと余計な処理も実行されてしまいます。
ルーチンAがルーチンBから呼ばれた場合はフラグにより実行しないようにする。
上記のようにしたのでは、既に述べたようなプログラムによるミスへと繋がってしまうかもしれません。
そこで、この機能XというのをルーチンA内で処理するのではなく、独立させるのです。
そうすることで、ルーチンBから機能Xを使う場合に、ルーチンAの処理を除外する必要がなくなります。
ルーチンAから使う場合も一緒です。
ある一部分だけを独立させて汎用的に使いまわせるルーチンをサブルーチンと呼んでいます。
gotoexgoto命令は、元のルーチンに戻す為にどこからジャンプされたかを知っておく必要があります。
判別する為にはフラグを用いて、呼び出し元へそれぞれジャンプさせる必要があり、面倒です。
HSPではサブルーチンジャンプを目的とした下記命令が用意されています。
gosub ジャンプ先ラベル
ジャンプ先ラベル条件に合致しているなら直後のブロックを実行する
見た目はgoto命令となんら変わりません。
パラメータに指定したラベルに処理を移すだけです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
*routine_A
	mes "This is A"
	gosub *process_X // サブルーチンジャンプ

*routine_B
	mes "This is B"
	gosub *process_X // サブルーチンジャンプ
	stop

*process_X
	mes "Process X"
実行した結果もgoto命令と全く同じであるのは、gosub命令はサブルーチン用のジャンプに過ぎず、
サブルーチンから戻る場合は別の命令を使用する必要があるのです。
ジャンプさせるだけなのであれば、goto命令などでもいいのではないか、となりますが、
仕様上、goto命令は「行ったきり」、gosub命令は「行って帰ってくる」為の命令となっています。
スタックという言葉を聞いたことがあるかもしれません。
このスタックと呼ばれる領域に「どの位置から飛んできたのか」という情報を溜め込んでおくことにより、
どこからジャンプしてこようが正しく元の位置に戻ることが出来るのですね。
元の位置に戻ってくると、このどこからジャンプしてきたのかという情報は不必要である為に破棄されますが、
元の位置に戻らずに別のサブルーチンにジャンプすれば、その情報もスタックに溜め込まれます。
一方通行でどんどん別のルーチンへジャンプを繰り返されると情報がパンクしてしまいます。
サブルーチンジャンプさせた場合は、
goto命令で戻すのではなく、下記に紹介する命令で戻すようにしてください。
return 返り値
返り値結果値としてシステム変数に値を代入する。
return命令はHSP2以前からありましたが、HSP3からパラメータが指定可能となりました。
システム変数に値がセットされますが、指定値によって、セットされるシステム変数が変化します。
整数値ならばstatに、文字文字列ならばrefstrに、実数値ならrefdvalになります。
refdvalは、実数値が使えるようになったHSP3にあわせて追加された実数用システム変数です。
HSP2同様に、パラメータを省略した場合は返り値はセットされません。
省略値である0がstatにセットされるわけではないことにご注意ください。
それでは、先ほどのスクリプトにreturn命令を入れて実行してみましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
*routine_A
	mes "This is A"
	gosub *process_X

*routine_B
	mes "This is B"
	gosub *process_X
	stop

*process_X
	mes "Process X"
	return // 呼び出し元(の次)に処理を戻す
システム変数sublevにネストレベルがセットされますが、
HSP2では32(33回ネスト)、HSP3では128(129回ネスト)になるとエラーで終了します。
下記は悪い例です、実行しても正常に実行できません
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
	mes "サブルーチンの悪い使い方"
*main
	redraw
	wait 5
	redraw 0
	color 255, 255, 255 : boxf : color
	pos 0, 0 : mes "ここは main ルーチンです。"
	gosub *subroutine
	stop // 辿りつく事がないポイント

*subroutine
	if sublev = 1 {
		mes "ここは subroutine サブルーチンです。"
	} else {
		mes "スタックが溜まっています! 現在のレベル:" + sublev
	}
	goto *main // main ルーチンへreturnを使って戻していない!
サブルーチンのジャンプ先を、与えたパラメータにより分岐させる命令があります。
サブルーチン限定ではなく、通常のgotoによるジャンプも該当するものですが、使い方は下記の通りです。
on 分岐用の値 goto/gosub ラベル1, ラベル2, ラベル3, …
分岐用の値分岐の条件とする数値、または数値型の変数を指定する。
goto/gosubジャンプ方法をgotoまたはgosubと記述する。
ラベル1ラベル2を設定している場合、分岐値が1未満の時のジャンプ先ラベルを指定する。
ラベル2ラベル3を設定している場合、分岐値が2未満の時のジャンプ先ラベルを指定する。
ラベル3ラベル4を設定している場合、分岐値が3未満の時のジャンプ先ラベルを指定する。
以降、繰り返し。
exgoto命令も条件によりジャンプ先を分岐させるものでしたが、
on命令はジャンプ先を複数切り分けることが可能です。
なので、メインルーチン内に組み込んで、制御パラメータ値をon命令の第1パラメータに渡し、
処理をスマートに分岐させるようなことが簡単に出来ますね。
ジャンプ方法は、gotoによる一方通行、gosubによるreturnが必要なジャンプ、どちらでもOKです。
ラベルは、1つ目のラベルを数値が0の時のジャンプ先として、
1の時、2の時、3の時…と、幾つでも設定できると思われます(800個までは問題なかった)。
もし仮に、「on X goto *A, *B, *C」と、ラベルを3つしか設定しなかった場合、
Xが1に満たない時はラベルAへ、1以上2未満はラベルBへ、2以上はラベルCへジャンプします。
分岐値は整数、実数のどちらでも構いませんが、文字列型ではエラーとなり動作しません。
実数指定の場合は、整数同様に、1.0未満は1つ目、2.0未満は2つ目のラベルという具合です。
分岐値が1ずつしか設定ラベルを切り分けられませんが、
例えば0〜99は1つ目のラベル、100〜199は2つ目のラベル…としたい場合は、
下記のサンプルのように指定する分岐値を操作してみてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	proc_code = 123
	mes "制御コードにより、処理を行うルーチンを変更します。"
	mes "指定コード:" + proc_code
	on proc_code / 100 gosub *code000_099, *code100_199, *code200_ // 処理を分割する
	mes "各機能毎の処理の後に行う共通的な処理。"
	stop

*code000_099
	mes "コードが0から99までの場合の処理。"
	return

*code100_199
	mes "コードが100から199までの場合の処理。"
	mes "例えば、さらに細かく分岐する場合は、内部で分けるのも良いでしょう。"
	// 2通りに分ける
	switch proc_code \ 2
	case 0
		mes "コードが偶数です。"
		swbreak
	case 1
		mes "コードが奇数です。"
		swbreak
	swend
	return

*code200_
	mes "コードが200以上の場合の処理。"
	return
今回はサブルーチン先でメッセージを表示させるだけでしたが、
小さな機能を同じ用法で独立させることで、再利用しやすい処理ができます。