feat(joinir): Phase 88 - Pattern4 continue + variable step increment
Continue Pattern 拡張: - then側の i=i+const 差分加算 + acc更新を許可 - continue_pattern.rs:193 で可変ステップ検出 Dev Router 許可: - ast_lowerer/mod.rs:92 で normalized_dev feature時に新パターンを有効化 Fixtures & Tests: - jsonparser_unescape_string_step2_min fixture追加(submodule) - normalized_joinir_min.rs に shape テスト追加 - shapes.rs に expected shape 定義 Documentation: - joinir-architecture-overview.md に Phase 88 到達点を追記 Impact: - Pattern4 continue + 可変インクリメント(i+=1 or i+=2)対応 - _unescape_string 制御構造の土台確立 - normalized_dev tests PASS Next: _unescape_string 残り複合ループ対応 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -833,6 +833,8 @@ Pattern2/4 への統合(実際に Body-local 更新を使うループを JoinI
|
|||||||
(P2 + P4 + P5 の組み合わせで表現可能なことを設計上確認済み)
|
(P2 + P4 + P5 の組み合わせで表現可能なことを設計上確認済み)
|
||||||
- 低優先度だが理論上は P1–P4 からの拡張で吸収可能:
|
- 低優先度だが理論上は P1–P4 からの拡張で吸収可能:
|
||||||
- `_unescape_string` など、複雑な continue / 条件付き更新を含むループ
|
- `_unescape_string` など、複雑な continue / 条件付き更新を含むループ
|
||||||
|
- Phase 88(dev-only)で、`i+=2 + continue`(かつ continue 分岐側で `acc` 更新)を最小フィクスチャとして抽出し、
|
||||||
|
frontend の continue pattern を「`i = i + const` の差分加算」に限定して段階拡張した。
|
||||||
|
|
||||||
方針:
|
方針:
|
||||||
|
|
||||||
|
|||||||
Submodule docs/private updated: 00e3e63780...fd8368f491
@ -83,6 +83,7 @@ pub fn lower(
|
|||||||
&parsed.func_name,
|
&parsed.func_name,
|
||||||
loop_cond_expr,
|
loop_cond_expr,
|
||||||
continue_cond_expr,
|
continue_cond_expr,
|
||||||
|
continue_if_stmt,
|
||||||
loop_body,
|
loop_body,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -136,6 +137,7 @@ fn create_loop_step_function_continue(
|
|||||||
func_name: &str,
|
func_name: &str,
|
||||||
loop_cond_expr: &serde_json::Value,
|
loop_cond_expr: &serde_json::Value,
|
||||||
continue_cond_expr: &serde_json::Value,
|
continue_cond_expr: &serde_json::Value,
|
||||||
|
continue_if_stmt: &serde_json::Value,
|
||||||
loop_body: &[serde_json::Value],
|
loop_body: &[serde_json::Value],
|
||||||
) -> Result<JoinFunction, LoweringError> {
|
) -> Result<JoinFunction, LoweringError> {
|
||||||
use super::super::context::ExtractCtx;
|
use super::super::context::ExtractCtx;
|
||||||
@ -188,6 +190,67 @@ fn create_loop_step_function_continue(
|
|||||||
body.extend(i_insts);
|
body.extend(i_insts);
|
||||||
step_ctx.register_param("i".to_string(), i_next);
|
step_ctx.register_param("i".to_string(), i_next);
|
||||||
|
|
||||||
|
// Phase 88: Continue 分岐で i を追加更新できるようにする(例: i += 2)
|
||||||
|
//
|
||||||
|
// `continue_if` の then 内に `Local i = i + K` がある場合、
|
||||||
|
// loop_body の通常更新 `Local i = i + K0` との差分 (K-K0) を i_next に加算して
|
||||||
|
// continue パスの次 i を構成する(Linear increment のみ対応)。
|
||||||
|
fn extract_add_i_const(expr: &serde_json::Value) -> Option<i64> {
|
||||||
|
if expr["type"].as_str()? != "Binary" || expr["op"].as_str()? != "+" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let lhs = &expr["lhs"];
|
||||||
|
let rhs = &expr["rhs"];
|
||||||
|
// i + const
|
||||||
|
if lhs["type"].as_str()? == "Var" && lhs["name"].as_str()? == "i" {
|
||||||
|
if rhs["type"].as_str()? == "Int" {
|
||||||
|
return rhs["value"].as_i64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const + i
|
||||||
|
if rhs["type"].as_str()? == "Var" && rhs["name"].as_str()? == "i" {
|
||||||
|
if lhs["type"].as_str()? == "Int" {
|
||||||
|
return lhs["value"].as_i64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut i_next_continue = i_next;
|
||||||
|
let continue_then = continue_if_stmt["then"]
|
||||||
|
.as_array()
|
||||||
|
.ok_or_else(|| LoweringError::InvalidLoopBody {
|
||||||
|
message: "Continue pattern If must have 'then' array".to_string(),
|
||||||
|
})?;
|
||||||
|
if let Some(then_i_local) = continue_then
|
||||||
|
.iter()
|
||||||
|
.find(|stmt| stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i"))
|
||||||
|
{
|
||||||
|
let base_k = extract_add_i_const(i_expr).ok_or_else(|| LoweringError::InvalidLoopBody {
|
||||||
|
message: "Continue pattern requires i update of form (i + const)".to_string(),
|
||||||
|
})?;
|
||||||
|
let then_k =
|
||||||
|
extract_add_i_const(&then_i_local["expr"]).ok_or_else(|| LoweringError::InvalidLoopBody {
|
||||||
|
message: "Continue pattern requires then i update of form (i + const)".to_string(),
|
||||||
|
})?;
|
||||||
|
let delta = then_k - base_k;
|
||||||
|
if delta != 0 {
|
||||||
|
let delta_const = step_ctx.alloc_var();
|
||||||
|
body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||||
|
dst: delta_const,
|
||||||
|
value: ConstValue::Integer(delta),
|
||||||
|
}));
|
||||||
|
let bumped = step_ctx.alloc_var();
|
||||||
|
body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||||
|
dst: bumped,
|
||||||
|
op: crate::mir::join_ir::BinOpKind::Add,
|
||||||
|
lhs: i_next,
|
||||||
|
rhs: delta_const,
|
||||||
|
}));
|
||||||
|
i_next_continue = bumped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Continue 条件を評価
|
// 3. Continue 条件を評価
|
||||||
let (continue_cond_var, continue_cond_insts) =
|
let (continue_cond_var, continue_cond_insts) =
|
||||||
lowerer.extract_value(continue_cond_expr, &mut step_ctx);
|
lowerer.extract_value(continue_cond_expr, &mut step_ctx);
|
||||||
@ -205,21 +268,41 @@ fn create_loop_step_function_continue(
|
|||||||
let (acc_increment, acc_insts) = lowerer.extract_value(acc_expr, &mut step_ctx);
|
let (acc_increment, acc_insts) = lowerer.extract_value(acc_expr, &mut step_ctx);
|
||||||
body.extend(acc_insts);
|
body.extend(acc_insts);
|
||||||
|
|
||||||
// 5. Select: Continue なら acc そのまま、そうでなければ更新
|
// Phase 88: Continue 分岐側でも acc を更新できるようにする(例: acc += 1)
|
||||||
|
let mut acc_then_val = step_acc;
|
||||||
|
if let Some(then_acc_local) = continue_then.iter().find(|stmt| {
|
||||||
|
stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("acc")
|
||||||
|
}) {
|
||||||
|
let (acc_then, acc_then_insts) = lowerer.extract_value(&then_acc_local["expr"], &mut step_ctx);
|
||||||
|
body.extend(acc_then_insts);
|
||||||
|
acc_then_val = acc_then;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Select: Continue/通常 で acc を切り替える
|
||||||
let acc_next = step_ctx.alloc_var();
|
let acc_next = step_ctx.alloc_var();
|
||||||
body.push(JoinInst::Select {
|
body.push(JoinInst::Select {
|
||||||
dst: acc_next,
|
dst: acc_next,
|
||||||
cond: continue_cond_var,
|
cond: continue_cond_var,
|
||||||
then_val: step_acc, // Continue: 更新しない
|
then_val: acc_then_val,
|
||||||
else_val: acc_increment, // 通常: 更新
|
else_val: acc_increment,
|
||||||
type_hint: None, // Phase 63-3
|
type_hint: None, // Phase 63-3
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Phase 88: Continue/通常 で次 i を切り替える
|
||||||
|
let i_next_selected = step_ctx.alloc_var();
|
||||||
|
body.push(JoinInst::Select {
|
||||||
|
dst: i_next_selected,
|
||||||
|
cond: continue_cond_var,
|
||||||
|
then_val: i_next_continue,
|
||||||
|
else_val: i_next,
|
||||||
|
type_hint: None,
|
||||||
|
});
|
||||||
|
|
||||||
// 6. 末尾再帰
|
// 6. 末尾再帰
|
||||||
let recurse_result = step_ctx.alloc_var();
|
let recurse_result = step_ctx.alloc_var();
|
||||||
body.push(JoinInst::Call {
|
body.push(JoinInst::Call {
|
||||||
func: ctx.loop_step_id,
|
func: ctx.loop_step_id,
|
||||||
args: vec![i_next, acc_next, step_n],
|
args: vec![i_next_selected, acc_next, step_n],
|
||||||
k_next: None,
|
k_next: None,
|
||||||
dst: Some(recurse_result),
|
dst: Some(recurse_result),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -89,6 +89,11 @@ fn resolve_function_route(func_name: &str) -> Result<FunctionRoute, String> {
|
|||||||
"jsonparser_parse_object_continue_skip_ws",
|
"jsonparser_parse_object_continue_skip_ws",
|
||||||
FunctionRoute::LoopFrontend,
|
FunctionRoute::LoopFrontend,
|
||||||
),
|
),
|
||||||
|
// Phase 88: JsonParser _unescape_string core (step2 + continue) minimal fixture
|
||||||
|
(
|
||||||
|
"jsonparser_unescape_string_step2_min",
|
||||||
|
FunctionRoute::LoopFrontend,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if let Some((_, route)) = TABLE.iter().find(|(name, _)| *name == func_name) {
|
if let Some((_, route)) = TABLE.iter().find(|(name, _)| *name == func_name) {
|
||||||
|
|||||||
@ -796,6 +796,33 @@ pub fn build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_
|
|||||||
module
|
module
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// JsonParser _unescape_string の「i+=2 + continue」コアを Structured で組み立てるヘルパー(dev-only)。
|
||||||
|
///
|
||||||
|
/// 実ループ(`tools/hako_shared/json_parser.hako::_unescape_string`)から、
|
||||||
|
/// 文字列処理を除いて制御構造(continue + 可変ステップ更新)だけを抽出した最小フィクスチャ。
|
||||||
|
///
|
||||||
|
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_unescape_string_step2_min.program.json
|
||||||
|
pub fn build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev() -> JoinModule {
|
||||||
|
const FIXTURE: &str = include_str!(
|
||||||
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_unescape_string_step2_min.program.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
let program_json: serde_json::Value = serde_json::from_str(FIXTURE)
|
||||||
|
.expect("jsonparser_unescape_string_step2_min fixture should be valid JSON");
|
||||||
|
|
||||||
|
let mut lowerer = AstToJoinIrLowerer::new();
|
||||||
|
let module = lowerer.lower_program_json(&program_json);
|
||||||
|
|
||||||
|
if joinir_dev_enabled() && joinir_test_debug_enabled() {
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/normalized-dev] jsonparser_unescape_string_step2_min structured module: {:#?}",
|
||||||
|
module
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
|
|
||||||
/// まとめて import したいとき用のプレリュード。
|
/// まとめて import したいとき用のプレリュード。
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{
|
pub use super::{
|
||||||
@ -806,6 +833,7 @@ pub mod prelude {
|
|||||||
build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev,
|
build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev,
|
||||||
build_jsonparser_skip_ws_real_structured_for_normalized_dev,
|
build_jsonparser_skip_ws_real_structured_for_normalized_dev,
|
||||||
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev,
|
||||||
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
||||||
build_pattern3_if_sum_min_structured_for_normalized_dev,
|
build_pattern3_if_sum_min_structured_for_normalized_dev,
|
||||||
build_pattern3_if_sum_multi_min_structured_for_normalized_dev,
|
build_pattern3_if_sum_multi_min_structured_for_normalized_dev,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use nyash_rust::mir::join_ir::normalized::fixtures::{
|
|||||||
build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev,
|
build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev,
|
||||||
build_jsonparser_skip_ws_real_structured_for_normalized_dev,
|
build_jsonparser_skip_ws_real_structured_for_normalized_dev,
|
||||||
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev,
|
||||||
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
||||||
build_pattern3_if_sum_min_structured_for_normalized_dev,
|
build_pattern3_if_sum_min_structured_for_normalized_dev,
|
||||||
build_pattern3_if_sum_multi_min_structured_for_normalized_dev,
|
build_pattern3_if_sum_multi_min_structured_for_normalized_dev,
|
||||||
|
|||||||
@ -295,6 +295,24 @@ fn test_normalized_pattern4_jsonparser_parse_object_continue_skip_ws_vm_bridge_d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Phase 88: JsonParser _unescape_string コア(i+=2 + continue)を canonical route で固定する。
|
||||||
|
#[test]
|
||||||
|
fn test_phase88_jsonparser_unescape_string_step2_min_canonical_matches_structured() {
|
||||||
|
let structured = build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev();
|
||||||
|
let entry = structured.entry.expect("structured entry required");
|
||||||
|
|
||||||
|
// n=10 → i=0,2,4,6,8 で acc++ → 5
|
||||||
|
let args = [JoinValue::Int(10)];
|
||||||
|
let structured_res = run_joinir_vm_bridge_structured_only(&structured, entry, &args);
|
||||||
|
let canonical = run_joinir_vm_bridge(&structured, entry, &args, false);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
structured_res, canonical,
|
||||||
|
"canonical unescape(step2) mismatch"
|
||||||
|
);
|
||||||
|
assert_eq!(canonical, JoinValue::Int(5));
|
||||||
|
}
|
||||||
|
|
||||||
/// Phase 48-C: JsonParser _parse_object continue skip_ws canonical route should match Structured
|
/// Phase 48-C: JsonParser _parse_object continue skip_ws canonical route should match Structured
|
||||||
#[test]
|
#[test]
|
||||||
fn test_normalized_pattern4_jsonparser_parse_object_continue_skip_ws_canonical_matches_structured()
|
fn test_normalized_pattern4_jsonparser_parse_object_continue_skip_ws_canonical_matches_structured()
|
||||||
|
|||||||
Reference in New Issue
Block a user