hspcmp(exe版)のコードを読む 1　～main.cppとlabel.cpp/.hとhspcmd.cpp編～

# 概要  
このシリーズは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β)のものと異なる可能性があることに注意してください。
:::


# main.cpp
hspcmp.exeのエントリポイントである[`main`メソッド](#mainメソッド)を含むファイルです。
では見ていきましょう。
### usage1メソッド
```c++:main.cpp(32～53行目)
static void usage1( void )
{
static 	char *p[] = {
	"usage: hspcmp [options] [filename]",
	"       -o??? set output file to ???",
	"       -d    add debug information",
	"       -p    preprocessor only",
	"       -c    HSP2.55 compatible mode",
	"       -i    input UTF-8 source code",
	"       -u    output UTF-8 strings",
	"       -w    force debug window on",
	"       -e?   execute/view .ax runtime",
	"       -r    execute runtime with result",
	"       -s    output string map",
	"       -h??? output command help",
	"       --syspath=??? set system folder for execute",
	"       --compath=??? set common path to ???",
	NULL };
	int i;
	for(i=0; p[i]; i++)
		printf( "%s\n", p[i]);
}
```
まんま、コマンドプロンプト等にhspcmd.exeの使い方を表示させるだけのメソッドですね。
### mainメソッド
先ほども書きましたが、これがhspcmp.exeのエントリポイントになります。
ここで実行時に渡された引数の解析をして、プリプロセスや`.ax`ファイルを作成するメソッド等を呼び出しています。
#### 引数
- int argc
    - 与えられた引数の数
- char *argv[]
    - 与えられた引数

#### 使用されている変数
- char a1,a2,a3
    - 引数を確認する際に引数の最初3文字がそれぞれにコピーされます。
- char fname[HSP_MAX_PATH]
    - ここに[filename]が代入されます。
- char oname[HSP_MAX_PATH];
    - ここに`-o???`オプションの???が代入されます。また、`-o???`が指定されなければ`fname`のファイル名が使われます。
- char compath[HSP_MAX_PATH];
    - ここに`--compath=???`オプションの???が代入されます。また、`--compath=???`オプションが指定されなければ'common/'が代入されます。
- char syspath[HSP_MAX_PATH];
    - ここに`--syspath=???`オプションの???が代入されます。今のところ`-h`オプション使用時にしか参照されていません。
- char helpkey[256];
    - ここに`-h???`オプションの???が代入されます。
- CHsc3 *hsc3=NULL;
    - これがコンパイラ本体です。

#### コードを見てみる
最初の変数を定義している部分は飛ばします。

```c++:main.cpp(72～86行目)
if (argc<2) { usage1();return -1; }
st = 0; ppopt = 0; cmpopt = 0; utfopt = 0; pponly = 0; strmap = 0; hsphelp = 0;
execobj = 0;
fname[0]=0;
fname2[0]=0;
oname[0]=0;
syspath[0]=0;
helpkey[0] = 0;
#ifdef HSPLINUX
    strcpy( compath,"common/" );
#else
    strcpy( compath,"common\\" );
#endif
```
ここでは、変数を初期化していますね。
ちなみに、`HSPLINUX`でWindows版とLinux版で使用するコードを分けているようです。

```c++:main.cpp(88～142行目)
for (b=1;b<argc;b++) {
	a1=*argv[b];a2=tolower(*(argv[b]+1));
#ifdef HSPLINUX
	if (a1!='-') {
#else
	if ((a1!='/')&&(a1!='-')) {
#endif
		strcpy(fname,argv[b]);
	} else {
		a3=tolower(*(argv[b]+2));
		if (strncmp(argv[b], "--compath=", 10) == 0) {
			strcpy( compath, argv[b] + 10 );
			continue;
		}
		if (strncmp(argv[b], "--syspath=", 10) == 0) {
			strcpy( syspath, argv[b] + 10 );
			continue;
		}
		switch (a2) {
		case 'c':
			ppopt |= HSC3_OPT_NOHSPDEF; break;
		case 'p':
			pponly=1; break;
		case 'd':
			ppopt |= HSC3_OPT_DEBUGMODE; cmpopt|=HSC3_MODE_DEBUG; break;
		case 'i':
			ppopt |= HSC3_OPT_UTF8IN; utfopt=1; cmpopt|=HSC3_MODE_UTF8; break;
		case 'u':
			utfopt=1; cmpopt|=HSC3_MODE_UTF8; break;
		case 'w':
			cmpopt|=HSC3_MODE_DEBUGWIN; break;
		case 's':
			strmap = 1; cmpopt |= HSC3_MODE_STRMAP; break;
		case 'o':
			strcpy(oname, argv[b] + 2);
			break;
		case 'e':
			execobj = 1;
			if ( a3=='0' ) execobj|=8;
			break;
		case 'r':
			execobj = 2;
			if ( a3=='0' ) execobj|=8;
			break;
		case 'h':
			hsphelp = 1;
			strcpy(helpkey, argv[b] + 2);
			break;
		default:
			st=1;break;
		}
	}
}
if (st) { printf("Illegal switch selected.\n");return 1; }
```
ここで引数の解析をしています。
`for`の一行目で`a1`,`a2`に引数(`b`個目)の最初の2文字をコピーして、二行目で`a1`が`-`か`/`でなかったら引数(`b`個目)を`fname`にコピーします。
そうでなかったら、3文字目を`a3`にコピーして`a2`で`switch`して変数の中身を設定しているようです。
それでどれにも引っかからない引数が与えられた場合は`st`に1を設定して`for`の後の`if`文でエラーをコマンドプロンプト等に出力してプログラムを終了しています。

```c++:main.cpp(144～155行目)
//		help main
if (hsphelp) {
	int res;
	HspHelpManager hman;
	strcat(syspath, "hsphelp");
	res = hman.initalize(syspath);
	if (res == 0) {
		res = hman.searchIndex(helpkey);
	}
	puts(hman.getMessage());
	return res;
}
```
ここで`-h???`オプションの処理をしているようです。
おそらく???で指定した命令などが検索できるようになるのでしょう。(3.6β3からできるようになると思われます)

```c++:main.cpp(157～169行目)
if (fname[0]==0) { printf("No file name selected.\n");return 1; }

if (oname[0]==0) {
	strcpy( oname,fname ); cutext( oname );
	if (strmap) {
		addext(oname, "strmap");
	}
	else {
		addext(oname, "ax");
	}
}
strcpy( fname2, fname ); cutext( fname2 ); addext( fname2,"i" );
addext( fname,"hsp" );			// 拡張子がなければ追加する
```
ここで、`.ax`ファイルにコンパイルする`.hsp`ファイルが指定されているかをチェックして、`-o???`オプションが指定されていなければ出力ファイル名を設定しています。
最後の部分では、プリコンパイルされたファイルを出力する際のファイル名を設定しているようです。

```c++:main.cpp(171～175行目)
//		call main
hsc3 = new CHsc3;
hsc3->SetCommonPath( compath );
```
ここでコンパイラが定義されたクラスを初期化して、commonフォルダ[^1]のパスを設定しています。	

```c++:main.cpp(177～223行目)
if ( execobj ) {
	//		ランタイムを起動	
	char execmd[4096];
	st = hsc3->GetRuntimeFromHeader( fname, oname );
	if ( st != 1 ) {
		strcpy( oname, "hsp3.exe" );			// デフォルトランタイム
	}
#ifdef HSPLINUX
	cutext( oname );
	if ( execobj & 8 ) {
		printf("Runtime[%s].\n",oname);
	} else {
		int result;
		printf("Execute from %s runtime[%s](%d).\n",fname,oname,execobj);
		sprintf(execmd,"%s./%s %s",syspath,oname,fname);
		//sprintf(execmd,"%s./%s %s >%s.hspres",syspath,oname,fname,syspath);
	
		result = system(execmd);
		if ( WIFEXITED(result) ) {
			result = WEXITSTATUS(result);
			printf("hsed: Process end %d.\n",result);
			if ( execobj==2 ) {
				if ( result != 0 ) {			// エラーがあった時
					while(1) {
						result = getchar();
						if (( result == 13 )||( result == 10 )) break;
					}
				}
			}
		} else {
			printf("hsed: Process error.\n");
		}
	}
#else
	if ( execobj & 8 ) {
		printf("Runtime[%s].\n",oname);
	} else {
		sprintf( execmd, "%s %s", oname, fname );
		st = WinExec( execmd, SW_SHOW );
		if ( st < 32 ) {
			printf("Runtime file missing.\n");
		}
	}
#endif
}
```
ここは、`-e`オプションか`-r`オプションを指定した際に実行されます。
[filename]で指定されたファイルをいくつかのチェックをしたあと、`hsp3`にコンパイルさせるだけのようです。
しかし、このときに与えるファイルはどうやらコンパイル済みの`.ax`ファイルでないとダメそうです。
`.hsp`ファイルを指定しているときはエラー吐かれます。(私の環境ではそうでした)


```c++:main.cpp(223～235行目)
else {
	//		通常のコンパイル
	st = hsc3->PreProcess( fname, fname2, ppopt, fname );
	if (( pponly == 0 )&&( st == 0 )) {
		st = hsc3->Compile( fname2, oname, cmpopt );
	}
	puts( hsc3->GetError() );
	hsc3->PreProcessEnd();
}
if ( hsc3 != NULL ) { delete hsc3; hsc3=NULL; }
return st;
```
ここでは、`.hsp`ファイルのプリプロセスと`.ax`ファイルへのコンパイルを行っています。
ただし、`-p`オプションが有効な場合はコンパイルされません。
最後にコンパイラ(`hsc3`)を開放してプログラム終了です。

# label.cpp/.h
これについては[OpenHSPのチュートリアル/CLabelを読む](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%EF%BC%8FCLabel%E3%82%92%E8%AA%AD%E3%82%80.html)を読んでね！
と言いたいんですけど、[OpenHSPのチュートリアル/CLabelを読む](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%EF%BC%8FCLabel%E3%82%92%E8%AA%AD%E3%82%80.html)で使われているコードが幾分か古いので今と違う部分の解説をします。
## LABOBJ構造体
識別子１つの情報を管理する構造体です。
### フィールド
違うところだけ

- short init
    - 使われているかどうかを示す。使われていなければ`LAB_INIT_NO`(0)で使われていれば`LAB_INIT_DONE`(1)。
- short typefix
    - codegen.cppにて使われているそうです。
- char const* def_file
    - 識別子が定義されているファイル名。
- int def_line
    - 識別子が定義されているファイルで何行目に定義されているか。

## CLabelクラス
これがlabel.cpp/.hの本体です。
### フィールド
ここも違うところだけ

- LabelMap labels;
    - これは識別子を登録した際に、その登録した識別子名とその識別子のidをセットにした`std::multimap<std::string, int>`です。
- FileNameSet filenames;
    - ファイル名のプール。(`LABOBJ`の`def_file`にポイントされている)

### Registメソッド
識別子を`mem_lab`フィールドに登録しています。引数の違いで2種類あります。
まず一つ目の方を見ていきましょう
#### 引数(一つ目の方)
- char *name
    - 登録する識別子名
- int type
    - 登録する識別子のタイプ(`LAB_TYPE_*`)
- int opt
    - 登録する識別子に対応付けられた整数値(`LABOBJ`の`opt`）

#### コードを見てみる(一つ目)
```c++:label.cpp(97行目)
return Regist(name, type, opt, NULL, -1);
```
どうやらひとつめの`Regist`は、定義されているファイルがない識別子(HSPの命令など)を登録する際に使うようですね。
#### 引数(二つ目の方)
- char *name, int type, int opt
    - 一つ目と一緒
- char const *filename
    - 登録する識別子が定義されているファイル名
- int line
    - 登録する識別子が定義されているファイルで、識別子が定義されている箇所ある行。

#### 使用されている変数(二つ目の方)
- int label_id
    - 登録される識別子のid(登録されるときの`cur`フィールド)。
- LABOBJ *lab = &mem_lab[cur++]
    - 登録される識別子のデータ。これに`mem_lab[cur]`のポインタを当てて登録する。

#### コードを見てみる(二つ目)
最後の方以外はほぼ一緒なので最後の部分以外を省略します。
```c++:label.cpp(132～135行目)
SetDefinition(label_id, filename, line);
labels.insert(std::make_pair(lab->name, label_id));
return label_id;
```
ここでは、`SetDefinition`メソッドで`label`指定した`mem_lab`の`def_file`と`def_line`を設定しています。
つぎに、`labels.insert`で登録される識別子名とそのidを`pair`にして`labels`フィールドに登録しています。
最後に`label_id`を返しておしまいです。

### Searchメソッド
引数として受け取ったものを`labels`フィールドから検索してヒットしたら`labels`でセットになっている`int`の値を返します。
見つからなかったら、-1を返します。  
#### 引数
- char *oname
    - 検索する文字列

#### コードを見てみる
```c++:label.cpp(217～229行目)
if (cur==0) return -1;

int hash = StrCase( oname );
if (*oname != 0) {
	std::pair<LabelMap::iterator, LabelMap::iterator> r = labels.equal_range( oname );
	for(LabelMap::iterator it = r.first; it != r.second; ++it) {
		LABOBJ *lab = mem_lab + it->second;
		if ( lab->flag >= 0 ) {
			return it->second;
		}
	}
}
return -1;
```
最初の`if`文で識別子が一つでも登録されたかを確認して、なかったら-1を返していますね。
また、`for`文で`multimap`の機能を使って検索していますね。
ちなみに`int hash`は使われていませんが、`StrCase`メソッドで`oname`を小文字に変換しています。

### SearchLocalメソッド
与えられた「グローバル名」と「ローカル名」の識別子が存在するかどうかを調べるメソッドです。
#### 引数
- char *global_name
    - 検索する「グローバル名」
- char *local_name
    - 検索する「ローカル名」

#### 使用されている変数
- int i
    - [`Search`メソッド](#searchメソッド)の結果が代入される。

#### コードを見てみる
```c++:label.cpp(239～247行目)
int i = Search(local_name);
if (i >= 0 && !mem_lab[i].eternal) return i;
i = Search(global_name);	
if (i >= 0 && mem_lab[i].eternal) return i;
return -1;
```
上2行で「ローカル名」を検索して、次の2行で「グローバル名」を検索しています。
検索に引っかかったやつがローカルかグローバルかは`if`文の`mem_lab[i].eternal`で見分けているようです。

### AddReferenceメソッド
指定されたidの`mem_lab`の`ref`をインクリメントするだけです。
こうすることで指定されたidの識別子の参照回数が一回増えたことになります。

### GetReferenceメソッド
指定されたidの識別子の参照回数を取得します。
#### 引数
- int id
    - 参照する`mem_lab`(識別子のデータ)を指定する。

#### 使用されている変数
- int total
    - 参照回数を示す。
- LABREL *rel
    - 指定された`mem_lab`の`rel`が代入される。
- LABOBJ *lab
    - 指定された`mem_lab`が代入される。

#### コードを見てみる
変数の定義部分は飛ばします。

```c++:label.cpp(699～708行目)
total = lab->ref;
rel = lab->rel;
if ( rel != NULL ) {
	while(1) {
		total += GetReference( rel->rel_id );
		if ( rel->link == NULL ) break;
		rel = rel->link;
	}
}
return total;
```
ここでは、「指定した識別子が別の識別子に参照されていれば、参照している識別子の参照回数を`total`に加算する」を繰り返すことで、すべての参照回数を調べています。
逆に、参照されていなければ(つまり`rel`がnull)であれば 0 がtotalに代入されることになります。

### SearchRelationメソッド
第一引数で指定したidの識別子が、第二引数で指定したidの識別子に参照されているかどうかを調べます。
参照されていれば1が、参照されていなければ0が返されます。
#### 引数
- int id
    - 検索対象の識別子のid
- int rel_id
    - `id`の識別子に参照されているかもしれない識別子のid
 
#### 使用されている変数
- LABREL *tmp;
- LABOBJ *lab
    - 検索対象の`mem_lab[id]`(識別子のデータ)
 
#### コードを見てみる
変数定義部分とコメントは省略します。

```c++:label.cpp(719～726行目)
tmp = lab->rel;
if ( tmp == NULL ) return 0;
while(1) {
	if ( tmp->link == NULL ) break;
	if ( tmp->rel_id == rel_id ) return 1;
	tmp = tmp->link;
}
return 0;
```
ここでは、`label.rel`の参照先がなくなる(つまり`tmp`がnull)になるか、`rel_id`の識別子に参照されていることがわかるまで繰り返しています。
`label.rel`の参照先がなくなる(つまり`tmp`がnull)になると 0 が返ります。

### AddRelationメソッド
二種類ありますが、第一引数で指定した識別子に第二引数で指定したidの識別子が参照しているという情報を加えるだけです。
#### 引数(一つ目の方)
- int id
    - `rel`が指定される識別子のid
- int rel_id
    - 指定される`rel`の`LABREL->rel_id`に代入されるやつ

#### 使用されている変数(一つ目の方)
- LABOBJ *lab
    - `mem_lab[id]`のポインタが代入されます。
- LABREL *rel
    - `lab->rel`に代入される`LABREL`です。
- LABREL *tmp
    - 一時的に`LABREL`が代入されます。

#### コードを見てみる(一つ目の方)
変数定義部分は飛ばします。

```c++:label.cpp(737～753行目)
if ( id == rel_id ) return;				// 循環するようなデータは登録しない
rel = (LABREL *)ExpandSymbolBuffer( sizeof(LABREL) );
rel->link = NULL;
rel->rel_id = rel_id;
LABOBJ *lab=&mem_lab[id];
if ( lab->rel == NULL ) {
	lab->rel = rel;
	return;
}
tmp = lab->rel;
while(1) {
	if ( tmp->link == NULL ) break;
	tmp = tmp->link;
}
tmp->link = rel;
```
最初に識別子が識別子自身を参照する場合をはじいています。
つぎに、設定する`LABREL`となる`rel`を作成します。
最後に、`lab->rel`がnullかどうかを判断して、nullでなければそのまま`rel`を代入して、そうでなければ`lab->rel`を最後まで参照して最後に`rel`を代入します。

#### 引数(二つ目の方)
- char *name
    - `rel`が指定される識別子名
- int rel_id

#### 使用されている変数(二つ目の方)
- int i
    - `Search`メソッドの結果が代入されます。

#### コードを見てみる(二つ目の方)
```c++:label.cpp(759～762行目)
int i;
i = Search( name );
if ( i < 0 ) return;
AddRelation( i, rel_id );
```
[`Search`メソッド](#searchメソッド)で`rel`が指定される識別子のidを`i`に代入して一つ目の方を呼び出しています。

### RegistList3メソッド

```c++:label.cpp(521行目)
// キーワードリストを色分けテーブル用に登録する
```
らしいです。いまいち使用目的がわかりませんし、執筆時点ではこれが使用されているメソッドがhspcmp.exe内で使われていません。

### DumpLabelメソッド
`mem_lab`に登録されたすべての識別子のデータをある文字列に変換して引数に追加します。
執筆時点ではこのメソッドはhspcmp.exe内で使用されていません。
#### 引数
- char *str
    - これに`mem_lab`のデータが出力されます。

#### 使用されている変数
- char tmp[256]
    - 一時的に文字列を保存されます。
- char *p
    - `str`が割り当てられて、これが操作されます。
- int a
    - `for`文のループカウンタです。
- LABOBJ *lab
    - `mem_lab`を引き出すのに使われます。

#### コードを見てみる
```c++:label.cpp(613～625行目)
p = str;
p=Prt( p,"#Debug dump" );
sprintf( tmp,"#Labels:%d",cur );
p=Prt( p,tmp );
for(a=0;a<cur;a++) {
	LABOBJ *lab=&mem_lab[a];
	sprintf( tmp,"#ID:%d (%s) flag:%d  type:%d  opt:%x",a,lab->name,lab->flag,lab->type,lab->opt );
	p=Prt( p,tmp );
	//（コメントにつき中略）
}
*p=0;
```
`p`に`str`を割り当てて、
1. `tmp`に「#Debug dump」を代入して、`p`の行末に`\r\n`を`tmp`につけて代入。
2. `tmp`に「#Labels:(`cur`の値)」を代入して、`p`の行末に`\r\n`を`tmp`につけて代入。
3. `tmp`に「#ID:(`a`の値) ((`lab->name`)の値) flag:(`lab->flag`の値)  type:(`lab->type`の値)  opt:(`lab->opt`の16進数の値)」を代入して、`p`の行末に`\r\n`を`tmp`につけて代入。
4. 3.を`mem_lab`すべてでする。
5. `p`に`\0`を末に追加する。
という動作になっています。

### DumpHSPLabelメソッド
`mem_lab`に登録された識別子のデータをある文字列に変換して、指定された文字数に到達しない限り引数に追加します。
執筆時点ではこれが使用されているメソッドのメソッドはhspcmp.exe内で使われていません。
#### 引数
- char *str
    - これに`mem_lab`のデータが出力されます。
- int option
    - 出力するデータの選択に使われます。
- int maxsize
    - `mem_lab`から引き出す最大文字数です。

#### 使用されている変数
- char tmp[256]
    - 一時的に文字列を保存されます。
- char *typem
    - 一時的に文字列を保存されます。
- char *p
    - `str`が割り当てられて、これが操作されます。
- char *p_limit
    - `p`がとりうる最大のバッファです。
- int a
    - `for`文のループカウンタです。
- LABOBJ *lab
    - `mem_lab`を引き出すのに使われます。

#### コードを見てみる
変数の定義部分は飛ばします。

```c++:label.cpp(637～643行目)
p = str;
p_limit = p + maxsize;
for(a=0;a<cur;a++) {
	if ( p >= p_limit ) break;
	LABOBJ *lab=&mem_lab[a]; typem = NULL;
```
まず`p`に`str`を割り当てて、`p_limit`を設定しています。
次に、`p`が`p_limit`を超えていないかを確認して、超えていなければ`lab`に`mem_lab[a]`を割り当てています。

```c++:label.cpp(644～663行目)
	switch( lab->type ) {
	case LAB_TYPE_PPEX_PRECMD:
		if ( option & LAB_DUMPMODE_RESCMD ) typem = "pre|func";
		break;
	case LAB_TYPE_PPEX_EXTCMD:
		if ( option & LAB_DUMPMODE_RESCMD ) typem = "sys|func|1";
		break;
	case LAB_TYPE_PPDLLFUNC:
		if ( option & LAB_DUMPMODE_DLLCMD ) typem = "sys|func|2";
		break;
	case LAB_TYPE_PPMODFUNC:
		if ( option & LAB_DUMPMODE_DLLCMD ) typem = "sys|func|3";
		break;
	case LAB_TYPE_PPMAC:
	case LAB_TYPE_PPVAL:
		if ( option & LAB_DUMPMODE_RESCMD ) typem = "sys|macro";
		break;
	default:
		if ( option & LAB_DUMPMODE_RESCMD ) typem = "sys|func";
		break;
	}
```
ここでは、識別子のタイプと`option`の値によって`typem`が設定されています。

```c++:label.cpp(674～679行目)
	if ( typem != NULL ) {
		sprintf( tmp,"%s\t,%s",lab->name,typem );
		p=Prt( p,tmp );
	}
}
*p=0;
```
ここでは`typem`が NULL でなければ、`tmp`に「(`lab->name`の値)`\t`(`typem`の値)」を代入して、`p`の行末に`\r\n`を`tmp`につけて代入しています。
最後に`p`に`\0`を末に追加して終了です。

# hspcmd.cpp
これはHSPのすべての命令を変数に格納してあるだけですが、変数の紹介だけしておきます。
#### 使用されている変数
- char s_rec[1]
    - 謎です。
- char *hsp_prestr[]
    - HSPの命令が登録されています。`CToken::LabelRegist`などで使われてます。
- char *hsp_prepp[]
    - HSPのプリプロセッサ命令が登録されています。`CToken::LabelRegist3`で使われてます。

# 次は
次回は、membuf.cpp/.hを解説します。
[hspcmp(exe版)のコードを読む 2-1　～membuf.cpp/.h編～](https://qiita.com/aiueo-1234/items/322666956088547ad0be)

[^1]: hspのシステムフォルダ(普通なら`C:\hsp3.?\`)内にあるフォルダです。