diff --git a/src/macro/ast_json.rs b/src/macro/ast_json.rs index 0220be03..321ddb8c 100644 --- a/src/macro/ast_json.rs +++ b/src/macro/ast_json.rs @@ -79,19 +79,36 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { "static": is_static, "override": is_override, }), - ASTNode::Variable { name, .. } => json!({"kind":"Variable","name":name}), - ASTNode::Literal { value, .. } => json!({"kind":"Literal","value": lit_to_json(&value)}), + // Phase 52: Variable → Var ノード(JoinIR Frontend 互換) + ASTNode::Variable { name, .. } => json!({ + "kind": "Variable", + "type": "Var", // JoinIR Frontend expects "type": "Var" + "name": name + }), + // Phase 52: Literal → Int/Bool/String ノード(JoinIR Frontend 互換) + ASTNode::Literal { value, .. } => literal_to_joinir_json(&value), + // Phase 52: BinaryOp → Binary/Compare ノード(JoinIR Frontend 互換) ASTNode::BinaryOp { operator, left, right, .. - } => json!({ - "kind":"BinaryOp", - "op": bin_to_str(&operator), - "left": ast_to_json(&left), - "right": ast_to_json(&right), - }), + } => { + let op_str = bin_to_str(&operator); + // JoinIR Frontend distinguishes between Binary (arithmetic) and Compare + let type_str = if is_compare_op(&operator) { "Compare" } else { "Binary" }; + json!({ + "kind": "BinaryOp", + "type": type_str, + "op": op_str, + // JoinIR Frontend expects "lhs"/"rhs" not "left"/"right" + "lhs": ast_to_json(&left), + "rhs": ast_to_json(&right), + // Also keep "left"/"right" for backward compatibility + "left": ast_to_json(&left), + "right": ast_to_json(&right), + }) + } ASTNode::UnaryOp { operator, operand, .. } => json!({ @@ -99,16 +116,22 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { "op": un_to_str(&operator), "operand": ast_to_json(&operand), }), + // Phase 52: MethodCall → Method ノード(JoinIR Frontend 互換) ASTNode::MethodCall { object, method, arguments, .. } => json!({ - "kind":"MethodCall", - "object": ast_to_json(&object), + "kind": "MethodCall", + "type": "Method", // JoinIR Frontend expects "type": "Method" + // JoinIR Frontend expects "receiver" not "object" + "receiver": ast_to_json(&object), + "object": ast_to_json(&object), // Keep for backward compatibility "method": method, - "arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::>() + // JoinIR Frontend expects "args" not "arguments" + "args": arguments.iter().map(|a| ast_to_json(a)).collect::>(), + "arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::>() // Keep for backward compatibility }), ASTNode::FunctionCall { name, arguments, .. @@ -142,6 +165,28 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { })).collect::>(), "else": ast_to_json(&else_expr), }), + // Phase 52: FieldAccess → Field ノード(JoinIR Frontend 互換) + ASTNode::FieldAccess { object, field, .. } => json!({ + "kind": "FieldAccess", + "type": "Field", // JoinIR Frontend expects "type": "Field" + "object": ast_to_json(&object), + "field": field + }), + // Phase 52: Me → Var("me") ノード(JoinIR Frontend 互換) + ASTNode::Me { .. } => json!({ + "kind": "Me", + "type": "Var", // JoinIR Frontend expects "type": "Var" + "name": "me" + }), + // Phase 52: New → NewBox ノード(JoinIR Frontend 互換) + ASTNode::New { + class, arguments, .. + } => json!({ + "kind": "New", + "type": "NewBox", // JoinIR Frontend expects "type": "NewBox" + "box_name": class, + "args": arguments.into_iter().map(|a| ast_to_json(&a)).collect::>() + }), other => json!({"kind":"Unsupported","debug": format!("{:?}", other)}), } } @@ -459,3 +504,55 @@ fn str_to_un(s: &str) -> Option { _ => return None, }) } + +/// Phase 52: Check if a binary operator is a comparison operator +fn is_compare_op(op: &BinaryOperator) -> bool { + matches!( + op, + BinaryOperator::Equal + | BinaryOperator::NotEqual + | BinaryOperator::Less + | BinaryOperator::Greater + | BinaryOperator::LessEqual + | BinaryOperator::GreaterEqual + ) +} + +/// Phase 52: Convert a literal value to JoinIR-compatible JSON format +/// +/// JoinIR Frontend expects: +/// - Integer: {"type": "Int", "value": } +/// - Boolean: {"type": "Bool", "value": } +/// - String: {"type": "String", "value": } +fn literal_to_joinir_json(v: &LiteralValue) -> Value { + match v { + LiteralValue::Integer(i) => json!({ + "kind": "Literal", + "type": "Int", // JoinIR Frontend expects "type": "Int" + "value": i + }), + LiteralValue::Bool(b) => json!({ + "kind": "Literal", + "type": "Bool", // JoinIR Frontend expects "type": "Bool" + "value": b + }), + LiteralValue::String(s) => json!({ + "kind": "Literal", + "type": "String", + "value": s + }), + LiteralValue::Float(f) => json!({ + "kind": "Literal", + "type": "Float", + "value": f + }), + LiteralValue::Null => json!({ + "kind": "Literal", + "type": "Null" + }), + LiteralValue::Void => json!({ + "kind": "Literal", + "type": "Void" + }), + } +} diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index b5ccb124..87845a96 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -173,6 +173,17 @@ 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 = if binding.needs_me_receiver() { + if debug { + eprintln!("[cf_loop/joinir] Adding 'me' to params (instance method)"); + } + vec![serde_json::json!("me")] + } else { + vec![] + }; + // 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 @@ -180,7 +191,7 @@ impl super::MirBuilder { "defs": [ { "name": "simple", - "params": [], + "params": params, "body": { "type": "Block", "body": [ @@ -190,7 +201,7 @@ impl super::MirBuilder { n_local, { "type": "Loop", - "condition": condition_json, + "cond": condition_json, // JoinIR Frontend expects "cond" not "condition" "body": body_json }, // Return the accumulator (or null for side-effect loops) diff --git a/src/mir/builder/loop_frontend_binding.rs b/src/mir/builder/loop_frontend_binding.rs index af6ec1f0..08da7f5f 100644 --- a/src/mir/builder/loop_frontend_binding.rs +++ b/src/mir/builder/loop_frontend_binding.rs @@ -71,6 +71,16 @@ pub enum LoopPattern { } 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 の構造: @@ -135,6 +145,44 @@ impl LoopFrontendBinding { 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 タプル @@ -142,6 +190,7 @@ impl LoopFrontendBinding { /// 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( @@ -201,14 +250,15 @@ impl LoopFrontendBinding { }) } BoundExpr::MethodCall { receiver, method } => { - // メソッド呼び出しを評価(print_tokens の me.tokens.length() 等) - // JoinIR Frontend expects "Method" type + // 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": { "type": "Var", "name": receiver }, + "receiver": receiver_json, "method": method, "args": [] } @@ -349,4 +399,40 @@ mod tests { 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"); + } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs index 70b12516..3bdb6e00 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs @@ -220,12 +220,22 @@ impl AstToJoinIrLowerer { let acc_init = ctx.get_var("acc").expect("acc must be initialized"); let n_param = ctx.get_var("n").expect("n must be parameter"); + // Phase 52: Get me from context if it was registered as a param + let me_param = ctx.get_var("me"); + 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 { + vec![me_id, i_init, acc_init, n_param] + } else { + vec![i_init, acc_init, n_param] + }; + let mut entry_body = init_insts; entry_body.push(JoinInst::Call { func: loop_step_id, - args: vec![i_init, acc_init, n_param], + args: entry_call_args, k_next: None, dst: Some(loop_result), }); @@ -244,11 +254,34 @@ impl AstToJoinIrLowerer { }; // loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i (Vec, StatementEffect) { + let stmt_type = stmt["type"] + .as_str() + .expect("Statement must have 'type' field"); + + match stmt_type { + "Local" => self.lower_local_stmt(stmt, ctx), + "Assignment" => self.lower_assignment_stmt(stmt, ctx), + "Print" => self.lower_print_stmt(stmt, ctx), + "Method" => self.lower_method_stmt(stmt, ctx), + "If" => self.lower_if_stmt_in_loop(stmt, ctx), + other => panic!( + "Unsupported statement type in loop body: {}. \ + Expected: Local, Assignment, Print, Method, If", + other + ), + } + } + + /// Local ステートメント: `local x = expr` + fn lower_local_stmt( + &mut self, + stmt: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (Vec, StatementEffect) { + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + + let (value_id, insts) = self.extract_value(expr, ctx); + ctx.register_param(var_name.clone(), value_id); + + ( + insts, + StatementEffect::VarUpdate { + name: var_name, + value_id, + }, + ) + } + + /// Assignment ステートメント: `x = expr` + /// + /// Phase 53-2: `i = i + 1` などの代入文を処理 + fn lower_assignment_stmt( + &mut self, + stmt: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (Vec, StatementEffect) { + let target = stmt["target"] + .as_str() + .expect("Assignment must have 'target'") + .to_string(); + let expr = &stmt["expr"]; + + let (value_id, insts) = self.extract_value(expr, ctx); + ctx.register_param(target.clone(), value_id); + + ( + insts, + StatementEffect::VarUpdate { + name: target, + value_id, + }, + ) + } + + /// Print ステートメント: `print(expr)` + /// + /// Phase 53-3: ConsoleBox.print 呼び出しに変換 + fn lower_print_stmt( + &mut self, + stmt: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (Vec, StatementEffect) { + let expr = &stmt["expr"]; + let (arg_id, mut insts) = self.extract_value(expr, ctx); + + // print は BoxCall として実装(ConsoleBox.print) + let result_id = ctx.alloc_var(); + insts.push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(result_id), + box_name: "ConsoleBox".to_string(), + method: "print".to_string(), + args: vec![arg_id], + })); + + (insts, StatementEffect::SideEffect) + } + + /// Method ステートメント(式文として): `obj.method(args)` + /// + /// 戻り値を捨てるメソッド呼び出し(print など) + fn lower_method_stmt( + &mut self, + stmt: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (Vec, StatementEffect) { + // extract_value で Method を評価(戻り値は捨てる) + let (_, insts) = self.extract_value(stmt, ctx); + (insts, StatementEffect::SideEffect) + } + + /// If ステートメント(ループ内): `if cond { then } else { else }` + /// + /// Phase 53-4: ガードパターン対応 + /// + /// 単純な変数更新のみの場合は Select 命令に変換し、 + /// 複雑な場合はパニック(Phase 54 で対応予定) + fn lower_if_stmt_in_loop( + &mut self, + stmt: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (Vec, StatementEffect) { + let cond_expr = &stmt["cond"]; + let then_body = stmt["then"].as_array(); + let else_body = stmt["else"].as_array(); + + // 条件を評価 + let (cond_id, mut insts) = self.extract_value(cond_expr, ctx); + + // 単純なケース: then/else が空または単一の変数更新 + let then_stmts = then_body.map(|v| v.as_slice()).unwrap_or(&[]); + let else_stmts = else_body.map(|v| v.as_slice()).unwrap_or(&[]); + + // ケース 1: 空の If(条件チェックのみ) + if then_stmts.is_empty() && else_stmts.is_empty() { + return (insts, StatementEffect::None); + } + + // ケース 2: 単一変数更新 → Select に変換 + if let (Some(then_update), None) = + (Self::extract_single_var_update(then_stmts), Self::extract_single_var_update(else_stmts)) + { + // then のみ更新、else は元の値を維持 + let (var_name, then_expr) = then_update; + + // then の式を評価 + let (then_val, then_insts) = self.extract_value(then_expr, ctx); + insts.extend(then_insts); + + // else は元の値 + let else_val = ctx + .get_var(&var_name) + .expect(&format!("Variable '{}' must exist for If/else", var_name)); + + // Select: cond ? then_val : else_val + let result_id = ctx.alloc_var(); + insts.push(JoinInst::Select { + dst: result_id, + cond: cond_id, + then_val, + else_val, + }); + + ctx.register_param(var_name.clone(), result_id); + + return ( + insts, + StatementEffect::VarUpdate { + name: var_name, + value_id: result_id, + }, + ); + } + + // ケース 3: 両方に単一変数更新(同じ変数) + if let (Some((then_name, then_expr)), Some((else_name, else_expr))) = ( + Self::extract_single_var_update(then_stmts), + Self::extract_single_var_update(else_stmts), + ) { + if then_name == else_name { + let (then_val, then_insts) = self.extract_value(then_expr, ctx); + insts.extend(then_insts); + + let (else_val, else_insts) = self.extract_value(else_expr, ctx); + insts.extend(else_insts); + + let result_id = ctx.alloc_var(); + insts.push(JoinInst::Select { + dst: result_id, + cond: cond_id, + then_val, + else_val, + }); + + ctx.register_param(then_name.clone(), result_id); + + return ( + insts, + StatementEffect::VarUpdate { + name: then_name, + value_id: result_id, + }, + ); + } + } + + // ケース 4: 複雑なケース(Phase 54 で対応) + panic!( + "Complex If statement in loop body not yet supported (Phase 54). \ + then: {} stmts, else: {} stmts", + then_stmts.len(), + else_stmts.len() + ); + } + + /// ステートメント配列から単一の変数更新を抽出 + /// + /// Returns: Some((変数名, 式)) if 単一の Local/Assignment + fn extract_single_var_update( + stmts: &[serde_json::Value], + ) -> Option<(String, &serde_json::Value)> { + if stmts.len() != 1 { + return None; + } + + let stmt = &stmts[0]; + let stmt_type = stmt["type"].as_str()?; + + match stmt_type { + "Local" => { + let name = stmt["name"].as_str()?.to_string(); + let expr = &stmt["expr"]; + Some((name, expr)) + } + "Assignment" => { + let name = stmt["target"].as_str()?.to_string(); + let expr = &stmt["expr"]; + Some((name, expr)) + } + _ => None, + } + } +}