🎨 feat: EguiBox GUI開発基盤完成 + パーサー無限ループバグ修正
## 🚀 主要機能追加 ### EguiBox - GUI開発基盤 - Windows版GUIメモ帳アプリ (simple_notepad.rs, nyash_notepad_jp.rs) - 日本語フォント対応 (NotoSansJP-VariableFont_wght.ttf) - BMPアイコン表示システム (c_drive_icon.bmp) - Windowsエクスプローラー風アプリ (nyash_explorer.rs) - アイコン抽出システム (test_icon_extraction.rs) ### ビジュアルプログラミング準備 - NyashFlow プロジェクト設計完成 (NYASHFLOW_PROJECT_HANDOVER.md) - ビジュアルノードプロトタイプ基盤 - WebAssembly対応準備 ## 🔧 重大バグ修正 ### パーサー無限ループ問題 (3引数メソッド呼び出し) - 原因: メソッドパラメータ解析ループの予約語処理不備 - 修正: src/parser/mod.rs - 非IDENTIFIERトークンのエラーハンドリング追加 - 効果: "from"等の予約語で適切なエラー報告、ハング→瞬時エラー ### MapBoxハング問題調査 - MapBox+3引数メソッド呼び出し組み合わせ問題特定 - バグレポート作成 (MAPBOX_HANG_BUG_REPORT.md) - 事前評価vs必要時評価の設計問題明確化 ## 🧹 コード品質向上 - box_methods.rs を8モジュールに機能分離 - 一時デバッグコード全削除 (eprintln\!, unsafe等) - 構文チェック通過確認済み ## 📝 ドキュメント整備 - CLAUDE.md にGUI開発セクション追加 - Gemini/ChatGPT先生相談ログ保存 (sessions/) - 段階的デバッグ手法確立 ## 🎯 次の目標 - must_advance\!マクロ実装 (無限ループ早期検出) - コマンド引数でデバッグ制御 (--debug-fuel) - MapBox問題の根本修正 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
165
src/boxes/egui_box.rs
Normal file
165
src/boxes/egui_box.rs
Normal file
@ -0,0 +1,165 @@
|
||||
// Nyash EguiBox Implementation
|
||||
// Everything is Box哲学によるGUIフレームワーク統合
|
||||
// 「なんでもBoxにできる」化け物言語の第一歩!
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use crate::interpreter::RuntimeError;
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use eframe::{self, epaint::Vec2};
|
||||
|
||||
/// EguiBox - GUI アプリケーションを包むBox
|
||||
///
|
||||
/// # 使用例
|
||||
/// ```nyash
|
||||
/// app = new EguiBox()
|
||||
/// app.setTitle("My Nyash App")
|
||||
/// app.setSize(800, 600)
|
||||
/// app.run()
|
||||
/// ```
|
||||
pub struct EguiBox {
|
||||
title: String,
|
||||
size: Vec2,
|
||||
app_state: Arc<Mutex<Box<dyn Any + Send>>>,
|
||||
update_fn: Option<Arc<dyn Fn(&mut Box<dyn Any + Send>, &egui::Context) + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EguiBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EguiBox")
|
||||
.field("title", &self.title)
|
||||
.field("size", &self.size)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl EguiBox {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
title: "Nyash GUI Application".to_string(),
|
||||
size: Vec2::new(800.0, 600.0),
|
||||
app_state: Arc::new(Mutex::new(Box::new(()) as Box<dyn Any + Send>)),
|
||||
update_fn: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// アプリケーション状態を設定
|
||||
pub fn set_app_state<T: Any + Send + 'static>(&mut self, state: T) {
|
||||
self.app_state = Arc::new(Mutex::new(Box::new(state)));
|
||||
}
|
||||
|
||||
/// 更新関数を設定
|
||||
pub fn set_update_fn<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(&mut Box<dyn Any + Send>, &egui::Context) + Send + Sync + 'static
|
||||
{
|
||||
self.update_fn = Some(Arc::new(f));
|
||||
}
|
||||
}
|
||||
|
||||
// NyashApp - eframe::Appを実装する内部構造体
|
||||
struct NyashApp {
|
||||
app_state: Arc<Mutex<Box<dyn Any + Send>>>,
|
||||
update_fn: Arc<dyn Fn(&mut Box<dyn Any + Send>, &egui::Context) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl eframe::App for NyashApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
if let Ok(mut state) = self.app_state.lock() {
|
||||
(self.update_fn)(&mut *state, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for EguiBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(
|
||||
format!("EguiBox('{}', {}x{})", self.title, self.size.x, self.size.y)
|
||||
)
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
// GUI Boxはクローン不可(単一インスタンス)
|
||||
Box::new(Self {
|
||||
title: self.title.clone(),
|
||||
size: self.size,
|
||||
app_state: Arc::new(Mutex::new(Box::new(()) as Box<dyn Any + Send>)),
|
||||
update_fn: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_egui) = other.as_any().downcast_ref::<EguiBox>() {
|
||||
BoolBox::new(self.title == other_egui.title && self.size == other_egui.size)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"EguiBox"
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
// 簡易的なIDとしてポインタアドレスを使用
|
||||
self as *const _ as u64
|
||||
}
|
||||
}
|
||||
|
||||
// EguiBoxのメソッド実装(実際にはインタープリターから呼ばれない)
|
||||
impl EguiBox {
|
||||
pub fn run_gui(&self) -> Result<(), RuntimeError> {
|
||||
if let Some(update_fn) = &self.update_fn {
|
||||
let app_state = Arc::clone(&self.app_state);
|
||||
let update_fn = Arc::clone(update_fn);
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size(self.size)
|
||||
.with_title(&self.title),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let app = NyashApp {
|
||||
app_state,
|
||||
update_fn,
|
||||
};
|
||||
|
||||
// 注意: これはブロッキング呼び出し
|
||||
let _ = eframe::run_native(
|
||||
&self.title,
|
||||
options,
|
||||
Box::new(|_cc| Ok(Box::new(app))),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RuntimeError::InvalidOperation {
|
||||
message: "No update function set for EguiBox".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_egui_box_creation() {
|
||||
let gui = EguiBox::new();
|
||||
assert_eq!(gui.title, "Nyash GUI Application");
|
||||
assert_eq!(gui.size, Vec2::new(800.0, 600.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_egui_box_to_string() {
|
||||
let gui = EguiBox::new();
|
||||
let s = gui.to_string_box();
|
||||
assert_eq!(s.value, "EguiBox('Nyash GUI Application', 800x600)");
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,10 @@ pub mod console_box;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web;
|
||||
|
||||
// GUI Box(条件付きコンパイル)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod egui_box;
|
||||
|
||||
// 共通で使う型とトレイトを再エクスポート
|
||||
pub use string_box::StringBox;
|
||||
pub use integer_box::IntegerBox;
|
||||
@ -32,15 +36,28 @@ pub use sound_box::SoundBox;
|
||||
pub use map_box::MapBox;
|
||||
pub use console_box::ConsoleBox;
|
||||
|
||||
// EguiBoxの再エクスポート(非WASM環境のみ)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use egui_box::EguiBox;
|
||||
|
||||
// Web Box群の再エクスポート(WASM環境のみ)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web::{WebDisplayBox, WebConsoleBox, WebCanvasBox};
|
||||
|
||||
pub mod null_box;
|
||||
|
||||
// P2P通信Box群
|
||||
// pub mod intent_box;
|
||||
// pub mod intent_box_wrapper;
|
||||
// pub mod p2p_box;
|
||||
|
||||
// 今後追加予定のBox型(コメントアウト)
|
||||
// pub mod array_box;
|
||||
// pub use array_box::ArrayBox;
|
||||
|
||||
// null関数も再エクスポート
|
||||
pub use null_box::{NullBox, null};
|
||||
pub use null_box::{NullBox, null};
|
||||
|
||||
// P2P通信Boxの再エクスポート
|
||||
// pub use intent_box::IntentBox;
|
||||
// pub use p2p_box::P2PBox;
|
||||
62
src/boxes/simple_intent_box.rs
Normal file
62
src/boxes/simple_intent_box.rs
Normal file
@ -0,0 +1,62 @@
|
||||
// シンプルなIntentBox - 最小限の実装
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimpleIntentBox {
|
||||
id: u64,
|
||||
// ノードID -> コールバック関数のマップ
|
||||
listeners: Arc<Mutex<HashMap<String, Vec<String>>>>, // 仮実装
|
||||
}
|
||||
|
||||
impl SimpleIntentBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
SimpleIntentBox {
|
||||
id,
|
||||
listeners: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for SimpleIntentBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("IntentBox")
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_intent) = other.as_any().downcast_ref::<SimpleIntentBox>() {
|
||||
BoolBox::new(self.id == other_intent.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"IntentBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
// IntentBoxは共有されるので、新しいインスタンスを作らない
|
||||
Box::new(SimpleIntentBox {
|
||||
id: self.id,
|
||||
listeners: self.listeners.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user