docs+runner+parser: SSOT+AST using finalized (legacy text inlining removed); provider verify reads nyash.toml; preflight warn hook; method-body guard removed; CURRENT_TASK updated for next JSON work

This commit is contained in:
Selfhosting Dev
2025-09-26 00:27:02 +09:00
parent d9f26d4549
commit 85084664c2
77 changed files with 1259 additions and 1026 deletions

View File

@ -8,6 +8,21 @@ Quick status
- Parser: TokenCursor 統一 Step2/3 完了env ゲート)
- PHI: if/else の incoming pred を exit ブロックへ修正VM 未定義値を根治)
## ADR 受理: No CoreBox & Everything is PluginProvider/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” Decision20250926
合意(いいとこどり)
@ -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 既定ONprodは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 既定ONdev/ciで緑。
- ministarts_withが VM fallback / LLVM / PyVM のいずれか基準で PASSVM fallback は暫定メソッドで通せばOK
受け入れ基準
- StringUtils の `--dump-ast` に stray FunctionCall が出ない(宣言のみ)。
- ministarts_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 受け口 20250926 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, dowhile not adopted)
- Concurrency docs (design-only): box model, semantics, and patterns/checklist added
@ -1297,7 +1339,7 @@ Progress
- [x] MiniVM ソースの @ 採用apps/selfhostvm 配下の入口/補助を段階 @ 化)
- [x] Runner CLI: clap ArgActionbool フラグを一通り点検・SetTrue 指定panic 回避)
- [ ] Docs: invariants/constraints/testingmatrix へ反映追加(前処理: using前置/@正規化)
- [x] Docs: Using→Loader 統合メモ(短尺)— docs/design/using-loader-integration.mdREADMEにリンク済
- [x] Docs: Using→Loader 統合メモ(短尺)— docs/development/design/legacy/using-loader-integration.mdREADMEにリンク済
### Guardrailsactive
- 参照実行: PyVM が常時緑、マクロ正規化は preMIR で一度だけ
@ -1305,16 +1347,16 @@ Progress
- テスト: VM/goldens は軽量維持、IR は任意ジョブ
## PostBootstrap BacklogDocs only
- Language: Scope reuse blocksdesign — docs/proposals/scope-reuse.md
- Language: Flow blocks & `->` pipingdesign — docs/design/flow-blocks.md
- Language: Scope reuse blocksdesign — docs/development/proposals/scope-reuse.md
- Language: Flow blocks & `->` pipingdesign — docs/development/design/legacy/flow-blocks.md
- Guards: Range/CharClass sugarreference — docs/reference/language/match-guards.md
- Strings: `toDigitOrNull` / `toIntOrNull`design note — docs/reference/language/strings.md
- Concurrency: Box modelRoutine/Channel/Select/Scope — docs/proposals/concurrency/boxes.md
- Concurrency semanticsblocking/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

View File

@ -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 SelfHosting](development/roadmap/phases/phase-17-loopform-selfhost/)**
- 💡 **[Rust所有権統合候補](private/ideas/new-features/2025-09-22-rust-ownership-fusion.md)** - Phase 17+候補
- 🧩 **[MiniVM 構築ロードマップ](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
---

View File

@ -108,4 +108,38 @@
2. 空ディレクトリの削除
3. 相互リンクの確認と修正
4. 最終的な整合性チェック
5. CLAUDE.mdの参照更新
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 を配置し、後方互換を確保

View File

@ -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` を参照。

View File

@ -0,0 +1,9 @@
# Architecture Docs Moved
アーキテクチャの正本は `docs/reference/architecture/` へ集約しました。
- 入口: [../reference/architecture/](../reference/architecture/)
- 代表: [PHI and SSA](../reference/architecture/phi-and-ssa.md)
このディレクトリは互換のための案内のみ残しています。

View File

@ -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 Ifchain 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 finalizePHI is trustworthy across Loop/If/Match.
- Expand goldens to cover nested joins and multicarrier loops while keeping CI light.
この stub は後方互換のために残しています。

View File

@ -0,0 +1,8 @@
# Moved: Blueprints
Blueprints は `development/design/blueprints/` に集約しました。
- 新しい場所: [../development/design/blueprints/](../development/design/blueprints/)
この stub は後方互換のために残しています。

View File

@ -1,61 +1,7 @@
# Strings Blueprint — UTF8 First, Bytes Separate
# Moved: Strings Blueprint — UTF8 / Bytes
Status: active (FeaturePause 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 UTF8 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; optin only
- substring: follow the same split (CP vs Byte). Do not mix semantics.
- Document preconditions for indices (outofrange clamped/errored per guide).
Implementation Plan (staged, nonbreaking)
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 (multibyte CP, boundaries)
3) Replace adhoc scans in Nyash scripts with cursor usage (MiniVM/macros)
- Migrate internal scanners (no external behavior change)
4) Introduce ByteCursorBox only where bytelevel 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 featurepause; 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; dowhile 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/nonASCII, indexOf not found, substring slices.
- Avoid perf work; focus on semantics + observability.

View File

@ -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, tradeoffs, 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 longform papers should remain under `docs/private/` until finalized.
このディレクトリは互換のための案内のみ残しています。

View File

@ -1,80 +1,5 @@
# Flow Blocks and Arrow Piping (Design Draft)
# Moved: Flow Blocks (Design Draft)
Status: design-only during the featurepause (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 featurepause (postbootstrap).

View File

@ -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 (pausesafe)
- Parser stays Phase0: keep `nyashstd` restriction; do not widen grammar.
- Integration is a runner step (preparse): 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 thinization (MiniVM) 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.
- Linehead `@name[:T] = expr` is normalized to `local name[:T] = expr` as a purely textual preexpand (no semantic change). Inline `@` is not recognized; keep `@` at line head.
- These steps are pausesafe: 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

View File

@ -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 だけを提供し、箱の実装は一切持たない。
- 機能はすべて PluginTypeBox 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` に集約。

View File

@ -0,0 +1,61 @@
# Strings Blueprint — UTF8 First, Bytes Separate
Status: active (FeaturePause 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 UTF8 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; optin only
- substring: follow the same split (CP vs Byte). Do not mix semantics.
- Document preconditions for indices (outofrange clamped/errored per guide).
Implementation Plan (staged, nonbreaking)
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 (multibyte CP, boundaries)
3) Replace adhoc scans in Nyash scripts with cursor usage (MiniVM/macros)
- Migrate internal scanners (no external behavior change)
4) Introduce ByteCursorBox only where bytelevel 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 featurepause; 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; dowhile 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/nonASCII, indexOf not found, substring slices.
- Avoid perf work; focus on semantics + observability.

View File

@ -0,0 +1,13 @@
# Nyash Design Notes
Public, stable design documents and architecture explanations.
Use for rationale, tradeoffs, 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 longform papers should remain under `docs/private/` until finalized.

View File

@ -0,0 +1,80 @@
# Flow Blocks and Arrow Piping (Design Draft)
Status: design-only during the featurepause (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 featurepause (postbootstrap).

View File

@ -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 (pausesafe)
- Parser stays Phase0: keep `nyashstd` restriction; do not widen grammar.
- Integration is a runner step (preparse): 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 thinization (MiniVM) 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.
- Linehead `@name[:T] = expr` is normalized to `local name[:T] = expr` as a purely textual preexpand (no semantic change). Inline `@` is not recognized; keep `@` at line head.
- These steps are pausesafe: 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

View File

@ -0,0 +1,77 @@
# Nyash Concurrency — Box Model (Proposal, docs-only)
Status: design-only during the featurepause. No runtime or spec changes. Implement after MiniVM 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 (Phase0: 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`.
NonGoals (Phase0)
- M:N scheduler, OS-level park/unpark, net poller integration (deferred to Phase2 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 (Phase0: simulated park)
- try_send(v) -> Bool
- receive() -> Any // blocks if empty (Phase0: 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:
- Phase0 userland uses cooperative wait queues; no busy loops. try_* and timeout variants provided.
- Select fairness:
- If multiple ready, choose random/roundrobin. Starvation avoidance is a design requirement; precise algorithm can evolve.
- Types:
- `TypedChannelBox<T>` is a future extension; Phase0 uses runtime tags/guards documented in reference.
- Cancellation:
- RoutineScopeBox cancels children on fini; Channel waits should return (canceled) promptly.
Phases
- Phase0 (userland MVP / PyVM first)
- Implement the 4 boxes above with minimal queues/waits, plus trace hooks.
- Smokes: pingpong, bounded producer/consumer, twoway select, close semantics, scope cancel.
- Phase1 (park/unpark abstraction)
- Introduce `WaiterBox`/`CondBox` that map to efficient OS waits where available. Keep same APIs.
- Phase2 (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 waitfor graph.
- API contracts explicitly define error return for misuse (send on closed, double close, etc.).
Deliverables (docsonly during the featurepause)
- This proposal (boxes & semantics).
- Reference page with blocking/close/select rules (see reference/concurrency/semantics.md).
- Test plan with named smokes and expected outputs.

View File

@ -0,0 +1,74 @@
# Scope Reuse Blocks (MVP Proposal)
Status: design-only during the featurepause (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 (its 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 featurepause (postbootstrap).

View File

@ -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 (Stage3)
- 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<T>` 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`
- Stage3 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.

View File

@ -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.

View File

@ -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統計・既知の制約

View File

@ -29,6 +29,5 @@ Onepager 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
- MiniVM roadmap: docs/development/roadmap/phases/phase-17-loopform-selfhost/MINI_VM_ROADMAP.md

View File

@ -41,8 +41,8 @@ Acceptance & guardrails (featurepause)
- 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 — UTF8 first; proposed digit helpers

View File

@ -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.

View File

@ -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統計・既知の制約

6
docs/proposals/README.md Normal file
View File

@ -0,0 +1,6 @@
# Proposals Moved
提案(RFC)は `docs/development/proposals/` に集約しました。
- 新しい場所: [../development/proposals/](../development/proposals/)

View File

@ -1,77 +1,5 @@
# Nyash Concurrency — Box Model (Proposal, docs-only)
# Moved: Concurrency — Box Model (Proposal)
Status: design-only during the featurepause. No runtime or spec changes. Implement after MiniVM 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 (Phase0: 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`.
NonGoals (Phase0)
- M:N scheduler, OS-level park/unpark, net poller integration (deferred to Phase2 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 (Phase0: simulated park)
- try_send(v) -> Bool
- receive() -> Any // blocks if empty (Phase0: 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:
- Phase0 userland uses cooperative wait queues; no busy loops. try_* and timeout variants provided.
- Select fairness:
- If multiple ready, choose random/roundrobin. Starvation avoidance is a design requirement; precise algorithm can evolve.
- Types:
- `TypedChannelBox<T>` is a future extension; Phase0 uses runtime tags/guards documented in reference.
- Cancellation:
- RoutineScopeBox cancels children on fini; Channel waits should return (canceled) promptly.
Phases
- Phase0 (userland MVP / PyVM first)
- Implement the 4 boxes above with minimal queues/waits, plus trace hooks.
- Smokes: pingpong, bounded producer/consumer, twoway select, close semantics, scope cancel.
- Phase1 (park/unpark abstraction)
- Introduce `WaiterBox`/`CondBox` that map to efficient OS waits where available. Keep same APIs.
- Phase2 (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 waitfor graph.
- API contracts explicitly define error return for misuse (send on closed, double close, etc.).
Deliverables (docsonly during the featurepause)
- This proposal (boxes & semantics).
- Reference page with blocking/close/select rules (see reference/concurrency/semantics.md).
- Test plan with named smokes and expected outputs.

View File

@ -1,74 +1,5 @@
# Scope Reuse Blocks (MVP Proposal)
# Moved: Scope Reuse Blocks (MVP Proposal)
Status: design-only during the featurepause (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 (its 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 featurepause (postbootstrap).

View File

@ -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 (Stage3)
- 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<T>` 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`
- Stage3 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.

View File

@ -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統計とプラグイン周りの既知制約

View File

@ -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 Ifchain 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 finalizePHI is trustworthy across Loop/If/Match.
- Expand goldens to cover nested joins and multicarrier loops while keeping CI light.

View File

@ -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.<name>]`、エイリアス `[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 NameSTNを Provider IDPVNにマッピング。未指定時は現行ランタイム既定。
```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 }
```
## Profilesusing / AST
`NYASH_USING_PROFILE={dev|ci|prod}`
- dev/ci: AST プレリュード既定ONfile-usingはdevで許可、ciは警告/限定)
- prod: AST 既定OFFtoml 由来のみ、file-using はエラー)
実装ノート:
- AST 既定は `src/config/env.rs: using_ast_enabled()` でプロファイルに従い決定。
- 既存のレガシー前置きは prod で禁止、dev/ci でも段階的に削除予定。
## Verifyplugin-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

View File

@ -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 preprocessing: `NYASH_ENABLE_USING=1`
- CLI from-the-top registration: `--using "ns as Alias"` or `--using '"apps/foo.nyash" as Foo'` (repeatable)
- Using profiles (phasein): `NYASH_USING_PROFILE={dev|ci|prod}`
- dev: toml + file usingpath可、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 SmokesAST + Profiles
開発・CIで最小コストに確認できるスモークを用意しています。AST プレリュードとプロファイルdev/prodの基本動作をカバーします。

View File

@ -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 initGC/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 NameSTN: `StringBox`, `IntegerBox` など。コード上の型名は不変。
- Provider IDPVN: `kernel:string@1.0` / `acme:string@2.1` など実装提供者。
- TOML で STN→PVN をバインドし、置換は TOML 側で行う。
Provider Lockロック
- 起動時に `types.<Type>` の 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 による変換のみ許可UTF8 などの正規形式を介する)
- auto: 暗黙変換を許可し、変換回数・バイト数をメトリクスに集計(本番非推奨)
## 現状の段階
- 受け口/ドキュメントの整備を先行(挙動は不変)。
- using は SSOT+AST に移行済みprod は file-using 禁止)。
- VM fallback の個別救済は暫定(短期で Bootstrap Pack へ移行し撤去)。
関連ドキュメント
- nyash.toml のスキーマと例: docs/reference/config/nyash-toml.md
- usingSSOT/AST/Profiles: docs/reference/language/using.md

6
docs/status/README.md Normal file
View File

@ -0,0 +1,6 @@
# Status/Golden moved
ゴールデン出力などのステータス関連ドキュメントは `docs/development/testing/golden/` へ移動しました。
- 新しい場所: [../development/testing/golden/](../development/testing/golden/)

6
docs/tests/README.md Normal file
View File

@ -0,0 +1,6 @@
# Tests moved
テスト関連のドキュメントは `docs/development/testing/` に移動しました。
- 新しい場所: [../development/testing/](../development/testing/)

View File

@ -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"

View File

@ -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<Box<dyn NyashBox>> = Vec::with_capacity(args.len());
for vid in args {
converted.push(self.reg_load(*vid)?.to_nyash_box());

View File

@ -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

View File

@ -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!(

View File

@ -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<nyash_rust::ast::ASTNode> = 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)

View File

@ -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};

View File

@ -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<String, String> {
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<String>,
) -> Result<String, String> {
let mut out = String::with_capacity(code.len());
let mut prelude = String::new();
let mut used: Vec<(String, Option<String>)> = 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<String> = 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.<ns>].bid)
let mut boxes: Vec<String> = 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<String> = 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<String> = std::collections::HashSet::new();
let mut out_txt = String::with_capacity(prelude_text.len());
let bytes: Vec<char> = prelude_text.chars().collect();
let mut i = 0usize;
while i < bytes.len() {
if i + 12 < bytes.len() && bytes[i..].iter().take(11).collect::<String>() == "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::<String>());
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<char> = 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::<String>());
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::<String>());
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::<String>()); }
out_txt.push('}'); out_txt.push('\n'); i = k; continue;
} else { out_txt.push_str(&bytes[j + 1..k].iter().collect::<String>()); 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
}

View File

@ -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 };

View File

@ -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 };

View File

@ -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<nyash_rust::ast::ASTNode> = 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

View File

@ -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();
}
}

View File

@ -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; }
}
}

View File

@ -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.*) 登録・診断用レジストリ

View File

@ -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(())
}

View File

@ -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<String, Vec<String>> {
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<String> = 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<String, Vec<String>> {
let mut map: HashMap<String, Vec<String>> = 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<String> = 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.<TypeName>].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<String> = 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) }
}

View File

@ -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"]));
}

View File

@ -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
}
}