Cアプリケーションに組み込める、フリーのJavaScriptエンジンを探していて、
Duktapeというプロジェクトが見つかり、興味があったのでドキュメントを読んでいました。
以下は、途中までですが、読みながら翻訳していたものを公開します。
== 以下、翻訳版注意書き ==
この文章は、アプリケーション組み込み向けEcmascript(Javascrip)エンジンであるDuktapeのドキュメントの非公式日本語訳です。
この翻訳は、自分がDuktapeについて把握することを目的として行ったものです。全体を通して翻訳が雑です。というか誤訳を含みます。翻訳ミスしても実際のサンプルコードと自作コードで確認すれば気づくだろう、だからいいや、というレベルです。訳しづらかったところを飛ばしていたりもします。翻訳ミス前提で読んでください。
日本語としてメチャクチャなので、頭の中で再度訳しながら読むことになるかと思います。
ライセンスは原文に準じます。原文が許す限り、新しい翻訳のベースなど自由に使っていただいて構いません。とはいえ、新規に訳したほうが早いでしょう。
指摘・修正はメール(michinari.nukazawa@gmail.com)または
twitterに頂けると嬉しく思います。
原文
http://duktape.org/guide.html
== 以下、翻訳文 ==
version 1.4.0 (2016-01-10)
## このドキュメントの範囲
このガイドの内容は、あなたのプログラムでDuktapeを使いはじめるためのイントロダクションです。あなたが基礎知識をすでに持っているならば、わかりやすい<a href>API reference</a>にてAPI構成を一覧できます。<a href>Duktape Wiki</a>の内容は、さらなる概要と使用例、ベストプラクティスです。
このドキュメントはDuktapeの内部構造について扱いません。(もしあなたが我々と共にDuktapeを弄ることに興味があるようならば、Duktape repoをご覧ください。)
## Duktapeとは何か?
Duktapeは、移植性とコンパクトなフットプリントに注力した、組み込み可能なEcmascript E5/E5.1エンジンです。Duktapeにより、あなたのC/C++プログラムへ、スクリプティング機能を簡単に拡張することができます。あなたはEcmascriptの中へ、プログラムの中心的な制御フローを作ることができ、同時に高速動作するC関数を使い、重い処理を取り扱うことができます。
(訳注:適当訳)EcmascriptとJavascriptは、大まかに等価ですが、ブラウザ向け拡張などが付いている場合もあって等しくはありません。Duktapeも例外ではなく、よく使われるprint(),alart()を持っています。なので、我々はEcmascriptの全体を実装したと言い表しています。
## Conformance
(略)
## Features
(略)
## Goals
(略)
## ドキュメント構成
<a href>Getting started</a>は、あなたがダウンロード、コンパイル、そしてDuktapeをあなたのプログラムに導入するまでを取り扱っています。これの内容は確固とした例によって、あなたがどのようにあなたのプログラムにスクリプティング機能を導入するかを含んでいます。
<Programming Model><Stack types><C types>で語られているのは、Duktapeヒープのコアコンセプト, context, value stacks, Duktape API, Duktape C関数 です。Dukatapeのスタック構造とC型システムのラッパーについて概要を語っています。
Duktapeが実装しているEcmascript機能についていくつかのセクションで語っています: Type algorithms (for custom types), Duktape built-ins (additional built-ins), Ecmascript E6 features (features borrowed from ES6), Custom behavior (behavior differing from standard), Custom JSON formats, Custom directives, Buffer objects, Error objects (properties and traceback support), Function objects (properties), Debugger, Modules, Logging, Finalization, Coroutines, Virtual properties, Internal properties, Bytecode dump/load, Threading, Sandboxing.
<Performance>では(略)
<Comparison to Lua>では(略)
## Getting started
(略)
## Downloading
(略)
## Command line tool
(略)
## Integrating Duktape into your program
コマンドラインツールは、簡単なサンプルプログラムでDuktapeの組み込み方法を示しています。
組み込んでDuktapeをあなたのプログラムで使う方法はシンプルです:duktape.c, duktape.h, and duk_config.hを追加してビルドし、Duktape APIをあなたのプログラムのどこかから呼ぶ。
Duktapeの配布物に含まれるとても単純なサンプルプログラムの、hello.cがそれを示します。コンパイルとテストをこのプログラムで試すには():
The distributable contains a very simple example program, hello.c, which illustrates this process. Compile the test program e.g. as (<Compiling>の compiler optionも参照してください):
(サンプル略)
テストプログラムは、Duktape contextを作り、任意のEcmascriptコードを実行します:
(サンプル略)
Duktapeは組み込み可能なエンジンであるため、あなたのプログラムの基本のコントロールフローを変更する必要はありません。一般的な使い方は:
・プログラムを起動した際に、Duktape contextを作る(または、スクリプティングが必要となったタイミングで作る)。(訳注:適当訳)通常は、あなたのスクリプトがロードされる間に初期化が終わり、必要なタイミングでそれを実行することができます。
・一意な箇所があなたのプログラムの中にあり、そこでscriptを実行する、またscript内の関数をそこに挿入して呼ぶ。
・script内関数を呼ぶ。最初にpushして呼び出した引数はDuktapeコンテキストのvalue stacksに入れられてDuktape APIから使われる。これは、Duktape APIが実際に呼び出された際に使われる。
・ひとつのscriptを最後まで実行する。戻り値を渡す方法を制御でき、あなたのプログラムへ(Duktape API呼び出しの)戻り値として渡すか、Duktapeコンテキストのvalue stackに置くことができる。Cコードはこれらの戻り値をDuktape APIを通して使うことができる。
単純なサンプルプログラムを見てみましょう。プログラムはstdinを使った行の読み込みをCのmainloop内で行い、呼び出したEcmascriptの助けにより行文字列を変換して、結果を出力します。
スクリプトコードは"process.js"です。サンプルの文字列変換関数は、プレーンテキストの行文字列をHTMLに変換し、アスタリスク'*'で囲まれた文字を太字(bold)にします。
(サンプルコード略)
Cコードは"processlines.c"で、Duktapeコンテキストを初期化し、scriptを実行し、スクリプトが変換する処理対象の行文字列をstdinから読み込み、processLine()関数を行文字列ごとに実行します。
(サンプルコード略)
サンプルコード内のDuktape機能呼び出し箇所を、部分ごとに見て行きましょう。(適当訳)さらなる情報が欲しいならば、<Programming model>に書いてあります。
(サンプルコード略)
まずDuktapeコンテキストを生成します。コンテキストはEcmascriptの変数を変換し、value staskに入出力(pushing,popping)します。以降のDuktapeAPI動作の呼び出しではvalue stackが付き、入出力(pushing,popping)と実行がスタック上で行われます。製品コードには、望むならば、duk_create_heap()によってfatal error handlerをセットすることができます。<Error handling>を参照することで、エラーハンドリングのベストプラクティスについての議論を見ることができます。
(サンプルコード略)
評価(eval)呼び出しで"process.js"ファイルを読み込み、それがスクリプトをコンパイルし実行します。scriptに登録されているprocessLine()関数はEcmascriptグローバルオブジェクトへ、後で使うために格納されます。実行の際、スクリプトエラー・シンタックスエラーはハンドリングされ、fatal errorはハンドリングされません。もしエラーが起これば、エラーメッセージは強制的に安全に使うduk_safe_to_string()を通して提供され、それ以上に深刻なエラーにはなりません。実行結果の文字列は強制的にconst char * (ポインタ)に変換されるためリードオンリで、NULL終端のUTF-8エンコーディング文字列であるため、printfで直接使うことができます。
(サンプルコード略)
1行目は、Ecmascriptグローバルオブジェクトをvalue staskにpushしています。2行目はグローバルオブジェクトからprocessLineプロパティを探索しています("process.js"スクリプトファイル内に定義されている)。引数の-1はvalue stackのインデックスで、マイナス値はスタックを先頭から始め、よって-1はグローバルオブジェクトを一番先頭にスタックします。
(サンプルコード略)
Pushして行文字列が入っている文字列ポインタをvalue stackに入れます。文字列長は自動で、NULL終端で測られます(strlen()のように)。Duktapeは文字列のコピーを作成してstackにpushしますので、この関数の呼び出し後に文字列バッファを開放・書き込みしても構いません。
(サンプルコード略)
ここでvalue stackに入っているものは:グローバルオブジェクト,processLine関数,行文字列。
duk_pcall()メソッドは関数を実行し、指定された数の引数をvalue stack上から与え、それらを関数,引数,戻り値に置き換えます。ここで実行後のvalue stackに入っているものは:グローバルオブジェクト,実行結果 です。実行時のエラーは保護されており、取得してprint出力できます。duk_safe_to_string() APIを呼び出して安全エラーを表示することができます。最終的に、結果(またはエラー)がvalue stackからpopされます。
(サンプルコード略)
Duktapeコンテキストを破棄し、コンテキストのすべてのリソースを開放します。この呼び出しはvalue stackとその上の参照をすべて開放します。このサンプルではvalue stackの左端にglobal objectが入っているというくわだてです。これは問題ありません:メモリリークは起こりません、もしheapを破棄する際にvalue stackが空でも。
コンパイルは次のように。
(サンプルコード略)
テスト実行(process.jsがカレントディレクトリに必要)
(サンプルコード略)
## EcmascriptからCコードを呼び出す(Duktape/C bindings)
この導入サンプルでは、どのようにEcmascriptからCコードを呼び出して実行するか、難しくないCコードをEcmascriptから簡単に呼び出す方法を示します。
Ecmascriptは例にもれずしばしば、逆のシチュエーションとして中からCコードを呼び出したくなります。事例としては、スクリプト内で何らかの処理をとても多く実行する場合や、最適化されない低レベルbyte操作や文字列操作です。最適化されたCヘルパが呼び出し可能であり、あなたはすばらしいEcmascript言語の中で、あなたのスクリプトのロジックを記述して、その中で性能要求がクリティカルな部分をCコード呼び出しにすることができます。別の推測では、ネイティブなライブラリへのアクセスに使われるでしょう。
あなたが書くネイティブC関数の実装は標準的なC関数を、特殊な呼び出し規約の Duktape/C バインディングに適合させたものです。Duktape/C 関数は1つの引数を持ち、Duktapeコンテキストと1つの戻り値としてエラーまたは数値戻り値を持ちます。関数が呼び出し時の引数を得たり戻り値をセットする際にはDuktapeコンテキストのvalue stackに対して、Duktape APIを用いてそれを操作します。我々がさらに深くDuktape/CバインディングとDuktapeAPIを見るのはもう少し後にしましょう。サンプルは:
(サンプルコード略)
行ごとに見ていきます:
(サンプルコード略)
value stackのインデックス0番(スタックの先頭から、関数呼び出し時に指定された最初の引数)の内容が数値(number)であるか確認しています、この場合はこれは数値(number)です;もしそうでなかったなら、errorを投げて、値を返しません。もし内容が数値(number)であるならば、その数値をdouble型で返します。
(サンプルコード略)
引数を2乗して、value stackにプッシュします。
(サンプルコード略)
関数呼び出しからreturnして、これ(訳注:return 1)が指し示すものは(1つの)戻り値がvalue stackの先頭に存在することです。あなたはreturn 0を返すこともでき、これは戻り値が無いことを示します(DuktapeのデフォルトではEcmascriptのundefinedを返す)。負の値の戻り値は、errorが発生し、自動的に投げられたことを示す:これはエラーの投げ方としては単純なものです。なお、あなたがstackから値をポップして返すことを必要としないならば、Duktapeはあなたに関数からのreturnを自動で提供することができます。Programming modelにて詳細を参照ください。
我々の素数判定コードはネイティブコードによりEcmascriptで記述してあるアルゴリズムを高速化することができます。さらにテストプログラム側では1000000以下から素数を探すことに加えて値の下位が'9999'で終わる数値を探します。Ecmascript版プログラムは以下の通り:
(サンプルコード略)
雑記として、このプログラムはネイティブ関数を呼びますが、もしそれが使えない場合にはEcmascript版にフォールバックします。これにより、Ecmascriptコードは(訳注:今回のサンプルプログラム以外の)他のプログラムからも使用可能になります。一方で、もしこの素数判定プログラムを他のプラットフォームへ移植する場合でもネイティブコードは再コンパイル以外の変更は必要なく、ヘルパ関数を移植するまで遅いだけです。このプログラムの場合、ネイティブなヘルパ関数を検出するのはスクリプトを読み込む際です。あなたは一方でヘルパ関数の検出を任意のタイミングにフレキシブルに変更することができます。
ネイティブヘルパ関数には機能としてprimeCheckEcmascriptと等価なものを素直に実装しました。そこにmainを追加した"primecheck.c"は以下の通りです:
(サンプルコード略)
新しい関数呼び出しがあるので、行毎に見ていきます:
(サンプルコード略)
この2行の呼び出しは2引数をネイティブヘルパ関数が要求するためです。もし値がEcmascriptの数値型ではなかった場合、エラーを投げます。もしこれらが数値であった場合、数値を整数に変換し、それぞれローカル変数のval,limに格納します。index 0が第一引数でindex 1が第二引数です。
技術的には、duk_require_int()が返すのはduk_int_t型です;これは間接的にintへ変換されますがそれはintの長さが16bitまでしかない環境では問題が発生します。一般的なアプリケーションであれば、あなたはこれを心配する必要はありません。さらなる詳細はC typesを参照ください。
(サンプルコード略)
value stackにEcmascript falseをプッシュします。Cコードからreturn 1した場合、呼び出し元のEcmascript側ではこれを戻り値falseが返されたと解釈します。
(サンプルコード略)
1行目は、この後に、Ecmascriptグローバルオブジェクトをvalue stackにpushしています。2行目は、Ecmascript関数オブジェクトを作成し、それをvalue stackにpushしています。関数オブジェクトは、Duktype/C関数であるnative_prime_checkをバインドしてます:この時にEcmascript関数を、Ecmascript側から呼ぶために作成することで、C関数を呼び出せるようになります。第二引数の(2)は、このC関数が呼び出される際にvalue stackに必要な引数の数を示しています。もし呼び出し元がこれより少ない引数で関数を呼び出した場合、足りない引数はundefinedで埋められます; もし呼び出し元がこれより多い引数で関数を呼び出した場合、余った引数は自動で無視されます。最後の3行目で実行されていることは、関数オブジェクトをグローバルオブジェクトに格納しつつ、"primeCheckNative"という名前を付けながら関数オブジェクトをスタックからpopして取り出しています。
(サンプルコード略)
このときvalue stackにはすでにglobal objectがスタックの先頭に格納されています。1行目はprimeTest関数をglobal objectから探索しています(ロード済みのスクリプト内で定義されています)。2-4行目は、primeTest関数を引数無しで呼び出し、保護されたエラーが起こっていないかチェックしています。5行目は、実行結果をpopしてスタックから取り除いています; ここで戻り値は必要でないため。
コンパイル方法は以下:
(サンプルコード略)
実行します("prime.js"がカレントディレクトリに必要です):
(サンプルコード略)
さらに素数チェックに必要な時間を測って、スピードアップがEcmascriptと比べてどの程度有効か見てみましょう。あなたがこれを試すには"prime.js"を編集してネイティブヘルパ関数を無効にします。
(サンプルコード略)
再コンパイルして再実行すると:
(サンプルコード略)
## プログラミング・モデル
## 概観
Duktapeによるプログラミングは直裁的には以下の通りです。
・Duktapeソースコード(duktape.c)とヘッダ(duktape.hとduk_config.h)をあなたのビルドに追加
・Duktapeのheap(ガベージコレクション領域)を作成し、context(スレッドハンドラ的なもの)を初期化することを、あなたのプログラム内で行う
・必要なEcmascriptスクリプトファイルを読み込み、Duktape/C関数を準備しておく。Duktape/C関数はC関数をEcmascriptコード側から呼び出すことで、妥当な性能を出すことやネイティブライブラリへのアクセスを提供する。
・Duktape APIを用いて、Ecmasccript関数を妥当なタイミングで呼び出す。Duktape APIは関数と変数へのアクセスを提供する。変数はC変数型とDuktape内部形式(Ecmascript互換)を相互変換することができる。
・Duktape APIは一方で、Duktape/C関数(をEcmascriptから呼び出した)から、関数呼び出し時の引数および、戻り値へアクセスする方法を提供する。
これらすべてのステップを見ることが、我々の提供するさらなる情報にアクセスするのに必要です。
Heapとコンテキスト
Duktape heapは、ガベージコレクションを提供する単一の領域です。heapは文字列のために確保される領域、Ecmascriptオブジェクトやその他の変数に必要な領域、ガベージコレクション情報などに使われます。heap上のオブジェクトは、heapの先頭に持っている管理に必要な情報により、リファレンスカウント、mark-and-sweepガベージコレクション、オブジェクトのfinalizationなどを行います。Heapオブジェクトはリファレンスなどにより、ガーベージコレクションから見た到達可能性グラフ(reachability graph from a garbace collection perspective)を作ります。インスタンスに対しては、Ecmascriptオブジェクトへの参照を、キーと値によるオブジェクトとプロパティのセットにより提供します。あなたは複数のheapを持つことができ、オブジェクトは異なるheap上に直接存在することができません; (適当訳)あなたは異なるheap間では値をserializationしなければなりません。
Duktapeコンテキストは、Ecmascriptの"実行スレッド"としてDuktape heap内に生成されます。コンテキストはduk_context *によりDuktape APIから参照可能で、Duktape内部のコルーチン(a form of a co-operation thread)により組織化されます。いくつかのコンテキストを、共通のglobal objectを(訳注:ベースの?)環境として組織化することができます; コンテキストは任意のglobal objectを共有することができる一方で、異なる環境を持つことができます。コンテキストハンドルはほとんどのDuktape API呼び出しが要求し、そこからDuktapeコルーチンでvalue stackへアクセスします: 値の挿入と探索、関数の呼び出し、など。
コルーチンたちはcall stackにより実行を制御され、関数呼び出しの追跡、ネイティブまたはEcmascriptの分類、などをEcmascriptエンジン内で行います。(適当訳)コルーチンたちは一方でvalue stackを持ち、すべてのコルーチンのアクティブなコールスタックをstoreします。
== 以下、翻訳未終了 ==