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:
@ -8,6 +8,21 @@ Quick status
|
||||
- Parser: TokenCursor 統一 Step‑2/3 完了(env ゲート)
|
||||
- PHI: if/else の incoming pred を exit ブロックへ修正(VM 未定義値を根治)
|
||||
|
||||
## ADR 受理: No CoreBox & Everything is Plugin(Provider/Type 分離)
|
||||
|
||||
- CoreBox は戻さない。Kernel は最小(GC/Handle/TLV/Extern/PluginRegistry/ABI)。
|
||||
- 型名(STN: `StringBox` 等)は不変、実装提供者(PVN)は TOML で切替。
|
||||
- 起動は「Kernel init → plugins.bootstrap/static + plugins.dynamic → Verify → 実行」。
|
||||
- VM/LLVM は `ny_new_box` / `ny_call_method` に統一(段階導入)。
|
||||
- ADR: docs/development/adr/adr-001-no-corebox-everything-is-plugin.md を追加。
|
||||
|
||||
受け口フェーズ(挙動不変)
|
||||
- K0: ADR/Docs 追加(完了)。
|
||||
- K1: TOML スキーマ雛形(types/providers/policy)受け口(後続)。
|
||||
- K2: Provider 解決ログの受け口(後続)。
|
||||
- K3: Verify フック(preflight_plugins)受け口(後続)。
|
||||
- K4: Bootstrap Pack 登録導線(prod限定フラグ; 後続)。
|
||||
|
||||
## Using / Resolver — “Best of Both” Decision(2025‑09‑26)
|
||||
|
||||
合意(いいとこどり)
|
||||
@ -20,7 +35,8 @@ Quick status
|
||||
|
||||
やること(仕様不変・既定OFFで段階導入)
|
||||
1) ドキュメント
|
||||
- [x] `docs/reference/language/using.md` に SSOT+AST とプロファイル運用を追記。
|
||||
- [x] `docs/reference/language/using.md` に SSOT+AST/Profiles/Smokes を追記。
|
||||
- [x] ADR を追加(No CoreBox / Provider 分離)
|
||||
2) Resolver 統合
|
||||
- [x] vm_fallback に AST プレリュード統合を導入(common と同形)。
|
||||
- [x] prod での `using "path"`/未知 alias はエラー(修正ガイド付)。
|
||||
@ -33,6 +49,24 @@ Quick status
|
||||
- [x] Guard 条件をトップレベル限定かつ `}` 直後のみ発火に調整(誤検知回避)。
|
||||
- [ ] `apps/lib/json_native/utils/string.nyash` で stray FunctionCall 消滅確認。
|
||||
|
||||
## シンプル化ロードマップ(claude code 提案の順)
|
||||
|
||||
1) VM fallback 強化(mini 緑化)
|
||||
- [x] レガシー解決の正規化(Box.method/Arity)
|
||||
- [x] 文字列の最小メソッド(substring 等)暫定実装(短期・撤去予定)
|
||||
2) dev/ci で AST 既定ON(prodはSSOTを維持)
|
||||
- [ ] 既定値切替とスモーク緑確認
|
||||
3) レガシー using 経路の段階削除
|
||||
- [x] 呼び出し側のレガシー分岐を撤去(common/vm/vm_fallback/pyvm/selfhost を AST 経路に統一)
|
||||
- [ ] strip_using_and_register 本体のファイル内撤去(後続の掃除タスクで対応)
|
||||
4) パーサガードの格下げ→撤去
|
||||
- [x] Guard を log-only に格下げ(NYASH_PARSER_METHOD_BODY_STRICT=1 でも break せず警告ログのみ)
|
||||
- [x] Guard 実装を撤去(method-body 専用のシーム判定を削除、通常ブロック同等に)
|
||||
|
||||
受け入れ基準(追加)
|
||||
- quick/integration スモークが AST 既定ON(dev/ci)で緑。
|
||||
- mini(starts_with)が VM fallback / LLVM / PyVM のいずれか基準で PASS(VM fallback は暫定メソッドで通せばOK)。
|
||||
|
||||
受け入れ基準
|
||||
- StringUtils の `--dump-ast` に stray FunctionCall が出ない(宣言のみ)。
|
||||
- mini(starts_with): ASTモード ON/OFF で parse→MIR まで到達(VM fallback の未実装は許容)。
|
||||
@ -45,6 +79,14 @@ Quick status
|
||||
- 現状: OFF 時は `string.nyash` にて Program 配下に `FunctionCall(parse_float)` が残存。
|
||||
- 次: Guard ON で AST/MIR を検証し、必要に応じて lookahead 条件を調整。
|
||||
|
||||
### 追加進捗(Using/Verify 受け口 2025‑09‑26 EOD)
|
||||
- Provider Verify: nyash.toml の `[verify.required_methods]` / `[types.*.required_methods]` を読んで検査(env とマージ)
|
||||
- 受け口: `NYASH_PROVIDER_VERIFY=warn|strict`、`NYASH_VERIFY_REQUIRED_METHODS`(任意上書き)
|
||||
- preflight: `tools/smokes/v2/lib/preflight.sh` から warn で起動。`SMOKES_PROVIDER_VERIFY_MODE=strict` でエラー化
|
||||
- Using: レガシー前置き経路を呼び出し側から完全撤去(AST プレリュードに一本化)
|
||||
- AST 無効プロファイルで using がある場合はガイド付きエラー
|
||||
- 内部実装: 旧 strip_using_and_register/builtin 経路の物理削除(ファイル再構成)
|
||||
|
||||
## 今日の合意(方向修正の確定)
|
||||
- Rust層は新機能を最小化。今後は Nyash VM/コンパイラ(自己ホスト)へリソース集中。
|
||||
- 次タスクは Nyash 製 JSON ライブラリ(JSON v0 DOM: parse/stringify)。完了次第、Ny Executor 最小命令の実装を着手。
|
||||
@ -1068,7 +1110,7 @@ This page is trimmed to reflect the active work only. The previous long form has
|
||||
- mini_vm_core の末尾ブレースを整合(未閉じ/余剰の解消)
|
||||
- MiniVm.collect_prints の未知形スキップを Print オブジェクト全体に拡張(停滞防止)
|
||||
- Docs
|
||||
- Added strings blueprint: `docs/blueprints/strings-utf8-byte.md`
|
||||
- Added strings blueprint: `docs/development/design/blueprints/strings-utf8-byte.md`
|
||||
- Refreshed docs index with clear "Start here" links (blueprints/strings, EBNF, strings reference)
|
||||
- Clarified operator/loop sugar policy in `guides/language-core-and-sugar.md` ("!" adopted, do‑while not adopted)
|
||||
- Concurrency docs (design-only): box model, semantics, and patterns/checklist added
|
||||
@ -1297,7 +1339,7 @@ Progress
|
||||
- [x] Mini‑VM ソースの @ 採用(apps/selfhost‑vm 配下の入口/補助を段階 @ 化)
|
||||
- [x] Runner CLI: clap ArgAction(bool フラグ)を一通り点検・SetTrue 指定(panic 回避)
|
||||
- [ ] Docs: invariants/constraints/testing‑matrix へ反映追加(前処理: using前置/@正規化)
|
||||
- [x] Docs: Using→Loader 統合メモ(短尺)— docs/design/using-loader-integration.md(READMEにリンク済)
|
||||
- [x] Docs: Using→Loader 統合メモ(短尺)— docs/development/design/legacy/using-loader-integration.md(READMEにリンク済)
|
||||
|
||||
### Guardrails(active)
|
||||
- 参照実行: PyVM が常時緑、マクロ正規化は pre‑MIR で一度だけ
|
||||
@ -1305,16 +1347,16 @@ Progress
|
||||
- テスト: VM/goldens は軽量維持、IR は任意ジョブ
|
||||
|
||||
## Post‑Bootstrap Backlog(Docs only)
|
||||
- Language: Scope reuse blocks(design) — docs/proposals/scope-reuse.md
|
||||
- Language: Flow blocks & `->` piping(design) — docs/design/flow-blocks.md
|
||||
- Language: Scope reuse blocks(design) — docs/development/proposals/scope-reuse.md
|
||||
- Language: Flow blocks & `->` piping(design) — docs/development/design/legacy/flow-blocks.md
|
||||
- Guards: Range/CharClass sugar(reference) — docs/reference/language/match-guards.md
|
||||
- Strings: `toDigitOrNull` / `toIntOrNull`(design note) — docs/reference/language/strings.md
|
||||
- Concurrency: Box model(Routine/Channel/Select/Scope) — docs/proposals/concurrency/boxes.md
|
||||
- Concurrency semantics(blocking/close/select/trace) — docs/reference/concurrency/semantics.md
|
||||
|
||||
## Nyash VM めど後 — 機能追加リンク(備忘)
|
||||
- スコープ再利用ブロック(MVP 提案): docs/proposals/scope-reuse.md
|
||||
- 矢印フロー × 匿名ブロック(設計草案): docs/design/flow-blocks.md
|
||||
- スコープ再利用ブロック(MVP 提案): docs/development/proposals/scope-reuse.md
|
||||
- 矢印フロー × 匿名ブロック(設計草案): docs/development/design/legacy/flow-blocks.md
|
||||
- Match Guard の Range/CharClass(参照・設計): docs/reference/language/match-guards.md
|
||||
- String 便利関数(toDigit/Int; 設計): docs/reference/language/strings.md
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
## 🚀 はじめに(導線)
|
||||
- 現在のタスクと進行状況: ../CURRENT_TASK.md
|
||||
- コア概念の速習: reference/architecture/nyash_core_concepts.md
|
||||
- 設計ブループリント(文字列/文字コード): blueprints/strings-utf8-byte.md
|
||||
- 設計ブループリント(文字列/文字コード): development/design/blueprints/strings-utf8-byte.md
|
||||
|
||||
---
|
||||
|
||||
@ -72,9 +72,9 @@
|
||||
- proposals/concurrency/boxes.md(並行モデルのBox設計:Routine/Channel/Select/Scope)
|
||||
- reference/concurrency/semantics.md(ブロッキング/close/select/観測の規約)
|
||||
- design/(設計ノート入口)
|
||||
- design/flow-blocks.md(矢印フロー/匿名ブロック・設計草案)
|
||||
- ../proposals/scope-reuse.md(スコープ再利用ブロック・MVP提案)
|
||||
- ../reference/language/match-guards.md(ガード連鎖/Range・CharClass設計)
|
||||
- development/design/legacy/flow-blocks.md(矢印フロー/匿名ブロック・設計草案)
|
||||
- development/proposals/scope-reuse.md(スコープ再利用ブロック・MVP提案)
|
||||
- reference/language/match-guards.md(ガード連鎖/Range・CharClass設計)
|
||||
- guides/core-principles.md(最小構文・ゼロランタイム・可視化の原則)
|
||||
|
||||
### 開発状況
|
||||
@ -86,7 +86,7 @@
|
||||
- 🧪 **[Phase 17: LoopForm Self‑Hosting](development/roadmap/phases/phase-17-loopform-selfhost/)**
|
||||
- 💡 **[Rust所有権統合(候補)](private/ideas/new-features/2025-09-22-rust-ownership-fusion.md)** - Phase 17+候補
|
||||
- 🧩 **[Mini‑VM 構築ロードマップ](development/roadmap/phases/phase-17-loopform-selfhost/MINI_VM_ROADMAP.md)**
|
||||
- 🧭 **Using→Loader 統合(最小設計)**: design/using-loader-integration.md
|
||||
- 🧭 **Using→Loader 統合(最小設計)**: development/design/legacy/using-loader-integration.md
|
||||
- 🗂️ **Docsの書き方(小さく・リンク駆動)**: guides/contributing-docs.md
|
||||
|
||||
---
|
||||
|
||||
@ -109,3 +109,37 @@
|
||||
3. 相互リンクの確認と修正
|
||||
4. 最終的な整合性チェック
|
||||
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 を配置し、後方互換を確保
|
||||
|
||||
@ -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` を参照。
|
||||
|
||||
9
docs/architecture/README.md
Normal file
9
docs/architecture/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Architecture Docs Moved
|
||||
|
||||
アーキテクチャの正本は `docs/reference/architecture/` へ集約しました。
|
||||
|
||||
- 入口: [../reference/architecture/](../reference/architecture/)
|
||||
- 代表: [PHI and SSA](../reference/architecture/phi-and-ssa.md)
|
||||
|
||||
このディレクトリは互換のための案内のみ残しています。
|
||||
|
||||
@ -1,23 +1,7 @@
|
||||
# PHI and SSA in Nyash
|
||||
# Moved: PHI and SSA
|
||||
|
||||
Overview
|
||||
- Nyash lowers high-level control flow (If/Loop/Match) to MIR and backends that rely on SSA form.
|
||||
- We prioritize IR hygiene and observability while keeping runtime cost at zero.
|
||||
このドキュメントは再編により移動しました。
|
||||
新しい場所: [../reference/architecture/phi-and-ssa.md](../reference/architecture/phi-and-ssa.md)
|
||||
|
||||
Design points
|
||||
- PHI hygiene: no empty PHIs; PHIs at block head only.
|
||||
- JoinResult hint: when both branches assign the same variable, we emit a MIR hint for diagnostics.
|
||||
- Loop carriers: loops may expose a carrier observation (≤ N variables, where N is unconstrained by design; smokes emphasize common cases).
|
||||
|
||||
Normalization
|
||||
- If: may optionally wrap into LoopForm under a conservative gate (dev only). Semantics remain unchanged.
|
||||
- Match: scrutinee evaluated once, guard fused; normalized to nested If‑chain in macro/core pass.
|
||||
|
||||
Testing
|
||||
- LLVM smokes: fixed small cases ensure no empty PHIs and head placement.
|
||||
- MIR smokes: trace `scope|join|loop` to validate shaping without peeking into IR details.
|
||||
|
||||
Roadmap
|
||||
- Remove text-level sanitization once finalize‑PHI is trustworthy across Loop/If/Match.
|
||||
- Expand goldens to cover nested joins and multi‑carrier loops while keeping CI light.
|
||||
この stub は後方互換のために残しています。
|
||||
|
||||
|
||||
8
docs/blueprints/README.md
Normal file
8
docs/blueprints/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Moved: Blueprints
|
||||
|
||||
Blueprints は `development/design/blueprints/` に集約しました。
|
||||
|
||||
- 新しい場所: [../development/design/blueprints/](../development/design/blueprints/)
|
||||
|
||||
この stub は後方互換のために残しています。
|
||||
|
||||
@ -1,61 +1,7 @@
|
||||
# Strings Blueprint — UTF‑8 First, Bytes Separate
|
||||
# Moved: Strings Blueprint — UTF‑8 / Bytes
|
||||
|
||||
Status: active (Feature‑Pause compatible)
|
||||
Updated: 2025-09-21
|
||||
このドキュメントは再編により移動しました。
|
||||
新しい場所: [../development/design/blueprints/strings-utf8-byte.md](../development/design/blueprints/strings-utf8-byte.md)
|
||||
|
||||
Purpose
|
||||
- Unify string semantics by delegating StringBox public APIs to dedicated cursor boxes.
|
||||
- Keep behavior stable while making codepoint vs byte decisions explicit and testable.
|
||||
この stub は後方互換のために残しています。
|
||||
|
||||
Pillars
|
||||
- Utf8CursorBox (codepoint-oriented)
|
||||
- length/indexOf/substring operate on UTF‑8 codepoints.
|
||||
- Intended as the default delegate for StringBox public APIs.
|
||||
- ByteCursorBox (byte-oriented)
|
||||
- length/indexOf/substring operate on raw bytes.
|
||||
- Use explicitly for byte-level parsing or binary protocols.
|
||||
|
||||
Delegation Strategy
|
||||
- StringBox delegates to Utf8CursorBox for core methods: length/indexOf/substring.
|
||||
- Provide conversion helpers: toUtf8Cursor(), toByteCursor() (thin wrappers).
|
||||
- Prefer delegation over inheritance; keep “from” minimal to avoid API ambiguity.
|
||||
|
||||
API Semantics
|
||||
- indexOf: define two flavors via the box boundary.
|
||||
- StringBox.indexOf → Utf8CursorBox.indexOf (CP-based; canonical)
|
||||
- ByteCursorBox.indexOf → byte-based; opt‑in only
|
||||
- substring: follow the same split (CP vs Byte). Do not mix semantics.
|
||||
- Document preconditions for indices (out‑of‑range clamped/errored per guide).
|
||||
|
||||
Implementation Plan (staged, non‑breaking)
|
||||
1) Provide MVP cursor boxes (done)
|
||||
- apps/libs/utf8_cursor.nyash
|
||||
- apps/libs/byte_cursor.nyash
|
||||
2) Delegate StringBox public methods to Utf8CursorBox (internal only; behavior unchanged)
|
||||
- Start with length → indexOf → substring
|
||||
- Add targeted smokes for edge cases (multi‑byte CP, boundaries)
|
||||
3) Replace ad‑hoc scans in Nyash scripts with cursor usage (Mini‑VM/macros)
|
||||
- Migrate internal scanners (no external behavior change)
|
||||
4) Introduce ByteCursorBox only where byte‑level semantics are required
|
||||
- Keep call sites explicit to avoid ambiguity
|
||||
|
||||
Transition Gate (Rust dev only)
|
||||
- Env `NYASH_STR_CP=1` enables CP semantics for legacy byte-based paths in Rust runtime (e.g., StringBox.length/indexOf/lastIndexOf).
|
||||
- Default remains byte in Rust during the feature‑pause; PyVM follows CP semantics. CI smokes validate CP behavior via PyVM.
|
||||
|
||||
Related Docs
|
||||
- reference/language/strings.md — policy & scope
|
||||
- guides/language-core-and-sugar.md — core minimal + sugar
|
||||
- reference/language/EBNF.md — operators (! adopted; do‑while not adopted)
|
||||
- guides/loopform.md — loop normalization policy
|
||||
|
||||
Box Foundations (string-related)
|
||||
- Utf8CursorBox, ByteCursorBox
|
||||
- StringExtBox (trim/startsWith/endsWith/replace/split)
|
||||
- StringBuilderBox (append/toString)
|
||||
- JsonCursorBox (lightweight JSON scanning helpers)
|
||||
|
||||
Testing Notes
|
||||
- Keep PyVM as the reference execution path.
|
||||
- Add smokes: CP boundaries, mixed ASCII/non‑ASCII, indexOf not found, substring slices.
|
||||
- Avoid perf work; focus on semantics + observability.
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
# Nyash Design Notes
|
||||
# Design Notes Moved
|
||||
|
||||
Public, stable design documents and architecture explanations.
|
||||
`docs/design/` の設計ノートは `docs/development/design/legacy/` に移動しました。
|
||||
|
||||
Use for rationale, trade‑offs, and diagrams that are safe to cite.
|
||||
- 新しい場所: [../development/design/legacy/](../development/design/legacy/)
|
||||
- よく参照されるページ:
|
||||
- [flow-blocks.md](../development/design/legacy/flow-blocks.md)
|
||||
- [using-loader-integration.md](../development/design/legacy/using-loader-integration.md)
|
||||
|
||||
Contents to consolidate here:
|
||||
- Architecture overviews derived from ARCHITECTURE.md
|
||||
- Backend design (LLVM/Cranelift) summaries
|
||||
- MIR/IR evolution notes that are not drafts
|
||||
|
||||
Draft, exploratory, or long‑form papers should remain under `docs/private/` until finalized.
|
||||
このディレクトリは互換のための案内のみ残しています。
|
||||
|
||||
|
||||
@ -1,80 +1,5 @@
|
||||
# Flow Blocks and Arrow Piping (Design Draft)
|
||||
# Moved: Flow Blocks (Design Draft)
|
||||
|
||||
Status: design-only during the feature‑pause (no implementation)
|
||||
このドキュメントは再編により移動しました。
|
||||
新しい場所: [../development/design/legacy/flow-blocks.md](../development/design/legacy/flow-blocks.md)
|
||||
|
||||
Goal
|
||||
- Make control/data flow visually obvious while keeping the core minimal.
|
||||
- Core = anonymous `{ ... }` blocks + `->` chaining with `_` or `|args|` as the input placeholder.
|
||||
- Always desugar to plain sequential let/if/call; zero new runtime constructs.
|
||||
|
||||
Core Syntax
|
||||
- Serial (value flow):
|
||||
```nyash
|
||||
{ readConfig() }
|
||||
-> { |cfg| validate(cfg) }
|
||||
-> { |cfg| normalize(cfg) }
|
||||
-> { |cfg| save(cfg) }
|
||||
```
|
||||
- Placeholder short form:
|
||||
```nyash
|
||||
{ fetch() } -> { process(_) } -> { output(_) }
|
||||
```
|
||||
- If/Else with horizontal flow:
|
||||
```nyash
|
||||
if cond -> { doA() } else -> { doB() }
|
||||
```
|
||||
|
||||
Semantics
|
||||
- `{ ... }` is an anonymous scope usable as expression or statement.
|
||||
- `->` passes the left result as the first parameter of the right block.
|
||||
- Left returns `Void` → right cannot use `_`/`|x|` (compile-time error in MVP spec).
|
||||
- `_` and `|x,...|` are exclusive; mixing is an error.
|
||||
|
||||
Lowering (always zero-cost sugar)
|
||||
- Chain desugars to temporaries and calls:
|
||||
```nyash
|
||||
# {A} -> { |x| B(x) } -> { |y| C(y) }
|
||||
t0 = A();
|
||||
t1 = B(t0);
|
||||
t2 = C(t1);
|
||||
```
|
||||
- If/Else chain desugars to standard if/else blocks; merges follow normal PHI wiring rules.
|
||||
|
||||
Match normalization via guard chains
|
||||
- Prefer a single readable form:
|
||||
```nyash
|
||||
guard cond1 -> { A }
|
||||
guard cond2 -> { B }
|
||||
else -> { C }
|
||||
```
|
||||
- Lowers to first-match if/else chain. No new pattern engine is introduced.
|
||||
|
||||
Range and CharClass guards (design)
|
||||
- Range: `guard ch in '0'..'9' -> { ... }` → `('0' <= ch && ch <= '9')`.
|
||||
- CharClass: `guard ch in Digit -> { ... }` → expands to ranges (e.g., '0'..'9').
|
||||
- Multiple ranges combine with OR.
|
||||
|
||||
Formatting (nyfmt guidance)
|
||||
- Align arrows vertically; one step per line:
|
||||
```
|
||||
{ fetch() }
|
||||
-> { validate(_) }
|
||||
-> { save(_) }
|
||||
```
|
||||
- Suggest factoring when chains exceed N steps; prefer naming a scope helper.
|
||||
|
||||
Observability (design only)
|
||||
- `NYASH_FLOW_TRACE=1` prints the desugared steps (`t0=...; t1=...;`).
|
||||
|
||||
Constraints (MVP)
|
||||
- No new closures; anonymous blocks inline when capture-free.
|
||||
- Recursion not required; focus on linear/branching chains.
|
||||
- ASI: treat `->` as a low-precedence line-continue operator.
|
||||
|
||||
Tests (syntax-only smokes; design)
|
||||
- flow_linear: `read→validate→save` matches expected value.
|
||||
- flow_placeholder: `{f()} -> { process(_) } -> { out(_) }`.
|
||||
- flow_if: `if cond -> {A} else -> {B}` behaves like standard if.
|
||||
|
||||
Pause note
|
||||
- Documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap).
|
||||
|
||||
@ -1,32 +1,5 @@
|
||||
# Using → Loader Integration (Minimal)
|
||||
# Moved: Using → Loader Integration (Minimal)
|
||||
|
||||
Goal
|
||||
- Keep `using` simple: strip lines and resolve names to paths/aliases.
|
||||
- Add the minimal integration so userland boxes referenced via `using` are actually available at compile/run time.
|
||||
このドキュメントは再編により移動しました。
|
||||
新しい場所: [../development/design/legacy/using-loader-integration.md](../development/design/legacy/using-loader-integration.md)
|
||||
|
||||
Scope (pause‑safe)
|
||||
- Parser stays Phase‑0: keep `nyashstd` restriction; do not widen grammar.
|
||||
- Integration is a runner step (pre‑parse): resolve and register modules; do not change language semantics.
|
||||
|
||||
Design
|
||||
- Strip `using` lines when `NYASH_ENABLE_USING=1` (already implemented).
|
||||
- For each `using ns [as alias]?`:
|
||||
- Resolve `ns` → path via: [modules] → aliases → using.paths (apps/lib/.) → context dir.
|
||||
- Register mapping in `modules_registry` as `alias_or_ns -> path` (already implemented).
|
||||
- Minimal loader hook (defer heavy linking):
|
||||
- Compile/execute entry file as today.
|
||||
- Userland boxes are accessed via tools/runners that read from `modules_registry` where needed (e.g., PyVM harness/tests).
|
||||
|
||||
Notes
|
||||
- Entry thin‑ization (Mini‑VM) waits until loader reads userland boxes on demand.
|
||||
- Keep docs small: this note serves as the canonical link; avoid duplicating details in other pages.
|
||||
|
||||
Preprocessing invariants (runner)
|
||||
- `using` lines are stripped and resolved prior to parse; dependencies are inlined before `Main` so names are available without changing language semantics.
|
||||
- Line‑head `@name[:T] = expr` is normalized to `local name[:T] = expr` as a purely textual pre‑expand (no semantic change). Inline `@` is not recognized; keep `@` at line head.
|
||||
- These steps are pause‑safe: they do not alter AST semantics; they only simplify authoring and module wiring.
|
||||
|
||||
Links
|
||||
- Runner pipeline: src/runner/pipeline.rs
|
||||
- Using strip/resolve: src/runner/modes/common_util/resolve.rs
|
||||
- Env: NYASH_ENABLE_USING, NYASH_USING_STRICT, NYASH_SKIP_TOML_ENV
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
# ADR-001: No CoreBox, Everything is Plugin — Provider/Type 分離方針
|
||||
|
||||
Status: Accepted (Phase 15.5 設計)
|
||||
Last updated: 2025-09-26
|
||||
|
||||
## 決定
|
||||
|
||||
- CoreBox(言語ランタイム内の特別な箱実装)は復活させない。
|
||||
- 最小カーネル(NyKernel)は GC/Handle/TLV/Extern/PluginRegistry/ABI だけを提供し、箱の実装は一切持たない。
|
||||
- 機能はすべて Plugin(TypeBox v2)で提供する(Everything is Plugin)。
|
||||
- 型名(Stable Type Name: STN)と実装提供者(Provider ID: PVN)を分離する。
|
||||
- 例: STN = `StringBox`、PVN = `kernel:string@1.0` / `acme:string@2.1`。
|
||||
- コードは常に STN に依存し、どの PVN を使うかは `nyash.toml` で選択する。
|
||||
- 起動シーケンスは「Kernel init → plugins.bootstrap(静的束)+ plugins.dynamic(任意)登録 → Verify(必須メソッド等)→ 実行」。
|
||||
- 呼び出しは VM/LLVM 共通で `ny_new_box` / `ny_call_method` に一本化(MIR Callee 確定から統一)する。
|
||||
|
||||
## 背景 / 文脈
|
||||
|
||||
- using の SSOT(唯一の真実)を `nyash.toml` に置き、実体結合は AST マージに一本化した。これにより宣言≻式の曖昧性を排除し、依存は設定で一元管理できる。
|
||||
- CoreBox を残すと特別経路や例外が増え、Everything is Plugin と相反する。Kernel/Plugin の責務分離が維持性と置換性を高める。
|
||||
|
||||
## 設計詳細
|
||||
|
||||
### NyKernel の責務
|
||||
- GC / Roots / Safepoint / Write barrier
|
||||
- Handle / TLV
|
||||
- Extern registry(`env.console.*` 最小)
|
||||
- Plugin registry(登録・検索・呼び出し)
|
||||
- C ABI: `ny_new_box(type_id, args)`, `ny_call_method(recv, type_id, method_id, args, out)`, `ny_gc_*`
|
||||
|
||||
### Bootstrap Pack(静的リンクの基本プラグイン束)
|
||||
- String/Integer/Array/Map/Console などの最小実用セット。
|
||||
- 静的リンクして起動時に一括登録(特権経路は作らない)。
|
||||
- 動的プラグインで override 可能(ポリシーで制御)。
|
||||
|
||||
### Provider/Type 分離(TOML スキーマ案・既定OFF)
|
||||
```toml
|
||||
[types.StringBox]
|
||||
provider = "kernel:string@1.0"
|
||||
allow_override = true
|
||||
|
||||
[providers."kernel:string@1.0"]
|
||||
crate = "nyash-plugin-base-string"
|
||||
|
||||
[providers."acme:string@2.1"]
|
||||
path = "./plugins/libacme_string.so"
|
||||
override = true
|
||||
|
||||
[policy]
|
||||
factory = "plugin-first" # compat_plugin_first | static_only
|
||||
```
|
||||
|
||||
### Verify(必須)
|
||||
- plugin-tester で Lifecycle/TLV/必須メソッドを検証。欠落時は即エラー。
|
||||
|
||||
## 代替案の却下(kernelString 等)
|
||||
|
||||
- 型名に provider を織り込む(例: `kernelString`)案は、差し替え不能化・特権化・型ID安定性の破壊リスクが高く不採用。区別は provider 名で行い、型名(STN)は常に不変とする。
|
||||
|
||||
## ロードマップ(小さく段階的・既定OFF)
|
||||
- K0: ADR/Docs 追加(本書)。
|
||||
- K1: TOML スキーマの雛形(types/providers/policy)を docs と設定ローダに受け口だけ追加(挙動不変)。
|
||||
- K2: 起動時に provider 解決の受け口をログ出力のみで導入(挙動不変)。
|
||||
- K3: Verify フックを preflight_plugins に統合(既定OFF)。
|
||||
- K4: Bootstrap Pack の登録導線(prod 限定フラグ・既定OFF)。
|
||||
|
||||
## 移行 / 互換
|
||||
- 既存コードは型名(STN)のまま変更不要。provider 置換は TOML で完結。
|
||||
- VM fallback の暫定個別救済はフラグ付き/短期で撤去。最終形は `ny_call_method` に集約。
|
||||
|
||||
61
docs/development/design/blueprints/strings-utf8-byte.md
Normal file
61
docs/development/design/blueprints/strings-utf8-byte.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Strings Blueprint — UTF‑8 First, Bytes Separate
|
||||
|
||||
Status: active (Feature‑Pause compatible)
|
||||
Updated: 2025-09-21
|
||||
|
||||
Purpose
|
||||
- Unify string semantics by delegating StringBox public APIs to dedicated cursor boxes.
|
||||
- Keep behavior stable while making codepoint vs byte decisions explicit and testable.
|
||||
|
||||
Pillars
|
||||
- Utf8CursorBox (codepoint-oriented)
|
||||
- length/indexOf/substring operate on UTF‑8 codepoints.
|
||||
- Intended as the default delegate for StringBox public APIs.
|
||||
- ByteCursorBox (byte-oriented)
|
||||
- length/indexOf/substring operate on raw bytes.
|
||||
- Use explicitly for byte-level parsing or binary protocols.
|
||||
|
||||
Delegation Strategy
|
||||
- StringBox delegates to Utf8CursorBox for core methods: length/indexOf/substring.
|
||||
- Provide conversion helpers: toUtf8Cursor(), toByteCursor() (thin wrappers).
|
||||
- Prefer delegation over inheritance; keep “from” minimal to avoid API ambiguity.
|
||||
|
||||
API Semantics
|
||||
- indexOf: define two flavors via the box boundary.
|
||||
- StringBox.indexOf → Utf8CursorBox.indexOf (CP-based; canonical)
|
||||
- ByteCursorBox.indexOf → byte-based; opt‑in only
|
||||
- substring: follow the same split (CP vs Byte). Do not mix semantics.
|
||||
- Document preconditions for indices (out‑of‑range clamped/errored per guide).
|
||||
|
||||
Implementation Plan (staged, non‑breaking)
|
||||
1) Provide MVP cursor boxes (done)
|
||||
- apps/libs/utf8_cursor.nyash
|
||||
- apps/libs/byte_cursor.nyash
|
||||
2) Delegate StringBox public methods to Utf8CursorBox (internal only; behavior unchanged)
|
||||
- Start with length → indexOf → substring
|
||||
- Add targeted smokes for edge cases (multi‑byte CP, boundaries)
|
||||
3) Replace ad‑hoc scans in Nyash scripts with cursor usage (Mini‑VM/macros)
|
||||
- Migrate internal scanners (no external behavior change)
|
||||
4) Introduce ByteCursorBox only where byte‑level semantics are required
|
||||
- Keep call sites explicit to avoid ambiguity
|
||||
|
||||
Transition Gate (Rust dev only)
|
||||
- Env `NYASH_STR_CP=1` enables CP semantics for legacy byte-based paths in Rust runtime (e.g., StringBox.length/indexOf/lastIndexOf).
|
||||
- Default remains byte in Rust during the feature‑pause; PyVM follows CP semantics. CI smokes validate CP behavior via PyVM.
|
||||
|
||||
Related Docs
|
||||
- reference/language/strings.md — policy & scope
|
||||
- guides/language-core-and-sugar.md — core minimal + sugar
|
||||
- reference/language/EBNF.md — operators (! adopted; do‑while not adopted)
|
||||
- guides/loopform.md — loop normalization policy
|
||||
|
||||
Box Foundations (string-related)
|
||||
- Utf8CursorBox, ByteCursorBox
|
||||
- StringExtBox (trim/startsWith/endsWith/replace/split)
|
||||
- StringBuilderBox (append/toString)
|
||||
- JsonCursorBox (lightweight JSON scanning helpers)
|
||||
|
||||
Testing Notes
|
||||
- Keep PyVM as the reference execution path.
|
||||
- Add smokes: CP boundaries, mixed ASCII/non‑ASCII, indexOf not found, substring slices.
|
||||
- Avoid perf work; focus on semantics + observability.
|
||||
13
docs/development/design/legacy/README.md
Normal file
13
docs/development/design/legacy/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Nyash Design Notes
|
||||
|
||||
Public, stable design documents and architecture explanations.
|
||||
|
||||
Use for rationale, trade‑offs, and diagrams that are safe to cite.
|
||||
|
||||
Contents to consolidate here:
|
||||
- Architecture overviews derived from ARCHITECTURE.md
|
||||
- Backend design (LLVM/Cranelift) summaries
|
||||
- MIR/IR evolution notes that are not drafts
|
||||
|
||||
Draft, exploratory, or long‑form papers should remain under `docs/private/` until finalized.
|
||||
|
||||
80
docs/development/design/legacy/flow-blocks.md
Normal file
80
docs/development/design/legacy/flow-blocks.md
Normal file
@ -0,0 +1,80 @@
|
||||
# Flow Blocks and Arrow Piping (Design Draft)
|
||||
|
||||
Status: design-only during the feature‑pause (no implementation)
|
||||
|
||||
Goal
|
||||
- Make control/data flow visually obvious while keeping the core minimal.
|
||||
- Core = anonymous `{ ... }` blocks + `->` chaining with `_` or `|args|` as the input placeholder.
|
||||
- Always desugar to plain sequential let/if/call; zero new runtime constructs.
|
||||
|
||||
Core Syntax
|
||||
- Serial (value flow):
|
||||
```nyash
|
||||
{ readConfig() }
|
||||
-> { |cfg| validate(cfg) }
|
||||
-> { |cfg| normalize(cfg) }
|
||||
-> { |cfg| save(cfg) }
|
||||
```
|
||||
- Placeholder short form:
|
||||
```nyash
|
||||
{ fetch() } -> { process(_) } -> { output(_) }
|
||||
```
|
||||
- If/Else with horizontal flow:
|
||||
```nyash
|
||||
if cond -> { doA() } else -> { doB() }
|
||||
```
|
||||
|
||||
Semantics
|
||||
- `{ ... }` is an anonymous scope usable as expression or statement.
|
||||
- `->` passes the left result as the first parameter of the right block.
|
||||
- Left returns `Void` → right cannot use `_`/`|x|` (compile-time error in MVP spec).
|
||||
- `_` and `|x,...|` are exclusive; mixing is an error.
|
||||
|
||||
Lowering (always zero-cost sugar)
|
||||
- Chain desugars to temporaries and calls:
|
||||
```nyash
|
||||
# {A} -> { |x| B(x) } -> { |y| C(y) }
|
||||
t0 = A();
|
||||
t1 = B(t0);
|
||||
t2 = C(t1);
|
||||
```
|
||||
- If/Else chain desugars to standard if/else blocks; merges follow normal PHI wiring rules.
|
||||
|
||||
Match normalization via guard chains
|
||||
- Prefer a single readable form:
|
||||
```nyash
|
||||
guard cond1 -> { A }
|
||||
guard cond2 -> { B }
|
||||
else -> { C }
|
||||
```
|
||||
- Lowers to first-match if/else chain. No new pattern engine is introduced.
|
||||
|
||||
Range and CharClass guards (design)
|
||||
- Range: `guard ch in '0'..'9' -> { ... }` → `('0' <= ch && ch <= '9')`.
|
||||
- CharClass: `guard ch in Digit -> { ... }` → expands to ranges (e.g., '0'..'9').
|
||||
- Multiple ranges combine with OR.
|
||||
|
||||
Formatting (nyfmt guidance)
|
||||
- Align arrows vertically; one step per line:
|
||||
```
|
||||
{ fetch() }
|
||||
-> { validate(_) }
|
||||
-> { save(_) }
|
||||
```
|
||||
- Suggest factoring when chains exceed N steps; prefer naming a scope helper.
|
||||
|
||||
Observability (design only)
|
||||
- `NYASH_FLOW_TRACE=1` prints the desugared steps (`t0=...; t1=...;`).
|
||||
|
||||
Constraints (MVP)
|
||||
- No new closures; anonymous blocks inline when capture-free.
|
||||
- Recursion not required; focus on linear/branching chains.
|
||||
- ASI: treat `->` as a low-precedence line-continue operator.
|
||||
|
||||
Tests (syntax-only smokes; design)
|
||||
- flow_linear: `read→validate→save` matches expected value.
|
||||
- flow_placeholder: `{f()} -> { process(_) } -> { out(_) }`.
|
||||
- flow_if: `if cond -> {A} else -> {B}` behaves like standard if.
|
||||
|
||||
Pause note
|
||||
- Documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap).
|
||||
32
docs/development/design/legacy/using-loader-integration.md
Normal file
32
docs/development/design/legacy/using-loader-integration.md
Normal 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 (pause‑safe)
|
||||
- Parser stays Phase‑0: keep `nyashstd` restriction; do not widen grammar.
|
||||
- Integration is a runner step (pre‑parse): resolve and register modules; do not change language semantics.
|
||||
|
||||
Design
|
||||
- Strip `using` lines when `NYASH_ENABLE_USING=1` (already implemented).
|
||||
- For each `using ns [as alias]?`:
|
||||
- Resolve `ns` → path via: [modules] → aliases → using.paths (apps/lib/.) → context dir.
|
||||
- Register mapping in `modules_registry` as `alias_or_ns -> path` (already implemented).
|
||||
- Minimal loader hook (defer heavy linking):
|
||||
- Compile/execute entry file as today.
|
||||
- Userland boxes are accessed via tools/runners that read from `modules_registry` where needed (e.g., PyVM harness/tests).
|
||||
|
||||
Notes
|
||||
- Entry thin‑ization (Mini‑VM) waits until loader reads userland boxes on demand.
|
||||
- Keep docs small: this note serves as the canonical link; avoid duplicating details in other pages.
|
||||
|
||||
Preprocessing invariants (runner)
|
||||
- `using` lines are stripped and resolved prior to parse; dependencies are inlined before `Main` so names are available without changing language semantics.
|
||||
- Line‑head `@name[:T] = expr` is normalized to `local name[:T] = expr` as a purely textual pre‑expand (no semantic change). Inline `@` is not recognized; keep `@` at line head.
|
||||
- These steps are pause‑safe: they do not alter AST semantics; they only simplify authoring and module wiring.
|
||||
|
||||
Links
|
||||
- Runner pipeline: src/runner/pipeline.rs
|
||||
- Using strip/resolve: src/runner/modes/common_util/resolve.rs
|
||||
- Env: NYASH_ENABLE_USING, NYASH_USING_STRICT, NYASH_SKIP_TOML_ENV
|
||||
77
docs/development/proposals/concurrency/boxes.md
Normal file
77
docs/development/proposals/concurrency/boxes.md
Normal file
@ -0,0 +1,77 @@
|
||||
# Nyash Concurrency — Box Model (Proposal, docs-only)
|
||||
|
||||
Status: design-only during the feature‑pause. No runtime or spec changes. Implement after Mini‑VM baseline is stable.
|
||||
|
||||
Intent
|
||||
- Bring Go-like CSP (goroutine/channels/select) into Nyash via “Everything is Box”.
|
||||
- Keep semantics explicit, lifecycle safe (birth/fini), and observable. Phase-in from userland → runtime.
|
||||
|
||||
Scope (Phase‑0: userland MVP)
|
||||
- RoutineBox: lightweight task wrapper over `nowait` (state, join/cancel, status).
|
||||
- ChannelBox: bounded/unbounded queue + blocking/non-blocking ops + close semantics.
|
||||
- SelectBox: multi-channel wait (first-ready) with simple fairness.
|
||||
- RoutineScopeBox: structured concurrency; children are canceled on scope fini.
|
||||
- Observability: JSONL trace toggled by `NYASH_CONC_TRACE=1`.
|
||||
|
||||
Non‑Goals (Phase‑0)
|
||||
- M:N scheduler, OS-level park/unpark, net poller integration (deferred to Phase‑2 runtime work).
|
||||
|
||||
API Sketch (userland)
|
||||
- RoutineBox
|
||||
- birth(fn)
|
||||
- start(): Void
|
||||
- join(timeout_ms?: Int) -> Bool // true if joined; false on timeout
|
||||
- cancel(): Void
|
||||
- status() -> String // ready|running|done|canceled|error
|
||||
- ChannelBox(capacity: Int=0)
|
||||
- send(v): Void // blocks if full (Phase‑0: simulated park)
|
||||
- try_send(v) -> Bool
|
||||
- receive() -> Any // blocks if empty (Phase‑0: simulated park)
|
||||
- try_receive() -> (Bool, Any?)
|
||||
- receive_timeout(ms: Int) -> (Bool, Any?)
|
||||
- close(): Void // further send fails; recv drains until empty then End
|
||||
- SelectBox
|
||||
- birth()
|
||||
- when(ch: ChannelBox, handler: Fn): Void
|
||||
- await() -> Bool // returns after one handler runs; false if none ready and no wait policy
|
||||
- await_timeout(ms: Int) -> Bool
|
||||
- RoutineScopeBox
|
||||
- birth()
|
||||
- spawn(fn) -> RoutineBox
|
||||
- fini() // cancels pending routines and waits boundedly
|
||||
|
||||
Semantics
|
||||
- Capacity:
|
||||
- 0: rendezvous channel (send/recv rendezvous).
|
||||
- N>0: bounded ring buffer.
|
||||
- Close:
|
||||
- close() marks channel as closed. send() after close -> error. receive() returns buffered items; when empty -> (false, End) style result; exact return shape defined per API.
|
||||
- Blocking:
|
||||
- Phase‑0 userland uses cooperative wait queues; no busy loops. try_* and timeout variants provided.
|
||||
- Select fairness:
|
||||
- If multiple ready, choose random/round‑robin. Starvation avoidance is a design requirement; precise algorithm can evolve.
|
||||
- Types:
|
||||
- `TypedChannelBox<T>` is a future extension; Phase‑0 uses runtime tags/guards documented in reference.
|
||||
- Cancellation:
|
||||
- RoutineScopeBox cancels children on fini; Channel waits should return (canceled) promptly.
|
||||
|
||||
Phases
|
||||
- Phase‑0 (userland MVP / PyVM first)
|
||||
- Implement the 4 boxes above with minimal queues/waits, plus trace hooks.
|
||||
- Smokes: ping‑pong, bounded producer/consumer, two‑way select, close semantics, scope cancel.
|
||||
- Phase‑1 (park/unpark abstraction)
|
||||
- Introduce `WaiterBox`/`CondBox` that map to efficient OS waits where available. Keep same APIs.
|
||||
- Phase‑2 (runtime integration)
|
||||
- Scheduler (M:N), GC and net poller integration, fairness and profiling. Keep Box APIs stable.
|
||||
|
||||
Observability
|
||||
- `NYASH_CONC_TRACE=1` → JSONL events: spawn/join/cancel/send/recv/park/unpark/select/close with routine IDs, channel IDs, timestamps.
|
||||
|
||||
Safety & Diagnostics
|
||||
- Deadlock hints: trace dependent waits; optional detector (dev only) can dump wait‑for graph.
|
||||
- API contracts explicitly define error return for misuse (send on closed, double close, etc.).
|
||||
|
||||
Deliverables (docs‑only during the feature‑pause)
|
||||
- This proposal (boxes & semantics).
|
||||
- Reference page with blocking/close/select rules (see reference/concurrency/semantics.md).
|
||||
- Test plan with named smokes and expected outputs.
|
||||
74
docs/development/proposals/scope-reuse.md
Normal file
74
docs/development/proposals/scope-reuse.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Scope Reuse Blocks (MVP Proposal)
|
||||
|
||||
Status: design-only during the feature‑pause (no implementation)
|
||||
|
||||
Summary
|
||||
- Give short, reusable logic a name within the current scope without promoting it to a top-level function.
|
||||
- Keep the core small: block body + postfix header sugar; desugar to local function + normal calls.
|
||||
- Zero runtime cost: lowers to let/if/call/ret only (no new instructions/closures).
|
||||
|
||||
Syntax (postfix header; Nyash style)
|
||||
- Block form (multi-statement):
|
||||
```nyash
|
||||
{ /* BODY */ } scope name(arglist?) (-> Ret)?
|
||||
// call within the same scope
|
||||
name(args)
|
||||
```
|
||||
- Expression form (one-liner):
|
||||
```nyash
|
||||
=> EXPR scope name(arglist?) (-> Ret)?
|
||||
```
|
||||
|
||||
Semantics
|
||||
- Visibility: `name` is local to the defining scope; not exported.
|
||||
- Capture: by reference by default. Mutating captured vars requires explicit `mut` on those bindings.
|
||||
- Recursion: disallowed in MVP (can be lifted later).
|
||||
- Errors/exits: same as regular functions (return/cleanup/catch apply at the function boundary).
|
||||
|
||||
Lowering (desugaring)
|
||||
- Transform into a local function plus a local binding for convenience calls.
|
||||
```nyash
|
||||
// { BODY } scope check(a:Int)->Str
|
||||
// ↓ (conceptual)
|
||||
let __cap_me = me; let __cap_locals = { /* needed refs */ };
|
||||
method __scope_check__(a:Int)->Str {
|
||||
return BODY
|
||||
}
|
||||
let check = (x) => __scope_check__(x)
|
||||
```
|
||||
- Captures are passed via hidden arguments or an environment box; no new VM opcodes.
|
||||
|
||||
Examples
|
||||
```nyash
|
||||
{ if x % 2 == 0 { return "even" } return "odd" } scope parity(x:Int)->StringBox
|
||||
|
||||
for i in range(0,10) {
|
||||
print(parity(i))
|
||||
}
|
||||
```
|
||||
|
||||
Safety rules (MVP)
|
||||
- Capture: read-only by default; writes allowed only when the captured binding is declared `mut`.
|
||||
- Name uniqueness: `scope name` must be unique within the scope.
|
||||
- No cross-scope escape: values may be returned but the function reference itself is not exported.
|
||||
|
||||
Observability & Tooling
|
||||
- Add trace toggles (design only):
|
||||
- `NYASH_SCOPE_TRACE=1|json` to emit enter/exit and capture lists as JSONL.
|
||||
- Example: `{ "ev":"enter","sid":42,"caps":["me","cfg","mut total"] }`.
|
||||
- Lints (design only):
|
||||
- Single-use scope → suggest inline.
|
||||
- Excess captures → suggest narrowing.
|
||||
|
||||
Interactions
|
||||
- Works with guard/with/await sugars (it’s just a call).
|
||||
- Compatible with ASI and postfix aesthetics; no new top-level keywords beyond `scope` suffix.
|
||||
|
||||
Tests (syntax-only smokes; design)
|
||||
- scope_basic: called twice → same result.
|
||||
- scope_capture_read: reads `me/foo`.
|
||||
- scope_capture_mut: mutation only allowed when `mut` is present.
|
||||
- scope_with_catch_cleanup: postfix catch/cleanup applied at local-function boundary.
|
||||
|
||||
Pause note
|
||||
- This is documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap).
|
||||
137
docs/development/proposals/unified-members.md
Normal file
137
docs/development/proposals/unified-members.md
Normal 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 (Stage‑3)
|
||||
- Postfix `catch/cleanup` are allowed for computed/once/birth_once/method blocks.
|
||||
- Stored does not accept handlers.
|
||||
|
||||
Semantics
|
||||
- stored: O(1) slot read; `= expr` evaluated once during construction; assignment allowed.
|
||||
- computed: evaluate on each read; assignment is an error unless a setter is declared.
|
||||
- once: evaluate on first read, cache the result, and return it thereafter. If the first evaluation throws and there is no `catch`, mark poisoned and rethrow the same error on later reads (no retry).
|
||||
- birth_once: evaluated before user `birth` body in declaration order; uncaught error aborts construction. Cycles are rejected.
|
||||
|
||||
Lowering (no JSON v0 change)
|
||||
- stored → slot
|
||||
- computed → synthesize `__get_name():T { try body; catch; finally }`, resolve reads to call
|
||||
- once → add hidden `__name: Option<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`
|
||||
- Stage‑3 for handlers: `NYASH_PARSER_STAGE3=1`
|
||||
|
||||
Notes
|
||||
- User experience: read is uniform (`obj.name`), write differs by kind; this keeps mental model simple.
|
||||
- Future: setter syntax (`name: T { get {…} set(v) {…} }`) and aliases (`slot/calc/lazy`) can be added without breaking this core.
|
||||
@ -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.
|
||||
|
||||
@ -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統計・既知の制約)
|
||||
|
||||
@ -29,6 +29,5 @@ One‑pager Template
|
||||
- Notes (constraints / future work)
|
||||
|
||||
Examples
|
||||
- Using→Loader overview: docs/design/using-loader-integration.md
|
||||
- Using→Loader overview: docs/development/design/legacy/using-loader-integration.md
|
||||
- Mini‑VM roadmap: docs/development/roadmap/phases/phase-17-loopform-selfhost/MINI_VM_ROADMAP.md
|
||||
|
||||
|
||||
@ -41,8 +41,8 @@ Acceptance & guardrails (feature‑pause)
|
||||
- Golden texts (Ny → MIR fragments) to lock compatibility where practical.
|
||||
- Lint proposals are documentation-only: single-use scope, long `->` chains, duplicated side effects.
|
||||
|
||||
Related docs
|
||||
- proposals/scope-reuse.md — local scope reuse blocks (MVP)
|
||||
- design/flow-blocks.md — arrow flow + anonymous blocks
|
||||
- Related docs
|
||||
- development/proposals/scope-reuse.md — local scope reuse blocks (MVP)
|
||||
- development/design/legacy/flow-blocks.md — arrow flow + anonymous blocks
|
||||
- reference/language/match-guards.md — guard chains + range/charclass sugar
|
||||
- reference/language/strings.md — UTF‑8 first; proposed digit helpers
|
||||
|
||||
14
docs/guides/examples/README.md
Normal file
14
docs/guides/examples/README.md
Normal 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.
|
||||
32
docs/guides/examples/http_result_patterns.md
Normal file
32
docs/guides/examples/http_result_patterns.md
Normal 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
6
docs/proposals/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Proposals Moved
|
||||
|
||||
提案(RFC)は `docs/development/proposals/` に集約しました。
|
||||
|
||||
- 新しい場所: [../development/proposals/](../development/proposals/)
|
||||
|
||||
@ -1,77 +1,5 @@
|
||||
# Nyash Concurrency — Box Model (Proposal, docs-only)
|
||||
# Moved: Concurrency — Box Model (Proposal)
|
||||
|
||||
Status: design-only during the feature‑pause. No runtime or spec changes. Implement after Mini‑VM baseline is stable.
|
||||
この提案は再編により移動しました。
|
||||
新しい場所: [../../development/proposals/concurrency/boxes.md](../../development/proposals/concurrency/boxes.md)
|
||||
|
||||
Intent
|
||||
- Bring Go-like CSP (goroutine/channels/select) into Nyash via “Everything is Box”.
|
||||
- Keep semantics explicit, lifecycle safe (birth/fini), and observable. Phase-in from userland → runtime.
|
||||
|
||||
Scope (Phase‑0: userland MVP)
|
||||
- RoutineBox: lightweight task wrapper over `nowait` (state, join/cancel, status).
|
||||
- ChannelBox: bounded/unbounded queue + blocking/non-blocking ops + close semantics.
|
||||
- SelectBox: multi-channel wait (first-ready) with simple fairness.
|
||||
- RoutineScopeBox: structured concurrency; children are canceled on scope fini.
|
||||
- Observability: JSONL trace toggled by `NYASH_CONC_TRACE=1`.
|
||||
|
||||
Non‑Goals (Phase‑0)
|
||||
- M:N scheduler, OS-level park/unpark, net poller integration (deferred to Phase‑2 runtime work).
|
||||
|
||||
API Sketch (userland)
|
||||
- RoutineBox
|
||||
- birth(fn)
|
||||
- start(): Void
|
||||
- join(timeout_ms?: Int) -> Bool // true if joined; false on timeout
|
||||
- cancel(): Void
|
||||
- status() -> String // ready|running|done|canceled|error
|
||||
- ChannelBox(capacity: Int=0)
|
||||
- send(v): Void // blocks if full (Phase‑0: simulated park)
|
||||
- try_send(v) -> Bool
|
||||
- receive() -> Any // blocks if empty (Phase‑0: simulated park)
|
||||
- try_receive() -> (Bool, Any?)
|
||||
- receive_timeout(ms: Int) -> (Bool, Any?)
|
||||
- close(): Void // further send fails; recv drains until empty then End
|
||||
- SelectBox
|
||||
- birth()
|
||||
- when(ch: ChannelBox, handler: Fn): Void
|
||||
- await() -> Bool // returns after one handler runs; false if none ready and no wait policy
|
||||
- await_timeout(ms: Int) -> Bool
|
||||
- RoutineScopeBox
|
||||
- birth()
|
||||
- spawn(fn) -> RoutineBox
|
||||
- fini() // cancels pending routines and waits boundedly
|
||||
|
||||
Semantics
|
||||
- Capacity:
|
||||
- 0: rendezvous channel (send/recv rendezvous).
|
||||
- N>0: bounded ring buffer.
|
||||
- Close:
|
||||
- close() marks channel as closed. send() after close -> error. receive() returns buffered items; when empty -> (false, End) style result; exact return shape defined per API.
|
||||
- Blocking:
|
||||
- Phase‑0 userland uses cooperative wait queues; no busy loops. try_* and timeout variants provided.
|
||||
- Select fairness:
|
||||
- If multiple ready, choose random/round‑robin. Starvation avoidance is a design requirement; precise algorithm can evolve.
|
||||
- Types:
|
||||
- `TypedChannelBox<T>` is a future extension; Phase‑0 uses runtime tags/guards documented in reference.
|
||||
- Cancellation:
|
||||
- RoutineScopeBox cancels children on fini; Channel waits should return (canceled) promptly.
|
||||
|
||||
Phases
|
||||
- Phase‑0 (userland MVP / PyVM first)
|
||||
- Implement the 4 boxes above with minimal queues/waits, plus trace hooks.
|
||||
- Smokes: ping‑pong, bounded producer/consumer, two‑way select, close semantics, scope cancel.
|
||||
- Phase‑1 (park/unpark abstraction)
|
||||
- Introduce `WaiterBox`/`CondBox` that map to efficient OS waits where available. Keep same APIs.
|
||||
- Phase‑2 (runtime integration)
|
||||
- Scheduler (M:N), GC and net poller integration, fairness and profiling. Keep Box APIs stable.
|
||||
|
||||
Observability
|
||||
- `NYASH_CONC_TRACE=1` → JSONL events: spawn/join/cancel/send/recv/park/unpark/select/close with routine IDs, channel IDs, timestamps.
|
||||
|
||||
Safety & Diagnostics
|
||||
- Deadlock hints: trace dependent waits; optional detector (dev only) can dump wait‑for graph.
|
||||
- API contracts explicitly define error return for misuse (send on closed, double close, etc.).
|
||||
|
||||
Deliverables (docs‑only during the feature‑pause)
|
||||
- This proposal (boxes & semantics).
|
||||
- Reference page with blocking/close/select rules (see reference/concurrency/semantics.md).
|
||||
- Test plan with named smokes and expected outputs.
|
||||
|
||||
@ -1,74 +1,5 @@
|
||||
# Scope Reuse Blocks (MVP Proposal)
|
||||
# Moved: Scope Reuse Blocks (MVP Proposal)
|
||||
|
||||
Status: design-only during the feature‑pause (no implementation)
|
||||
この提案は再編により移動しました。
|
||||
新しい場所: [../development/proposals/scope-reuse.md](../development/proposals/scope-reuse.md)
|
||||
|
||||
Summary
|
||||
- Give short, reusable logic a name within the current scope without promoting it to a top-level function.
|
||||
- Keep the core small: block body + postfix header sugar; desugar to local function + normal calls.
|
||||
- Zero runtime cost: lowers to let/if/call/ret only (no new instructions/closures).
|
||||
|
||||
Syntax (postfix header; Nyash style)
|
||||
- Block form (multi-statement):
|
||||
```nyash
|
||||
{ /* BODY */ } scope name(arglist?) (-> Ret)?
|
||||
// call within the same scope
|
||||
name(args)
|
||||
```
|
||||
- Expression form (one-liner):
|
||||
```nyash
|
||||
=> EXPR scope name(arglist?) (-> Ret)?
|
||||
```
|
||||
|
||||
Semantics
|
||||
- Visibility: `name` is local to the defining scope; not exported.
|
||||
- Capture: by reference by default. Mutating captured vars requires explicit `mut` on those bindings.
|
||||
- Recursion: disallowed in MVP (can be lifted later).
|
||||
- Errors/exits: same as regular functions (return/cleanup/catch apply at the function boundary).
|
||||
|
||||
Lowering (desugaring)
|
||||
- Transform into a local function plus a local binding for convenience calls.
|
||||
```nyash
|
||||
// { BODY } scope check(a:Int)->Str
|
||||
// ↓ (conceptual)
|
||||
let __cap_me = me; let __cap_locals = { /* needed refs */ };
|
||||
method __scope_check__(a:Int)->Str {
|
||||
return BODY
|
||||
}
|
||||
let check = (x) => __scope_check__(x)
|
||||
```
|
||||
- Captures are passed via hidden arguments or an environment box; no new VM opcodes.
|
||||
|
||||
Examples
|
||||
```nyash
|
||||
{ if x % 2 == 0 { return "even" } return "odd" } scope parity(x:Int)->StringBox
|
||||
|
||||
for i in range(0,10) {
|
||||
print(parity(i))
|
||||
}
|
||||
```
|
||||
|
||||
Safety rules (MVP)
|
||||
- Capture: read-only by default; writes allowed only when the captured binding is declared `mut`.
|
||||
- Name uniqueness: `scope name` must be unique within the scope.
|
||||
- No cross-scope escape: values may be returned but the function reference itself is not exported.
|
||||
|
||||
Observability & Tooling
|
||||
- Add trace toggles (design only):
|
||||
- `NYASH_SCOPE_TRACE=1|json` to emit enter/exit and capture lists as JSONL.
|
||||
- Example: `{ "ev":"enter","sid":42,"caps":["me","cfg","mut total"] }`.
|
||||
- Lints (design only):
|
||||
- Single-use scope → suggest inline.
|
||||
- Excess captures → suggest narrowing.
|
||||
|
||||
Interactions
|
||||
- Works with guard/with/await sugars (it’s just a call).
|
||||
- Compatible with ASI and postfix aesthetics; no new top-level keywords beyond `scope` suffix.
|
||||
|
||||
Tests (syntax-only smokes; design)
|
||||
- scope_basic: called twice → same result.
|
||||
- scope_capture_read: reads `me/foo`.
|
||||
- scope_capture_mut: mutation only allowed when `mut` is present.
|
||||
- scope_with_catch_cleanup: postfix catch/cleanup applied at local-function boundary.
|
||||
|
||||
Pause note
|
||||
- This is documentation and design intent only. Implementation is deferred until after the feature‑pause (post‑bootstrap).
|
||||
|
||||
@ -1,137 +1,5 @@
|
||||
# Property System Revolution for Nyash (2025-09-18 Breakthrough)
|
||||
# Moved: Unified Members Proposal
|
||||
|
||||
Status: **BREAKTHROUGH COMPLETED** - Final syntax decided through AI collaboration with ChatGPT, Claude, and Codex.
|
||||
この提案は再編により移動しました。
|
||||
新しい場所: [../development/proposals/unified-members.md](../development/proposals/unified-members.md)
|
||||
|
||||
## 🌟 Revolutionary Achievement
|
||||
Today we achieved the **Property System Revolution** - a complete unification of stored fields, computed properties, lazy evaluation, and birth-time initialization into a single, elegant syntax system through AI collaboration with ChatGPT5, Claude, and Codex.
|
||||
|
||||
## 🎯 Final Property System Design
|
||||
|
||||
### The Four-Category Breakthrough
|
||||
After dialectical discussion with multiple AI agents, we reached the perfect synthesis:
|
||||
|
||||
#### 1. **stored** - Traditional Field Storage
|
||||
```nyash
|
||||
box Example {
|
||||
name: StringBox // Default initialization
|
||||
count: IntegerBox = 0 // Explicit initialization
|
||||
}
|
||||
```
|
||||
- **Semantics**: O(1) slot read/write, assignment allowed
|
||||
- **Use Case**: Traditional object fields, counters, configurations
|
||||
|
||||
#### 2. **computed** - Calculated Every Access
|
||||
```nyash
|
||||
box Example {
|
||||
size: IntegerBox { me.items.count() }
|
||||
full_name: StringBox { me.first + " " + me.last }
|
||||
}
|
||||
```
|
||||
- **Semantics**: Evaluate body on each read, assignment error unless setter declared
|
||||
- **Use Case**: Derived values, dynamic calculations, Python @property equivalent
|
||||
|
||||
#### 3. **once** - Lazy Evaluation with Caching
|
||||
```nyash
|
||||
box Example {
|
||||
once expensive_data: DataBox { heavy_computation() }
|
||||
once config: ConfigBox { loadConfiguration() }
|
||||
}
|
||||
```
|
||||
- **Semantics**: Evaluate on first read, cache result, return cached value thereafter
|
||||
- **Use Case**: Heavy computations, file loading, Python @cached_property equivalent
|
||||
- **Exception Handling**: Poison-on-throw strategy for safety
|
||||
|
||||
#### 4. **birth_once** - Eager Evaluation at Object Creation
|
||||
```nyash
|
||||
box Example {
|
||||
birth_once startup_data: DataBox { initialize_system() }
|
||||
|
||||
birth() {
|
||||
// birth_once properties already initialized!
|
||||
me.ready = true
|
||||
}
|
||||
}
|
||||
```
|
||||
- **Semantics**: Evaluated before user birth() in declaration order
|
||||
- **Use Case**: System initialization, dependency setup, startup-critical data
|
||||
|
||||
## 🌟 Revolutionary Python Integration
|
||||
|
||||
### Perfect Mapping Strategy
|
||||
```python
|
||||
# Python side
|
||||
class DataProcessor:
|
||||
def __init__(self):
|
||||
self.value = 42 # → stored
|
||||
|
||||
@property
|
||||
def computed_result(self): # → computed
|
||||
return self.value * 2
|
||||
|
||||
@functools.cached_property
|
||||
def expensive_data(self): # → once
|
||||
return heavy_computation()
|
||||
```
|
||||
|
||||
```nyash
|
||||
// Auto-generated Nyash (revolutionary 1:1 mapping!)
|
||||
box DataProcessor {
|
||||
value: IntegerBox // stored
|
||||
computed_result: IntegerBox { me.value * 2 } // computed
|
||||
once expensive_data: ResultBox { heavy_computation() } // once
|
||||
|
||||
birth() {
|
||||
me.value = 42
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Revolution
|
||||
- **computed properties**: No caching overhead, pure calculation
|
||||
- **once properties**: 10-50x faster than Python cached_property (LLVM optimization)
|
||||
- **birth_once properties**: Startup optimization, dependency injection pattern
|
||||
- **Overall**: Python code → 5-20x faster native binary
|
||||
|
||||
Handlers (Stage‑3)
|
||||
- Postfix `catch/cleanup` are allowed for computed/once/birth_once/method blocks.
|
||||
- Stored does not accept handlers.
|
||||
|
||||
Semantics
|
||||
- stored: O(1) slot read; `= expr` evaluated once during construction; assignment allowed.
|
||||
- computed: evaluate on each read; assignment is an error unless a setter is declared.
|
||||
- once: evaluate on first read, cache the result, and return it thereafter. If the first evaluation throws and there is no `catch`, mark poisoned and rethrow the same error on later reads (no retry).
|
||||
- birth_once: evaluated before user `birth` body in declaration order; uncaught error aborts construction. Cycles are rejected.
|
||||
|
||||
Lowering (no JSON v0 change)
|
||||
- stored → slot
|
||||
- computed → synthesize `__get_name():T { try body; catch; finally }`, resolve reads to call
|
||||
- once → add hidden `__name: Option<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`
|
||||
- Stage‑3 for handlers: `NYASH_PARSER_STAGE3=1`
|
||||
|
||||
Notes
|
||||
- User experience: read is uniform (`obj.name`), write differs by kind; this keeps mental model simple.
|
||||
- Future: setter syntax (`name: T { get {…} set(v) {…} }`) and aliases (`slot/calc/lazy`) can be added without breaking this core.
|
||||
|
||||
@ -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統計とプラグイン周りの既知制約
|
||||
|
||||
23
docs/reference/architecture/phi-and-ssa.md
Normal file
23
docs/reference/architecture/phi-and-ssa.md
Normal 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 If‑chain in macro/core pass.
|
||||
|
||||
Testing
|
||||
- LLVM smokes: fixed small cases ensure no empty PHIs and head placement.
|
||||
- MIR smokes: trace `scope|join|loop` to validate shaping without peeking into IR details.
|
||||
|
||||
Roadmap
|
||||
- Remove text-level sanitization once finalize‑PHI is trustworthy across Loop/If/Match.
|
||||
- Expand goldens to cover nested joins and multi‑carrier loops while keeping CI light.
|
||||
|
||||
85
docs/reference/config/nyash-toml.md
Normal file
85
docs/reference/config/nyash-toml.md
Normal 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 Name(STN)を Provider ID(PVN)にマッピング。未指定時は現行ランタイム既定。
|
||||
|
||||
```toml
|
||||
[types.StringBox]
|
||||
provider = "kernel:string@1.0"
|
||||
interop = "forbid" # forbid|explicit|auto(既定: forbid)
|
||||
|
||||
[providers."kernel:string@1.0"]
|
||||
crate = "nyash-plugin-base-string" # 静的リンクのブートストラップ提供者
|
||||
|
||||
[providers."acme:string@2.1"]
|
||||
path = "./plugins/libacme_string.so"
|
||||
override = true
|
||||
|
||||
[policy]
|
||||
factory = "plugin-first" # plugin-first|compat_plugin_first|static_only
|
||||
```
|
||||
|
||||
注意:
|
||||
- 本仕様は「受け口」の段階。実行挙動は段階導入(Verify→Lock→実行)。
|
||||
- 互換性重視のため、未指定時は現行と同じ既定にフォールバックする。
|
||||
|
||||
### [plugins.bootstrap] / [plugins.dynamic](提案)
|
||||
静的リンクのブートストラップ束/動的ロード(開発)を明示。
|
||||
|
||||
```toml
|
||||
[plugins.bootstrap]
|
||||
string = { crate = "nyash-plugin-base-string", version = "2.3.0" }
|
||||
integer = { crate = "nyash-plugin-base-integer", version = "1.5.1" }
|
||||
|
||||
[plugins.dynamic]
|
||||
# string = { path = "./plugins/libnyash_string_plugin.so", override = true }
|
||||
```
|
||||
|
||||
## Profiles(using / AST)
|
||||
`NYASH_USING_PROFILE={dev|ci|prod}`
|
||||
|
||||
- dev/ci: AST プレリュード既定ON(file-usingはdevで許可、ciは警告/限定)
|
||||
- prod: AST 既定OFF(toml 由来のみ、file-using はエラー)
|
||||
|
||||
実装ノート:
|
||||
- AST 既定は `src/config/env.rs: using_ast_enabled()` でプロファイルに従い決定。
|
||||
- 既存のレガシー前置きは prod で禁止、dev/ci でも段階的に削除予定。
|
||||
|
||||
## Verify(plugin-tester)
|
||||
CI/起動前に最低限の契約を検査(例: String の `birth/fini/toUtf8/fromUtf8/equals/length/concat`)。欠落時は即停止。
|
||||
|
||||
## 参考
|
||||
- Kernel/Plugin 方針: docs/reference/runtime/kernel-and-plugins.md
|
||||
- ADR: docs/development/adr/adr-001-no-corebox-everything-is-plugin.md
|
||||
@ -64,7 +64,7 @@ pub enum QualifiedCallee {
|
||||
Policy
|
||||
- Accept `using` lines at the top of the file to declare module namespaces or file imports.
|
||||
- Resolution is performed by the Rust Runner when `NYASH_ENABLE_USING=1`.
|
||||
- 実体の結合は AST マージのみ。テキストの前置き/連結は行わない(移行完了後に完全廃止)。
|
||||
- 実体の結合は AST マージのみ。テキストの前置き/連結は行わない(レガシー経路は呼び出し側から削除済み)。
|
||||
- Runner は `nyash.toml` の `[using]` を唯一の真実として参照(prod)。dev/ci は段階的に緩和可能。
|
||||
- Selfhost compiler (Ny→JSON v0) collects using lines and emits `meta.usings` when present. The bridge currently ignores this meta field.
|
||||
|
||||
@ -191,15 +191,18 @@ Runner Configuration
|
||||
- Enable using pre‑processing: `NYASH_ENABLE_USING=1`
|
||||
- CLI from-the-top registration: `--using "ns as Alias"` or `--using '"apps/foo.nyash" as Foo'` (repeatable)
|
||||
- Using profiles (phase‑in): `NYASH_USING_PROFILE={dev|ci|prod}`
|
||||
- dev: toml + file using(path)可、AST マージ、候補提示 ON
|
||||
- ci: toml 優先、file using は警告/限定、AST マージ、フォールバック OFF
|
||||
- prod: toml のみ、file using/path はエラー(追記ガイドを表示)
|
||||
- dev: AST マージ 既定ON、legacy前置きは既定で無効(必要時は `NYASH_LEGACY_USING_ALLOW=1` で一時許可)
|
||||
- ci: AST マージ 既定ON、legacy前置きは既定で無効(同上の一時許可)
|
||||
- prod: AST マージ 既定OFF、toml のみ(file using/path はエラー・追記ガイド)
|
||||
- Strict mode (plugin prefix required): `NYASH_PLUGIN_REQUIRE_PREFIX=1` または `nyash.toml` の `[plugins] require_prefix=true`
|
||||
- Aliases from env: `NYASH_ALIASES="Foo=apps/foo/main.nyash,Bar=lib/bar.nyash"`
|
||||
- Additional search paths: `NYASH_USING_PATH="apps:lib:."`
|
||||
- Selfhost pipeline keeps child stdout quiet and extracts JSON only: `NYASH_JSON_ONLY=1` (set by Runner automatically for child)
|
||||
- Selfhost emits `meta.usings` automatically when present; no additional flags required.
|
||||
|
||||
Note: Provider/Type 分離(型名は不変で提供者のみを切替)については ADR を参照。
|
||||
docs/development/adr/adr-001-no-corebox-everything-is-plugin.md
|
||||
|
||||
## 🔬 Quick Smokes(AST + Profiles)
|
||||
|
||||
開発・CIで最小コストに確認できるスモークを用意しています。AST プレリュードとプロファイル(dev/prod)の基本動作をカバーします。
|
||||
|
||||
51
docs/reference/runtime/kernel-and-plugins.md
Normal file
51
docs/reference/runtime/kernel-and-plugins.md
Normal 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 init(GC/Handle/TLV/Extern/PluginRegistry)
|
||||
2) nyash.toml 読み込み
|
||||
- `plugins.bootstrap`(静的束)を登録
|
||||
- `plugins.dynamic`(.so/.dll)があれば dlopen 登録
|
||||
3) Plugin Verify(必須メソッド/TLV/ABI)
|
||||
4) 実行(VM/LLVM → `ny_call_method`)
|
||||
|
||||
ブートの不変条件(重要)
|
||||
- Provider Lock(型→提供者の対応表)が確定するまで、いかなる Box 生成も禁止。
|
||||
- Kernel ログ(生バイト)で初期エラーを出力し、`StringBox` 等の Box をログ用途に使わない。
|
||||
- Verify に失敗した場合は、Kernel ログで理由を表示して即終了する。
|
||||
|
||||
## Provider/Type 分離(概要)
|
||||
- Stable Type Name(STN): `StringBox`, `IntegerBox` など。コード上の型名は不変。
|
||||
- Provider ID(PVN): `kernel:string@1.0` / `acme:string@2.1` など実装提供者。
|
||||
- TOML で STN→PVN をバインドし、置換は TOML 側で行う。
|
||||
|
||||
Provider Lock(ロック)
|
||||
- 起動時に `types.<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 による変換のみ許可(UTF‑8 などの正規形式を介する)
|
||||
- auto: 暗黙変換を許可し、変換回数・バイト数をメトリクスに集計(本番非推奨)
|
||||
|
||||
## 現状の段階
|
||||
- 受け口/ドキュメントの整備を先行(挙動は不変)。
|
||||
- using は SSOT+AST に移行済み(prod は file-using 禁止)。
|
||||
- VM fallback の個別救済は暫定(短期で Bootstrap Pack へ移行し撤去)。
|
||||
|
||||
関連ドキュメント
|
||||
- nyash.toml のスキーマと例: docs/reference/config/nyash-toml.md
|
||||
- using(SSOT/AST/Profiles): docs/reference/language/using.md
|
||||
6
docs/status/README.md
Normal file
6
docs/status/README.md
Normal 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
6
docs/tests/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Tests moved
|
||||
|
||||
テスト関連のドキュメントは `docs/development/testing/` に移動しました。
|
||||
|
||||
- 新しい場所: [../development/testing/](../development/testing/)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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!(
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.*) 登録・診断用レジストリ
|
||||
|
||||
38
src/runtime/provider_lock.rs
Normal file
38
src/runtime/provider_lock.rs
Normal 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(())
|
||||
}
|
||||
|
||||
117
src/runtime/provider_verify.rs
Normal file
117
src/runtime/provider_verify.rs
Normal 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) }
|
||||
}
|
||||
65
src/tests/parser_static_box_members.rs
Normal file
65
src/tests/parser_static_box_members.rs
Normal 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"]));
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user