json-extractor 0.1.0

High-performance two-stage JSON fragment scanner with SIMD acceleration
Documentation
//! Stage 2: Fragment Extraction from Structural Indices
//!
//! This module implements the second stage of the two-stage JSON parsing pipeline.
//! It walks through the structural character positions identified in Stage 1,
//! matching brackets to identify complete JSON fragments.
//!
//! # Architecture
//!
//! - Walk structural indices (not raw bytes)
//! - Use simple depth counter for bracket matching
//! - Identify fragment boundaries from opening/closing brackets
//! - Build Fragment objects with positions and status
//!
//! # Performance
//!
//! Expected throughput: 3-8 GiB/s on fragment extraction

use crate::{ErrorKind, Fragment, FragmentStatus};

/// Extract fragments from structural indices (reusable buffer) with precomputed metadata
///
/// # Arguments
/// * `data` - Complete JSON document bytes
/// * `structural_indices` - Positions of structural characters from Stage 1
/// * `bracket_pairs` - Precomputed bracket pairings (opening_pos, closing_pos)
/// * `string_ranges` - Precomputed string boundaries (start_quote, end_quote)
/// * `output` - Mutable reference to fragments buffer to reuse
#[inline]
pub fn extract_fragments(
    data: &[u8],
    structural_indices: &[u32],
    bracket_pairs: &[(u32, u32)],
    output: &mut Vec<Fragment>,
) {
    // Note: No need to pre-allocate capacity - clear() preserves existing capacity
    // and Vec will grow as needed. Pre-allocation can cause unbounded growth.

    let mut idx = 0;
    while idx < structural_indices.len() {
        let pos = structural_indices[idx];
        let byte = data[pos as usize];

        // Look for fragment start: { or [
        match byte {
            b'{' | b'[' => {
                // Binary search for matching bracket pair (bracket_pairs is sorted by opening position)
                let close_pos_opt = bracket_pairs
                    .binary_search_by_key(&pos, |(open, _close)| *open)
                    .ok()
                    .map(|pair_idx| bracket_pairs[pair_idx].1);

                // Check if we found a matching bracket pair
                if let Some(close_pos) = close_pos_opt {
                    // Validate fragment
                    let status = FragmentStatus::Complete;

                    output.push(Fragment {
                        start: pos as usize,
                        length: (close_pos - pos + 1) as usize,
                        status,
                    });

                    // Skip to position after closing bracket
                    // For typical JSON with small fragments, linear search is faster than binary search
                    idx += 1;
                    while idx < structural_indices.len() && structural_indices[idx] <= close_pos {
                        idx += 1;
                    }
                } else {
                    // No closing bracket found - incomplete fragment
                    // Scan ahead to find where this incomplete fragment ends
                    let fragment_start = pos;
                    let mut scan_idx = idx + 1;
                    let mut fragment_end = data.len() as u32;
                    // Track braces and brackets separately to handle mismatched types
                    let mut unclosed_braces = if byte == b'{' { 1 } else { 0 };
                    let mut unclosed_brackets = if byte == b'[' { 1 } else { 0 };

                    // Scan ahead to find where this incomplete fragment should end
                    while scan_idx < structural_indices.len() {
                        let scan_pos = structural_indices[scan_idx];
                        let scan_byte = data[scan_pos as usize];

                        match scan_byte {
                            b'{' | b'[' => {
                                // Check if this opening bracket has a pair using binary search
                                let has_pair = bracket_pairs
                                    .binary_search_by_key(&scan_pos, |(open, _close)| *open)
                                    .is_ok();

                                if has_pair {
                                    // Found a paired opening bracket
                                    // This likely starts a new complete fragment
                                    fragment_end = scan_pos;
                                    break;
                                } else {
                                    // Another unpaired bracket - increment appropriate counter
                                    if scan_byte == b'{' {
                                        unclosed_braces += 1;
                                    } else {
                                        unclosed_brackets += 1;
                                    }
                                }
                            }
                            b'}' => {
                                // Closing brace - only decrement if we have unclosed braces
                                if unclosed_braces > 0 {
                                    unclosed_braces -= 1;
                                    if unclosed_braces == 0 && unclosed_brackets == 0 {
                                        // All brackets closed
                                        fragment_end = scan_pos + 1;
                                        break;
                                    }
                                }
                            }
                            b']' => {
                                // Closing bracket - only decrement if we have unclosed brackets
                                if unclosed_brackets > 0 {
                                    unclosed_brackets -= 1;
                                    if unclosed_braces == 0 && unclosed_brackets == 0 {
                                        // All brackets closed
                                        fragment_end = scan_pos + 1;
                                        break;
                                    }
                                }
                            }
                            _ => {}
                        }
                        scan_idx += 1;
                    }

                    output.push(Fragment {
                        start: fragment_start as usize,
                        length: (fragment_end - fragment_start) as usize,
                        status: FragmentStatus::Incomplete(if byte == b'{' {
                            ErrorKind::MissingClosingBrace
                        } else {
                            ErrorKind::MissingClosingBracket
                        }),
                    });

                    // Skip to the end of this incomplete fragment
                    idx = scan_idx;
                }
            }
            _ => {
                // Not a fragment start, skip
                idx += 1;
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_extract_simple_object() {
        let json = br#"{"key":"value"}"#;
        let indices = vec![0, 1, 6, 7, 14]; // { " : " }
        let bracket_pairs = vec![(0, 14)]; // { at 0, } at 14
        // let string_ranges = vec![(1, 6), (7, 14)]; // Two strings: "key" and "value"

        let mut fragments = vec![];
        extract_fragments(
            json,
            &indices,
            &bracket_pairs,
            // &string_ranges,
            &mut fragments,
        );

        assert_eq!(fragments.len(), 1);
        assert_eq!(fragments[0].start, 0);
        assert_eq!(fragments[0].length, 15);
        assert!(fragments[0].is_complete());
    }

    #[test]
    fn test_extract_simple_array() {
        let json = br#"[1,2,3]"#;
        let indices = vec![0, 2, 4, 6]; // [ , , ]
        let bracket_pairs = vec![(0, 6)]; // [ at 0, ] at 6
        // let string_ranges = vec![]; // No strings in this array

        let mut fragments = vec![];
        extract_fragments(
            json,
            &indices,
            &bracket_pairs,
            // &string_ranges,
            &mut fragments,
        );

        assert_eq!(fragments.len(), 1);
        assert_eq!(fragments[0].start, 0);
        assert_eq!(fragments[0].length, 7);
        assert!(fragments[0].is_complete());
    }
}