今回は COMET2A 用の簡単な「連結リストライブラリ」を作ってみましょう。
連結リスト (Linked List) はデータを一方向 [*1] につないだデータ構造です。基本的には Lisp / Scheme のリストと同じデータ構造ですが、拙作のページ「Python 入門 第 5 回 連結リスト」や「Ruby 入門 第 10 回 連結リスト」で作成したプログラムのように、複数の要素を格納するデータ構造として使用することを目的とします。下図に連結リストの構造を示します。
(1)変数
┌─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│・┼─→│10│・┼→│20│・┼→│30│/│ /:終端(null : 0)
└─┘ └─┴─┘ └─┴─┘ └─┴─┘
(2)ヘッダセル
┌─┬─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│ │・┼─→│10│・┼→│20│・┼→│30│/│ /:終端(null : 0)
└─┴─┘ └─┴─┘ └─┴─┘ └─┴─┘
図 : 連結リストの構造
連結リストはセル (cell) というデータをつなげて作ります。セルにはデータを格納する場所 (CAR) と、次のセルを指し示す場所 (CDR) から構成されます。上図でいうと、箱がひとつのセルを表していて、左側にデータを格納し、右側に次のセルへのアドレスを格納します。リストの終わりを示すため、最後のセルの右側には特別な値 (null : 0) を格納します。
そして、図 (1) のように先頭セルへのアドレスを変数に格納しておけば、この変数を使って連結リストにアクセスすることができます。また、図 (2) のようにヘッダセルを用意する方法もあります。今回は (2) のようにヘッダセルを使ってプログラムを作ることにします。
連結リストの長所は、データの挿入や削除が簡単にできることです。配列でデータの削除や挿入を行う場合、要素を移動しなければいけませんが、連結リストはセルを付け替えるだけで実現できます。逆に、配列はどの要素にも一定の時間でアクセスすることができますが、連結リストはセルを順番にたどっていくため、後ろのデータになるほどアクセスに時間がかかります。これが連結リストの短所です。
連結リストを操作する基本的なサブルーチンを表に示します。
| サブルーチン | 機能 |
|---|---|
| make-list | 空の連結リストを生成する |
| destory-list ls | 連結リスト ls を廃棄する |
| list-ref ls n | リスト ls の n 番目の要素を返す |
| list-set ls n x | リスト ls の n 番目の要素をデータ x に置き換える 置き換えられた要素を返す |
| list-insert ls n x | リスト ls の n 番目の位置にデータ x を追加する |
| list-delete ls n | リスト ls の n 番目の要素を削除する |
| list-clear ls | リスト ls を空にする |
| list-size ls | 要素の個数を返す |
| list-empty | 空リストであれば 1 を、そうでなければ 0 を返す |
| vector->list vec n | 大きさ n のベクタ vec を連結リストに変換する |
引数は右側からスタックに積むことにします。つまり、左側の引数がスタックの低位アドレスに格納されます。要素の位置はベクタと同様に 0 から数えます。位置 n がリストの長さよりも大きい場合はエラー終了することにしましょう。
それではプログラムを作りましょう。まず最初に、セルを表すデータ構造を決めておきます。次の図を見てください。
CELL + 0 [ data ] : CAR
+ 1 [ adrs ] : CDR size = 2 word
gr1 = CELL とすると
ld gr0 0 gr1 => data
ld gr0 1 gr1 => adrs
図 : セルのデータ構造
セルの大きさは 2 word とします。必要なメモリは malloc で取得します。先頭アドレスを CELL とすると (CELL + 0) 番地にデータを格納し、(CELL + 1) 番地に次のセルの先頭アドレスを格納します。
CELL のアドレスを gr1 にセットすると、データは (ld gr0 0 gr1) で、次のセルのアドレスは (ld gr0 1 gr1) で gr0 に取り出すことができます。リストの終端は null (0) で表します。
セルのたどり方は実に簡単です。次の図を見てください。
cp1 cp2 cp3
┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│10│・┼→│20│・┼→│30│・┼→
└─┴─┘ └─┴─┘ └─┴─┘
↑ ↑
(1) (2)
(1) gr1 = cp1 : (ld gr1 1 gr1) => gr1 = cp2
(1) gr1 = cp2 : (ld gr1 1 gr1) => gr1 = cp3
図 : セルのたどり方
セル cp1 の CDR にはセル cp2 のアドレスが格納されています。レジスタ gr1 が cp1 を指している場合、gr1 の値を (ld gr1 1 gr1) で更新すれば、gr1 の値はセル cp2 になります (1)。さらに gr1 の値を (ld gr1 1 gr1) で更新すれば、gr1 の値は cp3 になります (2)。
次に、セルを生成するサブルーチン make-cell とセルを廃棄する delete-cell を作ります。
リスト : セルの生成と廃棄
; 新しいセルの生成
; 入力 : +2) a データ
; +3) b セル or null
; 出力 : gr0 新しいセル
make-cell
(link gr7 0)
(push 2) ; セルの大きさ
(call malloc) ; new cell -> gr0
(pop gr1) ; 引数を廃棄
(and gr0 gr0)
(jze make-cell-err)
(ld gr1 2 gr7)
(st gr1 0 gr0) ; CAR をセット
(ld gr1 3 gr7)
(st gr1 1 gr0) ; CDR をセット
(unlk gr7)
(ret)
make-cell-err
(push list-error0)
(call write-line)
(halt)
list-error0
(dc "make-cell : Out of Memory" 0)
; セルを解放する
; 入力 sp + 1) : セル
; 出力 None
delete-cell
(ld gr0 1 sp)
(push 0 gr0)
(call free)
(pop gr0)
(ret)
make-cell の引数は格納するデータと、後ろに連結するセルのアドレスです。Lisp の関数 cons と同じ働きをします。最初に、malloc で 2 word のメモリを取得します。そして、引数を取り出してセルの CAR と CDR にセットするだけです。malloc が 0 を返した場合はエラーメッセージを出力して halt で終了します。delete-cell も簡単です。引数のセルを取り出して、それを free で解放するだけです。
write-line は文字列を出力するサブルーチンです。次のリストを見てください。
リスト : 文字列の出力
; 入力 +2) : バッファ, null (0) で終端していること
; 出力 None
write-line
(link gr7 0)
(push 0 gr2)
(ld gr2 2 gr7)
write-line-loop
(ld gr0 0 gr2)
(jze write-line-exit)
(push 0 gr0)
(call write-char)
(pop gr0)
(lad gr2 1 gr2)
(jump write-line-loop)
write-line-exit
(pop gr2)
(unlk gr7)
(ret)
データを順番に取り出して write-char で出力するだけです。これをライブラリ lib.cas に追加しておきます。
次はリストを生成する make-list と、リストを廃棄する destroy-list を作ります。
リスト : リストの生成と廃棄
; リストの生成
; 入力 : None
; 出力 : gr0 ヘッダセル
make-list
(lad sp -2 sp)
(xor gr0 gr0)
(st gr0 0 sp)
(st gr0 1 sp)
(call make-cell)
(lad sp 2 sp)
(ret)
; リストを廃棄する
; 入力 +2) : ヘッダセル
; 出力 None
destroy-list
(link gr7 0)
(push 0 gr2)
(ld gr1 2 gr7) ; ヘッダセルを取得
destroy-list-loop
(ld gr2 1 gr1) ; (CDR gr1) -> gr2
(push 0 gr1)
(call delete-cell) ; 不要なセル gr1 を回収
(pop gr1)
(ld gr1 gr2)
(jnz destroy-list-loop)
(pop gr2)
(unlk gr7)
(ret)
リストの生成は簡単です。make-cell で新しいセルを取得し、それを返すだけです。このとき、CDR に 0 をセットすることで、空の連結リスト (ヘッダだけのリスト) を生成することができます。ヘッダの CAR にセットされる値は連結リストの要素数なので 0 に初期化します。
destroy-list はセルを順番にたどり、free でセルを解放します。gr1 に解放するセルをセットし、次のセルを gr2 にセットします。delete-cell で gr1 のセルを解放したら、gr2 のセルを gr1 にセットします。このとき、gr1 が null (0) であれば処理を終了します。そうでなければ、destroy-list-loop にジャンプして処理を続けます。
次に、作業用のサブルーチンとして n 番目のセルを求める処理を作ります。サブルーチン名は cell-ref としました。次のリストを見てください。
リスト : n 番目のセルを求める
; 入力 : +2) ヘッダセル
; : +3) 位置 N
; 出力 : gr0 セル
cell-ref
(link gr7 0)
(ld gr0 2 gr7) ; ヘッダセル -> gr0
(lad gr1 -1) ; ヘッダセルからカウント
cell-ref-loop
(cpl gr1 3 gr7)
(jze cell-ref-exit) ; 該当セル
(ld gr0 1 gr0)
(jze cell-ref-err) ; null チェック
(lad gr1 1 gr1)
(jump cell-ref-loop)
cell-ref-exit
(unlk gr7)
(ret)
cell-ref-err
(push list-error1)
(call write-line)
(halt)
list-error1
(dc "cell-ref : Out of range" 0)
cell-ref は N 番目のセルを返します。ヘッダセルを gr0 にセットし、現在の位置を gr1 で表します。最初はヘッダセルなので、gr1 は -1 に初期化します。gr1 が引数 N と等しい場合はセル gr0 を返します。そうでなければ、gr0 を次のセルへ更新して gr1 の値を +1 します。ここでリストの終端をチェックし、gr0 が 0 ならばエラーメッセージを出力して終了します。
次は n 番目の要素を求めるサブルーチン list-ref を作ります。次のリストを見てください。
リスト : n 番目の要素を求める
; 入力 +2) : ヘッダセル
; +3) : 位置 N
; 出力 gr0 : データ
list-ref
(link gr7 0)
(lad sp -2 sp)
(ld gr0 2 gr7)
(st gr0 0 sp)
(ld gr0 3 gr7)
(st gr0 1 sp)
(call cell-ref) ; -> gr0 (セル)
(lad sp 2 sp)
(ld gr0 0 gr0) ; (CAR gr0) -> gr0
(unlk gr7)
(ret)
cell-ref を呼び出して n 番目のセルを求めます。そして、CAR に格納されているデータを取り出して返すだけです。
N 番目のデータを書き換えるサブルーチン list-set も簡単です。次のリストを見てください。
リスト : ; N 番目の値を書き換える
; 入力 +2) : ヘッダセル
; +3) : 位置
; +4) : 値
; 出力 gr0 : 元の値
list-set
(link gr7 0)
(push 0 gr2)
(lad sp -2 sp)
(ld gr0 2 gr7) ; ヘッダセル
(st gr0 0 sp)
(ld gr0 3 gr7) ; 位置
(st gr0 1 sp)
(call cell-ref) ; -> gr0 (セル)
(lad sp 2 sp)
(ld gr1 gr0)
(ld gr0 0 gr1) ; 元の値 -> gr0
(ld gr2 4 gr7)
(st gr2 0 gr1) ; 値を書き換える
(pop gr2)
(unlk gr7)
(ret)
cell-ref を呼び出して N 番目のセルを求めます。次に、セルの CAR から値を取り出して gr0 にセットします。これが返り値になります。それから引数の値を取り出して、セルの CAR にセットします。これでリストの N 番目の要素を書き換えることができます。
次は、データを挿入するサブルーチン list-insert を作りましょう。データの挿入はセルの CDR を書き換えることで実現できます。次の図を見てください。セル (1) とセル (2) の間に、セル (3) を挿入します。
top (1) (2)
┌─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│ ┼──→│10│・┼─ X ─→│20│・┼→│30│/│
└─┘ └─┴┼┘ └─┴─┘ └─┴─┘
│ (3) ↑
│ ┌─┬─┐│
└→│40│・┼┘
└─┴─┘
セル(1)とセル(2)の間にセル(3)を挿入する場合
図 : データの挿入
セル (1) の後ろにセル (3) を挿入する場合、セル (1) の CDR にはセル (2) へのアドレスがセットされているので、この値をセル (3) の CDR にセットします。これで、セル (3) とセル (2) がリンクされます。次に、セル (1) の CDR にセル (3) へのアドレスをセットします。これで、セル (1) とセル (2) の間に、セル (3) を挿入することができます。
プログラムは次のようになります。
リスト : データの挿入
; N 番目にデータを挿入する
; 入力 +2) ヘッダセル
; +3) 位置 N
; +4) データ
; 出力 : 無し
list-insert
(link gr7 0)
(push 0 gr2)
(lad sp -2 sp)
(ld gr0 2 gr7)
(st gr0 0 sp) ; ヘッダセル
(ld gr0 3 gr7) ; 位置 N
(lad gr0 -1 gr0)
(st gr0 1 sp) ; N - 1
(call cell-ref) ; -> gr0 (セル)
(ld gr2 gr0) ; (N-1) -> gr2
(ld gr0 1 gr2) ; (CDR gr2) -> gr0 (N)
(st gr0 1 sp) ; (N) をセット
(ld gr0 4 gr7)
(st gr0 0 sp) ; データ
(call make-cell) ; -> gr0 (NEW)
(st gr0 1 gr2) ; (N-1) -> (NEW) -> (N)
(ld gr0 2 gr7) ; ヘッダセル
(st gr0 0 sp)
(call inc-size) ; size += 1
(lad sp 2 sp)
(pop gr2)
(unlk gr7)
(ret)
連結リストにデータを挿入する場合、挿入する位置のひとつ手前のセルが必要になります。cell-ref で N - 1 番目のセルを求めます。セル (N-1) が見つかれば、その後ろに新しいセル (NEW) を挿入します。N が 0 の場合、cell-ref はヘッダセルを返すので、連結リストの先頭にデータが挿入されることになります。
新しいセルは make-cell で取得します。第 2 引数にセル (N-1) の CDR を指定することで、新しいセルの後ろにセル (N) を接続することができます。そして、(N-1) の CDR を新しいセル (NEW) に書き換えます。これで (N-1) の後ろに (NEW) を挿入することができます。
次は、データを削除するサブルーチン list-delete を作りましょう。データを削除する場合も、セルを付け替えるだけで済ますことができます。次の図を見てください。
(1) (2) (3)
┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│10│・┼×→│20│・┼→│30│・┼→
└─┴┼┘ └─┴─┘ └─┴─┘
│ ↑
└─────────┘
図 : データの削除:セル(2) を削除する場合
セル (1) の後ろにあるセル (2) を削除する場合、セル (1) の CDR をセル (3) へのアドレスに書き換えればいいのです。セル (3) はセル (2) の CDR から求めることができます。
プログラムは次のようになります。
リスト : データの削除
; 入力 : +2) ヘッダセル
; : +3) 位置 N
; 出力 : None
list-delete
(link gr7 0)
(push 0 gr2)
(lad sp -2 sp)
(ld gr0 2 gr7)
(st gr0 0 sp) ; ヘッダセルセット
(ld gr0 3 gr7)
(lad gr0 -1 gr0)
(st gr0 1 sp) ; N - 1 をセット
(call cell-ref) ; -> gr0 セル
(lad sp 2 sp)
(ld gr1 gr0) ; gr1 = (N-1)
(ld gr2 1 gr1) ; gr2 = (N)
(jze list-delete-err) ; 削除データ無し
(ld gr0 1 gr2) ; gr0 = (N+1)
(st gr0 1 gr1) ; (N-1) -> (N+1)
(lad sp -1 sp)
(st gr2 0 sp)
(call delete-cell) ; 不要なセルを回収
(ld gr0 2 gr7)
(st gr0 0 sp)
(call dec-size) ; size -= 1
(lad sp 1 sp)
(pop gr2)
(unlk gr7)
(ret)
list-delete-err
(push list-error2)
(call write-line)
(halt)
list-error2
(dc "list-delete : Out of range" 0)
データを削除する場合も、削除する位置のひとつ手前のセルが必要になります。cell-ref で N - 1 番目のセルを求めます。セル (N-1) が見つかれば、(N-1) の後ろのセル (N) を削除します。
セル (N-1) を gr1 にセットして、削除するセル (N) を (ld gr2 1 gr1) で求めます。gr2 が 0 ならば削除するセルがないので、エラーメッセージを出力して処理を終了します。そうでなければ、gr2 の後ろのセル (N+1) を (ld gr0 1 gr2) で求めます。あとは gr0 を gr1 の CDR にセットすれば、セル (N) を削除することができます。削除したセルは delete-cell でメモリを解放することをお忘れなく。
次はベクタをリストに変換するサブルーチン vector->list を作ります。
リスト : ベクタをリストに変換
; 入力 +2) : ベクタ
; +3) : 個数
; 出力 gr0 : リスト
vector->list
(link gr7 0)
(push 0 gr2)
(push 0 gr3)
(call make-list)
(ld gr3 gr0) ; gr3 : 連結リスト
(ld gr2 3 gr7)
(addl gr2 2 gr7)
(lad gr2 -1 gr2) ; 末尾の要素から挿入する
vector->list-loop
(cpl gr2 2 gr7)
(jmi vector->list-exit)
(ld gr0 0 gr2)
(lad sp -3 sp)
(st gr0 2 sp)
(st gr3 0 sp)
(xor gr0 gr0)
(st gr0 1 sp) ; 先頭に追加していく
(call list-insert)
(lad sp 3 sp)
(lad gr2 -1 gr2)
(jump vector->list-loop)
vector->list-exit
(ld gr0 gr3)
(pop gr3)
(pop gr2)
(unlk gr7)
(ret)
最初に make-list で空の連結リストを生成します。これを gr3 にセットします。gr1 にベクタの先頭アドレス、gr2 に末尾要素のアドレスをセットし、末尾から順番に要素を取り出して、list-insert で連結リストの先頭に追加していきます。
データを連結リストの末尾へ追加する場合、セルをたどらなくてはならないので効率的ではありません。逆に、データを連結リストの先頭に追加するのはとても簡単です。最後に、gr3 を gr0 にセットして呼び出し元に戻ります。
最後に、連結リストを表示するサブルーチン print-list を作ります。
リスト : 連結リストの表示
; リストの要素にサブルーチンを適用する
; 入力 +2) : サブルーチン
; +3) : ヘッダセル
; 出力 None
for-each
(link gr7 0)
(push 0 gr2)
(push 0 gr3)
(ld gr2 2 gr7) ; サブルーチン
(ld gr3 3 gr7) ; ヘッダセル
(ld gr3 1 gr3) ; 最初のセル
for-each-loop
(jze for-each-exit)
(ld gr0 0 gr3) ; 要素をセット
(push 0 gr0)
(call 0 gr2) ; サブルーチンを呼び出す
(pop gr0)
(ld gr3 1 gr3) ; 次のセル
(jump for-each-loop)
for-each-exit
(pop gr3)
(pop gr2)
(unlk gr7)
(ret)
; 空白付き出力
; 入力 +2) : 数値
; 出力 None
prints
(link gr7 0)
(lad sp -1 sp)
(ld gr0 2 gr7)
(st gr0 0 sp)
(call print) ; 要素を出力
(lad gr0 32) ; 空白
(st gr0 0 sp)
(call write-char)
(lad sp 1 sp)
(unlk gr7)
(ret)
; リストの表示
; 入力 +2) : ヘッダセル
print-list
(link gr7 0)
(lad sp -2 sp)
(lad gr0 prints) ; 表示用関数
(st gr0 0 sp)
(ld gr0 2 gr7) ; ヘッダセル
(st gr0 1 sp)
(call for-each)
(lad sp 2 sp)
(unlk gr7)
(ret)
for-each は引数にサブルーチンを受け取り、リストの要素をサブルーチンに渡して呼び出します。prints は引数を print で表示したあと、空白を出力します。print-list は表示用関数として prints を渡して for-each を呼び出すだけです。これで連結リストの要素を表示することができます。
あとのサブルーチンは簡単なので説明は割愛いたします。詳細はプログラムリストをお読みください。
それでは簡単な実行例を示しましょう。
リスト : 簡単なテスト
list-test
(call initialize-heap)
(call print-free-block)
(lad sp -2 sp)
(lad gr0 data00) ; data00 を連結リストに変換
(st gr0 0 sp)
(ld gr0 len00)
(st gr0 1 sp)
(call vector->list) ; -> gr0
(ld gr3 gr0)
(st gr0 0 sp)
(call print-list) ; => 10 20 30 40 50 60 70 80
(call newline)
(call list-size) ; -> gr0
(st gr0 0 sp)
(call print) ; => 8
(call newline)
(lad sp 2 sp)
;
(xor gr4 gr4)
(lad gr2 8)
list-test-loop
(cpl gr4 gr2)
(jze list-test-exit)
(lad sp -3 sp)
(st gr3 0 sp)
(st gr4 1 sp)
(call list-ref) ; -> gr0
(st gr0 0 sp)
(call print) ; 要素を表示
(call newline)
(st gr4 1 sp)
(st gr3 0 sp)
(st gr4 2 sp)
(call list-set) ; 要素を gr4 で書き換える
(lad sp 3 sp)
(lad gr4 1 gr4)
(jump list-test-loop)
list-test-exit
(lad sp -2 sp)
(st gr3 0 sp)
(call print-list) ; => 0 1 2 3 4 5 6 7
(call newline)
(lad gr0 7)
(st gr0 1 sp)
(call list-delete) ; 7 番目の要素を削除
(call print-list) ; => 0 1 2 3 4 5 6
(call newline)
(call list-size)
(st gr0 0 sp)
(call print) ; => 7
(call newline)
(st gr3 0 sp)
(lad gr0 3)
(st gr0 1 sp)
(call list-delete) ; 3 番目の要素を削除
(call print-list) ; => 0 1 2 4 5 6
(call newline)
(call list-size)
(st gr0 0 sp)
(call print) ; => 6
(call newline)
(st gr3 0 sp)
(lad gr0 0)
(st gr0 1 sp)
(call list-delete) ; 先頭要素を削除
(call print-list) ; => 1 2 4 5 6
(call newline)
(call list-size)
(st gr0 0 sp)
(call print) ; => 5
(call newline)
(call print-free-block)
(st gr3 0 sp)
(call destroy-list)
(lad sp 2 sp)
(call print-free-block)
(halt)
len00 (dc 8)
data00 (dc 10 20 30 40 50 60 70 80)
; フリーブロックの表示
print-free-block
(push 0 gr2)
(ld gr2 heap-head)
print-free-block-loop
(jze print-free-block-exit)
(lad sp -2 sp)
(st gr2 0 sp)
(lad gr0 16)
(st gr0 1 sp)
(call printu)
(lad gr0 32)
(st gr0 0 sp)
(call write-char)
(ld gr0 1 gr2)
(st gr0 0 sp)
(call print)
(call newline)
(lad sp 2 sp)
(ld gr2 0 gr2)
(jump print-free-block-loop)
print-free-block-exit
(pop gr2)
(ret)
* (asm-run "list.cas") 43A 28131 10 20 30 40 50 60 70 80 8 10 20 30 40 50 60 70 80 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 4 5 6 6 1 2 4 5 6 5 43A 28115 DFE8 2 DFF8 2 43A 28131 NIL
正常に動作していますね。
ところで、今回のプログラムは malloc でセルを取得しているため、セルの大きさが 2 word であっても、実際に使用されるメモリの大きさは 4 word (2 unit) になります。セルのように大きさが等しいデータ構造を多数用いる場合、ひとつずつ malloc でメモリを取得するのは効率的ではありません。この場合、あらかじめ大きなメモリを malloc で取得しておいて、それを分割してセルに割り当てるとよいでしょう。
また、不要になったメモリを自動的に回収するガベージコレクション (Garbage Collection : GC) があると便利です。GC がないプログラミング言語では、不要になったメモリは自動的に回収されないので、それを行うようにプログラムする必要があります。Lisp / Scheme のように GC があるプログラミング言語では、ゴミになったメモリは自動的に回収されるので、プログラマの負担はそれだけ軽くなります。
今回はここまでです。次回はヘッダセルを使わずに Lisp / Scheme のような GC 機能付きの「連結リスト」の作成に挑戦してみましょう。
;
; list.cas : 簡単な連結リストライブラリ
;
; Copyright (C) 2011 Makoto Hiroi
;
;;;
;;; 内部で使用するサブルーチン
;;;
; 新しいセルの生成
; 入力 : +2) a データ
; +3) b セル or null
; 出力 : gr0 新しいセル
make-cell
(link gr7 0)
(push 2) ; セルの大きさ
(call malloc) ; new cell -> gr0
(pop gr1) ; 引数を廃棄
(and gr0 gr0)
(jze make-cell-err)
(ld gr1 2 gr7)
(st gr1 0 gr0) ; CAR をセット
(ld gr1 3 gr7)
(st gr1 1 gr0) ; CDR をセット
(unlk gr7)
(ret)
make-cell-err
(push list-error0)
(call write-line)
(halt)
list-error0
(dc "make-cell : Out of Memory" 0)
; セルを解放する
; 入力 sp + 1) : セル
; 出力 None
delete-cell
(ld gr0 1 sp)
(push 0 gr0)
(call free)
(pop gr0)
(ret)
; N 番目のセルを求める
; 入力 : +2) ヘッダセル
; : +3) 位置 N
; 出力 : gr0 セル
cell-ref
(link gr7 0)
(ld gr0 2 gr7) ; ヘッダセル -> gr0
(lad gr1 -1) ; ヘッダセルからカウント
cell-ref-loop
(cpl gr1 3 gr7)
(jze cell-ref-exit) ; 該当セル
(ld gr0 1 gr0)
(jze cell-ref-err) ; null チェック
(lad gr1 1 gr1)
(jump cell-ref-loop)
cell-ref-exit
(unlk gr7)
(ret)
cell-ref-err
(push list-error1)
(call write-line)
(halt)
list-error1
(dc "cell-ref : Out of range" 0)
; 要素数を +1 する
; 入力 +2) : ヘッダセル
inc-size
(link gr7 0)
(ld gr1 2 gr7)
(ld gr0 0 gr1)
(lad gr0 1 gr0)
(st gr0 0 gr1)
(unlk gr7)
(ret)
; 要素数を -1 する
; 入力 +2) : ヘッダセル
dec-size
(link gr7 0)
(ld gr1 2 gr7)
(ld gr0 0 gr1)
(lad gr0 -1 gr0)
(st gr0 0 gr1)
(unlk gr7)
(ret)
;;;
;;; 連結リスト操作用サブルーチン
;;;
; リストの生成
; 入力 : None
; 出力 : gr0 ヘッダセル
make-list
(lad sp -2 sp)
(xor gr0 gr0)
(st gr0 0 sp)
(st gr0 1 sp)
(call make-cell)
(lad sp 2 sp)
(ret)
; リストを廃棄する
; 入力 +2) : ヘッダセル
; 出力 None
destroy-list
(link gr7 0)
(push 0 gr2)
(ld gr1 2 gr7) ; ヘッダセルを取得
destroy-list-loop
(ld gr2 1 gr1) ; (CDR gr1) -> gr2
(push 0 gr1)
(call delete-cell) ; 不要なセル gr1 を回収
(pop gr1)
(ld gr1 gr2)
(jnz destroy-list-loop)
(pop gr2)
(unlk gr7)
(ret)
; N 番目の要素を返す
; 入力 +2) : ヘッダセル
; +3) : 位置 N
; 出力 gr0 : データ
list-ref
(link gr7 0)
(lad sp -2 sp)
(ld gr0 2 gr7)
(st gr0 0 sp)
(ld gr0 3 gr7)
(st gr0 1 sp)
(call cell-ref) ; -> gr0 (セル)
(lad sp 2 sp)
(ld gr0 0 gr0) ; (CAR gr0) -> gr0
(unlk gr7)
(ret)
; N 番目の値を書き換える
; 入力 +2) : ヘッダセル
; +3) : 位置
; +4) : 値
; 出力 gr0 : 元の値
list-set
(link gr7 0)
(push 0 gr2)
(lad sp -2 sp)
(ld gr0 2 gr7) ; ヘッダセル
(st gr0 0 sp)
(ld gr0 3 gr7) ; 位置
(st gr0 1 sp)
(call cell-ref) ; -> gr0 (セル)
(lad sp 2 sp)
(ld gr1 gr0)
(ld gr0 0 gr1) ; 元の値 -> gr0
(ld gr2 4 gr7)
(st gr2 0 gr1) ; 値を書き換える
(pop gr2)
(unlk gr7)
(ret)
; N 番目にデータを挿入する
; 入力 +2) ヘッダセル
; +3) 位置 N
; +4) データ
; 出力 : 無し
list-insert
(link gr7 0)
(push 0 gr2)
(lad sp -2 sp)
(ld gr0 2 gr7)
(st gr0 0 sp) ; ヘッダセル
(ld gr0 3 gr7) ; 位置 N
(lad gr0 -1 gr0)
(st gr0 1 sp) ; N - 1
(call cell-ref) ; -> gr0 (セル)
(ld gr2 gr0) ; (N-1) -> gr2
(ld gr0 1 gr2) ; (CDR gr2) -> gr0 (N)
(st gr0 1 sp) ; (N) をセット
(ld gr0 4 gr7)
(st gr0 0 sp) ; データ
(call make-cell) ; -> gr0 (NEW)
(st gr0 1 gr2) ; (N-1) -> (NEW) -> (N)
(ld gr0 2 gr7) ; ヘッダセル
(st gr0 0 sp)
(call inc-size) ; size += 1
(lad sp 2 sp)
(pop gr2)
(unlk gr7)
(ret)
; N 番目のセルを削除
; 入力 : +2) ヘッダセル
; : +3) 位置 N
; 出力 : None
list-delete
(link gr7 0)
(push 0 gr2)
(lad sp -2 sp)
(ld gr0 2 gr7)
(st gr0 0 sp) ; ヘッダセルセット
(ld gr0 3 gr7)
(lad gr0 -1 gr0)
(st gr0 1 sp) ; N - 1 をセット
(call cell-ref) ; -> gr0 セル
(lad sp 2 sp)
(ld gr1 gr0) ; gr1 = (N-1)
(ld gr2 1 gr1) ; gr2 = (N)
(jze list-delete-err) ; 削除データ無し
(ld gr0 1 gr2) ; gr0 = (N+1)
(st gr0 1 gr1) ; (N-1) -> (N+1)
(lad sp -1 sp)
(st gr2 0 sp)
(call delete-cell) ; 不要なセルを回収
(ld gr0 2 gr7)
(st gr0 0 sp)
(call dec-size) ; size -= 1
(lad sp 1 sp)
(pop gr2)
(unlk gr7)
(ret)
list-delete-err
(push list-error2)
(call write-line)
(halt)
list-error2
(dc "list-delete : Out of range" 0)
; リストを空にする
; 入力 +2) : ヘッダ
; 出力 None
list-clear
(link gr7 0)
(push 0 gr2)
(ld gr2 2 gr7)
(ld gr0 1 gr2) ; (CDR gr2) -> gr0
(jze list-clear-exit)
(push 0 gr0)
(call destroy-list) ; ヘッダセル以外のセルを回収
(pop gr0)
(xor gr0 gr0)
(st gr0 1 gr2) ; ヘッダセルの CDR を null で終端
(st gr0 0 gr2) ; size を 0 クリア
list-clear-exit
(pop gr2)
(unlk gr7)
(ret)
; リストの長さを求める
; 入力 sp + 1) : ヘッダセル
; 出力 gr0 : 長さ
list-size
(ld gr0 1 sp)
(ld gr0 0 gr0)
(ret)
; リストは空か
; 入力 sp +1) : ヘッダセル
; 出力 gr0 : 0 空ではない, 1 空
list-empty
(ld gr0 1 sp) ; ヘッダセル
(ld gr0 0 gr0) ; size
(jze empty-list-true)
(xor gr0 gr0)
(ret)
empty-list-true
(lad gr0 1)
(ret)
; ベクタをリストに変換
; 入力 +2) : ベクタ
; +3) : 個数
vector->list
(link gr7 0)
(push 0 gr2)
(push 0 gr3)
(call make-list)
(ld gr3 gr0) ; gr3 : 連結リスト
(ld gr2 3 gr7)
(addl gr2 2 gr7)
(lad gr2 -1 gr2) ; 末尾の要素から挿入する
vector->list-loop
(cpl gr2 2 gr7)
(jmi vector->list-exit)
(ld gr0 0 gr2)
(lad sp -3 sp)
(st gr0 2 sp)
(st gr3 0 sp)
(xor gr0 gr0)
(st gr0 1 sp) ; 先頭に追加していく
(call list-insert)
(lad sp 3 sp)
(lad gr2 -1 gr2)
(jump vector->list-loop)
vector->list-exit
(ld gr0 gr3)
(pop gr3)
(pop gr2)
(unlk gr7)
(ret)
; リストの要素にサブルーチンを適用する
; 入力 +2) : サブルーチン
; +3) : ヘッダセル
; 出力 None
for-each
(link gr7 0)
(push 0 gr2)
(push 0 gr3)
(ld gr2 2 gr7) ; サブルーチン
(ld gr3 3 gr7) ; ヘッダセル
(ld gr3 1 gr3) ; 最初のセル
for-each-loop
(jze for-each-exit)
(ld gr0 0 gr3) ; 要素をセット
(push 0 gr0)
(call 0 gr2) ; サブルーチンを呼び出す
(pop gr0)
(ld gr3 1 gr3) ; 次のセル
(jump for-each-loop)
for-each-exit
(pop gr3)
(pop gr2)
(unlk gr7)
(ret)
; 空白付き出力
; 入力 +2) : 数値
; 出力 None
prints
(link gr7 0)
(lad sp -1 sp)
(ld gr0 2 gr7)
(st gr0 0 sp)
(call print) ; 要素を出力
(lad gr0 32) ; 空白
(st gr0 0 sp)
(call write-char)
(lad sp 1 sp)
(unlk gr7)
(ret)
; リストの表示
; 入力 +2) : ヘッダセル
print-list
(link gr7 0)
(lad sp -2 sp)
(lad gr0 prints) ; 表示用関数
(st gr0 0 sp)
(ld gr0 2 gr7) ; ヘッダセル
(st gr0 1 sp)
(call for-each)
(lad sp 2 sp)
(unlk gr7)
(ret)