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