Files
hakorune/docs/development/current/main/phase192-complex-addend-design.md
nyash-codex b7bf4a721e docs: Phase 191 completion + Phase 192 Complex addend design
- Updated joinir-architecture-overview.md:
  - Phase 191 body-local init integration complete
  - Phase 192 Complex addend normalization strategy
- Updated CURRENT_TASK.md: Phase 191 complete with results
- Created phase192-complex-addend-design.md:
  - ComplexAddendNormalizer design (temp variable decomposition)
  - Integration with LoopUpdateAnalyzer and Pattern2
  - No changes to emission layer (reuses existing boxes)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-09 03:59:53 +09:00

12 KiB
Raw Blame History

Phase 192: Complex Addend Design (Doc-Only)

Status: Design Phase Date: 2025-12-09 Prerequisite: Phase 191 complete (body-local init integrated)


目的

result = result * 10 + digits.indexOf(ch) のような 「加算側がメソッド呼び出し」のパターンを、 既存の NumberAccumulation ラインの前処理として安全に扱えるようにする。

新パターンは増やさず、Analyzer/Lowerer の箱を拡張するだけに留める。


Section 1: 対象 RHS パターン一覧

1.1 JsonParser._parse_number (lines 106-142)

ループパターン:

local num_str = ""
local digits = "0123456789"
loop(p < s.length()) {
    local ch = s.substring(p, p+1)
    local digit_pos = digits.indexOf(ch)

    if digit_pos < 0 {
        break
    }

    num_str = num_str + ch    // ← StringAppendChar (already supported)
    p = p + 1
}

Note: このループは string accumulation なので Phase 192 の対象外(既にサポート済み)。

1.2 JsonParser._atoi (lines 436-467)

ループパターン:

local v = 0
local digits = "0123456789"
loop(i < n) {
    local ch = s.substring(i, i+1)
    if ch < "0" || ch > "9" { break }
    local pos = digits.indexOf(ch)
    if pos < 0 { break }
    v = v * 10 + pos    // ← NumberAccumulation with body-local (Phase 191 supported)
    i = i + 1
}

Current Status: Phase 191 で body-local pos が使えるようになったので対応可能。

1.3 Complex Addend Pattern (Target for Phase 192)

パターン:

local v = 0
local digits = "0123456789"
loop(i < n) {
    local ch = s.substring(i, i+1)
    v = v * 10 + digits.indexOf(ch)    // ← Complex addend (method call)
    i = i + 1
}

AST Form:

Assign(
  lhs = "v",
  rhs = BinaryOp(
    op = Add,
    left = BinaryOp(
      op = Mul,
      left = Variable("v"),
      right = Literal(Integer(10))
    ),
    right = MethodCall(
      receiver = Variable("digits"),
      method = "indexOf",
      args = [Variable("ch")]
    )
  )
)

Characteristics:

  • LHS appears exactly once in RHS (in left-most multiplication)
  • Base: 10 (constant)
  • Addend: MethodCall (complex expression)
  • Current behavior: Rejected as UpdateKind::Complex

Section 2: LHS 出現回数とMethod Call位置の整理

2.1 パターンマトリクス

Pattern LHS Count Base Addend Current Phase 192
v = v * 10 + pos 1 Const(10) Variable NumberAccumulation No change
v = v * 10 + 5 1 Const(10) Const(5) NumberAccumulation No change
v = v * 10 + digits.indexOf(ch) 1 Const(10) MethodCall Complex Normalize
v = v * base + x 1 Variable Variable Complex Fail-Fast
v = v * 10 + (a + b) 1 Const(10) BinaryOp Complex Future
v = v * 10 + v 2 Const(10) Variable Complex Fail-Fast

2.2 Method Call の位置分類

Type A: Addend に Method CallPhase 192 対象):

v = v * 10 + digits.indexOf(ch)

→ Normalize: temp = digits.indexOf(ch); v = v * 10 + temp

Type B: Base に Method Call対象外:

v = v * getBase() + x

→ Fail-Fastbase は constant のみ許可)

Type C: LHS 側に Method Call対象外:

v = obj.getValue() * 10 + x

→ Fail-FastLHS は simple variable のみ)

2.3 Nested Method Call将来拡張

v = v * 10 + parser.parse(s.substring(i, i+1))

→ Phase 192 では Fail-Fast、Phase 193+ で対応検討


Section 3: Temp 分解戦略

3.1 正規化アプローチ

Before (Complex):

result = result * 10 + digits.indexOf(ch)

After (Normalized):

local temp_digit = digits.indexOf(ch)
result = result * 10 + temp_digit

3.2 正規化の責務

新しい箱: ComplexAddendNormalizer

入力:

  • Assign(lhs, complex_rhs) where complex_rhs has MethodCall in addend
  • ループ本体 ASTtemp 変数を挿入する位置情報)

出力:

  • temp 定義: local temp = methodCall(...)
  • 正規化された Assign: lhs = lhs * base + temp

配置:

  • Pattern2 lowering の前処理ライン(can_lower の前)

Section 4: ComplexAddendNormalizer 擬似コード

4.1 検出ロジック

fn is_complex_addend_pattern(update_expr: &UpdateExpr) -> bool {
    // Check structure: lhs = lhs * base + addend
    let UpdateExpr::BinOp { op: BinOpKind::Add, left, right } = update_expr else {
        return false;
    };

    // Left side: lhs * base (multiplication)
    let UpdateRhs::BinOp { op: BinOpKind::Mul, .. } = left else {
        return false;
    };

    // Right side (addend): MethodCall or complex expression
    matches!(right, UpdateRhs::MethodCall(_) | UpdateRhs::BinaryOp(_))
}

4.2 正規化ロジック

fn normalize_complex_addend(
    lhs: &str,
    update_expr: &UpdateExpr,
    body_ast: &mut Vec<ASTNode>,
) -> Result<(String, UpdateExpr), String> {
    // Extract addend (method call or complex expression)
    let addend = extract_addend(update_expr)?;

    // Generate temp variable name
    let temp_name = format!("temp_{}_addend", lhs);

    // Insert temp assignment at current position
    // local temp = digits.indexOf(ch)
    let temp_init = ASTNode::LocalDeclaration {
        name: temp_name.clone(),
        init: Some(Box::new(addend.to_ast())),
    };
    body_ast.insert(0, temp_init);  // Insert at loop body start

    // Create normalized update expression
    // lhs = lhs * 10 + temp
    let normalized_expr = UpdateExpr::BinOp {
        op: BinOpKind::Add,
        left: Box::new(UpdateRhs::BinOp {
            op: BinOpKind::Mul,
            left: Box::new(UpdateRhs::Variable(lhs.to_string())),
            right: Box::new(UpdateRhs::Const(extract_base(update_expr)?)),
        }),
        right: Box::new(UpdateRhs::Variable(temp_name.clone())),
    };

    Ok((temp_name, normalized_expr))
}

4.3 Phase 191 との統合

Phase 191 の LoopBodyLocalInitLowerertemp の初期化を処理:

  • local temp_digit = digits.indexOf(ch) → JoinIR に emit
  • LoopBodyLocalEnvtemp_digit -> join_id を登録
  • UpdateEnvtemp_digit を解決して NumberAccumulation に渡す

Section 5: LoopUpdateAnalyzer との責務分け

5.1 現在のフロー

AST → LoopUpdateAnalyzer → UpdateKind::Complex (Fail-Fast)

5.2 Phase 192 フロー

AST → LoopUpdateAnalyzer → UpdateKind::Complex
  ↓ (Pattern2 内 can_lower 前)
ComplexAddendNormalizer → 前処理 (temp 生成)
  ↓
再度 LoopUpdateAnalyzer → UpdateKind::NumberAccumulation { base: 10 }
  ↓
Pattern2 lowering → JoinIR emission

5.3 can_lower の変更点

Before (Phase 191):

fn can_lower_carrier_updates(updates: &HashMap<String, UpdateExpr>) -> bool {
    for (name, update_expr) in updates {
        let kind = classify_update_kind(update_expr);
        match kind {
            UpdateKind::Complex => {
                eprintln!("[joinir/freeze] Complex carrier update");
                return false;
            }
            _ => { /* OK */ }
        }
    }
    true
}

After (Phase 192):

fn can_lower_carrier_updates_with_normalization(
    updates: &HashMap<String, UpdateExpr>,
    body_ast: &mut Vec<ASTNode>,
) -> Result<HashMap<String, UpdateExpr>, String> {
    let mut normalized_updates = HashMap::new();

    for (name, update_expr) in updates {
        let kind = classify_update_kind(update_expr);
        match kind {
            UpdateKind::Complex => {
                // Try normalization
                if is_complex_addend_pattern(update_expr) {
                    let (temp_name, normalized_expr) =
                        ComplexAddendNormalizer::normalize(name, update_expr, body_ast)?;

                    // Re-classify
                    let normalized_kind = classify_update_kind(&normalized_expr);
                    if matches!(normalized_kind, UpdateKind::NumberAccumulation { .. }) {
                        normalized_updates.insert(name.clone(), normalized_expr);
                    } else {
                        return Err("[joinir/freeze] Normalization failed".to_string());
                    }
                } else {
                    return Err("[joinir/freeze] Complex pattern not supported".to_string());
                }
            }
            _ => {
                normalized_updates.insert(name.clone(), update_expr.clone());
            }
        }
    }

    Ok(normalized_updates)
}

Section 6: Emission 側への影響

6.1 JoinIR Emission変更なし

ComplexAddendNormalizer で前処理するため、既存の emission ラインは変更不要:

  1. LoopBodyLocalInitLowerer (Phase 191):

    • local temp_digit = digits.indexOf(ch) を JoinIR に emit
    • MethodCall の lowering は既存の式 lowerer に委譲
  2. CarrierUpdateLowerer (Phase 190):

    • result = result * 10 + temp_digit を NumberAccumulation として emission
    • temp_digit は UpdateEnv 経由で解決
  3. NumberAccumulation emission:

    • Phase 190 の 2-instruction emission そのまま:
      tmp = result * 10
      result = tmp + temp_digit
      

6.2 設計原則

  • Separation of Concerns: 正規化 (ComplexAddendNormalizer) と emission (CarrierUpdateLowerer) を分離
  • Reusability: 既存の body-local init / NumberAccumulation emission を再利用
  • Fail-Fast: 対応できないパターンは明示的エラー

Section 7: Implementation Phases (TBD)

Phase 192-impl-A: ComplexAddendNormalizer 実装

  1. is_complex_addend_pattern() 検出ロジック
  2. normalize_complex_addend() 正規化ロジック
  3. temp 変数生成とAST挿入

Phase 192-impl-B: Pattern2 統合

  1. can_lowercan_lower_with_normalization に拡張
  2. 正規化後の UpdateExpr で再解析
  3. Unit tests (5+ cases)

Phase 192-impl-C: E2E テスト

  1. phase192_complex_addend_atoi.hako 作成
  2. result = result * 10 + digits.indexOf(ch) パターンで期待値確認
  3. 退行テストphase191_*.hako

Section 8: 制約と Non-Goals

8.1 対応パターン

Phase 192 で対応:

  • Addend に simple MethodCall: v = v * 10 + digits.indexOf(ch)
  • Addend が Variable の場合は Phase 191 で対応済み

Phase 192 で非対応(将来拡張):

  • Nested method call: v = v * 10 + parser.parse(s.substring(...))
  • Complex binary expression: v = v * 10 + (a + b * c)
  • Multiple method calls in same update

8.2 Fail-Fast ケース

  • Base が variable: v = v * base + f(x)
  • LHS が複数回出現: v = v * 10 + v
  • Method call が base 側: v = v * getBase() + x

Appendix A: AST Examples

A.1 Before Normalization

Source:

result = result * 10 + digits.indexOf(ch)

AST:

Assignment {
    target: Variable { name: "result" },
    value: BinaryOp {
        operator: Add,
        left: BinaryOp {
            operator: Mul,
            left: Variable { name: "result" },
            right: Literal { value: Integer(10) },
        },
        right: MethodCall {
            receiver: Variable { name: "digits" },
            method: "indexOf",
            args: [Variable { name: "ch" }],
        },
    },
}

A.2 After Normalization

Source:

local temp_result_addend = digits.indexOf(ch)
result = result * 10 + temp_result_addend

AST:

[
  LocalDeclaration {
      name: "temp_result_addend",
      init: Some(MethodCall {
          receiver: Variable { name: "digits" },
          method: "indexOf",
          args: [Variable { name: "ch" }],
      }),
  },
  Assignment {
      target: Variable { name: "result" },
      value: BinaryOp {
          operator: Add,
          left: BinaryOp {
              operator: Mul,
              left: Variable { name: "result" },
              right: Literal { value: Integer(10) },
          },
          right: Variable { name: "temp_result_addend" },
      },
  },
]

Revision History

  • 2025-12-09: Initial design document (Section 1-8)