//! Pattern Recognition Helpers //! //! Phase 140-P4-B: This module now delegates to SSOT implementations in ast_feature_extractor. //! Provides backward-compatible wrappers for existing callsites. use crate::ast::ASTNode; use crate::mir::{detect_skip_whitespace_pattern as ast_detect, SkipWhitespaceInfo}; // ============================================================================ // Skip Whitespace Pattern (Phase 140-P4-B SSOT Wrapper) // ============================================================================ /// 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. /// /// # Phase 140-P4-B: SSOT Migration /// /// This function now delegates to `ast_feature_extractor::detect_skip_whitespace_pattern` /// for SSOT implementation. This wrapper maintains backward compatibility for existing callsites. pub fn try_extract_skip_whitespace_pattern( body: &[ASTNode], ) -> Option<(String, i64, Vec)> { ast_detect(body).map(|info| (info.carrier_name, info.delta, info.body_stmts)) } #[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()); } }