モジュール機能ガイド
このテキストには、Hot Soup Processor ver3.0以降でサポートされている
モジュール機能についての説明が含まれています。
モジュール機能は、HSPをより深く高度に使いたいという方のための
拡張機能です。しかし、この機能はすべての人に必要なものではありません。
初心者の方や、これからHSPを使う方は、まだモジュールについての習得は
しなくても大丈夫です。しかし、中級者以上の方はより効率的にソースの
再利用が可能になる重要な要素ですので、是非マスターしてください。
また、上級者は、さらに高度なモジュール変数機能を利用することが可能です。
モジュール変数についての詳細は、このガイドでは説明していませんので、
詳しくは、プログラミングガイドを参照してください。
HSPモジュール機能について
モジュール機能は、複数のスクリプトをラベル名や変数名の衝突を気にせず
結合するための仕組みです。
この機能は、大きなサイズのスクリプトを作る時や、多量の変数名を管理
する場合に有効です。モジュール機能を活用することにより、
より汎用性の高いスクリプトを組むことができるようになります。
また、モジュール機能を使わない人であっても、他の人がモジュール機能を
使って追加した新しい命令を使うことが可能です。これは、DLLによる
拡張プラグインの仕組みと変わりません。
まず、モジュールについて説明してみましょう。
たとえば、「test1.as」というソーススクリプトがあったとしましょう。
このソーススクリプトには、変数aと変数bを使っているとします。
別な人が、「test2.as」というソーススクリプトを作ったとして、そこに
とても便利なサブルーチンがあったとしたら、どうなるでしょう。
「test2.as」で変数aと変数bという名前を使っていなければ問題なく、
そのままサブルーチンだけを持ってくることができるかもしれません。
しかし、もし「test2.as」でも変数aと変数bを別な用途で使っていたとしたら
とてもやっかいです。
HSPモジュール機能を使うと、「test1.as」から「test2.as」のスクリプトを
呼び出すことが可能になりますが、「test1.as」と「test2.as」で使われて
いる変数は(たとえ名前が同じであっても)独立したものとして扱われます。
また、この独立したスクリプト内のサブルーチンを、新規命令として登録
することができ、パラメータを渡したり、受け取ったりすることが可能です。
過去に作ったモジュールを再利用したり、人に使ってもらうために公開したり、
誰か他の人が作ったモジュールを使うなど、HSPスクリプトをより広く応用
することが可能になります。
HSPモジュールを使いこなすためには、ユーザー拡張命令、モジュールイン
ポート命令などを使う必要があります。これらは、単体でも便利な
機能を提供する命令です。一度に覚えようとしないで、わかるところから1つ
1つマスターしていきましょう。
ユーザー定義命令について
ユーザー定義命令は、HSPモジュール機能とともに追加された命令の1つで、
新しい名前の命令をユーザーが任意に追加できるというものです。
これは、HSPモジュール機能とは別に単体で使っても非常に強力なものと
なるでしょう。
ユーザー定義命令は、以下のように使います。
例:
goto *main
#deffunc routine
mes "sub-routine"
return
*main
routine
stop
HSPの命令には「routine」はありませんから、いままでならエラーになって
しまうところですが、実際にこのスクリプトを実行すると、「sub-routine」
という表示がされて、「routine」という命令が実行されます。
ユーザー定義命令は、「#deffunc」という命令によって定義できます。
#deffunc 命令の名前
で、新しい名前の命令が追加されます。
これ以降、新しい命令が出てきた場合には、「#deffunc」のある場所に
サブルーチンジャンプします。
つまり、
例:
goto *main
*routine
mes "sub-routine"
return
*main
gosub *routine
のようなスクリプトでも、
例:
goto *main
#deffunc routine
mes "sub-routine"
return
*main
routine
でも同じということです。
ただし、「#deffunc」には1つだけ注意点があります。
「#deffunc」は実際に命令を使う位置よりも前に置くこと。
これはたとえば、gosub命令の場合は呼び出すサブルーチン(ラベル)が
gosub命令よりも前にあっても、後にあっても問題はありませんでした。
「#deffunc」では追加した命令が使えるようになるのは、「#deffunc」
の定義位置から先になります。
これは、スクリプトのコンパイル時に新規キーワードが変数か命令かを
決定するために、あらかじめ定義されていることが必要なためです。
「#deffunc」を使って新しい命令を作る場合は、サブルーチンを先に、
メインのスクリプトはその後にするような構成を取るように心がけて
みてください。
またユーザー定義命令は、サブルーチンにパラメータを渡すことを
可能にしています。
例:
goto *main
#deffunc routine int prm1, int prm2
mes "パラメータ1は、"+prm1+"です。"
mes "パラメータ2は、"+prm2+"です。"
return
*main
routine 10,20
いままでのgosub命令では、値をサブルーチンに渡す時には、あらかじめ
決められた変数に値を入れて、呼び出すしかありませんでした。
ユーザー定義命令では、それに代わってスマートな方法で値を渡すことを
可能にしています。
また、渡すパラメータは数値だけでなく、文字列、変数(配列)など
いくつものバリエーションがあります。また、サブルーチンから戻る際に
システム変数statに値を代入したり、パラメータとして指定した変数に
値を書き戻すことも可能です。
このパラメーターは、エイリアスとも呼ばれていて、#deffunc命令により
新規命令を定義する際に設定することができます。
#deffunc 命令名 パラメーター型 エイリアス名,…
のように記述することで、指定されたパラメーター型を取得して、
エイリアス名で参照することができるようになります。
詳しくは、「#deffunc命令詳細」を参照してください。
また、HSP3.0からは、#defcfunc命令によりユーザーが関数を定義することが
可能になっています。これも、#deffunc命令と同様の指定で、簡単に関数を
追加することができます。
モジュールの使い方(基本)
モジュールは、変数名やラベル名が独立して扱えるソースの単位を指します。
推奨される最も簡単なモジュールの使い方は、モジュール部分を1つの
ソーススクリプトファイル(asファイル)にしておき、別なファイルから
モジュールとして呼び出すことです。
たとえば、test1.asというファイルにモジュールとなるソースを書きます。
例:test1.asの内容
#module
#deffunc test1
mes "test1.asで表示しています。"
return
#global
モジュールは、必ず「#module」で始まり「#global」を最後に書くのが
お約束だと思って下さい。
このように記述すると、モジュール内に「test1」という命令で呼び出す
ことのできるサブルーチンができあがります。
次に、これを呼び出すためのソースを「test2.as」というファイルに
書いてみましょう。
例:test2.asの内容
#include "test1.as"
mes "test2.asです。"
test1
stop
「#include」命令でモジュールを登録するためのソース「test1.as」を
読み込んでいます。
この後、「test1」という命令が書かれた行が出てきます。
これはユーザー定義命令なので(詳しくは「ユーザー定義命令について」
を参照)、「test1.as」のサブルーチンが呼び出されます。
実際にこのスクリプトを実行すると、
test2.asです。
test1.asで表示しています。
という2つの行が現われ、test2.asからtest1.asのモジュールが呼び出された
ことがわかります。
この基本的な例では、メッセージを表示しているだけなので、gosub命令による
サブルーチン呼び出しと、あまり変わっておらず、モジュールとしての威力は
発揮されていません。
では、次の例ではどうでしょうか。
例:test1.asの内容
#module
#deffunc test1
a=456
mes "test1では、"
mes "A="+a+"です。"
return
#global
例:test2.asの内容
#include "test1.as"
a=123
test1
mes "test2では、"
mes "A="+a+"です。"
stop
これで「test2.as」を実行してみると、
test1では、
A=456です。
test2では、
A=123です。
となります。普通ならば、先に「test1.as」側で変数aに456が代入されて
いるので、「test2.as」でも同じ値になるところですが、モジュールの
中と外では名前が独立しているので、同じ変数aでも、「test1.as」と、
「test2.as」では別々の内容を保持しています。
このように、変数やラベルの名前が重複していても、まったく問題なく
それぞれのスクリプトが動作するという点が、モジュールの基本的な概念
です。
しかし、変数が独立していると困る部分もあります。モジュールを呼び出して
何らかの結果を得たい場合や、モジュールに値を渡したいこともあります。
実は、このための機能がユーザー定義命令に用意されていて、別なモジ
ュールとのパラメータのやり取りに使うことができます。
例:test1.asの内容
#module
#deffunc test1 int prm1
mes "パラメータは、"+prm1+"です。"
return 1
#global
例:test2.asの内容
#include "test1.as"
a=543
test1 a
mes "STATは、"+stat+"です。"
stop
上の例では、ユーザー定義命令test1の呼び出しで、「test1 a」という
パラメータ付きの記述で、変数aの内容、543という値を渡しています。
「test1.as」では、この渡された値を、prm1というエイリアス名によって
取り出し、さらに、システム変数statに値1を代入して終了します。
重要なことは、「test2.as」では「test1.as」で使われている変数名
などは気にする必要がないことです。
同様に、「test1.as」からも呼び出す側との変数名が重なることを
気にする必要がありません。
独立した機能を持ったサブルーチンを、完全に分離してしまうことが
HSPモジュール機能なのです。
以上が基本的なモジュールの使い方です。
さらなる応用については、次の項を見てください。
モジュールの使い方(応用)
基本の説明では、1つのモジュールに1つの命令だけを定義していましたが、
1つのモジュールの中に、複数のユーザー定義命令を作ることもできます。
例:test1.asの内容
#module
#deffunc test1
mes "test1で表示しています。"
return
#deffunc test2
mes "test2で表示しています。"
return
#global
上の例では、「test1」と「test2」という命令が新たに追加されます。
この場合、「test1」と「test2」の間では変数名やラベル名は同じものに
なります。
「test1」と「test2」でやはり独立した変数名やラベル名を使いたい場合は、
#module
#deffunc test1
mes "test1で表示しています。"
return
#global
#module
#deffunc test2
mes "test2で表示しています。"
return
#global
このように記述することで実現できます。
つまりモジュールとは、「#module」〜「#global」で区切られた区間と
それ以外の区間をまったく別な世界と考えるように指示を出している
ことなのです。
基本の説明では、「test1.as」と「test2.as」という2つのファイルに
モジュールと、そうでない部分(グローバル領域)を分けて説明して
いました。実際のところ「test1.as」をインクルードすることなく
1つのファイル内でもモジュールをどんどん作っていくことも可能ですが、
なるべく1つのファイルには1つのモジュールのみにして、1つのソース
スクリプトでは変数名とラベル名を統一した方がいいでしょう。
特にラベル名は、1つのソーススクリプト内に同じラベルがいくつも
あると混乱を招きます。
モジュール内の変数は、基本的にすべて静的(static)に共有されています。
つまり一度設定された変数は、他の値が代入されない限り、内容を失う
ことはありません。このため、再帰呼び出し(自分自身を呼び出すこと)
を行なう場合には注意が必要です。
それぞれの定義命令内では、local宣言により明示的にローカルな変数を
作成することができます。再帰呼び出しを行なう場合には、必ず内部で
使用する変数をローカルにするようにしてください。
(ローカル宣言を行なうと、変数の生成と破棄のために通常よりも
実行コストがかかることも留意しておいてください。)
モジュール定義命令
#module "モジュール名" モジュール開始
#global モジュール終了
指定された区間をモジュールとして別な空間に割り当てます。
モジュール内の変数やラベルは、モジュール外のものからは独立したものに
なります。
"モジュール名"は、複数のモジュールを名前で区分けする時につけることの
できる名前で、モジュール名が同じもの同士は、変数名やラベル名を共有
します。モジュール名が違うものの間では、変数名やラベル名はまったく
違うものとして扱われます。
"モジュール名"を省略した場合は、他とは違う名前が自動的に割り当て
られます。
モジュールは、必ず「#module」で開始を指示し、「#global」で終了
しなければなりません。このようにモジュールの区間を指定することにより、
その中を他から独立した空間にすることができます。
"モジュール名"は、18文字以内の長さでなければなりません。
また、スペースや記号を含まない文字列を指定するようにしてください。
(モジュール名で使用できる文字種は、a〜zまでのアルファベット、0〜9までの数字、「_」記号
となります。変数として使用できる文字列と同等です。)
#moduleおよび、#globalはプリプロセッサ命令です。「#」で始まり、1行に
他の命令は記述できません。
#module命令には、さらにモジュール変数という機能を利用するための
パラメーターが用意されています。モジュール変数の詳細については、
リファレンスマニュアルや言語仕様マニュアルを参照してください。
ユーザー拡張命令詳細
#deffunc p1 p2,p3… 新規命令を割り当てる
p1 : 割り当てられる命令の名前
p2 p3〜 : バラメータタイプ名・エイリアス名
ユーザーによる新規命令を登録します。
p1に新規命令の名前を、p2以降に呼び出しパラメータタイプを指定します。
#deffunc命令で定義した位置より以降は、指定された名前を命令語として
使用することが可能です。
新規命令は、#deffuncで指定された行以降が実行される内容になります。
実行は gosub命令と同じくサブルーチンジャンプとして行なわれ、return命令
でもとの実行位置に戻ります。
例 :
#deffunc test int a
mes "パラメーター="+a
return
追加された新規命令ではパラメータを受け取ることができるようになります。
それぞれのパラメータには、パラメータタイプとエイリアス名の指定が可能で
す。指定するパラメータタイプには以下のものがあります。
int : 整数値
var : 変数(配列なし)
array : 変数(配列あり)
str : 文字列
double : 実数値
label : ラベル
local : ローカル変数
エイリアス名は、渡されたパラメーターの内容を示すもので、変数とほとんど
同じ感覚で使用することができます。
varとarrayの使い分けには注意が必要なほか、 ローカル変数を示すlocalタイ
プは特殊な用途となります。
#defcfunc p1 p2,p3… 新規関数を割り当てる
p1 : 割り当てられる関数の名前
p2 p3〜 : バラメータタイプ名・エイリアス名
ユーザーによる新規関数を登録します。
p1に新規関数の名前を、p2以降に呼び出しパラメータタイプを指定します。
#defcfunc命令で定義した位置より以降は、 指定された名前を関数として使用
することが可能です。
^
新規関数は、 #defcfuncで指定された行以降が実行される内容になります。実
行はgosub命令と同じくサブルーチンジャンプとして行なわれ、 return命令で
もとの実行位置に戻ります。
その際にreturn命令に戻り値のパラメーターを指定する必要があります。
例 :
#defcfunc half int a
return a/2
追加された新規関数ではパラメータを受け取ることができるようになります。
それぞれのパラメータには、#deffunc命令の指定と同様になります。
モジュールのクリーンアップ機能について
クリーンアップ機能は、HSPプログラムが終了する直前にモジュール内のスクリプトが
自動的に呼び出されるもので、モジュールによって機能を拡張した場合などにその後始末、
システムやメモリの解放などが行なえるようにしたものです。これをクリーンアップ機能と
呼んでいます。この機能を使うには、モジュール内でのユーザー定義命令宣言時に、
#deffunc 名前 onexit
のように、引数を記述する部分に「onexit」を入れておいてください。
HSPのプログラムが終了した場合に、その命令が自動的に実行されます。
ただし、この命令内では以下の点に注意してください。
- end、stop、wait、await、dialogなど時間待ちが起こる命令は使用できません
- mesなど画面に出力する命令は機能しません
つまり最低限のメモリ解放や外部DLLの呼び出しなど最終的に行なわれる作業だけを
記述するものと考えて下さい。
同様の動作をするものにonexit命令がありますが、システムの処理としては
[HSPのプログラムを中断]
↓
[onexit命令の飛び先を実行]
↓
[モジュールのクリーンアップ先を実行]
↓
[HSPリソースの完全な破棄]
という順番になっています。
onexit命令の飛び先では、プログラムを中断すること自体を中止することも
可能ですが、クリーンアップ機能の場合は中断はできません。
また、複数のモジュールや、複数のクリーンアップ命令が登録された場合には、
登録された順番とは逆順に辿って次々に実行されていきます。
標準キーワードと「@hsp」モジュール名について
標準キーワードは「@hsp」というモジュール名の空間に割り当てられています。
これは、たとえばmes命令であれば、「mes@hsp」という名前が正式な名称であることを意味します。
ただし、通常のグローバルな空間でも使用できるように「@hsp」がない名称も別名として登録されているので、いままで通りの命令名でそのまま使用することができます。
たとえば、mes命令であれば「#define global mes mes@hsp」が最初から定義されているのと同じです。
(プリプロセッサ処理後に、標準キーワードは「@hsp」が付けられた正規の名称に展開されます。コンパイル時に出力される「hsptmp.i」を開いてみるとわかると思います。)
これにより、標準キーワードとして登録されている名称そのものを別名にしたり、ユーザーが定義し直すことが可能になります。
以下は、mes命令をマクロにより置き換えている例です。
#undef mes
#define mes(%1) mes@hsp "MES->"+%1
mes "メッセージです。"
stop
「mes」というキーワードを、#undef命令により取り消した後、再定義しています。
HSPで使用される標準キーワードすべては、同様に取り消し、再定義することが可能です。
以下は、mes命令をユーザー定義命令に置き換えている例です。
#module
#deffunc mes str _p1
_y=ginfo_cy
color 0,0,0
pos ginfo_cx+1,_y+1
mes@hsp _p1
color 0,192,255
pos ginfo_cx,_y
mes@hsp _p1
return
#global
mes "mes命令を影文字にしてみました。"
標準キーワードの再定義は、それ以降のキーワードすべてに影響があるため注意して使用してください。