ちまた(っても Squeak 界隈限定)で流行りの Coke で遊んでみました 2


id:sumim:20070306:p1 の続き。たらいを回しているだけでは、なんだか申し訳ないような気もしてきたので、Coke Python の壊れているところでも直してみようかな…と思ったのですがまだ理解とスキルが足りていないので、それはやめにして、Coke JavaScript の出力を SpiderMonkey (JavaScript-C) でも動かせるようにできないかチャレンジしてみました。具体的には document.write の代わりに、print を実装して出力にはそれを使えるようにする…ということです。Coke ならでは(?)の、アクロバティックなこともちょっとだけ試しています。


その前に、Coke の JavaScript ではなぜか使えない Date.getTime() をやっつけておきます。getSeconds() とかがどうなっているのか調べてみると、examples/jolt/Object-lib.st の最後に、

Date getSeconds       [ ^self seconds ]
Date getMilliseconds  [ ^self milliseconds ]
Date getMicroseconds  [ ^self microseconds ]

という idst(Pepsi)で書かれた定義を見つけました。どうやらこいつらが examples/jolt/javascript/test.js で使われている Date.getSeconds() 、Date.getMilliseconds() の正体のようです。


ためしにここに、

Date getTime          [ ^Time millisecondClockValue ]


と追加して examples/jolt 下で make してみると、Coke JavaScript 処理系(examples/jolt/javascript 下で ../main boot.k meta-repl.k js.mk - により起動…)で、

Entering JavaScript read-eval-print loop.
:document.write(new Date().getTime());
736083577

というように使えるようになりました。


もっとも、idst まで遡る必要はないので、examples/jolt/javascript/js.mk の (define Date (import "Date")) に続けて、

(define Date (import "Date"))
(define [Date getTime] [(import "Time") millisecondClockValue])

と追加し、Coke で完結させたほうがよいように思います。


これで単純に差をとるだけで、“分またぎ”を気にせずにミリ秒の算出が可能になります。では本題。


▼ print を関数(というかマクロ)として実装する

とりあえず、examples/jolt/python/monty.k に mp-print という良さそうなのがあるので、これを次のように書き直して、先ほどの getTime の定義の直後あたり(最後の read-eval-print ループより前)に追加してみます。

(define PRINT
  (lambda (elements index)
    (let ((result [OrderedCollection new]) (limit  [elements size]))
      (while [index < limit]
        (let ((element [elements at: index]))
          [result add: `[StdOut nextPutAll: [,element asString]]]
          (if [index < [limit - '1]] [result add: '[StdOut space]]))
        (set index [index + '1]))
    result)))

(syntax print
  (lambda (form compiler)
    `(let () ,@(PRINT form '1))))


すると、print が使えるようになります。

Parsing and compiling JavaScript compiler... done.
Entering JavaScript read-eval-print loop.
:print(3,4,5+6,"7\n");
3 4 11 7
:

予約語、構文として print を追加する

Coke の真骨頂は、処理系を走らせながら言語仕様を変えられることにある(…との理解でいる)ので、それをやってみます。


examples/jolt/javascript/js.mk には(ちょうど、getTime を追加した上のあたりに…)、BNF っぽい記法で JavaScirpt の文法を記述したコードがあるのが見つけられます。なお、この記法のシンタックス自体は js.mk の直前に読み込ませている meta-repl.k を介して load される meta-meta.k に記述されています。


この BNF もどきで書かれた JavaScript シンタックスの定義に倣って(というか、それをいじって…)、print 構文追加のために、次の2つの式を用意しておきましょう。

JSName ::= (Letter | '_') (Letter | Digit | '_' | ':')*
  => (let ((s [[[data flattened] concatenated] asSymbol]))
       (if (or (== s 'else) (== s 'for) (== s 'function) (== s 'if) (== s 'new) 
               (== s 'return) (== s 'this) (== s 'var) (== s 'while) (== s 'meta)
               (== s 'print)) `(,s)
         `(name ,s))).
SrcElem ::= #function #name FuncRest   => `(define ,[data second] ,[data third])
          | #print #"(" Expr #")" #";" => `[StdOut nextPutAll: [,[data third] asString]]
          | Stmt                       => [data first].

面倒くさいので、マクロ版と異なり、引数は一つだけにしてあります。


ところで、この JSName と、js.mk の Cmd の定義を見るとわかりますが、Coke JavaScript 処理系には meta というバックドアのようなものが設けられていて、JavaScript の read-eval-print ループの中で、この BNF ライクな式(Meta)も評価できるようになっています。

Cmd ::= #meta Meta => [data second]
      | SrcElem    => [data first].

(余談ですが、この Meta を MCoke に変えると BNF もどきに加えて、素の Coke コードも、meta に続けて書いて評価できるようになります。)


そこで、Coke JavaScript 処理系を起動した後、meta に続けて、先に用意した二つの式を貼り付けてやります。

$ ../main boot.k meta-repl.k js.mk -
Welcome to Jolt 0.1 [VPU 4.0.1 ppc darwin 2004-05-03 14:46:24]
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'syntax.k
; loading: 'object.k
; loading: 'meta.k
; loading: 'meta-coke.k
; loading: 'meta-meta.k
; loading: 'CheckpointStream.k
Parsing and compiling JavaScript compiler... done.
Entering JavaScript read-eval-print loop.
:meta JSName ::= (Letter | '_') (Letter | Digit | '_' | ':')*
:  => (let ((s [[[data flattened] concatenated] asSymbol]))
:       (if (or (== s 'else) (== s 'for) (== s 'function) (== s 'if) (== s 'new) 
:               (== s 'return) (== s 'this) (== s 'var) (== s 'while) (== s 'meta)
:               (== s 'print)) `(,s)
:         `(name ,s))).
:
:
:meta SrcElem ::= #function #name FuncRest   => `(define ,[data second] ,[data third])
:          | #print #"(" Expr #")" #";" => `[StdOut nextPutAll: [,[data third] asString]]
:          | Stmt                       => [data first].

すると、その直後から構文として print が使えるようになります。

:print("3\n");
3


先に定義したマクロ版が起動しているわけではないことは、引数を二つ以上とらせてやると異常終了するという非常に後ろ向きな方法で確認できます。w

:print(3,4);

unexpected end of file!
Abort trap

▼ print(複数引数のとれるマクロ版)と Date.getTime() を使って taky.js を書き直す

function taky(x, y, z){
  if (x <= y) return y;
  else return taky(taky(x-1, y, z), taky(y-1, z, x), taky(z-1, x, y));
}

var x = 12, y = 6, z = 0;

var t0 = new Date().getTime();
var ans = taky(x, y, z);
var t1 = new Date().getTime();
print("tarai(", x, ",", y, ",", z, ") =", ans, "took", t1-t0, "ms\n");
$ ../main boot.k meta-repl.k js.mk taky.gett.print.js
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'syntax.k
; loading: 'object.k
; loading: 'meta.k
; loading: 'meta-coke.k
; loading: 'meta-meta.k
; loading: 'CheckpointStream.k
Parsing and compiling JavaScript compiler... done.
Entering JavaScript read-eval-print loop.
tarai( 12 , 6 , 0 ) = 12 took 7453 ms

$ js taky.gett.print.js
tarai( 12 , 6 , 0 ) = 12 took 44058 ms