feat(mir): Phase 92 P2-2 - Body-local variable support for ConditionalStep

Phase 92 P2-2完了:ConditionalStepのcondition(ch == '\\'など)でbody-local変数をサポート

## 主要変更

### 1. condition_lowerer.rs拡張
- `lower_condition_to_joinir`に`body_local_env`パラメータ追加
- 変数解決優先度:ConditionEnv → LoopBodyLocalEnv
- すべての再帰ヘルパー(comparison, logical_and, logical_or, not, value_expression)対応

### 2. conditional_step_emitter.rs修正
- `emit_conditional_step_update`に`body_local_env`パラメータ追加
- condition loweringにbody-local環境を渡す

### 3. loop_with_break_minimal.rs修正
- break condition loweringをbody-local init の**後**に移動(line 411)
- header_break_lowering::lower_break_conditionにbody_local_env渡す
- emit_conditional_step_updateにbody_local_env渡す(line 620)

### 4. header_break_lowering.rs修正
- `lower_break_condition`に`body_local_env`パラメータ追加
- scope_managerにbody-local環境を渡す

### 5. 全呼び出し箇所修正
- expr_lowerer.rs (2箇所)
- method_call_lowerer.rs (2箇所)
- loop_with_if_phi_if_sum.rs (3箇所)
- loop_with_continue_minimal.rs (1箇所)
- carrier_update_emitter.rs (1箇所・legacy)

## アーキテクチャ改善

### Break Condition Lowering順序修正
旧: Header → **Break Cond** → Body-local Init
新: Header → **Body-local Init** → Break Cond

理由:break conditionが`ch == '\"'`のようにbody-local変数を参照する場合、body-local initが先に必要

### 変数解決優先度(Phase 92 P2-2)
1. ConditionEnv(ループパラメータ、captured変数)
2. LoopBodyLocalEnv(body-local変数like `ch`)

## テスト

### ビルド
 cargo build --release成功(30 warnings、0 errors)

### E2E
⚠️ body-local promotion問題でブロック(Phase 92範囲外)
- Pattern2はbody-local変数をcarrier promotionする必要あり
- 既存パターン(A-3 Trim, A-4 DigitPos)に`ch = get_char(i)`が該当しない
- **Phase 92 P2-2目標(condition loweringでbody-local変数サポート)は達成**

## 次タスク(Phase 92 P3以降)
- body-local variable promotion拡張(Pattern2で`ch`のような変数を扱う)
- P5b E2Eテスト完全動作確認

## Phase 92 P2-2完了
 Body-local変数のcondition lowering対応完了
 ConditionalStepでbody-local変数参照可能
 Break condition lowering順序修正
This commit is contained in:
nyash-codex
2025-12-16 17:08:15 +09:00
parent 3093ac2ca4
commit 568619df89
18 changed files with 371 additions and 55 deletions

View File

@ -29,6 +29,8 @@
- **Phase 135 完了**: ConditionLoweringBox allocator SSOTP0: 根治修正 + P1: contract_checks Fail-Fast 強化)。
- **Phase 136 完了**: MirBuilder Context SSOT 化(+ ValueId allocator 掃討)。
- **Phase 137-141 完了**: Loop Canonicalizer前処理 SSOTを実装・箱化・型安全化・統合・ドキュメントまで完了既定挙動は不変
- **Phase 91 完了**: P5b escaperecognition + parityまで完了。
- **Phase 92 進行中**: P5b escape の loweringConditionalStep emitter 箱化まで完了)。
- **Phase 88 完了**: continue + 可変ステップi=i+const 差分)を dev-only fixture で固定、StepCalculator Box 抽出。
- **Phase 89 完了**: P0ContinueReturn detector+ P1lowering 実装)完了。
- **Phase 90 完了**: ParseStringComposite + `Null` literal + ContinueReturn同一値の複数 return-ifを dev-only fixture で固定。
@ -53,11 +55,11 @@
## 次の指示書(優先順位)
### P0: Canonicalizer の適用範囲拡大(実ループ 1 本)
### P0: Phase 92 P2P5b E2E を 1 本通す
**状態**: ✅ Phase 137-141 完了、次は “適用対象を増やす” 段階
**状態**: ✅ P1 完了、P2 へ
**目的**: canonicalizer を「実アプリ由来ループ」に 1 本ずつ適用し、認識→decision→strict parity を増やして coverage を広げる既定挙動は不変、dev-only
**目的**: `ConditionalStep` を Pattern2 lowering で実際に使い、`test_pattern5b_escape_minimal.hako` を E2Eまずは VMで通す
SSOT:
- `docs/development/current/main/design/loop-canonicalizer.md`
@ -65,6 +67,8 @@ SSOT:
- `src/mir/loop_canonicalizer/mod.rs`
SSOT入口
- `src/mir/builder/control_flow/joinir/routing.rs``choose_pattern_kind`
Phase 記録:
- `docs/development/current/main/phases/phase-92/README.md`
**次に触るSSOT**:
- Loop系の設計: `docs/development/current/main/joinir-architecture-overview.md`

View File

@ -0,0 +1,50 @@
// Phase 92 P2-3: Minimal P5b E2E Test
//
// This is a MINIMAL test to verify ConditionalStep emission works.
// Pattern: Loop with break check + conditional increment (P5b escape pattern)
//
// Expected behavior:
// - i=0: not quote, not escape, i=1
// - i=1: not quote, not escape, i=2
// - i=2: not quote, IS escape, i=4
// - i=4: IS quote, break
// - Output: 4
static box Main {
main() {
local i = 0
local len = 10
local ch = "a"
local str = "ab\\cd\"ef"
loop(i < len) {
// Body statement: Simulate character access from string
// In real parser, this would be: ch = str.charAt(i)
// Here we simplify by checking position
if i == 2 {
ch = "\\" // Position 2: backslash
} else {
if i == 4 {
ch = "\"" // Position 4: quote
} else {
ch = "a" // Other positions: normal char
}
}
// Break check (P5b pattern requirement)
if ch == "\"" {
break
}
// Escape check with conditional increment (P5b pattern)
if ch == "\\" {
i = i + 2 // Escape: skip 2
} else {
i = i + 1 // Normal: skip 1
}
}
print(i)
return 0
}
}

View File

@ -0,0 +1,52 @@
// Phase 92 P2-3: P5b Test That Matches Pattern Recognizer
//
// This test is designed to exactly match what the pattern recognizer expects:
// 1. Body statement with simple ch assignment
// 2. Break check: if ch == "\"" { break }
// 3. Escape check: if ch == "\\" { i = i + 2 } else { i = i + 1 }
//
// Expected behavior (simulated string "a\\b\"c"):
// - i=0: ch='a', not quote, not escape, i=1
// - i=1: ch='\\', not quote, IS escape, i=3
// - i=3: ch='\"', IS quote, break
// - Output: 3
static box Main {
get_char(pos) {
// Simulate string access: "a\\b\"c"
// Position 0: 'a', Position 1: '\\', Position 3: '\"'
if pos == 1 {
return "\\"
}
if pos == 3 {
return "\""
}
return "a"
}
main() {
local i = 0
local len = 5
local ch = "a"
loop(i < len) {
// Body statement: simple assignment (not method call)
ch = me.get_char(i)
// Break check (P5b pattern requirement)
if ch == "\"" {
break
}
// Escape check with conditional increment (P5b pattern)
if ch == "\\" {
i = i + 2 // Escape: skip 2
} else {
i = i + 1 // Normal: skip 1
}
}
print(i)
return 0
}
}

View File

@ -0,0 +1,38 @@
// Phase 92 P2-3: Simplest P5b E2E Test (no body-local variables)
//
// This test uses position-based checks instead of a 'ch' variable
// to avoid the body-local variable complexity for initial testing.
//
// Pattern: Loop with break check + conditional increment (P5b escape pattern)
//
// Expected behavior:
// - i=0: not quote (0!=4), not escape (0!=2), i=1
// - i=1: not quote (1!=4), not escape (1!=2), i=2
// - i=2: not quote (2!=4), IS escape (2==2), i=4
// - i=4: IS quote (4==4), break
// - Output: 4
static box Main {
main() {
local i = 0
local len = 10
loop(i < len) {
// Break check (simulated: position 4 is quote)
if i == 4 {
break
}
// Escape check with conditional increment (P5b pattern)
// Position 2 is escape character
if i == 2 {
i = i + 2 // Escape: skip 2
} else {
i = i + 1 // Normal: skip 1
}
}
print(i)
return 0
}
}

View File

@ -34,6 +34,11 @@
- 実装: `src/mir/loop_canonicalizer/mod.rs`+ 観測: `src/mir/builder/control_flow/joinir/routing.rs`
- Phase 記録: `docs/development/current/main/phases/phase-137/README.md`
## 20251216Phase 92短報
- P5b escape の lowering を進行中。P1 で `ConditionalStep` emitter を箱化して、Pattern2 本体の肥大化を防いだ。
- Phase 記録: `docs/development/current/main/phases/phase-92/README.md`
## 20251214現状サマリ
補足docs が増えて迷子になったときの「置き場所ルールSSOT」:

View File

@ -281,6 +281,25 @@ pub enum CarrierRole {
---
## 再帰設計Loop / If の境界)
目的: 「複雑な分岐を再帰的に扱いたい」欲求と、責務混在検出正規化配線PHIの爆発を両立させる。
原則(おすすめの境界):
- **Loop canonicalizer が再帰してよい範囲**は「構造の観測/収集」まで。
- loop body 内の if を再帰的に走査して、`break/continue/return/update` の存在・位置・個数・更新種別ConstStep/ConditionalStep 等)を抽出するのは OK。
- ただし **JoinIR/MIR の配線BlockId/ValueId/PHI/merge/exit_bindingsには踏み込まない**
- **if の値契約PHI 相当)**は別箱If canonicalize/lowerに閉じる。
- loop 側では「if が存在する」「if の中に exit がある」などを `notes` / `missing_caps` に落とし、`chosen` は最終 lowerer 選択結果として安定に保つ。
- **nested looploop 内 loop**は当面 capability で Fail-Fast将来の P6 で解禁)。
- これにより “再帰地獄” を設計で遮断できる。
- **PHI 排除env + 継続への正規化)**の本線は `Structured JoinIR → Normalized JoinIR` 側に置く。
- canonicalizer は「どの carrier/env フィールドが更新されるか」を契約として宣言するだけに留める。
将来案(必要になったら):
- LoopSkeleton を肥大化させず、制御だけの再帰ツリー(`ControlTree/StepTree`)を **別 SSOT** として新設し、`LoopSkeleton` は “loop の箱” のまま維持する。
## 実装の入口(現状)
実装Phase 12はここ

View File

@ -10,6 +10,7 @@
- **Phase 135**: ConditionLoweringBox allocator SSOTValueId 衝突の根治)
- **Phase 136**: MirBuilder Context SSOT 化(+ ValueId allocator 掃討)
- **Phase 137141**: Loop Canonicalizer前処理 SSOT導入Phase 137 フォルダに統合して記録)
- **Phase 9192**: Selfhost depth2 coverageP5b escape recognition → lowering
## Phase フォルダ構成(推奨)

View File

@ -0,0 +1,48 @@
# Phase 92: Lowering (ConditionalStep / P5b Escape)
## Status
- ✅ P0: Contract + skeleton-to-lowering wiring (foundations)
- ✅ P1: Boxification / module isolation (ConditionalStep emitter)
- 🔶 P2: Wire emitter into Pattern2 + enable E2E
## Goal
- Phase 91 で認識した P5bescape skip: +1 / +2 の条件付き更新を、JoinIR lowering まで落とせるようにする。
- 既定挙動は不変。Fail-Fast を維持し、未対応は理由付きで止める。
## P0完了
- P0-1: ConditionalStep 契約SSOT
- 実装/記録: `src/mir/loop_canonicalizer/skeleton_types.rs`(契約コメント)
- 記録: `docs/development/current/main/phases/phase-92/p0-2-skeleton-to-context.md`
- P0-2: Skeleton → lowering への配線Option A
- `LoopPatternContext` に skeleton を optional に通した(後に P1 で境界を整理)
- P0-3: ConditionalStep を JoinIRSelect 等)で表現する基盤を追加
## P1完了: 箱化・モジュール化
- ConditionalStep lowering を 1 箱に隔離
- `src/mir/join_ir/lowering/common/conditional_step_emitter.rs`
- `src/mir/join_ir/lowering/common.rs` から export
- 目的: Pattern2 本体を肥大化させず、条件付き更新の責務を emitter に閉じ込める
- 境界の整理SSOT
- routing 層から skeleton を取り除き、Pattern2 側で skeleton/recognizer 情報の取得を内部化
- recognizer は cond/delta 抽出に限定(スコープ/寿命の判断を混ぜない)
- E2E fixture
- `test_pattern5b_escape_minimal.hako` は用意済みbody-local 対応後に実行固定)
## P2: E2E を通す最小1本
### P2-1: Pattern2 で emitter を実際に使用する
- Pattern2 lowerer の update emission で `ConditionalStep` を検出したら emitter に委譲する
- AST 再検出を増やさないcanonicalizer/recognizer の SSOT を使う)
### P2-2: body-local 変数(`ch`)問題を解く
- recognizer は cond/delta 抽出に限定し、スコープ/寿命の扱いは Skeleton 側へ寄せる
### P2-3: E2E fixture を 1 本だけ通す
- `test_pattern5b_escape_minimal.hako`Phase 91 の最小fixture
## Acceptance
- `NYASH_JOINIR_DEV=1 HAKO_JOINIR_STRICT=1` で parity が green のまま
- E2E が 1 本通る(まずは VM でOK
- 既定挙動不変フラグOFFで無影響

View File

@ -4,6 +4,11 @@
ConditionalStep情報をPattern2 lowererに渡すため、LoopPatternContextにSkeletonフィールドを追加しました。
## UpdatePhase 92 P1
Phase 92 P1 で境界を整理し、routing 層から skeleton を取り除いた。
本ドキュメントは「P0-2 で一度試した配線Option A」の記録として残し、現行の入口は `docs/development/current/main/phases/phase-92/README.md` を SSOT とする。
## SSOT原則
**中心的なルール**: Canonicalizerが一度検出したConditionalStep情報を、lowering側で再検出しない。Skeleton経由でUpdate情報を渡す。

View File

@ -408,7 +408,8 @@ pub fn emit_conditional_step_update(
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Step 1: Lower the condition expression
let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env)?;
// Phase 92 P2-2: No body-local support in legacy emitter (use common/conditional_step_emitter instead)
let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env, None)?;
instructions.extend(cond_insts);
// Step 2: Get carrier parameter ValueId from env

View File

@ -18,6 +18,7 @@ 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::lowering::loop_body_local_env::LoopBodyLocalEnv; // Phase 92 P2-2
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst, VarId};
use crate::mir::{MirType, ValueId};
@ -44,8 +45,14 @@ use crate::mir::{MirType, ValueId};
/// * `else_delta` - Delta to add when condition is false
/// * `alloc_value` - ValueId allocator closure
/// * `env` - ConditionEnv for variable resolution
/// * `body_local_env` - Phase 92 P2-2: Optional body-local variable environment
/// * `instructions` - Output vector to append instructions to
///
/// # Phase 92 P2-2: Body-Local Variable Support
///
/// When the condition references body-local variables (e.g., `ch == '\\'` in escape patterns),
/// the `body_local_env` provides name → ValueId mappings for variables defined in the loop body.
///
/// # Returns
///
/// ValueId of the computed update result (the dst of Select)
@ -63,6 +70,7 @@ pub fn emit_conditional_step_update(
else_delta: i64,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Phase 92 P1-1: Fail-Fast check - then_delta must differ from else_delta
@ -73,8 +81,8 @@ pub fn emit_conditional_step_update(
));
}
// Step 1: Lower the condition expression
let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env).map_err(|e| {
// Phase 92 P2-2: Lower the condition expression with body-local support
let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env, body_local_env).map_err(|e| {
format!(
"ConditionalStep invariant violated: condition must be pure expression for carrier '{}': {}",
carrier_name, e

View File

@ -17,6 +17,7 @@ use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeIns
use crate::mir::ValueId;
use super::condition_env::ConditionEnv;
use super::loop_body_local_env::LoopBodyLocalEnv; // Phase 92 P2-2: Body-local support
use super::method_call_lowerer::MethodCallLowerer;
/// Lower an AST condition to JoinIR instructions
@ -26,6 +27,7 @@ use super::method_call_lowerer::MethodCallLowerer;
/// * `cond_ast` - AST node representing the boolean condition
/// * `alloc_value` - ValueId allocator function
/// * `env` - ConditionEnv for variable resolution (JoinIR-local ValueIds)
/// * `body_local_env` - Phase 92 P2-2: Optional body-local variable environment
///
/// # Returns
///
@ -38,6 +40,16 @@ use super::method_call_lowerer::MethodCallLowerer;
/// - Logical: `a && b`, `a || b`, `!cond`
/// - Variables and literals
///
/// # Phase 92 P2-2: Body-Local Variable Support
///
/// When lowering conditions that reference body-local variables (e.g., `ch == '\\'`
/// in escape patterns), the `body_local_env` parameter provides name → ValueId
/// mappings for variables defined in the loop body.
///
/// Variable resolution priority:
/// 1. ConditionEnv (loop parameters, captured variables)
/// 2. LoopBodyLocalEnv (body-local variables like `ch`)
///
/// # Example
///
/// ```ignore
@ -45,6 +57,9 @@ use super::method_call_lowerer::MethodCallLowerer;
/// env.insert("i".to_string(), ValueId(0));
/// env.insert("end".to_string(), ValueId(1));
///
/// let mut body_env = LoopBodyLocalEnv::new();
/// body_env.insert("ch".to_string(), ValueId(5)); // Phase 92 P2-2
///
/// let mut value_counter = 2u32;
/// let mut alloc_value = || {
/// let id = ValueId(value_counter);
@ -52,30 +67,37 @@ use super::method_call_lowerer::MethodCallLowerer;
/// id
/// };
///
/// // Lower condition: i < end
/// // Lower condition: ch == '\\'
/// let (cond_value, cond_insts) = lower_condition_to_joinir(
/// condition_ast,
/// &mut alloc_value,
/// &env,
/// Some(&body_env), // Phase 92 P2-2: Body-local support
/// )?;
/// ```
pub fn lower_condition_to_joinir(
cond_ast: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
) -> Result<(ValueId, Vec<JoinInst>), String> {
let mut instructions = Vec::new();
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, &mut instructions)?;
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, body_local_env, &mut instructions)?;
Ok((result_value, instructions))
}
/// Recursive helper for condition lowering
///
/// Handles all supported AST node types and emits appropriate JoinIR instructions.
///
/// # Phase 92 P2-2
///
/// Added `body_local_env` parameter to support body-local variable resolution.
fn lower_condition_recursive(
cond_ast: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
match cond_ast {
@ -92,10 +114,10 @@ fn lower_condition_recursive(
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
| BinaryOperator::Greater => {
lower_comparison(operator, left, right, alloc_value, env, instructions)
lower_comparison(operator, left, right, alloc_value, env, body_local_env, instructions)
}
BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, instructions),
BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, instructions),
BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, body_local_env, instructions),
BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, body_local_env, instructions),
_ => Err(format!(
"Unsupported binary operator in condition: {:?}",
operator
@ -107,12 +129,22 @@ fn lower_condition_recursive(
operator: UnaryOperator::Not,
operand,
..
} => lower_not_operator(operand, alloc_value, env, instructions),
} => lower_not_operator(operand, alloc_value, env, body_local_env, instructions),
// Variables - resolve from ConditionEnv
ASTNode::Variable { name, .. } => env
.get(name)
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name)),
// Phase 92 P2-2: Variables - resolve from ConditionEnv or LoopBodyLocalEnv
ASTNode::Variable { name, .. } => {
// Priority 1: ConditionEnv (loop parameters, captured variables)
if let Some(value_id) = env.get(name) {
return Ok(value_id);
}
// Priority 2: LoopBodyLocalEnv (body-local variables like `ch`)
if let Some(body_env) = body_local_env {
if let Some(value_id) = body_env.get(name) {
return Ok(value_id);
}
}
Err(format!("Variable '{}' not found in ConditionEnv or LoopBodyLocalEnv", name))
}
// Literals - emit as constants
ASTNode::Literal { value, .. } => lower_literal(value, alloc_value, instructions),
@ -128,11 +160,12 @@ fn lower_comparison(
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Lower left and right sides
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
let cmp_op = match operator {
@ -162,11 +195,12 @@ fn lower_logical_and(
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Logical AND: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
// Emit BinOp And instruction
@ -186,11 +220,12 @@ fn lower_logical_or(
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Logical OR: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
// Emit BinOp Or instruction
@ -209,9 +244,10 @@ fn lower_not_operator(
operand: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
let operand_val = lower_condition_recursive(operand, alloc_value, env, instructions)?;
let operand_val = lower_condition_recursive(operand, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
// Emit UnaryOp Not instruction
@ -258,17 +294,33 @@ fn lower_literal(
///
/// This handles the common case where we need to evaluate a simple value
/// (variable or literal) as part of a comparison.
///
/// # Phase 92 P2-2
///
/// Added `body_local_env` parameter to support body-local variable resolution
/// (e.g., `ch` in `ch == '\\'`).
pub fn lower_value_expression(
expr: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
match expr {
// Variables - look up in ConditionEnv
ASTNode::Variable { name, .. } => env
.get(name)
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name)),
// Phase 92 P2-2: Variables - resolve from ConditionEnv or LoopBodyLocalEnv
ASTNode::Variable { name, .. } => {
// Priority 1: ConditionEnv (loop parameters, captured variables)
if let Some(value_id) = env.get(name) {
return Ok(value_id);
}
// Priority 2: LoopBodyLocalEnv (body-local variables like `ch`)
if let Some(body_env) = body_local_env {
if let Some(value_id) = body_env.get(name) {
return Ok(value_id);
}
}
Err(format!("Variable '{}' not found in ConditionEnv or LoopBodyLocalEnv", name))
}
// Literals - emit as constants
ASTNode::Literal { value, .. } => lower_literal(value, alloc_value, instructions),
@ -279,7 +331,7 @@ pub fn lower_value_expression(
left,
right,
..
} => lower_arithmetic_binop(operator, left, right, alloc_value, env, instructions),
} => lower_arithmetic_binop(operator, left, right, alloc_value, env, body_local_env, instructions),
// Phase 224-C: MethodCall support with arguments (e.g., s.length(), s.indexOf(ch))
ASTNode::MethodCall {
@ -289,7 +341,7 @@ pub fn lower_value_expression(
..
} => {
// 1. Lower receiver (object) to ValueId
let recv_val = lower_value_expression(object, alloc_value, env, instructions)?;
let recv_val = lower_value_expression(object, alloc_value, env, body_local_env, instructions)?;
// 2. Lower method call using MethodCallLowerer (will lower arguments internally)
MethodCallLowerer::lower_for_condition(
@ -316,10 +368,11 @@ fn lower_arithmetic_binop(
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
let bin_op = match operator {

View File

@ -211,7 +211,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
};
let (result_value, instructions) =
lower_condition_to_joinir(ast, &mut alloc_value, &condition_env)
lower_condition_to_joinir(ast, &mut alloc_value, &condition_env, None) // Phase 92 P2-2
.map_err(|e| ExprLoweringError::LoweringError(e))?;
// Phase 235: 保存しておき、テストから観察できるようにする
@ -297,7 +297,7 @@ impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'e
// Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT).
let (result_value, instructions) =
lower_condition_to_joinir(condition, &mut *context.alloc_value, &condition_env)
lower_condition_to_joinir(condition, &mut *context.alloc_value, &condition_env, None) // Phase 92 P2-2
.map_err(|e| e.to_string())?;
self.last_instructions = instructions;

View File

@ -63,8 +63,10 @@ mod tests;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, JoinFragmentMeta};
use crate::mir::join_ir::lowering::carrier_update_emitter::{
emit_carrier_update, emit_carrier_update_with_env, emit_conditional_step_update,
emit_carrier_update, emit_carrier_update_with_env,
};
// Phase 92 P2-1: Import ConditionalStep emitter from dedicated module
use crate::mir::join_ir::lowering::common::conditional_step_emitter::emit_conditional_step_update;
use crate::mir::join_ir::lowering::condition_to_joinir::ConditionEnv;
use crate::mir::loop_canonicalizer::UpdateKind;
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
@ -290,15 +292,8 @@ pub(crate) fn lower_loop_with_break_minimal(
// After condition lowering, allocate remaining ValueIds
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
// Phase 170-B / Phase 236-EX / Phase 244: Lower break condition
let (break_cond_value, break_cond_instructions) = lower_break_condition(
break_condition,
env,
carrier_info,
loop_var_name,
i_param,
&mut alloc_value,
)?;
// Phase 92 P2-2: Break condition lowering moved after body-local init
// (will be called later after body_local_env is populated)
let _const_1 = alloc_value(); // Increment constant
let _i_next = alloc_value(); // i + 1
@ -409,6 +404,20 @@ pub(crate) fn lower_loop_with_break_minimal(
);
}
// ------------------------------------------------------------------
// Phase 170-B / Phase 244 / Phase 92 P2-2: Lower break condition
// ------------------------------------------------------------------
// Phase 92 P2-2: Moved after body-local init to support body-local variable references
let (break_cond_value, break_cond_instructions) = lower_break_condition(
break_condition,
env,
carrier_info,
loop_var_name,
i_param,
&mut alloc_value,
body_local_env.as_ref().map(|e| &**e), // Phase 92 P2-2: Pass body_local_env
)?;
// ------------------------------------------------------------------
// Phase 170-B: Break Condition Check (delegated to condition_to_joinir)
// ------------------------------------------------------------------
@ -588,27 +597,39 @@ pub(crate) fn lower_loop_with_break_minimal(
continue;
}
// Phase 92 P0-3: Check if skeleton has ConditionalStep for this carrier
// Phase 92 P2-1: Check if skeleton has ConditionalStep for this carrier
if let Some(skel) = skeleton {
if let Some(carrier_slot) = skel.carriers.iter().find(|c| c.name == *carrier_name) {
if let UpdateKind::ConditionalStep { cond, then_delta, else_delta } = &carrier_slot.update_kind {
// Use emit_conditional_step_update instead of emit_carrier_update
// Phase 92 P2-1: Use ConditionalStepEmitter (dedicated module)
eprintln!(
"[joinir/pattern2] Phase 92 P0-3: ConditionalStep detected for carrier '{}': then={}, else={}",
"[joinir/pattern2] Phase 92 P2-1: ConditionalStep detected for carrier '{}': then={}, else={}",
carrier_name, then_delta, else_delta
);
// Phase 92 P2-1: Get carrier parameter ValueId (must be set by header PHI)
let carrier_param = carrier.join_id.ok_or_else(|| {
format!(
"[pattern2/conditional_step] Carrier '{}' join_id not set (header PHI not generated?)",
carrier_name
)
})?;
// Phase 92 P2-2: Pass body_local_env for condition lowering
let updated_value = emit_conditional_step_update(
carrier,
carrier_name,
carrier_param,
&*cond,
*then_delta,
*else_delta,
&mut alloc_value,
env,
body_local_env.as_ref().map(|e| &**e), // Phase 92 P2-2
&mut carrier_update_block,
)?;
).map_err(|e| format!("[pattern2/conditional_step] {}", e))?;
updated_carrier_values.push(updated_value);
eprintln!(
"[joinir/pattern2] Phase 92 P0-3: ConditionalStep carrier '{}' updated → {:?}",
"[joinir/pattern2] Phase 92 P2-1: ConditionalStep carrier '{}' updated → {:?}",
carrier_name, updated_value
);
continue; // Skip normal carrier update

View File

@ -77,11 +77,16 @@ pub(crate) fn lower_header_condition(
"[joinir/pattern2/phase244] Header condition via legacy path (not yet supported by ConditionLoweringBox)"
);
let mut shim = || alloc_value();
lower_condition_to_joinir(condition, &mut shim, env)
lower_condition_to_joinir(condition, &mut shim, env, None) // Phase 92 P2-2: No body-local for header
}
}
/// Lower the break condition via ExprLowerer (no legacy fallback).
///
/// # Phase 92 P2-2: Body-Local Variable Support
///
/// Added `body_local_env` parameter to support break conditions that reference
/// body-local variables (e.g., `ch == '"'` in escape patterns).
pub(crate) fn lower_break_condition(
break_condition: &ASTNode,
env: &ConditionEnv,
@ -89,14 +94,18 @@ pub(crate) fn lower_break_condition(
loop_var_name: &str,
loop_var_id: ValueId,
alloc_value: &mut dyn FnMut() -> ValueId,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
) -> Result<(ValueId, Vec<JoinInst>), String> {
use crate::mir::join_ir::lowering::condition_lowering_box::ConditionLoweringBox;
// Phase 92 P2-2: Use provided body_local_env or empty if None
let empty_body_env = LoopBodyLocalEnv::new();
let body_env_ref = body_local_env.unwrap_or(&empty_body_env);
let empty_captured_env = CapturedEnv::new();
let scope_manager = make_scope_manager(
env,
Some(&empty_body_env),
Some(body_env_ref), // Phase 92 P2-2: Pass body_local_env to scope
Some(&empty_captured_env),
carrier_info,
);

View File

@ -284,7 +284,7 @@ pub(crate) fn lower_loop_with_continue_minimal(
} else {
// Legacy path: condition_to_joinir (for complex conditions not yet supported)
eprintln!("[joinir/pattern4/phase244] Header condition via legacy path (not yet supported by ConditionLoweringBox)");
lower_condition_to_joinir(condition, &mut alloc_value, &env)?
lower_condition_to_joinir(condition, &mut alloc_value, &env, None)? // Phase 92 P2-2: No body-local for header
}
};

View File

@ -464,7 +464,7 @@ where
column: 1,
},
};
lower_value_expression(&var_node, alloc_value, cond_env, &mut limit_instructions)?
lower_value_expression(&var_node, alloc_value, cond_env, None, &mut limit_instructions)? // Phase 92 P2-2
}
};
@ -500,10 +500,10 @@ where
// Lower left-hand side (complex expression)
let mut instructions = Vec::new();
let lhs_val = lower_value_expression(left, alloc_value, cond_env, &mut instructions)?;
let lhs_val = lower_value_expression(left, alloc_value, cond_env, None, &mut instructions)?; // Phase 92 P2-2
// Lower right-hand side
let rhs_val = lower_value_expression(right, alloc_value, cond_env, &mut instructions)?;
let rhs_val = lower_value_expression(right, alloc_value, cond_env, None, &mut instructions)?; // Phase 92 P2-2
// Extract base variable name from LHS if possible
let var_name = extract_base_variable(left);

View File

@ -127,6 +127,7 @@ impl MethodCallLowerer {
arg_ast,
alloc_value,
env,
None, // Phase 92 P2-2: No body-local for method call args
instructions,
)?;
lowered_args.push(arg_val);
@ -305,6 +306,7 @@ impl MethodCallLowerer {
expr,
alloc_value,
cond_env,
None, // Phase 92 P2-2: No body-local for method call expressions
instructions,
),
}