HSP のループ処理について

repeat や foreach では cnt 変数にループ回数がカウントされていきます。
foreach はモジュール型変数の配列を扱う際に便利だったりするのですが、その内部で repeat や foreach を使ったら cnt が上書きされて処理が正常にならないのではないかと心配になり、実際にテストしてみました。
結論からいうと、 repeat や foreach は入れ子にしてもそれぞれ独立した cnt を参照するので問題ありませんでした。

以下は、ループについて実際にテストした結果とその考察です。
テスト環境は HSP3.5, windows 7 personal 64bit です。

#repeat と foreach の cnt は入れ子にしても大丈夫

```HSP
#module
	#deffunc testRepeat1
		repeat 2
			_value1 = 1
			repeat 3
				_value1 = -1
				logmes "repeat(2) cnt = " + cnt	// 3回呼び出され、 0, 1, 2 が表示される
			loop
			logmes "_value1 = " + _value1	// -1 が表示される。1層目のrepeatと2層目のrepeatでは同じ _value を参照している。
			logmes "repeat(1) cnt = " + cnt			// 1周目は 0 、2周目は 1 が表示される。1 層目の repeat と 2 層目の repeat では独立した cnt をそれぞれ参照している。
		loop
	return
#global
testRepeat1
```

logmes によるログ出力は以下のとおりです。

```console
repeat(2) cnt = 0
repeat(2) cnt = 1
repeat(2) cnt = 2
_value1 = -1
repeat(1) cnt = 0
repeat(2) cnt = 0
repeat(2) cnt = 1
repeat(2) cnt = 2
_value1 = -1
repeat(1) cnt = 1
```

これをみると、 repeat 1 層目と 2 層目の cnt は独立していてそれぞれの値を保っています。
ですが、 repeat ~ loop 間はスコープが変わるわけではなく、 _value1 のようなユーザ定義の静的変数は層に関係なく同じものが参照されます。

# repeat, foreach のネスト回数
repeat, foreach は入れ子にできますが、その階層の数は 31 までです。
　参照：[HSPTV!掲示板](http://hsp.tv/play/pforum.php?mode=pastwch&num=30382)
現在の階層については looplev で確認できます。

```HSP
#module
	#deffunc testRepeatLevel
#define R repeat 1:logmes "looplev = " + looplev:
#define L loop:
	//	1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 
		R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  
		L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  L  
#undef R
#undef L
	return
#global
testRepeatLevel
```

コンソールでは 1 ~ 31 階層までの31回のネストまで実行されたことが表示されています。

```console
looplev = 1
looplev = 2
looplev = 3
(略)
looplev = 29
looplev = 30
looplev = 31

```

それ以降(32番目のR,N)は、Error9「サブルーチンやループのネストが深すぎます」が発生しました。
![2018-08-19_105708.png](https://qiita-image-store.s3.amazonaws.com/0/46088/a2d7aba8-9c5e-91e5-6563-fef6b771413c.png)

#foreachでモジュール型変数の配列を扱う
repeat, foreach は階層が 31 階層までという制限はありますが、 foreach はモジュール型変数の配列を扱う際に便利です。
配列の一部の要素を delmod した際に、それをスキップしてくれます。

```HSP
#module Person id_, name_
	#modinit int _id, str _name
		id_ = _id
		name_ = _name
		logmes "modinit " + serialize(thismod)
	return

	#modterm
		logmes "modterm " + serialize(thismod)
	return

	#modcfunc local serialize
	return "Person," + id_ + "," + name_
#global

#module
	#deffunc testModuleInstanceArray1
		// モジュール型変数の配列の要素に delmod すると要素自体が減るのか、変わらないのかのテスト
		newmod _persons, Person, 1, "test1-1"
		logmes "length(_persons) = " + length(_persons)
		newmod _persons, Person, 2, "test1-2"
		logmes "length(_persons) = " + length(_persons)
		newmod _persons, Person, 3, "test1-3"
		logmes "length(_persons) = " + length(_persons)

		delmod _persons(1)
		logmes "length(_persons) = " + length(_persons)	// 3 が表示される。delmodで配列の要素自体は除去されず、各変数への要素番号は変わらない。

		foreach _persons	// delmod された要素番号 1 はスキップされ、ループ内の処理は2回行われ cnt は 0, 2 となる。
			logmes "(" + cnt + ")" + serialize@Person(_persons(cnt))
		loop
		//logmes "(" + 1 + ")" + serialize@Person(_persons(1))	// delmod された要素の関数を呼び出すと、「モジュール型変数の指定が無効です」エラーになる。
	return
#global
testModuleInstanceArray1
```

結果は次のようになります。

```console
modinit Person,1,test1-1
length(_persons) = 1
modinit Person,2,test1-2
length(_persons) = 2
modinit Person,3,test1-3
length(_persons) = 3
modterm Person,2,test1-2
length(_persons) = 3
(0)Person,1,test1-1
(2)Person,3,test1-3
```

同じ変数に newmod を繰り返すと要素が1つずつ追加されていきます。
一部の要素を delmod した場合も、配列のサイズや要素の順番には変化はありません。
foreach では delmod された要素はスキップされます。
例えば、上の例では foreach 内の処理は2回呼び出され、delmod された 1 番目をのぞいた 0, 2 の cnt の値の処理が行われます。
当然ながら _persons(1) のように delmod した要素を使おうとすると「モジュール型変数の指定が無効です」エラーになります。

#for ~ next, while ~ wend はマクロ
HSPエディタのヘルプ > HSP検索キーワードなどで表示できる HSP Document Library で for を調べると、マクロによって実現されていると書かれています。
そのため、 repeat, foreach で使えた break, continue は使えず、そのかわりに _break, _continue でマクロループを脱出できます。

#サブルーチンは511回までネストできる
サブルーチンの階層は sublev システム変数で確認できます。
次のテストコードで何階層まで呼べるか確認しました。

```HSP
*sub1
	logmes "sublev = " + sublev
	gosub *sub1
```
結果のログ出力は以下です。

```console
sublev = 0
sublev = 1
sublev = 2
...（略）...
sublev = 508
sublev = 509
sublev = 510
```

この直後にError29「スタック領域のオーバーフローです」がダイアログで表示されました。

![2018-08-21_141726.png](https://qiita-image-store.s3.amazonaws.com/0/46088/0f494efd-f015-7264-b559-40db2de61c1e.png)



#参考サイト
- [HSPTV!掲示板](http://hsp.tv/play/pforum.php?mode=pastwch&num=30382)