Skip to main content

pjson_rs/parser/
mod.rs

1//! High-performance JSON parsing module with hybrid approach
2//!
3//! This module provides both SIMD-optimized parsing and serde fallback,
4//! allowing rapid MVP development while building towards maximum performance.
5
6#[cfg(feature = "partial-parse")]
7pub mod partial;
8
9#[cfg(feature = "partial-parse")]
10pub use partial::{
11    JiterConfig, JiterPartialParser, ParseDiagnostic, PartialJsonParser, PartialParseResult,
12    StreamingHint,
13};
14
15pub mod aligned_alloc;
16pub mod buffer_pool;
17pub mod scanner;
18pub mod simd;
19pub mod simd_zero_copy;
20pub mod simple;
21pub mod sonic;
22pub mod value;
23pub mod zero_copy;
24
25pub use aligned_alloc::{AlignedAllocator, aligned_allocator};
26pub use buffer_pool::{
27    BufferPool, BufferSize, PoolConfig, PooledBuffer, SimdType, global_buffer_pool,
28};
29pub use scanner::{JsonScanner, ScanResult, StringLocation};
30pub use simd_zero_copy::{
31    SimdParseResult, SimdParsingStats, SimdZeroCopyConfig, SimdZeroCopyParser,
32};
33pub use simple::{ParseConfig, ParseStats, SimpleParser};
34pub use sonic::{SonicConfig, SonicParser};
35pub use value::{JsonValue, LazyArray, LazyObject};
36pub use zero_copy::{IncrementalParser, LazyJsonValue, LazyParser, MemoryUsage, ZeroCopyParser};
37
38use crate::{Result, SemanticMeta};
39
40/// High-performance hybrid parser with SIMD acceleration
41pub struct Parser {
42    sonic: SonicParser,
43    simple: SimpleParser,
44    use_sonic: bool,
45}
46
47impl Parser {
48    /// Create new parser with default configuration.
49    ///
50    /// Selects the sonic-rs SIMD backend when any `simd-*` Cargo feature is
51    /// enabled (which is the default via `simd-auto`). Without any `simd-*`
52    /// feature, falls back to the portable serde-based parser.
53    pub fn new() -> Self {
54        Self {
55            sonic: SonicParser::new(),
56            simple: SimpleParser::new(),
57            use_sonic: cfg!(pjs_simd),
58        }
59    }
60
61    /// Create parser with custom configuration
62    pub fn with_config(config: ParseConfig) -> Self {
63        let sonic_config = SonicConfig {
64            detect_semantics: config.detect_semantics,
65            max_input_size: config.max_size_mb * 1024 * 1024,
66        };
67
68        Self {
69            sonic: SonicParser::with_config(sonic_config),
70            simple: SimpleParser::with_config(config),
71            use_sonic: cfg!(pjs_simd),
72        }
73    }
74
75    /// Create parser with serde fallback (for compatibility)
76    pub fn with_serde_fallback() -> Self {
77        Self {
78            sonic: SonicParser::new(),
79            simple: SimpleParser::new(),
80            use_sonic: false,
81        }
82    }
83
84    /// Create parser optimized for zero-copy performance
85    pub fn zero_copy_optimized() -> Self {
86        Self {
87            sonic: SonicParser::new(),
88            simple: SimpleParser::new(),
89            use_sonic: false,
90        }
91    }
92
93    /// Parse JSON bytes into PJS Frame using optimal strategy
94    pub fn parse(&self, input: &[u8]) -> Result<crate::Frame> {
95        if self.use_sonic {
96            // Try sonic-rs first for performance
97            match self.sonic.parse(input) {
98                Ok(frame) => Ok(frame),
99                Err(_) => {
100                    // Fallback to serde for compatibility
101                    self.simple.parse(input)
102                }
103            }
104        } else {
105            self.simple.parse(input)
106        }
107    }
108
109    /// Parse with explicit semantic hints
110    pub fn parse_with_semantics(
111        &self,
112        input: &[u8],
113        semantics: &SemanticMeta,
114    ) -> Result<crate::Frame> {
115        if self.use_sonic {
116            // Sonic parser doesn't support explicit semantics yet
117            // Use simple parser for this case
118            self.simple.parse_with_semantics(input, semantics)
119        } else {
120            self.simple.parse_with_semantics(input, semantics)
121        }
122    }
123
124    /// Parse the largest valid JSON prefix from `input`, tolerating truncation.
125    ///
126    /// Delegates to [`JiterPartialParser`] with default configuration.
127    ///
128    /// Returns `Ok(None)` when `consumed == 0` (no structurally complete prefix
129    /// could be recovered — e.g. input `[` or `-`). Returns `Ok(Some(_))` when
130    /// at least one byte was committed.
131    ///
132    /// # Errors
133    ///
134    /// Returns [`crate::error::Error::InvalidJson`] for syntactically invalid
135    /// input (e.g. stray `}`). Returns [`crate::error::Error::Buffer`] when the
136    /// input exceeds the default `max_input_size` (100 MiB).
137    ///
138    /// # Examples
139    ///
140    /// ```rust,no_run
141    /// use pjson_rs::parser::Parser;
142    ///
143    /// let parser = Parser::new();
144    /// let result = parser.parse_partial(b"{\"a\":1,\"b\":[2,3").unwrap();
145    /// assert!(result.is_some());
146    /// ```
147    #[cfg(feature = "partial-parse")]
148    pub fn parse_partial(&self, input: &[u8]) -> crate::Result<Option<PartialParseResult>> {
149        use partial::PartialJsonParser as _;
150        let result = JiterPartialParser::default().parse_partial(input)?;
151        if result.consumed == 0 {
152            Ok(None)
153        } else {
154            Ok(Some(result))
155        }
156    }
157
158    /// Get parser statistics
159    pub fn stats(&self) -> ParseStats {
160        if self.use_sonic {
161            let sonic_stats = self.sonic.get_stats();
162            ParseStats {
163                total_parses: sonic_stats.total_parses,
164                semantic_detections: sonic_stats.sonic_successes,
165                avg_parse_time_ms: sonic_stats.avg_parse_time_ns as f64 / 1_000_000.0,
166            }
167        } else {
168            self.simple.stats()
169        }
170    }
171}
172
173impl Default for Parser {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179/// JSON value types for initial classification
180#[derive(Debug, Clone, Copy, PartialEq)]
181pub enum ValueType {
182    Object,
183    Array,
184    String,
185    Number,
186    Boolean,
187    Null,
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_parser_creation() {
196        let parser = Parser::new();
197        assert_eq!(parser.stats().total_parses, 0);
198    }
199
200    #[test]
201    fn test_simple_parsing() {
202        let parser = Parser::new();
203        let input = br#"{"hello": "world"}"#;
204        let result = parser.parse(input);
205        assert!(result.is_ok());
206
207        let frame = result.unwrap();
208        // Simple JSON may not have semantic metadata
209        assert_eq!(frame.payload.len(), input.len());
210    }
211
212    #[test]
213    fn test_numeric_array_parsing() {
214        let parser = Parser::new();
215        let input = b"[1.0, 2.0, 3.0, 4.0]";
216        let result = parser.parse(input);
217        assert!(result.is_ok());
218    }
219
220    #[test]
221    fn test_semantic_parsing() {
222        let parser = Parser::new();
223        let input = b"[1, 2, 3, 4]";
224
225        let semantics = crate::SemanticMeta::new(crate::semantic::SemanticType::NumericArray {
226            dtype: crate::semantic::NumericDType::I32,
227            length: Some(4),
228        });
229
230        let result = parser.parse_with_semantics(input, &semantics);
231        assert!(result.is_ok());
232    }
233
234    #[test]
235    fn test_custom_config() {
236        let config = ParseConfig {
237            detect_semantics: false,
238            max_size_mb: 50,
239            stream_large_arrays: false,
240            stream_threshold: 500,
241        };
242
243        let parser = Parser::with_config(config);
244        let input = br#"{"test": "data"}"#;
245        let result = parser.parse(input);
246        assert!(result.is_ok());
247    }
248}