Files
hakorune/src/mir/builder/loop_frontend_binding.rs

439 lines
15 KiB
Rust
Raw Normal View History

//! 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)]
pub enum BoundExpr {
/// 変数名 (e.g., "n")
Variable(String),
/// メソッド呼び出し (e.g., "me.tokens", "length")
MethodCall { receiver: String, method: String },
/// 不明(フォールバック用)
Unknown,
}
/// ループパターン種別
#[derive(Debug, Clone, PartialEq)]
pub enum LoopPattern {
/// 単純カウンタループ (print_tokens 相当)
/// has_accumulator: アキュムレータが存在するか
Simple { has_accumulator: bool },
/// フィルターパターン (if + push)
Filter,
/// マップパターン (変換 + push)
Map,
/// 不明(フォールバック用)
Unknown,
}
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()
/// loop(i < n) {
/// local v = arr.get(i)
/// if pred(v) { out.push(v) }
/// i = i + 1
/// }
/// return out
/// ```
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
bound_expr: BoundExpr::Variable("n".to_string()),
external_refs: vec!["arr".to_string(), "pred".to_string()],
pattern: LoopPattern::Filter,
}
}
/// ループ条件と本体から変数パターンを分析して 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::Unknown => {
// フォールバック: 0 を使う
json!({
"type": "Local",
"name": "n",
"expr": {
"type": "Int",
"value": 0
}
})
}
};
(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");
// n should reference the "n" variable (Var type)
assert_eq!(n_local["name"], "n");
assert_eq!(n_local["expr"]["type"], "Var");
assert_eq!(n_local["expr"]["name"], "n");
}
// 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");
}
}