diff --git a/docs/development/roadmap/phases/phase-9/phase_9_8_bid_registry_and_codegen.md b/docs/development/roadmap/phases/phase-9/phase_9_8_bid_registry_and_codegen.md index 6e357337..74e1e855 100644 --- a/docs/development/roadmap/phases/phase-9/phase_9_8_bid_registry_and_codegen.md +++ b/docs/development/roadmap/phases/phase-9/phase_9_8_bid_registry_and_codegen.md @@ -2,7 +2,7 @@ 目的(What/Why) - 外部ライブラリをBox(BID)として配布・発見・利用するための基盤を用意する。 -- BID(Box Interface Definition)から各ターゲット(WASM/VM/LLVM/TS/Python)のスタブや宣言を自動生成し、開発者の負担を最小化する。 +- 当面は nyash.toml にBID情報を“埋め込む”方式で回し、将来は外部BID(manifest)参照+自動生成へ段階拡張する。 成果物(Deliverables) - BIDレジストリ仕様(YAML/JSON スキーマ定義・バージョニング・依存関係・権限メタ) @@ -14,22 +14,21 @@ - TypeScript/Python: ラッパ(FFI呼び出しAPIのプロキシ) - サンプルBIDからの生成例(console/canvas) -範囲(Scope) -1) スキーマ - - `version`, `interfaces[]`, `methods[]`, `params`, `returns`, `effect`, `permissions`(9.9の権限連携) - - 例: `docs/nyir/bid_samples/console.yaml`, `docs/nyir/bid_samples/canvas.yaml` -2) CLI - - `nyash bid gen --target ` → `out///...` に生成 - - オプション: `--out`, `--force`, `--dry-run` -3) テンプレート - - 各ターゲット用のMustache/Handlebars相当のテンプレート群 -4) ドキュメント - - `docs/予定/native-plan/box_ffi_abi.md` にBID→生成の流れを追記 +範囲(Scope:段階的) +A) すぐやる(埋め込みBID) + - nyash.toml に最小BID情報(署名・効果・権限)を記述し、ランタイムローダが読み込む + - ExternCall/Plugin呼び出し時にBIDの`effects/permissions`を参照して実行可否を判定 +B) 次にやる(参照BID) + - nyash.toml から外部BID(bid.yaml 等)を参照・マージ可能にする(アグリゲータ) +C) 自動生成(安定後) + - CLI: `nyash bid gen --target ` → `out///...` に生成 + - テンプレート: WASM(importObject), VM(関数テーブル), LLVM(declare), TS/Python(RTEラッパ) + - ドキュメント: `docs/予定/native-plan/box_ffi_abi.md` にBID→生成の流れを追記 -受け入れ基準(Acceptance) -- console/canvas のBIDから、WASM/VM/LLVM/TS/Python の最小スタブが生成される -- 生成物を用いて、9.7 のE2E(console.log / canvas.fillRect)が通る -- `--dry-run` で生成前にプレビューが確認できる +受け入れ基準(Acceptance:段階的) +- A: nyash.toml の BID 情報だけでランタイム実行・権限判定が可能(外部BIDなしでも動作) +- B: 外部BID(manifest)を nyash.toml から参照・マージできる +- C: console/canvas のBIDから、WASM/VM/LLVM/TS/Python の最小スタブが生成される(`--dry-run` 対応) 非スコープ(Out of Scope) - 高度な最適化生成、双方向同期、型高級機能(ジェネリクス/オーバーロード) @@ -42,6 +41,6 @@ - 計画: `docs/予定/native-plan/copilot_issues.txt`(9.7/9.8/9.9) メモ(運用) +- 対応する形式が増えたら、まず nyash.toml にBIDを追記(“その都度対応”の方針) - 将来的に「BID→RuntimeImports/ExternCall宣言」の自動接続まで拡張予定(WASM/VM/LLVM)。 - 権限メタ(permissions)は 9.9 のモデルに合わせて必須化を検討。 - diff --git a/docs/ideas/dual-mode-design.md b/docs/ideas/dual-mode-design.md new file mode 100644 index 00000000..97778e4c --- /dev/null +++ b/docs/ideas/dual-mode-design.md @@ -0,0 +1,361 @@ +# Nyash 2モード設計:スクリプトモード vs アプリケーションモード + +## 🎯 設計理念 + +Nyashは「明示性重視」と「使いやすさ」の両方を実現するため、**2つの実行モード**を永続的にサポートする。移行や非推奨化は行わず、それぞれのモードが異なる用途で輝く設計とする。 + +## 📊 モード比較 + +### スクリプトモード(Script Mode) +```nyash +// ファイル:hello.nyash +name = "Nyash" +count = 42 +print("Hello, " + name + "!") +print("The answer is " + count) + +// 関数も自由に定義 +function greet(who) { + print("Greetings, " + who) +} + +greet("World") +``` + +### アプリケーションモード(Application Mode) +```nyash +// ファイル:app.nyash +static box Main { + init { name, count } + + main() { + me.name = "Nyash" + me.count = 42 + me.greet() + } + + greet() { + print("Hello, " + me.name + "!") + print("The answer is " + me.count) + } +} +``` + +## 🔍 自動検出ルール + +### 基本原則:「見た目で分かる」 +1. **`static box`が1つでもある** → アプリケーションモード +2. **`static box`が1つもない** → スクリプトモード +3. **明示的な指定は不要**(ファイル内容で自動判定) + +### 検出アルゴリズム +```rust +fn detect_mode(ast: &AST) -> ExecutionMode { + // ASTを走査してstatic boxの存在をチェック + for node in ast.walk() { + match node { + ASTNode::StaticBoxDeclaration { .. } => { + return ExecutionMode::Application; + } + _ => continue, + } + } + ExecutionMode::Script +} +``` + +## 🌟 各モードの特徴 + +### スクリプトモード + +#### 利点 +- **素早いプロトタイピング** - アイデアをすぐ試せる +- **学習曲線が緩やか** - 初心者に優しい +- **REPL完全互換** - コピペで動く +- **ワンライナー対応** - `nyash -e "print(2+2)"` + +#### 変数スコープ +```nyash +// グローバルスコープに直接定義 +x = 10 +y = 20 + +// 関数内でもアクセス可能 +function sum() { + return x + y // グローバル変数を参照 +} + +// ローカル変数 +function calculate() { + local temp = 100 // 関数ローカル + return temp + x +} +``` + +#### エラーハンドリング +```nyash +// 未定義変数アクセス → 実行時エラー +print(undefined_var) // Runtime Error: undefined variable 'undefined_var' + +// 型エラーも実行時 +x = "hello" +y = x + 10 // Runtime Error: cannot add String and Integer +``` + +### アプリケーションモード + +#### 利点 +- **明示的な変数管理** - どこで何が定義されているか明確 +- **静的解析可能** - IDEサポートが充実 +- **大規模開発対応** - チーム開発で威力を発揮 +- **名前空間の分離** - static box単位でスコープ管理 + +#### 変数スコープ +```nyash +static box Calculator { + init { x, y, result } // 必ず宣言 + + calculate() { + // me.を通じてアクセス(明示的) + me.result = me.x + me.y + + // ローカル変数 + local temp = me.result * 2 + return temp + } +} + +// ❌ エラー:グローバル変数は定義できない +global_var = 10 // Error: Global variables not allowed in application mode +``` + +#### コンパイル時チェック +```nyash +static box TypedCalc { + init { value } + + setValue(v) { + // 将来的に型推論や型チェックも可能 + me.value = v + } + + calculate() { + // 未定義フィールドアクセス → コンパイルエラー + return me.undefined_field // Error: 'undefined_field' not declared in init + } +} +``` + +## 🚀 REPL統合 + +### REPLの動作 +```bash +$ nyash +Nyash REPL v1.0 (Script Mode) +> x = 42 +> print(x) +42 +> function double(n) { return n * 2 } +> print(double(x)) +84 + +# アプリケーションモードのコードも貼り付け可能 +> static box Counter { +. init { count } +. increment() { me.count = me.count + 1 } +. } +> c = new Counter() +> c.increment() +> print(c.count) +1 +``` + +### モード切り替え +```bash +# 通常起動(スクリプトモード) +$ nyash + +# アプリケーションモードでREPL起動(将来実装) +$ nyash --app-mode +Nyash REPL v1.0 (Application Mode) +> x = 42 # Error: Global variables not allowed +> static box Main { ... } # OK +``` + +## 📝 実装詳細 + +### パーサーの拡張 +```rust +pub struct Parser { + mode: ExecutionMode, + // スクリプトモードでは緩い解析 + // アプリケーションモードでは厳格な解析 +} + +impl Parser { + pub fn parse(&mut self, source: &str) -> Result { + let ast = self.parse_initial(source)?; + self.mode = detect_mode(&ast); + + match self.mode { + ExecutionMode::Script => self.validate_script_mode(&ast), + ExecutionMode::Application => self.validate_app_mode(&ast), + } + } +} +``` + +### インタープリターの対応 +```rust +impl Interpreter { + pub fn execute(&mut self, ast: &AST, mode: ExecutionMode) -> Result { + match mode { + ExecutionMode::Script => { + // グローバル変数を許可 + self.allow_globals = true; + self.execute_script(ast) + } + ExecutionMode::Application => { + // グローバル変数を禁止 + self.allow_globals = false; + self.execute_application(ast) + } + } + } +} +``` + +## 🎨 ベストプラクティス + +### いつスクリプトモードを使うか +1. **データ処理スクリプト** + ```nyash + data = readFile("input.csv") + lines = data.split("\n") + for line in lines { + fields = line.split(",") + print(fields[0] + ": " + fields[1]) + } + ``` + +2. **設定ファイル** + ```nyash + # config.nyash + server_host = "localhost" + server_port = 8080 + debug_mode = true + ``` + +3. **テストコード** + ```nyash + # test_math.nyash + assert(2 + 2 == 4) + assert(10 / 2 == 5) + print("All tests passed!") + ``` + +### いつアプリケーションモードを使うか +1. **Webアプリケーション** + ```nyash + static box WebServer { + init { port, routes } + + start() { + me.port = 8080 + me.routes = new MapBox() + me.setupRoutes() + me.listen() + } + } + ``` + +2. **ゲーム開発** + ```nyash + static box Game { + init { player, enemies, score } + + main() { + me.initialize() + loop(me.isRunning()) { + me.update() + me.render() + } + } + } + ``` + +3. **ライブラリ開発** + ```nyash + static box MathLib { + init { precision } + + static factorial(n) { + if n <= 1 { return 1 } + return n * MathLib.factorial(n - 1) + } + } + ``` + +## 🔮 将来の拡張 + +### 混在モード(検討中) +```nyash +#!script +// ファイルの最初の部分はスクリプトモード +config = loadConfig() +debug = true + +#!application +// ここからアプリケーションモード +static box Main { + init { config } + + main() { + // スクリプト部分の変数を参照できる? + me.config = ::config // グローバルスコープ参照構文 + } +} +``` + +### プロジェクト設定 +```toml +# nyash.toml +[project] +name = "my-app" +default_mode = "application" # プロジェクト全体のデフォルト + +[scripts] +# 特定のファイルはスクリプトモードで実行 +mode = "script" +files = ["scripts/*.nyash", "tests/*.nyash"] +``` + +## 🌈 他言語との比較 + +### Python +- **類似点**: REPLとファイル実行で同じ動作 +- **相違点**: Nyashはモードを明確に分離 + +### JavaScript/TypeScript +- **類似点**: strictモードの概念 +- **相違点**: Nyashは見た目で判定(`"use strict"`不要) + +### C# +- **優位性**: + - トップレベルステートメントより柔軟 + - 明示的なモード分離で混乱なし + - REPLとの完全な統合 + +### Ruby +- **類似点**: スクリプトとクラスベースの両対応 +- **相違点**: Nyashはより明確なモード分離 + +## 🎯 まとめ + +Nyashの2モード設計は: +1. **移行不要** - 両モード永続サポート +2. **自動判定** - ファイル内容で自動切り替え +3. **REPL対応** - 同じルールで直感的 +4. **いいとこ取り** - 用途に応じて最適なモードを選択 + +この設計により、Nyashは「**初心者に優しく、プロに強力**」な言語となる。 \ No newline at end of file diff --git a/src/backend/vm.rs b/src/backend/vm.rs index 2f25de1d..3a674123 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -477,7 +477,67 @@ impl VM { } crate::mir::TypeOpKind::Cast => { let v = self.get_value(*value)?; - self.set_value(*dst, v); + let casted = match ty { + crate::mir::MirType::Integer => match v { + VMValue::Integer(_) => v, + VMValue::Float(f) => VMValue::Integer(f as i64), + other => { + return Err(VMError::TypeError(format!( + "Cannot cast {:?} to Integer", + other + ))); + } + }, + crate::mir::MirType::Float => match v { + VMValue::Float(_) => v, + VMValue::Integer(i) => VMValue::Float(i as f64), + other => { + return Err(VMError::TypeError(format!( + "Cannot cast {:?} to Float", + other + ))); + } + }, + // For other types, only allow no-op cast when already same category + crate::mir::MirType::Bool => match v { + VMValue::Bool(_) => v, + other => { + return Err(VMError::TypeError(format!( + "Cannot cast {:?} to Bool", + other + ))); + } + }, + crate::mir::MirType::String => match v { + VMValue::String(_) => v, + other => { + return Err(VMError::TypeError(format!( + "Cannot cast {:?} to String", + other + ))); + } + }, + crate::mir::MirType::Void => match v { + VMValue::Void => v, + other => { + return Err(VMError::TypeError(format!( + "Cannot cast {:?} to Void", + other + ))); + } + }, + crate::mir::MirType::Box(name) => match v { + VMValue::BoxRef(ref arc) if arc.type_name() == name => v, + other => { + return Err(VMError::TypeError(format!( + "Cannot cast {:?} to Box<{}>", + other, name + ))); + } + }, + _ => v, + }; + self.set_value(*dst, casted); } } Ok(ControlFlow::Continue) @@ -640,8 +700,11 @@ impl VM { return Ok(ControlFlow::Continue); } - // Fast-path for ArrayBox methods using original BoxRef (preserve state) + // Fast-path for common Box methods (Array/Map/String-like) + let fastpath_disabled = std::env::var("NYASH_VM_DISABLE_FASTPATH").is_ok(); + if !fastpath_disabled { if let VMValue::BoxRef(ref arc_any) = box_vm_value { + // ArrayBox: get/set/push if let Some(arr) = arc_any.as_any().downcast_ref::() { match method.as_str() { "get" => { @@ -660,9 +723,52 @@ impl VM { return Ok(ControlFlow::Continue); } } + "push" => { + if let Some(arg0) = arg_values.get(0) { + let res = arr.push((*arg0).clone_or_share()); + if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } + return Ok(ControlFlow::Continue); + } + } _ => {} } } + // MapBox: get/set/has + if let Some(map) = arc_any.as_any().downcast_ref::() { + match method.as_str() { + "get" => { + if let Some(arg0) = arg_values.get(0) { + let res = map.get((*arg0).clone_or_share()); + if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } + return Ok(ControlFlow::Continue); + } + } + "set" => { + if arg_values.len() >= 2 { + let k = (*arg_values.get(0).unwrap()).clone_or_share(); + let vval = (*arg_values.get(1).unwrap()).clone_or_share(); + let res = map.set(k, vval); + if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } + return Ok(ControlFlow::Continue); + } + } + "has" => { + if let Some(arg0) = arg_values.get(0) { + let res = map.has((*arg0).clone_or_share()); + if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } + return Ok(ControlFlow::Continue); + } + } + _ => {} + } + } + // toString(): generic fast-path for any BoxRef (0-arg) + if method == "toString" && arg_values.is_empty() { + let res = box_nyash.to_string_box(); + if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(Box::new(res)); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } + return Ok(ControlFlow::Continue); + } + } } // Call the method - unified dispatch for all Box types diff --git a/src/mir/verification.rs b/src/mir/verification.rs index b8c825f2..8cfcbfac 100644 --- a/src/mir/verification.rs +++ b/src/mir/verification.rs @@ -54,6 +54,20 @@ pub enum VerificationError { merge_block: BasicBlockId, pred_block: BasicBlockId, }, + /// WeakRef(load) must originate from a WeakNew/WeakRef(new) + InvalidWeakRefSource { + weak_ref: ValueId, + block: BasicBlockId, + instruction_index: usize, + reason: String, + }, + /// Barrier pointer must not be a void constant + InvalidBarrierPointer { + ptr: ValueId, + block: BasicBlockId, + instruction_index: usize, + reason: String, + }, } /// MIR verifier for SSA form and semantic correctness @@ -113,6 +127,10 @@ impl MirVerifier { if let Err(mut merge_errors) = self.verify_merge_uses(function) { local_errors.append(&mut merge_errors); } + // 5. Minimal checks for WeakRef/Barrier + if let Err(mut weak_barrier_errors) = self.verify_weakref_and_barrier(function) { + local_errors.append(&mut weak_barrier_errors); + } if local_errors.is_empty() { Ok(()) @@ -120,6 +138,89 @@ impl MirVerifier { Err(local_errors) } } + + /// Verify WeakRef/Barrier minimal semantics + fn verify_weakref_and_barrier(&self, function: &MirFunction) -> Result<(), Vec> { + use super::MirInstruction; + let mut errors = Vec::new(); + // Build def map value -> (block, idx, &inst) + let mut def_map: HashMap = HashMap::new(); + for (bid, block) in &function.blocks { + for (idx, inst) in block.all_instructions().enumerate() { + if let Some(dst) = inst.dst_value() { + def_map.insert(dst, (*bid, idx, inst)); + } + } + } + + for (bid, block) in &function.blocks { + for (idx, inst) in block.all_instructions().enumerate() { + match inst { + MirInstruction::WeakRef { op: super::WeakRefOp::Load, value, .. } => { + match def_map.get(value) { + Some((_db, _di, def_inst)) => match def_inst { + MirInstruction::WeakRef { op: super::WeakRefOp::New, .. } | MirInstruction::WeakNew { .. } => {} + _ => { + errors.push(VerificationError::InvalidWeakRefSource { + weak_ref: *value, + block: *bid, + instruction_index: idx, + reason: "weakref.load source is not a weakref.new/weak_new".to_string(), + }); + } + }, + None => { + errors.push(VerificationError::InvalidWeakRefSource { + weak_ref: *value, + block: *bid, + instruction_index: idx, + reason: "weakref.load source is undefined".to_string(), + }); + } + } + } + MirInstruction::WeakLoad { weak_ref, .. } => { + match def_map.get(weak_ref) { + Some((_db, _di, def_inst)) => match def_inst { + MirInstruction::WeakNew { .. } | MirInstruction::WeakRef { op: super::WeakRefOp::New, .. } => {} + _ => { + errors.push(VerificationError::InvalidWeakRefSource { + weak_ref: *weak_ref, + block: *bid, + instruction_index: idx, + reason: "weak_load source is not a weak_new/weakref.new".to_string(), + }); + } + }, + None => { + errors.push(VerificationError::InvalidWeakRefSource { + weak_ref: *weak_ref, + block: *bid, + instruction_index: idx, + reason: "weak_load source is undefined".to_string(), + }); + } + } + } + MirInstruction::Barrier { ptr, .. } | MirInstruction::BarrierRead { ptr } | MirInstruction::BarrierWrite { ptr } => { + if let Some((_db, _di, def_inst)) = def_map.get(ptr) { + if let MirInstruction::Const { value: super::ConstValue::Void, .. } = def_inst { + errors.push(VerificationError::InvalidBarrierPointer { + ptr: *ptr, + block: *bid, + instruction_index: idx, + reason: "barrier pointer is void".to_string(), + }); + } + } + } + _ => {} + } + } + } + + if errors.is_empty() { Ok(()) } else { Err(errors) } + } /// Verify SSA form properties fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec> { @@ -419,6 +520,12 @@ impl std::fmt::Display for VerificationError { write!(f, "Merge block {} uses predecessor-defined value {} from block {} without Phi", merge_block, value, pred_block) }, + VerificationError::InvalidWeakRefSource { weak_ref, block, instruction_index, reason } => { + write!(f, "Invalid WeakRef source {} in block {} at {}: {}", weak_ref, block, instruction_index, reason) + }, + VerificationError::InvalidBarrierPointer { ptr, block, instruction_index, reason } => { + write!(f, "Invalid Barrier pointer {} in block {} at {}: {}", ptr, block, instruction_index, reason) + }, } } } diff --git a/src/tests/mir_vm_poc.rs b/src/tests/mir_vm_poc.rs index 84afc611..78b84ed2 100644 --- a/src/tests/mir_vm_poc.rs +++ b/src/tests/mir_vm_poc.rs @@ -44,6 +44,61 @@ mod tests { let _ = vm.execute_module(&module).expect("VM should execute module"); } + #[test] + fn vm_exec_typeop_cast_int_float() { + let mut func = make_main(); + let bb = func.entry_block; + + let v0 = func.next_value_id(); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v0, value: ConstValue::Integer(3) }); + + let v1 = func.next_value_id(); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeOp { dst: v1, op: crate::mir::TypeOpKind::Cast, value: v0, ty: MirType::Float }); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: None }); + + let mut module = MirModule::new("test".to_string()); + module.add_function(func); + let mut vm = VM::new(); + let _ = vm.execute_module(&module).expect("int->float cast should succeed"); + } + + #[test] + fn vm_exec_typeop_cast_float_int() { + let mut func = make_main(); + let bb = func.entry_block; + + let v0 = func.next_value_id(); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v0, value: ConstValue::Float(3.7) }); + + let v1 = func.next_value_id(); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeOp { dst: v1, op: crate::mir::TypeOpKind::Cast, value: v0, ty: MirType::Integer }); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: None }); + + let mut module = MirModule::new("test".to_string()); + module.add_function(func); + let mut vm = VM::new(); + let _ = vm.execute_module(&module).expect("float->int cast should succeed"); + } + + #[test] + fn vm_exec_typeop_cast_invalid_should_error() { + let mut func = make_main(); + let bb = func.entry_block; + + let v0 = func.next_value_id(); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v0, value: ConstValue::String("x".to_string()) }); + + let v1 = func.next_value_id(); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeOp { dst: v1, op: crate::mir::TypeOpKind::Cast, value: v0, ty: MirType::Integer }); + func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: None }); + + let mut module = MirModule::new("test".to_string()); + module.add_function(func); + let mut vm = VM::new(); + let res = vm.execute_module(&module); + assert!(res.is_err(), "invalid cast should return error"); + } + #[test] fn vm_exec_legacy_typecheck_cast() { let mut func = make_main();