Skip to main content

ron_schema/ron/
parser.rs

1/*************************
2 * Author: Bradley Hunter
3 */
4
5use crate::span::{Position, Span, Spanned};
6use crate::error::{RonErrorKind, RonParseError};
7use super::{RonValue, RonStruct};
8
9#[derive(Debug)]
10struct Parser<'a> {
11    source: &'a str,
12    bytes: &'a [u8],
13    offset: usize,
14    line: usize,
15    column: usize,
16}
17
18impl<'a> Parser<'a> {
19    fn new(source: &'a str) -> Self {
20        Self { source, bytes: source.as_bytes(), offset: 0, line: 1, column: 1 }
21    }
22
23    fn position(&self) -> Position {
24        Position { offset: self.offset, line: self.line, column: self.column }
25    }
26
27    fn peek(&self) -> Option<u8> {
28        self.bytes.get(self.offset).copied()
29    }
30
31    fn advance(&mut self) {
32        if let Some(byte) = self.peek() {
33            if byte == b'\n'{
34                self.column = 1;
35                self.line += 1;
36            } else {
37                self.column += 1;
38            }
39            self.offset += 1;
40        } 
41    }
42
43    fn skip_whitespace(&mut self) {
44        loop {
45            match self.peek() {
46                Some(b' ' | b'\t' | b'\n' | b'\r') => self.advance(),
47                Some(b'/') if self.bytes.get(self.offset + 1) == Some(&b'/') => {
48                    while self.peek().is_some_and(|b| b != b'\n') {
49                        self.advance();
50                    }
51                }
52                _ => break,
53            }
54        }
55    }
56
57    fn expect_char(&mut self, expected: u8) -> Result<(), RonParseError> {
58        let start = self.position();
59        match self.peek() {
60            Some(b) if b == expected => {
61                self.advance();
62                Ok(())
63            },
64            Some(b) => {
65                self.advance();
66                let end = self.position();
67                Err(RonParseError { 
68                    span: Span { 
69                        start, 
70                        end 
71                    }, 
72                    kind: RonErrorKind::UnexpectedToken { 
73                        expected: format!("'{}'", expected as char), 
74                        found: format!("'{}'", b as char) 
75                    } 
76                })
77            },
78            None => {
79                Err(RonParseError { 
80                    span: Span { 
81                        start, 
82                        end: start 
83                    }, 
84                    kind: RonErrorKind::UnexpectedToken { 
85                        expected: format!("'{}'", expected as char), 
86                        found: "end of input".to_string() 
87                    } 
88                })
89            }
90        }
91    }
92
93    fn parse_identifier(&mut self) -> Result<Spanned<String>, RonParseError> {
94        let start = self.position();
95
96        // Check for valid identifier start
97        match self.peek() {
98            Some(b) if b.is_ascii_alphabetic() || b == b'_' => {},
99            Some(b) => {
100                self.advance();
101                let end = self.position();
102                return Err(RonParseError {
103                    span: Span { start, end },
104                    kind: RonErrorKind::UnexpectedToken {
105                        expected: "identifier".to_string(),
106                        found: format!("'{}'", b as char),
107                    },
108                });
109            },
110            None => {
111                return Err(RonParseError {
112                    span: Span { start, end: start },
113                    kind: RonErrorKind::UnexpectedToken {
114                        expected: "identifier".to_string(),
115                        found: "end of input".to_string(),
116                    },
117                });
118            },
119        }
120
121        // Consume all identifier continuation characters
122        while self.peek().is_some_and(|b| b.is_ascii_alphanumeric() || b == b'_') {
123            self.advance();
124        }
125
126        // Slice out the identifier text
127        let end = self.position();
128        Ok(Spanned {
129            value: self.source[start.offset..end.offset].to_string(),
130            span: Span { start, end },
131        })
132    }
133
134    #[allow(clippy::too_many_lines)]
135    fn parse_value(&mut self) -> Result<Spanned<RonValue>, RonParseError> {
136        self.skip_whitespace();
137        let start = self.position();
138
139        match self.peek() {
140            Some(b'"') => {
141                self.advance(); // skip opening quote
142                let mut content = String::new();
143                loop {
144                    match self.peek() {
145                        Some(b'"') => {
146                            self.advance(); // skip closing quote
147                            break;
148                        }
149                        // b'\\' is a single backslash byte — Rust escapes it in source code.
150                        // We detect RON escape sequences (like \n, \t, \") by first matching
151                        // the backslash, then checking the next character to decide what to emit.
152                        Some(b'\\') => {
153                            self.advance(); // skip the backslash
154                            match self.peek() {
155                                Some(b'n') => { content.push('\n'); self.advance(); }
156                                Some(b't') => { content.push('\t'); self.advance(); }
157                                Some(b'\\') => { content.push('\\'); self.advance(); }
158                                Some(b'"') => { content.push('"'); self.advance(); }
159                                Some(b) => { content.push(b as char); self.advance(); }
160                                None => {
161                                    return Err(RonParseError {
162                                        span: Span { start, end: self.position() },
163                                        kind: RonErrorKind::UnterminatedString,
164                                    });
165                                }
166                            }
167                        }
168                        Some(b) => {
169                            content.push(b as char);
170                            self.advance();
171                        }
172                        None => {
173                            return Err(RonParseError {
174                                span: Span { start, end: self.position() },
175                                kind: RonErrorKind::UnterminatedString,
176                            });
177                        }
178                    }
179                }
180                let end = self.position();
181                Ok(Spanned {
182                    value: RonValue::String(content),
183                    span: Span { start, end },
184                })
185            },
186            Some(b) if b.is_ascii_digit() || b == b'-' => {
187                if b == b'-' {
188                    self.advance();
189                }
190
191                let mut has_dot = false;
192        
193                loop {
194                    match self.peek() {
195                        Some(b) if b.is_ascii_digit() => {self.advance();},
196                        Some(b'.') if !has_dot => {
197                            has_dot = true;
198                            self.advance();
199                        },
200                        Some(_) | None => {break;}
201                    }
202                }
203
204                let end = self.position();
205                let number_str = &self.source[start.offset..end.offset];
206                if has_dot {
207                    let num_float = number_str.parse::<f64>();
208                    if let Ok(num) = num_float {
209                        Ok(Spanned {
210                            value: RonValue::Float(num),
211                            span: Span { start, end },
212                        })
213                    } else {
214                        Err(RonParseError { 
215                            span: Span { start, end }, 
216                            kind: RonErrorKind::InvalidNumber { text: number_str.to_string() } 
217                        })
218                    }
219                } else {
220                    let num_int = number_str.parse::<i64>();
221                    if let Ok(num) = num_int {
222                        Ok(Spanned {
223                            value: RonValue::Integer(num),
224                            span: Span { start, end },
225                        })
226                    } else {
227                        Err(RonParseError { 
228                            span: Span { start, end }, 
229                            kind: RonErrorKind::InvalidNumber { text: number_str.to_string() } 
230                        })
231                    }
232                }
233            },
234            Some(b) if b.is_ascii_alphabetic() => {
235                let identifier = self.parse_identifier()?;
236                let word = identifier.value.as_str();
237                let identifier_span = identifier.span;
238                match word {
239                    "true" => {
240                        Ok(Spanned { value: RonValue::Bool(true), span: identifier_span })
241                    },
242                    "false" => {
243                        Ok(Spanned { value: RonValue::Bool(false), span: identifier_span })
244                    }
245                    "None" => {
246                        Ok(Spanned { value: RonValue::Option(None), span: identifier_span })
247                    }
248                    "Some" => {
249                        self.skip_whitespace();
250                        self.expect_char(b'(')?;
251                        let inner = self.parse_value()?;
252                        self.expect_char(b')')?;
253                        Ok(Spanned { 
254                            value: RonValue::Option(Some(Box::new(inner))), 
255                            span: Span { start, end: self.position() } 
256                        })
257                    }
258                    _ => {
259                        Ok(Spanned { 
260                            value: RonValue::Identifier(word.to_string()), 
261                            span: identifier_span 
262                        })
263                    }
264                }
265            },
266            Some(b'[') => {
267                self.advance();
268                let mut elements = Vec::new();
269                loop {
270                    self.skip_whitespace();
271                    if let Some(b']') = self.peek() {
272                        break;
273                    }
274                    let value = self.parse_value()?;
275                    elements.push(value);
276                    self.skip_whitespace();
277                    if let Some(b',') = self.peek() {
278                        self.advance();
279                    }
280                }
281                self.expect_char(b']')?;
282                Ok(Spanned { 
283                    value: RonValue::List(elements), 
284                    span: Span { start, end: self.position() } 
285                })
286            },
287            Some(b'{') => {
288                self.advance();
289                let mut entries: Vec<(Spanned<RonValue>, Spanned<RonValue>)> = Vec::new();
290                loop {
291                    self.skip_whitespace();
292                    if let Some(b'}') = self.peek() {
293                        break;
294                    }
295                    let key = self.parse_value()?;
296                    self.skip_whitespace();
297                    self.expect_char(b':')?;
298                    self.skip_whitespace();
299                    let value = self.parse_value()?;
300                    entries.push((key, value));
301                    self.skip_whitespace();
302                    if let Some(b',') = self.peek() {
303                        self.advance();
304                    }
305                }
306                self.expect_char(b'}')?;
307                Ok(Spanned {
308                    value: RonValue::Map(entries),
309                    span: Span { start, end: self.position() },
310                })
311            },
312            Some(b'(') => {
313                self.advance();
314                let mut fields: Vec<(Spanned<String>, Spanned<RonValue>)> = Vec::new();
315                loop {
316                    self.skip_whitespace();
317                    if let Some(b')') = self.peek() {
318                        break;
319                    }
320                    let field = self.parse_identifier()?;
321                    self.skip_whitespace();
322                    self.expect_char(b':')?;
323                    self.skip_whitespace();
324                    let value = self.parse_value()?;
325                    fields.push((field, value));
326                    self.skip_whitespace();
327                    match self.peek() {
328                        Some(b',') => self.advance(),
329                        Some(_) => {}
330                        None => {
331                            return Err(RonParseError { 
332                                span: Span { start, end: self.position() } , 
333                                kind: RonErrorKind::UnexpectedToken { 
334                                    expected: "character".to_string(), 
335                                    found: "end of file".to_string() } 
336                                });
337                        }
338                    }
339                }
340                let close_span_start = self.position();
341                self.expect_char(b')')?;
342                let close_span = Span{ start: close_span_start, end: self.position() };
343                Ok(Spanned { 
344                    value: RonValue::Struct(RonStruct { fields, close_span }), 
345                    span: Span { start, end: self.position() } 
346                })
347            }
348            Some(b) => {
349                self.advance();
350                let end = self.position();
351                Err(RonParseError { 
352                    span: Span { start, end }, 
353                    kind: RonErrorKind::UnexpectedToken { 
354                        expected: "value".to_string(), 
355                        found: format!("{}", b as char) 
356                    } 
357                })
358            },
359            None => {
360                Err(RonParseError { 
361                    span: Span { start, end: start }, 
362                    kind: RonErrorKind::UnexpectedToken { 
363                        expected: "value".to_string(), 
364                        found: "end of file".to_string() 
365                    } 
366                })
367            }
368        }
369    }
370}
371
372/// Parses a RON data source string into a spanned value tree.
373///
374/// # Errors
375///
376/// Returns a [`RonParseError`] if the source contains syntax errors.
377pub fn parse_ron(source: &str) -> Result<Spanned<RonValue>, RonParseError> {
378    let mut parser = Parser::new(source);
379    parser.parse_value()
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    fn parser(source: &str) -> Parser<'_> {
387        Parser::new(source)
388    }
389
390    // ========================================================
391    // parse_value() — string parsing
392    // ========================================================
393
394    // Parses a simple quoted string.
395    #[test]
396    fn string_simple() {
397        let mut p = parser("\"hello\"");
398        let v = p.parse_value().unwrap();
399        assert_eq!(v.value, RonValue::String("hello".to_string()));
400    }
401
402    // Parses an empty string.
403    #[test]
404    fn string_empty() {
405        let mut p = parser("\"\"");
406        let v = p.parse_value().unwrap();
407        assert_eq!(v.value, RonValue::String("".to_string()));
408    }
409
410    // Parses a string with spaces.
411    #[test]
412    fn string_with_spaces() {
413        let mut p = parser("\"Ashborn Hound\"");
414        let v = p.parse_value().unwrap();
415        assert_eq!(v.value, RonValue::String("Ashborn Hound".to_string()));
416    }
417
418    // Escape sequence: \" becomes a literal quote.
419    #[test]
420    fn string_escaped_quote() {
421        let mut p = parser("\"say \\\"hi\\\"\"");
422        let v = p.parse_value().unwrap();
423        assert_eq!(v.value, RonValue::String("say \"hi\"".to_string()));
424    }
425
426    // Escape sequence: \\ becomes a single backslash.
427    #[test]
428    fn string_escaped_backslash() {
429        let mut p = parser("\"a\\\\b\"");
430        let v = p.parse_value().unwrap();
431        assert_eq!(v.value, RonValue::String("a\\b".to_string()));
432    }
433
434    // Escape sequence: \n becomes a newline.
435    #[test]
436    fn string_escaped_newline() {
437        let mut p = parser("\"line1\\nline2\"");
438        let v = p.parse_value().unwrap();
439        assert_eq!(v.value, RonValue::String("line1\nline2".to_string()));
440    }
441
442    // Escape sequence: \t becomes a tab.
443    #[test]
444    fn string_escaped_tab() {
445        let mut p = parser("\"col1\\tcol2\"");
446        let v = p.parse_value().unwrap();
447        assert_eq!(v.value, RonValue::String("col1\tcol2".to_string()));
448    }
449
450    // Unterminated string is an error.
451    #[test]
452    fn string_unterminated() {
453        let mut p = parser("\"hello");
454        let err = p.parse_value().unwrap_err();
455        assert_eq!(err.kind, RonErrorKind::UnterminatedString);
456    }
457
458    // ========================================================
459    // parse_value() — integer parsing
460    // ========================================================
461
462    // Parses a positive integer.
463    #[test]
464    fn integer_positive() {
465        let mut p = parser("42");
466        let v = p.parse_value().unwrap();
467        assert_eq!(v.value, RonValue::Integer(42));
468    }
469
470    // Parses zero.
471    #[test]
472    fn integer_zero() {
473        let mut p = parser("0");
474        let v = p.parse_value().unwrap();
475        assert_eq!(v.value, RonValue::Integer(0));
476    }
477
478    // Parses a negative integer.
479    #[test]
480    fn integer_negative() {
481        let mut p = parser("-7");
482        let v = p.parse_value().unwrap();
483        assert_eq!(v.value, RonValue::Integer(-7));
484    }
485
486    // ========================================================
487    // parse_value() — float parsing
488    // ========================================================
489
490    // Parses a simple float.
491    #[test]
492    fn float_simple() {
493        let mut p = parser("3.14");
494        let v = p.parse_value().unwrap();
495        assert_eq!(v.value, RonValue::Float(3.14));
496    }
497
498    // Parses a negative float.
499    #[test]
500    fn float_negative() {
501        let mut p = parser("-0.5");
502        let v = p.parse_value().unwrap();
503        assert_eq!(v.value, RonValue::Float(-0.5));
504    }
505
506    // Parses 1.0 as a float, not an integer.
507    #[test]
508    fn float_one_point_zero() {
509        let mut p = parser("1.0");
510        let v = p.parse_value().unwrap();
511        assert_eq!(v.value, RonValue::Float(1.0));
512    }
513
514    // ========================================================
515    // parse_value() — boolean parsing
516    // ========================================================
517
518    // Parses "true" as Bool(true).
519    #[test]
520    fn bool_true() {
521        let mut p = parser("true");
522        let v = p.parse_value().unwrap();
523        assert_eq!(v.value, RonValue::Bool(true));
524    }
525
526    // Parses "false" as Bool(false).
527    #[test]
528    fn bool_false() {
529        let mut p = parser("false");
530        let v = p.parse_value().unwrap();
531        assert_eq!(v.value, RonValue::Bool(false));
532    }
533
534    // ========================================================
535    // parse_value() — option parsing
536    // ========================================================
537
538    // Parses "None" as Option(None).
539    #[test]
540    fn option_none() {
541        let mut p = parser("None");
542        let v = p.parse_value().unwrap();
543        assert_eq!(v.value, RonValue::Option(None));
544    }
545
546    // Parses "Some(5)" as Option(Some(Integer(5))).
547    #[test]
548    fn option_some_integer() {
549        let mut p = parser("Some(5)");
550        let v = p.parse_value().unwrap();
551        if let RonValue::Option(Some(inner)) = &v.value {
552            assert_eq!(inner.value, RonValue::Integer(5));
553        } else {
554            panic!("expected Option(Some(...))");
555        }
556    }
557
558    // Parses "Some(\"hi\")" as Option(Some(String)).
559    #[test]
560    fn option_some_string() {
561        let mut p = parser("Some(\"hi\")");
562        let v = p.parse_value().unwrap();
563        if let RonValue::Option(Some(inner)) = &v.value {
564            assert_eq!(inner.value, RonValue::String("hi".to_string()));
565        } else {
566            panic!("expected Option(Some(...))");
567        }
568    }
569
570    // ========================================================
571    // parse_value() — identifier parsing
572    // ========================================================
573
574    // Bare identifier is parsed as Identifier.
575    #[test]
576    fn identifier_bare() {
577        let mut p = parser("Creature");
578        let v = p.parse_value().unwrap();
579        assert_eq!(v.value, RonValue::Identifier("Creature".to_string()));
580    }
581
582    // Another bare identifier.
583    #[test]
584    fn identifier_another() {
585        let mut p = parser("Sentinels");
586        let v = p.parse_value().unwrap();
587        assert_eq!(v.value, RonValue::Identifier("Sentinels".to_string()));
588    }
589
590    // ========================================================
591    // parse_value() — list parsing
592    // ========================================================
593
594    // Parses an empty list.
595    #[test]
596    fn list_empty() {
597        let mut p = parser("[]");
598        let v = p.parse_value().unwrap();
599        if let RonValue::List(elems) = &v.value {
600            assert!(elems.is_empty());
601        } else {
602            panic!("expected List");
603        }
604    }
605
606    // Parses a list with one element.
607    #[test]
608    fn list_single_element() {
609        let mut p = parser("[Creature]");
610        let v = p.parse_value().unwrap();
611        if let RonValue::List(elems) = &v.value {
612            assert_eq!(elems.len(), 1);
613            assert_eq!(elems[0].value, RonValue::Identifier("Creature".to_string()));
614        } else {
615            panic!("expected List");
616        }
617    }
618
619    // Parses a list with multiple elements.
620    #[test]
621    fn list_multiple_elements() {
622        let mut p = parser("[Creature, Trap, Artifact]");
623        let v = p.parse_value().unwrap();
624        if let RonValue::List(elems) = &v.value {
625            assert_eq!(elems.len(), 3);
626        } else {
627            panic!("expected List");
628        }
629    }
630
631    // Trailing comma in list is allowed.
632    #[test]
633    fn list_trailing_comma() {
634        let mut p = parser("[Creature, Trap,]");
635        let v = p.parse_value().unwrap();
636        if let RonValue::List(elems) = &v.value {
637            assert_eq!(elems.len(), 2);
638        } else {
639            panic!("expected List");
640        }
641    }
642
643    // List of strings.
644    #[test]
645    fn list_of_strings() {
646        let mut p = parser("[\"Vigilance\", \"Haste\"]");
647        let v = p.parse_value().unwrap();
648        if let RonValue::List(elems) = &v.value {
649            assert_eq!(elems.len(), 2);
650            assert_eq!(elems[0].value, RonValue::String("Vigilance".to_string()));
651            assert_eq!(elems[1].value, RonValue::String("Haste".to_string()));
652        } else {
653            panic!("expected List");
654        }
655    }
656
657    // ========================================================
658    // parse_value() — struct parsing
659    // ========================================================
660
661    // Parses an empty struct.
662    #[test]
663    fn struct_empty() {
664        let mut p = parser("()");
665        let v = p.parse_value().unwrap();
666        if let RonValue::Struct(s) = &v.value {
667            assert!(s.fields.is_empty());
668        } else {
669            panic!("expected Struct");
670        }
671    }
672
673    // Parses a struct with one field.
674    #[test]
675    fn struct_single_field() {
676        let mut p = parser("(name: \"Ashborn Hound\")");
677        let v = p.parse_value().unwrap();
678        if let RonValue::Struct(s) = &v.value {
679            assert_eq!(s.fields.len(), 1);
680            assert_eq!(s.fields[0].0.value, "name");
681            assert_eq!(s.fields[0].1.value, RonValue::String("Ashborn Hound".to_string()));
682        } else {
683            panic!("expected Struct");
684        }
685    }
686
687    // Parses a struct with multiple fields.
688    #[test]
689    fn struct_multiple_fields() {
690        let mut p = parser("(name: \"foo\", age: 5)");
691        let v = p.parse_value().unwrap();
692        if let RonValue::Struct(s) = &v.value {
693            assert_eq!(s.fields.len(), 2);
694        } else {
695            panic!("expected Struct");
696        }
697    }
698
699    // Trailing comma in struct is allowed.
700    #[test]
701    fn struct_trailing_comma() {
702        let mut p = parser("(name: \"foo\",)");
703        let v = p.parse_value().unwrap();
704        if let RonValue::Struct(s) = &v.value {
705            assert_eq!(s.fields.len(), 1);
706        } else {
707            panic!("expected Struct");
708        }
709    }
710
711    // Struct captures close_span for the closing paren.
712    #[test]
713    fn struct_close_span_captured() {
714        let mut p = parser("(x: 1)");
715        let v = p.parse_value().unwrap();
716        if let RonValue::Struct(s) = &v.value {
717            assert_eq!(s.close_span.start.offset, 5);
718            assert_eq!(s.close_span.end.offset, 6);
719        } else {
720            panic!("expected Struct");
721        }
722    }
723
724    // Nested struct.
725    #[test]
726    fn struct_nested() {
727        let mut p = parser("(cost: (generic: 2, sigil: 1))");
728        let v = p.parse_value().unwrap();
729        if let RonValue::Struct(s) = &v.value {
730            assert_eq!(s.fields.len(), 1);
731            assert_eq!(s.fields[0].0.value, "cost");
732            if let RonValue::Struct(inner) = &s.fields[0].1.value {
733                assert_eq!(inner.fields.len(), 2);
734            } else {
735                panic!("expected nested Struct");
736            }
737        } else {
738            panic!("expected Struct");
739        }
740    }
741
742    // ========================================================
743    // parse_value() — whitespace and comments
744    // ========================================================
745
746    // Leading whitespace is skipped.
747    #[test]
748    fn whitespace_leading() {
749        let mut p = parser("  42");
750        let v = p.parse_value().unwrap();
751        assert_eq!(v.value, RonValue::Integer(42));
752    }
753
754    // Comments are skipped.
755    #[test]
756    fn comment_before_value() {
757        let mut p = parser("// comment\n42");
758        let v = p.parse_value().unwrap();
759        assert_eq!(v.value, RonValue::Integer(42));
760    }
761
762    // ========================================================
763    // parse_value() — span accuracy
764    // ========================================================
765
766    // Span start is after whitespace, not before.
767    #[test]
768    fn span_starts_after_whitespace() {
769        let mut p = parser("  42");
770        let v = p.parse_value().unwrap();
771        assert_eq!(v.span.start.offset, 2);
772    }
773
774    // Span covers the full value.
775    #[test]
776    fn span_covers_string() {
777        let mut p = parser("\"hello\"");
778        let v = p.parse_value().unwrap();
779        assert_eq!(v.span.start.offset, 0);
780        assert_eq!(v.span.end.offset, 7);
781    }
782
783    // ========================================================
784    // parse_value() — error cases
785    // ========================================================
786
787    // Empty input is an error.
788    #[test]
789    fn error_empty_input() {
790        let mut p = parser("");
791        let err = p.parse_value().unwrap_err();
792        match err.kind {
793            RonErrorKind::UnexpectedToken { found, .. } => {
794                assert_eq!(found, "end of file");
795            }
796            other => panic!("expected UnexpectedToken, got {:?}", other),
797        }
798    }
799
800    // Unexpected character is an error.
801    #[test]
802    fn error_unexpected_char() {
803        let mut p = parser("@");
804        assert!(p.parse_value().is_err());
805    }
806
807    // ========================================================
808    // parse_ron() integration tests
809    // ========================================================
810
811    // Parses a complete card-like struct.
812    #[test]
813    fn ron_full_struct() {
814        let source = r#"(
815            name: "Ashborn Hound",
816            card_types: [Creature],
817            legendary: false,
818            power: Some(1),
819            toughness: None,
820            keywords: [],
821            flavor_text: "placeholder",
822        )"#;
823        let v = parse_ron(source).unwrap();
824        if let RonValue::Struct(s) = &v.value {
825            assert_eq!(s.fields.len(), 7);
826            assert_eq!(s.fields[0].0.value, "name");
827            assert_eq!(s.fields[0].1.value, RonValue::String("Ashborn Hound".to_string()));
828        } else {
829            panic!("expected Struct");
830        }
831    }
832
833    // ========================================================
834    // parse_value() — map parsing
835    // ========================================================
836
837    // Parses an empty map.
838    #[test]
839    fn map_empty() {
840        let mut p = parser("{}");
841        let v = p.parse_value().unwrap();
842        if let RonValue::Map(entries) = &v.value {
843            assert!(entries.is_empty());
844        } else {
845            panic!("expected Map");
846        }
847    }
848
849    // Parses a map with string keys.
850    #[test]
851    fn map_string_keys() {
852        let mut p = parser("{\"str\": 5, \"dex\": 3}");
853        let v = p.parse_value().unwrap();
854        if let RonValue::Map(entries) = &v.value {
855            assert_eq!(entries.len(), 2);
856            assert_eq!(entries[0].0.value, RonValue::String("str".to_string()));
857            assert_eq!(entries[0].1.value, RonValue::Integer(5));
858        } else {
859            panic!("expected Map");
860        }
861    }
862
863    // Parses a map with integer keys.
864    #[test]
865    fn map_integer_keys() {
866        let mut p = parser("{1: \"one\", 2: \"two\"}");
867        let v = p.parse_value().unwrap();
868        if let RonValue::Map(entries) = &v.value {
869            assert_eq!(entries.len(), 2);
870            assert_eq!(entries[0].0.value, RonValue::Integer(1));
871        } else {
872            panic!("expected Map");
873        }
874    }
875
876    // Parses a map with trailing comma.
877    #[test]
878    fn map_trailing_comma() {
879        let mut p = parser("{\"a\": 1,}");
880        let v = p.parse_value().unwrap();
881        if let RonValue::Map(entries) = &v.value {
882            assert_eq!(entries.len(), 1);
883        } else {
884            panic!("expected Map");
885        }
886    }
887}