Skip to main content

serializer/
parser.rs

1//! Parser for DX Machine format
2//!
3//! Implements all DX features:
4//! - Schema-guided vacuum parsing
5//! - Alias system ($)
6//! - Prefix inheritance (^)
7//! - Vertical ditto (_)
8//! - Type hints (%i, %s, %f, %b, %x, %#)
9//! - Anchor references (@)
10//! - DX ∞: Base62 integers (%x), Auto-increment (%#)
11//!
12//! ## Thread Safety
13//!
14//! The [`Parser`] struct is **not** thread-safe (`Send` but not `Sync`) because it
15//! maintains mutable parsing state. However, the module-level [`parse()`] function
16//! is completely stateless and can be called concurrently from multiple threads:
17//!
18//! ```rust
19//! use std::thread;
20//! use serializer::parse;
21//!
22//! // Safe: each thread creates its own Parser internally
23//! let handles: Vec<_> = (0..4).map(|i| {
24//!     thread::spawn(move || {
25//!         let input = format!("key{}:value{}", i, i);
26//!         parse(input.as_bytes())
27//!     })
28//! }).collect();
29//!
30//! for handle in handles {
31//!     assert!(handle.join().unwrap().is_ok());
32//! }
33//! ```
34//!
35//! For parallel parsing, create a separate [`Parser`] instance per thread rather
36//! than sharing one instance.
37
38use crate::base62::decode_base62;
39use crate::error::{DxError, Result};
40use crate::schema::{Schema, TypeHint};
41use crate::tokenizer::{Token, Tokenizer};
42use crate::types::{DxArray, DxObject, DxTable, DxValue};
43use rustc_hash::FxHashMap;
44
45/// Parser state
46pub struct Parser<'a> {
47    tokenizer: Tokenizer<'a>,
48    /// Alias map ($c = context)
49    aliases: FxHashMap<String, String>,
50    /// Anchor storage (@1, @2, ...)
51    anchors: Vec<DxValue>,
52    /// Current prefix for inheritance (^)
53    prefix_stack: Vec<String>,
54    /// Schema registry for tables
55    schemas: FxHashMap<String, Schema>,
56    /// Auto-increment counters per table
57    auto_counters: FxHashMap<String, i64>,
58}
59
60impl<'a> Parser<'a> {
61    /// Create a parser over DX machine-format input bytes.
62    pub fn new(input: &'a [u8]) -> Self {
63        Self {
64            tokenizer: Tokenizer::new(input),
65            aliases: FxHashMap::default(),
66            anchors: Vec::new(),
67            auto_counters: FxHashMap::default(),
68            prefix_stack: Vec::new(),
69            schemas: FxHashMap::default(),
70        }
71    }
72
73    /// Parse the entire input
74    pub fn parse(&mut self) -> Result<DxValue> {
75        let mut root = DxObject::new();
76
77        loop {
78            self.tokenizer.skip_whitespace();
79            if self.tokenizer.is_eof() {
80                break;
81            }
82
83            let token = self.tokenizer.peek_token()?;
84            match token {
85                Token::Eof => break,
86                Token::Newline => {
87                    self.tokenizer.next_token()?;
88                    continue;
89                }
90                Token::Dollar => {
91                    // Could be alias definition ($c=context) or key reference ($c.task:value)
92                    // Need to look ahead to determine which
93                    if self.is_alias_definition()? {
94                        self.parse_alias()?;
95                    } else {
96                        // Key reference using alias
97                        let (key, value) = self.parse_key_value_with_alias()?;
98                        root.insert(key, value);
99                    }
100                }
101                Token::Ident(_) | Token::Caret => {
102                    // Key-value pair or table
103                    let (key, value) = self.parse_key_value()?;
104                    root.insert(key, value);
105                }
106                _ => {
107                    return Err(DxError::InvalidSyntax {
108                        pos: self.tokenizer.pos(),
109                        msg: format!("Unexpected token: {:?}", token),
110                    });
111                }
112            }
113        }
114
115        Ok(DxValue::Object(root))
116    }
117
118    /// Check if the current $ starts an alias definition (has = after alias name)
119    fn is_alias_definition(&mut self) -> Result<bool> {
120        // Save position
121        let saved_pos = self.tokenizer.pos();
122
123        // Consume $
124        self.tokenizer.next_token()?;
125
126        // Get alias name (may include dots like c.task)
127        let alias_token = self.tokenizer.next_token()?;
128        if !matches!(alias_token, Token::Ident(_)) {
129            // Reset and return false
130            self.tokenizer.reset_to(saved_pos);
131            return Ok(false);
132        }
133
134        // Check next token
135        self.tokenizer.skip_whitespace();
136        let next = self.tokenizer.peek_token()?;
137        let is_definition = matches!(next, Token::Equals);
138
139        // Reset position
140        self.tokenizer.reset_to(saved_pos);
141
142        Ok(is_definition)
143    }
144
145    /// Parse key-value pair that starts with $ (alias reference)
146    fn parse_key_value_with_alias(&mut self) -> Result<(String, DxValue)> {
147        // Consume $
148        self.tokenizer.next_token()?;
149
150        // Get the full identifier (may include dots like c.task)
151        let full_ident = match self.tokenizer.next_token()? {
152            Token::Ident(bytes) => std::str::from_utf8(bytes)?.to_string(),
153            _ => {
154                return Err(DxError::InvalidSyntax {
155                    pos: self.tokenizer.pos(),
156                    msg: "Expected alias name after $".to_string(),
157                });
158            }
159        };
160
161        // Split on first dot to get alias and suffix
162        let (alias, suffix) = if let Some(dot_pos) = full_ident.find('.') {
163            (&full_ident[..dot_pos], Some(&full_ident[dot_pos..]))
164        } else {
165            (full_ident.as_str(), None)
166        };
167
168        // Resolve alias
169        let resolved = self
170            .aliases
171            .get(alias)
172            .cloned()
173            .ok_or_else(|| DxError::UnknownAlias(alias.to_string()))?;
174
175        // Build full key
176        let key = if let Some(suffix) = suffix {
177            format!("{}{}", resolved, suffix)
178        } else {
179            resolved
180        };
181
182        // Read operator
183        self.tokenizer.skip_whitespace();
184        let operator = self.tokenizer.next_token()?;
185
186        let value = match operator {
187            Token::Colon => self.parse_value()?,
188            Token::Bang => DxValue::Bool(true),
189            Token::Void => DxValue::Null,
190            _ => {
191                return Err(DxError::InvalidSyntax {
192                    pos: self.tokenizer.pos(),
193                    msg: format!("Expected :, !, or ? after key, got {:?}", operator),
194                });
195            }
196        };
197
198        Ok((key, value))
199    }
200
201    /// Parse alias definition: $c=context
202    fn parse_alias(&mut self) -> Result<()> {
203        self.tokenizer.next_token()?; // consume $
204
205        let alias = match self.tokenizer.next_token()? {
206            Token::Ident(bytes) => std::str::from_utf8(bytes)?.to_string(),
207            _ => {
208                return Err(DxError::InvalidSyntax {
209                    pos: self.tokenizer.pos(),
210                    msg: "Expected alias name after $".to_string(),
211                });
212            }
213        };
214
215        // Expect =
216        if !matches!(self.tokenizer.next_token()?, Token::Equals) {
217            return Err(DxError::InvalidSyntax {
218                pos: self.tokenizer.pos(),
219                msg: "Expected = after alias".to_string(),
220            });
221        }
222
223        let value = match self.tokenizer.next_token()? {
224            Token::Ident(bytes) => std::str::from_utf8(bytes)?.to_string(),
225            _ => {
226                return Err(DxError::InvalidSyntax {
227                    pos: self.tokenizer.pos(),
228                    msg: "Expected value after alias =".to_string(),
229                });
230            }
231        };
232
233        self.aliases.insert(alias, value);
234        Ok(())
235    }
236
237    /// Parse key-value pair or table definition
238    fn parse_key_value(&mut self) -> Result<(String, DxValue)> {
239        let mut key = String::new();
240
241        // Handle prefix inheritance (^)
242        if matches!(self.tokenizer.peek_token()?, Token::Caret) {
243            self.tokenizer.next_token()?;
244            if let Some(prefix) = self.prefix_stack.last() {
245                key.push_str(prefix);
246                key.push('.');
247            }
248        }
249
250        // Defensive: Check recursion depth (prefix stack depth)
251        if self.prefix_stack.len() > crate::error::MAX_RECURSION_DEPTH {
252            return Err(DxError::recursion_limit_exceeded(self.prefix_stack.len()));
253        }
254
255        // Read the key
256        match self.tokenizer.next_token()? {
257            Token::Ident(bytes) => {
258                let key_str = std::str::from_utf8(bytes)?;
259                // Resolve alias if starts with $
260                if let Some(alias_key) = key_str.strip_prefix('$') {
261                    if let Some(resolved) = self.aliases.get(alias_key) {
262                        key.push_str(resolved);
263                    } else {
264                        return Err(DxError::UnknownAlias(alias_key.to_string()));
265                    }
266                } else {
267                    key.push_str(key_str);
268                }
269            }
270            _ => {
271                return Err(DxError::InvalidSyntax {
272                    pos: self.tokenizer.pos(),
273                    msg: "Expected key".to_string(),
274                });
275            }
276        }
277
278        // Save prefix for potential child keys
279        let full_key = key.clone();
280
281        // Read operator
282        self.tokenizer.skip_whitespace();
283        let operator = self.tokenizer.next_token()?;
284
285        let value = match operator {
286            Token::Colon => {
287                // Simple key:value
288                self.prefix_stack.push(full_key.clone());
289                let val = self.parse_value()?;
290                self.prefix_stack.pop();
291                val
292            }
293            Token::Equals => {
294                // Schema definition: table=col1%i col2%s...
295                self.parse_table_definition(&key)?
296            }
297            Token::Stream => {
298                // Stream array: key>val1|val2|val3
299                self.parse_stream_array()?
300            }
301            Token::Bang => {
302                // Implicit true: admin!
303                DxValue::Bool(true)
304            }
305            Token::Void => {
306                // Implicit null: error?
307                DxValue::Null
308            }
309            _ => {
310                return Err(DxError::InvalidSyntax {
311                    pos: self.tokenizer.pos(),
312                    msg: format!("Expected :, =, or > after key, got {:?}", operator),
313                });
314            }
315        };
316
317        Ok((key, value))
318    }
319
320    /// Parse a value
321    ///
322    /// # Errors
323    ///
324    /// Returns `DxError::UnexpectedEof` if end of input is reached before a value is found.
325    /// Returns `DxError::InvalidSyntax` for unexpected tokens.
326    fn parse_value(&mut self) -> Result<DxValue> {
327        self.tokenizer.skip_whitespace();
328
329        let token = self.tokenizer.next_token()?;
330        match token {
331            Token::Eof => Err(DxError::UnexpectedEof(self.tokenizer.pos())),
332            Token::Newline => {
333                // Newline without value - treat as unexpected EOF for value context
334                Err(DxError::UnexpectedEof(self.tokenizer.pos()))
335            }
336            Token::Null | Token::Void => Ok(DxValue::Null),
337            Token::True => Ok(DxValue::Bool(true)),
338            Token::False => Ok(DxValue::Bool(false)),
339            Token::Int(i) => Ok(DxValue::Int(i)),
340            Token::Float(f) => Ok(DxValue::Float(f)),
341            Token::Ditto => Err(DxError::DittoNoPrevious(self.tokenizer.pos())),
342            Token::At => {
343                // Anchor reference: @1
344                let anchor_token = self.tokenizer.next_token()?;
345                let anchor_id = match anchor_token {
346                    Token::Eof => {
347                        return Err(DxError::UnexpectedEof(self.tokenizer.pos()));
348                    }
349                    Token::Int(i) => i as usize,
350                    _ => {
351                        return Err(DxError::InvalidSyntax {
352                            pos: self.tokenizer.pos(),
353                            msg: "Expected number after @".to_string(),
354                        });
355                    }
356                };
357                self.anchors
358                    .get(anchor_id)
359                    .cloned()
360                    .ok_or_else(|| DxError::UnknownAnchor(anchor_id.to_string()))
361            }
362            Token::Ident(bytes) => {
363                let s = std::str::from_utf8(bytes)?;
364                Ok(DxValue::String(s.to_string()))
365            }
366            _ => Err(DxError::InvalidSyntax {
367                pos: self.tokenizer.pos(),
368                msg: format!("Unexpected token in value: {:?}", token),
369            }),
370        }
371    }
372
373    /// Parse stream array: >a|b|c
374    fn parse_stream_array(&mut self) -> Result<DxValue> {
375        let mut values = Vec::new();
376
377        loop {
378            self.tokenizer.skip_whitespace();
379
380            let token = self.tokenizer.peek_token()?;
381            if matches!(token, Token::Newline | Token::Eof) {
382                break;
383            }
384
385            let val = self.parse_value()?;
386            values.push(val);
387
388            self.tokenizer.skip_whitespace();
389            if matches!(self.tokenizer.peek_token()?, Token::Pipe) {
390                self.tokenizer.next_token()?; // consume |
391            } else {
392                break;
393            }
394        }
395
396        Ok(DxValue::Array(DxArray::stream(values)))
397    }
398
399    /// Parse table definition and rows
400    fn parse_table_definition(&mut self, name: &str) -> Result<DxValue> {
401        // Read schema definition until newline
402        self.tokenizer.skip_whitespace();
403        let schema_line = self.tokenizer.read_until(b'\n');
404        let schema_str = std::str::from_utf8(schema_line)?;
405
406        let schema = Schema::parse_definition(name.to_string(), schema_str)?;
407        self.schemas.insert(name.to_string(), schema.clone());
408
409        // Consume newline
410        if matches!(self.tokenizer.peek(), Some(b'\n')) {
411            self.tokenizer.advance(1);
412        }
413
414        // Parse table rows
415        let mut table = DxTable::new(schema.clone());
416        let mut prev_row: Option<Vec<DxValue>> = None;
417
418        loop {
419            self.tokenizer.skip_whitespace();
420
421            // Check if this line is still part of the table
422            let token = self.tokenizer.peek_token()?;
423
424            // End of file - we're done
425            if matches!(token, Token::Eof) {
426                break;
427            }
428
429            // Skip empty lines
430            if matches!(token, Token::Newline) {
431                self.tokenizer.next_token()?;
432                continue;
433            }
434
435            // If we hit a key (identifier followed by : = or >), we're done
436            if matches!(token, Token::Ident(_)) {
437                let saved_pos = self.tokenizer.pos();
438                self.tokenizer.next_token()?; // consume ident
439                let next = self.tokenizer.peek_token()?;
440                self.tokenizer.reset_to(saved_pos);
441
442                if matches!(
443                    next,
444                    Token::Colon
445                        | Token::Equals
446                        | Token::Stream
447                        | Token::Bang
448                        | Token::Void
449                        | Token::Caret
450                        | Token::Dollar
451                ) {
452                    break;
453                }
454            }
455
456            // Parse row based on schema
457            let row = self.parse_table_row(&schema, prev_row.as_ref())?;
458
459            if row.is_empty() {
460                break;
461            }
462
463            prev_row = Some(row.clone());
464            table.add_row(row).map_err(DxError::SchemaError)?;
465
466            // Defensive: Check table row count limit
467            if table.rows.len() > crate::error::MAX_TABLE_ROWS {
468                return Err(DxError::table_too_large(table.rows.len()));
469            }
470
471            // Check for end of line
472            self.tokenizer.skip_whitespace();
473            if matches!(self.tokenizer.peek(), Some(b'\n')) {
474                self.tokenizer.advance(1);
475            }
476        }
477
478        Ok(DxValue::Table(table))
479    }
480
481    /// Parse a single table row based on schema
482    ///
483    /// # Errors
484    ///
485    /// Returns `DxError::UnexpectedEof` if end of input is reached before all columns are parsed.
486    /// Returns `DxError::TypeMismatch` if a column value doesn't match the expected type.
487    /// Returns `DxError::DittoNoPrevious` if ditto is used without a previous row.
488    fn parse_table_row(
489        &mut self,
490        schema: &Schema,
491        prev_row: Option<&Vec<DxValue>>,
492    ) -> Result<Vec<DxValue>> {
493        let mut row = Vec::with_capacity(schema.columns.len());
494
495        for (col_idx, column) in schema.columns.iter().enumerate() {
496            self.tokenizer.skip_whitespace();
497
498            // Handle auto-increment: generate value without reading input
499            if matches!(column.type_hint, TypeHint::AutoIncrement) {
500                let counter = self.auto_counters.entry(schema.name.clone()).or_insert(1);
501                row.push(DxValue::Int(*counter));
502                *counter += 1;
503                continue;
504            }
505
506            let token = self.tokenizer.peek_token()?;
507
508            // Handle EOF gracefully - return partial row if we hit EOF mid-row
509            if matches!(token, Token::Eof) {
510                // If we haven't parsed any columns yet, return empty row to signal end
511                if row.is_empty() {
512                    return Ok(row);
513                }
514                // Otherwise, we have an incomplete row - return error with position
515                return Err(DxError::UnexpectedEof(self.tokenizer.pos()));
516            }
517
518            // Handle newline - end of row (may be incomplete)
519            if matches!(token, Token::Newline) {
520                // If we haven't parsed any columns yet, return empty row
521                if row.is_empty() {
522                    return Ok(row);
523                }
524                // Otherwise, we have an incomplete row - return error with position
525                return Err(DxError::InvalidSyntax {
526                    pos: self.tokenizer.pos(),
527                    msg: format!(
528                        "Incomplete table row: expected {} columns, got {}",
529                        schema.columns.len(),
530                        row.len()
531                    ),
532                });
533            }
534
535            // Handle ditto (_)
536            if matches!(token, Token::Ditto) {
537                self.tokenizer.next_token()?;
538                if let Some(prev) = prev_row {
539                    if col_idx < prev.len() {
540                        row.push(prev[col_idx].clone());
541                    } else {
542                        return Err(DxError::DittoNoPrevious(self.tokenizer.pos()));
543                    }
544                } else {
545                    return Err(DxError::DittoNoPrevious(self.tokenizer.pos()));
546                }
547                continue;
548            }
549
550            // Parse value based on type hint
551            let value = match column.type_hint {
552                TypeHint::Int => match self.tokenizer.next_token()? {
553                    Token::Eof => {
554                        return Err(DxError::UnexpectedEof(self.tokenizer.pos()));
555                    }
556                    Token::Int(i) => DxValue::Int(i),
557                    Token::Ditto => {
558                        if let Some(prev) = prev_row {
559                            if col_idx < prev.len() {
560                                prev[col_idx].clone()
561                            } else {
562                                return Err(DxError::DittoNoPrevious(self.tokenizer.pos()));
563                            }
564                        } else {
565                            return Err(DxError::DittoNoPrevious(self.tokenizer.pos()));
566                        }
567                    }
568                    other => {
569                        return Err(DxError::TypeMismatch {
570                            expected: "int".to_string(),
571                            actual: format!("{:?}", other),
572                        });
573                    }
574                },
575                TypeHint::Float => match self.tokenizer.next_token()? {
576                    Token::Eof => {
577                        return Err(DxError::UnexpectedEof(self.tokenizer.pos()));
578                    }
579                    Token::Float(f) => DxValue::Float(f),
580                    Token::Int(i) => DxValue::Float(i as f64),
581                    other => {
582                        return Err(DxError::TypeMismatch {
583                            expected: "float".to_string(),
584                            actual: format!("{:?}", other),
585                        });
586                    }
587                },
588                TypeHint::Bool => match self.tokenizer.next_token()? {
589                    Token::Eof => {
590                        return Err(DxError::UnexpectedEof(self.tokenizer.pos()));
591                    }
592                    Token::True => DxValue::Bool(true),
593                    Token::False => DxValue::Bool(false),
594                    other => {
595                        return Err(DxError::TypeMismatch {
596                            expected: "bool".to_string(),
597                            actual: format!("{:?}", other),
598                        });
599                    }
600                },
601                TypeHint::Base62 => {
602                    // Parse Base62 encoded integer
603                    match self.tokenizer.next_token()? {
604                        Token::Eof => {
605                            return Err(DxError::UnexpectedEof(self.tokenizer.pos()));
606                        }
607                        Token::Ident(bytes) => {
608                            let s = std::str::from_utf8(bytes)?;
609                            let n = decode_base62(s)?;
610                            DxValue::Int(n as i64)
611                        }
612                        Token::Int(i) => DxValue::Int(i), // Fallback for regular numbers
613                        other => {
614                            return Err(DxError::TypeMismatch {
615                                expected: "base62".to_string(),
616                                actual: format!("{:?}", other),
617                            });
618                        }
619                    }
620                }
621                TypeHint::String => {
622                    // Vacuum parsing: read until next column type
623                    let next_is_number = col_idx + 1 < schema.columns.len()
624                        && matches!(
625                            schema.columns[col_idx + 1].type_hint,
626                            TypeHint::Int | TypeHint::Float | TypeHint::Base62
627                        );
628                    let bytes = self.tokenizer.read_string_vacuum(next_is_number);
629                    let s = std::str::from_utf8(bytes)?.trim().to_string();
630                    DxValue::String(s)
631                }
632                TypeHint::AutoIncrement => {
633                    // Should not reach here (handled above)
634                    unreachable!("AutoIncrement handled before loop")
635                }
636                TypeHint::Auto => self.parse_value()?,
637            };
638
639            row.push(value);
640        }
641
642        Ok(row)
643    }
644}
645
646/// Parse DX bytes into a value
647///
648/// Parses the DX machine format (binary) into a structured [`DxValue`].
649/// This is the primary entry point for parsing DX-formatted data.
650///
651/// # Example
652///
653/// ```rust
654/// use serializer::parse;
655///
656/// let input = b"name:Alice\nage:30\nactive:+";
657/// let value = parse(input).unwrap();
658/// ```
659///
660/// # Errors
661///
662/// Returns a [`DxError`] in the following cases:
663///
664/// - [`DxError::InputTooLarge`] - Input exceeds `MAX_INPUT_SIZE` (100 MB).
665///   This check happens before any allocation to prevent memory exhaustion.
666///
667/// - [`DxError::InvalidSyntax`] - Invalid syntax at a specific position:
668///   - Unexpected token where a key, value, or operator was expected
669///   - Invalid operator after key (expected `:`, `=`, `>`, `!`, or `?`)
670///   - Unexpected character in value position
671///
672/// - [`DxError::UnexpectedEof`] - Input ends prematurely:
673///   - EOF after `:` when a value was expected
674///   - EOF after `@` when an anchor reference was expected
675///   - EOF in the middle of a table row
676///
677/// - [`DxError::Utf8Error`] - Input contains invalid UTF-8 sequences.
678///   The error includes the byte offset of the first invalid byte.
679///
680/// - [`DxError::UnknownAlias`] - Reference to an undefined alias (e.g., `$undefined`).
681///
682/// - [`DxError::UnknownAnchor`] - Reference to an undefined anchor (e.g., `@999`).
683///
684/// - [`DxError::TypeMismatch`] - Value doesn't match the expected type hint:
685///   - Integer expected but got string
686///   - Float expected but got boolean
687///   - Boolean expected but got number
688///
689/// - [`DxError::DittoNoPrevious`] - Ditto operator (`_`) used without a previous row.
690///
691/// - [`DxError::RecursionLimitExceeded`] - Nesting depth exceeds `MAX_RECURSION_DEPTH` (1000).
692///
693/// - [`DxError::TableTooLarge`] - Table has more than `MAX_TABLE_ROWS` (10 million) rows.
694///
695/// - [`DxError::SchemaError`] - Invalid table schema definition.
696///
697/// - [`DxError::Base62Error`] - Invalid Base62 encoded value in a `%x` column.
698///
699/// # Example Error Handling
700///
701/// ```rust
702/// use serializer::{parse, DxError};
703///
704/// let result = parse(b"key:");
705/// match result {
706///     Ok(value) => println!("Parsed successfully"),
707///     Err(DxError::UnexpectedEof(pos)) => eprintln!("Unexpected EOF at position {}", pos),
708///     Err(DxError::InvalidSyntax { pos, msg }) => eprintln!("Syntax error at {}: {}", pos, msg),
709///     Err(DxError::InputTooLarge { size, max }) => eprintln!("Input {} bytes exceeds {} limit", size, max),
710///     Err(e) => eprintln!("Other error: {}", e),
711/// }
712/// ```
713///
714/// [`DxError`]: crate::error::DxError
715/// [`DxValue`]: crate::types::DxValue
716#[must_use = "parsing result should be used"]
717pub fn parse(input: &[u8]) -> Result<DxValue> {
718    // Defensive: Check input size before parsing
719    if input.len() > crate::error::MAX_INPUT_SIZE {
720        return Err(DxError::input_too_large(input.len()));
721    }
722
723    let mut parser = Parser::new(input);
724    parser.parse()
725}
726
727/// Parse DX from string
728///
729/// Convenience wrapper around [`parse()`] that accepts a string slice.
730///
731/// # Example
732///
733/// ```rust
734/// use serializer::parser::parse_str;
735///
736/// let value = parse_str("name:Alice\nage:30").unwrap();
737/// ```
738///
739/// # Errors
740///
741/// Returns the same errors as [`parse()`]. See that function for the complete
742/// list of error conditions.
743///
744/// [`parse()`]: crate::parser::parse
745pub fn parse_str(input: &str) -> Result<DxValue> {
746    parse(input.as_bytes())
747}
748
749/// Stream parser for large files
750///
751/// Reads the entire contents of a reader into memory and parses it.
752/// For very large files, consider using memory-mapped I/O instead.
753///
754/// # Example
755///
756/// ```rust
757/// use serializer::parser::parse_stream;
758/// use std::io::Cursor;
759///
760/// let data = Cursor::new(b"name:Test\nvalue:42");
761/// let value = parse_stream(data).unwrap();
762/// ```
763///
764/// # Errors
765///
766/// Returns a [`DxError`] in the following cases:
767///
768/// - [`DxError::Io`] - Failed to read from the input stream.
769///
770/// - All errors from [`parse()`] - After reading, the data is parsed using
771///   the standard parser, which may return any of its error types.
772///
773/// [`DxError`]: crate::error::DxError
774/// [`parse()`]: crate::parser::parse
775pub fn parse_stream<R: std::io::Read>(reader: R) -> Result<DxValue> {
776    let mut buffer = Vec::new();
777    let mut reader = reader;
778    reader.read_to_end(&mut buffer)?;
779    parse(&buffer)
780}
781
782#[cfg(test)]
783mod tests {
784    use super::*;
785
786    #[test]
787    fn test_simple_parse() {
788        let input = b"name:Alice
789age:30
790active:+";
791
792        let result = parse(input).unwrap();
793        if let DxValue::Object(obj) = result {
794            assert_eq!(obj.get("name"), Some(&DxValue::String("Alice".to_string())));
795            assert_eq!(obj.get("age"), Some(&DxValue::Int(30)));
796            assert_eq!(obj.get("active"), Some(&DxValue::Bool(true)));
797        } else {
798            panic!("Expected object");
799        }
800    }
801
802    #[test]
803    fn test_table_parse() {
804        let input = b"users=id%i name%s active%b
8051 Alice +
8062 Bob -";
807
808        let result = parse(input).unwrap();
809        if let DxValue::Object(obj) = result {
810            if let Some(DxValue::Table(table)) = obj.get("users") {
811                assert_eq!(table.row_count(), 2);
812                assert_eq!(table.rows[0][0], DxValue::Int(1));
813                assert_eq!(table.rows[0][1], DxValue::String("Alice".to_string()));
814            } else {
815                panic!("Expected table");
816            }
817        }
818    }
819
820    #[test]
821    fn test_stream_array() {
822        let input = b"tags>alpha|beta|gamma";
823
824        let result = parse(input).unwrap();
825        if let DxValue::Object(obj) = result {
826            if let Some(DxValue::Array(arr)) = obj.get("tags") {
827                assert!(arr.is_stream);
828                assert_eq!(arr.values.len(), 3);
829            } else {
830                panic!("Expected array");
831            }
832        }
833    }
834
835    #[test]
836    fn test_alias() {
837        let input = b"$c=context
838$c.task:Mission";
839
840        let result = parse(input).unwrap();
841        if let DxValue::Object(obj) = result {
842            assert_eq!(
843                obj.get("context.task"),
844                Some(&DxValue::String("Mission".to_string()))
845            );
846        }
847    }
848
849    #[test]
850    fn test_ditto() {
851        let input = b"data=id%i name%s
8521 Alice
853_ Bob";
854
855        let result = parse(input).unwrap();
856        if let DxValue::Object(obj) = result {
857            if let Some(DxValue::Table(table)) = obj.get("data") {
858                assert_eq!(table.rows[1][0], DxValue::Int(1)); // Ditto copies from above
859            }
860        }
861    }
862
863    #[test]
864    fn test_eof_in_value() {
865        // Test that EOF after colon returns UnexpectedEof error
866        let input = b"key:";
867        let result = parse(input);
868        assert!(result.is_err(), "EOF after colon should error");
869        if let Err(DxError::UnexpectedEof(pos)) = result {
870            assert!(pos <= input.len(), "Position should be within input bounds");
871        }
872    }
873
874    #[test]
875    fn test_eof_in_anchor_reference() {
876        // Test that EOF after @ returns UnexpectedEof error
877        let input = b"key:@";
878        let result = parse(input);
879        assert!(result.is_err(), "EOF after @ should error");
880    }
881
882    #[test]
883    fn test_empty_input_parses() {
884        // Empty input should parse to empty object
885        let result = parse(b"");
886        assert!(result.is_ok(), "Empty input should parse successfully");
887        if let Ok(DxValue::Object(obj)) = result {
888            assert!(
889                obj.fields.is_empty(),
890                "Empty input should produce empty object"
891            );
892        }
893    }
894
895    // ==========================================================================
896    // Thread Safety Compile-Time Assertions
897    // ==========================================================================
898
899    /// Compile-time assertion that DxValue implements Send
900    fn _assert_dx_value_send<T: Send>() {}
901
902    /// Compile-time assertion that DxValue implements Sync
903    fn _assert_dx_value_sync<T: Sync>() {}
904
905    #[test]
906    fn test_dx_value_is_send_sync() {
907        // These function calls verify at compile time that DxValue is Send + Sync
908        _assert_dx_value_send::<DxValue>();
909        _assert_dx_value_sync::<DxValue>();
910    }
911
912    #[test]
913    fn test_parser_is_send() {
914        // Parser should be Send (can be moved between threads)
915        fn assert_send<T: Send>() {}
916        assert_send::<Parser<'_>>();
917    }
918
919    #[test]
920    fn test_parse_is_stateless() {
921        // Verify that parse() can be called from multiple threads
922        // by running the same input through multiple threads and
923        // verifying consistent results
924        use std::sync::Arc;
925        use std::thread;
926
927        let input = Arc::new(b"name:Test\nvalue:42".to_vec());
928        let num_threads = 4;
929
930        let handles: Vec<_> = (0..num_threads)
931            .map(|_| {
932                let input_clone = Arc::clone(&input);
933                thread::spawn(move || parse(&input_clone))
934            })
935            .collect();
936
937        let results: Vec<_> = handles
938            .into_iter()
939            .map(|h| h.join().expect("Thread panicked"))
940            .collect();
941
942        // All results should be identical
943        let first = results[0].as_ref().expect("First parse failed");
944        for (i, result) in results.iter().enumerate().skip(1) {
945            let value = result
946                .as_ref()
947                .unwrap_or_else(|_| panic!("Parse {} failed", i));
948            assert_eq!(first, value, "Thread {} produced different result", i);
949        }
950    }
951}