diff --git a/src/mir/join_ir/lowering/loop_scope_shape/case_a_lowering_shape.rs b/src/mir/join_ir/lowering/loop_scope_shape/case_a_lowering_shape.rs new file mode 100644 index 00000000..0be17499 --- /dev/null +++ b/src/mir/join_ir/lowering/loop_scope_shape/case_a_lowering_shape.rs @@ -0,0 +1,165 @@ +//! Case-A Loop Lowering Shape Detection +//! +//! Phase 170-A: Structure-based routing for Case-A loops +//! +//! Replaces hardcoded function name matching with AST-based pattern detection. +//! Enables reuse of Case-A lowering functions for ANY loop with matching structure, +//! not just hardcoded function names like "Main.skip/1". + +/// Recognized Case-A loop body patterns +/// +/// Used to dispatch to appropriate lowerer when function name is unavailable. +/// +/// Case-A loops are characterized by: +/// - Single exit group (one loop exit path) +/// - Progress carrier (monotonically increasing/decreasing variable that drives termination) +/// - Pinned parameters (loop-invariant values that don't change during iteration) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CaseALoweringShape { + /// String examination loop: conditional carrier update + /// + /// Examples: Main.skip/1, FuncScannerBox.trim/1 + /// + /// Pattern: + /// - Loop body: single if statement examining character + /// - Condition: examines string character (e.g., ch == " ") + /// - Then branch: updates carrier (e.g., i = i + 1) + /// - Else branch: exits loop (break) + /// - Carriers: 1 (progress variable i or e) + /// - Return value: final progress carrier + /// + /// Signature: (StringBox, ...) -> Integer + StringExamination, + + /// Array accumulation loop: linear iteration with collection mutation + /// + /// Examples: FuncScannerBox.append_defs/2 + /// + /// Pattern: + /// - Loop body: access arr[i], mutate collection (push/add) + /// - Condition: i < array.length() + /// - Increment: i = i + 1 + /// - Exit: void or collection mutation + /// - Carriers: 1 (progress variable i) + /// - Pinned: 1-2 (array/collection, optional length) + /// + /// Signature: (CollectionBox, ...) -> Void or CollectionBox + ArrayAccumulation, + + /// Iteration with accumulation: linear iteration + value accumulation + /// + /// Examples: Stage1UsingResolverBox.resolve_for_source/5 + /// + /// Pattern: + /// - Loop body: access arr[i], update accumulator + /// - Multiple carriers: progress (i) + result (prefix, sum, etc.) + /// - Condition: i < array.length() + /// - Exit: accumulated value + /// - Carriers: 2+ (progress i + accumulator) + /// - Pinned: 2+ (array, initial value, etc.) + /// + /// Signature: (CollectionBox, InitialValueBox, ...) -> ResultBox + IterationWithAccumulation, + + /// Generic Case-A: falls outside recognized patterns + /// + /// Structure is Case-A (single exit + progress carrier) but body + /// doesn't match any known pattern. + /// + /// Lowering: Should either: + /// 1. Implement generic extraction from LoopForm (Phase 170-B+), or + /// 2. Return None to trigger fallback to legacy LoopBuilder + Generic, + + /// Unknown: not a Case-A loop at all + /// + /// Structural requirements not met: + /// - Multiple exit groups, OR + /// - No progress carrier + /// + /// Should use Pattern 1-4 routing instead. + NotCaseA, +} + +impl CaseALoweringShape { + /// Detect lowering shape from LoopScopeShape + /// + /// Phase 170-A: Heuristic-based shape detection + /// + /// Returns NotCaseA if structural requirements not met. + /// Returns one of the above if structural analysis suggests a pattern. + /// + /// # Heuristics + /// - Single carrier (1) → likely StringExamination or ArrayAccumulation + /// - Multiple carriers (2+) → likely IterationWithAccumulation + /// - Pinned count → disambiguation hint + /// + /// Note: More sophisticated pattern matching (analyzing loop body structure) + /// is deferred to Phase 170-B. + pub fn detect(scope: &super::shape::LoopScopeShape) -> Self { + use super::shape::LoopScopeShape; + + // Check if structurally Case-A + if scope.progress_carrier.is_none() { + return CaseALoweringShape::NotCaseA; + } + + // Phase 170-A: Simple heuristic based on carrier count + match scope.carriers.len() { + 0 => { + // This shouldn't happen if progress_carrier is Some, but be safe + CaseALoweringShape::NotCaseA + } + 1 => { + // Single carrier: could be StringExamination or ArrayAccumulation + // Further distinction requires analyzing loop body (Phase 170-B) + // For now, return Generic to allow both paths to be tried + CaseALoweringShape::Generic + } + 2.. => { + // Multiple carriers: likely Iteration with accumulation + // (progress carrier + accumulator) + CaseALoweringShape::IterationWithAccumulation + } + } + } + + /// Is this a recognized lowering shape? + pub fn is_recognized(&self) -> bool { + !matches!(self, CaseALoweringShape::NotCaseA | CaseALoweringShape::Generic) + } + + /// Get human-readable name for tracing/debugging + pub fn name(&self) -> &'static str { + match self { + CaseALoweringShape::StringExamination => "StringExamination", + CaseALoweringShape::ArrayAccumulation => "ArrayAccumulation", + CaseALoweringShape::IterationWithAccumulation => "IterationWithAccumulation", + CaseALoweringShape::Generic => "Generic", + CaseALoweringShape::NotCaseA => "NotCaseA", + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_shape_display() { + assert_eq!(CaseALoweringShape::StringExamination.name(), "StringExamination"); + assert_eq!(CaseALoweringShape::ArrayAccumulation.name(), "ArrayAccumulation"); + assert_eq!(CaseALoweringShape::IterationWithAccumulation.name(), "IterationWithAccumulation"); + assert_eq!(CaseALoweringShape::Generic.name(), "Generic"); + assert_eq!(CaseALoweringShape::NotCaseA.name(), "NotCaseA"); + } + + #[test] + fn test_is_recognized() { + assert!(CaseALoweringShape::StringExamination.is_recognized()); + assert!(CaseALoweringShape::ArrayAccumulation.is_recognized()); + assert!(CaseALoweringShape::IterationWithAccumulation.is_recognized()); + assert!(!CaseALoweringShape::Generic.is_recognized()); + assert!(!CaseALoweringShape::NotCaseA.is_recognized()); + } +} diff --git a/src/mir/join_ir/lowering/loop_scope_shape/mod.rs b/src/mir/join_ir/lowering/loop_scope_shape/mod.rs index 3f1a86a5..e8e09164 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/mod.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/mod.rs @@ -4,16 +4,19 @@ //! - `shape`: 変数分類のSSOTと質問系API //! - `builder`: LoopForm / 既存箱からの組み立て(Case-A ルーティング含む) //! - `case_a`: Case-A minimal ターゲット判定(Phase 30 F-3.1) +//! - `case_a_lowering_shape`: Phase 170-A Case-A lowering shape detection (構造ベース) //! - `context`: generic_case_a 用の共通コンテキスト //! - `structural`: ループの構造的性質解析(Phase 48-5.5) mod builder; mod case_a; +mod case_a_lowering_shape; mod context; mod shape; mod structural; pub(crate) use case_a::is_case_a_minimal_target; +pub(crate) use case_a_lowering_shape::CaseALoweringShape; pub(crate) use context::CaseAContext; pub(crate) use shape::LoopScopeShape;