From 8f8781219e62ab6fb65ce0b342f050a1268e7033 Mon Sep 17 00:00:00 2001 From: Moe Charm Date: Mon, 18 Aug 2025 21:25:43 +0900 Subject: [PATCH] docs: Add improved plugin migration guide v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nyash.tomlの型定義システムを詳しく解説 - TLVエンコーディングとの関係を明確化 - FileBoxの実装例を使った具体的な説明 - HttpClientBoxの実装イメージを追加 - よくある間違いと成功の秘訣を記載 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/plugin-migration-guide-v2.md | 249 ++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 docs/plugin-migration-guide-v2.md diff --git a/docs/plugin-migration-guide-v2.md b/docs/plugin-migration-guide-v2.md new file mode 100644 index 00000000..0ba074e5 --- /dev/null +++ b/docs/plugin-migration-guide-v2.md @@ -0,0 +1,249 @@ +# 📦 Nyash ビルトインBox → プラグイン化移行ガイド v2 + +## 🎯 概要 +NyashのビルトインBoxをプラグイン化し、コアを軽量化します。 +FileBoxプラグインの成功例を詳しく解説しながら、移行方法を説明します。 + +## 🔑 重要な概念:nyash.tomlの型定義システム + +### 型変換の仕組み +nyash.tomlでは、Nyash側とプラグイン側の型変換を明示的に定義します: + +```toml +# 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タグとの対応 +プラグインはTLV(Type-Length-Value)形式でデータを受け取ります: +- `to = "i32"` → TLV tag=2(32ビット整数) +- `to = "string"` → TLV tag=6(UTF-8文字列) +- `to = "bytes"` → TLV tag=7(バイト配列) + +## 📋 移行対象Box一覧(優先順位順) + +### 🌐 Phase 1: ネットワーク系(最優先・最も簡単) +既にスタブ実装があり、reqwest依存を追加するだけで完成します。 + +#### HttpClientBox +```toml +[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. プラグイン側での型受け取り例 + +```rust +// nyash.toml: write = { args = [{ from = "string", to = "bytes" }] } +METHOD_WRITE => { + // TLVでbytesとして受け取る + let data = tlv_parse_bytes(args)?; // Vecとして取得 + + // ファイルに書き込み + 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. 複数引数の解析例 + +```rust +// 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. 引数なしメソッドの例 + +```rust +// 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実装の具体例 + +```rust +// 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 +```toml +# 引数の型変換を明示 +write = { args = [{ from = "string", to = "bytes" }] } + +# 戻り値の型も指定可能 +exists = { args = [], returns = "bool" } +``` + +### ❌ よくある間違い +```toml +# 間違い:型情報がない +write = { args = ["string"] } # ❌ from/toが必要 + +# 間違い:不要なフィールド +get = { args = [{ type = "string" }] } # ❌ typeではなくfrom/to +``` + +### メモリ管理の注意点 +1. 文字列は必ずCString/CStr経由で変換 +2. プラグイン側でallocしたメモリはプラグイン側でfree +3. ホスト側のVtableを使ってログ出力 + +### エラーハンドリング +```rust +// パニックをFFI境界で止める +let result = std::panic::catch_unwind(|| { + // 実際の処理 +}); + +match result { + Ok(val) => val, + Err(_) => NYB_E_PLUGIN_ERROR +} +``` + +## 🧪 テスト方法 + +### 1. プラグインビルド +```bash +cd plugins/nyash-http-plugin +cargo build --release +``` + +### 2. plugin-testerで診断 +```bash +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で実行 +```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の実装を参考にしてください。 +すべての答えがそこにあります! \ No newline at end of file