データ型を作っていきましょう。type.h
, type.c
というファイルで管理します。
ソースコードへのリンク
オブジェクト型の識別方法
これは、私が以前関わっていたKuin言語の方式にならってそうしているのですが、オブジェクトの先頭にヘッダー部をつけています。例えばコンスオブジェクトなら、次のような感じになります。
typedef struct HEADER {
TYPE type;
} HEADER;
typedef struct CONS {
HEADER h;
void *car;
void *cdr;
} CONS;
型の種類
型の種類は以下7種類になります。
typedef unsigned char TYPE;
#define TYPE_SYMBOL 0
#define TYPE_CONS 1
#define TYPE_STRING 2
#define TYPE_NUMBER 3
#define TYPE_BUILT_IN_FUNC 4
#define TYPE_USER_DEFINED_FUNC 5
#define TYPE_SPECIAL_OPERATOR 6
ここで、どうしてenum
型を使わないのか、ということになりますが、笹川賢一さんのMonolisに対するAmazonレビューで、型の識別に4バイトも使うなんて贅沢すぎるといった意見を書いていた人がいたためです。ちなみに、もともとconst TYPE
型の定数で定義していたのですが、#define
で書き直しているのはリンクエラーを回避するためです。
シンボル型
シンボルは、主に変数や関数の名前に用いられます。TとNILもシンボル型にしています(@masagl07s さん、ご教示頂きありがとうございます)。
typedef struct SYMBOL {
HEADER h;
char str[];
} SYMBOL;
この構造体にはサイズが指定されていない配列メンバstr
があります。これはフレキシブル配列メンバというもので、実体はサイズ0の配列です(@SaitoAtsushiさん、ご教示頂きありがとうございます)。フレキシブル配列メンバは以下のように使用します。
SYMBOL * make_symbol(char *str1) {
size_t len;
SYMBOL *symbol;
len = strlen(str1) + 1;
symbol = malloc(sizeof(SYMBOL) + len);
symbol->h.type = TYPE_SYMBOL;
strcpy(symbol->str, str1);
return symbol;
}
文字列型
文字列型は、他の言語と同様の文字列の型です。シンボル型同様、可変長データとなります。
typedef struct STRING {
HEADER h;
char str[];
} STRING;
コンス型
LISPのリストはコンスセルの集合からなります。
typedef struct CONS {
HEADER h;
void *car;
void *cdr;
} CONS;
数値型
my-lisp2の数値型はdoubleだけです。数値型のサブタイプを増やすとシステムが複雑になるのでそのようにしています。
typedef struct NUMBER {
HEADER h;
double num;
} NUMBER;
組み込み関数型
組み込み関数のオブジェクトは関数ポインタを保持します。
typedef struct BUILT_IN_FUNC {
HEADER h;
void *(*f)(void *);
} BUILT_IN_FUNC;
ユーザー定義関数型
ユーザー定義関数のオブジェクトは、ラムダ形式と環境を保持します。
typedef struct USER_DEFINED_FUNC {
HEADER h;
void *body;
void *func_env;
void *var_env;
} USER_DEFINED_FUNC;
スペシャルオペレーター型
Common Lispには関数でもマクロでもないスペシャルオペレーターと呼ばれるものがあります。スペシャルオペレーター自体をオブジェクトとして扱うのはおかしいかもしれませんが、関数・マクロと同じ環境の中に入れて一元管理したかったので型を用意しています。
typedef struct SPECIAL_OPERATOR {
HEADER h;
void *(*op)(void *, void *, void *);
} SPECIAL_OPERATOR;
TとNILはグローバル変数として公開する
T値とNIL値はグローバル領域にオブジェクトを持たせ、必要があればそれらのオブジェクトを参照させるようにします。
extern void *T;
extern void *NIL;
char T_BODY[] = { TYPE_SYMBOL, 'T', '\0' };
char NIL_BODY[] = { TYPE_SYMBOL, 'N', 'I', 'L', '\0' };
void *T = (void *)&T_BODY;
void *NIL = (void *)&NIL_BODY;