feat(joinir): Phase 52-53 LoopFrontendBinding JSON + Statement Handlers
Phase 52: LoopFrontendBinding JSON generation fixes - Add receiver_to_json() for Field node structure (me.tokens) - Add needs_me_receiver() for instance method detection - Fix "condition" → "cond" key for JoinIR Frontend - Add me parameter propagation in loop_patterns.rs - Add JoinIR-compatible type fields in ast_json.rs - Variable → "type": "Var" - Literal → "type": "Int"/"Bool" (literal_to_joinir_json) - BinaryOp → "type": "Binary"/"Compare" (is_compare_op) - MethodCall → "type": "Method" Phase 53: Statement Handler module for loop body - NEW: stmt_handlers.rs with StatementEffect type - Support: Local, Assignment, Print, Method, If statements - If lowering: single variable update → Select instruction - Remove hardcoded assert in loop_patterns.rs - Replace with generic lower_statement() calls Test results: 56 JoinIR tests PASS, 7 loop_frontend_binding tests PASS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -79,19 +79,36 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
|||||||
"static": is_static,
|
"static": is_static,
|
||||||
"override": is_override,
|
"override": is_override,
|
||||||
}),
|
}),
|
||||||
ASTNode::Variable { name, .. } => json!({"kind":"Variable","name":name}),
|
// Phase 52: Variable → Var ノード(JoinIR Frontend 互換)
|
||||||
ASTNode::Literal { value, .. } => json!({"kind":"Literal","value": lit_to_json(&value)}),
|
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 {
|
ASTNode::BinaryOp {
|
||||||
operator,
|
operator,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
..
|
..
|
||||||
} => json!({
|
} => {
|
||||||
"kind":"BinaryOp",
|
let op_str = bin_to_str(&operator);
|
||||||
"op": bin_to_str(&operator),
|
// JoinIR Frontend distinguishes between Binary (arithmetic) and Compare
|
||||||
"left": ast_to_json(&left),
|
let type_str = if is_compare_op(&operator) { "Compare" } else { "Binary" };
|
||||||
"right": ast_to_json(&right),
|
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 {
|
ASTNode::UnaryOp {
|
||||||
operator, operand, ..
|
operator, operand, ..
|
||||||
} => json!({
|
} => json!({
|
||||||
@ -99,16 +116,22 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
|||||||
"op": un_to_str(&operator),
|
"op": un_to_str(&operator),
|
||||||
"operand": ast_to_json(&operand),
|
"operand": ast_to_json(&operand),
|
||||||
}),
|
}),
|
||||||
|
// Phase 52: MethodCall → Method ノード(JoinIR Frontend 互換)
|
||||||
ASTNode::MethodCall {
|
ASTNode::MethodCall {
|
||||||
object,
|
object,
|
||||||
method,
|
method,
|
||||||
arguments,
|
arguments,
|
||||||
..
|
..
|
||||||
} => json!({
|
} => json!({
|
||||||
"kind":"MethodCall",
|
"kind": "MethodCall",
|
||||||
"object": ast_to_json(&object),
|
"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,
|
"method": method,
|
||||||
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
|
// JoinIR Frontend expects "args" not "arguments"
|
||||||
|
"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
|
||||||
}),
|
}),
|
||||||
ASTNode::FunctionCall {
|
ASTNode::FunctionCall {
|
||||||
name, arguments, ..
|
name, arguments, ..
|
||||||
@ -142,6 +165,28 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
|||||||
})).collect::<Vec<_>>(),
|
})).collect::<Vec<_>>(),
|
||||||
"else": ast_to_json(&else_expr),
|
"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::<Vec<_>>()
|
||||||
|
}),
|
||||||
other => json!({"kind":"Unsupported","debug": format!("{:?}", other)}),
|
other => json!({"kind":"Unsupported","debug": format!("{:?}", other)}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -459,3 +504,55 @@ fn str_to_un(s: &str) -> Option<UnaryOperator> {
|
|||||||
_ => return None,
|
_ => 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": <number>}
|
||||||
|
/// - Boolean: {"type": "Bool", "value": <bool>}
|
||||||
|
/// - String: {"type": "String", "value": <string>}
|
||||||
|
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"
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -173,6 +173,17 @@ impl super::MirBuilder {
|
|||||||
// Phase 50: Generate Local declarations from binding
|
// Phase 50: Generate Local declarations from binding
|
||||||
let (i_local, acc_local, n_local) = binding.generate_local_declarations();
|
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() {
|
||||||
|
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
|
// Step 2: Construct JSON v0 format with "defs" array
|
||||||
// The function is named "simple" to match JoinIR Frontend's pattern matching
|
// 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
|
// Phase 50: Include i/acc/n Local declarations to satisfy JoinIR Frontend expectations
|
||||||
@ -180,7 +191,7 @@ impl super::MirBuilder {
|
|||||||
"defs": [
|
"defs": [
|
||||||
{
|
{
|
||||||
"name": "simple",
|
"name": "simple",
|
||||||
"params": [],
|
"params": params,
|
||||||
"body": {
|
"body": {
|
||||||
"type": "Block",
|
"type": "Block",
|
||||||
"body": [
|
"body": [
|
||||||
@ -190,7 +201,7 @@ impl super::MirBuilder {
|
|||||||
n_local,
|
n_local,
|
||||||
{
|
{
|
||||||
"type": "Loop",
|
"type": "Loop",
|
||||||
"condition": condition_json,
|
"cond": condition_json, // JoinIR Frontend expects "cond" not "condition"
|
||||||
"body": body_json
|
"body": body_json
|
||||||
},
|
},
|
||||||
// Return the accumulator (or null for side-effect loops)
|
// Return the accumulator (or null for side-effect loops)
|
||||||
|
|||||||
@ -71,6 +71,16 @@ pub enum LoopPattern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LoopFrontendBinding {
|
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 専用のバインディングを生成
|
||||||
///
|
///
|
||||||
/// print_tokens の構造:
|
/// print_tokens の構造:
|
||||||
@ -135,6 +145,44 @@ impl LoopFrontendBinding {
|
|||||||
None
|
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 宣言を生成
|
/// JoinIR Frontend 用の JSON v0 Local 宣言を生成
|
||||||
///
|
///
|
||||||
/// Returns: (i_local, acc_local, n_local) の JSON Value タプル
|
/// Returns: (i_local, acc_local, n_local) の JSON Value タプル
|
||||||
@ -142,6 +190,7 @@ impl LoopFrontendBinding {
|
|||||||
/// Note: JoinIR Frontend expects specific type names:
|
/// Note: JoinIR Frontend expects specific type names:
|
||||||
/// - "Int" for integer literals (with "value" field)
|
/// - "Int" for integer literals (with "value" field)
|
||||||
/// - "Var" for variable references (with "name" 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)
|
/// - "Method" for method calls (with "receiver", "method", "args" fields)
|
||||||
/// - "NewBox" for box instantiation
|
/// - "NewBox" for box instantiation
|
||||||
pub fn generate_local_declarations(
|
pub fn generate_local_declarations(
|
||||||
@ -201,14 +250,15 @@ impl LoopFrontendBinding {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
BoundExpr::MethodCall { receiver, method } => {
|
BoundExpr::MethodCall { receiver, method } => {
|
||||||
// メソッド呼び出しを評価(print_tokens の me.tokens.length() 等)
|
// Phase 52: メソッド呼び出しを評価(print_tokens の me.tokens.length() 等)
|
||||||
// JoinIR Frontend expects "Method" type
|
// receiver が "me.tokens" のようにドット区切りの場合は Field ノードに分解
|
||||||
|
let receiver_json = Self::receiver_to_json(receiver);
|
||||||
json!({
|
json!({
|
||||||
"type": "Local",
|
"type": "Local",
|
||||||
"name": "n",
|
"name": "n",
|
||||||
"expr": {
|
"expr": {
|
||||||
"type": "Method",
|
"type": "Method",
|
||||||
"receiver": { "type": "Var", "name": receiver },
|
"receiver": receiver_json,
|
||||||
"method": method,
|
"method": method,
|
||||||
"args": []
|
"args": []
|
||||||
}
|
}
|
||||||
@ -349,4 +399,40 @@ mod tests {
|
|||||||
assert_eq!(n_local["expr"]["type"], "Var");
|
assert_eq!(n_local["expr"]["type"], "Var");
|
||||||
assert_eq!(n_local["expr"]["name"], "n");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -220,12 +220,22 @@ impl AstToJoinIrLowerer {
|
|||||||
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
||||||
let n_param = ctx.get_var("n").expect("n must be parameter");
|
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();
|
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;
|
let mut entry_body = init_insts;
|
||||||
entry_body.push(JoinInst::Call {
|
entry_body.push(JoinInst::Call {
|
||||||
func: loop_step_id,
|
func: loop_step_id,
|
||||||
args: vec![i_init, acc_init, n_param],
|
args: entry_call_args,
|
||||||
k_next: None,
|
k_next: None,
|
||||||
dst: Some(loop_result),
|
dst: Some(loop_result),
|
||||||
});
|
});
|
||||||
@ -244,11 +254,34 @@ impl AstToJoinIrLowerer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i<n)) → body → Call(loop_step)
|
// loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i<n)) → body → Call(loop_step)
|
||||||
let step_i = crate::mir::ValueId(0);
|
// Phase 52: Check if "me" is present in original params (for instance methods)
|
||||||
let step_acc = crate::mir::ValueId(1);
|
let has_me = params
|
||||||
let step_n = crate::mir::ValueId(2);
|
.iter()
|
||||||
|
.any(|p| p.as_str() == Some("me"));
|
||||||
|
|
||||||
let mut step_ctx = ExtractCtx::new(3);
|
// Adjust ValueIds based on whether "me" is present
|
||||||
|
// If "me" is present, it takes slot 0, and i/acc/n shift to 1/2/3
|
||||||
|
let (step_i, step_acc, step_n, step_me) = if has_me {
|
||||||
|
(
|
||||||
|
crate::mir::ValueId(1),
|
||||||
|
crate::mir::ValueId(2),
|
||||||
|
crate::mir::ValueId(3),
|
||||||
|
Some(crate::mir::ValueId(0)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
crate::mir::ValueId(0),
|
||||||
|
crate::mir::ValueId(1),
|
||||||
|
crate::mir::ValueId(2),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let num_step_params = if has_me { 4 } else { 3 };
|
||||||
|
let mut step_ctx = ExtractCtx::new(num_step_params);
|
||||||
|
if let Some(me_id) = step_me {
|
||||||
|
step_ctx.register_param("me".to_string(), me_id);
|
||||||
|
}
|
||||||
step_ctx.register_param("i".to_string(), step_i);
|
step_ctx.register_param("i".to_string(), step_i);
|
||||||
step_ctx.register_param("acc".to_string(), step_acc);
|
step_ctx.register_param("acc".to_string(), step_acc);
|
||||||
step_ctx.register_param("n".to_string(), step_n);
|
step_ctx.register_param("n".to_string(), step_n);
|
||||||
@ -281,23 +314,13 @@ impl AstToJoinIrLowerer {
|
|||||||
cond: Some(exit_cond),
|
cond: Some(exit_cond),
|
||||||
});
|
});
|
||||||
|
|
||||||
// loop body を処理(Jump で抜けなかった場合のみ実行される)
|
// Phase 53: loop body を処理(汎用 statement handler を使用)
|
||||||
|
// Jump で抜けなかった場合のみ実行される
|
||||||
for body_stmt in loop_body_stmts {
|
for body_stmt in loop_body_stmts {
|
||||||
assert_eq!(
|
let (insts, _effect) = self.lower_statement(body_stmt, &mut step_ctx);
|
||||||
body_stmt["type"].as_str(),
|
|
||||||
Some("Local"),
|
|
||||||
"Loop body must contain only Local statements"
|
|
||||||
);
|
|
||||||
|
|
||||||
let var_name = body_stmt["name"]
|
|
||||||
.as_str()
|
|
||||||
.expect("Local must have 'name'")
|
|
||||||
.to_string();
|
|
||||||
let expr = &body_stmt["expr"];
|
|
||||||
|
|
||||||
let (var_id, insts) = self.extract_value(expr, &mut step_ctx);
|
|
||||||
loop_step_body.extend(insts);
|
loop_step_body.extend(insts);
|
||||||
step_ctx.register_param(var_name, var_id);
|
// Note: lower_statement 内で ctx.register_param() が呼ばれるため、
|
||||||
|
// ここでの追加登録は不要
|
||||||
}
|
}
|
||||||
|
|
||||||
// body 処理後の i_next, acc_next を取得
|
// body 処理後の i_next, acc_next を取得
|
||||||
@ -309,10 +332,17 @@ impl AstToJoinIrLowerer {
|
|||||||
.expect("acc must be updated in loop body");
|
.expect("acc must be updated in loop body");
|
||||||
|
|
||||||
// loop_step を再帰的に Call(末尾再帰)
|
// loop_step を再帰的に Call(末尾再帰)
|
||||||
|
// Phase 52: Include me in args when present
|
||||||
|
let 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]
|
||||||
|
};
|
||||||
|
|
||||||
let recurse_result = step_ctx.alloc_var();
|
let recurse_result = step_ctx.alloc_var();
|
||||||
loop_step_body.push(JoinInst::Call {
|
loop_step_body.push(JoinInst::Call {
|
||||||
func: loop_step_id,
|
func: loop_step_id,
|
||||||
args: vec![i_next, acc_next, step_n],
|
args: recurse_args,
|
||||||
k_next: None,
|
k_next: None,
|
||||||
dst: Some(recurse_result),
|
dst: Some(recurse_result),
|
||||||
});
|
});
|
||||||
@ -321,10 +351,17 @@ impl AstToJoinIrLowerer {
|
|||||||
value: Some(recurse_result),
|
value: Some(recurse_result),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Phase 52: Include me in params when present
|
||||||
|
let 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]
|
||||||
|
};
|
||||||
|
|
||||||
let loop_step_func = JoinFunction {
|
let loop_step_func = JoinFunction {
|
||||||
id: loop_step_id,
|
id: loop_step_id,
|
||||||
name: format!("{}_loop_step", func_name),
|
name: format!("{}_loop_step", func_name),
|
||||||
params: vec![step_i, step_acc, step_n],
|
params: loop_step_params,
|
||||||
body: loop_step_body,
|
body: loop_step_body,
|
||||||
exit_cont: None,
|
exit_cont: None,
|
||||||
};
|
};
|
||||||
@ -495,28 +532,16 @@ impl AstToJoinIrLowerer {
|
|||||||
cond: Some(break_cond_var),
|
cond: Some(break_cond_var),
|
||||||
});
|
});
|
||||||
|
|
||||||
// loop body を処理(Break の後の Local 命令群)
|
// Phase 53: loop body を処理(汎用 statement handler を使用)
|
||||||
|
// Break の後のステートメント群
|
||||||
for body_stmt in loop_body {
|
for body_stmt in loop_body {
|
||||||
// If + Break はスキップ(Jump で処理済み)
|
// If + Break はスキップ(Jump で処理済み)
|
||||||
if body_stmt["type"].as_str() == Some("If") {
|
if body_stmt["type"].as_str() == Some("If") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
let (insts, _effect) = self.lower_statement(body_stmt, &mut step_ctx);
|
||||||
body_stmt["type"].as_str(),
|
|
||||||
Some("Local"),
|
|
||||||
"Loop body must contain only Local statements after If"
|
|
||||||
);
|
|
||||||
|
|
||||||
let var_name = body_stmt["name"]
|
|
||||||
.as_str()
|
|
||||||
.expect("Local must have 'name'")
|
|
||||||
.to_string();
|
|
||||||
let expr = &body_stmt["expr"];
|
|
||||||
|
|
||||||
let (var_id, insts) = self.extract_value(expr, &mut step_ctx);
|
|
||||||
loop_step_body.extend(insts);
|
loop_step_body.extend(insts);
|
||||||
step_ctx.register_param(var_name, var_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// body 処理後の i_next, acc_next を取得
|
// body 処理後の i_next, acc_next を取得
|
||||||
|
|||||||
@ -32,6 +32,7 @@ mod if_return;
|
|||||||
mod loop_patterns;
|
mod loop_patterns;
|
||||||
mod nested_if;
|
mod nested_if;
|
||||||
mod read_quoted;
|
mod read_quoted;
|
||||||
|
mod stmt_handlers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
272
src/mir/join_ir/frontend/ast_lowerer/stmt_handlers.rs
Normal file
272
src/mir/join_ir/frontend/ast_lowerer/stmt_handlers.rs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
//! Phase 53: Statement Handler モジュール
|
||||||
|
//!
|
||||||
|
//! ループ本体の各ステートメントタイプを JoinIR に変換する。
|
||||||
|
//!
|
||||||
|
//! ## 対応ステートメント
|
||||||
|
//!
|
||||||
|
//! - `Local`: 変数宣言 + 初期化
|
||||||
|
//! - `Assignment`: 変数への代入
|
||||||
|
//! - `Print`: 出力(副作用)
|
||||||
|
//! - `If`: 条件分岐
|
||||||
|
//! - `Method`: メソッド呼び出し(式文として)
|
||||||
|
|
||||||
|
use super::{AstToJoinIrLowerer, ExtractCtx, JoinInst};
|
||||||
|
use crate::mir::join_ir::MirLikeInst;
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
|
/// ステートメントの効果(変数更新 or 副作用のみ)
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum StatementEffect {
|
||||||
|
/// 変数を更新(Assignment/Local)
|
||||||
|
VarUpdate { name: String, value_id: ValueId },
|
||||||
|
/// 副作用のみ(Print)
|
||||||
|
SideEffect,
|
||||||
|
/// 効果なし(空の If など)
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AstToJoinIrLowerer {
|
||||||
|
/// Phase 53: ステートメントを JoinIR に変換
|
||||||
|
///
|
||||||
|
/// 対応タイプ: Local, Assignment, Print, If, Method
|
||||||
|
pub fn lower_statement(
|
||||||
|
&mut self,
|
||||||
|
stmt: &serde_json::Value,
|
||||||
|
ctx: &mut ExtractCtx,
|
||||||
|
) -> (Vec<JoinInst>, 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<JoinInst>, 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<JoinInst>, 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<JoinInst>, 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<JoinInst>, 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<JoinInst>, 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user