CLabelを読む †
hspcmp/label.h, hspcmp/label.cpp のCLabelクラスを読みます。
CLabel は、hspcmp で識別子を管理するためのクラスです。プリプロセス時とコンパイル時に使用されます。
- Regist
- 識別子を登録し、その識別子に対応するIDを返します。
- Search, SearchLocal?
- で名前からそれに対応するIDを検索します。なければ -1 を返します。
- GetOpt?, SetOpt?, GetData?, SetData?, GetData2, SetData2
- 識別子に対応付けられたデータを取得(Get)、設定(Set)する関数です。
識別子1つの情報を管理する構造体です。
1
2
3
4
5
6
7
8
9
10
11
12
| -
|
|
|
|
|
|
|
|
|
|
!
| typedef struct LABOBJ {
int flag; int type; int opt; short eternal; short ref; int hash; char *name; char *data; char *data2; LABREL *rel; } LABOBJ;
|
- flag
- 通常は 1 で、#undef されると -1 になります。
- type
- LAB_TYPE_* または TYPE_* の定数の値が入ります。
- opt
- 識別子に対応付けられた整数値です。
- eternal
- スコープを無視して永続的に使用できる識別子の場合、1 になり、そうでない場合 0 になります。例えば、#deffunc などは前者で、global がない #define などは後者です。
- ref
- 識別子の参照回数です。#deffunc, #defcfunc, #func で定義された識別子の場合のみ使われます。
- hash
- 識別子を表現する文字列のハッシュ値です。
- name
- 識別子を表現する文字列 ( へのポインタ ) です。
- data
- 識別子に対応付けられたC形式文字列 ( へのポインタ ) です。
- data2
- 識別子に対応付けられた、'\0' も含められるバイト列 ( へのポインタ ) です。
- rel
- 未使用 #func, モジュール削除のための連結リストです。詳しく後述します。
コンパイル中には type, opt の値はそれぞれ、Code SegmentのType値、Code値に使われます。たとえば "mes@hsp" は type = TYPE_EXTCMD, opt = 0x0f です。
一方、プリプロセス中には、type に LAB_TYPE_* の定数値が入ります。opt の値は、識別子の種類(typeの値)に依存します。
繰り返しになりますが、プリプロセス時に登録される識別子のtype値は LAB_TYPE_* 定数の値が入ります。
1
2
3
4
5
6
7
8
9
10
|
| #define LAB_TYPE_PPVAL 0x110
#define LAB_TYPE_PPMAC 0x111
#define LAB_TYPE_PPMODFUNC 0x112
#define LAB_TYPE_PPDLLFUNC 0x113
#define LAB_TYPE_PPINTMAC 0x114
#define LAB_TYPE_PPEX_INTCMD 0x115
#define LAB_TYPE_PPEX_EXTCMD 0x116
#define LAB_TYPE_PPEX_PRECMD 0x117
#define LAB_TYPE_COMVAR 0x200
#define LAB_TYPE_PP_PREMODFUNC 0x212
|
- LAB_TYPE_PPVAL
- #enum, #constで定義される定数。値は、int なら opt に、double なら data2 に格納されます。
- LAB_TYPE_PPMAC
- #defineで定義されるマクロ。opt が引数の数と ctypeフラグ(PRM_MASK, PRM_FLAG_CTYPE)、dataが置き換えられる文字列、data2がデフォルト引数データ(MACDEF) となっています。
- LAB_TYPE_PPMODFUNC
- #deffuncなどで定義される命令・関数です。
- LAB_TYPE_PPDLLFUNC
- #func, #moduleなどで定義される識別子。一緒になっているのはおそらくプリプロセス時にはこれらを区別できるようになっている必要がないからと推測されます。
- LAB_TYPE_PPINTMAC
- 内部マクロ。標準命令*1や、#cmdで定義される識別子で使われます。LAB_TYPE_PPMACとの違いは、プリプロセス行のときはマクロ展開しないという点です。
- LAB_TYPE_PPEX_***
- hsc3_getsym用。これは、標準スクリプトエディタの色分けに使われます。
- LAB_TYPE_COMVAR
- #usecomで定義されるインターフェース名。
- LAB_TYPE_PP_PREMODFUNC
- プロトタイプ宣言されたユーザー定義命令・関数。ただし、HSP3.2現在、プロトタイプ宣言は未実装です。乞うご期待?
1
2
3
4
5
6
7
8
9
|
-
|
|
|
|
|
|
!
| class CLabel {
……(略)……
private:
LABOBJ *mem_lab; int cur; int maxlab; ……(略)……
}
|
mem_labがLABOBJの配列です。登録された識別子のLABOBJは全て、この配列に格納されます。maxlabがmem_labのサイズ、curがmem_labの位置です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-
|
|
|
|
|
!
-
|
|
|
|
!
| CLabel::CLabel( void )
{
……(略)……
maxlab = def_maxlab;
mem_lab = (LABOBJ *)malloc( sizeof(LABOBJ)*maxlab );
……(略)……
Reset();
}
void CLabel::Reset( void )
{
int i;
cur = 0;
for(i=0;i<maxlab;i++) { mem_lab[i].flag = -1; }
……(略)……
}
|
見てのとおり、mem_labを初期化しています。
識別子の登録です。
余談ですが、Register (登録する、という意味の動詞) の間違いだと思います。
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
|
-
|
|
|
-
|
|
|
|
|
|
|
|
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
| int CLabel::Regist( char *name, int type, int opt )
{
int a;
if ( name[0]==0 ) return -1;
if ( cur>=maxlab ) { LABOBJ *tmp;
int i,oldsize;
oldsize = sizeof(LABOBJ)*maxlab;
maxlab += def_maxlab;
tmp = (LABOBJ *)malloc( sizeof(LABOBJ)*maxlab );
for(i=0;i<maxlab;i++) { tmp[i].flag = -1; }
memcpy( (char *)tmp, (char *)mem_lab, oldsize );
free( mem_lab );
mem_lab = tmp;
}
a = cur;
LABOBJ *lab=&mem_lab[cur++];
lab->flag = 1;
lab->type = type;
lab->opt = opt;
lab->eternal = 0;
lab->ref = 0;
lab->name = RegistSymbol( name );
lab->data = NULL;
lab->data2 = NULL;
lab->hash = StrCase( lab->name );
lab->rel = NULL;
return a;
}
|
maxlabまで使いきったらサイズを拡張 (def_maxlab個) します。
RegistSymbol?は名前を内部のバッファにコピーして、そのアドレスを返します。StrCase?は名前を小文字化させ、ハッシュ値を計算します。
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
|
-
|
|
|
|
|
|
|
|
|
|
-
-
-
|
|
-
|
|
|
|
!
!
!
|
!
|
!
| int CLabel::Search( char *oname )
{
char as;
int a;
int hash;
char *str1,*str2;
if (cur==0) return -1;
hash = StrCase( oname );
LABOBJ *lab = mem_lab;
for(a=0;a<cur;a++) {
if ( lab->flag >= 0 ) {
if ( hash == lab->hash ) {
str1=oname;
str2=lab->name;
while(1) {
as=*str1;
if (as!=*str2) break;
if (as==0) return a;
str1++;str2++;
}
}
}
lab++;
}
return -1;
}
|
識別子から、それに対応するIDを検索します。登録されていない場合、負数(-1)を返します。
引数の文字列を StrCase?() で書き換えるため、使うときには注意が必要です。
ハッシュ値を用いた線形探索で検索しています。
ちなみに、線形探索ではなく std::map (STLのコンテナの1つ、連想配列) を使うようにするパッチも作られていますが、まだ採用はされていません (HSP3.2現在)。
これは Search の特殊版です。
例えば、foo というモジュール内で "bar" という識別子があったとき、eternal な "bar" と、eternal な "bar@foo" を同時に検索します。この例では、引数 oname に "bar"、同 loname に "bar@foo" が指定されます。
ここで、eternal な "bar" と非eternalな"bar@foo"が両方ある場合、どちらを用いるかは未定義として実装されています。
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
33
34
35
36
|
-
|
|
|
|
|
|
|
|
|
|
|
-
-
-
|
|
-
|
|
!
-
|
-
|
|
|
|
!
!
!
|
!
|
!
| int CLabel::SearchLocal( char *oname, char *loname )
{
char as;
int a;
int hash,hash2,myhash;
char *str1,*str2;
if (cur==0) return -1;
hash = StrCase( oname );
hash2 = GetHash( loname );
LABOBJ *lab = mem_lab;
for(a=0;a<cur;a++) {
if ( lab->flag >= 0 ) {
if (lab->eternal) {
str1 = oname;
myhash = hash;
} else {
str1 = loname;
myhash = hash2;
}
if ( lab->hash == myhash ) {
str2=lab->name;
while(1) {
as=*str1;
if (as!=*str2) break;
if (as==0) return a;
str1++;str2++;
}
}
}
lab++;
}
return -1;
}
|
onameは小文字化して、lonameは小文字化していない (StrCase?ではなく小文字化しないGetHash?を呼んでいる)のは謎です。もっとも、両方とも小文字化されてから呼ばれるので、CLabelで小文字化する必要はないのかもしれません。
どんな識別子がどんなデータで登録されているかを、以下の関数を追加して呼び出せば、調べることができます。(label.hに「void Dump( void );」と宣言をお忘れなく)
プリプロセス時のデータならCToken::GetLabelInfo?に、コンパイル時のデータならCToken::GenerateCode?の「res = GenerateCodeMain?( srcbuf );」の後ろあたりに「lb->Dump();」を追加するといいでしょう。
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
|
!
-
|
|
|
-
|
|
|
|
|
|
|
|
-
|
!
|
-
|
-
|
|
!
|
!
|
|
!
!
| #include "../hsp3/hsp3struct.h"
static char *get_label_type_name( int type )
{
switch(type) {
#define T(type) case type: return #type;
T(TYPE_MARK)
T(TYPE_VAR)
T(TYPE_STRING)
T(TYPE_DNUM)
T(TYPE_INUM)
T(TYPE_STRUCT)
T(TYPE_XLABEL)
T(TYPE_LABEL)
T(TYPE_INTCMD)
T(TYPE_EXTCMD)
T(TYPE_EXTSYSVAR)
T(TYPE_CMPCMD)
T(TYPE_MODCMD)
T(TYPE_INTFUNC)
T(TYPE_SYSVAR)
T(TYPE_PROGCMD)
T(TYPE_DLLFUNC)
T(TYPE_DLLCTRL)
T(TYPE_USERDEF)
T(LAB_TYPE_PPVAL)
T(LAB_TYPE_PPMAC)
T(LAB_TYPE_PPMODFUNC)
T(LAB_TYPE_PPDLLFUNC)
T(LAB_TYPE_PPINTMAC)
T(LAB_TYPE_PPEX_INTCMD)
T(LAB_TYPE_PPEX_EXTCMD)
T(LAB_TYPE_PPEX_PRECMD)
T(LAB_TYPE_COMVAR)
T(LAB_TYPE_PP_PREMODFUNC)
#undef T
}
return "unknown";
}
void CLabel::Dump( void )
{
LABOBJ *p = mem_lab;
LABOBJ *pend = p + cur;
int i = 0;
while (p < pend) {
printf("#ID:%d (%s) flag:%d type:%s opt:%#x eternal:%d ref:%d",
i,
p->name,
p->flag,
get_label_type_name(p->type),
p->opt,
p->eternal,
p->ref);
if(p->data) {
printf(" data=<%s>", p->data);
}
puts("");
if(p->rel) {
LABREL *l = p->rel;
while(l) {
printf(" -> %d (%s)", l->rel_id, mem_lab[l->rel_id].name);
l = l->link;
}
puts("");
}
p ++;
i ++;
}
}
|
以下はプリプロセス時のダンプの一部です。
#ID:0 (goto) flag:1 type:LAB_TYPE_PPINTMAC opt:0 eternal:1 ref:0 data=<goto@hsp>
#ID:1 (gosub) flag:1 type:LAB_TYPE_PPINTMAC opt:0 eternal:1 ref:0 data=<gosub@hsp>
#ID:2 (return) flag:1 type:LAB_TYPE_PPINTMAC opt:0 eternal:1 ref:0 data=<return@hsp>
標準命令の名前に"@hsp"をつけるようにマクロ定義されているのがわかります。
そして、以下がコンパイル時のダンプの一部です。
#ID:0 (goto@hsp) flag:1 type:TYPE_PROGCMD opt:0 eternal:1 ref: 0
#ID:1 (gosub@hsp) flag:1 type:TYPE_PROGCMD opt:0x1 eternal:1 ref: 0
#ID:2 (return@hsp) flag:1 type:TYPE_PROGCMD opt:0x2 eternal:1 ref: 0
コンパイル時にはtypeにはCode SegmentのためのType値、optにはCode SegmentのためのCode値が使われているのがわかります。ここで登録されたType値やCode値がCode Segmentに出力されるときに使われます。
関数名を見ただけで何をするのか分かるので、説明の必要もないでしょう。
1
2
3
4
5
6
7
8
9
10
11
12
|
| void SetEternal( int id );
int GetEternal( int id );
void SetFlag( int id, int val );
int GetFlag( int id );
void SetOpt( int id, int val );
int GetOpt( int id );
void SetData( int id, char *str );
char *GetData( int id );
void SetData2( int id, char *str, int size );
char *GetData2( int id );
int GetType( int id );
char *GetName( int id );
|
#undefされるとSetFlag?(id, -1)が呼ばれ、無効になりますが、データは消していないようです。
hspcmd.cppで定義されているような標準命令の一覧から、コンパイル用に識別子を登録します。
hspcmp.cppで定義されているhsp_prestrは "$000 15 goto" のような文字列の配列になっています。$000がCode SegmentのためのCode値(16進数)、15がCode SegmentのためのType値です (TYPE_PROGCMD == 15)。
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
|
-
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
!
|
!
| int CLabel::RegistList( char **list, char *modname )
{
char *p;
char **plist;
char tmp[256];
int id,i,type,opt;
i = 1;
plist = list;
while(1) {
p = tmp;
strcpy( p, plist[i++] );
if (p[0]!='$') break;
p++;
p = GetListToken( p );
opt = HtoI();
p = GetListToken( p );
type = atoi( token );
p = GetListToken( p );
strcat( token, modname );
id = Regist( token, type, opt );
SetEternal( id );
}
return 0;
}
|
引数modnameには"@hsp"が指定されて呼び出されます。
GetListToken?は引数のアドレスからスペース以外の文字で構成されたトークンを取り出します。取り出したトークンはメンバ変数の固定長文字列tokenにコピーされます。そして、読み進めたアドレスを返します。たとえば引数に" foo bar"を指定すると、tokenには文字列"foo"がコピーされ、引数の文字列アドレス+strlen(" foo")が返されます。
HtoIはtokenを16進数文字列としてintに変換する、つまり atoi の16進数バージョンです。
RegistList2はRegistList?のプリプロセス版です。
「登録されたデータをダンプ」で見たように、標準命令の名前に"@hsp"をつけるようにマクロ定義しています。
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
|
-
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
|
!
| int CLabel::RegistList2( char **list, char *modname )
{
char *p;
char **plist;
char tmp[256];
int id,i,type,opt;
i = 1;
plist = list;
while(1) {
p = tmp;
strcpy( p, plist[i++] );
if (p[0]!='$') break;
p++;
p = GetListToken( p );
opt = HtoI();
p = GetListToken( p );
type = atoi( token );
p = GetListToken( p );
id = Regist( token, LAB_TYPE_PPINTMAC, 0 ); strcat( token, modname );
SetData( id, token );
SetEternal( id );
}
return 0;
}
|
識別子の個数 (つまりメンバ変数curの値) を返します。
なぜか同じ意味のメンバ関数が二つもありますが、ミスか、それとも何か意図があるのかはわかりません。
symbolバッファは、(LABOBJの) name や data, data2 のためのバッファです。malloc でバッファを確保するのはすこし重いので、確保するときは大きくどーんと確保して、後で必要な大きさに切って使います。いわゆるストレージというヤツです。
小分けで確保した部分は、一度使うと、もし不要になっても hspcmp が処理を終えるまで解放されない、かつ使い回せない仕様になっています。
- symblock
- symbolバッファ (へのポインタ) の配列です。
- curblock
- symblock の使っている要素の数です。言い換えると、symblockの、次に使われる要素の番号です。
- maxsymbol
- それぞれのsymbolバッファのサイズです。symbolバッファはどれも同じサイズのようです。
- symbol
- 今使っているsymbolバッファ (つまりsymblock[curblock - 1]) へのポインタです。
- symcur
- symbol の中で使用しているサイズです。言い換えると、未使用領域の先頭の位置です。
「初期化」の項であえて無視したsymbolバッファの初期化部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
-
|
|
|
|
|
|
!
-
|
|
|
|
|
!
| CLabel::CLabel( void )
{
int i;
maxsymbol = def_maxsymbol;
maxlab = def_maxlab;
mem_lab = (LABOBJ *)malloc( sizeof(LABOBJ)*maxlab );
for(i=0;i<def_maxblock;i++) { symblock[i] = NULL; }
Reset();
}
void CLabel::Reset( void )
{
int i;
cur = 0;
for(i=0;i<maxlab;i++) { mem_lab[i].flag = -1; }
DisposeSymbolBuffer();
MakeSymbolBuffer();
}
|
今回は、mem_labの初期化部分はスルーして読みます。
symblockの全要素をNULLにし、DisposeSymbolBuffer?とMakeSymbolBuffer?を呼びます。
新しいsymbolバッファを追加します。
1
2
3
4
5
6
7
|
-
|
|
|
|
!
| void CLabel::MakeSymbolBuffer( void )
{
symbol = (char *)malloc( maxsymbol );
symblock[curblock] = symbol;
curblock++;
symcur = 0;
}
|
全てのsymbolバッファを破棄します。
1
2
3
4
5
6
7
8
9
10
11
|
-
|
-
-
|
|
!
!
|
!
| void CLabel::DisposeSymbolBuffer( void )
{
int i;
for(i=0;i<def_maxblock;i++) {
if ( symblock[i] != NULL ) {
free( symblock[i] );
symblock[i] = NULL;
}
}
curblock = 0;
}
|
sizeバイト書き込める領域を返します。
現在のsymbolバッファを使い切って足りない場合はMakeSymbolBuffer?を呼び出して新しいsymbolバッファを追加します。
ただし、size が maxsymbolより大きいことは考慮していません。
1
2
3
4
5
6
7
8
9
10
11
12
|
-
|
|
|
-
|
|
|
!
|
!
| char *CLabel::ExpandSymbolBuffer( int size )
{
char *p;
p = symbol + symcur;
symcur += size;
if ( symcur >= maxsymbol ) {
MakeSymbolBuffer();
symcur += size;
return symbol;
}
return p;
}
|
symbolバッファに、指定サイズのバイナリデータを登録します。SetData?, SetData2ではこれが使われます。
1
2
3
4
5
6
7
8
9
10
11
|
-
|
|
|
|
|
|
|
|
!
| char *CLabel::RegistTable( char *str, int size )
{
char *p;
char *src;
src = str;
p = ExpandSymbolBuffer( size );
memcpy( p, src, size );
return p;
}
|
symbolバッファに識別子の文字列をコピーして、そのアドレスを返します。RegistTable?(str, strlen(str)+1)とはほとんど変わりませんが、サイズを maxname バイトに制限しています。
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
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
!
|
|
|
|
|
!
| char *CLabel::RegistSymbol( char *str )
{
char *p;
char *pmaster;
char *src;
char a1,a2;
int i;
i = 0;
p = ExpandSymbolBuffer( strlen(str)+1 );
pmaster = p;
src = str;
a2 = *src;
while(1) {
a1=*src++;
*p++ = a1;
if ( a1 == 0 ) break;
if ( i >= (maxname-1) ) { *p=0; i++; break; }
i++;
}
return pmaster;
}
|
LABOBJ::ref, LABOBJ::relは未使用な #func, モジュールを削除するためにあります。
LABOBJ::ref の型 LABREL がどんな型か見てみましょう。
1
2
3
4
5
6
|
-
|
|
!
| typedef struct LABREL LABREL;
struct LABREL {
LABREL *link; int rel_id; };
|
単方向連結リストになっています。ラベルが参照する複数のラベルを繋ぎます。
それが実際にどのように使われているか「登録されたデータをダンプ」のダンプ結果を見て調べてみましょう。以下のスクリプトをプリプロセスしたダンプを見てみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
| #module mod_f
#deffunc f1
return
#deffunc f2
return
#deffunc f3
return
#global
#module mod_g
#deffunc g1
return
#deffunc g2
return
#deffunc g3
return
#global
f1 : f1 : f1 : f1 : f1
f2 : f2 : f2
|
(...前略...)
#ID:201 (mod_f) flag:1 type:LAB_TYPE_PPDLLFUNC opt:0 eternal:1 ref:0
-> 202 (f1) -> 203 (f2) -> 204 (f3)
#ID:202 (f1) flag:1 type:LAB_TYPE_PPMODFUNC opt:0 eternal:1 ref:5
#ID:203 (f2) flag:1 type:LAB_TYPE_PPMODFUNC opt:0 eternal:1 ref:3
#ID:204 (f3) flag:1 type:LAB_TYPE_PPMODFUNC opt:0 eternal:1 ref:0
#ID:205 (mod_g) flag:1 type:LAB_TYPE_PPDLLFUNC opt:0 eternal:1 ref:0
-> 206 (g1) -> 207 (g2) -> 208 (g3)
#ID:206 (g1) flag:1 type:LAB_TYPE_PPMODFUNC opt:0 eternal:1 ref:0
#ID:207 (g2) flag:1 type:LAB_TYPE_PPMODFUNC opt:0 eternal:1 ref:0
#ID:208 (g3) flag:1 type:LAB_TYPE_PPMODFUNC opt:0 eternal:1 ref:0
LABOBJ::rel から参照されているラベルは「-> label id (name) -> label id (name) ...」のように表示しています ( 以下のコードの通り )。
1
2
3
4
5
6
7
8
| -
|
-
|
|
!
|
!
| if(p->rel) {
LABREL *l = p->rel;
while(l) {
printf(" -> %d (%s)", l->rel_id, mem_lab[l->rel_id].name);
l = l->link;
}
puts("");
}
|
これを見ると、「モジュール名のラベル」からそのモジュール内の全部のユーザ定義命令・関数を連結リストで結んでいるようです。
また、f1, f2 の ref がそれぞれ 5, 3 になっています。呼び出しの回数を記録しているようですね。
未使用モジュールの削除の手順、まとめ:
- プリプロセス時にユーザ定義命令・関数が定義されたとき、そのモジュールのラベルの rel 連結リストに、定義するラベルを追加する。
- プリプロセス時にユーザ定義命令・関数の呼び出しがあれば、その関数の呼び出し回数をインクリメントする。
- コンパイル時に、プリプロセス時のラベル情報を使って、モジュール内の全てのユーザ定義命令・関数の呼び出し回数を調べ、すべて 0 の場合、そのモジュールをコンパイルしない。
#funcで定義される関数もほぼ同様で、呼び出し回数をカウントし、0 なら定義を無視されます。
なお、モジュール内で #func 関数を呼んだときは、呼び出し回数を増やすのではなく、連結リストにモジュールのラベルを追加しています。これは、未使用モジュールのみから呼ばれた#func関数も削除されるようにということだと思います。
- あまりよい実装とは思いませんが、露骨に批判的なのはどうかと思いますので、加筆ついでに修整しておきました。
- 「(relが、連結リストではなく)配列でもできることをしている」の件について:リンク1本で結合できるのが連結リストの強み(の一つ)なので、記述を削除しました。
「(symbolバッファが)name や data, data2 が変更されても再利用できない」の件について:一時的に使用するだけなので、大問題ではありませんから、表現を緩めました。 -- UD?
- 校正ありがとうございます! -- fujidig?