AM-Scheme(3)

この所、私のWindows機はご難続きである。今度は、メーラーとして使っている Thunderbirdの ごみ箱が忽然として姿を消してしまったのである。

ごみ箱が無いと、毎日押し寄せてくるSPAMを消せないのだ。ごみ箱が無いとどんな症状になるか と言うと、メニューから、迷惑メールを削除を選んでも、ごみ箱へ行かないのだ。ごみ箱って、 大切ですよ。

ごみ箱が無いなら作っちゃえとばかり、まんまの名前のフォルダーを作ってみた。 でも、これは Thunderbirdから、ごみ箱認定を受けられなかったみたいで、迷惑メール削除を選んでも、移動 してくれなかった。こりゃ困ったわい。早速、先生に聞いてみましたよ。

そしたら、やっぱり同例があり、解決方法は、一時バックアップを取り、プロファイルマネージャで 新規ユーザーを作り、、という方法が提示されていた。でもね、その通りにやるのもちょっと 負けた気分になるので、ちょいと調べてみたよ。

幸いおいらのマシンは女房と共有してるんで、女房のアカウントと比べてみた。そしたら、女房のには 、ごみ箱の変わりに、Trash.sbd と言うフォルダー、Trash.mstとTrashと言うファイルが存在 してた。おいらのには、それらが全く無かった。

だったら、この3つ組をコピーしちゃえと思ったけど、それは思い留めて、英語版のフォルダー Trash を Thunderbird上で作成してあげた。そして、Thunderbirdを再起動してあげたら、ちゃんとTrashが 消えて、ごみ箱になってた。Thunderbirdもこのごみ箱は認定してくれたよ。

何となく、Thunderbirdの仕組みが見えてきましたねぇ。Windows上のごみ箱も同じだろうな。

相も変わらず、amscmの追っかけ

次は、REPLの P を、先に見ておこうと思う。また、gdbで追いかける。gdbもコマンドが色々 あるので、リファレンス・カード を、取り寄せておくといいだろう。但し、"GDB under GNU Emacs"欄のキーバインドは、昔の ものなので、使えないです。

(gdb) b printlist
Breakpoint 1 at 0x8055e26: file print.c, line 154.
  :
> "abc"

Breakpoint 1, printlist (list=0x28216fa8, file=0x805b444, f=1, pr=1)
    at print.c:154
154         register pr_operator = OP_PRBEGIN;
(gdb) n
155         register long length = 0;
(gdb) n
158         switch(pr_operator) {
(gdb) n
161         g_push(&list) g_push(&file)
(gdb) n
162         p_save(OP_PREND, NIL)
(gdb) n
163         p_goto(OP_PRLST)
(gdb) n
158         switch(pr_operator) {
(gdb) n
170         if (!ispair(list)) {
(gdb) n
171             if (isvector(list)) {
(gdb) n
187             } else if (isbox(list)) {
(gdb)
194                 length += printatom(list, file, f, pr);
(gdb) s
printatom (l=0x28216fa8, file=0x805b444, f=1, pr=1) at print.c:56
56          if (l == NIL) {
(gdb)
59          } else if (l == T) {
(gdb)
62          } else if (l == F) {
(gdb)
65          } else if (l == eof_object) {
(gdb)
68          } else if (isinteger(l)) {
(gdb)
70          } else if (isreal(l)) {
(gdb)
74          } else if (isstring(l)) {
(gdb)
75              if (f) {   /* write */
(gdb)
76                  len = strlength(l); p = strvalue(l);

どうやら、場合分けして表示してるようですねぇ。 細かに追って行っても、面白くなさそうなので、後は、ソースを見ると言う事で、お茶を 濁す事にしますか。

次はいよいよ、Evalに突入だけど、その前に、、

AM-Schemeでは、schemeのデータがHeap上にTAGを付けて格納されてます。そのデータをgdbから 確認しようとすると

> (cons 1 '(2 3))

Breakpoint 1, printlist (list=0x28217058, file=0x805b444, f=1, pr=1)
    at print.c:154
154         register pr_operator = OP_PRBEGIN;
(gdb) p list
$2 = 0x28217058
(gdb) p *list
$3 = {_type = 10, _new = 0x28216fb8}

こんな風にしか表示してくれません。えーと、_type が10って、何だっけな? amscm.hを見る はめになって、はなはだ宜しくない。そこで、gdb用のスクリプトを書く事にしました。

以下は、.gdbinitの内容です。(全部のstructは定義してませんが)

## .gdbinit for AM-Scheme struct

define rp
    set $rbasic = (struct dummy_cell *)$arg0
    set $flags = (*$rbasic)._type & 0xff

    if ($flags == 0x01)
      print "STRING"
      print *(struct _str_cell*)$rbasic
    end
    if ($flags == 0x02)
      print "INTEGER"
      print *(struct _int_cell*)$rbasic
    end
    if ($flags == 0x06)
      print "SYMBOL"
      print *(struct _symbol_cell*)$rbasic
    end
    if ($flags == 0x07)
      print "Syntax"
      print *(struct _symbol_cell*)$rbasic
    end
    if ($flags == 0x08)
      print "Prim1"
      print *(struct _primitive_cell*)$rbasic
    end
    if ($flags == 0x09)
      print "Prim2"
      print *(struct _primitive_cell*)$rbasic
    end
    if ($flags == 0x0a)
      print "PAIR"
      print *(struct _cons_cell*)$rbasic
    end
    if ($flags == 12)
      print "CLOSURE"
      print *(struct _cons_cell*)$rbasic
    end
    if ($flags == 14)
      print "MACRO"
      print *(struct _cons_cell*)$rbasic
    end
end

分かる人が見れば、ははー、どこからコピペしたなと直ぐに分かってしまいます。さて 何処からコピペしたでしょうか? (ほら、あなたの身近にありますよ)

(gdb) rp list
$4 = "PAIR"
$5 = {_type = 10, _car = 0x28216fb8, _cdr = 0x28216ff8}
(gdb) rp 0x28216fb8
$6 = "INTEGER"
$7 = {_type = 2, _sign = 1 '\001', _size = 1, _array = {1}}
(gdb) rp 0x28216ff8
$8 = "PAIR"
$9 = {_type = 10, _car = 0x28216fc8, _cdr = 0x28216fe8}
(gdb) rp 0x28216fc8
$10 = "INTEGER"
$11 = {_type = 2, _sign = 1 '\001', _size = 1, _array = {2}}
(gdb) rp 0x28216fe8
$12 = "PAIR"
$13 = {_type = 10, _car = 0x28216fd8, _cdr = 0x805b460}
(gdb) rp 0x28216fd8
$14 = "INTEGER"
$15 = {_type = 2, _sign = 1 '\001', _size = 1, _array = {3}}
(gdb) c
Continuing.
(1 2 3)

こんな具合に、見やすくなりました。

いよいよ eval

Eval_CycleにBPを置いて、だらだらと動きを追ってみる。

Breakpoint 2 at 0x8049037: file eval.c, line 44.
(gdb) c
Continuing.
> (cons "abc" '(123 456))

Breakpoint 2, Eval_Cycle (eval_operator=1) at eval.c:44

codeはPAIRだけどSYMBOLじゃないので、OP_E0ARGSを次にやるんだよと指示しつつ、condeの car(この場合は、cons)を取り出している。

続いて、OP_E0ARGSの所では、MACROじゃないので、次は、codeにcdrを取りつつ、OP_E1ARGSの評価準備。 続いて評価を進めると(この評価って、らっきょの皮を剥くように剥がしていくのが面白い) QUOTEが出てくるので、そこで取り合えず、剥くのは止め。

その後、OP_E3ARGSの中で、args[0]にconsを復帰、引数はargs[1]([2])にセットしてからOP_APPLYに突入。args[0]を解析した 結果、consは、T_PRIM2族である事が判明。該当するバイナリーの実行ルーチンを呼び出す。

実際に呼び出されるのは、PR_CONS() 。その中で、cons(args[1],args[2])を呼び出して、その 結果を返している。argsにはどんなデータが渡っているかと言うと

(gdb) rp args[1]
$21 = "STRING"
$22 = {_type = 1, _len = 3, _svalue = "a"}
(gdb) rp args[2]
$23 = "PAIR"
$24 = {_type = 10, _car = 0x28216fb8, _cdr = 0x28216fd8}
(gdb) rp 0x28216fb8
$25 = "INTEGER"
$26 = {_type = 2, _sign = 1 '\001', _size = 1, _array = {123}}

最終的な結果は

(gdb) c
Continuing.
("abc" 123 456)

と言う事で、Eval_Cycleの長い旅はやっと終わったのです。

長い旅を振り返ってみると、Eval_Cycleって、継続って言うかCPS変換されてるんですね。 OP_Eval -> OP_E0ARGS -> OP_E1ARGS -> OP_E2ARGS -> OP_E3ARGS -> APPLY -> T_PEIM2 -> OP_EVEND という具合に(PRIM2族の場合)制御が流れるように仕組まれてる。

馬鹿とgdbは使いよう

ここまで、gdb の n(ext)コマンドとb(reak)コマンドを主に使ってきた。でも、冒頭のQuick referenceを 見てたら、もっと便利に使えるコマンドが載ってたよ。

  1. tb 一回だけbreak させるコマンド
  2. watch ある変数を見張ってて、値が変化したらbreak
  3. finish 関数の終わりまで、継続実行
  4. until for(or while)の終わりまで実行

ちと遊んでいたら、、、

こうして、いろいろ遊んでいたら、gensym なんて言う手続きに遭遇した。これってMACROによく 登場するやつだ。だぶる事がないSYMBOLを作ってくれるはずなんだが。。

> (gensym)
g0
> (gensym)
g0

だぶってるじゃん。goshではどうなるか、調べてみると、

gosh> (gensym)
G1
gosh> (gensym)
G2

そうだよなあ。こうならなくっちゃ。ひょっとして、20年を経て、今蘇ったBUG? 早速、ソースを見る。

[sakae@nil ~/v110/src]$ grep gensym *.c
init.c:    mk_prim2(PR_GENSYM, "gensym", AT_LEAST_N(0));
mk.c:pointer gensym(head)
prim.c:pointer PR_GENSYM() /* gensym */
prim.c:            Error("gensym -- first argument must be string");
prim.c:        return (gensym(strvalue(args[1])));
prim.c:    } else return(gensym(NULL));

本体は、mk.c に有り?

pointer gensym(head)
char *head;
{
    long symcnt = 0, len;
    register pointer x;
    int hashpos;

    do {
        /* generate a symbol, test whether it is new or not */
        if (head != NULL)
            sprintf(&strbuff[0], "%s%lu", head, symcnt++);
        else
            sprintf(&strbuff[0], "G%lu", symcnt++);
        /* check in oblist */
        len = (long)strlen(&strbuff[0]);
        hashpos = hash(&strbuff[0], len);
        for (x = oblist[hashpos]; x != NIL ; x = cdr(x)) {
            if (str_eqv(&strbuff[0], len, symname(car(x)), symlen(car(x))))
                break;
        }
    } while (x != NIL);
    x = mk_sym(&strbuff[0], len, STDCHARCASE);
    return (x);
}

どう動くのかな? 引数は有っても無くてもよくて、無い場合は、"G"で始まる文字に連番の 数字をくっつけて、仮のシンボルを作る。それが、既に登録されてない(新規)事を確認した 後、登録してるんか。あれ? 先ほどの例では、"g1"から始まったな。"G1"を確認して"g1"を 登録してたら、おかしいよね。STDCHARCASEを確認したら小文字に変換を指定されてたよ。

どう修正してもいいけど、私は、頭文字を最初から"g"にした。

> (gensym)
g0
> (gensym)
g1

直った。直した。パチパチパチ。