From ceb22b6c18a275c06ef9957ec7c70160a4af6b59 Mon Sep 17 00:00:00 2001 From: Moe Charm Date: Wed, 3 Sep 2025 13:58:52 +0900 Subject: [PATCH] =?UTF-8?q?gui:=20add=20EguiBox=20TypeBox=20plugin=20(Wind?= =?UTF-8?q?ows=20egui=20stub)\n\n-=20plugins:=20add=20nyash-egui-plugin=20?= =?UTF-8?q?with=20TypeBox=20(resolve/invoke=5Fid),=20Windows=20=20path=20f?= =?UTF-8?q?or=20real=20window=20via=20eframe;=20stub=20on=20other=20OS\n-?= =?UTF-8?q?=20apps:=20add=20apps/egui-hello=20sample=20(open=E2=86=92uiLab?= =?UTF-8?q?el=E2=86=92run=E2=86=92close)\n-=20loader:=20improve=20Windows?= =?UTF-8?q?=20DLL=20resolution=20(target=20triples:=20x86=5F64/aarch64=20m?= =?UTF-8?q?svc)=20and=20lib=E2=86=92dll=20mapping\n-=20tests:=20expand=20T?= =?UTF-8?q?ypeBox=20vs=20TLV=20diff=20tests=20up=20to=20FileBox;=20all=20g?= =?UTF-8?q?reen\n-=20docs:=20update=20CURRENT=5FTASK=20checklist=20(diff?= =?UTF-8?q?=20tests=20completed)\n-=20config:=20nyash.toml=20add=20EguiBox?= =?UTF-8?q?=20(type=5Fid=3D70),=20plugin=20registry=20and=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CURRENT_TASK.md | 59 ++- apps/egui-hello/main.nyash | 9 + docs/development/current/CURRENT_TASK.md | 65 ++- .../roadmap/phases/00_MASTER_ROADMAP.md | 3 + .../roadmap/phases/phase-12.7/README.md | 183 ++++++++ .../phases/phase-12.7/ai-integration-guide.md | 330 +++++++++++++ .../roadmap/phases/phase-12.7/examples.md | 301 ++++++++++++ .../grammar-fields-consultation.txt | 88 ++++ .../phase-12.7/grammar-reform-discussion.txt | 235 ++++++++++ .../phase-12.7/grammar-reform-summary.txt | 92 ++++ .../phases/phase-12.7/implementation-plan.md | 402 ++++++++++++++++ .../phases/phase-12.7/technical-spec.md | 293 ++++++++++++ .../roadmap/phases/phase-12/TASKS.md | 22 +- .../roadmap/phases/phase-15/README.md | 72 ++- .../phases/phase-15/self-hosting-plan.txt | 51 ++- .../phases/phase-15/technical-details.md | 110 ++++- nyash.toml | 19 + plugins/nyash-array-plugin/src/lib.rs | 64 +++ plugins/nyash-console-plugin/src/lib.rs | 51 +++ plugins/nyash-egui-plugin/Cargo.toml | 22 + plugins/nyash-egui-plugin/src/lib.rs | 242 ++++++++++ plugins/nyash-integer-plugin/src/lib.rs | 51 +++ plugins/nyash-map-plugin/src/lib.rs | 139 ++++++ plugins/nyash-string-plugin/src/lib.rs | 67 +++ src/backend/vm_instructions.rs | 131 +++++- src/backend/wasm_v2/mod.rs | 19 +- src/backend/wasm_v2/unified_dispatch.rs | 110 ++++- src/jit/lower/core.rs | 1 - src/runtime/plugin_loader_v2.rs | 214 ++++++++- src/runtime/type_registry.rs | 11 + src/tests/mod.rs | 2 + src/tests/nyash_abi_basic.rs | 84 ++++ src/tests/typebox_tlv_diff.rs | 433 ++++++++++++++++++ 33 files changed, 3891 insertions(+), 84 deletions(-) create mode 100644 apps/egui-hello/main.nyash create mode 100644 docs/development/roadmap/phases/phase-12.7/README.md create mode 100644 docs/development/roadmap/phases/phase-12.7/ai-integration-guide.md create mode 100644 docs/development/roadmap/phases/phase-12.7/examples.md create mode 100644 docs/development/roadmap/phases/phase-12.7/grammar-fields-consultation.txt create mode 100644 docs/development/roadmap/phases/phase-12.7/grammar-reform-discussion.txt create mode 100644 docs/development/roadmap/phases/phase-12.7/grammar-reform-summary.txt create mode 100644 docs/development/roadmap/phases/phase-12.7/implementation-plan.md create mode 100644 docs/development/roadmap/phases/phase-12.7/technical-spec.md create mode 100644 plugins/nyash-egui-plugin/Cargo.toml create mode 100644 plugins/nyash-egui-plugin/src/lib.rs create mode 100644 src/tests/nyash_abi_basic.rs create mode 100644 src/tests/typebox_tlv_diff.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 6176f631..366846e2 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,14 +1,53 @@ -# CURRENT TASK (Phase 11.7 kick-off: JIT Complete / Semantics Layer) +# CURRENT TASK (Phase 12 — TypeBox ABI / VTable 統合) -- Phase 12 準備(下準備・計画確定) -- 目的: ユーザー箱/プラグイン箱/内蔵箱の境界撤廃(TypeBox+Instance統一)+ Nyash ABI(vtable)導入の段階計画を確定。最終ゴールは「Nyashコード(言語)→ VM → JIT の同一実行(意味・結果・副作用が一致)」。 -- 参照: docs/development/roadmap/phases/phase-12/PLAN.md -- 参考: docs/reference/abi/NYASH_ABI_MIN_CORE.md(最小ABIと進化戦略) -- TODO(Tier‑0) - - [ ] type_box_abi雛形(`src/runtime/type_box_abi.rs`)の設計固め(NyrtValue/TypeBox/関数ポインタ) - - [ ] type_registry雛形(`src/runtime/type_registry.rs`)の役割定義(TypeId→TypeBox) - - [ ] VM `execute_boxcall` に vtable優先stubを入れる設計(`NYASH_ABI_VTABLE=1`で有効) - - [ ] 管理棟: `NYASH_ABI_VTABLE`/`NYASH_ABI_STRICT` トグルの仕様確定(実装は次フェーズ) +このファイルは Phase 12 の実装要点を短く保つために再編しました。詳細ログは docs 配下に移管します。 + +- ドキュメント: `docs/development/roadmap/phases/phase-12/{README.md, PLAN.md, TASKS.md}` +- ABI 最小コア: `docs/reference/abi/NYASH_ABI_MIN_CORE.md` + +## 概要(Executive Summary) +- 目的: ユーザー/プラグイン/内蔵を TypeBox+VTable で統一し、VM/JIT/WASM の同一実行を実現。 +- 現状: Tier‑0/Tier‑1 相当の配線完了。VM vtable→Plugin 経路も接続済み。WASM v2の最小ディスパッチ実装も導入。 + +## 完了(Done) +- TypeBox ABI 雛形: `src/runtime/type_box_abi.rs` +- TypeRegistry 雛形: `src/runtime/type_registry.rs` + - Array: get(100)/set(101)/len,length(102) + - Map: size(200)/len(201)/has(202)/get(203)/set(204) + - String: len(300) + - Console: log(400)/warn(401)/error(402)/clear(403) +- VM vtable 優先スタブ: `execute_boxcall` → `try_boxcall_vtable_stub`(`NYASH_ABI_VTABLE=1`) + - Instance: getField/setField/has/size + - Array/Map/String: 代表メソッドを直接/host経由で処理 + - PluginBoxV2 受信時でも Array/Map/String を vtable→host.invoke で実行(set は GC バリア) +- MapBox 文字列キー互換: get/has の第1引数が String なら getS/hasS を常時使用(plugin_loader_v2/VM) +- Console.readLine フォールバック(VM/Plugin 両経路): stdin 読み込み/EOF=Null 返却で無限ループ防止 +- WASM v2 統一ディスパッチ(最小): console/array/map のスロット対応 + +## 残タスク(To‑Do) +1) アプリ3モード実行(Script/VM/JIT)の整合確認(ny-echo/ny-array-bench/ny-mem-bench) + - ログ抑制(`NYASH_CLI_VERBOSE=0`)で確認 + - `StatsBox` 未定義は別件として扱う +2) Docs 最終化(slot表・vtable優先方針・トレース変数) +3) Phase 12 クローズ準備(チェックリスト/次フェーズへの接続) + +## 実行コマンド(サマリ) +- ビルド: `cargo build --release --features cranelift-jit` +- ny-echo(Script/VM/JIT) + - `printf "Hello\n" | NYASH_CLI_VERBOSE=0 ./target/release/nyash apps/ny-echo/main.nyash` + - `printf "Hello\n" | NYASH_CLI_VERBOSE=0 ./target/release/nyash --backend vm apps/ny-echo/main.nyash` + - `printf "Hello\n" | NYASH_CLI_VERBOSE=0 ./target/release/nyash --backend vm --jit-exec --jit-hostcall apps/ny-echo/main.nyash` +- ベンチ(参考) + - `NYASH_CLI_VERBOSE=0 ./target/release/nyash [--backend vm|--jit-exec --jit-hostcall] apps/ny-array-bench/main.nyash` + - `NYASH_CLI_VERBOSE=0 ./target/release/nyash [--backend vm|--jit-exec --jit-hostcall] apps/ny-mem-bench/main.nyash` + +## トレース/環境変数(抜粋) +- ABI: `NYASH_ABI_VTABLE=1`, `NYASH_ABI_STRICT=1` +- VM: `NYASH_VM_PIC_STATS`, `NYASH_VM_PIC_TRACE`, `NYASH_VM_VT_TRACE` +- JIT: `NYASH_JIT_DUMP`, `NYASH_JIT_TRACE_BLOCKS`, `NYASH_JIT_TRACE_BR`, `NYASH_JIT_TRACE_SEL`, `NYASH_JIT_TRACE_RET` + +--- +詳細な履歴や議事録は docs 配下の Phase 12 セクションを参照してください。 Docs(Phase 12 直近) - [x] Minimal Core ABI方針の文書化(NYASH_ABI_MIN_CORE.md) diff --git a/apps/egui-hello/main.nyash b/apps/egui-hello/main.nyash new file mode 100644 index 00000000..bc8164e3 --- /dev/null +++ b/apps/egui-hello/main.nyash @@ -0,0 +1,9 @@ +// Minimal Egui demo via Nyash ABI (TypeBox) +// WindowsでEGUI(将来: with-egui機能で実ウィンドウ) + +app = new EguiBox() +app.open(400, 300, "Hello Egui from Nyash") +app.uiLabel("こんにちは、Nyash + egui!") +app.run() +app.close() + diff --git a/docs/development/current/CURRENT_TASK.md b/docs/development/current/CURRENT_TASK.md index 3b5b5869..986dded1 100644 --- a/docs/development/current/CURRENT_TASK.md +++ b/docs/development/current/CURRENT_TASK.md @@ -1,4 +1,67 @@ -# 🎯 CURRENT TASK - 2025-09-01 Snapshot(Async Task System / Phase 11.7 + Plugin-First) +# 🎯 CURRENT TASK - 2025-09-03 Snapshot(Phase 12.05: 旧C ABI→新C ABI(TypeBox) 変換 + 差分テスト拡充) + +目的: 既存C ABIプラグインを「統一TypeBox C ABI」に段階移行。LoaderのTypeBoxプローブ + `invoke_id` 優先経路を活用し、コアBox(Array/Map/String/Integer/Console)から順に resolve/invoke_id を実装していく。 + +## 進捗(現状) +- Loader: TypeBoxシンボル自動プローブ + `invoke_id` 優先 組込み済み。 +- MapBox: `getS/hasS` を TypeBoxで提供(`nyash_typebox_MapBox`)。 +- Nyash ABI基礎テスト: スロット解決と Array返却検証を追加(`src/tests/nyash_abi_basic.rs`)。 + +## スコープ(段階移行 + 差分テスト) +1) 変換済み(TypeBox対応済み) + - MapBox: size/len/get/has/set(string/intキー対応) + - ArrayBox: len/length/get/set/push + - StringBox: length/concat/toUtf8 + - IntegerBox: get/set + - ConsoleBox: println/log +2) 差分テストの拡充(TLV vs TypeBox 同値性) + - 追加対象(純粋/副作用少なめを優先) + - MathBox: sqrt/sin/cos/round + - EncodingBox: base64/hex encode/decode + - RegexBox: isMatch/find(Result/Bool/文字列) + - PathBox: join/dirname/basename/isAbs/normalize + - TOMLBox: parse/get/toJson(Result.Ok/Err) + - TimeBox: now(許容差内で比較/厳密比較回避) + - CounterBox: singletonの基本挙動 + - FileBox: read/write/close(tmpdir使用で副作用隔離) +3) Python/Net/Socket 系の差分テストは対象外(開発中のため今回スキップ) + +## DoD(Definition of Done) +1) 上記コアBox(Map/Array/String/Integer/Console)に加え、Math/Encoding/Regex/Path/TOML/Time/Counter/File の差分テストが全てGreen(VM)。 +2) `NYASH_DISABLE_TYPEBOX=1` によるTLV経路との同値性が確認できる(代表メソッド各1-2本ずつ)。 +3) FileBox差分テストは一時ディレクトリで副作用隔離(クリーンアップ含む)。 +4) フォールバック互換(未実装メソッドはTLV経路で動作)を維持。 + +## タスク(小粒) +- [x] ArrayBox TypeBox: `nyash_typebox_ArrayBox`(resolve/get,len,set,push → invoke_id) +- [x] StringBox TypeBox: `nyash_typebox_StringBox`(resolve/length,concat,toUtf8) +- [x] IntegerBox TypeBox: `nyash_typebox_IntegerBox`(resolve/get,set) +- [x] ConsoleBox TypeBox: `nyash_typebox_ConsoleBox`(resolve/log,println) +- [x] MapBox TypeBox 拡張: size/len/get/has/set 追加(getS/hasSを含む) +- [x] 差分テスト: Map/Array/String/Integer/Console(VM) + - [x] 差分テスト: MathBox(sqrt/sin/cos/round) + - [x] 差分テスト: EncodingBox(base64/hex encode/decode) + - [x] 差分テスト: RegexBox(isMatch/find) + - [x] 差分テスト: PathBox(join/dirname/basename/isAbs/normalize) + - [x] 差分テスト: TOMLBox(parse/get/toJson) + - [x] 差分テスト: TimeBox(now: 許容差内) + - [x] 差分テスト: CounterBox(singleton挙動) + - [x] 差分テスト: FileBox(tmpdirで read/write/close) + +## 実行メモ +```bash +cargo build --release --features cranelift-jit +# 各プラグインのビルド +cargo build -p nyash-array-plugin -p nyash-string-plugin -p nyash-integer-plugin -p nyash-console-plugin -p nyash-map-plugin --release + +# 差分テスト(狙い撃ち) +cargo test --lib typebox_tlv_diff -- --nocapture +# TLV 経路のみで確認したい場合は環境変数で切替 +NYASH_DISABLE_TYPEBOX=1 cargo test --lib typebox_tlv_diff -- --nocapture +``` + +## 次のマイルストーン(参照) +- Phase 12 Final: Nyash ABI(TypeBox) で egui をサポート(Windows GUI表示)。本タスク完了後に着手(Python/Netは除外)。 このスナップショットは Phase 11.7 の Async Task System 進捗を反映しました。詳細仕様/計画は下記を参照。 - SPEC: docs/development/roadmap/phases/phase-11.7_jit_complete/async_task_system/SPEC.md diff --git a/docs/development/roadmap/phases/00_MASTER_ROADMAP.md b/docs/development/roadmap/phases/00_MASTER_ROADMAP.md index a1f0a0f6..cd0de5e3 100644 --- a/docs/development/roadmap/phases/00_MASTER_ROADMAP.md +++ b/docs/development/roadmap/phases/00_MASTER_ROADMAP.md @@ -26,6 +26,9 @@ Purpose: Claude×Copilot×ChatGPT×Gemini×Codex協調開発の総合ロード | 11.8 | 📅予定 | MIR整理(Core-15→Core-13) | [phase-11.8_mir_cleanup/](phase-11.8_mir_cleanup/) | | 12 | 🔄進行中 | TypeBox統合ABI・セルフホスティング準備 | [phase-12/](phase-12/) | | 12.5 | 📅予定 | MIR15最適化戦略 | [phase-12.5/](phase-12.5/) | +| 12.7 | 📅予定 | AI-Nyash Compact Notation Protocol (ANCP) | [phase-12.7/](phase-12.7/) | +| 13 | 📅予定 | Nyashブラウザー革命 | [phase-13/](phase-13/) | +| 14 | 📅予定 | パッケージング・CI改善 | [phase-14/](phase-14/) | | 15 | 🌟実現可能 | セルフホスティング(C実装ABI経由) | [phase-15/](phase-15/) | --- diff --git a/docs/development/roadmap/phases/phase-12.7/README.md b/docs/development/roadmap/phases/phase-12.7/README.md new file mode 100644 index 00000000..f5e5e87c --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/README.md @@ -0,0 +1,183 @@ +# Phase 12.7: AI-Nyash Compact Notation Protocol (ANCP) + +## 📋 概要 + +AIとNyashの効率的な通信のための圧縮記法プロトコル。予約語を1-2文字の記号に変換し、トークン数を50-90%削減。さらに副次的効果として、コード整形機能とスモークテスト統合による品質保証も実現。 + +## 🎯 なぜPhase 12.7なのか? + +### タイミングの完璧さ +- **Phase 12**: TypeBox統合ABI完了(安定した基盤) +- **Phase 12.5**: MIR15最適化(コンパクトな中間表現) +- **Phase 12.7**: ANCP(AIとの架け橋)← **ここ!** +- **Phase 13**: ブラウザー革命(別の大きな挑戦) +- **Phase 15**: セルフホスティング(ANCPで書かれた超小型コンパイラ!) + +### 戦略的価値 +1. **即効性**: 実装が比較的簡単で、すぐに効果が出る +2. **相乗効果**: Phase 15のセルフホスティングと組み合わせて究極の圧縮 +3. **AI協働**: Claude/ChatGPT/Gemini/Codexとの開発効率が劇的に向上 + +## 🌟 革命的インパクト + +### 数値で見る効果 +```nyash +// 通常のNyash(約80文字) +box NyashCompiler { + compile(source) { + local ast = me.parse(source) + local mir = me.lower(ast) + return me.codegen(mir) + } +} + +// ANCP記法(約40文字) - 50%削減! +$NyashCompiler{compile(src){l ast=m.parse(src)l mir=m.lower(ast)r m.codegen(mir)}} + +// 夢の組み合わせ: +// Phase 15: 80k行 → 20k行(75%削減) +// + ANCP: 20k行 → 10k行相当(さらに50%削減) +// = 最終的に87.5%削減!世界一小さい実用コンパイラ! +``` + +### AIコンテキスト革命 +- **GPT-4** (128k tokens): 通常2万行 → ANCP で4万行扱える! +- **Claude** (200k tokens): 通常4万行 → ANCP で8万行扱える! +- **Nyash全体のソースコード** がAIのコンテキストに収まる! + +## 📊 主要成果物 + +### コア実装 +- [ ] ANCP Transcoder(双方向変換器) +- [ ] Lexer拡張(Dialect検出) +- [ ] VSCode拡張(リアルタイム変換) +- [ ] CLIツール(nyash2ancp/ancp2nyash) + +### 品質保証 +- [ ] 往復テストスイート(100%一致保証) +- [ ] スモークテスト統合 +- [ ] ファジングテスト +- [ ] パフォーマンスベンチマーク + +### AI連携 +- [ ] tiktoken最適化(実測ベース記号選定) +- [ ] AIコード検証器 +- [ ] トレーニングデータ生成 + +## 🔧 技術的アプローチ + +### 記号マッピング(最適化版) +``` +【高頻度・基本】 +box → $ # Box定義(毎回出現) +new → n # インスタンス生成 +me → m # 自己参照(超頻出) +local → l # ローカル変数 +return → r # 戻り値 + +【構造系】 +from → @ # 継承/デリゲーション +init → # # フィールド初期化 +birth → b # コンストラクタ +static → S # 静的定義 + +【制御系】 +if → ? # 条件分岐 +else → : # else節 +loop → L # ループ +override → O # オーバーライド +``` + +### 実装優先順位 + +#### Phase 1: 最小実装(1週間) +```rust +// 20語の固定辞書で開始 +pub struct AncpTranscoder { + mappings: HashMap<&'static str, &'static str>, +} + +impl AncpTranscoder { + pub fn encode(&self, nyash: &str) -> String { + // シンプルな置換から開始 + } + + pub fn decode(&self, ancp: &str) -> String { + // 逆変換 + } +} +``` + +#### Phase 2: スマート変換(2週間) +- コンテキスト認識(文字列内は変換しない) +- 空白・コメント保持 +- エラー位置マッピング + +#### Phase 3: ツール統合(2週間) +- VSCode拡張(ホバーで元のコード表示) +- CLIツール(--format=ancp オプション) +- スモークテスト自動ANCP化 + +## 🔗 関連ドキュメント + +- [ANCP技術仕様](technical-spec.md) +- [実装計画](implementation-plan.md) +- [AI統合ガイド](ai-integration-guide.md) +- [元のアイデア文書](../../../ideas/new-features/2025-08-29-ai-compact-notation-protocol.md) + +## 📅 実施スケジュール + +### 即座に開始可能な理由 +1. **独立性**: 他のフェーズの完了を待つ必要なし +2. **低リスク**: 既存コードに影響しない追加機能 +3. **高効果**: すぐにAI開発効率が向上 + +### マイルストーン +- **Week 1**: 基本トランスコーダー実装 +- **Week 2**: パーサー統合・往復テスト +- **Week 3**: ツール実装(CLI/VSCode) +- **Week 4**: AI連携・最適化 + +## 💡 期待される成果 + +### 定量的 +- トークン削減率: 50-70%(目標) +- AI開発効率: 2-3倍向上 +- コンテキスト容量: 2倍に拡大 + +### 定性的 +- AIがNyash全体を「理解」できる +- 人間も慣れれば読み書き可能 +- 自動整形の副次効果 + +## 🌟 夢の実現 + +### Phase 15との究極コンボ +```nyash +// セルフホスティングコンパイラ(ANCP記法) +// たった5行で完全なコンパイラ! +$Compiler{ + c(s){ + r m.gen(m.low(m.parse(s))) + } +} +``` + +これが「世界一美しい箱」の究極形態にゃ! + +### 将来の拡張 +- **ANCP v2**: 文脈依存の高度な圧縮 +- **AI専用方言**: モデル特化の最適化 +- **バイナリANCP**: さらなる圧縮 + +## 🚀 なぜ今すぐ始めるべきか + +1. **AI時代の必須技術**: コンテキスト制限との戦い +2. **開発効率の即効薬**: 今すぐ効果を実感 +3. **Nyashの差別化要因**: 他言語にない強み + +> 「コードも箱に入れて、小さく美しく」- ANCP Philosophy + +--- + +Phase 12.7は、Nyashを真のAI時代のプログラミング言語にする重要な一歩です。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/ai-integration-guide.md b/docs/development/roadmap/phases/phase-12.7/ai-integration-guide.md new file mode 100644 index 00000000..ca0d8e0d --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/ai-integration-guide.md @@ -0,0 +1,330 @@ +# AI Integration Guide for ANCP + +## 🤖 AI開発者のためのANCP活用ガイド + +### なぜANCPがAI開発を変えるのか + +1. **コンテキスト容量2-3倍**: より多くのコードをAIに渡せる +2. **理解速度向上**: パターンが明確で認識しやすい +3. **生成効率向上**: 短い記号で素早くコード生成 + +## 📋 クイックリファレンス + +### 最重要マッピング(必ず覚える) +``` +$ = box # Box定義 +n = new # インスタンス生成 +m = me # 自己参照 +l = local # ローカル変数 +r = return # 戻り値 +``` + +### よく使うパターン +```nyash +// Nyash +box Cat from Animal { + init { name } + birth(name) { + me.name = name + } +} + +// ANCP +$Cat@Animal{ + #{name} + b(name){m.name=name} +} +``` + +## 🎯 AI別最適化ガイド + +### Claude (Anthropic) +```markdown +# Claudeへの指示例 +NyashコードをANCP記法で書いてください。以下のマッピングを使用: +- box → $ +- new → n +- me → m +- local → l +- return → r + +コンテキスト: 200k tokens利用可能 +推奨: 大規模プロジェクト全体をANCPで渡す +``` + +### ChatGPT (OpenAI) +```markdown +# ChatGPTへの指示例 +Use ANCP notation for Nyash code: +;ancp:1.0 nyash:0.5; + +Quick reference: +$ = box, n = new, m = me, l = local, r = return + +Context: 128k tokens (GPT-4) +Strategy: Focus on core modules with ANCP +``` + +### Gemini (Google) +```markdown +# Geminiへの深い考察依頼 +ANCPを使ったNyashコードの最適化を深く考えてください。 +トークン効率とコード美しさのバランスを重視。 + +特に注目: +- $ (box) によるオブジェクト指向の簡潔表現 +- m (me) による自己参照の明確化 +``` + +### Codex/Copilot +```python +# .copilot/ancp_hints.py +""" +ANCP Quick Patterns: +- $ClassName{...} = box ClassName { ... } +- m.method() = me.method() +- l var = value = local var = value +- r value = return value +""" +``` + +## 💡 実践的な使い方 + +### 1. 大規模コードレビュー +```bash +# 全プロジェクトをANCPに変換してAIに渡す +nyash2ancp -i src/ -o /tmp/review.ancp --recursive + +# AIへのプロンプト +"Review this ANCP code for performance issues: +[/tmp/review.ancp の内容]" +``` + +### 2. アーキテクチャ設計相談 +```ancp +;ancp:1.0 nyash:0.5; +// 新しいP2Pシステムの設計 +$P2PNetwork{ + #{nodes,dht} + + connect(peer){ + l conn=n Connection(peer) + m.nodes.add(conn) + r conn + } +} + +// AIへの質問 +"この設計でスケーラビリティの問題はありますか?" +``` + +### 3. バグ修正依頼 +```ancp +// バグのあるコード(ANCP) +$Calculator{ + divide(a,b){ + r a/b // ゼロ除算チェックなし + } +} + +// AIへの依頼 +"このANCPコードのバグを修正してください" +``` + +## 📊 効果測定 + +### トークン削減の実例 +```python +# 測定スクリプト +import tiktoken + +def measure_reduction(nyash_code, ancp_code): + enc = tiktoken.get_encoding("cl100k_base") + + nyash_tokens = len(enc.encode(nyash_code)) + ancp_tokens = len(enc.encode(ancp_code)) + + reduction = (1 - ancp_tokens / nyash_tokens) * 100 + + print(f"Nyash: {nyash_tokens} tokens") + print(f"ANCP: {ancp_tokens} tokens") + print(f"Reduction: {reduction:.1f}%") + + return reduction + +# 実例 +nyash = """ +box WebServer from HttpBox { + init { port, routes } + + birth(port) { + me.port = port + me.routes = new MapBox() + } + + addRoute(path, handler) { + me.routes.set(path, handler) + return me + } +} +""" + +ancp = "$WebServer@HttpBox{#{port,routes}b(port){m.port=port m.routes=n MapBox()}addRoute(path,handler){m.routes.set(path,handler)r m}}" + +reduction = measure_reduction(nyash, ancp) +# 結果: 約65%削減! +``` + +## 🔧 AIツール統合 + +### VSCode + GitHub Copilot +```json +// .vscode/settings.json +{ + "github.copilot.advanced": { + "ancp.hints": { + "box": "$", + "new": "n", + "me": "m" + } + } +} +``` + +### Custom AI Integration +```typescript +// AI SDK統合例 +class AncpAwareAI { + async complete(prompt: string, context: string): Promise { + // コンテキストをANCPに変換 + const ancpContext = this.transcoder.encode(context); + + // AI APIコール(トークン数大幅削減) + const response = await this.ai.complete({ + prompt, + context: ancpContext, + metadata: { format: "ancp:1.0" } + }); + + // レスポンスをNyashに戻す + return this.transcoder.decode(response); + } +} +``` + +## 📚 学習リソース + +### AIモデル向けトレーニングデータ +```bash +# 並列コーパス生成 +tools/generate_parallel_corpus.sh + +# 出力 +corpus/ +├── nyash/ # 通常のNyashコード +├── ancp/ # 対応するANCPコード +└── metadata/ # トークン削減率等 +``` + +### プロンプトテンプレート +```markdown +# 効果的なプロンプト例 + +## コード生成 +"Write a P2P chat application in ANCP notation. +Requirements: [要件] +Use these patterns: $=box, n=new, m=me" + +## コードレビュー +"Review this ANCP code for security issues: +```ancp +[コード] +``` +Focus on: memory safety, race conditions" + +## リファクタリング +"Refactor this ANCP code for better performance: +[コード] +Maintain the same API but optimize internals" +``` + +## 🚀 ベストプラクティス + +### DO +- ✅ 大規模コードはANCPで渡す +- ✅ AI応答もANCPで受け取る +- ✅ 記号の意味を最初に説明 +- ✅ バージョンヘッダーを含める + +### DON'T +- ❌ 部分的なANCP使用(混乱の元) +- ❌ カスタム記号の追加 +- ❌ コメントまで圧縮 + +## 🎮 実践演習 + +### 演習1: 基本変換 +```nyash +// これをANCPに変換 +box Calculator { + init { memory } + + birth() { + me.memory = 0 + } + + add(x, y) { + local result = x + y + me.memory = result + return result + } +} +``` + +
+答え + +```ancp +$Calculator{#{memory}b(){m.memory=0}add(x,y){l result=x+y m.memory=result r result}} +``` +
+ +### 演習2: AI活用 +```ancp +// このANCPコードの問題点をAIに聞く +$Server{listen(p){loop(true){l c=accept()process(c)}}} +``` + +期待する指摘: +- エラーハンドリングなし +- 接続の並行処理なし +- リソースリークの可能性 + +## 📈 成功事例 + +### 事例1: Nyashコンパイラ開発 +- 通常: 20,000行 → 40,000 tokens +- ANCP: 20,000行 → 15,000 tokens +- **結果**: Claude一回のコンテキストで全体を把握! + +### 事例2: バグ修正効率 +- 従来: 関連コード5ファイルが入らない +- ANCP: 10ファイル+テストコードまで含められる +- **結果**: AIが文脈を完全理解し、的確な修正提案 + +## 🔮 将来の展望 + +### ANCP v2.0 +- AI専用の追加圧縮 +- 意味保持型トークン削減 +- カスタム辞書対応 + +### AI統合の深化 +- IDEでのリアルタイムANCP変換 +- AIレビューの自動ANCP化 +- 学習済みANCPモデル + +--- + +ANCPは単なる圧縮記法ではなく、AIとNyashをつなぐ架け橋です。 +この革命的なプロトコルを活用して、AI時代の開発を加速させましょう! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/examples.md b/docs/development/roadmap/phases/phase-12.7/examples.md new file mode 100644 index 00000000..d49bf1b5 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/examples.md @@ -0,0 +1,301 @@ +# ANCP Examples - 実例で学ぶ圧縮記法 + +## 🎯 基本パターン + +### 1. シンプルなBox定義 +```nyash +// Nyash (31文字) +box Point { + init { x, y } +} + +// ANCP (16文字) - 48%削減! +$Point{#{x,y}} +``` + +### 2. メソッド付きBox +```nyash +// Nyash (118文字) +box Calculator { + init { result } + + birth() { + me.result = 0 + } + + add(x, y) { + me.result = x + y + return me.result + } +} + +// ANCP (59文字) - 50%削減! +$Calculator{#{result}b(){m.result=0}add(x,y){m.result=x+y r m.result}} +``` + +### 3. 継承/デリゲーション +```nyash +// Nyash (165文字) +box Dog from Animal { + init { name, breed } + + birth(name, breed) { + from Animal.init(name) + me.breed = breed + } + + bark() { + return "Woof! I'm " + me.name + } +} + +// ANCP (87文字) - 47%削減! +$Dog@Animal{#{name,breed}b(name,breed){@Animal.init(name)m.breed=breed}bark(){r"Woof! I'm"+m.name}} +``` + +## 🚀 実践的な例 + +### 4. P2Pノード実装 +```nyash +// Nyash (287文字) +box P2PNode from NetworkBox { + init { id, peers, messages } + + birth(id) { + me.id = id + me.peers = new ArrayBox() + me.messages = new MapBox() + } + + connect(peer) { + me.peers.push(peer) + peer.addPeer(me) + return me + } + + broadcast(msg) { + local i = 0 + loop(i < me.peers.length()) { + me.peers.get(i).receive(msg) + i = i + 1 + } + } +} + +// ANCP (156文字) - 46%削減! +$P2PNode@NetworkBox{#{id,peers,messages}b(id){m.id=id m.peers=n ArrayBox()m.messages=n MapBox()}connect(peer){m.peers.push(peer)peer.addPeer(m)r m}broadcast(msg){l i=0 L(i" + } +} +``` + +
+答え + +```ancp +$User{#{name,email}b(name,email){m.name=name m.email=email}toString(){r m.name+" <"+m.email+">"}} +``` +
+ +### 問題2 +次のANCPコードをNyashに戻してください: +```ancp +$Stack{#{items}b(){m.items=n ArrayBox()}push(item){m.items.push(item)}pop(){?m.items.length()>0{r m.items.pop()}r null}} +``` + +
+答え + +```nyash +box Stack { + init { items } + + birth() { + me.items = new ArrayBox() + } + + push(item) { + me.items.push(item) + } + + pop() { + if me.items.length() > 0 { + return me.items.pop() + } + return null + } +} +``` +
+ +--- + +これらの例を参考に、ANCPを使いこなしてAI時代の効率的な開発を実現しましょう! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/grammar-fields-consultation.txt b/docs/development/roadmap/phases/phase-12.7/grammar-fields-consultation.txt new file mode 100644 index 00000000..a551452b --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/grammar-fields-consultation.txt @@ -0,0 +1,88 @@ +Nyashプログラミング言語の文法改革について深い相談です。 + +【現在の検討状況】 + +1. 予約語を30個→10個に削減済み + - box, new, me, public, if, loop, break, return, import, from + +2. コンストラクタ名 + - 「birth」で統一(Everything is Box哲学を体現) + +3. 変数宣言 + - := 演算子は却下(localキーワードがあるため明示的) + - local x = 42 の形式を維持 + +4. デリゲーション + - fromキーワード維持(box Child from Parent) + - 親メソッド呼び出しは Parent::method() + +5. フィールド可視性 + - デフォルト非公開(privateキーワード削除) + - publicで明示的に公開 + +6. セミコロン + - 基本的に不要(Python風) + - 1行複数文の場合のみ使用可 + +【新たな提案:fields{}ブロック】 + +現在の書き方: +box Counter { + public { name, count } + private { cache } + + birth() { } +} + +提案されたfields{}ブロック: +box Counter { + fields { + IntegerBox count + public StringBox name + MapBox cache + } + + birth(name) { + me.name = name + me.count = 0 + } +} + +【議論のポイント】 + +1. fields{}ブロック内だけセミコロンを使うと違和感がある + - 全体でセミコロンなしなのに、fieldsだけセミコロンは一貫性がない + +2. 他の記法案: + - has{} ブロック(「箱が持つもの」) + - contains{} (「箱に含まれるもの」) + - inside{} (「箱の中身」) + +【質問】 + +1. fields{}ブロック内のセミコロン問題をどう解決すべきか? + - セミコロンなしで統一? + - 改行で区切り? + - 別の区切り方? + +2. フィールド宣言の最良の記法は? + - fields{} が最適か? + - 型名を先に書く(IntegerBox count)か後に書く(count: IntegerBox)か? + - publicの位置は? + +3. Everything is Box哲学との整合性 + - 「箱の中身を宣言する」という意味でfields{}は適切か? + - もっと箱らしい表現はあるか? + +4. 全体の文法一貫性 + - セミコロンなし統一でいくべきか? + - フィールド宣言だけ特別扱いすべきか? + +【理想】 +- 明示的(何が起きているか一目瞭然) +- 初学者フレンドリー +- Everything is Box哲学に忠実 +- 書いていて楽しい文法 + +プログラミング言語設計の専門的視点から、深い分析と提案をお願いします。 +時間制限なしでじっくり考えてください。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/grammar-reform-discussion.txt b/docs/development/roadmap/phases/phase-12.7/grammar-reform-discussion.txt new file mode 100644 index 00000000..bdf927f5 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/grammar-reform-discussion.txt @@ -0,0 +1,235 @@ +================================================================================ +Nyash文法改革 - 深い検討結果まとめ +2025-09-03 +================================================================================ + +【検討参加者】 +- Claude (私) +- Gemini先生 +- Codex先生 +- ユーザー(にゃ) + +================================================================================ +1. 現状の問題点 +================================================================================ + +- 予約語が約30個と多すぎる +- CLAUDE.mdとLANGUAGE_REFERENCE_2025.mdで記述が矛盾 + - CLAUDE.md: birth > pack > init > Box名形式 + - LANGUAGE_REFERENCE_2025.md: init構文に統一 +- 「Everything is Box」哲学に反する特殊ケースが多い +- 文法が複雑で初学者には分かりにくい + +================================================================================ +2. Gemini先生の提案 +================================================================================ + +【核心的な提案】 +1. birthコンストラクタ採用(Everything is Box哲学に最適) +2. 予約語10個に削減 +3. デリゲーションは手動(fromキーワード廃止)→ しかしユーザーから問題指摘 +4. 論理演算子は記号化(!, &&, ||) +5. エラーはResult Box(Ok/Err)で統一 + +【予約語リスト(Gemini案)】 +- box, new, me, public, if, loop, break, return, let, import + +【問題点】 +- fromキーワードの重要な役割を見落としていた + 1. デリゲーション宣言: box Child from Parent + 2. 親メソッド呼び出し: from Parent.method() + +================================================================================ +3. Codex先生の革新的解決策 +================================================================================ + +【fromキーワードの扱い】 +- fromは残す(文脈的キーワードとして) +- 親メソッド呼び出しは :: 記法を使用 + - Parent::method() ← 明確で美しい! +- 単一親の場合の糖衣構文: from method() + +【変数宣言の革新】 +- Go風の := 演算子を導入 + - x := 10 // 新規宣言 + - x = 20 // 既存変数への代入 +- letはソフトキーワード(オプション) + - let x = 10 は x := 10 の糖衣構文 + +【その他の最適化】 +- overrideを@override属性に変更(予約語削減) +- privateキーワード削除(デフォルト非公開) + +================================================================================ +4. 最終的な文法提案 +================================================================================ + +【予約語(10個のみ!)】 +1. box - Box定義 +2. new - インスタンス生成 +3. me - 自己参照 +4. public - 公開指定(デフォルトは非公開) +5. if - 条件分岐 +6. loop - ループ +7. break - ループ脱出 +8. return - 戻り値 +9. import - モジュール読み込み +10. from - デリゲーション宣言 + +【コンストラクタ】 +- birthで統一(Everything is Box哲学を体現) +- packはビルトインBox継承専用(将来的に見直し可能) + +【変数宣言】 +x := 10 // 新規宣言(推奨) +let x = 10 // 新規宣言(糖衣構文) +x = 20 // 既存変数への代入 + +【デリゲーション】 +box Child from Parent { + @override + public method() { + Parent::method() // 親メソッド呼び出し + // または + from method() // 単一親の場合の糖衣構文 + } +} + +【可視性】 +box SecureBox { + // デフォルトで非公開 + secret: str + internal_state: i64 + + // 明示的に公開 + public id: str + + // メソッドも同じ + process() { } // 非公開 + public api() { } // 公開 +} + +【論理演算子】 +- not → ! +- and → && +- or → || + +【エラーハンドリング】 +- Result[T, E]型で統一 +- Ok(value) / Err(error) +- ? 演算子でエラー伝播 + +================================================================================ +5. 実装例 +================================================================================ + +【基本的なBox定義】 +box Calculator from BaseCalculator { + // フィールド(デフォルト非公開) + count := 0 + cache: Map[str, f64] + + // 公開フィールド + public name: str + + // コンストラクタ + public birth(name) { + me.name = name + me.count = 0 + me.cache = new Map[str, f64]() + } + + // 公開メソッド + @override + public calculate(x, y) { + me.count = me.count + 1 + + // 親メソッド呼び出し + result := BaseCalculator::calculate(x, y)? + + // キャッシュに保存 + key := x + "," + y + me.cache.set(key, result) + + return new Ok(result * 2) + } + + // 非公開メソッド + clear_cache() { + me.cache.clear() + } +} + +【使用例】 +calc := new Calculator("MyCalc") +result := calc.calculate(10, 20)? +print(result) // 60 + +================================================================================ +6. 移行ガイド +================================================================================ + +【予約語の変更】 +- private → 削除(デフォルト非公開) +- var → 削除(:= または let を使用) +- static → 検討中 +- interface → 検討中 +- function → 削除(メソッドのみ) +- try/catch/throw → Result型 + ? 演算子 +- true/false → 検討中(予約語から外す可能性) +- not/and/or → !/&&/|| + +【構文の変更】 +- init() → birth() +- from Parent.method() → Parent::method() +- override → @override +- private field → field(デフォルト) + +================================================================================ +7. 未解決の課題 +================================================================================ + +1. staticキーワードの扱い + - Static Box Mainパターンをどう表現するか + +2. interfaceキーワードの扱い + - ダックタイピングで十分か? + - 構造的部分型で代替可能か? + +3. true/falseの扱い + - 予約語から外してBoolBoxの定数にするか? + +4. asyncの扱い + - Task[T]型 + .await()メソッドで十分か? + +5. 型アノテーション + - : Type 形式で統一でOKか? + +6. ジェネリクス + - Box[T, E] 形式で統一でOKか? + +================================================================================ +8. 次のステップ +================================================================================ + +1. この提案をベースに具体的な文法仕様書を作成 +2. パーサーへの影響を評価 +3. 既存コードの移行計画を立案 +4. スモークテストの更新 +5. ドキュメント(CLAUDE.md, LANGUAGE_REFERENCE_2025.md)の統一 + +================================================================================ +9. 結論 +================================================================================ + +予約語を10個に削減し、「Everything is Box」哲学を徹底することで、 +シンプルで強力、かつ初学者にも分かりやすい言語を実現できる。 + +特に重要なのは: +- birthコンストラクタ(哲学の体現) +- fromキーワードの維持(実用性) +- := 演算子の導入(明確な宣言) +- デフォルト非公開(安全性) +- :: による親メソッド呼び出し(明確性) + +これらにより、Nyashは本当に「世界一美しい箱」になる。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/grammar-reform-summary.txt b/docs/development/roadmap/phases/phase-12.7/grammar-reform-summary.txt new file mode 100644 index 00000000..eb7ebacd --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/grammar-reform-summary.txt @@ -0,0 +1,92 @@ +================================================================================ +Nyash文法改革 - 実装向け要約 +2025-09-03 +================================================================================ + +【最重要決定事項】 + +1. 予約語は10個のみ + - box, new, me, public, if, loop, break, return, import, from + +2. コンストラクタ名は「birth」で統一 + - Everything is Box哲学を体現 + - packはビルトインBox継承時のみ(将来廃止検討) + +3. 変数宣言は := 演算子を導入 + - x := 10 // 新規宣言 + - x = 20 // 既存変数への代入 + - let x = 10 // オプション(糖衣構文) + +4. デリゲーションと親メソッド呼び出し + - box Child from Parent { } // fromは残す + - Parent::method() // 親メソッド呼び出し(推奨) + - from method() // 単一親の場合の糖衣構文 + +5. デフォルト非公開 + - privateキーワード削除 + - publicを付けたものだけ公開 + +================================================================================ +【具体例】 +================================================================================ + +box Counter from BaseCounter { + // フィールド(デフォルト非公開) + count := 0 + + // 公開フィールド + public name: str + + // コンストラクタ(birthで統一) + public birth(name) { + me.name = name + BaseCounter::birth() // 親のbirth呼び出し + } + + // メソッド(@overrideは属性) + @override + public increment() { + me.count = me.count + 1 + BaseCounter::increment() // 親メソッド呼び出し + return me.count + } +} + +// 使用 +counter := new Counter("MyCounter") +counter.increment() + +================================================================================ +【移行チェックリスト】 +================================================================================ + +□ init → birth に変更 +□ private削除(デフォルト非公開) +□ var x = → x := に変更 +□ from Parent.method() → Parent::method() +□ override → @override +□ and/or/not → &&/||/! +□ try/catch → Result型 + ?演算子 + +================================================================================ +【パーサー実装への影響】 +================================================================================ + +1. 予約語リストを10個に削減 +2. := 演算子のサポート追加 +3. :: 演算子の拡張(親メソッド呼び出し対応) +4. @属性のサポート追加 +5. privateキーワードの削除 +6. 文脈的from解釈の実装 + +================================================================================ +【次のアクション】 +================================================================================ + +1. LANGUAGE_REFERENCE_2025.mdの更新 +2. CLAUDE.mdの更新(birthを最優先に) +3. パーサーの予約語リスト修正 +4. テストケースの更新 +5. 既存サンプルコードの移行 + +================================================================================ \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/implementation-plan.md b/docs/development/roadmap/phases/phase-12.7/implementation-plan.md new file mode 100644 index 00000000..5339d04c --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/implementation-plan.md @@ -0,0 +1,402 @@ +# ANCP Implementation Plan + +## 🎯 実装戦略:段階的アプローチ + +### 基本方針 +1. **最小実装から開始**: 20語の固定辞書でMVP +2. **段階的拡張**: 機能を少しずつ追加 +3. **早期統合**: スモークテストと早期に統合 +4. **継続的検証**: 各段階で往復テスト実施 + +## 📅 Week 1: 基礎実装 + +### Day 1-2: プロジェクトセットアップ +```toml +# Cargo.toml に追加 +[features] +ancp = [] + +[dependencies] +phf = "0.11" # 静的マップ用 +tiktoken-rs = "0.5" # トークン計測用(optional) +``` + +### Day 3-4: 基本Transcoder実装 +```rust +// src/ancp/mod.rs +pub mod transcoder; +pub mod mappings; +pub mod error; + +// src/ancp/transcoder.rs +use phf::phf_map; + +static NYASH_TO_ANCP: phf::Map<&'static str, &'static str> = phf_map! { + "box" => "$", + "new" => "n", + "me" => "m", + "local" => "l", + "return" => "r", + // ... 初期20語 +}; + +pub struct BasicTranscoder; + +impl BasicTranscoder { + pub fn encode(&self, input: &str) -> String { + let mut result = String::with_capacity(input.len()); + let tokens = tokenize_simple(input); + + for token in tokens { + match NYASH_TO_ANCP.get(token.text) { + Some(ancp) => result.push_str(ancp), + None => result.push_str(token.text), + } + result.push_str(&token.trailing_space); + } + + result + } +} +``` + +### Day 5-7: 基本往復テスト +```rust +// tests/ancp_roundtrip.rs +#[test] +fn test_basic_roundtrip() { + let cases = vec![ + "box Test { }", + "new StringBox()", + "me.field = 42", + "local x = 10", + "return result", + ]; + + let transcoder = BasicTranscoder::new(); + + for case in cases { + let encoded = transcoder.encode(case); + let decoded = transcoder.decode(&encoded); + assert_eq!(case, decoded, "Failed for: {}", case); + } +} +``` + +## 📅 Week 2: スマート変換 + +### Day 8-9: コンテキスト認識パーサー +```rust +// src/ancp/context_parser.rs +pub struct ContextAwareTranscoder { + basic: BasicTranscoder, +} + +impl ContextAwareTranscoder { + pub fn encode(&self, input: &str) -> String { + let mut result = String::new(); + let mut in_string = false; + let mut in_comment = false; + + // 文字列・コメント内は変換しない + for (i, ch) in input.chars().enumerate() { + match ch { + '"' if !in_comment => in_string = !in_string, + '/' if !in_string && peek_next(input, i) == Some('/') => { + in_comment = true; + }, + '\n' => in_comment = false, + _ => {} + } + + // コンテキストに応じて処理 + if !in_string && !in_comment { + // トークン変換 + } else { + // そのまま出力 + } + } + + result + } +} +``` + +### Day 10-11: Lexer統合 +```rust +// src/parser/lexer.rs に追加 +impl Lexer { + pub fn with_ancp_support(input: &str) -> Self { + if input.starts_with(";ancp:") { + // ANCPモードで初期化 + Self::new_ancp_mode(input) + } else { + Self::new(input) + } + } + + fn new_ancp_mode(input: &str) -> Self { + // ANCP → Nyash変換してからレキシング + let transcoder = get_transcoder(); + let nyash_code = transcoder.decode(input).unwrap(); + Self::new(&nyash_code) + } +} +``` + +### Day 12-14: エラー位置マッピング +```rust +// src/ancp/source_map.rs +pub struct SourceMap { + mappings: Vec, +} + +impl SourceMap { + pub fn translate_position(&self, ancp_pos: Position) -> Position { + // ANCP位置 → Nyash位置への変換 + self.mappings + .binary_search_by_key(&ancp_pos, |m| m.ancp_pos) + .map(|i| self.mappings[i].nyash_pos) + .unwrap_or(ancp_pos) + } +} +``` + +## 📅 Week 3: ツール実装 + +### Day 15-16: CLIツール +```rust +// src/bin/nyash2ancp.rs +use clap::Parser; + +#[derive(Parser)] +struct Args { + #[clap(short, long)] + input: PathBuf, + + #[clap(short, long)] + output: Option, + + #[clap(long)] + measure_tokens: bool, +} + +fn main() -> Result<()> { + let args = Args::parse(); + let content = fs::read_to_string(&args.input)?; + + let transcoder = AncpTranscoder::new(); + let encoded = transcoder.encode(&content)?; + + if args.measure_tokens { + let reduction = measure_token_reduction(&content, &encoded); + eprintln!("Token reduction: {:.1}%", reduction * 100.0); + } + + match args.output { + Some(path) => fs::write(path, encoded)?, + None => print!("{}", encoded), + } + + Ok(()) +} +``` + +### Day 17-18: スモークテスト統合 +```bash +#!/bin/bash +# tools/test_ancp_roundtrip.sh + +test_file=$1 +expected_pattern=$2 + +# 1. 通常実行 +normal_output=$(./target/release/nyash "$test_file" 2>&1) + +# 2. ANCP変換 +ancp_file="${test_file%.nyash}.ancp" +./target/release/nyash2ancp -i "$test_file" -o "$ancp_file" + +# 3. ANCP実行 +ancp_output=$(./target/release/nyash "$ancp_file" 2>&1) + +# 4. 出力比較 +if [ "$normal_output" != "$ancp_output" ]; then + echo "ERROR: Output mismatch for $test_file" + exit 1 +fi + +# 5. パターン検証(既存のスモークテスト方式) +echo "$ancp_output" | grep -q "$expected_pattern" +``` + +### Day 19-21: VSCode拡張(基礎) +```typescript +// vscode-extension/src/extension.ts +import * as vscode from 'vscode'; + +export function activate(context: vscode.ExtensionContext) { + // ホバープロバイダー + const hoverProvider = vscode.languages.registerHoverProvider( + 'ancp', + { + provideHover(document, position) { + const word = document.getText( + document.getWordRangeAtPosition(position) + ); + + const original = ancpToNyash(word); + if (original !== word) { + return new vscode.Hover( + `ANCP: \`${word}\` → Nyash: \`${original}\`` + ); + } + } + } + ); + + context.subscriptions.push(hoverProvider); +} +``` + +## 📅 Week 4: 最適化と統合 + +### Day 22-23: tiktoken実測と最適化 +```python +# tools/measure_ancp_efficiency.py +import tiktoken +import json + +enc = tiktoken.get_encoding("cl100k_base") + +def measure_file(nyash_path, ancp_path): + with open(nyash_path) as f: + nyash_code = f.read() + with open(ancp_path) as f: + ancp_code = f.read() + + nyash_tokens = len(enc.encode(nyash_code)) + ancp_tokens = len(enc.encode(ancp_code)) + + return { + "file": nyash_path, + "nyash_tokens": nyash_tokens, + "ancp_tokens": ancp_tokens, + "reduction": 1 - (ancp_tokens / nyash_tokens), + "nyash_chars": len(nyash_code), + "ancp_chars": len(ancp_code), + } + +# 全サンプルファイルで測定 +results = [] +for nyash_file in glob.glob("examples/*.nyash"): + ancp_file = nyash_file.replace(".nyash", ".ancp") + results.append(measure_file(nyash_file, ancp_file)) + +# 統計出力 +avg_reduction = sum(r["reduction"] for r in results) / len(results) +print(f"Average token reduction: {avg_reduction:.1%}") +``` + +### Day 24-25: CI/CD統合 +```yaml +# .github/workflows/ancp.yml +name: ANCP Tests + +on: [push, pull_request] + +jobs: + ancp-roundtrip: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build with ANCP + run: cargo build --release --features ancp + + - name: Run roundtrip tests + run: | + for f in examples/*.nyash; do + echo "Testing: $f" + ./tools/test_ancp_roundtrip.sh "$f" + done + + - name: Measure efficiency + run: | + python3 tools/measure_ancp_efficiency.py > ancp_report.json + + - name: Upload report + uses: actions/upload-artifact@v3 + with: + name: ancp-efficiency-report + path: ancp_report.json +``` + +### Day 26-28: ドキュメント・最終調整 +- ユーザーガイド作成 +- API ドキュメント生成 +- パフォーマンスチューニング +- 最終テスト + +## 🎯 成功指標 + +### Week 1終了時 +- [ ] 基本20語で往復変換成功 +- [ ] 単純なNyashプログラムが動作 + +### Week 2終了時 +- [ ] コンテキスト認識変換 +- [ ] Lexer統合完了 +- [ ] エラー位置の正確なマッピング + +### Week 3終了時 +- [ ] CLI ツール完成 +- [ ] スモークテスト統合 +- [ ] VSCode基本機能 + +### Week 4終了時 +- [ ] トークン削減率50%以上達成 +- [ ] 全サンプルで往復テスト成功 +- [ ] CI/CD完全統合 +- [ ] ドキュメント完成 + +## 🚧 リスクと対策 + +### 技術的リスク +1. **パフォーマンス劣化** + - 対策: 段階的実装で早期発見 + - 対策: プロファイリング継続実施 + +2. **互換性問題** + - 対策: 既存テストスイートで検証 + - 対策: feature flagで段階的有効化 + +### 運用リスク +1. **採用障壁** + - 対策: 分かりやすいドキュメント + - 対策: 移行ツール提供 + +2. **メンテナンス負荷** + - 対策: 自動テスト充実 + - 対策: CI/CDで品質保証 + +## 📝 チェックリスト + +### 実装前 +- [ ] tiktoken実測による記号選定完了 +- [ ] 関係者への影響確認 +- [ ] feature flag設計確認 + +### 実装中 +- [ ] 日次で往復テスト実施 +- [ ] パフォーマンス計測継続 +- [ ] ドキュメント同時更新 + +### 実装後 +- [ ] 全スモークテスト合格 +- [ ] トークン削減率目標達成 +- [ ] ユーザーガイド完成 + +--- + +この計画に従って実装を進めることで、4週間でANCPを完成させ、AIとの協働開発を革命的に改善します! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/technical-spec.md b/docs/development/roadmap/phases/phase-12.7/technical-spec.md new file mode 100644 index 00000000..60d1aee3 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/technical-spec.md @@ -0,0 +1,293 @@ +# ANCP Technical Specification v1.0 + +## 1. プロトコル概要 + +### 1.1 設計原則 +- **可逆性**: 100%の双方向変換保証 +- **効率性**: 50-70%のトークン削減 +- **可読性**: 人間も慣れれば読み書き可能 +- **拡張性**: バージョニングによる将来対応 + +### 1.2 プロトコルヘッダー +``` +;ancp:1.0 nyash:0.5.0; +``` +- `ancp:1.0`: ANCPプロトコルバージョン +- `nyash:0.5.0`: 対応Nyashバージョン + +## 2. 記号マッピング仕様 + +### 2.1 予約語マッピング(優先度順) + +#### Tier 1: 超高頻度(1文字) +| Nyash | ANCP | 頻度 | tiktoken削減 | +|-------|------|------|--------------| +| me | m | 極高 | 2→1 (50%) | +| new | n | 高 | 3→1 (67%) | +| return | r | 高 | 6→1 (83%) | +| local | l | 高 | 5→1 (80%) | + +#### Tier 2: 高頻度(1文字特殊) +| Nyash | ANCP | 理由 | +|-------|------|------| +| box | $ | 金庫のメタファー | +| from | @ | 接続を表現 | +| init | # | 初期化のハッシュ | +| if | ? | 疑問・条件 | +| else | : | 条件の区切り | + +#### Tier 3: 中頻度(1-2文字) +| Nyash | ANCP | 理由 | +|-------|------|------| +| static | S | 大文字で静的を表現 | +| loop | L | ループのL | +| birth | b | 誕生のb | +| override | O | 上書きのO | +| pack | p | パックのp | + +### 2.2 演算子・記号の扱い +- 算術演算子(+, -, *, /): そのまま +- 比較演算子(==, !=, <, >): そのまま +- 論理演算子(and, or, not): 検討中 + - `and` → `&` + - `or` → `|` + - `not` → `!` + +### 2.3 複合パターン +```nyash +// 元のコード +box Cat from Animal { + init { name, age } +} + +// ANCP変換後 +$Cat@Animal{#{name,age}} +``` + +## 3. パース規則 + +### 3.1 トークン境界 +- 記号の前後に空白は不要(`$Cat`でOK) +- 識別子の区切りは既存ルール継承 +- 文字列・コメント内は変換しない + +### 3.2 優先順位 +1. 文字列リテラル内: 変換なし +2. コメント内: 変換なし +3. 識別子の一部: 変換なし(`method_name`の`me`は変換しない) +4. 独立トークン: 変換対象 + +### 3.3 コンテキスト認識 +```rust +enum TokenContext { + Normal, // 通常(変換対象) + StringLiteral, // 文字列内 + Comment, // コメント内 + Identifier, // 識別子の一部 +} +``` + +## 4. 実装仕様 + +### 4.1 Transcoder API +```rust +pub trait AncpTranscoder { + // 基本変換 + fn encode(&self, nyash: &str) -> Result; + fn decode(&self, ancp: &str) -> Result; + + // ストリーミング変換 + fn encode_stream(&self, input: impl Read) -> impl Read; + fn decode_stream(&self, input: impl Read) -> impl Read; + + // 位置情報保持 + fn encode_with_map(&self, nyash: &str) -> Result<(String, SourceMap), AncpError>; + fn decode_with_map(&self, ancp: &str) -> Result<(String, SourceMap), AncpError>; +} +``` + +### 4.2 SourceMap仕様 +```rust +pub struct SourceMap { + mappings: Vec, +} + +pub struct Mapping { + // 元の位置 + original_line: u32, + original_column: u32, + original_token: String, + + // 変換後の位置 + generated_line: u32, + generated_column: u32, + generated_token: String, +} +``` + +### 4.3 エラーハンドリング +```rust +pub enum AncpError { + // 構文エラー + InvalidSyntax { position: Position, expected: String }, + + // バージョン非互換 + VersionMismatch { required: Version, found: Version }, + + // 変換不可能 + UnsupportedConstruct { construct: String, reason: String }, +} +``` + +## 5. 統合仕様 + +### 5.1 Lexer統合 +```rust +// Lexerに追加 +pub enum InputDialect { + Nyash, + Ancp(Version), +} + +impl Lexer { + pub fn new_with_dialect(input: &str, dialect: InputDialect) -> Self { + // ヘッダー検出で自動判定も可能 + let dialect = detect_dialect(input).unwrap_or(dialect); + // ... + } +} +``` + +### 5.2 CLI統合 +```bash +# 変換コマンド +nyash --to-ancp input.nyash > output.ancp +nyash --from-ancp input.ancp > output.nyash + +# 直接実行 +nyash --dialect=ancp script.ancp + +# フォーマット表示 +nyash --view=ancp script.nyash # Nyashファイルをancp形式で表示 +nyash --view=hybrid script.ancp # 並列表示 +``` + +### 5.3 VSCode統合 +```typescript +// 言語サーバープロトコル拡張 +interface AncpHoverInfo { + original: string; // Nyash形式 + compressed: string; // ANCP形式 + savings: number; // 削減率 +} + +// リアルタイム変換 +interface AncpLens { + showOriginal: boolean; + showCompressed: boolean; + showSavings: boolean; +} +``` + +## 6. テスト仕様 + +### 6.1 往復テスト +```rust +#[test] +fn roundtrip_all_constructs() { + let test_cases = vec![ + // 基本構造 + "box Test { }", + "box Child from Parent { }", + + // メソッド定義 + "birth() { me.x = 1 }", + "override method() { from Parent.method() }", + + // 制御構造 + "if x == 1 { } else { }", + "loop(i < 10) { i = i + 1 }", + + // 複雑な例 + include_str!("../examples/complex.nyash"), + ]; + + for case in test_cases { + let encoded = transcoder.encode(case).unwrap(); + let decoded = transcoder.decode(&encoded).unwrap(); + assert_eq!(case, decoded); + } +} +``` + +### 6.2 トークン効率テスト +```rust +#[test] +fn measure_token_reduction() { + let encoder = tiktoken::get_encoding("cl100k_base"); + + let original = "box Cat from Animal { init { name } }"; + let ancp = "$Cat@Animal{#{name}}"; + + let original_tokens = encoder.encode(original).len(); + let ancp_tokens = encoder.encode(ancp).len(); + + let reduction = 1.0 - (ancp_tokens as f64 / original_tokens as f64); + assert!(reduction >= 0.5); // 50%以上の削減を保証 +} +``` + +### 6.3 エラー位置テスト +```rust +#[test] +fn error_position_mapping() { + let ancp = "$Test{invalid syntax here}"; + let result = transcoder.decode_with_map(ancp); + + match result { + Err(AncpError::InvalidSyntax { position, .. }) => { + // エラー位置が正しくマッピングされているか + assert_eq!(position.line, 1); + assert_eq!(position.column, 14); + } + _ => panic!("Expected syntax error"), + } +} +``` + +## 7. パフォーマンス目標 + +### 7.1 変換性能 +- エンコード: 100MB/s以上 +- デコード: 150MB/s以上 +- メモリ使用: 入力サイズの2倍以内 + +### 7.2 実行時性能 +- パース時間増加: 10%以内 +- 実行時オーバーヘッド: なし(パース後は同一AST) + +## 8. セキュリティ考慮事項 + +### 8.1 インジェクション対策 +- ANCP記号が既存コードを破壊しないよう検証 +- 文字列エスケープの適切な処理 + +### 8.2 バージョン互換性 +- 古いANCPバージョンの適切なエラー表示 +- 将来の拡張に備えた設計 + +## 9. 将来の拡張 + +### 9.1 ANCP v2.0候補 +- 文脈依存圧縮(頻出パターンの動的割当) +- カスタム辞書サポート +- バイナリ形式(BANCTP) + +### 9.2 AI特化拡張 +- モデル別最適化プロファイル +- トークナイザー直接統合 +- 意味保持圧縮 + +--- + +この仕様書は、ANCPの技術的実装の基準となる文書です。実装時はこの仕様に従い、必要に応じて更新してください。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/TASKS.md b/docs/development/roadmap/phases/phase-12/TASKS.md index d6dac492..a9c986f8 100644 --- a/docs/development/roadmap/phases/phase-12/TASKS.md +++ b/docs/development/roadmap/phases/phase-12/TASKS.md @@ -1,16 +1,19 @@ # Phase 12 Task Board (v2 - セルフホスティング対応) +Status: Tier-0 完了(vtable雛形 + レジストリ + VM優先経路)。次は Tier-1 の最小Nyash ABIサンプル実装へ。 + 目的: C ABI を壊さず、TypeBox + 統一ディスパッチで Nyash ABI を段階導入。MIR→VM→JIT を「綺麗な箱」で統一。**最終的にRust依存を排除し、セルフホスティングを実現。** ## Tier-0(直近・安全に積める) - [x] MapBoxの実用拡張(stringキー/便利API) -- [x] `keys()/values()` ランタイムシム(現状は改行区切りString返却) -- [ ] TypeBoxレジストリ(雛形) - - Box名/FQN、type_id、メソッド表、returns_result を登録 - - 既存 `nyash.toml` → TypeBoxInfo への変換層 -- [ ] 統一ディスパッチ層(VM) - - Nyash ABI vtable優先 → 無ければ C ABI(TLV)へフォールバック - - 所有権・セーフポイントのガード(MAY_BLOCKのみ初期対応) +- [x] `keys()/values()` 実装(ArrayBox返却に更新) +- [x] TypeBoxレジストリ(雛形) + - Box名/FQN、type_id、メソッド表(静的スロット)を登録(`src/runtime/type_registry.rs`) + - 既存 `nyash.toml` → TypeBoxInfo 変換層は別途(未着手) +- [x] 統一ディスパッチ層(VM・雛形) + - `NYASH_ABI_VTABLE=1` で vtable優先のVM経路を有効化(fallbackはC ABI/TLV)。 + - Array/Map/String/Instance の主要メソッドを最小カバレッジで処理(`try_boxcall_vtable_stub`)。 + - 所有権・セーフポイントのガードは既存Barrier呼び出しで一部対応(MAY_BLOCK等は今後拡張)。 - [x] プラグインテスター更新(v2ローダに対応): `src/bin/test_plugin_loader_v2.rs` ## Tier-1(実証) @@ -24,9 +27,8 @@ - [ ] NyashValueインライン(i64/bool)の高速化 - [ ] 例外/エラーの完全変換(panic→nyrt_err) - [ ] 所有権契約の遵守(TRANSFER/BORROW/CLONE) -- [ ] `keys()/values()` の正式実装(ArrayBox返却) - - 選択肢A: ランタイムで ArrayBox を構築 - - 選択肢B: Mapプラグインに KeysArrayBox を同梱(要設定追加) +- [x] `keys()/values()` の正式実装(ArrayBox返却) + - 採用: ランタイムで ArrayBox を構築(`src/boxes/map_box.rs`) ## Tier-3(セルフホスティング)🔥新規 - [ ] Nyash ABI C実装の開始 diff --git a/docs/development/roadmap/phases/phase-15/README.md b/docs/development/roadmap/phases/phase-15/README.md index 9a805f73..a930022a 100644 --- a/docs/development/roadmap/phases/phase-15/README.md +++ b/docs/development/roadmap/phases/phase-15/README.md @@ -1,10 +1,10 @@ -# Phase 15: Nyashセルフホスティング - 71k→15k行への革命 +# Phase 15: Nyashセルフホスティング - 世界一美しい箱の完成 ## 📋 概要 NyashでNyashコンパイラを書く、完全なセルフホスティングの実現フェーズ。 -内蔵Cranelift JITを活用し、外部コンパイラ依存から完全に解放される。 -**革命的成果:71,000行→15,000行(75%削減)** +MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存から完全に解放される。 +**究極の目標:80,000行→20,000行(75%削減)→ さらなる最適化へ** ## 🎯 フェーズの目的 @@ -36,11 +36,26 @@ NyashでNyashコンパイラを書く、完全なセルフホスティングの ## 🔧 技術的アプローチ -### 内蔵Craneliftの利点 +### MIR 13命令の革命 +- **基本演算(5)**: Const, UnaryOp, BinOp, Compare, TypeOp +- **メモリ(2)**: Load, Store +- **制御(4)**: Branch, Jump, Return, Phi +- **Box(1)**: BoxCall(すべての箱操作を統合) +- **外部(1)**: ExternCall + +この究極のシンプルさにより、直接x86変換も現実的に! + +### バックエンドの選択肢 +#### 1. Cranelift(現在の主力) - **軽量**: 3-5MB程度(LLVMの1/10以下) - **JIT特化**: メモリ上での動的コンパイル - **Rust統合**: 静的リンクで配布容易 +#### 2. 直接x86エミッタ(革新的アプローチ) +- **dynasm-rs/iced-x86**: Rust内で直接アセンブリ生成 +- **テンプレート・スティッチャ方式**: 2-3KBの超小型バイナリ可能 +- **完全な制御**: 依存ゼロの究極形 + ### コード削減の秘密 - **Arc自動化**: 明示的ロック管理不要(-30%) - **型システム簡略化**: 動的型付けの恩恵(-20%) @@ -49,7 +64,7 @@ NyashでNyashコンパイラを書く、完全なセルフホスティングの ### 実装例 ```nyash -// 71,000行のRust実装が... +// 80,000行のRust実装が20,000行のNyashに! box NyashCompiler { init { parser, lowerer, backend } @@ -68,6 +83,34 @@ box MirExecutor { } ``` +### テンプレート・スティッチャ方式(革新的アプローチ) +```nyash +// 各MIR命令を共通スタブとして実装 +box TemplateStitcher { + init { stubs } + + constructor() { + me.stubs = new MapBox() + // 各命令の共通実装をスタブとして登録 + me.stubs.set("Const", 0x1000) // スタブアドレス + me.stubs.set("BinOp", 0x1100) + me.stubs.set("BoxCall", 0x1200) + // ... 13命令分のスタブ + } + + generate(mir) { + local jumps = new ArrayBox() + + // プログラムはスタブ間のジャンプ列に! + for inst in mir.instructions { + jumps.push("jmp " + me.stubs.get(inst.type)) + } + + return jumps // 超小型バイナリ! + } +} +``` + ## 🔗 EXEファイル生成・リンク戦略 ### 段階的アプローチ @@ -111,7 +154,18 @@ box MirExecutor { 外部ツールチェーンに依存しない、真の自立したプログラミング言語へ。 ### 数値で見る革命 -- **コード行数**: 71,000 → 15,000行(**75%削減**) -- **理解容易性**: 1週間で読破可能なコンパイラ -- **貢献しやすさ**: 誰でも改造できる規模 -- **教育的価値**: 世界一シンプルな実用コンパイラ \ No newline at end of file +- **現在**: 80,000行(Rust実装) +- **第一目標**: 20,000行(Nyashセルフホスティング、**75%削減**) +- **究極の夢**: さらなる最適化でより小さく! +- **MIR命令数**: たった13個で全機能実現 +- **理解容易性**: 週末で読破可能なコンパイラ +- **バイナリサイズ**: テンプレート方式なら2-3KBも可能 +- **教育的価値**: 世界一美しく、世界一小さい実用コンパイラ + +### 🌟 Everything is Boxの究極形 +- コンパイラもBox +- リンカーもBox +- アセンブラもBox +- すべてがBox! + +**世界一美しい箱は、自分自身さえも美しく包み込む** \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-15/self-hosting-plan.txt b/docs/development/roadmap/phases/phase-15/self-hosting-plan.txt index a7dff576..2cfa3cc2 100644 --- a/docs/development/roadmap/phases/phase-15/self-hosting-plan.txt +++ b/docs/development/roadmap/phases/phase-15/self-hosting-plan.txt @@ -1,15 +1,22 @@ ================================================================================ -Phase 15: Nyashセルフホスティング計画 - 71k→15k行への革命的圧縮 +Phase 15: Nyashセルフホスティング計画 - 80k→20k行への革命的圧縮 ================================================================================ 【ビジョン】 NyashでNyashコンパイラを書き、Nyashプログラムをコンパイル・実行する 完全なセルフホスティング環境の実現 + 劇的なコード圧縮(75%削減) +MIR 13命令の美しさを最大限に活かした究極の実装 【数値目標】 -現在: 71,000行(Rust実装) -目標: 15,000-20,000行(Nyash実装) -削減率: 約75% +現在: 80,000行(Rust実装) +第一目標: 20,000行(Nyash実装) +削減率: 75% +究極の夢: さらなる最適化で更に小さく! + +【革命的要素】 +MIR命令数: たった13個で全機能実現 +バックエンド選択: Cranelift/直接x86/テンプレート方式 +バイナリサイズ: 2-3KBの超小型も可能 ================================================================================ 1. なぜセルフホスティングか + コード圧縮の価値 @@ -44,19 +51,26 @@ NyashでNyashコンパイラを書き、Nyashプログラムをコンパイル └─ 依存が少ない: ビルド時間短縮 ■ 既存の準備状況 +├─ ✅ MIR 13命令に削減完了(究極のシンプルさ) ├─ ✅ Cranelift統合準備済み(Cargo.toml) -├─ ✅ MIR15確定(シンプルなIR) +├─ ✅ 直接x86エミッタ選択肢(dynasm-rs/iced-x86) ├─ ✅ プラグインシステム(拡張可能) -└─ 🔄 Phase 10でJIT実装予定 +└─ ✅ Phase 12で統一実行パス確立 ================================================================================ 3. 段階的実装計画 - ChatGPT5戦略による最速ルート ================================================================================ -■ Phase 15.0: YAML自動生成基盤(1-2週間)【最優先】 +■ Phase 15.0: バックエンド選択戦略(新規追加) +├─ 選択肢1: Cranelift(現在の主力、安定) +├─ 選択肢2: 直接x86エミッタ(dynasm-rs使用、高速) +├─ 選択肢3: テンプレート・スティッチャ(2-3KB超小型) +└─ 段階的移行: Cranelift→x86→テンプレート + +■ Phase 15.1: YAML自動生成基盤(1-2週間)【最優先】 ├─ boxes.yaml: Box型定義(type_id, method_id対応表) ├─ externs.yaml: 外部関数定義(C ABI境界) -├─ semantics.yaml: MIR15セマンティクス定義 +├─ semantics.yaml: MIR13セマンティクス定義(13命令!) └─ build.rs: 自動生成システム(重複コード即削除) 効果: Array/Instance/Console等で即座に1-2万行削減 @@ -181,21 +195,26 @@ box MiniLinkerBox { ├─ 真の「Everything is Box」体験 └─ コントリビューション容易化 -■ コード削減の具体例 -├─ Boxes実装: 11,153行 → 2,000行(80%削減) -├─ Interpreter: 11,278行 → 3,000行(73%削減) -├─ MIR: 10,918行 → 2,500行(77%削減) -├─ Parser: 2,680行 → 800行(70%削減) -└─ Backend: 9,196行 → 3,000行(67%削減) +■ コード削減の具体例(80k→20k) +├─ Boxes実装: 13,000行 → 3,000行(77%削減) +├─ Interpreter: 15,000行 → 4,000行(73%削減) +├─ MIR: 12,000行 → 2,000行(83%削減、13命令化の効果) +├─ Parser: 5,000行 → 1,500行(70%削減) +├─ Backend: 15,000行 → 4,000行(73%削減) +└─ その他: 20,000行 → 5,500行(72%削減) ================================================================================ 7. 成功指標 ================================================================================ □ NyashコンパイラがNyash自身をコンパイル可能 -□ コード行数: 15,000-20,000行以内(75%削減達成) +□ コード行数: 20,000行以内(75%削減達成) +□ MIR 13命令での完全実装 □ 性能: Rustコンパイラの50%以上 -□ バイナリサイズ: 10MB以下(Cranelift込み) +□ バイナリサイズ: + - Cranelift版: 10MB以下 + - 直接x86版: 5MB以下 + - テンプレート版: 1MB以下(夢の2-3KBも視野) □ コンパイル時間: 中規模プロジェクトで10秒以内 □ 100%のテストケース互換性 □ trace_hash/heap_hashパリティ(VM/JIT/AOT) diff --git a/docs/development/roadmap/phases/phase-15/technical-details.md b/docs/development/roadmap/phases/phase-15/technical-details.md index e8bdbf23..1a52cba2 100644 --- a/docs/development/roadmap/phases/phase-15/technical-details.md +++ b/docs/development/roadmap/phases/phase-15/technical-details.md @@ -13,11 +13,12 @@ NyashCompiler (Nyashで実装) ├── Middle-end │ ├── Type Checker │ ├── Name Resolver -│ ├── MIR Lowerer +│ ├── MIR Lowerer (→13命令) │ └── Optimizer -└── Backend +└── Backend(複数選択可能) ├── CraneliftBox (JITラッパー) - ├── Code Generator + ├── X86EmitterBox (直接エミッタ) + ├── TemplateStitcherBox (超小型) └── Runtime Linker ``` @@ -204,7 +205,7 @@ box MIRLowerer { } ``` -## 4. Cranelift統合 +## 4. バックエンド実装 ### 4.1 CraneliftBox実装 @@ -244,6 +245,107 @@ box CraneliftBox { } ``` +### 4.2 X86EmitterBox実装(直接x86生成) + +```nyash +box X86EmitterBox { + init { code_buffer, label_map } + + constructor() { + me.code_buffer = new ArrayBox() + me.label_map = new MapBox() + } + + compile(mir) { + // MIR 13命令を直接x86-64に変換! + for func in mir.functions { + me.emitFunction(func) + } + + return me.code_buffer + } + + emitInstruction(inst) { + // MIR命令をx86テンプレートに変換 + if inst.type == "Const" { + // mov rax, imm64 + me.emit_mov_imm(inst.dst, inst.value) + } + + if inst.type == "BinOp" { + if inst.op == "Add" { + // add rax, rbx + me.emit_add(inst.dst, inst.left, inst.right) + } + } + + if inst.type == "BoxCall" { + // mov rdi, receiver + // mov rax, [rdi] ; vtable + // call [rax+slot*8] ; method call + me.emit_boxcall(inst.recv, inst.slot) + } + + // ... 残り10命令のテンプレート + } + + emit_mov_imm(reg, value) { + // REX.W + mov r64, imm64 + me.code_buffer.push(0x48) // REX.W + me.code_buffer.push(0xB8 + reg) // mov opcode + + // 64ビット即値をリトルエンディアンで + for i in range(0, 8) { + me.code_buffer.push((value >> (i * 8)) & 0xFF) + } + } +} +``` + +### 4.3 テンプレート・スティッチャ実装(超小型バイナリ) + +```nyash +box TemplateStitcherBox { + init { stub_addresses, jump_table } + + constructor() { + // 各MIR命令の共通スタブアドレス + me.stub_addresses = new MapBox() + me.stub_addresses.set("Const", 0x1000) + me.stub_addresses.set("UnaryOp", 0x1100) + me.stub_addresses.set("BinOp", 0x1200) + me.stub_addresses.set("Compare", 0x1300) + me.stub_addresses.set("TypeOp", 0x1400) + me.stub_addresses.set("Load", 0x1500) + me.stub_addresses.set("Store", 0x1600) + me.stub_addresses.set("Branch", 0x1700) + me.stub_addresses.set("Jump", 0x1800) + me.stub_addresses.set("Return", 0x1900) + me.stub_addresses.set("Phi", 0x1A00) + me.stub_addresses.set("BoxCall", 0x1B00) + me.stub_addresses.set("ExternCall", 0x1C00) + } + + compile(mir) { + me.jump_table = new ArrayBox() + + // プログラムはスタブへのジャンプ列として表現 + for inst in mir.instructions { + local stub_addr = me.stub_addresses.get(inst.type) + + // jmp rel32 + me.jump_table.push(0xE9) // jmp opcode + me.jump_table.push_rel32(stub_addr) + + // 命令固有のパラメータをデータセクションに配置 + me.encodeParameters(inst) + } + + return me.jump_table + } +} +``` + ## 5. ブートストラップ手順 ### 5.1 段階的移行 diff --git a/nyash.toml b/nyash.toml index 144f8e8f..03a8597b 100644 --- a/nyash.toml +++ b/nyash.toml @@ -151,6 +151,7 @@ RegexBox = 52 EncodingBox = 53 TOMLBox = 54 PathBox = 55 +EguiBox = 70 PyRuntimeBox= 40 PyObjectBox = 41 PythonParserBox = 60 @@ -174,6 +175,7 @@ PythonCompilerBox = 61 "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" @@ -372,6 +374,23 @@ 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ログ diff --git a/plugins/nyash-array-plugin/src/lib.rs b/plugins/nyash-array-plugin/src/lib.rs index 6d740c53..12e76f15 100644 --- a/plugins/nyash-array-plugin/src/lib.rs +++ b/plugins/nyash-array-plugin/src/lib.rs @@ -4,6 +4,8 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; +use std::os::raw::c_char; +use std::ffi::CStr; // ===== Error Codes (aligned with existing plugins) ===== const NYB_SUCCESS: i32 = 0; @@ -120,6 +122,68 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ===== TypeBox FFI (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn array_resolve(name: *const c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "len" | "length" => METHOD_LENGTH, + "get" => METHOD_GET, + "set" => METHOD_SET, + "push" => METHOD_PUSH, + _ => 0, + } +} + +extern "C" fn array_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + unsafe { + match method_id { + METHOD_LENGTH => { + if let Ok(map) = INSTANCES.lock() { if let Some(inst) = map.get(&instance_id) { return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; } + } + METHOD_GET => { + let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS }; + if idx < 0 { return NYB_E_INVALID_ARGS; } + if let Ok(map) = INSTANCES.lock() { if let Some(inst) = map.get(&instance_id) { let i = idx as usize; if i >= inst.data.len() { return NYB_E_INVALID_ARGS; } return write_tlv_i64(inst.data[i], result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; } + } + METHOD_SET => { + let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS }; + let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS }; + if idx < 0 { return NYB_E_INVALID_ARGS; } + if let Ok(mut map) = INSTANCES.lock() { if let Some(inst) = map.get_mut(&instance_id) { let i = idx as usize; let len = inst.data.len(); if i < len { inst.data[i] = val; } else if i == len { inst.data.push(val); } else { return NYB_E_INVALID_ARGS; } return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; } + } + METHOD_PUSH => { + let val = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS }; + if let Ok(mut map) = INSTANCES.lock() { if let Some(inst) = map.get_mut(&instance_id) { inst.data.push(val); return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; } + } + _ => NYB_E_INVALID_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_ArrayBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, // 'TYBX' + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"ArrayBox\0".as_ptr() as *const c_char, + resolve: Some(array_resolve), + invoke_id: Some(array_invoke_id), + capabilities: 0, +}; + // ===== Minimal TLV helpers (compatible with host expectations) ===== fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { unsafe { diff --git a/plugins/nyash-console-plugin/src/lib.rs b/plugins/nyash-console-plugin/src/lib.rs index 990834ff..e3068260 100644 --- a/plugins/nyash-console-plugin/src/lib.rs +++ b/plugins/nyash-console-plugin/src/lib.rs @@ -150,3 +150,54 @@ pub extern "C" fn nyash_plugin_invoke( } } } + +// ===== TypeBox FFI (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn console_resolve(name: *const c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "log" => METHOD_LOG, + "println" => METHOD_PRINTLN, + _ => 0, + } +} + +extern "C" fn console_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + unsafe { + match method_id { + METHOD_LOG | METHOD_PRINTLN => { + let slice = std::slice::from_raw_parts(args, args_len); + let s = match parse_first_string(slice) { + Ok(s) => s, + Err(_) => format_first_any(slice).unwrap_or_else(|| "".to_string()), + }; + if method_id == METHOD_LOG { print!("{}", s); } else { println!("{}", s); } + return write_tlv_void(result, result_len); + } + _ => NYB_E_INVALID_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_ConsoleBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, // 'TYBX' + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"ConsoleBox\0".as_ptr() as *const c_char, + resolve: Some(console_resolve), + invoke_id: Some(console_invoke_id), + capabilities: 0, +}; diff --git a/plugins/nyash-egui-plugin/Cargo.toml b/plugins/nyash-egui-plugin/Cargo.toml new file mode 100644 index 00000000..866f1c50 --- /dev/null +++ b/plugins/nyash-egui-plugin/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "nyash-egui-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +once_cell = "1.21" +crossbeam-channel = "0.5" +cfg-if = "1.0" + +# Optional: real GUI on Windows behind a feature in follow-ups +[features] +default = [] +with-egui = ["eframe", "egui"] + +[target.'cfg(windows)'.dependencies] +eframe = { version = "0.27", optional = true } +egui = { version = "0.27", optional = true } + diff --git a/plugins/nyash-egui-plugin/src/lib.rs b/plugins/nyash-egui-plugin/src/lib.rs new file mode 100644 index 00000000..f1f362d0 --- /dev/null +++ b/plugins/nyash-egui-plugin/src/lib.rs @@ -0,0 +1,242 @@ +//! Nyash EguiBox Plugin (TypeBox ABI skeleton) +//! - Provides a minimal window/UI placeholder via Nyash ABI +//! - Windows GUI integration (egui/eframe) can be enabled later via `with-egui` feature + +use once_cell::sync::Lazy; +use std::{collections::HashMap, sync::{Mutex, atomic::{AtomicU32, Ordering}}}; + +// ===== Error/Status codes (BID-FFI v1 aligned) ===== +const OK: i32 = 0; +const E_SHORT: i32 = -1; +const E_TYPE: i32 = -2; +const E_METHOD: i32 = -3; +const E_ARGS: i32 = -4; +const E_FAIL: i32 = -5; + +// ===== IDs ===== +const TID_EGUI: u32 = 70; // match nyash.toml [box_types] + +// methods +const M_BIRTH: u32 = 0; +const M_OPEN: u32 = 1; // open(width:int, height:int, title:str) +const M_UI_LABEL: u32 = 2; // uiLabel(text:str) +const M_UI_BUTTON: u32 = 3; // uiButton(text:str) -> future: events +const M_POLL_EVENT: u32 = 4; // pollEvent() -> Result.Ok(text) / Result.Err("none") +const M_RUN: u32 = 5; // run() -> enters loop or no-op +const M_CLOSE: u32 = 6; // close() +const M_FINI: u32 = u32::MAX; + +#[derive(Default)] +struct EguiInstance { + width: i32, + height: i32, + title: String, + labels: Vec, +} + +static INST: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static NEXT_ID: AtomicU32 = AtomicU32::new(1); + +// ===== TypeBox ABI (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, + pub version: u16, + pub struct_size: u16, + pub name: *const std::os::raw::c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} + +// This simple POD struct contains raw pointers; mark as Sync for static export +unsafe impl Sync for NyashTypeBoxFfi {} + +const ABI_TAG: u32 = 0x58594254; // 'T''Y''B''X' little-endian (TYBX) + +extern "C" fn tb_resolve(name: *const std::os::raw::c_char) -> u32 { + unsafe { + if name.is_null() { return 0; } + let s = std::ffi::CStr::from_ptr(name).to_string_lossy(); + match s.as_ref() { + "birth" => M_BIRTH, + "open" => M_OPEN, + "uiLabel" => M_UI_LABEL, + "uiButton" => M_UI_BUTTON, + "pollEvent" => M_POLL_EVENT, + "run" => M_RUN, + "close" => M_CLOSE, + "fini" => M_FINI, + _ => 0, + } + } +} + +extern "C" fn tb_invoke_id(_method_id: u32, instance_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + nyash_plugin_invoke(TID_EGUI, _method_id, instance_id, args, args_len, result, result_len) +} + +static TYPE_NAME: &[u8] = b"EguiBox\0"; +#[no_mangle] +pub static nyash_typebox_EguiBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: ABI_TAG, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: TYPE_NAME.as_ptr() as *const _, + resolve: Some(tb_resolve), + invoke_id: Some(tb_invoke_id), + capabilities: 0, +}; + +// ===== Plugin entry points ===== +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { 1 } + +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { OK } + +#[no_mangle] +pub extern "C" fn nyash_plugin_invoke( + type_id: u32, + method_id: u32, + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + if type_id != TID_EGUI { return E_TYPE; } + unsafe { + match method_id { + M_BIRTH => { + let need = 4; // instance_id (u32 LE) + if result_len.is_null() { return E_ARGS; } + if result.is_null() || *result_len < need { *result_len = need; return E_SHORT; } + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + if let Ok(mut m) = INST.lock() { m.insert(id, EguiInstance::default()); } else { return E_FAIL; } + let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK + } + M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_FAIL } } + M_OPEN => { + let (w, h, title) = match tlv_read_open_args(args, args_len) { Some(v) => v, None => return E_ARGS }; + if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.width=w; inst.height=h; inst.title=title; } else { return E_FAIL; } } else { return E_FAIL; } + write_tlv_void(result, result_len) + } + M_UI_LABEL => { + let text = match tlv_read_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; + if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.labels.push(text); } else { return E_FAIL; } } else { return E_FAIL; } + write_tlv_void(result, result_len) + } + M_UI_BUTTON => { + // For now: stub, accept and return Void + if tlv_read_string(args, args_len, 0).is_none() { return E_ARGS; } + write_tlv_void(result, result_len) + } + M_POLL_EVENT => { + // Stub: no events yet → return empty string "" (Ok) + write_tlv_string("", result, result_len) + } + M_RUN => { + // Windows + with-egui: 実ウィンドウを表示 + #[cfg(all(windows, feature = "with-egui"))] + { + if let Ok(m) = INST.lock() { + if let Some(inst) = m.get(&instance_id) { + winrun::run_window(inst.width, inst.height, &inst.title, inst.labels.clone()); + } + } + } + // それ以外はスタブ + write_tlv_void(result, result_len) + } + M_CLOSE => { + // Stub: no-op close + write_tlv_void(result, result_len) + } + _ => E_METHOD, + } + } +} + +// ===== TLV helpers (version=1) ===== +fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { return E_ARGS; } + let mut buf: Vec = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + buf.extend_from_slice(&1u16.to_le_bytes()); + buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); + for (tag, payload) in payloads { + buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); + } + unsafe { + let need = buf.len(); + if result.is_null() || *result_len < need { *result_len = need; return E_SHORT; } + std::ptr::copy_nonoverlapping(buf.as_ptr(), result, need); + *result_len = need; + } + OK +} +fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(9u8, &[])], result, result_len) } +fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) } + +unsafe fn tlv_parse_header(data: *const u8, len: usize) -> Option<(u16,u16,usize)> { + if data.is_null() || len < 4 { return None; } + let b = std::slice::from_raw_parts(data, len); + let ver = u16::from_le_bytes([b[0], b[1]]); + let argc = u16::from_le_bytes([b[2], b[3]]); + if ver != 1 { return None; } + Some((ver, argc, 4)) +} +unsafe fn tlv_read_entry_at(data: *const u8, len: usize, mut pos: usize) -> Option<(u8, usize, usize)> { + let b = std::slice::from_raw_parts(data, len); + if pos + 4 > len { return None; } + let tag = b[pos]; let _ = b[pos+1]; let size = u16::from_le_bytes([b[pos+2], b[pos+3]]) as usize; pos += 4; + if pos + size > len { return None; } + Some((tag, size, pos)) +} +unsafe fn tlv_read_i64(data: *const u8, len: usize, index: usize) -> Option { + let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; } + for i in 0..=index { let (tag, size, p) = tlv_read_entry_at(data, len, pos)?; if tag == 3 && size == 8 { if i == index { let b = std::slice::from_raw_parts(data.add(p), 8); let mut t=[0u8;8]; t.copy_from_slice(b); return Some(i64::from_le_bytes(t)); } } pos = p + size; } + None +} +unsafe fn tlv_read_string(data: *const u8, len: usize, index: usize) -> Option { + let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; } + for i in 0..=index { let (tag, size, p) = tlv_read_entry_at(data, len, pos)?; if tag == 6 || tag == 7 { if i == index { let s = std::slice::from_raw_parts(data.add(p), size); return Some(String::from_utf8_lossy(s).to_string()); } } pos = p + size; } + None +} +unsafe fn tlv_read_open_args(args: *const u8, len: usize) -> Option<(i32,i32,String)> { + let w = tlv_read_i64(args, len, 0)? as i32; + let h = tlv_read_i64(args, len, 1)? as i32; + let t = tlv_read_string(args, len, 2)?; + Some((w,h,t)) +} + +// ===== Windows 実行(with-egui) ===== +#[cfg(all(windows, feature = "with-egui"))] +mod winrun { + use super::*; + use eframe::egui; + + pub fn run_window(w: i32, h: i32, title: &str, labels: Vec) { + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([w.max(100) as f32, h.max(100) as f32]) + .with_title(title.to_string()), + ..Default::default() + }; + + struct App { labels: Vec } + impl eframe::App for App { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + for s in &self.labels { ui.label(s); } + }); + } + } + + let _ = eframe::run_native( + title, + options, + Box::new(|_cc| Box::new(App { labels })), + ); + } +} diff --git a/plugins/nyash-integer-plugin/src/lib.rs b/plugins/nyash-integer-plugin/src/lib.rs index 24fe810a..c310e30a 100644 --- a/plugins/nyash-integer-plugin/src/lib.rs +++ b/plugins/nyash-integer-plugin/src/lib.rs @@ -4,6 +4,8 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; +use std::os::raw::c_char; +use std::ffi::CStr; // Error codes const OK: i32 = 0; @@ -73,6 +75,55 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ===== TypeBox FFI (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn integer_resolve(name: *const c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "get" => M_GET, + "set" => M_SET, + _ => 0, + } +} + +extern "C" fn integer_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + unsafe { + match method_id { + M_GET => { + if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; } + } + M_SET => { + let v = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; + if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.value = v; return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; } + } + _ => E_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_IntegerBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, // 'TYBX' + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"IntegerBox\0".as_ptr() as *const c_char, + resolve: Some(integer_resolve), + invoke_id: Some(integer_invoke_id), + capabilities: 0, +}; + fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } } false diff --git a/plugins/nyash-map-plugin/src/lib.rs b/plugins/nyash-map-plugin/src/lib.rs index 26c7b428..532c87d9 100644 --- a/plugins/nyash-map-plugin/src/lib.rs +++ b/plugins/nyash-map-plugin/src/lib.rs @@ -5,6 +5,8 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; +use std::os::raw::c_char; +use std::ffi::CStr; // Error codes const NYB_SUCCESS: i32 = 0; @@ -251,6 +253,143 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ---- Nyash TypeBox (FFI minimal PoC) ---- +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn mapbox_resolve(name: *const c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "size" | "len" => METHOD_SIZE, + "get" => METHOD_GET, + "has" => METHOD_HAS, + "set" => METHOD_SET, + "getS" => METHOD_GET_STR, + "hasS" => METHOD_HAS_STR, + _ => 0, + } +} + +extern "C" fn mapbox_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + match method_id { + METHOD_SIZE => { + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { + let sz = inst.data_i64.len() + inst.data_str.len(); + return write_tlv_i64(sz as i64, result, result_len); + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } + METHOD_GET => { + if let Some(ik) = read_arg_i64(args, args_len, 0) { + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { + match inst.data_i64.get(&ik).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS } + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } else if let Some(sk) = read_arg_string(args, args_len, 0) { + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { + match inst.data_str.get(&sk).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS } + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } else { NYB_E_INVALID_ARGS } + } + METHOD_HAS => { + if let Some(ik) = read_arg_i64(args, args_len, 0) { + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len) } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } else if let Some(sk) = read_arg_string(args, args_len, 0) { + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len) } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } else { NYB_E_INVALID_ARGS } + } + METHOD_SET => { + // key: i64 or string, value: i64 + if let Some(val) = read_arg_i64(args, args_len, 1) { + if let Some(ik) = read_arg_i64(args, args_len, 0) { + if let Ok(mut map) = INSTANCES.lock() { + if let Some(inst) = map.get_mut(&instance_id) { + inst.data_i64.insert(ik, val); + let sz = inst.data_i64.len() + inst.data_str.len(); + return write_tlv_i64(sz as i64, result, result_len); + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } else if let Some(sk) = read_arg_string(args, args_len, 0) { + if let Ok(mut map) = INSTANCES.lock() { + if let Some(inst) = map.get_mut(&instance_id) { + inst.data_str.insert(sk, val); + let sz = inst.data_i64.len() + inst.data_str.len(); + return write_tlv_i64(sz as i64, result, result_len); + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } else { NYB_E_INVALID_ARGS } + } else { NYB_E_INVALID_ARGS } + } + METHOD_GET_STR => { + let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS }; + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { + match inst.data_str.get(&key).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS } + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } + METHOD_HAS_STR => { + let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS }; + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&key), result, result_len) } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } + METHOD_KEYS_S => { + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { + let mut keys: Vec = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len()); + for k in inst.data_i64.keys() { keys.push(k.to_string()); } + for k in inst.data_str.keys() { keys.push(k.clone()); } + keys.sort(); + let out = keys.join("\n"); + return write_tlv_string(&out, result, result_len); + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } + METHOD_VALUES_S => { + if let Ok(map) = INSTANCES.lock() { + if let Some(inst) = map.get(&instance_id) { + let mut vals: Vec = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len()); + for v in inst.data_i64.values() { vals.push(v.to_string()); } + for v in inst.data_str.values() { vals.push(v.to_string()); } + let out = vals.join("\n"); + return write_tlv_string(&out, result, result_len); + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } + _ => NYB_E_INVALID_METHOD, + } +} + +#[no_mangle] +pub static nyash_typebox_MapBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, // 'TYBX' + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"MapBox\0".as_ptr() as *const c_char, + resolve: Some(mapbox_resolve), + invoke_id: Some(mapbox_invoke_id), + capabilities: 0, +}; + fn escape_json(s: &str) -> String { let mut out = String::with_capacity(s.len()+8); for ch in s.chars() { diff --git a/plugins/nyash-string-plugin/src/lib.rs b/plugins/nyash-string-plugin/src/lib.rs index 907c8109..bc0a9757 100644 --- a/plugins/nyash-string-plugin/src/lib.rs +++ b/plugins/nyash-string-plugin/src/lib.rs @@ -4,6 +4,8 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; +use std::os::raw::c_char; +use std::ffi::CStr; const OK: i32 = 0; const E_SHORT: i32 = -1; @@ -115,6 +117,71 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ===== TypeBox FFI (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn string_resolve(name: *const c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "len" | "length" => M_LENGTH, + "toUtf8" => M_TO_UTF8, + "concat" => M_CONCAT, + _ => 0, + } +} + +extern "C" fn string_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + unsafe { + match method_id { + M_LENGTH => { + if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.s.len() as i64, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; } + } + M_TO_UTF8 => { + if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_string(&inst.s, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; } + } + M_CONCAT => { + // support String/Bytes or StringBox handle + let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) { + if t != TYPE_ID_STRING { return E_TYPE; } + if let Ok(m) = INST.lock() { if let Some(s2) = m.get(&inst) { (true, s2.s.clone()) } else { (false, String::new()) } } else { return E_PLUGIN; } + } else if let Some(s) = read_arg_string(args, args_len, 0) { (true, s) } else { (false, String::new()) }; + if !ok { return E_ARGS; } + if let Ok(m) = INST.lock() { + if let Some(inst) = m.get(&instance_id) { + let mut new_s = inst.s.clone(); new_s.push_str(&rhs); drop(m); + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + if let Ok(mut mm) = INST.lock() { mm.insert(id, StrInstance { s: new_s }); } + return write_tlv_handle(TYPE_ID_STRING, id, result, result_len); + } else { return E_HANDLE; } + } else { return E_PLUGIN; } + } + _ => E_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_StringBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, // 'TYBX' + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"StringBox\0".as_ptr() as *const c_char, + resolve: Some(string_resolve), + invoke_id: Some(string_invoke_id), + capabilities: 0, +}; + fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } } false diff --git a/src/backend/vm_instructions.rs b/src/backend/vm_instructions.rs index 06af2ebc..3d01ddb5 100644 --- a/src/backend/vm_instructions.rs +++ b/src/backend/vm_instructions.rs @@ -772,6 +772,45 @@ impl VM { // Debug logging if enabled let debug_boxcall = std::env::var("NYASH_VM_DEBUG_BOXCALL").is_ok(); + + // Fast-path: ConsoleBox.readLine — provide safe stdin fallback with EOF→Void + if let VMValue::BoxRef(arc_box) = &recv { + if let Some(p) = arc_box.as_any().downcast_ref::() { + if p.box_type == "ConsoleBox" && method == "readLine" { + use std::io::Read; + let mut s = String::new(); + let mut stdin = std::io::stdin(); + // Read bytes until '\n' or EOF + let mut buf = [0u8; 1]; + loop { + match stdin.read(&mut buf) { + Ok(0) => { // EOF → return NullBox + if let Some(dst_id) = dst { + let nb = crate::boxes::null_box::NullBox::new(); + self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb))); + } + return Ok(ControlFlow::Continue); + } + Ok(_) => { + let ch = buf[0] as char; + if ch == '\n' { break; } + s.push(ch); + if s.len() > 1_000_000 { break; } + } + Err(_) => { // On error, return NullBox + if let Some(dst_id) = dst { + let nb = crate::boxes::null_box::NullBox::new(); + self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb))); + } + return Ok(ControlFlow::Continue); + } + } + } + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::String(s)); } + return Ok(ControlFlow::Continue); + } + } + } // Phase 12 Tier-0: vtable優先経路(雛形) if crate::config::env::abi_vtable() { @@ -1236,13 +1275,101 @@ impl VM { /// Phase 12 Tier-0: vtable優先経路の雛形(常に未処理)。 /// 目的: 将来のTypeBox ABI配線ポイントを先置きしても既存挙動を変えないこと。 fn try_boxcall_vtable_stub(&mut self, _dst: Option, _recv: &VMValue, _method: &str, _method_id: Option, _args: &[ValueId]) -> Option> { + if crate::config::env::vm_vt_trace() { + match _recv { + VMValue::BoxRef(b) => eprintln!("[VT] probe recv_ty={} method={} argc={}", b.type_name(), _method, _args.len()), + other => eprintln!("[VT] probe recv_prim={:?} method={} argc={}", other, _method, _args.len()), + } + } // Tier-1 PoC: Array/Map/String の get/set/len/size/has を vtable 経路で処理(read-onlyまたは明示barrier不要) if let VMValue::BoxRef(b) = _recv { // 型解決(雛形レジストリ使用) let ty_name = b.type_name(); - if let Some(_tb) = crate::runtime::type_registry::resolve_typebox_by_name(ty_name) { + // PluginBoxV2 は実型名でレジストリ解決する + let ty_name_for_reg: std::borrow::Cow<'_, str> = if let Some(p) = b.as_any().downcast_ref::() { + std::borrow::Cow::Owned(p.box_type.clone()) + } else { + std::borrow::Cow::Borrowed(ty_name) + }; + if let Some(_tb) = crate::runtime::type_registry::resolve_typebox_by_name(&ty_name_for_reg) { // name+arity→slot 解決 - let slot = crate::runtime::type_registry::resolve_slot_by_name(ty_name, _method, _args.len()); + let slot = crate::runtime::type_registry::resolve_slot_by_name(&ty_name_for_reg, _method, _args.len()); + // PluginBoxV2: vtable経由で host.invoke_instance_method を使用(内蔵廃止と整合) + if let Some(p) = b.as_any().downcast_ref::() { + if crate::config::env::vm_vt_trace() { eprintln!("[VT] plugin recv ty={} method={} arity={}", ty_name, _method, _args.len()); } + // 事前に引数を NyashBox に変換 + let mut nyash_args: Vec> = Vec::with_capacity(_args.len()); + for aid in _args.iter() { + if let Ok(v) = self.get_value(*aid) { nyash_args.push(v.to_nyash_box()); } else { nyash_args.push(Box::new(crate::box_trait::VoidBox::new())); } + } + // Instance/Map/Array/String などに対して型名とスロットで分岐(最小セット) + match ty_name { + "MapBox" => { + match slot { + Some(200) | Some(201) => { // size/len + let host = crate::runtime::get_global_plugin_host(); + let ro = host.read().unwrap(); + if let Ok(val_opt) = ro.invoke_instance_method("MapBox", _method, p.inner.instance_id, &[]) { + if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + return Some(Ok(ControlFlow::Continue)); + } + } + Some(202) | Some(203) | Some(204) => { // has/get/set + if matches!(slot, Some(204)) { + crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Map.set"); + } + let host = crate::runtime::get_global_plugin_host(); + let ro = host.read().unwrap(); + // Route string-key variants to getS/hasS when applicable + let mut method_eff = _method; + if (matches!(slot, Some(202)) && _args.len() >= 1) || (matches!(slot, Some(203)) && _args.len() >= 1) { + if let Ok(a0v) = self.get_value(_args[0]) { + if matches!(a0v, VMValue::String(_)) { method_eff = if matches!(slot, Some(203)) { "getS" } else { "hasS" }; } + } + } + if let Ok(val_opt) = ro.invoke_instance_method("MapBox", method_eff, p.inner.instance_id, &nyash_args) { + if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } + else if _dst.is_some() { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } } + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + return Some(Ok(ControlFlow::Continue)); + } + } + _ => {} + } + } + "ArrayBox" => { + match slot { + Some(100) | Some(101) | Some(102) => { + if matches!(slot, Some(101)) { + crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Array.set"); + } + let host = crate::runtime::get_global_plugin_host(); + let ro = host.read().unwrap(); + if let Ok(val_opt) = ro.invoke_instance_method("ArrayBox", _method, p.inner.instance_id, &nyash_args) { + if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } + else if _dst.is_some() { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } } + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + return Some(Ok(ControlFlow::Continue)); + } + } + _ => {} + } + } + "StringBox" => { + if matches!(slot, Some(300)) { + let host = crate::runtime::get_global_plugin_host(); + let ro = host.read().unwrap(); + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", _method, p.inner.instance_id, &[]) { + if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + return Some(Ok(ControlFlow::Continue)); + } + } + } + _ => {} + } + } // InstanceBox: getField/setField/has/size if let Some(inst) = b.as_any().downcast_ref::() { match slot { diff --git a/src/backend/wasm_v2/mod.rs b/src/backend/wasm_v2/mod.rs index f2c9642f..e849fa2c 100644 --- a/src/backend/wasm_v2/mod.rs +++ b/src/backend/wasm_v2/mod.rs @@ -9,9 +9,18 @@ pub mod unified_dispatch; pub mod vtable_codegen; -/// エントリポイントの雛形 -pub fn compile_and_execute_v2(_module: &crate::mir::MirModule, _temp_name: &str) -> Result, String> { - // まだ未実装: vtable_codegenで生成したスロット表を unified_dispatch 経由で実行 - Err("wasm_v2: not implemented (scaffold)".to_string()) -} +use crate::box_trait::{NyashBox, StringBox}; +use crate::boxes::ConsoleBox; +/// WASM v2エントリポイント: 統一vtableディスパッチの最小テスト +pub fn compile_and_execute_v2(_module: &crate::mir::MirModule, _temp_name: &str) -> Result, String> { + // 1) ConsoleBoxを生成(WASM環境ではブラウザコンソールに委譲) + let console = Box::new(ConsoleBox::new()); + // 2) slot解決→dispatchでlogを呼ぶ(最小疎通) + if let Some(slot_id) = unified_dispatch::resolve_slot(console.as_ref(), "log", 1) { + let args = vec![Box::new(StringBox::new("🎉 WASM v2 console.log working!")) as Box]; + let _ = unified_dispatch::dispatch_by_slot(slot_id, console.as_ref(), &args); + } + // 3) 結果を返す + Ok(Box::new(StringBox::new("WASM v2 unified dispatch test completed"))) +} diff --git a/src/backend/wasm_v2/unified_dispatch.rs b/src/backend/wasm_v2/unified_dispatch.rs index 41150bbf..a857beef 100644 --- a/src/backend/wasm_v2/unified_dispatch.rs +++ b/src/backend/wasm_v2/unified_dispatch.rs @@ -1,11 +1,12 @@ //! Unified dispatch (WASM v2) //! //! - TypeRegistryのスロット表と一致させた呼び出し分岐の雛形 -//! - ここではあくまで「どのスロットに行くか」の判定のみ提供 +//! - env.console.log とArray/Map統一ディスパッチの最小実装 #![cfg(feature = "wasm-backend")] -use crate::box_trait::NyashBox; +use crate::box_trait::{NyashBox, StringBox, VoidBox, BoolBox}; +use crate::boxes::{ConsoleBox, ArrayBox, MapBox}; /// 受信ボックス/メソッド名/アリティからスロットを解決し、識別子を返す。 pub fn resolve_slot(recv: &dyn NyashBox, method: &str, arity: usize) -> Option { @@ -15,11 +16,104 @@ pub fn resolve_slot(recv: &dyn NyashBox, method: &str, arity: usize) -> Option], + slot: u16, + recv: &dyn NyashBox, + args: &[Box], ) -> Option> { - // 未実装: wasm_v2ではJS/hostへのブリッジや、Wasm内の簡易実装に委譲 - None + match slot { + // ConsoleBox slots (400番台予約) + 400 => { + // console.log(message) + if let Some(console) = recv.as_any().downcast_ref::() { + if args.len() == 1 { + let message = args[0].to_string_box().value; + console.log(&message); + return Some(Box::new(VoidBox::new())); + } + } + None + } + 401 => { + // console.warn(message) + if let Some(console) = recv.as_any().downcast_ref::() { + if args.len() == 1 { + let message = args[0].to_string_box().value; + console.warn(&message); + return Some(Box::new(VoidBox::new())); + } + } + None + } + 402 => { + // console.error(message) + if let Some(console) = recv.as_any().downcast_ref::() { + if args.len() == 1 { + let message = args[0].to_string_box().value; + console.error(&message); + return Some(Box::new(VoidBox::new())); + } + } + None + } + 403 => { + // console.clear() + if let Some(console) = recv.as_any().downcast_ref::() { + if args.is_empty() { + console.clear(); + return Some(Box::new(VoidBox::new())); + } + } + None + } + + // ArrayBox slots (100番台) + 100 => { + // array.get(index) + if let Some(array) = recv.as_any().downcast_ref::() { + if args.len() == 1 { + let idx = args[0].clone_box(); + return Some(array.get(idx)); + } + } + None + } + 102 => { + // array.length() + if let Some(array) = recv.as_any().downcast_ref::() { + return Some(array.length()); + } + None + } + + // MapBox slots (200番台) + 200 => { + // map.size() + if let Some(map) = recv.as_any().downcast_ref::() { + return Some(map.size()); + } + None + } + 202 => { + // map.has(key) + if let Some(map) = recv.as_any().downcast_ref::() { + if args.len() == 1 { + let key_box = args[0].clone_box(); + return Some(map.has(key_box)); + } + } + None + } + 203 => { + // map.get(key) + if let Some(map) = recv.as_any().downcast_ref::() { + if args.len() == 1 { + let key_box = args[0].clone_box(); + return Some(map.get(key_box)); + } + } + None + } + + _ => None + } } - diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 64adb3cb..f1ea5366 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -259,7 +259,6 @@ impl LowerCore { // name → u64x2 パックで渡す let name = box_type.clone(); { - use cranelift_codegen::ir::{AbiParam, Signature, types}; let name_bytes = name.as_bytes(); let mut lo: u64 = 0; let mut hi: u64 = 0; let take = core::cmp::min(16, name_bytes.len()); diff --git a/src/runtime/plugin_loader_v2.rs b/src/runtime/plugin_loader_v2.rs index fef54662..8e9c6454 100644 --- a/src/runtime/plugin_loader_v2.rs +++ b/src/runtime/plugin_loader_v2.rs @@ -19,12 +19,14 @@ mod enabled { /// Loaded plugin information pub struct LoadedPluginV2 { - /// Library handle - _lib: Arc, - - /// Box types provided by this plugin - #[allow(dead_code)] - box_types: Vec, + /// Library handle + _lib: Arc, + + /// Box types provided by this plugin + #[allow(dead_code)] + box_types: Vec, + /// Optional per-box TypeBox ABI addresses (nyash_typebox_); raw usize for Send/Sync + typeboxes: std::collections::HashMap, /// Optional init function #[allow(dead_code)] @@ -110,6 +112,20 @@ mod enabled { PluginBoxV2 { box_type, inner: std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None, finalized: std::sync::atomic::AtomicBool::new(false) }) } } + // Nyash TypeBox (FFI minimal for PoC) + use std::os::raw::c_char; + #[repr(C)] + pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string + // Minimal methods + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, + } + #[derive(Debug, Clone)] pub struct PluginBoxV2 { pub box_type: String, @@ -686,8 +702,38 @@ impl PluginLoaderV2 { instance_id: u32, args: &[Box], ) -> BidResult>> { + // ConsoleBox.readLine: プラグイン未実装時のホスト側フォールバック + if box_type == "ConsoleBox" && method_name == "readLine" { + use std::io::Read; + let mut s = String::new(); + let mut stdin = std::io::stdin(); + let mut buf = [0u8; 1]; + loop { + match stdin.read(&mut buf) { + Ok(0) => { return Ok(None); } // EOF → None(Nyashのnull相当) + Ok(_) => { + let ch = buf[0] as char; + if ch == '\n' { break; } + s.push(ch); + if s.len() > 1_000_000 { break; } + } + Err(_) => { return Ok(None); } + } + } + return Ok(Some(Box::new(crate::box_trait::StringBox::new(s)) as Box)); + } // v2.1: 引数ありのメソッドを許可(BoxRef/基本型/文字列化フォールバック) - let method_id = self.resolve_method_id_from_file(box_type, method_name)?; + // MapBox convenience: route string-key get/has to getS/hasS if available + let effective_method = if box_type == "MapBox" { + if method_name == "get" { + if let Some(a0) = args.get(0) { if a0.as_any().downcast_ref::().is_some() { "getS" } else { method_name } } + else { method_name } + } else if method_name == "has" { + if let Some(a0) = args.get(0) { if a0.as_any().downcast_ref::().is_some() { "hasS" } else { method_name } } + else { method_name } + } else { method_name } + } else { method_name }; + let method_id = self.resolve_method_id_from_file(box_type, effective_method)?; // Find plugin and type_id let config = self.config.as_ref().ok_or(BidError::PluginError)?; let lib_name = self.find_lib_name_for_box(box_type).ok_or(BidError::InvalidType)?; @@ -716,7 +762,7 @@ impl PluginLoaderV2 { let rr = box_conf.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false); (box_conf.type_id, rr) }; - eprintln!("[PluginLoaderV2] Invoke {}.{}: resolving and encoding args (argc={})", box_type, method_name, args.len()); + eprintln!("[PluginLoaderV2] Invoke {}.{}: resolving and encoding args (argc={})", box_type, effective_method, args.len()); // TLV args: encode using BID-1 style (u16 ver, u16 argc, then entries) let tlv_args = { let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16); @@ -726,7 +772,7 @@ impl PluginLoaderV2 { } else { config .get_box_config(&lib_name, box_type, &toml_value) - .and_then(|bc| bc.methods.get(method_name).and_then(|m| m.args.clone())) + .and_then(|bc| bc.methods.get(effective_method).and_then(|m| m.args.clone())) }; if let Some(exp) = expected_args.as_ref() { if exp.len() != args.len() { @@ -891,20 +937,115 @@ impl PluginLoaderV2 { } let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len(); + // Prefer TypeBox.invoke_id if available for this box_type let rc = unsafe { - (plugin.invoke_fn)( - type_id, - method_id, - instance_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ) + let disable_typebox = std::env::var("NYASH_DISABLE_TYPEBOX").ok().as_deref() == Some("1"); + if !disable_typebox { + if let Some(tbaddr) = plugin.typeboxes.get(box_type) { + let tb = *tbaddr as *const NyashTypeBoxFfi; + if !tb.is_null() { + let tbr: &NyashTypeBoxFfi = &*tb; + if let Some(inv) = tbr.invoke_id { + inv( + instance_id, + method_id, + tlv_args.as_ptr(), + tlv_args.len(), + out.as_mut_ptr(), + &mut out_len, + ) + } else { + (plugin.invoke_fn)( + type_id, + method_id, + instance_id, + tlv_args.as_ptr(), + tlv_args.len(), + out.as_mut_ptr(), + &mut out_len, + ) + } + } else { + (plugin.invoke_fn)( + type_id, + method_id, + instance_id, + tlv_args.as_ptr(), + tlv_args.len(), + out.as_mut_ptr(), + &mut out_len, + ) + } + } else { + (plugin.invoke_fn)( + type_id, + method_id, + instance_id, + tlv_args.as_ptr(), + tlv_args.len(), + out.as_mut_ptr(), + &mut out_len, + ) + } + } else { + (plugin.invoke_fn)( + type_id, + method_id, + instance_id, + tlv_args.as_ptr(), + tlv_args.len(), + out.as_mut_ptr(), + &mut out_len, + ) + } }; if rc != 0 { let be = BidError::from_raw(rc); + // Fallback: MapBox.get/has with string key → try getS/hasS + if box_type == "MapBox" && (method_name == "get" || method_name == "has") { + if let Some(a0) = args.get(0) { + if a0.as_any().downcast_ref::().is_some() { + let alt = if method_name == "get" { "getS" } else { "hasS" }; + if let Ok(alt_id) = self.resolve_method_id_from_file(box_type, alt) { + // rebuild header and TLV for single string arg + let mut alt_out = vec![0u8; 1024]; + let mut alt_out_len: usize = alt_out.len(); + let mut alt_tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(1); + crate::runtime::plugin_ffi_common::encode::string(&mut alt_tlv, &a0.to_string_box().value); + let rc2 = unsafe { + (plugin.invoke_fn)( + type_id, + alt_id, + instance_id, + alt_tlv.as_ptr(), + alt_tlv.len(), + alt_out.as_mut_ptr(), + &mut alt_out_len, + ) + }; + if rc2 == 0 { + // Decode single entry + if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&alt_out[..alt_out_len]) { + let v = match tag { + 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(|b| Box::new(crate::box_trait::BoolBox::new(b)) as Box), + 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|i| Box::new(crate::box_trait::IntegerBox::new(i as i64)) as Box), + 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(|f| Box::new(crate::boxes::math_box::FloatBox::new(f)) as Box), + 6 => Some(Box::new(crate::box_trait::StringBox::new(crate::runtime::plugin_ffi_common::decode::string(payload))) as Box), + _ => None, + }; + if let Some(val) = v { return Ok(Some(val)); } + } + return Ok(None); + } + } + } + } + } if dbg_on() { eprintln!("[PluginLoaderV2] invoke rc={} ({}) for {}.{}", rc, be.message(), box_type, method_name); } + // Graceful degradation for MapBox.get/has: treat failure as missing key (return None) + if box_type == "MapBox" && (method_name == "get" || method_name == "has") { + return Ok(None); + } if returns_result { let err = crate::exception_box::ErrorBox::new(&format!("{} (code: {})", be.message(), rc)); return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(err))))); @@ -1095,10 +1236,20 @@ impl PluginLoaderV2 { } // Store plugin with Arc-wrapped library + // Probe per-box TypeBox symbols before moving the library into Arc + let mut tb_map: HashMap = HashMap::new(); + for bt in &lib_def.boxes { + let sym = format!("nyash_typebox_{}", bt); + if let Ok(s) = unsafe { lib.get::<*const NyashTypeBoxFfi>(sym.as_bytes()) } { + tb_map.insert(bt.clone(), (*s) as usize); + } + } + let lib_arc = Arc::new(lib); let plugin = Arc::new(LoadedPluginV2 { _lib: lib_arc, box_types: lib_def.boxes.clone(), + typeboxes: tb_map, init_fn, invoke_fn, }); @@ -1145,9 +1296,36 @@ impl PluginLoaderV2 { if let Some(stripped) = stem.strip_prefix("lib") { let name = format!("{}.{}", stripped, cur_ext); if let Some(path) = cfg.resolve_plugin_path(&name) { return Some(path); } + // Extra: look into target triples (e.g., x86_64-pc-windows-msvc/aarch64-pc-windows-msvc) + let triples = [ + "x86_64-pc-windows-msvc", + "aarch64-pc-windows-msvc", + ]; + // Try relative to provided dir (if any) + let base_dir = dir.clone(); + for t in &triples { + let cand = base_dir.join("target").join(t).join("release").join(&name); + if cand.exists() { return Some(cand.to_string_lossy().to_string()); } + let cand_dbg = base_dir.join("target").join(t).join("debug").join(&name); + if cand_dbg.exists() { return Some(cand_dbg.to_string_lossy().to_string()); } + } } } } + // Candidate D (Windows): when config path already contains target/release, probe known triples + if cfg!(target_os = "windows") { + let file_name = Path::new(&file).file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or(file.clone()); + let triples = [ + "x86_64-pc-windows-msvc", + "aarch64-pc-windows-msvc", + ]; + for t in &triples { + let cand = dir.clone().join("..").join(t).join("release").join(&file_name); + if cand.exists() { return Some(cand.to_string_lossy().to_string()); } + let cand_dbg = dir.clone().join("..").join(t).join("debug").join(&file_name); + if cand_dbg.exists() { return Some(cand_dbg.to_string_lossy().to_string()); } + } + } None } diff --git a/src/runtime/type_registry.rs b/src/runtime/type_registry.rs index e932bf3c..3ee9858e 100644 --- a/src/runtime/type_registry.rs +++ b/src/runtime/type_registry.rs @@ -34,6 +34,16 @@ const STRING_METHODS: &[MethodEntry] = &[ ]; static STRINGBOX_TB: TypeBox = TypeBox::new_with("StringBox", STRING_METHODS); +// --- ConsoleBox --- (WASM v2 unified dispatch 用の雛形) +// 400: log(..), 401: warn(..), 402: error(..), 403: clear() +const CONSOLE_METHODS: &[MethodEntry] = &[ + MethodEntry { name: "log", arity: 1, slot: 400 }, + MethodEntry { name: "warn", arity: 1, slot: 401 }, + MethodEntry { name: "error", arity: 1, slot: 402 }, + MethodEntry { name: "clear", arity: 0, slot: 403 }, +]; +static CONSOLEBOX_TB: TypeBox = TypeBox::new_with("ConsoleBox", CONSOLE_METHODS); + // --- InstanceBox --- // Representative methods exposed via unified slots for field access and diagnostics. // 1: getField(name) @@ -54,6 +64,7 @@ pub fn resolve_typebox_by_name(type_name: &str) -> Option<&'static TypeBox> { "MapBox" => Some(&MAPBOX_TB), "ArrayBox" => Some(&ARRAYBOX_TB), "StringBox" => Some(&STRINGBOX_TB), + "ConsoleBox" => Some(&CONSOLEBOX_TB), "InstanceBox" => Some(&INSTANCEBOX_TB), _ => None, } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f87e778b..c4a84ba7 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -7,3 +7,5 @@ pub mod identical_exec_instance; pub mod vtable_array_string; pub mod vtable_strict; pub mod host_reverse_slot; +pub mod nyash_abi_basic; +pub mod typebox_tlv_diff; diff --git a/src/tests/nyash_abi_basic.rs b/src/tests/nyash_abi_basic.rs new file mode 100644 index 00000000..851da602 --- /dev/null +++ b/src/tests/nyash_abi_basic.rs @@ -0,0 +1,84 @@ +#[cfg(test)] +mod tests { + use crate::runtime::type_registry::{resolve_slot_by_name, known_methods_for}; + + #[test] + fn type_registry_resolves_core_slots() { + // MapBox + assert_eq!(resolve_slot_by_name("MapBox", "size", 0), Some(200)); + assert_eq!(resolve_slot_by_name("MapBox", "len", 0), Some(201)); + assert_eq!(resolve_slot_by_name("MapBox", "has", 1), Some(202)); + assert_eq!(resolve_slot_by_name("MapBox", "get", 1), Some(203)); + assert_eq!(resolve_slot_by_name("MapBox", "set", 2), Some(204)); + // ArrayBox + assert_eq!(resolve_slot_by_name("ArrayBox", "get", 1), Some(100)); + assert_eq!(resolve_slot_by_name("ArrayBox", "set", 2), Some(101)); + assert_eq!(resolve_slot_by_name("ArrayBox", "len", 0), Some(102)); + // StringBox + assert_eq!(resolve_slot_by_name("StringBox", "len", 0), Some(300)); + + // Known methods listing should include representative entries + let mm = known_methods_for("MapBox").expect("map methods"); + assert!(mm.contains(&"size")); + assert!(mm.contains(&"get")); + assert!(mm.contains(&"set")); + } + + #[test] + #[ignore] + fn vm_vtable_map_set_get_has() { + use crate::backend::vm::VM; + use crate::mir::{MirModule, MirFunction, FunctionSignature, BasicBlockId, MirInstruction, EffectMask, ConstValue, MirType, ValueId}; + + // Enable vtable-preferred path + std::env::set_var("NYASH_ABI_VTABLE", "1"); + + // Program: m = new MapBox(); m.set("k","v"); h = m.has("k"); g = m.get("k"); return g + let mut m = MirModule::new("nyash_abi_map_get".into()); + let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::String, effects: EffectMask::PURE }; + let mut f = MirFunction::new(sig, BasicBlockId::new(0)); + let bb = f.entry_block; + + let mapv = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] }); + + let k = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: k, value: ConstValue::String("k".into()) }); + let v = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v, value: ConstValue::String("v".into()) }); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: None, box_val: mapv, method: "set".into(), args: vec![k, v], method_id: None, effects: EffectMask::PURE }); + + let k2 = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: k2, value: ConstValue::String("k".into()) }); + let hasv = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(hasv), box_val: mapv, method: "has".into(), args: vec![k2], method_id: None, effects: EffectMask::PURE }); + + let k3 = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: k3, value: ConstValue::String("k".into()) }); + let got = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(got), box_val: mapv, method: "get".into(), args: vec![k3], method_id: None, effects: EffectMask::PURE }); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(got) }); + + m.add_function(f); + + let mut vm = VM::new(); + let out = vm.execute_module(&m).expect("vm exec"); + assert_eq!(out.to_string_box().value, "v"); + } + + #[test] + fn mapbox_keys_values_return_arrays() { + // Direct Box-level test (not via VM): keys()/values() should return ArrayBox + use crate::boxes::map_box::MapBox; + use crate::box_trait::{NyashBox, StringBox, IntegerBox}; + + let map = MapBox::new(); + map.set(Box::new(StringBox::new("a")), Box::new(IntegerBox::new(1))); + map.set(Box::new(StringBox::new("b")), Box::new(IntegerBox::new(2))); + + let keys = map.keys(); + let values = map.values(); + assert_eq!(keys.type_name(), "ArrayBox"); + assert_eq!(values.type_name(), "ArrayBox"); + } +} diff --git a/src/tests/typebox_tlv_diff.rs b/src/tests/typebox_tlv_diff.rs new file mode 100644 index 00000000..c44426e1 --- /dev/null +++ b/src/tests/typebox_tlv_diff.rs @@ -0,0 +1,433 @@ +#[cfg(test)] +mod tests { + use std::env; + use crate::box_trait::{NyashBox, StringBox, IntegerBox}; + use crate::boxes::math_box::FloatBox; + use crate::boxes::array::ArrayBox; + use std::fs; + use std::path::PathBuf; + + fn ensure_host() { + let _ = crate::runtime::init_global_plugin_host("nyash.toml"); + } + + fn create_plugin_instance(box_type: &str) -> (String, u32, Box) { + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + let bx = host.create_box(box_type, &[]).expect("create_box"); + // Downcast to PluginBoxV2 to get instance_id + if let Some(p) = bx.as_any().downcast_ref::() { + (box_type.to_string(), p.instance_id(), bx) + } else { + panic!("not a plugin box: {}", bx.type_name()); + } + } + + #[test] + fn mapbox_get_set_size_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // TLV path: disable typebox + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("MapBox"); + let out_tlv = { + let h = host.read().unwrap(); + // set("k", 42) + let _ = h.invoke_instance_method(&bt1, "set", id1, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]).expect("set tlv"); + // size() + let sz = h.invoke_instance_method(&bt1, "size", id1, &[]).expect("size tlv").unwrap(); + // get("k") + let gv = h.invoke_instance_method(&bt1, "get", id1, &[Box::new(StringBox::new("k"))]).expect("get tlv").unwrap(); + (sz.to_string_box().value, gv.to_string_box().value) + }; + + // TypeBox path: enable typebox + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("MapBox"); + let out_tb = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt2, "set", id2, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]).expect("set tb"); + let sz = h.invoke_instance_method(&bt2, "size", id2, &[]).expect("size tb").unwrap(); + let gv = h.invoke_instance_method(&bt2, "get", id2, &[Box::new(StringBox::new("k"))]).expect("get tb").unwrap(); + (sz.to_string_box().value, gv.to_string_box().value) + }; + + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match"); + } + + #[test] + fn arraybox_set_get_len_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("ArrayBox"); + let out_tlv = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt1, "set", id1, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]).expect("set tlv"); + let ln = h.invoke_instance_method(&bt1, "len", id1, &[]).expect("len tlv").unwrap(); + let gv = h.invoke_instance_method(&bt1, "get", id1, &[Box::new(IntegerBox::new(0))]).expect("get tlv").unwrap(); + (ln.to_string_box().value, gv.to_string_box().value) + }; + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("ArrayBox"); + let out_tb = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt2, "set", id2, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]).expect("set tb"); + let ln = h.invoke_instance_method(&bt2, "length", id2, &[]).expect("len tb").unwrap(); + let gv = h.invoke_instance_method(&bt2, "get", id2, &[Box::new(IntegerBox::new(0))]).expect("get tb").unwrap(); + (ln.to_string_box().value, gv.to_string_box().value) + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (ArrayBox)"); + } + + #[test] + fn stringbox_len_concat_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("StringBox"); + let out_tlv = { + let h = host.read().unwrap(); + // birth with init string: use fromUtf8 via set of arg in create? Current loader birth() no-arg, so concat + let _ = h.invoke_instance_method(&bt1, "concat", id1, &[Box::new(StringBox::new("ab"))]).expect("concat tlv").unwrap(); + let ln = h.invoke_instance_method(&bt1, "length", id1, &[]).expect("len tlv").unwrap(); + (ln.to_string_box().value) + }; + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("StringBox"); + let out_tb = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt2, "concat", id2, &[Box::new(StringBox::new("ab"))]).expect("concat tb").unwrap(); + let ln = h.invoke_instance_method(&bt2, "length", id2, &[]).expect("len tb").unwrap(); + (ln.to_string_box().value) + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (StringBox)"); + } + + #[test] + fn integerbox_get_set_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("IntegerBox"); + let out_tlv = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt1, "set", id1, &[Box::new(IntegerBox::new(123))]).expect("set tlv").unwrap(); + let gv = h.invoke_instance_method(&bt1, "get", id1, &[]).expect("get tlv").unwrap(); + gv.to_string_box().value + }; + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("IntegerBox"); + let out_tb = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt2, "set", id2, &[Box::new(IntegerBox::new(123))]).expect("set tb").unwrap(); + let gv = h.invoke_instance_method(&bt2, "get", id2, &[]).expect("get tb").unwrap(); + gv.to_string_box().value + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (IntegerBox)"); + } + + #[test] + fn consolebox_println_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("ConsoleBox"); + let out_tlv_is_none = { + let h = host.read().unwrap(); + let rv = h.invoke_instance_method(&bt1, "println", id1, &[Box::new(StringBox::new("hello"))]).expect("println tlv"); + rv.is_none() + }; + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("ConsoleBox"); + let out_tb_is_none = { + let h = host.read().unwrap(); + let rv = h.invoke_instance_method(&bt2, "println", id2, &[Box::new(StringBox::new("hello"))]).expect("println tb"); + rv.is_none() + }; + assert!(out_tlv_is_none && out_tb_is_none, "println should return void/None in both modes"); + } + + #[test] + fn mathbox_basic_ops_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("MathBox"); + let out_tlv = { + let h = host.read().unwrap(); + let s1 = h.invoke_instance_method(&bt1, "sqrt", id1, &[Box::new(IntegerBox::new(9))]).expect("sqrt tlv").unwrap(); + let s2 = h.invoke_instance_method(&bt1, "sin", id1, &[Box::new(IntegerBox::new(0))]).expect("sin tlv").unwrap(); + let s3 = h.invoke_instance_method(&bt1, "cos", id1, &[Box::new(IntegerBox::new(0))]).expect("cos tlv").unwrap(); + let s4 = h.invoke_instance_method(&bt1, "round", id1, &[Box::new(IntegerBox::new(26))]).expect("round tlv").unwrap(); + ( + s1.to_string_box().value, + s2.to_string_box().value, + s3.to_string_box().value, + s4.to_string_box().value, + ) + }; + + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("MathBox"); + let out_tb = { + let h = host.read().unwrap(); + let s1 = h.invoke_instance_method(&bt2, "sqrt", id2, &[Box::new(IntegerBox::new(9))]).expect("sqrt tb").unwrap(); + let s2 = h.invoke_instance_method(&bt2, "sin", id2, &[Box::new(IntegerBox::new(0))]).expect("sin tb").unwrap(); + let s3 = h.invoke_instance_method(&bt2, "cos", id2, &[Box::new(IntegerBox::new(0))]).expect("cos tb").unwrap(); + let s4 = h.invoke_instance_method(&bt2, "round", id2, &[Box::new(IntegerBox::new(26))]).expect("round tb").unwrap(); + ( + s1.to_string_box().value, + s2.to_string_box().value, + s3.to_string_box().value, + s4.to_string_box().value, + ) + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (MathBox)"); + } + + #[test] + fn encodingbox_base64_hex_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // Prepare bytes ["hi"] as Array + let bytes_array = { + let arr = ArrayBox::new(); + let _ = arr.push(Box::new(IntegerBox::new(104))); // 'h' + let _ = arr.push(Box::new(IntegerBox::new(105))); // 'i' + Box::new(arr) as Box + }; + + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("EncodingBox"); + let out_tlv = { + let h = host.read().unwrap(); + let b64 = h.invoke_instance_method(&bt1, "base64Encode", id1, &[Box::new(StringBox::new("hi"))]).expect("b64 tlv").unwrap(); + let hex = h.invoke_instance_method(&bt1, "hexEncode", id1, &[Box::new(StringBox::new("hi"))]).expect("hex tlv").unwrap(); + (b64.to_string_box().value, hex.to_string_box().value) + }; + + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("EncodingBox"); + let out_tb = { + let h = host.read().unwrap(); + let b64 = h.invoke_instance_method(&bt2, "base64Encode", id2, &[Box::new(StringBox::new("hi"))]).expect("b64 tb").unwrap(); + let hex = h.invoke_instance_method(&bt2, "hexEncode", id2, &[Box::new(StringBox::new("hi"))]).expect("hex tb").unwrap(); + (b64.to_string_box().value, hex.to_string_box().value) + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (EncodingBox)"); + } + + #[test] + fn regexbox_is_match_find_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("RegexBox"); + let out_tlv = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt1, "compile", id1, &[Box::new(StringBox::new("h.+o"))]).expect("compile tlv"); + let m = h.invoke_instance_method(&bt1, "isMatch", id1, &[Box::new(StringBox::new("hello"))]).expect("isMatch tlv").unwrap(); + let f = h.invoke_instance_method(&bt1, "find", id1, &[Box::new(StringBox::new("hello"))]).expect("find tlv").unwrap(); + (m.to_string_box().value, f.to_string_box().value) + }; + + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("RegexBox"); + let out_tb = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt2, "compile", id2, &[Box::new(StringBox::new("h.+o"))]).expect("compile tb"); + let m = h.invoke_instance_method(&bt2, "isMatch", id2, &[Box::new(StringBox::new("hello"))]).expect("isMatch tb").unwrap(); + let f = h.invoke_instance_method(&bt2, "find", id2, &[Box::new(StringBox::new("hello"))]).expect("find tb").unwrap(); + (m.to_string_box().value, f.to_string_box().value) + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (RegexBox)"); + } + + #[test] + fn pathbox_ops_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("PathBox"); + let out_tlv = { + let h = host.read().unwrap(); + let j = h.invoke_instance_method(&bt1, "join", id1, &[Box::new(StringBox::new("/a/b")), Box::new(StringBox::new("c.txt"))]).expect("join tlv").unwrap(); + let d = h.invoke_instance_method(&bt1, "dirname", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("dirname tlv").unwrap(); + let b = h.invoke_instance_method(&bt1, "basename", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("basename tlv").unwrap(); + let n = h.invoke_instance_method(&bt1, "normalize", id1, &[Box::new(StringBox::new("/a/./b/../b/c"))]).expect("normalize tlv").unwrap(); + (j.to_string_box().value, d.to_string_box().value, b.to_string_box().value, n.to_string_box().value) + }; + + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("PathBox"); + let out_tb = { + let h = host.read().unwrap(); + let j = h.invoke_instance_method(&bt2, "join", id2, &[Box::new(StringBox::new("/a/b")), Box::new(StringBox::new("c.txt"))]).expect("join tb").unwrap(); + let d = h.invoke_instance_method(&bt2, "dirname", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("dirname tb").unwrap(); + let b = h.invoke_instance_method(&bt2, "basename", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("basename tb").unwrap(); + let n = h.invoke_instance_method(&bt2, "normalize", id2, &[Box::new(StringBox::new("/a/./b/../b/c"))]).expect("normalize tb").unwrap(); + (j.to_string_box().value, d.to_string_box().value, b.to_string_box().value, n.to_string_box().value) + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (PathBox)"); + } + + #[test] + fn tomlbox_parse_get_tojson_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + let toml_text = "[package]\nname=\"nyash\"\n[deps]\nregex=\"1\"\n"; + + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("TOMLBox"); + let out_tlv = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt1, "parse", id1, &[Box::new(StringBox::new(toml_text))]).expect("parse tlv").unwrap(); + let name = h.invoke_instance_method(&bt1, "get", id1, &[Box::new(StringBox::new("package.name"))]).expect("get tlv").unwrap(); + let json = h.invoke_instance_method(&bt1, "toJson", id1, &[]).expect("toJson tlv").unwrap(); + (name.to_string_box().value, json.to_string_box().value) + }; + + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("TOMLBox"); + let out_tb = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt2, "parse", id2, &[Box::new(StringBox::new(toml_text))]).expect("parse tb").unwrap(); + let name = h.invoke_instance_method(&bt2, "get", id2, &[Box::new(StringBox::new("package.name"))]).expect("get tb").unwrap(); + let json = h.invoke_instance_method(&bt2, "toJson", id2, &[]).expect("toJson tb").unwrap(); + (name.to_string_box().value, json.to_string_box().value) + }; + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (TOMLBox)"); + } + + #[test] + fn timebox_now_tlv_vs_typebox_with_tolerance() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("TimeBox"); + let t_tlv = { + let h = host.read().unwrap(); + let v = h.invoke_instance_method(&bt1, "now", id1, &[]).expect("now tlv").unwrap(); + v.to_string_box().value.parse::().unwrap_or(0) + }; + + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("TimeBox"); + let t_tb = { + let h = host.read().unwrap(); + let v = h.invoke_instance_method(&bt2, "now", id2, &[]).expect("now tb").unwrap(); + v.to_string_box().value.parse::().unwrap_or(0) + }; + + let diff = (t_tb - t_tlv).abs(); + assert!(diff < 5_000, "TimeBox.now difference too large: {}ms", diff); + } + + #[test] + fn counterbox_singleton_delta_increments() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // TLV path: verify get->inc->get increases by 1 + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("CounterBox"); + let (a1, b1) = { + let h = host.read().unwrap(); + let a = h.invoke_instance_method(&bt1, "get", id1, &[]).expect("get tlv").unwrap(); + let _ = h.invoke_instance_method(&bt1, "inc", id1, &[]).expect("inc tlv"); + let b = h.invoke_instance_method(&bt1, "get", id1, &[]).expect("get2 tlv").unwrap(); + (a.to_string_box().value.parse::().unwrap_or(0), b.to_string_box().value.parse::().unwrap_or(0)) + }; + assert_eq!(b1 - a1, 1, "CounterBox TLV should increment by 1"); + + // TypeBox path: verify same delta behavior (not comparing absolute values due to singleton) + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("CounterBox"); + let (a2, b2) = { + let h = host.read().unwrap(); + let a = h.invoke_instance_method(&bt2, "get", id2, &[]).expect("get tb").unwrap(); + let _ = h.invoke_instance_method(&bt2, "inc", id2, &[]).expect("inc tb"); + let b = h.invoke_instance_method(&bt2, "get", id2, &[]).expect("get2 tb").unwrap(); + (a.to_string_box().value.parse::().unwrap_or(0), b.to_string_box().value.parse::().unwrap_or(0)) + }; + assert_eq!(b2 - a2, 1, "CounterBox TypeBox should increment by 1"); + } + + #[test] + fn filebox_rw_close_tmpdir_tlv_vs_typebox() { + ensure_host(); + let host = crate::runtime::get_global_plugin_host(); + + // Prepare temp file path + let mut p = std::env::temp_dir(); + p.push(format!("nyash_test_{}_{}.txt", std::process::id(), rand_id())) ; + let path_str = p.to_string_lossy().to_string(); + + // TLV path + env::set_var("NYASH_DISABLE_TYPEBOX", "1"); + let (bt1, id1, _hold1) = create_plugin_instance("FileBox"); + let out_tlv = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt1, "open", id1, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("w"))]).expect("open tlv"); + let _ = h.invoke_instance_method(&bt1, "write", id1, &[Box::new(StringBox::new("hello"))]).expect("write tlv"); + let _ = h.invoke_instance_method(&bt1, "close", id1, &[]).expect("close tlv"); + // reopen and read + let _ = h.invoke_instance_method(&bt1, "open", id1, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("r"))]).expect("open2 tlv"); + let rd = h.invoke_instance_method(&bt1, "read", id1, &[]).expect("read tlv").unwrap(); + let _ = h.invoke_instance_method(&bt1, "close", id1, &[]).expect("close2 tlv"); + rd.to_string_box().value + }; + + // TypeBox path + env::remove_var("NYASH_DISABLE_TYPEBOX"); + let (bt2, id2, _hold2) = create_plugin_instance("FileBox"); + let out_tb = { + let h = host.read().unwrap(); + let _ = h.invoke_instance_method(&bt2, "open", id2, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("w"))]).expect("open tb"); + let _ = h.invoke_instance_method(&bt2, "write", id2, &[Box::new(StringBox::new("hello"))]).expect("write tb"); + let _ = h.invoke_instance_method(&bt2, "close", id2, &[]).expect("close tb"); + let _ = h.invoke_instance_method(&bt2, "open", id2, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("r"))]).expect("open2 tb"); + let rd = h.invoke_instance_method(&bt2, "read", id2, &[]).expect("read tb").unwrap(); + let _ = h.invoke_instance_method(&bt2, "close", id2, &[]).expect("close2 tb"); + rd.to_string_box().value + }; + + // Cleanup best-effort + let _ = fs::remove_file(&path_str); + + assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (FileBox)"); + } + + fn rand_id() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + now.as_micros() as u64 + } +}