feat: Add multi-box plugin support (v2) with test implementation
- Extend plugin-tester to support multi-box plugins with v2 API - Add nyash_plugin_get_box_count/get_box_info/get_type_id functions - Create test multi-box plugin providing TestBoxA and TestBoxB - Update plugin-system.md documentation for v2 format - Add nyash.toml v2 specification for multi-box support - Successfully tested multi-box plugin lifecycle and type resolution This enables one plugin to provide multiple Box types, solving the dependency issue where HTTPServerBox needs SocketBox. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -144,25 +144,39 @@ read = {
|
||||
- [phase_8_6_vm_performance_improvement.md](../予定/native-plan/issues/phase_8_6_vm_performance_improvement.md) - 詳細技術分析
|
||||
- [copilot_issues.txt](../予定/native-plan/copilot_issues.txt) - 全体開発計画
|
||||
|
||||
## 🔧 **現在進行中:ビルトインBoxプラグイン化プロジェクト**(2025-08-18開始)
|
||||
## 🔧 **現在進行中:マルチBox型プラグイン対応**(2025-08-18開始)
|
||||
|
||||
### **目的**
|
||||
- **依存関係の解決**: HTTPServerBoxとSocketBoxを同一プラグインに
|
||||
- **効率的な配布**: 関連Box群を1つのライブラリで提供
|
||||
- **ビルド時間短縮**: 3分 → 30秒以下
|
||||
- **バイナリサイズ削減**: 最小構成で500KB以下
|
||||
- **保守性向上**: 各プラグイン独立開発
|
||||
|
||||
### **対象Box(13種類)**
|
||||
```
|
||||
Phase 1: ネットワーク系(HttpBox系、SocketBox)
|
||||
Phase 2: GUI系(EguiBox、Canvas系、Web系)
|
||||
Phase 3: 特殊用途系(AudioBox、QRBox、StreamBox等)
|
||||
### **nyash.toml v2設計**
|
||||
```toml
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox", "HttpClientBox"]
|
||||
}
|
||||
```
|
||||
|
||||
### **実装フェーズ**
|
||||
1. **Phase 1**: plugin-tester拡張(進行中)
|
||||
- 複数Box型の検出機能
|
||||
- 各Box型のメソッド検証
|
||||
|
||||
2. **Phase 2**: テストプラグイン作成
|
||||
- 簡単な複数Box型プラグイン
|
||||
- FFI実装確認
|
||||
|
||||
3. **Phase 3**: Nyash本体対応
|
||||
- nyash.toml v2パーサー
|
||||
- マルチBox型ローダー
|
||||
|
||||
### **進捗状況**
|
||||
- ✅ プラグイン移行依頼書作成(`docs/plugin-migration-request.md`)
|
||||
- ✅ CopilotのBID変換コード抽出(`src/bid-converter-copilot/`)
|
||||
- ✅ CopilotのBIDコード生成機能抽出(`src/bid-codegen-from-copilot/`)
|
||||
- 🔄 HttpBoxプラグイン化作業をCopilotに依頼中
|
||||
- ✅ nyash.toml v2仕様策定完了
|
||||
- ✅ plugin-system.mdドキュメント更新
|
||||
- 🔄 plugin-tester拡張作業開始
|
||||
|
||||
## 📋 **今日の重要決定事項(2025年8月18日)**
|
||||
|
||||
@ -192,11 +206,12 @@ Phase 3: 特殊用途系(AudioBox、QRBox、StreamBox等)
|
||||
---
|
||||
|
||||
**最終更新**: 2025年8月18日
|
||||
**次回レビュー**: HttpBoxプラグイン完成時
|
||||
**開発状況**: ビルトインBoxプラグイン化進行中
|
||||
**次回レビュー**: マルチBox型プラグイン対応完了時
|
||||
**開発状況**: nyash.toml v2(マルチBox型)対応開始
|
||||
|
||||
### 🎯 **次のアクション**
|
||||
1. HttpBoxプラグイン化の完成待ち(Copilot作業中)
|
||||
2. plugin-testerでの動作確認
|
||||
3. 次のプラグイン化対象(EguiBox等)の準備
|
||||
1. plugin-testerを複数Box型対応に拡張
|
||||
2. 簡単な複数Box型プラグインのテスト作成
|
||||
3. Nyash本体のローダーを複数Box型対応に拡張
|
||||
4. 複数Box型の読み込みテスト実施
|
||||
|
||||
|
||||
107
docs/nyash-toml-v2-spec.md
Normal file
107
docs/nyash-toml-v2-spec.md
Normal file
@ -0,0 +1,107 @@
|
||||
# nyash.toml v2 仕様 - マルチBox型プラグイン対応
|
||||
|
||||
## 🎯 概要
|
||||
1つのプラグインライブラリが複数のBox型を提供できるように拡張した仕様。
|
||||
|
||||
## 📝 基本構造
|
||||
|
||||
### 1. 後方互換性のある現行形式(単一Box型)
|
||||
```toml
|
||||
[plugins]
|
||||
FileBox = "nyash-filebox-plugin"
|
||||
|
||||
[plugins.FileBox.methods]
|
||||
read = { args = [] }
|
||||
write = { args = [{ from = "string", to = "bytes" }] }
|
||||
```
|
||||
|
||||
### 2. 新形式:マルチBox型プラグイン
|
||||
```toml
|
||||
# ライブラリ定義
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox"]
|
||||
}
|
||||
|
||||
# 各Box型の詳細定義
|
||||
[plugins.types.SocketBox]
|
||||
library = "nyash-network"
|
||||
type_id = 100
|
||||
methods = {
|
||||
bind = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]}
|
||||
}
|
||||
|
||||
[plugins.types.HTTPServerBox]
|
||||
library = "nyash-network"
|
||||
type_id = 101
|
||||
methods = {
|
||||
bind = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]},
|
||||
route = { args = [
|
||||
{ name = "path", from = "string", to = "string" },
|
||||
{ name = "handler", from = "box", to = "box" }
|
||||
]}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 型システム
|
||||
|
||||
### サポートする型(基本型のみ)
|
||||
```toml
|
||||
{ from = "string", to = "string" } # 文字列
|
||||
{ from = "integer", to = "i64" } # 整数
|
||||
{ from = "float", to = "f64" } # 浮動小数点
|
||||
{ from = "bool", to = "bool" } # 真偽値
|
||||
{ from = "bytes", to = "bytes" } # バイト配列
|
||||
```
|
||||
|
||||
**重要**: プラグインとNyash本体間では基本型のみやり取り。Box型の受け渡しは行わない(箱は箱で完結)。
|
||||
|
||||
### 2. プラグインFFI拡張
|
||||
```c
|
||||
// 既存: 単一Box型
|
||||
nyash_plugin_abi_version()
|
||||
nyash_plugin_init()
|
||||
|
||||
// 新規: 複数Box型
|
||||
nyash_plugin_get_box_count() // 提供するBox型の数
|
||||
nyash_plugin_get_box_info(index) // 各Box型の情報
|
||||
```
|
||||
|
||||
## 📊 実装優先順位
|
||||
|
||||
1. **Phase 1**: nyash.toml v2パーサー実装
|
||||
- 後方互換性維持
|
||||
- 新形式の読み込み
|
||||
|
||||
2. **Phase 2**: plugin-tester拡張
|
||||
- 複数Box型の検出
|
||||
- 各Box型のメソッド検証
|
||||
|
||||
3. **Phase 3**: ローダー拡張
|
||||
- 複数Box型の登録
|
||||
- 型ID管理
|
||||
|
||||
## 🎯 HTTPServerBox依存問題の解決
|
||||
|
||||
この設計により、以下が可能になります:
|
||||
|
||||
```toml
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox"]
|
||||
}
|
||||
|
||||
# HTTPServerBoxはプラグイン内でSocketBoxを直接使用可能
|
||||
# MapBoxへの依存は以下のように解決:
|
||||
# - HTTPResponseBoxは内部でHashMapを使用
|
||||
# - get_header("name") で個別アクセス
|
||||
# - get_all_headers() は文字列配列として返す
|
||||
```
|
||||
@ -65,32 +65,158 @@ lifecycle:
|
||||
|
||||
## 🔧 設定ファイル(nyash.toml)
|
||||
|
||||
### 基本形式(v1) - 単一Box型プラグイン
|
||||
|
||||
```toml
|
||||
# プロジェクトルートのnyash.toml
|
||||
[plugins]
|
||||
FileBox = "filebox" # FileBoxはプラグイン版を使用
|
||||
FileBox = "nyash-filebox-plugin" # FileBoxはプラグイン版を使用
|
||||
# StringBox = "mystring" # コメントアウト = ビルトイン使用
|
||||
|
||||
# FileBoxの型情報定義
|
||||
[plugins.FileBox.methods]
|
||||
read = { args = [] }
|
||||
write = { args = [{ from = "string", to = "bytes" }] }
|
||||
open = { args = [
|
||||
{ name = "path", from = "string", to = "string" },
|
||||
{ name = "mode", from = "string", to = "string" }
|
||||
] }
|
||||
close = { args = [] }
|
||||
exists = { args = [], returns = "bool" }
|
||||
```
|
||||
|
||||
### 拡張形式(v2) - マルチBox型プラグイン
|
||||
|
||||
```toml
|
||||
# 1つのプラグインで複数のBox型を提供
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox", "HttpClientBox"]
|
||||
}
|
||||
|
||||
"nyash-stdlib" = {
|
||||
plugin_path = "libnyash_stdlib.so",
|
||||
provides = ["MathBox", "TimeBox", "RandomBox"]
|
||||
}
|
||||
|
||||
# 各Box型の詳細定義
|
||||
[plugins.types.SocketBox]
|
||||
library = "nyash-network"
|
||||
type_id = 100
|
||||
methods = {
|
||||
bind = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]},
|
||||
connect = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]},
|
||||
read = { args = [], returns = "string" },
|
||||
write = { args = [{ from = "string", to = "bytes" }] },
|
||||
close = { args = [] }
|
||||
}
|
||||
|
||||
[plugins.types.HTTPServerBox]
|
||||
library = "nyash-network"
|
||||
type_id = 101
|
||||
methods = {
|
||||
bind = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]},
|
||||
route = { args = [
|
||||
{ name = "path", from = "string", to = "string" },
|
||||
{ name = "method", from = "string", to = "string" }
|
||||
]},
|
||||
start = { args = [] }
|
||||
}
|
||||
|
||||
[plugins.types.HttpClientBox]
|
||||
library = "nyash-network"
|
||||
type_id = 102
|
||||
methods = {
|
||||
get = { args = [{ name = "url", from = "string", to = "string" }], returns = "string" },
|
||||
post = { args = [
|
||||
{ name = "url", from = "string", to = "string" },
|
||||
{ name = "body", from = "string", to = "string" }
|
||||
], returns = "string" }
|
||||
}
|
||||
```
|
||||
|
||||
### 型マッピング仕様
|
||||
|
||||
#### 基本型
|
||||
| Nyash型 | FFI型 | TLVタグ | 説明 |
|
||||
|---------|-------|---------|------|
|
||||
| `string` | `string` | 0x01 | UTF-8文字列 |
|
||||
| `integer` | `i64` | 0x02 | 64ビット整数 |
|
||||
| `float` | `f64` | 0x03 | 64ビット浮動小数点 |
|
||||
| `bool` | `bool` | 0x04 | 真偽値 |
|
||||
| `bytes` | `Vec<u8>` | 0x05 | バイト配列 |
|
||||
|
||||
|
||||
### プラグイン検索パス
|
||||
|
||||
```toml
|
||||
[plugin_paths]
|
||||
search_paths = [
|
||||
"./plugins/*/target/release", # 開発時リリースビルド
|
||||
"./plugins/*/target/debug", # 開発時デバッグビルド
|
||||
"/usr/local/lib/nyash/plugins", # システムインストール
|
||||
"~/.nyash/plugins" # ユーザーローカル
|
||||
]
|
||||
```
|
||||
|
||||
## 🏗️ アーキテクチャ
|
||||
|
||||
### 1. Boxレジストリ
|
||||
### 1. Boxレジストリ(v2対応版)
|
||||
|
||||
```rust
|
||||
// 起動時の動作
|
||||
let mut registry = HashMap::new();
|
||||
let mut loaded_plugins = HashMap::new();
|
||||
|
||||
// 1. ビルトインBoxを登録
|
||||
registry.insert("FileBox", BoxProvider::Builtin(native_filebox));
|
||||
registry.insert("StringBox", BoxProvider::Builtin(native_stringbox));
|
||||
|
||||
// 2. nyash.toml読み込み
|
||||
let config = parse_nyash_toml()?;
|
||||
let config = parse_nyash_toml_v2()?;
|
||||
|
||||
// 3. プラグイン設定で上書き
|
||||
// 3a. v1形式:単一Box型プラグイン
|
||||
for (box_name, plugin_name) in config.plugins {
|
||||
registry.insert(box_name, BoxProvider::Plugin(plugin_name));
|
||||
}
|
||||
|
||||
// 3b. v2形式:マルチBox型プラグイン
|
||||
if let Some(libraries) = config.libraries {
|
||||
for (lib_name, lib_def) in libraries.libraries {
|
||||
// プラグインを一度だけロード
|
||||
let plugin = load_plugin(&lib_def.plugin_path)?;
|
||||
loaded_plugins.insert(lib_name.clone(), plugin);
|
||||
|
||||
// 提供する全Box型を登録
|
||||
for box_type in &lib_def.provides {
|
||||
registry.insert(box_type, BoxProvider::MultiPlugin(lib_name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### マルチBox型プラグインFFI
|
||||
|
||||
```c
|
||||
// v2プラグインの追加エクスポート関数
|
||||
// 提供するBox型の数を返す
|
||||
extern "C" u32 nyash_plugin_get_box_count();
|
||||
|
||||
// 各Box型の情報を取得
|
||||
extern "C" NyashPluginInfo* nyash_plugin_get_box_info(u32 index);
|
||||
|
||||
// Box型名からtype_idを解決
|
||||
extern "C" u32 nyash_plugin_get_type_id(const char* box_name);
|
||||
```
|
||||
|
||||
### 2. 透過的なディスパッチ
|
||||
@ -232,30 +358,68 @@ impl Drop for PluginBox {
|
||||
|
||||
## 🚀 段階的導入計画
|
||||
|
||||
### Phase 1: 基本実装(現在)
|
||||
### Phase 1: 基本実装(完了)
|
||||
- [x] BID-FFI基盤
|
||||
- [x] FileBoxプラグイン実装
|
||||
- [ ] nyash.tomlパーサー
|
||||
- [ ] PluginBoxプロキシ
|
||||
- [ ] 手動プラグインロード
|
||||
- [x] nyash.toml v1パーサー
|
||||
- [x] PluginBoxプロキシ
|
||||
- [x] プラグインロード機能
|
||||
|
||||
### Phase 2: 開発体験向上
|
||||
### Phase 2: マルチBox型対応(進行中)
|
||||
- [ ] nyash.toml v2パーサー実装
|
||||
- [ ] マルチBox型プラグインFFI拡張
|
||||
- [ ] plugin-testerの複数Box型対応
|
||||
- [ ] ネットワーク系プラグイン統合
|
||||
- HttpClientBox(新規実装)
|
||||
- SocketBox(既存移行)
|
||||
- HTTPServerBox(既存移行)
|
||||
- HTTPRequestBox(既存移行)
|
||||
- HTTPResponseBox(既存移行)
|
||||
|
||||
### Phase 3: 開発体験向上
|
||||
- [ ] YAMLからFFIコード自動生成
|
||||
- [ ] エラーメッセージ改善
|
||||
- [ ] プラグインテンプレート
|
||||
- [ ] ホットリロード対応
|
||||
|
||||
### Phase 3: エコシステム
|
||||
### Phase 4: エコシステム
|
||||
- [ ] プラグインレジストリ
|
||||
- [ ] バージョン管理
|
||||
- [ ] 依存関係解決
|
||||
- [ ] プラグイン間通信
|
||||
|
||||
## 🎉 利点
|
||||
|
||||
### v1形式の利点
|
||||
1. **ビルド時間短縮** - 使わないBoxはコンパイル不要
|
||||
2. **動的拡張** - 再コンパイルなしで新Box追加
|
||||
3. **Everything is Box維持** - 哲学は変わらない
|
||||
4. **段階的移行** - 1つずつBoxをプラグイン化
|
||||
|
||||
### v2形式の追加利点
|
||||
5. **依存関係の解決** - 関連Box群を1つのプラグインに
|
||||
6. **効率的な配布** - 複数Box型を1ライブラリで提供
|
||||
7. **メモリ効率** - 共有ライブラリは1度だけロード
|
||||
8. **内部連携** - 同一プラグイン内で直接通信可能
|
||||
|
||||
### 実例:HTTPServerBoxの依存問題解決
|
||||
|
||||
```toml
|
||||
# v1では困難だった構成
|
||||
# HTTPServerBoxはSocketBoxに依存するが...
|
||||
[plugins]
|
||||
SocketBox = "socket-plugin" # 別プラグイン
|
||||
HTTPServerBox = "http-plugin" # SocketBoxが使えない!
|
||||
|
||||
# v2なら簡単に解決
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox"]
|
||||
}
|
||||
# HTTPServerBoxは同じプラグイン内でSocketBoxを直接使用可能
|
||||
```
|
||||
|
||||
## 📚 関連ドキュメント
|
||||
|
||||
- [BID-FFI仕様](./ffi-abi-specification.md)
|
||||
|
||||
14
local_tests/test_http_minimal.nyash
Normal file
14
local_tests/test_http_minimal.nyash
Normal file
@ -0,0 +1,14 @@
|
||||
// HttpClientBox最小テスト
|
||||
static box Main {
|
||||
init { http }
|
||||
|
||||
main() {
|
||||
me.http = new HttpClientBox()
|
||||
|
||||
// GET test
|
||||
local result = me.http.get("https://httpbin.org/get")
|
||||
print(result)
|
||||
|
||||
return "done"
|
||||
}
|
||||
}
|
||||
33
local_tests/test_http_server.nyash
Normal file
33
local_tests/test_http_server.nyash
Normal file
@ -0,0 +1,33 @@
|
||||
// 🌐 HTTPServerBox簡単テスト
|
||||
static box Main {
|
||||
init { server, console }
|
||||
|
||||
main() {
|
||||
me.console = new ConsoleBox()
|
||||
me.console.log("🚀 HTTPサーバーテスト開始...")
|
||||
|
||||
// HTTPServerBoxを作成
|
||||
me.server = new HTTPServerBox()
|
||||
|
||||
// サーバー設定
|
||||
me.server.port(8080)
|
||||
|
||||
// ルートハンドラー設定
|
||||
me.server.route("/", "GET", function(request) {
|
||||
return "Welcome to Nyash HTTP Server!"
|
||||
})
|
||||
|
||||
// JSONレスポンス
|
||||
me.server.route("/api/hello", "GET", function(request) {
|
||||
local response = new MapBox()
|
||||
response.set("message", "Hello from Nyash!")
|
||||
response.set("timestamp", new TimeBox().now())
|
||||
return response
|
||||
})
|
||||
|
||||
me.console.log("🌍 サーバー起動: http://localhost:8080")
|
||||
me.server.start()
|
||||
|
||||
return "サーバー実行中..."
|
||||
}
|
||||
}
|
||||
25
local_tests/test_http_server_working.nyash
Normal file
25
local_tests/test_http_server_working.nyash
Normal file
@ -0,0 +1,25 @@
|
||||
// HTTPServerBoxの動作テスト
|
||||
static box Main {
|
||||
init {
|
||||
ConsoleBox console,
|
||||
HTTPServerBox server
|
||||
}
|
||||
|
||||
main() {
|
||||
me.console = new ConsoleBox()
|
||||
me.console.log("🌐 HTTPServerBoxテスト開始...")
|
||||
|
||||
// HTTPServerBoxを作成
|
||||
me.server = new HTTPServerBox()
|
||||
me.console.log("✅ HTTPServerBox作成成功")
|
||||
|
||||
// 基本情報を表示
|
||||
me.console.log("HTTPServerBox: " + me.server.toString())
|
||||
|
||||
// bind設定(実際のサーバー起動はしない)
|
||||
me.server.bind("127.0.0.1", 8080)
|
||||
me.console.log("✅ Bind設定完了: 127.0.0.1:8080")
|
||||
|
||||
return "完了"
|
||||
}
|
||||
}
|
||||
28
local_tests/test_http_simple.nyash
Normal file
28
local_tests/test_http_simple.nyash
Normal file
@ -0,0 +1,28 @@
|
||||
// 🌐 HttpClientBox簡単テスト
|
||||
static box Main {
|
||||
init { http, console }
|
||||
|
||||
main() {
|
||||
me.console = new ConsoleBox()
|
||||
me.console.log("🌐 HTTP通信テスト開始...")
|
||||
|
||||
// HttpClientBoxを作成
|
||||
me.http = new HttpClientBox()
|
||||
|
||||
// 1. 簡単なGETリクエスト
|
||||
me.console.log("📥 GET: https://httpbin.org/get")
|
||||
local getResult = me.http.get("https://httpbin.org/get")
|
||||
me.console.log("結果: " + getResult.toString())
|
||||
|
||||
// 2. POSTリクエスト
|
||||
me.console.log("")
|
||||
me.console.log("📤 POST: https://httpbin.org/post")
|
||||
local postResult = me.http.post(
|
||||
"https://httpbin.org/post",
|
||||
"Hello from Nyash!"
|
||||
)
|
||||
me.console.log("結果: " + postResult.toString())
|
||||
|
||||
return "✅ HTTPテスト完了"
|
||||
}
|
||||
}
|
||||
22
local_tests/test_socket_echo.nyash
Normal file
22
local_tests/test_socket_echo.nyash
Normal file
@ -0,0 +1,22 @@
|
||||
// 🔌 SocketBox簡単テスト(TCP通信)
|
||||
static box Main {
|
||||
init { console }
|
||||
|
||||
main() {
|
||||
me.console = new ConsoleBox()
|
||||
me.console.log("🔌 TCP通信テスト...")
|
||||
|
||||
// シンプルなTCPクライアント
|
||||
local socket = new SocketBox()
|
||||
|
||||
// Google DNSに接続してテスト
|
||||
socket.connect("8.8.8.8", 53)
|
||||
me.console.log("✅ 接続成功: 8.8.8.8:53")
|
||||
|
||||
// 切断
|
||||
socket.close()
|
||||
me.console.log("✅ 切断完了")
|
||||
|
||||
return "ソケットテスト完了"
|
||||
}
|
||||
}
|
||||
21
local_tests/test_socket_working.nyash
Normal file
21
local_tests/test_socket_working.nyash
Normal file
@ -0,0 +1,21 @@
|
||||
// SocketBoxの動作テスト
|
||||
static box Main {
|
||||
init {
|
||||
ConsoleBox console,
|
||||
SocketBox socket
|
||||
}
|
||||
|
||||
main() {
|
||||
me.console = new ConsoleBox()
|
||||
me.console.log("🔌 SocketBoxテスト開始...")
|
||||
|
||||
// SocketBoxを作成
|
||||
me.socket = new SocketBox()
|
||||
me.console.log("✅ SocketBox作成成功")
|
||||
|
||||
// 基本情報を表示
|
||||
me.console.log("SocketBox: " + me.socket.toString())
|
||||
|
||||
return "完了"
|
||||
}
|
||||
}
|
||||
42
local_tests/test_working_boxes.nyash
Normal file
42
local_tests/test_working_boxes.nyash
Normal file
@ -0,0 +1,42 @@
|
||||
// 動作するBox型のテスト
|
||||
static box Main {
|
||||
init {
|
||||
StringBox console,
|
||||
IntegerBox count,
|
||||
ArrayBox list,
|
||||
MapBox data
|
||||
}
|
||||
|
||||
main() {
|
||||
// ConsoleBoxで出力
|
||||
me.console = new ConsoleBox()
|
||||
me.console.log("🎉 動作するBoxのテスト開始!")
|
||||
|
||||
// StringBox
|
||||
local text = new StringBox("Hello Nyash!")
|
||||
me.console.log("StringBox: " + text.toString())
|
||||
|
||||
// IntegerBox
|
||||
me.count = new IntegerBox(42)
|
||||
me.console.log("IntegerBox: " + me.count.toString())
|
||||
|
||||
// ArrayBox
|
||||
me.list = new ArrayBox()
|
||||
me.list.push("item1")
|
||||
me.list.push("item2")
|
||||
me.console.log("ArrayBox size: " + me.list.size().toString())
|
||||
|
||||
// MapBox
|
||||
me.data = new MapBox()
|
||||
me.data.set("name", "Nyash")
|
||||
me.data.set("version", "1.0")
|
||||
me.console.log("MapBox keys: " + me.data.keys().toString())
|
||||
|
||||
// MathBox
|
||||
local math = new MathBox()
|
||||
local result = math.sqrt(16)
|
||||
me.console.log("MathBox sqrt(16): " + result.toString())
|
||||
|
||||
return "✅ テスト完了!"
|
||||
}
|
||||
}
|
||||
9
plugins/nyash-test-multibox/Cargo.toml
Normal file
9
plugins/nyash-test-multibox/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "nyash-test-multibox"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
373
plugins/nyash-test-multibox/src/lib.rs
Normal file
373
plugins/nyash-test-multibox/src/lib.rs
Normal file
@ -0,0 +1,373 @@
|
||||
//! Test Multi-Box Plugin for Nyash
|
||||
//!
|
||||
//! Provides TestBoxA and TestBoxB to demonstrate multi-box plugin support
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use std::sync::Mutex;
|
||||
|
||||
// ============ FFI Types ============
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NyashHostVtable {
|
||||
pub alloc: unsafe extern "C" fn(size: usize) -> *mut u8,
|
||||
pub free: unsafe extern "C" fn(ptr: *mut u8),
|
||||
pub wake: unsafe extern "C" fn(handle: u64),
|
||||
pub log: unsafe extern "C" fn(level: i32, msg: *const c_char),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NyashMethodInfo {
|
||||
pub method_id: u32,
|
||||
pub name: *const c_char,
|
||||
pub signature: u32,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NyashMethodInfo {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NyashPluginInfo {
|
||||
pub type_id: u32,
|
||||
pub type_name: *const c_char,
|
||||
pub method_count: usize,
|
||||
pub methods: *const NyashMethodInfo,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NyashPluginInfo {}
|
||||
|
||||
// Error codes
|
||||
const NYB_SUCCESS: i32 = 0;
|
||||
const NYB_E_INVALID_ARGS: i32 = -4;
|
||||
const NYB_E_INVALID_HANDLE: i32 = -8;
|
||||
|
||||
// Method IDs
|
||||
const METHOD_BIRTH: u32 = 0;
|
||||
const METHOD_HELLO: u32 = 1;
|
||||
const METHOD_GREET: u32 = 2;
|
||||
const METHOD_FINI: u32 = u32::MAX;
|
||||
|
||||
// Type IDs
|
||||
const TYPE_ID_TESTBOX_A: u32 = 200;
|
||||
const TYPE_ID_TESTBOX_B: u32 = 201;
|
||||
|
||||
// Global state
|
||||
static mut HOST_VTABLE: Option<&'static NyashHostVtable> = None;
|
||||
static mut INSTANCES: Option<Mutex<HashMap<u32, TestInstance>>> = None;
|
||||
static mut INSTANCE_COUNTER: u32 = 1;
|
||||
|
||||
enum TestInstance {
|
||||
BoxA { message: String },
|
||||
BoxB { counter: i32 },
|
||||
}
|
||||
|
||||
// ============ Plugin Info for Each Box Type ============
|
||||
|
||||
static TESTBOX_A_NAME: &[u8] = b"TestBoxA\0";
|
||||
static TESTBOX_B_NAME: &[u8] = b"TestBoxB\0";
|
||||
|
||||
static METHOD_HELLO_NAME: &[u8] = b"hello\0";
|
||||
static METHOD_GREET_NAME: &[u8] = b"greet\0";
|
||||
static METHOD_BIRTH_NAME: &[u8] = b"birth\0";
|
||||
static METHOD_FINI_NAME: &[u8] = b"fini\0";
|
||||
|
||||
static TESTBOX_A_METHODS: [NyashMethodInfo; 3] = [
|
||||
NyashMethodInfo {
|
||||
method_id: METHOD_BIRTH,
|
||||
name: METHOD_BIRTH_NAME.as_ptr() as *const c_char,
|
||||
signature: 0,
|
||||
},
|
||||
NyashMethodInfo {
|
||||
method_id: METHOD_HELLO,
|
||||
name: METHOD_HELLO_NAME.as_ptr() as *const c_char,
|
||||
signature: 0,
|
||||
},
|
||||
NyashMethodInfo {
|
||||
method_id: METHOD_FINI,
|
||||
name: METHOD_FINI_NAME.as_ptr() as *const c_char,
|
||||
signature: 0,
|
||||
},
|
||||
];
|
||||
|
||||
static TESTBOX_B_METHODS: [NyashMethodInfo; 3] = [
|
||||
NyashMethodInfo {
|
||||
method_id: METHOD_BIRTH,
|
||||
name: METHOD_BIRTH_NAME.as_ptr() as *const c_char,
|
||||
signature: 0,
|
||||
},
|
||||
NyashMethodInfo {
|
||||
method_id: METHOD_GREET,
|
||||
name: METHOD_GREET_NAME.as_ptr() as *const c_char,
|
||||
signature: 0,
|
||||
},
|
||||
NyashMethodInfo {
|
||||
method_id: METHOD_FINI,
|
||||
name: METHOD_FINI_NAME.as_ptr() as *const c_char,
|
||||
signature: 0,
|
||||
},
|
||||
];
|
||||
|
||||
static TESTBOX_A_INFO: NyashPluginInfo = NyashPluginInfo {
|
||||
type_id: TYPE_ID_TESTBOX_A,
|
||||
type_name: TESTBOX_A_NAME.as_ptr() as *const c_char,
|
||||
method_count: 3,
|
||||
methods: TESTBOX_A_METHODS.as_ptr(),
|
||||
};
|
||||
|
||||
static TESTBOX_B_INFO: NyashPluginInfo = NyashPluginInfo {
|
||||
type_id: TYPE_ID_TESTBOX_B,
|
||||
type_name: TESTBOX_B_NAME.as_ptr() as *const c_char,
|
||||
method_count: 3,
|
||||
methods: TESTBOX_B_METHODS.as_ptr(),
|
||||
};
|
||||
|
||||
// ============ Plugin Entry Points ============
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1 // BID-1 ABI version
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init(
|
||||
host: *const NyashHostVtable,
|
||||
_info: *mut std::ffi::c_void, // For v2, we use get_box_info instead
|
||||
) -> i32 {
|
||||
if host.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
HOST_VTABLE = Some(&*host);
|
||||
INSTANCES = Some(Mutex::new(HashMap::new()));
|
||||
|
||||
// Log initialization
|
||||
log_info("Multi-box test plugin initialized");
|
||||
}
|
||||
|
||||
NYB_SUCCESS
|
||||
}
|
||||
|
||||
// ============ Multi-Box v2 Functions ============
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_get_box_count() -> u32 {
|
||||
2 // TestBoxA and TestBoxB
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_get_box_info(index: u32) -> *const NyashPluginInfo {
|
||||
match index {
|
||||
0 => &TESTBOX_A_INFO as *const NyashPluginInfo,
|
||||
1 => &TESTBOX_B_INFO as *const NyashPluginInfo,
|
||||
_ => ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_get_type_id(box_name: *const c_char) -> u32 {
|
||||
if box_name.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let name = std::ffi::CStr::from_ptr(box_name).to_string_lossy();
|
||||
match name.as_ref() {
|
||||
"TestBoxA" => TYPE_ID_TESTBOX_A,
|
||||
"TestBoxB" => TYPE_ID_TESTBOX_B,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Method Invocation ============
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
type_id: u32,
|
||||
method_id: u32,
|
||||
instance_id: u32,
|
||||
_args: *const u8,
|
||||
_args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
match (type_id, method_id) {
|
||||
// TestBoxA methods
|
||||
(TYPE_ID_TESTBOX_A, METHOD_BIRTH) => {
|
||||
create_instance_a(result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_A, METHOD_HELLO) => {
|
||||
hello_method(instance_id, result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_A, METHOD_FINI) => {
|
||||
destroy_instance(instance_id)
|
||||
}
|
||||
|
||||
// TestBoxB methods
|
||||
(TYPE_ID_TESTBOX_B, METHOD_BIRTH) => {
|
||||
create_instance_b(result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_B, METHOD_GREET) => {
|
||||
greet_method(instance_id, result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_B, METHOD_FINI) => {
|
||||
destroy_instance(instance_id)
|
||||
}
|
||||
|
||||
_ => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Method Implementations ============
|
||||
|
||||
unsafe fn create_instance_a(result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if let Some(ref mutex) = INSTANCES {
|
||||
if let Ok(mut map) = mutex.lock() {
|
||||
let id = INSTANCE_COUNTER;
|
||||
INSTANCE_COUNTER += 1;
|
||||
|
||||
map.insert(id, TestInstance::BoxA {
|
||||
message: "Hello from TestBoxA!".to_string(),
|
||||
});
|
||||
|
||||
// Return instance ID
|
||||
if *result_len >= 4 {
|
||||
let bytes = id.to_le_bytes();
|
||||
ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
|
||||
log_info(&format!("Created TestBoxA instance {}", id));
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
|
||||
unsafe fn create_instance_b(result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if let Some(ref mutex) = INSTANCES {
|
||||
if let Ok(mut map) = mutex.lock() {
|
||||
let id = INSTANCE_COUNTER;
|
||||
INSTANCE_COUNTER += 1;
|
||||
|
||||
map.insert(id, TestInstance::BoxB {
|
||||
counter: 0,
|
||||
});
|
||||
|
||||
// Return instance ID
|
||||
if *result_len >= 4 {
|
||||
let bytes = id.to_le_bytes();
|
||||
ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
|
||||
log_info(&format!("Created TestBoxB instance {}", id));
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
|
||||
unsafe fn hello_method(instance_id: u32, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if let Some(ref mutex) = INSTANCES {
|
||||
if let Ok(map) = mutex.lock() {
|
||||
if let Some(TestInstance::BoxA { message }) = map.get(&instance_id) {
|
||||
// Return message as TLV string
|
||||
write_tlv_string(message, result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn greet_method(instance_id: u32, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if let Some(ref mutex) = INSTANCES {
|
||||
if let Ok(mut map) = mutex.lock() {
|
||||
if let Some(TestInstance::BoxB { counter }) = map.get_mut(&instance_id) {
|
||||
*counter += 1;
|
||||
let message = format!("Greeting #{} from TestBoxB!", counter);
|
||||
|
||||
// Return message as TLV string
|
||||
write_tlv_string(&message, result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn destroy_instance(instance_id: u32) -> i32 {
|
||||
if let Some(ref mutex) = INSTANCES {
|
||||
if let Ok(mut map) = mutex.lock() {
|
||||
if map.remove(&instance_id).is_some() {
|
||||
log_info(&format!("Destroyed instance {}", instance_id));
|
||||
NYB_SUCCESS
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Helper Functions ============
|
||||
|
||||
unsafe fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
let bytes = s.as_bytes();
|
||||
let needed = 8 + bytes.len(); // header(4) + entry(4) + string
|
||||
|
||||
if *result_len < needed {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
|
||||
// TLV header
|
||||
*result = 1; // version low
|
||||
*result.offset(1) = 0; // version high
|
||||
*result.offset(2) = 1; // argc low
|
||||
*result.offset(3) = 0; // argc high
|
||||
|
||||
// String entry
|
||||
*result.offset(4) = 6; // Tag::String
|
||||
*result.offset(5) = 0; // padding
|
||||
let len_bytes = (bytes.len() as u16).to_le_bytes();
|
||||
*result.offset(6) = len_bytes[0];
|
||||
*result.offset(7) = len_bytes[1];
|
||||
|
||||
// String data
|
||||
ptr::copy_nonoverlapping(bytes.as_ptr(), result.offset(8), bytes.len());
|
||||
|
||||
*result_len = needed;
|
||||
NYB_SUCCESS
|
||||
}
|
||||
|
||||
unsafe fn log_info(message: &str) {
|
||||
if let Some(vtable) = HOST_VTABLE {
|
||||
if let Ok(c_str) = std::ffi::CString::new(message) {
|
||||
(vtable.log)(1, c_str.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_shutdown() {
|
||||
unsafe {
|
||||
log_info("Multi-box test plugin shutting down");
|
||||
INSTANCES = None;
|
||||
HOST_VTABLE = None;
|
||||
}
|
||||
}
|
||||
145
src/config/nyash_toml_v2.rs
Normal file
145
src/config/nyash_toml_v2.rs
Normal file
@ -0,0 +1,145 @@
|
||||
//! nyash.toml v2 configuration parser
|
||||
//!
|
||||
//! Supports both legacy single-box plugins and new multi-box plugins
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Root configuration structure
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct NyashConfigV2 {
|
||||
/// Legacy single-box plugins (for backward compatibility)
|
||||
#[serde(default)]
|
||||
pub plugins: HashMap<String, String>,
|
||||
|
||||
/// Plugin-specific configurations (legacy)
|
||||
#[serde(flatten)]
|
||||
pub plugin_configs: HashMap<String, toml::Value>,
|
||||
|
||||
/// New multi-box plugin libraries
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub libraries: Option<PluginLibraries>,
|
||||
|
||||
/// Box type definitions
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub types: Option<HashMap<String, BoxTypeDefinition>>,
|
||||
}
|
||||
|
||||
/// Plugin libraries section
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct PluginLibraries {
|
||||
#[serde(flatten)]
|
||||
pub libraries: HashMap<String, LibraryDefinition>,
|
||||
}
|
||||
|
||||
/// Library definition
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct LibraryDefinition {
|
||||
pub plugin_path: String,
|
||||
pub provides: Vec<String>,
|
||||
}
|
||||
|
||||
/// Box type definition
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct BoxTypeDefinition {
|
||||
pub library: String,
|
||||
pub type_id: u32,
|
||||
pub methods: HashMap<String, MethodDefinition>,
|
||||
}
|
||||
|
||||
/// Method definition
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct MethodDefinition {
|
||||
#[serde(default)]
|
||||
pub args: Vec<ArgumentDefinition>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub returns: Option<String>,
|
||||
}
|
||||
|
||||
/// Argument definition
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ArgumentDefinition {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
impl NyashConfigV2 {
|
||||
/// Parse nyash.toml file
|
||||
pub fn from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let config: NyashConfigV2 = toml::from_str(&content)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Check if using v2 format
|
||||
pub fn is_v2_format(&self) -> bool {
|
||||
self.libraries.is_some() || self.types.is_some()
|
||||
}
|
||||
|
||||
/// Get all box types provided by a library
|
||||
pub fn get_box_types_for_library(&self, library_name: &str) -> Vec<String> {
|
||||
if let Some(libs) = &self.libraries {
|
||||
if let Some(lib_def) = libs.libraries.get(library_name) {
|
||||
return lib_def.provides.clone();
|
||||
}
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Get library name for a box type
|
||||
pub fn get_library_for_box_type(&self, box_type: &str) -> Option<String> {
|
||||
// Check v2 format first
|
||||
if let Some(types) = &self.types {
|
||||
if let Some(type_def) = types.get(box_type) {
|
||||
return Some(type_def.library.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to legacy format
|
||||
self.plugins.get(box_type).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_legacy_format() {
|
||||
let toml_str = r#"
|
||||
[plugins]
|
||||
FileBox = "nyash-filebox-plugin"
|
||||
|
||||
[plugins.FileBox.methods]
|
||||
read = { args = [] }
|
||||
"#;
|
||||
|
||||
let config: NyashConfigV2 = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(config.plugins.get("FileBox"), Some(&"nyash-filebox-plugin".to_string()));
|
||||
assert!(!config.is_v2_format());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_v2_format() {
|
||||
let toml_str = r#"
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox"]
|
||||
}
|
||||
|
||||
[plugins.types.SocketBox]
|
||||
library = "nyash-network"
|
||||
type_id = 100
|
||||
methods = { bind = { args = [] } }
|
||||
"#;
|
||||
|
||||
let config: NyashConfigV2 = toml::from_str(toml_str).unwrap();
|
||||
assert!(config.is_v2_format());
|
||||
assert_eq!(config.get_box_types_for_library("nyash-network"), vec!["SocketBox", "HTTPServerBox"]);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user