/*! * Unified Box Factory Architecture * * Phase 9.78: 統合BoxFactoryアーキテクチャ * すべてのBox生成(ビルトイン、ユーザー定義、プラグイン)を統一的に扱う * * Design principles: * - "Everything is Box" 哲学の実装レベルでの体現 * - birth/finiライフサイクルの明確な責務分離 * - 保守性と拡張性の劇的向上 */ use crate::box_trait::NyashBox; use std::collections::HashMap; use std::sync::{Arc, RwLock}; /// Factory Priority Policy for Box creation (Phase 15.5 "Everything is Plugin") /// /// Determines the order in which different Box factories are consulted /// during Box creation to solve the StringBox/IntegerBox plugin priority issue. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FactoryPolicy { /// Strict Plugin Priority: plugins > user > builtin /// ⚡ SOLVES THE CORE PROBLEM: Plugins have highest priority /// Use when plugins should completely replace builtins (Phase 15.5) StrictPluginFirst, /// Compatible Plugin Priority: plugins > builtin > user /// 🔧 Compatibility mode: Plugins first, but builtins before user-defined /// Use for gradual migration scenarios CompatPluginFirst, /// Legacy Builtin Priority: builtin > user > plugin (CURRENT DEFAULT) /// ⚠️ PROBLEMATIC: Plugins can never override builtins /// Only use for compatibility with existing setups BuiltinFirst, } /// Factory type classification for policy-based ordering #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FactoryType { /// Built-in factory (StringBox, IntegerBox, etc.) Builtin, /// User-defined Box factory User, /// Plugin-provided Box factory Plugin, } /// Runtime error types for Box operations #[derive(Debug, thiserror::Error)] pub enum RuntimeError { #[error("invalid operation: {message}")] InvalidOperation { message: String }, #[error("type error: {message}")] TypeError { message: String }, } /// Shared state for interpreter context (legacy compatibility) #[derive(Debug, Default, Clone)] pub struct SharedState; impl SharedState { pub fn new() -> Self { Self } } /// Unified interface for all Box creation pub trait BoxFactory: Send + Sync { /// Create a new Box instance with given arguments fn create_box( &self, name: &str, args: &[Box], ) -> Result, RuntimeError>; /// Check if this factory is currently available fn is_available(&self) -> bool { true } /// Get list of Box types this factory can create fn box_types(&self) -> Vec<&str>; /// Check if this factory supports birth/fini lifecycle fn supports_birth(&self) -> bool { true } /// Identify builtin factory to enforce reserved-name protections fn is_builtin_factory(&self) -> bool { false } /// Identify factory type for policy-based priority ordering fn factory_type(&self) -> FactoryType { if self.is_builtin_factory() { FactoryType::Builtin } else { FactoryType::Plugin // Default assumption for external factories } } } /// Registry that manages all BoxFactory implementations pub struct UnifiedBoxRegistry { /// Ordered list of factories with policy-based priority pub factories: Vec>, /// Quick lookup cache for performance type_cache: RwLock>, // maps type name to factory index /// Factory priority policy (Phase 15.5: Everything is Plugin) policy: FactoryPolicy, } impl UnifiedBoxRegistry { /// Create a new empty registry with default policy /// Phase 86: Default changed to StrictPluginFirst via with_env_policy() pub fn new() -> Self { Self::with_env_policy() } /// Create a new empty registry with specified policy pub fn with_policy(policy: FactoryPolicy) -> Self { Self { factories: Vec::new(), type_cache: RwLock::new(HashMap::new()), policy, } } /// Create registry with policy from environment variable (Phase 15.5 setup) pub fn with_env_policy() -> Self { let policy = match crate::config::env::box_factory_policy().as_deref() { Some("compat_plugin_first") => FactoryPolicy::CompatPluginFirst, Some("builtin_first") => FactoryPolicy::BuiltinFirst, Some("strict_plugin_first") | _ => FactoryPolicy::StrictPluginFirst, // Phase 15.5: Plugin First DEFAULT! }; eprintln!( "[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)", policy ); Self::with_policy(policy) } /// Get current factory policy pub fn get_policy(&self) -> FactoryPolicy { self.policy } /// Set factory policy and rebuild cache to reflect new priorities pub fn set_policy(&mut self, policy: FactoryPolicy) { if self.policy != policy { self.policy = policy; self.rebuild_cache(); } } /// Rebuild type cache based on current policy fn rebuild_cache(&mut self) { // Clear existing cache let mut cache = self.type_cache.write().unwrap(); cache.clear(); // Get factory priority order based on policy let factory_order = self.get_factory_order_by_policy(); // Re-register types with policy-based priority for &factory_index in factory_order.iter() { if let Some(factory) = self.factories.get(factory_index) { let types = factory.box_types(); // Phase 87: Reserved core types using CoreBoxId (型安全化) fn is_reserved_type(name: &str) -> bool { use crate::runtime::CoreBoxId; // Phase 15.5: 環境変数でプラグイン優先モード時は保護解除 if crate::config::env::use_plugin_builtins() { if let Some(types) = crate::config::env::plugin_override_types() { if types.iter().any(|t| t == name) { return false; // 予約型として扱わない } } } // Phase 87: CoreBoxId による型安全な判定 // core_required (6個) + 特殊型の一部を予約型として保護 CoreBoxId::from_name(name) .map(|id| { id.is_core_required() || matches!(id, CoreBoxId::Result | CoreBoxId::Method) }) .unwrap_or(false) } for type_name in types { // Enforce reserved names: only builtin factory may claim them if is_reserved_type(type_name) && !factory.is_builtin_factory() { eprintln!( "[UnifiedBoxRegistry] ❌ Rejecting registration of reserved type '{}' by non-builtin factory #{}", type_name, factory_index ); continue; } // Policy-based priority: first in order wins let entry = cache.entry(type_name.to_string()); use std::collections::hash_map::Entry; match entry { Entry::Occupied(existing) => { // Collision: type already claimed by higher-priority factory eprintln!("[UnifiedBoxRegistry] ⚠️ Policy '{}': type '{}' kept by higher priority factory #{}, ignoring factory #{}", format!("{:?}", self.policy), existing.key(), existing.get(), factory_index); } Entry::Vacant(v) => { v.insert(factory_index); } } } } } } /// Get factory indices ordered by current policy priority fn get_factory_order_by_policy(&self) -> Vec { let mut factory_indices: Vec = (0..self.factories.len()).collect(); // Sort by factory type according to policy factory_indices.sort_by_key(|&index| { if let Some(factory) = self.factories.get(index) { let factory_type = factory.factory_type(); match self.policy { FactoryPolicy::StrictPluginFirst => match factory_type { FactoryType::Plugin => 0, // Highest priority FactoryType::User => 1, // Medium priority FactoryType::Builtin => 2, // Lowest priority }, FactoryPolicy::CompatPluginFirst => match factory_type { FactoryType::Plugin => 0, // Highest priority FactoryType::Builtin => 1, // Medium priority FactoryType::User => 2, // Lowest priority }, FactoryPolicy::BuiltinFirst => match factory_type { FactoryType::Builtin => 0, // Highest priority (current default) FactoryType::User => 1, // Medium priority FactoryType::Plugin => 2, // Lowest priority }, } } else { 999 // Invalid factory index, put at end } }); factory_indices } /// Register a new factory (policy-aware) pub fn register(&mut self, factory: Arc) { // Simply add to the factory list self.factories.push(factory); // Rebuild cache to apply policy-based priority ordering // This ensures new factory is properly integrated with current policy self.rebuild_cache(); } /// Create a Box using the unified interface pub fn create_box( &self, name: &str, args: &[Box], ) -> Result, RuntimeError> { // Prefer plugin-builtins when enabled and provider is available in v2 registry // BUT: Skip if plugins are explicitly disabled let plugins_disabled = crate::config::env::disable_plugins(); if !plugins_disabled && crate::config::env::use_plugin_builtins() { use crate::runtime::{get_global_registry, BoxProvider}; // Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none) let allow: Vec = crate::config::env::plugin_override_types() .unwrap_or_default(); if allow.iter().any(|t| t == name) { let v2 = get_global_registry(); if let Some(provider) = v2.get_provider(name) { if let BoxProvider::Plugin(_lib) = provider { return v2.create_box(name, args).map_err(|e| { RuntimeError::InvalidOperation { message: format!("Plugin Box creation failed: {}", e), } }); } } } } // Check cache first let cache = self.type_cache.read().unwrap(); if let Some(&factory_index) = cache.get(name) { if let Some(factory) = self.factories.get(factory_index) { if factory.is_available() { return factory.create_box(name, args); } } } drop(cache); // Linear search through all factories with fallback support let mut last_error: Option = None; let factory_order = self.get_factory_order_by_policy(); for &factory_index in factory_order.iter() { if let Some(factory) = self.factories.get(factory_index) { if !factory.is_available() { continue; } // For factories that advertise types, check if they support this type let box_types = factory.box_types(); if !box_types.is_empty() && !box_types.contains(&name) { continue; } // Try to create the box if crate::config::env::debug_plugin() { eprintln!( "[UnifiedBoxRegistry] try factory#{} {:?} for {}", factory_index, factory.factory_type(), name ); } match factory.create_box(name, args) { Ok(boxed) => return Ok(boxed), Err(e) => { // FileBox special case: handle fallback based on mode if name == "FileBox" { if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e) { return fallback_result; } } // For other boxes or if FileBox fallback not applicable, continue last_error = Some(e); continue; } } } } // Final fallback: if v2 plugin registry has a provider for this name, try it once { let v2 = crate::runtime::get_global_registry(); if let Some(_prov) = v2.get_provider(name) { if let Ok(b) = v2.create_box(name, args) { return Ok(b); } } } // Return the last error if we have one, otherwise generic error if let Some(err) = last_error { Err(err) } else { Err(RuntimeError::InvalidOperation { message: format!("Unknown Box type: {}", name), }) } } /// Try FileBox fallback based on NYASH_FILEBOX_MODE /// Returns Some(result) if fallback is applicable, None if should continue trying other factories fn try_filebox_fallback( &self, name: &str, args: &[Box], original_error: &RuntimeError, ) -> Option, RuntimeError>> { use crate::runner::modes::common_util::provider_registry; let mode = provider_registry::read_filebox_mode_from_env(); match mode { provider_registry::FileBoxMode::PluginOnly => { // Fail-Fast: return the original error immediately eprintln!( "[FileBox] Plugin creation failed in plugin-only mode: {}", original_error ); Some(Err(RuntimeError::InvalidOperation { message: format!( "FileBox plugin creation failed (plugin-only mode): {}", original_error ), })) } provider_registry::FileBoxMode::Auto => { // Auto mode: try fallback to builtin/core-ro eprintln!( "[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}", original_error ); // Try builtin factory if available for factory in &self.factories { if factory.is_builtin_factory() && factory.box_types().contains(&name) { match factory.create_box(name, args) { Ok(boxed) => { eprintln!("[FileBox] Successfully created with builtin factory"); return Some(Ok(boxed)); } Err(e) => { eprintln!("[FileBox] Builtin factory also failed: {}", e); } } } } // If builtin failed, return None to continue with other factories None } provider_registry::FileBoxMode::CoreRo => { // Core-ro mode: try builtin factory eprintln!("[FileBox] Using core-ro mode, trying builtin factory"); for factory in &self.factories { if factory.is_builtin_factory() && factory.box_types().contains(&name) { match factory.create_box(name, args) { Ok(boxed) => return Some(Ok(boxed)), Err(e) => { return Some(Err(RuntimeError::InvalidOperation { message: format!("FileBox core-ro creation failed: {}", e), })); } } } } None } } } /// Check whether a type name is known to the registry pub fn has_type(&self, name: &str) -> bool { // Check cache first { let cache = self.type_cache.read().unwrap(); if let Some(&idx) = cache.get(name) { if let Some(factory) = self.factories.get(idx) { if factory.is_available() { return true; } } } } // Fallback: scan factories that can enumerate types for factory in &self.factories { if !factory.is_available() { continue; } let types = factory.box_types(); if !types.is_empty() && types.contains(&name) { return true; } } false } /// Get all available Box types pub fn available_types(&self) -> Vec { let mut types = Vec::new(); for factory in &self.factories { if factory.is_available() { for type_name in factory.box_types() { types.push(type_name.to_string()); } } } types.sort(); types.dedup(); types } } pub mod builtin; pub mod plugin; /// Re-export submodules #[cfg(feature = "interpreter-legacy")] pub mod user_defined; // Phase 15.5: Separated builtin implementations for easy deletion pub mod builtin_impls; #[cfg(test)] mod tests { use super::*; #[test] fn test_registry_creation() { let registry = UnifiedBoxRegistry::new(); assert_eq!(registry.available_types().len(), 0); } // Phase 86: BoxFactory Priority Tests #[test] fn test_default_policy_is_strict_plugin_first() { let prev = crate::config::env::box_factory_policy(); // Ensure NYASH_BOX_FACTORY_POLICY is not set crate::config::env::reset_box_factory_policy(); let registry = UnifiedBoxRegistry::new(); assert_eq!( registry.get_policy(), FactoryPolicy::StrictPluginFirst, "Default policy should be StrictPluginFirst" ); if let Some(v) = prev { crate::config::env::set_box_factory_policy(&v); } } #[test] fn test_env_policy_override() { let prev = crate::config::env::box_factory_policy(); // Test builtin_first override crate::config::env::set_box_factory_policy("builtin_first"); let registry = UnifiedBoxRegistry::with_env_policy(); assert_eq!(registry.get_policy(), FactoryPolicy::BuiltinFirst); // Test compat_plugin_first override crate::config::env::set_box_factory_policy("compat_plugin_first"); let registry = UnifiedBoxRegistry::with_env_policy(); assert_eq!(registry.get_policy(), FactoryPolicy::CompatPluginFirst); // Test strict_plugin_first explicit crate::config::env::set_box_factory_policy("strict_plugin_first"); let registry = UnifiedBoxRegistry::with_env_policy(); assert_eq!(registry.get_policy(), FactoryPolicy::StrictPluginFirst); // Cleanup if let Some(v) = prev { crate::config::env::set_box_factory_policy(&v); } else { crate::config::env::reset_box_factory_policy(); } } #[test] fn test_reserved_type_protection() { // Ensure env vars are cleared // Note: can't clear env vars directly; tests rely on default behavior // Create a mock non-builtin factory that claims a reserved type struct MockPluginFactory; impl BoxFactory for MockPluginFactory { fn create_box( &self, name: &str, _args: &[Box], ) -> Result, RuntimeError> { // This should never be called for StringBox since it's rejected Err(RuntimeError::InvalidOperation { message: format!("Mock factory attempted to create: {}", name), }) } fn box_types(&self) -> Vec<&str> { vec!["StringBox", "CustomBox"] // Claims a reserved type } fn is_builtin_factory(&self) -> bool { false // Non-builtin } fn factory_type(&self) -> FactoryType { FactoryType::Plugin } } let mut registry = UnifiedBoxRegistry::new(); registry.register(Arc::new(MockPluginFactory)); // Test that create_box fails for StringBox (not registered in cache) let result = registry.create_box("StringBox", &[]); assert!( result.is_err(), "StringBox creation should fail when only non-builtin factory provides it" ); // Verify the error message indicates it's unknown (not in cache) if let Err(e) = result { let err_msg = format!("{}", e); assert!( err_msg.contains("Unknown Box type") || err_msg.contains("Mock factory"), "Error message should indicate StringBox is not properly registered: {}", err_msg ); } } #[test] fn test_plugin_override_with_env() { // This test verifies that NYASH_USE_PLUGIN_BUILTINS or // NYASH_PLUGIN_OVERRIDE_TYPES allows plugins to override reserved types // Create a mock plugin factory struct MockPluginFactory; impl BoxFactory for MockPluginFactory { fn create_box( &self, name: &str, _args: &[Box], ) -> Result, RuntimeError> { if name == "StringBox" { // Return a mock box for testing Err(RuntimeError::InvalidOperation { message: "Mock plugin StringBox".to_string(), }) } else { Err(RuntimeError::InvalidOperation { message: "Unknown".to_string(), }) } } fn box_types(&self) -> Vec<&str> { vec!["StringBox"] } fn is_builtin_factory(&self) -> bool { false } fn factory_type(&self) -> FactoryType { FactoryType::Plugin } } // Test with NYASH_PLUGIN_OVERRIDE_TYPES crate::config::env::set_box_factory_policy("strict_plugin_first"); // ensure plugin first std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", "StringBox"); let mut registry = UnifiedBoxRegistry::new(); registry.register(Arc::new(MockPluginFactory)); // With override enabled, StringBox should not be rejected // (Note: has_type will be false because create_box fails, but registration shouldn't be rejected) std::env::remove_var("NYASH_PLUGIN_OVERRIDE_TYPES"); } #[test] fn test_non_reserved_plugin_priority() { // Test that non-reserved types (like FileBox) can be overridden by plugins struct MockBuiltinFactory; impl BoxFactory for MockBuiltinFactory { fn create_box( &self, _name: &str, _args: &[Box], ) -> Result, RuntimeError> { Err(RuntimeError::InvalidOperation { message: "Builtin FileBox".to_string(), }) } fn box_types(&self) -> Vec<&str> { vec!["FileBox"] } fn is_builtin_factory(&self) -> bool { true } fn factory_type(&self) -> FactoryType { FactoryType::Builtin } } struct MockPluginFactory; impl BoxFactory for MockPluginFactory { fn create_box( &self, _name: &str, _args: &[Box], ) -> Result, RuntimeError> { Err(RuntimeError::InvalidOperation { message: "Plugin FileBox".to_string(), }) } fn box_types(&self) -> Vec<&str> { vec!["FileBox"] } fn is_builtin_factory(&self) -> bool { false } fn factory_type(&self) -> FactoryType { FactoryType::Plugin } } let mut registry = UnifiedBoxRegistry::new(); // Register builtin first, then plugin registry.register(Arc::new(MockBuiltinFactory)); registry.register(Arc::new(MockPluginFactory)); // With StrictPluginFirst policy, plugin should have priority // Both fail, but the error message tells us which was tried first let result = registry.create_box("FileBox", &[]); assert!(result.is_err()); // The error should be from plugin (tried first) or builtin (fallback) // This test just verifies the mechanism works } }