//! Phase 170-C-2: LoopUpdateSummary - ループ更新パターン解析 //! //! キャリア変数の更新パターン(CounterLike / AccumulationLike)を判定し、 //! CaseALoweringShape の検出精度を向上させる。 //! //! ## 設計思想 //! //! - 責務: AST のループ body から「各キャリアがどう更新されているか」を判定 //! - 差し替え可能: 名前ヒューリスティック → AST 解析 → MIR 解析と段階的に精度向上 //! - LoopFeatures / CaseALoweringShape から独立したモジュール //! //! ## 使用例 //! //! ```ignore //! let summary = analyze_loop_updates(&carrier_names); //! if summary.has_single_counter() { //! // StringExamination パターン //! } //! ``` /// キャリア変数の更新パターン /// /// Phase 170-C-2: 3種類のパターンを区別 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UpdateKind { /// カウンタ系: i = i + 1, i = i - 1, i += 1 /// /// 典型的な skip/trim パターン。進捗変数として使われる。 CounterLike, /// 蓄積系: result = result + x, arr.push(x), list.append(x) /// /// 典型的な collect/filter パターン。結果を蓄積する変数。 AccumulationLike, /// 判定不能 /// /// 複雑な更新パターン、または解析できなかった場合。 Other, } impl UpdateKind { /// デバッグ用の名前を返す pub fn name(&self) -> &'static str { match self { UpdateKind::CounterLike => "CounterLike", UpdateKind::AccumulationLike => "AccumulationLike", UpdateKind::Other => "Other", } } } /// 単一キャリアの更新情報 #[derive(Debug, Clone)] pub struct CarrierUpdateInfo { /// キャリア変数名 pub name: String, /// 更新パターン pub kind: UpdateKind, } /// ループ全体の更新サマリ /// /// Phase 170-C-2: CaseALoweringShape の判定入力として使用 #[derive(Debug, Clone, Default)] pub struct LoopUpdateSummary { /// 各キャリアの更新情報 pub carriers: Vec, } impl LoopUpdateSummary { /// 空のサマリを作成 pub fn empty() -> Self { Self { carriers: vec![] } } /// 単一 CounterLike キャリアを持つか /// /// StringExamination パターンの判定に使用 pub fn has_single_counter(&self) -> bool { self.carriers.len() == 1 && self.carriers[0].kind == UpdateKind::CounterLike } /// AccumulationLike キャリアを含むか /// /// ArrayAccumulation パターンの判定に使用 pub fn has_accumulation(&self) -> bool { self.carriers .iter() .any(|c| c.kind == UpdateKind::AccumulationLike) } /// CounterLike キャリアの数 pub fn counter_count(&self) -> usize { self.carriers .iter() .filter(|c| c.kind == UpdateKind::CounterLike) .count() } /// AccumulationLike キャリアの数 pub fn accumulation_count(&self) -> usize { self.carriers .iter() .filter(|c| c.kind == UpdateKind::AccumulationLike) .count() } } /// キャリア名から UpdateKind を推定(暫定実装) /// /// Phase 170-C-2a: 名前ヒューリスティック /// Phase 170-C-2b: AST 解析に置き換え予定 fn infer_update_kind_from_name(name: &str) -> UpdateKind { // 典型的なインデックス変数名 → CounterLike if is_typical_index_name(name) { return UpdateKind::CounterLike; } // その他 → AccumulationLike(蓄積系と推定) UpdateKind::AccumulationLike } /// 典型的なインデックス変数名か判定 /// /// Phase 170-C-1 から移植 fn is_typical_index_name(name: &str) -> bool { matches!( name, "i" | "e" | "idx" | "index" | "pos" | "position" | "start" | "end" | "n" | "j" | "k" ) } /// キャリア名リストからループ更新サマリを作成 /// /// # Phase 170-C-2 暫定実装 /// /// 現在は名前ヒューリスティックを使用。 /// 将来的に AST/MIR 解析に置き換え可能。 /// /// # Arguments /// /// * `carrier_names` - キャリア変数名のリスト /// /// # Returns /// /// 各キャリアの更新パターンをまとめた LoopUpdateSummary pub fn analyze_loop_updates(carrier_names: &[String]) -> LoopUpdateSummary { let carriers = carrier_names .iter() .map(|name| CarrierUpdateInfo { name: name.clone(), kind: infer_update_kind_from_name(name), }) .collect(); LoopUpdateSummary { carriers } } #[cfg(test)] mod tests { use super::*; #[test] fn test_update_kind_name() { assert_eq!(UpdateKind::CounterLike.name(), "CounterLike"); assert_eq!(UpdateKind::AccumulationLike.name(), "AccumulationLike"); assert_eq!(UpdateKind::Other.name(), "Other"); } #[test] fn test_typical_index_names() { assert!(is_typical_index_name("i")); assert!(is_typical_index_name("idx")); assert!(is_typical_index_name("pos")); assert!(!is_typical_index_name("result")); assert!(!is_typical_index_name("sum")); } #[test] fn test_analyze_single_counter() { let names = vec!["i".to_string()]; let summary = analyze_loop_updates(&names); assert!(summary.has_single_counter()); assert!(!summary.has_accumulation()); assert_eq!(summary.counter_count(), 1); } #[test] fn test_analyze_accumulation() { let names = vec!["result".to_string()]; let summary = analyze_loop_updates(&names); assert!(!summary.has_single_counter()); assert!(summary.has_accumulation()); assert_eq!(summary.accumulation_count(), 1); } #[test] fn test_analyze_mixed() { let names = vec!["i".to_string(), "sum".to_string()]; let summary = analyze_loop_updates(&names); assert!(!summary.has_single_counter()); // 2つあるので false assert!(summary.has_accumulation()); assert_eq!(summary.counter_count(), 1); assert_eq!(summary.accumulation_count(), 1); } }