refactor(mir): Phase 93 リファクタリング - 箱化モジュール化
## 概要 Phase 93 P0実装後のコード整理。スケジュール決定ロジックとbreak semanticsを 明確化し、デバッグログを統一。 ## 変更内容 ### 1. スケジュール決定ロジックの関数化 (step_schedule.rs) - `ScheduleDecision`構造体追加(判定結果+理由+デバッグコンテキスト) - `decide_pattern2_schedule()` - スケジュール決定のSSOT - `build_pattern2_schedule_from_decision()` - 新しい決定ベースAPI - 判定理由が4種類で明確化(ConditionOnly → body-local → loop-local → default) - 後方互換性維持(`Pattern2ScheduleContext`はwrapperに) ### 2. ConditionOnlyRecipe強化 (condition_only_emitter.rs) - `BreakSemantics` enum追加(WhenMatch vs WhenNotMatch) - `generate_break_condition()` - semanticsに基づくAST生成 - `from_trim_helper_condition_only()` - factory method追加 - break semanticsがrecipeに明示的に含まれる ### 3. trim_loop_lowering.rs簡素化 - `generate_condition_only_break_condition()`削除(DRY原則) - `recipe.generate_break_condition()`で統一 - break条件生成ロジックが1箇所に集約 ### 4. デバッグログ統一 - `[phase93/schedule]` - スケジュール決定 - `[phase93/condition-only]` - ConditionOnlyレシピ作成 - `[phase93/break-cond]` - break条件生成 - 既存の`joinir_dev_enabled()`使用(新規env var不要) ## テスト結果 - step_schedule: 10 tests PASS - condition_only_emitter: 4 tests PASS - 後方互換性維持 ## 統計 - 3ファイル変更 - +249行 / -57行 = +192 net 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -369,28 +369,34 @@ impl TrimLoopLowerer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Step 7: Generate break condition based on pattern type
|
// Step 7: Generate break condition based on pattern type
|
||||||
// Phase 93 P0: ConditionOnly uses non-negated condition (break when is_ch_match is TRUE)
|
// Phase 93 Refactoring: Use recipe.generate_break_condition() for unified logic
|
||||||
// Normal Trim uses negated condition (break when !is_ch_match, i.e., ch is NOT whitespace)
|
let trim_break_condition = if let Some(ref recipe) = condition_only_recipe {
|
||||||
let trim_break_condition = if condition_only_recipe.is_some() {
|
// Use recipe's break semantics (WhenMatch or WhenNotMatch)
|
||||||
// ConditionOnly: "break when match" semantics
|
|
||||||
// Generate: is_ch_match (TRUE when we should break)
|
|
||||||
Self::generate_condition_only_break_condition(trim_helper)
|
|
||||||
} else {
|
|
||||||
// Normal Trim: "break when NOT match" semantics
|
|
||||||
// Generate: !is_ch_match (TRUE when we should break)
|
|
||||||
Self::generate_trim_break_condition(trim_helper)
|
|
||||||
};
|
|
||||||
|
|
||||||
trace.emit_if(
|
trace.emit_if(
|
||||||
"trim",
|
"trim",
|
||||||
"cond",
|
"break-cond",
|
||||||
&format!(
|
&format!(
|
||||||
"Break condition: {} (ConditionOnly={})",
|
"Generated break condition from recipe: {} (semantics: {:?})",
|
||||||
trim_helper.carrier_name,
|
trim_helper.carrier_name,
|
||||||
condition_only_recipe.is_some()
|
recipe.break_semantics
|
||||||
),
|
),
|
||||||
verbose,
|
verbose,
|
||||||
);
|
);
|
||||||
|
recipe.generate_break_condition()
|
||||||
|
} else {
|
||||||
|
// Normal Trim: "break when NOT match" semantics
|
||||||
|
// Generate: !is_ch_match (TRUE when we should break)
|
||||||
|
trace.emit_if(
|
||||||
|
"trim",
|
||||||
|
"break-cond",
|
||||||
|
&format!(
|
||||||
|
"Generated normal trim break condition: !{}",
|
||||||
|
trim_helper.carrier_name
|
||||||
|
),
|
||||||
|
verbose,
|
||||||
|
);
|
||||||
|
Self::generate_trim_break_condition(trim_helper)
|
||||||
|
};
|
||||||
|
|
||||||
// Step 8: Return result with all updates
|
// Step 8: Return result with all updates
|
||||||
Ok(Some(TrimLoweringResult {
|
Ok(Some(TrimLoweringResult {
|
||||||
@ -533,27 +539,11 @@ impl TrimLoopLowerer {
|
|||||||
TrimPatternLowerer::generate_trim_break_condition(trim_helper)
|
TrimPatternLowerer::generate_trim_break_condition(trim_helper)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate ConditionOnly break condition
|
|
||||||
///
|
|
||||||
/// Phase 93 P0: For ConditionOnly patterns where break happens WHEN the condition is TRUE.
|
|
||||||
///
|
|
||||||
/// Returns: is_carrier (non-negated carrier check)
|
|
||||||
/// Used for "break when match" semantics (e.g., find-first pattern)
|
|
||||||
fn generate_condition_only_break_condition(
|
|
||||||
trim_helper: &crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper,
|
|
||||||
) -> ASTNode {
|
|
||||||
use crate::ast::Span;
|
|
||||||
// Return just the carrier variable (non-negated)
|
|
||||||
// When is_ch_match is TRUE, we should break
|
|
||||||
ASTNode::Variable {
|
|
||||||
name: trim_helper.carrier_name.clone(),
|
|
||||||
span: Span::unknown(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setup ConditionEnv bindings for Trim carrier
|
/// Setup ConditionEnv bindings for Trim carrier
|
||||||
///
|
///
|
||||||
/// Phase 180-3: Extracted from Pattern2 (lines 345-377)
|
/// Phase 180-3: Extracted from Pattern2 (lines 345-377)
|
||||||
|
/// Phase 93 Refactoring: Use explicit factory methods for recipe creation
|
||||||
///
|
///
|
||||||
/// Creates bindings for:
|
/// Creates bindings for:
|
||||||
/// 1. Carrier variable (e.g., "is_ch_match")
|
/// 1. Carrier variable (e.g., "is_ch_match")
|
||||||
@ -569,15 +559,16 @@ impl TrimLoopLowerer {
|
|||||||
let verbose = crate::config::env::joinir_dev_enabled() || trace.is_joinir_enabled();
|
let verbose = crate::config::env::joinir_dev_enabled() || trace.is_joinir_enabled();
|
||||||
|
|
||||||
// Phase 93 P0: Do NOT add is_ch_match to ConditionBinding
|
// Phase 93 P0: Do NOT add is_ch_match to ConditionBinding
|
||||||
// Instead, create a ConditionOnlyRecipe for recalculation every iteration
|
// Phase 93 Refactoring: Use explicit factory method for ConditionOnly pattern
|
||||||
let recipe = ConditionOnlyRecipe::from_trim_helper(trim_helper);
|
let recipe = ConditionOnlyRecipe::from_trim_helper_condition_only(trim_helper);
|
||||||
|
|
||||||
trace.emit_if(
|
trace.emit_if(
|
||||||
"trim",
|
"trim",
|
||||||
"condition-only",
|
"condition-only",
|
||||||
&format!(
|
&format!(
|
||||||
"Phase 93 P0: Created ConditionOnlyRecipe for '{}' (will be recalculated each iteration, not carried via ConditionBinding)",
|
"[phase93/condition-only] Created ConditionOnlyRecipe for '{}' (semantics: {:?}, will be recalculated each iteration)",
|
||||||
trim_helper.carrier_name
|
trim_helper.carrier_name,
|
||||||
|
recipe.break_semantics
|
||||||
),
|
),
|
||||||
verbose,
|
verbose,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -36,9 +36,21 @@ use crate::mir::join_ir::JoinInst;
|
|||||||
use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper;
|
use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper;
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
|
/// Break semantics for ConditionOnly patterns
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: Explicit break condition semantics
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum BreakSemantics {
|
||||||
|
/// Break when condition is TRUE (e.g., find-first: break on ch == "b")
|
||||||
|
WhenMatch,
|
||||||
|
/// Break when condition is FALSE (e.g., trim: break on ch != whitespace)
|
||||||
|
WhenNotMatch,
|
||||||
|
}
|
||||||
|
|
||||||
/// ConditionOnly変数の再計算レシピ
|
/// ConditionOnly変数の再計算レシピ
|
||||||
///
|
///
|
||||||
/// Phase 93 P0: Trim patternの`is_ch_match`など、毎イテレーション再計算される変数
|
/// Phase 93 P0: Trim patternの`is_ch_match`など、毎イテレーション再計算される変数
|
||||||
|
/// Phase 93 Refactoring: Break semantics明確化
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConditionOnlyRecipe {
|
pub struct ConditionOnlyRecipe {
|
||||||
/// 変数名(例: "is_ch_match")
|
/// 変数名(例: "is_ch_match")
|
||||||
@ -47,15 +59,68 @@ pub struct ConditionOnlyRecipe {
|
|||||||
pub original_var: String,
|
pub original_var: String,
|
||||||
/// Trim pattern用のホワイトスペース文字リスト
|
/// Trim pattern用のホワイトスペース文字リスト
|
||||||
pub whitespace_chars: Vec<String>,
|
pub whitespace_chars: Vec<String>,
|
||||||
|
/// Break semantics (WhenMatch or WhenNotMatch)
|
||||||
|
pub break_semantics: BreakSemantics,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConditionOnlyRecipe {
|
impl ConditionOnlyRecipe {
|
||||||
/// Trim patternからレシピを作成
|
/// Trim patternからレシピを作成(ConditionOnly pattern用)
|
||||||
pub fn from_trim_helper(trim_helper: &TrimLoopHelper) -> Self {
|
///
|
||||||
|
/// Phase 93 P0: ConditionOnly pattern(WhenMatch semantics)
|
||||||
|
/// 例: find-first pattern(ch == "b"のときにbreak)
|
||||||
|
pub fn from_trim_helper_condition_only(trim_helper: &TrimLoopHelper) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: trim_helper.carrier_name.clone(),
|
name: trim_helper.carrier_name.clone(),
|
||||||
original_var: trim_helper.original_var.clone(),
|
original_var: trim_helper.original_var.clone(),
|
||||||
whitespace_chars: trim_helper.whitespace_chars.clone(),
|
whitespace_chars: trim_helper.whitespace_chars.clone(),
|
||||||
|
break_semantics: BreakSemantics::WhenMatch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trim patternからレシピを作成(Normal Trim pattern用)
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: Normal Trim pattern(WhenNotMatch semantics)
|
||||||
|
/// 例: str.trim()(ch != whitespaceのときにbreak)
|
||||||
|
pub fn from_trim_helper_normal_trim(trim_helper: &TrimLoopHelper) -> Self {
|
||||||
|
Self {
|
||||||
|
name: trim_helper.carrier_name.clone(),
|
||||||
|
original_var: trim_helper.original_var.clone(),
|
||||||
|
whitespace_chars: trim_helper.whitespace_chars.clone(),
|
||||||
|
break_semantics: BreakSemantics::WhenNotMatch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trim patternからレシピを作成(後方互換性)
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: WhenMatchをデフォルトとして使用
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn from_trim_helper(trim_helper: &TrimLoopHelper) -> Self {
|
||||||
|
Self::from_trim_helper_condition_only(trim_helper)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Break条件AST生成(semanticsに基づく)
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: Break semanticsに基づいて適切な条件を生成
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `WhenMatch`: carrier変数そのまま(TRUE時にbreak)
|
||||||
|
/// - `WhenNotMatch`: !carrier(FALSE時にbreak)
|
||||||
|
pub fn generate_break_condition(&self) -> ASTNode {
|
||||||
|
use crate::ast::{Span, UnaryOperator};
|
||||||
|
|
||||||
|
let carrier_var = ASTNode::Variable {
|
||||||
|
name: self.name.clone(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.break_semantics {
|
||||||
|
BreakSemantics::WhenMatch => carrier_var,
|
||||||
|
BreakSemantics::WhenNotMatch => ASTNode::UnaryOp {
|
||||||
|
operator: UnaryOperator::Not,
|
||||||
|
operand: Box::new(carrier_var),
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,6 +266,7 @@ mod tests {
|
|||||||
name: "is_ch_match".to_string(),
|
name: "is_ch_match".to_string(),
|
||||||
original_var: "ch".to_string(),
|
original_var: "ch".to_string(),
|
||||||
whitespace_chars: vec!["b".to_string()],
|
whitespace_chars: vec!["b".to_string()],
|
||||||
|
break_semantics: BreakSemantics::WhenMatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut body_local_env = LoopBodyLocalEnv::new();
|
let mut body_local_env = LoopBodyLocalEnv::new();
|
||||||
@ -245,6 +311,7 @@ mod tests {
|
|||||||
name: "is_ws".to_string(),
|
name: "is_ws".to_string(),
|
||||||
original_var: "ch".to_string(),
|
original_var: "ch".to_string(),
|
||||||
whitespace_chars: vec![" ".to_string(), "\t".to_string(), "\n".to_string()],
|
whitespace_chars: vec![" ".to_string(), "\t".to_string(), "\n".to_string()],
|
||||||
|
break_semantics: BreakSemantics::WhenMatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut body_local_env = LoopBodyLocalEnv::new();
|
let mut body_local_env = LoopBodyLocalEnv::new();
|
||||||
@ -280,6 +347,7 @@ mod tests {
|
|||||||
name: "is_ch_match".to_string(),
|
name: "is_ch_match".to_string(),
|
||||||
original_var: "ch".to_string(),
|
original_var: "ch".to_string(),
|
||||||
whitespace_chars: vec!["b".to_string()],
|
whitespace_chars: vec!["b".to_string()],
|
||||||
|
break_semantics: BreakSemantics::WhenMatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
let body_local_env = LoopBodyLocalEnv::new(); // Empty - no "ch"
|
let body_local_env = LoopBodyLocalEnv::new(); // Empty - no "ch"
|
||||||
|
|||||||
@ -71,7 +71,84 @@ impl Pattern2StepSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedule decision result with reasoning
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: Unified schedule decision with explicit reasons
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ScheduleDecision {
|
||||||
|
/// Whether body-init should come before break check
|
||||||
|
pub body_init_first: bool,
|
||||||
|
/// Human-readable reason for this decision
|
||||||
|
pub reason: &'static str,
|
||||||
|
/// Debug context for logging
|
||||||
|
pub debug_ctx: ScheduleDebugContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug context for schedule decisions
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ScheduleDebugContext {
|
||||||
|
pub has_body_local_init: bool,
|
||||||
|
pub has_loop_local_carrier: bool,
|
||||||
|
pub has_condition_only_recipe: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decide Pattern2 schedule based on loop characteristics
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: Single source of truth for schedule decisions
|
||||||
|
///
|
||||||
|
/// # Decision Logic
|
||||||
|
///
|
||||||
|
/// Body-init comes BEFORE break check if any of these conditions are true:
|
||||||
|
/// 1. ConditionOnly recipe exists (derived slots need recalculation)
|
||||||
|
/// 2. Body-local variables exist (break condition depends on them)
|
||||||
|
/// 3. Loop-local carriers exist (need initialization before use)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `body_local_env` - Body-local variable environment
|
||||||
|
/// * `carrier_info` - Carrier information (for loop-local detection)
|
||||||
|
/// * `has_condition_only_recipe` - Whether ConditionOnly derived slots exist
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `ScheduleDecision` with decision, reason, and debug context
|
||||||
|
pub(crate) fn decide_pattern2_schedule(
|
||||||
|
body_local_env: Option<&LoopBodyLocalEnv>,
|
||||||
|
carrier_info: &CarrierInfo,
|
||||||
|
has_condition_only_recipe: bool,
|
||||||
|
) -> ScheduleDecision {
|
||||||
|
let has_body_local_init = body_local_env.map(|env| !env.is_empty()).unwrap_or(false);
|
||||||
|
let has_loop_local_carrier = carrier_info
|
||||||
|
.carriers
|
||||||
|
.iter()
|
||||||
|
.any(|c| matches!(c.init, CarrierInit::LoopLocalZero));
|
||||||
|
|
||||||
|
let body_init_first = has_condition_only_recipe || has_body_local_init || has_loop_local_carrier;
|
||||||
|
|
||||||
|
let reason = if has_condition_only_recipe {
|
||||||
|
"ConditionOnly requires body-init before break"
|
||||||
|
} else if has_body_local_init {
|
||||||
|
"body-local variables require init before break"
|
||||||
|
} else if has_loop_local_carrier {
|
||||||
|
"loop-local carrier requires init before break"
|
||||||
|
} else {
|
||||||
|
"default schedule"
|
||||||
|
};
|
||||||
|
|
||||||
|
ScheduleDecision {
|
||||||
|
body_init_first,
|
||||||
|
reason,
|
||||||
|
debug_ctx: ScheduleDebugContext {
|
||||||
|
has_body_local_init,
|
||||||
|
has_loop_local_carrier,
|
||||||
|
has_condition_only_recipe,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Minimal context for deciding the step order.
|
/// Minimal context for deciding the step order.
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: Kept for backward compatibility, delegates to `decide_pattern2_schedule`
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) struct Pattern2ScheduleContext {
|
pub(crate) struct Pattern2ScheduleContext {
|
||||||
pub(crate) has_body_local_init: bool,
|
pub(crate) has_body_local_init: bool,
|
||||||
@ -81,30 +158,23 @@ pub(crate) struct Pattern2ScheduleContext {
|
|||||||
impl Pattern2ScheduleContext {
|
impl Pattern2ScheduleContext {
|
||||||
/// Build schedule context from environment.
|
/// Build schedule context from environment.
|
||||||
///
|
///
|
||||||
/// # Phase 93 P0: has_condition_only_recipe parameter
|
/// # Phase 93 Refactoring: Backward compatibility wrapper
|
||||||
///
|
///
|
||||||
/// When a ConditionOnly recipe exists, body-local init MUST happen before break check
|
/// Delegates to `decide_pattern2_schedule()` for actual decision making.
|
||||||
/// even if body_local_env is currently empty. This is because ConditionOnly variables
|
/// Note: When `has_condition_only_recipe` is true, we set `has_body_local_init` to true
|
||||||
/// (e.g., `is_ch_match`) are recalculated in body_init_block, and the break condition
|
/// to ensure the correct schedule is generated.
|
||||||
/// depends on them.
|
|
||||||
pub(crate) fn from_env(
|
pub(crate) fn from_env(
|
||||||
body_local_env: Option<&LoopBodyLocalEnv>,
|
body_local_env: Option<&LoopBodyLocalEnv>,
|
||||||
carrier_info: &CarrierInfo,
|
carrier_info: &CarrierInfo,
|
||||||
has_condition_only_recipe: bool,
|
has_condition_only_recipe: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Phase 93 P0: body_local_init is true if:
|
let decision = decide_pattern2_schedule(body_local_env, carrier_info, has_condition_only_recipe);
|
||||||
// 1. body_local_env has entries, OR
|
|
||||||
// 2. condition_only_recipe exists (will be emitted to body_init_block)
|
|
||||||
let has_body_local_init = body_local_env.map(|env| !env.is_empty()).unwrap_or(false)
|
|
||||||
|| has_condition_only_recipe;
|
|
||||||
let has_loop_local_carrier = carrier_info
|
|
||||||
.carriers
|
|
||||||
.iter()
|
|
||||||
.any(|c| matches!(c.init, CarrierInit::LoopLocalZero));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
has_body_local_init,
|
// Phase 93 Refactoring: Include condition_only_recipe in has_body_local_init
|
||||||
has_loop_local_carrier,
|
// to maintain backward compatibility with requires_body_init_before_break()
|
||||||
|
has_body_local_init: decision.debug_ctx.has_body_local_init
|
||||||
|
|| decision.debug_ctx.has_condition_only_recipe,
|
||||||
|
has_loop_local_carrier: decision.debug_ctx.has_loop_local_carrier,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +185,46 @@ impl Pattern2ScheduleContext {
|
|||||||
|
|
||||||
/// Build a schedule for Pattern 2 lowering.
|
/// Build a schedule for Pattern 2 lowering.
|
||||||
///
|
///
|
||||||
|
/// Phase 93 Refactoring: Now accepts `ScheduleDecision` for explicit reasoning
|
||||||
|
///
|
||||||
|
/// - Default P2: header → break → body-init → updates → tail
|
||||||
|
/// - Body-local break dependency (DigitPos/_atoi style):
|
||||||
|
/// header → body-init → break → updates → tail
|
||||||
|
pub(crate) fn build_pattern2_schedule_from_decision(
|
||||||
|
decision: &ScheduleDecision,
|
||||||
|
) -> Pattern2StepSchedule {
|
||||||
|
let schedule = if decision.body_init_first {
|
||||||
|
Pattern2StepSchedule {
|
||||||
|
steps: vec![
|
||||||
|
Pattern2StepKind::HeaderCond,
|
||||||
|
Pattern2StepKind::BodyInit,
|
||||||
|
Pattern2StepKind::BreakCheck,
|
||||||
|
Pattern2StepKind::Updates,
|
||||||
|
Pattern2StepKind::Tail,
|
||||||
|
],
|
||||||
|
reason: decision.reason,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Pattern2StepSchedule {
|
||||||
|
steps: vec![
|
||||||
|
Pattern2StepKind::HeaderCond,
|
||||||
|
Pattern2StepKind::BreakCheck,
|
||||||
|
Pattern2StepKind::BodyInit,
|
||||||
|
Pattern2StepKind::Updates,
|
||||||
|
Pattern2StepKind::Tail,
|
||||||
|
],
|
||||||
|
reason: decision.reason,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log_schedule_from_decision(decision, &schedule);
|
||||||
|
schedule
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a schedule for Pattern 2 lowering (legacy wrapper).
|
||||||
|
///
|
||||||
|
/// Phase 93 Refactoring: Kept for backward compatibility
|
||||||
|
///
|
||||||
/// - Default P2: header → break → body-init → updates → tail
|
/// - Default P2: header → break → body-init → updates → tail
|
||||||
/// - Body-local break dependency (DigitPos/_atoi style):
|
/// - Body-local break dependency (DigitPos/_atoi style):
|
||||||
/// header → body-init → break → updates → tail
|
/// header → body-init → break → updates → tail
|
||||||
@ -147,6 +257,28 @@ pub(crate) fn build_pattern2_schedule(ctx: &Pattern2ScheduleContext) -> Pattern2
|
|||||||
schedule
|
schedule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_schedule_from_decision(decision: &ScheduleDecision, schedule: &Pattern2StepSchedule) {
|
||||||
|
if !(env::joinir_dev_enabled() || joinir_test_debug_enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let steps_desc = schedule
|
||||||
|
.steps()
|
||||||
|
.iter()
|
||||||
|
.map(Pattern2StepKind::as_str)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" -> ");
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[phase93/schedule] steps={} reason={} ctx={{body_local_init={}, loop_local_carrier={}, condition_only={}}}",
|
||||||
|
steps_desc,
|
||||||
|
schedule.reason(),
|
||||||
|
decision.debug_ctx.has_body_local_init,
|
||||||
|
decision.debug_ctx.has_loop_local_carrier,
|
||||||
|
decision.debug_ctx.has_condition_only_recipe
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn log_schedule(ctx: &Pattern2ScheduleContext, schedule: &Pattern2StepSchedule) {
|
fn log_schedule(ctx: &Pattern2ScheduleContext, schedule: &Pattern2StepSchedule) {
|
||||||
if !(env::joinir_dev_enabled() || joinir_test_debug_enabled()) {
|
if !(env::joinir_dev_enabled() || joinir_test_debug_enabled()) {
|
||||||
return;
|
return;
|
||||||
@ -288,6 +420,7 @@ mod tests {
|
|||||||
Pattern2StepKind::Tail
|
Pattern2StepKind::Tail
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
// Phase 93 Refactoring: Reason is now preserved from backward compat wrapper
|
||||||
assert_eq!(schedule.reason(), "body-local break dependency");
|
assert_eq!(schedule.reason(), "body-local break dependency");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user