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:
nyash-codex
2025-12-16 15:53:40 +09:00
parent a718af3213
commit 3093ac2ca4
7 changed files with 330 additions and 21 deletions

View File

@ -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};

View 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"));
}
}