- New module: complex_addend_normalizer.rs (320 lines, 5 unit tests) - Transforms `result = result * 10 + f(x)` into temp variable pattern - Pattern2 preprocessing integration (~40 lines added) - Zero changes to emission layers (reuses Phase 191 + Phase 190) Tests: - Unit tests: 5/5 passing (normalization logic) - Regression: phase190/191 tests all pass - Demo: phase192_normalization_demo.hako → 123 Limitation: Full E2E requires Phase 193 (MethodCall in init) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
17 KiB
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 Call(Phase 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-Fast(base は constant のみ許可)
Type C: LHS 側に Method Call(対象外):
v = obj.getValue() * 10 + x
→ Fail-Fast(LHS は 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)wherecomplex_rhshas MethodCall in addend- ループ本体 AST(temp 変数を挿入する位置情報)
出力:
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 の LoopBodyLocalInitLowerer が temp の初期化を処理:
local temp_digit = digits.indexOf(ch)→ JoinIR に emitLoopBodyLocalEnvにtemp_digit -> join_idを登録UpdateEnvがtemp_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 ラインは変更不要:
-
LoopBodyLocalInitLowerer (Phase 191):
local temp_digit = digits.indexOf(ch)を JoinIR に emit- MethodCall の lowering は既存の式 lowerer に委譲
-
CarrierUpdateLowerer (Phase 190):
result = result * 10 + temp_digitを NumberAccumulation として emissiontemp_digitは UpdateEnv 経由で解決
-
NumberAccumulation emission:
- Phase 190 の 2-instruction emission そのまま:
tmp = result * 10 result = tmp + temp_digit
- Phase 190 の 2-instruction emission そのまま:
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 実装
is_complex_addend_pattern()検出ロジックnormalize_complex_addend()正規化ロジック- temp 変数生成とAST挿入
Phase 192-impl-B: Pattern2 統合
can_lowerをcan_lower_with_normalizationに拡張- 正規化後の UpdateExpr で再解析
- Unit tests (5+ cases)
Phase 192-impl-C: E2E テスト
phase192_complex_addend_atoi.hako作成result = result * 10 + digits.indexOf(ch)パターンで期待値確認- 退行テスト(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" },
},
},
]
Section 9: Implementation Complete (2025-12-09)
9.1 Implementation Summary
Status: ✅ Phase 192-impl Complete
Deliverables:
- ✅
ComplexAddendNormalizermodule implemented (src/mir/join_ir/lowering/complex_addend_normalizer.rs) - ✅ 5 unit tests all passing (method call, simple variable, wrong LHS, no multiplication, subtraction)
- ✅ Pattern2 integration complete (preprocessing before carrier update analysis)
- ✅ Existing tests verified (phase190/191 tests still pass)
- ✅ Documentation updated
9.2 Actual Implementation
File: src/mir/join_ir/lowering/complex_addend_normalizer.rs
API:
pub enum NormalizationResult {
Unchanged,
Normalized { temp_def: ASTNode, new_assign: ASTNode, temp_name: String },
}
impl ComplexAddendNormalizer {
pub fn normalize_assign(assign: &ASTNode) -> NormalizationResult;
}
Integration Point (Pattern2 - line 243-279):
// Phase 192: Normalize complex addend patterns in loop body
let mut normalized_body = Vec::new();
let mut has_normalization = false;
for node in _body {
match ComplexAddendNormalizer::normalize_assign(node) {
NormalizationResult::Normalized { temp_def, new_assign, temp_name } => {
normalized_body.push(temp_def); // local temp = <complex_expr>
normalized_body.push(new_assign); // lhs = lhs * base + temp
has_normalization = true;
}
NormalizationResult::Unchanged => {
normalized_body.push(node.clone());
}
}
}
let analysis_body = if has_normalization { &normalized_body } else { _body };
// Pass analysis_body to LoopUpdateAnalyzer and lower_loop_with_break_minimal
9.3 AST Transformation Example
Before:
result = result * 10 + digits.indexOf(ch)
After (Normalized AST):
local temp_result_addend = digits.indexOf(ch)
result = result * 10 + temp_result_addend
AST Structure:
[
Local { variables: ["temp_result_addend"], initial_values: [MethodCall(...)] },
Assignment { target: "result", value: BinOp(Add, BinOp(Mul, "result", 10), "temp_result_addend") }
]
9.4 Test Results
Unit Tests (5/5 passing):
- ✅
test_normalize_complex_addend_method_call- Core normalization pattern - ✅
test_normalize_simple_variable_unchanged- No-op for simple patterns - ✅
test_normalize_wrong_lhs_unchanged- Reject invalid patterns - ✅
test_normalize_no_multiplication_unchanged- Reject non-accumulation patterns - ✅
test_normalize_subtraction_complex_addend- Subtraction variant
Integration Tests (regression verified):
- ✅
phase190_atoi_impl.hako→ 12 (no regression) - ✅
phase190_parse_number_impl.hako→ 123 (no regression) - ✅
phase191_body_local_atoi.hako→ 123 (no regression) - ✅
phase192_normalization_demo.hako→ 123 (new demo test)
9.5 Current Limitations (Phase 193+ Work)
Limitation: Full E2E flow with MethodCall in temp variables requires extending LoopBodyLocalInitLowerer (Phase 186).
Current Behavior:
local temp = digits.indexOf(ch) // ❌ Phase 186 error: "Unsupported init expression: method call"
Phase 186 Scope: Only supports int/arithmetic operations (+, -, *, /, constants, variables)
Future Work (Phase 193+):
- Extend
LoopBodyLocalInitLowerer::lower_init_expr()to handle:ASTNode::MethodCall(e.g.,digits.indexOf(ch))ASTNode::Call(e.g.,parseInt(s))
- Add emission logic for method call results in JoinIR
- Add UpdateEnv resolution for method call temps
Workaround: For now, complex addend normalization works at the AST level, but lowering requires manual temp extraction outside the loop.
9.6 Design Principles Validated
✅ 箱化 (Box-First):
- ComplexAddendNormalizer is a pure AST transformer (single responsibility)
- No emission logic mixed in (delegation to existing Phase 191/190 infrastructure)
✅ Fail-Fast:
- Unsupported patterns return
NormalizationResult::Unchanged - Phase 186 limitation explicitly documented
✅ Reusability:
- Normalizer works with any BinaryOp pattern (Add/Subtract)
- Integration point is clean (10 lines in Pattern2)
✅ Minimal Changes:
- Zero changes to emission layers (CarrierUpdateEmitter, LoopBodyLocalInitLowerer)
- Only preprocessing added to Pattern2 (before carrier analysis)
9.7 Trace Output
When running a test with complex addend pattern:
[pattern2/phase192] Normalized complex addend: temp='temp_result_addend' inserted before update
[cf_loop/pattern2] Phase 176-3: Analyzed 1 carrier updates
Revision History
- 2025-12-09: Initial design document (Section 1-8)
- 2025-12-09: Implementation complete (Section 9)