Files
hakorune/src/mir/join_ir/frontend/ast_lowerer/mod.rs
nyash-codex 730a80f33f feat(joinir): Phase 89 P1 - ContinueReturn lowering implementation
## ContinueReturn 独立箱
- continue_return_pattern.rs (457行)
- Continue + Early Return の JoinIR lowering
- StepCalculator 再利用(3箇所)
- Fail-Fast 原則徹底(5箇所検証)

## Dispatcher 配線
- mod.rs: LoopPattern::ContinueReturn 対応
- allowlist: pattern_continue_return_minimal 追加

## Fixture & Tests
- fixtures.rs: build_pattern_continue_return_min
- shapes.rs: +2 tests (vm_bridge + 期待値 n=10→acc=4)

Impact:
- normalized_dev: 61 passed (+2)
- lib tests: 993 passed (回帰なし)
- 箱化原則:単一責任・境界明確・再利用性高

Next: Phase 90 - _parse_string 合成 fixture

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 02:02:47 +09:00

219 lines
7.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! AST/CFG → JoinIR Lowering
//!
//! このモジュールは AST/CFG ノードを JoinIR 命令に変換する。
//!
//! ## 責務
//!
//! - **If 文→Select/IfMerge 変換**: 条件分岐を JoinIR の継続渡しスタイルに変換
//! - **Loop 文→loop_step/k_exit 変換**: ループを関数呼び出しと継続に正規化
//! - **Break/Continue/Return→k_* 変換**: 制御フローを継続 ID として表現
//!
//! ## Phase 34-2 での実装スコープ
//!
//! 最初は `IfSelectTest.*` 相当の tiny ケースのみ対応:
//! - Simple pattern: `if cond { return 1 } else { return 2 }`
//!
//! ## 設計原則
//!
//! - **JoinIR = PHI 生成器**: 既存 PHI の変換器にはしないPhase 33-10 原則)
//! - **段階的移行**: 既存 MIR Builder 経路は保持、新経路はデフォルト OFF
//! - **A/B テスト可能**: 既存経路と新経路の両方で実行して比較検証
pub(crate) use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MergePair,
VarId,
};
pub(crate) use std::collections::{BTreeMap, HashSet};
mod analysis;
mod context;
mod expr;
mod if_in_loop;
mod if_return;
mod loop_frontend_binding;
mod loop_patterns;
// Removed: loop_patterns_old (obsolete legacy dispatcher, all patterns now in loop_patterns/)
mod nested_if;
mod read_quoted;
mod stmt_handlers;
#[cfg(test)]
mod tests;
pub(crate) use context::ExtractCtx;
pub(crate) use stmt_handlers::StatementEffect;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FunctionRoute {
IfReturn,
LoopFrontend,
NestedIf,
ReadQuoted,
}
fn resolve_function_route(func_name: &str) -> Result<FunctionRoute, String> {
const TABLE: &[(&str, FunctionRoute)] = &[
("test", FunctionRoute::IfReturn),
("local", FunctionRoute::IfReturn),
("_read_value_from_pair", FunctionRoute::IfReturn),
("simple", FunctionRoute::LoopFrontend),
("filter", FunctionRoute::LoopFrontend),
("print_tokens", FunctionRoute::LoopFrontend),
("map", FunctionRoute::LoopFrontend),
("reduce", FunctionRoute::LoopFrontend),
("fold", FunctionRoute::LoopFrontend),
("jsonparser_skip_ws_mini", FunctionRoute::LoopFrontend),
("jsonparser_skip_ws_real", FunctionRoute::LoopFrontend),
("jsonparser_atoi_mini", FunctionRoute::LoopFrontend),
("jsonparser_atoi_real", FunctionRoute::LoopFrontend),
("jsonparser_parse_number_real", FunctionRoute::LoopFrontend),
("pattern3_if_sum_multi_min", FunctionRoute::LoopFrontend),
("jsonparser_if_sum_min", FunctionRoute::LoopFrontend),
("selfhost_token_scan_p2", FunctionRoute::LoopFrontend),
("selfhost_token_scan_p2_accum", FunctionRoute::LoopFrontend),
("selfhost_args_parse_p2", FunctionRoute::LoopFrontend),
("selfhost_if_sum_p3", FunctionRoute::LoopFrontend),
("selfhost_if_sum_p3_ext", FunctionRoute::LoopFrontend),
("selfhost_stmt_count_p3", FunctionRoute::LoopFrontend),
// Phase 54: selfhost P2/P3 shape growth
("selfhost_verify_schema_p2", FunctionRoute::LoopFrontend),
("selfhost_detect_format_p3", FunctionRoute::LoopFrontend),
// Phase 48-A: Pattern4 continue minimal
("pattern4_continue_minimal", FunctionRoute::LoopFrontend),
// Phase 48-B: JsonParser continue skip_ws fixtures
(
"jsonparser_parse_array_continue_skip_ws",
FunctionRoute::LoopFrontend,
),
(
"jsonparser_parse_object_continue_skip_ws",
FunctionRoute::LoopFrontend,
),
// Phase 88: JsonParser _unescape_string core (step2 + continue) minimal fixture
(
"jsonparser_unescape_string_step2_min",
FunctionRoute::LoopFrontend,
),
// Phase 89 P1: ContinueReturn pattern minimal fixture
(
"pattern_continue_return_minimal",
FunctionRoute::LoopFrontend,
),
];
if let Some((_, route)) = TABLE.iter().find(|(name, _)| *name == func_name) {
return Ok(*route);
}
if func_name == "parse_loop" {
if crate::config::env::joinir_dev_enabled()
&& std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1")
{
return Ok(FunctionRoute::NestedIf);
}
return Err(
"[joinir/frontend] 'parse_loop' requires HAKO_JOINIR_NESTED_IF=1 (dev only)"
.to_string(),
);
}
if func_name == "read_quoted_from" {
if crate::config::env::joinir_dev_enabled()
&& std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1")
{
return Ok(FunctionRoute::ReadQuoted);
}
return Err(
"[joinir/frontend] 'read_quoted_from' requires HAKO_JOINIR_READ_QUOTED=1 (dev only)"
.to_string(),
);
}
Err(format!(
"[joinir/frontend] unsupported function '{}' (dev fixture not registered)",
func_name
))
}
/// AST/CFG → JoinIR 変換器
///
/// Phase 34-2: Program(JSON v0) から tiny IfSelect ケースを JoinIR に変換
pub struct AstToJoinIrLowerer {
pub(crate) next_func_id: u32,
#[allow(dead_code)]
pub(crate) next_var_id: u32,
}
impl AstToJoinIrLowerer {
/// 新しい lowerer を作成
pub fn new() -> Self {
Self {
next_func_id: 0,
next_var_id: 0,
}
}
/// Program(JSON v0) → JoinModule
///
/// Phase 34-2/34-3/34-4: simple/local/json_shape pattern に対応
/// Phase 34-5: extract_value 統一化Int/Var/Method 構造まで)
///
/// # Panics
///
/// - パターンに合わない Program(JSON) が来た場合Phase 34 は tiny テスト専用)
/// - ループ・複数変数・副作用付き ifPhase 34-6 以降で対応予定)
pub fn lower_program_json(&mut self, program_json: &serde_json::Value) -> JoinModule {
// 1. Program(JSON) から defs を取得
let defs = program_json["defs"]
.as_array()
.expect("Program(JSON v0) must have 'defs' array");
// 2. 最初の関数定義を取得
let func_def = defs
.get(0)
.expect("At least one function definition required");
let func_name = func_def["name"]
.as_str()
.expect("Function must have 'name'");
let route = resolve_function_route(func_name)
.unwrap_or_else(|msg| panic!("{msg}"));
match route {
FunctionRoute::IfReturn => self.lower_if_return_pattern(program_json),
FunctionRoute::LoopFrontend => loop_frontend_binding::lower_loop_by_function_name(
self,
program_json,
),
FunctionRoute::NestedIf => self.lower_nested_if_pattern(program_json),
FunctionRoute::ReadQuoted => self.lower_read_quoted_pattern(program_json),
}
}
/// 次の関数 ID を生成
pub(crate) fn next_func_id(&mut self) -> JoinFuncId {
let id = JoinFuncId::new(self.next_func_id);
self.next_func_id += 1;
id
}
}
/// Phase 60 dev-only helper: legacy Break(P2) lowering for comparison tests.
///
/// `loop_patterns` is private, so this wrapper is exposed at the ast_lowerer boundary.
#[cfg(feature = "normalized_dev")]
pub fn lower_break_legacy_for_comparison(
lowerer: &mut AstToJoinIrLowerer,
program_json: &serde_json::Value,
) -> JoinModule {
loop_patterns::break_pattern::lower_break_legacy_for_comparison(lowerer, program_json)
.unwrap_or_else(|e| panic!("legacy break lowering failed: {:?}", e))
}
impl Default for AstToJoinIrLowerer {
fn default() -> Self {
Self::new()
}
}