refactor(mir): Phase 138-P1-A - loop_canonicalizer を4モジュール分割
## 概要 - 931行の mod.rs を 4モジュール + 161行 mod.rs に分割 - 全14テスト PASS、退行なし ## モジュール構成 - skeleton_types.rs (213行) - LoopSkeleton/SkeletonStep/UpdateKind/CarrierSlot/ExitContract - capability_guard.rs (104行) - RoutingDecision/capability_tags - pattern_recognizer.rs (249行) - try_extract_skip_whitespace_pattern - canonicalizer.rs (414行) - canonicalize_loop_expr + 統合テスト - mod.rs (161行) - 型定義と re-export ## ファイルサイズ達成 - 最大ファイル: canonicalizer.rs 414行(目標250行を一部超過するが許容範囲) - mod.rs: 931行 → 161行 (83%削減) - 合計: 1141行(元の931行 + tests分離で増加) ## テスト結果 - 14 tests passed - loop_canonicalizer::* 全テスト green
This commit is contained in:
249
src/mir/loop_canonicalizer/pattern_recognizer.rs
Normal file
249
src/mir/loop_canonicalizer/pattern_recognizer.rs
Normal file
@ -0,0 +1,249 @@
|
||||
//! Pattern Recognition Helpers
|
||||
//!
|
||||
//! This module contains pattern detection logic for specific loop structures.
|
||||
//! Currently supports the skip_whitespace pattern, with room for future patterns.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
// ============================================================================
|
||||
// Skip Whitespace Pattern
|
||||
// ============================================================================
|
||||
|
||||
/// Try to extract skip_whitespace pattern from loop
|
||||
///
|
||||
/// Pattern structure:
|
||||
/// ```
|
||||
/// loop(cond) {
|
||||
/// // ... optional body statements (Body)
|
||||
/// if check_cond {
|
||||
/// carrier = carrier + const
|
||||
/// } else {
|
||||
/// break
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Returns (carrier_name, delta, body_stmts) if pattern matches.
|
||||
pub fn try_extract_skip_whitespace_pattern(
|
||||
body: &[ASTNode],
|
||||
) -> Option<(String, i64, Vec<ASTNode>)> {
|
||||
if body.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Last statement must be if-else with break
|
||||
let last_stmt = &body[body.len() - 1];
|
||||
|
||||
let (then_body, else_body) = match last_stmt {
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body: Some(else_body),
|
||||
..
|
||||
} => (then_body, else_body),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Then branch must be single assignment: carrier = carrier + const
|
||||
if then_body.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (carrier_name, delta) = match &then_body[0] {
|
||||
ASTNode::Assignment { target, value, .. } => {
|
||||
// Extract target variable name
|
||||
let target_name = match target.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Value must be: target + const
|
||||
match value.as_ref() {
|
||||
ASTNode::BinaryOp {
|
||||
operator: crate::ast::BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
// Left must be same variable
|
||||
let left_name = match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if left_name != &target_name {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Right must be integer literal
|
||||
let delta = match right.as_ref() {
|
||||
ASTNode::Literal {
|
||||
value: crate::ast::LiteralValue::Integer(n),
|
||||
..
|
||||
} => *n,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
(target_name, delta)
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Else branch must be single break
|
||||
if else_body.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &else_body[0] {
|
||||
ASTNode::Break { .. } => {
|
||||
// Success! Extract body statements (all except last if)
|
||||
let body_stmts = body[..body.len() - 1].to_vec();
|
||||
Some((carrier_name, delta, body_stmts))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span};
|
||||
|
||||
#[test]
|
||||
fn test_skip_whitespace_basic_pattern() {
|
||||
// Build: if is_ws { p = p + 1 } else { break }
|
||||
let body = vec![ASTNode::If {
|
||||
condition: Box::new(ASTNode::Variable {
|
||||
name: "is_ws".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_body: vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
else_body: Some(vec![ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
}]),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let result = try_extract_skip_whitespace_pattern(&body);
|
||||
assert!(result.is_some());
|
||||
|
||||
let (carrier_name, delta, body_stmts) = result.unwrap();
|
||||
assert_eq!(carrier_name, "p");
|
||||
assert_eq!(delta, 1);
|
||||
assert_eq!(body_stmts.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_whitespace_with_body() {
|
||||
// Build: local ch = get_char(p); if is_ws { p = p + 1 } else { break }
|
||||
let body = vec![
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "ch".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::FunctionCall {
|
||||
name: "get_char".to_string(),
|
||||
arguments: vec![ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::If {
|
||||
condition: Box::new(ASTNode::Variable {
|
||||
name: "is_ws".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_body: vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
else_body: Some(vec![ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
}]),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
|
||||
let result = try_extract_skip_whitespace_pattern(&body);
|
||||
assert!(result.is_some());
|
||||
|
||||
let (carrier_name, delta, body_stmts) = result.unwrap();
|
||||
assert_eq!(carrier_name, "p");
|
||||
assert_eq!(delta, 1);
|
||||
assert_eq!(body_stmts.len(), 1); // The assignment before the if
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_whitespace_rejects_no_else() {
|
||||
// Build: if is_ws { p = p + 1 } (no else)
|
||||
let body = vec![ASTNode::If {
|
||||
condition: Box::new(ASTNode::Variable {
|
||||
name: "is_ws".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_body: vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
else_body: None,
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let result = try_extract_skip_whitespace_pattern(&body);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user