- 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>
12 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" },
},
},
]
Revision History
- 2025-12-09: Initial design document (Section 1-8)