hspcmp(exe版)のコードを読む 3　～tagstack.cpp/.h編～

# 概要  
このシリーズはHSPの`.ax`ファイルの作成をするhspcmpを読んでいくものです。
わかったことをどんどん書き込んでいくスタイルでいきます。
間違いに気づいた方や、加筆すべきポイントを見つけた人はコメントで指摘してください。

あとここに掲載されたコードは
[Hot Soup Processor (HSP) / OpenHSP Copyright (c) 1997-2020, onion software/onitama in collaboration with Sencha, Yume-Yume Yuuka, Y-JINN, chobin, Usuaji, Kenji Yuukoku, puma, tom, sakura, fujidig, zakki, naznyark, Lonely Wolf, Shark++, HyperPageProject, Chokuto, S.Programs, Yuki, K-K, USK, NGND001, yoshis, naka, JET, eller, arue, mjhd_otsuka, tds12 All rights reserved.](https://github.com/onitama/OpenHSP)
から一部改変したものです。
必要に応じて元のコードからインデントを調整したり、コメントや空行を省いています。

あと、これは2023/06/23時点のコードになります。

:::note 
以前別名義で投稿していたものをほぼそのまま再投稿したものです。  
またこれらのコードはOpenHSP 3.6のコードに基づいているため、3.6正式版や最新(3.7β)のものと異なる可能性があることに注意してください。
:::

# tagstack.cpp(tagstack.h)
>\#define特殊展開マクロ用スタック
引用: [OpenHSPのチュートリアル - HSP開発wiki](https://wiki.hsp.moe/OpenHSP%E3%81%AE%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB.html)

だそうです。
つまり、プリプロセスの際に`%t`やら`%n`やらを処理するために使われます。
ここでは使用されたスタックの一つ一つを「タグ」として管理し、処理しています。

## #difine一覧
- TAGSTK_MAX 256
    - 全体で積めるスタック数の最大値
- TAGSTK_SIZE 124
    - スタックの内容の文字数の最大値。ただし終端文字も含みます。
- TAGSTK_TAGMAX 256
    - 登録できるタグの最大値
- TAGSTK_TAGSIZE 56
    - タグ名の文字数の最大値。ただし終端文字も含みます。

## TAGINF構造体
タグの本体です

- char name[TAGSTK_TAGSIZE]
    - タグ名
- int check
    - タグのスタックが解決されているかどうか。
- int uid
    - [GetTagUniqueNameメソッド](#gettaguniquenameメソッド)で何回呼び出されたか。
 
## TAGDATA構造体
タグごとの1スタックの内容を表します。

- int tagid
    - タグのID
- char data[TAGSTK_SIZE]
    - スタックの内容

## CTagStackクラス
これがtagstack.cpp/.hの本体です。
### フィールド
- TAGINF mem_tag[TAGSTK_TAGMAX]
    - タグの管理配列。
    - タグIDとはこの配列で何番目に登録されているかを示します。
- TAGDATA mem_buf[TAGSTK_MAX];
    - スタック本体。ここにすべてのスタックの中身が登録されます。
- char tagerr[8]
    - エラーメッセージ
- char tagent
    - 次の処理で使われる`mem_tag`のインデックス
    - タグがどれだけ登録されたかを示します。
- char lastidx
    - 次の処理で使われる`mem_buf`のインデックス
    - すべてのタグを含め、全部でどれだけのスタックが積まれたのかを表します。
- char gcount
    - [GetTagUniqueNameメソッド](#gettaguniquenameメソッド)で何回ありえないタグIDが指定されたか。

### コンストラクタ
一種類しかありません。ただフィールド変数を初期化しているだけです。
#### 引数
なし
#### コードを見てみる
```c++:tagstack.cpp(192～207行目)
CTagStack::CTagStack()
{
	int i;
	for(i=0;i<TAGSTK_TAGMAX;i++) {
		mem_tag[i].name[0] = 0;
		mem_tag[i].uid = 0;
	}
	for(i=0;i<TAGSTK_MAX;i++) {
		mem_buf[i].data[0] = 0;
		mem_buf[i].tagid = -1;
	}
	tagent = 0;
	lastidx = 0;
	gcount = 0;
	strcpy( tagerr, "%err%" );
}
```
`mem_buf`の`tagid`は`-1`で、`tagerr`は`%err%`で初期化され、これ以外は`0`で初期化されます。
ただし、`mem_tag`の`check`は初期化されません。([StackCheckメソッド](#stackcheckメソッド)で初期化されるため)

### StrCmpメソッド
指定した文字列が等しければ`-1`を、等しくなければ`0`を返します。
ただし、一致するのは文字コードが同じときのみです。（つまり大文字小文字・全角半角が区別されます）
#### 引数
- char *str1
    - 比較する文字列その1
- char *str2
    - 比較する文字列その2

#### 使われている変数
- int ap
    - いま何文字目を比較しているのかを表す
- char as
    - いま比較されている`str1`の文字 

#### コードを見てみる
```c++:tagstack.cpp(15～29行目)
int CTagStack::StrCmp( char *str1, char *str2 )
{
	int ap;
	char as;
	ap=0;
	while(1) {
		as=str1[ap];
		if (as!=str2[ap]) return 0;
		if (as==0) break;
		ap++;
	}
	return -1;
}
```
`while`で全部の文字の比較が終わるか、一致しない文字が現れるまでループしていだけです。

### SearchTagIDメソッド
指定したタグ名のタグIDを検索して、あればその値を、なければ`-1`を返します。
#### 引数
- char *tag
    - タグ名

#### 使われている変数
- int i
    - ループカウンタ

#### コードを見てみる
```c++:tagstack.cpp(32～42行目)
int CTagStack::SearchTagID( char *tag )
{
	int i;
	if ( tagent==0 ) return -1;
	for(i=0;i<tagent;i++) {
		if ( StrCmp( mem_tag[i].name, tag )) return i;
	}
	return -1;
}
```
初めにそもそもタグが一つでも登録されているのかを調べ、なければ`-1`を返します。
つぎに、タグの個数(`tagent`)の数だけループを回して[StrCmpメソッド](#strcmpメソッド)で`tag`と一致する登録されたタグ名(`mem_tag[i].name`)を検索して、一致するものがあればそのID(`i`)を、なければ`-1`を返します。

### RegistTagIDメソッド
指定されたタグ名のタグを登録し、そのタグのIDを返します。
#### 引数
- char *tag
    - 登録するタグ名

#### 使用されている変数
- int i
    - タグID
- int len
    - タグ名の文字数。ただし終端文字も含みます。

#### コードを見てみる
```c++:tagstack.cpp(45～55行目)
int CTagStack::RegistTagID( char *tag )
{
	int i,len;
	if ( tagent>=TAGSTK_TAGMAX ) return -1;
	i = tagent; tagent++;
	len = strlen( tag );if ( len>=TAGSTK_TAGSIZE ) tag[TAGSTK_TAGSIZE-1]=0;
	strcpy( mem_tag[i].name, tag );
	return i;
}
```
初めに、登録するタグが最大個数(`TAGSTK_TAGMAX`)を超えないことを確認して、タグIDを設定して`tagent`を`1`増やします。
次に、タグ名の文字数(終端文字も含む)が最大値(`TAGSTK_TAGSIZE`)を超えるかどうか確認して、超えるのならばそこで`\0`を挿入します。
最後に、`mem_tag[i].name`にタグ名をコピー(登録)して、タグIDを返します。

### GetTagUniqueNameメソッド
指定されたタグのタグ名を含んだ新たな文字列を生成します。
生成される文字列は、`_%s_%04x`に従います。(`s`はタグ名または`TagErr`、`x`は適当な数)
#### 引数
- int tagid
    - 指定するタグのタグID
- char *outname
    - 生成された文字列が書き込まれるポインタ

#### 使われている変数
- TAGINF *t
    - 指定されたタグの情報

#### コードを見てみる
```c++:tagstack.cpp(58～69行目)
void CTagStack::GetTagUniqueName( int tagid, char *outname )
{
	TAGINF *t;
	if (( tagid < 0 )||( tagid >= TAGSTK_TAGMAX )) {
		sprintf( outname,"TagErr%04x",gcount++ );
	} else {
		t = &mem_tag[tagid];
		sprintf( outname,"_%s_%04x", t->name, t->uid++ );
	}
}
```
一つ目の分岐では、ありえないタグIDが指定されたときで、新たな文字列を作り出しています。
二つ目の分岐では、指定されたタグの情報を`t`に取り出して、タグ名を用いて新たな文字列を作り出しています。


### GetTagIDメソッド
指定したタグ名のタグIDを返します。タグが登録されていなければ、登録をしてそのタグIDを返します。ただし、登録できなかった場合は`-1`を返します。
#### 引数
- char *tag
    - 指定するタグ名

#### 使用されている変数
- int i
    - 返すタグID

#### コードを見てみる
```c++:tagstack.cpp(72～80行目)
int CTagStack::GetTagID( char *tag )
{
	int i;
	i = SearchTagID( tag );
	if ( i<0 ) { i = RegistTagID( tag ); }
	return i;
}
```
初めに、[SearchTagIDメソッド](#searchtagidメソッド)でタグ名からタグIDを取得し、取得できなければ[RegistTagIDメソッド](#registtagidメソッド)でタグを登録してそのタグIDを取得します。
最後に、取得したタグIDを返します。

### GetTagNameメソッド
指定したタグIDからタグ名を取得します。ありえないタグIDが指定されると`tagerr`が返されます。
#### 引数
- int tagid
    - 指定するタグID

#### コードを見てみる
```c++:tagstack.cpp(83～89行目)
char *CTagStack::GetTagName( int tagid )
{
	if (( tagid < 0 )||( tagid >= TAGSTK_TAGMAX )) return tagerr;
	return mem_tag[tagid].name;
}
``` 
ただありえないタグIDをはじいて、タグIDから`mem_tag[tagid].name`でタグ名を取得しているだけです。

### StackCheckメソッド
全てのスタックが解決されていれば`0`を返し、指定した変数の値を`\t`に書き換えます。
解決されていないスタックがあれば、そのスタックが存在するタグの個数を返し、指定した変数の値をそのスタックの情報に書き換えます。
#### 引数
- char *res
    - 書き換えられる変数

#### 使われている変数
- int i
    - ループ中で検証される`mem_buf`のインデックス
- int n
    - 解決されていないスタックが存在するタグの個数
- TAGDATA *t
    - ループ中で検証されるスタックデータ

#### コードを見てみる
```c++:tagstack.cpp(92～101行目)
int CTagStack::StackCheck( char *res )
{
	int i,n;
	TAGDATA *t;
	strcpy ( res, "\t" );
	for(i=0;i<TAGSTK_TAGMAX;i++) { mem_tag[i].check = 0; }
	if ( lastidx<1 ) return 0;
```
ここでは、`res`を`\t`で書き換えて、全タグ(のスタック)が解決されたことを示すように変更しています。
あと、`lastidx < 1`の時は全スタックが解決されているので`0`を返します。

```c++:tagstack.cpp(102～117行目)
	i = lastidx;
	while(1) {
		i--; if ( i<0 ) break;
		t = &mem_buf[i];
		if ( t->tagid >= 0 ) mem_tag[ t->tagid ].check = -1;
	}
	n=0;
	for(i=0;i<TAGSTK_TAGMAX;i++) {
		if ( mem_tag[i].check ) {
			if ( n ) strcat( res, ", " );
			strcat( res, mem_tag[i].name );
			n++;
		}
	}
	return n;
}
```
ここでは、全スタックが解決されたかどうかを調べています。
初めに、スタックが登録されている`mem_buf`の一番最後のインデックスを`i`に代入します。
次に、`while`で解決されていないスタック(`TAGDATA->tagid >= 0`)のタグの`check`を`-1`に設定します。
あと、`for`で解決されてないスタックが存在するタグ(`mem_tag[i].check != 0`)のタグ名を`res`に書き込み、`n++`をします。
最後に、解決されていないスタックが存在するタグ数を返します。

### PushTagメソッド
指定したタグのスタックを積み、積んだスタックが全体で何番目のスタックなのかを返します。
#### 引数
- int tagid
    - 指定するタグのタグID
- char *str
    - 積むスタックの内容

#### 使われている変数
- int i
    - 積むスタックが全体で何番目のスタックなのか
- int len
    - 積むスタックの内容の文字数。ただし終端文字含みます。
- TAGDATA *t
    - 積むスタックのデータ

#### コードを見てみる
```c++:tagstack.cpp(120～127行目)
int CTagStack::PushTag( int tagid, char *str )
{
	int i,len;
	TAGDATA *t;
	if (( tagid < 0 )||( tagid >= TAGSTK_TAGMAX )) return -1;
	if ( lastidx >= TAGSTK_MAX ) return -1;
```
ここでは、ありえないタグIDが指定されていないことやスタックを最大まで登録していないかを確認し、されていたら`-1`を返します。

```c++:tagstack.cpp(128～134行目)
	i = lastidx; lastidx++;
	t = &mem_buf[i];
	t->tagid = tagid;
	len = strlen( str );if ( len>=TAGSTK_SIZE ) str[TAGSTK_SIZE-1]=0;
	strcpy( t->data, str );
	return i;
}
``` 
ここでは、初めに、使う`mem_buf`のインデックスを`i`に代入し、すでに初期化で確保されている`mem_buf`の`i`番目のポインタを`t`に代入します。
また、次に使う`mem_buf`のインデックスを`lastidx`に代入します。
次に、`t->tagid`に`tagid`を代入し、積むスタックの内容の文字数(終端文字含む)が最大値(`TAGSTK_SIZE`)を超えていないことを確認してから`t->data`に`str`をコピー(登録)します。ここで指定したタグのスタックを積んでいます。
最後に、積んだスタックが全体で何番目のスタックなのかを返します。

### PopTagメソッド
指定されたタグのスタックをPOPして、その値を返します。
#### 引数
- int tagid
    - 指定するタグのタグID

#### 使われている変数
- int i
    - ループ中で検証される`mem_buf`のインデックス
- TAGDATA *t
    - ループ中で検証されるスタックデータ
- char *p
    - POPするスタックの内容

#### コードを見てみる
```c++:tagstack.cpp(137～146行目)
char *CTagStack::PopTag( int tagid )
{
	int i;
	TAGDATA *t;
	char *p;
	if (( tagid < 0 )||( tagid >= TAGSTK_TAGMAX )) return NULL;
	if ( lastidx<1 ) return NULL;
	i = lastidx;
```
ここまでで、指定されたタグIDがありえないものかどうかや、そもそもスタックが積まれているのかを確認して、問題があれば`NULL`を返します。
そのあと、`lastidx`の値が`i`に代入されます。

```c++:tagstack.cpp(147～152行目)
	while(1) {
		i--; if ( i<0 ) return NULL;
		t = &mem_buf[i];
		if ( t->tagid == tagid ) break;
	}
	p = t->data;
```
ここでは、すべてのタグのスタックを総当たりで検証しています。
まず、検証するスタックを`t`に`mem_buf`から取り出します。
そして、`t`が指定されたタグのスタックであるかどうかを検証して一致するとループから抜け、`p`にそのスタックの内容(`t->data`)を代入します。

```c++:tagstack.cpp(153～162行目)
	t->tagid = -1;
	i = lastidx - 1;
	while(1) {
		if ( i<0 ) break;
		if ( mem_buf[i].tagid != -1 ) break;
		lastidx = i;
		i--;
	}
	return p;
}
```
ここでは、POPしたスタックの無効化と次の処理で使う`mem_buf`のインデックスを設定しています。
初めに、`t->tagid = -1`でスタックを無効化して、スタックが登録されている`mem_buf`の一番最後のインデックスを`i`に代入します。
次に、ループで一番最後からのPOP済みのスタック分だけ`lastidx`を巻き戻します。
最後に`p`(POPするスタックの内容)を返します。

### LookupTagメソッド
指定されたタグの指定されたスタックの内容を読み取りその値を返すか、指定されたスタックが存在しなければ`NULL`を返します。
ただし、POPしません。
#### 引数
- int tagid
    - 指定するタグのタグID
- int level
    - 指定するスタックの層(`0`～`9`)
    - つまるところ、`%p`の後ろの値

#### 使われている変数
- int i
    - ループ中で検証される`mem_buf`のインデックス
- int lv
    - `level`の値が代入されます。
- TAGDATA *t
    - ループ中で検証されるスタックデータ


#### コードを見てみる
```c++:tagstack.cpp(165～175行目)
char *CTagStack::LookupTag( int tagid, int level )
{
	int i,lv;
	TAGDATA *t;
	if (( tagid < 0 )||( tagid >= TAGSTK_TAGMAX )) return NULL;
	if ( lastidx<1 ) return NULL;
	lv = level;
	i = lastidx;
```
ここまでで、指定されたタグIDがありえないものかどうかや、そもそもスタックが積まれているのかを確認して、問題があれば`NULL`を返します。
そのあと、どの層のスタックを読みだすのかを`lv`に代入し、`lastidx`の値が`i`に代入されます。

```c++:tagstack.cpp(176～185行目)
	while(1) {
		i--;if ( i<0 ) return NULL;
		t = &mem_buf[i];
		if ( t->tagid == tagid ) {
			if ( lv==0 ) break;
			lv--;
		}
	}
	return t->data;
}
```
ここではすべてのタグのスタックを総当たりで検証しています。
まず、検証するスタックを`t`に`mem_buf`から取り出します。
そして、`t`が指定されたタグのスタックであるかどうかを確かめて、それが指定された層のスタックであればループを抜け`t->data`(スタックの内容)を返し、そうでなければ`lv--`をしています。

なぜこの処理でよいのかというと、このループではスタックの上層から読みだしていっており、指定される値(`level`)は上から〇番目のスタックを表すので、指定されたタグのスタックを検証するたびに`lv`(`level`)の値を引いていくことで、`lv`が`0`になった時の`t`が読み出したいスタックになるからです。

# 次は
次回は、token.hを解説します。(投稿は未定)