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:
@ -29,6 +29,8 @@
|
||||
- **Phase 135 完了**: ConditionLoweringBox allocator SSOT(P0: 根治修正 + P1: contract_checks Fail-Fast 強化)。
|
||||
- **Phase 136 完了**: MirBuilder Context SSOT 化(+ ValueId allocator 掃討)。
|
||||
- **Phase 137-141 完了**: Loop Canonicalizer(前処理 SSOT)を実装・箱化・型安全化・統合・ドキュメントまで完了(既定挙動は不変)。
|
||||
- **Phase 91 完了**: P5b escape(recognition + parity)まで完了。
|
||||
- **Phase 92 進行中**: P5b escape の lowering(ConditionalStep emitter 箱化まで完了)。
|
||||
- **Phase 88 完了**: continue + 可変ステップ(i=i+const 差分)を dev-only fixture で固定、StepCalculator Box 抽出。
|
||||
- **Phase 89 完了**: P0(ContinueReturn detector)+ P1(lowering 実装)完了。
|
||||
- **Phase 90 完了**: ParseStringComposite + `Null` literal + ContinueReturn(同一値の複数 return-if)を dev-only fixture で固定。
|
||||
@ -53,11 +55,11 @@
|
||||
|
||||
## 次の指示書(優先順位)
|
||||
|
||||
### P0: Canonicalizer の適用範囲拡大(実ループ 1 本)
|
||||
### P0: Phase 92 P2(P5b 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`
|
||||
|
||||
50
apps/tests/test_pattern5b_minimal_p2.hako
Normal file
50
apps/tests/test_pattern5b_minimal_p2.hako
Normal 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
|
||||
}
|
||||
}
|
||||
52
apps/tests/test_pattern5b_recognized.hako
Normal file
52
apps/tests/test_pattern5b_recognized.hako
Normal 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
|
||||
}
|
||||
}
|
||||
38
apps/tests/test_pattern5b_simple.hako
Normal file
38
apps/tests/test_pattern5b_simple.hako
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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`
|
||||
|
||||
## 2025‑12‑16:Phase 92(短報)
|
||||
|
||||
- P5b escape の lowering を進行中。P1 で `ConditionalStep` emitter を箱化して、Pattern2 本体の肥大化を防いだ。
|
||||
- Phase 記録: `docs/development/current/main/phases/phase-92/README.md`
|
||||
|
||||
## 2025‑12‑14:現状サマリ
|
||||
|
||||
(補足)docs が増えて迷子になったときの「置き場所ルール(SSOT)」:
|
||||
|
||||
@ -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 loop(loop 内 loop)**は当面 capability で Fail-Fast(将来の P6 で解禁)。
|
||||
- これにより “再帰地獄” を設計で遮断できる。
|
||||
- **PHI 排除(env + 継続への正規化)**の本線は `Structured JoinIR → Normalized JoinIR` 側に置く。
|
||||
- canonicalizer は「どの carrier/env フィールドが更新されるか」を契約として宣言するだけに留める。
|
||||
|
||||
将来案(必要になったら):
|
||||
- LoopSkeleton を肥大化させず、制御だけの再帰ツリー(`ControlTree/StepTree`)を **別 SSOT** として新設し、`LoopSkeleton` は “loop の箱” のまま維持する。
|
||||
|
||||
## 実装の入口(現状)
|
||||
|
||||
実装(Phase 1–2)はここ:
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
- **Phase 135**: ConditionLoweringBox allocator SSOT(ValueId 衝突の根治)
|
||||
- **Phase 136**: MirBuilder Context SSOT 化(+ ValueId allocator 掃討)
|
||||
- **Phase 137–141**: Loop Canonicalizer(前処理 SSOT)導入(Phase 137 フォルダに統合して記録)
|
||||
- **Phase 91–92**: Selfhost depth‑2 coverage(P5b escape recognition → lowering)
|
||||
|
||||
## Phase フォルダ構成(推奨)
|
||||
|
||||
|
||||
48
docs/development/current/main/phases/phase-92/README.md
Normal file
48
docs/development/current/main/phases/phase-92/README.md
Normal 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 で認識した P5b(escape 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 を JoinIR(Select 等)で表現する基盤を追加
|
||||
|
||||
## 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で無影響)
|
||||
@ -4,6 +4,11 @@
|
||||
|
||||
ConditionalStep情報をPattern2 lowererに渡すため、LoopPatternContextにSkeletonフィールドを追加しました。
|
||||
|
||||
## Update(Phase 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情報を渡す。
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user