Skip to content

Complete CLI-to-Crate Unification: Phase 4 (S/H/E Frame Parsers) #13

@coderabbitai

Description

@coderabbitai

Context

Follow-up to PR #12 which successfully completed issue #11 (Phases 1-3). This issue addresses remaining code duplication discovered during PR review.

Reference: PR #12 comment thread


Summary

PR #12 eliminated 471 net lines of duplication by unifying helpers, parse_frame_data, and BBLDataStream. However, ~202 additional lines of frame parser duplication remain in src/main.rs:

  1. Dead code: parse_i_frame (31 lines) — never called
  2. Duplicated: parse_s_frame (63 lines) — needs verification (has TAG2_3S32 handling)
  3. Duplicated: parse_h_frame (43 lines) — nearly identical to crate version
  4. Duplicated: parse_e_frame (65 lines) — crate version is MORE comprehensive

Detailed Analysis

1. Dead Code: parse_i_frame (Lines 2299-2329)

Status: 🔴 Dead code — never called

Evidence:

# Function defined but never invoked
$ rg 'parse_i_frame\(' src/main.rs
2299:fn parse_i_frame(  # definition only, no call sites

I-frames use the crate's parse_frame_data directly (line 1750):

'I' => {
    // ... existing setup ...
    parse_frame_data(
        &mut stream,
        &header.i_frame_def,
        &mut i_frame_values,
        // ...
    )?;
}

Action: Delete lines 2299-2329.


2. Duplication: parse_s_frame (Lines 2330-2392)

Status: ⚠️ Verification required — implementations differ

Key Difference:

  • CLI version: Handles ENCODING_TAG2_3S32 (reads 3 fields at once)
  • Crate version (src/parser/frame.rs:600): Does NOT handle TAG2_3S32

CLI implementation snippet:

ENCODING_TAG2_3S32 => {
    let mut values = [0i32; 8];
    stream.read_tag2_3s32(&mut values)?;
    for j in 0..3 {
        if field_index + j < frame_def.fields.len() {
            let current_field = &frame_def.fields[field_index + j];
            data.insert(current_field.name.clone(), values[j]);
        }
    }
    field_index += 3;
}

Crate implementation (src/parser/frame.rs:600):

fn parse_s_frame(...) -> Result<HashMap<String, i32>> {
    let mut data = HashMap::new();
    for field in &frame_def.fields {
        let value = match field.encoding {
            ENCODING_SIGNED_VB => stream.read_signed_vb()?,
            ENCODING_UNSIGNED_VB => stream.read_unsigned_vb()? as i32,
            ENCODING_NEG_14BIT => stream.read_neg_14bit()?,
            ENCODING_NULL => 0,
            _ => { /* fallback */ }
        };
        data.insert(field.name.clone(), value);
    }
    Ok(data)
}

Migration Strategy:

Option A (Safer): Enhance crate's parse_s_frame first

  1. Add TAG2_3S32 handling to src/parser/frame.rs:parse_s_frame
  2. Match CLI's field iteration logic (field_index increment by group size)
  3. Add test to verify S-frame parsing with TAG2_3S32 encoding
  4. Replace CLI call site (line 1946) with crate import
  5. Delete CLI implementation (lines 2330-2392)

Option B (Riskier): Verify TAG2_3S32 is never used in S-frames

  1. Analyze test logs to confirm S-frames never use TAG2_3S32
  2. If confirmed unused, directly replace with crate version
  3. Add regression test to catch TAG2_3S32 in S-frames if it appears

Recommended: Option A — preserving existing behavior is safer.


3. Duplication: parse_h_frame (Lines 2393-2435)

Status: ✅ Safe to replace — implementations are equivalent

Comparison:

Aspect CLI (main.rs:2393) Crate (gps.rs:22)
Structure Identical Identical
ENCODING_NEG_14BIT -(sign_extend_14bit(...)) read_neg_14bit() (same result)
Error handling unwrap_or(0) fallback unwrap_or(0) fallback
Debug output Same format Same format

Migration:

  1. Update src/main.rs line 1979 call site:
    // Before
    if let Ok(data) = parse_h_frame(&mut stream, &header.h_frame_def, debug) {
    
    // After
    use bbl_parser::parser::gps::parse_h_frame;
    if let Ok(data) = parse_h_frame(&mut stream, &header.h_frame_def, debug) {
  2. Delete CLI implementation (lines 2393-2435)

4. Duplication: parse_e_frame (Lines 2436-2500)

Status: ✅ Safe to replace — crate version is superior

Why Crate Version is Better:

  • Handles additional event types: 10, 11, 12, 13, 14
  • Includes parse_inflight_adjustment helper (DRY principle)
  • More comprehensive event type coverage
  • Better documented with module-level comments

Event Types Comparison:

Event Type CLI Support Crate Support Notes
0-6 Identical
10-12 UNUSED autotune variants
13 Inflight adjustment (alternate numbering)
14 Logging resume (newer numbering)
30, 255 Identical

Migration:

  1. Update src/main.rs line 2123 call site:
    // Before
    if let Ok(mut event_frame) = parse_e_frame(&mut stream, debug) {
    
    // After
    use bbl_parser::parser::event::parse_e_frame;
    if let Ok(mut event_frame) = parse_e_frame(&mut stream, debug) {
  2. Delete CLI implementation (lines 2436-2500)

Step-by-Step Migration Plan

Phase 4a: Remove Dead Code (Low Risk)

Goal: Delete unused parse_i_frame

# 1. Verify function is never called
rg 'parse_i_frame\(' src/main.rs  # Should only show definition

# 2. Delete lines 2299-2329
sed -i '2299,2329d' src/main.rs

# 3. Validate
cargo build
cargo test

Success Criteria: Build passes, all 50 tests pass


Phase 4b: Replace H-frame Parser (Low Risk)

Goal: Use crate's parse_h_frame

# 1. Add import to main.rs (near top with other bbl_parser imports)
# Add: use bbl_parser::parser::gps::parse_h_frame;

# 2. Verify call site uses imported version (line ~1979)
rg -A 3 'parse_h_frame' src/main.rs

# 3. Delete CLI implementation (lines 2393-2435)
# Note: Line numbers shift after previous deletion, recalculate

# 4. Validate
cargo build
cargo test --all

# 5. A/B test: Compare CSV output
cargo run -- --input tests/sample.bbl --output /tmp/before.csv
# (after changes)
cargo run -- --input tests/sample.bbl --output /tmp/after.csv
diff /tmp/before.csv /tmp/after.csv  # Should be empty

Success Criteria:

  • Build passes
  • All tests pass
  • CSV outputs identical

Phase 4c: Replace E-frame Parser (Low Risk)

Goal: Use crate's superior parse_e_frame

# 1. Add import to main.rs
# Add: use bbl_parser::parser::event::parse_e_frame;

# 2. Verify call site (line ~2123)
rg -A 3 'parse_e_frame' src/main.rs

# 3. Delete CLI implementation (lines 2436-2500, adjusted for previous deletions)

# 4. Validate
cargo build
cargo test --all

# 5. Verify event parsing works (especially event types 10-14)
cargo run -- --input tests/sample_with_events.bbl --output /tmp/events.csv
# Check for any new event types captured

Success Criteria:

  • Build passes
  • All tests pass
  • Event frames parsed correctly (check stats output)

Phase 4d: Replace S-frame Parser (⚠️ Higher Risk)

Goal: Unify parse_s_frame after verifying TAG2_3S32 handling

Step 1: Investigate TAG2_3S32 Usage

# Check if any test logs use TAG2_3S32 in S-frames
for f in tests/*.bbl; do
  echo "Checking $f"
  cargo run -- --input "$f" --debug 2>&1 | grep -i 'S-frame.*TAG2_3S32'
done

# Check frame definitions
rg 'TAG2_3S32' src/

Step 2a: If TAG2_3S32 is used (Safer Path)

# 1. Enhance crate's parse_s_frame in src/parser/frame.rs
#    Add TAG2_3S32 handling matching CLI implementation:
// In src/parser/frame.rs:parse_s_frame, change to:
fn parse_s_frame(...) -> Result<HashMap<String, i32>> {
    let mut data = HashMap::new();
    let mut field_index = 0;
    
    while field_index < frame_def.fields.len() {
        let field = &frame_def.fields[field_index];
        
        match field.encoding {
            ENCODING_SIGNED_VB => { /* existing */ }
            ENCODING_UNSIGNED_VB => { /* existing */ }
            ENCODING_NEG_14BIT => { /* existing */ }
            ENCODING_TAG2_3S32 => {
                // NEW: Handle 3 fields at once
                let mut values = [0i32; 8];
                stream.read_tag2_3s32(&mut values)?;
                for j in 0..3 {
                    if field_index + j < frame_def.fields.len() {
                        let current_field = &frame_def.fields[field_index + j];
                        data.insert(current_field.name.clone(), values[j]);
                    }
                }
                field_index += 3;
                continue;
            }
            ENCODING_NULL => { /* existing */ }
            _ => { /* existing fallback */ }
        };
        
        data.insert(field.name.clone(), value);
        field_index += 1;
    }
    
    Ok(data)
}
# 2. Test enhanced crate function
cargo test

# 3. Replace CLI call site (line ~1946)
# Add import: use bbl_parser::parser::frame::parse_s_frame;

# 4. Delete CLI implementation

# 5. Validate with all test logs
for f in tests/*.bbl; do
  cargo run -- --input "$f" --output /tmp/test_output.csv
  # Compare with known-good output if available
done

Step 2b: If TAG2_3S32 is NOT used (Simpler Path)

# 1. Add import to main.rs
# Add: use bbl_parser::parser::frame::parse_s_frame;

# 2. Replace call site (line ~1946)

# 3. Delete CLI implementation

# 4. Add regression test to catch TAG2_3S32 if it appears:
#    Add test that fails if TAG2_3S32 encoding is found in S-frames

Success Criteria:

  • Build passes
  • All 50 tests pass
  • CSV outputs identical across all test logs
  • No S-frame parsing errors in debug output

Validation Checklist

After completing all phases:

  • cargo build — no errors
  • cargo test --all — all 50 tests pass
  • cargo clippy — zero warnings
  • rg 'fn parse_[ishe]_frame' src/main.rs — returns nothing
  • tokei src/main.rs — verify ~200 lines removed
  • A/B test all test logs — CSV outputs identical
  • Visual inspection: rg 'parse_[ISPHE]' src/main.rs — only crate imports remain

Rollback Plan

If any phase fails validation:

# Discard changes and return to current state
git checkout src/main.rs
git checkout src/parser/frame.rs  # if modified
cargo test --all  # Verify stable state

Expected Benefits

  • Code reduction: ~202 lines removed from CLI
  • Maintenance: Single source of truth for frame parsing
  • Functionality: Gain event types 10-14 in E-frame parsing
  • Consistency: All frame parsing uses crate implementations

Dependencies


Notes

  • Phases 4a-c are low risk (straightforward replacements)
  • Phase 4d (S-frames) requires verification due to TAG2_3S32 handling difference
  • Recommend committing each phase separately for easy rollback
  • All changes preserve behavior (no algorithm modifications)

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions