Skip to main content

libconfig/
de.rs

1//! Deserializer for libconfig format
2
3use std::cell::Cell;
4
5use serde::Deserialize;
6use serde::de::{self, DeserializeSeed, IntoDeserializer, MapAccess, SeqAccess, Visitor};
7
8use crate::error::{Error, ErrorKind, Result};
9
10thread_local! {
11    /// Tracks whether the sequence about to be visited is a list `(...)` or
12    /// an array `[...]`. Set by this crate's deserializers immediately before
13    /// `visit_seq`, and taken (consumed) by `ValueVisitor`. `None` means the
14    /// visitor is being driven by a foreign `Deserializer` (e.g. serde_json),
15    /// which has no array/list distinction.
16    static IS_LIST_CONTEXT: Cell<Option<bool>> = const { Cell::new(None) };
17}
18
19/// Consumes and returns the pending sequence-kind marker, if one was set by
20/// this crate's deserializers.
21pub(crate) fn take_list_context() -> Option<bool> {
22    IS_LIST_CONTEXT.with(|c| c.take())
23}
24
25/// Marks the sequence about to be visited as a list (true) or array (false).
26pub(crate) fn set_list_context(is_list: bool) {
27    IS_LIST_CONTEXT.with(|c| c.set(Some(is_list)));
28}
29
30/// Clears any leftover sequence-kind marker. Called when leaving this
31/// crate's entry points so that a later `Value` deserialization driven by a
32/// foreign format cannot observe stale state.
33pub(crate) fn clear_list_context() {
34    IS_LIST_CONTEXT.with(|c| c.set(None));
35}
36
37/// Returns `s` with leading whitespace and comments (`#`, `//`, `/* */`)
38/// removed.
39///
40/// Line comments run to the end of the line or input (section 2.10 says
41/// "to the end of the line"). A block comment is only a comment between a
42/// `/*` and a closing `*/`; an unterminated `/*` is left in place so the
43/// parser reports an error at its position.
44pub(crate) fn strip_trivia(mut s: &str) -> &str {
45    loop {
46        s = s.trim_start();
47        if s.starts_with('#') || s.starts_with("//") {
48            match s.find('\n') {
49                Some(pos) => s = &s[pos + 1..],
50                None => return "",
51            }
52        } else if s.starts_with("/*") {
53            match s.find("*/") {
54                Some(pos) => s = &s[pos + 2..],
55                None => return s,
56            }
57        } else {
58            return s;
59        }
60    }
61}
62
63/// Returns true if `c` may appear in a setting name after the first character.
64/// Per the grammar, names match `[A-Za-z*][-A-Za-z0-9_*]*` (ASCII only).
65fn is_name_char(c: char) -> bool {
66    c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '*')
67}
68
69/// A structure that deserializes libconfig format into Rust values
70pub struct Deserializer<'de> {
71    input: &'de str,
72    /// True until the first nested value is parsed. The grammar defines a
73    /// configuration as a setting list (never a braced group), so `{` is
74    /// rejected at the document root.
75    at_root: bool,
76}
77
78impl<'de> Deserializer<'de> {
79    /// Create a deserializer from a `&str`
80    // std::str::FromStr cannot be implemented here: it does not permit
81    // borrowing from the input string
82    #[allow(clippy::should_implement_trait)]
83    pub fn from_str(input: &'de str) -> Self {
84        Deserializer {
85            input,
86            at_root: true,
87        }
88    }
89
90    /// Checks that the input has been fully consumed (only trailing
91    /// whitespace and comments remain).
92    ///
93    /// # Errors
94    ///
95    /// Returns [`ErrorKind::TrailingCharacters`] if unparsed input remains.
96    pub fn end(&mut self) -> Result<()> {
97        self.skip_whitespace();
98        if self.input.is_empty() {
99            Ok(())
100        } else {
101            Err(ErrorKind::TrailingCharacters.into())
102        }
103    }
104
105    /// Look at the first character without consuming it
106    fn peek_char(&self) -> Result<char> {
107        self.input.chars().next().ok_or_else(|| ErrorKind::Eof.into())
108    }
109
110    /// Consume and return the next character
111    fn next_char(&mut self) -> Result<char> {
112        let ch = self.peek_char()?;
113        self.input = &self.input[ch.len_utf8()..];
114        Ok(ch)
115    }
116
117    /// Skip whitespace and comments
118    fn skip_whitespace(&mut self) {
119        self.input = strip_trivia(self.input);
120    }
121
122    /// Check if the input starts with an identifier followed by '=' or ':',
123    /// indicating the start of a setting in an implicit group.
124    /// This is used by `deserialize_any` to disambiguate between booleans
125    /// (true/false) and identifiers that happen to start with t/T/f/F.
126    fn is_implicit_group_start(&self) -> bool {
127        let input = self.input;
128
129        // Skip the identifier: [A-Za-z\*][-A-Za-z0-9_\*]*
130        let mut len = 0;
131        for (idx, ch) in input.char_indices() {
132            if idx == 0 {
133                if ch.is_ascii_alphabetic() || ch == '*' {
134                    len = ch.len_utf8();
135                } else {
136                    return false;
137                }
138            } else if is_name_char(ch) {
139                len = idx + ch.len_utf8();
140            } else {
141                break;
142            }
143        }
144
145        if len == 0 {
146            return false;
147        }
148
149        // Skip whitespace and comments after the identifier
150        let rest = strip_trivia(&input[len..]);
151
152        // Check for '=' or ':'
153        matches!(rest.chars().next(), Some('=' | ':'))
154    }
155
156    /// Parse a boolean value (case-insensitive per libconfig spec)
157    fn parse_bool(&mut self) -> Result<bool> {
158        self.skip_whitespace();
159        // `.get()` (rather than slicing) avoids panicking when a multi-byte
160        // character straddles the boundary.
161        if self
162            .input
163            .get(..4)
164            .is_some_and(|s| s.eq_ignore_ascii_case("true"))
165            && !self.input[4..].chars().next().is_some_and(is_name_char)
166        {
167            self.input = &self.input[4..];
168            Ok(true)
169        } else if self
170            .input
171            .get(..5)
172            .is_some_and(|s| s.eq_ignore_ascii_case("false"))
173            && !self.input[5..].chars().next().is_some_and(is_name_char)
174        {
175            self.input = &self.input[5..];
176            Ok(false)
177        } else {
178            Err(ErrorKind::ExpectedBoolean.into())
179        }
180    }
181
182    /// Parse an identifier (setting name)
183    /// Per spec: names must match `[A-Za-z*][-A-Za-z0-9_*]*`
184    fn parse_identifier(&mut self) -> Result<&'de str> {
185        self.skip_whitespace();
186        let start = self.input;
187        let mut len = 0;
188
189        for (idx, ch) in self.input.char_indices() {
190            if idx == 0 {
191                if ch.is_ascii_alphabetic() || ch == '*' {
192                    len = ch.len_utf8();
193                } else {
194                    break;
195                }
196            } else if is_name_char(ch) {
197                len = idx + ch.len_utf8();
198            } else {
199                break;
200            }
201        }
202
203        if len == 0 {
204            // strip_trivia consumes every well-formed comment, so a leading
205            // "/*" here can only be an unterminated block comment
206            if self.input.starts_with("/*") {
207                return Err(ErrorKind::UnterminatedComment.into());
208            }
209            return Err(ErrorKind::ExpectedIdentifier.into());
210        }
211
212        let ident = &start[..len];
213        self.input = &self.input[len..];
214        Ok(ident)
215    }
216
217    /// Parse a single quoted string (without adjacent concatenation)
218    fn parse_single_string(&mut self) -> Result<String> {
219        if self.next_char()? != '"' {
220            return Err(ErrorKind::ExpectedString.into());
221        }
222
223        let mut result = String::new();
224        let mut escape = false;
225
226        loop {
227            if self.input.is_empty() {
228                return Err(ErrorKind::Eof.into());
229            }
230
231            let ch = self.next_char()?;
232
233            if escape {
234                match ch {
235                    '"' => result.push('"'),
236                    '\\' => result.push('\\'),
237                    'n' => result.push('\n'),
238                    'r' => result.push('\r'),
239                    't' => result.push('\t'),
240                    'f' => result.push('\x0C'),
241                    'b' => result.push('\x08'),
242                    'v' => result.push('\x0B'),
243                    'a' => result.push('\x07'),
244                    'x' => {
245                        // Parse hex escape \xNN (exactly two hex digits per spec).
246                        // `.get()` avoids panicking when a multi-byte character
247                        // straddles the two-byte boundary.
248                        let hex = self.input.get(..2).ok_or(ErrorKind::InvalidEscape)?;
249                        if let Ok(byte) = u8::from_str_radix(hex, 16) {
250                            result.push(byte as char);
251                            self.input = &self.input[2..];
252                        } else {
253                            return Err(ErrorKind::InvalidEscape.into());
254                        }
255                    }
256                    _ => return Err(ErrorKind::InvalidEscape.into()),
257                }
258                escape = false;
259            } else if ch == '\\' {
260                escape = true;
261            } else if ch == '"' {
262                break;
263            } else {
264                result.push(ch);
265            }
266        }
267
268        Ok(result)
269    }
270
271    /// Fast path for borrowed strings: a single quoted string containing no
272    /// escape sequences and not followed by an adjacent string (which would
273    /// require concatenation into an owned buffer). Returns `Ok(None)` —
274    /// consuming nothing — when the slow path is needed.
275    fn parse_borrowed_string(&mut self) -> Result<Option<&'de str>> {
276        let original = self.input;
277        if !original.starts_with('"') {
278            return Err(ErrorKind::ExpectedString.into());
279        }
280        let bytes = original.as_bytes();
281        let mut i = 1;
282        while i < bytes.len() {
283            match bytes[i] {
284                // Escapes need decoding: fall back to the owned path
285                b'\\' => return Ok(None),
286                b'"' => {
287                    // Adjacent string concatenation also needs owning
288                    if strip_trivia(&original[i + 1..]).starts_with('"') {
289                        return Ok(None);
290                    }
291                    let inner = &original[1..i];
292                    self.input = &original[i + 1..];
293                    return Ok(Some(inner));
294                }
295                _ => i += 1,
296            }
297        }
298        Err(ErrorKind::Eof.into())
299    }
300
301    /// Parse a string value with adjacent string concatenation
302    /// Per spec: "hello" " world" => "hello world"
303    fn parse_string(&mut self) -> Result<String> {
304        self.skip_whitespace();
305        let mut result = self.parse_single_string()?;
306
307        // Check for adjacent strings (concatenation)
308        loop {
309            self.skip_whitespace();
310            if self.input.starts_with('"') {
311                result.push_str(&self.parse_single_string()?);
312            } else {
313                break;
314            }
315        }
316
317        Ok(result)
318    }
319
320    /// Parse a number (integer or float)
321    ///
322    /// Follows the grammar terminals from the libconfig manual, Appendix B:
323    ///   integer    [-+]?[0-9]+            integer64  [-+]?[0-9]+L(L)?
324    ///   hex        0[xX][0-9A-Fa-f]{1,8}  hex64      0[xX][0-9A-Fa-f]{1,16}L(L)?
325    ///   bin        0[bB][01]{1,32}        bin64      0[bB][01]{1,64}L(L)?
326    ///   oct        0[oOqQ][0-7]{1,10}     oct64      0[oOqQ][0-7]{1,21}L(L)?
327    ///   float      digits with a decimal point and/or exponent [eE][-+]?[0-9]+
328    fn parse_number(&mut self) -> Result<Number> {
329        self.skip_whitespace();
330        let s = self.input;
331        let bytes = s.as_bytes();
332        let mut i = 0;
333
334        #[derive(PartialEq, Clone, Copy)]
335        enum Base {
336            Dec,
337            Hex,
338            Bin,
339            Oct,
340        }
341
342        // Sign is only allowed for decimal integers and floats
343        let has_sign = matches!(bytes.first(), Some(b'+' | b'-'));
344        if has_sign {
345            i += 1;
346        }
347
348        let mut base = Base::Dec;
349        if !has_sign && bytes.len() > i + 1 && bytes[i] == b'0' {
350            match bytes[i + 1] {
351                b'x' | b'X' => {
352                    base = Base::Hex;
353                    i += 2;
354                }
355                b'b' | b'B' => {
356                    base = Base::Bin;
357                    i += 2;
358                }
359                b'o' | b'O' | b'q' | b'Q' => {
360                    base = Base::Oct;
361                    i += 2;
362                }
363                _ => {}
364            }
365        }
366
367        let digits_start = i;
368        let mut digit_count = 0usize;
369        let mut is_float = false;
370
371        if base == Base::Dec {
372            while i < bytes.len() && bytes[i].is_ascii_digit() {
373                i += 1;
374                digit_count += 1;
375            }
376            // Fraction
377            if i < bytes.len() && bytes[i] == b'.' {
378                is_float = true;
379                i += 1;
380                while i < bytes.len() && bytes[i].is_ascii_digit() {
381                    i += 1;
382                    digit_count += 1;
383                }
384            }
385            // Exponent: [eE][-+]?[0-9]+ — only consumed if at least one digit follows
386            if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
387                let mut j = i + 1;
388                if j < bytes.len() && (bytes[j] == b'+' || bytes[j] == b'-') {
389                    j += 1;
390                }
391                let exp_digits_start = j;
392                while j < bytes.len() && bytes[j].is_ascii_digit() {
393                    j += 1;
394                }
395                if j > exp_digits_start {
396                    is_float = true;
397                    i = j;
398                }
399            }
400        } else {
401            while i < bytes.len()
402                && match base {
403                    Base::Hex => bytes[i].is_ascii_hexdigit(),
404                    Base::Bin => matches!(bytes[i], b'0' | b'1'),
405                    Base::Oct => matches!(bytes[i], b'0'..=b'7'),
406                    Base::Dec => unreachable!(),
407                }
408            {
409                i += 1;
410                digit_count += 1;
411            }
412        }
413
414        if digit_count == 0 {
415            return Err(ErrorKind::ExpectedInteger.into());
416        }
417
418        // Enforce the terminal digit-count bounds from Appendix B:
419        // bin64 {1,64}, oct64 {1,21}, hex64 {1,16}. Longer tokens are not
420        // derivable from the grammar even when their value would fit.
421        let max_digits = match base {
422            Base::Dec => usize::MAX, // [0-9]+ is unbounded; range-checked below
423            Base::Hex => 16,
424            Base::Bin => 64,
425            Base::Oct => 21,
426        };
427        if digit_count > max_digits {
428            return Err(ErrorKind::InvalidNumber.into());
429        }
430
431        let digits_end = i;
432
433        // 'L' or 'LL' suffix (integers only; floats never take a suffix)
434        let mut is_long = false;
435        if !is_float && i < bytes.len() && bytes[i] == b'L' {
436            is_long = true;
437            i += 1;
438            if i < bytes.len() && bytes[i] == b'L' {
439                i += 1;
440            }
441        }
442
443        let num_str = &s[..i];
444        self.input = &s[i..];
445
446        if is_float {
447            let val = num_str.parse::<f64>().map_err(|_| ErrorKind::InvalidNumber)?;
448            return Ok(Number::Float(val));
449        }
450
451        let digits = &s[digits_start..digits_end];
452        let (radix, promote_digits) = match base {
453            Base::Dec => (10, usize::MAX),
454            Base::Hex => (16, 8),
455            Base::Bin => (2, 32),
456            Base::Oct => (8, 10),
457        };
458
459        if base == Base::Dec {
460            let signed = &s[..digits_end];
461            if is_long {
462                let val = signed.parse::<i64>().map_err(|_| ErrorKind::InvalidNumber)?;
463                Ok(Number::I64(val))
464            } else {
465                // Auto-promote to i64 if value exceeds i32 range
466                match signed.parse::<i32>() {
467                    Ok(val) => Ok(Number::I32(val)),
468                    Err(_) => {
469                        let val = signed.parse::<i64>().map_err(|_| ErrorKind::InvalidNumber)?;
470                        Ok(Number::I64(val))
471                    }
472                }
473            }
474        } else {
475            let val = i64::from_str_radix(digits, radix).map_err(|_| ErrorKind::InvalidNumber)?;
476            if is_long || digit_count > promote_digits {
477                Ok(Number::I64(val))
478            } else {
479                // Values that use all 32 bits wrap to negative, matching the
480                // C library, which parses these with strtoul and casts to int.
481                Ok(Number::I32(val as i32))
482            }
483        }
484    }
485}
486
487/// Deserialize an instance of type `T` from a string of libconfig text.
488///
489/// # Errors
490///
491/// Returns an error if the input is not valid libconfig or does not match
492/// the shape of `T`. Parse errors carry the line and column of the failure
493/// (see [`Error::line`] and [`Error::column`]).
494pub fn from_str<'a, T>(s: &'a str) -> Result<T>
495where
496    T: Deserialize<'a>,
497{
498    let mut deserializer = Deserializer::from_str(s);
499    let result = T::deserialize(&mut deserializer).and_then(|t| {
500        deserializer.end()?;
501        Ok(t)
502    });
503    clear_list_context();
504    result.map_err(|e| {
505        let (line, column) = locate(s, deserializer.input);
506        e.with_location(line, column)
507    })
508}
509
510/// Deserialize an instance of type `T` from an [`std::io::Read`] source of
511/// libconfig text.
512///
513/// The entire source is read into memory before parsing. Note that, like
514/// [`from_str`], this does not process `@include` directives; use
515/// [`crate::Config::from_file`] for include support.
516///
517/// # Errors
518///
519/// Returns [`crate::ErrorKind::Io`] if reading fails, or a parse error as
520/// for [`from_str`].
521pub fn from_reader<R, T>(mut reader: R) -> Result<T>
522where
523    R: std::io::Read,
524    T: de::DeserializeOwned,
525{
526    let mut s = String::new();
527    reader
528        .read_to_string(&mut s)
529        .map_err(|e| ErrorKind::Io(e.to_string()))?;
530    from_str(&s)
531}
532
533/// Computes the 1-based line and column of the position in `original` where
534/// `remaining` begins.
535fn locate(original: &str, remaining: &str) -> (usize, usize) {
536    let consumed = original.len().saturating_sub(remaining.len());
537    let consumed_text = &original[..consumed.min(original.len())];
538    let line = consumed_text.matches('\n').count() + 1;
539    let column = consumed_text
540        .rsplit('\n')
541        .next()
542        .map(|l| l.chars().count())
543        .unwrap_or(0)
544        + 1;
545    (line, column)
546}
547
548#[derive(Debug, Clone)]
549enum Number {
550    I32(i32),
551    I64(i64),
552    Float(f64),
553}
554
555/// Implements an integer `deserialize_*` method with range checking.
556macro_rules! deserialize_integer {
557    ($method:ident, $visit:ident, $ty:ty) => {
558        fn $method<V>(self, visitor: V) -> Result<V::Value>
559        where
560            V: Visitor<'de>,
561        {
562            match self.parse_number()? {
563                Number::I32(v) => {
564                    visitor.$visit(<$ty>::try_from(v).map_err(|_| ErrorKind::IntegerOutOfRange)?)
565                }
566                Number::I64(v) => {
567                    visitor.$visit(<$ty>::try_from(v).map_err(|_| ErrorKind::IntegerOutOfRange)?)
568                }
569                Number::Float(_) => Err(ErrorKind::ExpectedInteger.into()),
570            }
571        }
572    };
573}
574
575impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> {
576    type Error = Error;
577
578    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
579    where
580        V: Visitor<'de>,
581    {
582        self.skip_whitespace();
583        // An empty configuration is valid per the grammar:
584        // <configuration> ::= <setting-list> | <empty>
585        if self.input.is_empty() {
586            return visitor.visit_map(ImplicitGroupAccess::new(self));
587        }
588        match self.peek_char()? {
589            't' | 'T' | 'f' | 'F' => {
590                // Could be a boolean OR the start of an identifier in an implicit group.
591                // Look ahead: if this is an identifier followed by '=' or ':', it's a setting.
592                if self.is_implicit_group_start() {
593                    visitor.visit_map(ImplicitGroupAccess::new(self))
594                } else {
595                    self.deserialize_bool(visitor)
596                }
597            }
598            '"' => self.deserialize_string(visitor),
599            '[' => self.deserialize_seq(visitor),
600            '(' => self.deserialize_seq(visitor), // Lists are treated as sequences
601            '{' => self.deserialize_map(visitor),
602            '-' | '+' | '0'..='9' | '.' => {
603                // Try to parse as number
604                let num = self.parse_number()?;
605                match num {
606                    Number::I32(v) => visitor.visit_i32(v),
607                    Number::I64(v) => visitor.visit_i64(v),
608                    Number::Float(v) => visitor.visit_f64(v),
609                }
610            }
611            c if c.is_ascii_alphabetic() || c == '*' => {
612                // Starts with a letter or '*' — must be an implicit group setting
613                visitor.visit_map(ImplicitGroupAccess::new(self))
614            }
615            '/' if self.input.starts_with("/*") => Err(ErrorKind::UnterminatedComment.into()),
616            _ => Err(ErrorKind::Syntax.into()),
617        }
618    }
619
620    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
621    where
622        V: Visitor<'de>,
623    {
624        visitor.visit_bool(self.parse_bool()?)
625    }
626
627    // Integer deserialization: values are range-checked rather than silently
628    // truncated. Per the manual (section 2.6), int/int64 are interchangeable
629    // only "to the extent that a conversion ... would not result in an
630    // overflow or underflow"; otherwise the call must fail. Floats are never
631    // silently converted to integers (number auto-conversion is off by
632    // default in libconfig).
633    deserialize_integer!(deserialize_i8, visit_i8, i8);
634    deserialize_integer!(deserialize_i16, visit_i16, i16);
635    deserialize_integer!(deserialize_i32, visit_i32, i32);
636    deserialize_integer!(deserialize_i64, visit_i64, i64);
637    deserialize_integer!(deserialize_u8, visit_u8, u8);
638    deserialize_integer!(deserialize_u16, visit_u16, u16);
639    deserialize_integer!(deserialize_u32, visit_u32, u32);
640    deserialize_integer!(deserialize_u64, visit_u64, u64);
641
642    fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value>
643    where
644        V: Visitor<'de>,
645    {
646        let num = self.parse_number()?;
647        match num {
648            Number::I32(v) => visitor.visit_f32(v as f32),
649            Number::I64(v) => visitor.visit_f32(v as f32),
650            Number::Float(v) => {
651                let f = v as f32;
652                // A finite f64 that becomes infinite in f32 is out of range
653                if f.is_infinite() && v.is_finite() {
654                    return Err(ErrorKind::FloatOutOfRange.into());
655                }
656                visitor.visit_f32(f)
657            }
658        }
659    }
660
661    fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
662    where
663        V: Visitor<'de>,
664    {
665        let num = self.parse_number()?;
666        match num {
667            Number::I32(v) => visitor.visit_f64(v as f64),
668            Number::I64(v) => visitor.visit_f64(v as f64),
669            Number::Float(v) => visitor.visit_f64(v),
670        }
671    }
672
673    fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
674    where
675        V: Visitor<'de>,
676    {
677        let s = self.parse_string()?;
678        let mut chars = s.chars();
679        let ch = chars.next().ok_or(ErrorKind::ExpectedString)?;
680        if chars.next().is_some() {
681            return Err(ErrorKind::ExpectedString.into());
682        }
683        visitor.visit_char(ch)
684    }
685
686    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
687    where
688        V: Visitor<'de>,
689    {
690        // String values must be quoted; setting names are handled separately
691        // by MapKeyDeserializer. Strings without escapes or concatenation
692        // borrow from the input, enabling zero-copy &str fields; others fall
693        // back to an owned String (so &str targets fail for escaped strings,
694        // as in serde_json).
695        self.skip_whitespace();
696        match self.parse_borrowed_string()? {
697            Some(s) => visitor.visit_borrowed_str(s),
698            None => visitor.visit_string(self.parse_string()?),
699        }
700    }
701
702    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
703    where
704        V: Visitor<'de>,
705    {
706        self.deserialize_str(visitor)
707    }
708
709    fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
710    where
711        V: Visitor<'de>,
712    {
713        self.deserialize_seq(visitor)
714    }
715
716    fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
717    where
718        V: Visitor<'de>,
719    {
720        self.deserialize_seq(visitor)
721    }
722
723    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
724    where
725        V: Visitor<'de>,
726    {
727        visitor.visit_some(self)
728    }
729
730    fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
731    where
732        V: Visitor<'de>,
733    {
734        visitor.visit_unit()
735    }
736
737    fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
738    where
739        V: Visitor<'de>,
740    {
741        self.deserialize_unit(visitor)
742    }
743
744    fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
745    where
746        V: Visitor<'de>,
747    {
748        visitor.visit_newtype_struct(self)
749    }
750
751    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
752    where
753        V: Visitor<'de>,
754    {
755        self.skip_whitespace();
756        let ch = self.peek_char()?;
757
758        if ch == '[' {
759            self.next_char()?;
760            set_list_context(false);
761            let value = visitor.visit_seq(ArrayAccess::new(self, ']', false))?;
762            self.skip_whitespace();
763            if self.next_char()? == ']' {
764                Ok(value)
765            } else {
766                Err(ErrorKind::ExpectedArrayEnd.into())
767            }
768        } else if ch == '(' {
769            self.next_char()?;
770            set_list_context(true);
771            let value = visitor.visit_seq(ArrayAccess::new(self, ')', true))?;
772            self.skip_whitespace();
773            if self.next_char()? == ')' {
774                Ok(value)
775            } else {
776                Err(ErrorKind::ExpectedListEnd.into())
777            }
778        } else {
779            Err(ErrorKind::ExpectedArray.into())
780        }
781    }
782
783    fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
784    where
785        V: Visitor<'de>,
786    {
787        self.deserialize_seq(visitor)
788    }
789
790    fn deserialize_tuple_struct<V>(
791        self,
792        _name: &'static str,
793        _len: usize,
794        visitor: V,
795    ) -> Result<V::Value>
796    where
797        V: Visitor<'de>,
798    {
799        self.deserialize_seq(visitor)
800    }
801
802    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
803    where
804        V: Visitor<'de>,
805    {
806        self.skip_whitespace();
807        // Braced group (nested only), top-level implicit group, or empty
808        // configuration. The grammar has no braces at the document root:
809        // <configuration> ::= <setting-list> | <empty>
810        if self.input.starts_with('{') {
811            if self.at_root {
812                return Err(ErrorKind::TopLevelBraces.into());
813            }
814            self.next_char()?;
815            let value = visitor.visit_map(GroupAccess::new(self))?;
816            self.skip_whitespace();
817            if self.next_char()? == '}' {
818                Ok(value)
819            } else {
820                Err(ErrorKind::ExpectedGroupEnd.into())
821            }
822        } else {
823            visitor.visit_map(ImplicitGroupAccess::new(self))
824        }
825    }
826
827    fn deserialize_struct<V>(
828        self,
829        _name: &'static str,
830        _fields: &'static [&'static str],
831        visitor: V,
832    ) -> Result<V::Value>
833    where
834        V: Visitor<'de>,
835    {
836        self.deserialize_map(visitor)
837    }
838
839    fn deserialize_enum<V>(
840        self,
841        _name: &'static str,
842        _variants: &'static [&'static str],
843        visitor: V,
844    ) -> Result<V::Value>
845    where
846        V: Visitor<'de>,
847    {
848        self.skip_whitespace();
849        match self.peek_char()? {
850            // Unit variant, written as a string: "VariantName"
851            '"' => visitor.visit_enum(self.parse_string()?.into_deserializer()),
852            // Data-carrying variant, written as a single-setting group:
853            // { VariantName = <value>; }. This is the serde value encoding
854            // for enums, not a document form, so it is exempt from the
855            // top-level brace check (like scalars at the root).
856            '{' => {
857                self.next_char()?;
858                self.at_root = false;
859                let value = visitor.visit_enum(EnumGroupAccess { de: &mut *self })?;
860                self.skip_whitespace();
861                if matches!(self.peek_char(), Ok(';' | ',')) {
862                    self.next_char()?;
863                    self.skip_whitespace();
864                }
865                if self.next_char()? == '}' {
866                    Ok(value)
867                } else {
868                    Err(ErrorKind::ExpectedGroupEnd.into())
869                }
870            }
871            _ => Err(ErrorKind::ExpectedString.into()),
872        }
873    }
874
875    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
876    where
877        V: Visitor<'de>,
878    {
879        visitor.visit_str(self.parse_identifier()?)
880    }
881
882    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
883    where
884        V: Visitor<'de>,
885    {
886        self.deserialize_any(visitor)
887    }
888}
889
890/// Deserializer for setting names (group keys). Names are bare identifiers
891/// matching `[A-Za-z*][-A-Za-z0-9_*]*`, never quoted strings.
892struct MapKeyDeserializer<'a, 'de: 'a> {
893    de: &'a mut Deserializer<'de>,
894}
895
896impl<'de> de::Deserializer<'de> for MapKeyDeserializer<'_, 'de> {
897    type Error = Error;
898
899    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
900    where
901        V: Visitor<'de>,
902    {
903        visitor.visit_borrowed_str(self.de.parse_identifier()?)
904    }
905
906    serde::forward_to_deserialize_any! {
907        bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string
908        bytes byte_buf option unit unit_struct newtype_struct seq tuple
909        tuple_struct map struct enum identifier ignored_any
910    }
911}
912
913/// EnumAccess for data-carrying variants written as `{ VariantName = value; }`
914struct EnumGroupAccess<'a, 'de: 'a> {
915    de: &'a mut Deserializer<'de>,
916}
917
918impl<'de, 'a> de::EnumAccess<'de> for EnumGroupAccess<'a, 'de> {
919    type Error = Error;
920    type Variant = VariantGroupAccess<'a, 'de>;
921
922    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
923    where
924        V: DeserializeSeed<'de>,
925    {
926        let variant = self.de.parse_identifier()?;
927        let variant_de: de::value::StrDeserializer<'de, Error> = variant.into_deserializer();
928        let value = seed.deserialize(variant_de)?;
929        self.de.skip_whitespace();
930        let ch = self.de.next_char()?;
931        if ch != '=' && ch != ':' {
932            return Err(ErrorKind::ExpectedEquals.into());
933        }
934        Ok((value, VariantGroupAccess { de: self.de }))
935    }
936}
937
938struct VariantGroupAccess<'a, 'de: 'a> {
939    de: &'a mut Deserializer<'de>,
940}
941
942impl<'de> de::VariantAccess<'de> for VariantGroupAccess<'_, 'de> {
943    type Error = Error;
944
945    fn unit_variant(self) -> Result<()> {
946        // Unit variants are written as plain strings, not groups
947        Err(ErrorKind::ExpectedString.into())
948    }
949
950    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
951    where
952        T: DeserializeSeed<'de>,
953    {
954        seed.deserialize(&mut *self.de)
955    }
956
957    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
958    where
959        V: Visitor<'de>,
960    {
961        de::Deserializer::deserialize_seq(&mut *self.de, visitor)
962    }
963
964    fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
965    where
966        V: Visitor<'de>,
967    {
968        de::Deserializer::deserialize_map(&mut *self.de, visitor)
969    }
970}
971
972struct ArrayAccess<'a, 'de: 'a> {
973    de: &'a mut Deserializer<'de>,
974    first: bool,
975    end_char: char,
976    is_list: bool,
977}
978
979impl<'a, 'de> ArrayAccess<'a, 'de> {
980    fn new(de: &'a mut Deserializer<'de>, end_char: char, is_list: bool) -> Self {
981        ArrayAccess {
982            de,
983            first: true,
984            end_char,
985            is_list,
986        }
987    }
988}
989
990impl<'de, 'a> SeqAccess<'de> for ArrayAccess<'a, 'de> {
991    type Error = Error;
992
993    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
994    where
995        T: DeserializeSeed<'de>,
996    {
997        self.de.skip_whitespace();
998
999        // Check if we're at the end
1000        if self.de.peek_char()? == self.end_char {
1001            return Ok(None);
1002        }
1003
1004        // Require comma between elements (except first)
1005        if !self.first {
1006            if self.de.peek_char()? == ',' {
1007                self.de.next_char()?;
1008                self.de.skip_whitespace();
1009                // Check for trailing comma
1010                if self.de.peek_char()? == self.end_char {
1011                    return Ok(None);
1012                }
1013            } else {
1014                return Err(ErrorKind::ExpectedArrayComma.into());
1015            }
1016        }
1017        self.first = false;
1018
1019        // Arrays may only contain scalar values (grammar: <array> ::=
1020        // "[" <scalar-value-list> "]"); groups, lists, and nested arrays
1021        // are only permitted inside lists.
1022        if !self.is_list && matches!(self.de.peek_char()?, '{' | '[' | '(') {
1023            return Err(ErrorKind::ArrayElementNotScalar.into());
1024        }
1025
1026        self.de.at_root = false;
1027        seed.deserialize(&mut *self.de).map(Some)
1028    }
1029}
1030
1031struct GroupAccess<'a, 'de: 'a> {
1032    de: &'a mut Deserializer<'de>,
1033    first: bool,
1034}
1035
1036impl<'a, 'de> GroupAccess<'a, 'de> {
1037    fn new(de: &'a mut Deserializer<'de>) -> Self {
1038        GroupAccess { de, first: true }
1039    }
1040}
1041
1042struct ImplicitGroupAccess<'a, 'de: 'a> {
1043    de: &'a mut Deserializer<'de>,
1044    first: bool,
1045}
1046
1047impl<'a, 'de> ImplicitGroupAccess<'a, 'de> {
1048    fn new(de: &'a mut Deserializer<'de>) -> Self {
1049        ImplicitGroupAccess { de, first: true }
1050    }
1051}
1052
1053impl<'de, 'a> MapAccess<'de> for GroupAccess<'a, 'de> {
1054    type Error = Error;
1055
1056    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
1057    where
1058        K: DeserializeSeed<'de>,
1059    {
1060        self.de.skip_whitespace();
1061
1062        // Check if we're at the end
1063        if self.de.peek_char()? == '}' {
1064            return Ok(None);
1065        }
1066
1067        // Require semicolon or comma between settings (except first)
1068        if !self.first {
1069            let ch = self.de.peek_char()?;
1070            if ch == ';' || ch == ',' {
1071                self.de.next_char()?;
1072                self.de.skip_whitespace();
1073                // Check for trailing separator
1074                if self.de.peek_char()? == '}' {
1075                    return Ok(None);
1076                }
1077            }
1078            // Semicolons are optional, so don't error if missing
1079        }
1080        self.first = false;
1081
1082        seed.deserialize(MapKeyDeserializer { de: &mut *self.de })
1083            .map(Some)
1084    }
1085
1086    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
1087    where
1088        V: DeserializeSeed<'de>,
1089    {
1090        self.de.skip_whitespace();
1091
1092        // Expect '=' or ':'
1093        let ch = self.de.next_char()?;
1094        if ch != '=' && ch != ':' {
1095            return Err(ErrorKind::ExpectedEquals.into());
1096        }
1097
1098        self.de.at_root = false;
1099        seed.deserialize(&mut *self.de)
1100    }
1101}
1102
1103impl<'de, 'a> MapAccess<'de> for ImplicitGroupAccess<'a, 'de> {
1104    type Error = Error;
1105
1106    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
1107    where
1108        K: DeserializeSeed<'de>,
1109    {
1110        self.de.skip_whitespace();
1111
1112        // Check if we're at the end (end of input)
1113        if self.de.input.is_empty() {
1114            return Ok(None);
1115        }
1116
1117        // Require semicolon or comma between settings (except first)
1118        if !self.first {
1119            let ch = self.de.peek_char()?;
1120            if ch == ';' || ch == ',' {
1121                self.de.next_char()?;
1122                self.de.skip_whitespace();
1123                // Check for trailing separator
1124                if self.de.input.is_empty() {
1125                    return Ok(None);
1126                }
1127            }
1128            // Semicolons are optional, so don't error if missing
1129        }
1130        self.first = false;
1131
1132        seed.deserialize(MapKeyDeserializer { de: &mut *self.de })
1133            .map(Some)
1134    }
1135
1136    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
1137    where
1138        V: DeserializeSeed<'de>,
1139    {
1140        self.de.skip_whitespace();
1141
1142        // Expect '=' or ':'
1143        let ch = self.de.next_char()?;
1144        if ch != '=' && ch != ':' {
1145            return Err(ErrorKind::ExpectedEquals.into());
1146        }
1147
1148        self.de.at_root = false;
1149        seed.deserialize(&mut *self.de)
1150    }
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155    use super::*;
1156    use serde::Deserialize;
1157
1158    #[test]
1159    fn test_deserialize_primitives() {
1160        assert_eq!(from_str::<u32>("42").unwrap(), 42);
1161        assert_eq!(from_str::<i64>("42L").unwrap(), 42);
1162        assert_eq!(from_str::<f64>("2.25").unwrap(), 2.25);
1163        assert!(from_str::<bool>("true").unwrap());
1164        assert_eq!(from_str::<String>("\"hello\"").unwrap(), "hello");
1165    }
1166
1167    #[test]
1168    fn test_deserialize_array() {
1169        let arr: Vec<i32> = from_str("[1, 2, 3]").unwrap();
1170        assert_eq!(arr, vec![1, 2, 3]);
1171    }
1172
1173    #[test]
1174    fn test_deserialize_struct() {
1175        #[derive(Debug, Deserialize, PartialEq)]
1176        struct Test {
1177            a: i32,
1178            b: String,
1179        }
1180
1181        let input = r#"a = 42; b = "hello";"#;
1182        let result: Test = from_str(input).unwrap();
1183        assert_eq!(
1184            result,
1185            Test {
1186                a: 42,
1187                b: "hello".to_string()
1188            }
1189        );
1190    }
1191
1192    #[test]
1193    fn test_deserialize_with_comments() {
1194        let input = r#"
1195            # This is a comment
1196            // Another comment
1197            a = 42; /* inline comment */
1198            b = "hello";
1199        "#;
1200
1201        #[derive(Debug, Deserialize, PartialEq)]
1202        struct Test {
1203            a: i32,
1204            b: String,
1205        }
1206
1207        let result: Test = from_str(input).unwrap();
1208        assert_eq!(
1209            result,
1210            Test {
1211                a: 42,
1212                b: "hello".to_string()
1213            }
1214        );
1215    }
1216
1217    #[test]
1218    fn test_case_insensitive_booleans() {
1219        assert!(from_str::<bool>("true").unwrap());
1220        assert!(from_str::<bool>("True").unwrap());
1221        assert!(from_str::<bool>("TRUE").unwrap());
1222        assert!(!from_str::<bool>("false").unwrap());
1223        assert!(!from_str::<bool>("False").unwrap());
1224        assert!(!from_str::<bool>("FALSE").unwrap());
1225        assert!(!from_str::<bool>("FaLsE").unwrap());
1226    }
1227
1228    #[test]
1229    fn test_bool_word_boundary() {
1230        // "trueblah" should not parse as bool true
1231        assert!(from_str::<bool>("trueblah").is_err());
1232        assert!(from_str::<bool>("falsehood").is_err());
1233    }
1234
1235    #[test]
1236    fn test_adjacent_string_concatenation() {
1237        let result: String = from_str(r#""hello" " world""#).unwrap();
1238        assert_eq!(result, "hello world");
1239
1240        let result: String = from_str(r#""a" "b" "c""#).unwrap();
1241        assert_eq!(result, "abc");
1242    }
1243
1244    #[test]
1245    fn test_integer_auto_promotion() {
1246        // Value exceeding i32 range should auto-promote to i64
1247        let val: i64 = from_str("3000000000").unwrap();
1248        assert_eq!(val, 3_000_000_000i64);
1249    }
1250
1251    #[test]
1252    fn test_hex_binary_octal() {
1253        assert_eq!(from_str::<i32>("0xFF").unwrap(), 255);
1254        assert_eq!(from_str::<i32>("0b1010").unwrap(), 10);
1255        assert_eq!(from_str::<i32>("0o17").unwrap(), 15);
1256        assert_eq!(from_str::<i32>("0q17").unwrap(), 15);
1257    }
1258
1259    #[test]
1260    fn test_hex_auto_promotion_by_digit_count() {
1261        // 9+ hex digits should auto-promote to i64
1262        let val: i64 = from_str("0x1FFFFFFFF").unwrap();
1263        assert_eq!(val, 0x1FFFFFFFFi64);
1264    }
1265
1266    #[test]
1267    fn test_float_leading_dot() {
1268        let val: f64 = from_str(".5").unwrap();
1269        assert_eq!(val, 0.5);
1270    }
1271
1272    #[test]
1273    fn test_list_deserialization() {
1274        let arr: Vec<i32> = from_str("(1, 2, 3)").unwrap();
1275        assert_eq!(arr, vec![1, 2, 3]);
1276    }
1277
1278    #[test]
1279    fn test_implicit_group_as_value() {
1280        use crate::Value;
1281
1282        let raw_string = r#"
1283            title = "My HTTP server";
1284            listen_ports = [ 80, 443 ];
1285            misc = {
1286                owner = "Chuck Norris";
1287                location = "CA";
1288                contact = {
1289                    phone = "415-256-9999";
1290                    emails = ["chuck@norris.com", "chuck.norris@gmail.com"];
1291                };
1292            };"#;
1293
1294        let parsed_value: Value = from_str(raw_string).expect("Failed to parse value");
1295        assert_eq!(parsed_value["title"], "My HTTP server");
1296        assert_eq!(parsed_value["listen_ports"][0], 80);
1297        assert_eq!(parsed_value["listen_ports"][1], 443);
1298        assert_eq!(parsed_value["misc"]["owner"], "Chuck Norris");
1299        assert_eq!(parsed_value["misc"]["contact"]["phone"], "415-256-9999");
1300    }
1301
1302    #[test]
1303    fn test_implicit_group_bool_key() {
1304        // Identifiers starting with t/f should work as setting names
1305        use crate::Value;
1306
1307        let input = r#"flag = true; total = 100;"#;
1308        let v: Value = from_str(input).expect("Failed to parse");
1309        assert_eq!(v["flag"], true);
1310        assert_eq!(v["total"], 100);
1311    }
1312
1313    #[test]
1314    fn test_standalone_bool_still_works() {
1315        // Plain booleans without '=' should still parse as booleans
1316        assert!(from_str::<bool>("true").unwrap());
1317        assert!(!from_str::<bool>("false").unwrap());
1318
1319        use crate::Value;
1320        let v: Value = from_str("true").unwrap();
1321        assert_eq!(v, Value::Bool(true));
1322
1323        let v: Value = from_str("false").unwrap();
1324        assert_eq!(v, Value::Bool(false));
1325    }
1326
1327    #[test]
1328    fn test_optional_semicolons() {
1329        #[derive(Debug, Deserialize, PartialEq)]
1330        struct Test {
1331            a: i32,
1332            b: i32,
1333            c: i32,
1334        }
1335
1336        // With semicolons
1337        let result: Test = from_str(r#"a = 1; b = 2; c = 3;"#).unwrap();
1338        assert_eq!(result, Test { a: 1, b: 2, c: 3 });
1339
1340        // Without semicolons (settings separated only by whitespace)
1341        let result: Test = from_str("a = 1\n b = 2\n c = 3").unwrap();
1342        assert_eq!(result, Test { a: 1, b: 2, c: 3 });
1343
1344        // Mixed: some with, some without
1345        let result: Test = from_str("a = 1; b = 2\n c = 3").unwrap();
1346        assert_eq!(result, Test { a: 1, b: 2, c: 3 });
1347
1348        // Trailing semicolon before closing brace
1349        let result: Test = from_str(r#"a = 1; b = 2; c = 3;"#).unwrap();
1350        assert_eq!(result, Test { a: 1, b: 2, c: 3 });
1351    }
1352
1353    #[test]
1354    fn test_commas_as_setting_separators() {
1355        #[derive(Debug, Deserialize, PartialEq)]
1356        struct Test {
1357            a: i32,
1358            b: i32,
1359            c: i32,
1360        }
1361
1362        // Commas instead of semicolons
1363        let result: Test = from_str(r#"a = 1, b = 2, c = 3"#).unwrap();
1364        assert_eq!(result, Test { a: 1, b: 2, c: 3 });
1365
1366        // Trailing comma before closing brace
1367        let result: Test = from_str(r#"a = 1, b = 2, c = 3,"#).unwrap();
1368        assert_eq!(result, Test { a: 1, b: 2, c: 3 });
1369    }
1370
1371    #[test]
1372    fn test_optional_separators_in_implicit_group() {
1373        use crate::Value;
1374
1375        // Implicit group (no braces) without semicolons
1376        let v: Value = from_str("a = 1\nb = 2\nc = 3").unwrap();
1377        assert_eq!(v["a"], 1);
1378        assert_eq!(v["b"], 2);
1379        assert_eq!(v["c"], 3);
1380
1381        // Implicit group with semicolons
1382        let v: Value = from_str("a = 1; b = 2; c = 3;").unwrap();
1383        assert_eq!(v["a"], 1);
1384        assert_eq!(v["b"], 2);
1385        assert_eq!(v["c"], 3);
1386    }
1387
1388    #[test]
1389    fn test_auto_promotion_i64_via_value() {
1390        use crate::Value;
1391
1392        // i32 range value stays as Integer
1393        let v: Value = from_str("42").unwrap();
1394        assert!(v.is_i32());
1395        assert_eq!(v, Value::Integer(42));
1396
1397        // Value exceeding i32::MAX auto-promotes to Integer64
1398        let v: Value = from_str("3000000000").unwrap();
1399        assert!(v.is_i64());
1400        assert_eq!(v.as_i64(), Some(3_000_000_000i64));
1401
1402        // Explicit L suffix produces Integer64
1403        let v: Value = from_str("42L").unwrap();
1404        assert!(v.is_i64());
1405        assert_eq!(v.as_i64(), Some(42i64));
1406
1407        // Negative value exceeding i32::MIN auto-promotes to Integer64
1408        let v: Value = from_str("-3000000000").unwrap();
1409        assert!(v.is_i64());
1410        assert_eq!(v.as_i64(), Some(-3_000_000_000i64));
1411
1412        // i32::MAX is still i32
1413        let v: Value = from_str("2147483647").unwrap();
1414        assert!(v.is_i32());
1415
1416        // i32::MAX + 1 promotes to i64
1417        let v: Value = from_str("2147483648").unwrap();
1418        assert!(v.is_i64());
1419    }
1420
1421    #[test]
1422    fn test_negative_numbers() {
1423        // Negative integer
1424        assert_eq!(from_str::<i32>("-42").unwrap(), -42);
1425        assert_eq!(from_str::<i32>("-1").unwrap(), -1);
1426
1427        // Negative i64
1428        assert_eq!(from_str::<i64>("-42L").unwrap(), -42);
1429
1430        // Negative float
1431        assert_eq!(from_str::<f64>("-2.25").unwrap(), -2.25);
1432        assert_eq!(from_str::<f64>("-0.5").unwrap(), -0.5);
1433
1434        // Negative in a struct
1435        #[derive(Debug, Deserialize, PartialEq)]
1436        struct Coords {
1437            x: i32,
1438            y: i32,
1439            z: f64,
1440        }
1441
1442        let result: Coords = from_str(r#"x = -10; y = -20; z = -1.5;"#).unwrap();
1443        assert_eq!(
1444            result,
1445            Coords {
1446                x: -10,
1447                y: -20,
1448                z: -1.5,
1449            }
1450        );
1451
1452        // Negative via Value
1453        use crate::Value;
1454        let v: Value = from_str("-42").unwrap();
1455        assert_eq!(v, Value::Integer(-42));
1456
1457        let v: Value = from_str("-2.25").unwrap();
1458        assert_eq!(v, Value::Float(-2.25));
1459    }
1460
1461    #[test]
1462    fn test_identifier_must_start_with_letter_or_asterisk() {
1463        #[derive(Debug, Deserialize)]
1464        #[allow(dead_code)]
1465        struct Test {
1466            a: i32,
1467        }
1468        // Valid identifiers
1469        let _: Test = from_str(r#"a = 1;"#).unwrap();
1470
1471        // Identifier starting with digit should fail
1472        assert!(from_str::<Test>(r#"1a = 1;"#).is_err());
1473    }
1474
1475    #[test]
1476    fn test_float_signed_exponent() {
1477        // Grammar: exponent is [eE][-+]?[0-9]+
1478        assert_eq!(from_str::<f64>("1e+5").unwrap(), 1e5);
1479        assert_eq!(from_str::<f64>("1e-5").unwrap(), 1e-5);
1480        assert_eq!(from_str::<f64>("3.1e-2").unwrap(), 3.1e-2);
1481        assert_eq!(from_str::<f64>("-2.5E+3").unwrap(), -2.5e3);
1482        assert_eq!(from_str::<f64>("1e5").unwrap(), 1e5);
1483
1484        use crate::Value;
1485        let v: Value = from_str("avogadro = 6.02e+23;").unwrap();
1486        assert_eq!(v["avogadro"], 6.02e23);
1487
1488        // Exponent without digits is not consumed as a float
1489        assert!(from_str::<f64>("1e").is_err());
1490        assert!(from_str::<f64>("1e+").is_err());
1491    }
1492
1493    #[test]
1494    fn test_ll_suffix() {
1495        // Grammar: integer64 is [-+]?[0-9]+L(L)?, same for hex/oct/bin
1496        assert_eq!(from_str::<i64>("42LL").unwrap(), 42);
1497        assert_eq!(from_str::<i64>("-42LL").unwrap(), -42);
1498        assert_eq!(from_str::<i64>("0xFFLL").unwrap(), 255);
1499        assert_eq!(from_str::<i64>("0b1010LL").unwrap(), 10);
1500        assert_eq!(from_str::<i64>("0o17LL").unwrap(), 15);
1501
1502        use crate::Value;
1503        let v: Value = from_str("42LL").unwrap();
1504        assert!(v.is_i64());
1505    }
1506
1507    #[test]
1508    fn test_multibyte_input_does_not_panic() {
1509        // These previously panicked on non-char-boundary slicing
1510        use crate::Value;
1511        assert!(from_str::<Value>("tr€").is_err());
1512        assert!(from_str::<bool>("tr€").is_err());
1513        assert!(from_str::<bool>("fals€").is_err());
1514        assert!(from_str::<String>("\"\\x€\"").is_err());
1515    }
1516
1517    #[test]
1518    fn test_integer_range_checked() {
1519        // Out-of-range values error instead of silently wrapping (manual
1520        // section 2.6: conversions fail on overflow/underflow)
1521        assert!(matches!(from_str::<u8>("300"), Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange));
1522        assert!(matches!(from_str::<u32>("-1"), Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange));
1523        assert!(matches!(
1524            from_str::<i8>("128"),
1525            Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange
1526        ));
1527        assert!(matches!(
1528            from_str::<i32>("3000000000"),
1529            Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange
1530        ));
1531
1532        // In-range conversions succeed in both directions
1533        assert_eq!(from_str::<u8>("255").unwrap(), 255);
1534        assert_eq!(from_str::<i32>("100L").unwrap(), 100);
1535        assert_eq!(from_str::<i8>("-128").unwrap(), -128);
1536    }
1537
1538    #[test]
1539    fn test_float_not_coerced_to_integer() {
1540        // Number auto-conversion is off by default in libconfig
1541        assert!(from_str::<i8>("3.9").is_err());
1542        assert!(from_str::<i64>("1.0").is_err());
1543        // Integers are still accepted where floats are expected (lossless)
1544        assert_eq!(from_str::<f64>("42").unwrap(), 42.0);
1545    }
1546
1547    #[test]
1548    fn test_unquoted_string_values_rejected() {
1549        // Per the manual, a value is only a string if it is enclosed in
1550        // double quotes; anything else "results in a parse error"
1551        assert!(from_str::<String>("foo").is_err());
1552
1553        #[derive(Debug, Deserialize)]
1554        #[allow(dead_code)]
1555        struct S {
1556            name: String,
1557        }
1558        assert!(from_str::<S>("name = foo;").is_err());
1559        assert!(from_str::<S>("name = \"foo\";").is_ok());
1560    }
1561
1562    #[test]
1563    fn test_empty_configuration() {
1564        // Grammar: <configuration> ::= <setting-list> | <empty>
1565        use crate::Value;
1566        let v: Value = from_str("").unwrap();
1567        assert_eq!(v, Value::Group(crate::Map::new()));
1568
1569        let v: Value = from_str("  # just a comment\n").unwrap();
1570        assert_eq!(v, Value::Group(crate::Map::new()));
1571
1572        #[derive(Debug, Deserialize, PartialEq)]
1573        struct AllOptional {
1574            a: Option<i32>,
1575        }
1576        assert_eq!(from_str::<AllOptional>("").unwrap(), AllOptional { a: None });
1577    }
1578
1579    #[test]
1580    fn test_map_from_implicit_group() {
1581        use indexmap::IndexMap;
1582        let m: IndexMap<String, i32> = from_str("a = 1; b = 2;").unwrap();
1583        assert_eq!(m["a"], 1);
1584        assert_eq!(m["b"], 2);
1585
1586        let m: IndexMap<String, i32> = from_str("").unwrap();
1587        assert!(m.is_empty());
1588    }
1589
1590    #[test]
1591    fn test_top_level_braces_rejected() {
1592        // Grammar: <configuration> ::= <setting-list> | <empty>. A braced
1593        // group is only a <value>; it cannot be the document itself.
1594        use crate::Value;
1595        use indexmap::IndexMap;
1596
1597        assert!(matches!(
1598            from_str::<Value>("{ a = 1; }"),
1599            Err(e) if *e.kind() == ErrorKind::TopLevelBraces
1600        ));
1601        assert!(matches!(
1602            from_str::<IndexMap<String, i32>>("{ a = 1; }"),
1603            Err(e) if *e.kind() == ErrorKind::TopLevelBraces
1604        ));
1605
1606        #[derive(Debug, Deserialize)]
1607        #[allow(dead_code)]
1608        struct S {
1609            a: i32,
1610        }
1611        assert!(matches!(
1612            from_str::<S>("{ a = 1; }"),
1613            Err(e) if *e.kind() == ErrorKind::TopLevelBraces
1614        ));
1615
1616        // Braces remain required for nested groups
1617        let v: Value = from_str("g = { a = 1; }; l = ({ b = 2; });").unwrap();
1618        assert_eq!(v["g.a"], 1);
1619        assert_eq!(v["l.[0].b"], 2);
1620    }
1621
1622    #[test]
1623    fn test_arrays_are_scalar_only() {
1624        use crate::Value;
1625        // Groups, lists, and nested arrays are not allowed in arrays
1626        assert!(matches!(
1627            from_str::<Value>("x = [{ a = 1; }];"),
1628            Err(e) if *e.kind() == ErrorKind::ArrayElementNotScalar
1629        ));
1630        assert!(matches!(
1631            from_str::<Value>("x = [[1, 2]];"),
1632            Err(e) if *e.kind() == ErrorKind::ArrayElementNotScalar
1633        ));
1634        assert!(matches!(
1635            from_str::<Value>("x = [(1, 2)];"),
1636            Err(e) if *e.kind() == ErrorKind::ArrayElementNotScalar
1637        ));
1638        // ... but all of those are fine in lists
1639        assert!(from_str::<Value>("x = ({ a = 1; }, [1, 2], (3));").is_ok());
1640    }
1641
1642    #[test]
1643    fn test_array_elements_same_type() {
1644        use crate::Value;
1645        assert!(from_str::<Value>("x = [1, \"a\"];").is_err());
1646        assert!(from_str::<Value>("x = [true, 1];").is_err());
1647        // int and int64 are interchangeable (manual section 2.6)
1648        assert!(from_str::<Value>("x = [1, 5000000000];").is_ok());
1649        // heterogeneous values belong in lists
1650        assert!(from_str::<Value>("x = (1, \"a\");").is_ok());
1651    }
1652
1653    #[test]
1654    fn test_duplicate_setting_names_rejected() {
1655        use crate::Value;
1656        // Manual section 2.2: "each setting must have a unique name within
1657        // the group"; overrides are opt-in in the C library
1658        assert!(from_str::<Value>("a = 1; a = 2;").is_err());
1659        assert!(from_str::<Value>("g = { x = 1; x = 2; };").is_err());
1660    }
1661
1662    #[test]
1663    fn test_enum_round_trip() {
1664        use crate::to_string;
1665
1666        #[derive(Debug, Deserialize, serde::Serialize, PartialEq)]
1667        enum E {
1668            Unit,
1669            Newtype(i32),
1670            Tuple(i32, bool),
1671            Struct { x: i32 },
1672        }
1673
1674        for e in [
1675            E::Unit,
1676            E::Newtype(5),
1677            E::Tuple(7, true),
1678            E::Struct { x: 9 },
1679        ] {
1680            let s = to_string(&e).unwrap();
1681            let back: E = from_str(&s).unwrap();
1682            assert_eq!(back, e, "round-trip failed for {s:?}");
1683        }
1684
1685        // Explicit textual forms
1686        assert_eq!(from_str::<E>("\"Unit\"").unwrap(), E::Unit);
1687        assert_eq!(from_str::<E>("{ Newtype = 5; }").unwrap(), E::Newtype(5));
1688        assert_eq!(
1689            from_str::<E>("{ Tuple = (7, true); }").unwrap(),
1690            E::Tuple(7, true)
1691        );
1692        assert_eq!(
1693            from_str::<E>("{ Struct = { x = 9; }; }").unwrap(),
1694            E::Struct { x: 9 }
1695        );
1696        // ':' also works as the assignment token
1697        assert_eq!(from_str::<E>("{ Newtype: 5 }").unwrap(), E::Newtype(5));
1698    }
1699
1700    #[test]
1701    fn test_identifier_is_ascii_only() {
1702        use crate::Value;
1703        // Grammar: names match [A-Za-z*][-A-Za-z0-9_*]* (ASCII only)
1704        assert!(from_str::<Value>("café = 1;").is_err());
1705        assert!(from_str::<Value>("*wild-card_2* = 1;").is_ok());
1706    }
1707
1708    #[test]
1709    fn test_comment_between_identifier_and_equals() {
1710        use crate::Value;
1711        // t/f-initial identifiers with a comment before '=' still parse
1712        let v: Value = from_str("total /* count */ = 3;").unwrap();
1713        assert_eq!(v["total"], 3);
1714    }
1715
1716    #[test]
1717    fn test_string_concatenation_with_comments() {
1718        // Manual section 2.9 example: comments between adjacent strings
1719        let s: String =
1720            from_str("\"The quick\" /* comment */ \" brown fox \" // another comment\n\"jumped\"")
1721                .unwrap();
1722        assert_eq!(s, "The quick brown fox jumped");
1723    }
1724
1725    #[test]
1726    fn test_parse_errors_carry_line_and_column() {
1727        use crate::Value;
1728        let err = from_str::<Value>("a = 1;\nb = 2;\nc = @;\n").unwrap_err();
1729        assert_eq!(err.line(), Some(3));
1730        assert!(err.column().is_some());
1731        let msg = err.to_string();
1732        assert!(msg.contains("line 3"), "message was: {msg}");
1733
1734        // Non-parse errors carry no location
1735        let err = crate::to_string(&f64::NAN).unwrap_err();
1736        assert_eq!(err.line(), None);
1737    }
1738
1739    #[test]
1740    fn test_deserializer_end() {
1741        let mut de = Deserializer::from_str("42 # trailing comment");
1742        let v: i32 = serde::Deserialize::deserialize(&mut de).unwrap();
1743        assert_eq!(v, 42);
1744        assert!(de.end().is_ok());
1745
1746        let mut de = Deserializer::from_str("42 garbage");
1747        let _: i32 = serde::Deserialize::deserialize(&mut de).unwrap();
1748        assert!(matches!(
1749            de.end().unwrap_err().kind(),
1750            ErrorKind::TrailingCharacters
1751        ));
1752    }
1753
1754    #[test]
1755    fn test_f32_out_of_range_errors() {
1756        assert!(matches!(
1757            from_str::<f32>("1e300").unwrap_err().kind(),
1758            ErrorKind::FloatOutOfRange
1759        ));
1760        assert_eq!(from_str::<f32>("2.5").unwrap(), 2.5);
1761        // Large integers into f32 remain a lossy-but-finite promotion
1762        assert!(from_str::<f32>("5000000000L").is_ok());
1763    }
1764
1765    // =========================================================================
1766    // Appendix B conformance: one test per production / terminal of the
1767    // configuration file grammar. The manual is the sole authority.
1768    // =========================================================================
1769
1770    use crate::Value;
1771
1772    fn parses(input: &str) -> Value {
1773        from_str::<Value>(input)
1774            .unwrap_or_else(|e| panic!("expected {input:?} to parse, got: {e}"))
1775    }
1776
1777    fn rejects(input: &str) {
1778        assert!(
1779            from_str::<Value>(input).is_err(),
1780            "expected {input:?} to be rejected"
1781        );
1782    }
1783
1784    #[test]
1785    fn appendix_b_configuration() {
1786        // <configuration> ::= <setting-list> | <empty>
1787        assert_eq!(parses(""), Value::Group(crate::Map::new()));
1788        parses("a = 1;");
1789        parses("a = 1; b = 2;");
1790        // A braced group is not a <configuration>
1791        rejects("{ a = 1; }");
1792        // Nor is a bare <value>... except through the serde value model,
1793        // which is exercised via typed from_str, not Value documents
1794    }
1795
1796    #[test]
1797    fn appendix_b_setting() {
1798        // <setting> ::= <name> (":" | "=") <value> (";" | "," | <empty>)
1799        parses("a = 1;"); // "=" with ";"
1800        parses("a : 1;"); // ":" with ";"
1801        parses("a = 1,"); // "," terminator
1802        parses("a = 1"); // empty terminator
1803        parses("a = 1 b = 2"); // empty terminators between settings
1804        parses("a = 1, b = 2; c = 3");
1805        rejects("a 1;"); // missing assignment token
1806        rejects("a = ;"); // missing value
1807        rejects("= 1;"); // missing name
1808    }
1809
1810    #[test]
1811    fn appendix_b_name() {
1812        // <name> ::= [A-Za-z*][-A-Za-z0-9_*]*
1813        parses("a = 1;");
1814        parses("A = 1;");
1815        parses("* = 1;");
1816        parses("*a-b_c9* = 1;");
1817        rejects("1a = 1;"); // digit start
1818        rejects("_a = 1;"); // underscore start
1819        rejects("-a = 1;"); // dash start
1820        rejects("café = 1;"); // non-ASCII
1821    }
1822
1823    #[test]
1824    fn appendix_b_group() {
1825        // <group> ::= "{" (<setting-list> | <empty>) "}"
1826        let v = parses("g = { a = 1; b = 2; };");
1827        assert_eq!(v["g.a"], 1);
1828        let v = parses("g = { };");
1829        assert_eq!(v["g"], Value::Group(crate::Map::new()));
1830        rejects("g = { ; };"); // ";" alone is not a setting
1831        rejects("g = { a = 1; ;");
1832    }
1833
1834    #[test]
1835    fn appendix_b_array() {
1836        // <array> ::= "[" (<scalar-value-list> | <empty>) "]"
1837        parses("x = [];");
1838        parses("x = [1];");
1839        parses("x = [1, 2, 3];");
1840        parses("x = [1, 2,];"); // <scalar-value-list> ","
1841        parses("x = [-1, +2];"); // signed scalars
1842        parses("x = [\"a\" \"b\", \"c\"];"); // adjacent string concat
1843        rejects("x = [,];"); // no leading comma
1844        rejects("x = [1,, 2];"); // only one trailing comma derivable
1845        rejects("x = [{ a = 1; }];"); // groups are not scalar-values
1846        rejects("x = [[1]];"); // nor arrays
1847        rejects("x = [(1)];"); // nor lists
1848    }
1849
1850    #[test]
1851    fn appendix_b_list() {
1852        // <list> ::= "(" (<value-list> | <empty>) ")"
1853        parses("x = ();");
1854        parses("x = (1, \"two\", true, 4.5);");
1855        parses("x = (1, 2,);"); // <value-list> ","
1856        parses("x = (( \"abc\", 123, true ), 1.234, ( ));"); // manual's example
1857        parses("x = ({ a = 1; }, [1, 2]);"); // groups and arrays nest in lists
1858        rejects("x = (,);");
1859    }
1860
1861    #[test]
1862    fn appendix_b_boolean() {
1863        // <boolean> ::= ([Tt][Rr][Uu][Ee])|([Ff][Aa][Ll][Ss][Ee])
1864        for t in ["true", "True", "TRUE", "tRuE"] {
1865            assert_eq!(parses(&format!("b = {t};"))["b"], true);
1866        }
1867        for f in ["false", "False", "FALSE", "fAlSe"] {
1868            assert_eq!(parses(&format!("b = {f};"))["b"], false);
1869        }
1870        rejects("b = truex;"); // lexes as a name, not a boolean
1871        rejects("b = yes;");
1872    }
1873
1874    #[test]
1875    fn appendix_b_integers() {
1876        // <integer> [-+]?[0-9]+ / <integer64> [-+]?[0-9]+L(L)?
1877        assert_eq!(parses("i = 0;")["i"], 0);
1878        assert_eq!(parses("i = +42;")["i"], 42);
1879        assert_eq!(parses("i = -42;")["i"], -42);
1880        assert_eq!(parses("i = 007;")["i"], 7); // [0-9]+ allows leading zeros
1881        assert!(parses("i = 42L;")["i"].is_i64());
1882        assert!(parses("i = 42LL;")["i"].is_i64());
1883        assert!(parses("i = -42LL;")["i"].is_i64());
1884    }
1885
1886    #[test]
1887    fn appendix_b_radix_integers() {
1888        // <bin> 0[bB][01]{1,32} / <bin64> 0[bB][01]{1,64}L(L)?
1889        // <oct> 0[oOqQ][0-7]{1,10} / <oct64> ...{1,21}L(L)?
1890        // <hex> 0[xX][0-9A-Fa-f]{1,8} / <hex64> ...{1,16}L(L)?
1891        assert_eq!(parses("i = 0b1010;")["i"], 10);
1892        assert_eq!(parses("i = 0B1;")["i"], 1);
1893        assert_eq!(parses("i = 0o17;")["i"], 15);
1894        assert_eq!(parses("i = 0O17;")["i"], 15);
1895        assert_eq!(parses("i = 0q17;")["i"], 15);
1896        assert_eq!(parses("i = 0Q17;")["i"], 15);
1897        assert_eq!(parses("i = 0xff;")["i"], 255);
1898        assert_eq!(parses("i = 0XFF;")["i"], 255);
1899        assert!(parses("i = 0xffLL;")["i"].is_i64());
1900
1901        // No sign is derivable for radix forms
1902        rejects("i = -0x10;");
1903        rejects("i = +0b1;");
1904        // Digits outside the character class
1905        rejects("i = 0o8;");
1906        rejects("i = 0b2;");
1907        rejects("i = 0x;");
1908
1909        // Terminal digit-count bounds are part of the grammar, even when
1910        // the value would fit
1911        parses(&format!("i = 0b{};", "0".repeat(64)));
1912        rejects(&format!("i = 0b{};", "0".repeat(65)));
1913        parses(&format!("i = 0x{};", "0".repeat(16)));
1914        rejects(&format!("i = 0x{};", "0".repeat(17)));
1915        parses(&format!("i = 0o{};", "0".repeat(21)));
1916        rejects(&format!("i = 0o{};", "0".repeat(22)));
1917    }
1918
1919    #[test]
1920    fn appendix_b_float_exponent_case_insensitive() {
1921        // Grammar: [eE][-+]?[0-9]+ — uppercase E in every position
1922        assert_eq!(from_str::<f64>("1E5").unwrap(), 1e5);
1923        assert_eq!(from_str::<f64>("1E+5").unwrap(), 1e5);
1924        assert_eq!(from_str::<f64>("1E-5").unwrap(), 1e-5);
1925        assert_eq!(from_str::<f64>("2.5E3").unwrap(), 2500.0);
1926        assert_eq!(from_str::<f64>(".5E-2").unwrap(), 0.005);
1927        assert_eq!(from_str::<f64>("5.E3").unwrap(), 5000.0);
1928        assert_eq!(from_str::<f64>("-2.5E+3").unwrap(), -2500.0);
1929        // ... matching the lowercase forms exactly
1930        assert_eq!(
1931            from_str::<f64>("1E5").unwrap(),
1932            from_str::<f64>("1e5").unwrap()
1933        );
1934    }
1935
1936    #[test]
1937    fn test_zero_copy_borrowed_strings() {
1938        #[derive(Debug, Deserialize, PartialEq)]
1939        struct S<'a> {
1940            name: &'a str,
1941            owned: String,
1942        }
1943
1944        // Escape-free strings borrow directly from the input
1945        let input = r#"name = "zero copy"; owned = "also fine";"#;
1946        let s: S = from_str(input).unwrap();
1947        assert_eq!(s.name, "zero copy");
1948        assert_eq!(s.owned, "also fine");
1949
1950        // Escaped strings cannot be borrowed: &str targets fail (as in
1951        // serde_json), while String targets decode normally
1952        assert!(from_str::<S>(r#"name = "no\tborrow"; owned = "x";"#).is_err());
1953        #[derive(Debug, Deserialize)]
1954        struct Owned {
1955            owned: String,
1956        }
1957        let o: Owned = from_str(r#"owned = "has\ttab";"#).unwrap();
1958        assert_eq!(o.owned, "has\ttab");
1959
1960        // Concatenated strings also fall back to owned decoding
1961        let o: Owned = from_str(r#"owned = "a" "b";"#).unwrap();
1962        assert_eq!(o.owned, "ab");
1963    }
1964
1965    #[test]
1966    fn appendix_b_float() {
1967        // <float> ::= [-+]?[0-9]*\.[0-9]*([eE][-+]?[0-9]+)?
1968        //           | [-+]?[0-9]+(\.[0-9]*)?[eE][-+]?[0-9]+
1969        assert_eq!(parses("f = 1.5;")["f"], 1.5);
1970        assert_eq!(parses("f = .5;")["f"], 0.5);
1971        assert_eq!(parses("f = 5.;")["f"], 5.0);
1972        assert_eq!(parses("f = 5.e3;")["f"], 5000.0);
1973        assert_eq!(parses("f = 1e5;")["f"], 1e5);
1974        assert_eq!(parses("f = 1e+5;")["f"], 1e5);
1975        assert_eq!(parses("f = 1.25e-2;")["f"], 1.25e-2);
1976        assert_eq!(parses("f = -2.5E+3;")["f"], -2.5e3);
1977        // Exponent requires at least one digit
1978        rejects("f = 1e;");
1979        rejects("f = 1e+;");
1980        // A lone "." matches the Appendix B regex but contradicts section
1981        // 2.7 ("a series of one or more digits"); the prose resolves it
1982        rejects("f = .;");
1983        // Floats never take an L suffix
1984        rejects("f = 1.5L;");
1985    }
1986
1987    #[test]
1988    fn section_2_10_comments() {
1989        // All three comment styles, anywhere whitespace can appear
1990        let v = parses("# script\n// c++\n/* c */ a = /* mid */ 1; # end");
1991        assert_eq!(v["a"], 1);
1992
1993        // Block comments span line breaks
1994        assert_eq!(parses("a = /* spans\nseveral\nlines */ 1;")["a"], 1);
1995
1996        // Comments do not nest: the first */ closes the comment
1997        let v = parses("a = 1; /* outer /* inner */ b = 2;");
1998        assert_eq!(v["b"], 2);
1999
2000        // "comment delimiters appearing within quoted strings are treated
2001        // as literal text" (section 2.10)
2002        let v = parses(r#"s = "has # and // and /* inside";"#);
2003        assert_eq!(v["s"], "has # and // and /* inside");
2004
2005        // Line comments run "to the end of the line" — end of input counts
2006        assert_eq!(parses("a = 1; # no newline")["a"], 1);
2007        assert_eq!(parses("a = 1; // no newline")["a"], 1);
2008
2009        // A block comment is a delimited pair; an unterminated /* is not a
2010        // comment and therefore a syntax error
2011        assert!(matches!(
2012            from_str::<Value>("a = 1; /* never closed").unwrap_err().kind(),
2013            ErrorKind::UnterminatedComment
2014        ));
2015        assert!(matches!(
2016            from_str::<Value>("/* only").unwrap_err().kind(),
2017            ErrorKind::UnterminatedComment
2018        ));
2019        #[derive(Debug, Deserialize)]
2020        #[allow(dead_code)]
2021        struct S {
2022            a: i32,
2023        }
2024        assert!(from_str::<S>("a = /* open 1;").is_err());
2025
2026        // ... while a properly closed comment-only document is empty/valid
2027        assert_eq!(parses("/* nothing */"), Value::Group(crate::Map::new()));
2028    }
2029
2030    #[test]
2031    fn appendix_b_string() {
2032        // <string> ::= \"([^\"\\]|\\.)*\"
2033        assert_eq!(parses("s = \"\";")["s"], "");
2034        assert_eq!(parses("s = \"hello\";")["s"], "hello");
2035        // The negated class matches newline: strings may span lines
2036        assert_eq!(parses("s = \"line1\nline2\";")["s"], "line1\nline2");
2037        // Named escapes from the appendix
2038        assert_eq!(
2039            parses(r#"s = "\"\\\a\b\f\n\r\t\v";"#)["s"],
2040            "\"\\\x07\x08\x0C\n\r\t\x0B"
2041        );
2042        // \x takes exactly two hex digits
2043        assert_eq!(parses(r#"s = "\x41\x4212";"#)["s"], "AB12");
2044        // Section 2.9: "No other escape sequences are currently supported"
2045        rejects(r#"s = "\q";"#);
2046        rejects("s = \"unterminated;");
2047        // Adjacent strings concatenate (section 2.9)
2048        assert_eq!(parses(r#"s = "a" "b" "c";"#)["s"], "abc");
2049    }
2050}