HSP : Hot Soup Processor ver3.2 / onion software 1997-2009(c)

# HSP3 モジュール機能ガイド

1. HSPモジュール機能について
2. ユーザー定義命令について
3. モジュールの使い方(基本)
4. モジュールの使い方(応用)
5. モジュール定義命令
6. モジュールのクリーンアップ機能について
7. 標準キーワードと「@hsp」モジュール名について

このテキストには、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宣言により明示的にローカルな変数を 作成することができます。再帰呼び出しを行なう場合には、必ず内部で 使用する変数をローカルにするようにしてください。 (ローカル宣言を行なうと、変数の生成と破棄のために通常よりも 実行コストがかかることも留意しておいてください。)

## モジュール定義命令

HSP3ではモジュール定義命令として、以下のプリプロセッサ命令が用意されています。

- #module
- #global
- #deffunc
- #defcfunc

各命令の使い方はヘルプブラウザから確認してください。

## モジュールのクリーンアップ機能について

クリーンアップ機能は、HSPプログラムが終了する直前にモジュール内のスクリプトが 自動的に呼び出されるもので、モジュールによって機能を拡張した場合などにその後始末、 システムやメモリの解放などが行なえるようにしたものです。これをクリーンアップ機能と 呼んでいます。この機能を使うには、モジュール内でのユーザー定義命令宣言時に、

    #deffunc 名前 onexit

のように、引数を記述する部分に「onexit」を入れておいてください。 HSPのプログラムが終了した場合に、その命令が自動的に実行されます。 ただし、この命令内では以下の点に注意してください。

- end、stop、wait、await、dialogなど時間待ちが起こる命令は使用できません
- mesなど画面に出力する命令は機能しません

つまり最低限のメモリ解放や外部DLLの呼び出しなど最終的に行なわれる作業だけを 記述するものと考えて下さい。 同様の動作をするものにonexit命令がありますが、システムの処理としては

1. [HSPのプログラムを中断]
2. [onexit命令の飛び先を実行]
3. [モジュールのクリーンアップ先を実行]
4. [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 "メッセージです。"  // 「MES->メッセージです。」と表示
        stop

「mes」というキーワードを、#undef命令により取り消した後、再定義しています。 HSPで使用される標準キーワードすべては、同様に取り消し、再定義することが可能です。

以下は、mes命令をユーザー定義命令に置き換えている例です。

    #undef 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命令を影文字にしてみました。"

標準キーワードの再定義は、それ以降のキーワードすべてに影響があるため注意して使用してください。