diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 1b088892..d355f0fa 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -8,6 +8,21 @@ Quick status - Parser: TokenCursor 統一 Step‑2/3 完了(env ゲート) - PHI: if/else の incoming pred を exit ブロックへ修正(VM 未定義値を根治) +## ADR 受理: No CoreBox & Everything is Plugin(Provider/Type 分離) + +- CoreBox は戻さない。Kernel は最小(GC/Handle/TLV/Extern/PluginRegistry/ABI)。 +- 型名(STN: `StringBox` 等)は不変、実装提供者(PVN)は TOML で切替。 +- 起動は「Kernel init → plugins.bootstrap/static + plugins.dynamic → Verify → 実行」。 +- VM/LLVM は `ny_new_box` / `ny_call_method` に統一(段階導入)。 +- ADR: docs/development/adr/adr-001-no-corebox-everything-is-plugin.md を追加。 + +受け口フェーズ(挙動不変) +- K0: ADR/Docs 追加(完了)。 +- K1: TOML スキーマ雛形(types/providers/policy)受け口(後続)。 +- K2: Provider 解決ログの受け口(後続)。 +- K3: Verify フック(preflight_plugins)受け口(後続)。 +- K4: Bootstrap Pack 登録導線(prod限定フラグ; 後続)。 + ## Using / Resolver — “Best of Both” Decision(2025‑09‑26) 合意(いいとこどり) @@ -20,7 +35,8 @@ Quick status やること(仕様不変・既定OFFで段階導入) 1) ドキュメント - - [x] `docs/reference/language/using.md` に SSOT+AST とプロファイル運用を追記。 + - [x] `docs/reference/language/using.md` に SSOT+AST/Profiles/Smokes を追記。 + - [x] ADR を追加(No CoreBox / Provider 分離) 2) Resolver 統合 - [x] vm_fallback に AST プレリュード統合を導入(common と同形)。 - [x] prod での `using "path"`/未知 alias はエラー(修正ガイド付)。 @@ -33,6 +49,24 @@ Quick status - [x] Guard 条件をトップレベル限定かつ `}` 直後のみ発火に調整(誤検知回避)。 - [ ] `apps/lib/json_native/utils/string.nyash` で stray FunctionCall 消滅確認。 +## シンプル化ロードマップ(claude code 提案の順) + +1) VM fallback 強化(mini 緑化) + - [x] レガシー解決の正規化(Box.method/Arity) + - [x] 文字列の最小メソッド(substring 等)暫定実装(短期・撤去予定) +2) dev/ci で AST 既定ON(prodはSSOTを維持) + - [ ] 既定値切替とスモーク緑確認 +3) レガシー using 経路の段階削除 + - [x] 呼び出し側のレガシー分岐を撤去(common/vm/vm_fallback/pyvm/selfhost を AST 経路に統一) + - [ ] strip_using_and_register 本体のファイル内撤去(後続の掃除タスクで対応) +4) パーサガードの格下げ→撤去 + - [x] Guard を log-only に格下げ(NYASH_PARSER_METHOD_BODY_STRICT=1 でも break せず警告ログのみ) + - [x] Guard 実装を撤去(method-body 専用のシーム判定を削除、通常ブロック同等に) + +受け入れ基準(追加) +- quick/integration スモークが AST 既定ON(dev/ci)で緑。 +- mini(starts_with)が VM fallback / LLVM / PyVM のいずれか基準で PASS(VM fallback は暫定メソッドで通せばOK)。 + 受け入れ基準 - StringUtils の `--dump-ast` に stray FunctionCall が出ない(宣言のみ)。 - mini(starts_with): ASTモード ON/OFF で parse→MIR まで到達(VM fallback の未実装は許容)。 @@ -45,6 +79,14 @@ Quick status - 現状: OFF 時は `string.nyash` にて Program 配下に `FunctionCall(parse_float)` が残存。 - 次: Guard ON で AST/MIR を検証し、必要に応じて lookahead 条件を調整。 +### 追加進捗(Using/Verify 受け口 2025‑09‑26 EOD) +- Provider Verify: nyash.toml の `[verify.required_methods]` / `[types.*.required_methods]` を読んで検査(env とマージ) + - 受け口: `NYASH_PROVIDER_VERIFY=warn|strict`、`NYASH_VERIFY_REQUIRED_METHODS`(任意上書き) + - preflight: `tools/smokes/v2/lib/preflight.sh` から warn で起動。`SMOKES_PROVIDER_VERIFY_MODE=strict` でエラー化 +- Using: レガシー前置き経路を呼び出し側から完全撤去(AST プレリュードに一本化) + - AST 無効プロファイルで using がある場合はガイド付きエラー + - 内部実装: 旧 strip_using_and_register/builtin 経路の物理削除(ファイル再構成) + ## 今日の合意(方向修正の確定) - Rust層は新機能を最小化。今後は Nyash VM/コンパイラ(自己ホスト)へリソース集中。 - 次タスクは Nyash 製 JSON ライブラリ(JSON v0 DOM: parse/stringify)。完了次第、Ny Executor 最小命令の実装を着手。 @@ -1068,7 +1110,7 @@ This page is trimmed to reflect the active work only. The previous long form has - mini_vm_core の末尾ブレースを整合(未閉じ/余剰の解消) - MiniVm.collect_prints の未知形スキップを Print オブジェクト全体に拡張(停滞防止) - Docs - - Added strings blueprint: `docs/blueprints/strings-utf8-byte.md` + - Added strings blueprint: `docs/development/design/blueprints/strings-utf8-byte.md` - Refreshed docs index with clear "Start here" links (blueprints/strings, EBNF, strings reference) - Clarified operator/loop sugar policy in `guides/language-core-and-sugar.md` ("!" adopted, do‑while not adopted) - Concurrency docs (design-only): box model, semantics, and patterns/checklist added @@ -1297,7 +1339,7 @@ Progress - [x] Mini‑VM ソースの @ 採用(apps/selfhost‑vm 配下の入口/補助を段階 @ 化) - [x] Runner CLI: clap ArgAction(bool フラグ)を一通り点検・SetTrue 指定(panic 回避) - [ ] Docs: invariants/constraints/testing‑matrix へ反映追加(前処理: using前置/@正規化) - - [x] Docs: Using→Loader 統合メモ(短尺)— docs/design/using-loader-integration.md(READMEにリンク済) + - [x] Docs: Using→Loader 統合メモ(短尺)— docs/development/design/legacy/using-loader-integration.md(READMEにリンク済) ### Guardrails(active) - 参照実行: PyVM が常時緑、マクロ正規化は pre‑MIR で一度だけ @@ -1305,16 +1347,16 @@ Progress - テスト: VM/goldens は軽量維持、IR は任意ジョブ ## Post‑Bootstrap Backlog(Docs only) -- Language: Scope reuse blocks(design) — docs/proposals/scope-reuse.md -- Language: Flow blocks & `->` piping(design) — docs/design/flow-blocks.md +- Language: Scope reuse blocks(design) — docs/development/proposals/scope-reuse.md +- Language: Flow blocks & `->` piping(design) — docs/development/design/legacy/flow-blocks.md - Guards: Range/CharClass sugar(reference) — docs/reference/language/match-guards.md - Strings: `toDigitOrNull` / `toIntOrNull`(design note) — docs/reference/language/strings.md - Concurrency: Box model(Routine/Channel/Select/Scope) — docs/proposals/concurrency/boxes.md - Concurrency semantics(blocking/close/select/trace) — docs/reference/concurrency/semantics.md ## Nyash VM めど後 — 機能追加リンク(備忘) -- スコープ再利用ブロック(MVP 提案): docs/proposals/scope-reuse.md -- 矢印フロー × 匿名ブロック(設計草案): docs/design/flow-blocks.md +- スコープ再利用ブロック(MVP 提案): docs/development/proposals/scope-reuse.md +- 矢印フロー × 匿名ブロック(設計草案): docs/development/design/legacy/flow-blocks.md - Match Guard の Range/CharClass(参照・設計): docs/reference/language/match-guards.md - String 便利関数(toDigit/Int; 設計): docs/reference/language/strings.md diff --git a/docs/README.md b/docs/README.md index 05dbfd39..07529931 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ ## 🚀 はじめに(導線) - 現在のタスクと進行状況: ../CURRENT_TASK.md - コア概念の速習: reference/architecture/nyash_core_concepts.md -- 設計ブループリント(文字列/文字コード): blueprints/strings-utf8-byte.md +- 設計ブループリント(文字列/文字コード): development/design/blueprints/strings-utf8-byte.md --- @@ -72,9 +72,9 @@ - proposals/concurrency/boxes.md(並行モデルのBox設計:Routine/Channel/Select/Scope) - reference/concurrency/semantics.md(ブロッキング/close/select/観測の規約) - design/(設計ノート入口) - - design/flow-blocks.md(矢印フロー/匿名ブロック・設計草案) - - ../proposals/scope-reuse.md(スコープ再利用ブロック・MVP提案) - - ../reference/language/match-guards.md(ガード連鎖/Range・CharClass設計) + - development/design/legacy/flow-blocks.md(矢印フロー/匿名ブロック・設計草案) + - development/proposals/scope-reuse.md(スコープ再利用ブロック・MVP提案) + - reference/language/match-guards.md(ガード連鎖/Range・CharClass設計) - guides/core-principles.md(最小構文・ゼロランタイム・可視化の原則) ### 開発状況 @@ -86,7 +86,7 @@ - 🧪 **[Phase 17: LoopForm Self‑Hosting](development/roadmap/phases/phase-17-loopform-selfhost/)** - 💡 **[Rust所有権統合(候補)](private/ideas/new-features/2025-09-22-rust-ownership-fusion.md)** - Phase 17+候補 - 🧩 **[Mini‑VM 構築ロードマップ](development/roadmap/phases/phase-17-loopform-selfhost/MINI_VM_ROADMAP.md)** -- 🧭 **Using→Loader 統合(最小設計)**: design/using-loader-integration.md +- 🧭 **Using→Loader 統合(最小設計)**: development/design/legacy/using-loader-integration.md - 🗂️ **Docsの書き方(小さく・リンク駆動)**: guides/contributing-docs.md --- diff --git a/docs/REORGANIZATION_REPORT.md b/docs/REORGANIZATION_REPORT.md index 9c1f29f5..ac43c02a 100644 --- a/docs/REORGANIZATION_REPORT.md +++ b/docs/REORGANIZATION_REPORT.md @@ -108,4 +108,38 @@ 2. 空ディレクトリの削除 3. 相互リンクの確認と修正 4. 最終的な整合性チェック -5. CLAUDE.mdの参照更新 \ No newline at end of file +5. CLAUDE.mdの参照更新 + +--- + +## 追加再編(2025-09-25、小規模・互換維持) + +概要 +- トップレベルに散在していたいくつかのカテゴリを、既存の4本柱へ段階的に集約しました(既定挙動は不変)。 + +主な移動 +- architecture → reference/architecture + - phi-and-ssa.md を reference/architecture/ へ移動(旧パスに stub 追加) +- blueprints → development/design/blueprints + - strings-utf8-byte.md を移動(旧パスに stub 追加) +- design → development/design/legacy + - 設計ノート一式を legacy サブフォルダへ移動(旧フォルダに案内 README/stub 追加) +- proposals → development/proposals + - proposals/* を移動(旧パスに README/stub 追加) +- examples → guides/examples + - 例一式を移動(旧パスに README/stub 追加) +- status/golden → development/testing/golden + - ゴールデン出力を移動(旧パスに README 追加) +- tests → development/testing + - テストドキュメントを移動(旧パスに README 追加) + +リンク更新 +- docs/README.md の該当リンク(blueprints/design/proposals/using-loader)を最小修正 +- docs/VM_README.md の examples 参照を guides/examples に更新 + +非対象 +- docs/private/ 配下は今回の再編から除外(方針どおり) + +受け入れチェック +- 既定挙動は不変、差分は最小 +- 移動元ディレクトリに stub/README を配置し、後方互換を確保 diff --git a/docs/VM_README.md b/docs/VM_README.md index 75f1670b..32aec0b8 100644 --- a/docs/VM_README.md +++ b/docs/VM_README.md @@ -104,4 +104,4 @@ tools/run_vm_stats.sh local_tests/vm_stats_http_err.nyash vm_stats_err.json - unreachable(接続不可/タイムアウト): `Result.Err(ErrorBox)` - 404/500 等のHTTPエラー: `Result.Ok(Response)`(アプリ側で `response.status` を評価) -詳細: `docs/reference/architecture/mir-to-vm-mapping.md` と `docs/examples/http_result_patterns.md` を参照。 +詳細: `docs/reference/architecture/mir-to-vm-mapping.md` と `docs/guides/examples/http_result_patterns.md` を参照。 diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 00000000..8618facd --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,9 @@ +# Architecture Docs Moved + +アーキテクチャの正本は `docs/reference/architecture/` へ集約しました。 + +- 入口: [../reference/architecture/](../reference/architecture/) +- 代表: [PHI and SSA](../reference/architecture/phi-and-ssa.md) + +このディレクトリは互換のための案内のみ残しています。 + diff --git a/docs/architecture/phi-and-ssa.md b/docs/architecture/phi-and-ssa.md index fb836dd7..13682cfc 100644 --- a/docs/architecture/phi-and-ssa.md +++ b/docs/architecture/phi-and-ssa.md @@ -1,23 +1,7 @@ -# PHI and SSA in Nyash +# Moved: PHI and SSA -Overview -- Nyash lowers high-level control flow (If/Loop/Match) to MIR and backends that rely on SSA form. -- We prioritize IR hygiene and observability while keeping runtime cost at zero. +このドキュメントは再編により移動しました。 +新しい場所: [../reference/architecture/phi-and-ssa.md](../reference/architecture/phi-and-ssa.md) -Design points -- PHI hygiene: no empty PHIs; PHIs at block head only. -- JoinResult hint: when both branches assign the same variable, we emit a MIR hint for diagnostics. -- Loop carriers: loops may expose a carrier observation (≤ N variables, where N is unconstrained by design; smokes emphasize common cases). - -Normalization -- If: may optionally wrap into LoopForm under a conservative gate (dev only). Semantics remain unchanged. -- Match: scrutinee evaluated once, guard fused; normalized to nested If‑chain in macro/core pass. - -Testing -- LLVM smokes: fixed small cases ensure no empty PHIs and head placement. -- MIR smokes: trace `scope|join|loop` to validate shaping without peeking into IR details. - -Roadmap -- Remove text-level sanitization once finalize‑PHI is trustworthy across Loop/If/Match. -- Expand goldens to cover nested joins and multi‑carrier loops while keeping CI light. +この stub は後方互換のために残しています。 diff --git a/docs/blueprints/README.md b/docs/blueprints/README.md new file mode 100644 index 00000000..3e9ae5f4 --- /dev/null +++ b/docs/blueprints/README.md @@ -0,0 +1,8 @@ +# Moved: Blueprints + +Blueprints は `development/design/blueprints/` に集約しました。 + +- 新しい場所: [../development/design/blueprints/](../development/design/blueprints/) + +この stub は後方互換のために残しています。 + diff --git a/docs/blueprints/strings-utf8-byte.md b/docs/blueprints/strings-utf8-byte.md index ab888154..d2844bac 100644 --- a/docs/blueprints/strings-utf8-byte.md +++ b/docs/blueprints/strings-utf8-byte.md @@ -1,61 +1,7 @@ -# Strings Blueprint — UTF‑8 First, Bytes Separate +# Moved: Strings Blueprint — UTF‑8 / Bytes -Status: active (Feature‑Pause compatible) -Updated: 2025-09-21 +このドキュメントは再編により移動しました。 +新しい場所: [../development/design/blueprints/strings-utf8-byte.md](../development/design/blueprints/strings-utf8-byte.md) -Purpose -- Unify string semantics by delegating StringBox public APIs to dedicated cursor boxes. -- Keep behavior stable while making codepoint vs byte decisions explicit and testable. +この stub は後方互換のために残しています。 -Pillars -- Utf8CursorBox (codepoint-oriented) - - length/indexOf/substring operate on UTF‑8 codepoints. - - Intended as the default delegate for StringBox public APIs. -- ByteCursorBox (byte-oriented) - - length/indexOf/substring operate on raw bytes. - - Use explicitly for byte-level parsing or binary protocols. - -Delegation Strategy -- StringBox delegates to Utf8CursorBox for core methods: length/indexOf/substring. -- Provide conversion helpers: toUtf8Cursor(), toByteCursor() (thin wrappers). -- Prefer delegation over inheritance; keep “from” minimal to avoid API ambiguity. - -API Semantics -- indexOf: define two flavors via the box boundary. - - StringBox.indexOf → Utf8CursorBox.indexOf (CP-based; canonical) - - ByteCursorBox.indexOf → byte-based; opt‑in only -- substring: follow the same split (CP vs Byte). Do not mix semantics. - - Document preconditions for indices (out‑of‑range clamped/errored per guide). - -Implementation Plan (staged, non‑breaking) -1) Provide MVP cursor boxes (done) - - apps/libs/utf8_cursor.nyash - - apps/libs/byte_cursor.nyash -2) Delegate StringBox public methods to Utf8CursorBox (internal only; behavior unchanged) - - Start with length → indexOf → substring - - Add targeted smokes for edge cases (multi‑byte CP, boundaries) -3) Replace ad‑hoc scans in Nyash scripts with cursor usage (Mini‑VM/macros) - - Migrate internal scanners (no external behavior change) -4) Introduce ByteCursorBox only where byte‑level semantics are required - - Keep call sites explicit to avoid ambiguity - -Transition Gate (Rust dev only) -- Env `NYASH_STR_CP=1` enables CP semantics for legacy byte-based paths in Rust runtime (e.g., StringBox.length/indexOf/lastIndexOf). -- Default remains byte in Rust during the feature‑pause; PyVM follows CP semantics. CI smokes validate CP behavior via PyVM. - -Related Docs -- reference/language/strings.md — policy & scope -- guides/language-core-and-sugar.md — core minimal + sugar -- reference/language/EBNF.md — operators (! adopted; do‑while not adopted) -- guides/loopform.md — loop normalization policy - -Box Foundations (string-related) -- Utf8CursorBox, ByteCursorBox -- StringExtBox (trim/startsWith/endsWith/replace/split) -- StringBuilderBox (append/toString) -- JsonCursorBox (lightweight JSON scanning helpers) - -Testing Notes -- Keep PyVM as the reference execution path. -- Add smokes: CP boundaries, mixed ASCII/non‑ASCII, indexOf not found, substring slices. -- Avoid perf work; focus on semantics + observability. diff --git a/docs/design/README.md b/docs/design/README.md index abf9c898..1732bedf 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -1,13 +1,11 @@ -# Nyash Design Notes +# Design Notes Moved -Public, stable design documents and architecture explanations. +`docs/design/` の設計ノートは `docs/development/design/legacy/` に移動しました。 -Use for rationale, trade‑offs, and diagrams that are safe to cite. +- 新しい場所: [../development/design/legacy/](../development/design/legacy/) +- よく参照されるページ: + - [flow-blocks.md](../development/design/legacy/flow-blocks.md) + - [using-loader-integration.md](../development/design/legacy/using-loader-integration.md) -Contents to consolidate here: -- Architecture overviews derived from ARCHITECTURE.md -- Backend design (LLVM/Cranelift) summaries -- MIR/IR evolution notes that are not drafts - -Draft, exploratory, or long‑form papers should remain under `docs/private/` until finalized. +このディレクトリは互換のための案内のみ残しています。 diff --git a/docs/design/flow-blocks.md b/docs/design/flow-blocks.md index 3afb26a9..bb1f0c36 100644 --- a/docs/design/flow-blocks.md +++ b/docs/design/flow-blocks.md @@ -1,80 +1,5 @@ -# Flow Blocks and Arrow Piping (Design Draft) +# Moved: Flow Blocks (Design Draft) -Status: design-only during the feature‑pause (no implementation) +このドキュメントは再編により移動しました。 +新しい場所: [../development/design/legacy/flow-blocks.md](../development/design/legacy/flow-blocks.md) -Goal -- Make control/data flow visually obvious while keeping the core minimal. -- Core = anonymous `{ ... }` blocks + `->` chaining with `_` or `|args|` as the input placeholder. -- Always desugar to plain sequential let/if/call; zero new runtime constructs. - -Core Syntax -- Serial (value flow): - ```nyash - { readConfig() } - -> { |cfg| validate(cfg) } - -> { |cfg| normalize(cfg) } - -> { |cfg| save(cfg) } - ``` -- Placeholder short form: - ```nyash - { fetch() } -> { process(_) } -> { output(_) } - ``` -- If/Else with horizontal flow: - ```nyash - if cond -> { doA() } else -> { doB() } - ``` - -Semantics -- `{ ... }` is an anonymous scope usable as expression or statement. -- `->` passes the left result as the first parameter of the right block. -- Left returns `Void` → right cannot use `_`/`|x|` (compile-time error in MVP spec). -- `_` and `|x,...|` are exclusive; mixing is an error. - -Lowering (always zero-cost sugar) -- Chain desugars to temporaries and calls: - ```nyash - # {A} -> { |x| B(x) } -> { |y| C(y) } - t0 = A(); - t1 = B(t0); - t2 = C(t1); - ``` -- If/Else chain desugars to standard if/else blocks; merges follow normal PHI wiring rules. - -Match normalization via guard chains -- Prefer a single readable form: - ```nyash - guard cond1 -> { A } - guard cond2 -> { B } - else -> { C } - ``` -- Lowers to first-match if/else chain. No new pattern engine is introduced. - -Range and CharClass guards (design) -- Range: `guard ch in '0'..'9' -> { ... }` → `('0' <= ch && ch <= '9')`. -- CharClass: `guard ch in Digit -> { ... }` → expands to ranges (e.g., '0'..'9'). -- Multiple ranges combine with OR. - -Formatting (nyfmt guidance) -- Align arrows vertically; one step per line: - ``` - { fetch() } - -> { validate(_) } - -> { save(_) } - ``` -- Suggest factoring when chains exceed N steps; prefer naming a scope helper. - -Observability (design only) -- `NYASH_FLOW_TRACE=1` prints the desugared steps (`t0=...; t1=...;`). - -Constraints (MVP) -- No new closures; anonymous blocks inline when capture-free. -- Recursion not required; focus on linear/branching chains. -- ASI: treat `->` as a low-precedence line-continue operator. - -Tests (syntax-only smokes; design) -- flow_linear: `read→validate→save` matches expected value. -- flow_placeholder: `{f()} -> { process(_) } -> { out(_) }`. -- flow_if: `if cond -> {A} else -> {B}` behaves like standard if. - -Pause note -- Documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap). diff --git a/docs/design/using-loader-integration.md b/docs/design/using-loader-integration.md index a9ad6407..7f6bee11 100644 --- a/docs/design/using-loader-integration.md +++ b/docs/design/using-loader-integration.md @@ -1,32 +1,5 @@ -# Using → Loader Integration (Minimal) +# Moved: Using → Loader Integration (Minimal) -Goal -- Keep `using` simple: strip lines and resolve names to paths/aliases. -- Add the minimal integration so userland boxes referenced via `using` are actually available at compile/run time. +このドキュメントは再編により移動しました。 +新しい場所: [../development/design/legacy/using-loader-integration.md](../development/design/legacy/using-loader-integration.md) -Scope (pause‑safe) -- Parser stays Phase‑0: keep `nyashstd` restriction; do not widen grammar. -- Integration is a runner step (pre‑parse): resolve and register modules; do not change language semantics. - -Design -- Strip `using` lines when `NYASH_ENABLE_USING=1` (already implemented). -- For each `using ns [as alias]?`: - - Resolve `ns` → path via: [modules] → aliases → using.paths (apps/lib/.) → context dir. - - Register mapping in `modules_registry` as `alias_or_ns -> path` (already implemented). -- Minimal loader hook (defer heavy linking): - - Compile/execute entry file as today. - - Userland boxes are accessed via tools/runners that read from `modules_registry` where needed (e.g., PyVM harness/tests). - -Notes -- Entry thin‑ization (Mini‑VM) waits until loader reads userland boxes on demand. -- Keep docs small: this note serves as the canonical link; avoid duplicating details in other pages. - -Preprocessing invariants (runner) -- `using` lines are stripped and resolved prior to parse; dependencies are inlined before `Main` so names are available without changing language semantics. -- Line‑head `@name[:T] = expr` is normalized to `local name[:T] = expr` as a purely textual pre‑expand (no semantic change). Inline `@` is not recognized; keep `@` at line head. -- These steps are pause‑safe: they do not alter AST semantics; they only simplify authoring and module wiring. - -Links -- Runner pipeline: src/runner/pipeline.rs -- Using strip/resolve: src/runner/modes/common_util/resolve.rs -- Env: NYASH_ENABLE_USING, NYASH_USING_STRICT, NYASH_SKIP_TOML_ENV diff --git a/docs/development/adr/adr-001-no-corebox-everything-is-plugin.md b/docs/development/adr/adr-001-no-corebox-everything-is-plugin.md new file mode 100644 index 00000000..c0864497 --- /dev/null +++ b/docs/development/adr/adr-001-no-corebox-everything-is-plugin.md @@ -0,0 +1,70 @@ +# ADR-001: No CoreBox, Everything is Plugin — Provider/Type 分離方針 + +Status: Accepted (Phase 15.5 設計) +Last updated: 2025-09-26 + +## 決定 + +- CoreBox(言語ランタイム内の特別な箱実装)は復活させない。 +- 最小カーネル(NyKernel)は GC/Handle/TLV/Extern/PluginRegistry/ABI だけを提供し、箱の実装は一切持たない。 +- 機能はすべて Plugin(TypeBox v2)で提供する(Everything is Plugin)。 +- 型名(Stable Type Name: STN)と実装提供者(Provider ID: PVN)を分離する。 + - 例: STN = `StringBox`、PVN = `kernel:string@1.0` / `acme:string@2.1`。 + - コードは常に STN に依存し、どの PVN を使うかは `nyash.toml` で選択する。 +- 起動シーケンスは「Kernel init → plugins.bootstrap(静的束)+ plugins.dynamic(任意)登録 → Verify(必須メソッド等)→ 実行」。 +- 呼び出しは VM/LLVM 共通で `ny_new_box` / `ny_call_method` に一本化(MIR Callee 確定から統一)する。 + +## 背景 / 文脈 + +- using の SSOT(唯一の真実)を `nyash.toml` に置き、実体結合は AST マージに一本化した。これにより宣言≻式の曖昧性を排除し、依存は設定で一元管理できる。 +- CoreBox を残すと特別経路や例外が増え、Everything is Plugin と相反する。Kernel/Plugin の責務分離が維持性と置換性を高める。 + +## 設計詳細 + +### NyKernel の責務 +- GC / Roots / Safepoint / Write barrier +- Handle / TLV +- Extern registry(`env.console.*` 最小) +- Plugin registry(登録・検索・呼び出し) +- C ABI: `ny_new_box(type_id, args)`, `ny_call_method(recv, type_id, method_id, args, out)`, `ny_gc_*` + +### Bootstrap Pack(静的リンクの基本プラグイン束) +- String/Integer/Array/Map/Console などの最小実用セット。 +- 静的リンクして起動時に一括登録(特権経路は作らない)。 +- 動的プラグインで override 可能(ポリシーで制御)。 + +### Provider/Type 分離(TOML スキーマ案・既定OFF) +```toml +[types.StringBox] +provider = "kernel:string@1.0" +allow_override = true + +[providers."kernel:string@1.0"] +crate = "nyash-plugin-base-string" + +[providers."acme:string@2.1"] +path = "./plugins/libacme_string.so" +override = true + +[policy] +factory = "plugin-first" # compat_plugin_first | static_only +``` + +### Verify(必須) +- plugin-tester で Lifecycle/TLV/必須メソッドを検証。欠落時は即エラー。 + +## 代替案の却下(kernelString 等) + +- 型名に provider を織り込む(例: `kernelString`)案は、差し替え不能化・特権化・型ID安定性の破壊リスクが高く不採用。区別は provider 名で行い、型名(STN)は常に不変とする。 + +## ロードマップ(小さく段階的・既定OFF) +- K0: ADR/Docs 追加(本書)。 +- K1: TOML スキーマの雛形(types/providers/policy)を docs と設定ローダに受け口だけ追加(挙動不変)。 +- K2: 起動時に provider 解決の受け口をログ出力のみで導入(挙動不変)。 +- K3: Verify フックを preflight_plugins に統合(既定OFF)。 +- K4: Bootstrap Pack の登録導線(prod 限定フラグ・既定OFF)。 + +## 移行 / 互換 +- 既存コードは型名(STN)のまま変更不要。provider 置換は TOML で完結。 +- VM fallback の暫定個別救済はフラグ付き/短期で撤去。最終形は `ny_call_method` に集約。 + diff --git a/docs/development/design/blueprints/strings-utf8-byte.md b/docs/development/design/blueprints/strings-utf8-byte.md new file mode 100644 index 00000000..ab888154 --- /dev/null +++ b/docs/development/design/blueprints/strings-utf8-byte.md @@ -0,0 +1,61 @@ +# Strings Blueprint — UTF‑8 First, Bytes Separate + +Status: active (Feature‑Pause compatible) +Updated: 2025-09-21 + +Purpose +- Unify string semantics by delegating StringBox public APIs to dedicated cursor boxes. +- Keep behavior stable while making codepoint vs byte decisions explicit and testable. + +Pillars +- Utf8CursorBox (codepoint-oriented) + - length/indexOf/substring operate on UTF‑8 codepoints. + - Intended as the default delegate for StringBox public APIs. +- ByteCursorBox (byte-oriented) + - length/indexOf/substring operate on raw bytes. + - Use explicitly for byte-level parsing or binary protocols. + +Delegation Strategy +- StringBox delegates to Utf8CursorBox for core methods: length/indexOf/substring. +- Provide conversion helpers: toUtf8Cursor(), toByteCursor() (thin wrappers). +- Prefer delegation over inheritance; keep “from” minimal to avoid API ambiguity. + +API Semantics +- indexOf: define two flavors via the box boundary. + - StringBox.indexOf → Utf8CursorBox.indexOf (CP-based; canonical) + - ByteCursorBox.indexOf → byte-based; opt‑in only +- substring: follow the same split (CP vs Byte). Do not mix semantics. + - Document preconditions for indices (out‑of‑range clamped/errored per guide). + +Implementation Plan (staged, non‑breaking) +1) Provide MVP cursor boxes (done) + - apps/libs/utf8_cursor.nyash + - apps/libs/byte_cursor.nyash +2) Delegate StringBox public methods to Utf8CursorBox (internal only; behavior unchanged) + - Start with length → indexOf → substring + - Add targeted smokes for edge cases (multi‑byte CP, boundaries) +3) Replace ad‑hoc scans in Nyash scripts with cursor usage (Mini‑VM/macros) + - Migrate internal scanners (no external behavior change) +4) Introduce ByteCursorBox only where byte‑level semantics are required + - Keep call sites explicit to avoid ambiguity + +Transition Gate (Rust dev only) +- Env `NYASH_STR_CP=1` enables CP semantics for legacy byte-based paths in Rust runtime (e.g., StringBox.length/indexOf/lastIndexOf). +- Default remains byte in Rust during the feature‑pause; PyVM follows CP semantics. CI smokes validate CP behavior via PyVM. + +Related Docs +- reference/language/strings.md — policy & scope +- guides/language-core-and-sugar.md — core minimal + sugar +- reference/language/EBNF.md — operators (! adopted; do‑while not adopted) +- guides/loopform.md — loop normalization policy + +Box Foundations (string-related) +- Utf8CursorBox, ByteCursorBox +- StringExtBox (trim/startsWith/endsWith/replace/split) +- StringBuilderBox (append/toString) +- JsonCursorBox (lightweight JSON scanning helpers) + +Testing Notes +- Keep PyVM as the reference execution path. +- Add smokes: CP boundaries, mixed ASCII/non‑ASCII, indexOf not found, substring slices. +- Avoid perf work; focus on semantics + observability. diff --git a/docs/design/ARCHITECTURE.md b/docs/development/design/legacy/ARCHITECTURE.md similarity index 100% rename from docs/design/ARCHITECTURE.md rename to docs/development/design/legacy/ARCHITECTURE.md diff --git a/docs/design/LLVM_LAYER_OVERVIEW.md b/docs/development/design/legacy/LLVM_LAYER_OVERVIEW.md similarity index 100% rename from docs/design/LLVM_LAYER_OVERVIEW.md rename to docs/development/design/legacy/LLVM_LAYER_OVERVIEW.md diff --git a/docs/design/LOWERING_CONTEXTS.md b/docs/development/design/legacy/LOWERING_CONTEXTS.md similarity index 100% rename from docs/design/LOWERING_CONTEXTS.md rename to docs/development/design/legacy/LOWERING_CONTEXTS.md diff --git a/docs/design/LOWERING_LLVM.md b/docs/development/design/legacy/LOWERING_LLVM.md similarity index 100% rename from docs/design/LOWERING_LLVM.md rename to docs/development/design/legacy/LOWERING_LLVM.md diff --git a/docs/development/design/legacy/README.md b/docs/development/design/legacy/README.md new file mode 100644 index 00000000..abf9c898 --- /dev/null +++ b/docs/development/design/legacy/README.md @@ -0,0 +1,13 @@ +# Nyash Design Notes + +Public, stable design documents and architecture explanations. + +Use for rationale, trade‑offs, and diagrams that are safe to cite. + +Contents to consolidate here: +- Architecture overviews derived from ARCHITECTURE.md +- Backend design (LLVM/Cranelift) summaries +- MIR/IR evolution notes that are not drafts + +Draft, exploratory, or long‑form papers should remain under `docs/private/` until finalized. + diff --git a/docs/design/RESOLVER_API.md b/docs/development/design/legacy/RESOLVER_API.md similarity index 100% rename from docs/design/RESOLVER_API.md rename to docs/development/design/legacy/RESOLVER_API.md diff --git a/docs/design/aot-plan-v1.md b/docs/development/design/legacy/aot-plan-v1.md similarity index 100% rename from docs/design/aot-plan-v1.md rename to docs/development/design/legacy/aot-plan-v1.md diff --git a/docs/design/backend-cranelift-aot-design.md b/docs/development/design/legacy/backend-cranelift-aot-design.md similarity index 100% rename from docs/design/backend-cranelift-aot-design.md rename to docs/development/design/legacy/backend-cranelift-aot-design.md diff --git a/docs/design/backend-llvm-implementation-guide.md b/docs/development/design/legacy/backend-llvm-implementation-guide.md similarity index 100% rename from docs/design/backend-llvm-implementation-guide.md rename to docs/development/design/legacy/backend-llvm-implementation-guide.md diff --git a/docs/design/cranelift-aot-box.md b/docs/development/design/legacy/cranelift-aot-box.md similarity index 100% rename from docs/design/cranelift-aot-box.md rename to docs/development/design/legacy/cranelift-aot-box.md diff --git a/docs/development/design/legacy/flow-blocks.md b/docs/development/design/legacy/flow-blocks.md new file mode 100644 index 00000000..3afb26a9 --- /dev/null +++ b/docs/development/design/legacy/flow-blocks.md @@ -0,0 +1,80 @@ +# Flow Blocks and Arrow Piping (Design Draft) + +Status: design-only during the feature‑pause (no implementation) + +Goal +- Make control/data flow visually obvious while keeping the core minimal. +- Core = anonymous `{ ... }` blocks + `->` chaining with `_` or `|args|` as the input placeholder. +- Always desugar to plain sequential let/if/call; zero new runtime constructs. + +Core Syntax +- Serial (value flow): + ```nyash + { readConfig() } + -> { |cfg| validate(cfg) } + -> { |cfg| normalize(cfg) } + -> { |cfg| save(cfg) } + ``` +- Placeholder short form: + ```nyash + { fetch() } -> { process(_) } -> { output(_) } + ``` +- If/Else with horizontal flow: + ```nyash + if cond -> { doA() } else -> { doB() } + ``` + +Semantics +- `{ ... }` is an anonymous scope usable as expression or statement. +- `->` passes the left result as the first parameter of the right block. +- Left returns `Void` → right cannot use `_`/`|x|` (compile-time error in MVP spec). +- `_` and `|x,...|` are exclusive; mixing is an error. + +Lowering (always zero-cost sugar) +- Chain desugars to temporaries and calls: + ```nyash + # {A} -> { |x| B(x) } -> { |y| C(y) } + t0 = A(); + t1 = B(t0); + t2 = C(t1); + ``` +- If/Else chain desugars to standard if/else blocks; merges follow normal PHI wiring rules. + +Match normalization via guard chains +- Prefer a single readable form: + ```nyash + guard cond1 -> { A } + guard cond2 -> { B } + else -> { C } + ``` +- Lowers to first-match if/else chain. No new pattern engine is introduced. + +Range and CharClass guards (design) +- Range: `guard ch in '0'..'9' -> { ... }` → `('0' <= ch && ch <= '9')`. +- CharClass: `guard ch in Digit -> { ... }` → expands to ranges (e.g., '0'..'9'). +- Multiple ranges combine with OR. + +Formatting (nyfmt guidance) +- Align arrows vertically; one step per line: + ``` + { fetch() } + -> { validate(_) } + -> { save(_) } + ``` +- Suggest factoring when chains exceed N steps; prefer naming a scope helper. + +Observability (design only) +- `NYASH_FLOW_TRACE=1` prints the desugared steps (`t0=...; t1=...;`). + +Constraints (MVP) +- No new closures; anonymous blocks inline when capture-free. +- Recursion not required; focus on linear/branching chains. +- ASI: treat `->` as a low-precedence line-continue operator. + +Tests (syntax-only smokes; design) +- flow_linear: `read→validate→save` matches expected value. +- flow_placeholder: `{f()} -> { process(_) } -> { out(_) }`. +- flow_if: `if cond -> {A} else -> {B}` behaves like standard if. + +Pause note +- Documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap). diff --git a/docs/design/linker-box.md b/docs/development/design/legacy/linker-box.md similarity index 100% rename from docs/design/linker-box.md rename to docs/development/design/legacy/linker-box.md diff --git a/docs/development/design/legacy/using-loader-integration.md b/docs/development/design/legacy/using-loader-integration.md new file mode 100644 index 00000000..a9ad6407 --- /dev/null +++ b/docs/development/design/legacy/using-loader-integration.md @@ -0,0 +1,32 @@ +# Using → Loader Integration (Minimal) + +Goal +- Keep `using` simple: strip lines and resolve names to paths/aliases. +- Add the minimal integration so userland boxes referenced via `using` are actually available at compile/run time. + +Scope (pause‑safe) +- Parser stays Phase‑0: keep `nyashstd` restriction; do not widen grammar. +- Integration is a runner step (pre‑parse): resolve and register modules; do not change language semantics. + +Design +- Strip `using` lines when `NYASH_ENABLE_USING=1` (already implemented). +- For each `using ns [as alias]?`: + - Resolve `ns` → path via: [modules] → aliases → using.paths (apps/lib/.) → context dir. + - Register mapping in `modules_registry` as `alias_or_ns -> path` (already implemented). +- Minimal loader hook (defer heavy linking): + - Compile/execute entry file as today. + - Userland boxes are accessed via tools/runners that read from `modules_registry` where needed (e.g., PyVM harness/tests). + +Notes +- Entry thin‑ization (Mini‑VM) waits until loader reads userland boxes on demand. +- Keep docs small: this note serves as the canonical link; avoid duplicating details in other pages. + +Preprocessing invariants (runner) +- `using` lines are stripped and resolved prior to parse; dependencies are inlined before `Main` so names are available without changing language semantics. +- Line‑head `@name[:T] = expr` is normalized to `local name[:T] = expr` as a purely textual pre‑expand (no semantic change). Inline `@` is not recognized; keep `@` at line head. +- These steps are pause‑safe: they do not alter AST semantics; they only simplify authoring and module wiring. + +Links +- Runner pipeline: src/runner/pipeline.rs +- Using strip/resolve: src/runner/modes/common_util/resolve.rs +- Env: NYASH_ENABLE_USING, NYASH_USING_STRICT, NYASH_SKIP_TOML_ENV diff --git a/docs/development/proposals/concurrency/boxes.md b/docs/development/proposals/concurrency/boxes.md new file mode 100644 index 00000000..25afaf31 --- /dev/null +++ b/docs/development/proposals/concurrency/boxes.md @@ -0,0 +1,77 @@ +# Nyash Concurrency — Box Model (Proposal, docs-only) + +Status: design-only during the feature‑pause. No runtime or spec changes. Implement after Mini‑VM baseline is stable. + +Intent +- Bring Go-like CSP (goroutine/channels/select) into Nyash via “Everything is Box”. +- Keep semantics explicit, lifecycle safe (birth/fini), and observable. Phase-in from userland → runtime. + +Scope (Phase‑0: userland MVP) +- RoutineBox: lightweight task wrapper over `nowait` (state, join/cancel, status). +- ChannelBox: bounded/unbounded queue + blocking/non-blocking ops + close semantics. +- SelectBox: multi-channel wait (first-ready) with simple fairness. +- RoutineScopeBox: structured concurrency; children are canceled on scope fini. +- Observability: JSONL trace toggled by `NYASH_CONC_TRACE=1`. + +Non‑Goals (Phase‑0) +- M:N scheduler, OS-level park/unpark, net poller integration (deferred to Phase‑2 runtime work). + +API Sketch (userland) +- RoutineBox + - birth(fn) + - start(): Void + - join(timeout_ms?: Int) -> Bool // true if joined; false on timeout + - cancel(): Void + - status() -> String // ready|running|done|canceled|error +- ChannelBox(capacity: Int=0) + - send(v): Void // blocks if full (Phase‑0: simulated park) + - try_send(v) -> Bool + - receive() -> Any // blocks if empty (Phase‑0: simulated park) + - try_receive() -> (Bool, Any?) + - receive_timeout(ms: Int) -> (Bool, Any?) + - close(): Void // further send fails; recv drains until empty then End +- SelectBox + - birth() + - when(ch: ChannelBox, handler: Fn): Void + - await() -> Bool // returns after one handler runs; false if none ready and no wait policy + - await_timeout(ms: Int) -> Bool +- RoutineScopeBox + - birth() + - spawn(fn) -> RoutineBox + - fini() // cancels pending routines and waits boundedly + +Semantics +- Capacity: + - 0: rendezvous channel (send/recv rendezvous). + - N>0: bounded ring buffer. +- Close: + - close() marks channel as closed. send() after close -> error. receive() returns buffered items; when empty -> (false, End) style result; exact return shape defined per API. +- Blocking: + - Phase‑0 userland uses cooperative wait queues; no busy loops. try_* and timeout variants provided. +- Select fairness: + - If multiple ready, choose random/round‑robin. Starvation avoidance is a design requirement; precise algorithm can evolve. +- Types: + - `TypedChannelBox` is a future extension; Phase‑0 uses runtime tags/guards documented in reference. +- Cancellation: + - RoutineScopeBox cancels children on fini; Channel waits should return (canceled) promptly. + +Phases +- Phase‑0 (userland MVP / PyVM first) + - Implement the 4 boxes above with minimal queues/waits, plus trace hooks. + - Smokes: ping‑pong, bounded producer/consumer, two‑way select, close semantics, scope cancel. +- Phase‑1 (park/unpark abstraction) + - Introduce `WaiterBox`/`CondBox` that map to efficient OS waits where available. Keep same APIs. +- Phase‑2 (runtime integration) + - Scheduler (M:N), GC and net poller integration, fairness and profiling. Keep Box APIs stable. + +Observability +- `NYASH_CONC_TRACE=1` → JSONL events: spawn/join/cancel/send/recv/park/unpark/select/close with routine IDs, channel IDs, timestamps. + +Safety & Diagnostics +- Deadlock hints: trace dependent waits; optional detector (dev only) can dump wait‑for graph. +- API contracts explicitly define error return for misuse (send on closed, double close, etc.). + +Deliverables (docs‑only during the feature‑pause) +- This proposal (boxes & semantics). +- Reference page with blocking/close/select rules (see reference/concurrency/semantics.md). +- Test plan with named smokes and expected outputs. diff --git a/docs/development/proposals/scope-reuse.md b/docs/development/proposals/scope-reuse.md new file mode 100644 index 00000000..4102becf --- /dev/null +++ b/docs/development/proposals/scope-reuse.md @@ -0,0 +1,74 @@ +# Scope Reuse Blocks (MVP Proposal) + +Status: design-only during the feature‑pause (no implementation) + +Summary +- Give short, reusable logic a name within the current scope without promoting it to a top-level function. +- Keep the core small: block body + postfix header sugar; desugar to local function + normal calls. +- Zero runtime cost: lowers to let/if/call/ret only (no new instructions/closures). + +Syntax (postfix header; Nyash style) +- Block form (multi-statement): + ```nyash + { /* BODY */ } scope name(arglist?) (-> Ret)? + // call within the same scope + name(args) + ``` +- Expression form (one-liner): + ```nyash + => EXPR scope name(arglist?) (-> Ret)? + ``` + +Semantics +- Visibility: `name` is local to the defining scope; not exported. +- Capture: by reference by default. Mutating captured vars requires explicit `mut` on those bindings. +- Recursion: disallowed in MVP (can be lifted later). +- Errors/exits: same as regular functions (return/cleanup/catch apply at the function boundary). + +Lowering (desugaring) +- Transform into a local function plus a local binding for convenience calls. + ```nyash + // { BODY } scope check(a:Int)->Str + // ↓ (conceptual) + let __cap_me = me; let __cap_locals = { /* needed refs */ }; + method __scope_check__(a:Int)->Str { + return BODY + } + let check = (x) => __scope_check__(x) + ``` +- Captures are passed via hidden arguments or an environment box; no new VM opcodes. + +Examples +```nyash +{ if x % 2 == 0 { return "even" } return "odd" } scope parity(x:Int)->StringBox + +for i in range(0,10) { + print(parity(i)) +} +``` + +Safety rules (MVP) +- Capture: read-only by default; writes allowed only when the captured binding is declared `mut`. +- Name uniqueness: `scope name` must be unique within the scope. +- No cross-scope escape: values may be returned but the function reference itself is not exported. + +Observability & Tooling +- Add trace toggles (design only): + - `NYASH_SCOPE_TRACE=1|json` to emit enter/exit and capture lists as JSONL. + - Example: `{ "ev":"enter","sid":42,"caps":["me","cfg","mut total"] }`. +- Lints (design only): + - Single-use scope → suggest inline. + - Excess captures → suggest narrowing. + +Interactions +- Works with guard/with/await sugars (it’s just a call). +- Compatible with ASI and postfix aesthetics; no new top-level keywords beyond `scope` suffix. + +Tests (syntax-only smokes; design) +- scope_basic: called twice → same result. +- scope_capture_read: reads `me/foo`. +- scope_capture_mut: mutation only allowed when `mut` is present. +- scope_with_catch_cleanup: postfix catch/cleanup applied at local-function boundary. + +Pause note +- This is documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap). diff --git a/docs/development/proposals/unified-members.md b/docs/development/proposals/unified-members.md new file mode 100644 index 00000000..91c3a08e --- /dev/null +++ b/docs/development/proposals/unified-members.md @@ -0,0 +1,137 @@ +# Property System Revolution for Nyash (2025-09-18 Breakthrough) + +Status: **BREAKTHROUGH COMPLETED** - Final syntax decided through AI collaboration with ChatGPT, Claude, and Codex. + +## 🌟 Revolutionary Achievement +Today we achieved the **Property System Revolution** - a complete unification of stored fields, computed properties, lazy evaluation, and birth-time initialization into a single, elegant syntax system through AI collaboration with ChatGPT5, Claude, and Codex. + +## 🎯 Final Property System Design + +### The Four-Category Breakthrough +After dialectical discussion with multiple AI agents, we reached the perfect synthesis: + +#### 1. **stored** - Traditional Field Storage +```nyash +box Example { + name: StringBox // Default initialization + count: IntegerBox = 0 // Explicit initialization +} +``` +- **Semantics**: O(1) slot read/write, assignment allowed +- **Use Case**: Traditional object fields, counters, configurations + +#### 2. **computed** - Calculated Every Access +```nyash +box Example { + size: IntegerBox { me.items.count() } + full_name: StringBox { me.first + " " + me.last } +} +``` +- **Semantics**: Evaluate body on each read, assignment error unless setter declared +- **Use Case**: Derived values, dynamic calculations, Python @property equivalent + +#### 3. **once** - Lazy Evaluation with Caching +```nyash +box Example { + once expensive_data: DataBox { heavy_computation() } + once config: ConfigBox { loadConfiguration() } +} +``` +- **Semantics**: Evaluate on first read, cache result, return cached value thereafter +- **Use Case**: Heavy computations, file loading, Python @cached_property equivalent +- **Exception Handling**: Poison-on-throw strategy for safety + +#### 4. **birth_once** - Eager Evaluation at Object Creation +```nyash +box Example { + birth_once startup_data: DataBox { initialize_system() } + + birth() { + // birth_once properties already initialized! + me.ready = true + } +} +``` +- **Semantics**: Evaluated before user birth() in declaration order +- **Use Case**: System initialization, dependency setup, startup-critical data + +## 🌟 Revolutionary Python Integration + +### Perfect Mapping Strategy +```python +# Python side +class DataProcessor: + def __init__(self): + self.value = 42 # → stored + + @property + def computed_result(self): # → computed + return self.value * 2 + + @functools.cached_property + def expensive_data(self): # → once + return heavy_computation() +``` + +```nyash +// Auto-generated Nyash (revolutionary 1:1 mapping!) +box DataProcessor { + value: IntegerBox // stored + computed_result: IntegerBox { me.value * 2 } // computed + once expensive_data: ResultBox { heavy_computation() } // once + + birth() { + me.value = 42 + } +} +``` + +### Performance Revolution +- **computed properties**: No caching overhead, pure calculation +- **once properties**: 10-50x faster than Python cached_property (LLVM optimization) +- **birth_once properties**: Startup optimization, dependency injection pattern +- **Overall**: Python code → 5-20x faster native binary + +Handlers (Stage‑3) +- Postfix `catch/cleanup` are allowed for computed/once/birth_once/method blocks. +- Stored does not accept handlers. + +Semantics +- stored: O(1) slot read; `= expr` evaluated once during construction; assignment allowed. +- computed: evaluate on each read; assignment is an error unless a setter is declared. +- once: evaluate on first read, cache the result, and return it thereafter. If the first evaluation throws and there is no `catch`, mark poisoned and rethrow the same error on later reads (no retry). +- birth_once: evaluated before user `birth` body in declaration order; uncaught error aborts construction. Cycles are rejected. + +Lowering (no JSON v0 change) +- stored → slot +- computed → synthesize `__get_name():T { try body; catch; finally }`, resolve reads to call +- once → add hidden `__name: Option` and first-read initialization in `__get_name()`; poison on uncaught error +- birth_once → hidden `__name: T` initialized before user `birth` body in declaration order; handler blocks apply per initializer +- method → unchanged; postfix handlers lower to try/catch/finally + +EBNF (delta) +``` +box_decl := 'box' IDENT '{' member* '}' +member := stored | computed | once_decl | birth_once_decl | method_decl +stored := IDENT ':' TYPE ( '=' expr )? +computed := IDENT ':' TYPE block handler_tail? +once_decl := 'once' IDENT ':' TYPE block handler_tail? +birth_once_decl:= 'birth_once' IDENT ':' TYPE block handler_tail? +method_decl := IDENT '(' params? ')' ( ':' TYPE )? block handler_tail? +handler_tail := ( catch_block )? ( cleanup_block )? +catch_block := 'catch' ( '(' ( IDENT IDENT | IDENT )? ')' )? block +cleanup_block := 'cleanup' block +``` + +Diagnostics +- Assignment to computed/once/birth_once: error with fix-it (“define a setter or use stored property”). +- Once poison: first read throws → remember error; subsequent reads rethrow immediately. +- Birth order: evaluated before user `birth`, in declaration order; cycle detection emits a clear error with the chain. + +Flags +- Parser gate: `NYASH_ENABLE_UNIFIED_MEMBERS=1` +- Stage‑3 for handlers: `NYASH_PARSER_STAGE3=1` + +Notes +- User experience: read is uniform (`obj.name`), write differs by kind; this keeps mental model simple. +- Future: setter syntax (`name: T { get {…} set(v) {…} }`) and aliases (`slot/calc/lazy`) can be added without breaking this core. diff --git a/docs/tests/E2E_TESTS.md b/docs/development/testing/E2E_TESTS.md similarity index 100% rename from docs/tests/E2E_TESTS.md rename to docs/development/testing/E2E_TESTS.md diff --git a/docs/tests/aot_smoke_cranelift.md b/docs/development/testing/aot_smoke_cranelift.md similarity index 100% rename from docs/tests/aot_smoke_cranelift.md rename to docs/development/testing/aot_smoke_cranelift.md diff --git a/docs/status/golden/await_simple.mir.txt b/docs/development/testing/golden/await_simple.mir.txt similarity index 100% rename from docs/status/golden/await_simple.mir.txt rename to docs/development/testing/golden/await_simple.mir.txt diff --git a/docs/status/golden/boxcall_array_getset.mir.txt b/docs/development/testing/golden/boxcall_array_getset.mir.txt similarity index 100% rename from docs/status/golden/boxcall_array_getset.mir.txt rename to docs/development/testing/golden/boxcall_array_getset.mir.txt diff --git a/docs/status/golden/extern_console_log.mir.txt b/docs/development/testing/golden/extern_console_log.mir.txt similarity index 100% rename from docs/status/golden/extern_console_log.mir.txt rename to docs/development/testing/golden/extern_console_log.mir.txt diff --git a/docs/status/golden/loop_nested_if.mir.txt b/docs/development/testing/golden/loop_nested_if.mir.txt similarity index 100% rename from docs/status/golden/loop_nested_if.mir.txt rename to docs/development/testing/golden/loop_nested_if.mir.txt diff --git a/docs/status/golden/loop_simple.mir.txt b/docs/development/testing/golden/loop_simple.mir.txt similarity index 100% rename from docs/status/golden/loop_simple.mir.txt rename to docs/development/testing/golden/loop_simple.mir.txt diff --git a/docs/status/golden/typeop_in_if_loop_poc.mir.txt b/docs/development/testing/golden/typeop_in_if_loop_poc.mir.txt similarity index 100% rename from docs/status/golden/typeop_in_if_loop_poc.mir.txt rename to docs/development/testing/golden/typeop_in_if_loop_poc.mir.txt diff --git a/docs/status/golden/typeop_is_as_func_poc.mir.txt b/docs/development/testing/golden/typeop_is_as_func_poc.mir.txt similarity index 100% rename from docs/status/golden/typeop_is_as_func_poc.mir.txt rename to docs/development/testing/golden/typeop_is_as_func_poc.mir.txt diff --git a/docs/status/golden/typeop_is_as_poc.mir.txt b/docs/development/testing/golden/typeop_is_as_poc.mir.txt similarity index 100% rename from docs/status/golden/typeop_is_as_poc.mir.txt rename to docs/development/testing/golden/typeop_is_as_poc.mir.txt diff --git a/docs/status/golden/typeop_mixed.mir.txt b/docs/development/testing/golden/typeop_mixed.mir.txt similarity index 100% rename from docs/status/golden/typeop_mixed.mir.txt rename to docs/development/testing/golden/typeop_mixed.mir.txt diff --git a/docs/examples/README.md b/docs/examples/README.md index 299bf490..ad2ce604 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -1,14 +1,7 @@ -# Examples: Plugin BoxRef Return (v2.2) +# Examples moved -- File: `plugin_boxref_return.nyash` -- Purpose: Demonstrates a plugin method returning a Box (BoxRef/Handle), and passing Box as an argument. +例は `docs/guides/examples/` に移動しました。 -How to run (after full build): -- Ensure `nyash.toml` includes FileBox with methods: - - `copyFrom = { method_id = 7, args = [ { kind = "box", category = "plugin" } ] }` - - `cloneSelf = { method_id = 8 }` -- Build the plugin: `cd plugins/nyash-filebox-plugin && cargo build --release` -- Run the example: `./target/release/nyash docs/examples/plugin_boxref_return.nyash` +- 新しい場所: [../guides/examples/](../guides/examples/) +- 代表例: [http_result_patterns.md](../guides/examples/http_result_patterns.md) -Expected behavior: -- Creates two FileBox instances (`f`, `g`), writes to `f`, copies content to `g` via `copyFrom`, then closes both. diff --git a/docs/examples/http_result_patterns.md b/docs/examples/http_result_patterns.md index 1c73055c..4908f8f4 100644 --- a/docs/examples/http_result_patterns.md +++ b/docs/examples/http_result_patterns.md @@ -1,32 +1,5 @@ -# HTTP Result Patterns (VM×Plugins) +# Moved: HTTP Result Patterns -目的: Netプラグイン(HTTP)の戻り値モデルをVM視点で明確化します。E2Eの実行方法と、典型ケースでのResultの形をまとめます。 +このドキュメントは再編により移動しました。 +新しい場所: [../guides/examples/http_result_patterns.md](../guides/examples/http_result_patterns.md) -## 実行方法(代表) -```bash -tools/run_vm_stats.sh local_tests/vm_stats_http_ok.nyash vm_stats_ok.json -tools/run_vm_stats.sh local_tests/vm_stats_http_err.nyash vm_stats_err.json -tools/run_vm_stats.sh local_tests/vm_stats_http_404.nyash vm_stats_404.json -tools/run_vm_stats.sh local_tests/vm_stats_http_500.nyash vm_stats_500.json -``` - -## 戻り値モデル -- unreachable(接続不可/タイムアウト等): `Result.Err(ErrorBox)` - - ErrorBoxには原因メッセージ(例: "connection refused", "timeout")が入ります。 -- HTTPステータス 404/500 等: `Result.Ok(Response)` - - `response.status` が 404/500 を保持し、ボディやヘッダーは `response.body`, `response.headers` に格納されます。 - -## 使い分けの意図 -- ネットワーク層の到達不能(transport)は「例外的」な失敗としてErr。 -- アプリ層のHTTPステータスは「通常の結果」としてOkに包む(分岐の簡潔化・情報保持)。 - -## Tips -- デバッグ - - `NYASH_NET_LOG=1 NYASH_NET_LOG_FILE=net_plugin.log` でNetプラグインの内部ログを記録。 - - `NYASH_DEBUG_PLUGIN=1` で VM→Plugin のTLV先頭情報をダンプ。 -- 計測 - - `--vm-stats`/`--vm-stats-json` で命令プロファイルを取得し、BoxCallやNewBoxのホット度を把握。 - -関連ドキュメント -- `docs/reference/architecture/mir-to-vm-mapping.md`(Result/Handleの取り扱い) -- `docs/VM_README.md`(VM統計・既知の制約) diff --git a/docs/guides/contributing-docs.md b/docs/guides/contributing-docs.md index cbeae748..169bb6be 100644 --- a/docs/guides/contributing-docs.md +++ b/docs/guides/contributing-docs.md @@ -29,6 +29,5 @@ One‑pager Template - Notes (constraints / future work) Examples -- Using→Loader overview: docs/design/using-loader-integration.md +- Using→Loader overview: docs/development/design/legacy/using-loader-integration.md - Mini‑VM roadmap: docs/development/roadmap/phases/phase-17-loopform-selfhost/MINI_VM_ROADMAP.md - diff --git a/docs/guides/core-principles.md b/docs/guides/core-principles.md index 3aef2054..cfcd1fe8 100644 --- a/docs/guides/core-principles.md +++ b/docs/guides/core-principles.md @@ -41,8 +41,8 @@ Acceptance & guardrails (feature‑pause) - Golden texts (Ny → MIR fragments) to lock compatibility where practical. - Lint proposals are documentation-only: single-use scope, long `->` chains, duplicated side effects. -Related docs -- proposals/scope-reuse.md — local scope reuse blocks (MVP) -- design/flow-blocks.md — arrow flow + anonymous blocks +- Related docs +- development/proposals/scope-reuse.md — local scope reuse blocks (MVP) +- development/design/legacy/flow-blocks.md — arrow flow + anonymous blocks - reference/language/match-guards.md — guard chains + range/charclass sugar - reference/language/strings.md — UTF‑8 first; proposed digit helpers diff --git a/docs/guides/examples/README.md b/docs/guides/examples/README.md new file mode 100644 index 00000000..299bf490 --- /dev/null +++ b/docs/guides/examples/README.md @@ -0,0 +1,14 @@ +# Examples: Plugin BoxRef Return (v2.2) + +- File: `plugin_boxref_return.nyash` +- Purpose: Demonstrates a plugin method returning a Box (BoxRef/Handle), and passing Box as an argument. + +How to run (after full build): +- Ensure `nyash.toml` includes FileBox with methods: + - `copyFrom = { method_id = 7, args = [ { kind = "box", category = "plugin" } ] }` + - `cloneSelf = { method_id = 8 }` +- Build the plugin: `cd plugins/nyash-filebox-plugin && cargo build --release` +- Run the example: `./target/release/nyash docs/examples/plugin_boxref_return.nyash` + +Expected behavior: +- Creates two FileBox instances (`f`, `g`), writes to `f`, copies content to `g` via `copyFrom`, then closes both. diff --git a/docs/guides/examples/http_result_patterns.md b/docs/guides/examples/http_result_patterns.md new file mode 100644 index 00000000..1c73055c --- /dev/null +++ b/docs/guides/examples/http_result_patterns.md @@ -0,0 +1,32 @@ +# HTTP Result Patterns (VM×Plugins) + +目的: Netプラグイン(HTTP)の戻り値モデルをVM視点で明確化します。E2Eの実行方法と、典型ケースでのResultの形をまとめます。 + +## 実行方法(代表) +```bash +tools/run_vm_stats.sh local_tests/vm_stats_http_ok.nyash vm_stats_ok.json +tools/run_vm_stats.sh local_tests/vm_stats_http_err.nyash vm_stats_err.json +tools/run_vm_stats.sh local_tests/vm_stats_http_404.nyash vm_stats_404.json +tools/run_vm_stats.sh local_tests/vm_stats_http_500.nyash vm_stats_500.json +``` + +## 戻り値モデル +- unreachable(接続不可/タイムアウト等): `Result.Err(ErrorBox)` + - ErrorBoxには原因メッセージ(例: "connection refused", "timeout")が入ります。 +- HTTPステータス 404/500 等: `Result.Ok(Response)` + - `response.status` が 404/500 を保持し、ボディやヘッダーは `response.body`, `response.headers` に格納されます。 + +## 使い分けの意図 +- ネットワーク層の到達不能(transport)は「例外的」な失敗としてErr。 +- アプリ層のHTTPステータスは「通常の結果」としてOkに包む(分岐の簡潔化・情報保持)。 + +## Tips +- デバッグ + - `NYASH_NET_LOG=1 NYASH_NET_LOG_FILE=net_plugin.log` でNetプラグインの内部ログを記録。 + - `NYASH_DEBUG_PLUGIN=1` で VM→Plugin のTLV先頭情報をダンプ。 +- 計測 + - `--vm-stats`/`--vm-stats-json` で命令プロファイルを取得し、BoxCallやNewBoxのホット度を把握。 + +関連ドキュメント +- `docs/reference/architecture/mir-to-vm-mapping.md`(Result/Handleの取り扱い) +- `docs/VM_README.md`(VM統計・既知の制約) diff --git a/docs/examples/plugin_boxref_return.nyash b/docs/guides/examples/plugin_boxref_return.nyash similarity index 100% rename from docs/examples/plugin_boxref_return.nyash rename to docs/guides/examples/plugin_boxref_return.nyash diff --git a/docs/examples/visibility_error.nyash b/docs/guides/examples/visibility_error.nyash similarity index 100% rename from docs/examples/visibility_error.nyash rename to docs/guides/examples/visibility_error.nyash diff --git a/docs/examples/visibility_ok.nyash b/docs/guides/examples/visibility_ok.nyash similarity index 100% rename from docs/examples/visibility_ok.nyash rename to docs/guides/examples/visibility_ok.nyash diff --git a/docs/proposals/README.md b/docs/proposals/README.md new file mode 100644 index 00000000..aaf3de7a --- /dev/null +++ b/docs/proposals/README.md @@ -0,0 +1,6 @@ +# Proposals Moved + +提案(RFC)は `docs/development/proposals/` に集約しました。 + +- 新しい場所: [../development/proposals/](../development/proposals/) + diff --git a/docs/proposals/concurrency/boxes.md b/docs/proposals/concurrency/boxes.md index 25afaf31..76dce17b 100644 --- a/docs/proposals/concurrency/boxes.md +++ b/docs/proposals/concurrency/boxes.md @@ -1,77 +1,5 @@ -# Nyash Concurrency — Box Model (Proposal, docs-only) +# Moved: Concurrency — Box Model (Proposal) -Status: design-only during the feature‑pause. No runtime or spec changes. Implement after Mini‑VM baseline is stable. +この提案は再編により移動しました。 +新しい場所: [../../development/proposals/concurrency/boxes.md](../../development/proposals/concurrency/boxes.md) -Intent -- Bring Go-like CSP (goroutine/channels/select) into Nyash via “Everything is Box”. -- Keep semantics explicit, lifecycle safe (birth/fini), and observable. Phase-in from userland → runtime. - -Scope (Phase‑0: userland MVP) -- RoutineBox: lightweight task wrapper over `nowait` (state, join/cancel, status). -- ChannelBox: bounded/unbounded queue + blocking/non-blocking ops + close semantics. -- SelectBox: multi-channel wait (first-ready) with simple fairness. -- RoutineScopeBox: structured concurrency; children are canceled on scope fini. -- Observability: JSONL trace toggled by `NYASH_CONC_TRACE=1`. - -Non‑Goals (Phase‑0) -- M:N scheduler, OS-level park/unpark, net poller integration (deferred to Phase‑2 runtime work). - -API Sketch (userland) -- RoutineBox - - birth(fn) - - start(): Void - - join(timeout_ms?: Int) -> Bool // true if joined; false on timeout - - cancel(): Void - - status() -> String // ready|running|done|canceled|error -- ChannelBox(capacity: Int=0) - - send(v): Void // blocks if full (Phase‑0: simulated park) - - try_send(v) -> Bool - - receive() -> Any // blocks if empty (Phase‑0: simulated park) - - try_receive() -> (Bool, Any?) - - receive_timeout(ms: Int) -> (Bool, Any?) - - close(): Void // further send fails; recv drains until empty then End -- SelectBox - - birth() - - when(ch: ChannelBox, handler: Fn): Void - - await() -> Bool // returns after one handler runs; false if none ready and no wait policy - - await_timeout(ms: Int) -> Bool -- RoutineScopeBox - - birth() - - spawn(fn) -> RoutineBox - - fini() // cancels pending routines and waits boundedly - -Semantics -- Capacity: - - 0: rendezvous channel (send/recv rendezvous). - - N>0: bounded ring buffer. -- Close: - - close() marks channel as closed. send() after close -> error. receive() returns buffered items; when empty -> (false, End) style result; exact return shape defined per API. -- Blocking: - - Phase‑0 userland uses cooperative wait queues; no busy loops. try_* and timeout variants provided. -- Select fairness: - - If multiple ready, choose random/round‑robin. Starvation avoidance is a design requirement; precise algorithm can evolve. -- Types: - - `TypedChannelBox` is a future extension; Phase‑0 uses runtime tags/guards documented in reference. -- Cancellation: - - RoutineScopeBox cancels children on fini; Channel waits should return (canceled) promptly. - -Phases -- Phase‑0 (userland MVP / PyVM first) - - Implement the 4 boxes above with minimal queues/waits, plus trace hooks. - - Smokes: ping‑pong, bounded producer/consumer, two‑way select, close semantics, scope cancel. -- Phase‑1 (park/unpark abstraction) - - Introduce `WaiterBox`/`CondBox` that map to efficient OS waits where available. Keep same APIs. -- Phase‑2 (runtime integration) - - Scheduler (M:N), GC and net poller integration, fairness and profiling. Keep Box APIs stable. - -Observability -- `NYASH_CONC_TRACE=1` → JSONL events: spawn/join/cancel/send/recv/park/unpark/select/close with routine IDs, channel IDs, timestamps. - -Safety & Diagnostics -- Deadlock hints: trace dependent waits; optional detector (dev only) can dump wait‑for graph. -- API contracts explicitly define error return for misuse (send on closed, double close, etc.). - -Deliverables (docs‑only during the feature‑pause) -- This proposal (boxes & semantics). -- Reference page with blocking/close/select rules (see reference/concurrency/semantics.md). -- Test plan with named smokes and expected outputs. diff --git a/docs/proposals/scope-reuse.md b/docs/proposals/scope-reuse.md index 4102becf..dc931dd8 100644 --- a/docs/proposals/scope-reuse.md +++ b/docs/proposals/scope-reuse.md @@ -1,74 +1,5 @@ -# Scope Reuse Blocks (MVP Proposal) +# Moved: Scope Reuse Blocks (MVP Proposal) -Status: design-only during the feature‑pause (no implementation) +この提案は再編により移動しました。 +新しい場所: [../development/proposals/scope-reuse.md](../development/proposals/scope-reuse.md) -Summary -- Give short, reusable logic a name within the current scope without promoting it to a top-level function. -- Keep the core small: block body + postfix header sugar; desugar to local function + normal calls. -- Zero runtime cost: lowers to let/if/call/ret only (no new instructions/closures). - -Syntax (postfix header; Nyash style) -- Block form (multi-statement): - ```nyash - { /* BODY */ } scope name(arglist?) (-> Ret)? - // call within the same scope - name(args) - ``` -- Expression form (one-liner): - ```nyash - => EXPR scope name(arglist?) (-> Ret)? - ``` - -Semantics -- Visibility: `name` is local to the defining scope; not exported. -- Capture: by reference by default. Mutating captured vars requires explicit `mut` on those bindings. -- Recursion: disallowed in MVP (can be lifted later). -- Errors/exits: same as regular functions (return/cleanup/catch apply at the function boundary). - -Lowering (desugaring) -- Transform into a local function plus a local binding for convenience calls. - ```nyash - // { BODY } scope check(a:Int)->Str - // ↓ (conceptual) - let __cap_me = me; let __cap_locals = { /* needed refs */ }; - method __scope_check__(a:Int)->Str { - return BODY - } - let check = (x) => __scope_check__(x) - ``` -- Captures are passed via hidden arguments or an environment box; no new VM opcodes. - -Examples -```nyash -{ if x % 2 == 0 { return "even" } return "odd" } scope parity(x:Int)->StringBox - -for i in range(0,10) { - print(parity(i)) -} -``` - -Safety rules (MVP) -- Capture: read-only by default; writes allowed only when the captured binding is declared `mut`. -- Name uniqueness: `scope name` must be unique within the scope. -- No cross-scope escape: values may be returned but the function reference itself is not exported. - -Observability & Tooling -- Add trace toggles (design only): - - `NYASH_SCOPE_TRACE=1|json` to emit enter/exit and capture lists as JSONL. - - Example: `{ "ev":"enter","sid":42,"caps":["me","cfg","mut total"] }`. -- Lints (design only): - - Single-use scope → suggest inline. - - Excess captures → suggest narrowing. - -Interactions -- Works with guard/with/await sugars (it’s just a call). -- Compatible with ASI and postfix aesthetics; no new top-level keywords beyond `scope` suffix. - -Tests (syntax-only smokes; design) -- scope_basic: called twice → same result. -- scope_capture_read: reads `me/foo`. -- scope_capture_mut: mutation only allowed when `mut` is present. -- scope_with_catch_cleanup: postfix catch/cleanup applied at local-function boundary. - -Pause note -- This is documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap). diff --git a/docs/proposals/unified-members.md b/docs/proposals/unified-members.md index 91c3a08e..6287a01d 100644 --- a/docs/proposals/unified-members.md +++ b/docs/proposals/unified-members.md @@ -1,137 +1,5 @@ -# Property System Revolution for Nyash (2025-09-18 Breakthrough) +# Moved: Unified Members Proposal -Status: **BREAKTHROUGH COMPLETED** - Final syntax decided through AI collaboration with ChatGPT, Claude, and Codex. +この提案は再編により移動しました。 +新しい場所: [../development/proposals/unified-members.md](../development/proposals/unified-members.md) -## 🌟 Revolutionary Achievement -Today we achieved the **Property System Revolution** - a complete unification of stored fields, computed properties, lazy evaluation, and birth-time initialization into a single, elegant syntax system through AI collaboration with ChatGPT5, Claude, and Codex. - -## 🎯 Final Property System Design - -### The Four-Category Breakthrough -After dialectical discussion with multiple AI agents, we reached the perfect synthesis: - -#### 1. **stored** - Traditional Field Storage -```nyash -box Example { - name: StringBox // Default initialization - count: IntegerBox = 0 // Explicit initialization -} -``` -- **Semantics**: O(1) slot read/write, assignment allowed -- **Use Case**: Traditional object fields, counters, configurations - -#### 2. **computed** - Calculated Every Access -```nyash -box Example { - size: IntegerBox { me.items.count() } - full_name: StringBox { me.first + " " + me.last } -} -``` -- **Semantics**: Evaluate body on each read, assignment error unless setter declared -- **Use Case**: Derived values, dynamic calculations, Python @property equivalent - -#### 3. **once** - Lazy Evaluation with Caching -```nyash -box Example { - once expensive_data: DataBox { heavy_computation() } - once config: ConfigBox { loadConfiguration() } -} -``` -- **Semantics**: Evaluate on first read, cache result, return cached value thereafter -- **Use Case**: Heavy computations, file loading, Python @cached_property equivalent -- **Exception Handling**: Poison-on-throw strategy for safety - -#### 4. **birth_once** - Eager Evaluation at Object Creation -```nyash -box Example { - birth_once startup_data: DataBox { initialize_system() } - - birth() { - // birth_once properties already initialized! - me.ready = true - } -} -``` -- **Semantics**: Evaluated before user birth() in declaration order -- **Use Case**: System initialization, dependency setup, startup-critical data - -## 🌟 Revolutionary Python Integration - -### Perfect Mapping Strategy -```python -# Python side -class DataProcessor: - def __init__(self): - self.value = 42 # → stored - - @property - def computed_result(self): # → computed - return self.value * 2 - - @functools.cached_property - def expensive_data(self): # → once - return heavy_computation() -``` - -```nyash -// Auto-generated Nyash (revolutionary 1:1 mapping!) -box DataProcessor { - value: IntegerBox // stored - computed_result: IntegerBox { me.value * 2 } // computed - once expensive_data: ResultBox { heavy_computation() } // once - - birth() { - me.value = 42 - } -} -``` - -### Performance Revolution -- **computed properties**: No caching overhead, pure calculation -- **once properties**: 10-50x faster than Python cached_property (LLVM optimization) -- **birth_once properties**: Startup optimization, dependency injection pattern -- **Overall**: Python code → 5-20x faster native binary - -Handlers (Stage‑3) -- Postfix `catch/cleanup` are allowed for computed/once/birth_once/method blocks. -- Stored does not accept handlers. - -Semantics -- stored: O(1) slot read; `= expr` evaluated once during construction; assignment allowed. -- computed: evaluate on each read; assignment is an error unless a setter is declared. -- once: evaluate on first read, cache the result, and return it thereafter. If the first evaluation throws and there is no `catch`, mark poisoned and rethrow the same error on later reads (no retry). -- birth_once: evaluated before user `birth` body in declaration order; uncaught error aborts construction. Cycles are rejected. - -Lowering (no JSON v0 change) -- stored → slot -- computed → synthesize `__get_name():T { try body; catch; finally }`, resolve reads to call -- once → add hidden `__name: Option` and first-read initialization in `__get_name()`; poison on uncaught error -- birth_once → hidden `__name: T` initialized before user `birth` body in declaration order; handler blocks apply per initializer -- method → unchanged; postfix handlers lower to try/catch/finally - -EBNF (delta) -``` -box_decl := 'box' IDENT '{' member* '}' -member := stored | computed | once_decl | birth_once_decl | method_decl -stored := IDENT ':' TYPE ( '=' expr )? -computed := IDENT ':' TYPE block handler_tail? -once_decl := 'once' IDENT ':' TYPE block handler_tail? -birth_once_decl:= 'birth_once' IDENT ':' TYPE block handler_tail? -method_decl := IDENT '(' params? ')' ( ':' TYPE )? block handler_tail? -handler_tail := ( catch_block )? ( cleanup_block )? -catch_block := 'catch' ( '(' ( IDENT IDENT | IDENT )? ')' )? block -cleanup_block := 'cleanup' block -``` - -Diagnostics -- Assignment to computed/once/birth_once: error with fix-it (“define a setter or use stored property”). -- Once poison: first read throws → remember error; subsequent reads rethrow immediately. -- Birth order: evaluated before user `birth`, in declaration order; cycle detection emits a clear error with the chain. - -Flags -- Parser gate: `NYASH_ENABLE_UNIFIED_MEMBERS=1` -- Stage‑3 for handlers: `NYASH_PARSER_STAGE3=1` - -Notes -- User experience: read is uniform (`obj.name`), write differs by kind; this keeps mental model simple. -- Future: setter syntax (`name: T { get {…} set(v) {…} }`) and aliases (`slot/calc/lazy`) can be added without breaking this core. diff --git a/docs/reference/architecture/dynamic-plugin-flow.md b/docs/reference/architecture/dynamic-plugin-flow.md index 451805b6..5f8155cc 100644 --- a/docs/reference/architecture/dynamic-plugin-flow.md +++ b/docs/reference/architecture/dynamic-plugin-flow.md @@ -114,5 +114,5 @@ Nyash Source ──▶ MIR (Builder) - `docs/reference/architecture/mir-26-instruction-diet.md` See also -- `docs/examples/http_result_patterns.md` - HTTPのResult挙動(unreachable/404/500)のE2E例 +- `docs/guides/examples/http_result_patterns.md` - HTTPのResult挙動(unreachable/404/500)のE2E例 - `docs/VM_README.md` - VM統計とプラグイン周りの既知制約 diff --git a/docs/reference/architecture/phi-and-ssa.md b/docs/reference/architecture/phi-and-ssa.md new file mode 100644 index 00000000..fb836dd7 --- /dev/null +++ b/docs/reference/architecture/phi-and-ssa.md @@ -0,0 +1,23 @@ +# PHI and SSA in Nyash + +Overview +- Nyash lowers high-level control flow (If/Loop/Match) to MIR and backends that rely on SSA form. +- We prioritize IR hygiene and observability while keeping runtime cost at zero. + +Design points +- PHI hygiene: no empty PHIs; PHIs at block head only. +- JoinResult hint: when both branches assign the same variable, we emit a MIR hint for diagnostics. +- Loop carriers: loops may expose a carrier observation (≤ N variables, where N is unconstrained by design; smokes emphasize common cases). + +Normalization +- If: may optionally wrap into LoopForm under a conservative gate (dev only). Semantics remain unchanged. +- Match: scrutinee evaluated once, guard fused; normalized to nested If‑chain in macro/core pass. + +Testing +- LLVM smokes: fixed small cases ensure no empty PHIs and head placement. +- MIR smokes: trace `scope|join|loop` to validate shaping without peeking into IR details. + +Roadmap +- Remove text-level sanitization once finalize‑PHI is trustworthy across Loop/If/Match. +- Expand goldens to cover nested joins and multi‑carrier loops while keeping CI light. + diff --git a/docs/reference/config/nyash-toml.md b/docs/reference/config/nyash-toml.md new file mode 100644 index 00000000..71ad0fad --- /dev/null +++ b/docs/reference/config/nyash-toml.md @@ -0,0 +1,85 @@ +# nyash.toml — Configuration Reference (Phase 15.5) + +Status: Proposed(受け口から段階導入。未指定時は現行既定を維持) + +## 目的 +- 依存関係・実行方針の**唯一の真実(SSOT)**。 +- using の解決(AST プレリュード)と、将来の Provider/Type 分離(受け口)を一元管理。 + +## セクション一覧 + +### [env] +任意の既定環境変数(`NYASH_*`)。CI/ローカルで上書き可。 + +### [using] +検索ルート `paths = ["apps","lib","."]`、名前付きパッケージ `[using.]`、エイリアス `[using.aliases]`。 + +例: +```toml +[using] +paths = ["apps", "lib", "."] + +[using.json_native] +path = "apps/lib/json_native/" +main = "parser/parser.nyash" + +[using.string_utils] +path = "apps/lib/json_native/utils/string.nyash" + +[using.aliases] +json = "json_native" +StringUtils = "string_utils" +``` + +### Provider/Type(受け口・既定OFF) + +Stable Type Name(STN)を Provider ID(PVN)にマッピング。未指定時は現行ランタイム既定。 + +```toml +[types.StringBox] +provider = "kernel:string@1.0" +interop = "forbid" # forbid|explicit|auto(既定: forbid) + +[providers."kernel:string@1.0"] +crate = "nyash-plugin-base-string" # 静的リンクのブートストラップ提供者 + +[providers."acme:string@2.1"] +path = "./plugins/libacme_string.so" +override = true + +[policy] +factory = "plugin-first" # plugin-first|compat_plugin_first|static_only +``` + +注意: +- 本仕様は「受け口」の段階。実行挙動は段階導入(Verify→Lock→実行)。 +- 互換性重視のため、未指定時は現行と同じ既定にフォールバックする。 + +### [plugins.bootstrap] / [plugins.dynamic](提案) +静的リンクのブートストラップ束/動的ロード(開発)を明示。 + +```toml +[plugins.bootstrap] +string = { crate = "nyash-plugin-base-string", version = "2.3.0" } +integer = { crate = "nyash-plugin-base-integer", version = "1.5.1" } + +[plugins.dynamic] +# string = { path = "./plugins/libnyash_string_plugin.so", override = true } +``` + +## Profiles(using / AST) +`NYASH_USING_PROFILE={dev|ci|prod}` + +- dev/ci: AST プレリュード既定ON(file-usingはdevで許可、ciは警告/限定) +- prod: AST 既定OFF(toml 由来のみ、file-using はエラー) + +実装ノート: +- AST 既定は `src/config/env.rs: using_ast_enabled()` でプロファイルに従い決定。 +- 既存のレガシー前置きは prod で禁止、dev/ci でも段階的に削除予定。 + +## Verify(plugin-tester) +CI/起動前に最低限の契約を検査(例: String の `birth/fini/toUtf8/fromUtf8/equals/length/concat`)。欠落時は即停止。 + +## 参考 +- Kernel/Plugin 方針: docs/reference/runtime/kernel-and-plugins.md +- ADR: docs/development/adr/adr-001-no-corebox-everything-is-plugin.md diff --git a/docs/reference/language/using.md b/docs/reference/language/using.md index 4ce458c9..ae028050 100644 --- a/docs/reference/language/using.md +++ b/docs/reference/language/using.md @@ -64,7 +64,7 @@ pub enum QualifiedCallee { Policy - Accept `using` lines at the top of the file to declare module namespaces or file imports. - Resolution is performed by the Rust Runner when `NYASH_ENABLE_USING=1`. -- 実体の結合は AST マージのみ。テキストの前置き/連結は行わない(移行完了後に完全廃止)。 +- 実体の結合は AST マージのみ。テキストの前置き/連結は行わない(レガシー経路は呼び出し側から削除済み)。 - Runner は `nyash.toml` の `[using]` を唯一の真実として参照(prod)。dev/ci は段階的に緩和可能。 - Selfhost compiler (Ny→JSON v0) collects using lines and emits `meta.usings` when present. The bridge currently ignores this meta field. @@ -191,15 +191,18 @@ Runner Configuration - Enable using pre‑processing: `NYASH_ENABLE_USING=1` - CLI from-the-top registration: `--using "ns as Alias"` or `--using '"apps/foo.nyash" as Foo'` (repeatable) - Using profiles (phase‑in): `NYASH_USING_PROFILE={dev|ci|prod}` - - dev: toml + file using(path)可、AST マージ、候補提示 ON - - ci: toml 優先、file using は警告/限定、AST マージ、フォールバック OFF - - prod: toml のみ、file using/path はエラー(追記ガイドを表示) + - dev: AST マージ 既定ON、legacy前置きは既定で無効(必要時は `NYASH_LEGACY_USING_ALLOW=1` で一時許可) + - ci: AST マージ 既定ON、legacy前置きは既定で無効(同上の一時許可) + - prod: AST マージ 既定OFF、toml のみ(file using/path はエラー・追記ガイド) - Strict mode (plugin prefix required): `NYASH_PLUGIN_REQUIRE_PREFIX=1` または `nyash.toml` の `[plugins] require_prefix=true` - Aliases from env: `NYASH_ALIASES="Foo=apps/foo/main.nyash,Bar=lib/bar.nyash"` - Additional search paths: `NYASH_USING_PATH="apps:lib:."` - Selfhost pipeline keeps child stdout quiet and extracts JSON only: `NYASH_JSON_ONLY=1` (set by Runner automatically for child) - Selfhost emits `meta.usings` automatically when present; no additional flags required. +Note: Provider/Type 分離(型名は不変で提供者のみを切替)については ADR を参照。 +docs/development/adr/adr-001-no-corebox-everything-is-plugin.md + ## 🔬 Quick Smokes(AST + Profiles) 開発・CIで最小コストに確認できるスモークを用意しています。AST プレリュードとプロファイル(dev/prod)の基本動作をカバーします。 diff --git a/docs/reference/runtime/kernel-and-plugins.md b/docs/reference/runtime/kernel-and-plugins.md new file mode 100644 index 00000000..458a6499 --- /dev/null +++ b/docs/reference/runtime/kernel-and-plugins.md @@ -0,0 +1,51 @@ +# NyKernel と Plugins — 最小ランタイムとプラグイン体系(Phase 15.5) + +Status: Proposed (受け口のみ; 既定OFF) +ADR: docs/development/adr/adr-001-no-corebox-everything-is-plugin.md + +## 目的 +- Kernel を最小化(箱を持たない)し、機能はすべて Plugin で提供する。 +- VM/LLVM 双方から同一 ABI(`ny_new_box` / `ny_call_method`)で箱を扱う。 + +## 起動シーケンス(標準形) +1) NyKernel init(GC/Handle/TLV/Extern/PluginRegistry) +2) nyash.toml 読み込み + - `plugins.bootstrap`(静的束)を登録 + - `plugins.dynamic`(.so/.dll)があれば dlopen 登録 +3) Plugin Verify(必須メソッド/TLV/ABI) +4) 実行(VM/LLVM → `ny_call_method`) + +ブートの不変条件(重要) +- Provider Lock(型→提供者の対応表)が確定するまで、いかなる Box 生成も禁止。 +- Kernel ログ(生バイト)で初期エラーを出力し、`StringBox` 等の Box をログ用途に使わない。 +- Verify に失敗した場合は、Kernel ログで理由を表示して即終了する。 + +## Provider/Type 分離(概要) +- Stable Type Name(STN): `StringBox`, `IntegerBox` など。コード上の型名は不変。 +- Provider ID(PVN): `kernel:string@1.0` / `acme:string@2.1` など実装提供者。 +- TOML で STN→PVN をバインドし、置換は TOML 側で行う。 + +Provider Lock(ロック) +- 起動時に `types.` の provider を決定し、Provider Lock を作成・固定する。 +- Lock 前の `ny_new_box` / `ny_call_method` はエラー(E_PROVIDER_NOT_LOCKED)。 +- Handle は `{ type_id, provider_id }` を保持し、デバッグビルドでは不一致検知時に panic(本番では混入しない設計)。 + +## ポリシー(例) +- `plugin-first`(デフォルト): 動的プラグインの上書きを許可 +- `compat_plugin_first`: 静的→動的のフォールバックを許可(移行期) +- `static_only`(本番): 静的のみ許可 + +Interop(同一型の異 Provider 混在) +- 既定は混在禁止(forbid)。同一プロセス内で 1 Type = 1 Provider を維持する。 +- 研究・開発用途でのみ `explicit/auto` を許可できるが、本番非推奨。 + - explicit: 明示 API による変換のみ許可(UTF‑8 などの正規形式を介する) + - auto: 暗黙変換を許可し、変換回数・バイト数をメトリクスに集計(本番非推奨) + +## 現状の段階 +- 受け口/ドキュメントの整備を先行(挙動は不変)。 +- using は SSOT+AST に移行済み(prod は file-using 禁止)。 +- VM fallback の個別救済は暫定(短期で Bootstrap Pack へ移行し撤去)。 + +関連ドキュメント +- nyash.toml のスキーマと例: docs/reference/config/nyash-toml.md +- using(SSOT/AST/Profiles): docs/reference/language/using.md diff --git a/docs/status/README.md b/docs/status/README.md new file mode 100644 index 00000000..faf3801a --- /dev/null +++ b/docs/status/README.md @@ -0,0 +1,6 @@ +# Status/Golden moved + +ゴールデン出力などのステータス関連ドキュメントは `docs/development/testing/golden/` へ移動しました。 + +- 新しい場所: [../development/testing/golden/](../development/testing/golden/) + diff --git a/docs/tests/README.md b/docs/tests/README.md new file mode 100644 index 00000000..e9c392eb --- /dev/null +++ b/docs/tests/README.md @@ -0,0 +1,6 @@ +# Tests moved + +テスト関連のドキュメントは `docs/development/testing/` に移動しました。 + +- 新しい場所: [../development/testing/](../development/testing/) + diff --git a/nyash.toml b/nyash.toml index 72ca0347..7bb402f4 100644 --- a/nyash.toml +++ b/nyash.toml @@ -238,6 +238,14 @@ hexEncode = { method_id = 8 } hexDecode = { method_id = 9 } fini = { method_id = 4294967295 } +# Provider Verify — minimal required methods (dev warn by default) +[verify.required_methods] +# Note: Dynamic plugins may not be present in dev; preflight runs in warn mode. +StringBox = ["length", "concat"] +ArrayBox = ["len", "push", "get"] +MapBox = ["has", "get", "set"] +ConsoleBox = ["print"] + [libraries."libnyash_json_plugin.so"] boxes = ["JsonDocBox", "JsonNodeBox"] path = "plugins/nyash-json-plugin/libnyash_json_plugin.so" diff --git a/src/backend/mir_interpreter/handlers/boxes.rs b/src/backend/mir_interpreter/handlers/boxes.rs index 48b7503e..d1328477 100644 --- a/src/backend/mir_interpreter/handlers/boxes.rs +++ b/src/backend/mir_interpreter/handlers/boxes.rs @@ -8,6 +8,10 @@ impl MirInterpreter { box_type: &str, args: &[ValueId], ) -> Result<(), VMError> { + // Provider Lock guard (受け口・既定は挙動不変) + if let Err(e) = crate::runtime::provider_lock::guard_before_new_box(box_type) { + return Err(VMError::InvalidInstruction(e)); + } let mut converted: Vec> = Vec::with_capacity(args.len()); for vid in args { converted.push(self.reg_load(*vid)?.to_nyash_box()); diff --git a/src/config/env.rs b/src/config/env.rs index f2bbbaaf..556579f8 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -354,6 +354,17 @@ pub fn allow_using_file() -> bool { _ => true, // dev/ci default: allowed } } +/// Determine whether AST prelude merge for `using` is enabled. +/// Precedence: +/// 1) Explicit env `NYASH_USING_AST` = 1/true/on → enabled, = 0/false/off → disabled +/// 2) Default by profile: dev/ci → ON, prod → OFF +pub fn using_ast_enabled() -> bool { + match std::env::var("NYASH_USING_AST").ok().as_deref().map(|v| v.to_ascii_lowercase()) { + Some(ref s) if s == "1" || s == "true" || s == "on" => true, + Some(ref s) if s == "0" || s == "false" || s == "off" => false, + _ => !using_is_prod(), // dev/ci → true, prod → false + } +} pub fn resolve_fix_braces() -> bool { // Safer default: OFF(誤補正の副作用を避ける) // 明示ON: NYASH_RESOLVE_FIX_BRACES=1 diff --git a/src/parser/statements/mod.rs b/src/parser/statements/mod.rs index 6c2fa516..26dc8c4d 100644 --- a/src/parser/statements/mod.rs +++ b/src/parser/statements/mod.rs @@ -152,20 +152,6 @@ impl NyashParser { while !self.is_at_end() && !self.match_token(&TokenType::RBRACE) { statements.push(self.parse_statement()?); - // Conservative seam guard: apply only when env is ON, we just consumed a '}' - // (end of a nested block), and the next tokens at the top-level look like a - // method head. This limits the guard to real seams between members. - if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT").ok().as_deref() == Some("1") { - // If the next token would close the current method, do not guard here - if self.match_token(&TokenType::RBRACE) { break; } - // Check if we just consumed a '}' token from inner content - let just_saw_rbrace = if self.current > 0 { - matches!(self.tokens[self.current - 1].token_type, TokenType::RBRACE) - } else { false }; - if just_saw_rbrace && looks_like_method_head(self) { - break; - } - } } if trace_blocks { eprintln!( diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index f09874b1..a51451c6 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -49,16 +49,20 @@ impl NyashRunner { println!("\n🚀 Parsing and executing...\n"); } - // Using handling: either strip+inline (legacy) or AST-based prelude merge (when NYASH_USING_AST=1) - let use_ast = std::env::var("NYASH_USING_AST").ok().as_deref() == Some("1"); + // Using handling: AST-based prelude collection (legacy inlining removed) + let use_ast = crate::config::env::using_ast_enabled(); let mut code_ref: &str = &code; let cleaned_code_owned; let mut prelude_asts: Vec = Vec::new(); if crate::config::env::enable_using() { - if use_ast { - match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) { - Ok((clean, paths)) => { - cleaned_code_owned = clean; code_ref = &cleaned_code_owned; + match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) { + Ok((clean, paths)) => { + cleaned_code_owned = clean; code_ref = &cleaned_code_owned; + if !paths.is_empty() && !use_ast { + eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."); + std::process::exit(1); + } + if use_ast { // Parse each prelude file into AST and store for p in paths { match std::fs::read_to_string(&p) { @@ -72,13 +76,8 @@ impl NyashRunner { } } } - Err(e) => { eprintln!("❌ {}", e); std::process::exit(1); } - } - } else { - match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code, filename) { - Ok(s) => { cleaned_code_owned = s; code_ref = &cleaned_code_owned; } - Err(e) => { eprintln!("❌ {}", e); std::process::exit(1); } } + Err(e) => { eprintln!("❌ {}", e); std::process::exit(1); } } } // Optional dev sugar: @name[:T] = expr → local name[:T] = expr (line-head only) diff --git a/src/runner/modes/common_util/resolve/mod.rs b/src/runner/modes/common_util/resolve/mod.rs index bab779f1..9b93d2f9 100644 --- a/src/runner/modes/common_util/resolve/mod.rs +++ b/src/runner/modes/common_util/resolve/mod.rs @@ -8,4 +8,4 @@ pub mod strip; pub mod seam; // Public re-exports to preserve existing call sites -pub use strip::{strip_using_and_register, preexpand_at_local, collect_using_and_strip, resolve_prelude_paths_profiled}; +pub use strip::{preexpand_at_local, collect_using_and_strip, resolve_prelude_paths_profiled}; diff --git a/src/runner/modes/common_util/resolve/strip.rs b/src/runner/modes/common_util/resolve/strip.rs index ace36e46..9823e4c4 100644 --- a/src/runner/modes/common_util/resolve/strip.rs +++ b/src/runner/modes/common_util/resolve/strip.rs @@ -1,431 +1,4 @@ use crate::runner::NyashRunner; -use std::collections::HashSet; - -/// Generate content for built-in namespaces like builtin:nyashstd -fn generate_builtin_namespace_content(namespace_key: &str) -> String { - match namespace_key { - "builtin:nyashstd" => { - // Generate Nyash code that provides nyashstd functionality - // This exposes the built-in stdlib boxes as regular Nyash static boxes - format!(r#" -// Built-in nyashstd namespace (auto-generated) -static box string {{ - create(text) {{ - return new StringBox(text) - }} - upper(str) {{ - return new StringBox(str.upper()) - }} -}} - -static box integer {{ - create(value) {{ - return new IntegerBox(value) - }} -}} - -static box bool {{ - create(value) {{ - return new BoolBox(value) - }} -}} - -static box array {{ - create() {{ - return new ArrayBox() - }} -}} - -static box console {{ - log(message) {{ - print(message) - return null - }} -}} -"#) - } - _ => { - // Unknown built-in namespace - format!("// Unknown built-in namespace: {}\n", namespace_key) - } - } -} - -/// Strip `using` lines and register modules/aliases into the runtime registry. -/// Returns cleaned source. No-op when `NYASH_ENABLE_USING` is not set. -#[allow(dead_code)] -pub fn strip_using_and_register( - runner: &NyashRunner, - code: &str, - filename: &str, -) -> Result { - if !crate::config::env::enable_using() { - return Ok(code.to_string()); - } - // Profile guard: legacy text inlining is not allowed under prod profile - if crate::config::env::using_is_prod() { - return Err("using: text inlining is disabled in prod profile. Enable NYASH_USING_AST=1 and declare dependencies in nyash.toml [using]".to_string()); - } - // Optional external combiner (default OFF): NYASH_USING_COMBINER=1 - if std::env::var("NYASH_USING_COMBINER").ok().as_deref() == Some("1") { - let fix_braces = crate::config::env::resolve_fix_braces(); - let dedup_box = std::env::var("NYASH_RESOLVE_DEDUP_BOX").ok().as_deref() == Some("1"); - let dedup_fn = std::env::var("NYASH_RESOLVE_DEDUP_FN").ok().as_deref() == Some("1"); - let seam_dbg = std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1"); - let mut cmd = std::process::Command::new("python3"); - cmd.arg("tools/using_combine.py").arg("--entry").arg(filename); - if fix_braces { cmd.arg("--fix-braces"); } - if dedup_box { cmd.arg("--dedup-box"); } - if dedup_fn { cmd.arg("--dedup-fn"); } - if seam_dbg { cmd.arg("--seam-debug"); } - match cmd.output() { - Ok(out) => { - if out.status.success() { - let combined = String::from_utf8_lossy(&out.stdout).to_string(); - return Ok(preexpand_at_local(&combined)); - } else { - let err = String::from_utf8_lossy(&out.stderr); - return Err(format!("using combiner failed: {}", err)); - } - } - Err(e) => return Err(format!("using combiner spawn error: {}", e)), - } - } - - fn strip_and_inline( - runner: &NyashRunner, - code: &str, - filename: &str, - visited: &mut HashSet, - ) -> Result { - let mut out = String::with_capacity(code.len()); - let mut prelude = String::new(); - let mut used: Vec<(String, Option)> = Vec::new(); - for line in code.lines() { - let t = line.trim_start(); - if t.starts_with("using ") { - crate::cli_v!("[using] stripped line: {}", line); - let rest0 = t.strip_prefix("using ").unwrap().trim(); - // Strip trailing inline comments - let rest0 = rest0.split('#').next().unwrap_or(rest0).trim(); - let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim(); - let (target, alias) = if let Some(pos) = rest0.find(" as ") { - (rest0[..pos].trim().to_string(), Some(rest0[pos + 4..].trim().to_string())) - } else { (rest0.to_string(), None) }; - let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash"); - if is_path { - let path = target.trim_matches('"').to_string(); - let name = alias.clone().unwrap_or_else(|| { - std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string() - }); - used.push((name, Some(path))); - } else { - used.push((target, alias)); - } - continue; - } - out.push_str(line); - out.push('\n'); - } - // Register and inline - let using_ctx = runner.init_using_context(); - let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1"); - let verbose = crate::config::env::cli_verbose(); - let ctx_dir = std::path::Path::new(filename).parent(); - let trace = verbose || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1"); - let seam_dbg = std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1"); - if trace { - eprintln!( - "[using] ctx: modules={} using_paths={}", - using_ctx.pending_modules.len(), - using_ctx.using_paths.join(":") - ); - } - for (ns, alias_opt) in used { - // Two forms: - // - using path "..." [as Alias] - // - using namespace.with.dots [as Alias] - let resolved_path = if let Some(alias_or_path) = alias_opt { - // Disambiguate: when alias_opt looks like a file path, treat it as direct path. - let is_path_hint = alias_or_path.ends_with(".nyash") - || alias_or_path.contains('/') - || alias_or_path.contains('\\'); - if is_path_hint { - // Direct path provided (e.g., using "path/file.nyash" as Name) - let value = alias_or_path.clone(); - // Register: Name -> path - let sb = crate::box_trait::StringBox::new(value.clone()); - crate::runtime::modules_registry::set(ns.clone(), Box::new(sb)); - Some(value) - } else { - // alias string for a namespace (e.g., using ns.token as Alias) - let alias = alias_or_path; - // alias case: resolve namespace to a concrete path - let mut found: Option = using_ctx - .pending_modules - .iter() - .find(|(n, _)| n == &ns) - .map(|(_, p)| p.clone()); - if trace { - if let Some(f) = &found { - eprintln!("[using/resolve] alias '{}' -> '{}'", ns, f); - } - } - if found.is_none() { - match crate::runner::pipeline::resolve_using_target( - &ns, - false, - &using_ctx.pending_modules, - &using_ctx.using_paths, - &using_ctx.aliases, - &using_ctx.packages, - ctx_dir, - strict, - verbose, - ) { - Ok(v) => { - // Treat unchanged token (namespace) as unresolved - if v == ns { found = None; } else { found = Some(v) } - } - Err(e) => return Err(format!("using: {}", e)), - } - } - if let Some(value) = found.clone() { - let sb = crate::box_trait::StringBox::new(value.clone()); - crate::runtime::modules_registry::set(alias.clone(), Box::new(sb)); - let sb2 = crate::box_trait::StringBox::new(value.clone()); - crate::runtime::modules_registry::set(ns.clone(), Box::new(sb2)); - // Optional: autoload dylib when using kind="dylib" and NYASH_USING_DYLIB_AUTOLOAD=1 - if value.starts_with("dylib:") && std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") { - let lib_path = value.trim_start_matches("dylib:"); - // Derive lib name from file stem (strip leading 'lib') - let p = std::path::Path::new(lib_path); - if let Some(stem) = p.file_stem().and_then(|s| s.to_str()) { - let mut lib_name = stem.to_string(); - if lib_name.starts_with("lib") { lib_name = lib_name.trim_start_matches("lib").to_string(); } - // Determine box list from using packages (prefer [using.].bid) - let mut boxes: Vec = Vec::new(); - if let Some(pkg) = using_ctx.packages.get(&ns) { - if let Some(b) = &pkg.bid { boxes.push(b.clone()); } - } - if verbose { eprintln!("[using] autoload dylib: {} as {} boxes=[{}]", lib_path, lib_name, boxes.join(",")); } - let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); - let _ = host.read().unwrap().load_library_direct(&lib_name, lib_path, &boxes); - } - } - } else if trace { - eprintln!("[using] still unresolved: {} as {}", ns, alias); - } - found - } - } else { - // direct namespace without alias - match crate::runner::pipeline::resolve_using_target( - &ns, - false, - &using_ctx.pending_modules, - &using_ctx.using_paths, - &using_ctx.aliases, - &using_ctx.packages, - ctx_dir, - strict, - verbose, - ) { - Ok(value) => { - let sb = crate::box_trait::StringBox::new(value.clone()); - let ns_clone = ns.clone(); - crate::runtime::modules_registry::set(ns_clone, Box::new(sb)); - // Optional: autoload dylib when using kind="dylib" - if value.starts_with("dylib:") && std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") { - let lib_path = value.trim_start_matches("dylib:"); - let p = std::path::Path::new(lib_path); - if let Some(stem) = p.file_stem().and_then(|s| s.to_str()) { - let mut lib_name = stem.to_string(); - if lib_name.starts_with("lib") { lib_name = lib_name.trim_start_matches("lib").to_string(); } - let mut boxes: Vec = Vec::new(); - if let Some(pkg) = using_ctx.packages.get(&ns) { - if let Some(b) = &pkg.bid { boxes.push(b.clone()); } - } - if verbose { eprintln!("[using] autoload dylib: {} as {} boxes=[{}]", lib_path, lib_name, boxes.join(",")); } - let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); - let _ = host.read().unwrap().load_library_direct(&lib_name, lib_path, &boxes); - } - } - Some(value) - } - Err(e) => return Err(format!("using: {}", e)), - } - }; - if let Some(path) = resolved_path { - // Resolve relative to current file dir - // Guard: skip obvious namespace tokens (ns.ns without extension) - if (!path.contains('/') && !path.contains('\\')) && !path.ends_with(".nyash") && path.contains('.') { - if verbose { eprintln!("[using] unresolved '{}' (namespace token, skip inline)", path); } - continue; - } - let mut p = std::path::PathBuf::from(&path); - if p.is_relative() { - if !p.exists() { - if let Some(dir) = std::path::Path::new(filename).parent() { - let cand = dir.join(&p); - if cand.exists() { p = cand; } - } - } - } - if let Ok(abs) = std::fs::canonicalize(&p) { p = abs; } - let key = p.to_string_lossy().to_string(); - if visited.contains(&key) { - if verbose { eprintln!("[using] skipping already visited: {}", key); } - continue; - } - visited.insert(key.clone()); - if let Ok(text) = std::fs::read_to_string(&p) { - let inlined = strip_and_inline(runner, &text, &key, visited)?; - prelude.push_str(&inlined); - prelude.push_str("\n"); - crate::runner::modes::common_util::resolve::seam::log_inlined_tail(&key, &inlined, seam_dbg); - } else if key.starts_with("builtin:") { - // Handle built-in namespaces like builtin:nyashstd - let builtin_content = generate_builtin_namespace_content(&key); - prelude.push_str(&builtin_content); - prelude.push_str("\n"); - if verbose { - eprintln!("[using] loaded builtin namespace: {}", key); - } - } else if verbose { - eprintln!("[using] warn: could not read {}", p.display()); - } - } - } - if prelude.is_empty() { return Ok(out); } - // Optional de-dup of static boxes by name - let mut prelude_text = prelude; - if std::env::var("NYASH_RESOLVE_DEDUP_BOX").ok().as_deref() == Some("1") { - let mut seen: std::collections::HashSet = std::collections::HashSet::new(); - let mut out_txt = String::with_capacity(prelude_text.len()); - let bytes: Vec = prelude_text.chars().collect(); - let mut i = 0usize; - while i < bytes.len() { - if i + 12 < bytes.len() && bytes[i..].iter().take(11).collect::() == "static box " { - let mut j = i + 11; - let mut name = String::new(); - while j < bytes.len() { - let c = bytes[j]; - if c.is_alphanumeric() || c == '_' { name.push(c); j += 1; } else { break; } - } - while j < bytes.len() && bytes[j].is_whitespace() { j += 1; } - if j < bytes.len() && bytes[j] == '{' { - let mut k = j; - let mut depth = 0i32; - while k < bytes.len() { - let c = bytes[k]; - if c == '{' { depth += 1; } - if c == '}' { depth -= 1; if depth == 0 { k += 1; break; } } - k += 1; - } - if seen.contains(&name) { i = k; continue; } else { - seen.insert(name); - out_txt.push_str(&bytes[i..k].iter().collect::()); - i = k; continue; - } - } - } - out_txt.push(bytes[i]); - i += 1; - } - prelude_text = out_txt; - } - // Optional: function dedup (MiniVmPrints.print_prints_in_slice) - if std::env::var("NYASH_RESOLVE_DEDUP_FN").ok().as_deref() == Some("1") { - let mut out_txt = String::with_capacity(prelude_text.len()); - let bytes: Vec = prelude_text.chars().collect(); - let mut i = 0usize; - while i < bytes.len() { - let ahead: String = bytes[i..bytes.len().min(i + 12)].iter().collect(); - if ahead.starts_with("static box ") { - let mut j = i + 11; - let mut name = String::new(); - while j < bytes.len() { let c = bytes[j]; if c.is_ascii_alphanumeric() || c == '_' { name.push(c); j += 1; } else { break; } } - while j < bytes.len() && bytes[j].is_whitespace() { j += 1; } - if j < bytes.len() && bytes[j] == '{' { - let mut k = j; - let mut depth = 0i32; - let mut in_str = false; - while k < bytes.len() { - let c = bytes[k]; - if in_str { if c == '\\' { k += 2; continue; } if c == '"' { in_str = false; } k += 1; continue; } else { if c == '"' { in_str = true; k += 1; continue; } if c == '{' { depth += 1; } if c == '}' { depth -= 1; if depth == 0 { k += 1; break; } } k += 1; } - } - out_txt.push_str(&bytes[i..(j + 1)].iter().collect::()); - let body_end = k.saturating_sub(1); - if name == "MiniVmPrints" { - let mut kept = false; - let mut p = j + 1; - while p <= body_end { - let mut ls = p; if ls > j + 1 { while ls <= body_end && bytes[ls - 1] != '\n' { ls += 1; } } - if ls > body_end { break; } - let mut q = ls; while q <= body_end && bytes[q].is_whitespace() && bytes[q] != '\n' { q += 1; } - let rem: String = bytes[q..(body_end + 1).min(q + 64)].iter().collect(); - if rem.starts_with("print_prints_in_slice(") { - let mut r = q; let mut dp = 0i32; let mut instr = false; - while r <= body_end { - let c = bytes[r]; - if instr { if c == '\\' { r += 2; continue; } if c == '"' { instr = false; } r += 1; continue; } - if c == '"' { instr = true; r += 1; continue; } - if c == '(' { dp += 1; } - if c == ')' { dp -= 1; if dp == 0 { r += 1; break; } } - if dp == 0 && c == '{' { break; } - r += 1; - } - while r <= body_end && bytes[r].is_whitespace() { r += 1; } - if r <= body_end && bytes[r] == '{' { - let mut s = r; let mut bd = 0i32; let mut is2 = false; - while s <= body_end { - let c = bytes[s]; - if is2 { if c == '\\' { s += 2; continue; } if c == '"' { is2 = false; } s += 1; continue; } - if c == '"' { is2 = true; s += 1; continue; } - if c == '{' { bd += 1; } - if c == '}' { bd -= 1; if bd == 0 { s += 1; break; } } - s += 1; - } - if !kept { - out_txt.push_str(&bytes[q..s].iter().collect::()); - kept = true; - } - // advance outer scanner to the end of this function body - i = s; - let _ = i; // mark as read to satisfy unused_assignments lint - continue; - } - } - out_txt.push(bytes[p]); p += 1; - } - if !kept { out_txt.push_str(&bytes[j + 1..=body_end].iter().collect::()); } - out_txt.push('}'); out_txt.push('\n'); i = k; continue; - } else { out_txt.push_str(&bytes[j + 1..k].iter().collect::()); i = k; continue; } - } - } - out_txt.push(bytes[i]); i += 1; - } - prelude_text = out_txt; - } - // Seam join + optional fix - let prelude_clean = prelude_text.trim_end_matches('\n'); - crate::runner::modes::common_util::resolve::seam::log_prelude_body_seam(prelude_clean, &out, seam_dbg); - let mut combined = String::with_capacity(prelude_clean.len() + out.len() + 1); - combined.push_str(prelude_clean); - combined.push('\n'); - crate::runner::modes::common_util::resolve::seam::fix_prelude_braces_if_enabled(prelude_clean, &mut combined, trace); - combined.push_str(&out); - if std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1") { - let _ = std::fs::write("/tmp/nyash_using_combined.nyash", &combined); - } - Ok(combined) - } - - let mut visited = HashSet::new(); - let combined = strip_and_inline(runner, code, filename, &mut visited)?; - Ok(preexpand_at_local(&combined)) -} /// Collect using targets and strip using lines, without inlining. /// Returns (cleaned_source, prelude_paths) where prelude_paths are resolved file paths @@ -440,7 +13,6 @@ pub fn collect_using_and_strip( } let using_ctx = runner.init_using_context(); let prod = crate::config::env::using_is_prod(); - let dev = crate::config::env::using_is_dev(); let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1"); let verbose = crate::config::env::cli_verbose() || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1"); @@ -455,7 +27,7 @@ pub fn collect_using_and_strip( let rest0 = t.strip_prefix("using ").unwrap().trim(); let rest0 = rest0.split('#').next().unwrap_or(rest0).trim(); let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim(); - let (target, alias) = if let Some(pos) = rest0.find(" as ") { + let (target, _alias) = if let Some(pos) = rest0.find(" as ") { (rest0[..pos].trim().to_string(), Some(rest0[pos + 4..].trim().to_string())) } else { (rest0.to_string(), None) }; let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash"); @@ -602,3 +174,4 @@ pub fn preexpand_at_local(src: &str) -> String { } out } + diff --git a/src/runner/modes/pyvm.rs b/src/runner/modes/pyvm.rs index 958282c9..7ea3f1ee 100644 --- a/src/runner/modes/pyvm.rs +++ b/src/runner/modes/pyvm.rs @@ -14,10 +14,17 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { } }; - // Optional using pre-processing (strip lines and register modules) + // Using handling: AST-prelude collection (legacy inlining removed) let mut code = if crate::config::env::enable_using() { - match crate::runner::modes::common_util::resolve::strip_using_and_register(runner, &code, filename) { - Ok(s) => s, + match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(runner, &code, filename) { + Ok((clean, paths)) => { + if !paths.is_empty() && !crate::config::env::using_ast_enabled() { + eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."); + process::exit(1); + } + // PyVM pipeline currently does not merge prelude ASTs here; rely on main/common path for that. + clean + } Err(e) => { eprintln!("❌ {}", e); process::exit(1); } } } else { code }; diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index ca660956..66957003 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -101,10 +101,17 @@ impl NyashRunner { } }; - // Optional Phase-15: strip `using` lines and register aliases/modules + // Using handling: AST-prelude collection (legacy inlining removed) let code = if crate::config::env::enable_using() { - match crate::runner::modes::common::resolve::strip_using_and_register(self, &code, filename) { - Ok(s) => s, + match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) { + Ok((clean, paths)) => { + if !paths.is_empty() && !crate::config::env::using_ast_enabled() { + eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."); + process::exit(1); + } + // VM path currently does not merge prelude ASTs; rely on compile pipeline path for that. + clean + } Err(e) => { eprintln!("❌ {}", e); process::exit(1); } } } else { code }; diff --git a/src/runner/modes/vm_fallback.rs b/src/runner/modes/vm_fallback.rs index c223d89b..b36805e2 100644 --- a/src/runner/modes/vm_fallback.rs +++ b/src/runner/modes/vm_fallback.rs @@ -15,30 +15,27 @@ impl NyashRunner { // Using preprocessing (legacy inline or AST-prelude merge when NYASH_USING_AST=1) let mut code2 = code; let use_ast_prelude = crate::config::env::enable_using() - && std::env::var("NYASH_USING_AST").ok().as_deref() == Some("1"); + && crate::config::env::using_ast_enabled(); let mut prelude_asts: Vec = Vec::new(); if crate::config::env::enable_using() { - if use_ast_prelude { - match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code2, filename) { - Ok((clean, paths)) => { - code2 = clean; - for p in paths { - match std::fs::read_to_string(&p) { - Ok(src) => match NyashParser::parse_from_string(&src) { - Ok(ast) => prelude_asts.push(ast), - Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); process::exit(1); } - }, - Err(e) => { eprintln!("❌ Error reading using prelude {}: {}", p, e); process::exit(1); } - } + match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code2, filename) { + Ok((clean, paths)) => { + code2 = clean; + if !paths.is_empty() && !use_ast_prelude { + eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."); + process::exit(1); + } + for p in paths { + match std::fs::read_to_string(&p) { + Ok(src) => match NyashParser::parse_from_string(&src) { + Ok(ast) => prelude_asts.push(ast), + Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); process::exit(1); } + }, + Err(e) => { eprintln!("❌ Error reading using prelude {}: {}", p, e); process::exit(1); } } } - Err(e) => { eprintln!("❌ {}", e); process::exit(1); } - } - } else { - match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code2, filename) { - Ok(s) => { code2 = s; } - Err(e) => { eprintln!("❌ {}", e); process::exit(1); } } + Err(e) => { eprintln!("❌ {}", e); process::exit(1); } } } // Dev sugar pre-expand: @name = expr → local name = expr diff --git a/src/runner/plugins.rs b/src/runner/plugins.rs index 8cfb42c9..ae3545c9 100644 --- a/src/runner/plugins.rs +++ b/src/runner/plugins.rs @@ -63,5 +63,15 @@ impl NyashRunner { } } } + + // Provider verify (受け口): env で warn/strict のみ動作(未設定時は無処理) + match crate::runtime::provider_verify::verify_from_env() { + Ok(()) => {} + Err(e) => { eprintln!("❌ {}", e); std::process::exit(1); } + } + + // Provider Lock — lock after registry and plugins are initialized (受け口) + // Default: no-op behavior change. Exposed for future verify→lock sequencing. + crate::runtime::provider_lock::lock_providers(); } } diff --git a/src/runner/selfhost.rs b/src/runner/selfhost.rs index 215088e9..0def7ad6 100644 --- a/src/runner/selfhost.rs +++ b/src/runner/selfhost.rs @@ -25,8 +25,15 @@ impl NyashRunner { // Optional Phase-15: strip `using` lines and register modules (same policy as execute_nyash_file) let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code); if crate::config::env::enable_using() { - match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code, filename) { - Ok(s) => { code_ref = std::borrow::Cow::Owned(s); } + match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) { + Ok((clean, paths)) => { + if !paths.is_empty() && !crate::config::env::using_ast_enabled() { + eprintln!("[ny-compiler] using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."); + return false; + } + code_ref = std::borrow::Cow::Owned(clean); + // Selfhost compile path does not need to parse prelude ASTs here. + } Err(e) => { eprintln!("[ny-compiler] {}", e); return false; } } } diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 9cf656e9..c3059af8 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -17,6 +17,8 @@ pub mod plugin_loader_v2; pub mod scheduler; pub mod semantics; pub mod unified_registry; +pub mod provider_lock; +pub mod provider_verify; // pub mod plugin_box; // legacy - 古いPluginBox // pub mod plugin_loader; // legacy - Host VTable使用 pub mod extern_registry; // ExternCall (env.*) 登録・診断用レジストリ diff --git a/src/runtime/provider_lock.rs b/src/runtime/provider_lock.rs new file mode 100644 index 00000000..b9ff6f8e --- /dev/null +++ b/src/runtime/provider_lock.rs @@ -0,0 +1,38 @@ +/*! + * Provider Lock (skeleton) + * + * Phase 15.5 受け口: 型→Provider のロック状態を保持するための最小スケルトン。 + * 既定では挙動を変えず、環境変数により警告/エラー化のみ可能にする。 + */ + +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::OnceLock; + +static LOCKED: AtomicBool = AtomicBool::new(false); +static WARN_ONCE: OnceLock<()> = OnceLock::new(); + +/// Return true when providers are locked +pub fn is_locked() -> bool { LOCKED.load(Ordering::Relaxed) } + +/// Lock providers (idempotent) +pub fn lock_providers() { LOCKED.store(true, Ordering::Relaxed); } + +/// Guard called before creating a new box instance. +/// Default: no-op. When NYASH_PROVIDER_LOCK_STRICT=1, returns Err if not locked. +/// When NYASH_PROVIDER_LOCK_WARN=1, prints a warning once. +pub fn guard_before_new_box(box_type: &str) -> Result<(), String> { + if is_locked() { return Ok(()); } + let strict = std::env::var("NYASH_PROVIDER_LOCK_STRICT").ok().as_deref() == Some("1"); + let warn = std::env::var("NYASH_PROVIDER_LOCK_WARN").ok().as_deref() == Some("1"); + if strict { + return Err(format!("E_PROVIDER_NOT_LOCKED: attempted to create '{}' before Provider Lock", box_type)); + } + if warn { + // Print once per process + let _ = WARN_ONCE.get_or_init(|| { + eprintln!("[provider-lock][warn] NewBox emitted before Provider Lock. Set NYASH_PROVIDER_LOCK_STRICT=1 to error."); + }); + } + Ok(()) +} + diff --git a/src/runtime/provider_verify.rs b/src/runtime/provider_verify.rs new file mode 100644 index 00000000..b86aa82b --- /dev/null +++ b/src/runtime/provider_verify.rs @@ -0,0 +1,117 @@ +/*! + * Provider Verify (skeleton) + * + * Phase 15.5 受け口: 起動時に最小の必須メソッドを検証するための軽量フック。 + * 既定はOFF。環境変数で warn/strict に切替える。 + * + * Env: + * - NYASH_PROVIDER_VERIFY=warn|strict + * - NYASH_VERIFY_REQUIRED_METHODS="StringBox:length,concat;ArrayBox:push,get" + * (optional; merged with nyash.toml definitions when present) + * + * nyash.toml (optional; merged when present): + * - [verify.required_methods] + * StringBox = ["length","concat"] + * ArrayBox = ["push","get"] + * or + * - [verify.required_methods.StringBox] + * methods = ["length","concat"] + * - [types.StringBox] + * required_methods = ["length","concat"] + */ + +use std::collections::HashMap; + +fn parse_required_methods(spec: &str) -> HashMap> { + let mut map = HashMap::new(); + for part in spec.split(';') { + let p = part.trim(); + if p.is_empty() { continue; } + if let Some((ty, rest)) = p.split_once(':') { + let methods: Vec = rest + .split(',') + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect(); + if !methods.is_empty() { + map.insert(ty.trim().to_string(), methods); + } + } + } + map +} + +fn load_required_methods_from_toml() -> HashMap> { + let mut map: HashMap> = HashMap::new(); + let text = match std::fs::read_to_string("nyash.toml") { Ok(s) => s, Err(_) => return map }; + let doc: toml::Value = match toml::from_str(&text) { Ok(v) => v, Err(_) => return map }; + + // Helper: add entry if array-of-strings + let mut add_arr = |ty: &str, arr: &toml::Value| { + if let Some(a) = arr.as_array() { + let mut v: Vec = Vec::new(); + for e in a { if let Some(s) = e.as_str() { let s = s.trim(); if !s.is_empty() { v.push(s.to_string()); } } } + if !v.is_empty() { map.insert(ty.to_string(), v); } + } + }; + + // 1) [verify.required_methods] + if let Some(vrfy) = doc.get("verify") { + if let Some(req) = vrfy.get("required_methods") { + if let Some(tbl) = req.as_table() { + for (k, v) in tbl.iter() { + if v.is_array() { add_arr(k, v); continue; } + if let Some(t) = v.as_table() { if let Some(m) = t.get("methods") { add_arr(k, m); } } + } + } + } + } + + // 2) [types.].required_methods + if let Some(types) = doc.get("types") { + if let Some(tbl) = types.as_table() { + for (k, v) in tbl.iter() { + if let Some(t) = v.as_table() { if let Some(m) = t.get("required_methods") { add_arr(k, m); } } + } + } + } + + map +} + +pub fn verify_from_env() -> Result<(), String> { + let mode = std::env::var("NYASH_PROVIDER_VERIFY").unwrap_or_default(); + let mode = mode.to_ascii_lowercase(); + if !(mode == "warn" || mode == "strict") { return Ok(()); } + + // Merge: nyash.toml + env override + let mut req = load_required_methods_from_toml(); + let spec = std::env::var("NYASH_VERIFY_REQUIRED_METHODS").unwrap_or_default(); + if !spec.trim().is_empty() { + let env_map = parse_required_methods(&spec); + for (k, v) in env_map { req.entry(k).or_default().extend(v); } + } + if req.is_empty() { return Ok(()); } + + let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); + let host = host.read().unwrap(); + + let mut failures: Vec = Vec::new(); + for (ty, methods) in req.iter() { + for m in methods { + match host.resolve_method(ty, m) { + Ok(_) => { /* ok */ } + Err(_e) => failures.push(format!("{}.{}", ty, m)), + } + } + } + + if failures.is_empty() { return Ok(()); } + let msg = format!( + "Provider verify failed ({}): missing methods: {}", + mode, + failures.join(", ") + ); + if mode == "warn" { eprintln!("[provider-verify][warn] {}", msg); Ok(()) } else { Err(msg) } +} diff --git a/src/tests/parser_static_box_members.rs b/src/tests/parser_static_box_members.rs new file mode 100644 index 00000000..13a8e1d2 --- /dev/null +++ b/src/tests/parser_static_box_members.rs @@ -0,0 +1,65 @@ +use crate::parser::NyashParser; + +fn parse(src: &str) -> crate::ast::ASTNode { NyashParser::parse_from_string(src).expect("parse ok") } + +fn no_toplevel_funccall(ast: &crate::ast::ASTNode) -> bool { + match ast { + crate::ast::ASTNode::Program { statements, .. } => { + !statements.iter().any(|n| matches!(n, crate::ast::ASTNode::FunctionCall { .. })) + } + _ => true, + } +} + +fn box_has_methods(ast: &crate::ast::ASTNode, box_name: &str, methods: &[&str]) -> bool { + fn check_box(b: &crate::ast::ASTNode, box_name: &str, methods: &[&str]) -> bool { + if let crate::ast::ASTNode::BoxDeclaration { name, methods: m, is_static, .. } = b { + if name == box_name && *is_static { + return methods.iter().all(|k| { + if let Some(node) = m.get(*k) { + matches!(node, crate::ast::ASTNode::FunctionDeclaration { name, .. } if name == *k) + } else { false } + }); + } + } + false + } + match ast { + crate::ast::ASTNode::Program { statements, .. } => statements.iter().any(|n| check_box(n, box_name, methods)), + _ => false, + } +} + +#[test] +fn static_box_methods_no_stray_call_compact() { + let src = r#" +static box S { + f(a) { return a } + g(b) { return b } +} +"#; + let ast = parse(src); + assert!(no_toplevel_funccall(&ast), "no top-level FunctionCall expected"); + assert!(box_has_methods(&ast, "S", &["f", "g"]), "static box S should have f and g methods"); +} + +#[test] +fn static_box_methods_no_stray_call_newline_seams() { + // Newlines between ) and {, and tight seam between } and next method head + let src = r#" +static box S { + parse_float(s) + { + return s + } + is_empty_or_whitespace(s) + { + return 0 + } +} +"#; + let ast = parse(src); + assert!(no_toplevel_funccall(&ast), "no top-level FunctionCall expected at seams"); + assert!(box_has_methods(&ast, "S", &["parse_float", "is_empty_or_whitespace"])); +} + diff --git a/tools/smokes/v2/lib/preflight.sh b/tools/smokes/v2/lib/preflight.sh index 19e8b08a..1310c64d 100644 --- a/tools/smokes/v2/lib/preflight.sh +++ b/tools/smokes/v2/lib/preflight.sh @@ -135,6 +135,27 @@ preflight_plugins() { return 1 fi + # Provider Verify(段階導入): nyash.toml の [verify.required_methods] / [types.*.required_methods] + # 既定 warn。SMOKES_PROVIDER_VERIFY_MODE=strict でエラー化。 + local verify_mode="${SMOKES_PROVIDER_VERIFY_MODE:-warn}" + if [ -f "./target/release/nyash" ]; then + local tmp_preflight + tmp_preflight="/tmp/nyash_preflight_empty_$$.ny" + echo "/* preflight */" > "$tmp_preflight" + if NYASH_PROVIDER_VERIFY="$verify_mode" ./target/release/nyash "$tmp_preflight" >/dev/null 2>&1; then + echo "[INFO] Provider verify ($verify_mode): OK" >&2 + else + if [ "$verify_mode" = "strict" ]; then + echo "[ERROR] Provider verify failed (strict)" >&2 + rm -f "$tmp_preflight" 2>/dev/null || true + return 1 + else + echo "[WARN] Provider verify reported issues (mode=$verify_mode)" >&2 + fi + fi + rm -f "$tmp_preflight" 2>/dev/null || true + fi + echo "[INFO] Plugin integrity: OK" >&2 return 0 } @@ -275,4 +296,4 @@ Examples: # Repair with plugin rebuild PREFLIGHT_REBUILD_PLUGINS=1 source lib/preflight.sh && preflight_repair EOF -} \ No newline at end of file +}