feat(joinir): Phase 90 P0 - ParseStringComposite pattern

## Composite Pattern
- Continue(escape i+=2) + EarlyReturn(close quote)
- parse_string_composite_pattern.rs (50行、continue_return 再利用)
- 89% コード削減(450行→50行)

## Shape Detection
- BinOp Add const 2 検出(escape 特徴)
- LoopStepInspector 活用(Phase 89 リファクタ成果)

## SSOT Integration
- dev_fixtures.rs に登録
- StepCalculator 再利用(Phase 89-2 成果)

## Tests
- +2 tests (vm_bridge + 期待値 n=10→acc=5)
- normalized_dev: 61→63 passed
- lib: 993 passed (回帰なし)

Impact:
- Reuse over Duplication 実践
- Phase 89 リファクタ成果の完全活用
- 箱化原則 5/5 遵守

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-14 03:36:45 +09:00
parent 70ceec1e3e
commit 45add0f5d3
12 changed files with 353 additions and 2 deletions

View File

@ -29,6 +29,9 @@ pub enum ShapeCapabilityKind {
/// P4 Continue + Early Return family (Phase 89)
P4ContinueEarlyReturn,
/// Composite Parse String (continue + early return + variable step) (Phase 90)
CompositeParseString,
/// Selfhost P2 core (token scan)
SelfhostP2Core,
@ -90,6 +93,8 @@ pub enum NormalizedDevShape {
SelfhostDetectFormatP3,
// Phase 89: Continue + Early Return pattern (dev-only)
PatternContinueReturnMinimal,
// Phase 90: Parse String Composite pattern (dev-only: continue + early return + variable step)
ParseStringCompositeMinimal,
}
type Detector = fn(&JoinModule) -> bool;
@ -182,6 +187,11 @@ const SHAPE_DETECTORS: &[(NormalizedDevShape, Detector)] = &[
NormalizedDevShape::PatternContinueReturnMinimal,
detectors::is_pattern_continue_return_minimal,
),
// Phase 90: Parse String Composite pattern
(
NormalizedDevShape::ParseStringCompositeMinimal,
detectors::is_parse_string_composite_minimal,
),
];
/// direct ブリッジで扱う shapedev 限定)。
@ -226,6 +236,8 @@ pub fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability {
SelfhostDetectFormatP3 => SelfhostP3IfSum,
// Phase 89: Continue + Early Return pattern (dev-only, dedicated capability)
PatternContinueReturnMinimal => P4ContinueEarlyReturn,
// Phase 90: Parse String Composite pattern (dev-only, dedicated capability)
ParseStringCompositeMinimal => CompositeParseString,
};
ShapeCapability::new(kind)
@ -276,6 +288,7 @@ pub fn is_p2_core_capability(cap: &ShapeCapability) -> bool {
| P3IfSum
| P4ContinueSkipWs
| P4ContinueEarlyReturn
| CompositeParseString
| SelfhostP2Core
| SelfhostP3IfSum
)
@ -942,6 +955,58 @@ mod detectors {
&& k_exit_jumps_count >= 2 // At least 2: loop break + early return
}
/// Phase 90: Check if module matches Parse String Composite pattern
///
/// Structural characteristics:
/// - 3 functions (main, loop_step, k_exit)
/// - Has Select instruction (continue's core)
/// - Has TWO or more conditional Jumps to k_exit (loop break + early return)
/// - Has Compare instruction
/// - Has tail call (loop back)
/// - Has variable step increment (distinguishing feature from ContinueReturn)
///
/// Distinguishing from ContinueReturn:
/// - ParseStringComposite has i+=2 in continue branch (escape character handling)
/// - ContinueReturn has i+=1 in continue branch
/// - Detection: Check for BinOp Add with const value 2 in loop body
pub(crate) fn is_parse_string_composite_minimal(module: &JoinModule) -> bool {
// Must match basic Continue + Return structure first
if !is_pattern_continue_return_minimal(module) {
return false;
}
// Find loop_step function
let loop_step = match find_loop_step(module) {
Some(f) => f,
None => return false,
};
// Additional check: Must have BinOp Add with const value 2 (escape handling)
// This distinguishes ParseStringComposite from generic ContinueReturn
let has_variable_step = loop_step.body.iter().any(|inst| match inst {
JoinInst::Compute(mir_inst) => match mir_inst {
crate::mir::join_ir::MirLikeInst::BinOp { op, rhs, .. } => {
// Check if it's Add operation
if *op != crate::mir::join_ir::BinOpKind::Add {
return false;
}
// Check if rhs is a const value 2 (indicating i+=2 for escape)
// We need to check if rhs points to a Const instruction with value 2
loop_step.body.iter().any(|other_inst| match other_inst {
JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { dst, value }) => {
dst == rhs && matches!(value, crate::mir::join_ir::ConstValue::Integer(2))
}
_ => false,
})
}
_ => false,
},
_ => false,
});
has_variable_step
}
pub(super) fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> {
module
.functions