Home -> HSP講座 -> モジュールクラス

※追記(2012.07/09) この記事は完全に書き直しました (→モジュールクラス・ユージング)。そちらのほうが分かりやすいと思います……たぶん。


モジュールクラスに挑戦しよう

前回は、strtok_getnext() の最中に、新たに strtok を使用することができない、という話をしました。
この原因を、わかりにくく言うと、strtokモジュールが1つしかないからです。当たり前ですが。
しかし、この当たり前を覆すちょっと便利な機能が、このページで扱う、「モジュール変数」機能です。

まずは実際のスクリプトを見てください。

// ModuleClass strtok (string tokenizer)

// 簡易版

#ifndef IG_MODULE_CLASS_STRTOK_AS
#define IG_MODULE_CLASS_STRTOK_AS

#module strtok mBuf, mSplitter, mIndex, mlenBuf, mlenSplitter

// マクロ
#define global strtok_new(%1, %2, %3) newmod %1, strtok@, %2, %3
#define global strtok_delete(%1) delmod %1

//------------------------------------------------
// strtok 構築
// 
// @prm sBuf     : 区切られるテキスト
// @prm splitter : 区切り文字列
//------------------------------------------------
#modinit str sBuf, str splitter
	
	mBuf      = sBuf
	mSplitter = splitter
	mIndex    = 0
	
	mlenBuf      = strlen(mBuf)
	mlenSplitter = strlen(mSplitter)
	
	return
	
//------------------------------------------------
// 文字列を切り出す
//------------------------------------------------
#modcfunc strtok_getnext
	if ( strtok_isFinished(thismod) ) {		// 既に完了している
		return ""
	}
	
	// 次の区切り文字を検索
	i = instr( mBuf, mIndex, mSplitter )
	if ( i < 0 ) {
		i = mlenBuf - mIndex	// 残りすべて
	}
	
	// 結果
	sResult = strmid( mBuf, mIndex, i )
	
	mIndex += i
	mIndex += mlenSplitter	// 区切り文字列を飛ばす
	
	return sResult
	
//------------------------------------------------
// すでに取り出しが完了しているか
//------------------------------------------------
#modcfunc strtok_isFinished
	return (mlenBuf <= mIndex)
	
#global

// サンプル・スクリプト
#if 1		// (0 以外ならサンプルを実行)

	// 商品データ (という設定)
	// 各行は「商品名,単価,在庫数」ということにする
	buf = {"
		リンゴ,100,30
		みかん,170,12
		ぶどう,1600,2
	"}
	
	// buf を strtok で分解する (改行で区切る)
;	newmod lineToker, strtok, buf, "\n"
	strtok_new lineToker, buf, "\n"		// マクロを使用、newmod でも処理は同じ
	
	// すべての行について、1行ずつ処理
	repeat
		if ( strtok_isFinished( lineToker ) ) {
			break
		}
		
		// 一行取り出す
		data = strtok_getnext( lineToker )
		if ( data == "" ) { continue }
		
		// data を分解する ( ',' で区切る )
		strtok_new dataToker, data, ","
		
		name(cnt)  =      strtok_getnext( dataToker )
		price(cnt) = int( strtok_getnext( dataToker ) )
		stock(cnt) = int( strtok_getnext( dataToker ) )
		
		strtok_delete dataToker
		
		cntItem ++
	loop
	
	strtok_delete lineToker		// 必ず delete する、という必要はない
	
	// 商品リストを再構成
	repeat cntItem
		mes strf("「%s」は単価 %d 円で、%d 個の在庫があります。", name(cnt), price(cnt), stock(cnt))
	loop
	
	stop
	
#endif

変更点としては、strtok 用のデータを持つ変数の名前が変わったり、#module の行がやたら長くなったり、細かい点では、先頭のコメントの「string tokenize」が「string tokenizer」になったり、ということです。


抽象的説明

「文字列を分解するための道具(プログラムの部品)」のことを、strtok と呼びます。この道具の設計図は、#module の横に並ぶ変数名と、#module から #global の間に書いたものです。
ここまででは、ただ設計図を書いただけで、実際には道具は作られていません。

#module strtok mBuf, mSplitter, mIndex, mlenBuf, mlenSplitter
	...
#modinit str sBuf, str splitter
#modcfunc strtok_getnext
#modcfunc strtok_isFinished
	...
#global

※設計図部分を、具体的なコードは省略して、再掲。

実際に道具を作るには、newmod 命令を使います。道具を作ることを、strtok を実体化する、といいます。また、作った道具のことを、strtok のインスタンス(実体、instance)といいます。ここで、作られた道具 (= strtok インスタンス) は、上記のサンプルでは、変数 lineToker が持っています。

道具が作られると、直後にコンストラクタ (#modinit で定義される命令) が自動的に呼び出されます。
コンストラクタは、道具を初期化するのに使用します。

;	newmod lineToker, strtok, buf, "\n"
	strtok_new lineToker, buf, "\n"		// マクロを使用、newmod でも処理は同じ
	// 凡例
	newmod (インスタンスを格納する変数), (モジュールクラス名), (コンストラクタ引数...)

インスタンスは、内部にいくつかの変数 ( mBuf, mSplitter, ...(以下略) ) を持っています。このような、「実体化された道具」の中にある変数を、メンバ変数と呼びます。例えば、strtok を2つ実体化すると、メンバ変数 (mBuf, mSplitter, ...) を2セット作ったことになります。
ただし、インスタンスの持つメンバ変数を (外部から) 操作(代入したり、その中の値を得たり)することはできません。

下にある図のように、インスタンスとはメンバ変数の集まり(パッケージ)である、と考えてもいいでしょう。

strtok のインスタンスの例

実体化された道具を操作するには、strtok が持つ命令、関数を使います。モジュールクラスが持つ命令や関数を、メンバ関数と呼びます。例では、strtok_getnext() と strtok_isFinished() が、strtok のメンバ関数です。メンバ関数は、#modfunc, #modcfunc で定義されます (これらの使い方は、#deffunc, #defcfunc とほとんど同じ)。
メンバ関数の中では、特別に、メンバ変数を普通の変数と同じように使用できます。
例えば、メンバ関数 strtok_isFinished を:

#modcfunc strtok_isFinished
	return (mlenBuf <= mIndex)

次のように、lineToker に対して使用するとします。

	if ( strtok_isFinished( lineToker ) ) : ...

すると、strtok_isFinished が関数として呼び出されますが、その中にある mlenBuf, mIndex などは、lineToker が持つメンバ変数として扱います。
メンバ命令・関数からは、与えられたインスタンスの中にある各変数を、外にある変数と同じように操作することができる、ということです。

strtok の設計図、インスタンス、アクセス

以上で、いわゆる「モジュール変数機能」の説明はだいたい終了です。

大事な用語が文章の中に散乱してしまっているので、改めて語句を整理しておきます。
なお、基本的にすべて、上大が (C++という言語から持ってきて) 勝手に付けた名称です (公式では決まってないので)。ご容赦ください。
※だって、普通のモジュールとクラス的なモジュールは、異なる名称で呼び分ける必要があるんだもん。

クラスモジュール (Class-Module)
メンバ変数が宣言されているモジュールそのもののこと。
モジュールクラス (Module-Class)
上記の説明で「道具」と呼んだもの。あるいはその設計図のことを指す。
具体的なナイフ1本1本(a knife)に対する、「ナイフ (knives)」のような言い方。
インスタンス (実体, Module-Instance, modinst)
モジュールクラスを実体化したもののこと。
ストレートな言い方をすると、メンバ変数の塊。
必ず、いずれかの変数に格納された状態である。
メンバ変数(Member-Variable)
インスタンスが持つ変数のこと。普通は触れない。
「状態」を保存しておくのに使う。
メンバ命令(Member-Statement)
モジュールクラスが持つ命令のこと。メンバ関数ともいう。
#modfunc で定義する。
第一引数には、そのクラスのインスタンスを受け取る。
メンバ関数(Member-Function)
モジュールクラスが持つ関数のこと。
#modcfunc で定義する (HSP3.2以降のみ)。
第一引数には、そのクラスのインスタンスを受け取る。
コンストラクタ(constructor)
インスタンスが作成された直後に、自動的に呼ばれる命令のこと。
メンバ変数の初期化などを行う。
#modinit で定義する。
デストラクタ(destructor)
インスタンスが破棄される直前に、自動的に呼ばれる命令のこと。
#modterm で定義する。引数は取れない (実際は取れるが、たぶんバグ)
大抵は不要なので、書かないことが多い。

まぁ、あとは上記のサンプルで確認してください。


デバッグウィンドウで

なお、インスタンスの中にあるメンバ変数の値は、標準の DebugWindow からは見えません。
拙作 knowbug なら、見にくいですが、一応表示に対応しています。
+参照:knowbug


おわりに

さて。
ここでの説明は、かなりわかりにくいかと思います。申し訳ない。
いつも通りですが、質問などあれば掲示板までどうぞ。

次回は、モジュールから離れて、Dll関数の使い方についてやる予定です。執筆中……φ(;_;)。

※追記 (2011.10/06)
is finished ではなく has finished の方がいいかもしれない。
(ex) This function returns 'true' if this instance has finished splitting the given (charactor) string.
それと、全体的にわかりにくいので加筆か修正を考えていますが、あまり上手くいかない……。