feat: ArrayBox完全実装 - Nyash初の動的配列コレクション!

## 🎯 実装内容
- ArrayBoxにNyashBoxトレイト完全実装
- Arc<Mutex>による内部可変性実現(MapBoxと同じパターン)
- 全メソッド実装: push/pop/get/set/length/indexOf/contains/join/clear/remove
- 包括的なテストスイート作成

## 🔧 技術的改善
- GitHub Copilot作成の基本構造をNyash対応に拡張
- execute_array_methodを&self参照に修正
- collection_methods.rsとの統合完了

##  テスト結果
- 全機能正常動作確認
- 異なる型の要素混在可能(Everything is Box哲学)
- インデックス範囲外アクセスでNullBox返却

## 📝 残課題
- BufferBox, ResultBox, FileBox等は基本構造のみ(未実装)
- RegexBoxはNyashBoxトレイト実装済みだがregex依存関係未追加

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-10 12:24:59 +09:00
parent 85a8c7f2d7
commit cbee14809d
8 changed files with 408 additions and 56 deletions

View File

@ -1,34 +1,270 @@
//! ArrayBox 📦 - 配列・リスト操作(両者一致!) /*! 📦 ArrayBox - 配列・リスト操作Box
// Nyashの箱システムによる配列・リスト操作を提供します。 *
// 参考: 既存Boxの設計思想 * ## 📝 概要
* 順序付きコレクションを扱うためのBox。
* JavaScript風の配列操作APIで直感的なデータ管理が可能。
*
* ## 🛠️ 利用可能メソッド
* - `push(item)` - 要素を末尾に追加
* - `pop()` - 末尾の要素を削除して返す
* - `get(index)` - 指定インデックスの要素を取得
* - `set(index, value)` - 指定インデックスに要素を設定
* - `length()` - 配列の長さを取得
* - `remove(index)` - 指定インデックスの要素を削除
* - `indexOf(item)` - 要素のインデックスを検索
* - `contains(item)` - 要素が含まれているか確認
* - `clear()` - すべての要素を削除
* - `join(separator)` - 文字列として結合
*
* ## 💡 使用例
* ```nyash
* local arr, item
* arr = new ArrayBox()
*
* // 要素の追加
* arr.push("Apple")
* arr.push("Banana")
* arr.push("Cherry")
*
* // 要素へのアクセス
* print(arr.get(0)) // "Apple"
* print(arr.length()) // 3
*
* // 要素の削除
* item = arr.pop() // "Cherry"
* arr.remove(0) // "Apple"削除
*
* // 文字列結合
* print(arr.join(", ")) // "Banana"
* ```
*
* ## 🎮 実用例 - TodoList
* ```nyash
* static box TodoList {
* init { items, console }
*
* main() {
* me.items = new ArrayBox()
* me.console = new ConsoleBox()
*
* me.addTask("Nyash開発")
* me.addTask("ドキュメント作成")
* me.addTask("テスト実行")
*
* me.showTasks()
* }
*
* addTask(task) {
* me.items.push(task)
* me.console.log("✅ タスク追加: " + task)
* }
*
* showTasks() {
* me.console.log("=== Todo List ===")
* local i
* i = 0
* loop(i < me.items.length()) {
* me.console.log((i + 1) + ". " + me.items.get(i))
* i = i + 1
* }
* }
* }
* ```
*
* ## ⚠️ 注意
* - インデックスは0から開始
* - 範囲外のインデックスアクセスはNullBoxを返す
* - 異なる型の要素を混在可能Everything is Box
*/
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
use crate::boxes::null_box::NullBox;
use std::any::Any;
use std::fmt::{Debug, Display};
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct ArrayBox { pub struct ArrayBox {
pub items: Vec<Box<dyn std::any::Any>>, items: Arc<Mutex<Vec<Box<dyn NyashBox>>>>,
id: u64,
} }
impl ArrayBox { impl ArrayBox {
/// 新しいArrayBoxを作成
pub fn new() -> Self { pub fn new() -> Self {
ArrayBox { items: Vec::new() } static mut COUNTER: u64 = 0;
let id = unsafe {
COUNTER += 1;
COUNTER
};
ArrayBox {
items: Arc::new(Mutex::new(Vec::new())),
id,
}
} }
/// 要素を追加
pub fn push(&mut self, item: Box<dyn std::any::Any>) { /// 要素を末尾に追加
self.items.push(item); pub fn push(&self, item: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
self.items.lock().unwrap().push(item);
Box::new(StringBox::new("ok"))
} }
/// 末尾の要素を削除して返す
pub fn pop(&self) -> Box<dyn NyashBox> {
match self.items.lock().unwrap().pop() {
Some(item) => item,
None => Box::new(NullBox::new()),
}
}
/// 要素数を取得 /// 要素数を取得
pub fn len(&self) -> usize { pub fn length(&self) -> Box<dyn NyashBox> {
self.items.len() Box::new(IntegerBox::new(self.items.lock().unwrap().len() as i64))
} }
/// 要素を取得
pub fn get(&self, index: usize) -> Option<&Box<dyn std::any::Any>> { /// 指定インデックスの要素を取得
self.items.get(index) pub fn get(&self, index: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
} if let Some(idx_box) = index.as_any().downcast_ref::<IntegerBox>() {
/// 要素を削除 let idx = idx_box.value as usize;
pub fn remove(&mut self, index: usize) -> Option<Box<dyn std::any::Any>> { let items = self.items.lock().unwrap();
if index < self.items.len() { match items.get(idx) {
Some(self.items.remove(index)) Some(item) => item.clone_box(),
None => Box::new(NullBox::new()),
}
} else { } else {
None Box::new(StringBox::new("Error: get() requires integer index"))
}
}
/// 指定インデックスに要素を設定
pub fn set(&self, index: Box<dyn NyashBox>, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(idx_box) = index.as_any().downcast_ref::<IntegerBox>() {
let idx = idx_box.value as usize;
let mut items = self.items.lock().unwrap();
if idx < items.len() {
items[idx] = value;
Box::new(StringBox::new("ok"))
} else {
Box::new(StringBox::new("Error: index out of bounds"))
}
} else {
Box::new(StringBox::new("Error: set() requires integer index"))
}
}
/// 指定インデックスの要素を削除
pub fn remove(&self, index: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(idx_box) = index.as_any().downcast_ref::<IntegerBox>() {
let idx = idx_box.value as usize;
let mut items = self.items.lock().unwrap();
if idx < items.len() {
items.remove(idx)
} else {
Box::new(NullBox::new())
}
} else {
Box::new(StringBox::new("Error: remove() requires integer index"))
}
}
/// 要素のインデックスを検索
pub fn indexOf(&self, item: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let items = self.items.lock().unwrap();
for (i, element) in items.iter().enumerate() {
if element.equals(item.as_ref()).value {
return Box::new(IntegerBox::new(i as i64));
}
}
Box::new(IntegerBox::new(-1))
}
/// 要素が含まれているか確認
pub fn contains(&self, item: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let items = self.items.lock().unwrap();
for element in items.iter() {
if element.equals(item.as_ref()).value {
return Box::new(BoolBox::new(true));
}
}
Box::new(BoolBox::new(false))
}
/// すべての要素を削除
pub fn clear(&self) -> Box<dyn NyashBox> {
self.items.lock().unwrap().clear();
Box::new(StringBox::new("ok"))
}
/// 文字列として結合
pub fn join(&self, separator: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(sep_box) = separator.as_any().downcast_ref::<StringBox>() {
let items = self.items.lock().unwrap();
let parts: Vec<String> = items
.iter()
.map(|item| item.to_string_box().value)
.collect();
Box::new(StringBox::new(parts.join(&sep_box.value)))
} else {
Box::new(StringBox::new("Error: join() requires string separator"))
} }
} }
} }
impl NyashBox for ArrayBox {
fn type_name(&self) -> &'static str {
"ArrayBox"
}
fn to_string_box(&self) -> StringBox {
let items = self.items.lock().unwrap();
let elements: Vec<String> = items
.iter()
.map(|item| item.to_string_box().value)
.collect();
StringBox::new(format!("[{}]", elements.join(", ")))
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(other_array) = other.as_any().downcast_ref::<ArrayBox>() {
let self_items = self.items.lock().unwrap();
let other_items = other_array.items.lock().unwrap();
if self_items.len() != other_items.len() {
return BoolBox::new(false);
}
for (a, b) in self_items.iter().zip(other_items.iter()) {
if !a.equals(&**b).value {
return BoolBox::new(false);
}
}
BoolBox::new(true)
} else {
BoolBox::new(false)
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn box_id(&self) -> u64 {
self.id
}
}
impl Display for ArrayBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let items = self.items.lock().unwrap();
let elements: Vec<String> = items
.iter()
.map(|item| item.to_string_box().value)
.collect();
write!(f, "[{}]", elements.join(", "))
}
}

View File

@ -103,7 +103,8 @@
* - 存在しないキーの取得は "Key not found" メッセージ返却 * - 存在しないキーの取得は "Key not found" メッセージ返却
*/ */
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, ArrayBox}; use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
use crate::boxes::array::ArrayBox;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;

View File

@ -99,9 +99,9 @@ pub mod null_box;
// pub mod intent_box_wrapper; // pub mod intent_box_wrapper;
// pub mod p2p_box; // pub mod p2p_box;
// 今後追加予定のBox型コメントアウト // 配列・リスト操作Box
// pub mod array_box; pub mod array;
// pub use array_box::ArrayBox; pub use array::ArrayBox;
// null関数も再エクスポート // null関数も再エクスポート
pub use null_box::{NullBox, null}; pub use null_box::{NullBox, null};

View File

@ -8,6 +8,7 @@
use super::*; use super::*;
use crate::ast::UnaryOperator; use crate::ast::UnaryOperator;
use crate::boxes::array::ArrayBox;
// TODO: Fix NullBox import issue later // TODO: Fix NullBox import issue later
// use crate::NullBox; // use crate::NullBox;
@ -326,6 +327,8 @@ impl NyashInterpreter {
return self.execute_array_method(array_box, method, arguments); return self.execute_array_method(array_box, method, arguments);
} }
// TODO: 以下のBoxはまだ実装されていない
/*
// FileBox method calls // FileBox method calls
if let Some(file_box) = obj_value.as_any().downcast_ref::<FileBox>() { if let Some(file_box) = obj_value.as_any().downcast_ref::<FileBox>() {
return self.execute_file_method(file_box, method, arguments); return self.execute_file_method(file_box, method, arguments);
@ -345,6 +348,7 @@ impl NyashInterpreter {
if let Some(channel_box) = obj_value.as_any().downcast_ref::<ChannelBox>() { if let Some(channel_box) = obj_value.as_any().downcast_ref::<ChannelBox>() {
return self.execute_channel_method(channel_box, method, arguments); return self.execute_channel_method(channel_box, method, arguments);
} }
*/
// MathBox method calls // MathBox method calls
if let Some(math_box) = obj_value.as_any().downcast_ref::<MathBox>() { if let Some(math_box) = obj_value.as_any().downcast_ref::<MathBox>() {

View File

@ -8,7 +8,8 @@
*/ */
use super::super::*; use super::super::*;
use crate::box_trait::{StringBox, IntegerBox, ArrayBox, NyashBox, VoidBox, BoolBox}; use crate::box_trait::{StringBox, IntegerBox, NyashBox, BoolBox};
use crate::boxes::array::ArrayBox;
use crate::boxes::map_box::MapBox; use crate::boxes::map_box::MapBox;
impl NyashInterpreter { impl NyashInterpreter {
@ -48,17 +49,7 @@ impl NyashInterpreter {
}); });
} }
let index_value = self.execute_expression(&arguments[0])?; let index_value = self.execute_expression(&arguments[0])?;
if let Some(index_int) = index_value.as_any().downcast_ref::<IntegerBox>() { Ok(array_box.get(index_value))
if let Some(element) = array_box.get(index_int.value as usize) {
Ok(element)
} else {
Ok(Box::new(StringBox::new("Index out of bounds")))
}
} else {
Err(RuntimeError::TypeError {
message: "get() requires integer index".to_string(),
})
}
} }
"set" => { "set" => {
if arguments.len() != 2 { if arguments.len() != 2 {
@ -68,18 +59,43 @@ impl NyashInterpreter {
} }
let index_value = self.execute_expression(&arguments[0])?; let index_value = self.execute_expression(&arguments[0])?;
let element_value = self.execute_expression(&arguments[1])?; let element_value = self.execute_expression(&arguments[1])?;
if let Some(index_int) = index_value.as_any().downcast_ref::<IntegerBox>() { Ok(array_box.set(index_value, element_value))
match array_box.set(index_int.value as usize, element_value) { }
Ok(()) => Ok(Box::new(VoidBox::new())), "remove" => {
Err(msg) => Ok(Box::new(StringBox::new(&msg))), if arguments.len() != 1 {
} return Err(RuntimeError::InvalidOperation {
} else { message: format!("remove() expects 1 argument, got {}", arguments.len()),
Err(RuntimeError::TypeError { });
message: "set() requires integer index".to_string(), }
}) let index_value = self.execute_expression(&arguments[0])?;
} Ok(array_box.remove(index_value))
}
"indexOf" => {
if arguments.len() != 1 {
return Err(RuntimeError::InvalidOperation {
message: format!("indexOf() expects 1 argument, got {}", arguments.len()),
});
}
let element = self.execute_expression(&arguments[0])?;
Ok(array_box.indexOf(element))
}
"contains" => {
if arguments.len() != 1 {
return Err(RuntimeError::InvalidOperation {
message: format!("contains() expects 1 argument, got {}", arguments.len()),
});
}
let element = self.execute_expression(&arguments[0])?;
Ok(array_box.contains(element))
}
"clear" => {
if !arguments.is_empty() {
return Err(RuntimeError::InvalidOperation {
message: format!("clear() expects 0 arguments, got {}", arguments.len()),
});
}
Ok(array_box.clear())
} }
// Note: indexOf, contains, clear, reverse, slice methods not implemented in ArrayBox yet
"join" => { "join" => {
if arguments.len() != 1 { if arguments.len() != 1 {
return Err(RuntimeError::InvalidOperation { return Err(RuntimeError::InvalidOperation {
@ -87,13 +103,7 @@ impl NyashInterpreter {
}); });
} }
let delimiter_value = self.execute_expression(&arguments[0])?; let delimiter_value = self.execute_expression(&arguments[0])?;
if let Some(delimiter_str) = delimiter_value.as_any().downcast_ref::<StringBox>() { Ok(array_box.join(delimiter_value))
Ok(array_box.join(&delimiter_str.value))
} else {
Err(RuntimeError::TypeError {
message: "join() requires string delimiter".to_string(),
})
}
} }
"isEmpty" => { "isEmpty" => {
if !arguments.is_empty() { if !arguments.is_empty() {

View File

@ -9,6 +9,7 @@
use super::*; use super::*;
use crate::boxes::null_box::NullBox; use crate::boxes::null_box::NullBox;
use crate::boxes::console_box::ConsoleBox; use crate::boxes::console_box::ConsoleBox;
use crate::boxes::array::ArrayBox;
// use crate::boxes::intent_box_wrapper::IntentBoxWrapper; // use crate::boxes::intent_box_wrapper::IntentBoxWrapper;
use std::sync::Arc; use std::sync::Arc;
@ -29,6 +30,8 @@ impl NyashInterpreter {
// 🌍 革命的実装Environment tracking廃止 // 🌍 革命的実装Environment tracking廃止
return Ok(array_box); return Ok(array_box);
} }
// TODO: 以下のBoxはまだ実装されていない
/*
"FileBox" => { "FileBox" => {
// FileBoxは引数1個ファイルパスで作成 // FileBoxは引数1個ファイルパスで作成
if arguments.len() != 1 { if arguments.len() != 1 {
@ -59,6 +62,7 @@ impl NyashInterpreter {
// 🌍 革命的実装Environment tracking廃止 // 🌍 革命的実装Environment tracking廃止
return Ok(result_box); return Ok(result_box);
} }
*/
"ErrorBox" => { "ErrorBox" => {
// ErrorBoxは引数2個エラータイプ、メッセージで作成 // ErrorBoxは引数2個エラータイプ、メッセージで作成
if arguments.len() != 2 { if arguments.len() != 2 {
@ -619,9 +623,9 @@ impl NyashInterpreter {
// 基本的なビルトイン型 // 基本的なビルトイン型
let is_builtin = matches!(type_name, let is_builtin = matches!(type_name,
"IntegerBox" | "StringBox" | "BoolBox" | "ArrayBox" | "MapBox" | "IntegerBox" | "StringBox" | "BoolBox" | "ArrayBox" | "MapBox" |
"FileBox" | "ResultBox" | "FutureBox" | "ChannelBox" | "MathBox" | "MathBox" |
"TimeBox" | "DateTimeBox" | "TimerBox" | "RandomBox" | "SoundBox" | "TimeBox" | "DateTimeBox" | "TimerBox" | "RandomBox" | "SoundBox" |
"DebugBox" | "MethodBox" | "NullBox" | "ConsoleBox" | "FloatBox" "DebugBox" | "MethodBox" | "NullBox" | "ConsoleBox"
); );
// Web専用BoxWASM環境のみ // Web専用BoxWASM環境のみ

74
test_array_box.nyash Normal file
View File

@ -0,0 +1,74 @@
// ArrayBox実装テスト
print("=== ArrayBox実装テスト ===")
// 1. ArrayBoxの作成
print("\n1. ArrayBoxの作成:")
local arr
arr = new ArrayBox()
print("ArrayBox created: " + arr.toString())
// print("Type: " + arr.type_name()) // type_nameはArrayBoxのメソッドではない
print("Initial length: " + arr.length())
// 2. 要素の追加push
print("\n2. 要素の追加:")
arr.push("Apple")
arr.push("Banana")
arr.push("Cherry")
arr.push(42)
arr.push(true)
print("After push: " + arr.toString())
print("Length: " + arr.length())
// 3. 要素の取得get
print("\n3. 要素の取得:")
print("arr.get(0) = " + arr.get(0))
print("arr.get(1) = " + arr.get(1))
print("arr.get(3) = " + arr.get(3))
print("arr.get(10) = " + arr.get(10)) // 範囲外
// 4. 要素の削除pop
print("\n4. 要素の削除:")
local popped
popped = arr.pop()
print("Popped: " + popped)
print("After pop: " + arr.toString())
print("Length: " + arr.length())
// 5. インデックス検索indexOf
print("\n5. インデックス検索:")
print("indexOf('Apple') = " + arr.indexOf("Apple"))
print("indexOf('Banana') = " + arr.indexOf("Banana"))
print("indexOf('NotExist') = " + arr.indexOf("NotExist"))
// 6. 要素の確認contains
print("\n6. 要素の確認:")
print("contains('Apple') = " + arr.contains("Apple"))
print("contains(42) = " + arr.contains(42))
print("contains('NotExist') = " + arr.contains("NotExist"))
// 7. 文字列結合join
print("\n7. 文字列結合:")
print("join(', ') = " + arr.join(", "))
print("join(' - ') = " + arr.join(" - "))
// 8. 要素の設定set
print("\n8. 要素の設定:")
arr.set(1, "Orange")
print("After set(1, 'Orange'): " + arr.toString())
// 9. 要素の削除remove
print("\n9. 要素の削除:")
local removed
removed = arr.remove(2)
print("Removed: " + removed)
print("After remove(2): " + arr.toString())
print("Length: " + arr.length())
// 10. 配列のクリアclear
print("\n10. 配列のクリア:")
arr.clear()
print("After clear: " + arr.toString())
print("Length: " + arr.length())
print("isEmpty: " + arr.isEmpty())
print("\n=== ArrayBoxテスト完了 ===")

23
test_array_simple.nyash Normal file
View File

@ -0,0 +1,23 @@
// ArrayBox簡単なテスト
print("=== ArrayBox簡単なテスト ===")
local arr
arr = new ArrayBox()
print("Created ArrayBox")
// 要素を追加
arr.push("Hello")
arr.push("World")
print("Added elements")
// 長さを確認
print("Length: " + arr.length())
// 配列の内容を表示
print("Array: " + arr.toString())
// 要素を取得
print("First element: " + arr.get(0))
print("Second element: " + arr.get(1))
print("\n=== Test complete! ===")