diff --git a/AGENTS.md b/AGENTS.md index b18a0a16..982f9c2c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -297,8 +297,9 @@ fn check_layer_boundary() { - ランナー: `src/runner/modes/cranelift.rs`, `--jit-direct` は `src/runner/mod.rs` - 進行中の論点と手順は `CURRENT_TASK.md` を参照してね(最新のデバッグ方針・フラグが載ってるよ)。 -**PyVM 主経路(Phase‑15 方針)** -- 主経路: Python/llvmlite + PyVM を標準の実行/検証経路として扱うよ。Rust VM/JIT は補助(保守/比較/プラグイン検証)。 +**PyVM ライン(Phase‑15 歴史メモ/現在は撤退済み)** +> 注: 2025-12 現在、PyVM 実行経路は完全撤退中だよ。以下は Phase‑15 当時の方針と運用メモで、今は「歴史情報」としてだけ残しているよ。日常の開発・CI では Rust VM / LLVM ラインだけを使ってね。 +- 当時の主経路: Python/llvmlite + PyVM を標準の実行/検証経路として扱うよ。Rust VM/JIT は補助(保守/比較/プラグイン検証)。 - 使い分け: - PyVM(推奨・日常確認): `NYASH_VM_USE_PY=1 ./target/release/nyash --backend vm apps/APP/main.hako` - llvmlite ハーネス: `NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash --backend llvm apps/APP/main.hako` @@ -356,8 +357,9 @@ Notes - `NYASH_NY_COMPILER_CHILD_ARGS` → スペース区切りで子にそのまま渡す - 子側(apps/selfhost-compiler/compiler.hako)は `--read-tmp` を受理して `tmp/ny_parser_input.ny` を読む(plugins 必要)。 -## PyVM Scope & Policy(Stage‑2 開発用の範囲) -- 目的: PyVM は「開発用の参照実行器」だよ。JSON v0 → MIR 実行の意味論確認と llvmlite とのパリティ監視に使う(プロダクション最適化はしない)。 +## PyVM Scope & Policy(Stage‑2 開発用の範囲・歴史メモ) +> 注: このセクションも Phase‑15〜17 期の設計メモだよ。PyVM ラインは現在は撤退済みで、実行確認・CI は Rust VM / LLVM のみを対象にしているよ。 +- 目的: (当時)PyVM は「開発用の参照実行器」だよ。JSON v0 → MIR 実行の意味論確認と llvmlite とのパリティ監視に使う(プロダクション最適化はしない)。 - 必須命令: `const/binop/compare/branch/jump/ret/phi`、`call/externcall/boxcall`(最小)。 - Box/メソッド(最小実装): - ConsoleBox: `print/println/log` @@ -376,8 +378,9 @@ Notes - Bridge 短絡(RHS スキップ): `tools/ny_stage2_shortcircuit_smoke.sh` - CI: `.github/workflows/pyvm-smoke.yml` を常時緑に維持。LLVM18 がある環境では `tools/parity.sh --lhs pyvm --rhs llvmlite` を任意ジョブで回す。 -## Interpreter vs PyVM(実行経路の役割と優先度) -- 優先経路: PyVM(Python)を"意味論リファレンス実行器"として採用。日常の機能確認・CI の軽量ゲート・llvmlite とのパリティ監視を PyVM で行う。 +## Interpreter vs PyVM(実行経路の役割と優先度・歴史メモ) +> 注: ここで言う「優先経路: PyVM」は Phase‑15 期のものだよ。現在は PyVM ラインは撤退済みで、Rust VM / LLVM を優先経路として扱うよ。 +- (当時の方針)優先経路: PyVM(Python)を"意味論リファレンス実行器"として採用。日常の機能確認・CI の軽量ゲート・llvmlite とのパリティ監視を PyVM で行う。 - 補助経路: Rust の MIR Interpreter は純Rust単独で回る簡易器として維持。拡張はしない(BoxCall 等の未対応は既知)。Python が使えない環境での簡易再現や Pipe ブリッジの補助に限定。 - Bridge(--ny-parser-pipe): 既定は Rust MIR Interpreter を使用。副作用なしの短絡など、実装範囲内を確認。副作用を含む実行検証は PyVM スモーク側で担保。 - 開発の原則: 仕様差が出た場合、llvmlite に合わせて PyVM を優先調整。Rust Interpreter は保守維持(安全修正のみ)。 @@ -386,8 +389,9 @@ Notes - Phase‑15 中は Rust VM/JIT への新規機能追加を最小化し、Python(llvmlite/PyVM)側での実装・検証を優先する。 - Runner/Bridge は必要最小の配線のみ(子プロセスタイムアウト・静音・フォールバック)。意味論の追加はまず PyVM/llvmlite に実装し、必要時のみ Rust 側へ反映。 -## Self‑Hosting への移行(PyVM → Nyash)ロードマップ(将来) -- 目標: PyVM の最小実行器を Nyash スクリプトへ段階移植し、自己ホスト中も Python 依存を徐々に縮小する。 +## Self‑Hosting への移行(PyVM → Nyash)ロードマップ(歴史メモ) +> 注: このロードマップは「PyVM からの段階移行」を前提にした初期案だよ。現在は PyVM ライン自体が撤退しており、Rust VM / LLVM / Nyash 自己ホストの 3 本を前提に設計を進めているよ。 +- 目標: (当時の計画)PyVM の最小実行器を Nyash スクリプトへ段階移植し、自己ホスト中も Python 依存を徐々に縮小する。 - ステップ(小粒度): 1) Nyash で MIR(JSON) ローダ(ファイル→構造体)を実装(最小 op セット)。 2) const/binop/compare/branch/jump/ret/phi を Nyash で実装し、既存 PyVM スモークを通過。 diff --git a/docs/development/current/main/core_boxes_design.md b/docs/development/current/main/core_boxes_design.md index c32e5f6c..1fe77232 100644 --- a/docs/development/current/main/core_boxes_design.md +++ b/docs/development/current/main/core_boxes_design.md @@ -226,13 +226,43 @@ pub struct Ring0Context { pub io: Box, /* ... */ } | SSOT | 分散 | 1ファイル | 保守性向上 | | IDE支援 | なし | 補完可能 | 開発体験向上 | -### 5.3 Phase 85 との関係 +### 5.3 Phase 85 との関係(FileBox 再分類) -Phase 85 の調査結果を完全反映: +Phase 85 の時点では、次の 3 区分で Box を分類していた: - **core_required (6個)**: StringBox, IntegerBox, BoolBox, ArrayBox, MapBox, ConsoleBox - **core_optional (9個)**: FloatBox, NullBox, FileBox, PathBox, RegexBox, MathBox, TimeBox, JsonBox, TomlBox - **特殊型 (4個)**: FunctionBox, ResultBox, MethodBox, MissingBox +その後、Ring0/Ring1-Core の整理と selfhost ラインの安定化を進める中で、 +FileBox は selfhost/通常ランタイムでは事実上必須(ログ・ツール・ハコチェックなどで常用) +であることが明確になったため、「core_required 相当」として扱うよう設計を更新した。 + +現行の分類は次の通り: +- **core_required (7個)**: StringBox, IntegerBox, BoolBox, ArrayBox, MapBox, ConsoleBox, FileBox +- **core_optional (8個)**: FloatBox, NullBox, PathBox, RegexBox, MathBox, TimeBox, JsonBox, TomlBox +- **特殊型 (4個)**: FunctionBox, ResultBox, MethodBox, MissingBox + +最終的なソース・オブ・トゥルースは `src/runtime/core_box_ids.rs` の `CoreBoxId::is_core_required()` / +`CoreBoxId::category()` であり、このドキュメントはその意図を補足する設計メモとして位置づけている。 + +## Phase 106: 設計統一(案B) + +### 責務分離原則 + +- **CoreBoxId**: 「必須かどうか」の判定(is_core_required() / category()) + - selfhost/default では File が必須 + - 将来 minimal/no-fs プロファイルでは optional に変更可能 +- **provider_lock**: 「FileBox provider を登録・読む」のみ(シンプルなロック機構) +- **PluginHost**: startup 時に CoreBoxId.is_core_required() で provider をチェック + - 未登録なら CoreInitError::MissingService で fail-fast + +### Ring0.FsApi との関係(Phase 107 延期) + +Ring0.FsApi(write 能力あり)と FileIo trait(read-only)の統合は、 +Phase 107+ で実施予定。現在は概念を分離したまま。 + +(理由: Phase 106 は provider_lock 整理に専念し、FsApi 統合は別 phase で) + ### 5.4 今後の拡張 新しいメソッド追加は `src/runtime/core_box_ids.rs` の編集のみで完結: diff --git a/src/boxes/file/mod.rs b/src/boxes/file/mod.rs index 35326451..02e91c4b 100644 --- a/src/boxes/file/mod.rs +++ b/src/boxes/file/mod.rs @@ -2,6 +2,10 @@ // Nyashの箱システムによるファイル入出力を提供します。 // 参考: 既存Boxの設計思想 +// SSOT: FileBox は「FileIo provider を常に経由する」(provider_lock に一元化)。 +// provider の有無・必須/optional の判定は provider_lock/CoreBoxId の責務で、 +// FileBox 実装内では生の環境変数や静的状態を見ない設計。 + // SSOT provider design (ring‑0/1) — modules are currently placeholders pub mod box_shim; // Thin delegating shim pub mod builtin_factory; diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index fbe10f7a..f70d7d1d 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -20,7 +20,8 @@ use std::{fs, process}; impl NyashRunner { // legacy run_file_legacy removed (was commented out) - /// Helper: run PyVM harness over a MIR module, returning the exit code + /// Legacy helper: run PyVM harness over a MIR module, returning the exit code. + /// The PyVM line is retired from regular development/CI; kept for historical debugging only. fn run_pyvm_harness( &self, module: &nyash_rust::mir::MirModule, diff --git a/src/runner/modes/pyvm.rs b/src/runner/modes/pyvm.rs index 519a0525..c41b558c 100644 --- a/src/runner/modes/pyvm.rs +++ b/src/runner/modes/pyvm.rs @@ -2,7 +2,10 @@ use super::super::NyashRunner; use nyash_rust::{mir::MirCompiler, parser::NyashParser}; use std::{fs, process}; -/// Execute using PyVM only (no Rust VM runtime). Emits MIR(JSON) and invokes tools/pyvm_runner.py. +/// Legacy helper: execute using PyVM only (no Rust VM runtime). +/// Emits MIR(JSON) and invokes tools/pyvm_runner.py. +/// NOTE: The PyVM line is retired from regular development/CI; this mode is kept for +/// historical debugging and should not be used as a primary execution path. pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { if crate::config::env::env_bool("NYASH_PYVM_TRACE") { eprintln!("[pyvm] entry"); diff --git a/src/runtime/core_box_ids.rs b/src/runtime/core_box_ids.rs index c7ca9d99..9b564396 100644 --- a/src/runtime/core_box_ids.rs +++ b/src/runtime/core_box_ids.rs @@ -104,18 +104,27 @@ impl CoreBoxId { Self::iter().find(|id| id.name() == name) } - /// Phase 86: core_required チェック + /// Phase 106: core_required チェック + /// + /// FileBox は Phase 85 では core_optional として分類していたが、 + /// selfhost/通常ランタイムでは事実上必須(ログ・ツール・ハコチェック等で常用) + /// であることが明確になったため、「core_required 相当」として扱う設計に統一した。 + /// + /// **設計原則**: + /// - 必須判定は CoreBoxId に一本化(provider_lock は「登録・読む」だけ) + /// - 将来 minimal/no-fs プロファイルを導入する場合は、ここで profile パラメータを追加可能 pub fn is_core_required(&self) -> bool { use CoreBoxId::*; - matches!(self, String | Integer | Bool | Array | Map | Console) + matches!(self, String | Integer | Bool | Array | Map | Console | File) } /// Phase 87: カテゴリ分類 pub fn category(&self) -> CoreBoxCategory { use CoreBoxId::*; match self { - String | Integer | Bool | Array | Map | Console => CoreBoxCategory::CoreRequired, - Float | Null | File | Path | Regex | Math | Time | Json | Toml => CoreBoxCategory::CoreOptional, + // Phase 106: File を CoreRequired 側に移動(selfhost/通常ランタイムでは必須) + String | Integer | Bool | Array | Map | Console | File => CoreBoxCategory::CoreRequired, + Float | Null | Path | Regex | Math | Time | Json | Toml => CoreBoxCategory::CoreOptional, Function | Result | Method | Missing => CoreBoxCategory::Special, } } @@ -342,23 +351,24 @@ mod tests { #[test] fn test_core_box_id_is_core_required() { - // Phase 85: core_required (6個) + // Phase 85: core_required (6個) + FileBox を実質必須扱いに拡張 assert!(CoreBoxId::String.is_core_required()); assert!(CoreBoxId::Integer.is_core_required()); assert!(CoreBoxId::Bool.is_core_required()); assert!(CoreBoxId::Array.is_core_required()); assert!(CoreBoxId::Map.is_core_required()); assert!(CoreBoxId::Console.is_core_required()); + assert!(CoreBoxId::File.is_core_required()); - // Phase 85: core_optional - assert!(!CoreBoxId::File.is_core_required()); + // core_optional の代表例 assert!(!CoreBoxId::Float.is_core_required()); } #[test] fn test_core_box_id_category() { assert_eq!(CoreBoxId::String.category(), CoreBoxCategory::CoreRequired); - assert_eq!(CoreBoxId::File.category(), CoreBoxCategory::CoreOptional); + // Phase 106: File の分類を修正 + assert_eq!(CoreBoxId::File.category(), CoreBoxCategory::CoreRequired); assert_eq!(CoreBoxId::Function.category(), CoreBoxCategory::Special); } diff --git a/src/runtime/plugin_host.rs b/src/runtime/plugin_host.rs index ebc5bf2f..d4a9a2a6 100644 --- a/src/runtime/plugin_host.rs +++ b/src/runtime/plugin_host.rs @@ -153,6 +153,18 @@ impl PluginHost { config: CoreServicesConfig, ) -> Result { use crate::runtime::core_services::*; + use crate::runtime::provider_lock; + + // Phase 106: FileBox provider チェック追加 + // CoreBoxId がFileを必須と判定している場合、provider が登録されていることを確認 + if CoreBoxId::File.is_core_required() { + if provider_lock::get_filebox_provider().is_none() { + return Err(CoreInitError::MissingService { + box_id: CoreBoxId::File, + message: "FileBox provider not registered (required for selfhost/default profile)".to_string(), + }); + } + } let mut core = CoreServices { string: None, @@ -291,11 +303,16 @@ mod tests { // Phase 94: 実際の registry を使用してテスト use crate::runtime::ring0::default_ring0; use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::boxes::file::core_ro::CoreRoFileIo; + use crate::runtime::provider_lock; let ring0 = Arc::new(default_ring0()); let mut registry = UnifiedBoxRegistry::new(); registry.register(Arc::new(BuiltinBoxFactory::new())); + // Phase 106: Initialize FileBox provider before PluginHost creation + let _ = provider_lock::set_filebox_provider(Arc::new(CoreRoFileIo::new())); + let plugin_host = PluginHost::with_core_from_registry(ring0, ®istry) .expect("CoreServices should be initialized with builtin boxes"); @@ -308,11 +325,16 @@ mod tests { // Phase 94: 実際の registry を使用して全フィールドが存在することを確認 use crate::runtime::ring0::default_ring0; use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::boxes::file::core_ro::CoreRoFileIo; + use crate::runtime::provider_lock; let ring0 = Arc::new(default_ring0()); let mut registry = UnifiedBoxRegistry::new(); registry.register(Arc::new(BuiltinBoxFactory::new())); + // Phase 106: Initialize FileBox provider before PluginHost creation + let _ = provider_lock::set_filebox_provider(Arc::new(CoreRoFileIo::new())); + let plugin_host = PluginHost::with_core_from_registry(ring0, ®istry) .expect("CoreServices should be initialized"); @@ -371,17 +393,56 @@ mod tests { let result = PluginHost::with_core_from_registry(ring0, ®istry); assert!(result.is_err()); - // Phase 95.5: エラーメッセージに "not found in registry" が含まれることを確認 + // Phase 95.5 + Phase 106: エラーメッセージチェック + // FileBox provider が未登録の場合は、CoreBoxId::File のエラーが優先される if let Err(e) = result { let msg = format!("{}", e); eprintln!("Error message: {}", msg); // デバッグ出力 assert!( - msg.contains("not found in registry") || msg.contains("creation failed") || msg.contains("Unknown Box type"), - "Error message should contain 'not found in registry', 'creation failed' or 'Unknown Box type', got: {}", + msg.contains("not found in registry") || + msg.contains("creation failed") || + msg.contains("Unknown Box type") || + msg.contains("FileBox provider not registered"), + "Error message should contain expected error patterns, got: {}", msg ); } } + + #[test] + fn test_with_core_from_registry_filebox_required() { + // Phase 106: FileBox provider なし → エラー + // Note: この test は provider_lock::get_filebox_provider() が None を返す場合のみ有効。 + // OnceLock の性質上、同一プロセス内で他のテストが先に provider を set すると + // このテストは期待通りに動作しない(provider が既に存在するため)。 + // そのため、provider が既に set されている場合は test を skip する。 + + use crate::runtime::ring0::default_ring0; + use crate::box_factory::builtin::BuiltinBoxFactory; + use crate::runtime::provider_lock; + + // provider が既に set されている場合は test skip + if provider_lock::get_filebox_provider().is_some() { + eprintln!("Skipping test_with_core_from_registry_filebox_required: provider already set by another test"); + return; + } + + let ring0 = Arc::new(default_ring0()); + let mut registry = UnifiedBoxRegistry::new(); + registry.register(Arc::new(BuiltinBoxFactory::new())); + + // provider_lock を初期化せず(呼び出さず) + // provider が無い状態で with_core_from_registry() を呼ぶ + + let result = PluginHost::with_core_from_registry(ring0, ®istry); + assert!(result.is_err()); + + if let Err(CoreInitError::MissingService { box_id, .. }) = result { + assert_eq!(box_id, CoreBoxId::File); + } else { + panic!("Expected MissingService error for FileBox"); + } + } } #[cfg(test)]