Skip to main content

ron_schema/schema/
parser.rs

1/*************************
2 * Author: Bradley Hunter
3 */
4
5use crate::span::{Position, Span, Spanned};
6use crate::error::{SchemaParseError, SchemaErrorKind};
7use crate::ron::RonValue;
8use crate::ron::parser::Parser as RonParser;
9use super::{SchemaType, FieldDef, StructDef, EnumDef, HashSet, Schema, HashMap};
10
11#[derive(Debug)]
12struct Parser<'a> {
13    source: &'a str,
14    bytes: &'a [u8],
15    offset: usize,
16    line: usize,
17    column: usize,
18}
19
20impl<'a> Parser<'a> {
21    fn new(source: &'a str) -> Self {
22        Self { source, bytes: source.as_bytes(), offset: 0, line: 1, column: 1 }
23    }
24
25    fn position(&self) -> Position {
26        Position { offset: self.offset, line: self.line, column: self.column }
27    }
28
29    fn peek(&self) -> Option<u8> {
30        self.bytes.get(self.offset).copied()
31    }
32
33    fn advance(&mut self) {
34        if let Some(byte) = self.peek() {
35            if byte == b'\n'{
36                self.column = 1;
37                self.line += 1;
38            } else {
39                self.column += 1;
40            }
41            self.offset += 1;
42        } 
43    }
44
45    fn skip_whitespace(&mut self) {
46        loop {
47            match self.peek() {
48                Some(b' ' | b'\t' | b'\n' | b'\r') => self.advance(),
49                Some(b'/') if self.bytes.get(self.offset + 1) == Some(&b'/') => {
50                    while self.peek().is_some_and(|b| b != b'\n') {
51                        self.advance();
52                    }
53                }
54                _ => break,
55            }
56        }
57    }
58
59    fn expect_char(&mut self, expected: u8) -> Result<(), SchemaParseError> {
60        let start = self.position();
61        match self.peek() {
62            Some(b) if b == expected => {
63                self.advance();
64                Ok(())
65            },
66            Some(b) => {
67                self.advance();
68                let end = self.position();
69                Err(SchemaParseError { 
70                    span: Span { 
71                        start, 
72                        end 
73                    }, 
74                    kind: SchemaErrorKind::UnexpectedToken { 
75                        expected: format!("'{}'", expected as char), 
76                        found: format!("'{}'", b as char) 
77                    } 
78                })
79            },
80            None => {
81                Err(SchemaParseError { 
82                    span: Span { 
83                        start, 
84                        end: start 
85                    }, 
86                    kind: SchemaErrorKind::UnexpectedToken { 
87                        expected: format!("'{}'", expected as char), 
88                        found: "end of input".to_string() 
89                    } 
90                })
91            }
92        }
93    }
94
95    fn parse_identifier(&mut self) -> Result<Spanned<String>, SchemaParseError> {
96        let start = self.position();
97
98        // Check for valid identifier start
99        match self.peek() {
100            Some(b) if b.is_ascii_alphabetic() || b == b'_' => {},
101            Some(b) => {
102                self.advance();
103                let end = self.position();
104                return Err(SchemaParseError {
105                    span: Span { start, end },
106                    kind: SchemaErrorKind::UnexpectedToken {
107                        expected: "identifier".to_string(),
108                        found: format!("'{}'", b as char),
109                    },
110                });
111            },
112            None => {
113                return Err(SchemaParseError {
114                    span: Span { start, end: start },
115                    kind: SchemaErrorKind::UnexpectedToken {
116                        expected: "identifier".to_string(),
117                        found: "end of input".to_string(),
118                    },
119                });
120            },
121        }
122
123        // Consume all identifier continuation characters
124        while self.peek().is_some_and(|b| b.is_ascii_alphanumeric() || b == b'_') {
125            self.advance();
126        }
127
128        // Slice out the identifier text
129        let end = self.position();
130        Ok(Spanned {
131            value: self.source[start.offset..end.offset].to_string(),
132            span: Span { start, end },
133        })
134    }
135
136    #[allow(clippy::too_many_lines)]
137    fn parse_type(&mut self) -> Result<Spanned<SchemaType>, SchemaParseError> {
138        self.skip_whitespace();
139        let start = self.position();
140
141        match self.peek() {
142            Some(b'[') => {
143                // List: consume '[', parse inner type, expect ']'
144                self.advance();
145                self.skip_whitespace();
146                let inner = self.parse_type()?;
147                self.skip_whitespace();
148                self.expect_char(b']')?;
149                let end = self.position();
150                Ok(Spanned {
151                    value: SchemaType::List(Box::new(inner.value)),
152                    span: Span { start, end },
153                })
154            }
155            Some(b'{') => {
156                // Map: consume '{', parse key type, expect ':', parse value type, expect '}'
157                self.advance();
158                self.skip_whitespace();
159                let key_type = self.parse_type()?;
160                // Validate key type is String, Integer, or EnumRef
161                match &key_type.value {
162                    SchemaType::String | SchemaType::Integer | SchemaType::EnumRef(_) => {}
163                    _ => {
164                        return Err(SchemaParseError {
165                            span: key_type.span,
166                            kind: SchemaErrorKind::InvalidMapKeyType {
167                                found: format!("{:?}", key_type.value),
168                            },
169                        });
170                    }
171                }
172                self.skip_whitespace();
173                self.expect_char(b':')?;
174                self.skip_whitespace();
175                let value_type = self.parse_type()?;
176                self.skip_whitespace();
177                self.expect_char(b'}')?;
178                let end = self.position();
179                Ok(Spanned {
180                    value: SchemaType::Map(Box::new(key_type.value), Box::new(value_type.value)),
181                    span: Span { start, end },
182                })
183            }
184            Some(b'(') => {
185                // Disambiguate struct vs tuple:
186                // Save position, consume '(', skip whitespace.
187                // If ')' → empty struct. If identifier followed by ':' → struct.
188                // Otherwise → tuple (comma-separated types).
189                let saved = (self.offset, self.line, self.column);
190                self.advance(); // consume '('
191                self.skip_whitespace();
192
193                let is_struct = if self.peek() == Some(b')') {
194                    true // empty parens → treat as empty struct
195                } else {
196                    // Try to determine if this is name: Type (struct) or Type, Type (tuple)
197                    let probe_pos = (self.offset, self.line, self.column);
198                    let is_field = if let Ok(_id) = self.parse_identifier() {
199                        self.skip_whitespace();
200                        
201                        self.peek() == Some(b':')
202                    } else {
203                        false
204                    };
205                    // Rewind to after '('
206                    self.offset = probe_pos.0;
207                    self.line = probe_pos.1;
208                    self.column = probe_pos.2;
209                    is_field
210                };
211
212                // Rewind to before '(' and parse as struct or tuple
213                self.offset = saved.0;
214                self.line = saved.1;
215                self.column = saved.2;
216
217                if is_struct {
218                    let struct_def = self.parse_struct()?;
219                    let end = self.position();
220                    Ok(Spanned {
221                        value: SchemaType::Struct(struct_def),
222                        span: Span { start, end },
223                    })
224                } else {
225                    let types = self.parse_tuple_type()?;
226                    let end = self.position();
227                    Ok(Spanned {
228                        value: SchemaType::Tuple(types),
229                        span: Span { start, end },
230                    })
231                }
232            }
233            Some(b) if b.is_ascii_alphabetic() => {
234                // Identifier: could be primitive, Option, or EnumRef
235                let id = self.parse_identifier()?;
236                match id.value.as_str() {
237                    "String" => Ok(Spanned { value: SchemaType::String, span: id.span }),
238                    "Integer" => Ok(Spanned { value: SchemaType::Integer, span: id.span }),
239                    "Float" => Ok(Spanned { value: SchemaType::Float, span: id.span }),
240                    "Bool" => Ok(Spanned { value: SchemaType::Bool, span: id.span }),
241                    "Option" => {
242                        // expect '(', parse inner type, expect ')'
243                        self.skip_whitespace();
244                        self.expect_char(b'(')?;
245                        self.skip_whitespace();
246                        let inner = self.parse_type()?;
247                        self.skip_whitespace();
248                        self.expect_char(b')')?;
249                        let end = self.position();
250                        Ok(Spanned {
251                            value: SchemaType::Option(Box::new(inner.value)),
252                            span: Span { start, end },
253                        })
254                    }
255                    _ => Ok(Spanned { value: SchemaType::EnumRef(id.value), span: id.span }),
256                }
257            }
258            Some(b) => {
259                // Error: unexpected character
260                self.advance();
261                let end = self.position();
262                Err(SchemaParseError {
263                    span: Span { start, end },
264                    kind: SchemaErrorKind::UnexpectedToken {
265                        expected: "type".to_string(),
266                        found: format!("'{}'", b as char),
267                    },
268                })
269            }
270            None => {
271                Err(SchemaParseError {
272                    span: Span { start, end: start },
273                    kind: SchemaErrorKind::UnexpectedToken {
274                        expected: "type".to_string(),
275                        found: "end of input".to_string(),
276                    },
277                })
278            }
279        }
280    }
281
282    fn parse_field(&mut self) -> Result<FieldDef, SchemaParseError> {
283        self.skip_whitespace();
284        let name = self.parse_identifier()?;
285        self.skip_whitespace();
286        self.expect_char(b':')?;
287        self.skip_whitespace();
288        let type_ = self.parse_type()?;
289        self.skip_whitespace();
290
291        // Parse optional default value: `= <value>`
292        let default = if self.peek() == Some(b'=') {
293            self.advance(); // skip '='
294            self.skip_whitespace();
295            let mut ron_parser = RonParser::new_at(
296                self.source,
297                self.offset,
298                self.position(),
299            );
300            let value = ron_parser.parse_single_value().map_err(|e| {
301                SchemaParseError {
302                    span: e.span,
303                    kind: SchemaErrorKind::UnexpectedToken {
304                        expected: "default value".to_string(),
305                        found: format!("{:?}", e.kind),
306                    },
307                }
308            })?;
309            // Advance the schema parser past the value the RON parser consumed
310            let bytes_consumed = ron_parser.current_offset() - self.offset;
311            for _ in 0..bytes_consumed {
312                self.advance();
313            }
314            Some(value)
315        } else {
316            None
317        };
318
319        Ok(FieldDef{
320            name,
321            type_,
322            default,
323        })
324    }
325
326    fn parse_struct(&mut self) -> Result<StructDef, SchemaParseError> {
327        self.skip_whitespace();
328        self.expect_char(b'(')?;
329        let mut fields: Vec<FieldDef> = Vec::new();
330        loop {
331            self.skip_whitespace();
332            if let Some(byte) = self.peek() {
333                if byte == b')' {
334                    break ;
335                } 
336                let field = self.parse_field()?;
337                fields.push(field);
338                self.skip_whitespace();
339                if self.peek() == Some(b',') {
340                    self.advance();
341                }
342            } else {
343                return Err(SchemaParseError {
344                    span: Span { start: self.position(), end: self.position() },
345                    kind: SchemaErrorKind::UnexpectedToken { expected: ")".to_string(), found: "end of file".to_string() }
346                });
347            }
348        }
349        self.expect_char(b')')?;
350        Ok(StructDef { fields })
351    }
352
353    /// Parses `(Type, Type, ...)` as a tuple type.
354    fn parse_tuple_type(&mut self) -> Result<Vec<SchemaType>, SchemaParseError> {
355        self.skip_whitespace();
356        self.expect_char(b'(')?;
357        let mut types = Vec::new();
358        loop {
359            self.skip_whitespace();
360            if self.peek() == Some(b')') {
361                break;
362            }
363            let t = self.parse_type()?;
364            types.push(t.value);
365            self.skip_whitespace();
366            if self.peek() == Some(b',') {
367                self.advance();
368            }
369        }
370        self.expect_char(b')')?;
371        Ok(types)
372    }
373
374    fn parse_enum_def(&mut self) -> Result<EnumDef, SchemaParseError> {
375        self.skip_whitespace();
376        let keyword = self.parse_identifier()?;
377        if keyword.value != "enum" {
378            return Err(SchemaParseError {
379                span: keyword.span,
380                kind: SchemaErrorKind::UnexpectedToken {
381                    expected: "\"enum\"".to_string(),
382                    found: keyword.value,
383                },
384            });
385        }
386        self.skip_whitespace();
387        let name = self.parse_identifier()?;
388        self.skip_whitespace();
389        self.expect_char(b'{')?;
390        let mut variants = HashMap::new();
391        loop {
392            self.skip_whitespace();
393            if let Some(byte) = self.peek() {
394                if byte == b'}' {
395                    break;
396                }
397                let variant = self.parse_identifier()?;
398                // Check for associated data: Variant(Type)
399                self.skip_whitespace();
400                let data_type = if self.peek() == Some(b'(') {
401                    self.advance(); // consume '('
402                    self.skip_whitespace();
403                    let t = self.parse_type()?;
404                    self.skip_whitespace();
405                    self.expect_char(b')')?;
406                    Some(t.value)
407                } else {
408                    None
409                };
410                variants.insert(variant.value, data_type);
411                self.skip_whitespace();
412                if self.peek() == Some(b',') {
413                    self.advance();
414                }
415            } else {
416                return Err(SchemaParseError {
417                    span: Span { start: self.position(), end: self.position() },
418                    kind: SchemaErrorKind::UnexpectedToken { expected: "}".to_string(), found: "end of file".to_string() }
419                });
420            }
421        }
422
423        self.expect_char(b'}')?;
424        Ok(EnumDef { name: name.value, variants })
425    }
426
427    /// Parses `type Name = <type>` — assumes the "type" keyword has already been confirmed.
428    fn parse_alias_def(&mut self) -> Result<(String, Spanned<SchemaType>), SchemaParseError> {
429        self.skip_whitespace();
430        self.parse_identifier()?; // consume "type" keyword
431        self.skip_whitespace();
432        let name = self.parse_identifier()?;
433        self.skip_whitespace();
434        self.expect_char(b'=')?;
435        self.skip_whitespace();
436        let type_ = self.parse_type()?;
437        Ok((name.value, type_))
438    }
439}
440
441/// Parses a `.ronschema` source string into a [`Schema`].
442///
443/// # Errors
444///
445/// Returns a [`SchemaParseError`] if the source contains syntax errors,
446/// duplicate definitions, or unresolved enum references.
447pub fn parse_schema(source: &str) -> Result<Schema, SchemaParseError> {
448    let mut parser = Parser::new(source);
449    parser.skip_whitespace();
450
451    let mut root = if parser.peek() == Some(b'(') {
452        parser.parse_struct()?
453    } else {
454        StructDef { fields: Vec::new() }
455    };
456
457    let mut enums: HashMap<String, EnumDef> = HashMap::new();
458    let mut aliases: HashMap<String, Spanned<SchemaType>> = HashMap::new();
459
460    loop {
461        parser.skip_whitespace();
462        if parser.peek().is_none() {
463            break;
464        }
465
466        // Peek ahead to determine if this is "enum" or "type"
467        let start = parser.position();
468        let keyword = parser.parse_identifier()?;
469
470        match keyword.value.as_str() {
471            "enum" => {
472                // Rewind — parse_enum_def expects to consume "enum" itself
473                parser.offset = start.offset;
474                parser.line = start.line;
475                parser.column = start.column;
476
477                let enum_def = parser.parse_enum_def()?;
478                if let Some(old) = enums.insert(enum_def.name.clone(), enum_def) {
479                    return Err(SchemaParseError {
480                        span: Span { start: parser.position(), end: parser.position() },
481                        kind: SchemaErrorKind::DuplicateEnum { name: old.name },
482                    });
483                }
484            }
485            "type" => {
486                // Rewind — parse_alias_def expects to consume "type" itself
487                parser.offset = start.offset;
488                parser.line = start.line;
489                parser.column = start.column;
490
491                let (name, type_) = parser.parse_alias_def()?;
492                if aliases.contains_key(&name) {
493                    return Err(SchemaParseError {
494                        span: type_.span,
495                        kind: SchemaErrorKind::DuplicateAlias { name },
496                    });
497                }
498                aliases.insert(name, type_);
499            }
500            other => {
501                return Err(SchemaParseError {
502                    span: keyword.span,
503                    kind: SchemaErrorKind::UnexpectedToken {
504                        expected: "\"enum\" or \"type\"".to_string(),
505                        found: other.to_string(),
506                    },
507                });
508            }
509        }
510    }
511
512    // Reclassify EnumRefs that are actually aliases — in the root struct and in alias definitions.
513    // Collect alias names into a set to avoid borrow conflicts when mutating alias values.
514    let alias_names: HashSet<String> = aliases.keys().cloned().collect();
515    reclassify_refs_in_struct_by_name(&mut root, &alias_names);
516    for spanned_type in aliases.values_mut() {
517        reclassify_refs_in_type_by_name(&mut spanned_type.value, &alias_names);
518    }
519
520    // Verify all refs resolve to a known enum or alias
521    verify_refs(&root, &enums, &aliases)?;
522
523    // Check for recursive aliases
524    verify_no_recursive_aliases(&aliases)?;
525
526    // Verify default values match their declared types
527    verify_defaults(&root, &enums, &aliases)?;
528
529    Ok(Schema { root, enums, aliases })
530}
531
532/// Reclassifies `EnumRef` names that are actually type aliases into `AliasRef`.
533/// Mutates the struct in place.
534fn reclassify_refs_in_struct_by_name(
535    struct_def: &mut StructDef,
536    alias_names: &HashSet<String>,
537) {
538    for field in &mut struct_def.fields {
539        reclassify_refs_in_type_by_name(&mut field.type_.value, alias_names);
540    }
541}
542
543fn reclassify_refs_in_type_by_name(
544    schema_type: &mut SchemaType,
545    alias_names: &HashSet<String>,
546) {
547    match schema_type {
548        SchemaType::EnumRef(name) if alias_names.contains(name.as_str()) => {
549            *schema_type = SchemaType::AliasRef(name.clone());
550        }
551        SchemaType::Option(inner) | SchemaType::List(inner) => {
552            reclassify_refs_in_type_by_name(inner, alias_names);
553        }
554        SchemaType::Map(key, value) => {
555            reclassify_refs_in_type_by_name(key, alias_names);
556            reclassify_refs_in_type_by_name(value, alias_names);
557        }
558        SchemaType::Tuple(types) => {
559            for t in types {
560                reclassify_refs_in_type_by_name(t, alias_names);
561            }
562        }
563        SchemaType::Struct(struct_def) => {
564            reclassify_refs_in_struct_by_name(struct_def, alias_names);
565        }
566        _ => {}
567    }
568}
569
570/// Verifies all `EnumRef` names resolve to a defined enum.
571/// (`AliasRefs` have already been reclassified, so any remaining `EnumRef` must be an actual enum.)
572fn verify_refs(
573    struct_def: &StructDef,
574    enums: &HashMap<String, EnumDef>,
575    aliases: &HashMap<String, Spanned<SchemaType>>,
576) -> Result<(), SchemaParseError> {
577    for field in &struct_def.fields {
578        check_type_refs(&field.type_.value, field.type_.span, enums, aliases)?;
579    }
580    Ok(())
581}
582
583fn check_type_refs(
584    schema_type: &SchemaType,
585    span: Span,
586    enums: &HashMap<String, EnumDef>,
587    aliases: &HashMap<String, Spanned<SchemaType>>,
588) -> Result<(), SchemaParseError> {
589    match schema_type {
590        SchemaType::EnumRef(name) => {
591            if !enums.contains_key(name) {
592                return Err(SchemaParseError {
593                    span,
594                    kind: SchemaErrorKind::UnresolvedType { name: name.clone() },
595                });
596            }
597        }
598        SchemaType::AliasRef(name) => {
599            if !aliases.contains_key(name) {
600                return Err(SchemaParseError {
601                    span,
602                    kind: SchemaErrorKind::UnresolvedType { name: name.clone() },
603                });
604            }
605        }
606        SchemaType::Option(inner) | SchemaType::List(inner) => {
607            check_type_refs(inner, span, enums, aliases)?;
608        }
609        SchemaType::Map(key, value) => {
610            check_type_refs(key, span, enums, aliases)?;
611            check_type_refs(value, span, enums, aliases)?;
612        }
613        SchemaType::Tuple(types) => {
614            for t in types {
615                check_type_refs(t, span, enums, aliases)?;
616            }
617        }
618        SchemaType::Struct(struct_def) => {
619            verify_refs(struct_def, enums, aliases)?;
620        }
621        _ => {}
622    }
623    Ok(())
624}
625
626/// Detects recursive type aliases — an alias that references itself directly or indirectly.
627fn verify_no_recursive_aliases(
628    aliases: &HashMap<String, Spanned<SchemaType>>,
629) -> Result<(), SchemaParseError> {
630    for (name, spanned_type) in aliases {
631        let mut visited = HashSet::new();
632        visited.insert(name.as_str());
633        if let Some(cycle_name) = find_alias_cycle(&spanned_type.value, aliases, &mut visited) {
634            return Err(SchemaParseError {
635                span: spanned_type.span,
636                kind: SchemaErrorKind::RecursiveAlias { name: cycle_name },
637            });
638        }
639    }
640    Ok(())
641}
642
643fn find_alias_cycle<'a>(
644    schema_type: &'a SchemaType,
645    aliases: &'a HashMap<String, Spanned<SchemaType>>,
646    visited: &mut HashSet<&'a str>,
647) -> Option<String> {
648    match schema_type {
649        SchemaType::AliasRef(name) => {
650            if visited.contains(name.as_str()) {
651                return Some(name.clone());
652            }
653            visited.insert(name.as_str());
654            if let Some(target) = aliases.get(name) {
655                return find_alias_cycle(&target.value, aliases, visited);
656            }
657            None
658        }
659        SchemaType::Option(inner) | SchemaType::List(inner) => {
660            find_alias_cycle(inner, aliases, visited)
661        }
662        SchemaType::Map(key, value) => {
663            if let Some(cycle) = find_alias_cycle(key, aliases, visited) {
664                return Some(cycle);
665            }
666            find_alias_cycle(value, aliases, visited)
667        }
668        SchemaType::Tuple(types) => {
669            for t in types {
670                if let Some(cycle) = find_alias_cycle(t, aliases, visited) {
671                    return Some(cycle);
672                }
673            }
674            None
675        }
676        SchemaType::Struct(struct_def) => {
677            for field in &struct_def.fields {
678                if let Some(cycle) = find_alias_cycle(&field.type_.value, aliases, visited) {
679                    return Some(cycle);
680                }
681            }
682            None
683        }
684        _ => None,
685    }
686}
687
688/// Resolves a `SchemaType` through alias indirection to its underlying type.
689fn resolve_type<'a>(
690    schema_type: &'a SchemaType,
691    aliases: &'a HashMap<String, Spanned<SchemaType>>,
692) -> &'a SchemaType {
693    match schema_type {
694        SchemaType::AliasRef(name) => {
695            if let Some(target) = aliases.get(name) {
696                resolve_type(&target.value, aliases)
697            } else {
698                schema_type
699            }
700        }
701        _ => schema_type,
702    }
703}
704
705/// Checks whether a default value is compatible with a schema type.
706/// Returns `true` if the value matches the type.
707#[allow(clippy::match_same_arms)]
708fn default_matches_type(
709    value: &RonValue,
710    schema_type: &SchemaType,
711    enums: &HashMap<String, EnumDef>,
712    aliases: &HashMap<String, Spanned<SchemaType>>,
713) -> bool {
714    let resolved = resolve_type(schema_type, aliases);
715    match (resolved, value) {
716        (SchemaType::String, RonValue::String(_)) => true,
717        (SchemaType::Integer, RonValue::Integer(_)) => true,
718        (SchemaType::Float, RonValue::Float(_)) => true,
719        (SchemaType::Bool, RonValue::Bool(_)) => true,
720        (SchemaType::Option(_), RonValue::Option(None)) => true,
721        (SchemaType::Option(inner), RonValue::Option(Some(inner_val))) => {
722            default_matches_type(&inner_val.value, inner, enums, aliases)
723        }
724        (SchemaType::List(elem_type), RonValue::List(elements)) => {
725            elements.iter().all(|e| default_matches_type(&e.value, elem_type, enums, aliases))
726        }
727        (SchemaType::EnumRef(enum_name), RonValue::Identifier(variant)) => {
728            enums.get(enum_name).is_some_and(|e| e.variants.contains_key(variant))
729        }
730        (SchemaType::EnumRef(enum_name), RonValue::EnumVariant(variant, data)) => {
731            enums.get(enum_name).is_some_and(|e| {
732                matches!(e.variants.get(variant), Some(Some(data_type)) if default_matches_type(&data.value, data_type, enums, aliases))
733            })
734        }
735        (SchemaType::Tuple(types), RonValue::Tuple(values)) => {
736            types.len() == values.len()
737                && types.iter().zip(values.iter()).all(|(t, v)| default_matches_type(&v.value, t, enums, aliases))
738        }
739        (SchemaType::Map(_, _), RonValue::Map(_)) => true, // map default type checking is impractical at parse time
740        _ => false,
741    }
742}
743
744/// Describes a schema type for error messages.
745fn describe_type(schema_type: &SchemaType) -> String {
746    match schema_type {
747        SchemaType::String => "String".to_string(),
748        SchemaType::Integer => "Integer".to_string(),
749        SchemaType::Float => "Float".to_string(),
750        SchemaType::Bool => "Bool".to_string(),
751        SchemaType::Option(inner) => format!("Option({})", describe_type(inner)),
752        SchemaType::List(inner) => format!("[{}]", describe_type(inner)),
753        SchemaType::EnumRef(name) | SchemaType::AliasRef(name) => name.clone(),
754        SchemaType::Map(k, v) => format!("{{{}: {}}}", describe_type(k), describe_type(v)),
755        SchemaType::Tuple(types) => {
756            let inner: Vec<String> = types.iter().map(describe_type).collect();
757            format!("({})", inner.join(", "))
758        }
759        SchemaType::Struct(_) => "Struct".to_string(),
760    }
761}
762
763/// Describes a RON value for error messages.
764fn describe_value(value: &RonValue) -> String {
765    match value {
766        RonValue::String(s) => format!("String(\"{s}\")"),
767        RonValue::Integer(n) => format!("Integer({n})"),
768        RonValue::Float(f) => format!("Float({f})"),
769        RonValue::Bool(b) => format!("Bool({b})"),
770        RonValue::Option(None) => "None".to_string(),
771        RonValue::Option(Some(_)) => "Some(...)".to_string(),
772        RonValue::Identifier(s) => format!("Identifier({s})"),
773        RonValue::EnumVariant(name, _) => format!("{name}(...)"),
774        RonValue::List(_) => "List".to_string(),
775        RonValue::Map(_) => "Map".to_string(),
776        RonValue::Tuple(_) => "Tuple".to_string(),
777        RonValue::Struct(_) => "Struct".to_string(),
778    }
779}
780
781/// Verifies that all default values in a struct match their declared types.
782fn verify_defaults(
783    struct_def: &StructDef,
784    enums: &HashMap<String, EnumDef>,
785    aliases: &HashMap<String, Spanned<SchemaType>>,
786) -> Result<(), SchemaParseError> {
787    for field in &struct_def.fields {
788        if let Some(default) = &field.default {
789            if !default_matches_type(&default.value, &field.type_.value, enums, aliases) {
790                return Err(SchemaParseError {
791                    span: default.span,
792                    kind: SchemaErrorKind::InvalidDefault {
793                        field_name: field.name.value.clone(),
794                        expected: describe_type(&field.type_.value),
795                        found: describe_value(&default.value),
796                    },
797                });
798            }
799        }
800    }
801    // Check nested structs
802    for field in &struct_def.fields {
803        if let SchemaType::Struct(inner) = &field.type_.value {
804            verify_defaults(inner, enums, aliases)?;
805        }
806    }
807    Ok(())
808}
809
810#[cfg(test)]
811mod tests {
812    use super::*;
813    use crate::ron::RonValue;
814
815    // ========================================================
816    // Helper: constructs a Parser for direct method testing
817    // ========================================================
818
819    fn parser(source: &str) -> Parser<'_> {
820        Parser::new(source)
821    }
822
823    // ========================================================
824    // peek() tests
825    // ========================================================
826
827    // Returns the current byte without advancing.
828    #[test]
829    fn peek_returns_current_byte() {
830        let p = parser("abc");
831        assert_eq!(p.peek(), Some(b'a'));
832    }
833
834    // Returns None when at end of input.
835    #[test]
836    fn peek_returns_none_at_end() {
837        let p = parser("");
838        assert_eq!(p.peek(), None);
839    }
840
841    // ========================================================
842    // advance() tests
843    // ========================================================
844
845    // Moves to the next byte and increments column.
846    #[test]
847    fn advance_increments_offset_and_column() {
848        let mut p = parser("ab");
849        p.advance();
850        assert_eq!(p.offset, 1);
851        assert_eq!(p.column, 2);
852        assert_eq!(p.peek(), Some(b'b'));
853    }
854
855    // Newline resets column to 1 and increments line.
856    #[test]
857    fn advance_past_newline_increments_line() {
858        let mut p = parser("a\nb");
859        p.advance(); // past 'a'
860        p.advance(); // past '\n'
861        assert_eq!(p.line, 2);
862        assert_eq!(p.column, 1);
863    }
864
865    // Advancing at end of input is a no-op.
866    #[test]
867    fn advance_at_end_is_noop() {
868        let mut p = parser("");
869        p.advance();
870        assert_eq!(p.offset, 0);
871    }
872
873    // ========================================================
874    // position() tests
875    // ========================================================
876
877    // Initial position is offset 0, line 1, column 1.
878    #[test]
879    fn position_initial_state() {
880        let p = parser("abc");
881        let pos = p.position();
882        assert_eq!(pos.offset, 0);
883        assert_eq!(pos.line, 1);
884        assert_eq!(pos.column, 1);
885    }
886
887    // Position tracks correctly after advancing.
888    #[test]
889    fn position_after_advance() {
890        let mut p = parser("ab\nc");
891        p.advance(); // 'a'
892        p.advance(); // 'b'
893        p.advance(); // '\n'
894        let pos = p.position();
895        assert_eq!(pos.offset, 3);
896        assert_eq!(pos.line, 2);
897        assert_eq!(pos.column, 1);
898    }
899
900    // ========================================================
901    // skip_whitespace() tests
902    // ========================================================
903
904    // Skips spaces, tabs, and newlines.
905    #[test]
906    fn skip_whitespace_skips_spaces_tabs_newlines() {
907        let mut p = parser("  \t\nabc");
908        p.skip_whitespace();
909        assert_eq!(p.peek(), Some(b'a'));
910    }
911
912    // Skips line comments.
913    #[test]
914    fn skip_whitespace_skips_line_comment() {
915        let mut p = parser("// comment\nabc");
916        p.skip_whitespace();
917        assert_eq!(p.peek(), Some(b'a'));
918    }
919
920    // Skips whitespace after a comment.
921    #[test]
922    fn skip_whitespace_skips_comment_then_whitespace() {
923        let mut p = parser("// comment\n  abc");
924        p.skip_whitespace();
925        assert_eq!(p.peek(), Some(b'a'));
926    }
927
928    // Does nothing when already on a non-whitespace character.
929    #[test]
930    fn skip_whitespace_noop_on_nonwhitespace() {
931        let mut p = parser("abc");
932        p.skip_whitespace();
933        assert_eq!(p.offset, 0);
934    }
935
936    // ========================================================
937    // expect_char() tests
938    // ========================================================
939
940    // Consumes the expected character and returns Ok.
941    #[test]
942    fn expect_char_consumes_matching_byte() {
943        let mut p = parser("(abc");
944        assert!(p.expect_char(b'(').is_ok());
945        assert_eq!(p.peek(), Some(b'a'));
946    }
947
948    // Returns error when character doesn't match.
949    #[test]
950    fn expect_char_error_on_mismatch() {
951        let mut p = parser("abc");
952        let err = p.expect_char(b'(').unwrap_err();
953        assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
954    }
955
956    // Returns error at end of input.
957    #[test]
958    fn expect_char_error_at_end_of_input() {
959        let mut p = parser("");
960        let err = p.expect_char(b'(').unwrap_err();
961        match err.kind {
962            SchemaErrorKind::UnexpectedToken { found, .. } => {
963                assert_eq!(found, "end of input");
964            }
965            other => panic!("expected UnexpectedToken, got {:?}", other),
966        }
967    }
968
969    // ========================================================
970    // parse_identifier() tests
971    // ========================================================
972
973    // Reads a simple alphabetic identifier.
974    #[test]
975    fn parse_identifier_reads_alpha() {
976        let mut p = parser("name:");
977        let id = p.parse_identifier().unwrap();
978        assert_eq!(id.value, "name");
979    }
980
981    // Reads an identifier with underscores.
982    #[test]
983    fn parse_identifier_reads_snake_case() {
984        let mut p = parser("field_name:");
985        let id = p.parse_identifier().unwrap();
986        assert_eq!(id.value, "field_name");
987    }
988
989    // Reads an identifier with digits.
990    #[test]
991    fn parse_identifier_reads_alphanumeric() {
992        let mut p = parser("cost2:");
993        let id = p.parse_identifier().unwrap();
994        assert_eq!(id.value, "cost2");
995    }
996
997    // Reads a PascalCase identifier (for types/enums).
998    #[test]
999    fn parse_identifier_reads_pascal_case() {
1000        let mut p = parser("CardType ");
1001        let id = p.parse_identifier().unwrap();
1002        assert_eq!(id.value, "CardType");
1003    }
1004
1005    // Stops at non-identifier characters.
1006    #[test]
1007    fn parse_identifier_stops_at_delimiter() {
1008        let mut p = parser("name: String");
1009        let id = p.parse_identifier().unwrap();
1010        assert_eq!(id.value, "name");
1011        assert_eq!(p.peek(), Some(b':'));
1012    }
1013
1014    // Records correct span for the identifier.
1015    #[test]
1016    fn parse_identifier_span_is_correct() {
1017        let mut p = parser("name:");
1018        let id = p.parse_identifier().unwrap();
1019        assert_eq!(id.span.start.offset, 0);
1020        assert_eq!(id.span.end.offset, 4);
1021    }
1022
1023    // Error when starting with a digit.
1024    #[test]
1025    fn parse_identifier_error_on_digit_start() {
1026        let mut p = parser("42abc");
1027        assert!(p.parse_identifier().is_err());
1028    }
1029
1030    // Error at end of input.
1031    #[test]
1032    fn parse_identifier_error_at_end_of_input() {
1033        let mut p = parser("");
1034        assert!(p.parse_identifier().is_err());
1035    }
1036
1037    // ========================================================
1038    // parse_type() tests
1039    // ========================================================
1040
1041    // Parses "String" as SchemaType::String.
1042    #[test]
1043    fn parse_type_string() {
1044        let mut p = parser("String");
1045        let t = p.parse_type().unwrap();
1046        assert_eq!(t.value, SchemaType::String);
1047    }
1048
1049    // Parses "Integer" as SchemaType::Integer.
1050    #[test]
1051    fn parse_type_integer() {
1052        let mut p = parser("Integer");
1053        let t = p.parse_type().unwrap();
1054        assert_eq!(t.value, SchemaType::Integer);
1055    }
1056
1057    // Parses "Float" as SchemaType::Float.
1058    #[test]
1059    fn parse_type_float() {
1060        let mut p = parser("Float");
1061        let t = p.parse_type().unwrap();
1062        assert_eq!(t.value, SchemaType::Float);
1063    }
1064
1065    // Parses "Bool" as SchemaType::Bool.
1066    #[test]
1067    fn parse_type_bool() {
1068        let mut p = parser("Bool");
1069        let t = p.parse_type().unwrap();
1070        assert_eq!(t.value, SchemaType::Bool);
1071    }
1072
1073    // Parses "[String]" as a List wrapping String.
1074    #[test]
1075    fn parse_type_list() {
1076        let mut p = parser("[String]");
1077        let t = p.parse_type().unwrap();
1078        assert_eq!(t.value, SchemaType::List(Box::new(SchemaType::String)));
1079    }
1080
1081    // Parses "Option(Integer)" as an Option wrapping Integer.
1082    #[test]
1083    fn parse_type_option() {
1084        let mut p = parser("Option(Integer)");
1085        let t = p.parse_type().unwrap();
1086        assert_eq!(t.value, SchemaType::Option(Box::new(SchemaType::Integer)));
1087    }
1088
1089    // Parses an unknown PascalCase name as an EnumRef.
1090    #[test]
1091    fn parse_type_enum_ref() {
1092        let mut p = parser("Faction");
1093        let t = p.parse_type().unwrap();
1094        assert_eq!(t.value, SchemaType::EnumRef("Faction".to_string()));
1095    }
1096
1097    // Parses nested composites: [Option(String)].
1098    #[test]
1099    fn parse_type_nested_list_of_option() {
1100        let mut p = parser("[Option(String)]");
1101        let t = p.parse_type().unwrap();
1102        assert_eq!(
1103            t.value,
1104            SchemaType::List(Box::new(SchemaType::Option(Box::new(SchemaType::String))))
1105        );
1106    }
1107
1108    // Parses an inline struct type.
1109    #[test]
1110    fn parse_type_inline_struct() {
1111        let mut p = parser("(\n  x: Integer,\n)");
1112        let t = p.parse_type().unwrap();
1113        if let SchemaType::Struct(s) = &t.value {
1114            assert_eq!(s.fields.len(), 1);
1115            assert_eq!(s.fields[0].name.value, "x");
1116        } else {
1117            panic!("expected SchemaType::Struct");
1118        }
1119    }
1120
1121    // Error on unexpected token in type position.
1122    #[test]
1123    fn parse_type_error_on_unexpected_token() {
1124        let mut p = parser("42");
1125        let err = p.parse_type().unwrap_err();
1126        match err.kind {
1127            SchemaErrorKind::UnexpectedToken { expected, .. } => {
1128                assert_eq!(expected, "type");
1129            }
1130            other => panic!("expected UnexpectedToken, got {:?}", other),
1131        }
1132    }
1133
1134    // ========================================================
1135    // parse_field() tests
1136    // ========================================================
1137
1138    // Parses "name: String" into a FieldDef.
1139    #[test]
1140    fn parse_field_name_and_type() {
1141        let mut p = parser("name: String,");
1142        let f = p.parse_field().unwrap();
1143        assert_eq!(f.name.value, "name");
1144        assert_eq!(f.type_.value, SchemaType::String);
1145    }
1146
1147    // Error when colon is missing.
1148    #[test]
1149    fn parse_field_error_missing_colon() {
1150        let mut p = parser("name String");
1151        let err = p.parse_field().unwrap_err();
1152        assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
1153    }
1154
1155    // Field without default has None.
1156    #[test]
1157    fn parse_field_no_default() {
1158        let mut p = parser("name: String,");
1159        let f = p.parse_field().unwrap();
1160        assert!(f.default.is_none());
1161    }
1162
1163    // Field with string default.
1164    #[test]
1165    fn parse_field_default_string() {
1166        let mut p = parser("name: String = \"unnamed\",");
1167        let f = p.parse_field().unwrap();
1168        assert!(f.default.is_some());
1169        assert_eq!(f.default.unwrap().value, RonValue::String("unnamed".to_string()));
1170    }
1171
1172    // Field with integer default.
1173    #[test]
1174    fn parse_field_default_integer() {
1175        let mut p = parser("count: Integer = 0,");
1176        let f = p.parse_field().unwrap();
1177        assert_eq!(f.default.unwrap().value, RonValue::Integer(0));
1178    }
1179
1180    // Field with float default.
1181    #[test]
1182    fn parse_field_default_float() {
1183        let mut p = parser("weight: Float = 1.0,");
1184        let f = p.parse_field().unwrap();
1185        assert_eq!(f.default.unwrap().value, RonValue::Float(1.0));
1186    }
1187
1188    // Field with bool default.
1189    #[test]
1190    fn parse_field_default_bool() {
1191        let mut p = parser("active: Bool = false,");
1192        let f = p.parse_field().unwrap();
1193        assert_eq!(f.default.unwrap().value, RonValue::Bool(false));
1194    }
1195
1196    // Field with None default.
1197    #[test]
1198    fn parse_field_default_none() {
1199        let mut p = parser("label: Option(String) = None,");
1200        let f = p.parse_field().unwrap();
1201        assert_eq!(f.default.unwrap().value, RonValue::Option(None));
1202    }
1203
1204    // Field with Some default.
1205    #[test]
1206    fn parse_field_default_some() {
1207        let mut p = parser("label: Option(String) = Some(\"default\"),");
1208        let f = p.parse_field().unwrap();
1209        if let RonValue::Option(Some(inner)) = &f.default.unwrap().value {
1210            assert_eq!(inner.value, RonValue::String("default".to_string()));
1211        } else {
1212            panic!("expected Option(Some(...))");
1213        }
1214    }
1215
1216    // Field with empty list default.
1217    #[test]
1218    fn parse_field_default_empty_list() {
1219        let mut p = parser("tags: [String] = [],");
1220        let f = p.parse_field().unwrap();
1221        assert_eq!(f.default.unwrap().value, RonValue::List(vec![]));
1222    }
1223
1224    // Field with identifier default.
1225    #[test]
1226    fn parse_field_default_identifier() {
1227        let mut p = parser("status: Status = Active,");
1228        let f = p.parse_field().unwrap();
1229        assert_eq!(f.default.unwrap().value, RonValue::Identifier("Active".to_string()));
1230    }
1231
1232    // Default value has correct span.
1233    #[test]
1234    fn parse_field_default_has_span() {
1235        let mut p = parser("name: String = \"hi\",");
1236        let f = p.parse_field().unwrap();
1237        let default = f.default.unwrap();
1238        assert!(default.span.start.column > 1);
1239    }
1240
1241    // ========================================================
1242    // Default value type checking (parse_schema level)
1243    // ========================================================
1244
1245    // String default matches String type.
1246    #[test]
1247    fn default_type_check_string_accepts_string() {
1248        let result = parse_schema("(\n  name: String = \"hi\",\n)");
1249        assert!(result.is_ok());
1250    }
1251
1252    // Integer default rejected for String type.
1253    #[test]
1254    fn default_type_check_string_rejects_integer() {
1255        let err = parse_schema("(\n  name: String = 42,\n)").unwrap_err();
1256        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { field_name, .. } if field_name == "name"));
1257    }
1258
1259    // Integer default matches Integer type.
1260    #[test]
1261    fn default_type_check_integer_accepts_integer() {
1262        assert!(parse_schema("(\n  count: Integer = 0,\n)").is_ok());
1263    }
1264
1265    // String default rejected for Integer type.
1266    #[test]
1267    fn default_type_check_integer_rejects_string() {
1268        let err = parse_schema("(\n  count: Integer = \"zero\",\n)").unwrap_err();
1269        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { .. }));
1270    }
1271
1272    // Float default matches Float type.
1273    #[test]
1274    fn default_type_check_float_accepts_float() {
1275        assert!(parse_schema("(\n  weight: Float = 1.0,\n)").is_ok());
1276    }
1277
1278    // Integer default rejected for Float type.
1279    #[test]
1280    fn default_type_check_float_rejects_integer() {
1281        let err = parse_schema("(\n  weight: Float = 1,\n)").unwrap_err();
1282        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { .. }));
1283    }
1284
1285    // Bool default matches Bool type.
1286    #[test]
1287    fn default_type_check_bool_accepts_bool() {
1288        assert!(parse_schema("(\n  active: Bool = false,\n)").is_ok());
1289    }
1290
1291    // String default rejected for Bool type.
1292    #[test]
1293    fn default_type_check_bool_rejects_string() {
1294        let err = parse_schema("(\n  active: Bool = \"false\",\n)").unwrap_err();
1295        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { .. }));
1296    }
1297
1298    // None default matches Option type.
1299    #[test]
1300    fn default_type_check_option_accepts_none() {
1301        assert!(parse_schema("(\n  label: Option(String) = None,\n)").is_ok());
1302    }
1303
1304    // Some with correct inner type matches Option type.
1305    #[test]
1306    fn default_type_check_option_accepts_some_correct() {
1307        assert!(parse_schema("(\n  label: Option(String) = Some(\"hi\"),\n)").is_ok());
1308    }
1309
1310    // Some with wrong inner type rejected for Option type.
1311    #[test]
1312    fn default_type_check_option_rejects_some_wrong_type() {
1313        let err = parse_schema("(\n  label: Option(String) = Some(42),\n)").unwrap_err();
1314        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { .. }));
1315    }
1316
1317    // Empty list matches list type.
1318    #[test]
1319    fn default_type_check_list_accepts_empty() {
1320        assert!(parse_schema("(\n  tags: [String] = [],\n)").is_ok());
1321    }
1322
1323    // List with correct element type matches.
1324    #[test]
1325    fn default_type_check_list_accepts_correct_elements() {
1326        assert!(parse_schema("(\n  tags: [String] = [\"a\", \"b\"],\n)").is_ok());
1327    }
1328
1329    // List with wrong element type rejected.
1330    #[test]
1331    fn default_type_check_list_rejects_wrong_elements() {
1332        let err = parse_schema("(\n  tags: [String] = [1, 2],\n)").unwrap_err();
1333        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { .. }));
1334    }
1335
1336    // Valid enum variant accepted as default.
1337    #[test]
1338    fn default_type_check_enum_accepts_valid_variant() {
1339        assert!(parse_schema("(\n  status: Status = Active,\n)\nenum Status { Active, Inactive }").is_ok());
1340    }
1341
1342    // Invalid enum variant rejected as default.
1343    #[test]
1344    fn default_type_check_enum_rejects_invalid_variant() {
1345        let err = parse_schema("(\n  status: Status = Unknown,\n)\nenum Status { Active, Inactive }").unwrap_err();
1346        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { .. }));
1347    }
1348
1349    // InvalidDefault error includes expected type.
1350    #[test]
1351    fn default_type_check_error_includes_expected() {
1352        let err = parse_schema("(\n  name: String = 42,\n)").unwrap_err();
1353        if let SchemaErrorKind::InvalidDefault { expected, .. } = &err.kind {
1354            assert_eq!(expected, "String");
1355        } else {
1356            panic!("expected InvalidDefault");
1357        }
1358    }
1359
1360    // InvalidDefault error includes found value description.
1361    #[test]
1362    fn default_type_check_error_includes_found() {
1363        let err = parse_schema("(\n  name: String = 42,\n)").unwrap_err();
1364        if let SchemaErrorKind::InvalidDefault { found, .. } = &err.kind {
1365            assert!(found.contains("Integer"));
1366        } else {
1367            panic!("expected InvalidDefault");
1368        }
1369    }
1370
1371    // InvalidDefault error span points to the default value.
1372    #[test]
1373    fn default_type_check_error_has_span() {
1374        let err = parse_schema("(\n  name: String = 42,\n)").unwrap_err();
1375        assert!(err.span.start.line > 0);
1376    }
1377
1378    // Type alias resolved for default type checking.
1379    #[test]
1380    fn default_type_check_alias_resolved() {
1381        assert!(parse_schema("(\n  name: Name = \"hi\",\n)\ntype Name = String").is_ok());
1382    }
1383
1384    // Type alias with wrong default rejected.
1385    #[test]
1386    fn default_type_check_alias_rejects_wrong_type() {
1387        let err = parse_schema("(\n  name: Name = 42,\n)\ntype Name = String").unwrap_err();
1388        assert!(matches!(err.kind, SchemaErrorKind::InvalidDefault { .. }));
1389    }
1390
1391    // ========================================================
1392    // parse_struct() tests
1393    // ========================================================
1394
1395    // Parses an empty struct.
1396    #[test]
1397    fn parse_struct_empty() {
1398        let mut p = parser("()");
1399        let s = p.parse_struct().unwrap();
1400        assert!(s.fields.is_empty());
1401    }
1402
1403    // Parses a struct with one field.
1404    #[test]
1405    fn parse_struct_single_field() {
1406        let mut p = parser("(\n  name: String,\n)");
1407        let s = p.parse_struct().unwrap();
1408        assert_eq!(s.fields.len(), 1);
1409        assert_eq!(s.fields[0].name.value, "name");
1410    }
1411
1412    // Parses a struct with multiple fields.
1413    #[test]
1414    fn parse_struct_multiple_fields() {
1415        let mut p = parser("(\n  a: String,\n  b: Integer,\n)");
1416        let s = p.parse_struct().unwrap();
1417        assert_eq!(s.fields.len(), 2);
1418    }
1419
1420    // Struct without trailing comma is valid.
1421    #[test]
1422    fn parse_struct_no_trailing_comma() {
1423        let mut p = parser("(\n  name: String\n)");
1424        let s = p.parse_struct().unwrap();
1425        assert_eq!(s.fields.len(), 1);
1426    }
1427
1428    // Error on unclosed struct.
1429    #[test]
1430    fn parse_struct_error_on_unclosed() {
1431        let mut p = parser("(\n  name: String,\n");
1432        assert!(p.parse_struct().is_err());
1433    }
1434
1435    // ========================================================
1436    // parse_enum_def() tests
1437    // ========================================================
1438
1439    // Parses a simple enum definition.
1440    #[test]
1441    fn parse_enum_def_simple() {
1442        let mut p = parser("enum Dir { North, South }");
1443        let e = p.parse_enum_def().unwrap();
1444        assert_eq!(e.name, "Dir");
1445        assert_eq!(e.variants.len(), 2);
1446        assert!(e.variants.contains_key("North"));
1447        assert!(e.variants.contains_key("South"));
1448    }
1449
1450    // Trailing comma in variant list is allowed.
1451    #[test]
1452    fn parse_enum_def_trailing_comma() {
1453        let mut p = parser("enum Dir { North, South, }");
1454        let e = p.parse_enum_def().unwrap();
1455        assert_eq!(e.variants.len(), 2);
1456    }
1457
1458    // Single variant enum is valid.
1459    #[test]
1460    fn parse_enum_def_single_variant() {
1461        let mut p = parser("enum Single { Only }");
1462        let e = p.parse_enum_def().unwrap();
1463        assert_eq!(e.variants.len(), 1);
1464    }
1465
1466    // Error when keyword is not "enum".
1467    #[test]
1468    fn parse_enum_def_error_wrong_keyword() {
1469        let mut p = parser("struct Dir { North }");
1470        let err = p.parse_enum_def().unwrap_err();
1471        assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
1472    }
1473
1474    // Error on unclosed enum.
1475    #[test]
1476    fn parse_enum_def_error_on_unclosed() {
1477        let mut p = parser("enum Dir { North, South");
1478        assert!(p.parse_enum_def().is_err());
1479    }
1480
1481    // ========================================================
1482    // parse_schema() integration tests
1483    // ========================================================
1484
1485    // Empty input produces an empty schema.
1486    #[test]
1487    fn schema_empty_input() {
1488        let schema = parse_schema("").unwrap();
1489        assert!(schema.root.fields.is_empty());
1490    }
1491
1492    // Empty input produces no enums.
1493    #[test]
1494    fn schema_empty_input_no_enums() {
1495        let schema = parse_schema("").unwrap();
1496        assert!(schema.enums.is_empty());
1497    }
1498
1499    // Root struct with enum ref resolves when enum is defined.
1500    #[test]
1501    fn schema_enum_ref_resolves() {
1502        let source = "(\n  faction: Faction,\n)\nenum Faction { Sentinels, Reavers }";
1503        let schema = parse_schema(source).unwrap();
1504        assert_eq!(schema.root.fields[0].type_.value, SchemaType::EnumRef("Faction".to_string()));
1505    }
1506
1507    // Multiple enum definitions are all stored.
1508    #[test]
1509    fn schema_multiple_enums_stored() {
1510        let source = "enum A { X }\nenum B { Y }";
1511        let schema = parse_schema(source).unwrap();
1512        assert_eq!(schema.enums.len(), 2);
1513    }
1514
1515    // Comments before root struct are ignored.
1516    #[test]
1517    fn schema_comments_before_root() {
1518        let source = "// comment\n(\n  name: String,\n)";
1519        let schema = parse_schema(source).unwrap();
1520        assert_eq!(schema.root.fields.len(), 1);
1521    }
1522
1523    // Inline comment after field is ignored.
1524    #[test]
1525    fn schema_inline_comment_after_field() {
1526        let source = "(\n  name: String, // a name\n)";
1527        let schema = parse_schema(source).unwrap();
1528        assert_eq!(schema.root.fields[0].name.value, "name");
1529    }
1530
1531    // Unresolved type ref is an error.
1532    #[test]
1533    fn schema_unresolved_type_ref() {
1534        let err = parse_schema("(\n  f: Faction,\n)").unwrap_err();
1535        assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "Faction".to_string() });
1536    }
1537
1538    // Unresolved type ref inside Option is an error.
1539    #[test]
1540    fn schema_unresolved_type_ref_in_option() {
1541        let err = parse_schema("(\n  t: Option(Timing),\n)").unwrap_err();
1542        assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "Timing".to_string() });
1543    }
1544
1545    // Unresolved type ref inside List is an error.
1546    #[test]
1547    fn schema_unresolved_type_ref_in_list() {
1548        let err = parse_schema("(\n  t: [CardType],\n)").unwrap_err();
1549        assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "CardType".to_string() });
1550    }
1551
1552    // Duplicate enum name is an error.
1553    #[test]
1554    fn schema_duplicate_enum_name() {
1555        let err = parse_schema("enum A { X }\nenum A { Y }").unwrap_err();
1556        assert_eq!(err.kind, SchemaErrorKind::DuplicateEnum { name: "A".to_string() });
1557    }
1558
1559    // ========================================================
1560    // Type alias tests — parsing
1561    // ========================================================
1562
1563    // Basic type alias is stored in schema.aliases.
1564    #[test]
1565    fn alias_stored_in_schema() {
1566        let source = "(\n  cost: Cost,\n)\ntype Cost = (generic: Integer,)";
1567        let schema = parse_schema(source).unwrap();
1568        assert!(schema.aliases.contains_key("Cost"));
1569    }
1570
1571    // Alias field is reclassified from EnumRef to AliasRef.
1572    #[test]
1573    fn alias_ref_reclassified() {
1574        let source = "(\n  cost: Cost,\n)\ntype Cost = (generic: Integer,)";
1575        let schema = parse_schema(source).unwrap();
1576        assert_eq!(schema.root.fields[0].type_.value, SchemaType::AliasRef("Cost".to_string()));
1577    }
1578
1579    // Alias to a primitive type.
1580    #[test]
1581    fn alias_to_primitive() {
1582        let source = "(\n  name: Name,\n)\ntype Name = String";
1583        let schema = parse_schema(source).unwrap();
1584        assert_eq!(schema.aliases["Name"].value, SchemaType::String);
1585    }
1586
1587    // Alias to a list type.
1588    #[test]
1589    fn alias_to_list() {
1590        let source = "(\n  tags: Tags,\n)\ntype Tags = [String]";
1591        let schema = parse_schema(source).unwrap();
1592        assert_eq!(schema.aliases["Tags"].value, SchemaType::List(Box::new(SchemaType::String)));
1593    }
1594
1595    // Alias to an option type.
1596    #[test]
1597    fn alias_to_option() {
1598        let source = "(\n  power: Power,\n)\ntype Power = Option(Integer)";
1599        let schema = parse_schema(source).unwrap();
1600        assert_eq!(schema.aliases["Power"].value, SchemaType::Option(Box::new(SchemaType::Integer)));
1601    }
1602
1603    // Alias inside a list field is reclassified.
1604    #[test]
1605    fn alias_ref_inside_list_reclassified() {
1606        let source = "(\n  costs: [Cost],\n)\ntype Cost = (generic: Integer,)";
1607        let schema = parse_schema(source).unwrap();
1608        assert_eq!(
1609            schema.root.fields[0].type_.value,
1610            SchemaType::List(Box::new(SchemaType::AliasRef("Cost".to_string())))
1611        );
1612    }
1613
1614    // Alias inside an option field is reclassified.
1615    #[test]
1616    fn alias_ref_inside_option_reclassified() {
1617        let source = "(\n  cost: Option(Cost),\n)\ntype Cost = (generic: Integer,)";
1618        let schema = parse_schema(source).unwrap();
1619        assert_eq!(
1620            schema.root.fields[0].type_.value,
1621            SchemaType::Option(Box::new(SchemaType::AliasRef("Cost".to_string())))
1622        );
1623    }
1624
1625    // Enums and aliases can coexist.
1626    #[test]
1627    fn alias_and_enum_coexist() {
1628        let source = "(\n  cost: Cost,\n  kind: Kind,\n)\ntype Cost = (generic: Integer,)\nenum Kind { A, B }";
1629        let schema = parse_schema(source).unwrap();
1630        assert!(schema.aliases.contains_key("Cost"));
1631        assert!(schema.enums.contains_key("Kind"));
1632    }
1633
1634    // ========================================================
1635    // Type alias tests — error cases
1636    // ========================================================
1637
1638    // Duplicate alias name is an error.
1639    #[test]
1640    fn alias_duplicate_name() {
1641        let source = "type A = String\ntype A = Integer";
1642        let err = parse_schema(source).unwrap_err();
1643        assert_eq!(err.kind, SchemaErrorKind::DuplicateAlias { name: "A".to_string() });
1644    }
1645
1646    // Recursive alias is an error.
1647    #[test]
1648    fn alias_recursive_direct() {
1649        let source = "(\n  x: Foo,\n)\ntype Foo = Option(Foo)";
1650        let err = parse_schema(source).unwrap_err();
1651        assert_eq!(err.kind, SchemaErrorKind::RecursiveAlias { name: "Foo".to_string() });
1652    }
1653
1654    // Indirect recursive alias is an error.
1655    #[test]
1656    fn alias_recursive_indirect() {
1657        let source = "(\n  x: Foo,\n)\ntype Foo = Option(Bar)\ntype Bar = [Foo]";
1658        let err = parse_schema(source).unwrap_err();
1659        assert!(matches!(err.kind, SchemaErrorKind::RecursiveAlias { .. }));
1660    }
1661
1662    // ========================================================
1663    // Map type tests — parsing
1664    // ========================================================
1665
1666    // Parses a map type with String keys and Integer values.
1667    #[test]
1668    fn parse_type_map_string_to_integer() {
1669        let mut p = parser("{String: Integer}");
1670        let t = p.parse_type().unwrap();
1671        assert_eq!(
1672            t.value,
1673            SchemaType::Map(Box::new(SchemaType::String), Box::new(SchemaType::Integer))
1674        );
1675    }
1676
1677    // Parses a map type with Integer keys.
1678    #[test]
1679    fn parse_type_map_integer_keys() {
1680        let mut p = parser("{Integer: String}");
1681        let t = p.parse_type().unwrap();
1682        assert_eq!(
1683            t.value,
1684            SchemaType::Map(Box::new(SchemaType::Integer), Box::new(SchemaType::String))
1685        );
1686    }
1687
1688    // Map type field in a schema.
1689    #[test]
1690    fn schema_map_field() {
1691        let source = "(\n  attrs: {String: Integer},\n)";
1692        let schema = parse_schema(source).unwrap();
1693        assert_eq!(
1694            schema.root.fields[0].type_.value,
1695            SchemaType::Map(Box::new(SchemaType::String), Box::new(SchemaType::Integer))
1696        );
1697    }
1698
1699    // Map with enum key type is allowed.
1700    #[test]
1701    fn schema_map_enum_key() {
1702        let source = "(\n  scores: {Stat: Integer},\n)\nenum Stat { Str, Dex, Con }";
1703        let schema = parse_schema(source).unwrap();
1704        assert_eq!(
1705            schema.root.fields[0].type_.value,
1706            SchemaType::Map(Box::new(SchemaType::EnumRef("Stat".to_string())), Box::new(SchemaType::Integer))
1707        );
1708    }
1709
1710    // Map with Float key type is rejected.
1711    #[test]
1712    fn schema_map_float_key_rejected() {
1713        let source = "(\n  bad: {Float: String},\n)";
1714        let err = parse_schema(source).unwrap_err();
1715        assert!(matches!(err.kind, SchemaErrorKind::InvalidMapKeyType { .. }));
1716    }
1717
1718    // Map with Bool key type is rejected.
1719    #[test]
1720    fn schema_map_bool_key_rejected() {
1721        let source = "(\n  bad: {Bool: String},\n)";
1722        let err = parse_schema(source).unwrap_err();
1723        assert!(matches!(err.kind, SchemaErrorKind::InvalidMapKeyType { .. }));
1724    }
1725
1726    // ========================================================
1727    // Tuple type tests — parsing
1728    // ========================================================
1729
1730    // Parses a tuple type with two elements.
1731    #[test]
1732    fn parse_type_tuple() {
1733        let mut p = parser("(Float, Float)");
1734        let t = p.parse_type().unwrap();
1735        assert_eq!(t.value, SchemaType::Tuple(vec![SchemaType::Float, SchemaType::Float]));
1736    }
1737
1738    // Parses a tuple type with mixed types.
1739    #[test]
1740    fn parse_type_tuple_mixed() {
1741        let mut p = parser("(String, Integer, Bool)");
1742        let t = p.parse_type().unwrap();
1743        assert_eq!(
1744            t.value,
1745            SchemaType::Tuple(vec![SchemaType::String, SchemaType::Integer, SchemaType::Bool])
1746        );
1747    }
1748
1749    // Tuple type in a schema field.
1750    #[test]
1751    fn schema_tuple_field() {
1752        let source = "(\n  pos: (Float, Float),\n)";
1753        let schema = parse_schema(source).unwrap();
1754        assert_eq!(
1755            schema.root.fields[0].type_.value,
1756            SchemaType::Tuple(vec![SchemaType::Float, SchemaType::Float])
1757        );
1758    }
1759
1760    // Inline struct still works after tuple disambiguation.
1761    #[test]
1762    fn schema_struct_still_works() {
1763        let source = "(\n  cost: (generic: Integer,),\n)";
1764        let schema = parse_schema(source).unwrap();
1765        if let SchemaType::Struct(s) = &schema.root.fields[0].type_.value {
1766            assert_eq!(s.fields[0].name.value, "generic");
1767        } else {
1768            panic!("expected Struct");
1769        }
1770    }
1771
1772    // Empty parens still parse as empty struct.
1773    #[test]
1774    fn schema_empty_parens_is_struct() {
1775        let source = "(\n  empty: (),\n)";
1776        let schema = parse_schema(source).unwrap();
1777        assert!(matches!(schema.root.fields[0].type_.value, SchemaType::Struct(_)));
1778    }
1779
1780    // ========================================================
1781    // Enum variants with data — parsing
1782    // ========================================================
1783
1784    // Parses enum with data variants.
1785    #[test]
1786    fn parse_enum_data_variant() {
1787        let source = "enum Effect { Damage(Integer), Heal(Integer), Draw }";
1788        let schema = parse_schema(source).unwrap();
1789        let effect = schema.enums.get("Effect").unwrap();
1790        assert_eq!(effect.variants.get("Damage"), Some(&Some(SchemaType::Integer)));
1791        assert_eq!(effect.variants.get("Heal"), Some(&Some(SchemaType::Integer)));
1792        assert_eq!(effect.variants.get("Draw"), Some(&None));
1793    }
1794
1795    // Enum with struct data variant.
1796    #[test]
1797    fn parse_enum_struct_data_variant() {
1798        let source = "enum Action { Move((Integer, Integer)), Wait }";
1799        let schema = parse_schema(source).unwrap();
1800        let action = schema.enums.get("Action").unwrap();
1801        assert!(matches!(action.variants.get("Move"), Some(Some(SchemaType::Tuple(_)))));
1802        assert_eq!(action.variants.get("Wait"), Some(&None));
1803    }
1804}