title

Module

HSPのモジュール機能の解説

この読み物はHSPをある程度理解していて、
なおかつモジュール機能についてはあまり知らないという人を対象にしています。


まず、この読み物を読もうと思っているそこのあなた。
HSPのヘルプにある、モジュール機能の解説のことは綺麗さっぱり忘れてください。
なぜかあれはわざとモジュール機能に手を出そうとしているHSP中級者を
ぼこぼこにして尻尾を巻いて帰らざるを得なくするように、
"わざと"難解に書いてあるような気がしてならないからです。


まず、モジュール機能はどういうものか簡潔に説明すると、

#module~#globalで囲った部分はそこだけ独立し、
その部分は実行されず、飛ばされてそれ以降のスクリプトから実行される。


というものです。

本当はこれは正しい解釈ではないのですが、まずはこういうものだと思ってください。

サンプル
#module
mes "a" // この部分は飛ばされる
#global
mes "b"

それで、この機能がなんの役に立つかというと、
ずばり、 HSPで擬似関数を使うようにすることができる ということです。
それ以外の用途もあるのですが、これが一番多く使われていて、尚且つ便利な用法でしょう。

関数というのは、中学生あたりで習う関数とは若干違い、
独立したある機能をさせる塊の様なもの、とでも覚えておいてください。
では、どうやってその関数を作成し、利用していくかについて書きます。



関数とは別名サブルーチン、とも言われます。
(HSPの中では、命令と関数は区別されているようですが…)
どこかで聞いたことがありませんか?
そう、gosubのヘルプの中などで見かけましたね。
すでにHSP中級者であれば、gosubを使ったサブルーチンジャンプを利用していると思います。
ですが、gosubを使ったサブルーチンを書こうと思うと、必ずスクリプトの最後のほうでしかかけなくなります。
サブルーチンとして呼び出したいのに、勝手に実行されては困りますよね。
なので、stopやメインループの後にラベルを定義して、そこへgosubで飛んでいるわけです。

HSPのスクリプトは頭から実行されるから、最後のほうにサブルーチンを書かなくてはいけない、
でも、最後のほうに書くのはめんどくさい。
そんなときこそモジュール機能の出番です。
前にも説明したとおり、#module~#globalで囲った部分は飛ばされます。
その部分にサブルーチンを書いて、スクリプトの中で自由に呼び出せれば、便利ですよね。
そんな機能をサポートしてくれるプリプロセッサがあります。
(プリプロセッサとはコンパイルされる前に行う処理を定義したものです。)

以下のサンプルを実行してみてください。
#module
#deffunc testfunc
  mes "testfuncが呼び出されました"
  return
#global

mes "モジュール機能のテスト"
testfunc
mes "テスト終了"

まず、#deffuncというプリプロセッサでtestfuncという名前の関数を定義しています。
これはtestfuncという名前のラベルを定義しているようなものです。
そして、その下には普通にスクリプトを書いていくだけです。
最後に、サブルーチンジャンプを行うので、returnは忘れないようにしましょう。
これがないとサブルーチンのネストが深すぎますというエラーが出ます。
testfuncという関数を実行する、つまりtestfuncの下からreturnまでのスクリプトを実行したいときには
testfuncと通常の命令と同じように書けばOKです。
ただし、#deffuncで関数を定義するときには、使う場所よりも前に定義しなくてはなりません。


また、関数に引数を与えることもできます。
引数(ひきすう)とは、pos 10, 20 の10や20の部分に当たるものです。
pos命令はこの数値を受け取ってカレントポジションの変更を行っています。

では、一体どうやったらいいのでしょうか。
言葉で説明するよりもサンプルを見てもらったほうが早いと思うので、どうぞ。

サンプル
#module
#deffunc mul int p1, int p2
  mes "" + p1 + "と" + p2 + "をかけると " + (p1*p2) + " になります"
  return
#global

mul 5, 8
mul 3, 9
mul 14, 17
mul 85, 126

関数が引数を受け取るには、そのための記述が必要です。
書き方のフォーマットとしては、
#deffunc 関数名 引数の型 引数名, 引数の型 引数名, ...
となります。
この場合、mulが関数名、2つあるintが引数の型、p1とp2が引数名となっていますね。
引数はおそらくよほどの数を指定しない限り、何個でも指定できます。

intという引数の型は引数が整数値だということを表します。
他にも指定できるので、詳しくはマニュアルの#deffuncの項目を見てください。

よくやりがちなミスとしては、変数を指定するところに整数値や文字列をしてしまう、
といった点が上げられると思います。
例えば、strmidはp1に変数名を指定しなければならないのに、
文字列の型を指定してまってエラーが出るというものです。

#module
// 実行するとエラーになります。
#deffunc errfunc str p1
  tmp = strmid(p1, -1, 3)
  mes tmp
  return
#global
errfunc "sample string"

これは、errfuncの引数p1の型をstrではなくvarにしてやり、
"sample string"を適当な変数に入れて、その変数をerrfuncの引数として与えてやれば
無事エラーが出ずに実行することができます。


また、関数は値を1つ返すことができます。
値を返す場合は、returnに返す値を引数として与えてやればできます。

サンプル
#module
// p1とp2を足した値を返します
#deffunc add int p1, int p2
  return p1 + p2
#global
add 10, 20
mes stat
add 43, 57
mes stat

returnで返された値はstat等のシステム変数に代入されます。
値を返すというのは、stat等のシステム変数に値を代入することだと思ってもらっても
たぶん大丈夫だと思います。
返された値が整数値ならstatに、文字列ならrefstrに、実数値ならrefdvalに代入されています。

また、stat等のシステム変数をいちいち参照するのはめんどくさいんで、
直接値を返す方法もあります。
それは、#defcfuncプリプロセッサを使うことです。
#defcfuncプリプロセッサを使うと、mes testfunc()のように値を直接得ることができます。

サンプル
#module
// #deffuncから#defcfuncに変わっていることに注意
#defcfunc add int p1, int p2
  return p1 + p2
#global
mes add(10, 20)
mes add(43, 57)

こうやって、関数が返す値を直接受け取ることもできます。
関数の引数を指定するときに括弧で囲むのを忘れないようにしてください。


どうですか?
これが擬似関数の力です。
本当はモジュール機能には別の機能も用意されているのですが、
その機能についてはここでは触れません。
別に知らなくても、ここで触れた内容のことだけで大体のHSPのスクリプトが組まれています。

例えば、HSPをインストールしたフォルダの下にある、commonフォルダの中に、
d3m.hspというファイルがあると思います。
そのファイルを開いてみると、#deffuncが大量に使われていると思います。
#deffuncで関数を定義すると、その後に自由にスクリプトが書けるので、
こうやって別のファイルに分けて、#includeファイルで読み込み、その機能を使うこともできます。
大規模なプログラムにもなると、こうやってファイルを分割していく必要も出てきます。


これでこの読み物は終わりです。
HSPのマニュアルより1ミリでも分かりやすくなっていたら幸いです。
なにか質問等あれば、BBSにて受け付けています。
ですが、筆者は気まぐれかつ怠惰なので、返信がおくれても怒らないでください。