feat(joinir): Phase 50 Loop Frontend Binding layer

Phase 50 implements the Loop Frontend Binding layer that maps
actual loop variables to JoinIR Frontend's expected names (i, acc, n).

## Changes

- Add loop_frontend_binding.rs module with:
  - LoopFrontendBinding struct for variable mapping
  - for_print_tokens() and for_array_filter() factory methods
  - generate_local_declarations() for JSON v0 format
  - rename_body_variables() for out → acc renaming

- Integrate binding with cf_loop_joinir_impl:
  - Create binding based on function name
  - Inject i/acc/n Local declarations into JSON v0
  - Use correct JoinIR Frontend type names (Int/Var/Method)

## Limitations Found

JoinIR Frontend doesn't support:
- Field access (me.tokens) - blocks print_tokens
- NewBox (new ArrayBox()) - blocks array_filter

These will be addressed in Phase 51.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-28 20:59:54 +09:00
parent fb1b3132dd
commit 3dc691d39f
4 changed files with 394 additions and 5 deletions

View File

@ -132,17 +132,50 @@ impl super::MirBuilder {
func_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
use super::loop_frontend_binding::LoopFrontendBinding;
use crate::r#macro::ast_json::ast_to_json;
use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap};
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
use crate::mir::types::ConstValue;
// Phase 50: Create appropriate binding based on function name
let binding = match func_name {
"JsonTokenizer.print_tokens/0" => LoopFrontendBinding::for_print_tokens(),
"ArrayExtBox.filter/2" => LoopFrontendBinding::for_array_filter(),
_ => {
if debug {
eprintln!(
"[cf_loop/joinir] No binding defined for {}, falling back",
func_name
);
}
return Ok(None);
}
};
if debug {
eprintln!(
"[cf_loop/joinir] Using binding: counter={}, acc={:?}, pattern={:?}",
binding.counter_var,
binding.accumulator_var,
binding.pattern
);
}
// Step 1: Convert condition and body to JSON
let condition_json = ast_to_json(condition);
let body_json: Vec<serde_json::Value> = body.iter().map(|s| ast_to_json(s)).collect();
let mut body_json: Vec<serde_json::Value> =
body.iter().map(|s| ast_to_json(s)).collect();
// Phase 50: Rename variables in body (e.g., "out" → "acc" for filter)
binding.rename_body_variables(&mut body_json);
// Phase 50: Generate Local declarations from binding
let (i_local, acc_local, n_local) = binding.generate_local_declarations();
// Step 2: Construct JSON v0 format with "defs" array
// The function is named "simple" to match JoinIR Frontend's pattern matching
// Phase 50: Include i/acc/n Local declarations to satisfy JoinIR Frontend expectations
let program_json = serde_json::json!({
"defs": [
{
@ -151,16 +184,19 @@ impl super::MirBuilder {
"body": {
"type": "Block",
"body": [
// Placeholder locals (JoinIR Frontend will infer from loop)
// Phase 50: Inject i/acc/n Local declarations
i_local,
acc_local,
n_local,
{
"type": "Loop",
"condition": condition_json,
"body": body_json
},
// Placeholder return
// Return the accumulator (or null for side-effect loops)
{
"type": "Return",
"value": null
"value": { "kind": "Variable", "name": "acc" }
}
]
}