リストと配列の実装
リストや配列は複数の要素を格納する変数である。配列は、添字である数値によってランダムにアクセスすることができる。
配列の大きさをどのように決めれば良いか? リスト表現を使用すると、配列の大きさには制限がなくなるが、アクセスは遅くなる。
今回はLevel1のプログラムに配列とリストを実装した.
配列及びリストをするためにshell.yを以下のように書き加えた.
token()関数
・・・
438 } else if(('a' <= c && c <= 'z') || ('@' <=c && c <= 'Z') ||
439 ('*'<= c && c <= ':') || '~'== c || '$'==c){
440 file = ptr - 1;
441 while (('a' <= c && c <= 'z') || ('@' <=c && c <= 'Z') ||
442 ('*' <= c && c <= ':') || '~' == c || '$'==c )
~省略~
450 switch(getID(value)){
~省略~
457 case 3:
458 last_token = DELETE;
459 break;
460 case 4:
461 last_token = DELKY;
462 break;
~省略~
466 default:
467 if(value[0] == '$'){
468 last_token = ARR_VAL;
469 break;
470 }else if(value[0] == '@'){
471 last_token = LIS_VAL;
472 break;
473 }else{
474 last_token = 'f';
475 break;
476 }
477 }
478 return last_token;
・・・
token()関数では'@'と'$'を変数として扱えるように438~442の部分を書き換えた.通過してきた変数用の文字は466行のdefaultを通り,それぞれARR_VALとLIS_VALといったトークンで返される.また,配列やリストの削除をするためのコマンドを新しいトークンとして追加している,コマンドはあらかじめ予約語として登録しており,今回は450行でハッシュテーブルから探し出しswitchで割り振っている.
構文規則
・・・
66 expr : expr '|' expr { $$ = new_node('|',NULL,0,$1,$3);}
~省略~
74 | cmd val { $$ = new_node($1->type,$1->value,-1,NULL,$2);
free($1);}
75 | cmd val '=' file { $$ = new_node($1->type,$1->value,0,$2,$4);
free($1);} // list
76 | cmd val '[' file ']'
{ $$ = new_node($1->type,$1->value,atoi($4->value),NULL,$2); free($1); free($4);}
77 | cmd {$$ = new_node($1->type,make_args($1,NULL,0),0,NULL,NULL);}
78 | val '=' file { $$ = new_node('=',NULL,0,$1,$3); }
79 | val '[' file ']' '=' file
{ $$ = new_node('=',NULL,atoi($3->value),$1,$6); free_node($3);}
~省略~
85 ;
86
87 val : ARR_VAL { $$ = new_node(ARR_VAL,value,0,NULL,NULL); }
88 | LIS_VAL { $$ = new_node(LIS_VAL,value,0,NULL,NULL);}
89 ;
90 file : 'f' { $$ = new_node('f',value,0,NULL,NULL);}
91 cmd : WAIT { $$ = new_node(WAIT,value,0,NULL,NULL);}
92 | REF { $$ = new_node(REF ,value,0,NULL,NULL);}
93 | DELETE { $$ = new_node(DELETE,value,0,NULL,NULL);}
94 | DELKY { $$ = new_node(DELKY,value,0,NULL,NULL);}
95 | EXIT { $$ = new_node(EXIT,value,0,NULL,NULL);}
96 ;
・・・
構文規則は上記のように変更した.74~76行は値を削除する際のコマンドを扱
うために追加した規則である.78行と79行は変数に値を代入する際に必要とな
る規則である.87~96行はそれぞれの終端記号からノードを作成している.
command()関数
・・・
152 switch(d->type) {
153 case 'c':
~省略~
222 case DELETE:
223 if(d->right&&d->num>=0){
224 index_delete(d->right->value, d->num);
225 }else if(d->num==-1&&d->right->value[0]=='$'){
226 array_delete(d->right->value);
227 }
228 break;
229 case REF:
230 if(d->right){
231 if(d->right->value[0] == '$'){
232 if(d->num>=0)
233 printArrayValue(d->right->value, d->num);
234 else if(d->num==-1)
235 printArrayAll(d->right->value);
236 }else if(d->right->value[0] == '@')
237 printListValue(d->right->value);
238 else
239 printf("not variable\n");
240 }
241 break;
242 case EXIT:
243 exit(0);
244 break;
245 case DELKY:
246 if(d->right){
247 if(d->left&&d->left->value[0] == '@'){
248 key_delete(d->left->value, d->right->value);
249 }else if(d->right->value[0] == '@'){
250 list_delete(d->right->value);
251 }
252 break;
253 }else{
254 printf("usage : delkey @value [=] [list_name]\n");
255 break;
256 }
257
258 case '=':
259 if(d->left->value[0] == '$'){
260 insert_data(d->left->value,d->right->value,d->num);
261 }else if(d->left->value[0] == '@'){
262 insert_list(d->left->value,d->right->value);
263 }
264 break;
~省略~
270 }
・・・
command関数では与えられたノードのtypeをswitchで判断し,それぞれの処理を行なっ
ていく.
222行のDELETEでは配列の削除を行なうコマンドで,配列の添字を指
定したときとしてない時などの条件に合わせて関数を呼び出している.
229行目のREFは変数の値を表示させるコマンドで,自分の右のノードの値をそ
れぞれprintArrayValue,printListValueに渡して処理を行なう.245行の
DELKYはリストの要素を削除するもので,右のノードのvalueが'@'から始まる
ときに処理をする.258行のcaseでは値の代入に関する処理を行なう.このとき,
リストと配列では処理を行なう関数が違うのでここで振り分けている.
hash.cでの変更・追加部分
配列の処理
ここで,配列の処理における説明をする.配列の処理はhash.cにおける次の関
数で行なわれる.
- insert_data() ->source
データの挿入及び、上書きなどを行なう.操作の内容を説明すると,まず,入力された変
数が既にハッシュに存在するかを調べる.既に登録済みであった場合は挿入
する要素があるかを調べ,あった場合には上書き,無かったらcallocで領域
を確保し,strcpyで値を入れる.
ハッシュに存在しなかった場合はまず,H_DATAの構造体の領域をmallocで取
り,そのkeyの領域もmallocで確保する.それが全て上手く行けば残りの変
数の初期化としてlistと配列にそれぞれNULLを代入する.その後,callocで
値を代入する領域を取り,そこに値をコピーする.そしてnextをNULLに設定
し,それをハッシュ表に登録する.この時,コリジョンを起こした場合には
H_DATAをリスト化することで,上書きを避けている.
- printArrayValue() ->source
添字を指定して変数を出力する関数.取得したH_DATA構造体の
array_data[i]を出力するのみ.構造体が取得できない時はその変数がないと
見なされその旨の出力が行なわれる.
- printArrayAll() ->source
指定した変数に格納されている全ての値を列挙する.取得したH_DATA構造
体のarray_data[]を一つずつ調べ,NULLでない時に出力する.
- index_delete() ->source
配列の指定した添字の値を削除する.H_DATAを取得し,指定した添字に値
が存在した場合にその領域をfreeしてその配列にNULLを代入する.削除した
際,配列に何も値がない場合にはハッシュ表からその変数を削除する.これ
はh_list_del()にて行なわれる.
- array_delete() ->source
指定した変数の配列全体を削除する.取得したH_DATA構造体の中で値が存
在する要素をfree()し,最後にh_list_del()にてハッシュ表から取り除く
作業を行っている.
リストの処理
次にリストの処理を説明する.リストは新しく追加される度にその領域を確保
し,nextを繋げるという形をとる.また,新しいリストは後ろへと追加されて
いく形式とした.これを実現するのに実装したのが以下の関数である.
- insert_list() ->source
リストを挿入する.
初めにVALUEの構造体の領域を作成し,その中の
list_dataも代入する文字の大きさに合わせてcallocし,値のコピーを行な
う.次にH_DATA構造体を取得し,その変数が既に存在しているかを調べる.
登録済みだった場合にはそのlistを辿り,nextがNULLを指している場所に
作成したVALUE構造体を指すようにする.変数が未登録だった場合には新し
く領域を作成し,keyの領域の取得と値のコピー,listに作成したVALUEを指
すようにし,ハッシュ表に新しいH_DATA構造体を指すようにして完了とす
る.
- printListValue() ->source
リスト全体を表示する.取得したH_DATAの構造体からlistを参照し,その
値を出力する.次にそのnextを辿り値を出力する.これをnextがNULLになる
まで続ける.
- key_delete() ->source
リストの指定したデータを削除する.まず,変数の構造体(H_DATA)の取得
を行ない,そのlistへ繋がるVALUEの構造体を先に用意したv_dataに保存する.また,
v_dataのnextをv_tmpに代入する.次にv_dataの値が,指定のデータかどう
かを比較確認し,そうであった場合にはそのlist_dataを解放,v_dataを解
放し,後ろに続くデータがない場合には変数自体をハッシュ表から削除する.
指定のデータでない場合にはv_dataのnextを参照し,進めていく.その際,
v_dataとv_tmpを同時にずらしていき,値が見つかった場合,v_dataの
nextをv_tmpのnextに書き換えることでv_tmpの構造体をリストから切り離
し,v_tmpのlist_dataとv_list自身をfree()している.また,この時も
削除により,値が一つもなくなった場合にはその変数をハッシュ表から削除
している.
- list_delete() ->source
リスト全体の削除を行なう.取得した変数の構造体の中のVALUEをfree_key
を用いて再帰的にfree()を行なう.その後,変数をハッシュ表から取り除い
て終了する.
作成したプログラムで配列の処理を行なった場合とリストの処理を行なった場合のそれぞれの
実行結果を以下に示す.
配列の操作
j04013 % ./my-shell [~/Documents/classes/junior/unix/level3]
[j04013]% $a=tomoyose
[j04013]% $a[1]=uehara
[j04013]% $a[2]=chinen
[j04013]% ref $a
$a[0]=tomoyose
$a[1]=uehara
$a[2]=chinen
[j04013]% ref $a[1]
$a[1]=uehara
[j04013]% del $a[1]
[j04013]% ref $a
$a[0]=tomoyose
$a[2]=chinen
[j04013]% del $a
[j04013]% ref $a
no such variable $a
[j04013]% $a=tamayose
[j04013]% ref $a
$a[0]=tamayose
上記の実行では変数$a[0]に"tomoyose",$a[1]に"uehara",$a[2]に"chinen"
といった値を代入し,配列$aに入っている値を表示.次にdelkeyを用いて値
"kazuya"を削除し,削除されたのを確認.次に変数自体を削除し,削除されて
いることを確認.最後に一度削除した値がまた使えることを示している.
リストの操作
j04013 % ./my-shell [~/Documents/classes/junior/unix/level3]
[j04013]% @a=eisaku;@a=kazuya;@a=naohisa
[j04013]% ref @a
@a=eisaku->kazuya->naohisa
[j04013]% delkey @a=kazuya
[j04013]% ref @a
@a=eisaku->naohisa
[j04013]% delkey @a
[j04013]% ref @a
no such variable @a
[j04013]% @a=yuichiro
[j04013]% ref @a
@a=yuichiro
[j04013]% delkey @a=yuichiro
[j04013]% ref @a
no such variable @a
リストは変数に代入していくことでそれがリストとして保存されるようになっ
ている.
上記では"eisaku","kazuya","naohisa"をリストとして形成し,
間の"kazuya"を削除.次にこのリスト自体を削除し,最後に値を指定して削除
する際にリストが空になった時はハッシュ表から削除されているのを示してい
る.