🎉 initial commit: Nyash Programming Language完成版
🚀 主要機能: • Everything is Box哲学による革新的アーキテクチャ • WebAssemblyブラウザー対応プレイグラウンド • アーティスト協同制作デモ - 複数Boxインスタンス実証 • 視覚的デバッグシステム - DebugBox完全統合 • static box Mainパターン - メモリ安全設計 ⚡ 言語機能: • NOT/AND/OR/除算演算子完全実装 • ジェネリクス/テンプレートシステム • 非同期処理(nowait/await) • try/catchエラーハンドリング • Canvas統合グラフィックス 🎨 ブラウザー体験: • 9種類のインタラクティブデモ • リアルタイムコード実行 • WebCanvas/WebConsole/WebDisplay • モバイル対応完了 🤖 Built with Claude Code collaboration Ready for public release!
This commit is contained in:
67
src/boxes/bool_box.rs
Normal file
67
src/boxes/bool_box.rs
Normal file
@ -0,0 +1,67 @@
|
||||
// BoolBox implementation - Boolean values in Nyash
|
||||
use crate::box_trait::NyashBox;
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Boolean values in Nyash - true/false
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BoolBox {
|
||||
pub value: bool,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl BoolBox {
|
||||
pub fn new(value: bool) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { value, id }
|
||||
}
|
||||
|
||||
pub fn true_box() -> Self {
|
||||
Self::new(true)
|
||||
}
|
||||
|
||||
pub fn false_box() -> Self {
|
||||
Self::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for BoolBox {
|
||||
fn to_string_box(&self) -> crate::box_trait::StringBox {
|
||||
crate::box_trait::StringBox::new(if self.value { "true" } else { "false" })
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
if let Some(other_bool) = other.as_any().downcast_ref::<BoolBox>() {
|
||||
crate::box_trait::BoolBox::new(self.value == other_bool.value)
|
||||
} else {
|
||||
crate::box_trait::BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"BoolBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BoolBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", if self.value { "true" } else { "false" })
|
||||
}
|
||||
}
|
||||
154
src/boxes/console_box.rs
Normal file
154
src/boxes/console_box.rs
Normal file
@ -0,0 +1,154 @@
|
||||
/*!
|
||||
* ConsoleBox - ブラウザコンソール制御Box
|
||||
*
|
||||
* WebAssembly環境でブラウザのconsole APIにアクセス
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
// 🌐 Browser console access Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConsoleBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl ConsoleBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// Log messages to browser console
|
||||
pub fn log(&self, message: &str) {
|
||||
web_sys::console::log_1(&message.into());
|
||||
}
|
||||
|
||||
/// Log warning to browser console
|
||||
pub fn warn(&self, message: &str) {
|
||||
web_sys::console::warn_1(&message.into());
|
||||
}
|
||||
|
||||
/// Log error to browser console
|
||||
pub fn error(&self, message: &str) {
|
||||
web_sys::console::error_1(&message.into());
|
||||
}
|
||||
|
||||
/// Clear browser console
|
||||
pub fn clear(&self) {
|
||||
web_sys::console::clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for ConsoleBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("[ConsoleBox - Browser Console Interface]")
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
BoolBox::new(other.as_any().is::<ConsoleBox>())
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"ConsoleBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
// Non-WASM版 - モックアップ実装
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConsoleBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl ConsoleBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// Mock log method for non-WASM environments
|
||||
pub fn log(&self, message: &str) {
|
||||
println!("[Console LOG] {}", message);
|
||||
}
|
||||
|
||||
pub fn warn(&self, message: &str) {
|
||||
println!("[Console WARN] {}", message);
|
||||
}
|
||||
|
||||
pub fn error(&self, message: &str) {
|
||||
println!("[Console ERROR] {}", message);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
println!("[Console CLEAR]");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl NyashBox for ConsoleBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("[ConsoleBox - Mock Implementation]")
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
BoolBox::new(other.as_any().is::<ConsoleBox>())
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"ConsoleBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Display implementations for both WASM and non-WASM versions
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl Display for ConsoleBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[ConsoleBox - Browser Console Interface]")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Display for ConsoleBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[ConsoleBox - Mock Implementation]")
|
||||
}
|
||||
}
|
||||
246
src/boxes/debug_box.rs
Normal file
246
src/boxes/debug_box.rs
Normal file
@ -0,0 +1,246 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use chrono::Local;
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox};
|
||||
use crate::interpreter::RuntimeError;
|
||||
use crate::instance::InstanceBox;
|
||||
use std::any::Any;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebugBox {
|
||||
tracking_enabled: Arc<Mutex<bool>>,
|
||||
tracked_boxes: Arc<Mutex<HashMap<String, TrackedBoxInfo>>>,
|
||||
breakpoints: Arc<Mutex<Vec<String>>>,
|
||||
call_stack: Arc<Mutex<Vec<CallInfo>>>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TrackedBoxInfo {
|
||||
box_type: String,
|
||||
created_at: String,
|
||||
fields: String,
|
||||
value_repr: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CallInfo {
|
||||
function_name: String,
|
||||
args: Vec<String>,
|
||||
timestamp: String,
|
||||
}
|
||||
|
||||
impl DebugBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
DebugBox {
|
||||
tracking_enabled: Arc::new(Mutex::new(false)),
|
||||
tracked_boxes: Arc::new(Mutex::new(HashMap::new())),
|
||||
breakpoints: Arc::new(Mutex::new(Vec::new())),
|
||||
call_stack: Arc::new(Mutex::new(Vec::new())),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_tracking(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut enabled = self.tracking_enabled.lock().unwrap();
|
||||
*enabled = true;
|
||||
println!("[DEBUG] Tracking started");
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn stop_tracking(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut enabled = self.tracking_enabled.lock().unwrap();
|
||||
*enabled = false;
|
||||
println!("[DEBUG] Tracking stopped");
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn track_box(&self, box_value: &dyn NyashBox, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let enabled = self.tracking_enabled.lock().unwrap();
|
||||
if !*enabled {
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
}
|
||||
|
||||
let mut tracked = self.tracked_boxes.lock().unwrap();
|
||||
|
||||
let info = TrackedBoxInfo {
|
||||
box_type: box_value.type_name().to_string(),
|
||||
created_at: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
fields: self.get_box_fields(box_value),
|
||||
value_repr: box_value.to_string_box().value,
|
||||
};
|
||||
|
||||
tracked.insert(name.to_string(), info);
|
||||
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
fn get_box_fields(&self, box_value: &dyn NyashBox) -> String {
|
||||
// Try to downcast to InstanceBox to get fields
|
||||
if let Some(instance) = box_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
let fields = instance.fields.lock().unwrap();
|
||||
let field_names: Vec<String> = fields.keys().cloned().collect();
|
||||
field_names.join(", ")
|
||||
} else {
|
||||
"N/A".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_all(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
let mut output = String::from("=== Box State Dump ===\n");
|
||||
output.push_str(&format!("Time: {}\n", Local::now().format("%Y-%m-%d %H:%M:%S")));
|
||||
output.push_str(&format!("Total tracked boxes: {}\n\n", tracked.len()));
|
||||
|
||||
for (name, info) in tracked.iter() {
|
||||
output.push_str(&format!("Box: {}\n", name));
|
||||
output.push_str(&format!(" Type: {}\n", info.box_type));
|
||||
output.push_str(&format!(" Created: {}\n", info.created_at));
|
||||
output.push_str(&format!(" Fields: {}\n", info.fields));
|
||||
output.push_str(&format!(" Value: {}\n", info.value_repr));
|
||||
output.push_str("\n");
|
||||
}
|
||||
|
||||
Ok(Box::new(StringBox::new(output)))
|
||||
}
|
||||
|
||||
pub fn save_to_file(&self, filename: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let dump_result = self.dump_all()?;
|
||||
let content = dump_result.to_string_box().value;
|
||||
|
||||
// Write to file using std::fs
|
||||
std::fs::write(filename, content)
|
||||
.map_err(|e| RuntimeError::InvalidOperation {
|
||||
message: format!("Failed to write debug file: {}", e),
|
||||
})?;
|
||||
|
||||
println!("[DEBUG] Saved debug info to {}", filename);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn watch(&self, box_value: &dyn NyashBox, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let value_str = box_value.to_string_box().value;
|
||||
let type_name = box_value.type_name();
|
||||
|
||||
println!("[DEBUG] Watching {} ({}): {}", name, type_name, value_str);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn memory_report(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
let mut report = String::from("=== Memory Report ===\n");
|
||||
report.push_str(&format!("Tracked boxes: {}\n", tracked.len()));
|
||||
|
||||
// Count by type
|
||||
let mut type_counts: HashMap<String, usize> = HashMap::new();
|
||||
for info in tracked.values() {
|
||||
*type_counts.entry(info.box_type.clone()).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
report.push_str("\nBoxes by type:\n");
|
||||
for (box_type, count) in type_counts.iter() {
|
||||
report.push_str(&format!(" {}: {}\n", box_type, count));
|
||||
}
|
||||
|
||||
Ok(Box::new(StringBox::new(report)))
|
||||
}
|
||||
|
||||
// Advanced features
|
||||
pub fn set_breakpoint(&self, function_name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut breakpoints = self.breakpoints.lock().unwrap();
|
||||
breakpoints.push(function_name.to_string());
|
||||
println!("[DEBUG] Breakpoint set at function: {}", function_name);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn trace_call(&self, function_name: &str, args: Vec<String>) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut stack = self.call_stack.lock().unwrap();
|
||||
stack.push(CallInfo {
|
||||
function_name: function_name.to_string(),
|
||||
args,
|
||||
timestamp: Local::now().format("%H:%M:%S.%3f").to_string(),
|
||||
});
|
||||
|
||||
// Keep only last 100 calls to prevent memory issues
|
||||
if stack.len() > 100 {
|
||||
stack.remove(0);
|
||||
}
|
||||
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn show_call_stack(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let stack = self.call_stack.lock().unwrap();
|
||||
let mut output = String::from("=== Call Stack ===\n");
|
||||
|
||||
for (i, call) in stack.iter().enumerate() {
|
||||
output.push_str(&format!("{}: [{}] {}({})\n",
|
||||
i,
|
||||
call.timestamp,
|
||||
call.function_name,
|
||||
call.args.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Box::new(StringBox::new(output)))
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut tracked = self.tracked_boxes.lock().unwrap();
|
||||
tracked.clear();
|
||||
|
||||
let mut stack = self.call_stack.lock().unwrap();
|
||||
stack.clear();
|
||||
|
||||
println!("[DEBUG] Cleared all debug information");
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn is_tracking(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let enabled = self.tracking_enabled.lock().unwrap();
|
||||
Ok(Box::new(BoolBox::new(*enabled)))
|
||||
}
|
||||
|
||||
pub fn get_tracked_count(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
Ok(Box::new(crate::box_trait::IntegerBox::new(tracked.len() as i64)))
|
||||
}
|
||||
}
|
||||
|
||||
// Implement NyashBox trait for DebugBox
|
||||
impl NyashBox for DebugBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
StringBox::new(format!("DebugBox[{} tracked]", tracked.len()))
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_debug) = other.as_any().downcast_ref::<DebugBox>() {
|
||||
BoolBox::new(self.id == other_debug.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"DebugBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
64
src/boxes/integer_box.rs
Normal file
64
src/boxes/integer_box.rs
Normal file
@ -0,0 +1,64 @@
|
||||
// IntegerBox implementation - Integer values in Nyash
|
||||
use crate::box_trait::NyashBox;
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Integer values in Nyash - 64-bit signed integers
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct IntegerBox {
|
||||
pub value: i64,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl IntegerBox {
|
||||
pub fn new(value: i64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { value, id }
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::new(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for IntegerBox {
|
||||
fn to_string_box(&self) -> crate::box_trait::StringBox {
|
||||
crate::box_trait::StringBox::new(self.value.to_string())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
use crate::box_trait::BoolBox;
|
||||
if let Some(other_int) = other.as_any().downcast_ref::<IntegerBox>() {
|
||||
BoolBox::new(self.value == other_int.value)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"IntegerBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IntegerBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
175
src/boxes/map_box.rs
Normal file
175
src/boxes/map_box.rs
Normal file
@ -0,0 +1,175 @@
|
||||
/*!
|
||||
* Nyash Map Box - Key-Value store implementation
|
||||
*
|
||||
* キーバリューストアを提供するBox型
|
||||
* Everything is Box哲学に基づくマップデータ構造
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, ArrayBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// キーバリューストアを表すBox
|
||||
#[derive(Clone)]
|
||||
pub struct MapBox {
|
||||
data: Arc<Mutex<HashMap<String, Box<dyn NyashBox>>>>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl MapBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
data: Arc::new(Mutex::new(HashMap::new())),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 値を設定
|
||||
pub fn set(&self, key: Box<dyn NyashBox>, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
self.data.lock().unwrap().insert(key_str.clone(), value);
|
||||
Box::new(StringBox::new(&format!("Set key: {}", key_str)))
|
||||
}
|
||||
|
||||
/// 値を取得
|
||||
pub fn get(&self, key: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
match self.data.lock().unwrap().get(&key_str) {
|
||||
Some(value) => value.clone_box(),
|
||||
None => Box::new(StringBox::new(&format!("Key not found: {}", key_str))),
|
||||
}
|
||||
}
|
||||
|
||||
/// キーが存在するかチェック
|
||||
pub fn has(&self, key: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
Box::new(BoolBox::new(self.data.lock().unwrap().contains_key(&key_str)))
|
||||
}
|
||||
|
||||
/// キーを削除
|
||||
pub fn delete(&self, key: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
match self.data.lock().unwrap().remove(&key_str) {
|
||||
Some(_) => Box::new(StringBox::new(&format!("Deleted key: {}", key_str))),
|
||||
None => Box::new(StringBox::new(&format!("Key not found: {}", key_str))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 全てのキーを取得
|
||||
pub fn keys(&self) -> Box<dyn NyashBox> {
|
||||
let keys: Vec<String> = self.data.lock().unwrap().keys().cloned().collect();
|
||||
let array = ArrayBox::new();
|
||||
for key in keys {
|
||||
array.push(Box::new(StringBox::new(&key)));
|
||||
}
|
||||
Box::new(array)
|
||||
}
|
||||
|
||||
/// 全ての値を取得
|
||||
pub fn values(&self) -> Box<dyn NyashBox> {
|
||||
let values: Vec<Box<dyn NyashBox>> = self.data.lock().unwrap()
|
||||
.values()
|
||||
.map(|v| v.clone_box())
|
||||
.collect();
|
||||
let array = ArrayBox::new();
|
||||
for value in values {
|
||||
array.push(value);
|
||||
}
|
||||
Box::new(array)
|
||||
}
|
||||
|
||||
/// サイズを取得
|
||||
pub fn size(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.data.lock().unwrap().len() as i64))
|
||||
}
|
||||
|
||||
/// 全てクリア
|
||||
pub fn clear(&self) -> Box<dyn NyashBox> {
|
||||
self.data.lock().unwrap().clear();
|
||||
Box::new(StringBox::new("Map cleared"))
|
||||
}
|
||||
|
||||
/// 各要素に対して関数を実行
|
||||
pub fn forEach(&self, _callback: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
// 簡易実装:callbackの実行はスキップ
|
||||
let count = self.data.lock().unwrap().len();
|
||||
Box::new(StringBox::new(&format!("Iterated over {} items", count)))
|
||||
}
|
||||
|
||||
/// JSON文字列に変換
|
||||
pub fn toJSON(&self) -> Box<dyn NyashBox> {
|
||||
let data = self.data.lock().unwrap();
|
||||
let mut json_parts = Vec::new();
|
||||
|
||||
for (key, value) in data.iter() {
|
||||
let value_str = value.to_string_box().value;
|
||||
// 値が数値の場合はそのまま、文字列の場合は引用符で囲む
|
||||
let formatted_value = if value.as_any().downcast_ref::<IntegerBox>().is_some()
|
||||
|| value.as_any().downcast_ref::<BoolBox>().is_some() {
|
||||
value_str
|
||||
} else {
|
||||
format!("\"{}\"", value_str.replace("\"", "\\\""))
|
||||
};
|
||||
json_parts.push(format!("\"{}\":{}", key, formatted_value));
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("{{{}}}", json_parts.join(","))))
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for MapBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"MapBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
let size = self.data.lock().unwrap().len();
|
||||
StringBox::new(&format!("MapBox(size={})", size))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_map) = other.as_any().downcast_ref::<MapBox>() {
|
||||
// 同じインスタンスかチェック(データの共有を考慮)
|
||||
BoolBox::new(self.id == other_map.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MapBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_string_box().value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for MapBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let data = self.data.lock().unwrap();
|
||||
f.debug_struct("MapBox")
|
||||
.field("id", &self.id)
|
||||
.field("size", &data.len())
|
||||
.field("keys", &data.keys().collect::<Vec<_>>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
410
src/boxes/math_box.rs
Normal file
410
src/boxes/math_box.rs
Normal file
@ -0,0 +1,410 @@
|
||||
/*!
|
||||
* Nyash Math Box - Mathematical operations
|
||||
*
|
||||
* 数学演算を提供するBox型
|
||||
* Everything is Box哲学に基づく数学ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
|
||||
/// 数学演算を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MathBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl MathBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// 絶対値を計算
|
||||
pub fn abs(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value.abs()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.abs()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: abs() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 最大値を返す
|
||||
pub fn max(&self, a: Box<dyn NyashBox>, b: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(a_int), Some(b_int)) = (
|
||||
a.as_any().downcast_ref::<IntegerBox>(),
|
||||
b.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
Box::new(IntegerBox::new(a_int.value.max(b_int.value)))
|
||||
} else if let (Some(a_float), Some(b_float)) = (
|
||||
a.as_any().downcast_ref::<FloatBox>(),
|
||||
b.as_any().downcast_ref::<FloatBox>()
|
||||
) {
|
||||
Box::new(FloatBox::new(a_float.value.max(b_float.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: max() requires numeric inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 最小値を返す
|
||||
pub fn min(&self, a: Box<dyn NyashBox>, b: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(a_int), Some(b_int)) = (
|
||||
a.as_any().downcast_ref::<IntegerBox>(),
|
||||
b.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
Box::new(IntegerBox::new(a_int.value.min(b_int.value)))
|
||||
} else if let (Some(a_float), Some(b_float)) = (
|
||||
a.as_any().downcast_ref::<FloatBox>(),
|
||||
b.as_any().downcast_ref::<FloatBox>()
|
||||
) {
|
||||
Box::new(FloatBox::new(a_float.value.min(b_float.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: min() requires numeric inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 累乗を計算
|
||||
pub fn pow(&self, base: Box<dyn NyashBox>, exp: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(base_int), Some(exp_int)) = (
|
||||
base.as_any().downcast_ref::<IntegerBox>(),
|
||||
exp.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if exp_int.value >= 0 {
|
||||
let result = (base_int.value as f64).powi(exp_int.value as i32);
|
||||
Box::new(FloatBox::new(result))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: negative exponent"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: pow() requires numeric inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 平方根を計算
|
||||
pub fn sqrt(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value >= 0 {
|
||||
Box::new(FloatBox::new((int_box.value as f64).sqrt()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sqrt() of negative number"))
|
||||
}
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value >= 0.0 {
|
||||
Box::new(FloatBox::new(float_box.value.sqrt()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sqrt() of negative number"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sqrt() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 円周率πを返す
|
||||
#[allow(non_snake_case)]
|
||||
pub fn getPi(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(FloatBox::new(std::f64::consts::PI))
|
||||
}
|
||||
|
||||
/// 自然対数の底eを返す
|
||||
#[allow(non_snake_case)]
|
||||
pub fn getE(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(FloatBox::new(std::f64::consts::E))
|
||||
}
|
||||
|
||||
/// サイン(正弦)
|
||||
pub fn sin(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).sin()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.sin()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sin() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// コサイン(余弦)
|
||||
pub fn cos(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).cos()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.cos()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: cos() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// タンジェント(正接)
|
||||
pub fn tan(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).tan()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.tan()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: tan() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 自然対数
|
||||
pub fn log(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value > 0 {
|
||||
Box::new(FloatBox::new((int_box.value as f64).ln()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log() of non-positive number"))
|
||||
}
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value > 0.0 {
|
||||
Box::new(FloatBox::new(float_box.value.ln()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log() of non-positive number"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 常用対数(底10)
|
||||
pub fn log10(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value > 0 {
|
||||
Box::new(FloatBox::new((int_box.value as f64).log10()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log10() of non-positive number"))
|
||||
}
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value > 0.0 {
|
||||
Box::new(FloatBox::new(float_box.value.log10()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log10() of non-positive number"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log10() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 指数関数(e^x)
|
||||
pub fn exp(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).exp()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.exp()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: exp() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 床関数(切り下げ)
|
||||
pub fn floor(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value)) // 整数はそのまま
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(IntegerBox::new(float_box.value.floor() as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: floor() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 天井関数(切り上げ)
|
||||
pub fn ceil(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value)) // 整数はそのまま
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(IntegerBox::new(float_box.value.ceil() as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: ceil() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 四捨五入
|
||||
pub fn round(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value)) // 整数はそのまま
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(IntegerBox::new(float_box.value.round() as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: round() requires numeric input"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for MathBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"MathBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("MathBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_math) = other.as_any().downcast_ref::<MathBox>() {
|
||||
BoolBox::new(self.id == other_math.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MathBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "MathBox()")
|
||||
}
|
||||
}
|
||||
|
||||
/// 浮動小数点数Box
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatBox {
|
||||
pub value: f64,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl FloatBox {
|
||||
pub fn new(value: f64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { value, id }
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for FloatBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"FloatBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&self.value.to_string())
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_float) = other.as_any().downcast_ref::<FloatBox>() {
|
||||
BoolBox::new((self.value - other_float.value).abs() < f64::EPSILON)
|
||||
} else if let Some(other_int) = other.as_any().downcast_ref::<IntegerBox>() {
|
||||
BoolBox::new((self.value - other_int.value as f64).abs() < f64::EPSILON)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FloatBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
/// 範囲を表すBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RangeBox {
|
||||
pub start: i64,
|
||||
pub end: i64,
|
||||
pub step: i64,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl RangeBox {
|
||||
pub fn new(start: i64, end: i64, step: i64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { start, end, step, id }
|
||||
}
|
||||
|
||||
/// イテレータとして値を生成
|
||||
pub fn iter(&self) -> Vec<i64> {
|
||||
let mut result = Vec::new();
|
||||
let mut current = self.start;
|
||||
|
||||
if self.step > 0 {
|
||||
while current < self.end {
|
||||
result.push(current);
|
||||
current += self.step;
|
||||
}
|
||||
} else if self.step < 0 {
|
||||
while current > self.end {
|
||||
result.push(current);
|
||||
current += self.step;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for RangeBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"RangeBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&format!("Range({}, {}, {})", self.start, self.end, self.step))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_range) = other.as_any().downcast_ref::<RangeBox>() {
|
||||
BoolBox::new(
|
||||
self.start == other_range.start &&
|
||||
self.end == other_range.end &&
|
||||
self.step == other_range.step
|
||||
)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RangeBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Range({}, {}, {})", self.start, self.end, self.step)
|
||||
}
|
||||
}
|
||||
46
src/boxes/mod.rs
Normal file
46
src/boxes/mod.rs
Normal file
@ -0,0 +1,46 @@
|
||||
// Nyash Box Implementations Module
|
||||
// Everything is Box哲学に基づく各Box型の実装
|
||||
|
||||
// Nyashは意図的にJavaScript/TypeScriptスタイルのcamelCase命名規約を採用
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// 各Boxモジュールを宣言
|
||||
pub mod string_box;
|
||||
pub mod integer_box;
|
||||
pub mod bool_box;
|
||||
pub mod math_box;
|
||||
pub mod time_box;
|
||||
pub mod debug_box;
|
||||
pub mod random_box;
|
||||
pub mod sound_box;
|
||||
pub mod map_box;
|
||||
pub mod console_box;
|
||||
|
||||
// Web専用Box群(ブラウザ環境でのみ利用可能)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web;
|
||||
|
||||
// 共通で使う型とトレイトを再エクスポート
|
||||
pub use string_box::StringBox;
|
||||
pub use integer_box::IntegerBox;
|
||||
pub use bool_box::BoolBox;
|
||||
pub use math_box::MathBox;
|
||||
pub use time_box::TimeBox;
|
||||
pub use debug_box::DebugBox;
|
||||
pub use random_box::RandomBox;
|
||||
pub use sound_box::SoundBox;
|
||||
pub use map_box::MapBox;
|
||||
pub use console_box::ConsoleBox;
|
||||
|
||||
// Web Box群の再エクスポート(WASM環境のみ)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web::{WebDisplayBox, WebConsoleBox, WebCanvasBox};
|
||||
|
||||
pub mod null_box;
|
||||
|
||||
// 今後追加予定のBox型(コメントアウト)
|
||||
// pub mod array_box;
|
||||
// pub use array_box::ArrayBox;
|
||||
|
||||
// null関数も再エクスポート
|
||||
pub use null_box::{NullBox, null};
|
||||
149
src/boxes/null_box.rs
Normal file
149
src/boxes/null_box.rs
Normal file
@ -0,0 +1,149 @@
|
||||
/*!
|
||||
* Nyash Null Box - Null value representation
|
||||
*
|
||||
* null値を表現するBox型
|
||||
* Everything is Box哲学に基づくnull実装
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
|
||||
/// null値を表現するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NullBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl NullBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// null値かどうかを判定
|
||||
pub fn is_null(&self) -> bool {
|
||||
true // NullBoxは常にnull
|
||||
}
|
||||
|
||||
/// 値がnullでないかを判定
|
||||
pub fn is_not_null(&self) -> bool {
|
||||
false // NullBoxは常にnull
|
||||
}
|
||||
|
||||
/// 他の値がnullかどうかを判定
|
||||
pub fn check_null(value: &dyn NyashBox) -> bool {
|
||||
value.as_any().downcast_ref::<NullBox>().is_some()
|
||||
}
|
||||
|
||||
/// 他の値がnullでないかを判定
|
||||
pub fn check_not_null(value: &dyn NyashBox) -> bool {
|
||||
!Self::check_null(value)
|
||||
}
|
||||
|
||||
/// null安全な値の取得
|
||||
pub fn get_or_default(
|
||||
value: &dyn NyashBox,
|
||||
default: Box<dyn NyashBox>
|
||||
) -> Box<dyn NyashBox> {
|
||||
if Self::check_null(value) {
|
||||
default
|
||||
} else {
|
||||
value.clone_box()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for NullBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"NullBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("null")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
// すべてのNullBoxは等しい
|
||||
BoolBox::new(other.as_any().downcast_ref::<NullBox>().is_some())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NullBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "null")
|
||||
}
|
||||
}
|
||||
|
||||
// グローバルnullインスタンス用の関数
|
||||
pub fn null() -> Box<dyn NyashBox> {
|
||||
Box::new(NullBox::new())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::box_trait::IntegerBox;
|
||||
|
||||
#[test]
|
||||
fn test_null_creation() {
|
||||
let null_box = NullBox::new();
|
||||
assert!(null_box.is_null());
|
||||
assert!(!null_box.is_not_null());
|
||||
assert_eq!(null_box.to_string_box().value, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_check() {
|
||||
let null_box = null();
|
||||
let int_box = Box::new(IntegerBox::new(42));
|
||||
|
||||
assert!(NullBox::check_null(null_box.as_ref()));
|
||||
assert!(!NullBox::check_null(int_box.as_ref()));
|
||||
|
||||
assert!(!NullBox::check_not_null(null_box.as_ref()));
|
||||
assert!(NullBox::check_not_null(int_box.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_equality() {
|
||||
let null1 = NullBox::new();
|
||||
let null2 = NullBox::new();
|
||||
let int_box = IntegerBox::new(42);
|
||||
|
||||
assert!(null1.equals(&null2).value);
|
||||
assert!(!null1.equals(&int_box).value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_or_default() {
|
||||
let null_box = null();
|
||||
let default_value = Box::new(IntegerBox::new(100));
|
||||
let actual_value = Box::new(IntegerBox::new(42));
|
||||
|
||||
// nullの場合はデフォルト値を返す
|
||||
let result1 = NullBox::get_or_default(null_box.as_ref(), default_value.clone());
|
||||
assert_eq!(result1.to_string_box().value, "100");
|
||||
|
||||
// null以外の場合は元の値を返す
|
||||
let result2 = NullBox::get_or_default(actual_value.as_ref(), default_value);
|
||||
assert_eq!(result2.to_string_box().value, "42");
|
||||
}
|
||||
}
|
||||
225
src/boxes/random_box.rs
Normal file
225
src/boxes/random_box.rs
Normal file
@ -0,0 +1,225 @@
|
||||
/*!
|
||||
* Nyash Random Box - Random number generation
|
||||
*
|
||||
* 乱数生成を提供するBox型
|
||||
* Everything is Box哲学に基づく乱数ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, ArrayBox};
|
||||
use crate::boxes::math_box::FloatBox;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// 乱数生成を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RandomBox {
|
||||
// 簡易線形合同法による疑似乱数生成器
|
||||
seed: Arc<Mutex<u64>>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl RandomBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
// 現在時刻を種として使用
|
||||
let seed = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u64;
|
||||
|
||||
Self {
|
||||
seed: Arc::new(Mutex::new(seed)),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 種を設定
|
||||
pub fn seed(&self, new_seed: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = new_seed.as_any().downcast_ref::<IntegerBox>() {
|
||||
*self.seed.lock().unwrap() = int_box.value as u64;
|
||||
Box::new(StringBox::new("Seed set"))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: seed() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 次の乱数を生成(線形合同法)
|
||||
fn next_random(&self) -> u64 {
|
||||
let mut seed = self.seed.lock().unwrap();
|
||||
// 線形合同法の定数(Numerical Recipes より)
|
||||
*seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||
*seed
|
||||
}
|
||||
|
||||
/// 0.0-1.0の浮動小数点乱数
|
||||
pub fn random(&self) -> Box<dyn NyashBox> {
|
||||
let r = self.next_random();
|
||||
let normalized = (r as f64) / (u64::MAX as f64);
|
||||
Box::new(FloatBox::new(normalized))
|
||||
}
|
||||
|
||||
/// 指定範囲の整数乱数
|
||||
pub fn randInt(&self, min: Box<dyn NyashBox>, max: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(min_int), Some(max_int)) = (
|
||||
min.as_any().downcast_ref::<IntegerBox>(),
|
||||
max.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if min_int.value > max_int.value {
|
||||
return Box::new(StringBox::new("Error: min must be <= max"));
|
||||
}
|
||||
|
||||
let range = (max_int.value - min_int.value + 1) as u64;
|
||||
let r = self.next_random() % range;
|
||||
Box::new(IntegerBox::new(min_int.value + r as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: randInt() requires two integer inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// true/falseのランダム選択
|
||||
pub fn randBool(&self) -> Box<dyn NyashBox> {
|
||||
let r = self.next_random();
|
||||
Box::new(BoolBox::new(r % 2 == 0))
|
||||
}
|
||||
|
||||
/// 配列からランダム選択
|
||||
pub fn choice(&self, array: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(array_box) = array.as_any().downcast_ref::<ArrayBox>() {
|
||||
let length = array_box.length().to_string_box().value.parse::<i64>().unwrap_or(0);
|
||||
if length == 0 {
|
||||
return Box::new(StringBox::new("Error: cannot choose from empty array"));
|
||||
}
|
||||
|
||||
let index = self.next_random() % (length as u64);
|
||||
match array_box.get(index as usize) {
|
||||
Some(element) => element,
|
||||
None => Box::new(StringBox::new("Error: index out of bounds")),
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: choice() requires array input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 配列をシャッフル
|
||||
pub fn shuffle(&self, array: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(array_box) = array.as_any().downcast_ref::<ArrayBox>() {
|
||||
let length = array_box.length().to_string_box().value.parse::<i64>().unwrap_or(0);
|
||||
if length <= 1 {
|
||||
return array;
|
||||
}
|
||||
|
||||
// 新しい配列を作成
|
||||
let shuffled = ArrayBox::new();
|
||||
|
||||
// 元の配列の要素を全て新しい配列にコピー
|
||||
for i in 0..length {
|
||||
if let Some(element) = array_box.get(i as usize) {
|
||||
shuffled.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
// 簡易シャッフル実装(完全なFisher-Yatesは複雑なので)
|
||||
// 代わりに、元の配列からランダムに選んで新しい配列を作る
|
||||
let result = ArrayBox::new();
|
||||
let mut remaining_indices: Vec<usize> = (0..length as usize).collect();
|
||||
|
||||
while !remaining_indices.is_empty() {
|
||||
let random_idx = (self.next_random() % remaining_indices.len() as u64) as usize;
|
||||
let actual_idx = remaining_indices.remove(random_idx);
|
||||
if let Some(element) = array_box.get(actual_idx) {
|
||||
result.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(result)
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: shuffle() requires array input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// ランダムな文字列生成
|
||||
pub fn randString(&self, length: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(len_int) = length.as_any().downcast_ref::<IntegerBox>() {
|
||||
if len_int.value < 0 {
|
||||
return Box::new(StringBox::new("Error: length must be positive"));
|
||||
}
|
||||
|
||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let char_vec: Vec<char> = chars.chars().collect();
|
||||
let mut result = String::new();
|
||||
|
||||
for _ in 0..len_int.value {
|
||||
let index = self.next_random() % (char_vec.len() as u64);
|
||||
result.push(char_vec[index as usize]);
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&result))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: randString() requires integer length"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定確率でtrue
|
||||
pub fn probability(&self, prob: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(float_box) = prob.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value < 0.0 || float_box.value > 1.0 {
|
||||
return Box::new(StringBox::new("Error: probability must be 0.0-1.0"));
|
||||
}
|
||||
|
||||
let r = self.next_random() as f64 / u64::MAX as f64;
|
||||
Box::new(BoolBox::new(r < float_box.value))
|
||||
} else if let Some(int_box) = prob.as_any().downcast_ref::<IntegerBox>() {
|
||||
let prob_val = int_box.value as f64;
|
||||
if prob_val < 0.0 || prob_val > 1.0 {
|
||||
return Box::new(StringBox::new("Error: probability must be 0.0-1.0"));
|
||||
}
|
||||
|
||||
let r = self.next_random() as f64 / u64::MAX as f64;
|
||||
Box::new(BoolBox::new(r < prob_val))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: probability() requires numeric input"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for RandomBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"RandomBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("RandomBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_random) = other.as_any().downcast_ref::<RandomBox>() {
|
||||
BoolBox::new(self.id == other_random.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RandomBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "RandomBox()")
|
||||
}
|
||||
}
|
||||
221
src/boxes/sound_box.rs
Normal file
221
src/boxes/sound_box.rs
Normal file
@ -0,0 +1,221 @@
|
||||
/*!
|
||||
* Nyash Sound Box - Simple sound generation
|
||||
*
|
||||
* 音響効果を提供するBox型
|
||||
* Everything is Box哲学に基づく音響ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
|
||||
/// 音響効果を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SoundBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl SoundBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// ビープ音を鳴らす(基本)
|
||||
pub fn beep(&self) -> Box<dyn NyashBox> {
|
||||
// 端末ベル文字を出力
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Beep!"))
|
||||
}
|
||||
|
||||
/// 指定回数ビープ
|
||||
pub fn beeps(&self, count: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(count_int) = count.as_any().downcast_ref::<IntegerBox>() {
|
||||
if count_int.value <= 0 {
|
||||
return Box::new(StringBox::new("Beep count must be positive"));
|
||||
}
|
||||
|
||||
for i in 0..count_int.value {
|
||||
print!("\x07");
|
||||
if i < count_int.value - 1 {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("Beeped {} times", count_int.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: beeps() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定周波数のビープ(Linuxのみ)
|
||||
pub fn tone(&self, frequency: Box<dyn NyashBox>, duration: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(freq_int), Some(dur_int)) = (
|
||||
frequency.as_any().downcast_ref::<IntegerBox>(),
|
||||
duration.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if freq_int.value <= 0 || dur_int.value <= 0 {
|
||||
return Box::new(StringBox::new("Frequency and duration must be positive"));
|
||||
}
|
||||
|
||||
// Linuxのbeepコマンドを試行
|
||||
match Command::new("beep")
|
||||
.arg("-f")
|
||||
.arg(&freq_int.value.to_string())
|
||||
.arg("-l")
|
||||
.arg(&dur_int.value.to_string())
|
||||
.output()
|
||||
{
|
||||
Ok(_) => Box::new(StringBox::new(&format!("Played {}Hz for {}ms", freq_int.value, dur_int.value))),
|
||||
Err(_) => {
|
||||
// beepコマンドが無い場合は端末ベルを使用
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(dur_int.value as u64));
|
||||
Box::new(StringBox::new(&format!("Fallback beep ({}Hz, {}ms)", freq_int.value, dur_int.value)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: tone() requires two integer inputs (frequency, duration)"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 警告音
|
||||
pub fn alert(&self) -> Box<dyn NyashBox> {
|
||||
// 3回短いビープ
|
||||
for i in 0..3 {
|
||||
print!("\x07");
|
||||
if i < 2 {
|
||||
std::thread::sleep(Duration::from_millis(150));
|
||||
}
|
||||
}
|
||||
Box::new(StringBox::new("Alert sound played"))
|
||||
}
|
||||
|
||||
/// 成功音
|
||||
pub fn success(&self) -> Box<dyn NyashBox> {
|
||||
// 1回長めのビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Success sound played"))
|
||||
}
|
||||
|
||||
/// エラー音
|
||||
pub fn error(&self) -> Box<dyn NyashBox> {
|
||||
// 2回素早いビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(80));
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Error sound played"))
|
||||
}
|
||||
|
||||
/// カスタムビープパターン
|
||||
pub fn pattern(&self, pattern: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(pattern_str) = pattern.as_any().downcast_ref::<StringBox>() {
|
||||
let mut beep_count = 0;
|
||||
|
||||
for ch in pattern_str.value.chars() {
|
||||
match ch {
|
||||
'.' => {
|
||||
// 短いビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
beep_count += 1;
|
||||
}
|
||||
'-' => {
|
||||
// 長いビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
beep_count += 1;
|
||||
}
|
||||
' ' => {
|
||||
// 無音(待機)
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
}
|
||||
_ => {
|
||||
// その他の文字は無視
|
||||
}
|
||||
}
|
||||
|
||||
// 文字間の短い間隔
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("Played pattern '{}' ({} beeps)", pattern_str.value, beep_count)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: pattern() requires string input (use '.' for short, '-' for long, ' ' for pause)"))
|
||||
}
|
||||
}
|
||||
|
||||
/// システム音量チェック(簡易)
|
||||
pub fn volumeTest(&self) -> Box<dyn NyashBox> {
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Volume test beep - can you hear it?"))
|
||||
}
|
||||
|
||||
/// 指定間隔でビープ
|
||||
pub fn interval(&self, times: Box<dyn NyashBox>, interval_ms: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(times_int), Some(interval_int)) = (
|
||||
times.as_any().downcast_ref::<IntegerBox>(),
|
||||
interval_ms.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if times_int.value <= 0 || interval_int.value < 0 {
|
||||
return Box::new(StringBox::new("Times must be positive, interval must be non-negative"));
|
||||
}
|
||||
|
||||
for i in 0..times_int.value {
|
||||
print!("\x07");
|
||||
if i < times_int.value - 1 {
|
||||
std::thread::sleep(Duration::from_millis(interval_int.value as u64));
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("Played {} beeps with {}ms intervals", times_int.value, interval_int.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: interval() requires two integer inputs (times, interval_ms)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for SoundBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"SoundBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("SoundBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_sound) = other.as_any().downcast_ref::<SoundBox>() {
|
||||
BoolBox::new(self.id == other_sound.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SoundBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SoundBox()")
|
||||
}
|
||||
}
|
||||
141
src/boxes/string_box.rs
Normal file
141
src/boxes/string_box.rs
Normal file
@ -0,0 +1,141 @@
|
||||
// StringBox implementation - String values in Nyash
|
||||
use crate::box_trait::NyashBox;
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// String values in Nyash - UTF-8 encoded text
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct StringBox {
|
||||
pub value: String,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl StringBox {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
value: value.into(),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self::new("")
|
||||
}
|
||||
|
||||
// ===== String Methods for Nyash =====
|
||||
|
||||
/// Split string by delimiter and return ArrayBox
|
||||
pub fn split(&self, delimiter: &str) -> Box<dyn NyashBox> {
|
||||
use crate::box_trait::ArrayBox;
|
||||
let parts: Vec<String> = self.value.split(delimiter).map(|s| s.to_string()).collect();
|
||||
let array_elements: Vec<Box<dyn NyashBox>> = parts.into_iter()
|
||||
.map(|s| Box::new(StringBox::new(s)) as Box<dyn NyashBox>)
|
||||
.collect();
|
||||
Box::new(ArrayBox::new_with_elements(array_elements))
|
||||
}
|
||||
|
||||
/// Find substring and return position (or -1 if not found)
|
||||
pub fn find(&self, search: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::IntegerBox;
|
||||
match self.value.find(search) {
|
||||
Some(pos) => Box::new(IntegerBox::new(pos as i64)),
|
||||
None => Box::new(IntegerBox::new(-1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace all occurrences of old with new
|
||||
pub fn replace(&self, old: &str, new: &str) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.replace(old, new)))
|
||||
}
|
||||
|
||||
/// Trim whitespace from both ends
|
||||
pub fn trim(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.trim()))
|
||||
}
|
||||
|
||||
/// Convert to uppercase
|
||||
pub fn to_upper(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.to_uppercase()))
|
||||
}
|
||||
|
||||
/// Convert to lowercase
|
||||
pub fn to_lower(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.to_lowercase()))
|
||||
}
|
||||
|
||||
/// Check if string contains substring
|
||||
pub fn contains(&self, search: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::BoolBox;
|
||||
Box::new(BoolBox::new(self.value.contains(search)))
|
||||
}
|
||||
|
||||
/// Check if string starts with prefix
|
||||
pub fn starts_with(&self, prefix: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::BoolBox;
|
||||
Box::new(BoolBox::new(self.value.starts_with(prefix)))
|
||||
}
|
||||
|
||||
/// Check if string ends with suffix
|
||||
pub fn ends_with(&self, suffix: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::BoolBox;
|
||||
Box::new(BoolBox::new(self.value.ends_with(suffix)))
|
||||
}
|
||||
|
||||
/// Join array elements using this string as delimiter
|
||||
pub fn join(&self, array_box: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
use crate::box_trait::ArrayBox;
|
||||
if let Some(array) = array_box.as_any().downcast_ref::<ArrayBox>() {
|
||||
let strings: Vec<String> = array.elements.lock().unwrap()
|
||||
.iter()
|
||||
.map(|element| element.to_string_box().value)
|
||||
.collect();
|
||||
Box::new(StringBox::new(strings.join(&self.value)))
|
||||
} else {
|
||||
// If not an ArrayBox, treat as single element
|
||||
Box::new(StringBox::new(array_box.to_string_box().value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for StringBox {
|
||||
fn to_string_box(&self) -> crate::box_trait::StringBox {
|
||||
crate::box_trait::StringBox::new(self.value.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
use crate::box_trait::BoolBox;
|
||||
if let Some(other_string) = other.as_any().downcast_ref::<StringBox>() {
|
||||
BoolBox::new(self.value == other_string.value)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"StringBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StringBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
366
src/boxes/time_box.rs
Normal file
366
src/boxes/time_box.rs
Normal file
@ -0,0 +1,366 @@
|
||||
/*!
|
||||
* Nyash Time Box - Time and Date operations
|
||||
*
|
||||
* 時間と日付操作を提供するBox型
|
||||
* Everything is Box哲学に基づく時間ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::time::{SystemTime, Duration};
|
||||
use chrono::{DateTime, Local, TimeZone, Datelike, Timelike};
|
||||
|
||||
/// 時間操作を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl TimeBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// 現在時刻を取得
|
||||
pub fn now(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(DateTimeBox::now())
|
||||
}
|
||||
|
||||
/// UNIXタイムスタンプから日時を作成
|
||||
pub fn fromTimestamp(&self, timestamp: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = timestamp.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(DateTimeBox::from_timestamp(int_box.value))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: fromTimestamp() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 日時文字列をパース
|
||||
pub fn parse(&self, date_str: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(string_box) = date_str.as_any().downcast_ref::<StringBox>() {
|
||||
match DateTimeBox::parse(&string_box.value) {
|
||||
Ok(dt) => Box::new(dt),
|
||||
Err(e) => Box::new(StringBox::new(&format!("Error: {}", e))),
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: parse() requires string input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// ミリ秒スリープ
|
||||
pub fn sleep(&self, millis: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = millis.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value > 0 {
|
||||
std::thread::sleep(Duration::from_millis(int_box.value as u64));
|
||||
Box::new(StringBox::new("ok"))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sleep() requires positive milliseconds"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sleep() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 現在時刻をフォーマット
|
||||
pub fn format(&self, format_str: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(str_box) = format_str.as_any().downcast_ref::<StringBox>() {
|
||||
let now = Local::now();
|
||||
let formatted = now.format(&str_box.value).to_string();
|
||||
Box::new(StringBox::new(formatted))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: format() requires string format pattern"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for TimeBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"TimeBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("TimeBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_time) = other.as_any().downcast_ref::<TimeBox>() {
|
||||
BoolBox::new(self.id == other_time.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TimeBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "TimeBox()")
|
||||
}
|
||||
}
|
||||
|
||||
/// 日時を表すBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DateTimeBox {
|
||||
pub datetime: DateTime<Local>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl DateTimeBox {
|
||||
/// 現在時刻で作成
|
||||
pub fn now() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
datetime: Local::now(),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// UNIXタイムスタンプから作成
|
||||
pub fn from_timestamp(timestamp: i64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
let datetime = Local.timestamp_opt(timestamp, 0).unwrap();
|
||||
Self { datetime, id }
|
||||
}
|
||||
|
||||
/// 文字列からパース
|
||||
pub fn parse(date_str: &str) -> Result<Self, String> {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
// ISO 8601形式でパース
|
||||
match DateTime::parse_from_rfc3339(date_str) {
|
||||
Ok(dt) => Ok(Self {
|
||||
datetime: dt.with_timezone(&Local),
|
||||
id,
|
||||
}),
|
||||
Err(_) => {
|
||||
// シンプルな形式でパース (YYYY-MM-DD HH:MM:SS)
|
||||
match chrono::NaiveDateTime::parse_from_str(date_str, "%Y-%m-%d %H:%M:%S") {
|
||||
Ok(naive_dt) => {
|
||||
let datetime = Local.from_local_datetime(&naive_dt).unwrap();
|
||||
Ok(Self { datetime, id })
|
||||
}
|
||||
Err(e) => Err(format!("Failed to parse date: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 年を取得
|
||||
pub fn year(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.year() as i64))
|
||||
}
|
||||
|
||||
/// 月を取得
|
||||
pub fn month(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.month() as i64))
|
||||
}
|
||||
|
||||
/// 日を取得
|
||||
pub fn day(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.day() as i64))
|
||||
}
|
||||
|
||||
/// 時を取得
|
||||
pub fn hour(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.hour() as i64))
|
||||
}
|
||||
|
||||
/// 分を取得
|
||||
pub fn minute(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.minute() as i64))
|
||||
}
|
||||
|
||||
/// 秒を取得
|
||||
pub fn second(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.second() as i64))
|
||||
}
|
||||
|
||||
/// UNIXタイムスタンプを取得
|
||||
pub fn timestamp(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.timestamp()))
|
||||
}
|
||||
|
||||
/// ISO 8601形式でフォーマット
|
||||
pub fn toISOString(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(&self.datetime.to_rfc3339()))
|
||||
}
|
||||
|
||||
/// カスタムフォーマット
|
||||
pub fn format(&self, fmt: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(string_box) = fmt.as_any().downcast_ref::<StringBox>() {
|
||||
let formatted = self.datetime.format(&string_box.value).to_string();
|
||||
Box::new(StringBox::new(&formatted))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: format() requires string input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 日付を加算
|
||||
pub fn addDays(&self, days: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = days.as_any().downcast_ref::<IntegerBox>() {
|
||||
let new_datetime = self.datetime + chrono::Duration::days(int_box.value);
|
||||
Box::new(DateTimeBox {
|
||||
datetime: new_datetime,
|
||||
id: self.id,
|
||||
})
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: addDays() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 時間を加算
|
||||
pub fn addHours(&self, hours: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = hours.as_any().downcast_ref::<IntegerBox>() {
|
||||
let new_datetime = self.datetime + chrono::Duration::hours(int_box.value);
|
||||
Box::new(DateTimeBox {
|
||||
datetime: new_datetime,
|
||||
id: self.id,
|
||||
})
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: addHours() requires integer input"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for DateTimeBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"DateTimeBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&self.datetime.format("%Y-%m-%d %H:%M:%S").to_string())
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_dt) = other.as_any().downcast_ref::<DateTimeBox>() {
|
||||
BoolBox::new(self.datetime == other_dt.datetime)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DateTimeBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.datetime.format("%Y-%m-%d %H:%M:%S"))
|
||||
}
|
||||
}
|
||||
|
||||
/// タイマーを表すBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimerBox {
|
||||
start_time: SystemTime,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl TimerBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
start_time: SystemTime::now(),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 経過時間をミリ秒で取得
|
||||
pub fn elapsed(&self) -> Box<dyn NyashBox> {
|
||||
match self.start_time.elapsed() {
|
||||
Ok(duration) => {
|
||||
let millis = duration.as_millis() as i64;
|
||||
Box::new(IntegerBox::new(millis))
|
||||
}
|
||||
Err(_) => Box::new(IntegerBox::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// タイマーをリセット
|
||||
pub fn reset(&mut self) -> Box<dyn NyashBox> {
|
||||
self.start_time = SystemTime::now();
|
||||
Box::new(StringBox::new("Timer reset"))
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for TimerBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"TimerBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("TimerBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_timer) = other.as_any().downcast_ref::<TimerBox>() {
|
||||
BoolBox::new(self.id == other_timer.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TimerBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "TimerBox()")
|
||||
}
|
||||
}
|
||||
24
src/boxes/web/mod.rs
Normal file
24
src/boxes/web/mod.rs
Normal file
@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* Web Boxes Module - ブラウザ専用Box群
|
||||
*
|
||||
* WebAssembly環境専用のBox群を管理
|
||||
* HTML5 APIs、DOM操作、Canvas描画等をNyashから利用可能にする
|
||||
*/
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web_display_box;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web_console_box;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web_canvas_box;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_display_box::WebDisplayBox;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_console_box::WebConsoleBox;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_canvas_box::WebCanvasBox;
|
||||
304
src/boxes/web/web_canvas_box.rs
Normal file
304
src/boxes/web/web_canvas_box.rs
Normal file
@ -0,0 +1,304 @@
|
||||
/*!
|
||||
* WebCanvasBox - ブラウザCanvas完全制御Box
|
||||
*
|
||||
* WebAssembly環境でHTML5 Canvasの完全制御
|
||||
* ピクセルの世界を制圧する革命的Box!
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{
|
||||
HtmlCanvasElement,
|
||||
CanvasRenderingContext2d,
|
||||
};
|
||||
|
||||
// 🎨 Browser Canvas complete control Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebCanvasBox {
|
||||
id: u64,
|
||||
canvas_id: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl WebCanvasBox {
|
||||
pub fn new(canvas_id: String, width: u32, height: u32) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
let instance = Self {
|
||||
id,
|
||||
canvas_id: canvas_id.clone(),
|
||||
width,
|
||||
height,
|
||||
};
|
||||
|
||||
// キャンバス要素を初期化
|
||||
if let Some(canvas) = instance.get_canvas_element() {
|
||||
canvas.set_width(width);
|
||||
canvas.set_height(height);
|
||||
}
|
||||
|
||||
instance
|
||||
}
|
||||
|
||||
/// Canvas要素を取得
|
||||
fn get_canvas_element(&self) -> Option<HtmlCanvasElement> {
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
let element = document.get_element_by_id(&self.canvas_id)?;
|
||||
element.dyn_into::<HtmlCanvasElement>().ok()
|
||||
}
|
||||
|
||||
/// 2Dレンダリングコンテキストを取得
|
||||
fn get_2d_context(&self) -> Option<CanvasRenderingContext2d> {
|
||||
let canvas = self.get_canvas_element()?;
|
||||
canvas
|
||||
.get_context("2d")
|
||||
.ok()?
|
||||
.and_then(|ctx| ctx.dyn_into::<CanvasRenderingContext2d>().ok())
|
||||
}
|
||||
|
||||
/// キャンバスをクリア
|
||||
pub fn clear(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.clear_rect(0.0, 0.0, self.width as f64, self.height as f64);
|
||||
}
|
||||
}
|
||||
|
||||
/// 塗りつぶし色を設定
|
||||
pub fn set_fill_style(&self, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
}
|
||||
}
|
||||
|
||||
/// 線の色を設定
|
||||
pub fn set_stroke_style(&self, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
}
|
||||
}
|
||||
|
||||
/// 線の太さを設定
|
||||
pub fn set_line_width(&self, width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_line_width(width);
|
||||
}
|
||||
}
|
||||
|
||||
/// 塗りつぶし矩形を描画
|
||||
pub fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.fill_rect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// 枠線矩形を描画
|
||||
pub fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.stroke_rect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// 塗りつぶし円を描画
|
||||
pub fn fill_circle(&self, x: f64, y: f64, radius: f64, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.begin_path();
|
||||
ctx.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI).unwrap_or_default();
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
/// 枠線円を描画
|
||||
pub fn stroke_circle(&self, x: f64, y: f64, radius: f64, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.begin_path();
|
||||
ctx.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI).unwrap_or_default();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/// 直線を描画
|
||||
pub fn draw_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.begin_path();
|
||||
ctx.move_to(x1, y1);
|
||||
ctx.line_to(x2, y2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/// テキストを描画(塗りつぶし)
|
||||
pub fn fill_text(&self, text: &str, x: f64, y: f64, font: &str, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_font(font);
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.fill_text(text, x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// テキストを描画(枠線)
|
||||
pub fn stroke_text(&self, text: &str, x: f64, y: f64, font: &str, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_font(font);
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.stroke_text(text, x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// パス描画開始
|
||||
pub fn begin_path(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.begin_path();
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを指定位置に移動
|
||||
pub fn move_to(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.move_to(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// パスに直線を追加
|
||||
pub fn line_to(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.line_to(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを閉じる
|
||||
pub fn close_path(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.close_path();
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを塗りつぶし
|
||||
pub fn fill(&self, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを枠線描画
|
||||
pub fn stroke(&self, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/// 現在の描画状態を保存
|
||||
pub fn save(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.save();
|
||||
}
|
||||
}
|
||||
|
||||
/// 描画状態を復元
|
||||
pub fn restore(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/// 座標系を回転
|
||||
pub fn rotate(&self, angle: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.rotate(angle).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// 座標系をスケール
|
||||
pub fn scale(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.scale(x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// 座標系を平行移動
|
||||
pub fn translate(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.translate(x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// キャンバスのサイズを取得
|
||||
pub fn get_width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn get_height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
/// キャンバスのサイズを変更
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
|
||||
if let Some(canvas) = self.get_canvas_element() {
|
||||
canvas.set_width(width);
|
||||
canvas.set_height(height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for WebCanvasBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!(
|
||||
"WebCanvasBox({}, {}x{})",
|
||||
self.canvas_id,
|
||||
self.width,
|
||||
self.height
|
||||
))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"WebCanvasBox"
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_canvas) = other.as_any().downcast_ref::<WebCanvasBox>() {
|
||||
BoolBox::new(self.id == other_canvas.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/boxes/web/web_console_box.rs
Normal file
175
src/boxes/web/web_console_box.rs
Normal file
@ -0,0 +1,175 @@
|
||||
/*!
|
||||
* WebConsoleBox - ブラウザHTML要素コンソール出力Box
|
||||
*
|
||||
* WebAssembly環境でHTML要素へのコンソール風出力
|
||||
* F12コンソールの代わりに指定要素に出力
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{Element, HtmlElement};
|
||||
|
||||
// 🌐 Browser HTML element console output Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebConsoleBox {
|
||||
id: u64,
|
||||
target_element_id: String,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl WebConsoleBox {
|
||||
pub fn new(element_id: String) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self {
|
||||
id,
|
||||
target_element_id: element_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定した要素IDのHTML要素を取得
|
||||
fn get_target_element(&self) -> Option<Element> {
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
document.get_element_by_id(&self.target_element_id)
|
||||
}
|
||||
|
||||
/// コンソール出力を追加(改行付き)
|
||||
fn append_console_line(&self, message: &str, level: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let timestamp = js_sys::Date::new_0().to_iso_string().as_string().unwrap_or_default();
|
||||
let time_part = timestamp.split('T').nth(1).unwrap_or("00:00:00").split('.').nth(0).unwrap_or("00:00:00");
|
||||
|
||||
let (level_prefix, color) = match level {
|
||||
"log" => ("📝", "white"),
|
||||
"warn" => ("⚠️", "yellow"),
|
||||
"error" => ("❌", "red"),
|
||||
"info" => ("ℹ️", "cyan"),
|
||||
"debug" => ("🔍", "gray"),
|
||||
_ => ("📝", "white"),
|
||||
};
|
||||
|
||||
let formatted_line = format!(
|
||||
"<span style='color: {}'>[{}] {} {}</span><br>",
|
||||
color,
|
||||
time_part,
|
||||
level_prefix,
|
||||
message
|
||||
);
|
||||
|
||||
let current_content = element.inner_html();
|
||||
let new_content = format!("{}{}", current_content, formatted_line);
|
||||
element.set_inner_html(&new_content);
|
||||
|
||||
// 自動スクロール
|
||||
if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
|
||||
html_element.set_scroll_top(html_element.scroll_height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ログメッセージを出力
|
||||
pub fn log(&self, message: &str) {
|
||||
self.append_console_line(message, "log");
|
||||
}
|
||||
|
||||
/// 警告メッセージを出力
|
||||
pub fn warn(&self, message: &str) {
|
||||
self.append_console_line(message, "warn");
|
||||
}
|
||||
|
||||
/// エラーメッセージを出力
|
||||
pub fn error(&self, message: &str) {
|
||||
self.append_console_line(message, "error");
|
||||
}
|
||||
|
||||
/// 情報メッセージを出力
|
||||
pub fn info(&self, message: &str) {
|
||||
self.append_console_line(message, "info");
|
||||
}
|
||||
|
||||
/// デバッグメッセージを出力
|
||||
pub fn debug(&self, message: &str) {
|
||||
self.append_console_line(message, "debug");
|
||||
}
|
||||
|
||||
/// コンソールをクリア
|
||||
pub fn clear(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
element.set_inner_html("");
|
||||
}
|
||||
}
|
||||
|
||||
/// 区切り線を追加
|
||||
pub fn separator(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let separator_line = "<hr style='border: 1px solid #333; margin: 5px 0;'>";
|
||||
let new_content = format!("{}{}", current_content, separator_line);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// グループ開始(見出し付き)
|
||||
pub fn group(&self, title: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let group_header = format!(
|
||||
"<div style='font-weight: bold; color: #4ecdc4; margin: 10px 0 5px 0;'>📂 {}</div><div style='margin-left: 20px; color: white;'>",
|
||||
title
|
||||
);
|
||||
let new_content = format!("{}{}", current_content, group_header);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// グループ終了
|
||||
pub fn group_end(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let group_footer = "</div>";
|
||||
let new_content = format!("{}{}", current_content, group_footer);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for WebConsoleBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("WebConsoleBox({})", self.target_element_id))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"WebConsoleBox"
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_console) = other.as_any().downcast_ref::<WebConsoleBox>() {
|
||||
BoolBox::new(self.id == other_console.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/boxes/web/web_display_box.rs
Normal file
169
src/boxes/web/web_display_box.rs
Normal file
@ -0,0 +1,169 @@
|
||||
/*!
|
||||
* WebDisplayBox - ブラウザHTML要素表示制御Box
|
||||
*
|
||||
* WebAssembly環境でHTML要素への直接出力・スタイル制御
|
||||
* プレイグラウンドの出力パネル等を完全制御
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{Element, HtmlElement};
|
||||
|
||||
// 🌐 Browser HTML element display control Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebDisplayBox {
|
||||
id: u64,
|
||||
target_element_id: String,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl WebDisplayBox {
|
||||
pub fn new(element_id: String) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self {
|
||||
id,
|
||||
target_element_id: element_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定した要素IDのHTML要素を取得
|
||||
fn get_target_element(&self) -> Option<Element> {
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
document.get_element_by_id(&self.target_element_id)
|
||||
}
|
||||
|
||||
/// テキストを追加出力
|
||||
pub fn print(&self, message: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let new_content = if current_content.is_empty() {
|
||||
message.to_string()
|
||||
} else {
|
||||
format!("{}{}", current_content, message)
|
||||
};
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// テキストを改行付きで追加出力
|
||||
pub fn println(&self, message: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let new_content = if current_content.is_empty() {
|
||||
message.to_string()
|
||||
} else {
|
||||
format!("{}<br>{}", current_content, message)
|
||||
};
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// HTMLコンテンツを完全置換
|
||||
pub fn set_html(&self, html_content: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
element.set_inner_html(html_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// HTMLコンテンツを追加
|
||||
pub fn append_html(&self, html_content: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let new_content = format!("{}{}", current_content, html_content);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// CSSスタイルを設定
|
||||
pub fn set_css(&self, property: &str, value: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
|
||||
// HTMLElement の style プロパティへアクセス
|
||||
let _ = html_element.style().set_property(property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CSSクラスを追加
|
||||
pub fn add_class(&self, class_name: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let _ = element.class_list().add_1(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// CSSクラスを削除
|
||||
pub fn remove_class(&self, class_name: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let _ = element.class_list().remove_1(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// 内容をクリア
|
||||
pub fn clear(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
element.set_inner_html("");
|
||||
}
|
||||
}
|
||||
|
||||
/// 要素を表示
|
||||
pub fn show(&self) {
|
||||
self.set_css("display", "block");
|
||||
}
|
||||
|
||||
/// 要素を非表示
|
||||
pub fn hide(&self) {
|
||||
self.set_css("display", "none");
|
||||
}
|
||||
|
||||
/// スクロールを最下部に移動
|
||||
pub fn scroll_to_bottom(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
|
||||
html_element.set_scroll_top(html_element.scroll_height());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for WebDisplayBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("WebDisplayBox({})", self.target_element_id))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"WebDisplayBox"
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_display) = other.as_any().downcast_ref::<WebDisplayBox>() {
|
||||
BoolBox::new(self.id == other_display.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user