AM-Scheme(2)
前回見つけた継続渡し形式(CPS)のページを 辿ってみたら、Windows Script Host で、javascriptの真似事が出来る事を知った。 javascriptだと、println と書く所を
WScript.Echo("hello, world.");
の様に書かなければならない事を我慢すれば、javascriptと同様な事が出来るみたいだ。javascritのインタープリタの変わりに CScript /NoLogo hello.js とすれば、ターミナルに、hello, word. と、出てくるはずなんだが やってみたら、"js を持つスクリプトエンジンはありません。" という、意味不明な事を言ってきた。
これだから困るんだよな。Windows 7 もいいけど、その前にやる事あるだろうに! こういう場合は 冴子先生とか、ワトソン博士はあてにならないので、google先生頼り。
聞いてみたら、ファイル拡張子”.js”のスクリプトエンジンについての質問 なんてのが、出てきた。修正方法は、えーーーー、regedit.exe を使えと。仰せの通りに 調べてみたら、私のは、何も設定して無かった。壊れてこうなったのか、セキュリティー上の 理由で削除されたのかは、不明だ。 ちょいと、設定してあげたら、ちゃんと認識出来るようになった。やれやれ!
やれやれとため息をついていたら、Real World Haskellを 献本頂いた。本だけなく、かぶと虫のバッチ付き。RWH読書会のご縁で、頂いたものだ。 (ちゃんと勉強しろって励ましに、感謝致します。ありがとうございました。)
帯には、 "Haskellで書ける幸せ" なんて言うキャッチコピーが踊っている。自分には、ParsecとかQuickCheckが 一番嬉しいかな。また、Haskellで書いて幸せになろう。
AM-Schemeで、S式の読み込み
前回の続きです。前回は、Schemeが起動して、プロンプトが出てくるまでの流れを、気ままに 見てきた。その中で、すっとばしてしまった、初期ファイル(amscm.scm)の読み込みを追って みる事にする。
LoadFromFileName の中で 指定されたファイルをオープンし、mk_inputにファイルディスクリプタを 渡して、schemeが扱う構造を作る。そして、そのオブジェクトを引数にして、Rep_From_Inputを 呼び出す。
このRep_From_Inputは、いわゆる REPL(read eval print loop)だ。
139 g_push(&l) 140 for(;;) { 141 tok = get_token(l); 142 Is_read_complete = 0; 143 Read_Cycle(tok); 144 Is_need_prompt = 0; 145 if (Is_read_complete) { 146 Init_read_stack() 147 Is_need_prompt = 1; 148 if (r_value == eof_object) { 149 g_pop(1) 150 break; 151 } 152 read_white(l); 153 s_save(OP_EVALEND, code) 154 code = r_value; 155 r_value = Eval_Cycle(OP_EVAL); 156 if (IsVerbose) { 157 printlist(r_value, std_output, 1, 1); 158 PrintChar(std_output, '\n'); 159 } 160 } 161 } 162 return 1;
一つ トークンを読み込み、それを引数にして、それぞれのトークン毎の処理ををRead_Cycyleで行う。 読み込みが終了すると、読み込んだオブジェクトがeofか確認の後、スタックに評価終了TAGを積んで から、Eval_Cycleで評価を実行。結果を出力する必要がなるなら、printlistを呼んで表示させる。
オブジェクトが、eofだった場合は、LoadFromFileName内に復帰して、ファイルのクローズ処理等を 行う。
上の大まかな流れは、gdbを使って追いかけてみたんだけど、143行目で tok の値を確認しようと したら、そんなのここでは見られません(意訳)と言われてしまった。全く役立たずのgdbだ事!、 と、最初は思っていたんだけど、はっと、頭にひらめきが走りましたよ。 賢いコンパイラが最適化しちゃってるに違い無いと。 Makefile に記述してあった、最適化指定を、-O0 (オー、ゼロ)に変更してから、再コンパイル したら、ちゃんと見られるようになりました。これって、gdbを使う時の鉄則ですね。
そんじゃ、Read_Cycleを見て行くけど、肝になるのは、上のリストの142行目に出てくる Is_read_completeと言うフラグ。Read_Cycleの中で、解析した結果、Eval_Cycleして良い状態に なったら、このフラグを1にしている。(ようするに、S式を一つ読み込んだ状態) また、Read_CycleとEval_Cycleとの値の受け渡しには、グローバル変数の r_valueが使われている。
223 switch (tok) { 224 case TOK_LPAREN: 225 s_push(NULL) 226 r_push(OP_RDLST) 227 return; 228 case TOK_RPAREN: 229 switch (r_pop()) { 230 case OP_RDLST: 231 r_value = get_list_from_stack(NIL); 232 break; 233 case OP_RDDOT: 234 if (r_pop() != OP_RDLST) 235 Error("reader -- Illegal dot expression"); 236 r_value = get_list_from_stack(r_value); 237 break; 238 case OP_RDVEC: 239 r_value = get_vector_from_stack(); 240 break; 241 default: 242 Error("reader -- Illegal ')'"); 243 } 244 break; 245 case TOK_DOT: : 273 case TOK_EOF: 274 if (r_pop() != OP_RDEND) 275 Error("reader -- Illegal EOF"); 276 else { 277 Is_read_complete = 1; 278 r_value = eof_object; 279 return; 280 } 281 break; 282 default: 283 Error("reader -- Illegal token %d", tok); 284 } 285 286 AGAIN: 287 switch (r_pop()) { 288 case OP_RDLST: 289 s_push(r_value) 290 r_push(OP_RDLST) 291 return; : 311 case OP_RDEND: 312 Is_read_complete = 1; 313 return; 314 default: 315 Error("reader -- Illegal read operator"); 316 }
最初は、渡されてきたtokによって、処理を場合分けしてる。左括弧の場合は、こりゃリストが 始まりましたよというTAGをread_stackへプッシュして、このルーチンを抜ける。 右括弧の場合は、get_list_from_stack()を使って、スタックをポップしつつ、リストを構築。 その後、AGAIN:とラベルされた所で、更に後処理を行っている。
ちと実例をば
[sakae@nil ~/v110/src]$ cat z.scm (define xyz 12345678) xyz
こんなファイルを、REPL から、loadを使って読み込んでみます。数字の扱いをどうしてるかを 中心に見ます。
Breakpoint 2, LoadFromFileName (name=0x28216fb0 "z.scm", IsVerbose=1, IsInit=0) at generic_os.c:111 111 if ((fp = fopen(name, "r")) == NULL) { (gdb) b Read_Cycle Breakpoint 3 at 0x8056b06: file read.c, line 223. (gdb) p name $1 = 0x28216fb0 "z.scm" (gdb) c Continuing. ;loading z.scm Breakpoint 3, Read_Cycle (tok=1) at read.c:223 223 switch (tok) { (gdb) p tok $2 = 1
左括弧ですね。続けて行きます。
(gdb) c Continuing. Breakpoint 3, Read_Cycle (tok=5) at read.c:223 223 switch (tok) { (gdb) p tok $3 = 5
ATOMって事ですから、"define"でしょう。どうなるか、一応見ておきます。
(gdb) n 265 r_value = mk_atom(&strbuff[0]); (gdb) p strbuff $4 = 0x8059ea0 "define" (gdb) n 266 break; (gdb) n 287 switch (r_pop()) { (gdb) n 289 s_push(r_value) (gdb) n 290 r_push(OP_RDLST) (gdb) n 291 return;
"define"と言うATOMを作って(既にありますので、この時点で新規に作る事は無く、オブジェクトの 場所が、返されますが)、それを、schemeのスタックに入れてます。この時点では、まだ リスト中なので、read_stackには、リストでっせ情報を再度Pushしてから、抜けます。
(gdb) n 265 r_value = mk_atom(&strbuff[0]); (gdb) p strbuff $7 = 0x8059ea0 "12345678" (gdb) s mk_atom (q=0x8059ea0 "12345678") at mk.c:306 306 switch (numtype(q, 10)) {
ちょっと先へ進めました。丁度数字を登録しようとしています。numtypeでは、realかintかそれ 以外かを判定してます。今回は、intと判定してくれました。(この判定ルーチンがちと、やや こしいです。何故って、schemeの場合、123xyz なんてのも、いわゆる変数名として許されていますから。 それに、小数点がからんできたりするから、面倒です。)
mk_atom (q=0x8059ea0 "12345678") at mk.c:310 310 return (mki_f_string(q, (long)strlen(q), 10)); (gdb) mki_f_string (num=0x8059ea0 "12345678", len=8, base=10) at mk.c:49 (gdb) 55 size = get_formal_size_of_int(len, base); (gdb) n 56 x = (pointer)Get_Heap(INTEGERSIZE(size)); (gdb) p size $8 = 2
get_formal_size_of_intを使って、与えられた長さの数字列を、10000進法で表したら、何桁に なるか求めています。なお、これは多倍長整数を扱うためです。(INTEGERSIZEは、10000と定義されてます) その後、Short_MuitiとShort_Addを使って、データを格納しています。時間があったら、じっくりと 多倍長ルーチンを見てみたいな。(int.cは、独立性が高いから、ここだけ印刷して、見るのも可)
このルーチンを抜け、Read_Cycleに戻ってくると、オブジェトとして T_INTEGERが格納されてました。
(gdb) c Continuing. Breakpoint 3, Read_Cycle (tok=2) at read.c:223 223 switch (tok) { (gdb) p tok $10 = 2 (gdb) n 229 switch (r_pop()) { (gdb) 231 r_value = get_list_from_stack(NIL); (gdb) 232 break; (gdb) 287 switch (r_pop()) { (gdb) 312 Is_read_complete = 1; (gdb) 313 return;
最後に、トークンは右括弧が来て、これでS式を一つ読み込んだと言う事になります。 後は、このr_valueをcodeに代入して、評価が始まります。
最初のS式は、defineでしたから、12345678をxyzに束縛(代入のようなもの)されます。 次は、ただ xyz としか書かれていませんから、xyzという名前(シンボル)を参照する S式になります。これを評価すると、12345678 が、得られます。
ちと改造
AM-Schemeには、samplesとして、tools.scm というのがついている。内容を見ると、trace や清書機能 lisp-editorという、有用なものが入っていた。amscmの起動時に、コマンド引数から与えるとか、 起動した後に、replからloadコマンドを使って読み込んでもいいけど、ちとめんどい。 折角ソースがあるので、改造して、起動時に読み込んじゃえ。
[sakae@nil ~/v110/src]$ diff -u generic_os.c.org generic_os.c --- generic_os.c.org 2009-10-21 05:48:37.000000000 +0900 +++ generic_os.c 2009-10-30 13:57:02.000000000 +0900 @@ -189,6 +189,7 @@ else if (setjmp(reset_jmp) == 0) { /* changed by A.Kida */ LoadFromFileName(InitFileName, 0, 1); + LoadFromFileName("./tools.scm", 0, 1); /* A detection of any error in a file which is specified as a * command line parameter aborts loading and ignores the files * rest (if any). In such a case, AM-Scheme will begin read /
Cのソースに即値を書いちゃうのって、野良改造って言うんでしょうか?
[sakae@nil ~/v110/src]$ ./amscm Hello, This is AM-Scheme Interpreter Version 1.10 allocated 2*300000 bytes main memory areas allocated 4*10000 bytes stack areas ;loading ./amscm.scm ;loading ./tools.scm > (define (f n) (if (= n 0) 1 (* n (f (- n 1))))) f > (trace f) (f) > (f 4) (f 4) (f 3) (f 2) (f 1) (f 0) (f 0) ==> 1 (f 1) ==> 1 (f 2) ==> 2 (f 3) ==> 6 (f 4) ==> 24 24
続いて、lisp-editorを試してみる。
> (ed f) a,d,l,p,r,i,x,c,v,e,b,u,?,! (top): ? ----- list editor command ---- a -- Change current sublist to CAR d -- Change current sublist to CDR l -- Print outline of current sublist p -- Pretty print current sublist r -- Replace current sublist to one typed follow i -- Insert one typed follow to CAR x -- Delete CAR of current sublist c -- Copy current buffer to CAR v -- Yank current sublist to buffer e -- Edit buffer b -- Show buffer u -- Exit current sublist (up one level) ! -- Exit to toplevel a,d,l,p,r,i,x,c,v,e,b,u,?,! (top): p (define (f n) (if (= n 0) 1 (* n (f (- n 1))))) a,d,l,p,r,i,x,c,v,e,b,u,?,! (top): d d l a,d,l,p,r,i,x,c,v,e,b,u,?,! (1): a,d,l,p,r,i,x,c,v,e,b,u,?,! (2): ((if (= n 0) 1 &)) a,d,l,p,r,i,x,c,v,e,b,u,?,! (2): a l a,d,l,p,r,i,x,c,v,e,b,u,?,! (3): (if (= n 0) 1 &) a,d,l,p,r,i,x,c,v,e,b,u,?,! (3): d d l a,d,l,p,r,i,x,c,v,e,b,u,?,! (4): a,d,l,p,r,i,x,c,v,e,b,u,?,! (5): (1 (* n (f (- & &)))) a,d,l,p,r,i,x,c,v,e,b,u,?,! (5): d l a,d,l,p,r,i,x,c,v,e,b,u,?,! (6): ((* n (f (- n &)))) a,d,l,p,r,i,x,c,v,e,b,u,?,! (6): a l a,d,l,p,r,i,x,c,v,e,b,u,?,! (7): (* n (f (- n &))) a,d,l,p,r,i,x,c,v,e,b,u,?,! (7): a a,d,l,p,r,i,x,c,v,e,b,u,?,! (8): l * a,d,l,p,r,i,x,c,v,e,b,u,?,! (8): r + ? a,d,l,p,r,i,x,c,v,e,b,u,?,! (8): l + a,d,l,p,r,i,x,c,v,e,b,u,?,! (8): ! a,d,l,p,r,i,x,c,v,e,b,u,?,! (top): p (define (f n) (if (= n 0) 1 (+ n (f (- n 1))))) a,d,l,p,r,i,x,c,v,e,b,u,?,! (top): u A)bandan, E)valuate, C)ontinue : e f > (f 4) 11 > f #<CLOSURE>
S式の移動って面倒だなあ。今居る所を確認しながら移動してかないと、思わぬ所へ行っちゃう よ。a とか d で移動した後 自動で、l をやって欲しいぞ。 まるで、昔々の ed を使ってるみたいだ。(って、あんたは、どんだけ古い人なんよ。)