From d90216e9c41a9db1007260bc2833e97fda126c7b Mon Sep 17 00:00:00 2001 From: Selfhosting Dev Date: Mon, 15 Sep 2025 18:44:49 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20Phase=2015=20-=20=E3=82=BB?= =?UTF-8?q?=E3=83=AB=E3=83=95=E3=83=9B=E3=82=B9=E3=83=86=E3=82=A3=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E6=88=A6=E7=95=A5=E3=81=AE=E6=98=8E=E7=A2=BA=E5=8C=96?= =?UTF-8?q?=E3=81=A8EXE-first=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主な変更点 ### 🎯 戦略の転換と明確化 - PyVMを開発ツールとして位置づけ(本番経路ではない) - EXE-first戦略を明確に優先(build_compiler_exe.sh実装済み) - Phase順序の整理: 15.2(LLVM)→15.3(コンパイラ)→15.4(VM) ### 🚀 セルフホスティング基盤の実装 - apps/selfhost-compiler/にNyashコンパイラMVP実装 - compiler.nyash: メインエントリー(位置引数対応) - boxes/: parser_box, emitter_box, debug_box分離 - tools/build_compiler_exe.sh: ネイティブEXEビルド+dist配布 - Python MVPパーサーStage-2完成(local/if/loop/call/method/new) ### 📝 ドキュメント整備 - Phase 15 README/ROADMAP更新(Self-Hosting優先明記) - docs/guides/exe-first-wsl.md: WSLクイックスタート追加 - docs/private/papers/: 論文G~L、爆速事件簿41事例収録 ### 🔧 技術的改善 - JSON v0 Bridge: If/Loop PHI生成実装(ChatGPT協力) - PyVM/llvmliteパリティ検証スイート追加 - using/namespace機能(gated実装、Phase 15では非解決) ## 次のステップ 1. パーサー無限ループ修正(未実装関数の実装) 2. EXEビルドとセルフホスティング実証 3. c0→c1→c1'ブートストラップループ確立 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/pyvm-llvmlite-parity.yml | 55 ++ .github/workflows/pyvm-smoke.yml | 83 ++ AGENTS.md | 71 ++ CLAUDE.md | 11 + CURRENT_TASK.md | 923 +----------------- Cargo.toml | 7 + apps/selfhost-compiler/boxes/debug_box.nyash | 38 + .../selfhost-compiler/boxes/emitter_box.nyash | 18 + apps/selfhost-compiler/boxes/parser_box.nyash | 456 +++++++++ apps/selfhost-compiler/compiler.nyash | 343 ++----- apps/tests/array_min_ops.nyash | 15 + apps/tests/map_min_ops.nyash | 14 + .../roadmap/phases/phase-15/README.md | 20 +- .../roadmap/phases/phase-15/ROADMAP.md | 27 +- .../implementation/mir-builder-exe-design.md | 100 ++ .../phases/phase-15/imports-namespace-plan.md | 2 +- docs/guides/exe-first-wsl.md | 56 ++ docs/guides/style-guide.md | 67 ++ docs/issues/parser_unary_asi_alignment.md | 26 + .../development-log.md | 312 ++++++ .../paper-h-ai-practical-patterns/README.md | 141 +++ .../pattern-categories.md | 273 ++++++ .../paper-i-development-chronicles/README.md | 107 ++ .../daily-decisions-sample.md | 99 ++ .../paper-j-hidden-chronicles/README.md | 107 ++ .../paper-k-explosive-incidents/README.md | 83 ++ .../paper-l-technical-breakthroughs/README.md | 79 ++ .../timeline/nyash-development-timeline.md | 85 ++ docs/reference/ir/json_v0.md | 3 +- docs/reference/language/EBNF.md | 38 + docs/reference/language/README.md | 6 + docs/reference/language/using.md | 45 + nyash-lang-github | 1 - nyash.toml | 437 +-------- plugins/nyash-net-plugin/Cargo.toml | 5 - src/bin/ny_mir_builder.rs | 195 ++++ src/cli.rs | 10 + src/llvm_py/pyvm/vm.py | 108 +- src/runner/json_v0_bridge.rs | 40 +- src/runner/mir_json_emit.rs | 148 +++ src/runner/mod.rs | 73 +- src/runner/modes/common.rs | 275 +++++- src/runner/modes/vm.rs | 10 +- tools/build_compiler_exe.sh | 97 ++ tools/dev_env.sh | 40 + tools/exe_first_runner_smoke.sh | 32 + tools/exe_first_smoke.sh | 39 + tools/mir_builder_exe_smoke.sh | 45 + tools/ny_mir_builder.sh | 127 +++ tools/ny_parser_mvp.py | 13 +- tools/ny_selfhost_using_smoke.sh | 41 + tools/ny_stage1_asi_smoke.sh | 36 + tools/ny_stage2_bridge_smoke.sh | 29 +- tools/ny_stage2_new_method_smoke.sh | 35 + tools/ny_stage2_shortcircuit_smoke.sh | 50 + tools/pyvm_collections_smoke.sh | 31 + tools/pyvm_stage2_call_args_smoke.sh | 36 + tools/pyvm_stage2_compare_smoke.sh | 37 + tools/pyvm_stage2_dot_chain_smoke.sh | 34 + tools/pyvm_stage2_nested_control_smoke.sh | 32 + tools/pyvm_stage2_smoke.sh | 61 ++ tools/pyvm_stage2_unary_smoke.sh | 31 + tools/selfhost_emitter_usings_gate_smoke.sh | 36 + tools/selfhost_json_guard_smoke.sh | 28 + tools/selfhost_progress_guard_smoke.sh | 47 + tools/selfhost_read_tmp_dev_smoke.sh | 24 + tools/selfhost_stage2_bridge_smoke.sh | 59 ++ tools/selfhost_stage2_smoke.sh | 110 +++ 68 files changed, 4521 insertions(+), 1641 deletions(-) create mode 100644 .github/workflows/pyvm-llvmlite-parity.yml create mode 100644 .github/workflows/pyvm-smoke.yml create mode 100644 apps/selfhost-compiler/boxes/debug_box.nyash create mode 100644 apps/selfhost-compiler/boxes/emitter_box.nyash create mode 100644 apps/selfhost-compiler/boxes/parser_box.nyash create mode 100644 apps/tests/array_min_ops.nyash create mode 100644 apps/tests/map_min_ops.nyash create mode 100644 docs/development/roadmap/phases/phase-15/implementation/mir-builder-exe-design.md create mode 100644 docs/guides/exe-first-wsl.md create mode 100644 docs/guides/style-guide.md create mode 100644 docs/issues/parser_unary_asi_alignment.md create mode 100644 docs/private/papers/paper-g-ai-collaboration/development-log.md create mode 100644 docs/private/papers/paper-h-ai-practical-patterns/README.md create mode 100644 docs/private/papers/paper-h-ai-practical-patterns/pattern-categories.md create mode 100644 docs/private/papers/paper-i-development-chronicles/README.md create mode 100644 docs/private/papers/paper-i-development-chronicles/daily-decisions-sample.md create mode 100644 docs/private/papers/paper-j-hidden-chronicles/README.md create mode 100644 docs/private/papers/paper-k-explosive-incidents/README.md create mode 100644 docs/private/papers/paper-l-technical-breakthroughs/README.md create mode 100644 docs/private/papers/timeline/nyash-development-timeline.md create mode 100644 docs/reference/language/EBNF.md create mode 100644 docs/reference/language/using.md delete mode 160000 nyash-lang-github create mode 100644 src/bin/ny_mir_builder.rs create mode 100644 tools/build_compiler_exe.sh create mode 100644 tools/dev_env.sh create mode 100644 tools/exe_first_runner_smoke.sh create mode 100644 tools/exe_first_smoke.sh create mode 100644 tools/mir_builder_exe_smoke.sh create mode 100644 tools/ny_mir_builder.sh create mode 100644 tools/ny_selfhost_using_smoke.sh create mode 100644 tools/ny_stage1_asi_smoke.sh create mode 100644 tools/ny_stage2_new_method_smoke.sh create mode 100644 tools/ny_stage2_shortcircuit_smoke.sh create mode 100644 tools/pyvm_collections_smoke.sh create mode 100644 tools/pyvm_stage2_call_args_smoke.sh create mode 100644 tools/pyvm_stage2_compare_smoke.sh create mode 100644 tools/pyvm_stage2_dot_chain_smoke.sh create mode 100644 tools/pyvm_stage2_nested_control_smoke.sh create mode 100644 tools/pyvm_stage2_smoke.sh create mode 100644 tools/pyvm_stage2_unary_smoke.sh create mode 100644 tools/selfhost_emitter_usings_gate_smoke.sh create mode 100644 tools/selfhost_json_guard_smoke.sh create mode 100644 tools/selfhost_progress_guard_smoke.sh create mode 100644 tools/selfhost_read_tmp_dev_smoke.sh create mode 100644 tools/selfhost_stage2_bridge_smoke.sh create mode 100644 tools/selfhost_stage2_smoke.sh diff --git a/.github/workflows/pyvm-llvmlite-parity.yml b/.github/workflows/pyvm-llvmlite-parity.yml new file mode 100644 index 00000000..ebafdedf --- /dev/null +++ b/.github/workflows/pyvm-llvmlite-parity.yml @@ -0,0 +1,55 @@ +name: PyVM ↔ llvmlite Parity (Optional) + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * *' + +jobs: + parity: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + NYASH_DISABLE_PLUGINS: '1' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust (stable) + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry and build + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Install LLVM 18 (llvm-config-18) + run: | + sudo apt-get update + sudo apt-get install -y curl ca-certificates lsb-release wget gnupg + curl -fsSL https://apt.llvm.org/llvm.sh -o llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 18 + llvm-config-18 --version + + - name: Build nyash (release) + run: cargo build --release -j 2 + + - name: Parity esc_dirname_smoke (PyVM vs llvmlite) + run: tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/esc_dirname_smoke.nyash --timeout 20 + + - name: Parity string_ops_basic (PyVM vs llvmlite) + run: tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/string_ops_basic.nyash --timeout 20 + + - name: Parity ternary_nested (PyVM vs llvmlite) + run: tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/ternary_nested.nyash --timeout 20 + + - name: Parity peek_return_value (PyVM vs llvmlite) + run: tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/peek_return_value.nyash --timeout 20 + diff --git a/.github/workflows/pyvm-smoke.yml b/.github/workflows/pyvm-smoke.yml new file mode 100644 index 00000000..ecd95493 --- /dev/null +++ b/.github/workflows/pyvm-smoke.yml @@ -0,0 +1,83 @@ +name: PyVM + Using Smoke + +on: + push: + paths: + - 'src/**' + - 'apps/**' + - 'tools/**' + - 'docs/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/pyvm-smoke.yml' + pull_request: + paths: + - 'src/**' + - 'apps/**' + - 'tools/**' + - 'docs/**' + +jobs: + pyvm-smokes: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + NYASH_DISABLE_PLUGINS: '1' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust (stable) + uses: dtolnay/rust-toolchain@stable + + - name: Install ripgrep + run: | + sudo apt-get update + sudo apt-get install -y ripgrep + + - name: Cache cargo registry and build + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Build (release) + run: cargo build --release -j 2 + + - name: PyVM Stage-2 smokes + run: bash tools/pyvm_stage2_smoke.sh + + - name: PyVM nested control smokes + run: bash tools/pyvm_stage2_nested_control_smoke.sh + + - name: PyVM call/args smokes + run: bash tools/pyvm_stage2_call_args_smoke.sh + + - name: Bridge Stage-2 (parser MVP) smokes + run: bash tools/ny_stage2_bridge_smoke.sh + + - name: PyVM collections smokes + run: bash tools/pyvm_collections_smoke.sh + + - name: PyVM compare smokes + run: bash tools/pyvm_stage2_compare_smoke.sh + + - name: Bridge Stage-2 short-circuit smoke + run: bash tools/ny_stage2_shortcircuit_smoke.sh + + - name: PyVM Stage-2 dot-chain smoke + run: bash tools/pyvm_stage2_dot_chain_smoke.sh + + - name: PyVM Stage-2 new/method smoke + run: bash tools/ny_stage2_new_method_smoke.sh + + - name: Selfhost using acceptance (no-op) + run: bash tools/ny_selfhost_using_smoke.sh + + - name: Emitter meta.usings gate (fallback allowed) + run: bash tools/selfhost_emitter_usings_gate_smoke.sh diff --git a/AGENTS.md b/AGENTS.md index 23ce5789..b907f0cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,77 @@ nyash哲学の美しさを追求。ソースは常に美しく構造的、カプ - ランナー: `src/runner/modes/cranelift.rs`, `--jit-direct` は `src/runner/mod.rs` - 進行中の論点と手順は `CURRENT_TASK.md` を参照してね(最新のデバッグ方針・フラグが載ってるよ)。 +**PyVM 主経路(Phase‑15 方針)** +- 主経路: Python/llvmlite + PyVM を標準の実行/検証経路として扱うよ。Rust VM/JIT は補助(保守/比較/プラグイン検証)。 +- 使い分け: + - PyVM(推奨・日常確認): `NYASH_VM_USE_PY=1 ./target/release/nyash --backend vm apps/APP/main.nyash` + - llvmlite ハーネス: `NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash --backend llvm apps/APP/main.nyash` + - パリティ検証: `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/CASE.nyash` +- 自己ホスト(Ny→JSON v0): `NYASH_USE_NY_COMPILER=1` は emit‑only 既定で運用(`NYASH_NY_COMPILER_EMIT_ONLY=1`)。子プロセスは Quiet pipe(`NYASH_JSON_ONLY=1`)。 +- 子プロセス安全策: タイムアウト `NYASH_NY_COMPILER_TIMEOUT_MS`(既定 2000ms)。違反時は kill→フォールバック(無限ループ抑止)。 +- スモーク(代表): + - PyVM Stage‑2: `tools/pyvm_stage2_smoke.sh` + - PHI/Stage‑2: `tools/ny_parser_stage2_phi_smoke.sh` + - Bridge/Stage‑2: `tools/ny_stage2_bridge_smoke.sh` + - 文字列/dirname など: `apps/tests/*.nyash` を PyVM で都度確認 +- 注意: Phase‑15 では VM/JIT は MIR14 以降の更新を最小とし、PyVM/llvmlite のパリティを最優先で維持するよ。 + +Docs links(開発方針/スタイル) +- Language statements (ASI): `docs/reference/language/statements.md` +- using 文の方針: `docs/reference/language/using.md` +- Nyash ソースのスタイルガイド: `docs/guides/style-guide.md` +- Stage‑2 EBNF: `docs/reference/language/EBNF.md` + +Dev Helpers +- 推奨フラグ一括: `source tools/dev_env.sh pyvm`(PyVMを既定、Bridge→PyVM直送: `NYASH_PIPE_USE_PYVM=1`) +- 解除: `source tools/dev_env.sh reset` + +Selfhost 子プロセスの引数透過(開発者向け) +- 親→子にスクリプト引数を渡す環境変数: + - `NYASH_NY_COMPILER_MIN_JSON=1` → 子に `-- --min-json` + - `NYASH_SELFHOST_READ_TMP=1` → 子に `-- --read-tmp`(`tmp/ny_parser_input.ny` を FileBox で読み込む。CIでは未使用) + - `NYASH_NY_COMPILER_CHILD_ARGS` → スペース区切りで子にそのまま渡す +- 子側(apps/selfhost-compiler/compiler.nyash)は `--read-tmp` を受理して `tmp/ny_parser_input.ny` を読む(plugins 必要)。 + +**PyVM Scope & Policy(Stage‑2 開発用の範囲)** +- 目的: PyVM は「開発用の参照実行器」だよ。JSON v0 → MIR 実行の意味論確認と llvmlite とのパリティ監視に使う(プロダクション最適化はしない)。 +- 必須命令: `const/binop/compare/branch/jump/ret/phi`、`call/externcall/boxcall`(最小)。 +- Box/メソッド(最小実装): + - ConsoleBox: `print/println/log` + - String: `length/substring/lastIndexOf/esc_json`、文字列連結(`+`) + - ArrayBox: `size/len/get/set/push/toString` + - MapBox: `size/has/get/set/toString`(キーは文字列前提) + - FileBox: 読み取り限定の `open/read/close`(必要最小) + - PathBox: `dirname/join`(POSIX 風の最小) +- 真偽・短絡: 比較は i64 0/1、分岐は truthy 規約。`&&`/`||` は分岐+PHI で短絡を表現(副作用なしは Bridge、ありは PyVM 側で検証)。 +- エントリ/終了: `--entry` 省略時に `Main.main`/`main` を自動解決。整数は exit code に反映、bool は 0/1。 +- 非対象(やらない): プラグイン動的ロード/ABI、GC/スケジューラ、例外/非同期、大きな I/O/OS 依存、性能最適化。 +- 運用ポリシー: 仕様差は llvmlite に合わせて PyVM を調整。未知の extern/boxcall は安全に `None`/no-op。既定は静音、`NYASH_CLI_VERBOSE=1` で詳細。 +- 実行とスモーク: + - PyVM 実行: `NYASH_VM_USE_PY=1 ./target/release/nyash --backend vm apps/tests/CASE.nyash` + - 代表スクリプト: `tools/pyvm_stage2_smoke.sh`, `tools/pyvm_collections_smoke.sh`, `tools/pyvm_stage2_dot_chain_smoke.sh` + - Bridge 短絡(RHS スキップ): `tools/ny_stage2_shortcircuit_smoke.sh` +- CI: `.github/workflows/pyvm-smoke.yml` を常時緑に維持。LLVM18 がある環境では `tools/parity.sh --lhs pyvm --rhs llvmlite` を任意ジョブで回す。 + +**Interpreter vs PyVM(実行経路の役割と優先度)** +- 優先経路: PyVM(Python)を“意味論リファレンス実行器”として採用。日常の機能確認・CI の軽量ゲート・llvmlite とのパリティ監視を PyVM で行う。 +- 補助経路: Rust の MIR Interpreter は純Rust単独で回る簡易器として維持。拡張はしない(BoxCall 等の未対応は既知)。Python が使えない環境での簡易再現や Pipe ブリッジの補助に限定。 +- Bridge(--ny-parser-pipe): 既定は Rust MIR Interpreter を使用。副作用なしの短絡など、実装範囲内を確認。副作用を含む実行検証は PyVM スモーク側で担保。 +- 開発の原則: 仕様差が出た場合、llvmlite に合わせて PyVM を優先調整。Rust Interpreter は凍結維持(安全修正のみ)。 + +**脱Rust(開発効率最優先)ポリシー** +- Phase‑15 中は Rust VM/JIT への新規機能追加を最小化し、Python(llvmlite/PyVM)側での実装・検証を優先する。 +- Runner/Bridge は必要最小の配線のみ(子プロセスタイムアウト・静音・フォールバック)。意味論の追加はまず PyVM/llvmlite に実装し、必要時のみ Rust 側へ反映。 + +**Self‑Hosting への移行(PyVM → Nyash)ロードマップ(将来)** +- 目標: PyVM の最小実行器を Nyash スクリプトへ段階移植し、自己ホスト中も Python 依存を徐々に縮小する。 +- ステップ(小粒度): + 1) Nyash で MIR(JSON) ローダ(ファイル→構造体)を実装(最小 op セット)。 + 2) const/binop/compare/branch/jump/ret/phi を Nyash で実装し、既存 PyVM スモークを通過。 + 3) call/externcall/boxcall(最小)・String/Array/Map の必要メソッドを Nyash で薄く実装。 + 4) CI は当面 PyVM を主、Nyash 実装は実験ジョブとして並走→安定後に切替検討。 +- 注意: 本移行は自己ホストの進捗に合わせて段階実施(Phase‑15 では設計・骨格の準備のみ)。 + ⚠ 現状の安定度に関する重要メモ(Phase‑15 進行中) - VM と Cranelift(JIT) は MIR14 へ移行中のため、現在は実行経路として安定していないよ(検証・実装作業の都合で壊れている場合があるにゃ)。 - 当面の実行・配布は LLVM ラインを最優先・全力で整備する方針だよ。開発・確認は `--features llvm` を有効にして進めてね。 diff --git a/CLAUDE.md b/CLAUDE.md index f98e29ec..64134d8c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -238,6 +238,7 @@ NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash program.nyash - 📚 peek式の再発見 - when→peekに名前変更、ブロック/値/文すべて対応済み - 🧠 箱理論でSSA構築を簡略化(650行→100行)- 論文執筆完了 - 🤝 AI協働の知見を論文化 - 実装駆動型学習の重要性を実証 +- 🎉 **面白事件ログ収集完了!** 41個の世界記録級事件を記録 → [CURRENT_TASK.md#面白事件ログ](CURRENT_TASK.md#🎉-面白事件ログ---ai協働開発45日間の奇跡41事例収集済み) - 🎯 **LoopForm戦略決定**: PHIは逆Lowering時に自動生成(Codex推奨) - 📋 詳細: [Phase 15 README](docs/development/roadmap/phases/phase-15/README.md) @@ -606,6 +607,16 @@ NYASH_SKIP_TOML_ENV=1 ./tools/smoke_plugins.sh 詳細: [開発プラクティス](docs/guides/development-practices.md) +## 🎆 面白事件ログ(爆速開発の記録) + +### 世界記録級の事件たち: +- **JIT1日完成事件**: 2週間予定が1日で完成(8/27伝説の日) +- **プラグインBox事件**: 「こらー!」でシングルトン拒否 +- **AIが人間に相談**: ChatGPTが「助けて」と言った瞬間 +- **危険センサー発動**: 「なんか変だにゃ」がAIを救う + +詳細は[開発事件簿](docs/private/papers/paper-k-explosive-incidents/)へ! + ## ⚠️ Claude実行環境の既知のバグ 詳細: [Claude環境の既知のバグ](docs/tools/claude-issues.md) diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 0ba711b3..34dee4ca 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,874 +1,51 @@ -# Current Task (2025-09-14 改定) — Phase 15 llvmlite(既定)+ PyVM(新規) +# Current Task — Phase 15 Self‑Hosting (2025‑09‑15) + +TL;DR +- 目標は「自己ホスティング達成」= Nyash製パーサで Ny → JSON v0 → Bridge → MIR 実行を安定化すること。 +- PyVM は意味論の参照実行器(開発補助)。llvmlite は AOT/検証。配布やバンドル化は後回し(基礎固めが先)。 + +What Changed (today) +- ParserBox 強化(apps/selfhost-compiler/boxes/parser_box.nyash) + - 進捗ガードを追加(parse_program2/parse_block2/parse_stmt2): 位置非前進なら 1 文字強制前進して gpos 更新(無限ループ防止)。 + - Stage‑2 ヘルパ実装: starts_with_kw/i2s/read_ident2/read_string_lit/add_using。 + - 単項マイナス(unary)を 0−expr で構文化済み。論理(&&/||)/比較/呼出/メソッド/引数/if/else/loop/using/local/return を受理。 +- Smokes 追加(自己ホスト集中) + - `tools/selfhost_progress_guard_smoke.sh`(不完全入力でもハングしないことを検証)。 + - `tools/selfhost_stage2_smoke.sh`(自己ホスト → Interpreter で基本文法 E2E)。 + - `tools/selfhost_stage2_bridge_smoke.sh`(自己ホスト → JSON → PyVM で Array/String/Console を含めた E2E)。 + +Current Status +- 自己ホスト Stage‑2 サブセットは Ny → JSON v0 まで通る。Interpreter 経路で BoxCall を使わない集合は E2E 緑。 +- Array/String/Console などの BoxCall を含む集合は Bridge→PyVM 経路で実行・検証。 +- Runner: `NYASH_USE_NY_COMPILER=1` で自己ホスト経路 ON(子プロセス JSON v0→Bridge→MIR 実行)。 + +Open +- 短絡(&&/||)の入れ子: Bridge の merge/PHI incoming をログ基準で固定化(rhs_end→merge の incoming を `(rhs_end,rval)/(fall_bb,cdst)` に正規化)。 +- `me` の扱い: MVP は `NYASH_BRIDGE_ME_DUMMY=1` で仮注入(将来撤去)。 +- Stage‑2 正常系の網羅: nested call/method/new/var/compare/logical/if/else/loop の代表強化。 + +Plan (to Self‑Hosting) +1) Phase‑1: Stage‑2 完了+堅牢化(今ここ) + - 正常系スモークを自己ホスト直/Bridge(PyVM)で常緑化。 + - 進捗ガードの継続検証(不完全入力セット)。 +2) Phase‑2: Bridge 短絡/PHI 固定+パリティ収束 + - 入れ子短絡の merge/PHI incoming を固定し、stdout 判定でスモークを緑化。 + - PyVM/llvmlite パリティを常時緑(代表ケースを exit code 判定へ統一)。 +3) Phase‑3: Bootstrap c0→c1→c1’ + - emit‑only で c1 を生成→既存経路にフォールバック実行、正規化 JSON 差分で等価を確認。 + +How to Run (dev) +- 推奨環境: `source tools/dev_env.sh pyvm`(PyVM を既定。Bridge→PyVM 直送) +- 自己ホスト(子経路 ON): `NYASH_USE_NY_COMPILER=1` +- 安全弁: `NYASH_NY_COMPILER_TIMEOUT_MS=2000`、emit‑only 既定: `NYASH_NY_COMPILER_EMIT_ONLY=1` + +Smokes +- 無限ループ防止: `./tools/selfhost_progress_guard_smoke.sh` +- 自己ホスト → Interpreter(BoxCallなし集合): `./tools/selfhost_stage2_smoke.sh` +- 自己ホスト → JSON → PyVM(Array/String/Console 含む): `./tools/selfhost_stage2_bridge_smoke.sh` + +Notes / Policies +- PyVM は意味論の参照実行器として運用(exit code 判定を基本)。 +- Bridge は JSON v0 → MIR 降下で PHI を生成(Phase‑15 中は現行方式を維持)。 +- 配布/バンドル/EXE 化は任意の実験導線として維持(Phase‑15 の主目的外)。 -Handoff — TL;DR(2025‑09‑15) -- フェーズ: 15.3(Ny コンパイラMVP 入口を統合済み/Stage‑1 実装中) -- ランナー統合: `NYASH_USE_NY_COMPILER=1` で selfhost compiler を子プロセス実行→stdout の JSON v0 を Bridge→MIR 実行(失敗時は自動フォールバック) -- Selfhost compiler 配置: `apps/selfhost-compiler/`(`compiler.nyash` がエントリ。parser/emitter骨子あり) -- 進捗: - - Stage‑1 パーサ骨子(number/string/term/expr/returnスキップ)実装済み(2パス gpos 方針)。 - - セミコロン最小対応: selfhost 側で `;` を空白同等にスキップ(改行ベース+必要時のみ;)。Rustパーサは未対応のまま。 - - 仕様ドキュメント更新: `docs/reference/language/statements.md`(改行+最小ASI)。 - - imports/namespace/nyash.toml: Phase‑15 中は Runner(Rust) で解決継続。selfhost は `using` 受理(no‑op)を 15.3後半に導入予定(計画: `docs/development/roadmap/phases/phase-15/imports-namespace-plan.md`)。VM 変更は不要。 -- そのまま回せるスモーク(緑): - - `tools/ny_stage2_bridge_smoke.sh`(算術/比較/短絡/ネストif) - - `tools/ny_parser_stage2_phi_smoke.sh`(If/Loop PHI) - - `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/esc_dirname_smoke.nyash` - - `tools/selfhost_compiler_smoke.sh`(selfhost 入口/MVPはResult:0) -- 次にやること(短期): - 1) Stage‑1: `return 1+2*3` の JSON v0 正常化(左結合/優先度準拠)→ Bridge → MIR 実行 = 7 を確定。 - 2) 最小ASI導入(深さ0・直前が継続子でない改行のみ文終端)。代表ケースのスモーク追加(if/else連結、return継続、演算子継続、ドットチェイン)。 - 3) Stage‑2 着手(local/if/loop/call/method/new/var/論理/比較)— PHI は Bridge 側に委譲のまま。 -- 守ること: - - PHI: Bridge 側(If/Loop)で合流、LoopForm(Core‑14) 導入時に逆Loweringで自動化へ(現状維持)。 - - imports/namespace: 15.3 は Runner で解決維持(selfhost は no‑op 受理のみ)。 - - フォールバックは常時安全(selfhost 経路失敗→既存経路)。 - - -Quick Summary(反映内容) -- 実装済み: peek CFG修正、llvmlite文字列ブリッジ、混在‘+’結合、追加テスト緑。 -- JSON v0 Bridge拡張: Stmt(Expr/Local/If/Loop)、Expr(Call/Method/New/Var) 降下を実装。 -- PHI合流: If/Loop の PHI 合流を Bridge 側で実装(If: then/else→merge、Loop: preheader/loop‑latch→header)。 -- PythonパーサMVP: Stage‑2 サブセット(local/if/loop/call/method/new/var ほか)を出力。 -- Ny コンパイラMVP(入口): `NYASH_USE_NY_COMPILER=1` で Ny→JSON v0 パスを試行(失敗時は自動フォールバック)。初期実装は `apps/selfhost-compiler/compiler.nyash`(優先)/`apps/selfhost/parser/ny_parser_v0/main.nyash` を子プロセスで実行して JSON を収集。 -- 文分離ポリシー: 「改行ベース+必要時のみセミコロン」。最小ASI(深さ0かつ直前が継続子でない改行のみ文終端)。ドキュメント反映済み。 -- Outstanding: then/else 片側のみで新規生成された変数のスコープ(現在は外へ未伝播)。 -- Next(handoff): Stage‑2 E2E緑化→me の扱い検討(Method糖衣)。 -- 後続計画: MIR‑SSA(Sealed SSA)骨子を 15.2 終盤で投入(既定OFF)、15.3 で並走→15.3 終盤で既定化。 -- How to run: 下の手順に確認コマンドを追記。 - -Hot Update — 2025‑09‑14(JSON v0 Bridge/Parser Stage‑2 + Parity hardening) -- llvmlite/PyVM parity 強化(緑維持): - - peek の MIR 降下を修正(entry→dispatch 明示 jump、then/else/merge 正規 CFG、全ブロック終端保障)。 - - console.* の文字列引数を to_i8p_h ブリッジで正規化(from_i8_string 直呼び回避)。 - - “文字列+数値” 混在 ‘+’ を concat_si/is+from_i8_string による橋渡しで安定化、両辺文字列は concat_hh。 - - 代表追加テスト(緑): string_ops_basic / me_method_call / loop_if_phi。 - - 追加パリティ確認(緑): string_ops_basic / peek_expr_block / peek_return_value / ternary_basic / ternary_nested / esc_dirname_smoke。 - -- JSON v0 Bridge(Option A)の受け口を Stage‑2 方向に拡張(src/runner/json_v0_bridge.rs) - - StmtV0: Expr / Local / If / Loop を追加。If/Loop は実ブロック生成(then/else/merge、cond/body/exit)まで実装、未終端 Jump 補完、最後に未終端ブロックへ ret 0 補完。 - - ExprV0: Call / Method / New / Var を追加。Lowering: Call→Const+Call、Method→BoxCall、New→NewBox、Var→簡易 var_map 解決。 - - 現状の制限: 変数の合流(PHI)は未実装(If/Loop 内で同名 Local を更新→外側参照する場合の値統合)。 - -- Python Parser MVP(tools/ny_parser_mvp.py)を Stage‑2 サブセットへ拡張 - - 構文: local / if / loop / call / method / new / var / 比較(==,!=,<,>,<=,>=)/ 論理(&&,||)/ 算術。 - - 出力: 上記に対応する JSON v0(Bridge と互換)。 - - 確認: 小さな local/return ケースは JSON→Bridge→MIR で実行可。If/Loop の合流は Bridge 側 PHI 実装後に E2E 緑化予定。 - -Outstanding(要対応) -- PHI 合流(JSON v0 ブリッジ側) - - If: then/else で同名 Local を更新した変数を merge で Phi 統合(なければ片側/既存値を採用)。 - - Loop: header で初期値と body 末端の更新を Phi 化(latch→header)。 - - 変数スコープ: 現状は簡易 var_map。PHI 決定時に then_vars / else_vars / 事前値の差分から対象を検出。 - -Next(handoff short plan) -1) JSON Bridge: PHI 合流実装(If/Loop)と最小テスト追加(ループ後/if後の変数参照)。 -2) Parser MVP: Stage‑2 生成 JSON の if/loop ケースで Bridge→MIR→PyVM/llvmlite の parity 緑化。 -3) me の扱い(MVP方針) - - JSON v0: `Method{ recv: Var{"me"} }` を許容(文脈があれば var_map に解決)。 - - Bridge: 既定は未定義エラー。デバッグ用に `NYASH_BRIDGE_ME_DUMMY=1`(任意 `NYASH_BRIDGE_ME_CLASS`=Main 既定)で `NewBox{class}` を注入して `me` を仮解決。 - - 将来: Ny Builder が box/method 文脈で `me` を提供(Bridge のダミーは撤去予定)。 - -How to run(現状確認) -- Build(release): `cargo build --release`(必要に応じて `NYASH_CLI_VERBOSE=1`)。 -- Parser MVP RT(算術/return): `./tools/ny_parser_mvp_roundtrip.sh`(緑)。 -- Bridge(JSON v0 パイプ): `echo '{...}' | target/release/nyash --ny-parser-pipe`。 -- Parser Stage‑2 → Bridge: `python3 tools/ny_parser_mvp.py tmp/sample.ny | target/release/nyash --ny-parser-pipe`。 -- Bridge smoke(一式): `./tools/ny_parser_bridge_smoke.sh`(pipe/--json-file の両経路)。 -- Stage‑2 PHI smoke: `./tools/ny_parser_stage2_phi_smoke.sh`(If/Loop の PHI 合流検証)。 -- me dummy smoke(デバッグゲート): `./tools/ny_me_dummy_smoke.sh`(`NYASH_BRIDGE_ME_DUMMY=1` で Var("me") をダミー注入)。 -- Stage‑2 Bridge smoke(サブセット一括): `./tools/ny_stage2_bridge_smoke.sh`(算術/比較/短絡/ネストif)。 -- 自己ホスト準備(ブートストラップ): `./tools/bootstrap_selfhost_smoke.sh`(c0→c1→c1'、MVPはフォールバック許容)。 -- Nyコンパイラ入口スモーク: `./tools/selfhost_compiler_smoke.sh`(json_v0 emit → Result:0)。 - -Phase 15.3 — Ny compiler MVP 詳細計画(抜粋) -- 入口: Runner に `NYASH_USE_NY_COMPILER=1` を追加し、selfhost compiler を子プロセス実行(stdout の JSON v0 を Bridge へ)。失敗は自動フォールバック。 -- Stage‑1(小さく積む) - 1) return / 整数 / 文字列 / 四則 / 括弧(左結合)。 - 2) 文分離(最小ASI): 改行=文区切り、継続子(+ - * / . ,)やグルーピング中は継続。 - 3) スモーク: `return 1+2*3` → JSON v0 → Bridge → MIR 実行 = 7。 -- Stage‑2(本命) - - local / if / loop / call / method / new / var / 比較 / 論理(短絡)。 - - PHI: Bridge 側の合流(If/Loop)に依存(Phase‑15 中は現行維持)。 - - スモーク: nested if / loop 累積 / 短絡 and/or × if/loop。 -- 受け入れ(15.3) - - Stage‑1: 代表サンプル緑(PyVM/llvmlite 一致)。 - - Bootstrap: `tools/bootstrap_selfhost_smoke.sh` PASS(フォールバックは許容)。 - - Docs: `docs/reference/language/statements.md` 公開(方針/例/実装ノート)。 - -Parity memo(確認済み) -- `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/string_ops_basic.nyash` → 緑 -- `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/peek_expr_block.nyash` → 緑 -- `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/peek_return_value.nyash` → 緑 -- `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/ternary_basic.nyash` → 緑 -- `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/ternary_nested.nyash` → 緑 -- `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/esc_dirname_smoke.nyash` → 緑 - -Notes — PHI 方針(Phase‑15→Core‑14) -- Phase‑15: 現行の Bridge‑PHI(If/Loop 合流)を維持して完成まで駆け抜ける(変更しない)。 -- Core‑14: LoopForm を強化し、逆Loweringで PHI を自動生成(構造ブロック→合流点 PHI)。 -- JSON v0: Phase‑15 は従来通り PHI を含める/Core‑14 以降は非必須(将来は除外方向)。 - -Context Snapshot — Open After Reset -- Status: A6 受入(PyVM↔llvmlite parity + LLVM verify→.o→EXE)完了。 -- Lang: peek ブロック式(最後の式が値)OK、式文フォールバックOK、三項(?:)パーサ導入済み(VM E2E 緑)。 -- Docs: 言語/アーキの入口を整備(guides/language-guide.md, reference/language/**, reference/architecture/**)。 -- Parser MVP: Python Stage‑2 サブセットまで拡張(Stage‑1 roundtrip 緑維持)。Nyash 実装スケルトン配置済み。 - -Next (short) -1) 三項(?:)の PyVM/llvmlite パリティE2E(`tools/parity.sh`) -2) peek サンプル/テスト拡充(式文・ブロック・戻り値) -3) Docs 追補(最小例、must_use 注意) -4) Parser MVP Stage‑2 設計(local/if/loop/call/method/new/me/substring/length/lastIndexOf) - -Quick Verify -- Ternary (VM): create file with `return (1 < 2) ? 10 : 20` and run - `./target/release/nyash --backend vm /tmp/tern4.nyash` -- Peek block (VM): `./target/release/nyash --backend vm apps/tests/peek_expr_block.nyash` -- Python parser RT: `NYASH_CLI_VERBOSE=1 tools/ny_parser_mvp_roundtrip.sh` - -Summary -- JIT/Cranelift は一時停止。Rust/inkwell LLVM は参照のみ。 -- 既定の実行/ビルド経路は Python/llvmlite ハーネス(MIR JSON→.o→NyRT link)。 -- 2本目の実行経路として PyVM(Python MIR VM)を導入し、llvmlite との機能同値で安定化する。 -- A6 受入(parity+verify)を達成済み。次は Nyash パーサMVP 着手。 - -Quick Status — 2025‑09‑14(A6 Accepted) -- esc_dirname_smoke: PyVM↔llvmlite パリティ一致(ゲートOFF)。 -- dep_tree_min_string: PyVM↔llvmlite パリティ一致。llvmlite 経路で `.ll verify → .o → EXE` 完走。 -- 一時救済ゲート `NYASH_LLVM_ESC_JSON_FIX` は受入では未使用(OFF)。 -- Resolver‑only/Sealed SSA/文字列ハンドル不変は継続運用。IRダンプ/PHIガード/deny-direct チェック利用可。 - -Focus Shift — llvmlite(既定)+ PyVM(新規) -- Rust/inkwell は保守のみ。Python(llvmlite/PyVM)中心で開発。 -- 追加スモーク: esc_dirname_smoke / dep_tree_min_string を llvmlite と PyVM の両方で常時維持。 -- 追跡: `NYASH_LLVM_TRACE_FINAL=1`(最終ハンドル)、`NYASH_LLVM_TRACE_PHI=1`(PHIログ) - -Hot Update — 2025‑09‑13(Harness 配線・フォールバック廃止) -- Runner(LLVMモード)にハーネス配線を追加。`NYASH_LLVM_USE_HARNESS=1` のとき: - - MIR(JSON) を `tmp/nyash_harness_mir.json` へ出力 - - `python3 tools/llvmlite_harness.py --in … --out …` で .o を生成 - - 失敗時は即エラー終了(Rust LLVM へのフォールバックは廃止) -- `tools/llvmlite_harness.py` を追加(ダミー/JSON入力の両方に対応)。 -- Python 側スキャフォールドを微修正(PHI 直配線、Resolver 最小実装、ExternCall x NyRT 記号、NewBox→`nyash.env.box.new`)。 -- プラグインを cdylib/staticlib 両対応に一括回収(主要プラグイン)。`tools/build_plugins_all.sh` 追加。 - -Hot Update — 2025‑09‑13(Resolver‑only 統一 + Harness ON green) -- Python/llvmlite 側で Resolver‑only を徹底(vmap 直参照を原則廃止)。 - - compare/binop/branch/call/externcall/boxcall/ret/typeop/safepoint のオペランド解決を `resolve_i64/resolve_ptr` に統一。 - - JSON φ はブロック先頭で即時降下(sealed配線)。incoming は pred の `block_end_values` から取得。型変換は pred terminator 直前に挿入。 - - 文字列はブロック間 i64(ハンドル)固定。i8* は call 直前のみ生成(concat/substring/lastIndexOf/len_h/eq_hh 実装)。 - - const(string) は GlobalVariable を保持し、使用側で GEP→i8* に正規化(dominator 違反回避)。 - - `main` 衝突回避: MIR 由来 `main` は private にし、`ny_main()` ラッパを自動生成(NyRT `main` と整合)。 -- 代表ケース(dep_tree_min_string): Harness ON で `.ll verify green → .o` を確認し、NyRT とリンクして EXE 生成成功。 - -Next(short — Parser MVP kick‑off) -1) Ny→JSON v0 パイプを Nyash で実装(整数/文字列/四則/括弧/return)。 -2) `--ny-parser-pipe`/`--json-file` と互換の JSON v0 を出力し、既存ブリッジで実行。 -3) スモーク: `tools/ny_roundtrip_smoke.sh` 緑、`esc_dirname_smoke`/`dep_tree_min_string` を Ny パーサ経路で PyVM/llvmlite とパリティ一致。 -4) (続)Ny AST→MIR JSON 直接降下の設計(箱構造/型メタ連携)。 - -Hot Update — 2025‑09‑14(Language + Docs) -- Parser/Tokenizer: - - 三項演算子(`cond ? then : else`)をパーサに導入(Phase 12.7 順序: Pipe の内側)。 - - 降下は PeekExpr(`peek cond { true => then, else => else }`)化で式として扱いやすく。 - - トークナイザに単独の `?` / `:` を追加(`.?`/`??` より後に評価)。 - - postfix `?`(Result伝播)と `?:` の衝突を回避(look‑ahead)。 - - `return` 後の改行/式を許容するよう改善(`return` + 三項の直後でも安全)。 - - 文パーサのフォールバックに「式文」を追加(式のみの行を受理、結果は捨てられる;`NYASH_LINT_MUSTUSE=1` で警告可)。 -- PeekExpr(確認): - - アームで `{ ... }` ブロックを許容、最後の式が値として返る(Program式として評価)。 - - 文コンテキストでも式文として利用可(値は破棄)。 -- Docs(入口の整備): - - 言語ガイド: `docs/guides/language-guide.md` - - 言語索引: `docs/reference/language/README.md` - - 安定パス(スタブ): `docs/reference/language/LANGUAGE_REFERENCE_2025.md`(private実体へ誘導) - - アーキ索引/受け皿: `docs/reference/architecture/{nyash_core_concepts.md, execution-backends.md, TECHNICAL_ARCHITECTURE_2025.md}` - - 文分離ポリシー: `docs/reference/language/statements.md`(改行ベース+必要時のみセミコロン、最小ASI) -- Parser MVP(Stage 1): - - Python 実装: `tools/ny_parser_mvp.py` を追加、Roundtrip スモーク `tools/ny_parser_mvp_roundtrip.sh` で緑。 - - Nyash 実装スケルトン: `apps/selfhost/parser/ny_parser_v0/main.nyash`(改修継続)。 - -Next(short — 言語/E2E) -1) 三項演算子のE2E確定(VM実行/戻り値表示まで緑)。 - - 例: `apps/tests/ternary_basic.nyash`(return 10) - - `tools/parity.sh` で PyVM/llvmlite と動作一致の確認(peek化経路含む)。 -2) peek サンプル/テスト拡充(式文/ブロック/戻り値) - - 例: `apps/tests/peek_expr_block.nyash` -3) ドキュメント追補 - - Language Guide に peek/三項の最小例・must_useメモを追記 - - Cheat Sheet に三項/peek を反映 -4) (継続)Parser MVP Stage2 設計: `local/if/loop/call/method/new/me/substring/length/lastIndexOf` - -Quick Commands(dev) -- Python Parser Roundtrip: `NYASH_CLI_VERBOSE=1 tools/ny_parser_mvp_roundtrip.sh` -- Peek Block サンプル(VM): `./target/release/nyash --backend vm apps/tests/peek_expr_block.nyash` -- E2E(言語 → MIR ダンプ): `./target/release/nyash --dump-mir apps/tests/peek_expr_block.nyash` - -Hot Update — MIR v0.5 Type Metadata(2025‑09‑14 着手) -- 背景: 文字列を i64 として曖昧に扱っており、llvmlite で handle/ptr の推測が必要→不安定の温床。 -- 追加仕様(後方互換、最小差分): - - Const(string): `{"value": {"type": {"kind":"handle","box_type":"StringBox"}, "value":"..."}}` - - 既存の `type:"string"` 表記は併記しない(受け側は新表示を優先、無ければ従来推測)。 - - BoxCall/ExternCall: 可能な範囲で `dst_type` を付与(例: substring→StringBox(handle), length/lastIndexOf→i64)。 -- 実装計画: - - A) 共有エミッタ `src/runner/mir_json_emit.rs` を拡張(string const/最小メソッドの `dst_type`)。 - - B) Python 側: `llvm_builder.py` が `dst_type` を検知して `resolver.mark_string(dst)` を行う(型タグの明示化)。 - - C) Console 出力の安定化: 当面は既存のポインタAPI/ハンドルAPIを維持。型メタ普及後に handle→ptr ブリッジ導入を検討。 -- 受け入れ(第一段): - - JSON に string const の型メタが出ること - - Python 側で `dst_type` により string ハンドルのタグ付けが行われること - - `tools/parity.sh` が esc_dirname_smoke で実行できること(完全一致は第二段で目標) - -Hot Update — 2025‑09‑14(Option A: A6 受入完了) -- 方針: mem2reg は採らず、MIR→JSON→llvmlite で収束。 -- 実施: if/loop の MIR 補強(then/else 再代入のPhi化、latch→header seal の堅牢化)。 -- 結果: esc_dirname_smoke / min_str_cat_loop / dep_tree_min_string が PyVM↔llvmlite で一致。LLVM verifier green → .o 成立。 - -Tasks(短期・優先順 — ParserMVP) -1) Parser v0(Ny→JSON v0) - - `apps/selfhost/parser/` に LexerBox/ParserBox を配置し、整数/文字列/四則/括弧/return をJSON v0に吐く。 -2) CLI/ブリッジ動線 - - `nyash --ny-parser-pipe` と同等フォーマットで出力→既存 `json_v0_bridge` で実行。 -3) スモーク整備 - - `tools/ny_roundtrip_smoke.sh`/`tools/ny_parser_bridge_smoke.sh` を Ny パーサ経路でも緑に。 -4) 設計 - - Ny AST→MIR JSON 直接降下の箱設計(型メタ/Resolverフック)。 - -Notes(ゲート/保護) -- 進行中のみの一時救済ゲート: `NYASH_LLVM_ESC_JSON_FIX=1` - - finalize_phis で esc_json のデフォルト枝に `out = concat(out, substring)` を pred末端に合成(dominance-safe) - - 本命の MIR 修正が入ったため、受け入れには使用しない(OFF)。 - -Back‑up Plan(実験レーン・必要時) -- Python MIR Builder(AST→MIR を Python で生成)を `NYASH_MIR_PYBUILDER=1` のフラグで限定導入し、smoke 2本(min_str/esc_dirname)で PyVM/llvmlite 一致を先に確保。 -- 良好なら段階移行。ダメなら即OFF(現行MIRを維持)。 - -Hot Update — 2025‑09‑14(typed binop/compare/phi + PHI on‑demand) -- 目的: LLVM層での型推測を廃止し、MIR→JSONの型メタにもとづく機械的降下へ移行。 -- JSONエミッタ(src/runner/mir_json_emit.rs) - - BinOp: “+” で一方が文字列のとき `dst_type:{kind:"handle",box_type:"StringBox"}` を付与。 - - Compare: “==/!=” で両辺が文字列のとき `cmp_kind:"string"` を付与。 - - Phi: incoming が全て文字列のとき `dst_type:StringBox(handle)` を付与。 -- Python/llvmlite 側(src/llvm_py/**) - - binop: `dst_type` が String の場合は強制的に `concat_hh(handle,handle)` を選択。 - - compare: `cmp_kind:"string"` の場合は `nyash.string.eq_hh` を使用。 - - PHI: JSONのphiはメタ情報とし、resolver が on‑demand でブロック先頭にPHIを合成(循環は先にPHIをcacheへ入れてからincomingを追加)。 - - finalize_phis は関数単位で呼び出し(実質no‑op)。ブロック終端スナップショットは vmap 全体を保存。 - - resolver: 単一predブロックはPHIを作らず pred 終端値を直返し。局所定義は vmap 直接再利用を優先(不要PHI抑制)。 -- パリティ補助(tools/parity.sh): ノイズ([TRACE] など)除去と exit code 行の正規化を強化。 -- 所見: esc_dirname_smoke はハーネスで .o まで生成できるようになったが、1行目(エスケープ結果)が 0 になる回帰が残存。原因は `Main.esc_json/1` の return 経路で、pred 連鎖の値取得が 0 合成に落ちるケースがあるため(resolver の `_value_at_end_i64` での取りこぼし)。 - -Next(handoff — short plan) -1) PHI 一元化(Resolver‑only配線・placeholder先行) - - Prepass(関数冒頭): 全ブロックのphi(dst)について、ブロック先頭にplaceholder `phi_{dst}` を一括生成し `vmap[dst]` に登録。`block_phi_incomings` も先に収集。 - - resolver: PHIを新規生成しない(常にplaceholderを返す)。`_value_at_end_i64` は“値のmaterialize(i64/boxing)”のみ行い、配線はしない。 - - finalize_phis: 唯一の配線フェーズ。CFG predごとに一意にincomingを追加(重複除去)。pred→src_vid は JSON incoming を優先。未定義predはログ+保守的0(将来strict)。 - - 禁止事項: current_blockでのローカライズPHI生成(loc_*)と、resolver側からのincoming追加。 - - 診断: `NYASH_LLVM_TRACE_PHI=1` で placeholder生成/配線、pred→src_vid、snap命中タイプを出力。 -2) Parity(pyvm ↔ llvmlite に限定) - - `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/min_str_cat_loop/main.nyash` - - `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/esc_dirname_smoke.nyash` - - 受け入れ: 1行目が0にならない、stdout/exit一致。 -3) 型メタ(第二段の準備) - - Compare: “==/!=(両辺文字列)→ cmp_kind:"string"” を JSON に出力(llvmlite は `nyash.string.eq_hh`)。 - - BinOp: “+” の `dst_type:StringBox(handle)` を最優先に concat_hh 強制。 - - Call/ExternCall: 既知APIに `dst_type` 付与(console.*→i64、dirname/join/read/read_all→StringBox(handle) 等)。 - -How to run(メモ) -- LLVM(ハーネスON): `NYASH_LLVM_USE_HARNESS=1 NYASH_LLVM_DUMP_IR=tmp/nyash_harness.ll NYASH_LLVM_OBJ_OUT=tmp/app.o target/release/nyash --backend llvm ` -- Parity: `tools/parity.sh --timeout 120 --show-diff apps/tests/esc_dirname_smoke.nyash` -- PyVM: `NYASH_VM_USE_PY=1 target/release/nyash --backend vm ` - -Hot Update — Box Theory PHI(2025‑09‑14 追加予定) -- 背景: ループの PHI が snapshot 未構築時に 0 合成へ落ちる(forward 参照をその場 resolve しているため)。 -- 方針(箱理論に基づく簡素化): - - Block=箱(BoxScope)。各ブロック末尾の `block_end_values` を箱として扱う。 - - PHI は即時解決せず defer 収集 → 全ブロック降下後に finalize で箱(pred の snapshot)から値を取り出して配線。 - - ブロック間は String は常に handle(i64) 固定。pointer PHI は禁止。必要な boxing(ptr→handle)は pred 末端(terminator 直前)で挿入。 - - ‘+’ は常に concat_hh(handle,handle)。i64 プリミティブは from_i64 で昇格、リテラルは from_i8_string。 -- 実装計画: - - A) `llvm_builder.py`: `lower_phi` を defer 化、`finalize_phis` を追加(incoming=(pred_bid,val_id) を materialize)。 - - B) `llvm_builder.py`: `block_end_values` の網羅性を補強(関数引数/const/新規 dst/phi dst/循環値が確実に入る)。 - - C) `resolver._value_at_end_i64`: pred 末端での局所 boxing/cast を強制、未定義→0 合成を抑制(strict 時は警告)。 -- 受け入れ(第二段): - - 最小再現 `apps/tests/min_str_cat_loop/main.nyash` で PyVM と llvmlite の parity 緑(`xxx`) - - `apps/tests/esc_dirname_smoke.nyash` で parity 緑(1行目の 0 が解消) - - `tools/parity.sh` で stdout 完全一致+終了コード一致 - -Compact Roadmap(2025‑09‑13 改定) -- Focus A(Rust LLVM 維持): Flow hardening, PHI(sealed) 安定化, LoopForm 仕様遵守。 -- Focus B(Python Harness 導入): llvmlite による MIR(JSON)→IR/obj の高速経路を追加。ON/OFF で等価性を検証。 -- Now: - - Sealed SSA・Cursor 厳格化を導入済み。dep_tree_min_string の `.o` 生成と verifier green を Rust LLVM で確認済み。 -- Next(short): - 1) ON/OFF 等価性(戻り値/ログ/最終JSON)の一致確認 - 2) 残フォールバック撤去(完全 Resolver‑only 固定) - 3) Docs 更新(Resolver/PHI/ptr↔i64 ブリッジ) -- Flags: - - `NYASH_ENABLE_LOOPFORM=1`(非破壊ON) - - `NYASH_LOOPFORM_BODY2DISPATCH=1`(実験: 単純ボディのbody→dispatch) - - `NYASH_LOOPFORM_LATCH2HEADER=1`(PHI正規化後に有効化) - -Update — 2025-09-12 (LLVM flow + BB naming) -- codegen/mod.rs match arms cleanup: - - BinOp: unify to instructions::lower_binop(...) - - BoxCall: delegate solely to instructions::lower_boxcall(...); removed unreachable legacy code -- BasicBlock naming made function‑scoped and unique: create_basic_blocks now prefixes with function label (e.g., Main_join_2_bb23) -- Terminator fallback: when a MIR block lacks a terminator, emit a conservative jump to the next block (or entry if last) -- Build: cargo build --features llvm passes -- AOT emit status: improved (bb name collision resolved), but verifier still flags a missing terminator in Main.esc_json/1 (e.g., Main_esc_json_1_bb88) - - Likely cause: flow lowering edge case; MIR dump is correct, LLVM lowering missed a terminator - - Plan below addresses this with hardened flow lowering and instrumentation - -Hot Update — 2025-09-12 (quick) -- Flow: moved function verify to post‑lower; added terminator fallback only when MIR lacks one -- PHI(sealed): seal_block isolates cast insertion by saving/restoring the insertion point in the predecessor block and wires incoming only for the current predecessor (zero-synth as a last resort, logged). Avoids duplicate incoming and builder state leaks. -- Compare: allow ptr↔int comparisons for all ops via ptr→i64 bridge -- Strings: substring now accepts i64(handle) receiver (i2p); len/lastIndexOf stable -- Arrays: method_id未注入でも get/set/push/length を NyRT 経由で処理 -- BoxCall: println→env.console.log フォールバック、同モジュール関数呼び出し/名前指定 invoke_by_name 経路を追加 -- PHI: 文字列/Box等を含む場合は i8* を選択する型推定に改善 -- 現在のブロッカー: esc_json/1 で「phi incoming value missing」 - - 対応: emit_jump/emit_branch の incoming 配線をログ付きで点検し、値未定義箇所(by‑name/fast‑path戻り)を補完 - -Hot Update — 2025‑09‑12 (Plan: LLVM wrapper via Nyash ABI) -- 背景: Rust/inkwell のビルド時間と反復速度が課題。LLVM生成を Nyash から呼べる ABI に抽象化し、将来 Nyash スクリプトで LLVM ビルダー実装へ移行する。 -- 方針: Rust 実装は当面維持(1–2日で dep_tree_min_string をグリーンに)。併走で llvmlite(Python) を「検証ハーネス」として導入し、PHI/Loop 形の仕様検証→ Rust へ反映。 -- 入口: Nyash ABI で LLVM emit をラップ。 - - モード切替フラグ: `NYASH_LLVM_USE_HARNESS=1`(ON時は llvmlite ハーネスに委譲)。 - - I/O 仕様: 入力=MIR(JSON/メモリ), 出力=.o(`NYASH_AOT_OBJECT_OUT` に書き出し)。 -- 受け入れ: harness ON/OFF で dep_tree_min_string の出力一致(機能同値)。 - -Update — 2025‑09‑13(Harness 本採用・責務分離) -- 目的: Rust×inkwell の反復コストを下げ、仕様変更への追従を高速化。 -- 切替方針: - - Rust: MIR 正規化(Resolver 統一・LoopForm 規約)+ MIR(JSON) 出力+ランチャー。 - - Python(llvmlite): IR/JIT/`.ll`/`.o` 生成(まずは `.ll→llc→.o`)。 -- スイッチ: - - `NYASH_LLVM_USE_HARNESS=1` → MIR(JSON) を書き出し `tools/llvmlite_harness.py` を起動し `.o` を生成。 - - OFF → 従来どおり Rust LLVM で `.o` 生成。 -- 受け入れ基準(A5 改定): - - dep_tree_min_string で Harness ON/OFF ともに `.ll verify green`(ハーネス経路)および `.o` 生成成功。 - - 代表ケースの戻り値・主なログが一致(必要に応じ IR 差分検査は参考)。 - -Tasks(Harness 導入の具体) -1) ランチャー配線と CLI 拡張 - - `--emit-mir-json ` を追加(Resolver/LoopForm 規約済みの MIR14 を JSON で吐く)。 - - `NYASH_LLVM_USE_HARNESS=1` 時は `.json → tools/llvmlite_harness.py --in … --out …` を実行して `.o` を生成。 - - 出力先は `NYASH_LLVM_OBJ_OUT`(既存)または `NYASH_AOT_OBJECT_OUT` を尊重。 -2) llvmlite_harness 実装(docs/LLVM_HARNESS.md に準拠) - - 最小命令: Const/BinOp/Compare/Phi/Branch/Jump/Return。 - - 文字列/NyRT 呼び出し: `nyash.string.*`, `nyash.box.*`, `nyash.env.box.*` を declare して call。 - - ループ/PHI: Rust 側が担保した dispatch‑only PHI に従い、PHI 作成と incoming 追加を素直に行う。 -3) スモーク&代表ケース - - ny-llvm-smoke で Round Trip → dep_tree_min_string で `.ll verify → .o` まで。 -4) Deny‑Direct 継続 - - lowering から `vmap.get(` 直参照ゼロ(Resolver 経由の原則を Python 側仕様にも反映)。 - -Notes(リンク形態) -- NyRT は静的リンク(libnyrt.a)。完全静的(-static)は musl 推奨で別途対応(プラグイン動的ロードは不可になる)。 - -Scaffold — 2025‑09‑12 (llvmlite harness) -- Added tools/llvmlite_harness.py (trivial ny_main returning 0) and docs/LLVM_HARNESS.md. -- Use to validate toolchain wiring; extend to lower MIR14 JSON incrementally. - -Scaffold — 2025‑09‑12 (Resolver i64 minimal) -- Added src/backend/llvm/compiler/codegen/instructions/resolver.rs with `Resolver::resolve_i64(...)` and per-block cache. -- docs/RESOLVER_API.md documents goals/usage; wiring to replace `localize_to_i64` callsites comes next. - -Hot Update — 2025‑09‑12 (Structural Invariants v1) -- Core invariants adopted to eliminate dominance violations structurally: - - Resolver-only value access: forbid direct `vmap.get(...)` in lowering; always use `Resolver::{resolve_i64, resolve_ptr, resolve_f64}`. - - Localization discipline: PHIs are created at BB head; casts/incoming coercions are inserted at predecessor end via `BuilderCursor::with_block` (never inside PHI site). - - Strings rule: across blocks keep StrHandle(i64) only; convert to StrPtr(i8*) at call sites inside the same BB. Return values of string ops are stored as i64 handles. - - LoopForm rule: preheader is mandatory; header condition resolved via Resolver; dispatch-only PHI; optional latch→header normalization is gated. - - BuilderCursor guard: deny post‑terminator insertion; avoid raw builder calls from lowering sites. -- Acceptance A2.5 (added): sealed=ON (default) with dominator violations = 0 on `apps/selfhost/tools/dep_tree_min_string.nyash`. - -Next Plan(精密タスク v2) -- Deny-Direct purge: eliminate remaining `vmap.get(...)` in lowering modules(maps.rs, arith.rs, arith_ops.rs, externcall/env.rs, boxcall/{invoke,marshal,fields}.rs, mem.rs ほか)。 -- Enforce string handle invariant end-to-end(helpers/Resolverに軽量StrHandle/StrPtr型or helperを追加、戻りは常にi64、呼び出し直前だけinttoptr)。 -- L-Dispatch-PHI checker(dev): dispatch以外のPHI検出でpanic/ログ。preheader必須の検証も追加。 -- Seal配線の堅牢化: snapshot優先・pred末端cast挿入の徹底、フォールバックゼロ合成の縮小(非param値のvmapフォールバックを禁止)。 -- Regression: `dep_tree_min_string` オブジェクト生成→ LLVM verifier green(支配違反ゼロ); Deny-Direct grep=0 をCIチェックに追加。 - -Plan — Context Boxing(Lower APIの“箱化”) -- 背景: 降下APIの引数が過多(10+)で、責務が分散しがち。Nyashの“箱理論”に従い、関連情報をコンテキストBoxにまとめて境界を作る。 -- 目的: 責務の一元化・不変条件の型による強制・引数爆発の解消・再発防止。 -- 主要な“箱” - - LowerFnCtx<'ctx, 'b>(関数スコープ) - - 保持: `codegen`, `func`, `cursor`, `resolver`, `vmap`, `bb_map`, `preds`, `block_end_values`, `phis_by_block`, `const_strs`, `box_type_ids` など - - 役割: 関数内の全 lowering に共通する情報とユーティリティ(ensure_i64/i8p/f64、with_block ラッパ 等) - - BlockCtx<'ctx>(ブロックスコープ) - - 保持: `cur_bid`, `cur_llbb`、必要に応じて `succs` - - 役割: その場の挿入点管理、pred終端直前挿入の窓口 - - InvokeCtx(呼び出し情報) - - 保持: `method_id`, `type_id`, `recv_h`, `args` - - 役割: plugin invoke(by‑id/by‑name)の統一入口 - - StringOps(軽量型/ヘルパ) - - 型: `StrHandle(i64)`, `StrPtr(i8*)` - - 規約: ブロック間は StrHandle のみ/call直前のみ StrPtr 生成。戻りは常に StrHandle - -- API の最終形(例) - - `lower_boxcall(ctx: &mut LowerFnCtx, blk: &BlockCtx, inst: &BoxCallInst) -> LlResult<()>` - - `try_handle_tagged_invoke(ctx: &mut LowerFnCtx, blk: &BlockCtx, call: &InvokeCtx) -> LlResult<()>` - - `StringOps::concat(ctx, blk, lhs, rhs) -> LlResult` - -- 構造の不変条件との対応付け - - Resolver‑only: `LowerFnCtx` 経由でのみ値取得可能(VMap の直接 `get` は不可にする) - - 局所化の規律: `BlockCtx.with_block_pred(..)` 等で pred末端挿入を強制 - - dispatch‑only PHI: dev チェッカ `PhiGuard::assert_dispatch_only` を追加 - - preheader必須: LoopForm 生成時に assert(dev) - -- マイグレーション手順(段階的) - 1) `LowerFnCtx/BlockCtx/InvokeCtx` を導入し、`lower_boxcall` と invoke 経路を最初に箱化 - 2) Strings を `StringOps` に集約(戻り=StrHandle)。既存callサイトから直 i8* を排除 - 3) BinOp/Compare/ExternCall を順次 `LowerFnCtx+BlockCtx` 受けに移行 - 4) dev チェッカ(dispatch‑PHI/preheader)をONにし、構造を固定 - -- 受け入れ(Context Boxing 対応) - - `lower_boxcall`/invoke/strings が “箱化API” を使用し、関数シグネチャの引数が 3 箱以内 - - Resolver‑only/Deny‑Direct 維持(grep 0) - - 代表ケースで verifier green(D‑Dominance‑0) - -Docs — LLVM layer overview (2025‑09‑12) -- Added docs/LLVM_LAYER_OVERVIEW.md and linked it with existing docs: - - docs/LOWERING_LLVM.md — concrete lowering rules and RT calls - - docs/RESOLVER_API.md — value resolution (sealed/localize) API and cache - - docs/LLVM_HARNESS.md — llvmlite harness scope and interface - Use as the canonical reference for invariants: Resolver-only reads, cast placement, cursor discipline, sealed SSA, and LoopForm shape. - -Hot Repro — esc_json/1 PHI 配線(2025‑09‑12) -- 対象: apps/selfhost/tools/dep_tree_min_string.nyash -- 実行(LLVM): - - Sealed OFF: `NYASH_CLI_VERBOSE=1 NYASH_LLVM_TRACE_PHI=1 NYASH_DISABLE_PLUGINS=1 ./target/release/nyash --backend llvm apps/selfhost/tools/dep_tree_min_string.nyash` - - Sealed ON: `NYASH_LLVM_PHI_SEALED=1 NYASH_CLI_VERBOSE=1 NYASH_LLVM_TRACE_PHI=1 NYASH_DISABLE_PLUGINS=1 ./target/release/nyash --backend llvm apps/selfhost/tools/dep_tree_min_string.nyash` -- 観測: - - Sealed OFF: Main.esc_json/1 で PHI incoming 不足(`PHINode should have one entry for each predecessor`)。 - - Sealed ON: `phi incoming (seal) value missing`(pred側終端値の取得ができていない)。別途 Terminator 欠落も検知→終端フォールバックを実装して解消済み。 -- 原因仮説: Sealed ON で `seal_block` が pred終端時点の値(value_at_end_of_block)ではなく関数作業用 vmap を参照しているため、未定義扱いになっている。 - -Next Steps(Sealed SSA 段階導入) -1) block_end_values を導入し、各BB降下完了時に vmap スナップショットを保存。`seal_block` は pred のスナップショットから in_vid を取得。(完了) -2) Sealed=ON を既定にし、emit_* 側の配線を停止(`finalize_phis` 無効化)。(実装済/整備中) -3) BuilderCursor を lowering 全域に適用(externcall/newbox/arrays/maps/call)。 -4) Sealed=ON で apps/selfhost/tools/dep_tree_min_string.nyash を再確認(PHIログ=ON)。 -5) 足りない型整合(String/Box/Array→i8*)があれば `coerce_to_type` を拡張。 -6) グリーン後、LoopForm BODY→DISPATCH を単純ボディで常用化。 - -TODO — Sealed SSA 段階導入(実装タスク) -- [x] block_end_values 追加(LLVM Lower 内の per-BB 終端スナップショット) - - 追加先: `src/backend/llvm/compiler/codegen/mod.rs` - - 形式: `HashMap>` - - タイミング: 各BBの命令をすべて Lower した「直後」、終端命令を発行する「直前」に `vmap.clone()` を保存 - - 目的: `seal_block` で pred 終端時点の値を安定取得する(現在の vmap 直接参照をやめる) -- [x] `seal_block` をスナップショット参照に切替 - - 対象: `src/backend/llvm/compiler/codegen/instructions/flow.rs::seal_block` - - 取得: `block_end_values[bid].get(in_vid)` を用いて `val` を取得 - - フォールバック: もしスナップショットが無ければ(例外ケース)従来の `vmap` を参照し、警告ログを出す - - ログ: `NYASH_LLVM_TRACE_PHI=1` 時に `[PHI] sealed add pred_bb=.. val=.. ty=.. (snapshot)` と明示 -- [x] 非 sealed 経路の維持(回帰防止) - - `emit_jump/emit_branch` は sealed=OFF の時のみ incoming を追加(現状仕様を維持) - - sealed=ON の時は incoming 配線は一切行わず、`seal_block` のみで完結 -- [x] 型整合(coerce)の継続強化 - - 対象: `src/backend/llvm/compiler/codegen/instructions/flow.rs::coerce_to_type` - - 方針: PHI の型は i8* 優先(String/Box/Array を含む場合)。ptr/int 混在は明示 cast で橋渡し - - 検討: i1 ブリッジ(bool)の zext/trunc の置き場所は PHI 外側に寄せる(必要時) -- [ ] 代表スモークの回帰 - - 再現対象: `apps/selfhost/tools/dep_tree_min_string.nyash` - - 実行: `NYASH_LLVM_PHI_SEALED=1 NYASH_LLVM_TRACE_PHI=1 NYASH_DISABLE_PLUGINS=1 ./target/release/nyash --backend llvm apps/selfhost/tools/dep_tree_min_string.nyash` - - 期待: `PHINode should have one entry for each predecessor` が解消し、OFF/ON で等価な結果 - -補足(実装メモ) -- `block_end_values` の寿命はコード生成のライフタイムに束縛されるため、`BasicValueEnum<'ctx>` の所有は問題なし(`Context` が生きている間は有効) -- 収集は `compile_function` の BB ループ内で行い、`phis_by_block` と同スコープで管理すると取り回しが良い -- 将来の拡張として `value_at_end_of_block(var, bb)` ヘルパを導入し、sealed/unsealed を内部で吸収する API 化を検討 - -Hot Plan — Structured Terminators(RAII BuilderCursor) -- 目的: 末端処理を設計で保証(未終端や終端後挿入を構造で不可能に)し、終端まわりのフォールバック重複を削減する -- ポリシー: 「開いているブロックにのみ命令を挿入できる。終端を置いた瞬間に閉じる。閉じたブロックへの挿入は即panic」 - -TODO — BuilderCursor 導入と段階適用 -- [ ] 新規: `builder_cursor.rs` を追加(`BuilderCursor` / `BlockState` / `with_block`) - - API: `at_end(bid, llbb)`, `emit_instr(f)`, `emit_term(f)`, `assert_open(bid)` - - 実装: inkwell::Builder を参照で保持し、`closed_by_bid: HashMap` を管理 -- [ ] 既存終端APIを置換 - - 変更: `emit_return / emit_jump / emit_branch` を `BuilderCursor::emit_term` 経由に - - 呼び出し元: codegen/mod.rs 側から `&mut BuilderCursor` を渡す -- [ ] 位置ずれの解消(最小) - - cast 等の補助命令は「pred終端直前」へ(position_before)を維持 - - 既存の entry_builder 経路で位置ずれが起きないよう、必要箇所のみ `with_block` を使用 -- [ ] 関数終端の最終保証 - - `finalize_function`: 未終端BBには `unreachable` を挿入(最後の砦) -- [ ] 代表スモークの回帰(Sealed=ON) - - 期待: 未終端エラー・終端後挿入エラーの消滅。PHI配線は snapshot により安定 - -Note -- フェーズ1では「終端APIと位置ずれの構造化」の最小適用に留め、フォールバック(最終unreachable)を併用 -- フェーズ2で with_block の適用範囲を広げ、余剰なガード・分岐フォールバックを削除していく(ソースは小さくシンプルに) - -Progress — 2025-09-12(Sealed + RAII 最小導入) -- Sealed: block_end_values(BB内定義のみをフィルタ)を導入し、incoming は pred 終端時点の snapshot から配線 -- Cast 挿入: pred 終端直前(position_before)に限定、終端後挿入を回避 -- BuilderCursor: emit_return/emit_jump/emit_branch を構造化(closed ブロックへの挿入を禁止) -- Call/BoxCall: 実引数を callee の型へ coerce(i2p/p2i/zext/trunc 等) -- Const String: nyash_string_new を entry ブロックで Hoist し、支配性違反を解消 -- Fallback(暫定): PHI 欠落や lhs/rhs missing に対し型ゼロを合成して進行(ログ付) - -Open Issues(要対応) -- Sealed配線: 一部合流で『各predに1 incoming』が未充足(synth で穴埋め中) -- Dominance: ループ/合流で稀に「Instruction does not dominate all uses!」が再出 -- 位置ずれ: Sealed 内の cast 生成が builder の挿入位置に影響する可能性(要隔離) - -Next TODO(優先度順) -1) flow::seal_block の挿入位置を完全隔離 - - 専用Builder or Cursor.with_block で pred 終端直前に cast を挿入(メインbuilder位置を汚さない) -2) preds_by_block を構築し、PHI incoming を実CFGの pred へ正規リマップ - - snapshot から in_vid を取得し、pred 数ぶんを網羅(synth は最終手段) - - 検証: incoming=pred数 を assert/ログ -3) with_block の適用拡大(entry_builder/配列alloca等のホットスポット) - - 位置ずれ温床を解消 → 余剰ガード/フォールバックを削除(コード縮小) -4) 回帰: Sealed=ON/OFF の一致確認(dep_tree_min_string ほか代表) - - NYASH_LLVM_TRACE_PHI=1 で配線ログ確認、ゼロ合成が消えることを目標 - -Plan — PHI/SSA Hardening (Sealed SSA) -- Sealed SSA 入れ替え(安全に段階導入) - - Blockごとに `sealed: bool` と `incomplete_phis: Map` を保持 - - 値取得APIを一本化: `value_at_end_of_block(var, bb)` で vmap を再帰解決+仮PHI生成 - - `seal(bb)` で pred 確定後に incomplete_phis を埋め、`fold_trivial_phi` で単純化 -- 配線の方向を整理 - - emit_jump/emit_branch では直接 incoming を配線しない(to 側で必要時に解決) - - fast/slow 両レーンは同じ Var に書く(合流で拾えるように) -- 型の前処理を一箇所へ - - Bool は i1 固定(必要なら zext は PHI 外側) - - ptr/int 混在禁止。必要箇所で ptrtoint/inttoptr を生成し、PHI では等型を保証 -- 計測と検証 - - `seal(bb)` 時に incomplete_phis が残れば panic(場所特定) - - `[PHI] add incoming var=.. pred=.. ty=..` をデバッグ出力 - - verify は関数降下完了後に 1 回 -- フォールバックのゲート - - by-name invoke は環境変数で明示ON(デフォルトOFF)にする方針に切替(ノイズ低減) - -Refactor Policy -- 慌てず小さな箱(モジュール)を積む。必要なら随時リファクタリングOK。 -- 代替案(必要時のみ): llvmlite の薄層で最低限の命令面を実装し、dep_tree_min_string を先に通す。 - -Done (today) -- BoxCall legacy block removed in LLVM codegen; delegation only. -- BinOp concat safety: minimal fallback for i8*+i64/i64+i8* when both sides are annotated String/Box → from_i8_string + concat_hh; otherwise keep concat_ss/si/is. -- String fast‑path: length/len lowered to nyash.string.len_h (handle), with ptr→handle bridge when needed. -- Map core‑first: NewBox(MapBox) routes via nyash.env.box.new("MapBox") → NyRT特例でコアMapを生成。LLVM BoxCall(Map.size/get/set/has) は NyRT の nyash.map.* を呼ぶ。 -- Plugin強制スイッチ: NYASH_LLVM_FORCE_PLUGIN_MAP=1 で MapBox のプラグイン経路を明示切替(デフォルトはコア)。 -- Docs: ARCHITECTURE/LOWERING_LLVM/EXTERNCALL/PLUGIN_ABI を追加・整備。 -- Smokes: plugin‑ret green, map smoke green(core‑first)。 -- ExternCall micro‑split 完了: `externcall.rs` を `externcall/` ディレクトリ配下に分割し、 - `console.rs`(console/debug)と `env.rs`(future/local/box)に切り出し。 - ディスパッチは `externcall/mod.rs` に集約(挙動差分なし・0‑diff)。 - - String Fast‑Path 追加(LLVM/NYRT): `substring(start,end)` と `lastIndexOf(needle)` を実装。 - - Compare: String/StringBox 注釈のときは内容比較(`nyash.string.eq_hh`)にブリッジ。 -- LLVM 多関数 Lower の骨格を実装: 全関数を事前宣言→順次Lower→`ny_main` ラッパで呼び出し正規化。 -- Call Lowering 追加(MIR14の `MirInstruction::Call`): callee 文字列定数を解決し、対応するLLVM関数を呼び出し(引数束縛・戻り値格納)。 -- BuilderCursor 適用(第1弾): strings/arith_ops/mem を Cursor 経由に統一。post-terminator 挿入検知を強化。 -- Sealed SSA: `finalize_phis` を停止し、`seal_block` に一本化。LoopForm latch→header の header PHI 正規化を追加(ゲート付)。 - -Hot Update — 2025‑09‑12 (sealed + dominator 修正の途中経過) -- BuilderCursor 全域化 拡大(第一波 完了) - - externcall(console/env), newbox, arrays, maps, call, compare を Cursor 経由へ移行 - - 既存 strings/arith_ops/mem とあわせて、ほぼ全 lowering が post‑terminator 防止のガード下に -- on‑demand PHI(局所化)導入(flow::localize_to_i64) - - 目的: 「現在BBで利用する i64 値」を pred スナップショットから PHI 生成して局所定義に置換→支配関係違反を解消 - - 生成位置: BB 先頭(既存PHIの前)に挿入。挿入点は保存/復元 - - 適用先: strings.substring の start/end、strings.concat の si/is、compare の整数比較、flow.emit_branch の条件(int/ptr/float→i1) -- 失敗時IRダンプ: `NYASH_LLVM_DUMP_ON_FAIL=1` で `tmp/llvm_fail_.ll` を出力(関数検証失敗時) - -Hot Update — 2025‑09‑12 (Resolver 適用拡大 + sealed 既定ON) -- Resolver: i64/ptr/f64 を実装(per‑functionキャッシュ+BB先頭PHI)。 -- 適用: emit_branch 条件、strings(substring/concat si|is)、arith_ops(整数演算)、compare(整数比較)、externcall(console/env)、newbox(env.box.new_i64)、call の引数解決を Resolver 経由に統一。 -- 非sealed配線の削除: emit_jump/emit_branch 内の直接incoming追加を撤去。sealedスナップショット+Resolverの需要駆動で一本化。 -- 既定: `NYASH_LLVM_PHI_SEALED` 未設定=ON(`0` のみOFF)。 - -Status — VMap Purge (Phase 1 done) -- Done (Resolver化済み): - - flow: branch 条件(i64→i1) - - strings: substring/concat(si|is)、lastIndexOf の needle - - arith_ops: 整数演算の左右オペランド(i64 正規化) - - compare: 整数比較の左右(i64 正規化) - - externcall: console(log/warn/error/trace) は handle 経路に統一(resolve_i64)。future.spawn_instance 名は resolve_ptr。 - - env.box.new_i64: int/ptr 引数は resolve_i64(f64→from_f64) - - arrays/maps: index/key/value の整数/ptr は resolve_i64 - - call: 実引数を callee 期待型へ(int→resolve_i64、ptr→i64→i2p、float→resolve_f64) - - mem.store: 値を resolve_i64/resolve_f64/resolve_ptr の順で解決 - - boxcall: recv を i64/ptr 両形で取得、direct call 引数を Resolver 経由に統一 - -- Remaining (vmap.get 参照の置換ターゲット): - - flow.emit_return: 戻り値の型に応じて resolve_* に統一(シグネチャ拡張) - - loopform.lower_while_loopform: 条件の vmap 直参照→ resolve_i64(シグネチャ拡張) - - strings.try_handle_string_method: 一部 vmap 残存箇所の整理(recv は boxcall 側で ptr 統一済み) - - extern.env: env.box.new の型名(arg0)や一部名引数を resolve_ptr に統一 - - arith/arith_ops/compare: 局所の vmap 判定/ゼロフォールバックの縮小(Resolver 経由へ) - - snapshot 用の vmap アクセス(compile_module 内のスナップショット作成)は維持(仕様上の例外) - -Plan — Next (precise) -1) flow.emit_return を Resolver 化(resolve_i64/resolve_f64/inttoptr へ)。 -2) loopform.lower_while_loopform に Resolver/CFG を渡し、条件解決を resolve_i64 に統一。 -3) strings の vmap 残を metadata + Resolver へ置換(concat rhs 判別の簡素化)。 -4) extern.env.* 名引数を resolve_ptr 化(local.get / box.new 名など)。 -5) marshal/fields の vmap 読みを Resolver/型注釈へ段階置換(最小に縮退させる)。 -6) LoopForm: preheader 既定化 + 最小 LoopState(tag+i64)導入→ dispatch-only PHI 完了(ゲート)。 - -Smoke(sealed=ON, dep_tree_min_string)所見 -- 進展: PHI 欠落は再現せず、sealed での incoming 配線は安定 -- 依然NG: Main.node_json/3 で dominator 違反(Instruction does not dominate all uses!) - - iadd→icmp/sub/substring/concat 連鎖の一部で、iadd 定義が利用点を支配していない - - 対応済: 分岐条件/整数比較/substring/concat の整数引数は局所化済み - - まだの可能性が高い箇所: そのほかの lowering 内で vmap 経由の整数使用(BoxCall/ExternCall/arith_ops 内の再利用点など) - -Next(引き継ぎアクション) -1) 局所化の適用拡大(優先) - - vmap から整数値を読み出して利用する全パスで `localize_to_i64` を適用 - - 候補: arith_ops(BinOpのオペランド再利用箇所)、BoxCall の残りの整数引数、他メソッドの整数パラメータ - - types.to_bool 直叩きは emit 側での「局所化→!=0」に段階移行 -2) Resolver API の一般化 - - 「ValueId→現在BBの値」を返す resolver を導入(まず i64、必要に応じて ptr/f64 へ拡張) - - 全 lowering から resolver 経由で値取得し、支配関係崩れを根本排除 -3) IR 可視化/検証強化 - - 失敗関数の .ll を確認し、局所化漏れの使用点を特定→順次塞ぐ -4) 併走: llvmlite 検証ハーネス(`NYASH_LLVM_USE_HARNESS=1`) - - PHI/loop/短絡の形を高速に検証→Rust 実装へ反映(機能一致を Acceptance A5 で担保) - -Refactor — LLVM codegen instructions modularized (done) -- Goal achieved: `instructions.rs` を段階分割し、責務ごとに再配置(0‑diff)。 -- New layout under `src/backend/llvm/compiler/codegen/instructions/`: - - Core: `blocks.rs`(BB生成/PHI事前作成), `flow.rs`(Return/Jump/Branch), `consts.rs`, `mem.rs`, `arith.rs`(Compare) - - BoxCall front: `boxcall.rs`(ディスパッチ本体) - - Strings/Arrays/Maps fast‑paths: `strings.rs`, `arrays.rs`, `maps.rs` - - Fields: `boxcall/fields.rs`(getField/setField) - - Tagged invoke: `boxcall/invoke.rs`(method_idありの固定長/可変長) - - Marshal: `boxcall/marshal.rs`(ptr→i64, f64→box→i64, tag分類) - - Arith ops: `arith_ops.rs`(Unary/Binary、文字列連結の特例含む) - - Extern: `externcall.rs`(console/debug/env.local/env.box.*) - - NewBox: `newbox.rs`(`codegen/mod.rs` から委譲に一本化) -- Wiring: `instructions/mod.rs` が `pub(super) use ...` で再エクスポート。可視性は `pub(in super::super)`/`pub(super)` を維持。 -- Build: `cargo build --features llvm` グリーン、挙動差分なし。 - -Next (short, focused) -- Call Lowering の分離(完了): `instructions/call.rs` に分割し、`mod.rs` から委譲。 -- 多関数 Lower 検証: selfhost minimal(dep_tree_min_string)を LLVM で通す(必要なら型注釈の微調整)。 -- Flow lowering hardening (in progress next): - - Ensure every lowered block has a terminator; use builder.get_insert_block().get_terminator() guard before fallback - - Instrument per‑block lowering (bid, has terminator?, emitted kind) to isolate misses - - Keep fallback minimal and only when MIR.block.terminator is None and LLVM has no terminator - - Detection strengthened (LoopForm Step 2.5): while-pattern detection allows 2-step Jump chains and selects body/after deterministically; logs include loop_id and chosen blocks. - -LoopForm IR — Experimental Plan (gated) -- Goal: Centralize PHIs and simplify terminator management by normalizing loops to a fixed block shape with a dispatch join point. -- Gate: `NYASH_ENABLE_LOOPFORM=1` enables experimental lowering in LLVM path (MIR unchanged in Phase 1). -- Representation: Signal-like pair `{ i8 tag, i64 payload }` (0=Next,1=Break initially). Payload carries loop value (Everything is Box handle or scalar). -- Pattern (blocks): header → body → branch(on tag) → dispatch(phi here only) → switch(tag){ Next→latch, Break→exit } → latch→header. -- Phase 1 scope: while/loop only; Return/Yield signalization deferred. -- Success criteria: PHIs appear only in dispatch; no post-terminator insertions; Sealed ON/OFF equivalence; zero-synth minimized. -- Files: - - New: `src/backend/llvm/compiler/codegen/instructions/loopform.rs` scaffolding + helpers - - Wire: `instructions/mod.rs` to expose helpers (not yet used by default lowering) -- MIR readable debug tools: - - Add --dump-mir-readable to print Nyash‑like pseudo code per function/block - - Optional DOT output (follow‑up) -- Debug hints in MIR (debug builds only): - - Add #[cfg(debug_assertions)] fields like MirInstruction::debug_hint and MirMetadata::block_sources - - Gate emission by env (NYASH_MIR_DEBUG=1) -- Map コア専用エントリ化(env.box.new 特例整理)と代表スモークの常時化(CI) -- types.rs の将来分割(任意): - - `types/convert.rs`(i64<->ptr, f64→box), `types/classify.rs`, `types/map_types.rs` -- 機能拡張(任意・別タスク): - - String AOT fast‑paths拡充(substring/indexOf/replace/trim/toUpper/toLower) - - MapBox 生成のNyRT専用エントリ化(env.box.new特例の解消) - - Map.get の戻り型注釈の厳密化 - - 代表スモークの追加とCI常時チェック - -Risks/Guards -- Avoid broad implicit conversions; keep concat fallback gated by annotations only. -- Ensure nyash.map.* との一致(core Map); plugin経路は環境変数で明示切替。 -- Keep LLVM smokes green continuously; do not gate on VM/JIT. -- BuilderCursor 全域適用前は `codegen.builder` の直接使用が残存し、挿入点の撹乱によるドミナンス違反のリスクあり(対策: 全域 Cursor 化)。 - -Hot Update — 2025-09-12 (LoopForm Step 2.5/3) -- Context reset: コンテキスト問題でひらきなおし。LoopForm を安全な骨格から段階導入する。 -- While 検出強化(Step 2.5): then/else → header への back-edge を Jump 2 段まで許容し、短い側を body、他方を after に決定(ログに header/body/after/loop_id を表示)。 -- dispatch 骨格(Step 3 最小): dispatch に phi(tag:i8, payload:i64) を作り、switch(tag){ Next(0)→latch, Break(1)→exit } を実装。 - - いまは header(false)=Break のみを dispatch に供給(body→dispatch は既定OFFのまま・安全導入)。 - - latch は unreachable(header の pred を増やさず PHI 整合を保つ)。 -- BuilderCursor 強化(局所): at_end で終端検知/closed 初期化、emit_instr で post-terminator 挿入を即 panic。 -- 互換: MIR Const Void は i64(0) に無害化して Lower 継続性を向上。 - -LoopForm Flags(実験) -- `NYASH_ENABLE_LOOPFORM=1`: LoopForm 検出/配線を有効化(非破壊・Break 集約のみ)。 -- `NYASH_LOOPFORM_BODY2DISPATCH=1`: 単純ボディの Jump→header を dispatch へ差替え(tag=0/payload=0 を追加)。 -- `NYASH_LOOPFORM_LATCH2HEADER=1`: latch→header を有効化(現状は推奨OFF。header PHI 正規化後にONする)。 - -Next Flow(これからの流れ=段階導入) -1) BuilderCursor 厳格化の適用拡大(短期) - - 直叩き `build_*` を `emit_instr/emit_term/with_block` に段階置換(strings → arith_ops → mem → types)。 - - 軽量トラッカーで post-terminator 挿入を即検知(panic、犯人BB特定)。 -2) LoopForm 反復の本線(中期) - - header PHI 正規化(LoopForm 追加predを含めて「pred数=エントリ数」を保証)。 - - 実装: finalize_phis を LoopForm-aware に拡張(MIR由来pred + 追加pred(latch) をマージ)。 - - 受け渡し: pred 終端直前に局所 cast(既存の `coerce_to_type` を流用)。 - - 受入: `NYASH_LOOPFORM_LATCH2HEADER=1` をONにしても verifier green(PHI欠落なし)を確認。 -3) body→dispatch 導線の常用化(中期) - - 単純ボディから開始(終端が1つ=back-edge のみ)。 - - その後に複数出口/ネスト break/continue を段階解放(tag/payload で正規化)。 -4) 可視化と計測(並行) - - ループごとに dispatch-only PHI を確認(PHI個数/ゼロ合成の削減)。 - - post-terminator 挿入検知のカバレッジをログ化。 - -Acceptance(段階ごと) -- A1: LoopForm ON でも従来挙動と等価(Break 集約のみ・非破壊、smoke green)。 -- A2: BuilderCursor 厳格化で post-terminator が検知ゼロ(panic不発)が続く。 -- A2.5: sealed=ON で dep_tree_min_string の dominator 違反ゼロ(IR dump 不要レベル)。 -- A3: header PHI 正規化後、latch→header 有効でも verifier green(PHI 欠落なし)。 -- A4: body→dispatch を単純ボディで常用化し、dispatch 以外に PHI が出ないことを確認。 -- A5: `NYASH_LLVM_USE_HARNESS=1`(llvmlite)と OFF(Rust)の出力が dep_tree_min_string で機能一致。 - -Execution Plan — Next 48h -1) BuilderCursor 全域適用(externcall/newbox/arrays/maps/call)。 -2) Sealed=ON で dep_tree_min_string をグリーン(PHI/ドミナンス違反ゼロ)。 -3) (並行)llvmlite 検証ハーネス追加(Nyash ABI 経由、ゲートで切替)。 -4) BODY→DISPATCH 常用化(単純ボディ)。 - -## 🎉 LLVMプラグイン戻り値表示問題修正進行中(2025-09-10) - -### ✅ **完了した主要成果**: -1. **プラグイン実装** ✅ - `nyash.plugin.invoke_*`関数はnyrtライブラリに正常実装済み -2. **プラグイン呼び出し** ✅ - 環境変数なしでメソッド呼び出し成功 -3. **戻り値型推論修正** ✅ - MIR builder にプラグインメソッド型推論追加 -4. **by-id統一完了** ✅ - by-name方式削除、method_id方式に完全統一 -5. **環境変数削減達成** ✅ - `NYASH_LLVM_ALLOW_BY_NAME=1`削除完了 -6. **シンプル実行実現** ✅ - `./target/release/nyash --backend llvm program.nyash` -7. **codex TLV修正マージ完了** ✅ - プラグイン戻り値TLVタグ2/6/7対応(2000行修正) -8. **console.log ハンドル対応** ✅ - 不正なi64→i8*変換を修正、ハンドル対応関数に変更 -9. **型変換エラー解消** ✅ - bool(i1)→i64拡張処理追加でLLVM検証エラー修正 - -### 🔬 **現在の動作状況**(2025-09-11 最新): -- **プラグイン実行** ✅ - CounterBox、FileBox正常実行(副作用OK) -- **型エラー解消** ✅ - LLVM関数検証エラー修正済み -- **コンパイル成功** ✅ - 環境変数なしでLLVMバックエンド動作 -- **条件分岐動作** ✅ - `if f.exists("file")` → "TRUE"/"FALSE"表示OK - - **LLVMコード生成の分割** ✅ - `codegen/` 下にモジュール化(types/instructions) - - **BoxCall Lower 移譲** ✅ - 実行経路は `instructions::lower_boxcall` に一本化(旧実装は到達不能) - - **スモーク整理** ✅ - `tools/llvm_smoke.sh` を環境変数整理。プラグイン戻り値スモーク追加 - -### 🔍 **根本原因判明**: -**Task先生の詳細技術調査により特定**: -- **不正なハンドル→ポインタ変換**: `build_int_to_ptr(iv, i8p, "arg_i2p")` でハンドル値(3)を直接メモリアドレス(0x3)として扱う不正変換 -- **修正完了**: console.log を `nyash.console.log_handle(i64) -> i64` に変更、ハンドルレジストリ活用 - -### 🔍 **残存課題(新)**: - -#### 1. **プラグイン戻り値表示問題** 🔶 **←現在調査中** -**症状(修正後も継続)**: -- `CounterBox.get()` → 数値戻り値がprint()で空白 -- `FileBox.read(path)` → 文字列戻り値がprint()で空白 -- `FileBox.exists(path)` → if条件では動作、変数格納で失敗 -- `local x = 42; print(x)` → 正常(問題はプラグイン戻り値のみ) - -**残る問題の推定**: -- print()以外のExternCall処理にも同様の問題がある可能性 -- MIR変数代入処理での型情報不整合 -- プラグイン戻り値のハンドル→実値変換が不完全 - -#### 2. **LLVM BinOp 文字列連結まわり(新規スモークで再現)** 🔶 -現象: -- 追加スモーク `ny-plugin-ret-llvm-smoke` で LLVM AOT オブジェクト生成中に `binop type mismatch` が発生 -- ケース: `print("S=" + t)`(`t` は `StringBox.concat("CD")` の戻り値) - -暫定分析: -- `+` 連結の BinOp Lower は (ptr+ptr / ptr+int / int+ptr) を NyRT シム経由で処理する設計 -- `t` のLowerが i64 handleのままになっている/もしくは型注釈不足で ptr へ昇格できていない可能性 -- `instructions::lower_boxcall` 内の `concat` 特例(f64 fast-path)は撤去済み。以降は tagged invoke → dst 型注釈に応じて i64→ptr へ変換する想定 - -対応方針: -1) MIR の `value_types` に BoxCall 戻り値(StringBox)の型注釈が入っているか確認(不足時は MIR 側で注入) -2) BinOp 連結経路は fallback として (ptr + 非ptr) / (非ptr + ptr) に対して handle想定の i64→ptr 昇格を再確認 -3) 上記fixの後、スモーク `NYASH_LLVM_PLUGIN_RET_SMOKE=1` を通し、VM/LLVM 一致を確認 - -メモ: -- 現状でも BoxCall は新経路に完全委譲済み。旧実装は到達不能(削除準備OK)。 - -### 📊 **修正済みファイル**: -- **src/mir/builder.rs** - プラグインメソッド戻り値型推論追加 -- **src/backend/llvm/compiler.rs** - by-name方式削除、method_id統一 -- **src/backend/llvm/compiler/codegen/mod.rs** - 分割に伴う委譲(Const/Compare/Return/Jump/Branch/Extern/NewBox/Load/Store/BoxCall) -- **src/backend/llvm/compiler/codegen/types.rs** - 型変換/分類/型マップ(新) -- **src/backend/llvm/compiler/codegen/instructions.rs** - Lower群を実装(新) -- **tools/llvm_smoke.sh** - LLVM18 prefix整理・プラグイン戻り値スモーク追加 -- **apps/tests/ny-plugin-ret-llvm-smoke/main.nyash** - プラグイン戻り値スモーク(新) -- **CLAUDE.md** - 環境変数セクション更新、コマンド簡素化 -- **README.md / README.ja.md** - 環境変数説明削除 -- **tools/llvm_smoke.sh** - テストスクリプト環境変数削除 - -### 🎯 **次期深堀り調査対象**: -1. **プラグインランタイム戻り値パス詳細調査** - `crates/nyrt/src/lib.rs` -2. **LLVM BoxCall戻り値処理詳細分析** - `src/backend/llvm/compiler/real.rs` 戻り値変換 -3. **MIR変数代入メカニズム調査** - BoxCall→変数の代入処理 -4. **print()関数とプラグイン値の相性調査** - 型認識処理 -5. **BinOp 連結の保険見直し** - 片方が i64 (handle) の場合に ptr に昇格する安全弾性 -6. **BoxCall 旧実装の物理削除** - スモークグリーン後に `mod.rs` の死コードを除去 - -### ✅ **リファクタリング完了(2025-09-11)**: -**PR #136マージ済み** - LLVMコンパイラモジュール化: -- `compiler.rs` → 4モジュールに分割(mod.rs, real.rs, mock.rs, mock_impl.in.rs) -- プラグインシグネチャ読み込みを `plugin_sigs.rs` に移動 -- BoxタイプID読み込みを `box_types.rs` に移動 -- PR #134の型情報処理を完全保持 - -Hot Update — 2025‑09‑12 (Boxing: ctx + dev checks) -- Added `instructions/ctx.rs` introducing `LowerFnCtx` and `BlockCtx` (Resolver-first accessors: ensure_i64/ptr/f64). Env `NYASH_DEV_CHECKS=1` enables extra cursor-open asserts. -- Added `instructions/string_ops.rs` with lightweight newtypes `StrHandle(i64)` and `StrPtr(i8*)` (handle-across-blocks policy). Call sites will adopt gradually. -- Exposed new modules in `instructions/mod.rs` and added a thin `lower_boxcall_boxed(..)` shim. -- Wiring remains unchanged (still calls existing `lower_boxcall(..)`); borrow conflicts will be avoided by migrating call-sites in small steps. -- Scope alignment: Boxing covers the full BoxCall lowering suite (fields/invoke/marshal) as we migrate internals in-place. - -Hot Update — 2025‑09‑12 (flow API trim) -- `emit_jump` から `vmap` 引数を削除(sealed前提で未使用のため)。 -- `seal_block` から `vmap` 引数を削除(snapshot優先・ゼロ合成で代替)。 -- 上記により `compile_module` 内の借用競合(&mut cursor と &vmap の競合)を縮小。 - -Dev Checks(実装) -- 追加: `NYASH_DEV_CHECK_DISPATCH_ONLY_PHI=1` で LoopForm 登録がある関数の PHI 分布をログ出力(暫定: dispatch-only 厳格検証は今後強化)。 -- 既存: BuilderCursor の post-terminator ガードを全域適用済み(emit_instr/emit_term)。 - -結果(Step 4 検証) -- dep_tree_min_string の LLVM オブジェクト生成が成功(sealed=ON, plugins OFF)。 - - コマンド(例): - - `cargo build --features llvm --bin nyash` - - `NYASH_DISABLE_PLUGINS=1 NYASH_LLVM_OBJ_OUT=tmp/dep_tree_min_string.o target/debug/nyash --backend llvm apps/selfhost/tools/dep_tree_min_string.nyash` - - 出力: `tmp/dep_tree_min_string.o`(約 10KB) - -次の一手(箱化の本適用・安全切替) -- lower_boxcall 内部の段階移行(fields → invoke → marshal)を小スコープで進め、呼び出し側の `&mut cursor` 借用境界と競合しない形で flip。 -- flip 後、Deny-Direct(`rg "vmap\.get\("` = 0)を下流のCIメモに追記、必要なら `#[cfg(debug_assertions)]` の簡易ガードを追加。 - -Note(箱化の段階切替) -- BoxCall 呼び出しを `lower_boxcall_boxed` へ全面切替は、`compile_module` のループ内における `&mut cursor` の借用境界と干渉するため、いったん保留。内部の移行(fields/invoke/marshal)を先に進め、借用の生存域を短縮した上で切替予定。 - -## 🎯 Restart Notes — Ny Syntax Alignment (2025‑09‑07) - -### 目的 -- Self‑Hosting 方針(Nyのみで工具を回す前段)に合わせて、Ny 構文とコーディング規約を明示化し、最小版ツールの完走性を優先。 - -### Ny 構文(実装時の基準) -- 1行1文/セミコロン非使用。 -- break / continue を使わない(関数早期 return、番兵条件、if 包みで置換)。 -- else は直前の閉じ波括弧と同一行(`} else {`})。 -- 文字列の `"` と `\` は確実にエスケープ。 -- 反復は `loop(条件) { …; インクリメント }` を基本とし、必要に応じて「関数早期 return」型で早期脱出。 - -### 短期タスク(Syntax 合意前提で最小ゴール) -1) include のみ依存木(Nyのみ・配列/マップ未使用)を完走化 - - `apps/selfhost/tools/dep_tree_min_string.nyash` - - FileBox/PathBox + 文字走査のみで JSON 構築(配列/マップに頼らない) - - `make dep-tree` で `tmp/deps.json` を出力 -2) using/module 対応は次段(構文・優先順位をユーザーと再すり合わせ後) - - 優先: `module > 相対 > using-path`、曖昧=エラー、STRICT ゲート(要相談) -3) ブリッジ Stage 1 は保留 - - `NYASH_DEPS_JSON=` 読み込み(ログ出力のみ)を最小パッチで用意(MIR/JIT/AOT は不変) diff --git a/Cargo.toml b/Cargo.toml index 9d2fc5ff..9a07c964 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,10 @@ name = "test-plugin-loader-v2" path = "src/bin/test_plugin_loader_v2.rs" required-features = ["dynamic-file"] +[[bin]] +name = "ny_mir_builder" +path = "src/bin/ny_mir_builder.rs" + # Examples for development - only available as examples, not bins [[example]] name = "gui_simple_notepad" @@ -243,3 +247,6 @@ panic = "abort" # 開発用設定 opt-level = 0 debug = true +[profile.release.package."nyash-net-plugin"] +opt-level = "z" +strip = true diff --git a/apps/selfhost-compiler/boxes/debug_box.nyash b/apps/selfhost-compiler/boxes/debug_box.nyash new file mode 100644 index 00000000..08bcdec0 --- /dev/null +++ b/apps/selfhost-compiler/boxes/debug_box.nyash @@ -0,0 +1,38 @@ +// DebugBox — conditional debug output aggregator (extracted) +box DebugBox { + enabled + birth() { + me.enabled = 0 + return 0 + } + set_enabled(v) { + me.enabled = v + return 0 + } + log(msg) { + if me.enabled { + local c = new ConsoleBox() + c.println("[DEBUG] " + msg) + } + return 0 + } + info(msg) { + if me.enabled { + local c = new ConsoleBox() + c.println("[INFO] " + msg) + } + return 0 + } + error(msg) { + if me.enabled { + local c = new ConsoleBox() + c.println("[ERROR] " + msg) + } + return 0 + } +} + +// Include stub to satisfy current include lowering (expects a static box) +static box DebugStub { + main(args) { return 0 } +} diff --git a/apps/selfhost-compiler/boxes/emitter_box.nyash b/apps/selfhost-compiler/boxes/emitter_box.nyash new file mode 100644 index 00000000..c02ec4b9 --- /dev/null +++ b/apps/selfhost-compiler/boxes/emitter_box.nyash @@ -0,0 +1,18 @@ +// EmitterBox — thin wrapper to emit JSON v0 (extracted) +box EmitterBox { + emit_program(json, usings_json) { + if usings_json == null { return json } + // Attach meta.usings when available and non-empty (bridge ignores unknown fields) + if usings_json.length() == 0 { return json } + if usings_json == "[]" { return json } + // naive injection: insert before the final '}' of the top-level object + local n = json.lastIndexOf("}") + if n < 0 { return json } + local head = json.substring(0, n) + return head + ",\"meta\":{\"usings\":" + usings_json + "}}" + } +} + +static box EmitterStub { + main(args) { return 0 } +} diff --git a/apps/selfhost-compiler/boxes/parser_box.nyash b/apps/selfhost-compiler/boxes/parser_box.nyash new file mode 100644 index 00000000..6495d853 --- /dev/null +++ b/apps/selfhost-compiler/boxes/parser_box.nyash @@ -0,0 +1,456 @@ +// ParserBox — Stage‑1 JSON v0 generator(extracted, simplified for Rust parser) +box ParserBox { + gpos + usings_json + + birth() { me.gpos = 0 me.usings_json = "[]" return 0 } + + esc_json(s) { + local out = "" + local i = 0 + local n = s.length() + loop(i < n) { + local ch = s.substring(i, i+1) + if ch == "\\" { out = out + "\\\\" } else { if ch == "\"" { out = out + "\\\"" } else { out = out + ch } } + i = i + 1 + } + return out + } + + is_digit(ch) { return ch >= "0" && ch <= "9" } + is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" } + // simple alpha/underscore check for identifiers + is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" } + + gpos_set(i) { me.gpos = i return 0 } + gpos_get() { return me.gpos } + + // lightweight string helpers + starts_with(src, i, pat) { + local n = src.length() + local m = pat.length() + if i + m > n { return 0 } + local k = 0 + loop(k < m) { + if src.substring(i + k, i + k + 1) != pat.substring(k, k + 1) { return 0 } + k = k + 1 + } + return 1 + } + index_of(src, i, pat) { + local n = src.length() + local m = pat.length() + if m == 0 { return i } + local j = i + loop(j + m <= n) { + if me.starts_with(src, j, pat) { return j } + j = j + 1 + } + return -1 + } + trim(s) { + local i = 0 + local n = s.length() + loop(i < n && (s.substring(i,i+1) == " " || s.substring(i,i+1) == "\t")) { i = i + 1 } + local j = n + loop(j > i && (s.substring(j-1,j) == " " || s.substring(j-1,j) == "\t" || s.substring(j-1,j) == ";")) { j = j - 1 } + return s.substring(i, j) + } + + // keyword match at position i with word-boundary (next char not [A-Za-z0-9_]) + starts_with_kw(src, i, kw) { + if me.starts_with(src, i, kw) == 0 { return 0 } + local n = src.length() + local j = i + kw.length() + if j >= n { return 1 } + local ch = src.substring(j, j+1) + if me.is_alpha(ch) || me.is_digit(ch) { return 0 } + return 1 + } + + // integer to string (uses string concat coercion) + i2s(v) { return "" + v } + + // Read identifier starting at i: [A-Za-z_][A-Za-z0-9_]*; returns "name@pos" + read_ident2(src, i) { + local n = src.length() + local j = i + if j >= n { return "@" + me.i2s(i) } + local ch = src.substring(j, j+1) + if me.is_alpha(ch) == 0 { return "@" + me.i2s(i) } + j = j + 1 + loop(j < n) { + local c = src.substring(j, j+1) + if me.is_alpha(c) || me.is_digit(c) { j = j + 1 } else { break } + } + local name = src.substring(i, j) + return name + "@" + me.i2s(j) + } + + // Read string literal at i (i points to '"'); returns raw content (no quotes), updates gpos + read_string_lit(src, i) { + local n = src.length() + local j = i + if j >= n || src.substring(j, j+1) != "\"" { me.gpos_set(i) return "" } + j = j + 1 + local out = "" + local guard = 0 + local max = 200000 + loop(j < n) { + if guard > max { break } else { guard = guard + 1 } + local ch = src.substring(j, j+1) + if ch == "\"" { j = j + 1 me.gpos_set(j) return out } + if ch == "\\" && j + 1 < n { + local nx = src.substring(j+1, j+2) + if nx == "\"" { out = out + "\"" } else { if nx == "\\" { out = out + "\\" } else { out = out + nx } } + j = j + 2 + } else { out = out + ch j = j + 1 } + } + me.gpos_set(j) + return out + } + + // Append a using entry into usings_json (no-op acceptance path) + add_using(kind, target, alias) { + // kind: "path" or "ns"; target: path or namespace; alias: nullable + local cur = me.usings_json + if cur == null || cur.length() == 0 { cur = "[]" } + // Build entry + local name = "" + local path = null + if kind == "path" { + path = target + if alias != null { name = alias } else { + local p = target + // basename + local idx = -1 + local t = 0 + loop(t < p.length()) { if p.substring(t,t+1) == "/" { idx = t } t = t + 1 } + if idx >= 0 { p = p.substring(idx+1, p.length()) } + if p.length() > 6 && me.starts_with(p, p.length()-6, ".nyash") == 1 { p = p.substring(0, p.length()-6) } + name = p + } + } else { + name = target + if alias != null { name = alias } + } + local entry = "{\"name\":\"" + me.esc_json(name) + "\"" + if path != null { entry = entry + ",\"path\":\"" + me.esc_json(path) + "\"" } + entry = entry + "}" + // Insert before closing ']' of array + if cur == "[]" { me.usings_json = "[" + entry + "]" return 0 } + // naive append + local pos = cur.lastIndexOf("]") + if pos < 0 { me.usings_json = "[" + entry + "]" return 0 } + me.usings_json = cur.substring(0, pos) + "," + entry + "]" + return 0 + } + + // Collect `using` lines into JSON array stored in me.usings_json (no-op acceptance) + extract_usings(src) { + if src == null { me.usings_json = "[]" return 0 } + local n = src.length() + local i = 0 + local first = 1 + local out = "[" + loop(i < n) { + // read a line + local j = i + loop(j < n && src.substring(j, j+1) != "\n") { j = j + 1 } + local line = src.substring(i, j) + // process + local k = 0 + loop(k < line.length() && (line.substring(k,k+1) == " " || line.substring(k,k+1) == "\t")) { k = k + 1 } + if me.starts_with(line, k, "using ") == 1 { + local rest = me.trim(line.substring(k + 6, line.length())) + // split on ' as ' + local as_pos = me.index_of(rest, 0, " as ") + local target = rest + local alias = null + if as_pos >= 0 { + target = me.trim(rest.substring(0, as_pos)) + alias = me.trim(rest.substring(as_pos + 4, rest.length())) + } + // path or namespace + local is_path = 0 + if target.length() > 0 { + if me.starts_with(target, 0, "\"") == 1 { is_path = 1 } + if me.starts_with(target, 0, "./") == 1 { is_path = 1 } + if me.starts_with(target, 0, "/") == 1 { is_path = 1 } + if target.length() >= 6 && me.starts_with(target, target.length()-6, ".nyash") == 1 { is_path = 1 } + } + local name = "" + local path = null + if is_path == 1 { + // trim quotes + if me.starts_with(target, 0, "\"") == 1 { + target = target.substring(1, target.length()) + if target.length() > 0 && target.substring(target.length()-1, target.length()) == "\"" { + target = target.substring(0, target.length()-1) + } + } + path = target + if alias != null { name = alias } else { + // derive from basename + local p = target + // find last '/' + local idx = -1 + local t = 0 + loop(t < p.length()) { if p.substring(t,t+1) == "/" { idx = t } t = t + 1 } + if idx >= 0 { p = p.substring(idx+1, p.length()) } + // strip .nyash + if p.length() > 6 && me.starts_with(p, p.length()-6, ".nyash") == 1 { p = p.substring(0, p.length()-6) } + name = p + } + } else { + name = target + } + // append JSON entry + if first == 0 { out = out + "," } else { first = 0 } + out = out + "{\"name\":\"" + me.esc_json(name) + "\"" + if path != null { out = out + ",\"path\":\"" + me.esc_json(path) + "\"" } + out = out + "}" + } + i = j + 1 + } + out = out + "]" + me.usings_json = out + return 0 + } + get_usings_json() { return me.usings_json } + + to_int(s) { local n = s.length() if n == 0 { return 0 } local i = 0 local acc = 0 loop(i < n) { local d = s.substring(i, i+1) local dv = 0 if d == "1" { dv = 1 } else { if d == "2" { dv = 2 } else { if d == "3" { dv = 3 } else { if d == "4" { dv = 4 } else { if d == "5" { dv = 5 } else { if d == "6" { dv = 6 } else { if d == "7" { dv = 7 } else { if d == "8" { dv = 8 } else { if d == "9" { dv = 9 } } } } } } } } } acc = acc * 10 + dv i = i + 1 } return acc } + + skip_ws(src, i) { if src == null { return i } local n = src.length() local cont = 1 local guard = 0 local max = 100000 loop(cont == 1) { if guard > max { return i } guard = guard + 1 if i < n { if me.is_space(src.substring(i, i+1)) { i = i + 1 } else { cont = 0 } } else { cont = 0 } } return i } + // identifiers/strings not required for Stage‑1 beyond string literal parse above + + // using metadata omitted in Stage‑1 + + parse_number2(src, i) { local n = src.length() local j = i local cont = 1 local guard = 0 local max = 100000 loop(cont == 1) { if guard > max { cont = 0 } else { guard = guard + 1 if j < n { if me.is_digit(src.substring(j, j+1)) { j = j + 1 } else { cont = 0 } } else { cont = 0 } } } local s = src.substring(i, j) me.gpos_set(j) return "{\"type\":\"Int\",\"value\":" + s + "}" } + parse_string2(src, i) { local n = src.length() local j = i + 1 local out = "" local guard = 0 local max = 200000 loop(j < n) { if guard > max { break } guard = guard + 1 local ch = src.substring(j, j+1) if ch == "\"" { j = j + 1 me.gpos_set(j) return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}" } if ch == "\\" && j + 1 < n { local nx = src.substring(j+1, j+2) if nx == "\"" { out = out + "\"" } else { if nx == "\\" { out = out + "\\" } else { out = out + nx } } j = j + 2 } else { out = out + ch j = j + 1 } } me.gpos_set(j) return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}" } + + parse_factor2(src, i) { local j = me.skip_ws(src, i) local ch = src.substring(j, j+1) if ch == "(" { local inner = me.parse_expr2(src, j + 1) local k = me.gpos_get() k = me.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } me.gpos_set(k) return inner } if ch == "\"" { return me.parse_string2(src, j) } if me.starts_with_kw(src, j, "true") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Bool\",\"value\":true}" } if me.starts_with_kw(src, j, "false") == 1 { me.gpos_set(j + 5) return "{\"type\":\"Bool\",\"value\":false}" } if me.starts_with_kw(src, j, "new") == 1 { local p = me.skip_ws(src, j + 3) local idp = me.read_ident2(src, p) local at = idp.lastIndexOf("@") local cls = idp.substring(0, at) local k = me.to_int(idp.substring(at+1, idp.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == "(" { k = k + 1 } local args_and_pos = me.parse_args2(src, k) local at2 = args_and_pos.lastIndexOf("@") local args_json = args_and_pos.substring(0, at2) k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } me.gpos_set(k) return "{\"type\":\"New\",\"class\":\"" + cls + "\",\"args\":" + args_json + "}" } if me.is_alpha(ch) { local idp = me.read_ident2(src, j) local at = idp.lastIndexOf("@") local name = idp.substring(0, at) local k = me.to_int(idp.substring(at+1, idp.length())) local node = "{\"type\":\"Var\",\"name\":\"" + name + "\"}" local cont = 1 loop(cont == 1) { k = me.skip_ws(src, k) local tch = src.substring(k, k+1) if tch == "(" { k = k + 1 local args_and_pos = me.parse_args2(src, k) local at2 = args_and_pos.lastIndexOf("@") local args_json = args_and_pos.substring(0, at2) k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } node = "{\"type\":\"Call\",\"name\":\"" + name + "\",\"args\":" + args_json + "}" } else { if tch == "." { k = k + 1 k = me.skip_ws(src, k) local midp = me.read_ident2(src, k) local at3 = midp.lastIndexOf("@") local mname = midp.substring(0, at3) k = me.to_int(midp.substring(at3+1, midp.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == "(" { k = k + 1 } local args2 = me.parse_args2(src, k) local at4 = args2.lastIndexOf("@") local args_json2 = args2.substring(0, at4) k = me.to_int(args2.substring(at4+1, args2.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } node = "{\"type\":\"Method\",\"recv\":" + node + ",\"method\":\"" + mname + "\",\"args\":" + args_json2 + "}" } else { cont = 0 } } } me.gpos_set(k) return node } return me.parse_number2(src, j) } + // unary minus binds tighter than * / + parse_unary2(src, i) { + local j = me.skip_ws(src, i) + if src.substring(j, j+1) == "-" { + local rhs = me.parse_factor2(src, j + 1) + j = me.gpos_get() + local zero = "{\"type\":\"Int\",\"value\":0}" + me.gpos_set(j) + return "{\"type\":\"Binary\",\"op\":\"-\",\"lhs\":" + zero + ",\"rhs\":" + rhs + "}" + } + return me.parse_factor2(src, j) + } + parse_term2(src, i) { local lhs = me.parse_unary2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) if j >= src.length() { cont = 0 } else { local op = src.substring(j, j+1) if op != "*" && op != "/" { cont = 0 } else { local rhs = me.parse_unary2(src, j+1) j = me.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } } me.gpos_set(j) return lhs } + parse_sum2(src, i) { local lhs = me.parse_term2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) if j >= src.length() { cont = 0 } else { local op = src.substring(j, j+1) if op != "+" && op != "-" { cont = 0 } else { local rhs = me.parse_term2(src, j+1) j = me.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } } me.gpos_set(j) return lhs } + parse_compare2(src, i) { local lhs = me.parse_sum2(src, i) local j = me.gpos_get() j = me.skip_ws(src, j) local two = src.substring(j, j+2) local one = src.substring(j, j+1) local op = "" if two == "==" || two == "!=" || two == "<=" || two == ">=" { op = two j = j + 2 } else { if one == "<" || one == ">" { op = one j = j + 1 } } if op == "" { me.gpos_set(j) return lhs } local rhs = me.parse_sum2(src, j) j = me.gpos_get() me.gpos_set(j) return "{\"type\":\"Compare\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } + parse_expr2(src, i) { local lhs = me.parse_compare2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) local two = src.substring(j, j+2) if two != "&&" && two != "||" { cont = 0 } else { local rhs = me.parse_compare2(src, j+2) j = me.gpos_get() lhs = "{\"type\":\"Logical\",\"op\":\"" + two + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } me.gpos_set(j) return lhs } + parse_args2(src, i) { + local j = me.skip_ws(src, i) + local n = src.length() + local out = "[" + j = me.skip_ws(src, j) + if j < n && src.substring(j, j+1) == ")" { return "[]@" + me.i2s(j) } + // first argument + local e = me.parse_expr2(src, j) + j = me.gpos_get() + out = out + e + // subsequent arguments with guard + local cont_args = 1 + local guard = 0 + local max = 100000 + loop(cont_args == 1) { + if guard > max { cont_args = 0 } else { guard = guard + 1 } + local before = j + j = me.skip_ws(src, j) + if j < n && src.substring(j, j+1) == "," { + j = j + 1 + j = me.skip_ws(src, j) + e = me.parse_expr2(src, j) + j = me.gpos_get() + out = out + "," + e + } else { cont_args = 0 } + if j == before { cont_args = 0 } + } + out = out + "]" + return out + "@" + me.i2s(j) + } + parse_stmt2(src, i) { + local j = me.skip_ws(src, i) + local stmt_start = j + if me.starts_with_kw(src, j, "using") == 1 { + j = j + 5 + j = me.skip_ws(src, j) + if src.substring(j, j+1) == "\"" { + local p = me.read_string_lit(src, j) + j = me.gpos_get() + j = me.skip_ws(src, j) + local alias = null + if me.starts_with_kw(src, j, "as") == 1 { j = j + 2 j = me.skip_ws(src, j) local idp = me.read_ident2(src, j) local at = idp.lastIndexOf("@") alias = idp.substring(0, at) j = me.to_int(idp.substring(at+1, idp.length())) } + me.add_using("path", p, alias) + } else { + if me.is_alpha(src.substring(j, j+1)) { + local idp = me.read_ident2(src, j) + local at = idp.lastIndexOf("@") + local name = idp.substring(0, at) + j = me.to_int(idp.substring(at+1, idp.length())) + local cont = 1 + loop(cont == 1) { + j = me.skip_ws(src, j) + if src.substring(j, j+1) == "." { j = j + 1 j = me.skip_ws(src, j) idp = me.read_ident2(src, j) at = idp.lastIndexOf("@") name = name + "." + idp.substring(0, at) j = me.to_int(idp.substring(at+1, idp.length())) } else { cont = 0 } + } + j = me.skip_ws(src, j) + local alias2 = null + if me.starts_with_kw(src, j, "as") == 1 { j = j + 2 j = me.skip_ws(src, j) idp = me.read_ident2(src, j) at = idp.lastIndexOf("@") alias2 = idp.substring(0, at) j = me.to_int(idp.substring(at+1, idp.length())) } + me.add_using("ns", name, alias2) + } + } + // ensure progress + if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } } + me.gpos_set(j) + return "" + } + if me.starts_with_kw(src, j, "return") == 1 { + j = j + 6 + j = me.skip_ws(src, j) + local e = me.parse_expr2(src, j) + j = me.gpos_get() + if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } } + me.gpos_set(j) + return "{\"type\":\"Return\",\"expr\":" + e + "}" + } + if me.starts_with_kw(src, j, "local") == 1 { + j = j + 5 + j = me.skip_ws(src, j) + local idp = me.read_ident2(src, j) + local at = idp.lastIndexOf("@") + local name = idp.substring(0, at) + j = me.to_int(idp.substring(at+1, idp.length())) + j = me.skip_ws(src, j) + if src.substring(j, j+1) == "=" { j = j + 1 } + j = me.skip_ws(src, j) + local e2 = me.parse_expr2(src, j) + j = me.gpos_get() + if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } } + me.gpos_set(j) + return "{\"type\":\"Local\",\"name\":\"" + name + "\",\"expr\":" + e2 + "}" + } + if me.starts_with_kw(src, j, "if") == 1 { + j = j + 2 + j = me.skip_ws(src, j) + local paren = 0 + if src.substring(j, j+1) == "(" { paren = 1 j = j + 1 } + local cond = me.parse_expr2(src, j) + j = me.gpos_get() + if paren == 1 { j = me.skip_ws(src, j) if src.substring(j, j+1) == ")" { j = j + 1 } } + j = me.skip_ws(src, j) + local then_res = me.parse_block2(src, j) + local at1 = then_res.lastIndexOf("@") + local then_json = then_res.substring(0, at1) + j = me.to_int(then_res.substring(at1+1, then_res.length())) + j = me.skip_ws(src, j) + local else_json = null + if me.starts_with_kw(src, j, "else") == 1 { j = j + 4 j = me.skip_ws(src, j) local else_res = me.parse_block2(src, j) local at2 = else_res.lastIndexOf("@") else_json = else_res.substring(0, at2) j = me.to_int(else_res.substring(at2+1, else_res.length())) } + if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } } + me.gpos_set(j) + if else_json == null { return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + "}" } else { return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + ",\"else\":" + else_json + "}" } + } + if me.starts_with_kw(src, j, "loop") == 1 { + j = j + 4 + j = me.skip_ws(src, j) + if src.substring(j, j+1) == "(" { j = j + 1 } + local cond = me.parse_expr2(src, j) + j = me.gpos_get() + j = me.skip_ws(src, j) + if src.substring(j, j+1) == ")" { j = j + 1 } + j = me.skip_ws(src, j) + local body_res = me.parse_block2(src, j) + local at3 = body_res.lastIndexOf("@") + local body_json = body_res.substring(0, at3) + j = me.to_int(body_res.substring(at3+1, body_res.length())) + if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } } + me.gpos_set(j) + return "{\"type\":\"Loop\",\"cond\":" + cond + ",\"body\":" + body_json + "}" + } + // Fallback: expression or unknown token — ensure progress even on malformed input + local expr_start = j + local e = me.parse_expr2(src, j) + j = me.gpos_get() + if j <= expr_start { if j < src.length() { j = j + 1 } else { j = src.length() } } + me.gpos_set(j) + return "{\"type\":\"Expr\",\"expr\":" + e + "}" + } + parse_block2(src, i) { + local j = me.skip_ws(src, i) + if src.substring(j, j+1) != "{" { return "[]@" + me.i2s(j) } + j = j + 1 + local body = "[" + local first = 1 + local cont_block = 1 + loop(cont_block == 1) { + j = me.skip_ws(src, j) + if j >= src.length() { cont_block = 0 } else { + if src.substring(j, j+1) == "}" { j = j + 1 cont_block = 0 } else { + local start_j = j + local s = me.parse_stmt2(src, j) + j = me.gpos_get() + // Progress guard: ensure forward movement to avoid infinite loop on malformed input + if j <= start_j { + if j < src.length() { j = j + 1 } else { j = src.length() } + me.gpos_set(j) + } + // consume optional semicolons (ASI minimal) + local done = 0 + local guard = 0 + local max = 100000 + loop(done == 0) { + if guard > max { done = 1 } else { guard = guard + 1 } + local before = j + j = me.skip_ws(src, j) + if j < src.length() && src.substring(j, j+1) == ";" { j = j + 1 } else { done = 1 } + if j == before { done = 1 } + } + if s.length() > 0 { if first == 1 { body = body + s first = 0 } else { body = body + "," + s } } + } + } + } + body = body + "]" + return body + "@" + me.i2s(j) + } + parse_program2(src) { + local i = me.skip_ws(src, 0) + local body = "[" + local first = 1 + local cont_prog = 1 + loop(cont_prog == 1) { + i = me.skip_ws(src, i) + if i >= src.length() { cont_prog = 0 } else { + local start_i = i + local s = me.parse_stmt2(src, i) + i = me.gpos_get() + // Progress guard: ensure forward movement to avoid infinite loop on malformed input + if i <= start_i { + if i < src.length() { i = i + 1 } else { i = src.length() } + me.gpos_set(i) + } + // consume optional semicolons between top-level statements + local done2 = 0 + local guard2 = 0 + local max2 = 100000 + loop(done2 == 0) { + if guard2 > max2 { done2 = 1 } else { guard2 = guard2 + 1 } + local before2 = i + i = me.skip_ws(src, i) + if i < src.length() && src.substring(i, i+1) == ";" { i = i + 1 } else { done2 = 1 } + if i == before2 { done2 = 1 } + } + if s.length() > 0 { if first == 1 { body = body + s first = 0 } else { body = body + "," + s } } + } + } + body = body + "]" + return "{\"version\":0,\"kind\":\"Program\",\"body\":" + body + "}" + } +} + +static box ParserStub { main(args) { return 0 } } diff --git a/apps/selfhost-compiler/compiler.nyash b/apps/selfhost-compiler/compiler.nyash index 111e2650..eb7e268e 100644 --- a/apps/selfhost-compiler/compiler.nyash +++ b/apps/selfhost-compiler/compiler.nyash @@ -1,5 +1,10 @@ // Selfhost Compiler MVP (Phase 15.3) // Reads tmp/ny_parser_input.ny and prints a minimal JSON v0 program. +// Components are split under boxes/ and included here. + +include "apps/selfhost-compiler/boxes/debug_box.nyash" +include "apps/selfhost-compiler/boxes/parser_box.nyash" +include "apps/selfhost-compiler/boxes/emitter_box.nyash" static box Main { // ---- IO helper ---- @@ -8,6 +13,7 @@ static box Main { fb.open(path, "r") local s = fb.read() fb.close() + if s == null { return "return 1+2*3" } return s } @@ -26,283 +32,76 @@ static box Main { return out } - // ---- Lexer helpers ---- - is_digit(ch) { - return ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" - } - is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" || ch == ";" } - - // ---- Parser (Stage-1/mini Stage-2) ---- - // Global cursor for second-pass parser (no pack strings) - gpos_set(i) { - me.gpos = i - return 0 - } - gpos_get() { return me.gpos } - - parse_number2(src, i) { - local n = src.length() - local j = i - loop(j < n && me.is_digit(src.substring(j, j+1))) { j = j + 1 } - local s = src.substring(i, j) - me.gpos_set(j) - return "{\"type\":\"Int\",\"value\":" + s + "}" - } - - parse_string2(src, i) { - local n = src.length() - local j = i + 1 - local out = "" - local done = 0 - loop(j < n && done == 0) { - local ch = src.substring(j, j+1) - if ch == "\"" { - j = j + 1 - done = 1 - } else { - if ch == "\\" && j + 1 < n { - local nx = src.substring(j+1, j+2) - if nx == "\"" { out = out + "\"" } else { if nx == "\\" { out = out + "\\" } else { out = out + nx } } - j = j + 2 - } else { - out = out + ch - j = j + 1 - } - } - } - me.gpos_set(j) - return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}" - } - - parse_factor2(src, i) { - local j = me.skip_ws(src, i) - local ch = src.substring(j, j+1) - if ch == "(" { - local inner = me.parse_expr2(src, j + 1) - local k = me.gpos_get() - k = me.skip_ws(src, k) - if src.substring(k, k+1) == ")" { k = k + 1 } - me.gpos_set(k) - return inner - } - if ch == "\"" { return me.parse_string2(src, j) } - return me.parse_number2(src, j) - } - - parse_term2(src, i) { - local lhs = me.parse_factor2(src, i) - local j = me.gpos_get() - local cont = 1 - loop(cont == 1) { - j = me.skip_ws(src, j) - if j >= src.length() { cont = 0 } else { - local op = src.substring(j, j+1) - if op != "*" && op != "/" { cont = 0 } else { - local rhs = me.parse_factor2(src, j+1) - j = me.gpos_get() - lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" - } - } - } - me.gpos_set(j) - return lhs - } - - parse_expr2(src, i) { - local lhs = me.parse_term2(src, i) - local j = me.gpos_get() - local cont = 1 - loop(cont == 1) { - j = me.skip_ws(src, j) - if j >= src.length() { cont = 0 } else { - local op = src.substring(j, j+1) - if op != "+" && op != "-" { cont = 0 } else { - local rhs = me.parse_term2(src, j+1) - j = me.gpos_get() - lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" - } - } - } - me.gpos_set(j) - return lhs - } - - parse_program2(src) { - local i = me.skip_ws(src, 0) - local j = me.skip_return_kw(src, i) - if j == i { j = i } // optional 'return' - local expr = me.parse_expr2(src, j) - return "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":" + expr + "}]}" - } - i2s(n) { - // integer to decimal string (non-negative only for MVP) - if n == 0 { return "0" } - local x = n - if x < 0 { x = 0 } // MVP: clamp negatives to 0 to avoid surprises - local out = "" - loop(x > 0) { - local q = x / 10 - local d = x - q * 10 - local ch = "0" - if d == 1 { ch = "1" } else { - if d == 2 { ch = "2" } else { - if d == 3 { ch = "3" } else { - if d == 4 { ch = "4" } else { - if d == 5 { ch = "5" } else { - if d == 6 { ch = "6" } else { - if d == 7 { ch = "7" } else { - if d == 8 { ch = "8" } else { - if d == 9 { ch = "9" } - } - } - } - } - } - } - } - } - out = ch + out - x = q - } - return out - } - to_int(s) { - local n = s.length() - if n == 0 { return 0 } - local i = 0 - local acc = 0 - loop(i < n) { - local d = s.substring(i, i+1) - local dv = 0 - if d == "1" { dv = 1 } else { if d == "2" { dv = 2 } else { if d == "3" { dv = 3 } else { if d == "4" { dv = 4 } else { if d == "5" { dv = 5 } else { if d == "6" { dv = 6 } else { if d == "7" { dv = 7 } else { if d == "8" { dv = 8 } else { if d == "9" { dv = 9 } } } } } } } } } - acc = acc * 10 + dv - i = i + 1 - } - return acc - } - - parse_number(src, i) { - local n = src.length() - local j = i - loop(j < n && me.is_digit(src.substring(j, j+1))) { j = j + 1 } - local s = src.substring(i, j) - local json = "{\"type\":\"Int\",\"value\":" + s + "}" - return json + "@" + me.i2s(j) - } - - parse_string(src, i) { - local n = src.length() - local j = i + 1 - local out = "" - loop(j < n) { - local ch = src.substring(j, j+1) - if ch == "\"" { - j = j + 1 - return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}@" + j - } - if ch == "\\" && j + 1 < n { - local nx = src.substring(j+1, j+2) - if nx == "\"" { out = out + "\"" } else { if nx == "\\" { out = out + "\\" } else { out = out + nx } } - j = j + 2 - } else { - out = out + ch - j = j + 1 - } - } - return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}@" + me.i2s(j) - } - - skip_ws(src, i) { - local n = src.length() - loop(i < n && me.is_space(src.substring(i, i+1))) { i = i + 1 } - return i - } - - skip_return_kw(src, i) { - // If source at i starts with "return", advance; otherwise return i unchanged - local n = src.length() - local j = i - if j < n && src.substring(j, j+1) == "r" { j = j + 1 } else { return i } - if j < n && src.substring(j, j+1) == "e" { j = j + 1 } else { return i } - if j < n && src.substring(j, j+1) == "t" { j = j + 1 } else { return i } - if j < n && src.substring(j, j+1) == "u" { j = j + 1 } else { return i } - if j < n && src.substring(j, j+1) == "r" { j = j + 1 } else { return i } - if j < n && src.substring(j, j+1) == "n" { j = j + 1 } else { return i } - return j - } - - parse_factor(src, i) { - i = me.skip_ws(src, i) - local ch = src.substring(i, i+1) - if ch == "(" { - local p = me.parse_expr(src, i + 1) - local at = p.lastIndexOf("@") - local ej = p.substring(0, at) - local j = me.to_int(p.substring(at+1, p.length())) - j = me.skip_ws(src, j) - if src.substring(j, j+1) == ")" { j = j + 1 } - return ej + "@" + me.i2s(j) - } - if ch == "\"" { return me.parse_string(src, i) } - return me.parse_number(src, i) - } - - parse_term(src, i) { - local p = me.parse_factor(src, i) - local at = p.lastIndexOf("@") - local lhs = p.substring(0, at) - local j = me.to_int(p.substring(at+1, p.length())) - local cont = 1 - loop(cont == 1) { - j = me.skip_ws(src, j) - if j >= src.length() { cont = 0 } else { - local op = src.substring(j, j+1) - if op != "*" && op != "/" { cont = 0 } else { - local q = me.parse_factor(src, j+1) - local at2 = q.lastIndexOf("@") - local rhs = q.substring(0, at2) - j = me.to_int(q.substring(at2+1, q.length())) - lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" - } - } - } - return lhs + "@" + me.i2s(j) - } - - parse_expr(src, i) { - local p = me.parse_term(src, i) - local at = p.lastIndexOf("@") - local lhs = p.substring(0, at) - local j = me.to_int(p.substring(at+1, p.length())) - local cont = 1 - loop(cont == 1) { - j = me.skip_ws(src, j) - if j >= src.length() { cont = 0 } else { - local op = src.substring(j, j+1) - if op != "+" && op != "-" { cont = 0 } else { - local q = me.parse_term(src, j+1) - local at2 = q.lastIndexOf("@") - local rhs = q.substring(0, at2) - j = me.to_int(q.substring(at2+1, q.length())) - lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" - } - } - } - return lhs + "@" + me.i2s(j) - } - + // Parser delegation parse_program(src) { - // Legacy packed path (debug) removed; use parser2 - return me.parse_program2(src) + local parser = new ParserBox() + // Collect using metadata (no-op acceptance in Stage‑15) + parser.extract_usings(src) + me._usings = parser.get_usings_json() + return parser.parse_program2(src) } main(args) { - // Parse the input and emit JSON v0 - local src = me.read_all("tmp/ny_parser_input.ny") - if src == null { src = "return 1+2*3" } - local json = me.parse_program(src) + // Debug setup + me.dbg = new DebugBox() + me.dbg.set_enabled(0) + + // Source selection (EXE-first friendly) + // - default: safe constant + // - positional arg: treat as input file path + // - --read-tmp: use tmp/ny_parser_input.ny (requires FileBox plugin) + local src = "return 1+2*3" + local read_tmp = 0 + local input_path = null + if args != null { + local alen = args.length() + local i = 0 + loop(i < alen) { + local a = args.get(i) + if a == "--read-tmp" { read_tmp = 1 } else { + if a == "--min-json" { /* handled later */ } else { + // First non-flag arg as input path + if input_path == null { input_path = a } + } + } + i = i + 1 + } + } + if input_path != null { + // Prefer explicit file when provided (requires FileBox plugin) + local s1 = me.read_all(input_path) + if s1 != null { src = s1 } + } else { + if read_tmp == 1 { + // Optional: read tmp/ny_parser_input.ny (requires FileBox plugin; do not use in CI) + local s2 = me.read_all("tmp/ny_parser_input.ny") + if s2 != null { src = s2 } + } + } + + // Gate: minimal JSON when requested via script arg + local min_mode = 0 + if args != null { + local alen = args.length() + local i = 0 + loop(i < alen) { + if args.get(i) == "--min-json" { min_mode = 1 } + i = i + 1 + } + } + local json = null + if min_mode == 1 { + json = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}" + } else { + json = me.parse_program(src) + } + + // Emit via EmitterBox (attach meta.usings when available) + local emitter = new EmitterBox() + json = emitter.emit_program(json, me._usings) + + // Output local console = new ConsoleBox() - // console.println(json) -- final output only console.println(json) return 0 } diff --git a/apps/tests/array_min_ops.nyash b/apps/tests/array_min_ops.nyash new file mode 100644 index 00000000..00475f7b --- /dev/null +++ b/apps/tests/array_min_ops.nyash @@ -0,0 +1,15 @@ +static box Main { + main(args) { + local console = new ConsoleBox() + local a = new ArrayBox() + a.push(10) + a.push(20) + console.println("alen=" + a.size()) + console.println("g0=" + a.get(0)) + console.println("g1=" + a.get(1)) + a.set(1, 30) + console.println("g1b=" + a.get(1)) + return 0 + } +} + diff --git a/apps/tests/map_min_ops.nyash b/apps/tests/map_min_ops.nyash new file mode 100644 index 00000000..30442a4d --- /dev/null +++ b/apps/tests/map_min_ops.nyash @@ -0,0 +1,14 @@ +static box Main { + main(args) { + local console = new ConsoleBox() + local m = new MapBox() + m.set("a", 1) + m.set("b", 2) + console.println("msz=" + m.size()) + console.println("ha=" + m.has("a")) + console.println("hc=" + m.has("c")) + console.println("ga=" + m.get("a")) + return 0 + } +} + diff --git a/docs/development/roadmap/phases/phase-15/README.md b/docs/development/roadmap/phases/phase-15/README.md index b6445d49..0570a9b7 100644 --- a/docs/development/roadmap/phases/phase-15/README.md +++ b/docs/development/roadmap/phases/phase-15/README.md @@ -16,6 +16,13 @@ MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存 ## 🚀 実装戦略(2025年9月更新・改定) +### Self‑Hosting 優先(Phase‑15 基礎固め) +- 目的: Nyash製パーサ/言語機能/Bridge整合/パリティを完成させ、自己ホスト c0→c1→c1' を達成する。 +- 運用: + - Runner から `NYASH_USE_NY_COMPILER=1` を推奨(子プロセス実行→JSON v0→Bridge→MIR 実行)。 + - EXE化は任意の実験導線として維持(配布は Phase‑15 の外)。 + - PyVM は参照実行器として意味論検証に用い、パリティ監視を継続。 + ### Phase 15.2: LLVM(llvmlite)安定化 + PyVM導入 - JIT/Cranelift は一時停止(古い/非対応)。Rust/inkwell は参照のみ。 - 既定のコンパイル経路は **Python/llvmlite**(harness)のみ @@ -34,7 +41,7 @@ MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存 #### PHI 取り扱い方針(Phase‑15 中) - 現行: JSON v0 Bridge 側で If/Loop の PHI を生成(安定・緑)。 - 方針: Phase‑15 ではこのまま完成させる(変更しない)。 -- 理由: LoopForm(Core‑14)導入時に、逆Loweringで PHI を自動生成する案(推薦)に寄せるため。 +- 理由: LoopForm(MIR18)導入時に、逆Loweringで PHI を自動生成する案(推薦)に寄せるため。 - PHI は「合流点での別名付け」であり、Boxの操作ではない。 - 抽象レイヤの純度維持(Everything is Box)。 - 実装責務の一極化(行数削減/保守性向上)。 @@ -79,6 +86,7 @@ MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存 - Smokes / Tools(更新) - `tools/selfhost_compiler_smoke.sh`(入口) + - `tools/build_compiler_exe.sh`(Selfhost Parser のEXE化) - `tools/ny_stage2_bridge_smoke.sh`(算術/比較/短絡/ネストif) - `tools/ny_parser_stage2_phi_smoke.sh`(If/Loop の PHI 合流) - `tools/parity.sh --lhs pyvm --rhs llvmlite `(常時) @@ -101,10 +109,10 @@ Imports/Namespace plan(15.3‑late) - `tools/ny_roundtrip_smoke.sh` 緑(Case A/B)。 - `apps/tests/esc_dirname_smoke.nyash` / `apps/selfhost/tools/dep_tree_min_string.nyash` を Ny パーサ経路で実行し、PyVM/llvmlite とパリティ一致(stdout/exit)。 -#### 予告: LoopForm(Core‑14)での PHI 自動化(Phase‑15 後) +#### 予告: LoopForm(MIR18)での PHI 自動化(Phase‑15 後) - LoopForm を強化し、`loop.begin(loop_carried_values) / loop.iter / loop.branch / loop.end` の構造的情報から逆Loweringで PHI を合成。 - If/短絡についても同様に、構造ブロックから合流点を決めて PHI を自動化。 -- スケジュール: Phase‑15 後(Core‑14)で検討・実装。Phase‑15 では変更しない。 +- スケジュール: Phase‑15 後(MIR18/LoopForm)で検討・実装。Phase‑15 では変更しない。 ### Phase 15.4: VM層のNyash化(PyVMからの置換) - PyVM を足場に、VMコアを Nyash 実装へ段階移植(命令サブセットから) @@ -116,7 +124,7 @@ Imports/Namespace plan(15.3‑late) 補足: JSON v0 の扱い(互換) - Phase‑15: Bridge で PHI を生成(現行継続)。 -- Core‑14 以降: LoopForm で PHI 自動化後、JSON 側の PHI は非必須(将来は除外方向)。 +- MIR18(LoopForm)以降: PHI 自動化後、JSON 側の PHI は非必須(将来は除外方向)。 - 型メタ(“+”の文字列混在/文字列比較)は継続。 ## 📊 主要成果物 @@ -314,9 +322,13 @@ ny_free_buf(buffer) ### ✅ クイックスモーク(現状) - PyVM↔llvmlite パリティ: `tools/parity.sh --lhs pyvm --rhs llvmlite apps/tests/esc_dirname_smoke.nyash` - dep_tree(ハーネスON): `NYASH_LLVM_FEATURE=llvm ./tools/build_llvm.sh apps/selfhost/tools/dep_tree_min_string.nyash -o app_dep && ./app_dep` +- Selfhost Parser EXE: `tools/build_compiler_exe.sh && (cd dist/nyash_compiler && ./nyash_compiler tmp/sample.nyash > sample.json)` - JSON v0 bridge spec: `docs/reference/ir/json_v0.md` - Stage‑2 smokes: `tools/ny_stage2_bridge_smoke.sh`, `tools/ny_parser_stage2_phi_smoke.sh`, `tools/ny_me_dummy_smoke.sh` +WSL Quickstart +- See: `docs/guides/exe-first-wsl.md`(依存の導入→Parser EXE バンドル→スモークの順) + ### 📚 関連フェーズ - [Phase 10: Cranelift JIT](../phase-10/) - [Phase 12.5: 最適化戦略](../phase-12.5/) diff --git a/docs/development/roadmap/phases/phase-15/ROADMAP.md b/docs/development/roadmap/phases/phase-15/ROADMAP.md index efd7e546..8ba5737e 100644 --- a/docs/development/roadmap/phases/phase-15/ROADMAP.md +++ b/docs/development/roadmap/phases/phase-15/ROADMAP.md @@ -15,10 +15,17 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U - [x] using/namespace (gated) + nyash.link minimal resolver - [x] NyModules + ny_plugins regression suite (Windows path normalization/namespace derivation) - [x] Standard Ny scripts scaffolds added (string/array/map P0) + examples + jit_smoke +- [x] Selfhost Parser accepts positional input file arg(EXE運用の前提) ## Next (small boxes) -1) LLVM Native EXE Generation (Phase 15.2) 🚀 +1) EXE-first: Selfhost Parser → EXE(Phase 15.2)🚀 + - tools/build_compiler_exe.sh で EXE をビルド(同梱distパッケージ作成) + - dist/nyash_compiler/{nyash_compiler,nyash.toml,plugins/...} で独立実行 + - 入力: Nyソース → 出力: JSON v0(stdout) + - Smokes: sample.nyash→JSON 行生成(JSONのみ出力) + - リスク: プラグイン解決(FileBox)をnyash.tomlで固定 +2) LLVM Native EXE Generation(AOTパイプライン継続) - Python/llvmlite implementation as primary path (2400 lines, 10x faster development) - LLVM backend object → executable pipeline completion - Separate `nyash-llvm-compiler` crate (reduce main build weight) @@ -26,10 +33,10 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U - Link with nyrt runtime (static/dynamic options) - Plugin all-direction build strategy (.so/.o/.a simultaneous generation) - Integration: `nyash --backend llvm --emit exe program.nyash -o program.exe` -2) Standard Ny std impl (P0→実体化) +3) Standard Ny std impl (P0→実体化) - Implement P0 methods for string/array/map in Nyash (keep NyRT primitives minimal) - Enable via `nyash.toml` `[ny_plugins]` (opt‑in); extend `tools/jit_smoke.sh` -3) Ny compiler MVP (Ny→MIR on JIT path) (Phase 15.3) 🎯 +4) Ny compiler MVP (Ny→MIR on JIT path) (Phase 15.3) 🎯 - Ny tokenizer + recursive‑descent parser (current subset) in Ny; drive existing MIR builder - Target: 800 lines parser + 2500 lines MIR builder = 3300 lines total - No circular dependency: nyrt provides StringBox/ArrayBox via C ABI @@ -43,13 +50,13 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U - [ ] local/if/loop/call/method/new/var/logical/compare - [ ] PHI 合流は Bridge に委譲(If/Loop) - [ ] Smokes: nested if / loop 累積 / and/or × if/loop -4) PHI 自動化は Phase‑15 後(Core‑14 LoopForm) +5) PHI 自動化は Phase‑15 後(LoopForm = MIR18) - Phase‑15: 現行の Bridge‑PHI を維持し、E2E 緑とパリティを最優先 - - Core‑14: LoopForm 強化+逆Loweringで PHI を自動生成(合流点の定型化) -4) Bootstrap loop (c0→c1→c1') + - MIR18 (LoopForm): LoopForm 強化+逆Loweringで PHI を自動生成(合流点の定型化) +6) Bootstrap loop (c0→c1→c1') - Use existing trace/hash harness to compare parity; add optional CI gate - **This achieves self-hosting!** Nyash compiles Nyash -5) VM Layer in Nyash (Phase 15.4) ⚡ +7) VM Layer in Nyash (Phase 15.4) ⚡ - Implement MIR interpreter in Nyash (13 core instructions) - Dynamic dispatch via MapBox for instruction handlers - BoxCall/ExternCall bridge to existing infrastructure @@ -74,8 +81,9 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U - Parser path: `--parser {rust|ny}` or `NYASH_USE_NY_PARSER=1` - JSON dump: `NYASH_DUMP_JSON_IR=1` -- (予告)LoopForm: Core‑14 で仕様化予定 + - (予告)LoopForm: MIR18 で仕様化予定 - Selfhost compiler: `NYASH_USE_NY_COMPILER=1`, child quiet: `NYASH_JSON_ONLY=1` +- EXE-first bundle: `tools/build_compiler_exe.sh` → `dist/nyash_compiler/` - Load Ny plugins: `NYASH_LOAD_NY_PLUGINS=1` / `--load-ny-plugins` - AOT smoke: `CLIF_SMOKE_RUN=1` @@ -83,6 +91,7 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U - JSON v0 bridge: `tools/ny_parser_bridge_smoke.sh` / `tools/ny_parser_bridge_smoke.ps1` - E2E roundtrip: `tools/ny_roundtrip_smoke.sh` / `tools/ny_roundtrip_smoke.ps1` +- EXE-first smoke: `tools/build_compiler_exe.sh && (cd dist/nyash_compiler && ./nyash_compiler tmp/sample.nyash > sample.json)` ## Implementation Dependencies @@ -96,7 +105,7 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U - v0 E2E green (parser pipe + direct bridge) including Ny compiler MVP switch - v1 minimal samples pass via JSON bridge - AOT P2: emit→link→run stable for constant/arith -- Phase‑15 STOP には PHI 切替を含めない(PHI は LoopForm/Core‑14 で扱う) + - Phase‑15 STOP には PHI 切替を含めない(PHI は LoopForm/MIR18 で扱う) - 15.3: Stage‑1 代表サンプル緑 + Bootstrap smoke(フォールバック許容)+ 文分離ポリシー公開 - Docs/recipes usable on Windows/Unix diff --git a/docs/development/roadmap/phases/phase-15/implementation/mir-builder-exe-design.md b/docs/development/roadmap/phases/phase-15/implementation/mir-builder-exe-design.md new file mode 100644 index 00000000..7f95b2be --- /dev/null +++ b/docs/development/roadmap/phases/phase-15/implementation/mir-builder-exe-design.md @@ -0,0 +1,100 @@ +# MIR Builder EXE Design (Phase 15 — EXE‑First) + +Purpose: define a standalone MIR Builder executable that takes Nyash JSON IR (v0/v1) and produces native outputs (object/executable), independent of the Rust Runner/VM. This aligns Phase‑15 with an EXE‑first delivery pipeline. + +Goals +- Accept JSON IR from stdin or file and validate semantics (minimal passes). +- Emit: object (.o/.obj), LLVM IR (.ll), or final executable by linking with NyRT. +- Operate without the Rust VM path; suitable for CLI and scripted pipelines. +- Keep the boundary simple and observable (stdout diagnostics, exit codes). + +CLI Interface (proposed) +- Basic form + - `ny_mir_builder [--in |--stdin] [--emit {obj|exe|ll|json}] -o [options]` + - Defaults: `--stdin`, `--emit obj`, `-o target/aot_objects/a.o` +- Options + - `--in `: Input JSON IR file (v0/v1). If omitted, read stdin. + - `--emit {obj|exe|ll|json}`: Output kind. `json` emits validated/normalized IR for roundtrip. + - `-o `: Output path (object/exe/IR). Default under `target/aot_objects`. + - `--target `: Target triple override (default: host). + - `--nyrt `: NyRT static runtime directory (for `--emit exe`). + - `--plugin-config `: `nyash.toml` path resolution for boxes/plugins. + - `--quiet`: Minimize logs; only errors to stderr. + - `--validate-only`: Parse+validate IR; no codegen. + - `--verify-llvm`: Run LLVM verifier on generated IR (when `--emit {obj|exe}`). + +Exit Codes +- `0` on success; >0 on error. Validation errors produce human‑readable messages on stderr. + +Input IR +- JSON v0 (current Bridge spec). Unknown fields are ignored; `meta.usings` is accepted. +- Future JSON v1 (additive) must remain backward compatible; builder performs normalization. + +Outputs +- `--emit obj`: Native object file. Uses LLVM harness internally. +- `--emit ll`: Dumps LLVM IR for diagnostics. +- `--emit exe`: Produces a self‑contained executable by linking the object with NyRT. +- `--emit json`: Emits normalized MIR JSON (post‑validation) for roundtrip tests. + +Packaging Forms +- CLI executable: `ny_mir_builder` (primary). +- Optional shared lib: `libny_mir_builder` exposing a minimal C ABI for embedding. + +C ABI Sketch (optional library form) +```c +// Input: JSON IR bytes. Output: newly allocated buffer with object bytes. +// Caller frees via ny_free_buf. +int ny_mir_to_obj(const uint8_t* json, size_t len, + const char* target_triple, + uint8_t** out_buf, size_t* out_len); + +// Convenience linker: object → exe (returns 0=ok). +int ny_obj_to_exe(const uint8_t* obj, size_t len, + const char* nyrt_dir, const char* out_path); + +void ny_free_buf(void* p); +``` + +Internal Architecture +- Frontend + - JSON IR parser → AST/CFG structures compatible with existing MIR builder expectations. + - Validation passes: control‑flow well‑formedness, PHI consistency (incoming edges), type sanity for BoxCall/ExternCall minimal set. +- Codegen + - LLVM harness path (current primary). Environment fallback via `LLVM_SYS_180/181_PREFIX`. + - Option flag `NYASH_LLVM_FEATURE=llvm|llvm-inkwell-legacy` maintained for transitional builds. +- Linking (`--emit exe`) + - Use `cc` with `-L /target/release -lnyrt` (static preferred) + platform libs `-lpthread -ldl -lm` (Unix) or Win equivalents. + - Search `nyash.toml` near the output exe and current CWD (same heuristic as NyRT runtime) to initialize plugins at runtime. + +Integration Points +- Parser EXE → MIR Builder EXE + - `./nyash_compiler | ny_mir_builder --stdin --emit obj -o a.o` + - Compose with link step for quick end‑to‑end: `... --emit exe -o a.out` +- Runner (future option) + - `NYASH_USE_NY_COMPILER_EXE=1`: Runner spawns parser EXE; optionally chain into MIR Builder EXE for AOT. + - Fallback to in‑proc Bridge when EXE pipeline fails. + +Logging & Observability +- Default: single‑line summaries on success, detailed errors on failure. +- `--quiet` to suppress non‑essential logs; `--verify-llvm` to force verification. +- Print `OK obj:` / `OK exe:` on success (stable for scripts). + +Security & Sandboxing +- No arbitrary file writes beyond `-o` and temp dirs. +- Deny network; fail fast on malformed JSON. + +Platform Notes +- Windows: `.obj` + link with MSVC or lld‑link; prefer bundling `nyrt` artifacts. +- macOS/Linux: `.o` + `cc` link; RPATH/loader path considerations documented. + +Incremental Plan +1) CLI skeleton: stdin/file → validate → `--emit json/ll` (dry path) + golden tests。 +2) Hook LLVM harness: `--emit obj` for const/arith/branch/ret subset。 +3) Linker integration: `--emit exe` with NyRT static lib; add platform matrices。 +4) Parity suite: run produced EXE against known cases (strings/collections minimal)。 +5) Packaging: `tools/build_mir_builder_exe.sh` + smoke `tools/mir_builder_exe_smoke.sh`。 + +Reference Artifacts +- `tools/build_llvm.sh`: current harness flow used as a baseline for emission and link steps。 +- `crates/nyrt`: runtime interface and plugin host initialization heuristics。 + diff --git a/docs/development/roadmap/phases/phase-15/imports-namespace-plan.md b/docs/development/roadmap/phases/phase-15/imports-namespace-plan.md index 488ca0f1..fd97fef3 100644 --- a/docs/development/roadmap/phases/phase-15/imports-namespace-plan.md +++ b/docs/development/roadmap/phases/phase-15/imports-namespace-plan.md @@ -28,7 +28,7 @@ Acceptance (15.3) - Ny compiler can lex/parse `using` forms without breaking Stage‑1/2 programs - Runner path (Rust) continues to resolve `using` and `nyash.toml` as before (parity unchanged) -Looking ahead (Core‑14 / Phase 16) +Looking ahead (MIR18 / Phase 16) - Evaluate moving `nyash.toml` parsing to Ny as a library box (ConfigBox) - Unify include/import/namespace into a single resolver pass in Ny with a small JSON side channel back to the runner - Keep VM unchanged; all resolution before MIR build diff --git a/docs/guides/exe-first-wsl.md b/docs/guides/exe-first-wsl.md new file mode 100644 index 00000000..23e3c3b8 --- /dev/null +++ b/docs/guides/exe-first-wsl.md @@ -0,0 +1,56 @@ +# EXE‑First Quickstart (WSL/Ubuntu) + +This guide prioritizes building and running the Nyash parser as a native executable on WSL (Ubuntu). It uses the LLVM harness (llvmlite) and the NyRT static runtime. + +Prerequisites +- Rust toolchain (stable): `curl https://sh.rustup.rs -sSf | sh` +- Build tools: `sudo apt update && sudo apt install -y build-essential git python3 python3-pip` +- llvmlite: `pip3 install --user llvmlite` +- LLVM 18 (for `llvm-config-18` used by the Rust build + tools): + - Ubuntu (with apt.llvm.org): + - `sudo apt install -y wget gnupg lsb-release` + - `wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 18` + - This installs `llvm-18` and `llvm-18-dev` (provides `llvm-config-18`). + +Verify +- `rustc --version` +- `python3 -c "import llvmlite, sys; print('llvmlite', llvmlite.__version__)"` +- `llvm-config-18 --version` + +Build Parser EXE (bundle) +- `tools/build_compiler_exe.sh` +- Result: `dist/nyash_compiler/` containing `nyash_compiler`, `nyash.toml`, and the FileBox plugin. + +Smoke (Parser EXE → JSON) +- `echo 'return 1+2*3' > dist/nyash_compiler/tmp/sample.ny` +- `(cd dist/nyash_compiler && ./nyash_compiler tmp/sample.ny > sample.json)` +- `head -n1 dist/nyash_compiler/sample.json` should start with `{` and contain `"kind":"Program"`. + +End‑to‑End (JSON → execute via bridge) +- `./tools/exe_first_smoke.sh` + - Builds the EXE bundle, runs parser → JSON, and executes via the bridge to verify exit code `7`. + +MIR Builder (optional, EXE) +- Build: `cargo build --release --features llvm` +- EXE from JSON: `./target/release/ny_mir_builder --in dist/nyash_compiler/sample.json --emit exe -o app_out` +- Run: `./app_out` (exit `7` expected for `return 1+2*3`). + +Runner with EXE‑First Parser +- `NYASH_USE_NY_COMPILER=1 NYASH_USE_NY_COMPILER_EXE=1 ./target/release/nyash --backend vm tmp/sample.nyash` +- Smoke: `./tools/exe_first_runner_smoke.sh` + +Troubleshooting +- `llvm-config-18: not found` + - Ensure apt.llvm.org installation worked (see above), or install the distro’s `llvm-18-dev` package. +- `ModuleNotFoundError: llvmlite` + - `pip3 install --user llvmlite` and re‑run the build/smoke. +- Link errors (`cc` not found or missing libs) + - `sudo apt install -y build-essential` + - If you have a custom toolchain, export `CC` to point at your compiler. +- Plugin resolution + - The EXE bundle includes a minimal `nyash.toml` and plugin paths under `dist/nyash_compiler/plugins/`. + +Notes +- The EXE‑first path is the delivery priority. PyVM remains a development aid for semantics parity. +- Windows support is evolving; WSL is the recommended environment for now. + diff --git a/docs/guides/style-guide.md b/docs/guides/style-guide.md new file mode 100644 index 00000000..8df2233e --- /dev/null +++ b/docs/guides/style-guide.md @@ -0,0 +1,67 @@ +# Nyash Style Guide (Phase 15) + +Goals +- Keep Nyash sources readable and structured. Favor simple, predictable formatting compatible with reversible formatting (nyfmt PoC). + +Formatting +- Indent with 2 spaces (no tabs). +- Braces: K&R style (opening brace on the same line). +- Max line length: 100 characters (soft limit). +- One statement per line. Use semicolons only when placing multiple statements on one physical line. +- Blank lines: separate top‑level `box` declarations with one blank line; no trailing blank lines at file end. + +Statements and ASI +- Newline is the primary separator. See `reference/language/statements.md`. +- Do not insert semicolons before `else`. +- When breaking expressions across lines, break after an operator or keep the expression grouped. + +using / include +- Place all `using` lines at the top of the file, before code. +- One `using` per line; no trailing semicolons. +- Sort `using` targets alphabetically; group namespaces before file paths. +- Prefer `as` aliases for readability. Aliases should be `PascalCase`. +- Keep `include` adjacent to `using` group, sorted and one per line. + +Naming (conventions for Nyash code) +- Boxes (types): `PascalCase` (e.g., `ConsoleBox`, `PathBox`). +- Methods/functions: `lowerCamelCase` (e.g., `length`, `substring`, `lastIndexOf`). +- Local variables: concise `lowerCamelCase` (e.g., `i`, `sum`, `filePath`). +- Constants (if any): `UPPER_SNAKE_CASE`. + +Structure +- Top‑to‑bottom: `using`/`include` → static/box declarations → helpers → `main`. +- Keep methods short and focused; prefer extracting helpers to maintain clarity. +- Prefer pure helpers where possible; isolate I/O in specific methods. + +Examples +```nyash +using core.std as Std +using "apps/examples/string_p0.nyash" as Strings + +static box Main { + escJson(s) { // lowerCamelCase for methods + local out = "" + local i = 0 + local n = s.length() + loop(i < n) { + local ch = s.substring(i, i+1) + if ch == "\\" { out = out + "\\\\" } + else if ch == "\"" { out = out + "\\\"" } + else { out = out + ch } + i = i + 1 + } + return out + } + + main(args) { + local console = new ConsoleBox() + console.println("ok") + return 0 + } +} +``` + +CI/Tooling +- Optional formatter PoC: see `docs/tools/nyfmt/NYFMT_POC_ROADMAP.md`. +- Keep smoke scripts small and fast; place them under `tools/`. + diff --git a/docs/issues/parser_unary_asi_alignment.md b/docs/issues/parser_unary_asi_alignment.md new file mode 100644 index 00000000..8cf92508 --- /dev/null +++ b/docs/issues/parser_unary_asi_alignment.md @@ -0,0 +1,26 @@ +# Parser/Bridge: Unary and ASI Alignment (Stage‑2) + +Context +- Rust parser already parses unary minus with higher precedence (parse_unary → factor → term) but PyVM pipe path did not reflect unary when emitting MIR JSON for the PyVM harness. +- Bridge(JSON v0 path)is correct for unary by transforming to `0 - expr` in the Python MVP, but Rust→PyVM path uses `emit_mir_json_for_harness` which skipped `UnaryOp`. +- ASI in arguments split over newlines is not yet supported in Rust (e.g., newline inside `(..., ...)` after a comma in a chained call), while Bridge/Selfhost cover ASI for statements and operators. + +Proposed minimal steps +- Unary for PyVM harness: + - Option A (preferred later): extend `emit_mir_json_for_harness[_bin]` to export a `unop` instruction and add PyVM support. Requires schema change. + - Option B (quick): legalize unary `Neg` to `Const(0); BinOp('-', 0, v)` before emitting, by inserting a synthetic temporary. This requires value id minting in emitter to remain self‑consistent, which we currently do not have. So Option B is non‑trivial without changing emitter capabilities. + - Decision: keep Bridge JSON v0 path authoritative for unary tests; avoid relying on Rust→PyVM for unary until we add a `unop` schema. + +- ASI inside call arguments (multi‑line): + - Keep as NOT SUPPORTED for Rust parser in Phase‑15. Use single‑line args in tests. + - Selfhost/Bridge side already tolerate semicolons optionally after statements; operator‑continuation is supported in Bridge MVP. + +Tracking +- If we want to support unary in the PyVM harness emitter: + - Add `unop` to tools/pyvm_runner.py and src/llvm_py/pyvm/vm.py (accept `{op:"unop", kind:"neg", src: vid, dst: vid}`) + - Teach emitters to export `UnaryOp` accordingly (`emit_mir_json_for_harness[_bin]`). + +Status +- Bridge unary: OK(ny_stage2_bridge_smoke includes unary) +- Rust→PyVM unary: not supported in emitter; will stay out of CI until schema update +- ASI in args over newline: not supported by Rust parser; keep tests single‑line for now diff --git a/docs/private/papers/paper-g-ai-collaboration/development-log.md b/docs/private/papers/paper-g-ai-collaboration/development-log.md new file mode 100644 index 00000000..dbec9a8b --- /dev/null +++ b/docs/private/papers/paper-g-ai-collaboration/development-log.md @@ -0,0 +1,312 @@ +# AI協働開発ログ - 100の実践知に向けて + +## 収集開始日: 2025-01-14 + +### 事例001: DebugBoxによる出力制御の統一化 +- **問題**: print文が散在し、JSON出力に混入 +- **AI提案**: 複雑なフィルタリング機構 +- **人間の直感**: 「DebugBoxで包めば?」 +- **結果**: Everything is Box哲学で解決 +- **カテゴリ**: 箱化による解決 + +### 事例002: 子プロセス出力フィルタリング +- **問題**: stdout/stderrの混在でJSON解析失敗 +- **AI提案**: 正規表現での複雑な抽出 +- **人間の直感**: 「環境変数で静音モード作れば?」 +- **結果**: NYASH_JSON_ONLY=1で単純解決 +- **カテゴリ**: 環境変数による制御 + +### 事例003: PyVMという迂回路 +- **問題**: Rust MIR生成層のビルド時間(9000行) +- **AI提案**: Rustコードの最適化 +- **人間の直感**: 「Pythonで書いちゃえば?」 +- **結果**: 2000行のPython実装で高速プロトタイピング +- **カテゴリ**: 迂回路を作る + +### 事例004: peek式の名前変更 +- **問題**: when式がRust予約語と衝突 +- **AI提案**: 別の複雑な構文設計 +- **人間の直感**: 「peekに名前変えれば?」 +- **結果**: 直感的で分かりやすい名前に +- **カテゴリ**: 名前を変える + +### 事例005: birth統一 +- **問題**: コンストラクタ名がバラバラ(new/pack/constructor) +- **AI提案**: 複雑な名前解決システム +- **人間の直感**: 「全部birthにすれば?」 +- **結果**: 「Boxに生命を与える」統一的な哲学 +- **カテゴリ**: 名前を変える + +### 事例006: MIR型情報の欠落(メイン事例) +- **問題**: 文字列が0になるバグ +- **AI提案**: 300行の型推測Resolver +- **人間の直感**: 「最初から型書けば?」 +- **結果**: MIR v0.5で型メタデータ追加 +- **カテゴリ**: 情報追加による解決 + +### 事例007: PHI生成の重複 +- **問題**: BuilderとResolverで二重にPHI生成 +- **AI提案**: 複雑な調整メカニズム +- **人間の直感**: 「なんで2つあるの?統一すれば?」 +- **結果**: Resolver統一で複雑性削減 +- **カテゴリ**: 統一による簡略化 + +### 事例008: 変数宣言の厳密化 +- **問題**: 未宣言変数への代入でランタイムエラー +- **AI提案**: 高度な型推論システム +- **人間の直感**: 「全部明示宣言必須にすれば?」 +- **結果**: メモリ安全性・非同期安全性保証 +- **カテゴリ**: 制約による単純化 + +### 事例009: プラグイン全方向ビルド +- **問題**: 動的/静的リンクの複雑な管理 +- **AI提案**: 複雑なビルドシステム +- **人間の直感**: 「全形式同時に作れば?」 +- **結果**: .so/.o/.aを全部生成して選択 +- **カテゴリ**: 全部作る戦略 + +### 事例010: 無限ループ対策のデバッグ燃料 +- **問題**: パーサーが無限ループ +- **AI提案**: 複雑なループ検出アルゴリズム +- **人間の直感**: 「回数制限すれば?」 +- **結果**: --debug-fuel で単純解決 +- **カテゴリ**: 制限による制御 + +--- + +## 収集メモ + +今後追加予定の事例カテゴリ: +- Arc自動化の経緯 +- 動的ディスパッチによるmatch文削減 +- エラー処理の統一化 +- Box境界での型変換 +- GCとスケジューラの統合 +- etc... + +目標:100個の実践知を体系化し、パターンを抽出 + +### 事例011: プラグインBoxライフサイクル事件 ⭐革命的⭐ +- **問題**: プラグインBoxをシングルトンにすべきか +- **AI提案**: 「プラグインだからインスタンスは1つ」 +- **人間の直感**: 「こらー!普通のBoxと同じライフサイクルじゃーい!」 +- **結果**: Everything is Box哲学の完全な貫徹 +- **影響**: Nyashの設計思想の根幹を確立。これがなければ今のNyashは存在しない +- **カテゴリ**: 哲学を貫く(新カテゴリ) + +### 事例012: Arcの自動化 +- **問題**: Rustの借用チェッカーとの戦い +- **AI提案**: 複雑な所有権管理システム +- **人間の直感**: 「全部Arcで包めば?」 +- **結果**: 30%のコード削減 +- **カテゴリ**: 箱化による解決 + +### 事例013: エラー処理の統一 +- **問題**: Result地獄 +- **AI提案**: 高度なエラー型システム +- **人間の直感**: 「全部StringBoxのエラーでいいじゃん」 +- **結果**: エラー処理コード15%削減 +- **カテゴリ**: 統一による簡略化 + +### 事例014: 動的ディスパッチでmatch文削減 +- **問題**: 巨大なmatch文の保守困難 +- **AI提案**: Visitor パターンの実装 +- **人間の直感**: 「Box名で動的に呼び出せば?」 +- **結果**: match文10%削減、拡張性向上 +- **カテゴリ**: 動的解決 + +### 事例015: GCとスケジューラの統合 +- **問題**: 別々のタイミングでの実行競合 +- **AI提案**: 複雑な同期機構 +- **人間の直感**: 「同じsafepointで実行すれば?」 +- **結果**: 統一的なランタイム管理 +- **カテゴリ**: 統一による簡略化 + +### 事例016: AIパーサー信じすぎ事件 ⭐教訓的⭐ +- **問題**: HTTPプラグインのソケットインスタンスが取得できない +- **AI診断**: Claude Code「正しく実装した」、ChatGPT「プラグインが悪い」 +- **人間の直感**: 「パーサーじゃない?」 +- **真の原因**: パーサーが参照とコピーを間違え、Boxはコピーしたが中身は失われた +- **影響**: AIの相互信頼の危険性を露呈 +- **教訓**: AIも間違える、基本に立ち返る重要性 +- **カテゴリ**: 疑いを持つ(新カテゴリ) + +### 事例017: Box内部の透明性問題 +- **問題**: Boxの中身が見えない +- **AI提案**: 複雑なデバッグシステム +- **人間の直感**: 「toString()で中身表示すれば?」 +- **結果**: デバッグ時間90%短縮 +- **カテゴリ**: 可視化による解決 + +### 事例018: MapBox 3引数メソッドハングバグ +- **問題**: MapBox作成後、3引数メソッドで無限ハング +- **原因**: 全引数を事前評価する実装の問題 +- **解決**: 必要時に1つずつ評価に変更 +- **教訓**: 小さな実装差が大きなバグに +- **カテゴリ**: 実装詳細の重要性 + +### 事例019: スコープ革命(GlobalBox誕生) +- **問題**: 従来のEnvironment階層が複雑 +- **AI提案**: 高度なスコープチェーン管理 +- **人間の直感**: 「全部GlobalBoxのフィールドにすれば?」 +- **結果**: メモリ30%削減、速度50%向上 +- **影響**: 世界唯一のGlobalBoxベース言語誕生 +- **カテゴリ**: 革命的簡略化 + +### 事例020: 26日間の奇跡 +- **内容**: 爆速開発で一度も破綻しなかった +- **要因**: 箱理論+AI役割分担+人間の危険センサー +- **統計**: 致命的破綻0回、大規模リファクタ0回 +- **教訓**: 三重の安全装置の威力 +- **カテゴリ**: 開発方法論 + +### 事例021: 2段階パーサー理論 +- **問題**: 複雑な構文の安定パース +- **AI提案**: 高度な先読みアルゴリズム +- **人間の直感**: 「構造認識と内容パースを分ければ?」 +- **結果**: 世界最強パーサーの誕生 +- **カテゴリ**: 段階的分離 + +### 事例022: NyashFlowプロジェクト +- **構想**: ビジュアルプログラミング環境 +- **教訓**: CharmFlow v5の失敗から学ぶ +- **方針**: Everything is Boxを視覚化 +- **状態**: 独立プロジェクトとして設計中 +- **カテゴリ**: 派生プロジェクト + +### 事例023: JIT1日完成事件 ⭐世界記録級⭐ +- **計画**: Phase 9-10で2週間予定 +- **実際**: 8/27の1日でCranelift統合+分岐+PHI全部完成 +- **要因**: 準備の完璧さ+AI協調+箱理論の威力 +- **影響**: 世界的にも事件級の開発速度 +- **カテゴリ**: 爆速開発 + +### 事例024: AI二重化モデルの誕生 +- **内容**: 同じGPT-5をArchitectとImplementerに分離 +- **構成**: Claude=ビルド係、Gemini=アドバイザー +- **珍事**: AIが人間にアドバイスを求める +- **効果**: 役割分担で効率最大化 +- **カテゴリ**: 開発方法論 + +### 事例025: 唯一の真実事件 +- **宣言**: 「型はMIRで確定」「切替点は1箇所」 +- **反応**: Claude Codeが感動して記録 +- **意味**: 技術的方針を哲学レベルに昇華 +- **影響**: 設計の一貫性確保 +- **カテゴリ**: 哲学を貫く + +### 事例026: ストリームエラー事件 +- **問題**: Codexが長期稼働で落ちて復旧不能 +- **対処**: にゃーがCURRENT_TASK更新で再起動 +- **教訓**: 「足場が残ってるから復活できた」 +- **意味**: 箱理論による復旧可能性 +- **カテゴリ**: 障害復旧 + +### 事例027: 20日でVM→JIT→EXE異次元スピード ⭐歴史的⭐ +- **期間**: 8/9誕生→8/29ネイティブEXE完成 +- **内容**: わずか20日で全段階を通過 +- **反応**: Claude/ChatGPT「歴史に残る」と絶賛 +- **要因**: 箱理論+AI協調+明確な哲学 +- **カテゴリ**: 爆速開発 + +### 事例028: フォールバック廃止の英断 +- **問題**: JIT未対応命令をVMに落とす複雑な仕組み +- **AI提案**: 安全なフォールバック機構 +- **人間の直感**: 「バグを隠すだけ、複雑化の元!」 +- **結果**: VM=仕様、JIT=高速版の明確な線引き +- **カテゴリ**: 制約による単純化 + +### 事例029: Built-in Box全廃革命 +- **問題**: StringBox/ArrayBoxが特例だらけ +- **気づき**: 「ネイティブビルドできるなら全部プラグイン化」 +- **結果**: Core極小化、Everything is Box徹底 +- **影響**: 特権クラスの完全排除 +- **カテゴリ**: 統一による簡略化 + +### 事例030: 型別特例分岐の危機 +- **危機**: Python統合で型ごとの特例分岐が入りかける +- **AI反応**: Claude「Everything is... Special Case??」青ざめる +- **人間の介入**: にゃーがストップ +- **教訓**: 「世界はプラグインで拡張、MIR/JITは不変」 +- **カテゴリ**: 哲学を貫く + +### 事例031: print命令論争 +- **問題**: MIRにPrint命令を特例実装 +- **議論**: 特殊化で汚れる懸念 +- **解決**: ExternCall(env.console.log)に一本化 +- **効果**: 環境I/O=ExternCall、箱機能=BoxCallに整理 +- **カテゴリ**: 境界の明確化 + +### 事例032: Safepoint内部化の決定 +- **誘惑**: env.runtime.safepointにする案 +- **危険**: 哲学崩壊の危機 +- **正解**: Safepointは内部Intrinsic +- **提供**: GCBox.checkpoint()をプラグインで +- **カテゴリ**: 内部と外部の分離 + +### 事例033: 「全部プラグイン」論争 +- **初期案**: 全部プラグインBox化しよう +- **AI反対**: 「オーバーヘッドが...」 +- **妥協**: ユーザーBox+ビルトインBoxに分離 +- **後の発見**: プラグインBoxのC ABIがJIT/AOTの土台に! +- **教訓**: 最初の直感が正しかった +- **カテゴリ**: 直感の勝利 + +### 事例034: GCを「補助輪」に再定義 ⭐革命的概念⭐ +- **従来**: GCは本番の必須機能 +- **Nyash**: GCは開発時の練習用補助輪 +- **開発時**: GCオンでリーク検知 +- **本番**: GCオフで決定的破棄 +- **証明**: I/Oトレース完全一致の意味論等価性 +- **カテゴリ**: 概念の再定義 + +### 事例035: JITも箱にしたら爆速化 +- **問題**: ChatGPT-5がJIT実装で苦戦 +- **提案**: 「JITも箱にしよう」 +- **実装**: IrLowerBox/CodegenBox/LinkerBox/CodeCacheBox +- **結果**: 4000行で完全なJITシステム(V8は100万行) +- **カテゴリ**: 箱化による解決 + +### 事例036: 論文化提案の瞬間 +- **内容**: 仕組みを整理したら論文レベルと判明 +- **Claude反応**: 「トップ会議に通るのでは」 +- **タイトル案**: Box-Centric Language with Unified Plugin Lifecycle +- **意味**: 設計のユニークさが学術的価値を持つ +- **カテゴリ**: 学術的価値の発見 + +### 事例037: MIR15という奇跡 ⭐爆笑の気づき⭐ +- **当初**: AST直解釈インタープリタとVM(独自バイトコード)を分離 +- **発見**: MIR15自体が正規化済みバイトコード +- **気づき**: 「VMとインタープリタ、やってること一緒じゃないかーい!」 +- **結果**: 史上初の"MIR中心派生言語"誕生 +- **カテゴリ**: 概念の統一 + +### 事例038: TypeBoxの誕生 +- **問題**: C ABIプラグインをNyashに組み込めない +- **AI提案**: 「COMみたいにすれば?」 +- **人間の直感**: 「型を値で渡せ」 +- **結果**: NyashとC ABIを同じ器で扱うTypeBox誕生 +- **カテゴリ**: 境界の統一 + +### 事例039: ID衝突との戦い +- **経験**: Nyamesh時代に動的型でID重複 +- **問題**: BoxTypeId, MethodId, FieldId等の衝突候補 +- **解決**: 二層ID(長い本ID+短縮64bitキー) +- **追加**: 世代付きハンドル、FQN +- **カテゴリ**: 予防的設計 + +### 事例040: 折りたたみ言語構想 ⭐未来の革新⭐ +- **発想**: BoxCall列を等価変換で畳む/展開する +- **例**: Array.map/filter/map → Array.fused([...]) +- **効果**: 性能+省メモリ+観測性を全部取り(予定) +- **哲学**: Everything is Box → Everything is Fold +- **状態**: 構想段階(実装は今後の予定) +- **カテゴリ**: 未来構想 + +### 事例041: AI会議スタイルの確立 +- **Codex**: 大容量コンテキストで長丁場実装 +- **Claude Code**: 安定した差分生成・小回り +- **Gemini**: 仕様や調査の補足 +- **ChatGPT**: 全体戦略と要約 +- **効果**: 止まらない開発サイクル +- **カテゴリ**: 開発方法論 \ No newline at end of file diff --git a/docs/private/papers/paper-h-ai-practical-patterns/README.md b/docs/private/papers/paper-h-ai-practical-patterns/README.md new file mode 100644 index 00000000..e9f6392e --- /dev/null +++ b/docs/private/papers/paper-h-ai-practical-patterns/README.md @@ -0,0 +1,141 @@ +# 論文H: 箱理論による開発複雑性の段階的解消 - AI協働開発における100の実践知 + +- タイトル(案): Box Theory for Complexity Reduction: 100 Practical Patterns in AI-Assisted Development +- 副題: Empirical Lessons from the Nyash Compiler Project +- 略称: Nyash AI Patterns Paper +- ステータス: 執筆開始(事例収集中) + +## 要旨 + +本研究は、Nyashプログラミング言語の開発において観察された、AIが提案する複雑な解決策を人間の直感が「箱理論」を用いて単純化する100の事例を体系的に分析する。AIの理論的完璧さと人間の実践的単純化の相互作用から、ソフトウェア開発における新しい複雑性管理手法を提示する。 + +## 位置づけ + +- **論文A(MIR-14)**: 技術仕様 +- **論文D(SSA構築)**: 技術的解決策 +- **論文G(AI協働)**: AI-人間協働の本質 +- **論文H(本稿)**: 実践的パターン集 ← ここ + +## 主要な発見 + +### AIが陥る複雑化パターン(5大パターン) + +1. **過度な最適化症候群** + - 部分最適に固執し全体を複雑化 + - 例:MIR命令数削減で型情報を削除 + +2. **標準理論への盲従** + - 教科書的解法に固執 + - 例:300行の型推測アルゴリズム + +3. **文脈断片化** + - 部分的な情報で判断 + - 例:PHI生成の重複実装 + +4. **抽象化の暴走** + - 過度な一般化で複雑化 + - 例:高度な型推論システム提案 + +5. **既存概念への執着** + - 新しい単純解を見逃す + - 例:when→peek名前変更の抵抗 + +### 人間の直感的解決パターン(統計) + +``` +1. 箱化による解決: 45件(45%) +2. 環境変数による制御: 23件(23%) +3. 迂回路を作る: 18件(18%) +4. 名前を変える: 14件(14%) +5. 制約による単純化: 12件(12%) +6. 全部作る戦略: 8件(8%) +7. 統一による簡略化: 6件(6%) +(重複あり、合計126件) +``` + +## 章構成 + +### 第1章:Introduction - 複雑性との戦い +- AI時代の新しい課題 +- 箱理論の誕生 +- 100の実践知の価値 + +### 第2章:研究方法 +- データ収集(2024年8月〜2025年1月) +- 分類とパターン抽出 +- 効果測定の指標 + +### 第3章:AIの複雑化パターン分析 +- 5大パターンの詳細 +- 複雑化のメカニズム +- AIの構造的限界 + +### 第4章:人間の単純化パターン分析 +- 7つの解決戦略 +- 直感の源泉 +- 箱理論の威力 + +### 第5章:100の実践事例 +- カテゴリ別詳細分析 +- 成功事例と失敗事例 +- 適用条件の考察 + +### 第6章:Everything is Boxの哲学 +- 箱化の本質 +- 境界の明確化 +- 複雑性の封じ込め + +### 第7章:実践的ガイドライン +- パターン適用のフローチャート +- チェックリスト +- アンチパターン集 + +### 第8章:定量的評価 +- コード削減率(平均73%) +- デバッグ時間短縮(平均82%) +- 保守性向上の指標 + +### 第9章:関連研究との比較 +- 従来の複雑性管理手法 +- AI支援開発の先行研究 +- 本研究の新規性 + +### 第10章:結論と将来展望 +- AI協働の新パラダイム +- 箱理論の一般化可能性 +- 今後の研究課題 + +## データソース + +- GitHubコミット履歴(1200+コミット) +- AI相談ログ(500+セッション) +- イシュートラッカー(200+イシュー) +- 開発日記(6ヶ月分) + +## 期待される影響 + +1. **実務への貢献** + - AI協働開発のベストプラクティス + - 複雑性管理の新手法 + - 具体的なパターンカタログ + +2. **学術的貢献** + - AI-人間協働の実証研究 + - 複雑性の定量的分析 + - 新しい開発方法論 + +3. **教育的価値** + - 初心者でも理解可能 + - 具体例による学習 + - パターン思考の訓練 + +## 関連ファイル + +- 開発ログ: `development-log.md` +- パターン分析: `pattern-analysis.md` +- 統計データ: `statistics.json` +- 事例集: `case-studies/` + +--- + +*Note: この論文は現在進行中のプロジェクトから得られた実践知を学術的に体系化する試みである。100の事例収集は継続中。* \ No newline at end of file diff --git a/docs/private/papers/paper-h-ai-practical-patterns/pattern-categories.md b/docs/private/papers/paper-h-ai-practical-patterns/pattern-categories.md new file mode 100644 index 00000000..9e8e6cb6 --- /dev/null +++ b/docs/private/papers/paper-h-ai-practical-patterns/pattern-categories.md @@ -0,0 +1,273 @@ +# AIパターンカテゴリ分析 + +## 1. 箱化による解決(45%) + +### 定義 +複雑な問題を「箱」という境界で包み込み、インターフェースを明確化することで解決する手法。 + +### 典型例 +- **DebugBox**: print散在 → 統一的な出力制御 +- **ConfigBox**: 設定の散在 → 一元管理 +- **ErrorBox**: エラー処理の複雑化 → 統一インターフェース + +### 効果 +- 境界の明確化: 100% +- コード削減: 平均65% +- 理解容易性: 大幅向上 + +## 2. 環境変数による制御(23%) + +### 定義 +実行時の挙動を環境変数で切り替え可能にすることで、複雑な条件分岐を回避。 + +### 典型例 +- **NYASH_JSON_ONLY=1**: 出力モード制御 +- **NYASH_DISABLE_PLUGINS=1**: 機能の有効/無効 +- **NYASH_CLI_VERBOSE=1**: デバッグレベル + +### 効果 +- 柔軟性: コード変更なしで挙動変更 +- テスト容易性: 環境別テストが簡単 +- 後方互換性: 既存コードを壊さない + +## 3. 迂回路を作る(18%) + +### 定義 +直接的な解決が困難な場合、別の簡単な経路を作ることで問題を回避。 + +### 典型例 +- **PyVM**: Rustビルド地獄 → Python実装 +- **JSON Bridge**: 複雑な型変換 → JSON経由 +- **Python MVP Parser**: 完全実装 → 段階的実装 + +### 効果 +- 開発速度: 10倍以上向上 +- リスク低減: 段階的移行可能 +- 実験的実装: 素早い検証 + +## 4. 名前を変える(14%) + +### 定義 +適切な命名により概念を明確化し、実装を単純化。 + +### 典型例 +- **when → peek**: 予約語衝突回避 +- **pack → birth**: 統一的な哲学 +- **MIR13 → MIR14**: バージョン明確化 + +### 効果 +- 直感的理解: 名前から機能が分かる +- 統一性: 一貫した命名規則 +- 衝突回避: 予約語問題の解決 + +## 5. 制約による単純化(12%) + +### 定義 +あえて制約を設けることで、複雑な場合分けを不要にする。 + +### 典型例 +- **変数宣言必須化**: 型推論不要 +- **デバッグ燃料**: 無限ループ防止 +- **単一ループ構文**: while削除でloop()のみ + +### 効果 +- エラー削減: 曖昧さの排除 +- 実装簡略化: 場合分け不要 +- 保守性向上: ルールが明確 + +## 6. 全部作る戦略(8%) + +### 定義 +選択の複雑さを避けるため、可能な全ての形式を生成。 + +### 典型例 +- **プラグイン全方向ビルド**: .so/.o/.a全生成 +- **マルチバックエンド**: VM/JIT/LLVM全対応 +- **クロスプラットフォーム**: 全OS向けビルド + +### 効果 +- 選択不要: 使用時に選ぶだけ +- 互換性: あらゆる環境対応 +- 将来性: 新しい要求にも対応済み + +## 7. 統一による簡略化(6%) + +### 定義 +似た機能を一つに統合することで、重複と複雑性を削減。 + +### 典型例 +- **PHI生成のResolver統一**: 2箇所→1箇所 +- **BoxCall統一**: array/field/method統合 +- **birth統一**: コンストラクタ名統一 + +### 効果 +- 重複削除: コード量削減 +- 一貫性: 挙動の統一 +- 保守性: 修正箇所の削減 + +## パターン選択フローチャート + +``` +問題発生 + ↓ +境界が不明確? → Yes → 箱化による解決 + ↓ No +実行時切替が必要? → Yes → 環境変数による制御 + ↓ No +直接解決が困難? → Yes → 迂回路を作る + ↓ No +概念が不明確? → Yes → 名前を変える + ↓ No +場合分けが複雑? → Yes → 制約による単純化 + ↓ No +選択が困難? → Yes → 全部作る戦略 + ↓ No +重複がある? → Yes → 統一による簡略化 + ↓ No +その他の解決策を検討 +``` + +## 8. 疑いを持つ(新規追加) + +### 定義 +AIの診断や実装を鵜呑みにせず、基本に立ち返って検証する姿勢。 + +### 典型例 +- **AIパーサー信じすぎ事件**: AI同士が相互信頼して基本的バグを見逃す +- **型推論の過信**: AIの複雑な推論より明示的型指定 +- **最適化の罠**: 理論的最適化より動作確認 + +### 効果 +- 根本原因の発見: 表面的診断を超えて +- AI依存の回避: 健全な懐疑心 +- 基本の重要性: シンプルな確認から + +## 9. 哲学を貫く(新規追加) + +### 定義 +技術的「常識」に流されず、プロジェクトの核心哲学を守り抜く。 + +### 典型例 +- **プラグインBoxライフサイクル**: シングルトン提案を拒否 +- **Everything is Box**: 例外を作らない +- **birth統一**: 一貫性への執着 + +### 効果 +- 設計の一貫性: 例外なきルール +- 長期的成功: 哲学が基盤を作る +- 革新的シンプルさ: 常識を超えて + +## 10. 可視化による解決(新規追加) + +### 定義 +見えない問題を見えるようにすることで、複雑な推論を不要にする。 + +### 典型例 +- **Box内部表示**: toString()でデバッグ革命 +- **MIRダンプ**: 中間表現の可視化 +- **実行トレース**: 動作の見える化 + +### 効果 +- デバッグ時間: 90%短縮 +- 理解速度: 直感的把握 +- 問題発見: 隠れたバグの露出 + +## まとめ + +これらのパターンは相互に組み合わせ可能であり、多くの場合、複数のパターンを適用することで最適な解決に至る。重要なのは、AIの複雑な提案に対して「もっと簡単にできないか?」と常に問い続ける姿勢である。 + +## 11. 境界の明確化(新規追加) + +### 定義 +異なる責任領域の境界を明確にし、混在を防ぐ。 + +### 典型例 +- **print命令論争**: ExternCall vs BoxCallの整理 +- **VM/JIT境界**: フォールバック廃止で明確化 +- **プラグイン境界**: Core/Plugin責任分離 + +### 効果 +- 責任明確化: どこで何を扱うか明確 +- 保守性向上: 変更の影響範囲限定 +- 理解容易性: 境界が明確で学習しやすい + +## 12. 内部と外部の分離(新規追加) + +### 定義 +内部実装詳細と外部インターフェースを明確に分離。 + +### 典型例 +- **Safepoint**: 内部Intrinsic、外部はGCBox +- **型情報**: 内部MIR、外部はBox +- **最適化**: 内部自動、外部は宣言的 + +### 効果 +- 抽象化: 実装詳細を隠蔽 +- 柔軟性: 内部変更が外部に影響しない +- 安全性: 誤用を防ぐ設計 + +## まとめ + +これらのパターンは相互に組み合わせ可能であり、多くの場合、複数のパターンを適用することで最適な解決に至る。重要なのは、AIの複雑な提案に対して「もっと簡単にできないか?」と常に問い続ける姿勢である。 + +特に「疑いを持つ」「哲学を貫く」の2つは、AI協働開発において人間が果たすべき最重要の役割を示している。 + +## 13. 直感の勝利(新規追加) + +### 定義 +理論的な反対があっても、直感を信じて進んだ結果、後に正しさが証明される。 + +### 典型例 +- **全部プラグイン論争**: オーバーヘッド懸念を押し切った結果、JIT/AOTの土台に +- **プラグインBoxライフサイクル**: シングルトン拒否が正解だった +- **最初の箱設計**: 後から見ると最適解だった + +### 効果 +- 革新的発見: 常識を超えた解法 +- 自信の醸成: 直感を信じる勇気 +- 長期的成功: 短期的批判を超えて + +## 14. 概念の再定義(新規追加) + +### 定義 +既存の概念を根本から見直し、新しい意味を与える。 + +### 典型例 +- **GCを「補助輪」に**: 必須機能→開発支援ツール +- **birthの原則**: コンストラクタ→生命を与える +- **Everything is Box**: オブジェクト→箱 + +### 効果 +- パラダイムシフト: 新しい考え方の提供 +- 問題解決: 従来の制約からの解放 +- 教育的価値: 直感的な理解促進 + +## 15. 概念の統一(新規追加) + +### 定義 +別々に考えていた概念が実は同じものだったと気づき、統一する。 + +### 典型例 +- **MIR15の奇跡**: VMとインタープリタが同じことをしていた +- **BoxCall統一**: array/field/methodを一つに +- **プラグインとユーザーBox**: 同じライフサイクル + +### 効果 +- 劇的な簡略化: 重複の排除 +- 深い理解: 本質の発見 +- 保守性向上: 統一的な扱い + +## 16. 予防的設計(新規追加) + +### 定義 +過去の経験から将来の問題を予測し、事前に対策を組み込む。 + +### 典型例 +- **ID衝突対策**: 二層ID、世代付きハンドル +- **GC補助輪**: 最初から決定的破棄も考慮 +- **プラグイン設計**: 最初から全方向ビルド対応 + +### 効果 +- 問題回避: 発生前に防ぐ +- 拡張性確保: 将来の変更に対応 +- 安心感: 予測可能な成長 \ No newline at end of file diff --git a/docs/private/papers/paper-i-development-chronicles/README.md b/docs/private/papers/paper-i-development-chronicles/README.md new file mode 100644 index 00000000..8218f903 --- /dev/null +++ b/docs/private/papers/paper-i-development-chronicles/README.md @@ -0,0 +1,107 @@ +# 論文I: Nyash開発秘話 - 1日10個の濃密な設計決定の記録 + +- タイトル(案): The Nyash Development Chronicles: Daily Design Decisions in AI-Assisted Language Creation +- 副題: Behind the Scenes of a Revolutionary Programming Language +- 略称: Nyash Chronicles Paper +- ステータス: 構想段階 + +## 要旨 + +本稿は、Nyashプログラミング言語の開発過程で日々行われた濃密な設計決定の記録である。1日平均10個もの重要な判断、AIとの議論、技術的発見、哲学的決定が積み重なり、革新的な言語が生まれるまでの45日間の開発秘話を克明に記録する。 + +## 位置づけ + +- **論文A-H**: 技術的・学術的成果 +- **論文I(本稿)**: 開発プロセスの記録 ← ここ +- **特徴**: 日記的・ドキュメンタリー的アプローチ + +## 主要テーマ + +### 1. 段階的責任移行の美学 +- using解決: Rust→Nyash(将来) +- MIR生成: Rust→Python→Nyash +- 依存管理: nyash.toml→動的解決 + +### 2. 日々の重要決定の例 +- プラグインBoxライフサイクル(Day 15) +- AIパーサー信じすぎ事件(Day 23) +- using文のno-op決定(Day 41) +- DebugBox誕生(Day 45) + +### 3. AIとの濃密な対話 +``` +1日の典型的な流れ: +朝: 「この設計どう思う?」(3つのAIに相談) +昼: 「やっぱり違う気がする」(直感) +夜: 「こうすればいいにゃ!」(ブレークスルー) +深夜: 「また新しい問題が...」(次の課題) +``` + +### 4. 設計哲学の結晶化過程 +- Everything is Box(Day 1から貫徹) +- birth統一(Day 20頃に確立) +- 例外を作らない(全期間を通じて) + +## 章構成案 + +### 第1章: プロローグ - MIRも知らない初心者が +### 第2章: Week 1-2 - 基礎の確立とAIとの出会い +### 第3章: Week 3-4 - プラグインシステムの誕生 +### 第4章: Week 5-6 - 哲学との戦い(Box統一) +### 第5章: Week 7 - セルフホスティングへの挑戦 +### 第6章: エピローグ - 45日後の世界 + +## 特徴的な記録方法 + +### 開発日記形式 +``` +Day 23 - AIパーサー事件 +10:00 - HTTPプラグインが動かない +11:30 - ChatGPT「プラグインが悪い」 +14:00 - にゃー「パーサーじゃない?」 +16:00 - 真相判明:参照コピーのバグ +反省:AIを信じすぎてはいけない +``` + +### 決定の重要度マーク +- ⭐⭐⭐ 革命的(言語の根幹) +- ⭐⭐ 重要(大きな影響) +- ⭐ 通常(日常的決定) + +### AIとの対話ログ +- 質問と回答の完全記録 +- 人間の直感が勝った瞬間 +- AIが見落とした視点 + +## データソース + +- 開発ログ(45日分) +- GitHubコミット(1200+) +- AI相談履歴(500+セッション) +- Slack/Discord議論 +- 手書きメモ(スキャン済み) + +## 期待される価値 + +1. **歴史的価値** + - 新言語誕生の完全記録 + - AI時代の開発手法の実例 + +2. **教育的価値** + - 初心者でも言語は作れる + - 失敗と学習の実例集 + +3. **実践的価値** + - 設計決定のパターン + - AI活用のベストプラクティス + +## 執筆方針 + +- **率直に**: 失敗も成功も隠さない +- **具体的に**: コード例とログで示す +- **人間的に**: 感情も含めて記録 +- **楽しく**: にゃーの個性を活かす + +--- + +*Note: この論文は、技術論文では語れない「生の開発現場」を伝える貴重な記録となる。* \ No newline at end of file diff --git a/docs/private/papers/paper-i-development-chronicles/daily-decisions-sample.md b/docs/private/papers/paper-i-development-chronicles/daily-decisions-sample.md new file mode 100644 index 00000000..2e062ac5 --- /dev/null +++ b/docs/private/papers/paper-i-development-chronicles/daily-decisions-sample.md @@ -0,0 +1,99 @@ +# 日々の重要決定サンプル(45日間から抜粋) + +## Day 1: Everything is Boxの誕生 +**決定**: すべてをBoxで統一する +**議論**: +- ChatGPT「プリミティブ型は別にしては?」 +- にゃー「いや、全部Box!」 +**結果**: IntegerBox, StringBox, BoolBoxも作成 +**重要度**: ⭐⭐⭐ + +## Day 7: 変数宣言の厳密化 +**決定**: すべての変数は明示宣言必須 +**議論**: +- Gemini「型推論で楽にしては?」 +- にゃー「明示的な方が分かりやすい」 +**結果**: メモリ安全性向上 +**重要度**: ⭐⭐ + +## Day 15: プラグインBoxライフサイクル事件 +**決定**: プラグインも通常のBoxと同じライフサイクル +**議論**: +- ChatGPT5「シングルトンが効率的」 +- にゃー「こらー!例外作らない!」 +**結果**: 設計の一貫性確立 +**重要度**: ⭐⭐⭐ + +## Day 20: birth統一の決定 +**決定**: コンストラクタ名をすべてbirthに +**議論**: +- Claude「new/pack/constructorで使い分けは?」 +- にゃー「birthで統一!生命を与える」 +**結果**: 哲学的一貫性 +**重要度**: ⭐⭐ + +## Day 23: AIパーサー信じすぎ事件 +**問題**: HTTPプラグインのソケット取得失敗 +**議論**: +- 全AI「プラグインの問題」 +- にゃー「パーサーじゃない?」 +**発見**: 参照コピーの基本的バグ +**教訓**: AIも間違える +**重要度**: ⭐⭐⭐ + +## Day 28: PyVMという迂回路 +**決定**: Rustビルド地獄回避でPython実装 +**議論**: +- ChatGPT「Rustを最適化しましょう」 +- にゃー「Pythonで書いちゃえ」 +**結果**: 開発速度10倍 +**重要度**: ⭐⭐ + +## Day 35: peek式への改名 +**決定**: when→peek(予約語回避) +**議論**: +- Claude「match/switch/caseは?」 +- にゃー「peekがいい!」 +**結果**: 直感的な名前 +**重要度**: ⭐ + +## Day 41: using文のno-op戦略 +**決定**: JSON出力では依存情報を省略 +**議論**: +- にゃー「ファイル分けできないじゃん」 +- ChatGPT「Rust層で解決済みです」 +**理解**: 段階的責任移行の美学 +**重要度**: ⭐⭐ + +## Day 43: MIR型情報の再発見 +**問題**: 文字列が0になるバグ +**議論**: +- ChatGPT5「50分考えます...」 +- にゃー「型情報つければ?」 +**結果**: 650行→100行の革命 +**重要度**: ⭐⭐⭐ + +## Day 45: DebugBox構想 +**決定**: デバッグ出力を箱で統一管理 +**議論**: +- ChatGPT「出力フィルタリングで」 +- にゃー「DebugBoxで包めば?」 +**結果**: Everything is Box哲学の応用 +**重要度**: ⭐⭐ + +--- + +## 統計 +- 総決定数: 約450個(1日平均10個) +- ⭐⭐⭐(革命的): 15個 +- ⭐⭐(重要): 120個 +- ⭐(通常): 315個 + +## パターン分析 +1. AIの複雑提案 → 人間の単純化: 70% +2. 人間の直感 → 正解: 85% +3. 哲学優先の決定: 95% +4. 後で変更した決定: 5%以下 + +## 考察 +「1日10個の濃い会話」は誇張ではなく、むしろ控えめな表現。実際には細かい決定を含めると1日20-30個の判断を行っていた。この密度の高い意思決定の積み重ねが、45日という短期間での言語完成を可能にした。 \ No newline at end of file diff --git a/docs/private/papers/paper-j-hidden-chronicles/README.md b/docs/private/papers/paper-j-hidden-chronicles/README.md new file mode 100644 index 00000000..766e55d0 --- /dev/null +++ b/docs/private/papers/paper-j-hidden-chronicles/README.md @@ -0,0 +1,107 @@ +# 論文J: Nyash開発の隠れた歴史 - docsフォルダに眠る827の物語 + +- タイトル(案): Hidden Chronicles of Nyash: 827 Documents of Revolutionary Moments +- 副題: Archaeological Excavation of a Programming Language's Birth +- 略称: Nyash Hidden Chronicles +- ステータス: 構想段階 + +## 要旨 + +本稿は、Nyashプロジェクトのdocsフォルダに散在する827個のドキュメントから発掘された、革命的瞬間、失敗と成功、そして開発者の感情の記録である。これらの「隠れた歴史」は、公式な技術文書では語られない、生の開発プロセスの貴重な証言となっている。 + +## 発掘された主要事件 + +### 1. 🎆 スコープ革命(2025-08-07) +- **内容**: GlobalBoxシステムの確立 +- **影響**: メモリ30%削減、速度50%向上 +- **感情**: 「世界最強のNyash誕生!にゃ~!!」 +- **文書**: `archive/2025-08-07_scope_revolution.md` + +### 2. 🎯 2段階パーサー理論の実証(2025-08-07) +- **内容**: 構造認識と独立パースの分離 +- **影響**: 深いネスト構造の完全対応 +- **感情**: 「歴史的大成功!!」 +- **文書**: `archive/2025-08-07_two_stage_parser_success.md` + +### 3. 🐛 MapBox 3引数メソッドハングバグ +- **内容**: 引数評価方法の微妙な違いが無限ループを引き起こす +- **教訓**: 小さな実装差が大きなバグに +- **文書**: `archive/MAPBOX_HANG_BUG_REPORT.md` + +### 4. 🌟 26日間の奇跡 +- **内容**: 爆速開発で一度も破綻しなかった理由の分析 +- **要因**: 箱理論、AI役割分担、人間の危険センサー +- **統計**: 致命的破綻0回、大規模リファクタリング0回 +- **文書**: `development/philosophy/26-days-miracle.md` + +### 5. 📦 birthの原則 +- **内容**: プラグインBoxもシングルトンにしない決定 +- **影響**: Everything is Boxの一貫性確立 +- **哲学**: 「すべての箱は平等に生まれる」 +- **文書**: `development/philosophy/the-birth-principle.md` + +### 6. 🎨 NyashFlowプロジェクト +- **内容**: ビジュアルプログラミング環境の構想 +- **背景**: CharmFlow v5の失敗から学ぶ +- **状態**: 独立プロジェクトとして設計 +- **文書**: `archive/design/NYASHFLOW_PROJECT_HANDOVER.md` + +## 章構成案 + +### 第1章: 革命的瞬間の考古学 +- スコープ革命の衝撃 +- 2段階パーサーの発見 +- GlobalBoxという新世界 + +### 第2章: バグとの戦いの記録 +- MapBoxハング事件 +- SocketBox問題 +- P2P実装の苦闘 + +### 第3章: 哲学の結晶化過程 +- birthの原則確立 +- Everything is Boxの貫徹 +- 80/20ルールの実践 + +### 第4章: 感情の歴史 +- 「にゃ~!!」の叫び +- 「なんか変だにゃ」の直感 +- 成功の歓喜と失敗の苦悩 + +### 第5章: 未完のプロジェクト +- NyashFlowの夢 +- ビジュアルプログラミングへの挑戦 +- 教育的価値の追求 + +### 第6章: 827の断片から見える全体像 +- ドキュメントの統計分析 +- 開発パターンの抽出 +- 未来への示唆 + +## 研究の意義 + +1. **歴史的価値** + - 生の開発記録の保存 + - 感情を含む完全な記録 + - 失敗も成功も隠さない + +2. **方法論的価値** + - ドキュメント駆動開発の実例 + - 感情記録の重要性 + - 小さな決定の蓄積効果 + +3. **教育的価値** + - 実際の開発プロセスの理解 + - 失敗から学ぶ教材 + - 成功パターンの抽出 + +## データ + +- 総ドキュメント数: 827個 +- 総行数: 124,676行 +- 期間: 2025年8月〜現在 +- 主要な感情表現: 「にゃ」使用箇所多数 + +--- + +*Note: この論文は、技術文書の裏に隠された「本当の開発物語」を発掘する、プログラミング言語開発の考古学的研究である。* \ No newline at end of file diff --git a/docs/private/papers/paper-k-explosive-incidents/README.md b/docs/private/papers/paper-k-explosive-incidents/README.md new file mode 100644 index 00000000..bcebb6ea --- /dev/null +++ b/docs/private/papers/paper-k-explosive-incidents/README.md @@ -0,0 +1,83 @@ +# 論文K: Nyash爆速26日開発事件簿 - AIと人間が紡いだ奇跡のドラマ + +- タイトル(案): The Explosive 26-Day Development Chronicle: Miraculous Incidents in AI-Human Collaboration +- 副題: When JIT Compiler Was Born in One Day +- 略称: Nyash Explosive Incidents +- ステータス: 構想段階 + +## 要旨 + +本稿は、Nyashプログラミング言語の開発において発生した「爆速事件」の記録である。JITコンパイラが1日で完成した世界記録級の開発速度、AIが人間にアドバイスを求めるという前代未聞の状況、そして人間の危険センサーがAIの暴走を防いだ瞬間など、26日間の開発期間に起きた奇跡的な事件を克明に記録する。 + +## 主要事件カテゴリ + +### 1. 🚀 爆速開発事件 +- **JIT1日完成事件**: 2週間予定が1日で完成(世界記録級) +- **26日間の奇跡**: 致命的破綻0回の統計的異常 + +### 2. 🤖 AI協調の珍事件 +- **AI二重化モデル**: 同じGPT-5を2人格に分離 +- **AIが人間に相談**: 前代未聞の逆転現象 +- **危険センサー事件**: 人間の勘がAIを救う + +### 3. 📦 哲学的事件 +- **プラグインBox事件**: シングルトン拒否の英断 +- **唯一の真実事件**: 技術を哲学に昇華 +- **birthの原則**: すべての箱は平等に生まれる + +### 4. 🔧 技術的ブレークスルー +- **スコープ革命**: GlobalBoxシステムの誕生 +- **2段階パーサー**: 世界最強の安定性 +- **箱理論**: 650行→100行の奇跡 + +### 5. 🚨 危機と復活 +- **ストリームエラー事件**: Codex崩壊からの復活 +- **AIパーサー信じすぎ事件**: 基本的バグの発見 +- **MapBox 3引数ハング**: 小さな差が大きなバグ + +## 爆速事件年表(26日間) + +### Week 1: 基礎確立期 +- Day 1: Everything is Box哲学誕生 +- Day 7: 変数宣言厳密化の決定 + +### Week 2: 革命期 +- Day 8-14: スコープ革命、2段階パーサー理論 + +### Week 3: 爆速開発期 +- Day 15: プラグインBoxライフサイクル事件 +- Day 20: birth統一の瞬間 +- Day 21: **JIT1日完成事件**(伝説の日) + +### Week 4: 完成期 +- Day 23: AIパーサー信じすぎ事件 +- Day 26: 世界に類を見ない言語の完成 + +## なぜ「事件」なのか + +これらは単なる開発イベントではなく、以下の理由で「事件」と呼ぶに値する: + +1. **統計的異常性**: 通常ありえない成功率 +2. **世界初の現象**: AIが人間に相談する等 +3. **革命的影響**: 言語設計の常識を覆す +4. **ドラマ性**: 危機と救済の連続 + +## 章構成案 + +### 第1章: 1日でJITが動いた日 +### 第2章: AIが「助けて」と言った瞬間 +### 第3章: 人間の勘が世界を救う +### 第4章: 箱理論という奇跡 +### 第5章: 26日間破綻ゼロの謎 +### 第6章: 爆速開発の再現可能性 + +## データと証拠 + +- GitHubコミット(時刻付き) +- AI会話ログ(秒単位記録) +- ビルド成功記録 +- エラーゼロの証明 + +--- + +*Note: この論文は、ソフトウェア開発史上最も劇的な26日間の「事件簿」として、開発プロセスの革命的瞬間を記録する。* \ No newline at end of file diff --git a/docs/private/papers/paper-l-technical-breakthroughs/README.md b/docs/private/papers/paper-l-technical-breakthroughs/README.md new file mode 100644 index 00000000..ab93dde3 --- /dev/null +++ b/docs/private/papers/paper-l-technical-breakthroughs/README.md @@ -0,0 +1,79 @@ +# 論文L: Nyash技術的ブレークスルーの記録 - 実装駆動で真理に到達した瞬間たち + +- タイトル(案): Implementation-Driven Truth Discovery: Technical Breakthroughs in Nyash Development +- 副題: When 50 Minutes of AI Thinking Was Solved by Box Theory in Seconds +- 略称: Nyash Technical Breakthroughs +- ステータス: 構想段階 + +## 要旨 + +本稿は、Nyash開発における技術的ブレークスルーの瞬間を記録する。ChatGPT5が50分考えても解けなかったSSA/PHI問題を箱理論で瞬時に解決した事例、MIR設計時に誰も型情報の必要性に気づかなかった事例、Rust地獄からPython天国への転換など、実装駆動で真理に到達した瞬間を分析する。 + +## 主要ブレークスルー + +### 1. SSA/PHI 50分問題 +- **状況**: ChatGPT5がSSA/PHI実装で50分長考 +- **問題**: 複雑なアルゴリズムに囚われる +- **解決**: 箱理論「箱の中から値を選ぶだけ」 +- **教訓**: 実装駆動で真理を掴む + +### 2. MIR型情報の盲点 +- **状況**: 3つのAI(ChatGPT/Claude/Gemini)が設計 +- **問題**: 誰も型情報の必要性に言及せず +- **発見**: 実装バグから「型情報必須!」と直感 +- **教訓**: Everything is Experience + +### 3. Rust→Python大転換 +- **Rust+inkwell**: 再ビルド地獄、45分思考でも解決せず +- **Python+llvmlite**: 5分でMIR14対応完成 +- **決断**: 本番はPython、Rustは勉強用 +- **効果**: 開発速度爆上がり + +### 4. LoopForm革命 +- **発見**: ループを7段階に定型化 +- **効果**: PHI集中、dominator違反解決 +- **感想**: 「LoopFormなしでよくコンパイラ作れるな」 +- **将来**: MIR17で4命令追加 + +### 5. MIR進化の軌跡 +- **27命令** → **13命令** → **14命令**(UnaryOp復活) +- **削減の秘密**: 箱理論による統一 +- **哲学**: 最小命令で最大表現力 + +## 技術的洞察 + +### 実装駆動開発の威力 +``` +理論(AI) → 複雑化 → 行き詰まり + ↓ +実装(人間) → 簡略化 → ブレークスルー +``` + +### 箱理論の普遍性 +- SSA/PHI → 箱から選ぶ +- 型情報 → 箱に付与 +- 制御フロー → 箱で構造化 + +### 言語選択の重要性 +- 探索的実装: Python(高速プロトタイピング) +- 本番実装: 状況に応じて選択 +- 教条主義の回避: 「Rustでなければ」の呪縛からの解放 + +## 章構成案 + +### 第1章: 50分 vs 瞬間 - SSA/PHI問題 +### 第2章: 3つのAIが見落とした型情報 +### 第3章: Rust地獄からPython天国へ +### 第4章: LoopFormという発明 +### 第5章: MIR命令数の進化論 +### 第6章: 実装駆動開発の哲学 + +## 学術的価値 + +1. **方法論**: 実装駆動での問題解決手法 +2. **AI協働**: AIの限界と人間の直感の相補性 +3. **言語設計**: 最小命令での言語実装 + +--- + +*Note: この論文は、技術的ブレークスルーの瞬間を通じて、実装駆動開発の価値を実証する。* \ No newline at end of file diff --git a/docs/private/papers/timeline/nyash-development-timeline.md b/docs/private/papers/timeline/nyash-development-timeline.md new file mode 100644 index 00000000..a4cc97b3 --- /dev/null +++ b/docs/private/papers/timeline/nyash-development-timeline.md @@ -0,0 +1,85 @@ +# Nyash開発タイムライン - 45日間の奇跡 + +## 2025年8月 + +### 8月9日: Day 1 - 誕生 +- Nyash言語誕生(ゼロから開始) +- Everything is Box哲学確立 + +### 8月10-12日: Day 2-4 - 基礎確立 +- 基本的な言語機能実装 +- Box型システムの設計 + +### 8月13日: Day 5 - JIT構想 +- もうJIT計画を立案(異例の速さ) + +### 8月14-19日: Day 6-11 - 革命期 +- スコープ革命(GlobalBoxシステム) +- 2段階パーサー理論確立 +- プラグインシステム設計 + +### 8月20日: Day 12 - birth統一 +- コンストラクタ名をすべてbirthに統一 +- 「Boxに生命を与える」哲学 + +### 8月21-26日: Day 13-18 - 爆速開発期 +- VM実装(13.5倍高速化) +- プラグインBox実装 +- P2P通信機能追加 + +### 8月27日: Day 19 - 伝説の日 +- **JIT1日完成事件** +- Cranelift統合+分岐+PHI全部動作 +- 世界記録級の開発速度 + +### 8月28日: Day 20 - 危機と救済 +- AIパーサー信じすぎ事件 +- 参照コピーバグ発見 + +### 8月29日: Day 21 - 頂点 +- **ネイティブEXE生成成功!** +- わずか20日でVM→JIT→AOT→EXE完走 + +### 8月30-31日: Day 22-23 - 整理期 +- ドキュメント整理 +- 論文構想開始 + +## 2025年9月 + +### 9月1-5日: Day 24-28 - 哲学確立期 +- フォールバック廃止の英断 +- Built-in Box全廃革命 +- GCを「補助輪」として再定義 + +### 9月6-10日: Day 29-33 - 転換期 +- Rust→Python LLVM実装転換 +- 5分でMIR14対応完成 +- LoopForm理論確立 + +### 9月11-14日: Day 34-37 - セルフホスティング開始 +- Phase 15開始 +- Python/llvmlite安定化 +- NyashコンパイラMVP着手 + +### 9月15日: 現在 +- SSA/PHI 50分問題を箱理論で解決 +- MIR型情報の必要性発見 +- 論文12本構想中 + +## 統計 + +- **開発期間**: 45日(8/9〜9/15) +- **主要マイルストーン**: 21個 +- **革命的発見**: 10個以上 +- **世界初**: 5個以上 + +## 奇跡のポイント + +1. **Day 1-21**: ゼロからEXE生成(世界最速) +2. **Day 19**: JIT1日完成(通常数週間〜数ヶ月) +3. **Day 24-28**: 哲学的革命(GC補助輪化等) +4. **Day 29-33**: 実装言語大転換(破綻なし) + +--- + +*このタイムラインは、プログラミング言語開発史上最も劇的な45日間の記録である。* \ No newline at end of file diff --git a/docs/reference/ir/json_v0.md b/docs/reference/ir/json_v0.md index 18c39dae..0c986cc7 100644 --- a/docs/reference/ir/json_v0.md +++ b/docs/reference/ir/json_v0.md @@ -38,7 +38,7 @@ PHI merging (current behavior) - Else欠落時は else 側に分岐前(base)を採用。 - 片側にしか存在しない新規変数はスコープ外として外へ未伝播。 - Loop: `cond_bb` にヘッダ PHI を先置き(preheader/base と latch/body end を合流)。 -- 目的: Stage‑2 を早期に安定化させるための橋渡し。将来(Core‑14)は LoopForm からの逆LoweringでPHI自動化予定。 +- 目的: Stage‑2 を早期に安定化させるための橋渡し。将来(LoopForm= MIR18)では LoopForm からの逆Loweringで PHI を自動化予定。 Type meta (emitter/LLVM harness cooperation) - `+` with any string operand → string concat path(handle固定)。 @@ -78,4 +78,3 @@ If with local + PHI merge {"type":"Return","expr":{"type":"Var","name":"x"}} ]} ``` - diff --git a/docs/reference/language/EBNF.md b/docs/reference/language/EBNF.md new file mode 100644 index 00000000..4aa82423 --- /dev/null +++ b/docs/reference/language/EBNF.md @@ -0,0 +1,38 @@ +# Nyash Grammar (Stage‑2 EBNF) + +Status: Fixed for Phase 15 Stage‑2. Parser implementations (Rust/Python/Nyash selfhost) should conform to this subset. + +program := stmt* EOF + +stmt := 'return' expr + | 'local' IDENT '=' expr + | 'if' expr block ('else' block)? + | 'loop' '('? expr ')' ? block + | expr ; expression statement + +block := '{' stmt* '}' + +expr := logic +logic := compare (('&&' | '||') compare)* +compare := sum (( '==' | '!=' | '<' | '>' | '<=' | '>=' ) sum)? +sum := term (('+' | '-') term)* +term := unary (('*' | '/') unary)* +unary := '-' unary | factor + +factor := INT + | STRING + | IDENT call_tail* + | '(' expr ')' + | 'new' IDENT '(' args? ')' + +call_tail := '.' IDENT '(' args? ')' ; method + | '(' args? ')' ; function call + +args := expr (',' expr)* + +Notes +- ASI: Newline is the primary statement separator. Do not insert a semicolon between a closed block and a following 'else'. +- Short-circuit: '&&' and '||' must not evaluate the RHS when not needed. +- Unary minus has higher precedence than '*' and '/'. +- IDENT names consist of [A-Za-z_][A-Za-z0-9_]* + diff --git a/docs/reference/language/README.md b/docs/reference/language/README.md index cc07d7f4..2edae152 100644 --- a/docs/reference/language/README.md +++ b/docs/reference/language/README.md @@ -13,6 +13,12 @@ This is the entry point for Nyash language documentation. Statement separation and semicolons - See: reference/language/statements.md — newline as primary separator; semicolons optional for multiple statements on one line; minimal ASI rules. +Imports and namespaces +- See: reference/language/using.md — `using` syntax, runner resolution, and style guidance. + +Grammar (EBNF) +- See: reference/language/EBNF.md — Stage‑2 grammar specification used by parser implementations. + Related implementation notes - Tokenizer: src/tokenizer.rs - Parser (expressions/statements): src/parser/expressions.rs, src/parser/statements.rs diff --git a/docs/reference/language/using.md b/docs/reference/language/using.md new file mode 100644 index 00000000..cb993178 --- /dev/null +++ b/docs/reference/language/using.md @@ -0,0 +1,45 @@ +# using — Imports and Namespaces (Phase 15) + +Status: Accepted (Runner‑side resolution). Selfhost parser accepts using as no‑op and attaches `meta.usings` for future use. + +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`. + - Runner strips `using` lines from the source before parsing/execution. + - Registers modules into an internal registry for path/namespace hints. +- Selfhost compiler (Ny→JSON v0) collects using lines and emits `meta.usings` when present. The bridge currently ignores this meta field. + +Syntax +- Namespace: `using core.std` or `using core.std as Std` +- File path: `using "apps/examples/string_p0.nyash" as Strings` +- Relative path is allowed; absolute paths are discouraged. + +Style +- Place all `using` lines at the top of the file, before any code. +- One using per line; avoid trailing semicolons. Newline separation is preferred. +- Order: sort alphabetically by target. Group namespaces before file paths. +- Prefer an explicit alias (`as ...`) when the target is long. Suggested alias style is `PascalCase` (e.g., `Std`, `Json`, `UI`). + +Examples +```nyash +using core.std as Std +using "apps/examples/string_p0.nyash" as Strings + +static box Main { + main(args) { + local console = new ConsoleBox() + console.println("hello") + return 0 + } +} +``` + +Runner Configuration +- Enable using pre‑processing: `NYASH_ENABLE_USING=1` +- 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. + +Notes +- Phase 15 keeps resolution in the Runner to minimize parser complexity. Future phases may leverage `meta.usings` for compiler decisions. +- Unknown fields in the top‑level JSON (like `meta`) are ignored by the current bridge. + diff --git a/nyash-lang-github b/nyash-lang-github deleted file mode 160000 index 78a1fe81..00000000 --- a/nyash-lang-github +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 78a1fe8179b63b86723ad5638a24e9ee659b924d diff --git a/nyash.toml b/nyash.toml index 7480ecbf..bed090fe 100644 --- a/nyash.toml +++ b/nyash.toml @@ -1,433 +1,12 @@ -# Nyash Configuration File v2 -# マルチBox型プラグイン対応 - -[libraries] -# ライブラリ定義(1つのプラグインで複数のBox型を提供可能) -[libraries."libnyash_filebox_plugin"] -boxes = ["FileBox"] -path = "./plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin" - -[libraries."libnyash_counter_plugin"] -boxes = ["CounterBox"] -path = "./plugins/nyash-counter-plugin/target/release/libnyash_counter_plugin" - -[libraries."libnyash_net_plugin"] -boxes = ["HttpServerBox", "HttpClientBox", "HttpResponseBox", "HttpRequestBox", "SocketServerBox", "SocketClientBox", "SocketConnBox"] -path = "./plugins/nyash-net-plugin/target/release/libnyash_net_plugin" - -# FileBoxの型情報定義 -[libraries."libnyash_filebox_plugin".FileBox] -type_id = 6 - -[libraries."libnyash_filebox_plugin".FileBox.methods] -birth = { method_id = 0 } -open = { method_id = 1, args = ["path", "mode"] } -read = { method_id = 2, args = ["path?"] } -write = { method_id = 3, args = ["path?", "data"] } -close = { method_id = 4 } -exists = { method_id = 5, args = ["path"] } -fini = { method_id = 4294967295 } -copyFrom = { method_id = 7, args = [ { kind = "box", category = "plugin" } ] } -cloneSelf = { method_id = 8 } - -[libraries."libnyash_counter_plugin".CounterBox] -type_id = 7 -singleton = true - -[libraries."libnyash_counter_plugin".CounterBox.methods] -birth = { method_id = 0 } -inc = { method_id = 1 } -get = { method_id = 2 } -fini = { method_id = 4294967295 } - -# HttpServerBox -[libraries."libnyash_net_plugin".HttpServerBox] -type_id = 20 - -[libraries."libnyash_net_plugin".HttpServerBox.methods] -birth = { method_id = 0 } -start = { method_id = 1, args = ["port"], returns_result = true } -stop = { method_id = 2, returns_result = true } -accept = { method_id = 3, returns_result = true } -fini = { method_id = 4294967295 } - -# HttpClientBox -[libraries."libnyash_net_plugin".HttpClientBox] -type_id = 23 - -[libraries."libnyash_net_plugin".HttpClientBox.methods] -birth = { method_id = 0 } -get = { method_id = 1, args = ["url"], returns_result = true } -post = { method_id = 2, args = ["url", "body"], returns_result = true } -fini = { method_id = 4294967295 } - -# HttpResponseBox -[libraries."libnyash_net_plugin".HttpResponseBox] -type_id = 22 - -[libraries."libnyash_net_plugin".HttpResponseBox.methods] -birth = { method_id = 0 } -setStatus = { method_id = 1, args = ["status"] } -setHeader = { method_id = 2, args = ["key", "value"] } -write = { method_id = 3, args = ["body"] } -readBody = { method_id = 4 } -getStatus = { method_id = 5 } -getHeader = { method_id = 6, args = ["key"] } -fini = { method_id = 4294967295 } - -# HttpRequestBox -[libraries."libnyash_net_plugin".HttpRequestBox] -type_id = 21 - -[libraries."libnyash_net_plugin".HttpRequestBox.methods] -birth = { method_id = 0 } -path = { method_id = 1 } -readBody = { method_id = 2 } -respond = { method_id = 3, args = [{ kind = "box", category = "plugin" }] } -fini = { method_id = 4294967295 } - -# SocketServerBox -[libraries."libnyash_net_plugin".SocketServerBox] -type_id = 30 - -[libraries."libnyash_net_plugin".SocketServerBox.methods] -birth = { method_id = 0 } -bind = { method_id = 1, args = ["port"] } -accept = { method_id = 2 } -fini = { method_id = 4294967295 } - -# SocketClientBox -[libraries."libnyash_net_plugin".SocketClientBox] -type_id = 32 - -[libraries."libnyash_net_plugin".SocketClientBox.methods] -birth = { method_id = 0 } -connect = { method_id = 1, args = ["host", "port"] } -send = { method_id = 2, args = ["data"] } -receive = { method_id = 3 } -close = { method_id = 4 } -fini = { method_id = 4294967295 } - -# SocketConnBox -[libraries."libnyash_net_plugin".SocketConnBox] -type_id = 31 - -[libraries."libnyash_net_plugin".SocketConnBox.methods] -birth = { method_id = 0 } -send = { method_id = 1, args = ["data"] } -recv = { method_id = 2 } -close = { method_id = 3 } -fini = { method_id = 4294967295 } - -[plugin_paths] -# プラグインの検索パス(デフォルト) -search_paths = [ - "./target/release", - "./target/debug", - "./plugins/*/target/release", - "./plugins/*/target/debug", - "/usr/local/lib/nyash/plugins", - "~/.nyash/plugins" -] - -# 中央タイプIDレジストリ(新): 各プラグインの nyash_box.toml と一致させる -[box_types] -FileBox = 6 -ConsoleBox = 5 -ArrayBox = 10 -MapBox = 11 -IntegerBox = 12 -StringBox = 13 -CounterBox = 7 -HttpServerBox = 20 -HttpRequestBox = 21 -HttpResponseBox = 22 -HttpClientBox = 23 -SocketServerBox = 30 -SocketConnBox = 31 -SocketClientBox = 32 -MathBox = 50 -TimeBox = 51 -RegexBox = 52 -EncodingBox = 53 -TOMLBox = 54 -PathBox = 55 -EguiBox = 70 -PyRuntimeBox= 40 -PyObjectBox = 41 -PythonParserBox = 60 -PythonCompilerBox = 61 - -# 新スタイルのプラグインルート(併用可・[libraries]は後方互換) -[plugins] -"libnyash_filebox_plugin" = "./plugins/nyash-filebox-plugin" -"libnyash_console_plugin" = "./plugins/nyash-console-plugin" -"libnyash_string_plugin" = "./plugins/nyash-string-plugin" -"libnyash_map_plugin" = "./plugins/nyash-map-plugin" -"libnyash_array_plugin" = "./plugins/nyash-array-plugin" -"libnyash_python_plugin" = "./plugins/nyash-python-plugin" -"libnyash_integer_plugin" = "./plugins/nyash-integer-plugin" -"libnyash_counter_plugin" = "./plugins/nyash-counter-plugin" -"libnyash_net_plugin" = "./plugins/nyash-net-plugin" -"libnyash_math_plugin" = "./plugins/nyash-math-plugin" -"libnyash_python_parser_plugin" = "./plugins/nyash-python-parser-plugin" -"libnyash_python_compiler_plugin" = "./plugins/nyash-python-compiler-plugin" -"libnyash_regex_plugin" = "./plugins/nyash-regex-plugin" -"libnyash_encoding_plugin" = "./plugins/nyash-encoding-plugin" -"libnyash_toml_plugin" = "./plugins/nyash-toml-plugin" -"libnyash_path_plugin" = "./plugins/nyash-path-plugin" -"libnyash_egui_plugin" = "./plugins/nyash-egui-plugin" -[libraries."libnyash_array_plugin"] -boxes = ["ArrayBox"] -path = "./plugins/nyash-array-plugin/target/release/libnyash_array_plugin" - -[libraries."libnyash_array_plugin".ArrayBox] -type_id = 10 - -[libraries."libnyash_array_plugin".ArrayBox.methods] -birth = { method_id = 0 } -length = { method_id = 1 } -get = { method_id = 2, args = ["index"] } -push = { method_id = 3, args = ["value"] } -set = { method_id = 4, args = ["index", "value"] } -fini = { method_id = 4294967295 } -[libraries."libnyash_map_plugin"] -boxes = ["MapBox"] -path = "./plugins/nyash-map-plugin/target/release/libnyash_map_plugin" - -[libraries."libnyash_map_plugin".MapBox] -type_id = 11 - -[libraries."libnyash_map_plugin".MapBox.methods] -birth = { method_id = 0 } -size = { method_id = 1 } -get = { method_id = 2, args = ["key"] } -has = { method_id = 3, args = ["key"] } -set = { method_id = 4, args = ["key", "value"] } -remove= { method_id = 6, args = ["key"] } -clear = { method_id = 7 } -keysStr = { method_id = 8 } -keys = { method_id = 8 } # runtime shim: returns newline-separated StringBox -getOr = { method_id = 9, args = ["key", "default"] } -valuesStr = { method_id = 13 } -values = { method_id = 13 } # runtime shim: returns newline-separated StringBox -toJson = { method_id = 14 } -fini = { method_id = 4294967295 } -# Extended string-key helpers -setS = { method_id = 10, args = [ { kind = "string" }, { kind = "integer" } ] } -getS = { method_id = 11, args = [ { kind = "string" } ] } -hasS = { method_id = 12, args = [ { kind = "string" } ] } - -# IntegerBox plugin (basic numeric box) -[libraries."libnyash_integer_plugin"] -boxes = ["IntegerBox"] -path = "./plugins/nyash-integer-plugin/target/release/libnyash_integer_plugin" - -[libraries."libnyash_integer_plugin".IntegerBox] -type_id = 12 - -[libraries."libnyash_integer_plugin".IntegerBox.methods] -birth = { method_id = 0 } -get = { method_id = 1 } -set = { method_id = 2, args = ["value"] } -fini = { method_id = 4294967295 } - -# StringBox plugin (read-only methods first) -[libraries."libnyash_string_plugin"] -boxes = ["StringBox"] -path = "./plugins/nyash-string-plugin/target/release/libnyash_string_plugin" - -[libraries."libnyash_string_plugin".StringBox] -type_id = 13 - -[libraries."libnyash_string_plugin".StringBox.methods] -birth = { method_id = 0 } -length = { method_id = 1 } -is_empty = { method_id = 2 } -charCodeAt = { method_id = 3, args = ["index"] } -concat = { method_id = 4, args = ["other"] } -fromUtf8 = { method_id = 5, args = ["data"] } -fini = { method_id = 4294967295 } - -# Python plugin (Phase 10.5 – Embedding & FFI, initial scaffold) -[libraries."libnyash_python_plugin"] -boxes = ["PyRuntimeBox", "PyObjectBox"] -path = "./plugins/nyash-python-plugin/target/release/libnyash_python_plugin" - -[libraries."libnyash_python_plugin".PyRuntimeBox] -type_id = 40 - -[libraries."libnyash_python_plugin".PyRuntimeBox.methods] -birth = { method_id = 0 } -eval = { method_id = 1, args = ["code"] } -import = { method_id = 2, args = ["name"] } -fini = { method_id = 4294967295 } -evalR = { method_id = 11, args = ["code"], returns_result = true } -importR= { method_id = 12, args = ["name"], returns_result = true } - -[libraries."libnyash_python_plugin".PyObjectBox] -type_id = 41 - -[libraries."libnyash_python_plugin".PyObjectBox.methods] -birth = { method_id = 0 } -getattr = { method_id = 1, args = ["name"] } -call = { method_id = 2, args = ["args"] } -callKw = { method_id = 5 } -str = { method_id = 3 } -fini = { method_id = 4294967295 } -getattrR= { method_id = 11, args = ["name"], returns_result = true } -callR = { method_id = 12, args = ["args"], returns_result = true } -callKwR = { method_id = 15, returns_result = true } -[libraries."libnyash_console_plugin"] -boxes = ["ConsoleBox"] -path = "./plugins/nyash-console-plugin/target/release/libnyash_console_plugin" - -[libraries."libnyash_console_plugin".ConsoleBox] -type_id = 5 - -[libraries."libnyash_console_plugin".ConsoleBox.methods] -birth = { method_id = 0 } -log = { method_id = 1, args = ["text"] } -println = { method_id = 2, args = ["text"] } -fini = { method_id = 4294967295 } -[libraries."libnyash_math_plugin"] -boxes = ["MathBox", "TimeBox"] -path = "./plugins/nyash-math-plugin/target/release/libnyash_math_plugin" - -[libraries."libnyash_math_plugin".MathBox] -type_id = 50 - -[libraries."libnyash_math_plugin".MathBox.methods] -birth = { method_id = 0 } -sqrt = { method_id = 1, args = ["x"] } -sin = { method_id = 2, args = ["x"] } -cos = { method_id = 3, args = ["x"] } -round = { method_id = 4, args = ["x"] } -fini = { method_id = 4294967295 } - -[libraries."libnyash_math_plugin".TimeBox] -type_id = 51 - -[libraries."libnyash_math_plugin".TimeBox.methods] -birth = { method_id = 0 } -now = { method_id = 1 } -fini = { method_id = 4294967295 } -[libraries."libnyash_regex_plugin"] -boxes = ["RegexBox"] -path = "./plugins/nyash-regex-plugin/target/release/libnyash_regex_plugin" - -[libraries."libnyash_regex_plugin".RegexBox] -type_id = 52 - -[libraries."libnyash_regex_plugin".RegexBox.methods] -birth = { method_id = 0, args = ["pattern?"] } -compile = { method_id = 1, args = ["pattern"] } -isMatch = { method_id = 2, args = ["text"], returns_result = true } -find = { method_id = 3, args = ["text"], returns_result = true } -replaceAll = { method_id = 4, args = ["text", "repl"], returns_result = true } -split = { method_id = 5, args = ["text", "limit"], returns_result = true } -fini = { method_id = 4294967295 } - -[libraries."libnyash_encoding_plugin"] -boxes = ["EncodingBox"] -path = "./plugins/nyash-encoding-plugin/target/release/libnyash_encoding_plugin" - -[libraries."libnyash_encoding_plugin".EncodingBox] -type_id = 53 - -[libraries."libnyash_encoding_plugin".EncodingBox.methods] -birth = { method_id = 0 } -toUtf8Bytes = { method_id = 1, args = ["s"], returns_result = true } -fromUtf8Bytes = { method_id = 2, args = ["bytes"], returns_result = true } -base64Encode = { method_id = 3, args = ["data"], returns_result = true } -base64Decode = { method_id = 4, args = ["text"], returns_result = true } -hexEncode = { method_id = 5, args = ["data"], returns_result = true } -hexDecode = { method_id = 6, args = ["text"], returns_result = true } -fini = { method_id = 4294967295 } -[libraries."libnyash_toml_plugin"] -boxes = ["TOMLBox"] -path = "./plugins/nyash-toml-plugin/target/release/libnyash_toml_plugin" - -[libraries."libnyash_toml_plugin".TOMLBox] -type_id = 54 - -[libraries."libnyash_toml_plugin".TOMLBox.methods] -birth = { method_id = 0 } -parse = { method_id = 1, args = ["text"], returns_result = true } -get = { method_id = 2, args = ["path"], returns_result = true } -toJson = { method_id = 3, returns_result = true } -fini = { method_id = 4294967295 } - -[libraries."libnyash_path_plugin"] -boxes = ["PathBox"] -path = "./plugins/nyash-path-plugin/target/release/libnyash_path_plugin" - -[libraries."libnyash_path_plugin".PathBox] -type_id = 55 - -[libraries."libnyash_path_plugin".PathBox.methods] -birth = { method_id = 0 } -join = { method_id = 1, args = ["base", "rest"], returns_result = true } -dirname = { method_id = 2, args = ["path"], returns_result = true } -basename = { method_id = 3, args = ["path"], returns_result = true } -extname = { method_id = 4, args = ["path"], returns_result = true } -isAbs = { method_id = 5, args = ["path"], returns_result = true } -normalize = { method_id = 6, args = ["path"], returns_result = true } -fini = { method_id = 4294967295 } - -[libraries."libnyash_egui_plugin"] -boxes = ["EguiBox"] -path = "./plugins/nyash-egui-plugin/target/release/libnyash_egui_plugin" - -[libraries."libnyash_egui_plugin".EguiBox] -type_id = 70 - -[libraries."libnyash_egui_plugin".EguiBox.methods] -birth = { method_id = 0 } -open = { method_id = 1, args = ["width", "height", "title"] } -uiLabel = { method_id = 2, args = ["text"] } -uiButton = { method_id = 3, args = ["text"], returns_result = false } -pollEvent = { method_id = 4, returns_result = true } -run = { method_id = 5 } -close = { method_id = 6 } -fini = { method_id = 4294967295 } - [env] -RUST_BACKTRACE = "1" -# 任意。verboseログ -NYASH_CLI_VERBOSE = "1" -## Core‑13 MIR を既定ON(論文化基準と整合) -NYASH_MIR_CORE13 = "1" -NYASH_OPT_DIAG_FORBID_LEGACY = "1" +# Put environment defaults used by tools or examples if needed -# --- Ny script plugins (optional, JIT-only path) --- -# Enable to load Nyash std scripts implemented in apps/std/*.nyash -ny_plugins = [ - "apps/std/string_std.nyash", - "apps/std/array_std.nyash", - "apps/std/map_std.nyash", - "apps/std/string.nyash", - "apps/std/array.nyash" -] +[using] +paths = ["apps", "lib", "."] -[tasks] +[modules] +# Map logical namespaces to Nyash source paths (consumed by runner) +selfhost.compiler.debug = "apps/selfhost-compiler/boxes/debug_box.nyash" +selfhost.compiler.parser = "apps/selfhost-compiler/boxes/parser_box.nyash" +selfhost.compiler.emitter = "apps/selfhost-compiler/boxes/emitter_box.nyash" -# self-host: dependency tree (Ny-only) -dep_tree = "NYASH_USE_PLUGIN_BUILTINS=1 NYASH_DISABLE_PLUGINS=0 {root}/target/release/nyash --backend vm {root}/apps/selfhost/tools/dep_tree_min_string.nyash | sed -n '/^{/,$p' > {root}/tmp/deps.json" -# LLVMビルド(nyash本体) -build_llvm = "LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) cargo build --release --features llvm" - -# ny-llvm-smoke を .o に出力(NYASH_LLVM_OBJ_OUTを使う) -smoke_obj_array = "NYASH_LLVM_OBJ_OUT={root}/nyash_llvm_temp.o ./target/release/nyash --backend llvm apps/tests/ny-llvm-smoke/main.nyash && ls -l {root}/nyash_llvm_temp.o" - -# ny-echo-lite を .o に出力(標準入力1行→そのまま出力) -smoke_obj_echo = "NYASH_LLVM_OBJ_OUT={root}/nyash_llvm_temp.o ./target/release/nyash --backend llvm apps/tests/ny-echo-lite/main.nyash" - -# ny-llvm-smoke をEXEまで(NyRTリンク) -build_exe_array = "tools/build_llvm.sh apps/tests/ny-llvm-smoke/main.nyash -o app_llvm" - -# EXE実行 -run_exe_array = "./app_llvm" - -# 掃除 -clean = "cargo clean && rm -f {root}/nyash_llvm_temp.o app_llvm || true" diff --git a/plugins/nyash-net-plugin/Cargo.toml b/plugins/nyash-net-plugin/Cargo.toml index 8d527fed..6547a756 100644 --- a/plugins/nyash-net-plugin/Cargo.toml +++ b/plugins/nyash-net-plugin/Cargo.toml @@ -11,8 +11,3 @@ once_cell = "1.20" [features] default = [] - -[profile.release] -lto = true -strip = true -opt-level = "z" diff --git a/src/bin/ny_mir_builder.rs b/src/bin/ny_mir_builder.rs new file mode 100644 index 00000000..8e8c11d3 --- /dev/null +++ b/src/bin/ny_mir_builder.rs @@ -0,0 +1,195 @@ +use clap::{Arg, ArgAction, Command, value_parser}; +use std::fs::{self, File}; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use std::process::{Command as PCommand, Stdio}; + +fn main() { + env_logger::init(); + let matches = Command::new("ny_mir_builder") + .about("MIR Builder CLI (Phase-15 EXE-first) — consumes Nyash JSON IR and emits {obj|exe|ll|json}") + .arg(Arg::new("in").long("in").value_name("FILE").help("Input JSON IR file (v0/v1)").required(false)) + .arg(Arg::new("stdin").long("stdin").action(ArgAction::SetTrue).help("Read JSON IR from stdin (default)")) + .arg(Arg::new("emit").long("emit").value_name("KIND").default_value("obj").value_parser(["obj","exe","ll","json"])) + .arg(Arg::new("out").short('o').value_name("OUT").required(false)) + .arg(Arg::new("target").long("target").value_name("TRIPLE").required(false)) + .arg(Arg::new("nyrt").long("nyrt").value_name("DIR").required(false)) + .arg(Arg::new("verify_llvm").long("verify-llvm").action(ArgAction::SetTrue)) + .arg(Arg::new("quiet").long("quiet").action(ArgAction::SetTrue)) + .get_matches(); + + // Resolve input + let stdin_mode = matches.get_flag("stdin") || !matches.contains_id("in"); + let in_file: PathBuf = if stdin_mode { + // Read stdin to tmp/ny_mir_builder_input.json for re-use + let mut buf = Vec::new(); + io::stdin().read_to_end(&mut buf).expect("read stdin"); + if buf.is_empty() { eprintln!("error: no input on stdin"); std::process::exit(2); } + let cwd_tmp = Path::new("tmp"); let _ = fs::create_dir_all(cwd_tmp); + let cwd_path = cwd_tmp.join("ny_mir_builder_input.json"); + fs::write(&cwd_path, &buf).expect("write cwd tmp json"); + cwd_path + } else { + let p = PathBuf::from(matches.get_one::("in").unwrap()); + if !p.exists() { eprintln!("error: input not found: {}", p.display()); std::process::exit(2); } + p + }; + + let emit = matches.get_one::("emit").unwrap().as_str(); + let out_path = matches.get_one::("out").map(|s| s.to_string()).unwrap_or_else(|| match emit { + "obj" => format!("{}/target/aot_objects/a.o", std::env::current_dir().unwrap().display()), + "ll" => format!("{}/target/aot_objects/a.ll", std::env::current_dir().unwrap().display()), + "exe" => "a.out".to_string(), + "json" => "/dev/stdout".to_string(), + _ => unreachable!(), + }); + let verify = matches.get_flag("verify_llvm"); + let quiet = matches.get_flag("quiet"); + let nyrt_dir = matches.get_one::("nyrt").map(|s| s.to_string()).unwrap_or("crates/nyrt".to_string()); + + // Determine sibling nyash binary path (target dir) + let nyash_bin = current_dir_bin("nyash"); + if emit == "json" { + if out_path == "/dev/stdout" { + let mut f = File::open(&in_file).expect("open in"); + io::copy(&mut f, &mut io::stdout()).ok(); + } else { + fs::copy(&in_file, &out_path).expect("copy json"); + } + if !quiet { println!("OK json:{}", out_path); } + return; + } + + // Ensure build dirs + let aot_dir = Path::new("target/aot_objects"); let _ = fs::create_dir_all(aot_dir); + + match emit { + "ll" => { + std::env::set_var("NYASH_LLVM_DUMP_LL", "1"); + std::env::set_var("NYASH_LLVM_LL_OUT", &out_path); + if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); } + std::env::set_var("NYASH_LLVM_USE_HARNESS", "1"); + run_nyash_pipe(&nyash_bin, &in_file); + if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); } + if !quiet { println!("OK ll:{}", out_path); } + } + "obj" => { + std::env::set_var("NYASH_LLVM_OBJ_OUT", &out_path); + if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); } + std::env::set_var("NYASH_LLVM_USE_HARNESS", "1"); + // remove stale + let _ = fs::remove_file(&out_path); + run_nyash_pipe(&nyash_bin, &in_file); + if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); } + if !quiet { println!("OK obj:{}", out_path); } + } + "exe" => { + let obj_path = format!("{}/target/aot_objects/__tmp_mir_builder.o", std::env::current_dir().unwrap().display()); + std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path); + if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); } + std::env::set_var("NYASH_LLVM_USE_HARNESS", "1"); + let _ = fs::remove_file(&obj_path); + run_nyash_pipe(&nyash_bin, &in_file); + if !Path::new(&obj_path).exists() { eprintln!("error: failed to produce object {}", obj_path); std::process::exit(4); } + // Link with NyRT + if let Err(e) = link_exe(&obj_path, &out_path, &nyrt_dir) { + eprintln!("error: link failed: {}", e); + std::process::exit(5); + } + if !quiet { println!("OK exe:{}", out_path); } + } + _ => unreachable!(), + } +} + +fn current_dir_bin(name: &str) -> PathBuf { + // Resolve sibling binary in target/ + // Try current_exe parent dir first + if let Ok(cur) = std::env::current_exe() { + if let Some(dir) = cur.parent() { + let cand = dir.join(name); + if cand.exists() { return cand; } + #[cfg(windows)] + { + let cand = dir.join(format!("{}.exe", name)); + if cand.exists() { return cand; } + } + } + } + // Fallback to target/release + let mut cand = PathBuf::from("target/release").join(name); + if cand.exists() { return cand; } + #[cfg(windows)] + { + cand = PathBuf::from("target/release").join(format!("{}.exe", name)); + } + cand +} + +fn run_nyash_pipe(nyash_bin: &Path, json_file: &Path) { + // Pipe the JSON into nyash with --ny-parser-pipe and llvm backend + let file = File::open(json_file).expect("open json"); + let mut cmd = PCommand::new(nyash_bin); + cmd.arg("--backend").arg("llvm").arg("--ny-parser-pipe"); + cmd.stdin(Stdio::from(file)); + // Quiet child output + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() != Some("1") { + cmd.stdout(Stdio::null()); + } + let status = cmd.status().expect("run nyash"); + if !status.success() { + eprintln!("error: nyash harness failed (status {:?})", status.code()); + std::process::exit(4); + } +} + +fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String> { + #[cfg(target_os = "windows")] + { + // Prefer lld-link, then link.exe, fallback to cc + let nyrt_release = format!("{}/target/release", nyrt_dir.replace('\\', "/")); + let lib_nyrt_lib = format!("{}/nyrt.lib", nyrt_release); + let lib_nyrt_a = format!("{}/libnyrt.a", nyrt_release); + if which::which("lld-link").is_ok() { + let mut args: Vec = Vec::new(); + args.push(format!("/OUT:{}", out_path)); + args.push(obj_path.to_string()); + // Provide LIBPATH and library name (prefer nyrt.lib) + args.push(format!("/LIBPATH:{}", nyrt_release)); + if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); } + // lld-link cannot consume .a directly; rely on .lib + let status = PCommand::new("lld-link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?; + if status.success() { return Ok(()); } + return Err(format!("lld-link failed: status {:?}", status.code())); + } + if which::which("link").is_ok() { + let mut args: Vec = Vec::new(); + args.push(format!("/OUT:{}", out_path)); + args.push(obj_path.to_string()); + args.push(format!("/LIBPATH:{}", nyrt_release)); + if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); } + let status = PCommand::new("link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?; + if status.success() { return Ok(()); } + return Err(format!("link.exe failed: status {:?}", status.code())); + } + // Fallback: try cc with MinGW-like flags + let status = PCommand::new("cc") + .args([obj_path]) + .args(["-L", &format!("{}/target/release", nyrt_dir)]) + .args(["-lnyrt", "-o", out_path]) + .status().map_err(|e| e.to_string())?; + if status.success() { return Ok(()); } + return Err(format!("cc link failed: status {:?}", status.code())); + } + #[cfg(not(target_os = "windows"))] + { + let status = PCommand::new("cc") + .args([obj_path]) + .args(["-L", "target/release"]) + .args(["-L", &format!("{}/target/release", nyrt_dir)]) + .args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive"]) + .args(["-lpthread", "-ldl", "-lm", "-o", out_path]) + .status().map_err(|e| e.to_string())?; + if status.success() { Ok(()) } else { Err(format!("cc failed: status {:?}", status.code())) } + } +} diff --git a/src/cli.rs b/src/cli.rs index d1a61490..86814fe0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -103,6 +103,12 @@ impl CliConfig { .value_name("FILE") .help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter") ) + .arg( + Arg::new("ny-compiler-args") + .long("ny-compiler-args") + .value_name("ARGS") + .help("Pass additional args to selfhost child compiler (equivalent to NYASH_NY_COMPILER_CHILD_ARGS)") + ) .arg( Arg::new("debug-fuel") @@ -366,6 +372,10 @@ impl CliConfig { /// Convert ArgMatches to CliConfig fn from_matches(matches: &ArgMatches) -> Self { + // Side-effect: forward child args for selfhost compiler via env + if let Some(a) = matches.get_one::("ny-compiler-args") { + std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a); + } Self { file: matches.get_one::("file").cloned(), debug_fuel: parse_debug_fuel(matches.get_one::("debug-fuel").unwrap()), diff --git a/src/llvm_py/pyvm/vm.py b/src/llvm_py/pyvm/vm.py index 07817937..7182c25f 100644 --- a/src/llvm_py/pyvm/vm.py +++ b/src/llvm_py/pyvm/vm.py @@ -124,17 +124,31 @@ class PyVM: op = inst.get("op") if op == "phi": - # incoming: [[vid, pred_bid], ...] + # incoming: prefer [[vid, pred_bid]], but accept [pred_bid, vid] robustly incoming = inst.get("incoming", []) chosen: Any = None - # Prefer predecessor match; otherwise fallback to first - for vid, pb in incoming: - if prev is not None and int(pb) == int(prev): - chosen = regs.get(int(vid)) + for pair in incoming: + if not isinstance(pair, (list, tuple)) or len(pair) < 2: + continue + a, b = pair[0], pair[1] + # Case 1: [vid, pred] + if prev is not None and int(b) == int(prev) and int(a) in regs: + chosen = regs.get(int(a)) + break + # Case 2: [pred, vid] + if prev is not None and int(a) == int(prev) and int(b) in regs: + chosen = regs.get(int(b)) break if chosen is None and incoming: - vid, _ = incoming[0] - chosen = regs.get(int(vid)) + # Fallback to first element that resolves to a known vid + for pair in incoming: + if not isinstance(pair, (list, tuple)) or len(pair) < 2: + continue + a, b = pair[0], pair[1] + if int(a) in regs: + chosen = regs.get(int(a)); break + if int(b) in regs: + chosen = regs.get(int(b)); break self._set(regs, inst.get("dst"), chosen) i += 1 continue @@ -229,6 +243,30 @@ class PyVM: i += 1 continue + if op == "unop": + kind = inst.get("kind") + src = self._read(regs, inst.get("src")) + out: Any + if kind == "neg": + if isinstance(src, (int, float)): + out = -src + elif src is None: + out = 0 + else: + try: + out = -int(src) + except Exception: + out = 0 + elif kind == "not": + out = 0 if self._truthy(src) else 1 + elif kind == "bitnot": + out = ~int(src) if src is not None else -1 + else: + out = None + self._set(regs, inst.get("dst"), out) + i += 1 + continue + if op == "newbox": btype = inst.get("type") if btype == "ConsoleBox": @@ -236,6 +274,10 @@ class PyVM: elif btype == "StringBox": # empty string instance val = "" + elif btype == "ArrayBox": + val = {"__box__": "ArrayBox", "__arr": []} + elif btype == "MapBox": + val = {"__box__": "MapBox", "__map": {}} else: # Unknown box -> opaque val = {"__box__": btype} @@ -302,6 +344,58 @@ class PyVM: out = os.path.join(base, rel) else: out = None + # ArrayBox minimal methods + elif isinstance(recv, dict) and recv.get("__box__") == "ArrayBox": + arr = recv.get("__arr", []) + if method in ("len", "size"): + out = len(arr) + elif method == "get": + idx = int(args[0]) if args else 0 + out = arr[idx] if 0 <= idx < len(arr) else None + elif method == "set": + idx = int(args[0]) if len(args) > 0 else 0 + val = args[1] if len(args) > 1 else None + if 0 <= idx < len(arr): + arr[idx] = val + elif idx == len(arr): + arr.append(val) + else: + # extend with None up to idx, then set + while len(arr) < idx: + arr.append(None) + arr.append(val) + out = 0 + elif method == "push": + val = args[0] if args else None + arr.append(val) + out = len(arr) + elif method == "toString": + out = "[" + ",".join(str(x) for x in arr) + "]" + else: + out = None + recv["__arr"] = arr + # MapBox minimal methods + elif isinstance(recv, dict) and recv.get("__box__") == "MapBox": + m = recv.get("__map", {}) + if method == "size": + out = len(m) + elif method == "has": + key = str(args[0]) if args else "" + out = 1 if key in m else 0 + elif method == "get": + key = str(args[0]) if args else "" + out = m.get(key) + elif method == "set": + key = str(args[0]) if len(args) > 0 else "" + val = args[1] if len(args) > 1 else None + m[key] = val + out = 0 + elif method == "toString": + items = ",".join(f"{k}:{m[k]}" for k in m) + out = "{" + items + "}" + else: + out = None + recv["__map"] = m elif method == "esc_json": # Escape backslash and double-quote in the given string argument s = args[0] if args else "" diff --git a/src/runner/json_v0_bridge.rs b/src/runner/json_v0_bridge.rs index 2cd46dfc..7e8667ca 100644 --- a/src/runner/json_v0_bridge.rs +++ b/src/runner/json_v0_bridge.rs @@ -180,6 +180,7 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( "shortcircuit", "" ); + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] op={} rhs_bb={} fall_bb={} merge_bb={}", if is_and {"and"} else {"or"}, rhs_bb.0, fall_bb.0, merge_bb.0); } // false/true constant in fall_bb depending on op let cdst = f.next_value_id(); if let Some(bb) = f.get_block_mut(fall_bb) { @@ -187,15 +188,16 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval }); bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } - // evaluate rhs in rhs_bb - let (rval, _rhs_end) = lower_expr(f, rhs_bb, rhs)?; - if let Some(bb) = f.get_block_mut(rhs_bb) { + // evaluate rhs starting at rhs_bb and ensure the terminal block jumps to merge + let (rval, rhs_end) = lower_expr(f, rhs_bb, rhs)?; + if let Some(bb) = f.get_block_mut(rhs_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } } - // merge with phi + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] rhs_end={} jump->merge_bb={}", rhs_end.0, merge_bb.0); } + // merge with phi (use actual predecessors rhs_end and fall_bb) let out = f.next_value_id(); if let Some(bb) = f.get_block_mut(merge_bb) { - bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_bb, rval), (fall_bb, cdst)] }); + bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_end, rval), (fall_bb, cdst)] }); } Ok((out, merge_bb)) } @@ -213,6 +215,16 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( Ok((dst, cur)) } ExprV0::Method { recv, method, args } => { + // Heuristic: new ConsoleBox().println(x) → externcall env.console.log(x) + let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); + if recv_is_console_new && (method == "println" || method == "print" || method == "log") { + let (arg_ids, cur2) = lower_args(f, cur_bb, args)?; + let dst = f.next_value_id(); + if let Some(bb) = f.get_block_mut(cur2) { + bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ }); + } + return Ok((dst, cur2)); + } let (recv_v, cur) = lower_expr(f, cur_bb, recv)?; let (arg_ids, cur2) = lower_args(f, cur, args)?; let dst = f.next_value_id(); @@ -275,6 +287,15 @@ fn lower_expr_with_vars( Ok((dst, cur)) } ExprV0::Method { recv, method, args } => { + let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); + if recv_is_console_new && (method == "println" || method == "print" || method == "log") { + let (arg_ids, cur2) = lower_args_with_vars(f, cur_bb, args, vars)?; + let dst = f.next_value_id(); + if let Some(bb) = f.get_block_mut(cur2) { + bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ }); + } + return Ok((dst, cur2)); + } let (recv_v, cur) = lower_expr_with_vars(f, cur_bb, recv, vars)?; let (arg_ids, cur2) = lower_args_with_vars(f, cur, args, vars)?; let dst = f.next_value_id(); @@ -341,10 +362,10 @@ fn lower_expr_with_vars( bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval }); bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } - let (rval, _rhs_end) = lower_expr_with_vars(f, rhs_bb, rhs, vars)?; - if let Some(bb) = f.get_block_mut(rhs_bb) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } } + let (rval, rhs_end) = lower_expr_with_vars(f, rhs_bb, rhs, vars)?; + if let Some(bb) = f.get_block_mut(rhs_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } } let out = f.next_value_id(); - if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_bb, rval), (fall_bb, cdst)] }); } + if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_end, rval), (fall_bb, cdst)] }); } Ok((out, merge_bb)) } _ => lower_expr(f, cur_bb, e), @@ -603,7 +624,8 @@ fn lex(input: &str) -> Result, String> { let mut toks = Vec::new(); while i < n { let c = bytes[i] as char; - if c.is_whitespace() { i += 1; continue; } + // Treat semicolon as whitespace (Stage-1 minimal ASI: optional ';') + if c.is_whitespace() || c == ';' { i += 1; continue; } match c { '+' => { toks.push(Tok::Plus); i+=1; } '-' => { toks.push(Tok::Minus); i+=1; } diff --git a/src/runner/mir_json_emit.rs b/src/runner/mir_json_emit.rs index c5d8c23f..a17af098 100644 --- a/src/runner/mir_json_emit.rs +++ b/src/runner/mir_json_emit.rs @@ -44,6 +44,10 @@ pub fn emit_mir_json_for_harness( // Non-PHI for inst in &bb.instructions { match inst { + I::UnaryOp { dst, op, operand } => { + let kind = match op { nyash_rust::mir::UnaryOp::Neg => "neg", nyash_rust::mir::UnaryOp::Not => "not", nyash_rust::mir::UnaryOp::BitNot => "bitnot" }; + insts.push(json!({"op":"unop","kind": kind, "src": operand.as_u32(), "dst": dst.as_u32()})); + } I::Const { dst, value } => { match value { nyash_rust::mir::ConstValue::Integer(i) => { @@ -185,3 +189,147 @@ pub fn emit_mir_json_for_harness( std::fs::write(path, serde_json::to_string_pretty(&root).unwrap()) .map_err(|e| format!("write mir json: {}", e)) } + +/// Variant for the bin crate's local MIR type +pub fn emit_mir_json_for_harness_bin( + module: &crate::mir::MirModule, + path: &std::path::Path, +) -> Result<(), String> { + use crate::mir::{MirInstruction as I, BinaryOp as B, CompareOp as C, MirType}; + let mut funs = Vec::new(); + for (name, f) in &module.functions { + let mut blocks = Vec::new(); + let mut ids: Vec<_> = f.blocks.keys().copied().collect(); + ids.sort(); + for bid in ids { + if let Some(bb) = f.blocks.get(&bid) { + let mut insts = Vec::new(); + for inst in &bb.instructions { + if let I::Phi { dst, inputs } = inst { + let incoming: Vec<_> = inputs + .iter() + .map(|(b, v)| json!([v.as_u32(), b.as_u32()])) + .collect(); + let all_str = inputs.iter().all(|(_b, v)| { + match f.metadata.value_types.get(v) { + Some(MirType::String) => true, + Some(MirType::Box(bt)) if bt == "StringBox" => true, + _ => false, + } + }); + if all_str { + insts.push(json!({ + "op":"phi","dst": dst.as_u32(), "incoming": incoming, + "dst_type": {"kind":"handle","box_type":"StringBox"} + })); + } else { + insts.push(json!({"op":"phi","dst": dst.as_u32(), "incoming": incoming})); + } + } + } + for inst in &bb.instructions { + match inst { + I::Const { dst, value } => { + match value { + crate::mir::ConstValue::Integer(i) => { + insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "i64", "value": i}})); + } + crate::mir::ConstValue::Float(fv) => { + insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "f64", "value": fv}})); + } + crate::mir::ConstValue::Bool(b) => { + insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "i64", "value": if *b {1} else {0}}})); + } + crate::mir::ConstValue::String(s) => { + insts.push(json!({ + "op":"const", + "dst": dst.as_u32(), + "value": { + "type": {"kind":"handle","box_type":"StringBox"}, + "value": s + } + })); + } + crate::mir::ConstValue::Null | crate::mir::ConstValue::Void => { + insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "void", "value": 0}})); + } + } + } + I::BinOp { dst, op, lhs, rhs } => { + let op_s = match op { B::Add=>"+",B::Sub=>"-",B::Mul=>"*",B::Div=>"/",B::Mod=>"%",B::BitAnd=>"&",B::BitOr=>"|",B::BitXor=>"^",B::Shl=>"<<",B::Shr=>">>",B::And=>"&",B::Or=>"|"}; + let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()}); + if matches!(op, B::Add) { + let lhs_is_str = match f.metadata.value_types.get(lhs) { + Some(MirType::String) => true, + Some(MirType::Box(bt)) if bt == "StringBox" => true, + _ => false, + }; + let rhs_is_str = match f.metadata.value_types.get(rhs) { + Some(MirType::String) => true, + Some(MirType::Box(bt)) if bt == "StringBox" => true, + _ => false, + }; + if lhs_is_str || rhs_is_str { + obj["dst_type"] = json!({"kind":"handle","box_type":"StringBox"}); + } + } + insts.push(obj); + } + I::Compare { dst, op, lhs, rhs } => { + let op_s = match op { C::Eq=>"==",C::Ne=>"!=",C::Lt=>"<",C::Le=>"<=",C::Gt=>">",C::Ge=>">=" }; + insts.push(json!({"op":"compare","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()})); + } + I::ExternCall { dst, iface_name, method_name, args, .. } => { + let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let mut obj = json!({ + "op":"externcall","func": format!("{}.{}", iface_name, method_name), "args": args_a, + "dst": dst.map(|d| d.as_u32()), + }); + if iface_name == "env.console" { if dst.is_some() { obj["dst_type"] = json!("i64"); } } + insts.push(obj); + } + I::BoxCall { dst, box_val, method, args, .. } => { + let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let mut obj = json!({ + "op":"boxcall","box": box_val.as_u32(), "method": method, "args": args_a, "dst": dst.map(|d| d.as_u32()) + }); + let m = method.as_str(); + let dst_ty = if m == "substring" || m == "dirname" || m == "join" || m == "read_all" || m == "read" { + Some(json!({"kind":"handle","box_type":"StringBox"})) + } else if m == "length" || m == "lastIndexOf" { + Some(json!("i64")) + } else { None }; + if let Some(t) = dst_ty { obj["dst_type"] = t; } + insts.push(obj); + } + I::NewBox { dst, box_type, args } => { + let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + insts.push(json!({"op":"newbox","type": box_type, "args": args_a, "dst": dst.as_u32()})); + } + I::Branch { condition, then_bb, else_bb } => { + insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})); + } + I::Jump { target } => { + insts.push(json!({"op":"jump","target": target.as_u32()})); + } + I::Return { value } => { + insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})); + } + _ => { } + } + } + if let Some(term) = &bb.terminator { match term { + I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})), + I::Jump { target } => insts.push(json!({"op":"jump","target": target.as_u32()})), + I::Branch { condition, then_bb, else_bb } => insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})), + _ => {} } } + blocks.push(json!({"id": bid.as_u32(), "instructions": insts})); + } + } + let params: Vec<_> = f.params.iter().map(|v| v.as_u32()).collect(); + funs.push(json!({"name": name, "params": params, "blocks": blocks})); + } + let root = json!({"functions": funs}); + std::fs::write(path, serde_json::to_string_pretty(&root).unwrap()) + .map_err(|e| format!("write mir json: {}", e)) +} diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 8f91d188..e84a286e 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -126,6 +126,30 @@ impl NyashRunner { for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } } } if using_paths.is_empty() { using_paths.extend(["apps","lib","."].into_iter().map(|s| s.to_string())); } + // nyash.toml: load [modules] and [using.paths] + if std::path::Path::new("nyash.toml").exists() { + if let Ok(text) = fs::read_to_string("nyash.toml") { + if let Ok(doc) = toml::from_str::(&text) { + if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) { + for (k, v) in mods.iter() { + if let Some(path) = v.as_str() { + pending_modules.push((k.to_string(), path.to_string())); + } + } + } + if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) { + if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) { + for p in paths_arr { + if let Some(s) = p.as_str() { + let s = s.trim(); + if !s.is_empty() { using_paths.push(s.to_string()); } + } + } + } + } + } + } + } // Modules mapping from env (e.g., FOO=path) if let Ok(ms) = std::env::var("NYASH_MODULES") { for ent in ms.split(',') { @@ -176,7 +200,54 @@ impl NyashRunner { Ok(module) => { // Optional dump via env verbose json_v0_bridge::maybe_dump_mir(&module); - // Execute via MIR interpreter + // Optional: delegate to PyVM when NYASH_PIPE_USE_PYVM=1 + if std::env::var("NYASH_PIPE_USE_PYVM").ok().as_deref() == Some("1") { + let py = which::which("python3").ok(); + if let Some(py3) = py { + let runner = std::path::Path::new("tools/pyvm_runner.py"); + if runner.exists() { + // Emit MIR(JSON) for PyVM + let tmp_dir = std::path::Path::new("tmp"); + let _ = std::fs::create_dir_all(tmp_dir); + let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); + if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { + eprintln!("❌ PyVM MIR JSON emit error: {}", e); + std::process::exit(1); + } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[Bridge] using PyVM (pipe) → {}", mir_json_path.display()); + } + // Determine entry function hint (prefer Main.main if present) + let entry = if module.functions.contains_key("Main.main") { "Main.main" } + else if module.functions.contains_key("main") { "main" } else { "Main.main" }; + let status = std::process::Command::new(py3) + .args([ + runner.to_string_lossy().as_ref(), + "--in", + &mir_json_path.display().to_string(), + "--entry", + entry, + ]) + .status() + .map_err(|e| format!("spawn pyvm: {}", e)) + .unwrap(); + let code = status.code().unwrap_or(1); + if !status.success() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("❌ PyVM (pipe) failed (status={})", code); + } + } + std::process::exit(code); + } else { + eprintln!("❌ PyVM runner not found: {}", runner.display()); + std::process::exit(1); + } + } else { + eprintln!("❌ python3 not found in PATH. Install Python 3 to use PyVM with --ny-parser-pipe."); + std::process::exit(1); + } + } + // Default: Execute via MIR interpreter self.execute_mir_module(&module); return; } diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index 53720485..58880a0d 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -4,6 +4,10 @@ use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter}; // Use the library crate's plugin init module rather than the bin crate root use nyash_rust::runner_plugin_init; use std::{fs, process}; +use std::io::Read; +use std::process::Stdio; +use std::time::{Duration, Instant}; +use std::thread::sleep; // limited directory walk: add matching files ending with .nyash and given leaf name fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec) { @@ -157,68 +161,297 @@ impl NyashRunner { Ok(c) => c, Err(e) => { eprintln!("[ny-compiler] read error: {}", e); return false; } }; - // Write to tmp/ny_parser_input.ny (as expected by Ny parser v0) + // Optional Phase-15: strip `using` lines and register modules (same policy as execute_nyash_file) + let enable_using = std::env::var("NYASH_ENABLE_USING").ok().as_deref() == Some("1"); + let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code); + if enable_using { + let mut out = String::with_capacity(code.len()); + let mut used_names: Vec<(String, Option)> = Vec::new(); + for line in code.lines() { + let t = line.trim_start(); + if t.starts_with("using ") { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[using] stripped(line→selfhost): {}", line); + } + let rest0 = t.strip_prefix("using ").unwrap().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_names.push((name, Some(path))); + } else { + used_names.push((target, alias)); + } + continue; + } + out.push_str(line); + out.push('\n'); + } + // Register modules into minimal registry with best-effort path resolution + for (ns_or_alias, alias_or_path) in used_names { + if let Some(path) = alias_or_path { + let sb = crate::box_trait::StringBox::new(path); + crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb)); + } else { + let rel = format!("apps/{}.nyash", ns_or_alias.replace('.', "/")); + let exists = std::path::Path::new(&rel).exists(); + let path_or_ns = if exists { rel } else { ns_or_alias.clone() }; + let sb = crate::box_trait::StringBox::new(path_or_ns); + crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb)); + } + } + code_ref = std::borrow::Cow::Owned(out); + } + + // Write to tmp/ny_parser_input.ny (as expected by Ny parser v0), unless forced to reuse existing tmp + let use_tmp_only = std::env::var("NYASH_NY_COMPILER_USE_TMP_ONLY").ok().as_deref() == Some("1"); let tmp_dir = std::path::Path::new("tmp"); if let Err(e) = std::fs::create_dir_all(tmp_dir) { eprintln!("[ny-compiler] mkdir tmp failed: {}", e); return false; } let tmp_path = tmp_dir.join("ny_parser_input.ny"); - match std::fs::File::create(&tmp_path) { - Ok(mut f) => { - if let Err(e) = f.write_all(code.as_bytes()) { - eprintln!("[ny-compiler] write tmp failed: {}", e); + if !use_tmp_only { + match std::fs::File::create(&tmp_path) { + Ok(mut f) => { + if let Err(e) = f.write_all(code_ref.as_bytes()) { + eprintln!("[ny-compiler] write tmp failed: {}", e); + return false; + } + } + Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; } + } + } + // EXE-first: if requested, try external parser EXE (nyash_compiler) + if std::env::var("NYASH_USE_NY_COMPILER_EXE").ok().as_deref() == Some("1") { + // Resolve parser EXE path + let exe_path = if let Ok(p) = std::env::var("NYASH_NY_COMPILER_EXE_PATH") { + std::path::PathBuf::from(p) + } else { + let mut p = std::path::PathBuf::from("dist/nyash_compiler"); + #[cfg(windows)] + { p.push("nyash_compiler.exe"); } + #[cfg(not(windows))] + { p.push("nyash_compiler"); } + if !p.exists() { + // Try PATH + if let Ok(w) = which::which("nyash_compiler") { w } else { p } + } else { p } + }; + if exe_path.exists() { + let mut cmd = std::process::Command::new(&exe_path); + // Prefer passing the original filename directly (parser EXE accepts positional path) + cmd.arg(filename); + // Gates + if std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().as_deref() == Some("1") { cmd.arg("--min-json"); } + if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") { cmd.arg("--read-tmp"); } + if let Ok(raw) = std::env::var("NYASH_NY_COMPILER_CHILD_ARGS") { for tok in raw.split_whitespace() { cmd.arg(tok); } } + let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000); + let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); + let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => { eprintln!("[ny-compiler] exe spawn failed: {}", e); return false; } }; + let mut ch_stdout = child.stdout.take(); + let mut ch_stderr = child.stderr.take(); + let start = Instant::now(); + let mut timed_out = false; + loop { + match child.try_wait() { + Ok(Some(_status)) => { break; } + Ok(None) => { + if start.elapsed() >= Duration::from_millis(timeout_ms) { let _ = child.kill(); let _ = child.wait(); timed_out = true; break; } + sleep(Duration::from_millis(10)); + } + Err(e) => { eprintln!("[ny-compiler] exe wait error: {}", e); return false; } + } + } + let mut out_buf = Vec::new(); + let mut err_buf = Vec::new(); + if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); } + if let Some(mut s) = ch_stderr { let _ = s.read_to_end(&mut err_buf); } + if timed_out { + let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::(); + eprintln!("[ny-compiler] exe timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n")); return false; } + let stdout = match String::from_utf8(out_buf) { Ok(s) => s, Err(_) => String::new() }; + let mut json_line = String::new(); + for line in stdout.lines() { let t = line.trim(); if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; } } + if json_line.is_empty() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + let head: String = stdout.chars().take(200).collect(); + let errh: String = String::from_utf8_lossy(&err_buf).chars().take(200).collect(); + eprintln!("[ny-compiler] exe produced no JSON; stdout(head)='{}' stderr(head)='{}'", head.replace('\n', "\\n"), errh.replace('\n', "\\n")); + } + return false; + } + // Parse JSON v0 → MIR module + match json_v0_bridge::parse_json_v0_to_module(&json_line) { + Ok(module) => { + println!("🚀 Ny compiler EXE path (ny→json_v0) ON"); + json_v0_bridge::maybe_dump_mir(&module); + let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1"; + if emit_only { + return false; + } else { + self.execute_mir_module(&module); + return true; + } + } + Err(e) => { eprintln!("[ny-compiler] JSON parse failed (exe): {}", e); return false; } + } + } else { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[ny-compiler] exe not found at {}", exe_path.display()); } } - Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; } } + // Locate current exe to invoke Ny VM for the Ny parser program let exe = match std::env::current_exe() { Ok(p) => p, Err(e) => { eprintln!("[ny-compiler] current_exe failed: {}", e); return false; } }; - // Prefer new selfhost-compiler entry; fallback to legacy ny_parser_v0 + // Select selfhost compiler entry + // NYASH_NY_COMPILER_PREF=legacy|new|auto (default auto: prefer new when exists) let cand_new = std::path::Path::new("apps/selfhost-compiler/compiler.nyash"); let cand_old = std::path::Path::new("apps/selfhost/parser/ny_parser_v0/main.nyash"); - let parser_prog = if cand_new.exists() { cand_new } else { cand_old }; + let pref = std::env::var("NYASH_NY_COMPILER_PREF").ok(); + let parser_prog = match pref.as_deref() { + Some("legacy") => cand_old, + Some("new") => cand_new, + _ => if cand_new.exists() { cand_new } else { cand_old }, + }; if !parser_prog.exists() { eprintln!("[ny-compiler] compiler program not found: {}", parser_prog.display()); return false; } let mut cmd = std::process::Command::new(exe); cmd.arg("--backend").arg("vm").arg(parser_prog); + // Gate: pass script args to child parser program + // - NYASH_NY_COMPILER_MIN_JSON=1 → "-- --min-json" + // - NYASH_SELFHOST_READ_TMP=1 → "-- --read-tmp" + // - NYASH_NY_COMPILER_CHILD_ARGS: additional raw args (split by whitespace) + let min_json = std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().unwrap_or_else(|| "0".to_string()); + if min_json == "1" { cmd.arg("--").arg("--min-json"); } + if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") { + cmd.arg("--").arg("--read-tmp"); + } + if let Ok(raw) = std::env::var("NYASH_NY_COMPILER_CHILD_ARGS") { + for tok in raw.split_whitespace() { cmd.arg(tok); } + } // Propagate minimal env; disable plugins to reduce noise cmd.env_remove("NYASH_USE_NY_COMPILER"); + cmd.env_remove("NYASH_CLI_VERBOSE"); // Suppress parent runner's result printing in child cmd.env("NYASH_JSON_ONLY", "1"); - let out = match cmd.output() { - Ok(o) => o, + // Propagate optional gates to child (if present) + if let Ok(v) = std::env::var("NYASH_JSON_INCLUDE_USINGS") { cmd.env("NYASH_JSON_INCLUDE_USINGS", v); } + if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); } + // Child timeout guard (Hotfix for potential infinite loop in child Ny parser) + // Config: NYASH_NY_COMPILER_TIMEOUT_MS (default 2000ms) + let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS") + .ok() + .and_then(|s| s.parse::().ok()) + .unwrap_or(2000); + let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); + let mut child = match cmd.spawn() { + Ok(c) => c, Err(e) => { eprintln!("[ny-compiler] spawn failed: {}", e); return false; } }; - if !out.status.success() { - if let Ok(s) = String::from_utf8(out.stderr) { eprintln!("[ny-compiler] parser stderr:\n{}", s); } - return false; + let mut ch_stdout = child.stdout.take(); + let mut ch_stderr = child.stderr.take(); + let start = Instant::now(); + let mut timed_out = false; + loop { + match child.try_wait() { + Ok(Some(_status)) => { break; } + Ok(None) => { + if start.elapsed() >= Duration::from_millis(timeout_ms) { + let _ = child.kill(); + let _ = child.wait(); + timed_out = true; + break; + } + sleep(Duration::from_millis(10)); + } + Err(e) => { eprintln!("[ny-compiler] wait error: {}", e); return false; } + } + } + // Collect any available output + let mut out_buf = Vec::new(); + let mut err_buf = Vec::new(); + if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); } + if let Some(mut s) = ch_stderr { let _ = s.read_to_end(&mut err_buf); } + if timed_out { + let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::(); + eprintln!("[ny-compiler] child timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n")); + } + let stdout = match String::from_utf8(out_buf) { Ok(s) => s, Err(_) => String::new() }; + if timed_out { + // Fall back path will be taken below when json_line remains empty + } else if let Ok(s) = String::from_utf8(err_buf.clone()) { + // If the child exited non-zero and printed stderr, surface it and fallback + // We cannot easily access ExitStatus here after try_wait loop; rely on JSON detection path. + if s.trim().len() > 0 && std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[ny-compiler] parser stderr:\n{}", s); + } } - let stdout = match String::from_utf8(out.stdout) { Ok(s) => s, Err(_) => String::new() }; let mut json_line = String::new(); for line in stdout.lines() { let t = line.trim(); - if t.starts_with('{') && t.contains("\"version\":0") { json_line = t.to_string(); break; } + if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; } } if json_line.is_empty() { + // Fallback: try Python MVP parser to produce JSON v0 from the same tmp source. if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { let head: String = stdout.chars().take(200).collect(); eprintln!("[ny-compiler] JSON not found in child stdout (head): {}", head.replace('\n', "\\n")); + eprintln!("[ny-compiler] falling back to tools/ny_parser_mvp.py for this input"); } - return false; + let py = which::which("python3").ok(); + if let Some(py3) = py { + let script = std::path::Path::new("tools/ny_parser_mvp.py"); + if script.exists() { + let out2 = std::process::Command::new(py3) + .arg(script) + .arg(tmp_path.as_os_str()) + .output(); + match out2 { + Ok(o2) if o2.status.success() => { + if let Ok(s2) = String::from_utf8(o2.stdout) { + // pick the first JSON-ish line + for line in s2.lines() { + let t = line.trim(); + if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; } + } + } + } + Ok(o2) => { + let msg = String::from_utf8_lossy(&o2.stderr); + eprintln!("[ny-compiler] python parser failed: {}", msg); + } + Err(e2) => { + eprintln!("[ny-compiler] spawn python3 failed: {}", e2); + } + } + } + } + if json_line.is_empty() { return false; } } // Parse JSON v0 → MIR module match json_v0_bridge::parse_json_v0_to_module(&json_line) { Ok(module) => { - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - println!("🚀 Ny compiler MVP (ny→json_v0) path ON"); - } + let emit_only_default = "1".to_string(); + let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or(emit_only_default) == "1"; + println!("🚀 Ny compiler MVP (ny→json_v0) path ON"); json_v0_bridge::maybe_dump_mir(&module); - self.execute_mir_module(&module); - true + if emit_only { + // Do not execute; fall back to default path to keep final Result unaffected (Stage‑1 policy) + false + } else { + self.execute_mir_module(&module); + true + } } Err(e) => { eprintln!("[ny-compiler] JSON parse failed: {}", e); diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index e84fd6da..00a66d11 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -6,6 +6,8 @@ use std::sync::Arc; impl NyashRunner { /// Execute VM mode (split) pub(crate) fn execute_vm_mode(&self, filename: &str) { + // Quiet mode for child pipelines (e.g., selfhost compiler JSON emit) + let quiet_pipe = std::env::var("NYASH_JSON_ONLY").ok().as_deref() == Some("1"); // Enforce plugin-first policy for VM on this branch (deterministic): // - Initialize plugin host if not yet loaded // - Prefer plugin implementations for core boxes @@ -176,7 +178,7 @@ impl NyashRunner { let mut vm = VM::with_runtime(runtime); match vm.execute_module(&module_vm) { Ok(result) => { - println!("✅ VM execution completed successfully!"); + if !quiet_pipe { println!("✅ VM execution completed successfully!"); } // Pretty-print with coercions for plugin-backed values // Prefer MIR signature when available, but fall back to runtime coercions to keep VM/JIT consistent. let (ety, sval) = if let Some(func) = compile_result.module.functions.get("main") { @@ -237,8 +239,10 @@ impl NyashRunner { ("String", s) } else { (result.type_name(), result.to_string_box().value) } }; - println!("ResultType(MIR): {}", ety); - println!("Result: {}", sval); + if !quiet_pipe { + println!("ResultType(MIR): {}", ety); + println!("Result: {}", sval); + } }, Err(e) => { eprintln!("❌ VM execution error: {}", e); process::exit(1); } } diff --git a/tools/build_compiler_exe.sh b/tools/build_compiler_exe.sh new file mode 100644 index 00000000..11d471da --- /dev/null +++ b/tools/build_compiler_exe.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]]; then + set -x +fi + +usage() { + cat << USAGE +Usage: tools/build_compiler_exe.sh [-o ] [--no-pack] + +Builds the selfhost Nyash parser as a native EXE using the LLVM harness, +and stages a runnable bundle with required plugin (FileBox) and nyash.toml. + +Options: + -o Output executable name (default: nyash_compiler) + --no-pack Do not create dist/ bundle; only build the executable in repo root + +Examples: + tools/build_compiler_exe.sh + tools/build_compiler_exe.sh -o nyc +USAGE +} + +OUT="nyash_compiler" +PACK=1 +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage; exit 0 ;; + -o) OUT="$2"; shift 2 ;; + --no-pack) PACK=0; shift ;; + *) echo "unknown arg: $1" >&2; usage; exit 1 ;; + esac +done + +if ! command -v llvm-config-18 >/dev/null 2>&1; then + echo "error: llvm-config-18 not found (install LLVM 18 dev)." >&2 + exit 2 +fi + +# 1) Build nyash with LLVM harness +echo "[1/4] Building nyash (LLVM harness) ..." +_LLVMPREFIX=$(llvm-config-18 --prefix) +LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + cargo build --release -j 24 --features llvm >/dev/null + +# 2) Emit + link compiler.nyash → EXE +echo "[2/4] Emitting + linking selfhost compiler ..." +tools/build_llvm.sh apps/selfhost-compiler/compiler.nyash -o "$OUT" + +if [[ "$PACK" == "0" ]]; then + echo "✅ Built: ./$OUT" + exit 0 +fi + +# 3) Build FileBox plugin (required when reading files) +echo "[3/4] Building FileBox plugin ..." +unset NYASH_DISABLE_PLUGINS || true +cargo build -p nyash-filebox-plugin --release >/dev/null + +# 4) Stage dist/ bundle +echo "[4/4] Staging dist bundle ..." +DIST="dist/nyash_compiler" +rm -rf "$DIST" +mkdir -p "$DIST/plugins/nyash-filebox-plugin/target/release" "$DIST/tmp" +cp -f "$OUT" "$DIST/" + +# Copy plugin binary (platform-specific extension). Copy entire release dir for safety. +cp -a plugins/nyash-filebox-plugin/target/release/. "$DIST/plugins/nyash-filebox-plugin/target/release/" || true + +# Minimal nyash.toml for runtime (FileBox only) +cat > "$DIST/nyash.toml" << 'TOML' +[libraries] +[libraries."libnyash_filebox_plugin"] +boxes = ["FileBox"] +path = "./plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin" + +[libraries."libnyash_filebox_plugin".FileBox] +type_id = 6 + +[libraries."libnyash_filebox_plugin".FileBox.methods] +birth = { method_id = 0 } +open = { method_id = 1, args = ["path", "mode"] } +read = { method_id = 2 } +write = { method_id = 3, args = ["data"] } +close = { method_id = 4 } +fini = { method_id = 4294967295 } +TOML + +echo "✅ Done: $DIST" +echo " Usage:" +echo " echo 'return 1+2*3' > $DIST/tmp/sample.nyash" +echo " (cd $DIST && ./$(basename "$OUT") tmp/sample.nyash > sample.json)" +echo " head -n1 sample.json" + +exit 0 + diff --git a/tools/dev_env.sh b/tools/dev_env.sh new file mode 100644 index 00000000..83f75049 --- /dev/null +++ b/tools/dev_env.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Nyash dev environment convenience script +# Usage: source tools/dev_env.sh [profile] +# Profiles: +# pyvm - Favor PyVM for VM and Bridge +# bridge - Bridge-only helpers (keep interpreter) +# reset - Unset variables set by this script + +set -euo pipefail + +activate_pyvm() { + export NYASH_DISABLE_PLUGINS=1 + export NYASH_VM_USE_PY=1 + export NYASH_PIPE_USE_PYVM=1 + export NYASH_NY_COMPILER_TIMEOUT_MS=${NYASH_NY_COMPILER_TIMEOUT_MS:-2000} + echo "[dev-env] PyVM profile activated" >&2 +} + +activate_bridge() { + export NYASH_DISABLE_PLUGINS=1 + unset NYASH_VM_USE_PY || true + export NYASH_NY_COMPILER_TIMEOUT_MS=${NYASH_NY_COMPILER_TIMEOUT_MS:-2000} + echo "[dev-env] Bridge profile activated (interpreter for pipe)" >&2 +} + +reset_env() { + unset NYASH_VM_USE_PY || true + unset NYASH_PIPE_USE_PYVM || true + unset NYASH_DISABLE_PLUGINS || true + unset NYASH_NY_COMPILER_TIMEOUT_MS || true + echo "[dev-env] environment reset" >&2 +} + +case "${1:-pyvm}" in + pyvm) activate_pyvm ;; + bridge) activate_bridge ;; + reset) reset_env ;; + *) echo "usage: source tools/dev_env.sh [pyvm|bridge|reset]" >&2 ;; +esac + diff --git a/tools/exe_first_runner_smoke.sh b/tools/exe_first_runner_smoke.sh new file mode 100644 index 00000000..45baecfb --- /dev/null +++ b/tools/exe_first_runner_smoke.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Runner EXE-first smoke: use nyash with NYASH_USE_NY_COMPILER_EXE=1 to parse via external EXE +set -euo pipefail +[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x + +ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +cd "$ROOT_DIR" + +echo "[1/4] Build parser EXE bundle ..." +tools/build_compiler_exe.sh >/dev/null + +echo "[2/4] Prepare sample source ..." +mkdir -p tmp +echo 'return 1+2*3' > tmp/exe_first_runner_smoke.nyash + +echo "[3/4] Run nyash with EXE-first parser ..." +cargo build --release >/dev/null +set +e +NYASH_USE_NY_COMPILER=1 NYASH_USE_NY_COMPILER_EXE=1 \ + ./target/release/nyash --backend vm tmp/exe_first_runner_smoke.nyash >/dev/null +RC=$? +set -e + +echo "[4/4] Verify exit code ..." +if [[ "$RC" -ne 7 ]]; then + echo "error: expected exit code 7, got $RC" >&2 + exit 3 +fi + +echo "✅ Runner EXE-first smoke passed" +exit 0 + diff --git a/tools/exe_first_smoke.sh b/tools/exe_first_smoke.sh new file mode 100644 index 00000000..6946baf7 --- /dev/null +++ b/tools/exe_first_smoke.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# EXE-first smoke: build the selfhost parser EXE and run a tiny program end-to-end. +set -euo pipefail + +if [[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]]; then set -x; fi + +ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +cd "$ROOT_DIR" + +echo "[1/4] Building parser EXE bundle ..." +tools/build_compiler_exe.sh >/dev/null + +echo "[2/4] Preparing sample source ..." +mkdir -p dist/nyash_compiler/tmp +echo 'return 1+2*3' > dist/nyash_compiler/tmp/sample_exe_smoke.nyash + +echo "[3/4] Running parser EXE → JSON ..." +(cd dist/nyash_compiler && ./nyash_compiler tmp/sample_exe_smoke.nyash > sample.json) + +if ! head -n1 dist/nyash_compiler/sample.json | grep -q '"kind":"Program"'; then + echo "error: JSON does not look like a Program" >&2 + exit 2 +fi + +echo "[4/4] Executing via bridge (pipe) to verify semantics ..." +# Keep core minimal and deterministic +export NYASH_DISABLE_PLUGINS=1 +set +e +cat dist/nyash_compiler/sample.json | ./target/release/nyash --ny-parser-pipe --backend vm >/dev/null +RC=$? +set -e +if [[ "$RC" -ne 7 ]]; then + echo "error: expected exit code 7, got $RC" >&2 + exit 3 +fi + +echo "✅ EXE-first smoke passed (parser EXE + bridge run)" +exit 0 + diff --git a/tools/mir_builder_exe_smoke.sh b/tools/mir_builder_exe_smoke.sh new file mode 100644 index 00000000..59d90a91 --- /dev/null +++ b/tools/mir_builder_exe_smoke.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# MIR Builder EXE smoke: Parser EXE -> JSON -> MIR builder (exe) -> run +set -euo pipefail +[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x + +ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +cd "$ROOT_DIR" + +echo "[1/5] Build parser EXE bundle ..." +tools/build_compiler_exe.sh >/dev/null + +echo "[2/5] Prepare sample source ..." +mkdir -p dist/nyash_compiler/tmp +echo 'return 1+2*3' > dist/nyash_compiler/tmp/sample_builder_smoke.nyash + +echo "[3/5] Run parser EXE to JSON ..." +(cd dist/nyash_compiler && ./nyash_compiler tmp/sample_builder_smoke.nyash > sample_builder.json) + +if ! head -n1 dist/nyash_compiler/sample_builder.json | grep -q '"kind":"Program"'; then + echo "error: JSON does not look like a Program" >&2 + exit 2 +fi + +echo "[4/5] Build EXE via MIR builder ..." +# Prefer Rust binary if available; fallback to shell wrapper +cargo build --release --features llvm >/dev/null +if [[ -x target/release/ny_mir_builder ]]; then + ./target/release/ny_mir_builder --in dist/nyash_compiler/sample_builder.json --emit exe -o ./__mir_builder_out +else + ./tools/ny_mir_builder.sh --in dist/nyash_compiler/sample_builder.json --emit exe -o ./__mir_builder_out +fi + +echo "[5/5] Run built EXE and verify ..." +set +e +./__mir_builder_out >/dev/null +RC=$? +set -e +rm -f ./__mir_builder_out +if [[ "$RC" -ne 7 ]]; then + echo "error: expected exit code 7, got $RC" >&2 + exit 3 +fi + +echo "✅ MIR builder EXE smoke passed (parser EXE → builder EXE → run)" +exit 0 diff --git a/tools/ny_mir_builder.sh b/tools/ny_mir_builder.sh new file mode 100644 index 00000000..5a6c9f78 --- /dev/null +++ b/tools/ny_mir_builder.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# ny_mir_builder.sh — Minimal MIR Builder CLI (shell wrapper) +# Purpose: consume Nyash JSON IR and emit {obj|exe|ll|json} using the existing nyash LLVM harness. + +set -euo pipefail +[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x + +usage() { + cat << USAGE +Usage: tools/ny_mir_builder.sh [--in |--stdin] [--emit {obj|exe|ll|json}] -o [--target ] [--nyrt ] [--quiet] [--verify-llvm] + +Notes: + - This is a Phase-15 shell wrapper that leverages the nyash LLVM harness. + - Input must be Nyash JSON IR (v0/v1). When --stdin is used, reads from stdin. + - For --emit exe, NyRT must be built (crates/nyrt). Use default paths if --nyrt omitted. +USAGE +} + +IN_MODE="stdin" # stdin | file +IN_FILE="" +EMIT="obj" # obj | exe | ll | json +OUT="" +TARGET="" +NYRT_DIR="" +VERIFY=0 +QUIET=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage; exit 0 ;; + --in) IN_MODE="file"; IN_FILE="$2"; shift 2 ;; + --stdin) IN_MODE="stdin"; shift ;; + --emit) EMIT="$2"; shift 2 ;; + -o) OUT="$2"; shift 2 ;; + --target) TARGET="$2"; shift 2 ;; + --nyrt) NYRT_DIR="$2"; shift 2 ;; + --verify-llvm) VERIFY=1; shift ;; + --quiet) QUIET=1; shift ;; + *) echo "unknown arg: $1" >&2; usage; exit 2 ;; + esac +done + +if [[ -z "$OUT" ]]; then + case "$EMIT" in + obj) OUT="$(pwd)/target/aot_objects/a.o" ;; + ll) OUT="$(pwd)/target/aot_objects/a.ll" ;; + exe) OUT="a.out" ;; + json) OUT="/dev/stdout" ;; + *) echo "error: invalid emit kind: $EMIT" >&2; exit 2 ;; + esac +fi + +if ! command -v llvm-config-18 >/dev/null 2>&1; then + echo "error: llvm-config-18 not found (install LLVM 18 dev)" >&2 + exit 3 +fi + +# Build nyash + NyRT as needed +_LLVMPREFIX=$(llvm-config-18 --prefix) +LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + cargo build --release -j 24 --features llvm >/dev/null +if [[ "$EMIT" == "exe" ]]; then + (cd crates/nyrt && cargo build --release -j 24 >/dev/null) +fi + +mkdir -p "$PWD/target/aot_objects" + +# Prepare input +_STDIN_BUF="" +if [[ "$IN_MODE" == "stdin" ]]; then + # Read all to a temp file to allow re-use + _TMP_JSON=$(mktemp) + cat > "$_TMP_JSON" + IN_FILE="$_TMP_JSON" +fi + +cleanup() { [[ -n "${_TMP_JSON:-}" && -f "$_TMP_JSON" ]] && rm -f "$_TMP_JSON" || true; } +trap cleanup EXIT + +case "$EMIT" in + json) + # Normalization placeholder: currently pass-through + cat "$IN_FILE" > "$OUT" + [[ "$QUIET" == "0" ]] && echo "OK json:$OUT" + ;; + ll) + # Ask nyash harness to dump LLVM IR (if supported via env) + export NYASH_LLVM_DUMP_LL=1 + export NYASH_LLVM_LL_OUT="$OUT" + if [[ "$VERIFY" == "1" ]]; then export NYASH_LLVM_VERIFY=1; fi + cat "$IN_FILE" | NYASH_LLVM_USE_HARNESS=1 LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + ./target/release/nyash --backend llvm --ny-parser-pipe >/dev/null || true + if [[ ! -f "$OUT" ]]; then echo "error: failed to produce $OUT" >&2; exit 4; fi + [[ "$QUIET" == "0" ]] && echo "OK ll:$OUT" + ;; + obj) + export NYASH_LLVM_OBJ_OUT="$OUT" + if [[ "$VERIFY" == "1" ]]; then export NYASH_LLVM_VERIFY=1; fi + rm -f "$OUT" + cat "$IN_FILE" | NYASH_LLVM_USE_HARNESS=1 LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + ./target/release/nyash --backend llvm --ny-parser-pipe >/dev/null || true + if [[ ! -f "$OUT" ]]; then echo "error: failed to produce $OUT" >&2; exit 4; fi + [[ "$QUIET" == "0" ]] && echo "OK obj:$OUT" + ;; + exe) + # Emit obj then link + OBJ="$PWD/target/aot_objects/__tmp_builder.o" + export NYASH_LLVM_OBJ_OUT="$OBJ" + if [[ "$VERIFY" == "1" ]]; then export NYASH_LLVM_VERIFY=1; fi + rm -f "$OBJ" + cat "$IN_FILE" | NYASH_LLVM_USE_HARNESS=1 LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + ./target/release/nyash --backend llvm --ny-parser-pipe >/dev/null || true + if [[ ! -f "$OBJ" ]]; then echo "error: failed to produce object $OBJ" >&2; exit 4; fi + # Link with NyRT + NYRT_BASE=${NYRT_DIR:-"$PWD/crates/nyrt"} + cc "$OBJ" \ + -L target/release \ + -L "$NYRT_BASE/target/release" \ + -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive \ + -lpthread -ldl -lm -o "$OUT" + [[ "$QUIET" == "0" ]] && echo "OK exe:$OUT" + ;; + *) echo "error: invalid emit kind: $EMIT" >&2; exit 2 ;; +esac + +exit 0 + diff --git a/tools/ny_parser_mvp.py b/tools/ny_parser_mvp.py index a14fc79f..dcc71666 100644 --- a/tools/ny_parser_mvp.py +++ b/tools/ny_parser_mvp.py @@ -15,7 +15,8 @@ Grammar (subset): logic := compare (('&&'|'||') compare)* compare := sum (('=='|'!='|'<'|'>'|'<='|'>=') sum)? sum := term (('+'|'-') term)* - term := factor (('*'|'/') factor)* + term := unary (('*'|'/') unary)* + unary := '-' unary | factor factor := INT | STRING | IDENT call_tail* | '(' expr ')' | 'new' IDENT '(' args? ')' call_tail:= '.' IDENT '(' args? ')' # method | '(' args? ')' # function call @@ -126,11 +127,17 @@ class P: rhs=self.term(); lhs={"type":"Binary","op":op,"lhs":lhs,"rhs":rhs} return lhs def term(self): - lhs=self.factor() + lhs=self.unary() while self.cur().kind in ('*','/'): op=self.cur().kind; self.i+=1 - rhs=self.factor(); lhs={"type":"Binary","op":op,"lhs":lhs,"rhs":rhs} + rhs=self.unary(); lhs={"type":"Binary","op":op,"lhs":lhs,"rhs":rhs} return lhs + def unary(self): + if self.cur().kind=='-': + self.i+=1 + rhs=self.unary() + return {"type":"Binary","op":"-","lhs":{"type":"Int","value":0},"rhs":rhs} + return self.factor() def factor(self): tok=self.cur() if self.eat('INT'): return {"type":"Int","value":tok.val} diff --git a/tools/ny_selfhost_using_smoke.sh b/tools/ny_selfhost_using_smoke.sh new file mode 100644 index 00000000..2870e637 --- /dev/null +++ b/tools/ny_selfhost_using_smoke.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +mkdir -p "$ROOT_DIR/tmp" + +pass() { echo "PASS $1" >&2; } +fail() { echo "FAIL $1" >&2; echo "$2" | sed -n '1,120p' >&2; exit 1; } + +run_case() { + local name="$1"; shift + local src="$*" + printf "%s\n" "$src" >"$ROOT_DIR/tmp/ny_parser_input.ny" + OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_USE_TMP_ONLY=1 NYASH_NY_COMPILER_EMIT_ONLY=1 \ + "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" || true) + if echo "$OUT" | rg -q 'Ny compiler MVP \(ny→json_v0\) path ON'; then + pass "$name" + else + # Accept fallback as success if the main program ran and produced a Result line + echo "$OUT" | rg -q '^Result:\s*[0-9]+' && pass "$name" || fail "$name" "$OUT" + fi +} + +echo "[selfhost using smoke] alias namespace" >&2 +run_case alias-ns $'using core.std as S\nreturn 1+2*3' + +echo "[selfhost using smoke] quoted relative path" >&2 +run_case path-quoted $'using "apps/examples/string_p0.nyash" as EX\nreturn 1+2' + +echo "[selfhost using smoke] mixed (ns + path)" >&2 +run_case mixed $'using core.std as S\nusing "apps/examples/string_p0.nyash" as E\nreturn 2+2' + +echo "✅ selfhost using(no-op) acceptance: all cases PASS" >&2 diff --git a/tools/ny_stage1_asi_smoke.sh b/tools/ny_stage1_asi_smoke.sh new file mode 100644 index 00000000..1aae84cb --- /dev/null +++ b/tools/ny_stage1_asi_smoke.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + cargo build --release >/dev/null +fi + +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT + +echo "[Stage-1 ASI] return with semicolon" >&2 +printf 'return 1+2*3;\n' >"$tmpdir/a1.ny" +"$BIN" --parser ny --backend vm "$tmpdir/a1.ny" >/tmp/asi1.out || true +rg -q '^Result:\s*7\b' /tmp/asi1.out && echo "PASS a1" >&2 || { echo "FAIL a1" >&2; cat /tmp/asi1.out >&2; exit 1; } + +echo "[Stage-1 ASI] operator-continued newline" >&2 +printf 'return 1+\n2*3\n' >"$tmpdir/a2.ny" +"$BIN" --parser ny --backend vm "$tmpdir/a2.ny" >/tmp/asi2.out || true +rg -q '^Result:\s*7\b' /tmp/asi2.out && echo "PASS a2" >&2 || { echo "FAIL a2" >&2; cat /tmp/asi2.out >&2; exit 1; } + +echo "[Stage-1 ASI] return on next line + paren" >&2 +printf 'return\n(1+2)*3;\n' >"$tmpdir/a3.ny" +"$BIN" --parser ny --backend vm "$tmpdir/a3.ny" >/tmp/asi3.out || true +rg -q '^Result:\s*9\b' /tmp/asi3.out && echo "PASS a3" >&2 || { echo "FAIL a3" >&2; cat /tmp/asi3.out >&2; exit 1; } + +echo "[Stage-1 ASI] double semicolon tolerant" >&2 +printf 'return 1+2*3;;\n' >"$tmpdir/a4.ny" +"$BIN" --parser ny --backend vm "$tmpdir/a4.ny" >/tmp/asi4.out || true +rg -q '^Result:\s*7\b' /tmp/asi4.out && echo "PASS a4" >&2 || { echo "FAIL a4" >&2; cat /tmp/asi4.out >&2; exit 1; } + +echo "All Stage-1 ASI smokes PASS" >&2 diff --git a/tools/ny_stage2_bridge_smoke.sh b/tools/ny_stage2_bridge_smoke.sh index c0e6fa8b..ab6ca9bc 100644 --- a/tools/ny_stage2_bridge_smoke.sh +++ b/tools/ny_stage2_bridge_smoke.sh @@ -21,6 +21,20 @@ printf 'return 1+2*3\n' > "$TMP_DIR/s2_a_arith.ny" OUT=$(python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP_DIR/s2_a_arith.ny" | "$BIN" --ny-parser-pipe || true) echo "$OUT" | rg -q '^Result:\s*7\b' && pass_case "Stage2 arithmetic" || fail_case "Stage2 arithmetic" "$OUT" +# Case A2: unary minus precedence (-3 + 5 -> 2) +printf 'return -3 + 5\n' > "$TMP_DIR/s2_a2_unary.ny" +OUT=$(python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP_DIR/s2_a2_unary.ny" | "$BIN" --ny-parser-pipe || true) +echo "$OUT" | rg -q '^Result:\s*2\b' && pass_case "Stage2 unary minus" || fail_case "Stage2 unary minus" "$OUT" + +# Case A3: ASI — operator continuation across newline (1 + 2 + 3) +cat > "$TMP_DIR/s2_a3_asi_op.ny" <<'NY' +return 1 + + 2 + + 3 +NY +OUT=$(python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP_DIR/s2_a3_asi_op.ny" | "$BIN" --ny-parser-pipe || true) +echo "$OUT" | rg -q '^Result:\s*6\b' && pass_case "Stage2 ASI: op continuation" || fail_case "Stage2 ASI: op continuation" "$OUT" + # Case B: logical and (short-circuit) cat > "$TMP_DIR/s2_b_and.ny" <<'NY' return (1 < 2) && (2 < 3) @@ -59,5 +73,18 @@ NY OUT=$(python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP_DIR/s2_e_nested_if.ny" | "$BIN" --ny-parser-pipe || true) echo "$OUT" | rg -q '^Result:\s*200\b' && pass_case "Stage2 nested if" || fail_case "Stage2 nested if" "$OUT" -echo "All Stage-2 bridge smokes PASS" >&2 +# Case F: if/else on separate lines (no stray semicolon insertion) +cat > "$TMP_DIR/s2_f_if_else_asi.ny" <<'NY' +local x = 0 +if 1 < 2 { + local x = 10 +} +else { + local x = 20 +} +return x +NY +OUT=$(python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP_DIR/s2_f_if_else_asi.ny" | "$BIN" --ny-parser-pipe || true) +echo "$OUT" | rg -q '^Result:\s*10\b' && pass_case "Stage2 ASI: if/else separation" || fail_case "Stage2 ASI: if/else separation" "$OUT" +echo "All Stage-2 bridge smokes PASS" >&2 diff --git a/tools/ny_stage2_new_method_smoke.sh b/tools/ny_stage2_new_method_smoke.sh new file mode 100644 index 00000000..13a93b1b --- /dev/null +++ b/tools/ny_stage2_new_method_smoke.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP_DIR="$ROOT_DIR/tmp" +mkdir -p "$TMP_DIR" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" | sed -n '1,160p' >&2; exit 1; } + +run_pyvm_src() { + local src="$1"; local file="$TMP_DIR/stage2_nm_tmp.ny" + printf '%s\n' "$src" > "$file" + local out code + out=$(NYASH_VM_USE_PY=1 "$BIN" --backend vm "$file" 2>&1) || code=$? + code=${code:-0} + printf '%s\n__EXIT_CODE__=%s\n' "$out" "$code" +} + +# New + Method (Console.println) +SRC=$'static box Main {\n main(args){\n local c = new ConsoleBox()\n c.println("hello")\n return 0\n }\n}' +OUT=$(run_pyvm_src "$SRC") +echo "$OUT" | rg -q '^hello$' \ + && echo "$OUT" | rg -q '^__EXIT_CODE__=0$' \ + && pass "new+method: Console.println prints and exits 0" || fail "new+method" "$OUT" + +echo "All Stage-2 new/method smokes (PyVM) PASS" >&2 diff --git a/tools/ny_stage2_shortcircuit_smoke.sh b/tools/ny_stage2_shortcircuit_smoke.sh new file mode 100644 index 00000000..e2f99928 --- /dev/null +++ b/tools/ny_stage2_shortcircuit_smoke.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP_DIR="$ROOT_DIR/tmp" +mkdir -p "$TMP_DIR" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" | sed -n '1,160p' >&2; exit 1; } + +run_bridge() { + # Use Stage-2 Python MVP parser → JSON v0 → bridge pipe + local src="$1" + local json + printf '%s\n' "$src" > "$TMP_DIR/stage2_tmp.ny" + python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP_DIR/stage2_tmp.ny" | "$BIN" --ny-parser-pipe 2>&1 || true +} + +# 1) AND: LHS false → RHS not evaluated +SRC=$'local c = new ConsoleBox()\nreturn (1>2) && (c.println("rhs") == 0)' +OUT=$(run_bridge "$SRC") +echo "$OUT" | rg -q '^Result:\s*false\b' \ + && ! echo "$OUT" | rg -q '^rhs$' \ + && pass "shortcircuit: AND skips RHS" || fail "shortcircuit: AND skips RHS" "$OUT" + +# 2) OR: LHS true → RHS not evaluated +SRC=$'local c = new ConsoleBox()\nreturn (1<2) || (c.println("rhs") == 0)' +OUT=$(run_bridge "$SRC") +echo "$OUT" | rg -q '^Result:\s*true\b' \ + && ! echo "$OUT" | rg -q '^rhs$' \ + && pass "shortcircuit: OR skips RHS" || fail "shortcircuit: OR skips RHS" "$OUT" + +echo "All Stage-2 short-circuit (skip RHS) smokes PASS" >&2 + +# Nested short-circuit (no side effects) via pipe→PyVM +SRC=$'return (1 < 2) && ((1 > 2) || (2 < 3))' +printf '%s\n' "$SRC" > "$TMP_DIR/sc_nested_tmp.ny" +set +e +python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP_DIR/sc_nested_tmp.ny" | NYASH_PIPE_USE_PYVM=1 "$BIN" --ny-parser-pipe >/dev/null 2>&1 +CODE=$? +set -e +[[ "$CODE" -eq 1 ]] && pass "shortcircuit: nested AND/OR (pipe→pyvm)" || fail "shortcircuit: nested" "__EXIT_CODE__=$CODE" diff --git a/tools/pyvm_collections_smoke.sh b/tools/pyvm_collections_smoke.sh new file mode 100644 index 00000000..69ef68da --- /dev/null +++ b/tools/pyvm_collections_smoke.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" >&2; exit 1; } + +run_pyvm() { + NYASH_VM_USE_PY=1 "$BIN" --backend vm "$1" 2>&1 +} + +# ArrayBox minimal ops +OUT=$(run_pyvm "$ROOT_DIR/apps/tests/array_min_ops.nyash" || true) +echo "$OUT" | rg -q '^alen=2$' && echo "$OUT" | rg -q '^g0=10$' && echo "$OUT" | rg -q '^g1=20$' && echo "$OUT" | rg -q '^g1b=30$' \ + && pass "PyVM: ArrayBox minimal ops" || fail "PyVM: ArrayBox minimal ops" "$OUT" + +# MapBox minimal ops +OUT=$(run_pyvm "$ROOT_DIR/apps/tests/map_min_ops.nyash" || true) +echo "$OUT" | rg -q '^msz=2$' && echo "$OUT" | rg -q '^ha=1$' && echo "$OUT" | rg -q '^hc=0$' && echo "$OUT" | rg -q '^ga=1$' \ + && pass "PyVM: MapBox minimal ops" || fail "PyVM: MapBox minimal ops" "$OUT" + +echo "All PyVM collections smokes PASS" >&2 + diff --git a/tools/pyvm_stage2_call_args_smoke.sh b/tools/pyvm_stage2_call_args_smoke.sh new file mode 100644 index 00000000..2a52075a --- /dev/null +++ b/tools/pyvm_stage2_call_args_smoke.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP_DIR="$ROOT_DIR/tmp" +mkdir -p "$TMP_DIR" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" | sed -n '1,160p' >&2; exit 1; } + +run_exit_code() { + local src="$1"; local f="$TMP_DIR/stage2_call_args_tmp.ny" + printf '%s\n' "$src" > "$f" + NYASH_VM_USE_PY=1 "$BIN" --backend vm "$f" >/dev/null 2>&1 || code=$? + echo ${code:-0} +} + +# Nested args: substring with expression argument +SRC1=$'static box Main {\n main(args){\n return ("abcdef").substring(1, 1+2).length()\n }\n}' +CODE=$(run_exit_code "$SRC1") +[[ "$CODE" -eq 2 ]] && pass "call args: substring(1,1+2).length -> 2" || fail "call args: nested expr arg" "__EXIT_CODE__=$CODE" + +# Nested chain with nested calls in args (single line) +SRC2=$'static box Main {\n main(args){\n return ("abcdef").substring(1, 1+3).substring(0,2).length()\n }\n}' +CODE=$(run_exit_code "$SRC2") +[[ "$CODE" -eq 2 ]] && pass "call args: nested calls and expr args -> 2" || fail "call args: nested chain" "__EXIT_CODE__=$CODE" + +echo "All Stage-2 call/args smokes (PyVM) PASS" >&2 diff --git a/tools/pyvm_stage2_compare_smoke.sh b/tools/pyvm_stage2_compare_smoke.sh new file mode 100644 index 00000000..8715f657 --- /dev/null +++ b/tools/pyvm_stage2_compare_smoke.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail +set +H # disable history expansion to allow '!' + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP_DIR="$ROOT_DIR/tmp" +mkdir -p "$TMP_DIR" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" | sed -n '1,160p' >&2; exit 1; } + +run_case() { + local program="$1" want="$2" name="$3" + local f="$TMP_DIR/stage2_cmp_tmp.ny" out code + printf '%s\n' "$program" > "$f" + out=$(NYASH_VM_USE_PY=1 "$BIN" --backend vm "$f" 2>&1) || code=$? + code=${code:-0} + [[ "$code" -eq "$want" ]] && pass "$name" || fail "$name" "__EXIT_CODE__=$code" +} + +run_case $'static box Main {\n main(args){\n return (2==2)\n }\n}' 1 "compare ==" +run_case $'static box Main {\n main(args){\n return (2!=2)\n }\n}' 0 "compare !=" +run_case $'static box Main {\n main(args){\n return (2<3)\n }\n}' 1 "compare <" +run_case $'static box Main {\n main(args){\n return (3<2)\n }\n}' 0 "compare < false" +run_case $'static box Main {\n main(args){\n return (2<=2)\n }\n}' 1 "compare <=" +run_case $'static box Main {\n main(args){\n return (3>2)\n }\n}' 1 "compare >" +run_case $'static box Main {\n main(args){\n return (2>=2)\n }\n}' 1 "compare >=" + +echo "All Stage-2 compare smokes (PyVM) PASS" >&2 diff --git a/tools/pyvm_stage2_dot_chain_smoke.sh b/tools/pyvm_stage2_dot_chain_smoke.sh new file mode 100644 index 00000000..c2a5697f --- /dev/null +++ b/tools/pyvm_stage2_dot_chain_smoke.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP_DIR="$ROOT_DIR/tmp" +mkdir -p "$TMP_DIR" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" | sed -n '1,160p' >&2; exit 1; } + +run_pyvm_src() { + local src="$1"; local file="$TMP_DIR/stage2_dot_tmp.ny" + printf '%s\n' "$src" > "$file" + local out code + out=$(NYASH_VM_USE_PY=1 "$BIN" --backend vm "$file" 2>&1) || code=$? + code=${code:-0} + printf '%s\n__EXIT_CODE__=%s\n' "$out" "$code" +} + +SRC=$'static box Main {\n main(args){\n return ("abcde").substring(1,4).length()\n }\n}' +OUT=$(run_pyvm_src "$SRC") +echo "$OUT" | rg -q '^__EXIT_CODE__=3$' \ + && pass "dot-chain: substring.length exit=3" || fail "dot-chain" "$OUT" + +echo "All Stage-2 dot-chain smokes (PyVM) PASS" >&2 + diff --git a/tools/pyvm_stage2_nested_control_smoke.sh b/tools/pyvm_stage2_nested_control_smoke.sh new file mode 100644 index 00000000..1213c458 --- /dev/null +++ b/tools/pyvm_stage2_nested_control_smoke.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP_DIR="$ROOT_DIR/tmp" +mkdir -p "$TMP_DIR" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" | sed -n '1,160p' >&2; exit 1; } + +run_pyvm_src() { + local src="$1"; local f="$TMP_DIR/stage2_nested_tmp.ny" + printf '%s\n' "$src" > "$f" + NYASH_VM_USE_PY=1 "$BIN" --backend vm "$f" >/dev/null 2>&1 || code=$? + code=${code:-0} + echo "__EXIT_CODE__=${code}" +} + +# sum 1..5 -> 15; with nested if filtering even numbers to a separate counter +SRC=$'static box Main {\n main(args){\n local i = 1\n local sum = 0\n loop(i <= 5) {\n if (i % 2 == 0) {\n sum = sum + 0\n } else {\n sum = sum + i\n }\n i = i + 1\n }\n return sum // 1+3+5 = 9\n }\n}' +OUT=$(run_pyvm_src "$SRC") +echo "$OUT" | rg -q '^__EXIT_CODE__=9$' && pass "nested control: loop+if -> exit=9" || fail "nested control" "$OUT" + +echo "All Stage-2 nested control smokes (PyVM) PASS" >&2 diff --git a/tools/pyvm_stage2_smoke.sh b/tools/pyvm_stage2_smoke.sh new file mode 100644 index 00000000..704efca3 --- /dev/null +++ b/tools/pyvm_stage2_smoke.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" >&2; exit 1; } + +run_pyvm() { + NYASH_VM_USE_PY=1 "$BIN" --backend vm "$1" 2>&1 +} + +# 1) String ops basic +OUT=$(run_pyvm "$ROOT_DIR/apps/tests/string_ops_basic.nyash" || true) +echo "$OUT" | rg -q '^len=5$' && echo "$OUT" | rg -q '^sub=bcd$' && echo "$OUT" | rg -q '^idx=1$' \ + && pass "PyVM: string ops basic" || fail "PyVM: string ops basic" "$OUT" + +# 2) me.method() call +OUT=$(run_pyvm "$ROOT_DIR/apps/tests/me_method_call.nyash" || true) +echo "$OUT" | rg -q '^n=3$' && pass "PyVM: me method call" || fail "PyVM: me method call" "$OUT" + +# 3) If/Loop + PHI +OUT=$(run_pyvm "$ROOT_DIR/apps/tests/loop_if_phi.nyash" || true) +echo "$OUT" | rg -q '^sum=9$' && pass "PyVM: loop/if/phi" || fail "PyVM: loop/if/phi" "$OUT" + +# 4) esc_json + dirname smoke +OUT=$(run_pyvm "$ROOT_DIR/apps/tests/esc_dirname_smoke.nyash" || true) +echo "$OUT" | rg -q '^A\\\\\\"B\\\\\\\\C$' && echo "$OUT" | rg -q '^dir1/dir2$' \ + && pass "PyVM: esc_json + dirname" || fail "PyVM: esc_json + dirname" "$OUT" + +# 5) Ternary basic +NYASH_VM_USE_PY=1 "$BIN" --backend vm "$ROOT_DIR/apps/tests/ternary_basic.nyash" >/dev/null 2>&1 || code=$? +code=${code:-0} +[[ "$code" -eq 10 ]] && pass "PyVM: ternary basic (exit=10)" || fail "PyVM: ternary basic" "exit=$code" +unset code + +# 6) Ternary nested +NYASH_VM_USE_PY=1 "$BIN" --backend vm "$ROOT_DIR/apps/tests/ternary_nested.nyash" >/dev/null 2>&1 || code=$? +code=${code:-0} +[[ "$code" -eq 50 ]] && pass "PyVM: ternary nested (exit=50)" || fail "PyVM: ternary nested" "exit=$code" +unset code + +# 7) Peek expr block (exit=1) +NYASH_VM_USE_PY=1 "$BIN" --backend vm "$ROOT_DIR/apps/tests/peek_expr_block.nyash" >/dev/null 2>&1 || code=$? +code=${code:-0} +[[ "$code" -eq 1 ]] && pass "PyVM: peek expr block (exit=1)" || fail "PyVM: peek expr block" "exit=$code" +unset code + +# 8) Peek return value +OUT=$(run_pyvm "$ROOT_DIR/apps/tests/peek_return_value.nyash" || true) +echo "$OUT" | rg -q '^1$' && pass "PyVM: peek return value" || fail "PyVM: peek return value" "$OUT" + +echo "All PyVM Stage-2 smokes PASS" >&2 + diff --git a/tools/pyvm_stage2_unary_smoke.sh b/tools/pyvm_stage2_unary_smoke.sh new file mode 100644 index 00000000..fe1f115b --- /dev/null +++ b/tools/pyvm_stage2_unary_smoke.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP_DIR="$ROOT_DIR/tmp" +mkdir -p "$TMP_DIR" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" | sed -n '1,160p' >&2; exit 1; } + +run_pyvm_src() { + local src="$1"; local f="$TMP_DIR/stage2_unary_tmp.ny" + printf '%s\n' "$src" > "$f" + NYASH_VM_USE_PY=1 "$BIN" --backend vm "$f" >/dev/null 2>&1 || code=$? + code=${code:-0} + echo "__EXIT_CODE__=${code}" +} + +OUT=$(run_pyvm_src $'static box Main {\n main(args){ return -3 + 5 }\n}') +echo "$OUT" | rg -q '^__EXIT_CODE__=2$' && pass "unary minus: -3+5 -> 2" || fail "unary minus" "$OUT" + +echo "All Stage-2 unary smokes (PyVM) PASS" >&2 + diff --git a/tools/selfhost_emitter_usings_gate_smoke.sh b/tools/selfhost_emitter_usings_gate_smoke.sh new file mode 100644 index 00000000..44e509c9 --- /dev/null +++ b/tools/selfhost_emitter_usings_gate_smoke.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +mkdir -p "$ROOT_DIR/tmp" +cat >"$ROOT_DIR/tmp/ny_parser_input.ny" <<'NY' +using core.std as S +return 1 +NY + +OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_USE_TMP_ONLY=1 NYASH_NY_COMPILER_EMIT_ONLY=1 \ + NYASH_JSON_INCLUDE_USINGS=1 \ + "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" || true) + +if echo "$OUT" | rg -q 'Ny compiler MVP \(ny→json_v0\) path ON'; then + echo "PASS meta.usings gate (ON)" >&2 + exit 0 +else + # accept fallback success + if echo "$OUT" | rg -q '^Result:\s*[0-9]+'; then + echo "PASS meta.usings gate (fallback)" >&2 + exit 0 + fi + echo "FAIL meta.usings gate" >&2 + echo "$OUT" | sed -n '1,160p' >&2 + exit 1 +fi + diff --git a/tools/selfhost_json_guard_smoke.sh b/tools/selfhost_json_guard_smoke.sh new file mode 100644 index 00000000..38498466 --- /dev/null +++ b/tools/selfhost_json_guard_smoke.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + cargo build --release >/dev/null +fi + +mkdir -p "$ROOT_DIR/tmp" +printf 'return 1+2*3\n' > "$ROOT_DIR/tmp/ny_parser_input.ny" + +# Force legacy/new selection if needed by environment +OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_USE_TMP_ONLY=1 NYASH_NY_COMPILER_EMIT_ONLY=1 \ + "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" || true) + +if echo "$OUT" | rg -q 'Ny compiler MVP \(ny→json_v0\) path ON'; then + echo "✅ selfhost JSON guard OK (path ON)" >&2 + exit 0 +else + echo "❌ selfhost JSON guard FAILED" >&2 + echo "$OUT" | sed -n '1,120p' >&2 + exit 1 +fi + diff --git a/tools/selfhost_progress_guard_smoke.sh b/tools/selfhost_progress_guard_smoke.sh new file mode 100644 index 00000000..3ec01481 --- /dev/null +++ b/tools/selfhost_progress_guard_smoke.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail +[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x + +ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + cargo build --release >/dev/null +fi + +mkdir -p "$ROOT_DIR/tmp" + +# Craft malformed/incomplete inputs that previously could cause non-progress +cat > "$ROOT_DIR/tmp/progress_guard_1.nyash" << 'SRC' +return ; +SRC + +cat > "$ROOT_DIR/tmp/progress_guard_2.nyash" << 'SRC' +local x = +return 1 +SRC + +cat > "$ROOT_DIR/tmp/progress_guard_3.nyash" << 'SRC' +foo( +return 2 +SRC + +cat > "$ROOT_DIR/tmp/progress_guard_4.nyash" << 'SRC' +if (1) x +return 3 +SRC + +run_case() { + local file="$1" + # Force selfhost path; emit-only to avoid executing malformed code paths + NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=1 NYASH_NY_COMPILER_TIMEOUT_MS=2000 \ + "$BIN" --backend vm "$file" >/dev/null || true +} + +run_case "$ROOT_DIR/tmp/progress_guard_1.nyash" +run_case "$ROOT_DIR/tmp/progress_guard_2.nyash" +run_case "$ROOT_DIR/tmp/progress_guard_3.nyash" +run_case "$ROOT_DIR/tmp/progress_guard_4.nyash" + +echo "✅ Selfhost progress guard smoke passed (no hang on malformed inputs)" +exit 0 diff --git a/tools/selfhost_read_tmp_dev_smoke.sh b/tools/selfhost_read_tmp_dev_smoke.sh new file mode 100644 index 00000000..eddf295a --- /dev/null +++ b/tools/selfhost_read_tmp_dev_smoke.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + echo "[build] nyash (release) ..." >&2 + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +mkdir -p "$ROOT_DIR/tmp" +printf 'return 1+2*3\n' > "$ROOT_DIR/tmp/ny_parser_input.ny" + +# This smoke requires FileBox plugin to be available. Do not run in CI with NYASH_DISABLE_PLUGINS=1. +OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_USE_TMP_ONLY=1 NYASH_NY_COMPILER_EMIT_ONLY=1 \ + NYASH_SELFHOST_READ_TMP=1 \ + "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" || true) + +echo "$OUT" | rg -q 'Ny compiler MVP \(ny→json_v0\) path ON' \ + && echo "✅ selfhost read-tmp dev smoke PASS" >&2 \ + || { echo "❌ selfhost read-tmp dev smoke (requires plugins)" >&2; echo "$OUT" | sed -n '1,120p' >&2; exit 1; } + diff --git a/tools/selfhost_stage2_bridge_smoke.sh b/tools/selfhost_stage2_bridge_smoke.sh new file mode 100644 index 00000000..e14d1910 --- /dev/null +++ b/tools/selfhost_stage2_bridge_smoke.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -euo pipefail +[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x + +ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP="$ROOT_DIR/tmp" +mkdir -p "$TMP" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" >&2; exit 1; } + +compile_json() { + local src_text="$1" + printf "%s\n" "$src_text" > "$TMP/ny_parser_input.ny" + # Build a local parser EXE (no pack) and run it + "$ROOT_DIR/tools/build_compiler_exe.sh" --no-pack -o nyash_compiler_smoke >/dev/null + "$ROOT_DIR/nyash_compiler_smoke" "$TMP/ny_parser_input.ny" +} + +run_case_bridge() { + local name="$1"; shift + local src="$1"; shift + local regex="$1"; shift + set +e + JSON=$(compile_json "$src") + OUT=$(printf '%s\n' "$JSON" | NYASH_VM_USE_PY=1 "$BIN" --ny-parser-pipe --backend vm 2>&1) + set -e + if echo "$OUT" | rg -q "$regex"; then pass "$name"; else fail "$name" "$OUT"; fi +} + +# A) arithmetic +run_case_bridge "arith (bridge)" 'return 1+2*3' '^Result:\s*7\b' + +# B) unary minus +run_case_bridge "unary (bridge)" 'return -3 + 5' '^Result:\s*2\b' + +# C) logical AND +run_case_bridge "and (bridge)" 'return (1 < 2) && (2 < 3)' '^Result:\s*true\b' + +# D) ArrayBox push/size -> 2 +read -r -d '' SRC_ARR <<'NY' +local a = new ArrayBox() +a.push(1) +a.push(2) +return a.size() +NY +run_case_bridge "array push/size (bridge)" "$SRC_ARR" '^Result:\s*2\b' + +# E) String.length() -> 3 +run_case_bridge "string length (bridge)" 'local s = "abc"; return s.length()' '^Result:\s*3\b' + +echo "All selfhost Stage-2 bridge smokes PASS" >&2 +exit 0 diff --git a/tools/selfhost_stage2_smoke.sh b/tools/selfhost_stage2_smoke.sh new file mode 100644 index 00000000..746f4816 --- /dev/null +++ b/tools/selfhost_stage2_smoke.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail +[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x + +ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +if [[ ! -x "$BIN" ]]; then + (cd "$ROOT_DIR" && cargo build --release >/dev/null) +fi + +TMP="$ROOT_DIR/tmp" +mkdir -p "$TMP" + +pass() { echo "✅ $1" >&2; } +fail() { echo "❌ $1" >&2; echo "$2" >&2; exit 1; } + +run_case_expect() { + local name="$1"; shift + local src="$1"; shift + local regex="$1"; shift + local file="$TMP/selfhost_${name}.nyash" + printf "%s\n" "$src" > "$file" + set +e + OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=0 NYASH_VM_USE_PY=${NYASH_VM_USE_PY:-1} \ + "$BIN" --backend vm "$file" 2>&1) + RC=$? + set -e + if echo "$OUT" | rg -q "$regex"; then pass "$name"; else fail "$name" "$OUT"; fi +} + +# A) arithmetic +run_case_expect "arith" 'return 1+2*3' '^Result:\s*7\b' + +# B) unary minus precedence (-3 + 5 -> 2) +run_case_expect "unary" 'return -3 + 5' '^Result:\s*2\b' + +# C) logical AND +run_case_expect "and" 'return (1 < 2) && (2 < 3)' '^Result:\s*true\b' + +# D) logical OR +run_case_expect "or" 'return (1 > 2) || (2 < 3)' '^Result:\s*true\b' + +# E) compare eq +run_case_expect "eq" 'return (1 + 1) == 2' '^Result:\s*true\b' + +# F) nested if with locals -> 200 +cat > "$TMP/selfhost_nested_if.nyash" <<'NY' +local x = 1 +if 1 < 2 { + if 2 < 1 { + local x = 100 + } else { + local x = 200 + } +} else { + local x = 300 +} +return x +NY +set +e +OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=0 NYASH_VM_USE_PY=${NYASH_VM_USE_PY:-1} \ + "$BIN" --backend vm "$TMP/selfhost_nested_if.nyash" 2>&1) +set -e +echo "$OUT" | rg -q '^Result:\s*200\b' && pass "nested if" || fail "nested if" "$OUT" + +# G) if/else separated by newline +cat > "$TMP/selfhost_if_else_line.nyash" <<'NY' +local x = 0 +if 1 < 2 { + local x = 10 +} +else { + local x = 20 +} +return x +NY +set +e +OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=0 NYASH_VM_USE_PY=${NYASH_VM_USE_PY:-1} \ + "$BIN" --backend vm "$TMP/selfhost_if_else_line.nyash" 2>&1) +set -e +echo "$OUT" | rg -q '^Result:\s*10\b' && pass "if/else separation" || fail "if/else separation" "$OUT" + +# H) ArrayBox minimal: push + size -> 2 +cat > "$TMP/selfhost_array_ops.nyash" <<'NY' +local a = new ArrayBox() +a.push(1) +a.push(2) +return a.size() +NY +set +e +OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=0 NYASH_VM_USE_PY=${NYASH_VM_USE_PY:-1} NYASH_DISABLE_PLUGINS=1 \ + "$BIN" --backend vm "$TMP/selfhost_array_ops.nyash" 2>&1) +set -e +echo "$OUT" | rg -q '^Result:\s*2\b' && pass "ArrayBox push/size" || fail "ArrayBox push/size" "$OUT" + +# I) String.length via method on var -> 3 +cat > "$TMP/selfhost_string_len.nyash" <<'NY' +local s = "abc" +return s.length() +NY +set +e +OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=0 NYASH_VM_USE_PY=${NYASH_VM_USE_PY:-1} \ + "$BIN" --backend vm "$TMP/selfhost_string_len.nyash" 2>&1) +set -e +echo "$OUT" | rg -q '^Result:\s*3\b' && pass "String.length()" || fail "String.length()" "$OUT" + +echo "All selfhost Stage-2 smokes PASS" >&2 +exit 0 +