- Fixed ValueId collision between body-local and carrier params - Added ExitLine contract verifier (debug assertions) - Updated test files to use Main box - E2E verified: atoi→12, parse_number→123 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
26 KiB
Phase 190: NumberAccumulation Update Design (Doc-Only)
Status: Design Complete (Code TBD)
Date: 2025-12-09
Goal: Design JoinIR support for result = result * 10 + digit style updates
Section 1: Target Loops and RHS Patterns
1.1 JsonParserBox._atoi (lines 436-467)
Loop Pattern:
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 pattern
i = i + 1
}
AST Form of Update:
Assign(
lhs = "v",
rhs = BinaryOp(
op = Add,
left = BinaryOp(
op = Mul,
left = Variable("v"),
right = Literal(Integer(10))
),
right = Variable("pos")
)
)
Characteristics:
- LHS appears exactly once in RHS (in left-most multiplication)
- Base: 10 (constant)
- Addend:
pos(loop-local variable) - Type: Integer (MirType::Integer)
1.2 JsonParserBox._parse_number (lines 106-142)
Loop Pattern:
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: This is string accumulation (num_str + ch), not number accumulation.
Already handled by existing StringAppendChar / AccumulationLike.
1.3 Other Instances in Codebase
From grep results:
-
apps/tests/phase189_atoi_mini.hako:10
result = result * 10 + i- Simplified test case
-
apps/tests/phase183_p2_atoi.hako:25
result = result * 10 + digit- Similar pattern
-
apps/tests/phase183_p2_parse_number.hako:28
result = result * 10 + digit_pos- Same pattern with different variable name
-
lang/src/compiler/builder/ssa/exit_phi/break_finder.hako:277
result = result * 10 + (BreakFinderBox._char_to_digit(ch))- Includes method call on RHS (Complex)
1.4 Canonical Form Requirements
Safe NumberAccumulation Pattern:
lhs = lhs * BASE + addend
Where:
lhs: carrier variable (appears exactly 1 time in RHS)BASE: integer constant (typically 10)addend: one of:- Variable (loop-local or carrier)
- Constant
- NOT method call (→ Complex)
Variants:
lhs = lhs * BASE - addend(subtraction also allowed)- Base can be any integer constant (10, 2, 16, etc.)
Section 2: Safe Pattern Candidates
2.1 Whitelist Criteria
A NumberAccumulation update is safe if:
- Type Check: Carrier type is
MirType::Integer - Structure Check: AST matches
lhs = Add(Mul(lhs, Const(base)), addend) - LHS Occurrence:
lhsappears exactly 1 time in RHS (in multiplication) - Base Check: Base is integer constant (not variable)
- Addend Check: Addend is one of:
Variable(name)- loop-local or carrierConst(n)- integer constant
- No Method Calls: Addend does not contain method calls
2.2 Reject as Complex
Mark as UpdateKind::Complex if:
- LHS appears 2+ times in RHS
- Base is variable (not constant)
- Addend contains method call (e.g.,
_char_to_digit(ch)) - Addend is complex expression (nested BinaryOp)
- Type is not Integer
- LoopBodyLocal appears in addend (Phase 5 未サポート)
2.3 Pattern Matrix
| Pattern | LHS Count | Base | Addend | Decision |
|---|---|---|---|---|
v = v * 10 + pos |
1 | Const(10) | Variable | NumberAccumulation |
v = v * 10 + 5 |
1 | Const(10) | Const(5) | NumberAccumulation |
v = v * base + x |
1 | Variable | Variable | Complex |
v = v * 10 + f(x) |
1 | Const(10) | MethodCall | Complex |
v = v * 10 + v |
2 | Const(10) | Variable | Complex |
Section 3: UpdateKind::NumberAccumulation Definition
3.1 Enum Extension
Current (Phase 170-C-2):
pub enum UpdateKind {
CounterLike,
AccumulationLike,
Other,
}
Proposed (Phase 190):
pub enum UpdateKind {
CounterLike,
AccumulationLike,
/// Phase 190: Number accumulation: result = result * base + addend
///
/// Typical pattern: digit expansion (atoi, parse_number)
/// Example: v = v * 10 + digit
NumberAccumulation { base: i64 },
/// Phase 178+: String append patterns (already implemented)
StringAppendChar,
StringAppendLiteral,
/// Complex or unrecognized patterns
Complex,
/// Deprecated
Other,
}
3.2 UpdateKind::name() Extension
impl UpdateKind {
pub fn name(&self) -> &'static str {
match self {
UpdateKind::CounterLike => "CounterLike",
UpdateKind::AccumulationLike => "AccumulationLike",
UpdateKind::NumberAccumulation { base } => "NumberAccumulation",
UpdateKind::StringAppendChar => "StringAppendChar",
UpdateKind::StringAppendLiteral => "StringAppendLiteral",
UpdateKind::Complex => "Complex",
UpdateKind::Other => "Other",
}
}
}
3.3 Alternative: Subfield on AccumulationLike
Instead of new enum variant, could extend AccumulationLike:
pub enum UpdateKind {
CounterLike,
AccumulationLike {
style: AccumulationStyle,
},
Complex,
Other,
}
pub enum AccumulationStyle {
Simple, // result = result + x
NumberDigit { base: i64 }, // result = result * base + x
StringAppend, // result = result + "literal"
}
Decision: Use dedicated NumberAccumulation { base: i64 } for clarity.
Simpler to pattern-match, clearer intent.
Section 4: classify_number_update Pseudocode
4.1 Algorithm Overview
Input:
lhs: &str- carrier variable namerhs_ast: &ASTNode- RHS expression AST
Output:
UpdateKind- classified update pattern
Steps:
- Check if RHS is
BinaryOp(Add, left, right) - Check if
leftisBinaryOp(Mul, mul_left, mul_right) - Verify
mul_leftisVariable(lhs)(LHS appears once) - Verify
mul_rightisLiteral(Integer(base)) - Classify
right(addend):Const(n)→ NumberAccumulationVariable(name)→ NumberAccumulationMethodCall/Other→ Complex
4.2 Pseudocode
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
// Step 1: Check outer addition
let BinaryOp { op: Add, left: mul_expr, right: addend } = rhs_ast else {
return UpdateKind::Complex;
};
// Step 2: Check inner multiplication
let BinaryOp { op: Mul, left: mul_left, right: mul_right } = mul_expr else {
return UpdateKind::Complex;
};
// Step 3: Verify LHS appears in multiplication
let Variable { name: lhs_name } = mul_left else {
return UpdateKind::Complex;
};
if lhs_name != lhs {
return UpdateKind::Complex;
}
// Step 4: Extract base constant
let Literal { value: Integer(base) } = mul_right else {
return UpdateKind::Complex; // base is not constant
};
// Step 5: Classify addend
match addend {
// Variable or Const: NumberAccumulation
Literal { value: Integer(_) } | Variable { .. } => {
UpdateKind::NumberAccumulation { base: *base }
}
// Method call or complex expression: Complex
MethodCall { .. } | BinaryOp { .. } | Call { .. } => {
UpdateKind::Complex
}
_ => UpdateKind::Complex,
}
}
4.3 LHS Occurrence Check
Goal: Ensure LHS appears exactly 1 time in RHS.
Implementation:
fn count_lhs_occurrences(lhs: &str, rhs: &ASTNode) -> usize {
match rhs {
Variable { name } if name == lhs => 1,
BinaryOp { left, right, .. } => {
count_lhs_occurrences(lhs, left) + count_lhs_occurrences(lhs, right)
}
MethodCall { receiver, args, .. } => {
let mut count = count_lhs_occurrences(lhs, receiver);
for arg in args {
count += count_lhs_occurrences(lhs, arg);
}
count
}
_ => 0,
}
}
Usage:
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
// FIRST: Check LHS occurrence count
let lhs_count = count_lhs_occurrences(lhs, rhs_ast);
if lhs_count != 1 {
return UpdateKind::Complex;
}
// THEN: Check structure
// ... (rest of classify_number_update logic)
}
Section 5: Pattern2/4 can_lower Specification
5.1 Current Behavior
Pattern2 can_lower (Phase 176+):
- Accepts: CounterLike, AccumulationLike, StringAppendChar, StringAppendLiteral
- Rejects: Complex →
[joinir/freeze]
Pattern4 can_lower (Phase 33-19+):
- Similar criteria to Pattern2
5.2 Proposed Extension
Pattern2 can_lower (Phase 190+):
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 {
// Whitelist: Simple patterns
UpdateKind::CounterLike
| UpdateKind::AccumulationLike
| UpdateKind::StringAppendChar
| UpdateKind::StringAppendLiteral
| UpdateKind::NumberAccumulation { .. } // ← NEW!
=> { /* OK */ }
// Fail-Fast: Complex patterns
UpdateKind::Complex => {
eprintln!("[joinir/freeze] Complex carrier update detected");
return false;
}
UpdateKind::Other => {
eprintln!("[joinir/freeze] Unknown update pattern");
return false;
}
}
}
true
}
5.3 Type Constraint
NumberAccumulation Type Check:
fn verify_number_accumulation_type(carrier: &CarrierVar) -> bool {
// Phase 190: NumberAccumulation requires Integer type
// String types must use StringAppendChar/StringAppendLiteral
match carrier.mir_type {
MirType::Integer => true,
_ => {
eprintln!("[joinir/freeze] NumberAccumulation requires Integer type, got {:?}",
carrier.mir_type);
false
}
}
}
5.4 LoopBodyLocal Handling
Phase 5 未サポート:
fn check_loopbodylocal_constraint(addend: &UpdateRhs, loop_scope: &LoopScopeShape) -> bool {
match addend {
UpdateRhs::Variable(name) => {
// Check if variable is LoopBodyLocal
if loop_scope.body_locals.contains(name) {
eprintln!("[joinir/freeze] LoopBodyLocal in NumberAccumulation not supported yet");
return false;
}
true
}
_ => true,
}
}
5.5 Fallback Strategy
Fail-Fast Principle:
- NumberAccumulation detection failure →
Complex→[joinir/freeze] - NO silent fallback to LoopBuilder (already deleted)
- NO new fallback paths
Error Message Template:
[joinir/freeze] NumberAccumulation pattern not supported:
carrier: v
reason: addend contains method call
suggestion: extract method call to loop-local variable
Section 6: CarrierUpdateLowerer Pseudocode
6.1 Responsibility Assignment
CarrierUpdateLowerer (Phase 176+):
- Input:
CarrierVar,UpdateExpr,JoinIRBuilder - Output: Emits JoinIR instructions for carrier update
Current Support:
- CounterLike:
dst = lhs + 1 - AccumulationLike:
dst = lhs + rhs - StringAppendChar: (BoxCall to StringBox.append)
Phase 190 Extension:
- NumberAccumulation:
tmp = lhs * base; dst = tmp + addend
6.2 JoinIR Emission Design
Option A: 2-Instruction Approach (Recommended)
fn emit_number_accumulation(
builder: &mut JoinIRBuilder,
carrier: &CarrierVar,
base: i64,
addend: &UpdateRhs,
) -> ValueId {
// Step 1: Multiply by base
// tmp = lhs * base
let base_value = builder.alloc_value();
builder.emit(JoinInst::Const {
dst: base_value,
value: ConstValue::Integer(base),
});
let mul_result = builder.alloc_value();
builder.emit(JoinInst::BinOp {
dst: mul_result,
op: BinOpKind::Mul,
left: carrier.join_id.unwrap(), // Current value from LoopHeader PHI
right: base_value,
});
// Step 2: Add addend
// dst = tmp + addend
let addend_value = emit_addend_value(builder, addend);
let result = builder.alloc_value();
builder.emit(JoinInst::BinOp {
dst: result,
op: BinOpKind::Add,
left: mul_result,
right: addend_value,
});
result
}
fn emit_addend_value(builder: &mut JoinIRBuilder, addend: &UpdateRhs) -> ValueId {
match addend {
UpdateRhs::Const(n) => {
let val = builder.alloc_value();
builder.emit(JoinInst::Const {
dst: val,
value: ConstValue::Integer(*n),
});
val
}
UpdateRhs::Variable(name) => {
// Look up variable in boundary.join_inputs or condition_bindings
builder.lookup_variable(name)
}
_ => unreachable!("Complex addend should be rejected in can_lower"),
}
}
Option B: Single Complex Expression (Not Recommended)
// Would require JoinInst::ComplexExpr or similar
// Violates "flat instruction" principle of JoinIR
// NOT RECOMMENDED
Decision: Use Option A (2-instruction approach).
- Pros: Clean separation, easy to optimize later, follows JoinIR flat instruction principle
- Cons: None
6.3 Type Constraint Enforcement
impl CarrierUpdateLowerer {
pub fn emit_update(
&self,
builder: &mut JoinIRBuilder,
carrier: &CarrierVar,
update_expr: &UpdateExpr,
) -> Result<ValueId, String> {
match classify_update_kind(update_expr) {
UpdateKind::NumberAccumulation { base } => {
// Type check
if carrier.mir_type != MirType::Integer {
return Err(format!(
"NumberAccumulation requires Integer type, got {:?}",
carrier.mir_type
));
}
// Emit instructions
Ok(self.emit_number_accumulation(builder, carrier, base, &extract_addend(update_expr)))
}
// ... other cases
}
}
}
6.4 Name Dependency Prohibition
Phase 170-C-1 Principle: No name-based heuristics in lowering.
Enforcement:
// ✅ GOOD: Structural detection
fn is_number_accumulation(update_expr: &UpdateExpr) -> bool {
matches!(
update_expr,
UpdateExpr::BinOp {
op: BinOpKind::Add,
lhs: _,
rhs: UpdateRhs::Variable(_) | UpdateRhs::Const(_),
}
)
}
// ❌ BAD: Name-based detection
fn is_number_accumulation(carrier_name: &str) -> bool {
carrier_name.contains("result") || carrier_name.contains("v")
}
Rationale:
- Variable names are user-controlled and unreliable
- Structural AST analysis is robust and refactoring-safe
- Follows "箱理論" separation of concerns
6.5 Integration with LoopHeaderPhiBuilder
Phase 33-22 Context:
- LoopHeader PHI creates SSOT for carrier current value
carrier.join_idpoints to LoopHeader PHI dst
Usage in NumberAccumulation:
fn emit_number_accumulation(
builder: &mut JoinIRBuilder,
carrier: &CarrierVar,
base: i64,
addend: &UpdateRhs,
) -> ValueId {
// Use carrier.join_id (LoopHeader PHI dst) as current value
let current_value = carrier.join_id.expect("LoopHeader PHI should set join_id");
// tmp = current_value * base
let base_const = builder.emit_const(ConstValue::Integer(base));
let mul_result = builder.emit_binop(BinOpKind::Mul, current_value, base_const);
// dst = tmp + addend
let addend_value = emit_addend_value(builder, addend);
builder.emit_binop(BinOpKind::Add, mul_result, addend_value)
}
Invariant: Never directly access carrier.host_id in JoinIR emission.
Only use carrier.join_id (JoinIR-local ValueId).
Section 7: Implementation Phases
Phase 190-impl-A: Core Detection (2-3 commits)
Goal: Extend LoopUpdateAnalyzer to detect NumberAccumulation patterns.
Tasks:
- Add
UpdateKind::NumberAccumulation { base: i64 } - Implement
classify_number_update()in LoopUpdateAnalyzer - Add
count_lhs_occurrences()helper - Unit tests (5+ cases covering Section 2.3 matrix)
Success Criteria:
_atoiloop detected as NumberAccumulation { base: 10 }- Complex patterns (method calls) detected as Complex
- Type safety: Only Integer carriers allowed
Phase 190-impl-B: CarrierUpdateLowerer Extension (2-3 commits)
Goal: Emit JoinIR for NumberAccumulation updates.
Tasks:
- Extend
CarrierUpdateLowerer::emit_update()with NumberAccumulation branch - Implement 2-instruction emission (mul + add)
- Type constraint enforcement
- Unit tests (3+ cases)
Success Criteria:
- Correct JoinIR emission:
tmp = v * 10; result = tmp + digit - Type error on non-Integer carriers
- Integration with LoopHeader PHI (carrier.join_id)
Phase 190-impl-C: Pattern2/4 Integration (1-2 commits)
Goal: Enable NumberAccumulation in Pattern2/4 can_lower.
Tasks:
- Update
can_lower_carrier_updates()whitelist - Add NumberAccumulation test case to Pattern2
- Verify Fail-Fast for Complex patterns
Success Criteria:
phase189_atoi_mini.hakopasses with JoinIR- Complex patterns (with method calls) rejected at can_lower
[joinir/freeze]message for unsupported cases
Phase 190-impl-D: E2E Validation (1 commit)
Goal: Real-world JsonParserBox._atoi working via JoinIR.
Tasks:
- Enable JoinIR for
_atoimethod - Verify MIR output correctness
- Runtime test: parse "12345" → 12345
Success Criteria:
- JsonParserBox._atoi compiles via JoinIR Pattern2
- Correct MIR: multiply-add sequence
- Runtime correctness: atoi tests pass
Section 8: Future Extensions
8.1 Other Bases
Current: base is extracted from AST (any integer constant).
Example:
- Binary:
v = v * 2 + bit - Hex:
v = v * 16 + hex_digit
No change needed: Design already supports arbitrary bases.
8.2 Subtraction Variant
Pattern: v = v * 10 - offset
Extension:
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
match rhs_ast {
BinaryOp { op: Add | Sub, left: mul_expr, right: addend } => {
// ... (same logic for both Add and Sub)
}
_ => UpdateKind::Complex,
}
}
Decision: Support both Add and Sub in Phase 190-impl-A.
8.3 Complex Addends
Examples:
v = v * 10 + (ch - '0')v = v * 10 + digits.indexOf(ch)
Strategy:
- Phase 190: Reject as Complex (Fail-Fast)
- Phase 191+: Extend to handle BinaryOp/MethodCall in addend
- Emit extra JoinIR instructions for addend computation
- Store addend in temporary ValueId
Not in scope for Phase 190.
8.4 Multi-Base Patterns
Example (unlikely):
v1 = v1 * 10 + d1
v2 = v2 * 16 + d2
Current Design: Each carrier gets its own NumberAccumulation { base }.
Already supports different bases per carrier.
Section 9: Testing Strategy
9.1 Unit Tests (LoopUpdateAnalyzer)
Coverage Matrix:
| Test Case | Pattern | Expected Kind |
|---|---|---|
v = v * 10 + pos |
Basic NumberAccumulation | NumberAccumulation { base: 10 } |
v = v * 10 + 5 |
Const addend | NumberAccumulation { base: 10 } |
v = v * 2 + bit |
Binary base | NumberAccumulation { base: 2 } |
v = v * 10 - offset |
Subtraction | NumberAccumulation { base: 10 } |
v = v * base + x |
Variable base | Complex |
v = v * 10 + f(x) |
Method call | Complex |
v = v * 10 + v |
LHS appears 2x | Complex |
Implementation:
#[test]
fn test_number_accumulation_basic() {
let ast = parse("v = v * 10 + pos");
let kind = LoopUpdateAnalyzer::classify_update("v", &ast);
assert!(matches!(kind, UpdateKind::NumberAccumulation { base: 10 }));
}
9.2 Integration Tests (Pattern2)
Test Files:
-
apps/tests/phase190_number_update_basic.hako- Single carrier:
v = v * 10 + i - Verify JoinIR emission
- Single carrier:
-
apps/tests/phase190_number_update_multi.hako- Two carriers: counter + accumulator
- Verify multi-carrier lowering
-
apps/tests/phase190_number_update_complex.hako- Complex pattern (method call)
- Verify Fail-Fast rejection
9.3 E2E Tests (JsonParserBox)
Test Cases:
-
test_jsonparser_atoi_simple.hako- Input: "123"
- Expected: 123
-
test_jsonparser_atoi_negative.hako- Input: "-456"
- Expected: -456
-
test_jsonparser_parse_number_min_v2.hako- Full
_parse_numbermethod - Verify string + number accumulation
- Full
Success Criteria:
- All tests pass with JoinIR enabled
- Same behavior as LoopBuilder (deleted, but historical behavior)
Section 10: Migration Notes
10.1 From LoopBuilder (Deleted)
Legacy: LoopBuilder handled all loop patterns (deleted in Phase 170).
Current: JoinIR Pattern1-4 handle specific patterns.
NumberAccumulation Migration:
- Old: LoopBuilder would blindly emit MIR for any loop
- New: Pattern2/4 detect NumberAccumulation, emit specialized JoinIR
No fallback needed: LoopBuilder is deleted.
10.2 From Pattern Detection
Phase 170-C-1: Name-based heuristics (e.g., is_typical_index_name).
Phase 190: Structural AST analysis (no name dependency).
Principle: Lowering must be name-agnostic, only detection can use names.
10.3 Backward Compatibility
Existing Patterns:
- CounterLike:
i = i + 1(unchanged) - AccumulationLike:
sum = sum + x(unchanged) - StringAppendChar:
s = s + ch(unchanged)
New Pattern:
- NumberAccumulation:
v = v * 10 + digit(additive)
No Breaking Changes: All existing patterns continue to work.
Section 11: Open Questions
Q1: Should we support division?
Pattern: v = v / 10
Current: Only +, -, *, / in BinOpKind.
Decision: Out of scope for Phase 190 (no real-world use case found).
Q2: Should addend be restricted to loop params only?
Current Design: Allow any Variable (loop param, outer local, carrier).
Alternative: Only allow LoopParam variables in addend.
Decision: Current design is more flexible, restrict if problems arise.
Q3: How to handle floating-point bases?
Example: v = v * 1.5 + x
Current: base: i64 only supports integers.
Decision: Out of scope (no real-world use case in Nyash codebase).
Section 12: Success Metrics
12.1 Functional Metrics
- ✅ JsonParserBox._atoi compiles via JoinIR
- ✅ phase189_atoi_mini.hako passes
- ✅ Complex patterns rejected with
[joinir/freeze] - ✅ No silent fallbacks (Fail-Fast verified)
12.2 Code Quality Metrics
- ✅ No name-based heuristics in lowering
- ✅ Type safety enforced (Integer only)
- ✅ Unit test coverage > 80%
- ✅ Documentation updated (this doc + overview)
12.3 Performance Metrics
Not a goal for Phase 190: Focus on correctness, not optimization.
Future: Phase 191+ can optimize mul+add to single instruction if needed.
Appendix A: AST Examples
A.1 Basic NumberAccumulation
Source:
v = v * 10 + pos
AST:
Assignment {
target: Variable { name: "v" },
value: BinaryOp {
operator: Add,
left: BinaryOp {
operator: Mul,
left: Variable { name: "v" },
right: Literal { value: Integer(10) },
},
right: Variable { name: "pos" },
},
}
A.2 Complex Pattern (Rejected)
Source:
v = v * 10 + digits.indexOf(ch)
AST:
Assignment {
target: Variable { name: "v" },
value: BinaryOp {
operator: Add,
left: BinaryOp {
operator: Mul,
left: Variable { name: "v" },
right: Literal { value: Integer(10) },
},
right: MethodCall {
receiver: Variable { name: "digits" },
method: "indexOf",
args: [Variable { name: "ch" }],
},
},
}
Decision: Rejected as Complex (MethodCall in addend).
Appendix B: References
B.1 Related Phases
- Phase 170-C-1: Carrier name heuristics
- Phase 170-C-2: LoopUpdateSummary skeleton
- Phase 176: Multi-carrier lowering
- Phase 178: String update detection
B.2 Related Files
Core:
src/mir/join_ir/lowering/loop_update_summary.rssrc/mir/join_ir/lowering/loop_update_analyzer.rssrc/mir/join_ir/lowering/carrier_update_lowerer.rs
Patterns:
src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rssrc/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs
Tests:
tools/hako_shared/json_parser.hako(line 436: _atoi)apps/tests/phase189_atoi_mini.hako
B.3 Design Principles
- Fail-Fast: Reject Complex patterns explicitly
- Whitelist Control: Only safe patterns allowed
- Type Safety: Integer-only for NumberAccumulation
- Name Agnostic: No name-based lowering
- SSOT: LoopHeader PHI as single source of truth
Revision History
- 2025-12-09: Initial design (Section 1-12)
- 2025-12-09: Phase 190-impl 完了
- Phase 190-impl-A: LoopUpdateAnalyzer に NumberAccumulation 検出実装
- Phase 190-impl-B: CarrierUpdateLowerer で 2-instruction emission 実装
- Phase 190-impl-C: Pattern2 can_lower ホワイトリスト更新
- Phase 190-impl-D: E2E 検証成功 + PHI 配線修正
- バグ発見: body-local と carrier の ValueId 衝突問題
- 修正:
body_local_start_offset = env.len() + carrier_info.carriers.len()で安全な ValueId 空間分割 - E2E 結果:
phase190_atoi_impl.hako→ 12 ✅、phase190_parse_number_impl.hako→ 123 ✅ - 制約: body-local 変数 assignment は JoinIR 未対応(Phase 186 残タスク)
- ExitLine contract Verifier 追加(
#[cfg(debug_assertions)])