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:
Moe Charm
2025-09-03 13:58:52 +09:00
parent f939ad0033
commit ceb22b6c18
33 changed files with 3891 additions and 84 deletions

View File

@ -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と進化戦略
- TODOTier0
- [ ] 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 の同一実行を実現。
- 現状: Tier0/Tier1 相当の配線完了。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 のスロット対応
## 残タスクToDo
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-echoScript/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 セクションを参照してください。
DocsPhase 12 直近)
- [x] Minimal Core ABI方針の文書化NYASH_ABI_MIN_CORE.md

View 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()

View File

@ -1,4 +1,67 @@
# 🎯 CURRENT TASK - 2025-09-01 SnapshotAsync Task System / Phase 11.7 + Plugin-First
# 🎯 CURRENT TASK - 2025-09-03 SnapshotPhase 12.05: 旧C ABI→新C ABI(TypeBox) 変換 + 差分テスト拡充
目的: 既存C ABIプラグインを「統一TypeBox C ABI」に段階移行。LoaderのTypeBoxプローブ + `invoke_id` 優先経路を活用し、コアBoxArray/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/setstring/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/findResult/Bool/文字列)
- PathBox: join/dirname/basename/isAbs/normalize
- TOMLBox: parse/get/toJsonResult.Ok/Err
- TimeBox: now許容差内で比較厳密比較回避
- CounterBox: singletonの基本挙動
- FileBox: read/write/closetmpdir使用で副作用隔離
3) Python/Net/Socket 系の差分テストは対象外(開発中のため今回スキップ)
## DoDDefinition of Done
1) 上記コアBoxMap/Array/String/Integer/Consoleに加え、Math/Encoding/Regex/Path/TOML/Time/Counter/File の差分テストが全てGreenVM
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/ConsoleVM
- [x] 差分テスト: MathBoxsqrt/sin/cos/round
- [x] 差分テスト: EncodingBoxbase64/hex encode/decode
- [x] 差分テスト: RegexBoxisMatch/find
- [x] 差分テスト: PathBoxjoin/dirname/basename/isAbs/normalize
- [x] 差分テスト: TOMLBoxparse/get/toJson
- [x] 差分テスト: TimeBoxnow: 許容差内)
- [x] 差分テスト: CounterBoxsingleton挙動
- [x] 差分テスト: FileBoxtmpdirで 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

View File

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

View 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**: ANCPAIとの架け橋**ここ!**
- **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時代のプログラミング言語にする重要な一歩です。

View File

@ -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時代の開発を加速させましょう

View 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時代の効率的な開発を実現しましょう

View File

@ -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哲学に忠実
- 書いていて楽しい文法
プログラミング言語設計の専門的視点から、深い分析と提案をお願いします。
時間制限なしでじっくり考えてください。

View File

@ -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 BoxOk/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は本当に「世界一美しい箱」になる。

View File

@ -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. 既存サンプルコードの移行
================================================================================

View File

@ -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との協働開発を革命的に改善します

View 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の技術的実装の基準となる文書です。実装時はこの仕様に従い、必要に応じて更新してください。

View File

@ -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 ABITLVへフォールバック
- 所有権・セーフポイントのガード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実装の開始

View File

@ -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
**世界一美しい箱は、自分自身さえも美しく包み込む**

View File

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

View File

@ -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 段階的移行

View File

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

View File

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

View File

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

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

View 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 })),
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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内の簡易実装に委譲
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
}
}

View File

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

View File

@ -25,6 +25,8 @@ mod enabled {
/// 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 → NoneNyashの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,7 +937,24 @@ 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 {
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,
@ -901,10 +964,88 @@ impl PluginLoaderV2 {
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
}

View File

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

View File

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

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

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