diff --git a/src/bid/loader.rs b/src/bid/loader.rs index a40fa93f..cd26cec0 100644 --- a/src/bid/loader.rs +++ b/src/bid/loader.rs @@ -8,6 +8,7 @@ pub struct LoadedPlugin { pub library: Library, pub handle: PluginHandle, pub type_id: u32, + pub plugin_info: NyashPluginInfo, // プラグイン情報を保存 } impl LoadedPlugin { @@ -48,9 +49,46 @@ impl LoadedPlugin { handle.initialize(&host, &mut info)?; let type_id = info.type_id; - Ok(Self { library, handle, type_id }) + Ok(Self { library, handle, type_id, plugin_info: info }) } } + + /// Get the plugin's Box type name + pub fn get_type_name(&self) -> BidResult<&str> { + unsafe { self.plugin_info.name() } + } + + /// Get all available methods for this plugin + pub fn get_methods(&self) -> BidResult> { + let mut methods = Vec::new(); + + unsafe { + let methods_slice = self.plugin_info.methods_slice()?; + for method_info in methods_slice { + let method_name = method_info.name()?.to_string(); + methods.push(( + method_info.method_id, + method_name, + method_info.signature_hash, + )); + } + } + + Ok(methods) + } + + /// Find a method by name and return its info + pub fn find_method(&self, method_name: &str) -> BidResult> { + unsafe { + let methods_slice = self.plugin_info.methods_slice()?; + for method_info in methods_slice { + if method_info.name()? == method_name { + return Ok(Some((method_info.method_id, method_info.signature_hash))); + } + } + } + Ok(None) + } } /// Build a minimal host vtable for plugins diff --git a/src/bid/metadata.rs b/src/bid/metadata.rs index 662ab2eb..fbcd5ed8 100644 --- a/src/bid/metadata.rs +++ b/src/bid/metadata.rs @@ -35,6 +35,11 @@ pub struct NyashMethodInfo { pub signature_hash: u32, } +// SAFETY: The C pointers in NyashMethodInfo are read-only after initialization +// and point to valid memory managed by the plugin system +unsafe impl Send for NyashMethodInfo {} +unsafe impl Sync for NyashMethodInfo {} + impl NyashMethodInfo { /// Create method info with safe string handling pub fn new(method_id: u32, method_name: &str, signature_hash: u32) -> BidResult<(Self, CString)> { @@ -78,6 +83,11 @@ pub struct NyashPluginInfo { pub methods: *const NyashMethodInfo, } +// SAFETY: The C pointers in NyashPluginInfo are read-only after initialization +// and point to valid memory managed by the plugin system +unsafe impl Send for NyashPluginInfo {} +unsafe impl Sync for NyashPluginInfo {} + impl NyashPluginInfo { /// Create an empty plugin info pub fn empty() -> Self { diff --git a/src/bid/plugin_box.rs b/src/bid/plugin_box.rs index bcf20256..027d671c 100644 --- a/src/bid/plugin_box.rs +++ b/src/bid/plugin_box.rs @@ -111,6 +111,48 @@ impl PluginFileBox { pub fn read_bytes(&self, size: usize) -> BidResult> { self.inner.read(size) } pub fn write_bytes(&self, data: &[u8]) -> BidResult { self.inner.write(data) } pub fn close(&self) -> BidResult<()> { self.inner.close() } + + /// 汎用メソッド呼び出し(動的ディスパッチ) + pub fn call_method(&self, method_name: &str, args: &[u8]) -> BidResult> { + eprintln!("🔍 call_method: method_name='{}', args_len={}", method_name, args.len()); + + // プラグインからメソッドIDを動的取得 + match self.inner.plugin.find_method(method_name) { + Ok(Some((method_id, signature))) => { + eprintln!("🔍 Found method '{}': ID={}, signature=0x{:08X}", method_name, method_id, signature); + let mut out = Vec::new(); + match self.inner.plugin.handle.invoke( + self.inner.plugin.type_id, + method_id, + self.inner.instance_id, + args, + &mut out + ) { + Ok(()) => { + eprintln!("🔍 Plugin invoke succeeded, output_len={}", out.len()); + Ok(out) + } + Err(e) => { + eprintln!("🔍 Plugin invoke failed: {:?}", e); + Err(e) + } + } + } + Ok(None) => { + eprintln!("🔍 Method '{}' not found in plugin", method_name); + Err(BidError::InvalidArgs) // メソッドが見つからない + } + Err(e) => { + eprintln!("🔍 Error looking up method '{}': {:?}", method_name, e); + Err(e) + } + } + } + + /// プラグインのメソッド一覧を取得 + pub fn get_available_methods(&self) -> BidResult> { + self.inner.plugin.get_methods() + } } impl BoxCore for PluginFileBox { diff --git a/src/interpreter/methods/io_methods.rs b/src/interpreter/methods/io_methods.rs index 2686b1d5..e21b93a9 100644 --- a/src/interpreter/methods/io_methods.rs +++ b/src/interpreter/methods/io_methods.rs @@ -107,72 +107,156 @@ impl NyashInterpreter { } } - /// PluginFileBoxのメソッド呼び出しを実行 (BID-FFI system) - /// Handles plugin-backed file I/O operations via FFI interface - pub(in crate::interpreter) fn execute_plugin_file_method(&mut self, plugin_file_box: &PluginFileBox, method: &str, arguments: &[ASTNode]) + /// 汎用プラグインメソッド呼び出し実行 (BID-FFI system) + /// Handles generic plugin method calls via dynamic method discovery + pub(in crate::interpreter) fn execute_plugin_method_generic(&mut self, plugin_box: &PluginFileBox, method: &str, arguments: &[ASTNode]) -> Result, RuntimeError> { - match method { - "read" => { - if !arguments.is_empty() { - return Err(RuntimeError::InvalidOperation { - message: format!("read() expects 0 arguments, got {}", arguments.len()), - }); - } - // Read the entire file content - match plugin_file_box.read_bytes(8192) { // Read up to 8KB - Ok(bytes) => { - let content = String::from_utf8_lossy(&bytes).to_string(); - Ok(Box::new(StringBox::new(content))) - } - Err(e) => Err(RuntimeError::InvalidOperation { - message: format!("Plugin read failed: {:?}", e), - }) + + eprintln!("🔍 execute_plugin_method_generic: method='{}', args_count={}", method, arguments.len()); + + // まず利用可能なメソッドを確認 + match plugin_box.get_available_methods() { + Ok(methods) => { + eprintln!("🔍 Available plugin methods:"); + for (id, name, sig) in &methods { + eprintln!("🔍 - {} [ID: {}, Sig: 0x{:08X}]", name, id, sig); } } - "write" => { - if arguments.len() != 1 { - return Err(RuntimeError::InvalidOperation { - message: format!("write() expects 1 argument, got {}", arguments.len()), - }); - } - let content = self.execute_expression(&arguments[0])?; - let text = content.to_string_box().value; - match plugin_file_box.write_bytes(text.as_bytes()) { - Ok(bytes_written) => Ok(Box::new(StringBox::new("OK".to_string()))), - Err(e) => Err(RuntimeError::InvalidOperation { - message: format!("Plugin write failed: {:?}", e), - }) - } + Err(e) => eprintln!("⚠️ Failed to get plugin methods: {:?}", e), + } + + // 引数をTLVエンコード(メソッド名も渡す) + let encoded_args = self.encode_arguments_to_tlv(arguments, method)?; + eprintln!("🔍 Encoded args length: {} bytes", encoded_args.len()); + + // プラグインのメソッドを動的呼び出し + match plugin_box.call_method(method, &encoded_args) { + Ok(response_bytes) => { + eprintln!("🔍 Plugin method '{}' succeeded, response length: {} bytes", method, response_bytes.len()); + // レスポンスをデコードしてNyashBoxに変換 + self.decode_tlv_to_nyash_box(&response_bytes, method) } - "exists" => { - if !arguments.is_empty() { - return Err(RuntimeError::InvalidOperation { - message: format!("exists() expects 0 arguments, got {}", arguments.len()), - }); - } - // Plugin FileBox doesn't have exists() method in current implementation - // Return true if we can read (approximate) - match plugin_file_box.read_bytes(1) { - Ok(_) => Ok(Box::new(crate::box_trait::BoolBox::new(true))), - Err(_) => Ok(Box::new(crate::box_trait::BoolBox::new(false))), - } + Err(e) => { + eprintln!("🔍 Plugin method '{}' failed with error: {:?}", method, e); + Err(RuntimeError::InvalidOperation { + message: format!("Plugin method '{}' failed: {:?}", method, e), + }) } - "close" => { - if !arguments.is_empty() { - return Err(RuntimeError::InvalidOperation { - message: format!("close() expects 0 arguments, got {}", arguments.len()), - }); - } - match plugin_file_box.close() { - Ok(()) => Ok(Box::new(StringBox::new("OK".to_string()))), - Err(e) => Err(RuntimeError::InvalidOperation { - message: format!("Plugin close failed: {:?}", e), - }) - } - } - _ => Err(RuntimeError::InvalidOperation { - message: format!("Unknown method '{}' for PluginFileBox", method), - }) } } + + /// 引数をTLVエンコード(メソッドに応じて特殊処理) + fn encode_arguments_to_tlv(&mut self, arguments: &[ASTNode], method_name: &str) -> Result, RuntimeError> { + use crate::bid::tlv::TlvEncoder; + + let mut encoder = TlvEncoder::new(); + + // 特殊ケース: readメソッドは引数がなくても、サイズ引数が必要 + if method_name == "read" && arguments.is_empty() { + // デフォルトで8192バイト読み取り + encoder.encode_i32(8192) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV i32 encoding failed: {:?}", e), + })?; + } else { + // 通常の引数エンコード + for arg in arguments { + let value = self.execute_expression(arg)?; + + // 型に応じてエンコード + if let Some(str_box) = value.as_any().downcast_ref::() { + // 🔍 writeメソッドなど、文字列データはBytesとしてエンコード + // プラグインは通常、文字列データをBytesタグ(7)で期待する + encoder.encode_bytes(str_box.value.as_bytes()) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV bytes encoding failed: {:?}", e), + })?; + } else if let Some(int_box) = value.as_any().downcast_ref::() { + encoder.encode_i32(int_box.value as i32) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV integer encoding failed: {:?}", e), + })?; + } else if let Some(bool_box) = value.as_any().downcast_ref::() { + encoder.encode_bool(bool_box.value) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV bool encoding failed: {:?}", e), + })?; + } else { + // デフォルト: バイトデータとして扱う + let str_val = value.to_string_box().value; + encoder.encode_bytes(str_val.as_bytes()) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV default bytes encoding failed: {:?}", e), + })?; + } + } + } + + Ok(encoder.finish()) + } + + /// TLVレスポンスをNyashBoxに変換 + fn decode_tlv_to_nyash_box(&self, response_bytes: &[u8], method_name: &str) -> Result, RuntimeError> { + use crate::bid::tlv::TlvDecoder; + use crate::bid::types::BidTag; + + if response_bytes.is_empty() { + return Ok(Box::new(StringBox::new("".to_string()))); + } + + let mut decoder = TlvDecoder::new(response_bytes) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV decoder creation failed: {:?}", e), + })?; + + if let Some((tag, payload)) = decoder.decode_next() + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV decoding failed: {:?}", e), + })? { + + match tag { + BidTag::String => { + let text = String::from_utf8_lossy(payload).to_string(); + Ok(Box::new(StringBox::new(text))) + } + BidTag::Bytes => { + // ファイル読み取り等のバイトデータは文字列として返す + let text = String::from_utf8_lossy(payload).to_string(); + Ok(Box::new(StringBox::new(text))) + } + BidTag::I32 => { + let value = TlvDecoder::decode_i32(payload) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV i32 decoding failed: {:?}", e), + })?; + Ok(Box::new(crate::box_trait::IntegerBox::new(value as i64))) + } + BidTag::Bool => { + let value = TlvDecoder::decode_bool(payload) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("TLV bool decoding failed: {:?}", e), + })?; + Ok(Box::new(crate::box_trait::BoolBox::new(value))) + } + BidTag::Void => { + Ok(Box::new(StringBox::new("OK".to_string()))) + } + _ => { + Ok(Box::new(StringBox::new(format!("Unknown TLV tag: {:?}", tag)))) + } + } + } else { + Ok(Box::new(StringBox::new("".to_string()))) + } + } + + /// PluginFileBoxのメソッド呼び出しを実行 (BID-FFI system) - LEGACY HARDCODED VERSION + /// Handles plugin-backed file I/O operations via FFI interface + /// 🚨 DEPRECATED: This method has hardcoded method names and violates BID-FFI principles + /// Use execute_plugin_method_generic instead for true dynamic method calling + pub(in crate::interpreter) fn execute_plugin_file_method(&mut self, plugin_file_box: &PluginFileBox, method: &str, arguments: &[ASTNode]) + -> Result, RuntimeError> { + // 🎯 新しい汎用システムにリダイレクト + self.execute_plugin_method_generic(plugin_file_box, method, arguments) + } } \ No newline at end of file