異次元空間
前章でユーザー定義命令・関数なるものを紹介しました。
今回は、ユーザー定義命令・関数のため似に用意されたプリプロセッサといっても過言ではない
モジュール命令セットについて紹介します。
モジュールとは「機能単位、交換可能な構成部分という意味」の機能であり、
「ユーザが自由に追加機能を開発して公開したり、全体を入れ替えず機能強化するのに利用しているのが多い」
という風に某用語辞典では書かれています。
つまり…簡単に書くと「モジュールは使い回しができる小さな機能(パーツ)のこと」ということですね。
使い回しできる実際の処理部分は、#deffunc命令や#defcfunc関数の事を指していて、
前章の書き方でも、コピペするだけで使えないことはありませんが、
ユーザー定義命令・関数内部で使用する変数が、メインスクリプト内の変数と衝突してしまう恐れがあり、
重要な変数名とかぶらない様なユニークな名前を考えなければなりません。
自分の作成したユーザー定義命令・関数ならまだしも、他人のモノを流用する場合には
自分のメイン側の変数と同一でないかを確認しなければなりませんし、
逆に自分の作った汎用的な処理を第三者にも利用してもらえるように公開する場合も同様に、
ユーザー定義命令・関数内の変数名を熟考しなければなりません。

今回のHSPにおけるモジュールと言うのは、
ルーチン内部で使用している変数は元のプログラム内の変数と全く同一名であっても衝突することがなく、
メインスクリプト内の他所でも使用している同一名変数内容を書き換えてしまい予期せぬ不具合になる可能性
も下げてくれるというものです。
同じ変数名なのに全く別の変数として扱われる…?
HSPの基本はBASIC言語と同じく、これから説明する概念をあまり意識せずに使うようになっているので
他のプログラム言語を知らない方にとっては理解しにくいことかもしれませんが、
変数には「スコープ」と呼ばれる有効範囲があり、
その範囲内においてのみ、特定の変数として認められるようになっているのです。
そのスコープ外においては、変数内容を参照することはおろか、
事前に変数を定義しなければ利用できない言語では書き込むこともできません。
前章にHSP3から登場したローカル変数というものを紹介したのを覚えていますか?
#deffunc命令や#defcfunc関数のパラメータにlocalタイプ指定するというもののことですが、
このローカル変数と言うものは、ユーザー定義命令・関数の処理を終えて
return命令で元の処理位置に戻る際に使えなくなるというタイプの変数でした。
 1
 2
 3
 4
 5
 6
 7
 8
 9
	goto *@f

#deffunc test local i
	i = "スコープのテスト"
	return

*@
	test
	mes i // エラーになる
上記のように3行目で変数「i」はローカル変数だと定義すると、 この場合の変数「i」のスコープは4行目と5行目だけだから、 9行目のように変数内容を参照するとコンパイルエラーになり実行できません。 余談ですが参照できなくてエラーになる、と言うわけでないのは、HSPは定義なしで使用できるからであり、 定義してない変数を代入せずにいきなり参照しても、初期値0が表示されるため、 そのままでは普通に使用できてしまい、ローカル変数なのにローカル変数でないような矛盾が生じます。 そこで、HSPインタプリタ側でチェックしようとなった…のだと思います。 個人的には、「ユーザー定義命令・関数で使用するローカル変数のスコープはreturnするまで」と わかっているのだから敢えてはじく必要はないと思うのですが、そうなってるので従うしかありません。 さて、ローカル変数に上記のようなスコープが存在するということはわかっていただけたかと思いますが、 現状では、同一変数をスコープ外において別の変数として扱うには至っていません。 これから紹介するものが、コレを実現するための手続きであり、 再利用可能・切り離し可能なモジュール機能としてHSPに通知する命令になります。
#module モジュール名, 登録変数…
モジュール名省略可能。モジュールに18文字以内の変数のような名称を付けることが出来る。
登録変数省略可能。モジュールに関連付けられたローカルな変数を複数登録可能。
第1パラメータのモジュール名は、 モジュールの変数を参照する際に「どのモジュールの変数か」をわかりやすく明示するための名称であり、 無理に付けずに省略しても問題ありません。 付ける場合、変数名同様のルール(アンダーバーを除く半角記号や、半角数値から開始できない等)があり、 半角英字や全角文字を使用するようにしてください。 尚、モジュール名は文字列ということで「#module "sample"」のようにダブルクォートで囲んでも、 囲まなくてもどちらも同様に動作するようになっていますが、省略する場合は完全な省略だけであり、 ダブルクォートのみのいわゆる空文字指定「#module ""」ではエラーになりますので覚えておきましょう。 もし、モジュール名を付けなかったら、モジュール名はナシになるのかと言うとそうではありません。 付けなくても、内部的には自動的に割り振られるようになっており、スクリプトの上から順に、 1つ目のモジュールは「m0」、2つ目は「m1」、3つ目は「m2」…という具合です。 自動的に割り振られるようにはなっていますが、 もし、2つのモジュールを定義していて、1つ目は省略、2つ目に「m0」と言う名のモジュールを付けると、 1つ目のモジュールに自動的に「m0」が割り当てられ、 2つ目にも同一名のモジュールが存在する、としてコンパイルエラーになってしまいます。 また、省略されたモジュールの上から順に自動割り当てされるので、 1つ目は「m0」と言う名のモジュール、2つ目は省略…と言う場合でもエラーになってしまいます。 第2パラメータ以降の引数である登録変数については、 ソレを扱うためのプリプロセッサ及び命令が5個ほど増えてややこしくなりますし、 何より普通に利用する大半の場合には使用しないものですので、現段階では説明も省こうと思います。 どこからどこまでが対象範囲であるかブロック形式のものは、 repeat命令にはloop命令、switchマクロにはswendマクロ…等のように対をなす命令が存在し、 今回のモジュールの場合も同様に「ココまでがモジュールですよ」という合図が必要になり、 同時にスコープを決めるためのものでもあります。
#global
[パラメータなし]モジュール終了を示すだけのためにパラメータは必要ない。
名前からすると「ココからがメイン(グローバル)の処理ですよ」という合図のようですが、 #globalを書かずに別の#moduleを書くとエラーになってしまいます…。 よって、1つの#moduleに対して1つの#globalを書く、 つまりモジュールのブロックの開始と言う風にペアで書くと言うように覚えましょう。
 1
 2
 3
 4
 5
#module
	mes "モジュール空間内です。"
#global

	mes "グローバル空間です。"
#module#globalはそれぞれ上記のように使用するわけですが、 スクリプトを実行しても5行目の「グローバル空間です。」としか表示されません。 タイトルの「異次元空間」と呼ばれる所以はココにあり、 スクリプトに書いても、コメントであるかのごとく無視されるようになっているワケです。 スクリプトは上から下へ流れますが、通常のやり方ではモジュール内に入らないというわけなので、 #deffunc命令、#defcfunc関数を使う場合、goto命令でジャンプしなければならなかった問題を解決できます。 直接は実行されないブロックになりますが、内部で#deffunc命令、#defcfunc関数を定義していると、 別空間ではあるというものの、gosub命令のラベルジャンプ同様に正常動作してくれます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
#module mod
#deffunc sample str msg
	mes "モジュール内での表示メッセージ"
	mes "「" + msg + "」\n"
	return
#global

	sample "定義命令sampleに文字列を渡す"
	mes "モジュール外での表示メッセージ"
共通的に使用している変数に思わぬ値が入ってしまうことがあるgosub命令のサブルーチンに対して モジュールはメイン側と衝突することがなく、予期せぬ値による不具合が発生しにくいと説明しましたが、 次のスクリプトを実行していただければわかるかと思います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#module
#deffunc varset
	tmp = 123
	return

#deffunc varshow
	mes tmp
	return
#global

	tmp = "グローバルの変数"
	varset	// 変数tmpを書き換える?
	mes tmp	// 現在のtmpを表示
	varshow	// 現在のtmp(モジュール版)を表示
モジュールは無視されるために、まず11行目が実行されて変数tmpにテキストがセットされます。 続いて2行目のユーザー定義命令にジャンプし、3行目で同名変数tmpに別の値が入れられるわけですが、 13行目に戻ると、11行目でセットしたテキストが表示されるようになっています。 3行目でセットした値が無視されたわけではないことは14行目から6行目にジャンプし、 7行目のモジュール内で表示させたtmpを見れば正常にセットされていたことがわかりますね。 このようにメインスクリプト(グローバル)はメインスクリプト、 モジュールはモジュールと変数が別々に管理されていますので、 モジュールだけ切り離して、全く別のスクリプトに流用しても問題ないわけです。 ただ、どんなときでも別の方がイイかといえばそうではありませんね。 モジュール内でグローバルな変数データを操作したい時も当然あるわけですが、 その場合は、#deffunc命令、#defcfunc関数のパラメータで渡してあげればよいのです。 さて、モジュールとグローバルとで変数が衝突しないのが良いときもあるけど、 逆にグローバルの変数をそのままやり取りしたい場合もあると思います。 その場合は、intstrの定数で指定せずに、vararrayの変数をパラメータにすることで、 モジュール内部で書き換えた変数内容はメイン側でも変えることができます。 もし、モジュール内部で扱いたいメイン側の変数が大量にあったりして、 パラメータ形式でいちいち渡すのはどうも…という場合に、 変数語尾に半角のアットマークを付けることでグローバルの変数を操作できるわけですが、 再利用可能なモジュールと言う観点から考えれば、そのまま他のスクリプトで使うには、 モジュール内部で使用したグローバル変数を用意しなければならない、となるので汎用性に欠けますね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
#module
#deffunc global_varset
	tmp@ = 123
	return
#global

	tmp = "グローバルの変数"
	global_varset // 変数tmpを書き換える?
	mes tmp       // 現在のtmpを表示
とても推奨できる書き方ではないわけですが、 「モジュール内の変数をグローバルで参照する」ことも同様の「変数名+@+モジュール名」でできます。 モジュール名を付けてない場合は自動的に「m0」から順に「m1」「m2」と付けられるとありましたので、 モジュール名を省略された場合の扱いは次のようにしましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#module
#deffunc varset
	tmp = 123
	return
#global

	tmp = "グローバルの変数"
	varset     // 変数tmpを書き換える?
	mes tmp@m0 // 現在のtmp(モジュール版)を表示
	mes tmp    // 現在のtmp(グローバル版)を表示
以上でモジュールの使い方の説明を終了しますが、 #if命令の紹介時に書いていた「#if命令の一番多い使われ方」を書いておきます。 モジュールは一つのスクリプトのみならず複数のスクリプトで流用できる汎用的なルーチンであることから、 メインのルーチンとは別に、サンプルとして使い方の例を書いたスクリプトを添付しておくことがあります。 複数行コメント「/* 〜 */」のように実行を一度に制御できるもので、 サンプルを動かす時は「#if 1」、動かさない時は「#if 0」と数値を置き換えるだけで切り替えられます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#module
#defcfunc calc int value, int tax, local i
	if tax = 0 : i = 5 : else : i = tax
	return int(value * (i + 100) / 100)
#global

// 0 or 1 でサンプル部分の処理実行を制御する
#if 1
	total = 3200
	mes "消費税込みの金額を表示します。"
	mes strf("%d円の税込額は%dです。", total, calc(total))
#endif