1use serde_json::{
2 Map,
3 Number,
4 Value,
5};
6
7use crate::{
8 constants::{
9 KEYWORDS,
10 MAX_DEPTH,
11 },
12 decode::{
13 scanner::{
14 Scanner,
15 Token,
16 },
17 validation,
18 },
19 types::{
20 DecodeOptions,
21 Delimiter,
22 ErrorContext,
23 ToonError,
24 ToonResult,
25 },
26 utils::validation::validate_depth,
27};
28
29pub struct Parser<'a> {
31 scanner: Scanner,
32 current_token: Token,
33 options: DecodeOptions,
34 delimiter: Option<Delimiter>,
35 input: &'a str,
36}
37
38impl<'a> Parser<'a> {
39 pub fn new(input: &'a str, options: DecodeOptions) -> Self {
41 let mut scanner = Scanner::new(input);
42 let chosen_delim = options.delimiter;
43 scanner.set_active_delimiter(chosen_delim);
44 let current_token = scanner.scan_token().unwrap_or(Token::Eof);
45
46 Self {
47 scanner,
48 current_token,
49 delimiter: chosen_delim,
50 options,
51 input,
52 }
53 }
54
55 pub fn parse(&mut self) -> ToonResult<Value> {
57 if self.options.strict {
58 self.validate_indentation(self.scanner.get_last_line_indent())?;
59 }
60 self.parse_value()
61 }
62
63 fn advance(&mut self) -> ToonResult<()> {
64 self.current_token = self.scanner.scan_token()?;
65 Ok(())
66 }
67
68 fn skip_newlines(&mut self) -> ToonResult<()> {
69 while matches!(self.current_token, Token::Newline) {
70 self.advance()?;
71 }
72 Ok(())
73 }
74
75 fn parse_value(&mut self) -> ToonResult<Value> {
76 self.parse_value_with_depth(0)
77 }
78
79 fn parse_value_with_depth(&mut self, depth: usize) -> ToonResult<Value> {
80 validate_depth(depth, MAX_DEPTH)?;
81
82 self.skip_newlines()?;
83
84 match &self.current_token {
85 Token::Null => {
86 let next_char_is_colon = matches!(self.scanner.peek(), Some(':'));
88 if next_char_is_colon {
89 let key = KEYWORDS[0].to_string();
90 self.advance()?;
91 self.parse_object_with_initial_key(key, depth)
92 } else {
93 self.advance()?;
94 Ok(Value::Null)
95 }
96 }
97 Token::Bool(b) => {
98 let next_char_is_colon = matches!(self.scanner.peek(), Some(':'));
99 if next_char_is_colon {
100 let key = if *b {
101 KEYWORDS[1].to_string()
102 } else {
103 KEYWORDS[2].to_string()
104 };
105 self.advance()?;
106 self.parse_object_with_initial_key(key, depth)
107 } else {
108 let val = *b;
109 self.advance()?;
110 Ok(Value::Bool(val))
111 }
112 }
113 Token::Integer(i) => {
114 let next_char_is_colon = matches!(self.scanner.peek(), Some(':'));
115 if next_char_is_colon {
116 let key = i.to_string();
117 self.advance()?;
118 self.parse_object_with_initial_key(key, depth)
119 } else {
120 let val = *i;
121 self.advance()?;
122 Ok(serde_json::Number::from(val).into())
123 }
124 }
125 Token::Number(n) => {
126 let next_char_is_colon = matches!(self.scanner.peek(), Some(':'));
127 if next_char_is_colon {
128 let key = n.to_string();
129 self.advance()?;
130 self.parse_object_with_initial_key(key, depth)
131 } else {
132 let val = *n;
133 self.advance()?;
134 Ok(serde_json::Number::from_f64(val)
135 .ok_or_else(|| ToonError::InvalidInput(format!("Invalid number: {val}")))?
136 .into())
137 }
138 }
139 Token::String(s, _) => {
140 let first = s.clone();
141 self.advance()?;
142
143 match &self.current_token {
144 Token::Colon | Token::LeftBracket => {
145 self.parse_object_with_initial_key(first, depth)
146 }
147 _ => {
148 let mut accumulated = first;
151 while let Token::String(next, _) = &self.current_token {
152 if !accumulated.is_empty() {
153 accumulated.push(' ');
154 }
155 accumulated.push_str(next);
156 self.advance()?;
157 }
158 Ok(Value::String(accumulated))
159 }
160 }
161 }
162 Token::LeftBracket => self.parse_root_array(depth),
163 Token::Eof => Ok(Value::Object(Map::new())),
164 _ => self.parse_object(depth),
165 }
166 }
167
168 fn parse_object(&mut self, depth: usize) -> ToonResult<Value> {
169 validate_depth(depth, MAX_DEPTH)?;
170
171 let mut obj = Map::new();
172 let mut base_indent: Option<usize> = None;
173
174 loop {
175 while matches!(self.current_token, Token::Newline) {
176 self.advance()?;
177 }
178
179 if matches!(self.current_token, Token::Eof) {
180 break;
181 }
182
183 let current_indent = self.scanner.get_last_line_indent();
184 if let Some(expected) = base_indent {
185 if current_indent != expected {
186 break;
187 }
188 } else {
189 if self.options.strict {
190 self.validate_indentation(current_indent)?;
191 }
192 base_indent = Some(current_indent);
193 }
194
195 let key = match &self.current_token {
196 Token::String(s, _) => s.clone(),
197 _ => {
198 return Err(self
199 .parse_error_with_context(format!(
200 "Expected key, found {:?}",
201 self.current_token
202 ))
203 .with_suggestion("Object keys must be strings"));
204 }
205 };
206 self.advance()?;
207
208 let value = if matches!(self.current_token, Token::LeftBracket) {
209 self.parse_array(depth)?
210 } else {
211 if !matches!(self.current_token, Token::Colon) {
212 return Err(self
213 .parse_error_with_context(format!(
214 "Expected ':' or '[', found {:?}",
215 self.current_token
216 ))
217 .with_suggestion("Use ':' for object values or '[' for arrays"));
218 }
219 self.advance()?;
220 self.parse_field_value(depth)?
221 };
222
223 obj.insert(key, value);
224 }
225
226 Ok(Value::Object(obj))
227 }
228
229 fn parse_object_with_initial_key(&mut self, key: String, depth: usize) -> ToonResult<Value> {
230 validate_depth(depth, MAX_DEPTH)?;
231
232 let mut obj = Map::new();
233
234 let value = if matches!(self.current_token, Token::LeftBracket) {
235 self.parse_array(depth)?
236 } else {
237 if !matches!(self.current_token, Token::Colon) {
238 return Err(self
239 .parse_error_with_context(format!(
240 "Expected ':' or '[', found {:?}",
241 self.current_token
242 ))
243 .with_suggestion("Use ':' for object values or '[' for arrays"));
244 }
245 self.advance()?;
246 self.parse_field_value(depth)?
247 };
248
249 obj.insert(key, value);
250
251 self.skip_newlines()?;
252
253 loop {
254 if matches!(self.current_token, Token::Eof) {
255 break;
256 }
257
258 let next_key = match &self.current_token {
259 Token::String(s, _) => s.clone(),
260 _ => break,
261 };
262 self.advance()?;
263
264 let next_value = if matches!(self.current_token, Token::LeftBracket) {
265 self.parse_array(depth)?
266 } else {
267 if !matches!(self.current_token, Token::Colon) {
268 break;
269 }
270 self.advance()?;
271 self.parse_field_value(depth)?
272 };
273
274 obj.insert(next_key, next_value);
275 self.skip_newlines()?;
276 }
277
278 Ok(Value::Object(obj))
279 }
280
281 fn parse_field_value(&mut self, depth: usize) -> ToonResult<Value> {
282 match &self.current_token {
283 Token::Newline => self.parse_indented_object(depth + 1),
284 _ => self.parse_primitive(),
285 }
286 }
287
288 fn parse_indented_object(&mut self, depth: usize) -> ToonResult<Value> {
289 validate_depth(depth, MAX_DEPTH)?;
290
291 let mut obj = Map::new();
292
293 loop {
294 while matches!(self.current_token, Token::Newline) {
295 self.advance()?;
296 }
297 let current_indent = self.scanner.get_last_line_indent();
298 if self.options.strict {
299 self.validate_indentation(current_indent)?;
300 }
301
302 if self.scanner.get_last_line_indent() == 0 || matches!(self.current_token, Token::Eof)
303 {
304 break;
305 }
306
307 let key = match &self.current_token {
308 Token::String(s, _) => s.clone(),
309 _ => {
310 return Err(self
311 .parse_error_with_context(format!(
312 "Expected key, found {:?}",
313 self.current_token
314 ))
315 .with_suggestion("Object keys must be strings"));
316 }
317 };
318
319 self.advance()?;
320
321 let value = if matches!(self.current_token, Token::LeftBracket) {
322 self.parse_array(depth)?
323 } else {
324 if !matches!(self.current_token, Token::Colon) {
325 return Err(self
326 .parse_error_with_context(format!(
327 "Expected ':' or '[', found {:?}",
328 self.current_token
329 ))
330 .with_suggestion("Use ':' after object keys"));
331 }
332 self.advance()?;
333 self.parse_field_value(depth)?
334 };
335
336 obj.insert(key, value);
337 while matches!(self.current_token, Token::Newline) {
338 self.advance()?;
339 }
340 }
341
342 Ok(Value::Object(obj))
343 }
344
345 fn parse_primitive(&mut self) -> ToonResult<Value> {
346 match &self.current_token {
347 Token::String(s, is_quoted) => {
348 if *is_quoted {
349 let value = Value::String(s.clone());
351 self.advance()?;
352 Ok(value)
353 } else {
354 let mut accumulated = s.clone();
356 self.advance()?;
357
358 while let Token::String(next, false) = &self.current_token {
360 accumulated.push(' ');
361 accumulated.push_str(next);
362 self.advance()?;
363 }
364
365 if self.options.coerce_types {
367 Ok(self.coerce_string_to_type(&accumulated))
368 } else {
369 Ok(Value::String(accumulated))
370 }
371 }
372 }
373 Token::Integer(i) => {
374 let value = Value::Number((*i).into());
375 self.advance()?;
376 Ok(value)
377 }
378 Token::Number(f) => {
379 let value = Number::from_f64(*f)
380 .map(Value::Number)
381 .unwrap_or_else(|| Value::String(f.to_string()));
382 self.advance()?;
383 Ok(value)
384 }
385 Token::Bool(b) => {
386 let value = *b;
387 self.advance()?;
388 Ok(Value::Bool(value))
389 }
390 Token::Null => {
391 self.advance()?;
392 Ok(Value::Null)
393 }
394 _ => Err(self
395 .parse_error_with_context(format!(
396 "Expected primitive value, found {:?}",
397 self.current_token
398 ))
399 .with_suggestion("Expected a value (string, number, boolean, or null)")),
400 }
401 }
402
403 fn create_error_context(&self) -> ErrorContext {
404 let line = self.scanner.get_line();
405 let column = self.scanner.get_column();
406
407 ErrorContext::from_input(self.input, line, column, 2)
408 .unwrap_or_else(|| ErrorContext::new("").with_indicator(column))
409 }
410
411 fn parse_error_with_context(&self, message: impl Into<String>) -> ToonError {
412 let context = self.create_error_context();
413 ToonError::parse_error_with_context(
414 self.scanner.get_line(),
415 self.scanner.get_column(),
416 message,
417 context,
418 )
419 }
420
421 fn coerce_string_to_type(&self, s: &str) -> Value {
422 if s == "null" {
423 return Value::Null;
424 }
425
426 if s == "true" {
427 return Value::Bool(true);
428 }
429 if s == "false" {
430 return Value::Bool(false);
431 }
432
433 let mut chars = s.chars();
434 let first = chars.next();
435 let second = chars.next();
436 if first == Some('0') && second.is_some_and(|c| c.is_ascii_digit()) {
437 return Value::String(s.to_string());
438 }
439 if first == Some('-')
440 && second == Some('0')
441 && chars.next().is_some_and(|c| c.is_ascii_digit())
442 {
443 return Value::String(s.to_string());
444 }
445
446 if let Ok(i) = s.parse::<i64>() {
447 return Value::Number(i.into());
448 }
449
450 if let Ok(f) = s.parse::<f64>() {
451 if let Some(num) = Number::from_f64(f) {
452 return Value::Number(num);
453 }
454 }
455
456 Value::String(s.to_string())
457 }
458
459 fn parse_array(&mut self, depth: usize) -> ToonResult<Value> {
460 validate_depth(depth, MAX_DEPTH)?;
461
462 if !matches!(self.current_token, Token::LeftBracket) {
463 return Err(self
464 .parse_error_with_context("Expected '['")
465 .with_suggestion("Arrays must start with '['"));
466 }
467 self.advance()?;
468
469 let length = self.parse_array_length()?;
470
471 self.detect_or_consume_delimiter()?;
472
473 if !matches!(self.current_token, Token::RightBracket) {
474 return Err(self
475 .parse_error_with_context("Expected ']'")
476 .with_suggestion("Close array length with ']'"));
477 }
478 self.advance()?;
479
480 if self.delimiter.is_none() {
481 self.delimiter = Some(Delimiter::Comma);
482 }
483 self.scanner.set_active_delimiter(self.delimiter);
484
485 let fields = if matches!(self.current_token, Token::LeftBrace) {
486 Some(self.parse_field_list()?)
487 } else {
488 None
489 };
490
491 if !matches!(self.current_token, Token::Colon) {
492 return Err(self
493 .parse_error_with_context("Expected ':'")
494 .with_suggestion("Array header must end with ':'"));
495 }
496 self.advance()?;
497
498 if length == 0 {
499 return Ok(Value::Array(vec![]));
500 }
501
502 if let Some(fields) = fields {
503 validation::validate_field_list(&fields)?;
504 self.parse_tabular_array(length, fields, depth)
505 } else {
506 self.parse_regular_array(length, depth)
507 }
508 }
509
510 fn parse_root_array(&mut self, depth: usize) -> ToonResult<Value> {
511 validate_depth(depth, MAX_DEPTH)?;
512 self.parse_array(depth)
513 }
514
515 fn parse_array_length(&mut self) -> ToonResult<usize> {
516 if let Some(length_str) = match &self.current_token {
517 Token::String(s, _) if s.starts_with('#') => Some(s[1..].to_string()),
518 _ => None,
519 } {
520 self.advance()?;
521 return length_str.parse::<usize>().map_err(|_| {
522 self.parse_error_with_context(format!("Invalid array length: {length_str}"))
523 .with_suggestion("Length must be a positive number")
524 });
525 }
526
527 match &self.current_token {
528 Token::Integer(i) => {
529 let len = *i as usize;
530 self.advance()?;
531 Ok(len)
532 }
533 _ => Err(self
534 .parse_error_with_context(format!(
535 "Expected array length, found {:?}",
536 self.current_token
537 ))
538 .with_suggestion("Array must have a length like [5] or #5")),
539 }
540 }
541
542 fn detect_or_consume_delimiter(&mut self) -> ToonResult<()> {
543 match &self.current_token {
544 Token::Delimiter(delim) => {
545 if self.delimiter.is_none() {
546 self.delimiter = Some(*delim);
547 }
548 self.advance()?;
549 }
550 Token::String(s, _) if s == "," || s == "|" || s == "\t" => {
551 let delim = if s == "," {
552 Delimiter::Comma
553 } else if s == "|" {
554 Delimiter::Pipe
555 } else {
556 Delimiter::Tab
557 };
558 if self.delimiter.is_none() {
559 self.delimiter = Some(delim);
560 }
561 self.advance()?;
562 }
563 _ => {}
564 }
565 self.scanner.set_active_delimiter(self.delimiter);
566 Ok(())
567 }
568
569 fn parse_field_list(&mut self) -> ToonResult<Vec<String>> {
570 if !matches!(self.current_token, Token::LeftBrace) {
571 return Err(self
572 .parse_error_with_context("Expected '{'")
573 .with_suggestion("Tabular arrays need field list like {id,name}"));
574 }
575 self.advance()?;
576
577 let mut fields = Vec::new();
578
579 loop {
580 match &self.current_token {
581 Token::String(s, _) => {
582 fields.push(s.clone());
583 self.advance()?;
584
585 if matches!(self.current_token, Token::Delimiter(_)) {
586 self.advance()?;
587 } else if matches!(self.current_token, Token::RightBrace) {
588 break;
589 }
590 }
591 Token::RightBrace => break,
592 _ => {
593 return Err(self
594 .parse_error_with_context(format!(
595 "Expected field name, found {:?}",
596 self.current_token
597 ))
598 .with_suggestion("Field names must be strings separated by commas"));
599 }
600 }
601 }
602
603 if !matches!(self.current_token, Token::RightBrace) {
604 return Err(self
605 .parse_error_with_context("Expected '}'")
606 .with_suggestion("Close field list with '}'"));
607 }
608 self.advance()?;
609
610 Ok(fields)
611 }
612
613 fn parse_tabular_array(
614 &mut self,
615 length: usize,
616 fields: Vec<String>,
617 depth: usize,
618 ) -> ToonResult<Value> {
619 validate_depth(depth, MAX_DEPTH)?;
620
621 let mut rows = Vec::new();
622
623 self.skip_newlines()?;
624
625 self.scanner.set_active_delimiter(self.delimiter);
626
627 let expected_indent = self.options.indent.get_spaces() * (depth + 1);
628 loop {
629 if matches!(self.current_token, Token::Eof) {
630 break;
631 }
632 let current_indent = self.scanner.get_last_line_indent();
633 if current_indent < expected_indent {
634 break;
635 }
636 if self.options.strict && current_indent != expected_indent {
637 return Err(self.parse_error_with_context(format!(
638 "Invalid indentation for tabular row: expected {expected_indent} spaces, \
639 found {current_indent}"
640 )));
641 }
642 if matches!(self.current_token, Token::String(..)) && self.scanner.peek() == Some(':') {
643 break;
644 }
645
646 let row_index = rows.len();
647 let mut row = Map::new();
648 for (i, field) in fields.iter().enumerate() {
649 if i > 0 {
650 match &self.current_token {
651 Token::Delimiter(_) => {
652 self.advance()?;
653 }
654 Token::String(s, _) if s == "," || s == "|" || s == "\t" => {
655 self.advance()?;
656 }
657 _ => {
658 if self.options.strict {
659 return Err(self
660 .parse_error_with_context(format!(
661 "Tabular row {}: expected {} values, but found only {}",
662 row_index + 1,
663 fields.len(),
664 i
665 ))
666 .with_suggestion(format!(
667 "Row {} should have {} values",
668 row_index + 1,
669 fields.len()
670 )));
671 } else {
672 break;
673 }
674 }
675 }
676 }
677
678 if self.options.strict
679 && (matches!(self.current_token, Token::Newline)
680 || matches!(self.current_token, Token::Eof))
681 {
682 return Err(self
683 .parse_error_with_context(format!(
684 "Tabular row {}: expected {} values, but found only {}",
685 row_index + 1,
686 fields.len(),
687 i
688 ))
689 .with_suggestion(format!(
690 "Row {} should have {} values",
691 row_index + 1,
692 fields.len()
693 )));
694 }
695
696 let value = self.parse_primitive()?;
697 row.insert(field.clone(), value);
698 }
699
700 if self.options.strict {
701 match &self.current_token {
702 Token::Newline | Token::Eof => {}
703 _ => {
704 return Err(self
705 .parse_error_with_context(format!(
706 "Tabular row {}: expected {} values, but found extra values",
707 row_index + 1,
708 fields.len()
709 ))
710 .with_suggestion(format!(
711 "Row {} should have exactly {} values",
712 row_index + 1,
713 fields.len()
714 )));
715 }
716 }
717 }
718
719 if !self.options.strict && row.len() < fields.len() {
720 for field in fields.iter().skip(row.len()) {
721 row.insert(field.clone(), Value::Null);
722 }
723 }
724
725 rows.push(Value::Object(row));
726
727 if matches!(self.current_token, Token::Eof) {
728 break;
729 }
730
731 if !matches!(self.current_token, Token::Newline) {
732 if !self.options.strict {
733 while !matches!(self.current_token, Token::Newline | Token::Eof) {
734 self.advance()?;
735 }
736 if matches!(self.current_token, Token::Eof) {
737 break;
738 }
739 } else {
740 return Err(self.parse_error_with_context(format!(
741 "Expected newline after tabular row {}",
742 row_index + 1
743 )));
744 }
745 }
746
747 self.advance()?;
748 if self.options.strict && matches!(self.current_token, Token::Newline) {
749 return Err(self.parse_error_with_context(
750 "Blank lines are not allowed inside tabular arrays in strict mode",
751 ));
752 }
753
754 self.skip_newlines()?;
755 }
756
757 validation::validate_array_length(length, rows.len(), self.options.strict)?;
758
759 Ok(Value::Array(rows))
760 }
761
762 fn parse_regular_array(&mut self, length: usize, depth: usize) -> ToonResult<Value> {
763 let mut items = Vec::new();
764
765 match &self.current_token {
766 Token::Newline => {
767 self.skip_newlines()?;
768
769 let expected_indent = self.options.indent.get_spaces() * (depth + 1);
770
771 for i in 0..length {
772 let current_indent = self.scanner.get_last_line_indent();
773 if self.options.strict {
774 self.validate_indentation(current_indent)?;
775
776 if current_indent != expected_indent {
777 return Err(self.parse_error_with_context(format!(
778 "Invalid indentation for list item: expected {expected_indent} \
779 spaces, found {current_indent}"
780 )));
781 }
782 }
783 if !matches!(self.current_token, Token::Dash) {
784 return Err(self
785 .parse_error_with_context(format!(
786 "Expected '-' for list item, found {:?}",
787 self.current_token
788 ))
789 .with_suggestion(format!(
790 "List arrays need '-' prefix for each item (item {} of {})",
791 i + 1,
792 length
793 )));
794 }
795 self.advance()?;
796
797 let value = if matches!(self.current_token, Token::LeftBracket) {
798 self.parse_array(depth + 1)?
799 } else {
800 self.parse_primitive()?
801 };
802
803 items.push(value);
804
805 if items.len() < length {
806 if !matches!(self.current_token, Token::Newline) {
807 return Err(self.parse_error_with_context(format!(
808 "Expected newline after list item {}",
809 i + 1
810 )));
811 }
812 self.advance()?;
813
814 if self.options.strict && matches!(self.current_token, Token::Newline) {
815 return Err(self.parse_error_with_context(
816 "Blank lines are not allowed inside list arrays in strict mode",
817 ));
818 }
819
820 self.skip_newlines()?;
821 }
822 }
823 }
824 _ => {
825 for i in 0..length {
826 if i > 0 {
827 match &self.current_token {
828 Token::Delimiter(_) => {
829 self.advance()?;
830 }
831 Token::String(s, _) if s == "," || s == "|" || s == "\t" => {
832 self.advance()?;
833 }
834 _ => {
835 return Err(self
836 .parse_error_with_context(format!(
837 "Expected delimiter, found {:?}",
838 self.current_token
839 ))
840 .with_suggestion(format!(
841 "Expected delimiter between items (item {} of {})",
842 i + 1,
843 length
844 )));
845 }
846 }
847 }
848
849 let value = if matches!(self.current_token, Token::LeftBracket) {
850 self.parse_array(depth + 1)?
851 } else {
852 self.parse_primitive()?
853 };
854
855 items.push(value);
856 }
857 }
858 }
859
860 validation::validate_array_length(length, items.len(), self.options.strict)?;
861
862 Ok(Value::Array(items))
863 }
864
865 fn validate_indentation(&self, indent_amount: usize) -> ToonResult<()> {
866 if !self.options.strict {
867 return Ok(());
868 }
869
870 let indent_size = self.options.indent.get_spaces();
871 if indent_size > 0 && indent_amount > 0 && !indent_amount.is_multiple_of(indent_size) {
872 Err(self.parse_error_with_context(format!(
873 "Invalid indentation: found {indent_amount} spaces, but must be a multiple of \
874 {indent_size}"
875 )))
876 } else {
877 Ok(())
878 }
879 }
880}
881
882#[cfg(test)]
883mod tests {
884 use std::f64;
885
886 use serde_json::json;
887
888 use super::*;
889
890 fn parse(input: &str) -> ToonResult<Value> {
891 let mut parser = Parser::new(input, DecodeOptions::default());
892 parser.parse()
893 }
894
895 #[test]
896 fn test_parse_primitives() {
897 assert_eq!(parse("null").unwrap(), json!(null));
898 assert_eq!(parse("true").unwrap(), json!(true));
899 assert_eq!(parse("false").unwrap(), json!(false));
900 assert_eq!(parse("42").unwrap(), json!(42));
901 assert_eq!(parse("3.141592653589793").unwrap(), json!(f64::consts::PI));
902 assert_eq!(parse("hello").unwrap(), json!("hello"));
903 }
904
905 #[test]
906 fn test_parse_simple_object() {
907 let result = parse("name: Alice\nage: 30").unwrap();
908 assert_eq!(result["name"], json!("Alice"));
909 assert_eq!(result["age"], json!(30));
910 }
911
912 #[test]
913 fn test_parse_primitive_array() {
914 let result = parse("tags[3]: a,b,c").unwrap();
915 assert_eq!(result["tags"], json!(["a", "b", "c"]));
916 }
917
918 #[test]
919 fn test_parse_empty_array() {
920 let result = parse("items[0]:").unwrap();
921 assert_eq!(result["items"], json!([]));
922 }
923
924 #[test]
925 fn test_parse_tabular_array() {
926 let result = parse("users[2]{id,name}:\n 1,Alice\n 2,Bob").unwrap();
927 assert_eq!(
928 result["users"],
929 json!([
930 {"id": 1, "name": "Alice"},
931 {"id": 2, "name": "Bob"}
932 ])
933 );
934 }
935}