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