Major refactoring of JoinIR normalization pipeline: Key changes: - Structured→Normalized→MIR(direct) pipeline established - ShapeGuard enhanced with Pattern2 loop validation - dev_env.rs: New development fixtures and env control - fixtures.rs: jsonparser_parse_number_real fixture - normalized_bridge/direct.rs: Direct MIR generation from Normalized - pattern2_step_schedule.rs: Extracted step scheduling logic Files changed: - normalized.rs: Enhanced NormalizedJoinModule with DevEnv support - shape_guard.rs: Pattern2-specific validation (+300 lines) - normalized_bridge.rs: Unified bridge with direct path - loop_with_break_minimal.rs: Integrated step scheduling - Deleted: step_schedule.rs (moved to pattern2_step_schedule.rs) New files: - param_guess.rs: Loop parameter inference - pattern2_step_schedule.rs: Step scheduling for Pattern2 - phase43-norm-canon-p2-mid.md: Design doc Tests: 937/937 PASS (+6 from baseline 931) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.0 KiB
Phase 224-E: DigitPos Condition Normalizer
Status: Active(DigitPos 条件正規化ラインの設計メモ / 実装ガイド)
Scope: digit_pos → is_digit_pos Carrier / ConditionEnv / ExprLowerer の正規化経路の SSOT ドキュメントだよ。Phase 26‑H / 34 系の JsonParser _parse_number / _atoi でもこの設計を前提にしている。
Problem Statement
Background
Phase 228-8 successfully implemented the DigitPos pattern promotion:
digit_pos: i32→is_digit_pos: boolcarrier- CarrierRole::ConditionOnly + CarrierInit::BoolConst(false)
- ConditionEnv alias:
digit_pos → is_digit_pos
Current Error
Type error: unsupported compare Lt on Bool(false) and Integer(0)
Root Cause: The break condition AST still contains digit_pos < 0, which after alias resolution becomes Bool(is_digit_pos) < Integer(0), causing a type mismatch.
Why alias isn't enough:
- Alias only renames the variable reference
- The comparison operator and integer literal remain unchanged
- Need to transform the entire expression structure
Solution
Transformation
Transform the break condition AST from integer comparison to boolean negation:
Before: digit_pos < 0
BinaryOp {
operator: Lt,
left: Var("digit_pos"),
right: Const(0)
}
After: !is_digit_pos
UnaryOp {
operator: Not,
expr: Var("is_digit_pos")
}
Semantic Equivalence
digit_pos < 0means "indexOf() didn't find the character" (returns -1)!is_digit_posmeans "character is not a digit" (bool carrier is false)- Both express the same condition: "break when character not found/matched"
Design
Box: DigitPosConditionNormalizer
Responsibility: Transform digit_pos comparison patterns to boolean carrier expressions
API:
pub struct DigitPosConditionNormalizer;
impl DigitPosConditionNormalizer {
/// Normalize digit_pos condition AST
///
/// Transforms: `digit_pos < 0` → `!is_digit_pos`
///
/// # Arguments
/// * `cond` - Break/continue condition AST
/// * `promoted_var` - Original variable name (e.g., "digit_pos")
/// * `carrier_name` - Promoted carrier name (e.g., "is_digit_pos")
///
/// # Returns
/// Normalized AST (or original if pattern doesn't match)
pub fn normalize(
cond: &ASTNode,
promoted_var: &str,
carrier_name: &str,
) -> ASTNode;
}
Pattern Matching Logic
Match Pattern: <var> < 0 where:
- Operator is
Lt(Less than) - Left operand is
Var(promoted_var) - Right operand is
Const(0)
Transformation: → UnaryOp { op: Not, expr: Var(carrier_name) }
Non-Match Behavior: Return original AST unchanged (Fail-Safe)
Integration Point
Location: src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs
Integration Steps:
- After
LoopBodyCondPromoter::try_promote_for_condition()succeeds - Extract
promoted_varandcarrier_namefrom promotion result - Apply
DigitPosConditionNormalizer::normalize()to break condition AST - Use normalized AST in subsequent condition lowering
Code Position (around line 331):
ConditionPromotionResult::Promoted {
carrier_info: promoted_carrier,
promoted_var,
carrier_name,
} => {
// ... existing merge logic ...
// Phase 224-E: Normalize digit_pos condition before lowering
let normalized_break_condition = DigitPosConditionNormalizer::normalize(
&break_condition_node,
&promoted_var,
&carrier_name,
);
// Use normalized_break_condition in subsequent processing
}
Testing Strategy
Unit Tests (3-4 tests)
- Happy path:
digit_pos < 0→!is_digit_pos - Wrong operator:
digit_pos >= 0→ No change - Wrong variable:
other_var < 0→ No change - Wrong constant:
digit_pos < 10→ No change
E2E Test
Test File: apps/tests/phase2235_p2_digit_pos_min.hako
このテストは digit_pos 昇格と型整合性・SSA 安定性を確認するインフラ用途で、数値としての戻り値の意味論は今後の JsonParser 本体フェーズで定義する予定だよ。
Success Criteria:
- No type error ("unsupported compare Lt on Bool and Integer")
- Break condition correctly evaluates
- Loop exits when digit not found
Debug Command:
NYASH_JOINIR_DEBUG=1 ./target/release/hakorune apps/tests/phase2235_p2_digit_pos_min.hako
Regression Tests
- Verify existing Trim tests still pass
- Verify skip_whitespace tests still pass
- Check that 877/884 test count is maintained
Success Criteria
- ✅
cargo build --releasesucceeds - ✅ Unit tests (3-4) pass
- ✅ Type error eliminated from phase2235_p2_digit_pos_min.hako
- ✅ Existing 877/884 tests remain passing
- ✅ No regressions in Trim/skip_whitespace patterns
Box-First Principles
Single Responsibility
- DigitPosConditionNormalizer only handles AST transformation
- No side effects, no state mutation
- Pure pattern matching function
Fail-Safe Design
- Non-matching patterns returned unchanged
- No panics, no errors
- Conservative transformation strategy
Boundary Clarity
- Input: AST + promoted_var + carrier_name
- Output: Transformed or original AST
- Clear interface contract
Future Extensions
Pattern 4 Support: When Pattern 4 (continue) needs similar normalization, the same box can be reused with continue condition AST.
Other Comparison Operators: Currently handles < 0. Could extend to:
>= 0→is_digit_pos(no NOT)!= -1→is_digit_pos== -1→!is_digit_pos
Multiple Conditions: For complex boolean expressions with multiple promoted variables, apply normalization recursively.
References
- Phase 228-8: DigitPos promotion implementation
- Phase 229: Dynamic condition variable resolution
- CarrierInfo:
src/mir/join_ir/lowering/carrier_info.rs - ConditionEnv:
src/mir/join_ir/lowering/condition_env.rsStatus: Active
Scope: digitpos condition 正規化(ExprLowerer ライン)