feat(joinir): Phase 56 ArrayExtBox.filter JoinIR lowering完全実装

## Summary
ArrayExtBox.filter/2 の JoinIR Frontend lowering を完全実装し、
ConditionalMethodCall 命令を導入して filter パターンに対応。
56 JoinIR テスト全て PASS(退行なし)。

## Technical Changes

### 1. ConditionalMethodCall 命令追加
- **新規命令**: `if pred(v) { acc.push(v) }` パターン用
- **構造**: cond が true なら method 実行、false なら no-op
- **MIR 変換**: 4ブロック構造 (cond→then/else→merge)

### 2. AST JSON 拡張
- Break/Continue/FunctionCall に "type" フィールド追加
- ArrayLiteral/MapLiteral に "type" フィールド追加
- JoinIR Frontend 互換性向上

### 3. Expression Handler 拡張
- Unary 演算子(not, 負号)サポート
- Call(変数関数呼び出し)を MethodCall に変換

### 4. Loop Pattern Binding 修正
- `BoundExpr::Variable("n")` 問題修正
- `MethodCall { receiver: "arr", method: "size" }` に変更
- external_refs (arr, pred) を step 関数に伝播

### 5. If Statement Handler 拡張
- 条件付き側効果パターン(ケース4)追加
- MethodCall/Method 形式の statement を ConditionalMethodCall に変換

## Files Modified (10 files, +456/-45 lines)
- ast_json.rs: AST JSON "type" フィールド追加
- loop_frontend_binding.rs: n バインディング修正
- control_flow.rs: external_refs params 追加
- loop_patterns.rs: external_refs step 関数伝播
- expr.rs: Unary, Call handler 追加
- stmt_handlers.rs: ConditionalMethodCall パターン追加
- mod.rs: ConditionalMethodCall, UnaryOp 定義
- json.rs: ConditionalMethodCall, UnaryOp シリアライズ
- join_ir_runner.rs: ConditionalMethodCall, UnaryOp スタブ
- convert.rs: ConditionalMethodCall → MIR 変換

## Test Results
- 56 JoinIR tests:  PASSED
- Regression:  None
- ArrayExtBox.filter/2:  JoinIR lowering 成功

## Milestone
JoinIR 2ループ完走達成:
-  JsonTokenizer.print_tokens/0
-  ArrayExtBox.filter/2 (NEW!)

🤖 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-29 06:51:43 +09:00
parent ee6e95b164
commit ad9daf37ac
11 changed files with 456 additions and 45 deletions

View File

@ -131,6 +131,16 @@
- docs:
- `docs/private/roadmap2/phases/phase-46-ifmerge-loop-reassign/README.md`
### 1-00i. Phase 4956 — JoinIR Frontend 本線統合print_tokens / filter✅ 進行中
- Phase 4952: `cf_loop` に JoinIR Frontend ルートを追加しdev フラグ付き)、`LoopFrontendBinding` / JSON v0 / expr タイプField/NewBoxを整備。
- `HAKO_JOINIR_PRINT_TOKENS_MAIN` / `HAKO_JOINIR_ARRAY_FILTER_MAIN` で対象ループだけを Frontend 経由にルーティング。
- `merge_joinir_mir_blocks` で JoinIR→MIR のブロック/値 ID リマップと制御フロー接続を実装。
- Phase 5355: statement loweringLocal/Assignment/Print/Method/Ifと AST→JSON の `"type"`/`"expr"` 整備により、`JsonTokenizer.print_tokens/1` が JoinIR Frontend→Bridge→VM 経由で最後まで実行可能にRoute A/B 等価、dev フラグ ON 限定)。
- Phase 56: `ArrayExtBox.filter/2` 向けに `LoopFrontendBinding::for_array_filter` を MethodCall ベース(`arr.size()`)に修正し、外部参照 `arr/pred` を Binding 経由で渡す構造に統一。
- JoinIR に `ConditionalMethodCall` / Unary / Call を追加し、filter の「pred が true のときだけ push する」パターンを 4 ブロック構造で表現。
- `HAKO_JOINIR_ARRAY_FILTER_MAIN=1` で Route BJoinIR Frontend 経路)がフォールバックなし完走(テスト済み、既定は従来ルート)。
---
## 2. 中期 TODOざっくり

View File

@ -30,8 +30,16 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
"type": "Return", // JoinIR Frontend expects "type"
"value": value.as_ref().map(|v| ast_to_json(v)),
}),
ASTNode::Break { .. } => json!({"kind":"Break"}),
ASTNode::Continue { .. } => json!({"kind":"Continue"}),
// Phase 56: Break with JoinIR-compatible type field
ASTNode::Break { .. } => json!({
"kind": "Break",
"type": "Break" // JoinIR Frontend expects "type"
}),
// Phase 56: Continue with JoinIR-compatible type field
ASTNode::Continue { .. } => json!({
"kind": "Continue",
"type": "Continue" // JoinIR Frontend expects "type"
}),
// Phase 54: Assignment with JoinIR-compatible fields
ASTNode::Assignment { target, value, .. } => {
// Extract variable name if target is a simple Variable
@ -152,10 +160,12 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
"right": ast_to_json(&right),
})
}
// Phase 56: UnaryOp → Unary ードJoinIR Frontend 互換)
ASTNode::UnaryOp {
operator, operand, ..
} => json!({
"kind":"UnaryOp",
"kind": "UnaryOp",
"type": "Unary", // Phase 56: JoinIR Frontend expects "type" field
"op": un_to_str(&operator),
"operand": ast_to_json(&operand),
}),
@ -176,19 +186,27 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
"args": arguments.iter().map(|a| ast_to_json(a)).collect::<Vec<_>>(),
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>() // Keep for backward compatibility
}),
// Phase 56: FunctionCall with JoinIR-compatible type field
ASTNode::FunctionCall {
name, arguments, ..
} => json!({
"kind":"FunctionCall",
"kind": "FunctionCall",
"type": "Call", // JoinIR Frontend expects "type": "Call"
"name": name,
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
"func": name.clone(), // JoinIR expects "func" for function name
"args": arguments.iter().map(|a| ast_to_json(a)).collect::<Vec<_>>(), // JoinIR expects "args"
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>() // Keep for backward compatibility
}),
// Phase 56: ArrayLiteral with JoinIR-compatible type field
ASTNode::ArrayLiteral { elements, .. } => json!({
"kind":"Array",
"kind": "Array",
"type": "Array", // JoinIR Frontend expects "type"
"elements": elements.into_iter().map(|e| ast_to_json(&e)).collect::<Vec<_>>()
}),
// Phase 56: MapLiteral with JoinIR-compatible type field
ASTNode::MapLiteral { entries, .. } => json!({
"kind":"Map",
"kind": "Map",
"type": "Map", // JoinIR Frontend expects "type"
"entries": entries.into_iter().map(|(k,v)| json!({"k":k,"v":ast_to_json(&v)})).collect::<Vec<_>>()
}),
ASTNode::MatchExpr {

View File

@ -1,6 +1,6 @@
//! Control-flow entrypoints (if/loop/try/throw) centralized here.
use super::{Effect, EffectMask, MirInstruction, ValueId};
use crate::ast::{ASTNode, Span};
use crate::ast::ASTNode;
impl super::MirBuilder {
/// Control-flow: block
@ -173,16 +173,29 @@ impl super::MirBuilder {
// Phase 50: Generate Local declarations from binding
let (i_local, acc_local, n_local) = binding.generate_local_declarations();
// Phase 52: Check if `me` receiver is needed
// Instance methods (like print_tokens) need `me` to be passed as a parameter
let params: Vec<serde_json::Value> = if binding.needs_me_receiver() {
// Phase 52/56: Build params from external_refs
// Instance methods need `me`, static methods need their parameters (arr, pred, etc.)
let mut params: Vec<serde_json::Value> = Vec::new();
// Phase 52: Add 'me' for instance methods
if binding.needs_me_receiver() {
if debug {
eprintln!("[cf_loop/joinir] Adding 'me' to params (instance method)");
}
vec![serde_json::json!("me")]
} else {
vec![]
};
params.push(serde_json::json!("me"));
}
// Phase 56: Add external_refs as parameters (arr, pred for filter)
for ext_ref in &binding.external_refs {
// Skip "me" and "me.*" as they're handled above
if ext_ref == "me" || ext_ref.starts_with("me.") {
continue;
}
if debug {
eprintln!("[cf_loop/joinir] Adding '{}' to params (external_ref)", ext_ref);
}
params.push(serde_json::json!(ext_ref));
}
// Step 2: Construct JSON v0 format with "defs" array
// The function is named "simple" to match JoinIR Frontend's pattern matching

View File

@ -49,8 +49,6 @@ pub enum BoundExpr {
Variable(String),
/// メソッド呼び出し (e.g., "me.tokens", "length")
MethodCall { receiver: String, method: String },
/// 不明(フォールバック用)
Unknown,
}
/// ループパターン種別
@ -62,12 +60,6 @@ pub enum LoopPattern {
/// フィルターパターン (if + push)
Filter,
/// マップパターン (変換 + push)
Map,
/// 不明(フォールバック用)
Unknown,
}
impl LoopFrontendBinding {
@ -114,7 +106,7 @@ impl LoopFrontendBinding {
/// ```nyash
/// local out = new ArrayBox()
/// local i = 0
/// local n = arr.size()
/// local n = arr.size() // ← JoinIR では arr.size() を直接評価
/// loop(i < n) {
/// local v = arr.get(i)
/// if pred(v) { out.push(v) }
@ -122,12 +114,21 @@ impl LoopFrontendBinding {
/// }
/// 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
bound_expr: BoundExpr::Variable("n".to_string()),
// 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,
}
@ -264,17 +265,6 @@ impl LoopFrontendBinding {
}
})
}
BoundExpr::Unknown => {
// フォールバック: 0 を使う
json!({
"type": "Local",
"name": "n",
"expr": {
"type": "Int",
"value": 0
}
})
}
};
(i_local, acc_local, n_local)

View File

@ -246,6 +246,82 @@ impl AstToJoinIrLowerer {
(dst, insts)
}
// Phase 56: Unary 対応not 等)
"Unary" => {
let op = expr["op"]
.as_str()
.expect("Unary must have 'op' field");
let operand_expr = &expr["operand"];
// operand を再帰的に extract_value
let (operand_var, operand_insts) = self.extract_value(operand_expr, ctx);
// 結果変数を割り当て
let dst = ctx.alloc_var();
// UnaryOp 命令を生成
let unary_op = match op {
"not" => crate::mir::join_ir::UnaryOp::Not,
"-" => crate::mir::join_ir::UnaryOp::Neg,
_ => panic!("Unsupported unary op: {}", op),
};
let unary_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::UnaryOp {
dst,
op: unary_op,
operand: operand_var,
});
let mut insts = operand_insts;
insts.push(unary_inst);
(dst, insts)
}
// Phase 56: Call関数呼び出し、pred(v) 等)
// 変数参照の関数呼び出しは MethodCall で代替receiver=func_var, method="__call__"
"Call" => {
let func_name = expr["func"]
.as_str()
.or_else(|| expr["name"].as_str())
.expect("Call must have 'func' or 'name' field");
let empty_args = vec![];
let args_array = expr["args"]
.as_array()
.or_else(|| expr["arguments"].as_array())
.unwrap_or(&empty_args);
// func_name を変数として取得pred, callback 等)
let func_var = ctx
.get_var(func_name)
.unwrap_or_else(|| panic!("Undefined function variable: {}", func_name));
// args を extract_value で処理
let mut arg_vars = Vec::new();
let mut arg_insts = Vec::new();
for arg_expr in args_array {
let (arg_var, arg_inst) = self.extract_value(arg_expr, ctx);
arg_vars.push(arg_var);
arg_insts.extend(arg_inst);
}
// 結果変数を割り当て
let dst = ctx.alloc_var();
// MethodCall で代替func_var.__call__(args)
let call_inst = JoinInst::MethodCall {
dst,
receiver: func_var,
method: "__call__".to_string(),
args: arg_vars,
};
let mut insts = arg_insts;
insts.push(call_inst);
(dst, insts)
}
_ => panic!("Unsupported expr type: {}", expr_type),
}
}

View File

@ -211,6 +211,24 @@ impl AstToJoinIrLowerer {
// Call(loop_step) 末尾再帰
// - k_exit: 結果を返す
// Phase 56: Collect external_refs BEFORE entry_call_args construction
// These are params that are not loop-carried variables (me, i, acc, n)
// They need to be passed through to the step function for filter(arr, pred) etc.
let reserved_vars = ["me", "i", "acc", "n"];
let external_refs: Vec<(String, crate::mir::ValueId)> = params
.iter()
.enumerate()
.filter_map(|(idx, param)| {
param.as_str().and_then(|name| {
if !reserved_vars.contains(&name) {
Some((name.to_string(), crate::mir::ValueId(idx as u32)))
} else {
None
}
})
})
.collect();
let entry_id = self.next_func_id();
let loop_step_id = self.next_func_id();
let k_exit_id = self.next_func_id();
@ -225,12 +243,19 @@ impl AstToJoinIrLowerer {
let loop_result = ctx.alloc_var();
// Phase 52: Include me in args when present
let entry_call_args = if let Some(me_id) = me_param {
// Phase 52/56: Include me and external_refs in args when present
let mut entry_call_args = if let Some(me_id) = me_param {
vec![me_id, i_init, acc_init, n_param]
} else {
vec![i_init, acc_init, n_param]
};
// Phase 56: Add external_refs to entry call args
// Get them from ctx using their original names
for (name, _original_id) in &external_refs {
if let Some(var_id) = ctx.get_var(name) {
entry_call_args.push(var_id);
}
}
let mut entry_body = init_insts;
entry_body.push(JoinInst::Call {
@ -277,8 +302,15 @@ impl AstToJoinIrLowerer {
)
};
let num_step_params = if has_me { 4 } else { 3 };
// Phase 56: external_refs was already collected above (before entry_call_args)
// Calculate step function param count including external_refs
let base_params = if has_me { 4 } else { 3 }; // me?, i, acc, n
let num_step_params = base_params + external_refs.len() as u32;
let mut step_ctx = ExtractCtx::new(num_step_params);
// Register standard params with adjusted offsets
// Order: me?, i, acc, n, ...external_refs
let ext_offset = if has_me { 4 } else { 3 };
if let Some(me_id) = step_me {
step_ctx.register_param("me".to_string(), me_id);
}
@ -286,6 +318,12 @@ impl AstToJoinIrLowerer {
step_ctx.register_param("acc".to_string(), step_acc);
step_ctx.register_param("n".to_string(), step_n);
// Phase 56: Register external_refs with new ValueIds starting after n
for (i, (name, _original_id)) in external_refs.iter().enumerate() {
let new_id = crate::mir::ValueId(ext_offset + i as u32);
step_ctx.register_param(name.clone(), new_id);
}
// 条件式を評価i < n
let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx);
@ -332,12 +370,18 @@ impl AstToJoinIrLowerer {
.expect("acc must be updated in loop body");
// loop_step を再帰的に Call末尾再帰
// Phase 52: Include me in args when present
let recurse_args = if let Some(me_id) = step_me {
// Phase 52/56: Include me and external_refs in args when present
let mut recurse_args = if let Some(me_id) = step_me {
vec![me_id, i_next, acc_next, step_n]
} else {
vec![i_next, acc_next, step_n]
};
// Phase 56: Add external_refs to recurse args (they are passed through unchanged)
for (name, _) in &external_refs {
if let Some(var_id) = step_ctx.get_var(name) {
recurse_args.push(var_id);
}
}
let recurse_result = step_ctx.alloc_var();
loop_step_body.push(JoinInst::Call {
@ -351,12 +395,17 @@ impl AstToJoinIrLowerer {
value: Some(recurse_result),
});
// Phase 52: Include me in params when present
let loop_step_params = if let Some(me_id) = step_me {
// Phase 52/56: Include me and external_refs in params when present
let ext_offset = if has_me { 4 } else { 3 };
let mut loop_step_params = if let Some(me_id) = step_me {
vec![me_id, step_i, step_acc, step_n]
} else {
vec![step_i, step_acc, step_n]
};
// Phase 56: Add external_refs to step params
for (i, _) in external_refs.iter().enumerate() {
loop_step_params.push(crate::mir::ValueId(ext_offset + i as u32));
}
let loop_step_func = JoinFunction {
id: loop_step_id,

View File

@ -233,7 +233,65 @@ impl AstToJoinIrLowerer {
}
}
// ケース 4: 複雑なケースPhase 54 で対応
// ケース 4: Phase 56 条件付き側効果パターンfilter 用
// if pred(v) { acc.push(v) }
// then: 1 statement (MethodCall/Method), else: empty
if then_stmts.len() == 1 && else_stmts.is_empty() {
let stmt = &then_stmts[0];
let stmt_type = stmt["type"].as_str();
// MethodCall/Method 形式のステートメントをチェック
if matches!(stmt_type, Some("Method") | Some("MethodCall")) {
let receiver_expr = stmt.get("receiver").or_else(|| stmt.get("object"));
let method_name = stmt["method"].as_str();
let args_array = stmt.get("args").or_else(|| stmt.get("arguments"));
if let (Some(receiver_expr), Some(method_name), Some(args_array)) =
(receiver_expr, method_name, args_array)
{
// receiver を評価
let (receiver_var, receiver_insts) = self.extract_value(receiver_expr, ctx);
insts.extend(receiver_insts);
// args を評価
let mut arg_vars = Vec::new();
if let Some(args) = args_array.as_array() {
for arg_expr in args {
let (arg_var, arg_insts) = self.extract_value(arg_expr, ctx);
arg_vars.push(arg_var);
insts.extend(arg_insts);
}
}
// 結果変数を割り当てpush は配列を返すので結果は無視されることが多い)
let dst = ctx.alloc_var();
// ConditionalMethodCall 命令を生成
insts.push(JoinInst::ConditionalMethodCall {
cond: cond_id,
dst,
receiver: receiver_var,
method: method_name.to_string(),
args: arg_vars,
});
// receiver 変数を更新された値で登録push は receiver を返す)
// この場合、accumulator 変数 "acc" または "out" を更新する必要がある
let receiver_name = receiver_expr["name"].as_str().unwrap_or("acc");
ctx.register_param(receiver_name.to_string(), dst);
return (
insts,
StatementEffect::VarUpdate {
name: receiver_name.to_string(),
value_id: dst,
},
);
}
}
}
// ケース 5: 複雑なケース(未対応)
panic!(
"Complex If statement in loop body not yet supported (Phase 54). \
then: {} stmts, else: {} stmts",

View File

@ -7,7 +7,7 @@
use std::io::Write;
use super::{BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst};
use super::{BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst, UnaryOp};
/// JoinModule を JSON としてシリアライズする
///
@ -195,6 +195,29 @@ fn write_inst<W: Write>(inst: &JoinInst, out: &mut W) -> std::io::Result<()> {
write!(out, "]")?;
write!(out, "}}")?;
}
// Phase 56: ConditionalMethodCall instruction JSON serialization
JoinInst::ConditionalMethodCall {
cond,
dst,
receiver,
method,
args,
} => {
write!(out, "{{\"type\":\"conditional_method_call\"")?;
write!(out, ",\"cond\":{}", cond.0)?;
write!(out, ",\"dst\":{}", dst.0)?;
write!(out, ",\"receiver\":{}", receiver.0)?;
write!(out, ",\"method\":\"{}\"", escape_json_string(method))?;
write!(out, ",\"args\":[")?;
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(out, ",")?;
}
write!(out, "{}", arg.0)?;
}
write!(out, "]")?;
write!(out, "}}")?;
}
// Phase 41-4: NestedIfMerge instruction JSON serialization
JoinInst::NestedIfMerge {
conds,
@ -331,6 +354,14 @@ fn write_mir_like_inst<W: Write>(inst: &MirLikeInst, out: &mut W) -> std::io::Re
write!(out, "]")?;
write!(out, "}}")?;
}
// Phase 56: UnaryOp
MirLikeInst::UnaryOp { dst, op, operand } => {
write!(out, "{{\"kind\":\"unaryop\"")?;
write!(out, ",\"dst\":{}", dst.0)?;
write!(out, ",\"op\":\"{}\"", unaryop_to_str(*op))?;
write!(out, ",\"operand\":{}", operand.0)?;
write!(out, "}}")?;
}
}
Ok(())
}
@ -357,6 +388,14 @@ fn compare_to_str(op: CompareOp) -> &'static str {
}
}
// Phase 56: UnaryOp to string
fn unaryop_to_str(op: UnaryOp) -> &'static str {
match op {
UnaryOp::Not => "not",
UnaryOp::Neg => "neg",
}
}
/// JSON 文字列のエスケープ
fn escape_json_string(s: &str) -> String {
let mut escaped = String::with_capacity(s.len());

View File

@ -322,6 +322,19 @@ pub enum JoinInst {
args: Vec<VarId>,
},
/// Phase 56: 条件付きメソッド呼び出しfilter パターン用)
/// cond が true の場合のみ receiver.method(args) を実行
/// cond が false の場合は dst = receiver変更なし
///
/// 使用例: `if pred(v) { acc.push(v) }` → ConditionalMethodCall
ConditionalMethodCall {
cond: VarId,
dst: VarId,
receiver: VarId,
method: String,
args: Vec<VarId>,
},
/// Phase 51: フィールドアクセス
/// object.field の構造を JoinIR で表現
/// MIR 変換時に Load 命令に変換
@ -405,6 +418,22 @@ pub enum MirLikeInst {
method: String,
args: Vec<VarId>,
},
/// Phase 56: 単項演算not, 負号)
UnaryOp {
dst: VarId,
op: UnaryOp,
operand: VarId,
},
}
/// Phase 56: 単項演算種別
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
/// 論理否定
Not,
/// 算術否定(負号)
Neg,
}
/// 定数値MIR の ConstValue を簡略化)

View File

@ -217,6 +217,14 @@ fn execute_function(
"MethodCall is not supported in JoinIR Runner (use JoinIR→MIR→VM bridge instead)"
));
}
// Phase 56: ConditionalMethodCall instruction execution
JoinInst::ConditionalMethodCall { .. } => {
// Phase 56: ConditionalMethodCall は JoinIR Runner では未対応
// JoinIR → MIR 変換経由で VM が実行する
return Err(JoinRuntimeError::new(
"ConditionalMethodCall is not supported in JoinIR Runner (use JoinIR→MIR→VM bridge instead)"
));
}
// Phase 41-4: NestedIfMerge instruction execution
JoinInst::NestedIfMerge { .. } => {
// Phase 41-4: NestedIfMerge は JoinIR Runner では未対応
@ -323,6 +331,32 @@ fn eval_compute(
locals.insert(*dst_var, result_jv);
}
}
// Phase 56: UnaryOp
MirLikeInst::UnaryOp { dst, op, operand } => {
let operand_val = read_var(locals, *operand)?;
let result = match op {
crate::mir::join_ir::UnaryOp::Not => {
match operand_val {
JoinValue::Bool(b) => JoinValue::Bool(!b),
JoinValue::Int(i) => JoinValue::Bool(i == 0),
_ => return Err(JoinRuntimeError::new(format!(
"Cannot apply 'not' to {:?}",
operand_val
))),
}
}
crate::mir::join_ir::UnaryOp::Neg => {
match operand_val {
JoinValue::Int(i) => JoinValue::Int(-i),
_ => return Err(JoinRuntimeError::new(format!(
"Cannot apply '-' to {:?}",
operand_val
))),
}
}
};
locals.insert(*dst, result);
}
}
Ok(())
}

View File

@ -217,6 +217,89 @@ pub(crate) fn convert_join_function_to_mir(
};
current_instructions.push(mir_inst);
}
// Phase 56: ConditionalMethodCall → MIR (cond ? method : no-op)
JoinInst::ConditionalMethodCall {
cond,
dst,
receiver,
method,
args,
} => {
// Phase 56: ConditionalMethodCall を MIR の if/phi に変換
// cond が true の場合: receiver.method(args) → dst
// cond が false の場合: receiver → dst (変更なし)
//
// 4 ブロック構造: cond -> then/else -> merge
debug_log!(
"[joinir_vm_bridge] Converting ConditionalMethodCall: dst={:?}, cond={:?}, receiver={:?}, method={}",
dst, cond, receiver, method
);
// 1. cond ブロック(現在のブロック)
let cond_block = current_block_id;
// 2. then ブロック作成
let then_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 3. else ブロック作成
let else_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 4. merge ブロック作成
let merge_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 5. cond ブロックで分岐
let branch_terminator = MirInstruction::Branch {
condition: *cond,
then_bb: then_block,
else_bb: else_block,
};
finalize_block(
&mut mir_func,
cond_block,
current_instructions,
branch_terminator,
);
// 6. then ブロック: dst = receiver.method(args); jump merge
let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
then_block_obj.instructions.push(MirInstruction::BoxCall {
dst: Some(*dst),
box_val: *receiver,
method: method.clone(),
method_id: None,
args: args.clone(),
effects: EffectMask::PURE,
});
then_block_obj.instruction_spans.push(Span::unknown());
then_block_obj.terminator = Some(MirInstruction::Jump {
target: merge_block,
});
mir_func.blocks.insert(then_block, then_block_obj);
// 7. else ブロック: dst = receiver (no-op, keep original); jump merge
let mut else_block_obj = crate::mir::BasicBlock::new(else_block);
else_block_obj.instructions.push(MirInstruction::Copy {
dst: *dst,
src: *receiver,
});
else_block_obj.instruction_spans.push(Span::unknown());
else_block_obj.terminator = Some(MirInstruction::Jump {
target: merge_block,
});
mir_func.blocks.insert(else_block, else_block_obj);
// 8. merge ブロック作成(空)
let merge_block_obj = crate::mir::BasicBlock::new(merge_block);
mir_func.blocks.insert(merge_block, merge_block_obj);
// 9. merge ブロックに移動
current_block_id = merge_block;
current_instructions = Vec::new();
}
// Phase 51: FieldAccess → MIR BoxCall (getter pattern)
JoinInst::FieldAccess { dst, object, field } => {
// object.field を BoxCall(object, field, []) に変換
@ -832,5 +915,17 @@ pub(crate) fn convert_mir_like_inst(
effects: EffectMask::PURE, // Phase 27-shortterm: assume pure
})
}
// Phase 56: UnaryOp
MirLikeInst::UnaryOp { dst, op, operand } => {
let mir_op = match op {
crate::mir::join_ir::UnaryOp::Not => crate::mir::types::UnaryOp::Not,
crate::mir::join_ir::UnaryOp::Neg => crate::mir::types::UnaryOp::Neg,
};
Ok(MirInstruction::UnaryOp {
dst: *dst,
op: mir_op,
operand: *operand,
})
}
}
}