Dart (frogc) でJavaScriptの機能を直接呼ぶ方法

Dart specには含まれていないようだが、処理系によってはnativeキーワードでJavaScriptを参照できるようだ*1。ただし、使いたいメソッドすべてに宣言が必要なので多少の手間がかかる。

// JavaScriptのクラスというかオブジェクトを参照する
// 'Math'はJSのネイティブオブジェクト
class NativeMath native "Math" {
    static double floor(double value) native;
}

// native classは任意のJS式を参照できるので
// nodeの場合requireも使える
class Util native "require('util')" {
    // デフォルト引数をdart側で処理できる!
    static String inspect(Object value, [ bool all = false, int depth = 3]) native;
}

void main() {
    // 宣言したメソッドを使用できる
    // 宣言していないものを呼ぶとランタイムエラー
    Map map = { "hoge" : NativeMath.floor(3.14) };

    // nodeのutilモジュールのinspect関数を呼ぶ
    // 名前付き引数はDart処理系によって解釈される
    print( Util.inspect(map, depth: 4) );
}

frogcでコンパイルすると以下のようになる。肝心なところだけ抜粋。余計なラッパーなどは生成されない。

var NativeMath, Util;
// ...
//  ********** Library NativeCall **************
// ********** Code for NativeMath **************
NativeMath = Math;
// ********** Code for Util **************
Util = require('util');
// ********** Code for top level **************
function main() {
  var map = _map(["hoge", NativeMath.floor((3.14))]);
  print$(Util.inspect(map, false, (4)));
}
// ...

実行結果は以下のようになる。これは面白い。

{ _map: 
   { _numberOfEntries: 1,
     _numberOfDeleted: 0,
     _loadLimit: 6,
     _keys: [ , , , , , , 'hoge',  ],
     _values: 
      [ ,
        ,
        ,
        ,
        ,
        ,
        { _element: { key: 'hoge', value: 3 },
          _next: { _element: null, _next: [Circular], _previous: [Circular] },
          _previous: { _element: null, _next: [Circular], _previous: [Circular] } },
         ] },
  _list: 
   { _sentinel: 
      { _element: null,
        _next: 
         { _element: { key: 'hoge', value: 3 },
           _next: [Circular],
           _previous: [Circular] },
        _previous: 
         { _element: { key: 'hoge', value: 3 },
           _next: [Circular],
           _previous: [Circular] } } } }

Dartはオプショナルとはいえ静的方付けでしかも名前付き引数をサポートしているので、うまくバインディングを定義するともとのJSコードよりも使いやすいかもしれない。

*1:このエントリのコードはDartSDKに付属のDart to JSコンパイラであるfrogc(1)で試すことができるが、同じくDartSDK付属のDartVMであるdart(1)では実行できない。DartVMはJSランタイムを前提としないので当然だが。