diff --git a/src/boxes/file/mod.rs b/src/boxes/file/mod.rs index d0114951..34d15e56 100644 --- a/src/boxes/file/mod.rs +++ b/src/boxes/file/mod.rs @@ -46,12 +46,28 @@ impl Clone for FileBox { } impl FileBox { + /// Create new FileBox (Fail-Fast if provider not initialized) + /// + /// Phase 109: This method panics if FileBox provider is not initialized. + /// Use `try_new()` for graceful error handling. pub fn new() -> Self { - FileBox { - provider: provider_lock::get_filebox_provider().cloned(), + Self::try_new().expect("FileBox provider not initialized") + } + + /// Try to create new FileBox (Result-based) + /// + /// Phase 109: Returns Err if FileBox provider is not initialized. + /// This is the recommended API for graceful error handling. + pub fn try_new() -> Result { + let provider = provider_lock::get_filebox_provider() + .ok_or("FileBox provider not initialized")? + .clone(); + + Ok(FileBox { + provider: Some(provider), path: String::new(), base: BoxBase::new(), - } + }) } /// Create FileBox with explicit provider (for builtin fallback) @@ -140,42 +156,6 @@ impl FileBox { use std::path::Path; Box::new(BoolBox::new(Path::new(&self.path).exists())) } - - /// ファイルを削除 - pub fn delete(&self) -> Box { - let caps = self - .provider - .as_ref() - .map(|p| p.caps()) - .or_else(|| provider_lock::get_filebox_caps()) - .unwrap_or_else(|| provider::FileCaps::read_only()); - if !caps.write { - return Box::new(StringBox::new( - "Error: delete unsupported by provider (read-only)", - )); - } - Box::new(StringBox::new( - "Error: delete supported but not implemented in this build", - )) - } - - /// ファイルをコピー - pub fn copy(&self, _dest: &str) -> Box { - let caps = self - .provider - .as_ref() - .map(|p| p.caps()) - .or_else(|| provider_lock::get_filebox_caps()) - .unwrap_or_else(|| provider::FileCaps::read_only()); - if !caps.write { - return Box::new(StringBox::new( - "Error: copy unsupported by provider (read-only)", - )); - } - Box::new(StringBox::new( - "Error: copy supported but not implemented in this build", - )) - } } impl BoxCore for FileBox { diff --git a/src/grammar/generated.rs b/src/grammar/generated.rs index 25bb6e51..d94cb9c1 100644 --- a/src/grammar/generated.rs +++ b/src/grammar/generated.rs @@ -36,15 +36,37 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[ ]; pub fn lookup_keyword(word: &str) -> Option<&'static str> { for (k, t) in KEYWORDS { - if *k == word { - return Some(*t); - } + if *k == word { return Some(*t); } } None } pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ - "box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait", - "include", "local", "outbox", "try", "throw", "using", "from", + "box", + "global", + "function", + "static", + "if", + "loop", + "break", + "return", + "print", + "nowait", + "include", + "local", + "outbox", + "try", + "throw", + "using", + "from", ]; -pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"]; +pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[ + "add", + "sub", + "mul", + "div", + "and", + "or", + "eq", + "ne", +]; \ No newline at end of file diff --git a/src/runtime/core_box_ids.rs b/src/runtime/core_box_ids.rs index 52ea78ab..aef001ef 100644 --- a/src/runtime/core_box_ids.rs +++ b/src/runtime/core_box_ids.rs @@ -129,9 +129,6 @@ impl CoreBoxId { /// /// **Future expansion**: TestMock/Sandbox/ReadOnly/Embedded profiles will extend this logic pub fn is_required_in(&self, profile: &RuntimeProfile) -> bool { - use CoreBoxId::*; - let core_required = matches!(self, String | Integer | Bool | Array | Map | Console); - match profile { RuntimeProfile::Default => { // Phase 106: FileBox is required in Default profile @@ -139,7 +136,8 @@ impl CoreBoxId { } RuntimeProfile::NoFs => { // Phase 109: FileBox is optional in NoFs profile - core_required + // In NoFs profile, only non-FileBox core required boxes + self.is_core_required() && *self != Self::File } } } diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index bcf77aa0..7be0b5ce 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -106,15 +106,15 @@ macro_rules! console_println { /// Phase 95: global に登録して get_core_plugin_host() でアクセス可能に /// Phase 109: RuntimeProfile に基づく条件付き初期化 /// -/// # Responsibility Separation (Phase 109 Modification 1) +/// # Responsibility Separation (Phase 109) /// /// - **initialize_runtime**: 環境変数から profile を読む(唯一の env reader) -/// - **PluginHost**: profile を引数として受け取る(env に依存しない) +/// - **PluginHost**: profile を引数として受け取り、provider 初期化を実行(initialization hub) /// /// # Profile behavior /// /// - **Default**: FileBox provider 必須(Fail-Fast)、全 core services 有効 -/// - **NoFs**: FileBox provider optional(disabled stub)、core services のみ有効 +/// - **NoFs**: FileBox provider optional(NoFsFileIo stub)、core services のみ有効 pub fn initialize_runtime(ring0: std::sync::Arc) -> Result<(), CoreInitError> { use crate::box_factory::UnifiedBoxRegistry; use crate::box_factory::builtin::BuiltinBoxFactory; @@ -127,22 +127,7 @@ pub fn initialize_runtime(ring0: std::sync::Arc) -> Result<(), Cor // Phase 94: BuiltinBoxFactory を登録して core_required Boxes を提供 registry.register(std::sync::Arc::new(BuiltinBoxFactory::new())); - // Phase 109: Profile-aware FileBox provider initialization - // Note: This is done BEFORE PluginHost initialization to allow plugin override - match profile { - RuntimeProfile::Default => { - // Default profile: FileBox provider will be auto-registered in PluginHost - // (no action needed here, kept for documentation) - } - RuntimeProfile::NoFs => { - // NoFs profile: Register NoFsFileIo stub - use crate::runtime::provider_lock; - let _ = provider_lock::init_filebox_provider_for_profile(&ring0, &profile); - // Ignore error - PluginHost will handle missing provider gracefully - } - } - - // Phase 109: Pass profile to PluginHost (env-independent) + // Phase 109: PluginHost acts as initialization hub (handles FileBox provider) let plugin_host = plugin_host::PluginHost::with_core_from_registry_optional( ring0, ®istry, diff --git a/src/runtime/plugin_host.rs b/src/runtime/plugin_host.rs index 3fed8558..ad736841 100644 --- a/src/runtime/plugin_host.rs +++ b/src/runtime/plugin_host.rs @@ -27,18 +27,6 @@ pub struct CoreServicesConfig { } impl CoreServicesConfig { - /// 環境変数から設定を読み込み - pub fn from_env() -> Self { - Self { - string_enabled: std::env::var("NYASH_CORE_DISABLE_STRING").is_err(), - integer_enabled: std::env::var("NYASH_CORE_DISABLE_INTEGER").is_err(), - bool_enabled: std::env::var("NYASH_CORE_DISABLE_BOOL").is_err(), - array_enabled: std::env::var("NYASH_CORE_DISABLE_ARRAY").is_err(), - map_enabled: std::env::var("NYASH_CORE_DISABLE_MAP").is_err(), - console_enabled: std::env::var("NYASH_CORE_DISABLE_CONSOLE").is_err(), - } - } - /// すべてのサービスを有効化(デフォルト) pub fn all_enabled() -> Self { Self { @@ -73,22 +61,11 @@ pub struct PluginDescriptor { } /// Nyash Plugin の trait +/// +/// Phase 109: PluginRegistry skeleton removed (was Phase 92 placeholder) pub trait NyashPlugin: Send + Sync { fn descriptor(&self) -> PluginDescriptor; - fn register(&self, host: &mut PluginRegistry); -} - -/// Plugin 登録レジストリ(skeleton のみ、Phase 92 で実装) -pub struct PluginRegistry { - _placeholder: (), -} - -impl PluginRegistry { - pub fn new() -> Self { - Self { - _placeholder: (), - } - } + // Note: register() method signature will be redesigned when actual plugin system is implemented } use super::core_services::CoreServices; @@ -137,14 +114,6 @@ pub struct PluginHost { } impl PluginHost { - /// Phase 91: skeleton のみ(core は未実装) - /// Phase 92 以降で from_registry() を実装予定 - #[allow(dead_code)] - pub fn new_skeleton(ring0: Arc) -> Self { - // ダミー実装(Phase 92 で削除) - unimplemented!("Phase 92 で from_registry() 実装後に削除") - } - /// Phase 103/109: Optional CoreServices initialization with RuntimeProfile support /// /// Allows selective initialization based on CoreServicesConfig and RuntimeProfile. @@ -177,28 +146,28 @@ impl PluginHost { } } - // Phase 109: Profile-aware FileBox provider check + // Phase 109: FileBox provider initialization hub (Responsibility: PluginHost) + // This is the single source of truth for FileBox provider initialization match profile { RuntimeProfile::Default => { - // Phase 107: Auto-register default FileBox provider (Ring0.FsApi-based) - // This happens before the Phase 106 check, so plugins can still override. - // If a plugin has already registered, this returns Err but we continue. - if CoreBoxId::File.is_required_in(profile) { - match provider_lock::init_default_filebox_provider(&ring0) { + // Phase 109: Default profile requires FileBox provider + if provider_lock::get_filebox_provider().is_none() { + // Phase 107: Auto-register Ring0FsFileIo as default provider + use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; + let provider = Arc::new(Ring0FsFileIo::new(ring0.clone())); + + match provider_lock::set_filebox_provider(provider) { Ok(()) => { - // Default provider registered successfully - ring0.log.debug("[Phase 107] Ring0FsFileIo registered as default FileBox provider"); + ring0.log.debug("[Phase 109] Ring0FsFileIo registered as default FileBox provider"); } - Err(msg) => { + Err(_) => { // Plugin provider already registered - this is OK (plugin priority) - ring0.log.debug(&format!("[Phase 107] {}", msg)); + ring0.log.debug("[Phase 109] Plugin FileBox provider already registered (plugin priority)"); } } } - // Phase 106/109: FileBox provider チェック (Default profile) - // CoreBoxId がFileを必須と判定している場合、provider が登録されていることを確認 - // Phase 107: With auto-registration above, this check should always pass + // Phase 109: Verify FileBox provider exists (Fail-Fast) if CoreBoxId::File.is_required_in(profile) { if provider_lock::get_filebox_provider().is_none() { return Err(CoreInitError::MissingService { @@ -209,9 +178,14 @@ impl PluginHost { } } RuntimeProfile::NoFs => { - // Phase 109: FileBox provider is optional in NoFs profile - // We skip provider initialization and checks entirely - ring0.log.debug("[Phase 109] NoFs profile: FileBox provider skipped"); + // Phase 109: NoFs profile uses NoFsFileIo stub + if provider_lock::get_filebox_provider().is_none() { + use crate::providers::ring1::file::nofs_fileio::NoFsFileIo; + let provider = Arc::new(NoFsFileIo); + + let _ = provider_lock::set_filebox_provider(provider); + ring0.log.debug("[Phase 109] NoFsFileIo registered for NoFs profile"); + } } } @@ -344,11 +318,6 @@ mod tests { assert_eq!(desc.capabilities.len(), 1); } - #[test] - fn test_plugin_registry_creation() { - let _registry = PluginRegistry::new(); - // panic しないことを確認 - } #[test] fn test_core_services_all_fields() { @@ -407,9 +376,6 @@ mod tests { capabilities: vec![], } } - fn register(&self, _host: &mut PluginRegistry) { - // ダミー実装 - } } #[test] @@ -536,12 +502,4 @@ mod optional_core_tests { assert!(config.console_enabled, "console must remain enabled"); } - #[test] - fn test_core_services_config_from_env() { - // Test that from_env() reads environment variables correctly - // (This requires manual env setup in real tests) - let config = CoreServicesConfig::from_env(); - // If no env vars are set, all should be enabled (is_err() returns true) - assert!(config.string_enabled || !config.string_enabled, "config should be valid"); - } } diff --git a/src/runtime/provider_lock.rs b/src/runtime/provider_lock.rs index 2ee48ac6..3455ec71 100644 --- a/src/runtime/provider_lock.rs +++ b/src/runtime/provider_lock.rs @@ -67,65 +67,3 @@ pub fn get_filebox_provider() -> Option<&'static Arc> { pub fn get_filebox_caps() -> Option { get_filebox_provider().map(|p| p.caps()) } - -/// Phase 107: Initialize default FileBox provider using Ring0.FsApi -/// -/// This helper registers Ring0FsFileIo as the default FileBox provider. -/// It should be called during runtime initialization, after CoreServices setup. -/// -/// # Returns -/// -/// - `Ok(())`: Default provider registered successfully -/// - `Err(msg)`: Provider already registered (plugin took precedence) -/// -/// # Design -/// -/// Plugin providers have priority over the default. If a plugin has already -/// registered a FileBox provider, this function returns Err but the system -/// continues normally (plugin priority is honored). -pub fn init_default_filebox_provider( - ring0: &Arc -) -> Result<(), String> { - use crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo; - - let provider = Arc::new(Ring0FsFileIo::new(ring0.clone())); - set_filebox_provider(provider) - .map_err(|_| "Plugin FileBox provider already registered".to_string()) -} - -/// Phase 109: Initialize FileBox provider based on RuntimeProfile -/// -/// This helper registers the appropriate FileBox provider for the given profile: -/// - Default: Ring0FsFileIo (Ring0.FsApi-based, read/write support) -/// - NoFs: NoFsFileIo (stub that returns Unsupported errors) -/// -/// # Returns -/// -/// - `Ok(())`: Provider registered successfully -/// - `Err(msg)`: Provider already registered (plugin took precedence) -/// -/// # Design (Phase 109) -/// -/// This function is called during initialize_runtime() and respects the profile: -/// - Default profile: Registers Ring0FsFileIo for full filesystem access -/// - NoFs profile: Registers NoFsFileIo stub (all operations return Unsupported) -pub fn init_filebox_provider_for_profile( - ring0: &Arc, - profile: &crate::runtime::RuntimeProfile, -) -> Result<(), String> { - use crate::runtime::RuntimeProfile; - - match profile { - RuntimeProfile::Default => { - // Phase 107: Standard profile uses Ring0FsFileIo - init_default_filebox_provider(ring0) - } - RuntimeProfile::NoFs => { - // Phase 109: NoFs profile uses NoFsFileIo stub - use crate::providers::ring1::file::nofs_fileio::NoFsFileIo; - let provider = Arc::new(NoFsFileIo); - set_filebox_provider(provider) - .map_err(|_| "FileBox provider already set (unexpected in NoFs profile)".to_string()) - } - } -} diff --git a/src/runtime/runtime_profile.rs b/src/runtime/runtime_profile.rs index 74579afd..f5652a4d 100644 --- a/src/runtime/runtime_profile.rs +++ b/src/runtime/runtime_profile.rs @@ -2,6 +2,44 @@ //! //! Controls conditional core service initialization based on runtime profile. //! Supports Default (selfhost/standard) and NoFs (minimal runtime without filesystem). +//! +//! # Profile Variants (Current and Future) +//! +//! ## Current Profiles +//! +//! - **Default**: Standard runtime (selfhost/default) +//! - All core services enabled +//! - FileBox provider required (Fail-Fast) +//! - Full filesystem access via Ring0.FsApi +//! +//! - **NoFs**: Minimal runtime without FileSystem +//! - Core services only (String/Integer/Bool/Array/Map/Console) +//! - FileBox provider optional (NoFsFileIo stub) +//! - Suitable for sandboxed/embedded environments +//! +//! ## Future Expansion Plans +//! +//! The following profiles are planned for future implementation: +//! +//! - **TestMock**: Test-only profile +//! - All boxes return mocks for testing +//! - Predictable behavior for unit tests +//! - No side effects (no file I/O, no network) +//! +//! - **Sandbox**: Isolated filesystem +//! - FileBox limited to designated sandbox directory +//! - No external I/O (network disabled) +//! - Memory and resource limits enforced +//! +//! - **ReadOnly**: Read-only mode +//! - FileBox.read() enabled +//! - FileBox.write() denied at capability level +//! - Suitable for immutable environments +//! +//! - **Embedded**: Embedded profile +//! - Memory limits enforced +//! - Console output optional (may be disabled) +//! - Reduced feature set for resource-constrained devices /// Phase 109: RuntimeProfile /// @@ -71,4 +109,12 @@ mod tests { assert_eq!(RuntimeProfile::Default.name(), "Default"); assert_eq!(RuntimeProfile::NoFs.name(), "NoFs"); } + + #[test] + fn test_runtime_profile_from_env_unknown_defaults_to_default() { + // Unknown profile env var → Default に fallback + std::env::set_var("NYASH_RUNTIME_PROFILE", "unknown-profile"); + assert_eq!(RuntimeProfile::from_env(), RuntimeProfile::Default); + std::env::remove_var("NYASH_RUNTIME_PROFILE"); + } }