2025-12-16 06:41:46 +09:00
|
|
|
//! Pattern Recognition Helpers
|
|
|
|
|
//!
|
2025-12-16 07:09:22 +09:00
|
|
|
//! Phase 140-P4-B: This module now delegates to SSOT implementations in ast_feature_extractor.
|
|
|
|
|
//! Provides backward-compatible wrappers for existing callsites.
|
2025-12-16 06:41:46 +09:00
|
|
|
|
|
|
|
|
use crate::ast::ASTNode;
|
2025-12-16 07:09:22 +09:00
|
|
|
use crate::mir::{detect_skip_whitespace_pattern as ast_detect, SkipWhitespaceInfo};
|
2025-12-16 06:41:46 +09:00
|
|
|
|
|
|
|
|
// ============================================================================
|
2025-12-16 07:09:22 +09:00
|
|
|
// Skip Whitespace Pattern (Phase 140-P4-B SSOT Wrapper)
|
2025-12-16 06:41:46 +09:00
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// 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.
|
2025-12-16 07:09:22 +09:00
|
|
|
///
|
|
|
|
|
/// # 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.
|
2025-12-16 06:41:46 +09:00
|
|
|
pub fn try_extract_skip_whitespace_pattern(
|
|
|
|
|
body: &[ASTNode],
|
|
|
|
|
) -> Option<(String, i64, Vec<ASTNode>)> {
|
2025-12-16 07:09:22 +09:00
|
|
|
ast_detect(body).map(|info| (info.carrier_name, info.delta, info.body_stmts))
|
2025-12-16 06:41:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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());
|
|
|
|
|
}
|
|
|
|
|
}
|