1. トップ
  2. HSP講座
  3. プリプロセッサ命令編
  4. >ここ [
    1. ←前
    2. |
    3. 次→
    ]

モジュール実践

同じようなものは、固めておくのが良です。これは、プログラミングについても同じことがいえます。
前回に、#module の機能について解説したので、今回は、モジュールを使った、もっと実用的な話に移ります。

  1. モジュール、第二の意味
  2. 応用してみよう
  3. おわりに

モジュール、第二の意味

1つの #module と、1つの目的のための処理を行うユーザ定義命令・関数が #module の中にいくつか定義されているファイルを、(HSPでは) モジュールと呼びます。
例として、次のような「文字列切り出しモジュール(strtok)」を考えます。

// module - strtok (string tokenize)

// 簡易版

#ifndef IG_MODULE_STRTOK_AS
#define IG_MODULE_STRTOK_AS

#module strtok

//------------------------------------------------
// strtok バッファを設定する
// 
// @prm sBuf      : 区切られるテキスト
// @prm delimiter : 区切り文字列
//------------------------------------------------
#deffunc strtok_setBuffer str sBuf, str delimiter
	
	stt_buf      = sBuf
	stt_delimiter = delimiter
	stt_index    = 0
	
	stt_lenBuf       = strlen(stt_buf)
	stt_lenDelimiter = strlen(stt_delimiter)
	
	return
	
//------------------------------------------------
// 文字列を切り出す
//------------------------------------------------
#defcfunc strtok_getnext
	if ( stt_bFinished ) {		// 既に完了している
		return ""
	}
	
	// 次の区切り文字を検索
	i = instr( stt_buf, stt_index, stt_delimiter )
	if ( i < 0 ) {
		i = stt_lenBuf - stt_index	// 残りすべて
	}
	
	// 結果
	sResult = strmid( stt_buf, stt_index, i )
	
	stt_index += i
	stt_index += stt_lenDelimiter	// 区切り文字列を飛ばす
	
	return sResult
	
//------------------------------------------------
// すでに取り出しが完了しているか
//------------------------------------------------
#defcfunc strtok_isFinished
	return (stt_lenBuf <= stt_index)
	
#global

// サンプル・スクリプト
#if 0

	// 商品データ (という設定)
	// 「商品名,単価,在庫数」ということにする
	buf = {"
		リンゴ,100,30
		みかん,170,12
		ぶどう,1600,2
	"}
	
	// strtok バッファに設定
	strtok_setBuffer buf, "\n"
	
	// すべての行について、1行ずつ処理
	repeat
		if ( strtok_isFinished() ) {
			break
		}
		
		data = strtok_getnext()
		
		mes "#" + cnt + " " + data
	loop
	
	stop

#endif

最後の方にある「#if 0」から「#endif」は、使用例=サンプルです。「0」を真 (= 0 以外の数値) に変更すると、実行できます。これは、モジュールにサンプルを書くときの常套手段です。
まずは、これを実行してみて、動作を確認してみてください。
……これで、おおかたの使い道は分かると思います。getstr と同じようなことをしているだけですね (爆)。

これの仕様を確認しておきましょう。

初めに、strtok_setBuffer で、区切られる対象の文字列を選択してもらいます。その文字列は、strtok モジュールの中にある変数 stt_buf に保存しておきます。

次に、strtok_getnext() を呼び出します。これは名前の通り、「次の文字列を取り出す」ような関数です。引数がありませんが、必要な情報は既にモジュール内にあるので、大丈夫です。
ここでは、単純に、次の位置 (stt_index) から、次の区切り文字列を検索し、その手前までにある文字列を取り出します。

strtok_isFinished() は、文字列をすべて取り出し終えたかどうかを知るための関数です。この例のようなループの、終了条件に使います。

以上。かなり小規模なモジュールですね。

実際に使用するときは、これを strtok.as などの名前のファイルとして保存し、次のように書いておきます。

#include "strtok.as"

これで、先ほどのような命令、関数が使用できるようになります。
いちいち区切り処理を書かなくていいので、楽ですね!

つ getstr


応用してみよう

さて、strtok のサンプルにある文字列データは、各行がCSV形式[脚注]になっています。実際はこれも「リンゴ」「100」「30」に分解したいはずです。
じゃあ、文字列をカンマで区切ればいいんですね。うーん、どう書こうか……。

strtokモジュールがあるじゃないか!

というわけで、次のように書いてみました。

#include "strtok.as"

	// 商品データ (という設定)
	// 「商品名,単価,在庫数」ということにする
	
	buf = {"
		リンゴ,100,30
		みかん,170,12
		ぶどう,1600,2
	"}
	cntItem = 0		// 商品の種類数
	
	// strtok バッファに設定
	strtok_setBuffer buf, "\n"
	
	// すべての行について、1行ずつ処理
	repeat
		if ( strtok_isFinished() ) {
			break
		}
		
		data = strtok_getnext()
		
		// data を分解
		strtok_setBuffer data, ","
		
		name(cnt)  =      strtok_getnext()
		price(cnt) = int( strtok_getnext() )
		stock(cnt) = int( strtok_getnext() )
		
		cntItem ++
	loop
	
	// 商品リストを再構成
	repeat cntItem
		mes strf("「%s」は単価 %d 円で、%d 個の在庫があります。", name(cnt), price(cnt), stock(cnt))
	loop
	
	stop

「リンゴ」は単価 100 円で、30 個の在庫があります。

実行結果

……。あれ? リンゴしかない……?

原因は簡単です。
りんごの行を分解するときに、strtok にりんごの行のテキストを設定しますが、それによって元の商品リストの文字列が消滅してしまったのです。
そのため、ループの2回目の時点で、strtok は「取り出しが完了した状態」になっています (設定されているのはりんごの行)。

というわけで、こういう処理は、一旦各行に分割してから、その各行について strtok を使う、という手順を踏まなければいけません。面倒くさい!
今回の例ではそうして解決できるのですが……
もし、複数のプログラムが strtok を利用していて、その片方が strtok を利用した処理を終える前に、もう片方が strtok に新しい文字列を設定してしまった場合。
プログラムは見た目とは違う挙動、すなわちバグを生じてしまいます。


おわりに

今回も2本立てです。
実は、このページで言いたいことは、冒頭で述べた、「1つの #module と、1つの目的のための処理を行うユーザ定義命令・関数が #module の中にいくつか定義されているものを、(HSPでは) モジュールと呼ぶ」だけです。
その他は、すべて、次への布石なんです。今回が事件編、次回が解答編といったところ。

では、また次回