Files
hakorune/docs/reference/architecture/plugin-migration-guide-v2.md

7.0 KiB
Raw Blame History

📦 Nyash ビルトインBox → プラグイン化移行ガイド v2

🎯 概要

NyashのビルトインBoxをプラグイン化し、コアを軽量化します。 FileBoxプラグインの成功例を詳しく解説しながら、移行方法を説明します。

🔑 重要な概念nyash.tomlの型定義システム

型変換の仕組み

nyash.tomlでは、Nyash側とプラグイン側の型変換を明示的に定義します

# FileBoxの例
[plugins.FileBox.methods]
# writeメソッドNyashのstringをプラグインではbytesとして扱う
write = { args = [{ from = "string", to = "bytes" }] }

# openメソッド2つのstring引数型変換なし
open = { args = [
    { name = "path", from = "string", to = "string" },
    { name = "mode", from = "string", to = "string" }
] }

from/toの意味

  • from: Nyash側の型ユーザーが渡す型
  • to: プラグイン側で受け取る型TLVエンコーディング

TLVタグとの対応

プラグインはTLVType-Length-Value形式でデータを受け取ります

  • to = "i32" → TLV tag=232ビット整数
  • to = "string" → TLV tag=6UTF-8文字列
  • to = "bytes" → TLV tag=7バイト配列

📋 移行対象Box一覧優先順位順

🌐 Phase 1: ネットワーク系(最優先・最も簡単)

既にスタブ実装があり、reqwest依存を追加するだけで完成します。

HttpClientBox

[plugins.HttpClientBox.methods]
# シンプルなGETリクエスト
get = { 
    args = [{ from = "string", to = "string" }],  # URL
    returns = "string"  # レスポンスボディ
}

# POSTリクエストボディ付き
post = { 
    args = [
        { from = "string", to = "string" },  # URL
        { from = "string", to = "bytes" }    # ボディ(バイナリ対応)
    ],
    returns = "string"
}

# 詳細なリクエスト(ヘッダー等を含む)
request = {
    args = [
        { from = "string", to = "string" },  # メソッドGET/POST等
        { from = "string", to = "string" },  # URL
        { from = "map", to = "map" }         # オプションheaders, timeout等
    ],
    returns = "map"  # { status: i32, body: string, headers: map }
}

🖼️ Phase 2: GUI系プラットフォーム依存

EguiBoxは既にfeature分離されているので参考になります。

🎵 Phase 3: 特殊用途系(独立性高い)

TimerBox、QRBox等は単機能で実装しやすいです。

🔧 実装ガイドFileBoxを例に

1. プラグイン側での型受け取り例

// nyash.toml: write = { args = [{ from = "string", to = "bytes" }] }
METHOD_WRITE => {
    // TLVでbytesとして受け取る
    let data = tlv_parse_bytes(args)?;  // Vec<u8>として取得
    
    // ファイルに書き込み
    match file.write(&data) {
        Ok(n) => {
            file.flush()?;  // 重要:フラッシュを忘れずに!
            // 書き込んだバイト数を返すTLV i32
            write_tlv_i32(n as i32, result, result_len)
        }
        Err(_) => NYB_E_PLUGIN_ERROR
    }
}

2. 複数引数の解析例

// nyash.toml: open = { args = [{ from = "string", to = "string" }, { from = "string", to = "string" }] }
METHOD_OPEN => {
    // 2つのstring引数を解析
    let (path, mode) = tlv_parse_two_strings(args)?;
    
    // ファイルを開く
    let file = match mode.as_str() {
        "r" => File::open(&path)?,
        "w" => File::create(&path)?,
        "a" => OpenOptions::new().append(true).open(&path)?,
        _ => return NYB_E_INVALID_ARGS
    };
    
    // 成功時はVoidを返す
    write_tlv_void(result, result_len)
}

3. 引数なしメソッドの例

// nyash.toml: read = { args = [] }
METHOD_READ => {
    // 引数なし - ファイル全体を読む
    file.seek(SeekFrom::Start(0))?;
    let mut buf = Vec::new();
    file.read_to_end(&mut buf)?;
    
    // bytesとして返す
    write_tlv_bytes(&buf, result, result_len)
}

📝 HttpClientBox実装の具体例

// HttpClientBoxプラグインの実装イメージ
use reqwest::blocking::Client;

METHOD_GET => {
    // URLを解析
    let url = tlv_parse_string(args)?;
    
    // HTTPリクエスト実行
    let client = Client::new();
    let response = client.get(&url).send()?;
    let body = response.text()?;
    
    // 文字列として返す
    write_tlv_string(&body, result, result_len)
}

METHOD_POST => {
    // URL と ボディを解析
    let (url, body_bytes) = tlv_parse_string_and_bytes(args)?;
    
    // POSTリクエスト
    let client = Client::new();
    let response = client.post(&url)
        .body(body_bytes)
        .send()?;
    let body = response.text()?;
    
    write_tlv_string(&body, result, result_len)
}

💡 実装のコツとよくある間違い

正しいnyash.toml

# 引数の型変換を明示
write = { args = [{ from = "string", to = "bytes" }] }

# 戻り値の型も指定可能
exists = { args = [], returns = "bool" }

よくある間違い

# 間違い:型情報がない
write = { args = ["string"] }  # ❌ from/toが必要

# 間違い:不要なフィールド
get = { args = [{ type = "string" }] }  # ❌ typeではなくfrom/to

メモリ管理の注意点

  1. 文字列は必ずCString/CStr経由で変換
  2. プラグイン側でallocしたメモリはプラグイン側でfree
  3. ホスト側のVtableを使ってログ出力

エラーハンドリング

// パニックをFFI境界で止める
let result = std::panic::catch_unwind(|| {
    // 実際の処理
});

match result {
    Ok(val) => val,
    Err(_) => NYB_E_PLUGIN_ERROR
}

🧪 テスト方法

1. プラグインビルド

cd plugins/nyash-http-plugin
cargo build --release

2. plugin-testerで診断

cd ../../tools/plugin-tester
./target/release/plugin-tester ../../plugins/nyash-http-plugin/target/release/libnyash_http_plugin.so

# 期待される出力:
# Plugin Information:
#   Box Type: HttpClientBox (ID: 20)
#   Methods: 5
#   - birth [ID: 0] (constructor)
#   - get, post, put, delete
#   - fini [ID: 4294967295] (destructor)

3. Nyashで実行

// test_http.nyash
local http = new HttpClientBox()
local response = http.get("https://api.example.com/data")
print(response)

📚 参考資料

  • FileBoxプラグイン完全実装: plugins/nyash-filebox-plugin/src/lib.rs
  • TLVエンコーディング仕様: docs/説明書/reference/plugin-system/ffi-abi-specification.md
  • nyash.toml設定例: プロジェクトルートのnyash.toml

🎯 成功の秘訣

  1. FileBoxを完全に理解してから始める - コピペベースで改造
  2. nyash.tomlの型定義を正確に - from/toを明示
  3. TLVの理解 - tag=6(string), tag=7(bytes)の違い
  4. plugin-testerで早期検証 - 問題を早期発見

質問があれば、FileBoxの実装を参考にしてください。 すべての答えがそこにあります!