//! Phase 50: Loop Frontend Binding //! //! このモジュールは cf_loop から JoinIR Frontend への変数マッピングを提供する。 //! //! ## 問題背景 //! //! JoinIR Frontend (`loop_patterns.rs`) はハードコードされた変数名を期待: //! - `i`: カウンタ変数 //! - `acc`: アキュムレータ変数 //! - `n`: ループ上限 //! //! しかし実際のループは異なる名前や構造を持つ: //! - print_tokens: acc が存在しない(副作用のみ) //! - filter: acc は `out` という名前 //! //! ## 解決策 //! //! LoopFrontendBinding が「実際の変数名」と「JoinIR 期待変数名」をマッピングし、 //! JSON v0 生成時に適切な Local 宣言を注入する。 use crate::ast::ASTNode; /// ループ変数と JoinIR Frontend 期待変数のマッピング #[derive(Debug, Clone)] pub struct LoopFrontendBinding { /// カウンタ変数名 (e.g., "i") pub counter_var: String, /// カウンタの初期値 pub counter_init: i64, /// アキュムレータ変数名 (None for side-effect loops like print_tokens) pub accumulator_var: Option, /// ループ上限式 (e.g., "n" or "arr.size()") pub bound_expr: BoundExpr, /// 外部参照 (e.g., ["me.tokens", "arr", "pred"]) pub external_refs: Vec, /// ループパターン種別 pub pattern: LoopPattern, } /// ループ上限の表現 #[derive(Debug, Clone)] pub enum BoundExpr { /// 変数名 (e.g., "n") Variable(String), /// メソッド呼び出し (e.g., "me.tokens", "length") MethodCall { receiver: String, method: String }, /// 不明(フォールバック用) Unknown, } /// ループパターン種別 #[derive(Debug, Clone, PartialEq)] pub enum LoopPattern { /// 単純カウンタループ (print_tokens 相当) /// has_accumulator: アキュムレータが存在するか Simple { has_accumulator: bool }, /// フィルターパターン (if + push) Filter, /// マップパターン (変換 + push) Map, /// 不明(フォールバック用) Unknown, } impl LoopFrontendBinding { /// print_tokens 専用のバインディングを生成 /// /// print_tokens の構造: /// ```nyash /// local i = 0 /// loop(i < me.tokens.length()) { /// local token = me.tokens.get(i) /// print(token) /// i = i + 1 /// } /// ``` pub fn for_print_tokens() -> Self { Self { counter_var: "i".to_string(), counter_init: 0, accumulator_var: None, // print_tokens has no accumulator bound_expr: BoundExpr::MethodCall { receiver: "me.tokens".to_string(), method: "length".to_string(), }, external_refs: vec!["me.tokens".to_string()], pattern: LoopPattern::Simple { has_accumulator: false, }, } } /// array_ext.filter 専用のバインディングを生成 /// /// filter の構造: /// ```nyash /// local out = new ArrayBox() /// local i = 0 /// local n = arr.size() /// loop(i < n) { /// local v = arr.get(i) /// if pred(v) { out.push(v) } /// i = i + 1 /// } /// return out /// ``` pub fn for_array_filter() -> Self { Self { counter_var: "i".to_string(), counter_init: 0, accumulator_var: Some("out".to_string()), // filter has "out" as accumulator bound_expr: BoundExpr::Variable("n".to_string()), external_refs: vec!["arr".to_string(), "pred".to_string()], pattern: LoopPattern::Filter, } } /// ループ条件と本体から変数パターンを分析して Binding を生成 /// /// Phase 50-3 で実装予定の汎用分析。現在は関数名ベースのハードコード。 #[allow(dead_code)] pub fn analyze(_condition: &ASTNode, _body: &[ASTNode]) -> Option { // TODO: Phase 50-3 で AST 分析を実装 // 1. カウンタ変数の検出 (i = i + 1 パターン) // 2. アキュムレータの検出 (return で返される変数) // 3. ループ上限の検出 (i < X の X) None } /// JoinIR Frontend 用の JSON v0 Local 宣言を生成 /// /// Returns: (i_local, acc_local, n_local) の JSON Value タプル /// /// Note: JoinIR Frontend expects specific type names: /// - "Int" for integer literals (with "value" field) /// - "Var" for variable references (with "name" field) /// - "Method" for method calls (with "receiver", "method", "args" fields) /// - "NewBox" for box instantiation pub fn generate_local_declarations( &self, ) -> (serde_json::Value, serde_json::Value, serde_json::Value) { use serde_json::json; // i の Local 宣言 // JoinIR Frontend expects "Int" type with direct "value" field let i_local = json!({ "type": "Local", "name": "i", "expr": { "type": "Int", "value": self.counter_init } }); // acc の Local 宣言(合成または実際の変数から) let acc_local = if self.accumulator_var.is_some() { // 実際のアキュムレータがある場合(filter 等) // "out" → "acc" にリネームして宣言 json!({ "type": "Local", "name": "acc", "expr": { "type": "NewBox", "box_name": "ArrayBox", "args": [] } }) } else { // アキュムレータがない場合(print_tokens 等) // 合成の unit 値を使う(Int 0 として表現) json!({ "type": "Local", "name": "acc", "expr": { "type": "Int", "value": 0 } }) }; // n の Local 宣言 let n_local = match &self.bound_expr { BoundExpr::Variable(var) => { // 既存変数を参照(filter の n 等) // JoinIR Frontend expects "Var" type json!({ "type": "Local", "name": "n", "expr": { "type": "Var", "name": var } }) } BoundExpr::MethodCall { receiver, method } => { // メソッド呼び出しを評価(print_tokens の me.tokens.length() 等) // JoinIR Frontend expects "Method" type json!({ "type": "Local", "name": "n", "expr": { "type": "Method", "receiver": { "type": "Var", "name": receiver }, "method": method, "args": [] } }) } BoundExpr::Unknown => { // フォールバック: 0 を使う json!({ "type": "Local", "name": "n", "expr": { "type": "Int", "value": 0 } }) } }; (i_local, acc_local, n_local) } /// ループ本体の変数名をリネーム /// /// 実際の変数名 (e.g., "out") を JoinIR 期待名 (e.g., "acc") に置換 pub fn rename_body_variables(&self, body_json: &mut Vec) { if let Some(ref acc_name) = self.accumulator_var { if acc_name != "acc" { // "out" → "acc" にリネーム Self::rename_variable_in_json(body_json, acc_name, "acc"); } } } /// JSON 内の変数名を再帰的にリネーム fn rename_variable_in_json(json_array: &mut Vec, from: &str, to: &str) { for item in json_array.iter_mut() { Self::rename_variable_in_value(item, from, to); } } fn rename_variable_in_value(value: &mut serde_json::Value, from: &str, to: &str) { match value { serde_json::Value::Object(map) => { // Variable/Var ノードの名前をチェック // Note: ast_to_json uses "kind", JoinIR Frontend expects "type" let is_var = map.get("kind").and_then(|v| v.as_str()) == Some("Variable") || map.get("type").and_then(|v| v.as_str()) == Some("Var"); if is_var { if let Some(name) = map.get_mut("name") { if name.as_str() == Some(from) { *name = serde_json::Value::String(to.to_string()); } } } // Local ノードの名前をチェック if map.get("type").and_then(|v| v.as_str()) == Some("Local") || map.get("kind").and_then(|v| v.as_str()) == Some("Local") { if let Some(name) = map.get_mut("name") { if name.as_str() == Some(from) { *name = serde_json::Value::String(to.to_string()); } } } // 再帰的に処理 for (_, v) in map.iter_mut() { Self::rename_variable_in_value(v, from, to); } } serde_json::Value::Array(arr) => { for item in arr.iter_mut() { Self::rename_variable_in_value(item, from, to); } } _ => {} } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_print_tokens_binding() { let binding = LoopFrontendBinding::for_print_tokens(); assert_eq!(binding.counter_var, "i"); assert_eq!(binding.counter_init, 0); assert!(binding.accumulator_var.is_none()); assert_eq!( binding.pattern, LoopPattern::Simple { has_accumulator: false } ); } #[test] fn test_array_filter_binding() { let binding = LoopFrontendBinding::for_array_filter(); assert_eq!(binding.counter_var, "i"); assert_eq!(binding.accumulator_var, Some("out".to_string())); assert_eq!(binding.pattern, LoopPattern::Filter); } #[test] fn test_local_declarations_no_acc() { let binding = LoopFrontendBinding::for_print_tokens(); let (i_local, acc_local, _n_local) = binding.generate_local_declarations(); // i should be initialized to 0 assert_eq!(i_local["name"], "i"); assert_eq!(i_local["expr"]["type"], "Int"); assert_eq!(i_local["expr"]["value"], 0); // acc should be synthetic (Int 0 for side-effect loops) assert_eq!(acc_local["name"], "acc"); assert_eq!(acc_local["expr"]["type"], "Int"); assert_eq!(acc_local["expr"]["value"], 0); } #[test] fn test_local_declarations_with_acc() { let binding = LoopFrontendBinding::for_array_filter(); let (i_local, acc_local, n_local) = binding.generate_local_declarations(); // i should be initialized to 0 assert_eq!(i_local["name"], "i"); assert_eq!(i_local["expr"]["type"], "Int"); // acc should be new ArrayBox() assert_eq!(acc_local["name"], "acc"); assert_eq!(acc_local["expr"]["type"], "NewBox"); assert_eq!(acc_local["expr"]["box_name"], "ArrayBox"); // n should reference the "n" variable (Var type) assert_eq!(n_local["name"], "n"); assert_eq!(n_local["expr"]["type"], "Var"); assert_eq!(n_local["expr"]["name"], "n"); } }