tiny Cの処理系で使われる基本的なデータ構造について説明する。 プログラムでは、
の2つのファイルに定義されている。最初の講義で解説した通り、構文木は最も基本的なデータ構造である。 以下のように定義する。
AST.h typedef struct abstract_syntax_tree { enum code op; int val; struct symbol *sym; struct abstract_syntax_tree *left,*right; } AST;
ASTのノードを作る関数が、makeASTである。
また、NUMの数の定数のASTノードを作る関数がmakeNumである。
AST.c AST *makeAST(enum code op,AST *left,AST *right) { AST *p; p = (AST *)malloc(sizeof(AST)); p->op = op; p->right = right; p->left = left; return p; }
AST.c AST *makeNum(int val) { AST *p; p = (AST *)malloc(sizeof(AST)); p->op = NUM; p->val = val; return p; }
演算子の構文木は2分木であるが、いくつかの要素をならべて表現するリスト 構造があればいろいろと便利なことがある。そのために、ASTのopがLISTの場 合にリストとしてみなすことにする。
リストの生成は、makeASTを使って、マクロで定義してある。
リストの最後に要素(AST)を加える関数が、addListである。
AST.h #define makeList1(x1) makeAST(LIST,x1,NULL) #define makeList2(x1,x2) makeAST(LIST,x1,makeAST(LIST,x2,NULL)) #define makeList3(x1,x2,x3) makeAST(LIST,x1,makeAST(LIST,x2,makeAST(LIST,x3,NULL)))
最後を見付けて、LISTのASTノードを付け加える。 リストについてのアクセスは、n番目の要素を取り出すgetNthで行う。
AST.c AST *addLast(AST *l,AST *p) { AST *q; if(l == NULL) return makeAST(LIST,p,NULL); q = l; while(q->right != NULL) q = q->right; q->right = makeAST(LIST,p,NULL); return l; }
これを使えば、1番目をとるのは、getFirstは以下のように定義できる。
AST.c AST *getNth(AST *p,int nth) { if(p->op != LIST){ fprintf(stderr,"bad access to list\n"); exit(1); } if(nth > 0) return(getNth(p->right,nth-1)); else return p->left; }
また、関数getNextは、最初の要素をとったリストを返す。
AST.h #define getFirst(p) getNth(p,0)
この関数はリストの要素を1つつづ、アクセスするのに用いる。
AST.c AST *getNext(AST *p) { if(p->op != LIST){ fprintf(stderr,"bad access to list\n"); exit(1); } else return p->right; }
AST *list,x; for(list = ...; list != NULL; list = getNext(list)){ x = getFirst(list); /* 要素の取り出し */ xについての処理 }
PLUS_OP, MINUS_OPなどの演算子の他に、IF_STATEMENTなどの文のためのコー ドが定義しておく。 前は、#defineで定義したが、enumを使っておけば、デバックに便利である。
AST.h enum code { LIST, NUM, SYM, EQ_OP, PLUS_OP, MINUS_OP, MUL_OP, LT_OP, GT_OP, GET_ARRAY_OP, SET_ARRAY_OP, CALL_OP, PRINTLN_OP, IF_STATEMENT, BLOCK_STATEMENT, RETURN_STATEMENT, WHILE_STATEMENT, FOR_STATEMENT };
シンボルは同じの名前のシンボルを1つのデータ構造で管理するもので、 以下の様に定義する。
AST.h typedef struct symbol { char *name; int val; AST *func_params; AST *func_body; } Symbol;
同じ名前(識別子)は同じ構造体で管理するが、それを見付けるためにこのプロ グラムでは、単純サーチを使っている。大域変数n_symbolは、その数を数える 変数である。
AST.c Symbol SymbolTable[MAX_SYMBOLS]; int n_symbols = 0;
関数lookupSymbolは、名前を引数にして、それに対応するシンボル構造体を返 す。もしも、名前のシンボルがなかったら、それに対するシンボルを作る。
シンボルを表すASTは、opがSYMで、symにシンボルへのポインタをいれたもの である。関数makeSymbolは名前を与えて、それに対応するASTを作る。
AST.c Symbol *lookupSymbol(char *name) { int i; Symbol *sp; sp = NULL; for(i = 0; i < n_symbols; i++){ if(strcmp(SymbolTable[i].name,name) == 0){ sp = &SymbolTable[i]; break; } } if(sp == NULL){ sp = &SymbolTable[n_symbols++]; sp->name = strdup(name); } return sp; }
逆に、シンボルのASTのノードから、シンボルを取り出すのが関数getSymbolで ある。
AST.c AST *makeSymbol(char *name) { AST *p; p = (AST *)malloc(sizeof(AST)); p->op = SYM; p->sym = lookupSymbol(name); return p; }
AST.c Symbol *getSymbol(AST *p) { if(p->op != SYM){ fprintf(stderr,"bad access to symbol\n"); exit(1); } else return p->sym; }