From 5d1a140919984f1c48b612d7ba9b4b4bef693a49 Mon Sep 17 00:00:00 2001 From: Moe Charm Date: Mon, 18 Aug 2025 21:10:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=209.75g-0=20BID-FFI=E5=AE=8C?= =?UTF-8?q?=E4=BA=86=20+=20Phase=209.8=E6=BA=96=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- docs/CURRENT_TASK.md | 72 ++-- src/bid-codegen-from-copilot/README.md | 39 ++ .../codegen/generator.rs | 239 ++++++++++++ src/bid-codegen-from-copilot/codegen/mod.rs | 9 + .../codegen/targets/llvm.rs | 17 + .../codegen/targets/mod.rs | 9 + .../codegen/targets/python.rs | 17 + .../codegen/targets/typescript.rs | 17 + .../codegen/targets/vm.rs | 296 +++++++++++++++ .../codegen/targets/wasm.rs | 357 ++++++++++++++++++ src/bid-codegen-from-copilot/schema.rs | 287 ++++++++++++++ 11 files changed, 1332 insertions(+), 27 deletions(-) create mode 100644 src/bid-codegen-from-copilot/README.md create mode 100644 src/bid-codegen-from-copilot/codegen/generator.rs create mode 100644 src/bid-codegen-from-copilot/codegen/mod.rs create mode 100644 src/bid-codegen-from-copilot/codegen/targets/llvm.rs create mode 100644 src/bid-codegen-from-copilot/codegen/targets/mod.rs create mode 100644 src/bid-codegen-from-copilot/codegen/targets/python.rs create mode 100644 src/bid-codegen-from-copilot/codegen/targets/typescript.rs create mode 100644 src/bid-codegen-from-copilot/codegen/targets/vm.rs create mode 100644 src/bid-codegen-from-copilot/codegen/targets/wasm.rs create mode 100644 src/bid-codegen-from-copilot/schema.rs diff --git a/docs/CURRENT_TASK.md b/docs/CURRENT_TASK.md index 18cd9e02..365332b3 100644 --- a/docs/CURRENT_TASK.md +++ b/docs/CURRENT_TASK.md @@ -111,41 +111,59 @@ read = { - [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) - 全体開発計画 -## 📋 **今日の重要決定事項(2025年8月21日)** +## 🔧 **現在進行中:ビルトインBoxプラグイン化プロジェクト**(2025-08-18開始) -### **1. Phase 9.8戦略転換** -- ✅ nyash.tomlに既存の型情報が豊富に存在することを発見 -- ✅ BID用の新規YAMLファイル不要と判断 -- ✅ nyash.toml拡張によるBID機能実装を決定 -- ✅ GitHub Issue #116作成(Phase 9.8実装計画) +### **目的** +- **ビルド時間短縮**: 3分 → 30秒以下 +- **バイナリサイズ削減**: 最小構成で500KB以下 +- **保守性向上**: 各プラグイン独立開発 -### **2. 技術的洞察:JIT vs AOT** -- **MIRの存在により難易度が同等に** -- VM最適化でネイティブ速度に迫る可能性 -- 将来: VM JIT化も選択肢に - -### **3. 開発優先順位の更新** +### **対象Box(13種類)** ``` -1. ✅ Phase 8.6 VM性能改善(完了!50.94倍達成) -2. ✅ Phase 9.78 LLVM PoC基盤(MIR修正完了) -3. 🔄 Phase 9.8 BIDレジストリ(nyash.toml拡張方式) -4. → Phase 9.9 権限モデル(FileBoxで実証) -5. → Phase 10 LLVM本格実装(将来検討) +Phase 1: ネットワーク系(HttpBox系、SocketBox) +Phase 2: GUI系(EguiBox、Canvas系、Web系) +Phase 3: 特殊用途系(AudioBox、QRBox、StreamBox等) ``` -### **4. Windows戦略の具体化** -- Bitcodeキャッシュで1回生成→全OS対応 -- mingw-gnuで即座にWindows対応可能 -- APEは小規模ツール専用として位置づけ +### **進捗状況** +- ✅ プラグイン移行依頼書作成(`docs/plugin-migration-request.md`) +- ✅ CopilotのBID変換コード抽出(`src/bid-converter-copilot/`) +- ✅ 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日 -**次回レビュー**: Phase 9.8実装開始時 -**開発状況**: Phase 9.75g-0完了 → Phase 8.6完了 → Phase 9.78基盤完了 → Phase 9.8開始 +**最終更新**: 2025年8月18日 +**次回レビュー**: HttpBoxプラグイン完成時 +**開発状況**: ビルトインBoxプラグイン化進行中 ### 🎯 **次のアクション** -1. nyash.toml拡張仕様の設計 -2. VMバックエンドでのFileBox統合テスト準備 -3. 権限モデル(Phase 9.9)の実装計画 +1. HttpBoxプラグイン化の完成待ち(Copilot作業中) +2. plugin-testerでの動作確認 +3. 次のプラグイン化対象(EguiBox等)の準備 diff --git a/src/bid-codegen-from-copilot/README.md b/src/bid-codegen-from-copilot/README.md new file mode 100644 index 00000000..e96850ba --- /dev/null +++ b/src/bid-codegen-from-copilot/README.md @@ -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への大幅変更は含まれていない(別フォルダ保存) +- 必要に応じて段階的に統合可能 \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/generator.rs b/src/bid-codegen-from-copilot/codegen/generator.rs new file mode 100644 index 00000000..71f13f9b --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/generator.rs @@ -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 { + 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, + 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 { + 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> { + use super::targets::wasm::WasmGenerator; + WasmGenerator::generate(bid, options) + } + + /// Generate VM function table definitions + fn generate_vm(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult> { + use super::targets::vm::VmGenerator; + VmGenerator::generate(bid, options) + } + + /// Generate LLVM declare statements + fn generate_llvm(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult> { + use super::targets::llvm::LlvmGenerator; + LlvmGenerator::generate(bid, options) + } + + /// Generate TypeScript wrapper + fn generate_typescript(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult> { + use super::targets::typescript::TypeScriptGenerator; + TypeScriptGenerator::generate(bid, options) + } + + /// Generate Python wrapper + fn generate_python(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult> { + 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"); + } +} \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/mod.rs b/src/bid-codegen-from-copilot/codegen/mod.rs new file mode 100644 index 00000000..4cbc55ce --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/mod.rs @@ -0,0 +1,9 @@ +/*! + * Code Generation Module - Generate code from BID definitions + */ + +pub mod generator; +pub mod targets; + +pub use generator::*; +pub use targets::*; \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/targets/llvm.rs b/src/bid-codegen-from-copilot/codegen/targets/llvm.rs new file mode 100644 index 00000000..2870dbcb --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/targets/llvm.rs @@ -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> { + // TODO: Implement LLVM code generation + println!("🚧 LLVM code generation not yet implemented for {}", bid.name()); + Ok(vec![]) + } +} \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/targets/mod.rs b/src/bid-codegen-from-copilot/codegen/targets/mod.rs new file mode 100644 index 00000000..ffaec8fb --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/targets/mod.rs @@ -0,0 +1,9 @@ +/*! + * Target-specific code generators + */ + +pub mod wasm; +pub mod vm; +pub mod llvm; +pub mod typescript; +pub mod python; \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/targets/python.rs b/src/bid-codegen-from-copilot/codegen/targets/python.rs new file mode 100644 index 00000000..452960ac --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/targets/python.rs @@ -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> { + // TODO: Implement Python code generation + println!("🚧 Python code generation not yet implemented for {}", bid.name()); + Ok(vec![]) + } +} \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/targets/typescript.rs b/src/bid-codegen-from-copilot/codegen/targets/typescript.rs new file mode 100644 index 00000000..6da985e0 --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/targets/typescript.rs @@ -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> { + // TODO: Implement TypeScript code generation + println!("🚧 TypeScript code generation not yet implemented for {}", bid.name()); + Ok(vec![]) + } +} \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/targets/vm.rs b/src/bid-codegen-from-copilot/codegen/targets/vm.rs new file mode 100644 index 00000000..68ebec29 --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/targets/vm.rs @@ -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> { + 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 { + 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,\n"); + content.push_str("}\n\n"); + + content.push_str("/// VM function pointer type\n"); + content.push_str("type VmFunction = fn(&mut VM, &[ValueId]) -> BidResult>;\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> {\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 { + 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> {{\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 { + 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\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 { + 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 = 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::() + &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"); + } +} \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/codegen/targets/wasm.rs b/src/bid-codegen-from-copilot/codegen/targets/wasm.rs new file mode 100644 index 00000000..81dbc823 --- /dev/null +++ b/src/bid-codegen-from-copilot/codegen/targets/wasm.rs @@ -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> { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 = 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 + } +} \ No newline at end of file diff --git a/src/bid-codegen-from-copilot/schema.rs b/src/bid-codegen-from-copilot/schema.rs new file mode 100644 index 00000000..caee1f27 --- /dev/null +++ b/src/bid-codegen-from-copilot/schema.rs @@ -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, + pub metadata: Option, +} + +/// Metadata for a BID definition +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct BidMetadata { + pub name: Option, + pub description: Option, + pub author: Option, + pub version: Option, +} + +/// Interface definition +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct BidInterface { + pub name: String, + #[serde(rename = "box")] + pub box_type: Option, // Box type name + pub methods: Vec, +} + +/// Method definition +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct BidMethod { + pub name: String, + pub params: Vec, + pub returns: BidTypeRef, + pub effect: Option, +} + +/// 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), + /// Just a type name: "void" + Simple(String), +} + +impl BidDefinition { + /// Load BID definition from YAML file + pub fn load_from_file(path: &Path) -> Result { + 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 { + 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"); + } + } +} \ No newline at end of file