toml_parser/decoder/
scalar.rs

1use winnow::stream::ContainsToken as _;
2use winnow::stream::FindSlice as _;
3use winnow::stream::Offset as _;
4use winnow::stream::Stream as _;
5
6use crate::decoder::StringBuilder;
7use crate::ErrorSink;
8use crate::Expected;
9use crate::ParseError;
10use crate::Raw;
11use crate::Span;
12
13const ALLOCATION_ERROR: &str = "could not allocate for string";
14
15#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
16pub enum ScalarKind {
17    String,
18    Boolean(bool),
19    DateTime,
20    Float,
21    Integer(IntegerRadix),
22}
23
24impl ScalarKind {
25    pub fn description(&self) -> &'static str {
26        match self {
27            Self::String => "string",
28            Self::Boolean(_) => "boolean",
29            Self::DateTime => "date-time",
30            Self::Float => "float",
31            Self::Integer(radix) => radix.description(),
32        }
33    }
34
35    pub fn invalid_description(&self) -> &'static str {
36        match self {
37            Self::String => "invalid string",
38            Self::Boolean(_) => "invalid boolean",
39            Self::DateTime => "invalid date-time",
40            Self::Float => "invalid float",
41            Self::Integer(radix) => radix.invalid_description(),
42        }
43    }
44}
45
46#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
47pub enum IntegerRadix {
48    #[default]
49    Dec,
50    Hex,
51    Oct,
52    Bin,
53}
54
55impl IntegerRadix {
56    pub fn description(&self) -> &'static str {
57        match self {
58            Self::Dec => "integer",
59            Self::Hex => "hexadecimal",
60            Self::Oct => "octal",
61            Self::Bin => "binary",
62        }
63    }
64
65    pub fn value(&self) -> u32 {
66        match self {
67            Self::Dec => 10,
68            Self::Hex => 16,
69            Self::Oct => 8,
70            Self::Bin => 2,
71        }
72    }
73
74    pub fn invalid_description(&self) -> &'static str {
75        match self {
76            Self::Dec => "invalid integer number",
77            Self::Hex => "invalid hexadecimal number",
78            Self::Oct => "invalid octal number",
79            Self::Bin => "invalid binary number",
80        }
81    }
82
83    fn validator(&self) -> fn(char) -> bool {
84        match self {
85            Self::Dec => |c| c.is_ascii_digit(),
86            Self::Hex => |c| c.is_ascii_hexdigit(),
87            Self::Oct => |c| matches!(c, '0'..='7'),
88            Self::Bin => |c| matches!(c, '0'..='1'),
89        }
90    }
91}
92
93pub(crate) fn decode_unquoted_scalar<'i>(
94    raw: Raw<'i>,
95    output: &mut dyn StringBuilder<'i>,
96    error: &mut dyn ErrorSink,
97) -> ScalarKind {
98    let s = raw.as_str();
99    let Some(first) = s.as_bytes().first() else {
100        return decode_invalid(raw, output, error);
101    };
102    match first {
103        // number starts
104        b'+' | b'-' => {
105            let value = &raw.as_str()[1..];
106            decode_sign_prefix(raw, value, output, error)
107        }
108        // Report as if they were numbers because its most likely a typo
109        b'_' => decode_datetime_or_float_or_integer(raw.as_str(), raw, output, error),
110        // Date/number starts
111        b'0' => decode_zero_prefix(raw.as_str(), false, raw, output, error),
112        b'1'..=b'9' => decode_datetime_or_float_or_integer(raw.as_str(), raw, output, error),
113        // Report as if they were numbers because its most likely a typo
114        b'.' => {
115            let kind = ScalarKind::Float;
116            let stream = raw.as_str();
117            ensure_float(stream, raw, error);
118            decode_float_or_integer(stream, raw, kind, output, error)
119        }
120        b't' | b'T' => {
121            const SYMBOL: &str = "true";
122            let kind = ScalarKind::Boolean(true);
123            let expected = &[Expected::Literal(SYMBOL)];
124            decode_symbol(raw, SYMBOL, kind, expected, output, error)
125        }
126        b'f' | b'F' => {
127            const SYMBOL: &str = "false";
128            let kind = ScalarKind::Boolean(false);
129            let expected = &[Expected::Literal(SYMBOL)];
130            decode_symbol(raw, SYMBOL, kind, expected, output, error)
131        }
132        b'i' | b'I' => {
133            const SYMBOL: &str = "inf";
134            let kind = ScalarKind::Float;
135            let expected = &[Expected::Literal(SYMBOL)];
136            decode_symbol(raw, SYMBOL, kind, expected, output, error)
137        }
138        b'n' | b'N' => {
139            const SYMBOL: &str = "nan";
140            let kind = ScalarKind::Float;
141            let expected = &[Expected::Literal(SYMBOL)];
142            decode_symbol(raw, SYMBOL, kind, expected, output, error)
143        }
144        _ => decode_invalid(raw, output, error),
145    }
146}
147
148pub(crate) fn decode_sign_prefix<'i>(
149    raw: Raw<'i>,
150    value: &'i str,
151    output: &mut dyn StringBuilder<'i>,
152    error: &mut dyn ErrorSink,
153) -> ScalarKind {
154    let Some(first) = value.as_bytes().first() else {
155        return decode_invalid(raw, output, error);
156    };
157    match first {
158        // number starts
159        b'+' | b'-' => {
160            let start = value.offset_from(&raw.as_str());
161            let end = start + 1;
162            error.report_error(
163                ParseError::new("redundant numeric sign")
164                    .with_context(Span::new_unchecked(0, raw.len()))
165                    .with_expected(&[])
166                    .with_unexpected(Span::new_unchecked(start, end)),
167            );
168
169            let value = &value[1..];
170            decode_sign_prefix(raw, value, output, error)
171        }
172        // Report as if they were numbers because its most likely a typo
173        b'_' => decode_datetime_or_float_or_integer(value, raw, output, error),
174        // Date/number starts
175        b'0' => decode_zero_prefix(value, true, raw, output, error),
176        b'1'..=b'9' => decode_datetime_or_float_or_integer(value, raw, output, error),
177        // Report as if they were numbers because its most likely a typo
178        b'.' => {
179            let kind = ScalarKind::Float;
180            let stream = raw.as_str();
181            ensure_float(stream, raw, error);
182            decode_float_or_integer(stream, raw, kind, output, error)
183        }
184        b'i' | b'I' => {
185            const SYMBOL: &str = "inf";
186            let kind = ScalarKind::Float;
187            if value != SYMBOL {
188                let expected = &[Expected::Literal(SYMBOL)];
189                let start = value.offset_from(&raw.as_str());
190                let end = start + value.len();
191                error.report_error(
192                    ParseError::new(kind.invalid_description())
193                        .with_context(Span::new_unchecked(0, raw.len()))
194                        .with_expected(expected)
195                        .with_unexpected(Span::new_unchecked(start, end)),
196                );
197                decode_as(raw, SYMBOL, kind, output, error)
198            } else {
199                decode_as_is(raw, kind, output, error)
200            }
201        }
202        b'n' | b'N' => {
203            const SYMBOL: &str = "nan";
204            let kind = ScalarKind::Float;
205            if value != SYMBOL {
206                let expected = &[Expected::Literal(SYMBOL)];
207                let start = value.offset_from(&raw.as_str());
208                let end = start + value.len();
209                error.report_error(
210                    ParseError::new(kind.invalid_description())
211                        .with_context(Span::new_unchecked(0, raw.len()))
212                        .with_expected(expected)
213                        .with_unexpected(Span::new_unchecked(start, end)),
214                );
215                decode_as(raw, SYMBOL, kind, output, error)
216            } else {
217                decode_as_is(raw, kind, output, error)
218            }
219        }
220        _ => decode_invalid(raw, output, error),
221    }
222}
223
224pub(crate) fn decode_zero_prefix<'i>(
225    value: &'i str,
226    signed: bool,
227    raw: Raw<'i>,
228    output: &mut dyn StringBuilder<'i>,
229    error: &mut dyn ErrorSink,
230) -> ScalarKind {
231    debug_assert_eq!(value.as_bytes()[0], b'0');
232    if value.len() == 1 {
233        let kind = ScalarKind::Integer(IntegerRadix::Dec);
234        // No extra validation needed
235        decode_float_or_integer(raw.as_str(), raw, kind, output, error)
236    } else {
237        let radix = value.as_bytes()[1];
238        match radix {
239            b'x' | b'X' => {
240                if signed {
241                    error.report_error(
242                        ParseError::new("integers with a radix cannot be signed")
243                            .with_context(Span::new_unchecked(0, raw.len()))
244                            .with_expected(&[])
245                            .with_unexpected(Span::new_unchecked(0, 1)),
246                    );
247                }
248                if radix == b'X' {
249                    let start = value.offset_from(&raw.as_str());
250                    let end = start + 2;
251                    error.report_error(
252                        ParseError::new("radix must be lowercase")
253                            .with_context(Span::new_unchecked(0, raw.len()))
254                            .with_expected(&[Expected::Literal("0x")])
255                            .with_unexpected(Span::new_unchecked(start, end)),
256                    );
257                }
258                let radix = IntegerRadix::Hex;
259                let kind = ScalarKind::Integer(radix);
260                let stream = &value[2..];
261                ensure_radixed_value(stream, raw, radix, error);
262                decode_float_or_integer(stream, raw, kind, output, error)
263            }
264            b'o' | b'O' => {
265                if signed {
266                    error.report_error(
267                        ParseError::new("integers with a radix cannot be signed")
268                            .with_context(Span::new_unchecked(0, raw.len()))
269                            .with_expected(&[])
270                            .with_unexpected(Span::new_unchecked(0, 1)),
271                    );
272                }
273                if radix == b'O' {
274                    let start = value.offset_from(&raw.as_str());
275                    let end = start + 2;
276                    error.report_error(
277                        ParseError::new("radix must be lowercase")
278                            .with_context(Span::new_unchecked(0, raw.len()))
279                            .with_expected(&[Expected::Literal("0o")])
280                            .with_unexpected(Span::new_unchecked(start, end)),
281                    );
282                }
283                let radix = IntegerRadix::Oct;
284                let kind = ScalarKind::Integer(radix);
285                let stream = &value[2..];
286                ensure_radixed_value(stream, raw, radix, error);
287                decode_float_or_integer(stream, raw, kind, output, error)
288            }
289            b'b' | b'B' => {
290                if signed {
291                    error.report_error(
292                        ParseError::new("integers with a radix cannot be signed")
293                            .with_context(Span::new_unchecked(0, raw.len()))
294                            .with_expected(&[])
295                            .with_unexpected(Span::new_unchecked(0, 1)),
296                    );
297                }
298                if radix == b'B' {
299                    let start = value.offset_from(&raw.as_str());
300                    let end = start + 2;
301                    error.report_error(
302                        ParseError::new("radix must be lowercase")
303                            .with_context(Span::new_unchecked(0, raw.len()))
304                            .with_expected(&[Expected::Literal("0b")])
305                            .with_unexpected(Span::new_unchecked(start, end)),
306                    );
307                }
308                let radix = IntegerRadix::Bin;
309                let kind = ScalarKind::Integer(radix);
310                let stream = &value[2..];
311                ensure_radixed_value(stream, raw, radix, error);
312                decode_float_or_integer(stream, raw, kind, output, error)
313            }
314            b'd' | b'D' => {
315                if signed {
316                    error.report_error(
317                        ParseError::new("integers with a radix cannot be signed")
318                            .with_context(Span::new_unchecked(0, raw.len()))
319                            .with_expected(&[])
320                            .with_unexpected(Span::new_unchecked(0, 1)),
321                    );
322                }
323                let radix = IntegerRadix::Dec;
324                let kind = ScalarKind::Integer(radix);
325                let stream = &value[2..];
326                error.report_error(
327                    ParseError::new("redundant integer number prefix")
328                        .with_context(Span::new_unchecked(0, raw.len()))
329                        .with_expected(&[])
330                        .with_unexpected(Span::new_unchecked(0, 2)),
331                );
332                ensure_radixed_value(stream, raw, radix, error);
333                decode_float_or_integer(stream, raw, kind, output, error)
334            }
335            _ => decode_datetime_or_float_or_integer(value, raw, output, error),
336        }
337    }
338}
339
340pub(crate) fn decode_datetime_or_float_or_integer<'i>(
341    value: &'i str,
342    raw: Raw<'i>,
343    output: &mut dyn StringBuilder<'i>,
344    error: &mut dyn ErrorSink,
345) -> ScalarKind {
346    let Some(digit_end) = value
347        .as_bytes()
348        .offset_for(|b| !(b'0'..=b'9').contains_token(b))
349    else {
350        let kind = ScalarKind::Integer(IntegerRadix::Dec);
351        let stream = raw.as_str();
352        ensure_no_leading_zero(value, raw, error);
353        return decode_float_or_integer(stream, raw, kind, output, error);
354    };
355
356    #[cfg(feature = "unsafe")] // SAFETY: ascii digits ensures UTF-8 boundary
357    let rest = unsafe { &value.get_unchecked(digit_end..) };
358    #[cfg(not(feature = "unsafe"))]
359    let rest = &value[digit_end..];
360
361    if rest.starts_with("-") || rest.starts_with(":") {
362        decode_as_is(raw, ScalarKind::DateTime, output, error)
363    } else if rest.contains(" ") {
364        decode_invalid(raw, output, error)
365    } else if is_float(rest) {
366        let kind = ScalarKind::Float;
367        let stream = raw.as_str();
368        ensure_float(value, raw, error);
369        decode_float_or_integer(stream, raw, kind, output, error)
370    } else if rest.starts_with("_") {
371        let kind = ScalarKind::Integer(IntegerRadix::Dec);
372        let stream = raw.as_str();
373        ensure_no_leading_zero(value, raw, error);
374        decode_float_or_integer(stream, raw, kind, output, error)
375    } else {
376        decode_invalid(raw, output, error)
377    }
378}
379
380/// ```abnf
381/// ;; Float
382///
383/// float = float-int-part ( exp / frac [ exp ] )
384///
385/// float-int-part = dec-int
386/// frac = decimal-point zero-prefixable-int
387/// decimal-point = %x2E               ; .
388/// zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT )
389///
390/// exp = "e" float-exp-part
391/// float-exp-part = [ minus / plus ] zero-prefixable-int
392/// ```
393pub(crate) fn ensure_float<'i>(mut value: &'i str, raw: Raw<'i>, error: &mut dyn ErrorSink) {
394    ensure_dec_uint(&mut value, raw, false, "invalid mantissa", error);
395
396    if value.starts_with(".") {
397        let _ = value.next_token();
398        ensure_dec_uint(&mut value, raw, true, "invalid fraction", error);
399    }
400
401    if value.starts_with(['e', 'E']) {
402        let _ = value.next_token();
403        if value.starts_with(['+', '-']) {
404            let _ = value.next_token();
405        }
406        ensure_dec_uint(&mut value, raw, true, "invalid exponent", error);
407    }
408
409    if !value.is_empty() {
410        let start = value.offset_from(&raw.as_str());
411        let end = raw.len();
412        error.report_error(
413            ParseError::new(ScalarKind::Float.invalid_description())
414                .with_context(Span::new_unchecked(0, raw.len()))
415                .with_expected(&[])
416                .with_unexpected(Span::new_unchecked(start, end)),
417        );
418    }
419}
420
421pub(crate) fn ensure_dec_uint<'i>(
422    value: &mut &'i str,
423    raw: Raw<'i>,
424    zero_prefix: bool,
425    invalid_description: &'static str,
426    error: &mut dyn ErrorSink,
427) {
428    let start = *value;
429    let mut digit_count = 0;
430    while let Some(current) = value.chars().next() {
431        if current.is_ascii_digit() {
432            digit_count += 1;
433        } else if current == '_' {
434        } else {
435            break;
436        }
437        let _ = value.next_token();
438    }
439
440    match digit_count {
441        0 => {
442            let start = start.offset_from(&raw.as_str());
443            let end = start;
444            error.report_error(
445                ParseError::new(invalid_description)
446                    .with_context(Span::new_unchecked(0, raw.len()))
447                    .with_expected(&[Expected::Description("digits")])
448                    .with_unexpected(Span::new_unchecked(start, end)),
449            );
450        }
451        1 => {}
452        _ if start.starts_with("0") && !zero_prefix => {
453            let start = start.offset_from(&raw.as_str());
454            let end = start + 1;
455            error.report_error(
456                ParseError::new("unexpected leading zero")
457                    .with_context(Span::new_unchecked(0, raw.len()))
458                    .with_expected(&[])
459                    .with_unexpected(Span::new_unchecked(start, end)),
460            );
461        }
462        _ => {}
463    }
464}
465
466pub(crate) fn ensure_no_leading_zero<'i>(value: &'i str, raw: Raw<'i>, error: &mut dyn ErrorSink) {
467    if value.starts_with("0") {
468        let start = value.offset_from(&raw.as_str());
469        let end = start + 1;
470        error.report_error(
471            ParseError::new("unexpected leading zero")
472                .with_context(Span::new_unchecked(0, raw.len()))
473                .with_expected(&[])
474                .with_unexpected(Span::new_unchecked(start, end)),
475        );
476    }
477}
478
479pub(crate) fn ensure_radixed_value(
480    value: &str,
481    raw: Raw<'_>,
482    radix: IntegerRadix,
483    error: &mut dyn ErrorSink,
484) {
485    let invalid = ['+', '-'];
486    let value = if let Some(value) = value.strip_prefix(invalid) {
487        let pos = raw.as_str().find(invalid).unwrap();
488        error.report_error(
489            ParseError::new("unexpected sign")
490                .with_context(Span::new_unchecked(0, raw.len()))
491                .with_expected(&[])
492                .with_unexpected(Span::new_unchecked(pos, pos + 1)),
493        );
494        value
495    } else {
496        value
497    };
498
499    let valid = radix.validator();
500    for (index, c) in value.char_indices() {
501        if !valid(c) && c != '_' {
502            let pos = value.offset_from(&raw.as_str()) + index;
503            error.report_error(
504                ParseError::new(radix.invalid_description())
505                    .with_context(Span::new_unchecked(0, raw.len()))
506                    .with_unexpected(Span::new_unchecked(pos, pos)),
507            );
508        }
509    }
510}
511
512pub(crate) fn decode_float_or_integer<'i>(
513    stream: &'i str,
514    raw: Raw<'i>,
515    kind: ScalarKind,
516    output: &mut dyn StringBuilder<'i>,
517    error: &mut dyn ErrorSink,
518) -> ScalarKind {
519    output.clear();
520
521    let underscore = "_";
522
523    if has_underscore(stream) {
524        if stream.starts_with(underscore) {
525            error.report_error(
526                ParseError::new("`_` may only go between digits")
527                    .with_context(Span::new_unchecked(0, raw.len()))
528                    .with_expected(&[])
529                    .with_unexpected(Span::new_unchecked(0, underscore.len())),
530            );
531        }
532        if 1 < stream.len() && stream.ends_with(underscore) {
533            let start = stream.offset_from(&raw.as_str());
534            let end = start + stream.len();
535            error.report_error(
536                ParseError::new("`_` may only go between digits")
537                    .with_context(Span::new_unchecked(0, raw.len()))
538                    .with_expected(&[])
539                    .with_unexpected(Span::new_unchecked(end - underscore.len(), end)),
540            );
541        }
542
543        for part in stream.split(underscore) {
544            let part_start = part.offset_from(&raw.as_str());
545            let part_end = part_start + part.len();
546
547            if 0 < part_start {
548                let first = part.as_bytes().first().copied().unwrap_or(b'0');
549                if !is_any_digit(first, kind) {
550                    let start = part_start - 1;
551                    let end = part_start;
552                    debug_assert_eq!(&raw.as_str()[start..end], underscore);
553                    error.report_error(
554                        ParseError::new("`_` may only go between digits")
555                            .with_context(Span::new_unchecked(0, raw.len()))
556                            .with_unexpected(Span::new_unchecked(start, end)),
557                    );
558                }
559            }
560            if 1 < part.len() && part_end < raw.len() {
561                let last = part.as_bytes().last().copied().unwrap_or(b'0');
562                if !is_any_digit(last, kind) {
563                    let start = part_end;
564                    let end = start + underscore.len();
565                    debug_assert_eq!(&raw.as_str()[start..end], underscore);
566                    error.report_error(
567                        ParseError::new("`_` may only go between digits")
568                            .with_context(Span::new_unchecked(0, raw.len()))
569                            .with_unexpected(Span::new_unchecked(start, end)),
570                    );
571                }
572            }
573
574            if part.is_empty() && part_start != 0 && part_end != raw.len() {
575                let start = part_start;
576                let end = start + 1;
577                error.report_error(
578                    ParseError::new("`_` may only go between digits")
579                        .with_context(Span::new_unchecked(0, raw.len()))
580                        .with_unexpected(Span::new_unchecked(start, end)),
581                );
582            }
583
584            if !part.is_empty() && !output.push_str(part) {
585                error.report_error(
586                    ParseError::new(ALLOCATION_ERROR)
587                        .with_unexpected(Span::new_unchecked(part_start, part_end)),
588                );
589            }
590        }
591    } else {
592        if !output.push_str(stream) {
593            error.report_error(
594                ParseError::new(ALLOCATION_ERROR)
595                    .with_unexpected(Span::new_unchecked(0, raw.len())),
596            );
597        }
598    }
599
600    kind
601}
602
603fn is_any_digit(b: u8, kind: ScalarKind) -> bool {
604    if kind == ScalarKind::Float {
605        is_dec_integer_digit(b)
606    } else {
607        is_any_integer_digit(b)
608    }
609}
610
611fn is_any_integer_digit(b: u8) -> bool {
612    (b'0'..=b'9', b'a'..=b'f', b'A'..=b'F').contains_token(b)
613}
614
615fn is_dec_integer_digit(b: u8) -> bool {
616    (b'0'..=b'9').contains_token(b)
617}
618
619fn has_underscore(raw: &str) -> bool {
620    raw.as_bytes().find_slice(b'_').is_some()
621}
622
623fn is_float(raw: &str) -> bool {
624    raw.as_bytes().find_slice((b'.', b'e', b'E')).is_some()
625}
626
627pub(crate) fn decode_as_is<'i>(
628    raw: Raw<'i>,
629    kind: ScalarKind,
630    output: &mut dyn StringBuilder<'i>,
631    error: &mut dyn ErrorSink,
632) -> ScalarKind {
633    let kind = decode_as(raw, raw.as_str(), kind, output, error);
634    kind
635}
636
637pub(crate) fn decode_as<'i>(
638    raw: Raw<'i>,
639    symbol: &'i str,
640    kind: ScalarKind,
641    output: &mut dyn StringBuilder<'i>,
642    error: &mut dyn ErrorSink,
643) -> ScalarKind {
644    output.clear();
645    if !output.push_str(symbol) {
646        error.report_error(
647            ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
648        );
649    }
650    kind
651}
652
653pub(crate) fn decode_symbol<'i>(
654    raw: Raw<'i>,
655    symbol: &'static str,
656    kind: ScalarKind,
657    expected: &'static [Expected],
658    output: &mut dyn StringBuilder<'i>,
659    error: &mut dyn ErrorSink,
660) -> ScalarKind {
661    if raw.as_str() != symbol {
662        if raw.as_str().contains(" ") {
663            return decode_invalid(raw, output, error);
664        } else {
665            error.report_error(
666                ParseError::new(kind.invalid_description())
667                    .with_context(Span::new_unchecked(0, raw.len()))
668                    .with_expected(expected)
669                    .with_unexpected(Span::new_unchecked(0, raw.len())),
670            );
671        }
672    }
673
674    decode_as(raw, symbol, kind, output, error)
675}
676
677pub(crate) fn decode_invalid<'i>(
678    raw: Raw<'i>,
679    output: &mut dyn StringBuilder<'i>,
680    error: &mut dyn ErrorSink,
681) -> ScalarKind {
682    if raw.as_str().ends_with("'''") {
683        error.report_error(
684            ParseError::new("missing opening quote")
685                .with_context(Span::new_unchecked(0, raw.len()))
686                .with_expected(&[Expected::Literal(r#"'''"#)])
687                .with_unexpected(Span::new_unchecked(0, 0)),
688        );
689    } else if raw.as_str().ends_with(r#"""""#) {
690        error.report_error(
691            ParseError::new("missing opening quote")
692                .with_context(Span::new_unchecked(0, raw.len()))
693                .with_expected(&[Expected::Description("multi-line basic string")])
694                .with_expected(&[Expected::Literal(r#"""""#)])
695                .with_unexpected(Span::new_unchecked(0, 0)),
696        );
697    } else if raw.as_str().ends_with("'") {
698        error.report_error(
699            ParseError::new("missing opening quote")
700                .with_context(Span::new_unchecked(0, raw.len()))
701                .with_expected(&[Expected::Literal(r#"'"#)])
702                .with_unexpected(Span::new_unchecked(0, 0)),
703        );
704    } else if raw.as_str().ends_with(r#"""#) {
705        error.report_error(
706            ParseError::new("missing opening quote")
707                .with_context(Span::new_unchecked(0, raw.len()))
708                .with_expected(&[Expected::Literal(r#"""#)])
709                .with_unexpected(Span::new_unchecked(0, 0)),
710        );
711    } else {
712        error.report_error(
713            ParseError::new("string values must be quoted")
714                .with_context(Span::new_unchecked(0, raw.len()))
715                .with_expected(&[Expected::Description("literal string")])
716                .with_unexpected(Span::new_unchecked(0, raw.len())),
717        );
718    }
719
720    output.clear();
721    if !output.push_str(raw.as_str()) {
722        error.report_error(
723            ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
724        );
725    }
726    ScalarKind::String
727}