C API¶
YanmaJS は Zig API に加えて、共有ライブラリ(libyanmajs.so)と C11 ヘッダ(yanmajs_capi/include/yanmajs.h)による C ABI を提供します。C/C++ 製の GUI アプリケーションや、C FFI を持つ他言語からの組み込みに使えます。C ABI 層のソースは yanmajs_capi/ にあり、ルートの build.zig の capi ステップでビルドします。
zig build capi # zig-out/lib/libyanmajs.so と zig-out/include/yanmajs.h を生成
zig build examples-c # example/c/ の 4 例をビルドして実行
make build-capi / make examples-c でも同様のことができます(開発ガイド参照)。
API 面は QuickJS(quickjs.h)を設計モデルにした「GUI 組み込みに必要な最小完全集合」です。宣言と規約の一次情報はヘッダ yanmajs_capi/include/yanmajs.h のコメント、実装は yanmajs_capi/src/capi.zig です。
基本規約¶
値とライフタイム¶
YjsValue は NaN ボクシングされた 64bit のビット列で、コピーは値参照のコピーです。API が返す YjsValue はすべて借用であり、次にアロケーションやスクリプト実行を伴う API を呼ぶまでしか有効性が保証されません(GC に回収されうるため)。プリミティブ(undefined/null/bool/number)はビット列自体が値なのでこの制約を受けません。
呼び出しをまたいでオブジェクト系の値(文字列・オブジェクト・配列・関数など)を保持する場合は、必ず yjs_protect でルート化します。
YjsPersistent p = yjs_protect(ctx, obj);
/* ... 他の API 呼び出し ... */
YjsValue alive = yjs_persistent_get(ctx, p); /* GC 後も生存 */
yjs_unprotect(ctx, p);
GC は non-moving なので、ルート化された値のビット列は変化しません。なお現行版では unprotect したスロットは再利用されません(protect/unprotect を無制限に繰り返すホストのみ注意)。
文字列¶
- 入力はすべて UTF-8 の
(ポインタ, 長さ)ペアです。NUL 終端は不要で、参照もされません。 - 出力(
yjs_to_string)は NUL 終端された API 所有のバッファです。解放は必ずyjs_string_freeで行ってください。free()を直接呼んではいけません(エンジンとホストでアロケータが異なる可能性があるため)。
size_t len;
char *text = yjs_to_string(ctx, v, &len);
printf("%s\n", text);
yjs_string_free(ctx, text);
例外¶
失敗しうる呼び出しは、実値の代わりにセンチネル値(yjs_exception())を返します。yjs_is_exception() で判定し、yjs_get_exception() で保留中の例外オブジェクトを取得します(取得と同時にクリアされる消費型です)。
YjsValue v = yjs_eval(ctx, src, strlen(src));
if (yjs_is_exception(v)) {
YjsValue exc = yjs_get_exception(ctx);
char *msg = yjs_to_string(ctx, exc, NULL); /* Error はスタックトレース付き */
fprintf(stderr, "%s\n", msg);
yjs_string_free(ctx, msg);
}
センチネルは予約ビットパターンであり、本物の JS 値として現れることはありません。プロパティやグローバルへ格納したり、呼び出し引数として渡してはいけません。
Hello, world¶
example/c/hello.c の骨子です。
#include <stdio.h>
#include <string.h>
#include <yanmajs.h>
int main(void) {
YjsContext *ctx = yjs_new();
const char *src = "const greet = (who) => `hello, ${who}!`; greet('yanmajs')";
YjsValue result = yjs_eval(ctx, src, strlen(src));
if (!yjs_is_exception(result)) {
char *text = yjs_to_string(ctx, result, NULL);
printf("%s\n", text); /* hello, yanmajs! */
yjs_string_free(ctx, text);
}
yjs_free(ctx);
return 0;
}
メモリ上限付きのサンドボックスは yjs_new_with_memory_limit(max_bytes) で作れます(上限超過のアロケーションは OOM と同様に失敗します)。
ホスト関数¶
C の関数を JavaScript から呼べるようにするには yjs_new_function + yjs_set_global を使います(example/c/host_function.c 参照)。
static YjsValue greet(YjsContext *ctx, YjsValue this_val, int argc,
const YjsValue *argv, void *userdata) {
HostState *state = userdata; /* 登録時の userdata がそのまま届く */
if (argc < 1) {
/* C 側から例外を投げる: yjs_throw + センチネル返却の 2 段構え */
const char *msg = "greet() needs a name argument";
yjs_throw(ctx, yjs_new_error(ctx, "TypeError", 9, msg, strlen(msg)));
return yjs_exception();
}
char *who = yjs_to_string(ctx, argv[0], NULL);
printf("hello, %s!\n", who);
yjs_string_free(ctx, who);
return yjs_int32(++state->calls);
}
/* 登録(yjs_new_function の戻り値は未ルートなので即座にアタッチする) */
YjsValue fn = yjs_new_function(ctx, "greet", 5, greet, &state);
yjs_set_global(ctx, "greet", 5, fn);
argvの各値は呼び出し中のみ有効な借用です。保持するならyjs_protectしてください。- 例外を投げる場合は
yjs_throwを呼んでからyjs_exception()を返すの両方が必要です。yjs_throwなしでセンチネルを返す動作は未定義です。 - JS 関数を C から呼ぶには
yjs_call(ctx, fn, argc, argv)を使います(v1 ではthisは undefined 固定。将来yjs_call_methodの追加を予定した命名です)。
API 一覧(グループ別)¶
| グループ | 関数 |
|---|---|
| ライフサイクル | yjs_new / yjs_new_with_memory_limit / yjs_free / yjs_memory_usage / yjs_collect_garbage |
| 評価・例外 | yjs_eval / yjs_eval_module / yjs_is_exception / yjs_get_exception / yjs_exception / yjs_throw |
| 値の生成 | yjs_undefined / yjs_null / yjs_bool / yjs_int32 / yjs_float64 / yjs_string / yjs_object / yjs_array / yjs_new_error |
| 検査・変換 | yjs_type_tag / yjs_to_bool / yjs_to_int / yjs_to_float / yjs_to_string / yjs_string_free |
| GC ルート | yjs_protect / yjs_unprotect / yjs_persistent_get |
| プロパティ | yjs_get_property / yjs_set_property / yjs_get_index / yjs_set_index / yjs_length |
| グローバル・関数 | yjs_get_global / yjs_set_global / yjs_new_function / yjs_call |
| Promise | yjs_new_promise / yjs_resolve / yjs_reject / yjs_promise_state / yjs_promise_result / yjs_drain_microtasks |
| 中断・制限 | yjs_request_interrupt / yjs_clear_interrupt / yjs_set_time_limit_ms |
| モジュールローダー | yjs_set_module_loader |
| イベントループ | yjs_set_event_loop_hook / yjs_fire_timer / yjs_has_pending_timers |
| コンソール | yjs_set_console_handler |
コールバック系の補足¶
- モジュールローダー —
YjsModuleLoadFnはmalloc()した NUL 終端バッファを返す規約です。エンジンが内容を即座に自前のアロケータへ複製し、元のバッファをfree()します(所有権の混在はブリッジ層で遮断されます)。見つからない場合はNULLを返してください。YjsModuleResolveFn(任意)も同じ規約です。 - イベントループ — due 管理はホスト責務です。参照実装は
example/c/event_loop.c(と Zig 版のyanmajs_runtime(yanmajs_runtime/src/event_loop.zigのEventLoop))。詳細は組み込みガイドのイベントループ節を参照してください。 - コンソール —
YjsConsoleWriteFnに渡るtextは NUL 終端されておらず、呼び出し中のみ有効な借用です。保持する場合は必ず複製してください。write_fnにNULLを渡すと標準出力への既定動作に戻ります。 - watchdog —
yjs_set_time_limit_ms(ctx, 100)で各 eval/call に 100ms の壁時計バジェットを課せます(0 で解除)。例はexample/c/watchdog.c、仕様は context を参照してください。
サンプル¶
example/c/ に 4 つの実行可能な例があります(zig build examples-c または make examples-c でビルド+実行)。
| ファイル | 内容 |
|---|---|
hello.c |
eval → 文字列化 → 出力、例外ハンドリングの基本 |
host_function.c |
ホスト関数の登録・userdata・C からの例外送出・yjs_call |
event_loop.c |
ホスト駆動イベントループの参照実装(setTimeout/setInterval) |
watchdog.c |
実行時間制限による暴走スクリプトの打ち切り |