gui: add EguiBox TypeBox plugin (Windows egui stub)\n\n- plugins: add nyash-egui-plugin with TypeBox (resolve/invoke_id), Windows path for real window via eframe; stub on other OS\n- apps: add apps/egui-hello sample (open→uiLabel→run→close)\n- loader: improve Windows DLL resolution (target triples: x86_64/aarch64 msvc) and lib→dll mapping\n- tests: expand TypeBox vs TLV diff tests up to FileBox; all green\n- docs: update CURRENT_TASK checklist (diff tests completed)\n- config: nyash.toml add EguiBox (type_id=70), plugin registry and methods
This commit is contained in:
@ -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)
|
||||
|
||||
9
apps/egui-hello/main.nyash
Normal file
9
apps/egui-hello/main.nyash
Normal file
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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/) |
|
||||
|
||||
---
|
||||
|
||||
183
docs/development/roadmap/phases/phase-12.7/README.md
Normal file
183
docs/development/roadmap/phases/phase-12.7/README.md
Normal file
@ -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時代のプログラミング言語にする重要な一歩です。
|
||||
@ -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<string> {
|
||||
// コンテキストを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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>答え</summary>
|
||||
|
||||
```ancp
|
||||
$Calculator{#{memory}b(){m.memory=0}add(x,y){l result=x+y m.memory=result r result}}
|
||||
```
|
||||
</details>
|
||||
|
||||
### 演習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時代の開発を加速させましょう!
|
||||
301
docs/development/roadmap/phases/phase-12.7/examples.md
Normal file
301
docs/development/roadmap/phases/phase-12.7/examples.md
Normal file
@ -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<m.peers.length()){m.peers.get(i).receive(msg)i=i+1}}}
|
||||
```
|
||||
|
||||
### 5. 非同期WebServer
|
||||
```nyash
|
||||
// Nyash (342文字)
|
||||
box WebServer from HttpBox {
|
||||
init { port, routes, middleware }
|
||||
|
||||
birth(port) {
|
||||
from HttpBox.init(port)
|
||||
me.routes = new MapBox()
|
||||
me.middleware = new ArrayBox()
|
||||
}
|
||||
|
||||
route(path, handler) {
|
||||
me.routes.set(path, handler)
|
||||
return me
|
||||
}
|
||||
|
||||
use(middleware) {
|
||||
me.middleware.push(middleware)
|
||||
return me
|
||||
}
|
||||
|
||||
async start() {
|
||||
await from HttpBox.listen(me.port)
|
||||
print("Server running on port " + me.port)
|
||||
}
|
||||
}
|
||||
|
||||
// ANCP (183文字) - 46%削減!
|
||||
$WebServer@HttpBox{#{port,routes,middleware}b(port){@HttpBox.init(port)m.routes=n MapBox()m.middleware=n ArrayBox()}route(path,handler){m.routes.set(path,handler)r m}use(middleware){m.middleware.push(middleware)r m}async start(){await @HttpBox.listen(m.port)print("Server running on port"+m.port)}}
|
||||
```
|
||||
|
||||
## 💡 高度なパターン
|
||||
|
||||
### 6. エラーハンドリング
|
||||
```nyash
|
||||
// Nyash (198文字)
|
||||
box SafeCalculator {
|
||||
divide(a, b) {
|
||||
if b == 0 {
|
||||
return new ErrorBox("Division by zero")
|
||||
} else {
|
||||
return new ResultBox(a / b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANCP (93文字) - 53%削減!
|
||||
$SafeCalculator{divide(a,b){?b==0{r n ErrorBox("Division by zero")}:{r n ResultBox(a/b)}}}
|
||||
```
|
||||
|
||||
### 7. ジェネリック風パターン
|
||||
```nyash
|
||||
// Nyash (245文字)
|
||||
box Container {
|
||||
init { items, type }
|
||||
|
||||
birth(type) {
|
||||
me.items = new ArrayBox()
|
||||
me.type = type
|
||||
}
|
||||
|
||||
add(item) {
|
||||
if item.type() == me.type {
|
||||
me.items.push(item)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ANCP (124文字) - 49%削減!
|
||||
$Container{#{items,type}b(type){m.items=n ArrayBox()m.type=type}add(item){?item.type()==m.type{m.items.push(item)r true}r false}}
|
||||
```
|
||||
|
||||
## 🎮 完全なアプリケーション例
|
||||
|
||||
### 8. Todoアプリ(フル実装)
|
||||
```nyash
|
||||
// Nyash (562文字)
|
||||
box TodoApp {
|
||||
init { todos, nextId }
|
||||
|
||||
birth() {
|
||||
me.todos = new ArrayBox()
|
||||
me.nextId = 1
|
||||
}
|
||||
|
||||
addTodo(text) {
|
||||
local todo = new MapBox()
|
||||
todo.set("id", me.nextId)
|
||||
todo.set("text", text)
|
||||
todo.set("done", false)
|
||||
|
||||
me.todos.push(todo)
|
||||
me.nextId = me.nextId + 1
|
||||
|
||||
return todo.get("id")
|
||||
}
|
||||
|
||||
toggleTodo(id) {
|
||||
local i = 0
|
||||
loop(i < me.todos.length()) {
|
||||
local todo = me.todos.get(i)
|
||||
if todo.get("id") == id {
|
||||
todo.set("done", not todo.get("done"))
|
||||
return true
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
listTodos() {
|
||||
return me.todos
|
||||
}
|
||||
}
|
||||
|
||||
// ANCP (296文字) - 47%削減!
|
||||
$TodoApp{#{todos,nextId}b(){m.todos=n ArrayBox()m.nextId=1}addTodo(text){l todo=n MapBox()todo.set("id",m.nextId)todo.set("text",text)todo.set("done",false)m.todos.push(todo)m.nextId=m.nextId+1 r todo.get("id")}toggleTodo(id){l i=0 L(i<m.todos.length()){l todo=m.todos.get(i)?todo.get("id")==id{todo.set("done",not todo.get("done"))r true}i=i+1}r false}listTodos(){r m.todos}}
|
||||
```
|
||||
|
||||
## 📊 圧縮効果まとめ
|
||||
|
||||
| 例 | Nyash文字数 | ANCP文字数 | 削減率 |
|
||||
|----|------------|-----------|--------|
|
||||
| Point | 31 | 16 | 48% |
|
||||
| Calculator | 118 | 59 | 50% |
|
||||
| Dog | 165 | 87 | 47% |
|
||||
| P2PNode | 287 | 156 | 46% |
|
||||
| WebServer | 342 | 183 | 46% |
|
||||
| SafeCalculator | 198 | 93 | 53% |
|
||||
| Container | 245 | 124 | 49% |
|
||||
| TodoApp | 562 | 296 | 47% |
|
||||
|
||||
**平均削減率: 48.3%**
|
||||
|
||||
## 🔍 パターン分析
|
||||
|
||||
### 最も効果的な変換
|
||||
1. `return` → `r` (83%削減)
|
||||
2. `local` → `l` (80%削減)
|
||||
3. `new` → `n` (67%削減)
|
||||
4. `box` → `$` (75%削減)
|
||||
5. `me` → `m` (50%削減)
|
||||
|
||||
### 圧縮のコツ
|
||||
- 空白を最小限に(セミコロン不要)
|
||||
- 中括弧の直後に内容を書く
|
||||
- 演算子の前後の空白を省略
|
||||
- 文字列連結の空白も省略可能
|
||||
|
||||
## 🎯 練習問題
|
||||
|
||||
### 問題1
|
||||
次のNyashコードをANCPに変換してください:
|
||||
```nyash
|
||||
box User {
|
||||
init { name, email }
|
||||
|
||||
birth(name, email) {
|
||||
me.name = name
|
||||
me.email = email
|
||||
}
|
||||
|
||||
toString() {
|
||||
return me.name + " <" + me.email + ">"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>答え</summary>
|
||||
|
||||
```ancp
|
||||
$User{#{name,email}b(name,email){m.name=name m.email=email}toString(){r m.name+" <"+m.email+">"}}
|
||||
```
|
||||
</details>
|
||||
|
||||
### 問題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}}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>答え</summary>
|
||||
|
||||
```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
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
これらの例を参考に、ANCPを使いこなしてAI時代の効率的な開発を実現しましょう!
|
||||
@ -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哲学に忠実
|
||||
- 書いていて楽しい文法
|
||||
|
||||
プログラミング言語設計の専門的視点から、深い分析と提案をお願いします。
|
||||
時間制限なしでじっくり考えてください。
|
||||
@ -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は本当に「世界一美しい箱」になる。
|
||||
@ -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. 既存サンプルコードの移行
|
||||
|
||||
================================================================================
|
||||
@ -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<Mapping>,
|
||||
}
|
||||
|
||||
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<PathBuf>,
|
||||
|
||||
#[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との協働開発を革命的に改善します!
|
||||
293
docs/development/roadmap/phases/phase-12.7/technical-spec.md
Normal file
293
docs/development/roadmap/phases/phase-12.7/technical-spec.md
Normal file
@ -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<String, AncpError>;
|
||||
fn decode(&self, ancp: &str) -> Result<String, AncpError>;
|
||||
|
||||
// ストリーミング変換
|
||||
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<Mapping>,
|
||||
}
|
||||
|
||||
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の技術的実装の基準となる文書です。実装時はこの仕様に従い、必要に応じて更新してください。
|
||||
@ -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実装の開始
|
||||
|
||||
@ -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<Mutex>自動化**: 明示的ロック管理不要(-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週間で読破可能なコンパイラ
|
||||
- **貢献しやすさ**: 誰でも改造できる規模
|
||||
- **教育的価値**: 世界一シンプルな実用コンパイラ
|
||||
- **現在**: 80,000行(Rust実装)
|
||||
- **第一目標**: 20,000行(Nyashセルフホスティング、**75%削減**)
|
||||
- **究極の夢**: さらなる最適化でより小さく!
|
||||
- **MIR命令数**: たった13個で全機能実現
|
||||
- **理解容易性**: 週末で読破可能なコンパイラ
|
||||
- **バイナリサイズ**: テンプレート方式なら2-3KBも可能
|
||||
- **教育的価値**: 世界一美しく、世界一小さい実用コンパイラ
|
||||
|
||||
### 🌟 Everything is Boxの究極形
|
||||
- コンパイラもBox
|
||||
- リンカーもBox
|
||||
- アセンブラもBox
|
||||
- すべてがBox!
|
||||
|
||||
**世界一美しい箱は、自分自身さえも美しく包み込む**
|
||||
@ -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)
|
||||
|
||||
@ -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 段階的移行
|
||||
|
||||
19
nyash.toml
19
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ログ
|
||||
|
||||
@ -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<extern "C" fn(*const c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> 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::<NyashTypeBoxFfi>() 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 {
|
||||
|
||||
@ -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<extern "C" fn(*const c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> 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::<NyashTypeBoxFfi>() as u16,
|
||||
name: b"ConsoleBox\0".as_ptr() as *const c_char,
|
||||
resolve: Some(console_resolve),
|
||||
invoke_id: Some(console_invoke_id),
|
||||
capabilities: 0,
|
||||
};
|
||||
|
||||
22
plugins/nyash-egui-plugin/Cargo.toml
Normal file
22
plugins/nyash-egui-plugin/Cargo.toml
Normal file
@ -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 }
|
||||
|
||||
242
plugins/nyash-egui-plugin/src/lib.rs
Normal file
242
plugins/nyash-egui-plugin/src/lib.rs
Normal file
@ -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<String>,
|
||||
}
|
||||
|
||||
static INST: Lazy<Mutex<HashMap<u32, EguiInstance>>> = 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<extern "C" fn(*const std::os::raw::c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> 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::<NyashTypeBoxFfi>() 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<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
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<i64> {
|
||||
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<String> {
|
||||
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<String>) {
|
||||
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<String> }
|
||||
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 })),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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<extern "C" fn(*const c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> 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::<NyashTypeBoxFfi>() 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
|
||||
|
||||
@ -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<extern "C" fn(*const c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> 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<String> = 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<String> = 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::<NyashTypeBoxFfi>() 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() {
|
||||
|
||||
@ -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<extern "C" fn(*const c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> 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::<NyashTypeBoxFfi>() 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
|
||||
|
||||
@ -773,6 +773,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::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
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() {
|
||||
if let Some(res) = self.try_boxcall_vtable_stub(dst, &recv, method, method_id, args) { return res; }
|
||||
@ -1236,13 +1275,101 @@ impl VM {
|
||||
/// Phase 12 Tier-0: vtable優先経路の雛形(常に未処理)。
|
||||
/// 目的: 将来のTypeBox ABI配線ポイントを先置きしても既存挙動を変えないこと。
|
||||
fn try_boxcall_vtable_stub(&mut self, _dst: Option<ValueId>, _recv: &VMValue, _method: &str, _method_id: Option<u16>, _args: &[ValueId]) -> Option<Result<ControlFlow, VMError>> {
|
||||
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::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
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::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
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<Box<dyn NyashBox>> = 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::<crate::instance_v2::InstanceBox>() {
|
||||
match slot {
|
||||
|
||||
@ -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<Box<dyn crate::box_trait::NyashBox>, 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<Box<dyn crate::box_trait::NyashBox>, 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<dyn NyashBox>];
|
||||
let _ = unified_dispatch::dispatch_by_slot(slot_id, console.as_ref(), &args);
|
||||
}
|
||||
// 3) 結果を返す
|
||||
Ok(Box::new(StringBox::new("WASM v2 unified dispatch test completed")))
|
||||
}
|
||||
|
||||
@ -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<u16> {
|
||||
@ -15,11 +16,104 @@ pub fn resolve_slot(recv: &dyn NyashBox, method: &str, arity: usize) -> Option<u
|
||||
|
||||
/// 実際の呼び出し分岐は、将来的にここから生成済みのstubsに委譲する予定。
|
||||
pub fn dispatch_by_slot(
|
||||
_slot: u16,
|
||||
_recv: &dyn NyashBox,
|
||||
_args: &[Box<dyn NyashBox>],
|
||||
slot: u16,
|
||||
recv: &dyn NyashBox,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> Option<Box<dyn NyashBox>> {
|
||||
// 未実装: wasm_v2ではJS/hostへのブリッジや、Wasm内の簡易実装に委譲
|
||||
None
|
||||
}
|
||||
match slot {
|
||||
// ConsoleBox slots (400番台予約)
|
||||
400 => {
|
||||
// console.log(message)
|
||||
if let Some(console) = recv.as_any().downcast_ref::<ConsoleBox>() {
|
||||
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::<ConsoleBox>() {
|
||||
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::<ConsoleBox>() {
|
||||
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::<ConsoleBox>() {
|
||||
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::<ArrayBox>() {
|
||||
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::<ArrayBox>() {
|
||||
return Some(array.length());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// MapBox slots (200番台)
|
||||
200 => {
|
||||
// map.size()
|
||||
if let Some(map) = recv.as_any().downcast_ref::<MapBox>() {
|
||||
return Some(map.size());
|
||||
}
|
||||
None
|
||||
}
|
||||
202 => {
|
||||
// map.has(key)
|
||||
if let Some(map) = recv.as_any().downcast_ref::<MapBox>() {
|
||||
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::<MapBox>() {
|
||||
if args.len() == 1 {
|
||||
let key_box = args[0].clone_box();
|
||||
return Some(map.get(key_box));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -19,12 +19,14 @@ mod enabled {
|
||||
|
||||
/// Loaded plugin information
|
||||
pub struct LoadedPluginV2 {
|
||||
/// Library handle
|
||||
_lib: Arc<libloading::Library>,
|
||||
/// Library handle
|
||||
_lib: Arc<libloading::Library>,
|
||||
|
||||
/// Box types provided by this plugin
|
||||
#[allow(dead_code)]
|
||||
box_types: Vec<String>,
|
||||
/// Box types provided by this plugin
|
||||
#[allow(dead_code)]
|
||||
box_types: Vec<String>,
|
||||
/// Optional per-box TypeBox ABI addresses (nyash_typebox_<BoxName>); raw usize for Send/Sync
|
||||
typeboxes: std::collections::HashMap<String, usize>,
|
||||
|
||||
/// 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<extern "C" fn(*const c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> 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<dyn NyashBox>],
|
||||
) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||
// 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<dyn NyashBox>));
|
||||
}
|
||||
// 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::<crate::box_trait::StringBox>().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::<crate::box_trait::StringBox>().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::<crate::box_trait::StringBox>().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<dyn NyashBox>),
|
||||
2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|i| Box::new(crate::box_trait::IntegerBox::new(i as i64)) as Box<dyn NyashBox>),
|
||||
5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(|f| Box::new(crate::boxes::math_box::FloatBox::new(f)) as Box<dyn NyashBox>),
|
||||
6 => Some(Box::new(crate::box_trait::StringBox::new(crate::runtime::plugin_ffi_common::decode::string(payload))) as Box<dyn NyashBox>),
|
||||
_ => 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<String, usize> = 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
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
84
src/tests/nyash_abi_basic.rs
Normal file
84
src/tests/nyash_abi_basic.rs
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
433
src/tests/typebox_tlv_diff.rs
Normal file
433
src/tests/typebox_tlv_diff.rs
Normal file
@ -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<dyn NyashBox>) {
|
||||
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::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
(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<uint8>
|
||||
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<dyn NyashBox>
|
||||
};
|
||||
|
||||
// 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::<i64>().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::<i64>().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::<i64>().unwrap_or(0), b.to_string_box().value.parse::<i64>().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::<i64>().unwrap_or(0), b.to_string_box().value.parse::<i64>().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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user