diff --git a/local_tests/test_dynamic_filebox.nyash b/local_tests/test_dynamic_filebox.nyash new file mode 100644 index 00000000..22fdbb66 --- /dev/null +++ b/local_tests/test_dynamic_filebox.nyash @@ -0,0 +1,26 @@ +// Test for dynamic FileBox plugin (Phase 9.75f-1) +using nyashstd + +console.log("🔌 Testing Dynamic FileBox Plugin...") + +// Create a FileBox through dynamic library +local file = new FileBox("test_dynamic.txt") + +// Write some content +file.write("Hello from dynamic FileBox!\nこれは動的ライブラリ経由で書かれました。") +console.log("✅ Write successful") + +// Read back the content +local content = file.read() +console.log("📖 Read content: " + content) + +// Check if file exists +local exists = file.exists() +console.log("📁 File exists: " + exists.toString()) + +// Test with another file +local file2 = new FileBox("test_dynamic2.txt") +file2.write("Second test file") +console.log("✅ Second file created") + +console.log("🎉 Dynamic FileBox test completed!") \ No newline at end of file diff --git a/src/interpreter/expressions/calls.rs b/src/interpreter/expressions/calls.rs index 3d780b16..657fcfbe 100644 --- a/src/interpreter/expressions/calls.rs +++ b/src/interpreter/expressions/calls.rs @@ -255,6 +255,14 @@ impl NyashInterpreter { return self.execute_file_method(file_box, method, arguments); } + // FileBoxProxy method calls (動的ライブラリ版) + #[cfg(feature = "dynamic-file")] + { + if let Some(file_proxy) = obj_value.as_any().downcast_ref::() { + return self.execute_file_proxy_method(file_proxy, method, arguments); + } + } + // ResultBox method calls if let Some(result_box) = obj_value.as_any().downcast_ref::() { return self.execute_result_method(result_box, method, arguments); diff --git a/src/interpreter/methods/io_methods.rs b/src/interpreter/methods/io_methods.rs index 1f8aeaf5..32683735 100644 --- a/src/interpreter/methods/io_methods.rs +++ b/src/interpreter/methods/io_methods.rs @@ -10,11 +10,56 @@ use super::super::*; use crate::box_trait::{ResultBox, StringBox, NyashBox}; use crate::boxes::FileBox; +#[cfg(feature = "dynamic-file")] +use crate::interpreter::plugin_loader::FileBoxProxy; impl NyashInterpreter { /// FileBoxのメソッド呼び出しを実行 /// Handles file I/O operations including read, write, exists, delete, and copy pub(in crate::interpreter) fn execute_file_method(&mut self, file_box: &FileBox, method: &str, arguments: &[ASTNode]) + -> Result, RuntimeError> { + self.execute_file_method_static(file_box, method, arguments) + } + + /// FileBoxProxyのメソッド呼び出しを実行(動的ライブラリ版) + #[cfg(feature = "dynamic-file")] + pub(in crate::interpreter) fn execute_file_proxy_method(&mut self, file_box: &FileBoxProxy, 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()), + }); + } + file_box.read() + } + "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])?; + file_box.write(content) + } + "exists" => { + if !arguments.is_empty() { + return Err(RuntimeError::InvalidOperation { + message: format!("exists() expects 0 arguments, got {}", arguments.len()), + }); + } + file_box.exists() + } + _ => Err(RuntimeError::UndefinedMethod { + method: method.to_string(), + box_type: "FileBox".to_string(), + }), + } + } + + /// 静的FileBoxのメソッド実装 + fn execute_file_method_static(&mut self, file_box: &FileBox, method: &str, arguments: &[ASTNode]) -> Result, RuntimeError> { match method { "read" => { diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index dce29819..d4eb6e9f 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -40,10 +40,16 @@ mod math_methods; mod system_methods; mod web_methods; mod special_methods; +#[cfg(feature = "dynamic-file")] +mod plugin_loader; // Main interpreter implementation - will be moved from interpreter.rs pub use core::NyashInterpreter; +// Dynamic plugin support +#[cfg(feature = "dynamic-file")] +pub use plugin_loader::{PluginLoader, FileBoxProxy}; + /// 実行制御フロー #[derive(Debug)] diff --git a/src/interpreter/objects.rs b/src/interpreter/objects.rs index 99f49432..77bb76b2 100644 --- a/src/interpreter/objects.rs +++ b/src/interpreter/objects.rs @@ -96,9 +96,20 @@ impl NyashInterpreter { } let path_value = self.execute_expression(&arguments[0])?; if let Some(path_str) = path_value.as_any().downcast_ref::() { - let file_box = Box::new(FileBox::new(&path_str.value)) as Box; - // 🌍 革命的実装:Environment tracking廃止 - return Ok(file_box); + #[cfg(feature = "dynamic-file")] + { + // 動的ライブラリ経由でFileBoxを作成 + use crate::interpreter::plugin_loader::PluginLoader; + let file_box = PluginLoader::create_file_box(&path_str.value)?; + return Ok(file_box); + } + + #[cfg(not(feature = "dynamic-file"))] + { + // 静的リンク版 + let file_box = Box::new(FileBox::new(&path_str.value)) as Box; + return Ok(file_box); + } } else { return Err(RuntimeError::TypeError { message: "FileBox constructor requires string path argument".to_string(), diff --git a/src/interpreter/plugin_loader.rs b/src/interpreter/plugin_loader.rs new file mode 100644 index 00000000..35911ff5 --- /dev/null +++ b/src/interpreter/plugin_loader.rs @@ -0,0 +1,361 @@ +//! Dynamic Plugin Loader for Nyash +//! +//! Phase 9.75f: 動的ライブラリによるビルトインBox実装 + +use std::collections::HashMap; +use std::ffi::{c_char, c_void, CStr, CString}; +use std::os::raw::c_int; +use std::sync::RwLock; +use std::path::PathBuf; + +#[cfg(feature = "dynamic-file")] +use libloading::{Library, Symbol}; + +use crate::interpreter::RuntimeError; +use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase}; + +lazy_static::lazy_static! { + /// グローバルプラグインキャッシュ + static ref PLUGIN_CACHE: RwLock> = + RwLock::new(HashMap::new()); +} + +/// ロード済みプラグイン情報 +#[cfg(feature = "dynamic-file")] +struct LoadedPlugin { + library: Library, + info: PluginInfo, +} + +/// プラグイン情報 +#[derive(Clone)] +struct PluginInfo { + name: String, + version: u32, + api_version: u32, +} + +/// FileBoxプロキシ - 動的ライブラリのFileBoxをラップ +pub struct FileBoxProxy { + handle: *mut c_void, + path: String, + base: BoxBase, +} + +// FileBoxProxyは手動でSendとSyncを実装 +unsafe impl Send for FileBoxProxy {} +unsafe impl Sync for FileBoxProxy {} + +impl FileBoxProxy { + /// 新しいFileBoxProxyを作成 + pub fn new(handle: *mut c_void, path: String) -> Self { + FileBoxProxy { + handle, + path, + base: BoxBase::new(), + } + } + + /// ファイル読み取り + pub fn read(&self) -> Result, RuntimeError> { + #[cfg(feature = "dynamic-file")] + { + let cache = PLUGIN_CACHE.read().unwrap(); + if let Some(plugin) = cache.get("file") { + unsafe { + let read_fn: Symbol *mut c_char> = + plugin.library.get(b"nyash_file_read\0").map_err(|e| { + RuntimeError::InvalidOperation { + message: format!("Failed to get nyash_file_read: {}", e) + } + })?; + + let result_ptr = read_fn(self.handle); + if result_ptr.is_null() { + return Err(RuntimeError::InvalidOperation { + message: "Failed to read file".to_string() + }); + } + + let content = CStr::from_ptr(result_ptr).to_string_lossy().into_owned(); + + // 文字列を解放 + let free_fn: Symbol = + plugin.library.get(b"nyash_string_free\0").map_err(|e| { + RuntimeError::InvalidOperation { + message: format!("Failed to get nyash_string_free: {}", e) + } + })?; + free_fn(result_ptr); + + Ok(Box::new(StringBox::new(content))) + } + } else { + Err(RuntimeError::InvalidOperation { + message: "File plugin not loaded".to_string() + }) + } + } + + #[cfg(not(feature = "dynamic-file"))] + { + Err(RuntimeError::InvalidOperation { + message: "Dynamic file support not enabled".to_string() + }) + } + } + + /// ファイル書き込み + pub fn write(&self, content: Box) -> Result, RuntimeError> { + #[cfg(feature = "dynamic-file")] + { + let cache = PLUGIN_CACHE.read().unwrap(); + if let Some(plugin) = cache.get("file") { + let content_str = content.to_string_box().value; + let c_content = CString::new(content_str).map_err(|_| { + RuntimeError::InvalidOperation { + message: "Invalid content string".to_string() + } + })?; + + unsafe { + let write_fn: Symbol c_int> = + plugin.library.get(b"nyash_file_write\0").map_err(|e| { + RuntimeError::InvalidOperation { + message: format!("Failed to get nyash_file_write: {}", e) + } + })?; + + let result = write_fn(self.handle, c_content.as_ptr()); + if result == 0 { + return Err(RuntimeError::InvalidOperation { + message: "Failed to write file".to_string() + }); + } + + Ok(Box::new(StringBox::new("ok"))) + } + } else { + Err(RuntimeError::InvalidOperation { + message: "File plugin not loaded".to_string() + }) + } + } + + #[cfg(not(feature = "dynamic-file"))] + { + Err(RuntimeError::InvalidOperation { + message: "Dynamic file support not enabled".to_string() + }) + } + } + + /// ファイル存在確認 + pub fn exists(&self) -> Result, RuntimeError> { + Ok(Box::new(BoolBox::new(std::path::Path::new(&self.path).exists()))) + } +} + +impl Drop for FileBoxProxy { + fn drop(&mut self) { + #[cfg(feature = "dynamic-file")] + { + if !self.handle.is_null() { + let cache = PLUGIN_CACHE.read().unwrap(); + if let Some(plugin) = cache.get("file") { + unsafe { + if let Ok(free_fn) = plugin.library.get::>(b"nyash_file_free\0") { + free_fn(self.handle); + } + } + } + } + } + } +} + +impl BoxCore for FileBoxProxy { + fn box_id(&self) -> u64 { + self.base.id + } + + fn parent_type_id(&self) -> Option { + self.base.parent_type_id + } + + fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "FileBox({})", self.path) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + +impl NyashBox for FileBoxProxy { + fn type_name(&self) -> &'static str { + "FileBox" + } + + fn clone_box(&self) -> Box { + // FileBoxは再オープンで複製 + Box::new(VoidBox::new()) + } + + fn share_box(&self) -> Box { + self.clone_box() + } + + fn to_string_box(&self) -> StringBox { + StringBox::new(format!("FileBox({})", self.path)) + } + + fn equals(&self, other: &dyn NyashBox) -> BoolBox { + if let Some(other_file) = other.as_any().downcast_ref::() { + BoolBox::new(self.path == other_file.path) + } else { + BoolBox::new(false) + } + } +} + +impl std::fmt::Display for FileBoxProxy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.fmt_box(f) + } +} + +/// プラグインローダー公開API +pub struct PluginLoader; + +impl PluginLoader { + /// FileBoxプラグインをロード + #[cfg(feature = "dynamic-file")] + pub fn load_file_plugin() -> Result<(), RuntimeError> { + let mut cache = PLUGIN_CACHE.write().unwrap(); + + if cache.contains_key("file") { + return Ok(()); // 既にロード済み + } + + // プラグインパスを決定 + let lib_path = if cfg!(target_os = "windows") { + "./target/debug/nyash_file.dll" + } else if cfg!(target_os = "macos") { + "./target/debug/libnyash_file.dylib" + } else { + "./target/debug/libnyash_file.so" + }; + + // ライブラリをロード + unsafe { + let library = Library::new(lib_path).map_err(|e| { + RuntimeError::InvalidOperation { + message: format!("Failed to load file plugin: {}", e) + } + })?; + + // プラグイン初期化 + let init_fn: Symbol *const c_void> = + library.get(b"nyash_plugin_init\0").map_err(|e| { + RuntimeError::InvalidOperation { + message: format!("Failed to get plugin init: {}", e) + } + })?; + + let plugin_info_ptr = init_fn(); + if plugin_info_ptr.is_null() { + return Err(RuntimeError::InvalidOperation { + message: "Plugin initialization failed".to_string() + }); + } + + // マジックナンバーとバージョンチェック(簡略化) + let info = PluginInfo { + name: "file".to_string(), + version: 1, + api_version: 1, + }; + + cache.insert("file".to_string(), LoadedPlugin { + library, + info, + }); + } + + Ok(()) + } + + /// FileBoxを作成 + #[cfg(feature = "dynamic-file")] + pub fn create_file_box(path: &str) -> Result, RuntimeError> { + // プラグインがロードされているか確認 + Self::load_file_plugin()?; + + let cache = PLUGIN_CACHE.read().unwrap(); + if let Some(plugin) = cache.get("file") { + let c_path = CString::new(path).map_err(|_| { + RuntimeError::InvalidOperation { + message: "Invalid path string".to_string() + } + })?; + + unsafe { + let open_fn: Symbol *mut c_void> = + plugin.library.get(b"nyash_file_open\0").map_err(|e| { + RuntimeError::InvalidOperation { + message: format!("Failed to get nyash_file_open: {}", e) + } + })?; + + let handle = open_fn(c_path.as_ptr()); + if handle.is_null() { + return Err(RuntimeError::InvalidOperation { + message: format!("Failed to open file: {}", path) + }); + } + + Ok(Box::new(FileBoxProxy::new(handle, path.to_string()))) + } + } else { + Err(RuntimeError::InvalidOperation { + message: "File plugin not loaded".to_string() + }) + } + } + + /// FileBoxが存在するかチェック(静的メソッド) + #[cfg(feature = "dynamic-file")] + pub fn file_exists(path: &str) -> Result { + // プラグインがロードされているか確認 + Self::load_file_plugin()?; + + let cache = PLUGIN_CACHE.read().unwrap(); + if let Some(plugin) = cache.get("file") { + let c_path = CString::new(path).map_err(|_| { + RuntimeError::InvalidOperation { + message: "Invalid path string".to_string() + } + })?; + + unsafe { + let exists_fn: Symbol c_int> = + plugin.library.get(b"nyash_file_exists\0").map_err(|e| { + RuntimeError::InvalidOperation { + message: format!("Failed to get nyash_file_exists: {}", e) + } + })?; + + Ok(exists_fn(c_path.as_ptr()) != 0) + } + } else { + Err(RuntimeError::InvalidOperation { + message: "File plugin not loaded".to_string() + }) + } + } +} \ No newline at end of file