toon_format/decode/
parser.rs

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
29/// Parser that builds JSON values from a sequence of tokens.
30pub 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    /// Create a new parser with the given input and options.
40    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    /// Parse the input into a JSON value.
56    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                // "null:" indicates "null" is a key, not a value
87                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                        // Accumulate consecutive strings with spaces (e.g., "hello" "world" ->
149                        // "hello world")
150                        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                    // Quoted string: consume one token and return
350                    let value = Value::String(s.clone());
351                    self.advance()?;
352                    Ok(value)
353                } else {
354                    // Unquoted string: check for more parts
355                    let mut accumulated = s.clone();
356                    self.advance()?;
357
358                    // Loop while the next token is also an unquoted string
359                    while let Token::String(next, false) = &self.current_token {
360                        accumulated.push(' ');
361                        accumulated.push_str(next);
362                        self.advance()?;
363                    }
364
365                    // Now coerce the *full* accumulated string
366                    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}