Files
hakorune/src/mir/join_ir/lowering/loop_update_summary.rs

212 lines
6.1 KiB
Rust
Raw Normal View History

//! 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<CarrierUpdateInfo>,
}
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);
}
}