use super::*; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use crate::mir::loop_pattern_detection::LoopFeatures; fn span() -> Span { Span::unknown() } fn var(name: &str) -> ASTNode { ASTNode::Variable { name: name.to_string(), span: span(), } } fn lit_i(n: i64) -> ASTNode { ASTNode::Literal { value: LiteralValue::Integer(n), span: span(), } } fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode { ASTNode::BinaryOp { operator: op, left: Box::new(left), right: Box::new(right), span: span(), } } fn assignment(target: ASTNode, value: ASTNode) -> ASTNode { ASTNode::Assignment { target: Box::new(target), value: Box::new(value), span: span(), } } fn has_continue(node: &ASTNode) -> bool { match node { ASTNode::Continue { .. } => true, ASTNode::If { then_body, else_body, .. } => { then_body.iter().any(has_continue) || else_body .as_ref() .map_or(false, |b| b.iter().any(has_continue)) } ASTNode::Loop { body, .. } => body.iter().any(has_continue), _ => false, } } fn has_break(node: &ASTNode) -> bool { match node { ASTNode::Break { .. } => true, ASTNode::If { then_body, else_body, .. } => { then_body.iter().any(has_break) || else_body .as_ref() .map_or(false, |b| b.iter().any(has_break)) } ASTNode::Loop { body, .. } => body.iter().any(has_break), _ => false, } } fn has_if(body: &[ASTNode]) -> bool { body.iter().any(|n| matches!(n, ASTNode::If { .. })) } fn carrier_count(body: &[ASTNode]) -> usize { fn count(nodes: &[ASTNode]) -> usize { let mut c = 0; for n in nodes { match n { ASTNode::Assignment { .. } => c += 1, ASTNode::If { then_body, else_body, .. } => { c += count(then_body); if let Some(else_body) = else_body { c += count(else_body); } } _ => {} } } c } if count(body) > 0 { 1 } else { 0 } } fn classify_body(body: &[ASTNode]) -> LoopPatternKind { let has_continue_flag = body.iter().any(has_continue); let has_break_flag = body.iter().any(has_break); let features = LoopFeatures { has_break: has_break_flag, has_continue: has_continue_flag, has_if: has_if(body), has_if_else_phi: false, carrier_count: carrier_count(body), break_count: if has_break_flag { 1 } else { 0 }, continue_count: if has_continue_flag { 1 } else { 0 }, is_infinite_loop: false, // テストでは通常ループを想定 update_summary: None, ..Default::default() // Phase 188.1: Use default for new fields }; classify(&features) } #[test] fn pattern1_simple_while_is_detected() { // loop(i < len) { i = i + 1 } let body = vec![assignment( var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1)), )]; let kind = classify_body(&body); assert_eq!(kind, LoopPatternKind::Pattern1SimpleWhile); } #[test] fn pattern2_break_loop_is_detected() { // loop(i < len) { if i > 0 { break } i = i + 1 } let cond = bin(BinaryOperator::Greater, var("i"), lit_i(0)); let body = vec![ ASTNode::If { condition: Box::new(cond), then_body: vec![ASTNode::Break { span: span() }], else_body: None, span: span(), }, assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1))), ]; let kind = classify_body(&body); assert_eq!(kind, LoopPatternKind::Pattern2Break); } #[test] fn parse_number_like_loop_is_classified_as_pattern2() { // loop(p < len) { // if digit_pos < 0 { break } // p = p + 1 // } let break_cond = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0)); let body = vec![ ASTNode::If { condition: Box::new(break_cond), then_body: vec![ASTNode::Break { span: span() }], else_body: None, span: span(), }, assignment(var("p"), bin(BinaryOperator::Add, var("p"), lit_i(1))), ]; let kind = classify_body(&body); assert_eq!(kind, LoopPatternKind::Pattern2Break); } #[test] fn pattern3_if_sum_shape_is_detected() { // loop(i < len) { if i % 2 == 1 { sum = sum + 1 } i = i + 1 } let cond = bin( BinaryOperator::Equal, bin(BinaryOperator::Modulo, var("i"), lit_i(2)), lit_i(1), ); let body = vec![ ASTNode::If { condition: Box::new(cond), then_body: vec![assignment( var("sum"), bin(BinaryOperator::Add, var("sum"), lit_i(1)), )], else_body: None, span: span(), }, assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1))), ]; let kind = classify_body(&body); assert_eq!(kind, LoopPatternKind::Pattern3IfPhi); } #[test] fn pattern4_continue_loop_is_detected() { // loop(i < len) { if (i % 2 == 0) { continue } sum = sum + i; i = i + 1 } let cond = bin( BinaryOperator::Equal, bin(BinaryOperator::Modulo, var("i"), lit_i(2)), lit_i(0), ); let body = vec![ ASTNode::If { condition: Box::new(cond), then_body: vec![ASTNode::Continue { span: span() }], else_body: Some(vec![assignment( var("sum"), bin(BinaryOperator::Add, var("sum"), var("i")), )]), span: span(), }, assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1))), ]; let kind = classify_body(&body); assert_eq!(kind, LoopPatternKind::Pattern4Continue); } #[test] fn test_atoi_loop_classified_as_pattern2() { // Phase 246-EX Step 1: _atoi loop pattern classification // loop(i < len) { // local ch = s.substring(i, i+1) // local digit_pos = digits.indexOf(ch) // if digit_pos < 0 { break } // result = result * 10 + digit_pos // i = i + 1 // } // Simplified: loop with break + two carrier updates let break_cond = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0)); // result = result * 10 + digit_pos (NumberAccumulation pattern) let mul_expr = bin(BinaryOperator::Multiply, var("result"), lit_i(10)); let result_update = assignment( var("result"), bin(BinaryOperator::Add, mul_expr, var("digit_pos")), ); // i = i + 1 let i_update = assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1))); let body = vec![ ASTNode::If { condition: Box::new(break_cond), then_body: vec![ASTNode::Break { span: span() }], else_body: None, span: span(), }, result_update, i_update, ]; let kind = classify_body(&body); assert_eq!( kind, LoopPatternKind::Pattern2Break, "_atoi loop should be classified as Pattern2 (Break) due to if-break structure" ); }