Files
hakorune/src/mir/builder/loop_frontend_binding.rs
nyash-codex 5bc0fa861f feat(joinir): Phase 188 Pattern 1 Core Implementation + Phase 189 Planning
Phase 188 Status: Planning & Foundation Complete (100%)

Completed Tasks:
 Task 188-1: Error Inventory (5 patterns identified)
 Task 188-2: Pattern Classification (3 patterns selected)
 Task 188-3: Design (51KB comprehensive blueprint)
 Task 188-4: Implementation Foundation (1,802 lines scaffolding)
 Task 188-5: Verification & Documentation
 Pattern 1 Core Implementation: Detection + Lowering + Routing

Pattern 1 Implementation (322 lines):
- Pattern Detection: is_simple_while_pattern() in loop_pattern_detection.rs
- JoinIR Lowering: lower_simple_while_to_joinir() in simple_while_minimal.rs (219 lines)
  - Generates 3 functions: entry, loop_step (tail-recursive), k_exit
  - Implements condition negation: exit_cond = !(i < 3)
  - Tail-recursive Call pattern with state propagation
- Routing: Added "main" to function routing list in control_flow.rs
- Build:  SUCCESS (0 errors, 34 warnings)

Infrastructure Blocker Identified:
- merge_joinir_mir_blocks() only handles single-function JoinIR modules
- Pattern 1 generates 3 functions (entry + loop_step + k_exit)
- Current implementation only merges first function → loop body never executes
- Root cause: control_flow.rs line ~850 takes only .next() function

Phase 189 Planning Complete:
- Goal: Refactor merge_joinir_mir_blocks() for multi-function support
- Strategy: Sequential Merge (Option A) - merge all functions in call order
- Effort estimate: 5.5-7.5 hours
- Deliverables: README.md (16KB), current-analysis.md (15KB), QUICKSTART.md (5.8KB)

Files Modified/Created:
- src/mir/loop_pattern_detection.rs (+50 lines) - Pattern detection
- src/mir/join_ir/lowering/simple_while_minimal.rs (+219 lines) - Lowering
- src/mir/join_ir/lowering/loop_patterns.rs (+803 lines) - Foundation skeleton
- src/mir/join_ir/lowering/mod.rs (+2 lines) - Module registration
- src/mir/builder/control_flow.rs (+1 line) - Routing fix
- src/mir/builder/loop_frontend_binding.rs (+20 lines) - Binding updates
- tools/test_phase188_foundation.sh (executable) - Foundation verification
- CURRENT_TASK.md (updated) - Phase 188/189 status

Next: Phase 189 implementation (merge_joinir_mir_blocks refactor)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 07:47:22 +09:00

478 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Phase 50: Loop Frontend Binding
//!
//! このモジュールは cf_loop から JoinIR Frontend への変数マッピングを提供する。
//!
//! ## 問題背景
//!
//! JoinIR Frontend (`loop_patterns.rs`) はハードコードされた変数名を期待:
//! - `i`: カウンタ変数
//! - `acc`: アキュムレータ変数
//! - `n`: ループ上限
//!
//! しかし実際のループは異なる名前や構造を持つ:
//! - print_tokens: acc が存在しない(副作用のみ)
//! - filter: acc は `out` という名前
//!
//! ## 解決策
//!
//! LoopFrontendBinding が「実際の変数名」と「JoinIR 期待変数名」をマッピングし、
//! JSON v0 生成時に適切な Local 宣言を注入する。
use crate::ast::ASTNode;
/// ループ変数と JoinIR Frontend 期待変数のマッピング
#[derive(Debug, Clone)]
pub struct LoopFrontendBinding {
/// カウンタ変数名 (e.g., "i")
pub counter_var: String,
/// カウンタの初期値
pub counter_init: i64,
/// アキュムレータ変数名 (None for side-effect loops like print_tokens)
pub accumulator_var: Option<String>,
/// ループ上限式 (e.g., "n" or "arr.size()")
pub bound_expr: BoundExpr,
/// 外部参照 (e.g., ["me.tokens", "arr", "pred"])
pub external_refs: Vec<String>,
/// ループパターン種別
pub pattern: LoopPattern,
}
/// ループ上限の表現
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum BoundExpr {
/// 変数名 (e.g., "n")
Variable(String),
/// メソッド呼び出し (e.g., "me.tokens", "length")
MethodCall { receiver: String, method: String },
/// 定数値 (e.g., 3 for `i < 3`) - Phase 188-Impl-1
Constant(i64),
}
/// ループパターン種別
#[derive(Debug, Clone, PartialEq)]
pub enum LoopPattern {
/// 単純カウンタループ (print_tokens 相当)
/// has_accumulator: アキュムレータが存在するか
Simple { has_accumulator: bool },
/// フィルターパターン (if + push)
Filter,
}
impl LoopFrontendBinding {
/// Phase 52: Check if `me` receiver is needed for this loop
///
/// Returns true if any external_ref starts with "me" (e.g., "me.tokens")
/// This indicates an instance method that needs access to `me`.
pub fn needs_me_receiver(&self) -> bool {
self.external_refs
.iter()
.any(|r| r == "me" || r.starts_with("me."))
}
/// print_tokens 専用のバインディングを生成
///
/// print_tokens の構造:
/// ```nyash
/// local i = 0
/// loop(i < me.tokens.length()) {
/// local token = me.tokens.get(i)
/// print(token)
/// i = i + 1
/// }
/// ```
pub fn for_print_tokens() -> Self {
Self {
counter_var: "i".to_string(),
counter_init: 0,
accumulator_var: None, // print_tokens has no accumulator
bound_expr: BoundExpr::MethodCall {
receiver: "me.tokens".to_string(),
method: "length".to_string(),
},
external_refs: vec!["me.tokens".to_string()],
pattern: LoopPattern::Simple {
has_accumulator: false,
},
}
}
/// array_ext.filter 専用のバインディングを生成
///
/// filter の構造:
/// ```nyash
/// local out = new ArrayBox()
/// local i = 0
/// local n = arr.size() // ← JoinIR では arr.size() を直接評価
/// loop(i < n) {
/// local v = arr.get(i)
/// if pred(v) { out.push(v) }
/// i = i + 1
/// }
/// return out
/// ```
///
/// Phase 56: `BoundExpr::Variable("n")` から `BoundExpr::MethodCall` に変更。
/// `n` はループ外で宣言されているため、JoinIR コンテキストに存在しない。
/// 代わりに `arr.size()` を直接呼び出して `n` を初期化する。
pub fn for_array_filter() -> Self {
Self {
counter_var: "i".to_string(),
counter_init: 0,
accumulator_var: Some("out".to_string()), // filter has "out" as accumulator
// Phase 56: Variable("n") → MethodCall { arr, size }
// n はループ外で宣言されているため、arr.size() を直接呼び出す
bound_expr: BoundExpr::MethodCall {
receiver: "arr".to_string(),
method: "size".to_string(),
},
external_refs: vec!["arr".to_string(), "pred".to_string()],
pattern: LoopPattern::Filter,
}
}
/// Phase 188-Impl-1: main function simple while loop binding
///
/// Structure for loop_min_while.hako:
/// ```nyash
/// local i = 0
/// loop(i < 3) {
/// print(i)
/// i = i + 1
/// }
/// ```
///
/// This is a simple counter loop with:
/// - Counter variable: i (starts at 0)
/// - Bound: constant 3
/// - No accumulator (side-effect only loop)
/// - No external refs
pub fn for_main_simple_while() -> Self {
Self {
counter_var: "i".to_string(),
counter_init: 0,
accumulator_var: None, // No accumulator
bound_expr: BoundExpr::Constant(3), // Constant bound
external_refs: vec![], // No external refs
pattern: LoopPattern::Simple {
has_accumulator: false,
},
}
}
/// ループ条件と本体から変数パターンを分析して Binding を生成
///
/// Phase 50-3 で実装予定の汎用分析。現在は関数名ベースのハードコード。
#[allow(dead_code)]
pub fn analyze(_condition: &ASTNode, _body: &[ASTNode]) -> Option<Self> {
// TODO: Phase 50-3 で AST 分析を実装
// 1. カウンタ変数の検出 (i = i + 1 パターン)
// 2. アキュムレータの検出 (return で返される変数)
// 3. ループ上限の検出 (i < X の X)
None
}
/// Phase 52: ドット区切りの変数名を Field ノード構造に変換
///
/// 例: "me.tokens" → {"type": "Field", "object": {"type": "Var", "name": "me"}, "field": "tokens"}
/// 例: "arr" → {"type": "Var", "name": "arr"}
fn receiver_to_json(receiver: &str) -> serde_json::Value {
use serde_json::json;
if let Some(dot_pos) = receiver.find('.') {
// ドット区切りがある場合 → Field ノードに分解
let object_name = &receiver[..dot_pos];
let field_name = &receiver[dot_pos + 1..];
// ネストしたフィールドアクセス (e.g., "a.b.c") は再帰的に処理
if field_name.contains('.') {
// "me.tokens.inner" → Field(Field(Var("me"), "tokens"), "inner")
let inner_receiver = Self::receiver_to_json(field_name);
json!({
"type": "Field",
"object": { "type": "Var", "name": object_name },
"field": inner_receiver
})
} else {
// 単一レベルのフィールドアクセス (e.g., "me.tokens")
json!({
"type": "Field",
"object": { "type": "Var", "name": object_name },
"field": field_name
})
}
} else {
// ドットなし → 単純な Var ノード
json!({
"type": "Var",
"name": receiver
})
}
}
/// JoinIR Frontend 用の JSON v0 Local 宣言を生成
///
/// Returns: (i_local, acc_local, n_local) の JSON Value タプル
///
/// Note: JoinIR Frontend expects specific type names:
/// - "Int" for integer literals (with "value" field)
/// - "Var" for variable references (with "name" field)
/// - "Field" for field access (with "object", "field" fields) - Phase 52
/// - "Method" for method calls (with "receiver", "method", "args" fields)
/// - "NewBox" for box instantiation
pub fn generate_local_declarations(
&self,
) -> (serde_json::Value, serde_json::Value, serde_json::Value) {
use serde_json::json;
// i の Local 宣言
// JoinIR Frontend expects "Int" type with direct "value" field
let i_local = json!({
"type": "Local",
"name": "i",
"expr": {
"type": "Int",
"value": self.counter_init
}
});
// acc の Local 宣言(合成または実際の変数から)
let acc_local = if self.accumulator_var.is_some() {
// 実際のアキュムレータがある場合filter 等)
// "out" → "acc" にリネームして宣言
json!({
"type": "Local",
"name": "acc",
"expr": {
"type": "NewBox",
"box_name": "ArrayBox",
"args": []
}
})
} else {
// アキュムレータがない場合print_tokens 等)
// 合成の unit 値を使うInt 0 として表現)
json!({
"type": "Local",
"name": "acc",
"expr": {
"type": "Int",
"value": 0
}
})
};
// n の Local 宣言
let n_local = match &self.bound_expr {
BoundExpr::Variable(var) => {
// 既存変数を参照filter の n 等)
// JoinIR Frontend expects "Var" type
json!({
"type": "Local",
"name": "n",
"expr": {
"type": "Var",
"name": var
}
})
}
BoundExpr::MethodCall { receiver, method } => {
// Phase 52: メソッド呼び出しを評価print_tokens の me.tokens.length() 等)
// receiver が "me.tokens" のようにドット区切りの場合は Field ノードに分解
let receiver_json = Self::receiver_to_json(receiver);
json!({
"type": "Local",
"name": "n",
"expr": {
"type": "Method",
"receiver": receiver_json,
"method": method,
"args": []
}
})
}
BoundExpr::Constant(value) => {
// Phase 188-Impl-1: 定数値loop_min_while.hako の 3 等)
// JoinIR Frontend expects "Int" type with direct "value" field
json!({
"type": "Local",
"name": "n",
"expr": {
"type": "Int",
"value": value
}
})
}
};
(i_local, acc_local, n_local)
}
/// ループ本体の変数名をリネーム
///
/// 実際の変数名 (e.g., "out") を JoinIR 期待名 (e.g., "acc") に置換
pub fn rename_body_variables(&self, body_json: &mut Vec<serde_json::Value>) {
if let Some(ref acc_name) = self.accumulator_var {
if acc_name != "acc" {
// "out" → "acc" にリネーム
Self::rename_variable_in_json(body_json, acc_name, "acc");
}
}
}
/// JSON 内の変数名を再帰的にリネーム
fn rename_variable_in_json(json_array: &mut Vec<serde_json::Value>, from: &str, to: &str) {
for item in json_array.iter_mut() {
Self::rename_variable_in_value(item, from, to);
}
}
fn rename_variable_in_value(value: &mut serde_json::Value, from: &str, to: &str) {
match value {
serde_json::Value::Object(map) => {
// Variable/Var ノードの名前をチェック
// Note: ast_to_json uses "kind", JoinIR Frontend expects "type"
let is_var = map.get("kind").and_then(|v| v.as_str()) == Some("Variable")
|| map.get("type").and_then(|v| v.as_str()) == Some("Var");
if is_var {
if let Some(name) = map.get_mut("name") {
if name.as_str() == Some(from) {
*name = serde_json::Value::String(to.to_string());
}
}
}
// Local ノードの名前をチェック
if map.get("type").and_then(|v| v.as_str()) == Some("Local")
|| map.get("kind").and_then(|v| v.as_str()) == Some("Local")
{
if let Some(name) = map.get_mut("name") {
if name.as_str() == Some(from) {
*name = serde_json::Value::String(to.to_string());
}
}
}
// 再帰的に処理
for (_, v) in map.iter_mut() {
Self::rename_variable_in_value(v, from, to);
}
}
serde_json::Value::Array(arr) => {
for item in arr.iter_mut() {
Self::rename_variable_in_value(item, from, to);
}
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_tokens_binding() {
let binding = LoopFrontendBinding::for_print_tokens();
assert_eq!(binding.counter_var, "i");
assert_eq!(binding.counter_init, 0);
assert!(binding.accumulator_var.is_none());
assert_eq!(
binding.pattern,
LoopPattern::Simple {
has_accumulator: false
}
);
}
#[test]
fn test_array_filter_binding() {
let binding = LoopFrontendBinding::for_array_filter();
assert_eq!(binding.counter_var, "i");
assert_eq!(binding.accumulator_var, Some("out".to_string()));
assert_eq!(binding.pattern, LoopPattern::Filter);
}
#[test]
fn test_local_declarations_no_acc() {
let binding = LoopFrontendBinding::for_print_tokens();
let (i_local, acc_local, _n_local) = binding.generate_local_declarations();
// i should be initialized to 0
assert_eq!(i_local["name"], "i");
assert_eq!(i_local["expr"]["type"], "Int");
assert_eq!(i_local["expr"]["value"], 0);
// acc should be synthetic (Int 0 for side-effect loops)
assert_eq!(acc_local["name"], "acc");
assert_eq!(acc_local["expr"]["type"], "Int");
assert_eq!(acc_local["expr"]["value"], 0);
}
#[test]
fn test_local_declarations_with_acc() {
let binding = LoopFrontendBinding::for_array_filter();
let (i_local, acc_local, n_local) = binding.generate_local_declarations();
// i should be initialized to 0
assert_eq!(i_local["name"], "i");
assert_eq!(i_local["expr"]["type"], "Int");
// acc should be new ArrayBox()
assert_eq!(acc_local["name"], "acc");
assert_eq!(acc_local["expr"]["type"], "NewBox");
assert_eq!(acc_local["expr"]["box_name"], "ArrayBox");
// Phase 56: n should be arr.size() method call (not a Var anymore)
assert_eq!(n_local["name"], "n");
assert_eq!(n_local["expr"]["type"], "Method");
assert_eq!(n_local["expr"]["method"], "size");
// receiver should be a simple Var node pointing to "arr"
let receiver = &n_local["expr"]["receiver"];
assert_eq!(receiver["type"], "Var");
assert_eq!(receiver["name"], "arr");
}
// Phase 52: receiver_to_json のテスト
#[test]
fn test_receiver_to_json_simple_var() {
let json = LoopFrontendBinding::receiver_to_json("arr");
assert_eq!(json["type"], "Var");
assert_eq!(json["name"], "arr");
}
#[test]
fn test_receiver_to_json_field_access() {
let json = LoopFrontendBinding::receiver_to_json("me.tokens");
assert_eq!(json["type"], "Field");
assert_eq!(json["object"]["type"], "Var");
assert_eq!(json["object"]["name"], "me");
assert_eq!(json["field"], "tokens");
}
#[test]
fn test_print_tokens_n_local_has_field() {
// Phase 52: print_tokens の n (me.tokens.length()) が Field ノードを使っているか確認
let binding = LoopFrontendBinding::for_print_tokens();
let (_i_local, _acc_local, n_local) = binding.generate_local_declarations();
// n = me.tokens.length() の構造を確認
assert_eq!(n_local["name"], "n");
assert_eq!(n_local["expr"]["type"], "Method");
assert_eq!(n_local["expr"]["method"], "length");
// receiver が Field ノードであることを確認
let receiver = &n_local["expr"]["receiver"];
assert_eq!(receiver["type"], "Field");
assert_eq!(receiver["object"]["type"], "Var");
assert_eq!(receiver["object"]["name"], "me");
assert_eq!(receiver["field"], "tokens");
}
}