feat: Extract BID converter from Copilot and prepare plugin migration

- Extract Copilot's BID converter code to src/bid-converter-copilot/ for future use
- Create comprehensive plugin migration request document for Copilot
- Target 13 built-in boxes for plugin conversion (HTTP, GUI, Audio, etc.)
- Preserve existing nyash.toml-based plugin system
- Reorganize docs/説明書/reference/ structure for better organization
This commit is contained in:
Moe Charm
2025-08-18 20:40:19 +09:00
parent d788bcbf79
commit 012fc1930f
48 changed files with 3104 additions and 2469 deletions

View File

@ -0,0 +1,29 @@
# BID Converter from Copilot
このフォルダには、Copilotさんが実装してくれたBID (Box Interface Definition) の変換部分を保存しています。
## 📦 含まれるファイル
- **tlv.rs**: TLV (Type-Length-Value) エンコード/デコード実装
- **types.rs**: BID型定義NyashValue変換等
- **error.rs**: BIDエラー型定義
## 🎯 用途
将来的にnyash2.tomlを実装する際に、以下の用途で活用予定
1. **型変換**: Nyash型 ↔ BID型の相互変換
2. **シリアライズ**: プラグイン通信用のデータ変換
3. **エラーハンドリング**: 統一的なエラー処理
## 💡 なぜ保存?
- CopilotさんのTLV実装は汎用的で再利用価値が高い
- 現在のnyash.tomlベースの実装をシンプルに保ちつつ、将来の拡張に備える
- プラグイン間通信やネットワーク通信でも活用可能
## 📝 メモ
- 現在は使用していない既存のnyash.tomlベースが動作中
- Phase 9.8以降で活用予定
- 他言語プラグイン対応時には必須になる可能性

View File

@ -0,0 +1,80 @@
/// BID-1 Standard Error Codes
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BidError {
/// Operation successful
Success = 0,
/// Buffer too small (need to call again with larger buffer)
ShortBuffer = -1,
/// Invalid type ID
InvalidType = -2,
/// Invalid method ID
InvalidMethod = -3,
/// Invalid arguments
InvalidArgs = -4,
/// Plugin internal error
PluginError = -5,
/// Memory allocation failed
OutOfMemory = -6,
/// UTF-8 encoding error
InvalidUtf8 = -7,
/// Handle not found
InvalidHandle = -8,
/// Version mismatch
VersionMismatch = -9,
}
impl BidError {
/// Convert from raw i32
pub fn from_raw(code: i32) -> Self {
match code {
0 => BidError::Success,
-1 => BidError::ShortBuffer,
-2 => BidError::InvalidType,
-3 => BidError::InvalidMethod,
-4 => BidError::InvalidArgs,
-5 => BidError::PluginError,
-6 => BidError::OutOfMemory,
-7 => BidError::InvalidUtf8,
-8 => BidError::InvalidHandle,
-9 => BidError::VersionMismatch,
_ => BidError::PluginError, // Unknown errors map to plugin error
}
}
/// Get human-readable error message
pub fn message(&self) -> &'static str {
match self {
BidError::Success => "Operation successful",
BidError::ShortBuffer => "Buffer too small, call again with larger buffer",
BidError::InvalidType => "Invalid type ID",
BidError::InvalidMethod => "Invalid method ID",
BidError::InvalidArgs => "Invalid arguments",
BidError::PluginError => "Plugin internal error",
BidError::OutOfMemory => "Memory allocation failed",
BidError::InvalidUtf8 => "Invalid UTF-8 encoding",
BidError::InvalidHandle => "Handle not found",
BidError::VersionMismatch => "BID version mismatch",
}
}
}
impl std::fmt::Display for BidError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} (code: {})", self.message(), *self as i32)
}
}
impl std::error::Error for BidError {}
/// Result type for BID operations
pub type BidResult<T> = Result<T, BidError>;

View File

@ -0,0 +1,13 @@
//! BID Converter Module (from Copilot)
//!
//! 将来的にnyash2.tomlで使用予定の変換ロジック
//! 現在は参照用に保存
pub mod tlv;
pub mod types;
pub mod error;
// 将来的に必要になったらアンコメント
// pub use tlv::*;
// pub use types::*;
// pub use error::*;

View File

@ -0,0 +1,324 @@
use super::{BidError, BidResult, BidHandle, BidTag, BID_VERSION};
use std::mem;
/// BID-1 TLV Header
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BidTlvHeader {
pub version: u16, // BID version (1)
pub argc: u16, // Argument count
}
/// TLV Entry structure
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct TlvEntry {
pub tag: u8, // Type tag
pub reserved: u8, // Reserved for future use (0)
pub size: u16, // Payload size
// Payload follows immediately after
}
/// TLV encoder for BID-1 format
pub struct TlvEncoder {
buffer: Vec<u8>,
entry_count: u16,
}
impl TlvEncoder {
/// Create a new TLV encoder
pub fn new() -> Self {
let mut encoder = Self {
buffer: Vec::with_capacity(256),
entry_count: 0,
};
// Reserve space for header
encoder.buffer.extend_from_slice(&[0; mem::size_of::<BidTlvHeader>()]);
encoder
}
/// Encode a boolean value
pub fn encode_bool(&mut self, value: bool) -> BidResult<()> {
self.encode_entry(BidTag::Bool, &[if value { 1 } else { 0 }])
}
/// Encode a 32-bit integer
pub fn encode_i32(&mut self, value: i32) -> BidResult<()> {
self.encode_entry(BidTag::I32, &value.to_le_bytes())
}
/// Encode a 64-bit integer
pub fn encode_i64(&mut self, value: i64) -> BidResult<()> {
self.encode_entry(BidTag::I64, &value.to_le_bytes())
}
/// Encode a 32-bit float
pub fn encode_f32(&mut self, value: f32) -> BidResult<()> {
self.encode_entry(BidTag::F32, &value.to_le_bytes())
}
/// Encode a 64-bit float
pub fn encode_f64(&mut self, value: f64) -> BidResult<()> {
self.encode_entry(BidTag::F64, &value.to_le_bytes())
}
/// Encode a string
pub fn encode_string(&mut self, value: &str) -> BidResult<()> {
let bytes = value.as_bytes();
if bytes.len() > u16::MAX as usize {
return Err(BidError::InvalidArgs);
}
self.encode_entry(BidTag::String, bytes)
}
/// Encode binary data
pub fn encode_bytes(&mut self, value: &[u8]) -> BidResult<()> {
if value.len() > u16::MAX as usize {
return Err(BidError::InvalidArgs);
}
self.encode_entry(BidTag::Bytes, value)
}
/// Encode a handle
pub fn encode_handle(&mut self, handle: BidHandle) -> BidResult<()> {
self.encode_entry(BidTag::Handle, &handle.to_u64().to_le_bytes())
}
/// Encode void (no payload)
pub fn encode_void(&mut self) -> BidResult<()> {
self.encode_entry(BidTag::Void, &[])
}
/// Internal: encode a TLV entry
fn encode_entry(&mut self, tag: BidTag, payload: &[u8]) -> BidResult<()> {
let entry = TlvEntry {
tag: tag as u8,
reserved: 0,
size: payload.len() as u16,
};
// Write entry header
self.buffer.push(entry.tag);
self.buffer.push(entry.reserved);
self.buffer.extend_from_slice(&entry.size.to_le_bytes());
// Write payload
self.buffer.extend_from_slice(payload);
self.entry_count += 1;
Ok(())
}
/// Finalize the encoding and return the buffer
pub fn finish(mut self) -> Vec<u8> {
// Update header
let header = BidTlvHeader {
version: BID_VERSION,
argc: self.entry_count,
};
// Write header at the beginning
self.buffer[0..2].copy_from_slice(&header.version.to_le_bytes());
self.buffer[2..4].copy_from_slice(&header.argc.to_le_bytes());
self.buffer
}
}
/// TLV decoder for BID-1 format
pub struct TlvDecoder<'a> {
data: &'a [u8],
position: usize,
header: BidTlvHeader,
}
impl<'a> TlvDecoder<'a> {
/// Create a new TLV decoder
pub fn new(data: &'a [u8]) -> BidResult<Self> {
if data.len() < mem::size_of::<BidTlvHeader>() {
return Err(BidError::InvalidArgs);
}
// Read header safely
let version = u16::from_le_bytes([data[0], data[1]]);
let argc = u16::from_le_bytes([data[2], data[3]]);
let header = BidTlvHeader { version, argc };
if header.version != BID_VERSION {
return Err(BidError::VersionMismatch);
}
Ok(Self {
data,
position: mem::size_of::<BidTlvHeader>(),
header,
})
}
/// Get the argument count
pub fn arg_count(&self) -> u16 {
self.header.argc
}
/// Decode the next entry
pub fn decode_next(&mut self) -> BidResult<Option<(BidTag, &'a [u8])>> {
if self.position >= self.data.len() {
return Ok(None);
}
// Read entry header
if self.position + mem::size_of::<TlvEntry>() > self.data.len() {
return Err(BidError::InvalidArgs);
}
// Read entry safely
let tag = self.data[self.position];
let reserved = self.data[self.position + 1];
let size = u16::from_le_bytes([
self.data[self.position + 2],
self.data[self.position + 3],
]);
let entry = TlvEntry { tag, reserved, size };
self.position += mem::size_of::<TlvEntry>();
// Read payload
let payload_end = self.position + entry.size as usize;
if payload_end > self.data.len() {
return Err(BidError::InvalidArgs);
}
let payload = &self.data[self.position..payload_end];
self.position = payload_end;
// Convert tag
let tag = match entry.tag {
1 => BidTag::Bool,
2 => BidTag::I32,
3 => BidTag::I64,
4 => BidTag::F32,
5 => BidTag::F64,
6 => BidTag::String,
7 => BidTag::Bytes,
8 => BidTag::Handle,
9 => BidTag::Void,
20 => BidTag::Result,
21 => BidTag::Option,
22 => BidTag::Array,
_ => return Err(BidError::InvalidType),
};
Ok(Some((tag, payload)))
}
/// Decode a boolean from payload
pub fn decode_bool(payload: &[u8]) -> BidResult<bool> {
if payload.len() != 1 {
return Err(BidError::InvalidArgs);
}
Ok(payload[0] != 0)
}
/// Decode an i32 from payload
pub fn decode_i32(payload: &[u8]) -> BidResult<i32> {
if payload.len() != 4 {
return Err(BidError::InvalidArgs);
}
Ok(i32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]))
}
/// Decode an i64 from payload
pub fn decode_i64(payload: &[u8]) -> BidResult<i64> {
if payload.len() != 8 {
return Err(BidError::InvalidArgs);
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(payload);
Ok(i64::from_le_bytes(bytes))
}
/// Decode a handle from payload
pub fn decode_handle(payload: &[u8]) -> BidResult<BidHandle> {
if payload.len() != 8 {
return Err(BidError::InvalidArgs);
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(payload);
Ok(BidHandle::from_u64(u64::from_le_bytes(bytes)))
}
/// Decode an f32 from payload
pub fn decode_f32(payload: &[u8]) -> BidResult<f32> {
if payload.len() != 4 {
return Err(BidError::InvalidArgs);
}
Ok(f32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]))
}
/// Decode an f64 from payload
pub fn decode_f64(payload: &[u8]) -> BidResult<f64> {
if payload.len() != 8 {
return Err(BidError::InvalidArgs);
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(payload);
Ok(f64::from_le_bytes(bytes))
}
/// Decode a string from payload
pub fn decode_string(payload: &[u8]) -> BidResult<&str> {
std::str::from_utf8(payload).map_err(|_| BidError::InvalidUtf8)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode_primitives() {
let mut encoder = TlvEncoder::new();
encoder.encode_bool(true).unwrap();
encoder.encode_i32(42).unwrap();
encoder.encode_i64(9876543210).unwrap();
encoder.encode_string("Hello Nyash!").unwrap();
let data = encoder.finish();
let mut decoder = TlvDecoder::new(&data).unwrap();
assert_eq!(decoder.arg_count(), 4);
// Decode bool
let (tag, payload) = decoder.decode_next().unwrap().unwrap();
assert_eq!(tag, BidTag::Bool);
assert_eq!(TlvDecoder::decode_bool(payload).unwrap(), true);
// Decode i32
let (tag, payload) = decoder.decode_next().unwrap().unwrap();
assert_eq!(tag, BidTag::I32);
assert_eq!(TlvDecoder::decode_i32(payload).unwrap(), 42);
// Decode i64
let (tag, payload) = decoder.decode_next().unwrap().unwrap();
assert_eq!(tag, BidTag::I64);
assert_eq!(TlvDecoder::decode_i64(payload).unwrap(), 9876543210);
// Decode string
let (tag, payload) = decoder.decode_next().unwrap().unwrap();
assert_eq!(tag, BidTag::String);
assert_eq!(TlvDecoder::decode_string(payload).unwrap(), "Hello Nyash!");
}
#[test]
fn test_encode_decode_handle() {
let mut encoder = TlvEncoder::new();
let handle = BidHandle::new(6, 12345);
encoder.encode_handle(handle).unwrap();
let data = encoder.finish();
let mut decoder = TlvDecoder::new(&data).unwrap();
let (tag, payload) = decoder.decode_next().unwrap().unwrap();
assert_eq!(tag, BidTag::Handle);
assert_eq!(TlvDecoder::decode_handle(payload).unwrap(), handle);
}
}

View File

@ -0,0 +1,242 @@
use super::Usize;
/// BID-1 Type System (ChatGPT Enhanced Edition)
#[derive(Clone, Debug, PartialEq)]
pub enum BidType {
// === Primitives (pass by value across FFI) ===
Bool, // i32 (0=false, 1=true)
I32, // 32-bit signed integer
I64, // 64-bit signed integer
F32, // 32-bit floating point
F64, // 64-bit floating point
// === Composite types (pass as ptr+len) ===
String, // UTF-8 string (ptr: usize, len: usize)
Bytes, // Binary data (ptr: usize, len: usize)
// === Handle design (ChatGPT recommendation) ===
Handle {
type_id: u32, // Box type ID (1=StringBox, 6=FileBox, etc.)
instance_id: u32, // Instance identifier
},
// === Meta types ===
Void, // No return value
// === Phase 2 reserved (TLV tags reserved) ===
#[allow(dead_code)]
Option(Box<BidType>), // TLV tag=21
#[allow(dead_code)]
Result(Box<BidType>, Box<BidType>), // TLV tag=20
#[allow(dead_code)]
Array(Box<BidType>), // TLV tag=22
}
/// Handle representation for efficient Box references
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BidHandle {
pub type_id: u32,
pub instance_id: u32,
}
impl BidHandle {
/// Create a new handle
pub fn new(type_id: u32, instance_id: u32) -> Self {
Self { type_id, instance_id }
}
/// Pack into single u64 (type_id << 32 | instance_id)
pub fn to_u64(&self) -> u64 {
((self.type_id as u64) << 32) | (self.instance_id as u64)
}
/// Unpack from single u64
pub fn from_u64(packed: u64) -> Self {
Self {
type_id: (packed >> 32) as u32,
instance_id: (packed & 0xFFFFFFFF) as u32,
}
}
}
/// TLV (Type-Length-Value) tags for BID-1
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BidTag {
Bool = 1, // payload: 1 byte (0/1)
I32 = 2, // payload: 4 bytes (little-endian)
I64 = 3, // payload: 8 bytes (little-endian)
F32 = 4, // payload: 4 bytes (IEEE 754)
F64 = 5, // payload: 8 bytes (IEEE 754)
String = 6, // payload: UTF-8 bytes
Bytes = 7, // payload: binary data
Handle = 8, // payload: 8 bytes (type_id + instance_id)
Void = 9, // payload: 0 bytes
// Phase 2 reserved
Result = 20,
Option = 21,
Array = 22,
}
impl BidType {
/// Get the TLV tag for this type
pub fn tag(&self) -> BidTag {
match self {
BidType::Bool => BidTag::Bool,
BidType::I32 => BidTag::I32,
BidType::I64 => BidTag::I64,
BidType::F32 => BidTag::F32,
BidType::F64 => BidTag::F64,
BidType::String => BidTag::String,
BidType::Bytes => BidTag::Bytes,
BidType::Handle { .. } => BidTag::Handle,
BidType::Void => BidTag::Void,
_ => panic!("Phase 2 types not yet implemented"),
}
}
/// Get the expected payload size (None for variable-length types)
pub fn payload_size(&self) -> Option<usize> {
match self {
BidType::Bool => Some(1),
BidType::I32 => Some(4),
BidType::I64 => Some(8),
BidType::F32 => Some(4),
BidType::F64 => Some(8),
BidType::Handle { .. } => Some(8),
BidType::Void => Some(0),
BidType::String | BidType::Bytes => None, // Variable length
_ => panic!("Phase 2 types not yet implemented"),
}
}
}
/// Box type IDs (matching existing Nyash boxes)
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoxTypeId {
StringBox = 1,
IntegerBox = 2,
BoolBox = 3,
FloatBox = 4,
ArrayBox = 5,
FileBox = 6, // Plugin example
FutureBox = 7, // Existing async support
P2PBox = 8, // Existing P2P support
// ... more box types
}
// ========== Type Information Management ==========
// nyash.tomlでの型情報管理のための構造体
// ハードコーディングを避け、動的な型変換を実現
/// メソッドの型情報
#[derive(Debug, Clone)]
pub struct MethodTypeInfo {
/// 引数の型マッピング情報
pub args: Vec<ArgTypeMapping>,
/// 戻り値の型(将来拡張用)
pub returns: Option<String>,
}
/// 引数の型マッピング情報
#[derive(Debug, Clone)]
pub struct ArgTypeMapping {
/// 引数名(ドキュメント用、オプション)
pub name: Option<String>,
/// Nyash側の型名"string", "integer", "bool" など)
pub from: String,
/// プラグインが期待する型名("string", "bytes", "i32" など)
pub to: String,
}
impl ArgTypeMapping {
/// 新しい型マッピングを作成
pub fn new(from: String, to: String) -> Self {
Self {
name: None,
from,
to,
}
}
/// 名前付きの型マッピングを作成
pub fn with_name(name: String, from: String, to: String) -> Self {
Self {
name: Some(name),
from,
to,
}
}
/// Nyash型からBIDタグへの変換を決定
/// ハードコーディングを避けるため、型名の組み合わせで判定
pub fn determine_bid_tag(&self) -> Option<BidTag> {
match (self.from.as_str(), self.to.as_str()) {
// 文字列の変換パターン
("string", "string") => Some(BidTag::String),
("string", "bytes") => Some(BidTag::Bytes),
// 数値の変換パターン
("integer", "i32") => Some(BidTag::I32),
("integer", "i64") => Some(BidTag::I64),
("float", "f32") => Some(BidTag::F32),
("float", "f64") => Some(BidTag::F64),
// ブール値
("bool", "bool") => Some(BidTag::Bool),
// バイナリデータ
("bytes", "bytes") => Some(BidTag::Bytes),
("array", "bytes") => Some(BidTag::Bytes), // 配列をシリアライズ
// 未対応の組み合わせ
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handle_packing() {
let handle = BidHandle::new(6, 12345);
let packed = handle.to_u64();
let unpacked = BidHandle::from_u64(packed);
assert_eq!(handle, unpacked);
assert_eq!(unpacked.type_id, 6);
assert_eq!(unpacked.instance_id, 12345);
}
#[test]
fn test_type_tags() {
assert_eq!(BidType::Bool.tag(), BidTag::Bool);
assert_eq!(BidType::String.tag(), BidTag::String);
assert_eq!(BidType::Handle { type_id: 6, instance_id: 0 }.tag(), BidTag::Handle);
}
#[test]
fn test_arg_type_mapping() {
// string → bytes 変換のテストwriteメソッドで使用
let mapping = ArgTypeMapping::new("string".to_string(), "bytes".to_string());
assert_eq!(mapping.determine_bid_tag(), Some(BidTag::Bytes));
// integer → i32 変換のテスト
let mapping = ArgTypeMapping::new("integer".to_string(), "i32".to_string());
assert_eq!(mapping.determine_bid_tag(), Some(BidTag::I32));
// 名前付きマッピングのテスト
let mapping = ArgTypeMapping::with_name(
"content".to_string(),
"string".to_string(),
"string".to_string()
);
assert_eq!(mapping.name, Some("content".to_string()));
assert_eq!(mapping.determine_bid_tag(), Some(BidTag::String));
}
}