refactor(joinir): Phase 92 P1 - 箱化モジュール化・レガシー削除
P1-1: ConditionalStep lowering を1箱に隔離 - 新規作成: src/mir/join_ir/lowering/common/conditional_step_emitter.rs - emit_conditional_step_update() を carrier_update_emitter.rs から移動 - Fail-Fast 不変条件チェック追加(then_delta != else_delta) - 副作用を減らしたクリーンなインターフェース - 包括的なテストスイート(3テスト) P1-0: 境界SSOTの固定 - routing.rs: skeleton 設定をrouting層から削除 - pattern2_with_break.rs: skeleton 取得をlower()内部に閉じ込め - parity_checker から skeleton を直接取得 - skeleton の使用を Pattern2 のみに限定 P1-2: escape recognizer をSSOTに戻す - escape_pattern_recognizer.rs: 未使用フィールド削除 - quote_char, escape_char 削除(使われていない) - 責務を cond/delta 抽出のみに限定 - pattern_recognizer.rs: デフォルト値を使用 P1-3: E2Eテスト作成(実行は後回し) - apps/tests/test_pattern5b_escape_minimal.hako 作成 - body-local 変数対応後に検証予定 テスト結果: - conditional_step_emitter tests: 3 passed - Pattern2 tests: 18 passed - Regression: 0 failures 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
//! CFG sanity checks and dispatcher helpers for MIR-based lowering.
|
||||
|
||||
pub mod case_a;
|
||||
pub mod conditional_step_emitter; // Phase 92 P1-1: ConditionalStep emission module
|
||||
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||
|
||||
259
src/mir/join_ir/lowering/common/conditional_step_emitter.rs
Normal file
259
src/mir/join_ir/lowering/common/conditional_step_emitter.rs
Normal file
@ -0,0 +1,259 @@
|
||||
//! Phase 92 P1-1: ConditionalStep Emitter Module
|
||||
//!
|
||||
//! Specialized emitter for conditional step updates in P5b (escape sequence) patterns.
|
||||
//! Extracted from carrier_update_emitter.rs for improved modularity and single responsibility.
|
||||
//!
|
||||
//! # Design
|
||||
//!
|
||||
//! - **Single Responsibility**: Handles only ConditionalStep emission with Fail-Fast validation
|
||||
//! - **Isolated Logic**: No side effects, pure JoinIR generation
|
||||
//! - **Clean Interface**: Exports only `emit_conditional_step_update()`
|
||||
//!
|
||||
//! # Fail-Fast Invariants
|
||||
//!
|
||||
//! 1. `then_delta != else_delta` - ConditionalStep must have different deltas
|
||||
//! 2. Condition must be pure expression (no side effects)
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::condition_lowerer::lower_condition_to_joinir;
|
||||
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst, VarId};
|
||||
use crate::mir::{MirType, ValueId};
|
||||
|
||||
/// Emit JoinIR instructions for conditional step update (Phase 92 P1-1)
|
||||
///
|
||||
/// Handles the P5b escape sequence pattern where carrier update depends on a condition:
|
||||
/// ```text
|
||||
/// if escape_cond { carrier = carrier + then_delta }
|
||||
/// else { carrier = carrier + else_delta }
|
||||
/// ```
|
||||
///
|
||||
/// This generates:
|
||||
/// 1. Lower condition expression to get cond_id
|
||||
/// 2. Compute then_result = carrier + then_delta
|
||||
/// 3. Compute else_result = carrier + else_delta
|
||||
/// 4. JoinInst::Select { dst: carrier_new, cond: cond_id, then_val: then_result, else_val: else_result }
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `carrier_name` - Name of the carrier variable (e.g., "i", "pos")
|
||||
/// * `carrier_param` - ValueId of the carrier parameter in JoinIR
|
||||
/// * `cond_ast` - AST node for the condition expression (e.g., `ch == '\\'`)
|
||||
/// * `then_delta` - Delta to add when condition is true
|
||||
/// * `else_delta` - Delta to add when condition is false
|
||||
/// * `alloc_value` - ValueId allocator closure
|
||||
/// * `env` - ConditionEnv for variable resolution
|
||||
/// * `instructions` - Output vector to append instructions to
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// ValueId of the computed update result (the dst of Select)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error if:
|
||||
/// - `then_delta == else_delta` (Fail-Fast: invariant violation)
|
||||
/// - Condition lowering fails (Fail-Fast: must be pure expression)
|
||||
pub fn emit_conditional_step_update(
|
||||
carrier_name: &str,
|
||||
carrier_param: ValueId,
|
||||
cond_ast: &ASTNode,
|
||||
then_delta: i64,
|
||||
else_delta: i64,
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Phase 92 P1-1: Fail-Fast check - then_delta must differ from else_delta
|
||||
if then_delta == else_delta {
|
||||
return Err(format!(
|
||||
"ConditionalStep invariant violated: then_delta ({}) must differ from else_delta ({}) for carrier '{}'",
|
||||
then_delta, else_delta, carrier_name
|
||||
));
|
||||
}
|
||||
|
||||
// Step 1: Lower the condition expression
|
||||
let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env).map_err(|e| {
|
||||
format!(
|
||||
"ConditionalStep invariant violated: condition must be pure expression for carrier '{}': {}",
|
||||
carrier_name, e
|
||||
)
|
||||
})?;
|
||||
instructions.extend(cond_insts);
|
||||
|
||||
// Step 2: Compute then_result = carrier + then_delta
|
||||
let then_const_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: then_const_id,
|
||||
value: ConstValue::Integer(then_delta),
|
||||
}));
|
||||
let then_result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: then_result,
|
||||
op: BinOpKind::Add,
|
||||
lhs: carrier_param,
|
||||
rhs: then_const_id,
|
||||
}));
|
||||
|
||||
// Step 3: Compute else_result = carrier + else_delta
|
||||
let else_const_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: else_const_id,
|
||||
value: ConstValue::Integer(else_delta),
|
||||
}));
|
||||
let else_result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: else_result,
|
||||
op: BinOpKind::Add,
|
||||
lhs: carrier_param,
|
||||
rhs: else_const_id,
|
||||
}));
|
||||
|
||||
// Step 4: Emit Select instruction
|
||||
let carrier_new: VarId = alloc_value();
|
||||
instructions.push(JoinInst::Select {
|
||||
dst: carrier_new,
|
||||
cond: cond_id,
|
||||
then_val: then_result,
|
||||
else_val: else_result,
|
||||
type_hint: Some(MirType::Integer), // Carrier is always Integer
|
||||
});
|
||||
|
||||
Ok(carrier_new)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span};
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
|
||||
fn test_env() -> ConditionEnv {
|
||||
let mut env = ConditionEnv::new();
|
||||
env.insert("ch".to_string(), ValueId(10));
|
||||
env.insert("i".to_string(), ValueId(20));
|
||||
env
|
||||
}
|
||||
|
||||
fn ch_eq_backslash() -> ASTNode {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "ch".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::String("\\".to_string()),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_conditional_step_basic() {
|
||||
// Test: if ch == "\\" { i = i + 2 } else { i = i + 1 }
|
||||
let env = test_env();
|
||||
let cond_ast = ch_eq_backslash();
|
||||
|
||||
let mut value_counter = 100u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_conditional_step_update(
|
||||
"i",
|
||||
ValueId(20), // carrier_param
|
||||
&cond_ast,
|
||||
2, // then_delta (escape: i + 2)
|
||||
1, // else_delta (normal: i + 1)
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate:
|
||||
// 1. Condition lowering instructions (Compare)
|
||||
// 2. Const(2), BinOp(Add, i, 2) for then_result
|
||||
// 3. Const(1), BinOp(Add, i, 1) for else_result
|
||||
// 4. Select(cond, then_result, else_result)
|
||||
assert!(instructions.len() >= 6); // At least 6 instructions
|
||||
|
||||
// Check Select instruction exists
|
||||
let select_found = instructions.iter().any(|inst| matches!(inst, JoinInst::Select { .. }));
|
||||
assert!(select_found, "Select instruction should be emitted");
|
||||
|
||||
assert!(result_id.0 >= 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fail_fast_equal_deltas() {
|
||||
// Phase 92 P1-1: Test Fail-Fast when then_delta == else_delta
|
||||
let env = test_env();
|
||||
let cond_ast = ch_eq_backslash();
|
||||
|
||||
let mut value_counter = 200u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_conditional_step_update(
|
||||
"i",
|
||||
ValueId(20),
|
||||
&cond_ast,
|
||||
2, // then_delta
|
||||
2, // else_delta (SAME! Should fail)
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.contains("ConditionalStep invariant violated"));
|
||||
assert!(err.contains("then_delta"));
|
||||
assert!(err.contains("must differ from else_delta"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fail_fast_invalid_condition() {
|
||||
// Phase 92 P1-1: Test Fail-Fast when condition is not pure
|
||||
let env = ConditionEnv::new(); // Empty env - ch will not be found
|
||||
|
||||
let cond_ast = ch_eq_backslash();
|
||||
|
||||
let mut value_counter = 300u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_conditional_step_update(
|
||||
"i",
|
||||
ValueId(20),
|
||||
&cond_ast,
|
||||
2, // then_delta
|
||||
1, // else_delta
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.contains("ConditionalStep invariant violated"));
|
||||
assert!(err.contains("condition must be pure expression"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user