feat(9.75f-1): Implement plugin loader and interpreter integration
- Add plugin_loader.rs with FileBoxProxy implementation
- Integrate dynamic FileBox into interpreter (execute_new, method calls)
- Add feature flag 'dynamic-file' support throughout
- Create test program test_dynamic_filebox.nyash
- Plugin builds in 2.86s (vs main build 2+ minutes\!)
Build time improvement confirmed:
- Plugin-only build: 2.86s ✨
- Main build: 2+ minutes (timeout)
Next: Complete testing once main build finishes
This commit is contained in:
26
local_tests/test_dynamic_filebox.nyash
Normal file
26
local_tests/test_dynamic_filebox.nyash
Normal file
@ -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!")
|
||||
@ -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::<crate::interpreter::plugin_loader::FileBoxProxy>() {
|
||||
return self.execute_file_proxy_method(file_proxy, method, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
// ResultBox method calls
|
||||
if let Some(result_box) = obj_value.as_any().downcast_ref::<crate::box_trait::ResultBox>() {
|
||||
return self.execute_result_method(result_box, method, arguments);
|
||||
|
||||
@ -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<Box<dyn NyashBox>, 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<Box<dyn NyashBox>, 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<Box<dyn NyashBox>, RuntimeError> {
|
||||
match method {
|
||||
"read" => {
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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::<StringBox>() {
|
||||
let file_box = Box::new(FileBox::new(&path_str.value)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装: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<dyn NyashBox>;
|
||||
return Ok(file_box);
|
||||
}
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "FileBox constructor requires string path argument".to_string(),
|
||||
|
||||
361
src/interpreter/plugin_loader.rs
Normal file
361
src/interpreter/plugin_loader.rs
Normal file
@ -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<HashMap<String, LoadedPlugin>> =
|
||||
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<Box<dyn NyashBox>, RuntimeError> {
|
||||
#[cfg(feature = "dynamic-file")]
|
||||
{
|
||||
let cache = PLUGIN_CACHE.read().unwrap();
|
||||
if let Some(plugin) = cache.get("file") {
|
||||
unsafe {
|
||||
let read_fn: Symbol<unsafe extern "C" fn(*mut c_void) -> *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<unsafe extern "C" fn(*mut c_char)> =
|
||||
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<dyn NyashBox>) -> Result<Box<dyn NyashBox>, 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<unsafe extern "C" fn(*mut c_void, *const c_char) -> 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<Box<dyn NyashBox>, 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::<Symbol<unsafe extern "C" fn(*mut c_void)>>(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<std::any::TypeId> {
|
||||
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<dyn NyashBox> {
|
||||
// FileBoxは再オープンで複製
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
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::<FileBoxProxy>() {
|
||||
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<unsafe extern "C" fn() -> *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<Box<dyn NyashBox>, 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<unsafe extern "C" fn(*const c_char) -> *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<bool, 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 exists_fn: Symbol<unsafe extern "C" fn(*const c_char) -> 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user