feat: Phase 9.75g-0 BID-FFI完了 + Phase 9.8準備
- BID-FFI基盤実装完了(プラグインシステム動作確認) - Phase 8.6 VM性能改善完了(50.94倍高速化達成) - Phase 9.78 LLVM PoC基盤完成 - Phase 9.8 BIDレジストリ準備(nyash.toml活用戦略) - ビルドエラー修正、警告は後で対応 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -111,41 +111,59 @@ read = {
|
|||||||
- [phase_8_6_vm_performance_improvement.md](../予定/native-plan/issues/phase_8_6_vm_performance_improvement.md) - 詳細技術分析
|
- [phase_8_6_vm_performance_improvement.md](../予定/native-plan/issues/phase_8_6_vm_performance_improvement.md) - 詳細技術分析
|
||||||
- [copilot_issues.txt](../予定/native-plan/copilot_issues.txt) - 全体開発計画
|
- [copilot_issues.txt](../予定/native-plan/copilot_issues.txt) - 全体開発計画
|
||||||
|
|
||||||
## 📋 **今日の重要決定事項(2025年8月21日)**
|
## 🔧 **現在進行中:ビルトインBoxプラグイン化プロジェクト**(2025-08-18開始)
|
||||||
|
|
||||||
### **1. Phase 9.8戦略転換**
|
### **目的**
|
||||||
- ✅ nyash.tomlに既存の型情報が豊富に存在することを発見
|
- **ビルド時間短縮**: 3分 → 30秒以下
|
||||||
- ✅ BID用の新規YAMLファイル不要と判断
|
- **バイナリサイズ削減**: 最小構成で500KB以下
|
||||||
- ✅ nyash.toml拡張によるBID機能実装を決定
|
- **保守性向上**: 各プラグイン独立開発
|
||||||
- ✅ GitHub Issue #116作成(Phase 9.8実装計画)
|
|
||||||
|
|
||||||
### **2. 技術的洞察:JIT vs AOT**
|
### **対象Box(13種類)**
|
||||||
- **MIRの存在により難易度が同等に**
|
|
||||||
- VM最適化でネイティブ速度に迫る可能性
|
|
||||||
- 将来: VM JIT化も選択肢に
|
|
||||||
|
|
||||||
### **3. 開発優先順位の更新**
|
|
||||||
```
|
```
|
||||||
1. ✅ Phase 8.6 VM性能改善(完了!50.94倍達成)
|
Phase 1: ネットワーク系(HttpBox系、SocketBox)
|
||||||
2. ✅ Phase 9.78 LLVM PoC基盤(MIR修正完了)
|
Phase 2: GUI系(EguiBox、Canvas系、Web系)
|
||||||
3. 🔄 Phase 9.8 BIDレジストリ(nyash.toml拡張方式)
|
Phase 3: 特殊用途系(AudioBox、QRBox、StreamBox等)
|
||||||
4. → Phase 9.9 権限モデル(FileBoxで実証)
|
|
||||||
5. → Phase 10 LLVM本格実装(将来検討)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### **4. Windows戦略の具体化**
|
### **進捗状況**
|
||||||
- Bitcodeキャッシュで1回生成→全OS対応
|
- ✅ プラグイン移行依頼書作成(`docs/plugin-migration-request.md`)
|
||||||
- mingw-gnuで即座にWindows対応可能
|
- ✅ CopilotのBID変換コード抽出(`src/bid-converter-copilot/`)
|
||||||
- APEは小規模ツール専用として位置づけ
|
- ✅ CopilotのBIDコード生成機能抽出(`src/bid-codegen-from-copilot/`)
|
||||||
|
- 🔄 HttpBoxプラグイン化作業をCopilotに依頼中
|
||||||
|
|
||||||
|
## 📋 **今日の重要決定事項(2025年8月18日)**
|
||||||
|
|
||||||
|
### **1. CopilotのPR管理戦略**
|
||||||
|
- ✅ 大規模変更(1,735行)を含むPR #117をrevert
|
||||||
|
- ✅ 必要な新規ファイルのみ選択的に抽出・保存
|
||||||
|
- ✅ cli.rs/runner.rsへの大幅変更は取り込まない方針
|
||||||
|
|
||||||
|
### **2. Copilot成果物の保存**
|
||||||
|
- **BID変換部分**: `src/bid-converter-copilot/` (TLV、型変換)
|
||||||
|
- **コード生成部分**: `src/bid-codegen-from-copilot/` (各言語向け生成)
|
||||||
|
- **活用方針**: 将来的にnyash2.toml実装時に参考資料として使用
|
||||||
|
|
||||||
|
### **3. 開発優先順位の明確化**
|
||||||
|
```
|
||||||
|
1. 🔄 ビルトインBoxプラグイン化(HttpBox系から開始)
|
||||||
|
2. → Phase 9.8 BIDレジストリ(nyash.toml拡張方式)
|
||||||
|
3. → Phase 9.9 権限モデル(FileBoxで実証)
|
||||||
|
4. → Phase 10 LLVM本格実装(将来検討)
|
||||||
|
```
|
||||||
|
|
||||||
|
### **4. 選択的pull戦略の確立**
|
||||||
|
- **原則**: 必要な機能だけを取り込む
|
||||||
|
- **判断基準**: 現在の目標との関連性、複雑性、保守性
|
||||||
|
- **実践**: 新規ファイルは別フォルダに保存、既存ファイルの大幅変更は慎重に
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**最終更新**: 2025年8月21日
|
**最終更新**: 2025年8月18日
|
||||||
**次回レビュー**: Phase 9.8実装開始時
|
**次回レビュー**: HttpBoxプラグイン完成時
|
||||||
**開発状況**: Phase 9.75g-0完了 → Phase 8.6完了 → Phase 9.78基盤完了 → Phase 9.8開始
|
**開発状況**: ビルトインBoxプラグイン化進行中
|
||||||
|
|
||||||
### 🎯 **次のアクション**
|
### 🎯 **次のアクション**
|
||||||
1. nyash.toml拡張仕様の設計
|
1. HttpBoxプラグイン化の完成待ち(Copilot作業中)
|
||||||
2. VMバックエンドでのFileBox統合テスト準備
|
2. plugin-testerでの動作確認
|
||||||
3. 権限モデル(Phase 9.9)の実装計画
|
3. 次のプラグイン化対象(EguiBox等)の準備
|
||||||
|
|
||||||
|
|||||||
39
src/bid-codegen-from-copilot/README.md
Normal file
39
src/bid-codegen-from-copilot/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# BID Code Generation from Copilot
|
||||||
|
|
||||||
|
このフォルダには、CopilotさんがPR #117で実装したBIDコード生成機能を保存しています。
|
||||||
|
|
||||||
|
## 📦 含まれるファイル
|
||||||
|
|
||||||
|
### コア機能
|
||||||
|
- **schema.rs**: BIDスキーマ定義(YAML/JSONパース)
|
||||||
|
- **codegen/generator.rs**: コード生成エンジン
|
||||||
|
- **codegen/mod.rs**: モジュール定義
|
||||||
|
|
||||||
|
### 各言語向け生成ターゲット
|
||||||
|
- **codegen/targets/vm.rs**: VM用バイトコード生成
|
||||||
|
- **codegen/targets/wasm.rs**: WebAssembly生成(最も詳細)
|
||||||
|
- **codegen/targets/llvm.rs**: LLVM IR生成(スタブ)
|
||||||
|
- **codegen/targets/python.rs**: Pythonバインディング(スタブ)
|
||||||
|
- **codegen/targets/typescript.rs**: TypeScript定義(スタブ)
|
||||||
|
|
||||||
|
## 🎯 用途
|
||||||
|
|
||||||
|
将来的に以下の用途で活用可能:
|
||||||
|
|
||||||
|
1. **プラグインの多言語対応**:
|
||||||
|
- C以外の言語でプラグイン作成
|
||||||
|
- 各言語向けバインディング自動生成
|
||||||
|
|
||||||
|
2. **バックエンド統合**:
|
||||||
|
- VM/WASM/LLVM向けの統一インターフェース
|
||||||
|
- 外部関数定義の一元管理
|
||||||
|
|
||||||
|
3. **型安全性向上**:
|
||||||
|
- スキーマベースの型チェック
|
||||||
|
- コンパイル時の整合性検証
|
||||||
|
|
||||||
|
## 📝 メモ
|
||||||
|
|
||||||
|
- 現在は使用していない(既存のnyash.tomlベースが動作中)
|
||||||
|
- cli.rsとrunner.rsへの大幅変更は含まれていない(別フォルダ保存)
|
||||||
|
- 必要に応じて段階的に統合可能
|
||||||
239
src/bid-codegen-from-copilot/codegen/generator.rs
Normal file
239
src/bid-codegen-from-copilot/codegen/generator.rs
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
/*!
|
||||||
|
* Code Generator - Main entry point for BID code generation
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::bid::{BidDefinition, BidError, BidResult};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
/// Code generation target
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum CodeGenTarget {
|
||||||
|
Wasm,
|
||||||
|
VM,
|
||||||
|
LLVM,
|
||||||
|
TypeScript,
|
||||||
|
Python,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CodeGenTarget {
|
||||||
|
/// Parse target from string
|
||||||
|
pub fn from_str(target: &str) -> Result<Self, BidError> {
|
||||||
|
match target.to_lowercase().as_str() {
|
||||||
|
"wasm" => Ok(CodeGenTarget::Wasm),
|
||||||
|
"vm" => Ok(CodeGenTarget::VM),
|
||||||
|
"llvm" => Ok(CodeGenTarget::LLVM),
|
||||||
|
"ts" | "typescript" => Ok(CodeGenTarget::TypeScript),
|
||||||
|
"py" | "python" => Ok(CodeGenTarget::Python),
|
||||||
|
_ => Err(BidError::UnsupportedTarget(target.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the target name as string
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
CodeGenTarget::Wasm => "wasm",
|
||||||
|
CodeGenTarget::VM => "vm",
|
||||||
|
CodeGenTarget::LLVM => "llvm",
|
||||||
|
CodeGenTarget::TypeScript => "ts",
|
||||||
|
CodeGenTarget::Python => "py",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Code generation options
|
||||||
|
pub struct CodeGenOptions {
|
||||||
|
pub target: CodeGenTarget,
|
||||||
|
pub output_dir: PathBuf,
|
||||||
|
pub force: bool,
|
||||||
|
pub dry_run: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CodeGenOptions {
|
||||||
|
/// Create new options
|
||||||
|
pub fn new(target: CodeGenTarget, output_dir: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
target,
|
||||||
|
output_dir,
|
||||||
|
force: false,
|
||||||
|
dry_run: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set force overwrite
|
||||||
|
pub fn with_force(mut self, force: bool) -> Self {
|
||||||
|
self.force = force;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set dry run mode
|
||||||
|
pub fn with_dry_run(mut self, dry_run: bool) -> Self {
|
||||||
|
self.dry_run = dry_run;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generated file
|
||||||
|
pub struct GeneratedFile {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GeneratedFile {
|
||||||
|
pub fn new(path: PathBuf, content: String) -> Self {
|
||||||
|
Self { path, content }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Code generation result
|
||||||
|
pub struct CodeGenResult {
|
||||||
|
pub files: Vec<GeneratedFile>,
|
||||||
|
pub target: CodeGenTarget,
|
||||||
|
pub bid_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main code generator
|
||||||
|
pub struct CodeGenerator;
|
||||||
|
|
||||||
|
impl CodeGenerator {
|
||||||
|
/// Generate code from BID definition
|
||||||
|
pub fn generate(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<CodeGenResult> {
|
||||||
|
let bid_name = bid.name();
|
||||||
|
|
||||||
|
// Generate based on target
|
||||||
|
let files = match options.target {
|
||||||
|
CodeGenTarget::Wasm => Self::generate_wasm(bid, options)?,
|
||||||
|
CodeGenTarget::VM => Self::generate_vm(bid, options)?,
|
||||||
|
CodeGenTarget::LLVM => Self::generate_llvm(bid, options)?,
|
||||||
|
CodeGenTarget::TypeScript => Self::generate_typescript(bid, options)?,
|
||||||
|
CodeGenTarget::Python => Self::generate_python(bid, options)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write files unless dry run
|
||||||
|
if !options.dry_run {
|
||||||
|
Self::write_files(&files, options)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CodeGenResult {
|
||||||
|
files,
|
||||||
|
target: options.target.clone(),
|
||||||
|
bid_name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate WASM import declarations
|
||||||
|
fn generate_wasm(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
use super::targets::wasm::WasmGenerator;
|
||||||
|
WasmGenerator::generate(bid, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate VM function table definitions
|
||||||
|
fn generate_vm(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
use super::targets::vm::VmGenerator;
|
||||||
|
VmGenerator::generate(bid, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate LLVM declare statements
|
||||||
|
fn generate_llvm(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
use super::targets::llvm::LlvmGenerator;
|
||||||
|
LlvmGenerator::generate(bid, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate TypeScript wrapper
|
||||||
|
fn generate_typescript(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
use super::targets::typescript::TypeScriptGenerator;
|
||||||
|
TypeScriptGenerator::generate(bid, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate Python wrapper
|
||||||
|
fn generate_python(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
use super::targets::python::PythonGenerator;
|
||||||
|
PythonGenerator::generate(bid, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write generated files to disk
|
||||||
|
fn write_files(files: &[GeneratedFile], options: &CodeGenOptions) -> BidResult<()> {
|
||||||
|
for file in files {
|
||||||
|
// Check if file exists and force is not set
|
||||||
|
if file.path.exists() && !options.force {
|
||||||
|
return Err(BidError::IoError(format!(
|
||||||
|
"File already exists: {} (use --force to overwrite)",
|
||||||
|
file.path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create parent directories
|
||||||
|
if let Some(parent) = file.path.parent() {
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.map_err(|e| BidError::IoError(format!(
|
||||||
|
"Failed to create directory {}: {}",
|
||||||
|
parent.display(),
|
||||||
|
e
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write file
|
||||||
|
fs::write(&file.path, &file.content)
|
||||||
|
.map_err(|e| BidError::IoError(format!(
|
||||||
|
"Failed to write file {}: {}",
|
||||||
|
file.path.display(),
|
||||||
|
e
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preview generated files (for dry run)
|
||||||
|
pub fn preview_files(result: &CodeGenResult) {
|
||||||
|
println!("📁 Generated files for target '{}' (BID: {}):",
|
||||||
|
result.target.as_str(), result.bid_name);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
for file in &result.files {
|
||||||
|
println!("📄 {}", file.path.display());
|
||||||
|
println!(" {} lines, {} bytes",
|
||||||
|
file.content.lines().count(),
|
||||||
|
file.content.len());
|
||||||
|
|
||||||
|
// Show first few lines
|
||||||
|
let lines: Vec<&str> = file.content.lines().take(5).collect();
|
||||||
|
for line in lines {
|
||||||
|
println!(" │ {}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.content.lines().count() > 5 {
|
||||||
|
println!(" │ ... ({} more lines)", file.content.lines().count() - 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_code_gen_target_from_str() {
|
||||||
|
assert_eq!(CodeGenTarget::from_str("wasm").unwrap(), CodeGenTarget::Wasm);
|
||||||
|
assert_eq!(CodeGenTarget::from_str("vm").unwrap(), CodeGenTarget::VM);
|
||||||
|
assert_eq!(CodeGenTarget::from_str("llvm").unwrap(), CodeGenTarget::LLVM);
|
||||||
|
assert_eq!(CodeGenTarget::from_str("ts").unwrap(), CodeGenTarget::TypeScript);
|
||||||
|
assert_eq!(CodeGenTarget::from_str("typescript").unwrap(), CodeGenTarget::TypeScript);
|
||||||
|
assert_eq!(CodeGenTarget::from_str("py").unwrap(), CodeGenTarget::Python);
|
||||||
|
assert_eq!(CodeGenTarget::from_str("python").unwrap(), CodeGenTarget::Python);
|
||||||
|
|
||||||
|
assert!(CodeGenTarget::from_str("invalid").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_code_gen_target_as_str() {
|
||||||
|
assert_eq!(CodeGenTarget::Wasm.as_str(), "wasm");
|
||||||
|
assert_eq!(CodeGenTarget::VM.as_str(), "vm");
|
||||||
|
assert_eq!(CodeGenTarget::LLVM.as_str(), "llvm");
|
||||||
|
assert_eq!(CodeGenTarget::TypeScript.as_str(), "ts");
|
||||||
|
assert_eq!(CodeGenTarget::Python.as_str(), "py");
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/bid-codegen-from-copilot/codegen/mod.rs
Normal file
9
src/bid-codegen-from-copilot/codegen/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/*!
|
||||||
|
* Code Generation Module - Generate code from BID definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod generator;
|
||||||
|
pub mod targets;
|
||||||
|
|
||||||
|
pub use generator::*;
|
||||||
|
pub use targets::*;
|
||||||
17
src/bid-codegen-from-copilot/codegen/targets/llvm.rs
Normal file
17
src/bid-codegen-from-copilot/codegen/targets/llvm.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*!
|
||||||
|
* LLVM Target Generator - Generate LLVM IR declarations
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::bid::{BidDefinition, BidResult};
|
||||||
|
use crate::bid::codegen::{CodeGenOptions, GeneratedFile};
|
||||||
|
|
||||||
|
pub struct LlvmGenerator;
|
||||||
|
|
||||||
|
impl LlvmGenerator {
|
||||||
|
/// Generate LLVM declarations
|
||||||
|
pub fn generate(bid: &BidDefinition, _options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
// TODO: Implement LLVM code generation
|
||||||
|
println!("🚧 LLVM code generation not yet implemented for {}", bid.name());
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/bid-codegen-from-copilot/codegen/targets/mod.rs
Normal file
9
src/bid-codegen-from-copilot/codegen/targets/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/*!
|
||||||
|
* Target-specific code generators
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod wasm;
|
||||||
|
pub mod vm;
|
||||||
|
pub mod llvm;
|
||||||
|
pub mod typescript;
|
||||||
|
pub mod python;
|
||||||
17
src/bid-codegen-from-copilot/codegen/targets/python.rs
Normal file
17
src/bid-codegen-from-copilot/codegen/targets/python.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*!
|
||||||
|
* Python Target Generator - Generate Python FFI wrappers
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::bid::{BidDefinition, BidResult};
|
||||||
|
use crate::bid::codegen::{CodeGenOptions, GeneratedFile};
|
||||||
|
|
||||||
|
pub struct PythonGenerator;
|
||||||
|
|
||||||
|
impl PythonGenerator {
|
||||||
|
/// Generate Python wrappers
|
||||||
|
pub fn generate(bid: &BidDefinition, _options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
// TODO: Implement Python code generation
|
||||||
|
println!("🚧 Python code generation not yet implemented for {}", bid.name());
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/bid-codegen-from-copilot/codegen/targets/typescript.rs
Normal file
17
src/bid-codegen-from-copilot/codegen/targets/typescript.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*!
|
||||||
|
* TypeScript Target Generator - Generate TypeScript FFI wrappers
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::bid::{BidDefinition, BidResult};
|
||||||
|
use crate::bid::codegen::{CodeGenOptions, GeneratedFile};
|
||||||
|
|
||||||
|
pub struct TypeScriptGenerator;
|
||||||
|
|
||||||
|
impl TypeScriptGenerator {
|
||||||
|
/// Generate TypeScript wrappers
|
||||||
|
pub fn generate(bid: &BidDefinition, _options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
// TODO: Implement TypeScript code generation
|
||||||
|
println!("🚧 TypeScript code generation not yet implemented for {}", bid.name());
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
296
src/bid-codegen-from-copilot/codegen/targets/vm.rs
Normal file
296
src/bid-codegen-from-copilot/codegen/targets/vm.rs
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
/*!
|
||||||
|
* VM Target Generator - Generate VM function table definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::bid::{BidDefinition, BidInterface, BidMethod, BidResult};
|
||||||
|
use crate::bid::codegen::{CodeGenOptions, GeneratedFile};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct VmGenerator;
|
||||||
|
|
||||||
|
impl VmGenerator {
|
||||||
|
/// Generate VM function table definitions
|
||||||
|
pub fn generate(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
let bid_name = bid.name();
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
|
// Generate Rust function table definition
|
||||||
|
let rust_content = Self::generate_rust_function_table(bid)?;
|
||||||
|
let rust_path = options.output_dir.join(format!("{}_vm_table.rs", bid_name));
|
||||||
|
files.push(GeneratedFile::new(rust_path, rust_content));
|
||||||
|
|
||||||
|
// Generate dispatcher implementation
|
||||||
|
let dispatcher_content = Self::generate_dispatcher(bid)?;
|
||||||
|
let dispatcher_path = options.output_dir.join(format!("{}_vm_dispatcher.rs", bid_name));
|
||||||
|
files.push(GeneratedFile::new(dispatcher_path, dispatcher_content));
|
||||||
|
|
||||||
|
// Generate README
|
||||||
|
let readme_content = Self::generate_readme(bid)?;
|
||||||
|
let readme_path = options.output_dir.join("README.md");
|
||||||
|
files.push(GeneratedFile::new(readme_path, readme_content));
|
||||||
|
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate Rust function table definition
|
||||||
|
fn generate_rust_function_table(bid: &BidDefinition) -> BidResult<String> {
|
||||||
|
let bid_name = bid.name();
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
content.push_str(&format!(
|
||||||
|
"// VM function table for {}\n",
|
||||||
|
bid_name
|
||||||
|
));
|
||||||
|
content.push_str("// Generated by Nyash BID code generator\n\n");
|
||||||
|
|
||||||
|
content.push_str("use crate::bid::{{BidHandle, BidResult, BidRuntimeError}};\n");
|
||||||
|
content.push_str("use crate::backend::vm::{{VM, ValueId}};\n");
|
||||||
|
content.push_str("use std::collections::HashMap;\n\n");
|
||||||
|
|
||||||
|
// Generate function type definitions
|
||||||
|
content.push_str(&format!("/// Function table for {} interface\n", bid_name));
|
||||||
|
content.push_str(&format!("pub struct {}VmTable {{\n", Self::pascal_case(&bid_name)));
|
||||||
|
content.push_str(" functions: HashMap<String, VmFunction>,\n");
|
||||||
|
content.push_str("}\n\n");
|
||||||
|
|
||||||
|
content.push_str("/// VM function pointer type\n");
|
||||||
|
content.push_str("type VmFunction = fn(&mut VM, &[ValueId]) -> BidResult<Option<ValueId>>;\n\n");
|
||||||
|
|
||||||
|
content.push_str(&format!("impl {}VmTable {{\n", Self::pascal_case(&bid_name)));
|
||||||
|
content.push_str(" /// Create new function table\n");
|
||||||
|
content.push_str(" pub fn new() -> Self {\n");
|
||||||
|
content.push_str(" let mut functions = HashMap::new();\n\n");
|
||||||
|
|
||||||
|
// Add function mappings
|
||||||
|
for interface in &bid.interfaces {
|
||||||
|
for method in &interface.methods {
|
||||||
|
let function_name = format!("{}.{}", interface.name, method.name);
|
||||||
|
let impl_name = format!("impl_{}", method.name.replace("-", "_"));
|
||||||
|
content.push_str(&format!(
|
||||||
|
" functions.insert(\"{}\".to_string(), Self::{});\n",
|
||||||
|
function_name, impl_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str("\n Self { functions }\n");
|
||||||
|
content.push_str(" }\n\n");
|
||||||
|
|
||||||
|
content.push_str(" /// Call a function by name\n");
|
||||||
|
content.push_str(" pub fn call(&self, name: &str, vm: &mut VM, args: &[ValueId]) -> BidResult<Option<ValueId>> {\n");
|
||||||
|
content.push_str(" if let Some(func) = self.functions.get(name) {\n");
|
||||||
|
content.push_str(" func(vm, args)\n");
|
||||||
|
content.push_str(" } else {\n");
|
||||||
|
content.push_str(" Err(crate::bid::BidError::invalid_method())\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str(" }\n\n");
|
||||||
|
|
||||||
|
// Generate function implementations
|
||||||
|
for interface in &bid.interfaces {
|
||||||
|
content.push_str(&format!(" // {} methods\n", interface.name));
|
||||||
|
|
||||||
|
for method in &interface.methods {
|
||||||
|
let impl_function = Self::generate_vm_function_impl(interface, method)?;
|
||||||
|
content.push_str(&impl_function);
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str("}\n\n");
|
||||||
|
|
||||||
|
// Generate integration helper
|
||||||
|
content.push_str("/// Integration with VM\n");
|
||||||
|
content.push_str("pub fn register_vm_functions(vm: &mut VM) {\n");
|
||||||
|
content.push_str(&format!(" let table = {}VmTable::new();\n", Self::pascal_case(&bid_name)));
|
||||||
|
content.push_str(" \n");
|
||||||
|
content.push_str(" // Register each function with the VM\n");
|
||||||
|
content.push_str(" // TODO: Integrate with VM's external function registry\n");
|
||||||
|
content.push_str(" // vm.register_external_function_table(table);\n");
|
||||||
|
content.push_str("}\n");
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate VM function implementation
|
||||||
|
fn generate_vm_function_impl(interface: &BidInterface, method: &BidMethod) -> BidResult<String> {
|
||||||
|
let impl_name = format!("impl_{}", method.name.replace("-", "_"));
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
content.push_str(&format!(" /// Implementation of {}.{}\n", interface.name, method.name));
|
||||||
|
content.push_str(&format!(" fn {}(vm: &mut VM, args: &[ValueId]) -> BidResult<Option<ValueId>> {{\n", impl_name));
|
||||||
|
|
||||||
|
// Parameter validation
|
||||||
|
content.push_str(&format!(" // Validate argument count\n"));
|
||||||
|
content.push_str(&format!(" if args.len() != {} {{\n", method.params.len()));
|
||||||
|
content.push_str(" return Err(crate::bid::BidError::invalid_args());\n");
|
||||||
|
content.push_str(" }\n\n");
|
||||||
|
|
||||||
|
// Extract parameters
|
||||||
|
for (i, param) in method.params.iter().enumerate() {
|
||||||
|
let param_name = param.get_name().unwrap_or_else(|| format!("param_{}", i));
|
||||||
|
content.push_str(&format!(" let {} = &args[{}]; // {}\n",
|
||||||
|
param_name, i, param.get_type()));
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str("\n // TODO: Implement actual method logic\n");
|
||||||
|
content.push_str(&format!(" println!(\"VM: Called {}.{}({{:?}})\", args);\n",
|
||||||
|
interface.name, method.name));
|
||||||
|
|
||||||
|
// Return value
|
||||||
|
if method.returns.is_void() {
|
||||||
|
content.push_str("\n Ok(None) // void return\n");
|
||||||
|
} else {
|
||||||
|
content.push_str(&format!("\n // TODO: Return proper {} value\n", method.returns.type_name()));
|
||||||
|
content.push_str(" let result_id = vm.allocate_value(crate::value::NyashValue::Integer(0));\n");
|
||||||
|
content.push_str(" Ok(Some(result_id))\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str(" }\n\n");
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate dispatcher implementation
|
||||||
|
fn generate_dispatcher(bid: &BidDefinition) -> BidResult<String> {
|
||||||
|
let bid_name = bid.name();
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
content.push_str(&format!(
|
||||||
|
"// VM dispatcher for {}\n",
|
||||||
|
bid_name
|
||||||
|
));
|
||||||
|
content.push_str("// Generated by Nyash BID code generator\n\n");
|
||||||
|
|
||||||
|
content.push_str("use crate::backend::vm::VM;\n");
|
||||||
|
content.push_str("use crate::mir::MirInstruction;\n");
|
||||||
|
content.push_str("use crate::bid::BidResult;\n\n");
|
||||||
|
|
||||||
|
content.push_str(&format!("/// Dispatcher for {} VM calls\n", bid_name));
|
||||||
|
content.push_str(&format!("pub struct {}VmDispatcher;\n\n", Self::pascal_case(&bid_name)));
|
||||||
|
|
||||||
|
content.push_str(&format!("impl {}VmDispatcher {{\n", Self::pascal_case(&bid_name)));
|
||||||
|
content.push_str(" /// Handle ExternCall MIR instruction\n");
|
||||||
|
content.push_str(" pub fn handle_extern_call(\n");
|
||||||
|
content.push_str(" vm: &mut VM,\n");
|
||||||
|
content.push_str(" interface: &str,\n");
|
||||||
|
content.push_str(" method: &str,\n");
|
||||||
|
content.push_str(" args: &[crate::backend::vm::ValueId],\n");
|
||||||
|
content.push_str(" dst: Option<crate::backend::vm::ValueId>\n");
|
||||||
|
content.push_str(" ) -> BidResult<()> {\n");
|
||||||
|
|
||||||
|
content.push_str(" let function_name = format!(\"{}.{}\", interface, method);\n");
|
||||||
|
content.push_str(" \n");
|
||||||
|
content.push_str(" match function_name.as_str() {\n");
|
||||||
|
|
||||||
|
// Generate match arms for each method
|
||||||
|
for interface in &bid.interfaces {
|
||||||
|
for method in &interface.methods {
|
||||||
|
let function_name = format!("{}.{}", interface.name, method.name);
|
||||||
|
content.push_str(&format!(" \"{}\" => {{\n", function_name));
|
||||||
|
content.push_str(&format!(" // TODO: Call actual {} implementation\n", method.name));
|
||||||
|
content.push_str(" println!(\"VM Dispatcher: {} called\");\n");
|
||||||
|
|
||||||
|
if !method.returns.is_void() {
|
||||||
|
content.push_str(" if let Some(dst_id) = dst {\n");
|
||||||
|
content.push_str(" // Set return value\n");
|
||||||
|
content.push_str(" vm.set_value(dst_id, crate::value::NyashValue::Integer(0));\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str(" Ok(())\n");
|
||||||
|
content.push_str(" },\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str(" _ => {\n");
|
||||||
|
content.push_str(" Err(crate::bid::BidError::invalid_method())\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str("}\n");
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate README
|
||||||
|
fn generate_readme(bid: &BidDefinition) -> BidResult<String> {
|
||||||
|
let bid_name = bid.name();
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
content.push_str(&format!("# {} - VM Integration\n\n", bid_name));
|
||||||
|
content.push_str("Generated by Nyash BID code generator for VM target.\n\n");
|
||||||
|
|
||||||
|
content.push_str("## Files\n\n");
|
||||||
|
content.push_str(&format!("- `{}_vm_table.rs` - VM function table definitions\n", bid_name));
|
||||||
|
content.push_str(&format!("- `{}_vm_dispatcher.rs` - VM dispatcher implementation\n", bid_name));
|
||||||
|
content.push_str("- `README.md` - This file\n\n");
|
||||||
|
|
||||||
|
content.push_str("## Integration\n\n");
|
||||||
|
content.push_str("1. Add the generated files to your VM backend:\n");
|
||||||
|
content.push_str(" ```rust\n");
|
||||||
|
content.push_str(&format!(" mod {}_vm_table;\n", bid_name));
|
||||||
|
content.push_str(&format!(" mod {}_vm_dispatcher;\n", bid_name));
|
||||||
|
content.push_str(" ```\n\n");
|
||||||
|
|
||||||
|
content.push_str("2. Register the function table:\n");
|
||||||
|
content.push_str(" ```rust\n");
|
||||||
|
content.push_str(&format!(" {}::register_vm_functions(&mut vm);\n", bid_name));
|
||||||
|
content.push_str(" ```\n\n");
|
||||||
|
|
||||||
|
content.push_str("3. Handle ExternCall instructions:\n");
|
||||||
|
content.push_str(" ```rust\n");
|
||||||
|
content.push_str(" match instruction {\n");
|
||||||
|
content.push_str(" MirInstruction::ExternCall { interface, method, args, dst } => {\n");
|
||||||
|
content.push_str(&format!(" {}VmDispatcher::handle_extern_call(\n", Self::pascal_case(&bid_name)));
|
||||||
|
content.push_str(" &mut vm, interface, method, args, *dst\n");
|
||||||
|
content.push_str(" )?\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str(" // ... other instructions\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str(" ```\n\n");
|
||||||
|
|
||||||
|
content.push_str("## Available Functions\n\n");
|
||||||
|
for interface in &bid.interfaces {
|
||||||
|
content.push_str(&format!("### {}\n\n", interface.name));
|
||||||
|
for method in &interface.methods {
|
||||||
|
content.push_str(&format!("- `{}(", method.name));
|
||||||
|
let param_strs: Vec<String> = method.params.iter().map(|p| {
|
||||||
|
format!("{}: {}",
|
||||||
|
p.get_name().unwrap_or_else(|| "param".to_string()),
|
||||||
|
p.get_type())
|
||||||
|
}).collect();
|
||||||
|
content.push_str(¶m_strs.join(", "));
|
||||||
|
content.push_str(&format!(")` → `{}`\n", method.returns.type_name()));
|
||||||
|
}
|
||||||
|
content.push_str("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert snake_case to PascalCase
|
||||||
|
fn pascal_case(s: &str) -> String {
|
||||||
|
s.split('_')
|
||||||
|
.map(|word| {
|
||||||
|
let mut chars = word.chars();
|
||||||
|
match chars.next() {
|
||||||
|
None => String::new(),
|
||||||
|
Some(first) => first.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pascal_case() {
|
||||||
|
assert_eq!(VmGenerator::pascal_case("console"), "Console");
|
||||||
|
assert_eq!(VmGenerator::pascal_case("file_box"), "FileBox");
|
||||||
|
assert_eq!(VmGenerator::pascal_case("my_long_name"), "MyLongName");
|
||||||
|
}
|
||||||
|
}
|
||||||
357
src/bid-codegen-from-copilot/codegen/targets/wasm.rs
Normal file
357
src/bid-codegen-from-copilot/codegen/targets/wasm.rs
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
/*!
|
||||||
|
* WASM Target Generator - Generate WebAssembly import declarations
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::bid::{BidDefinition, BidInterface, BidMethod, BidParameter, BidResult, BidError};
|
||||||
|
use crate::bid::codegen::{CodeGenOptions, GeneratedFile};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct WasmGenerator;
|
||||||
|
|
||||||
|
impl WasmGenerator {
|
||||||
|
/// Generate WASM import declarations and host implementation templates
|
||||||
|
pub fn generate(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
|
||||||
|
let bid_name = bid.name();
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
|
// Generate WAT import declarations
|
||||||
|
let wat_content = Self::generate_wat_imports(bid)?;
|
||||||
|
let wat_path = options.output_dir.join(format!("{}_imports.wat", bid_name));
|
||||||
|
files.push(GeneratedFile::new(wat_path, wat_content));
|
||||||
|
|
||||||
|
// Generate JavaScript host implementation template
|
||||||
|
let js_content = Self::generate_js_host_template(bid)?;
|
||||||
|
let js_path = options.output_dir.join(format!("{}_host.js", bid_name));
|
||||||
|
files.push(GeneratedFile::new(js_path, js_content));
|
||||||
|
|
||||||
|
// Generate README with usage instructions
|
||||||
|
let readme_content = Self::generate_readme(bid)?;
|
||||||
|
let readme_path = options.output_dir.join("README.md");
|
||||||
|
files.push(GeneratedFile::new(readme_path, readme_content));
|
||||||
|
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate WAT import declarations
|
||||||
|
fn generate_wat_imports(bid: &BidDefinition) -> BidResult<String> {
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
content.push_str(&format!(
|
||||||
|
";; WebAssembly import declarations for {}\n",
|
||||||
|
bid.name()
|
||||||
|
));
|
||||||
|
content.push_str(";; Generated by Nyash BID code generator\n\n");
|
||||||
|
|
||||||
|
content.push_str("(module\n");
|
||||||
|
|
||||||
|
for interface in &bid.interfaces {
|
||||||
|
content.push_str(&format!(" ;; Interface: {}\n", interface.name));
|
||||||
|
|
||||||
|
for method in &interface.methods {
|
||||||
|
let wasm_import = Self::generate_wasm_import(interface, method)?;
|
||||||
|
content.push_str(&format!(" {}\n", wasm_import));
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str(" ;; Your WASM functions go here\n");
|
||||||
|
content.push_str(" ;; Example:\n");
|
||||||
|
content.push_str(" ;; (func $main (export \"main\")\n");
|
||||||
|
content.push_str(" ;; (call $env.console.log (i32.const 0) (i32.const 13)) ;; \"Hello, World!\"\n");
|
||||||
|
content.push_str(" ;; )\n\n");
|
||||||
|
|
||||||
|
content.push_str(" ;; Memory for string data\n");
|
||||||
|
content.push_str(" (memory $memory 1)\n");
|
||||||
|
content.push_str(" (export \"memory\" (memory $memory))\n");
|
||||||
|
content.push_str(")\n");
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate single WASM import declaration
|
||||||
|
fn generate_wasm_import(interface: &BidInterface, method: &BidMethod) -> BidResult<String> {
|
||||||
|
let import_name = format!("{}.{}", interface.name, method.name);
|
||||||
|
let func_name = format!("${}", import_name);
|
||||||
|
|
||||||
|
// Convert parameters to WASM types
|
||||||
|
let mut param_types = Vec::new();
|
||||||
|
for param in &method.params {
|
||||||
|
let wasm_type = Self::bid_type_to_wasm_type(¶m.get_type())?;
|
||||||
|
param_types.push(wasm_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert return type
|
||||||
|
let return_type = if method.returns.is_void() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!(" (result {})", Self::bid_type_to_wasm_type(&method.returns.type_name())?)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build parameter list
|
||||||
|
let params = if param_types.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!(" (param {})", param_types.join(" "))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"(import \"env\" \"{}\" (func {}{}{}))",
|
||||||
|
import_name,
|
||||||
|
func_name,
|
||||||
|
params,
|
||||||
|
return_type
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert BID type to WASM type
|
||||||
|
fn bid_type_to_wasm_type(bid_type: &str) -> BidResult<String> {
|
||||||
|
match bid_type {
|
||||||
|
"bool" => Ok("i32".to_string()), // 0/1 in i32
|
||||||
|
"i32" => Ok("i32".to_string()),
|
||||||
|
"i64" => Ok("i64".to_string()),
|
||||||
|
"f32" => Ok("f32".to_string()),
|
||||||
|
"f64" => Ok("f64".to_string()),
|
||||||
|
"string" => Ok("i32 i32".to_string()), // ptr + len
|
||||||
|
"bytes" => Ok("i32 i32".to_string()), // ptr + len
|
||||||
|
"handle" => Ok("i64".to_string()), // Handle as u64
|
||||||
|
"void" => Ok("".to_string()),
|
||||||
|
_ => Err(BidError::CodeGenError(format!("Unsupported type for WASM: {}", bid_type))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate JavaScript host implementation template
|
||||||
|
fn generate_js_host_template(bid: &BidDefinition) -> BidResult<String> {
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
content.push_str(&format!(
|
||||||
|
"// JavaScript host implementation for {}\n",
|
||||||
|
bid.name()
|
||||||
|
));
|
||||||
|
content.push_str("// Generated by Nyash BID code generator\n\n");
|
||||||
|
|
||||||
|
content.push_str("class NyashWasmHost {\n");
|
||||||
|
content.push_str(" constructor() {\n");
|
||||||
|
content.push_str(" this.memory = null;\n");
|
||||||
|
content.push_str(" this.textDecoder = new TextDecoder();\n");
|
||||||
|
content.push_str(" this.textEncoder = new TextEncoder();\n");
|
||||||
|
content.push_str(" }\n\n");
|
||||||
|
|
||||||
|
content.push_str(" // Called when WASM instance is created\n");
|
||||||
|
content.push_str(" setMemory(memory) {\n");
|
||||||
|
content.push_str(" this.memory = memory;\n");
|
||||||
|
content.push_str(" }\n\n");
|
||||||
|
|
||||||
|
content.push_str(" // Helper: Read string from WASM memory\n");
|
||||||
|
content.push_str(" readString(ptr, len) {\n");
|
||||||
|
content.push_str(" const bytes = new Uint8Array(this.memory.buffer, ptr, len);\n");
|
||||||
|
content.push_str(" return this.textDecoder.decode(bytes);\n");
|
||||||
|
content.push_str(" }\n\n");
|
||||||
|
|
||||||
|
content.push_str(" // Import object for WASM instantiation\n");
|
||||||
|
content.push_str(" getImportObject() {\n");
|
||||||
|
content.push_str(" return {\n");
|
||||||
|
content.push_str(" env: {\n");
|
||||||
|
|
||||||
|
for interface in &bid.interfaces {
|
||||||
|
for method in &interface.methods {
|
||||||
|
let js_method = Self::generate_js_method(interface, method)?;
|
||||||
|
content.push_str(&format!(" {}\n", js_method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str(" };\n");
|
||||||
|
content.push_str(" }\n");
|
||||||
|
content.push_str("}\n\n");
|
||||||
|
|
||||||
|
content.push_str("// Usage example:\n");
|
||||||
|
content.push_str("// const host = new NyashWasmHost();\n");
|
||||||
|
content.push_str("// const importObject = host.getImportObject();\n");
|
||||||
|
content.push_str("// const wasmInstance = await WebAssembly.instantiateStreaming(\n");
|
||||||
|
content.push_str("// fetch('your_module.wasm'),\n");
|
||||||
|
content.push_str("// importObject\n");
|
||||||
|
content.push_str("// );\n");
|
||||||
|
content.push_str("// host.setMemory(wasmInstance.instance.exports.memory);\n");
|
||||||
|
content.push_str("// wasmInstance.instance.exports.main();\n\n");
|
||||||
|
|
||||||
|
content.push_str("module.exports = { NyashWasmHost };\n");
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate JavaScript method implementation
|
||||||
|
fn generate_js_method(interface: &BidInterface, method: &BidMethod) -> BidResult<String> {
|
||||||
|
let import_name = format!("{}.{}", interface.name, method.name);
|
||||||
|
|
||||||
|
// Build parameter list with type comments
|
||||||
|
let mut params = Vec::new();
|
||||||
|
for param in &method.params {
|
||||||
|
match param.get_type().as_str() {
|
||||||
|
"string" => params.extend(vec!["ptr".to_string(), "len".to_string()]),
|
||||||
|
"bytes" => params.extend(vec!["ptr".to_string(), "len".to_string()]),
|
||||||
|
typ if typ.starts_with("i") || typ.starts_with("f") || typ == "bool" => {
|
||||||
|
params.push(param.get_name().unwrap_or_else(|| "value".to_string()));
|
||||||
|
},
|
||||||
|
"handle" => params.push("handle".to_string()),
|
||||||
|
_ => params.push("param".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let param_list = params.join(", ");
|
||||||
|
|
||||||
|
// Generate method body
|
||||||
|
let mut body = String::new();
|
||||||
|
|
||||||
|
// Handle string parameters
|
||||||
|
let mut string_conversions = Vec::new();
|
||||||
|
let mut converted_params = Vec::new();
|
||||||
|
|
||||||
|
for param in &method.params {
|
||||||
|
let param_name = param.get_name().unwrap_or_else(|| "value".to_string());
|
||||||
|
match param.get_type().as_str() {
|
||||||
|
"string" => {
|
||||||
|
string_conversions.push(format!(
|
||||||
|
" const {} = this.readString(ptr, len);",
|
||||||
|
param_name
|
||||||
|
));
|
||||||
|
converted_params.push(param_name);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
converted_params.push(param_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !string_conversions.is_empty() {
|
||||||
|
body.push_str(&string_conversions.join("\n"));
|
||||||
|
body.push_str("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate method call
|
||||||
|
body.push_str(&format!(
|
||||||
|
" // TODO: Implement {} method\n",
|
||||||
|
method.name
|
||||||
|
));
|
||||||
|
body.push_str(&format!(
|
||||||
|
" console.log('Called {}({})');",
|
||||||
|
method.name,
|
||||||
|
converted_params.join(", ")
|
||||||
|
));
|
||||||
|
|
||||||
|
if !method.returns.is_void() {
|
||||||
|
body.push_str("\n // TODO: Return appropriate value");
|
||||||
|
body.push_str(&format!(
|
||||||
|
"\n return 0; // Placeholder for {} return",
|
||||||
|
method.returns.type_name()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"\"{}\": ({}) => {{\n{}\n }},",
|
||||||
|
import_name,
|
||||||
|
param_list,
|
||||||
|
body
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate README with usage instructions
|
||||||
|
fn generate_readme(bid: &BidDefinition) -> BidResult<String> {
|
||||||
|
let bid_name = bid.name();
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
content.push_str(&format!("# {} - WASM Integration\n\n", bid_name));
|
||||||
|
content.push_str("Generated by Nyash BID code generator for WebAssembly target.\n\n");
|
||||||
|
|
||||||
|
content.push_str("## Files\n\n");
|
||||||
|
content.push_str(&format!("- `{}_imports.wat` - WebAssembly import declarations\n", bid_name));
|
||||||
|
content.push_str(&format!("- `{}_host.js` - JavaScript host implementation template\n", bid_name));
|
||||||
|
content.push_str("- `README.md` - This file\n\n");
|
||||||
|
|
||||||
|
content.push_str("## Usage\n\n");
|
||||||
|
content.push_str("1. Include the import declarations in your WASM module:\n");
|
||||||
|
content.push_str(" ```wat\n");
|
||||||
|
content.push_str(&format!(" ;; Copy imports from {}_imports.wat\n", bid_name));
|
||||||
|
content.push_str(" ```\n\n");
|
||||||
|
|
||||||
|
content.push_str("2. Implement the host functions in JavaScript:\n");
|
||||||
|
content.push_str(" ```javascript\n");
|
||||||
|
content.push_str(&format!(" const {{ NyashWasmHost }} = require('./{}_host.js');\n", bid_name));
|
||||||
|
content.push_str(" const host = new NyashWasmHost();\n");
|
||||||
|
content.push_str(" \n");
|
||||||
|
content.push_str(" // Customize the host implementation\n");
|
||||||
|
content.push_str(" const importObject = host.getImportObject();\n");
|
||||||
|
content.push_str(" ```\n\n");
|
||||||
|
|
||||||
|
content.push_str("3. Load and run your WASM module:\n");
|
||||||
|
content.push_str(" ```javascript\n");
|
||||||
|
content.push_str(" const wasmInstance = await WebAssembly.instantiateStreaming(\n");
|
||||||
|
content.push_str(" fetch('your_module.wasm'),\n");
|
||||||
|
content.push_str(" importObject\n");
|
||||||
|
content.push_str(" );\n");
|
||||||
|
content.push_str(" host.setMemory(wasmInstance.instance.exports.memory);\n");
|
||||||
|
content.push_str(" wasmInstance.instance.exports.main();\n");
|
||||||
|
content.push_str(" ```\n\n");
|
||||||
|
|
||||||
|
content.push_str("## Available Functions\n\n");
|
||||||
|
for interface in &bid.interfaces {
|
||||||
|
content.push_str(&format!("### {}\n\n", interface.name));
|
||||||
|
for method in &interface.methods {
|
||||||
|
content.push_str(&format!("- `{}(", method.name));
|
||||||
|
let param_strs: Vec<String> = method.params.iter().map(|p| {
|
||||||
|
format!("{}: {}",
|
||||||
|
p.get_name().unwrap_or_else(|| "param".to_string()),
|
||||||
|
p.get_type())
|
||||||
|
}).collect();
|
||||||
|
content.push_str(¶m_strs.join(", "));
|
||||||
|
content.push_str(&format!(")` → `{}`\n", method.returns.type_name()));
|
||||||
|
}
|
||||||
|
content.push_str("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::bid::{BidInterface, BidMethod, BidParameter, BidTypeRef};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bid_type_to_wasm_type() {
|
||||||
|
assert_eq!(WasmGenerator::bid_type_to_wasm_type("i32").unwrap(), "i32");
|
||||||
|
assert_eq!(WasmGenerator::bid_type_to_wasm_type("string").unwrap(), "i32 i32");
|
||||||
|
assert_eq!(WasmGenerator::bid_type_to_wasm_type("handle").unwrap(), "i64");
|
||||||
|
assert_eq!(WasmGenerator::bid_type_to_wasm_type("void").unwrap(), "");
|
||||||
|
|
||||||
|
assert!(WasmGenerator::bid_type_to_wasm_type("unknown").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_wasm_import() {
|
||||||
|
let interface = BidInterface {
|
||||||
|
name: "env.console".to_string(),
|
||||||
|
box_type: Some("Console".to_string()),
|
||||||
|
methods: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut param_map = HashMap::new();
|
||||||
|
param_map.insert("string".to_string(), "msg".to_string());
|
||||||
|
|
||||||
|
let method = BidMethod {
|
||||||
|
name: "log".to_string(),
|
||||||
|
params: vec![BidParameter {
|
||||||
|
param_type: BidTypeRef::Named(param_map),
|
||||||
|
}],
|
||||||
|
returns: BidTypeRef::Simple("void".to_string()),
|
||||||
|
effect: Some("io".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let import = WasmGenerator::generate_wasm_import(&interface, &method).unwrap();
|
||||||
|
assert!(import.contains("env.console.log"));
|
||||||
|
assert!(import.contains("$env.console.log"));
|
||||||
|
assert!(import.contains("param i32 i32")); // string = ptr + len
|
||||||
|
}
|
||||||
|
}
|
||||||
287
src/bid-codegen-from-copilot/schema.rs
Normal file
287
src/bid-codegen-from-copilot/schema.rs
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
/*!
|
||||||
|
* BID Schema Parsing - YAML/JSON schema for Box Interface Definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use crate::bid::BidError;
|
||||||
|
|
||||||
|
/// BID Definition - Root structure for BID files
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct BidDefinition {
|
||||||
|
pub version: u32,
|
||||||
|
pub interfaces: Vec<BidInterface>,
|
||||||
|
pub metadata: Option<BidMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata for a BID definition
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct BidMetadata {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub author: Option<String>,
|
||||||
|
pub version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface definition
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct BidInterface {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "box")]
|
||||||
|
pub box_type: Option<String>, // Box type name
|
||||||
|
pub methods: Vec<BidMethod>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Method definition
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct BidMethod {
|
||||||
|
pub name: String,
|
||||||
|
pub params: Vec<BidParameter>,
|
||||||
|
pub returns: BidTypeRef,
|
||||||
|
pub effect: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameter definition
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct BidParameter {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub param_type: BidTypeRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type reference in BID files
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum BidTypeRef {
|
||||||
|
/// Simple type: { string: "name" }
|
||||||
|
Named(HashMap<String, String>),
|
||||||
|
/// Just a type name: "void"
|
||||||
|
Simple(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BidDefinition {
|
||||||
|
/// Load BID definition from YAML file
|
||||||
|
pub fn load_from_file(path: &Path) -> Result<Self, BidError> {
|
||||||
|
let content = std::fs::read_to_string(path)
|
||||||
|
.map_err(|e| BidError::IoError(e.to_string()))?;
|
||||||
|
|
||||||
|
let bid: BidDefinition = serde_yaml::from_str(&content)
|
||||||
|
.map_err(|e| BidError::ParseError(format!("YAML parse error: {}", e)))?;
|
||||||
|
|
||||||
|
// Validate the definition
|
||||||
|
bid.validate()?;
|
||||||
|
|
||||||
|
Ok(bid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate the BID definition
|
||||||
|
pub fn validate(&self) -> Result<(), BidError> {
|
||||||
|
// Check version
|
||||||
|
if self.version > 1 {
|
||||||
|
return Err(BidError::UnsupportedVersion(self.version));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate interface names
|
||||||
|
let mut interface_names = std::collections::HashSet::new();
|
||||||
|
for interface in &self.interfaces {
|
||||||
|
if interface_names.contains(&interface.name) {
|
||||||
|
return Err(BidError::DuplicateInterface(interface.name.clone()));
|
||||||
|
}
|
||||||
|
interface_names.insert(interface.name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each interface
|
||||||
|
for interface in &self.interfaces {
|
||||||
|
interface.validate()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get interface by name
|
||||||
|
pub fn get_interface(&self, name: &str) -> Option<&BidInterface> {
|
||||||
|
self.interfaces.iter().find(|i| i.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the definition name (from metadata or derived from first interface)
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
if let Some(ref metadata) = self.metadata {
|
||||||
|
if let Some(ref name) = metadata.name {
|
||||||
|
return name.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive from first interface name
|
||||||
|
if let Some(interface) = self.interfaces.first() {
|
||||||
|
// Extract the last part of the interface name
|
||||||
|
// e.g., "env.console" -> "console"
|
||||||
|
interface.name.split('.').last().unwrap_or(&interface.name).to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BidInterface {
|
||||||
|
/// Validate the interface
|
||||||
|
pub fn validate(&self) -> Result<(), BidError> {
|
||||||
|
// Check for duplicate method names
|
||||||
|
let mut method_names = std::collections::HashSet::new();
|
||||||
|
for method in &self.methods {
|
||||||
|
if method_names.contains(&method.name) {
|
||||||
|
return Err(BidError::DuplicateMethod(method.name.clone()));
|
||||||
|
}
|
||||||
|
method_names.insert(method.name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each method
|
||||||
|
for method in &self.methods {
|
||||||
|
method.validate()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BidMethod {
|
||||||
|
/// Validate the method
|
||||||
|
pub fn validate(&self) -> Result<(), BidError> {
|
||||||
|
// Check for duplicate parameter names
|
||||||
|
let mut param_names = std::collections::HashSet::new();
|
||||||
|
for (i, param) in self.params.iter().enumerate() {
|
||||||
|
let param_name = param.get_name().unwrap_or_else(|| format!("param_{}", i));
|
||||||
|
if param_names.contains(¶m_name) {
|
||||||
|
return Err(BidError::DuplicateParameter(param_name));
|
||||||
|
}
|
||||||
|
param_names.insert(param_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BidParameter {
|
||||||
|
/// Get the parameter name (from the type definition)
|
||||||
|
pub fn get_name(&self) -> Option<String> {
|
||||||
|
match &self.param_type {
|
||||||
|
BidTypeRef::Named(map) => {
|
||||||
|
// Return the first value (parameter name)
|
||||||
|
map.values().next().cloned()
|
||||||
|
},
|
||||||
|
BidTypeRef::Simple(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the parameter type name
|
||||||
|
pub fn get_type(&self) -> String {
|
||||||
|
match &self.param_type {
|
||||||
|
BidTypeRef::Named(map) => {
|
||||||
|
// Return the first key (type name)
|
||||||
|
map.keys().next().cloned().unwrap_or_else(|| "unknown".to_string())
|
||||||
|
},
|
||||||
|
BidTypeRef::Simple(type_name) => type_name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BidTypeRef {
|
||||||
|
/// Get the type name
|
||||||
|
pub fn type_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
BidTypeRef::Named(map) => {
|
||||||
|
map.keys().next().cloned().unwrap_or_else(|| "unknown".to_string())
|
||||||
|
},
|
||||||
|
BidTypeRef::Simple(type_name) => type_name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this is a void type
|
||||||
|
pub fn is_void(&self) -> bool {
|
||||||
|
self.type_name() == "void"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::io::Write;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_console_bid() {
|
||||||
|
let yaml_content = r#"
|
||||||
|
version: 0
|
||||||
|
interfaces:
|
||||||
|
- name: env.console
|
||||||
|
box: Console
|
||||||
|
methods:
|
||||||
|
- name: log
|
||||||
|
params:
|
||||||
|
- { string: msg }
|
||||||
|
returns: void
|
||||||
|
effect: io
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut temp_file = NamedTempFile::new().unwrap();
|
||||||
|
writeln!(temp_file, "{}", yaml_content).unwrap();
|
||||||
|
|
||||||
|
let bid = BidDefinition::load_from_file(temp_file.path()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bid.version, 0);
|
||||||
|
assert_eq!(bid.interfaces.len(), 1);
|
||||||
|
|
||||||
|
let interface = &bid.interfaces[0];
|
||||||
|
assert_eq!(interface.name, "env.console");
|
||||||
|
assert_eq!(interface.box_type, Some("Console".to_string()));
|
||||||
|
assert_eq!(interface.methods.len(), 1);
|
||||||
|
|
||||||
|
let method = &interface.methods[0];
|
||||||
|
assert_eq!(method.name, "log");
|
||||||
|
assert_eq!(method.params.len(), 1);
|
||||||
|
assert!(method.returns.is_void());
|
||||||
|
|
||||||
|
let param = &method.params[0];
|
||||||
|
assert_eq!(param.get_type(), "string");
|
||||||
|
assert_eq!(param.get_name(), Some("msg".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bid_definition_name() {
|
||||||
|
let yaml_content = r#"
|
||||||
|
version: 0
|
||||||
|
interfaces:
|
||||||
|
- name: env.console
|
||||||
|
methods: []
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut temp_file = NamedTempFile::new().unwrap();
|
||||||
|
writeln!(temp_file, "{}", yaml_content).unwrap();
|
||||||
|
|
||||||
|
let bid = BidDefinition::load_from_file(temp_file.path()).unwrap();
|
||||||
|
assert_eq!(bid.name(), "console");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_duplicate_interface_validation() {
|
||||||
|
let yaml_content = r#"
|
||||||
|
version: 0
|
||||||
|
interfaces:
|
||||||
|
- name: env.console
|
||||||
|
methods: []
|
||||||
|
- name: env.console
|
||||||
|
methods: []
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut temp_file = NamedTempFile::new().unwrap();
|
||||||
|
writeln!(temp_file, "{}", yaml_content).unwrap();
|
||||||
|
|
||||||
|
let result = BidDefinition::load_from_file(temp_file.path());
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
if let Err(BidError::DuplicateInterface(name)) = result {
|
||||||
|
assert_eq!(name, "env.console");
|
||||||
|
} else {
|
||||||
|
panic!("Expected DuplicateInterface error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user