feat(joinir): Phase 222-2 ConditionPatternBox normalization implementation

Phase 222: If Condition Normalization - Part 2
Goal: Support '0 < i', 'i > j' patterns in addition to 'i > 0'

Changes:
1. condition_pattern.rs (+160 lines):
   - Added ConditionValue enum (Variable | Literal)
   - Added NormalizedCondition struct (left_var, op, right)
   - Added flip_compare_op() for operator reversal
   - Added binary_op_to_compare_op() converter
   - Added normalize_comparison() main normalization function
   - Extended analyze_condition_pattern() to accept 3 cases:
     * Phase 219: var CmpOp literal (e.g., i > 0)
     * Phase 222: literal CmpOp var (e.g., 0 < i) → normalized
     * Phase 222: var CmpOp var (e.g., i > j)
   - Added 9 unit tests (all passing)

2. loop_update_summary.rs (cleanup):
   - Commented out obsolete test_typical_index_names
   - Function is_typical_index_name() was removed in earlier phase

Test results:
- 7 normalization tests: PASS 
- 2 pattern analysis tests: PASS 

Next: Phase 222-3 - integrate normalization into is_if_sum_pattern()
Status: Ready for integration
This commit is contained in:
nyash-codex
2025-12-10 09:18:21 +09:00
parent 67c41d3b04
commit f0536fa330
3 changed files with 504 additions and 25 deletions

View File

@ -1,6 +1,7 @@
//! ConditionPatternBox: if条件パターン判定
//! ConditionPatternBox: if条件パターン判定と正規化
//!
//! Phase 219 regression fix: if条件が「単純比較」かどうかを判定
//! Phase 222: 左辺変数・右辺変数の両方をサポートする正規化を追加
//!
//! ## 問題
//!
@ -8,20 +9,21 @@
//! `loop_if_phi.hako` のような複雑条件 (`i % 2 == 1`) を
//! if-sumパターンと誤判定してしまう問題が発生。
//!
//! Phase 221で発見した制約if条件が `0 < i` や `i > j` のような形式を拒否。
//!
//! ## 解決策
//!
//! ConditionPatternBox を導入し、if条件が「単純比較」かどうかを判定する。
//! AST-based lowerer は単純比較のみ処理可能とし、複雑条件はlegacy modeへフォールバック。
//!
//! ## 単純比較の定義
//! Phase 222: 左右反転literal on left → var on leftと変数同士の比較をサポート。
//!
//! 以下のパターンのみ if-sum lowerer で処理可能:
//! - `var > literal` (e.g., `i > 0`)
//! - `var < literal` (e.g., `i < 10`)
//! - `var >= literal`
//! - `var <= literal`
//! - `var == literal`
//! - `var != literal`
//! ## 単純比較の定義Phase 222拡張版
//!
//! 以下のパターンを if-sum lowerer で処理可能:
//! - **Phase 219**: `var > literal` (e.g., `i > 0`)
//! - **Phase 222**: `literal < var` → `var > literal` に正規化
//! - **Phase 222**: `var > var` (e.g., `i > j`) - 変数同士の比較
//!
//! ## 複雑条件legacy mode へフォールバック)
//!
@ -30,7 +32,8 @@
//! - `method_call() > 0` (MethodCall)
//! - その他
use crate::ast::{ASTNode, BinaryOperator};
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::CompareOp;
/// if条件のパターン種別
#[derive(Debug, Clone, PartialEq, Eq)]
@ -93,18 +96,29 @@ pub fn analyze_condition_pattern(cond: &ASTNode) -> ConditionPattern {
return ConditionPattern::Complex;
}
// Check if LHS is a simple variable
// Check LHS/RHS patterns
let left_is_var = matches!(left.as_ref(), ASTNode::Variable { .. });
// Check if RHS is a literal
let left_is_literal = matches!(left.as_ref(), ASTNode::Literal { .. });
let right_is_var = matches!(right.as_ref(), ASTNode::Variable { .. });
let right_is_literal = matches!(right.as_ref(), ASTNode::Literal { .. });
// Phase 219: var CmpOp literal (e.g., i > 0)
if left_is_var && right_is_literal {
ConditionPattern::SimpleComparison
} else {
// Complex LHS/RHS (e.g., i % 2 == 1, method_call() > 0)
ConditionPattern::Complex
return ConditionPattern::SimpleComparison;
}
// Phase 222: literal CmpOp var (e.g., 0 < i)
if left_is_literal && right_is_var {
return ConditionPattern::SimpleComparison;
}
// Phase 222: var CmpOp var (e.g., i > j)
if left_is_var && right_is_var {
return ConditionPattern::SimpleComparison;
}
// Complex LHS/RHS (e.g., i % 2 == 1, method_call() > 0)
ConditionPattern::Complex
}
// Any other node type → Complex
_ => ConditionPattern::Complex,
@ -134,6 +148,144 @@ pub fn is_simple_comparison(cond: &ASTNode) -> bool {
analyze_condition_pattern(cond) == ConditionPattern::SimpleComparison
}
// ============================================================================
// Phase 222: Condition Normalization
// ============================================================================
/// 条件式の右辺値(変数 or リテラル)
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConditionValue {
/// 変数
Variable(String),
/// 整数リテラル
Literal(i64),
}
/// 正規化された条件式
///
/// 常に左辺が変数の形に正規化される:
/// - `i > 0` → `i > 0` (そのまま)
/// - `0 < i` → `i > 0` (左右反転)
/// - `i > j` → `i > j` (変数同士、そのまま)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NormalizedCondition {
/// 左辺変数名
pub left_var: String,
/// 比較演算子
pub op: CompareOp,
/// 右辺(変数 or リテラル)
pub right: ConditionValue,
}
/// 比較演算子を左右反転
///
/// 左辺がリテラル、右辺が変数の場合に使用。
///
/// # Examples
///
/// ```
/// // 0 < i → i > 0
/// assert_eq!(flip_compare_op(CompareOp::Lt), CompareOp::Gt);
///
/// // len > i → i < len
/// assert_eq!(flip_compare_op(CompareOp::Gt), CompareOp::Lt);
///
/// // 5 == i → i == 5 (不変)
/// assert_eq!(flip_compare_op(CompareOp::Eq), CompareOp::Eq);
/// ```
fn flip_compare_op(op: CompareOp) -> CompareOp {
match op {
CompareOp::Lt => CompareOp::Gt, // < → >
CompareOp::Gt => CompareOp::Lt, // > → <
CompareOp::Le => CompareOp::Ge, // <= → >=
CompareOp::Ge => CompareOp::Le, // >= → <=
CompareOp::Eq => CompareOp::Eq, // == → == (不変)
CompareOp::Ne => CompareOp::Ne, // != → != (不変)
}
}
/// BinaryOperator を CompareOp に変換
fn binary_op_to_compare_op(op: &BinaryOperator) -> Option<CompareOp> {
match op {
BinaryOperator::Less => Some(CompareOp::Lt),
BinaryOperator::Greater => Some(CompareOp::Gt),
BinaryOperator::LessEqual => Some(CompareOp::Le),
BinaryOperator::GreaterEqual => Some(CompareOp::Ge),
BinaryOperator::Equal => Some(CompareOp::Eq),
BinaryOperator::NotEqual => Some(CompareOp::Ne),
_ => None,
}
}
/// 条件式を正規化(左辺=変数 の形に統一)
///
/// # Arguments
///
/// * `cond` - 条件式ASTード
///
/// # Returns
///
/// - `Some(NormalizedCondition)` - 正規化成功
/// - `None` - 正規化失敗(複雑条件 or サポート外)
///
/// # Examples
///
/// ```rust
/// // i > 0 → NormalizedCondition { left_var: "i", op: Gt, right: Literal(0) }
/// let norm = normalize_comparison(&i_gt_0).unwrap();
/// assert_eq!(norm.left_var, "i");
/// assert_eq!(norm.op, CompareOp::Gt);
/// assert_eq!(norm.right, ConditionValue::Literal(0));
///
/// // 0 < i → NormalizedCondition { left_var: "i", op: Gt, right: Literal(0) }
/// let norm = normalize_comparison(&zero_lt_i).unwrap();
/// assert_eq!(norm.left_var, "i");
/// assert_eq!(norm.op, CompareOp::Gt); // 左右反転により Gt
///
/// // i > j → NormalizedCondition { left_var: "i", op: Gt, right: Variable("j") }
/// let norm = normalize_comparison(&i_gt_j).unwrap();
/// assert_eq!(norm.right, ConditionValue::Variable("j".to_string()));
/// ```
pub fn normalize_comparison(cond: &ASTNode) -> Option<NormalizedCondition> {
match cond {
ASTNode::BinaryOp { operator, left, right, .. } => {
// Comparison operator のみ受理
let compare_op = binary_op_to_compare_op(operator)?;
// Case 1: var CmpOp literal (e.g., i > 0)
if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Literal { value: LiteralValue::Integer(right_val), .. }) = (left.as_ref(), right.as_ref()) {
return Some(NormalizedCondition {
left_var: left_var.clone(),
op: compare_op,
right: ConditionValue::Literal(*right_val),
});
}
// Case 2: literal CmpOp var (e.g., 0 < i) → 左右反転
if let (ASTNode::Literal { value: LiteralValue::Integer(left_val), .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) {
return Some(NormalizedCondition {
left_var: right_var.clone(),
op: flip_compare_op(compare_op), // 演算子を反転
right: ConditionValue::Literal(*left_val),
});
}
// Case 3: var CmpOp var (e.g., i > j)
if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) {
return Some(NormalizedCondition {
left_var: left_var.clone(),
op: compare_op,
right: ConditionValue::Variable(right_var.clone()),
});
}
// その他BinaryOp, MethodCall等→ 正規化失敗
None
}
_ => None, // 非 BinaryOp → 正規化失敗
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -252,4 +404,92 @@ mod tests {
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::Complex);
assert!(!is_simple_comparison(&cond));
}
// ========================================================================
// Phase 222: Normalization Tests
// ========================================================================
#[test]
fn test_normalize_var_cmp_literal() {
// i > 0 → そのまま
let cond = binop(BinaryOperator::Greater, var("i"), int_lit(0));
let norm = normalize_comparison(&cond).unwrap();
assert_eq!(norm.left_var, "i");
assert_eq!(norm.op, CompareOp::Gt);
assert_eq!(norm.right, ConditionValue::Literal(0));
}
#[test]
fn test_normalize_literal_cmp_var() {
// 0 < i → i > 0 (左右反転)
let cond = binop(BinaryOperator::Less, int_lit(0), var("i"));
let norm = normalize_comparison(&cond).unwrap();
assert_eq!(norm.left_var, "i");
assert_eq!(norm.op, CompareOp::Gt); // 反転により Gt
assert_eq!(norm.right, ConditionValue::Literal(0));
}
#[test]
fn test_normalize_literal_gt_var() {
// len > i → i < len (左右反転)
let cond = binop(BinaryOperator::Greater, int_lit(10), var("i"));
let norm = normalize_comparison(&cond).unwrap();
assert_eq!(norm.left_var, "i");
assert_eq!(norm.op, CompareOp::Lt); // 反転により Lt
assert_eq!(norm.right, ConditionValue::Literal(10));
}
#[test]
fn test_normalize_literal_eq_var() {
// 5 == i → i == 5 (反転だが == は不変)
let cond = binop(BinaryOperator::Equal, int_lit(5), var("i"));
let norm = normalize_comparison(&cond).unwrap();
assert_eq!(norm.left_var, "i");
assert_eq!(norm.op, CompareOp::Eq); // == は不変
assert_eq!(norm.right, ConditionValue::Literal(5));
}
#[test]
fn test_normalize_var_cmp_var() {
// i > j → そのまま(変数同士)
let cond = binop(BinaryOperator::Greater, var("i"), var("j"));
let norm = normalize_comparison(&cond).unwrap();
assert_eq!(norm.left_var, "i");
assert_eq!(norm.op, CompareOp::Gt);
assert_eq!(norm.right, ConditionValue::Variable("j".to_string()));
}
#[test]
fn test_normalize_var_lt_var() {
// i < end → そのまま(変数同士)
let cond = binop(BinaryOperator::Less, var("i"), var("end"));
let norm = normalize_comparison(&cond).unwrap();
assert_eq!(norm.left_var, "i");
assert_eq!(norm.op, CompareOp::Lt);
assert_eq!(norm.right, ConditionValue::Variable("end".to_string()));
}
#[test]
fn test_normalize_fails_on_complex() {
// i % 2 == 1 → 正規化失敗BinaryOp in LHS
let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2));
let cond = binop(BinaryOperator::Equal, lhs, int_lit(1));
assert_eq!(normalize_comparison(&cond), None);
}
#[test]
fn test_analyze_pattern_literal_cmp_var() {
// Phase 222: 0 < i → SimpleComparison
let cond = binop(BinaryOperator::Less, int_lit(0), var("i"));
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
assert!(is_simple_comparison(&cond));
}
#[test]
fn test_analyze_pattern_var_cmp_var() {
// Phase 222: i > j → SimpleComparison
let cond = binop(BinaryOperator::Greater, var("i"), var("j"));
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
assert!(is_simple_comparison(&cond));
}
}