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                self.skip_whitespace();
315
316                // Empty parens → empty struct
317                if self.peek() == Some(b')') {
318                    let close_span_start = self.position();
319                    self.expect_char(b')')?;
320                    let close_span = Span { start: close_span_start, end: self.position() };
321                    return Ok(Spanned {
322                        value: RonValue::Struct(RonStruct { fields: Vec::new(), close_span }),
323                        span: Span { start, end: self.position() },
324                    });
325                }
326
327                // Disambiguate struct vs tuple by probing for identifier followed by ':'
328                let probe = (self.offset, self.line, self.column);
329                let is_struct = if let Ok(_id) = self.parse_identifier() {
330                    self.skip_whitespace();
331                    
332                    self.peek() == Some(b':')
333                } else {
334                    false
335                };
336                // Rewind to after '('
337                self.offset = probe.0;
338                self.line = probe.1;
339                self.column = probe.2;
340
341                if is_struct {
342                    // Parse as struct
343                    let mut fields: Vec<(Spanned<String>, Spanned<RonValue>)> = Vec::new();
344                    loop {
345                        self.skip_whitespace();
346                        if let Some(b')') = self.peek() {
347                            break;
348                        }
349                        let field = self.parse_identifier()?;
350                        self.skip_whitespace();
351                        self.expect_char(b':')?;
352                        self.skip_whitespace();
353                        let value = self.parse_value()?;
354                        fields.push((field, value));
355                        self.skip_whitespace();
356                        match self.peek() {
357                            Some(b',') => self.advance(),
358                            Some(_) => {}
359                            None => {
360                                return Err(RonParseError {
361                                    span: Span { start, end: self.position() },
362                                    kind: RonErrorKind::UnexpectedToken {
363                                        expected: "character".to_string(),
364                                        found: "end of file".to_string(),
365                                    },
366                                });
367                            }
368                        }
369                    }
370                    let close_span_start = self.position();
371                    self.expect_char(b')')?;
372                    let close_span = Span { start: close_span_start, end: self.position() };
373                    Ok(Spanned {
374                        value: RonValue::Struct(RonStruct { fields, close_span }),
375                        span: Span { start, end: self.position() },
376                    })
377                } else {
378                    // Parse as tuple
379                    let mut elements = Vec::new();
380                    loop {
381                        self.skip_whitespace();
382                        if self.peek() == Some(b')') {
383                            break;
384                        }
385                        let value = self.parse_value()?;
386                        elements.push(value);
387                        self.skip_whitespace();
388                        if self.peek() == Some(b',') {
389                            self.advance();
390                        }
391                    }
392                    self.expect_char(b')')?;
393                    Ok(Spanned {
394                        value: RonValue::Tuple(elements),
395                        span: Span { start, end: self.position() },
396                    })
397                }
398            }
399            Some(b) => {
400                self.advance();
401                let end = self.position();
402                Err(RonParseError { 
403                    span: Span { start, end }, 
404                    kind: RonErrorKind::UnexpectedToken { 
405                        expected: "value".to_string(), 
406                        found: format!("{}", b as char) 
407                    } 
408                })
409            },
410            None => {
411                Err(RonParseError { 
412                    span: Span { start, end: start }, 
413                    kind: RonErrorKind::UnexpectedToken { 
414                        expected: "value".to_string(), 
415                        found: "end of file".to_string() 
416                    } 
417                })
418            }
419        }
420    }
421}
422
423/// Parses a RON data source string into a spanned value tree.
424///
425/// # Errors
426///
427/// Returns a [`RonParseError`] if the source contains syntax errors.
428pub fn parse_ron(source: &str) -> Result<Spanned<RonValue>, RonParseError> {
429    let mut parser = Parser::new(source);
430    parser.parse_value()
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    fn parser(source: &str) -> Parser<'_> {
438        Parser::new(source)
439    }
440
441    // ========================================================
442    // parse_value() — string parsing
443    // ========================================================
444
445    // Parses a simple quoted string.
446    #[test]
447    fn string_simple() {
448        let mut p = parser("\"hello\"");
449        let v = p.parse_value().unwrap();
450        assert_eq!(v.value, RonValue::String("hello".to_string()));
451    }
452
453    // Parses an empty string.
454    #[test]
455    fn string_empty() {
456        let mut p = parser("\"\"");
457        let v = p.parse_value().unwrap();
458        assert_eq!(v.value, RonValue::String("".to_string()));
459    }
460
461    // Parses a string with spaces.
462    #[test]
463    fn string_with_spaces() {
464        let mut p = parser("\"Ashborn Hound\"");
465        let v = p.parse_value().unwrap();
466        assert_eq!(v.value, RonValue::String("Ashborn Hound".to_string()));
467    }
468
469    // Escape sequence: \" becomes a literal quote.
470    #[test]
471    fn string_escaped_quote() {
472        let mut p = parser("\"say \\\"hi\\\"\"");
473        let v = p.parse_value().unwrap();
474        assert_eq!(v.value, RonValue::String("say \"hi\"".to_string()));
475    }
476
477    // Escape sequence: \\ becomes a single backslash.
478    #[test]
479    fn string_escaped_backslash() {
480        let mut p = parser("\"a\\\\b\"");
481        let v = p.parse_value().unwrap();
482        assert_eq!(v.value, RonValue::String("a\\b".to_string()));
483    }
484
485    // Escape sequence: \n becomes a newline.
486    #[test]
487    fn string_escaped_newline() {
488        let mut p = parser("\"line1\\nline2\"");
489        let v = p.parse_value().unwrap();
490        assert_eq!(v.value, RonValue::String("line1\nline2".to_string()));
491    }
492
493    // Escape sequence: \t becomes a tab.
494    #[test]
495    fn string_escaped_tab() {
496        let mut p = parser("\"col1\\tcol2\"");
497        let v = p.parse_value().unwrap();
498        assert_eq!(v.value, RonValue::String("col1\tcol2".to_string()));
499    }
500
501    // Unterminated string is an error.
502    #[test]
503    fn string_unterminated() {
504        let mut p = parser("\"hello");
505        let err = p.parse_value().unwrap_err();
506        assert_eq!(err.kind, RonErrorKind::UnterminatedString);
507    }
508
509    // ========================================================
510    // parse_value() — integer parsing
511    // ========================================================
512
513    // Parses a positive integer.
514    #[test]
515    fn integer_positive() {
516        let mut p = parser("42");
517        let v = p.parse_value().unwrap();
518        assert_eq!(v.value, RonValue::Integer(42));
519    }
520
521    // Parses zero.
522    #[test]
523    fn integer_zero() {
524        let mut p = parser("0");
525        let v = p.parse_value().unwrap();
526        assert_eq!(v.value, RonValue::Integer(0));
527    }
528
529    // Parses a negative integer.
530    #[test]
531    fn integer_negative() {
532        let mut p = parser("-7");
533        let v = p.parse_value().unwrap();
534        assert_eq!(v.value, RonValue::Integer(-7));
535    }
536
537    // ========================================================
538    // parse_value() — float parsing
539    // ========================================================
540
541    // Parses a simple float.
542    #[test]
543    fn float_simple() {
544        let mut p = parser("3.14");
545        let v = p.parse_value().unwrap();
546        assert_eq!(v.value, RonValue::Float(3.14));
547    }
548
549    // Parses a negative float.
550    #[test]
551    fn float_negative() {
552        let mut p = parser("-0.5");
553        let v = p.parse_value().unwrap();
554        assert_eq!(v.value, RonValue::Float(-0.5));
555    }
556
557    // Parses 1.0 as a float, not an integer.
558    #[test]
559    fn float_one_point_zero() {
560        let mut p = parser("1.0");
561        let v = p.parse_value().unwrap();
562        assert_eq!(v.value, RonValue::Float(1.0));
563    }
564
565    // ========================================================
566    // parse_value() — boolean parsing
567    // ========================================================
568
569    // Parses "true" as Bool(true).
570    #[test]
571    fn bool_true() {
572        let mut p = parser("true");
573        let v = p.parse_value().unwrap();
574        assert_eq!(v.value, RonValue::Bool(true));
575    }
576
577    // Parses "false" as Bool(false).
578    #[test]
579    fn bool_false() {
580        let mut p = parser("false");
581        let v = p.parse_value().unwrap();
582        assert_eq!(v.value, RonValue::Bool(false));
583    }
584
585    // ========================================================
586    // parse_value() — option parsing
587    // ========================================================
588
589    // Parses "None" as Option(None).
590    #[test]
591    fn option_none() {
592        let mut p = parser("None");
593        let v = p.parse_value().unwrap();
594        assert_eq!(v.value, RonValue::Option(None));
595    }
596
597    // Parses "Some(5)" as Option(Some(Integer(5))).
598    #[test]
599    fn option_some_integer() {
600        let mut p = parser("Some(5)");
601        let v = p.parse_value().unwrap();
602        if let RonValue::Option(Some(inner)) = &v.value {
603            assert_eq!(inner.value, RonValue::Integer(5));
604        } else {
605            panic!("expected Option(Some(...))");
606        }
607    }
608
609    // Parses "Some(\"hi\")" as Option(Some(String)).
610    #[test]
611    fn option_some_string() {
612        let mut p = parser("Some(\"hi\")");
613        let v = p.parse_value().unwrap();
614        if let RonValue::Option(Some(inner)) = &v.value {
615            assert_eq!(inner.value, RonValue::String("hi".to_string()));
616        } else {
617            panic!("expected Option(Some(...))");
618        }
619    }
620
621    // ========================================================
622    // parse_value() — identifier parsing
623    // ========================================================
624
625    // Bare identifier is parsed as Identifier.
626    #[test]
627    fn identifier_bare() {
628        let mut p = parser("Creature");
629        let v = p.parse_value().unwrap();
630        assert_eq!(v.value, RonValue::Identifier("Creature".to_string()));
631    }
632
633    // Another bare identifier.
634    #[test]
635    fn identifier_another() {
636        let mut p = parser("Sentinels");
637        let v = p.parse_value().unwrap();
638        assert_eq!(v.value, RonValue::Identifier("Sentinels".to_string()));
639    }
640
641    // ========================================================
642    // parse_value() — list parsing
643    // ========================================================
644
645    // Parses an empty list.
646    #[test]
647    fn list_empty() {
648        let mut p = parser("[]");
649        let v = p.parse_value().unwrap();
650        if let RonValue::List(elems) = &v.value {
651            assert!(elems.is_empty());
652        } else {
653            panic!("expected List");
654        }
655    }
656
657    // Parses a list with one element.
658    #[test]
659    fn list_single_element() {
660        let mut p = parser("[Creature]");
661        let v = p.parse_value().unwrap();
662        if let RonValue::List(elems) = &v.value {
663            assert_eq!(elems.len(), 1);
664            assert_eq!(elems[0].value, RonValue::Identifier("Creature".to_string()));
665        } else {
666            panic!("expected List");
667        }
668    }
669
670    // Parses a list with multiple elements.
671    #[test]
672    fn list_multiple_elements() {
673        let mut p = parser("[Creature, Trap, Artifact]");
674        let v = p.parse_value().unwrap();
675        if let RonValue::List(elems) = &v.value {
676            assert_eq!(elems.len(), 3);
677        } else {
678            panic!("expected List");
679        }
680    }
681
682    // Trailing comma in list is allowed.
683    #[test]
684    fn list_trailing_comma() {
685        let mut p = parser("[Creature, Trap,]");
686        let v = p.parse_value().unwrap();
687        if let RonValue::List(elems) = &v.value {
688            assert_eq!(elems.len(), 2);
689        } else {
690            panic!("expected List");
691        }
692    }
693
694    // List of strings.
695    #[test]
696    fn list_of_strings() {
697        let mut p = parser("[\"Vigilance\", \"Haste\"]");
698        let v = p.parse_value().unwrap();
699        if let RonValue::List(elems) = &v.value {
700            assert_eq!(elems.len(), 2);
701            assert_eq!(elems[0].value, RonValue::String("Vigilance".to_string()));
702            assert_eq!(elems[1].value, RonValue::String("Haste".to_string()));
703        } else {
704            panic!("expected List");
705        }
706    }
707
708    // ========================================================
709    // parse_value() — struct parsing
710    // ========================================================
711
712    // Parses an empty struct.
713    #[test]
714    fn struct_empty() {
715        let mut p = parser("()");
716        let v = p.parse_value().unwrap();
717        if let RonValue::Struct(s) = &v.value {
718            assert!(s.fields.is_empty());
719        } else {
720            panic!("expected Struct");
721        }
722    }
723
724    // Parses a struct with one field.
725    #[test]
726    fn struct_single_field() {
727        let mut p = parser("(name: \"Ashborn Hound\")");
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, "name");
732            assert_eq!(s.fields[0].1.value, RonValue::String("Ashborn Hound".to_string()));
733        } else {
734            panic!("expected Struct");
735        }
736    }
737
738    // Parses a struct with multiple fields.
739    #[test]
740    fn struct_multiple_fields() {
741        let mut p = parser("(name: \"foo\", age: 5)");
742        let v = p.parse_value().unwrap();
743        if let RonValue::Struct(s) = &v.value {
744            assert_eq!(s.fields.len(), 2);
745        } else {
746            panic!("expected Struct");
747        }
748    }
749
750    // Trailing comma in struct is allowed.
751    #[test]
752    fn struct_trailing_comma() {
753        let mut p = parser("(name: \"foo\",)");
754        let v = p.parse_value().unwrap();
755        if let RonValue::Struct(s) = &v.value {
756            assert_eq!(s.fields.len(), 1);
757        } else {
758            panic!("expected Struct");
759        }
760    }
761
762    // Struct captures close_span for the closing paren.
763    #[test]
764    fn struct_close_span_captured() {
765        let mut p = parser("(x: 1)");
766        let v = p.parse_value().unwrap();
767        if let RonValue::Struct(s) = &v.value {
768            assert_eq!(s.close_span.start.offset, 5);
769            assert_eq!(s.close_span.end.offset, 6);
770        } else {
771            panic!("expected Struct");
772        }
773    }
774
775    // Nested struct.
776    #[test]
777    fn struct_nested() {
778        let mut p = parser("(cost: (generic: 2, sigil: 1))");
779        let v = p.parse_value().unwrap();
780        if let RonValue::Struct(s) = &v.value {
781            assert_eq!(s.fields.len(), 1);
782            assert_eq!(s.fields[0].0.value, "cost");
783            if let RonValue::Struct(inner) = &s.fields[0].1.value {
784                assert_eq!(inner.fields.len(), 2);
785            } else {
786                panic!("expected nested Struct");
787            }
788        } else {
789            panic!("expected Struct");
790        }
791    }
792
793    // ========================================================
794    // parse_value() — whitespace and comments
795    // ========================================================
796
797    // Leading whitespace is skipped.
798    #[test]
799    fn whitespace_leading() {
800        let mut p = parser("  42");
801        let v = p.parse_value().unwrap();
802        assert_eq!(v.value, RonValue::Integer(42));
803    }
804
805    // Comments are skipped.
806    #[test]
807    fn comment_before_value() {
808        let mut p = parser("// comment\n42");
809        let v = p.parse_value().unwrap();
810        assert_eq!(v.value, RonValue::Integer(42));
811    }
812
813    // ========================================================
814    // parse_value() — span accuracy
815    // ========================================================
816
817    // Span start is after whitespace, not before.
818    #[test]
819    fn span_starts_after_whitespace() {
820        let mut p = parser("  42");
821        let v = p.parse_value().unwrap();
822        assert_eq!(v.span.start.offset, 2);
823    }
824
825    // Span covers the full value.
826    #[test]
827    fn span_covers_string() {
828        let mut p = parser("\"hello\"");
829        let v = p.parse_value().unwrap();
830        assert_eq!(v.span.start.offset, 0);
831        assert_eq!(v.span.end.offset, 7);
832    }
833
834    // ========================================================
835    // parse_value() — error cases
836    // ========================================================
837
838    // Empty input is an error.
839    #[test]
840    fn error_empty_input() {
841        let mut p = parser("");
842        let err = p.parse_value().unwrap_err();
843        match err.kind {
844            RonErrorKind::UnexpectedToken { found, .. } => {
845                assert_eq!(found, "end of file");
846            }
847            other => panic!("expected UnexpectedToken, got {:?}", other),
848        }
849    }
850
851    // Unexpected character is an error.
852    #[test]
853    fn error_unexpected_char() {
854        let mut p = parser("@");
855        assert!(p.parse_value().is_err());
856    }
857
858    // ========================================================
859    // parse_ron() integration tests
860    // ========================================================
861
862    // Parses a complete card-like struct.
863    #[test]
864    fn ron_full_struct() {
865        let source = r#"(
866            name: "Ashborn Hound",
867            card_types: [Creature],
868            legendary: false,
869            power: Some(1),
870            toughness: None,
871            keywords: [],
872            flavor_text: "placeholder",
873        )"#;
874        let v = parse_ron(source).unwrap();
875        if let RonValue::Struct(s) = &v.value {
876            assert_eq!(s.fields.len(), 7);
877            assert_eq!(s.fields[0].0.value, "name");
878            assert_eq!(s.fields[0].1.value, RonValue::String("Ashborn Hound".to_string()));
879        } else {
880            panic!("expected Struct");
881        }
882    }
883
884    // ========================================================
885    // parse_value() — map parsing
886    // ========================================================
887
888    // Parses an empty map.
889    #[test]
890    fn map_empty() {
891        let mut p = parser("{}");
892        let v = p.parse_value().unwrap();
893        if let RonValue::Map(entries) = &v.value {
894            assert!(entries.is_empty());
895        } else {
896            panic!("expected Map");
897        }
898    }
899
900    // Parses a map with string keys.
901    #[test]
902    fn map_string_keys() {
903        let mut p = parser("{\"str\": 5, \"dex\": 3}");
904        let v = p.parse_value().unwrap();
905        if let RonValue::Map(entries) = &v.value {
906            assert_eq!(entries.len(), 2);
907            assert_eq!(entries[0].0.value, RonValue::String("str".to_string()));
908            assert_eq!(entries[0].1.value, RonValue::Integer(5));
909        } else {
910            panic!("expected Map");
911        }
912    }
913
914    // Parses a map with integer keys.
915    #[test]
916    fn map_integer_keys() {
917        let mut p = parser("{1: \"one\", 2: \"two\"}");
918        let v = p.parse_value().unwrap();
919        if let RonValue::Map(entries) = &v.value {
920            assert_eq!(entries.len(), 2);
921            assert_eq!(entries[0].0.value, RonValue::Integer(1));
922        } else {
923            panic!("expected Map");
924        }
925    }
926
927    // Parses a map with trailing comma.
928    #[test]
929    fn map_trailing_comma() {
930        let mut p = parser("{\"a\": 1,}");
931        let v = p.parse_value().unwrap();
932        if let RonValue::Map(entries) = &v.value {
933            assert_eq!(entries.len(), 1);
934        } else {
935            panic!("expected Map");
936        }
937    }
938
939    // ========================================================
940    // parse_value() — tuple parsing
941    // ========================================================
942
943    // Parses a tuple with two elements.
944    #[test]
945    fn tuple_two_elements() {
946        let mut p = parser("(1.0, 2.5)");
947        let v = p.parse_value().unwrap();
948        if let RonValue::Tuple(elems) = &v.value {
949            assert_eq!(elems.len(), 2);
950            assert_eq!(elems[0].value, RonValue::Float(1.0));
951            assert_eq!(elems[1].value, RonValue::Float(2.5));
952        } else {
953            panic!("expected Tuple, got {:?}", v.value);
954        }
955    }
956
957    // Parses a tuple with mixed types.
958    #[test]
959    fn tuple_mixed_types() {
960        let mut p = parser("(\"hello\", 42, true)");
961        let v = p.parse_value().unwrap();
962        if let RonValue::Tuple(elems) = &v.value {
963            assert_eq!(elems.len(), 3);
964            assert_eq!(elems[0].value, RonValue::String("hello".to_string()));
965            assert_eq!(elems[1].value, RonValue::Integer(42));
966            assert_eq!(elems[2].value, RonValue::Bool(true));
967        } else {
968            panic!("expected Tuple, got {:?}", v.value);
969        }
970    }
971
972    // Struct still parses correctly after tuple disambiguation.
973    #[test]
974    fn struct_still_parses() {
975        let mut p = parser("(name: \"foo\", age: 5)");
976        let v = p.parse_value().unwrap();
977        if let RonValue::Struct(s) = &v.value {
978            assert_eq!(s.fields.len(), 2);
979        } else {
980            panic!("expected Struct, got {:?}", v.value);
981        }
982    }
983
984    // Empty parens parse as empty struct.
985    #[test]
986    fn empty_parens_is_struct() {
987        let mut p = parser("()");
988        let v = p.parse_value().unwrap();
989        assert!(matches!(v.value, RonValue::Struct(_)));
990    }
991
992    // Single-element tuple with trailing comma.
993    #[test]
994    fn tuple_single_element_trailing_comma() {
995        let mut p = parser("(42,)");
996        let v = p.parse_value().unwrap();
997        if let RonValue::Tuple(elems) = &v.value {
998            assert_eq!(elems.len(), 1);
999        } else {
1000            panic!("expected Tuple, got {:?}", v.value);
1001        }
1002    }
1003}