Files
hakorune/docs/guides/loopform.md
nyash-codex d7805e5974 feat(joinir): Phase 213-2 Step 2-2 & 2-3 Data structure extensions
Extended PatternPipelineContext and CarrierUpdateInfo for Pattern 3 AST-based generalization.

Changes:
1. PatternPipelineContext:
   - Added loop_condition: Option<ASTNode>
   - Added loop_body: Option<Vec<ASTNode>>
   - Added loop_update_summary: Option<LoopUpdateSummary>
   - Updated build_pattern_context() for Pattern 3

2. CarrierUpdateInfo:
   - Added then_expr: Option<ASTNode>
   - Added else_expr: Option<ASTNode>
   - Updated analyze_loop_updates() with None defaults

Status: Phase 213-2 Steps 2-2 & 2-3 complete
Next: Create Pattern3IfAnalyzer to extract if statement and populate update summary
2025-12-10 00:01:53 +09:00

6.4 KiB
Raw Permalink Blame History

LoopForm ガイド — ループ正規化(ユーザーマクロ)

目的

  • while/for/foreach を Nyash のユーザーマクロで“キャリアタプル”に正規化し、MIR/LLVMにとって最適化しやすい形へ落とし込む。

使い方(予定)

  • マクロ登録(例):
export NYASH_MACRO_ENABLE=1
export NYASH_MACRO_PATHS=apps/macros/examples/loop_normalize_macro.hako
  • 自己ホスト前展開autoを利用して、parse直後にLoopForm展開を有効化PyVM環境

JSON生成ユーティリティJsonBuilder

  • ループ正規化では AST JSON v0 の断片を安全に構成する必要があります。
  • 最小ユーティリティとして apps/lib/json_builder.hako を提供していますincludeで読み込み、文字列でJSON断片を生成
  • 例:
local JB = include "apps/lib/json_builder.hako"
local v_i   = JB.variable("i")
local v_sum = JB.variable("sum")
local lit_0 = JB.literal_int(0)
local assign = JB.assignment(v_i, JB.binary("+", v_i, JB.literal_int(1)))

正規化の考え方

  • ループで更新される変数群をタプルに束ね、ヘッダに“1個のφ”を置く。
  • break/continue は“次キャリア”または“現キャリア”で遷移し、一貫した合流点を保つ。

キャリア正規化MVP-2の具体例

  • 前提: break/continue なし、更新変数は最大2個例: i と sum

例1: 基本的な whilei を 0..n-1 で加算)

Nyash入力・素朴形

local i = 0
local sum = 0
while (i < n) {
  sum = sum + i
  i = i + 1
}

正規化の狙い(概念)

  • ループ本体の末尾に更新Assignmentをそろえる既に末尾ならそのまま
  • ループヘッダの合流で i と sum を同一グループの PHI でまとめやすくする。

AST JSON v0 のスケッチJsonBuilder を用いた生成例)

local JB   = include "apps/lib/json_builder.hako"
local v_i  = JB.variable("i")
local v_s  = JB.variable("sum")
local v_n  = JB.variable("n")

// 先頭(ローカル導入)
local i0   = JB.local_decl("i", JB.literal_int(0))
local s0   = JB.local_decl("sum", JB.literal_int(0))

// 条件と本体(更新は末尾に揃える)
local cond = JB.binary("<", v_i, v_n)
local body_nonassign = JB.block([
  JB.assignment(v_s, JB.binary("+", v_s, v_i))
])
local body_updates   = JB.block([
  JB.assignment(v_i, JB.binary("+", v_i, JB.literal_int(1)))
])

// ループードMVP: while→Loop; キャリアは概念上)
local loop_node = JB.loop(cond, JB.concat_blocks(body_nonassign, body_updates))

JB.program([ i0, s0, loop_node ])

備考

  • MVP-2 では“新しいキャリア用ノード”は導入せず、既存の Local/If/Loop/Assignment で表現する。
  • 「非代入→代入」の順を崩すと意味が変わる可能性があるため、再配置は安全にできる場合のみ行う(既に末尾に更新がある等)。

例2: 2変数更新の順序混在安全な並べ替え

while (i < n) {
  print(i)
  sum = sum + i
  i = i + 1
}
  • 正規化: 非代入print→ 代入(sum) → 代入(i) の末尾整列。
  • 非代入が末尾に来るケースは再配置しない(意味が変わりうるため、スキップ)。

今後の拡張MVP-3 概要)

  • continue: 「次キャリア」へ(更新後にヘッダへ戻す)。
  • break: 「現キャリア」を exit へ(ヘッダ合流と衝突しないよう保持)。
  • いずれも 1 段ネストまでの最小対応から開始。

MVP-3実装済み・最小対応

  • 本体を break/continue でセグメント分割し、各セグメント内のみ安全に「非代入→代入」に整列。
  • ガード:
    • 代入先は変数のみ(フィールド等は対象外)
    • 全体の更新変数は最大2種MVP-2 制約を継承)
    • セグメント内で「代入の後に非代入」があれば整列しない(順序保持)
  • スモークv2: tools/smokes/v2/run.sh --profile quick --filter "loopform|macro"

for / foreach の糖衣と正規化(概要)

  • for: for(fn(){ init }, cond, fn(){ step }, fn(){ body })init; loop(cond){ body; step } へ正規化。
    • init/step は Assignment/Local 単体でも可。
  • foreach: foreach(arr, "x", fn(){ body })__ny_i で走査する Loop へ正規化し、xarr.get(__ny_i) に置換。
  • スモークv2: tools/smokes/v2/run.sh --profile quick --filter "macro|foreach|for"

対応状況MVP→順次拡張

  • Week1: whilebreak/continue無し
  • Week2: break/continue/ネスト最小対応、キャリア自動抽出
  • Week3: for/foreach限定

制約MVP

  • try/finally/throwとの相互作用は未対応。例外は後続フェーズで設計を明記。

検証

  • macrogolden展開後ASTのゴールデン
  • LLVM PHI健全性スモーク空PHI無し、先頭グループ化
  • 出力一致スモークtwovars の実行出力が同一であること)

手元での確認

  • ゴールデン(キー順無視の比較)
    • tools/test/golden/macro/loop_simple_user_macro_golden.sh
    • tools/test/golden/macro/loop_two_vars_user_macro_golden.sh
  • 出力一致スモークVM, v2
    • tools/smokes/v2/run.sh --profile quick --filter "loop_two_vars|macro"
  • 自己ホスト前展開PyVM 経由)
  • NYASH_VM_USE_PY=1 NYASH_USE_NY_COMPILER=1 NYASH_MACRO_ENABLE=1 NYASH_MACRO_PATHS=apps/macros/examples/loop_normalize_macro.hako ./target/release/hakorune --macro-preexpand --backend vm apps/tests/macro/loopform/simple.hako

Selfhost compiler prepass恒等→最小正規化

  • Runner が NYASH_LOOPFORM_NORMALIZE=1--loopform にマップして子に渡し、apps/lib/loopform_normalize.hako の前処理を適用(現状は恒等)。
  • 既定OFF。将来、キー順正規化→簡易キャリア整列を段階的に追加する。

実装メモ(内蔵変換ルート / Rust

  • 既定のマクロ実行は internalchildRust内蔵です。LoopNormalize は以下の保守的なガードで正規化します。
    • トップレベル本体に Break/Continue がないこと
    • 代入対象は最大2変数、かつ単純な変数フィールド代入などは除外
    • 代入の後ろに非代入が現れない(安全に末尾整列できる)
  • 条件を満たす場合のみ「非代入→代入」の順でボディを再構成します(意味は不変)。

参考

  • docs/private/roadmap2/phases/phase-17-loopform-selfhost/