1use winnow::stream::ContainsToken as _;
2use winnow::stream::FindSlice as _;
3use winnow::stream::Offset as _;
4use winnow::stream::Stream as _;
5
6use crate::ErrorSink;
7use crate::Expected;
8use crate::ParseError;
9use crate::Raw;
10use crate::Span;
11use crate::decoder::StringBuilder;
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 if !first.is_ascii_digit() && s.contains(" ") {
103 return decode_invalid(raw, output, error);
105 }
106 match first {
107 b'+' | b'-' => {
109 let value = &raw.as_str()[1..];
110 decode_sign_prefix(raw, value, output, error)
111 }
112 b'_' => decode_datetime_or_float_or_integer(raw.as_str(), raw, output, error),
114 b'0' => decode_zero_prefix(raw.as_str(), false, raw, output, error),
116 b'1'..=b'9' => decode_datetime_or_float_or_integer(raw.as_str(), raw, output, error),
117 b'.' => {
119 let kind = ScalarKind::Float;
120 let stream = raw.as_str();
121 if ensure_float(stream, raw, error) {
122 decode_float_or_integer(stream, raw, kind, output, error)
123 } else {
124 kind
125 }
126 }
127 b't' | b'T' => {
128 const SYMBOL: &str = "true";
129 let kind = ScalarKind::Boolean(true);
130 let expected = &[Expected::Literal(SYMBOL)];
131 decode_symbol(raw, SYMBOL, kind, expected, output, error)
132 }
133 b'f' | b'F' => {
134 const SYMBOL: &str = "false";
135 let kind = ScalarKind::Boolean(false);
136 let expected = &[Expected::Literal(SYMBOL)];
137 decode_symbol(raw, SYMBOL, kind, expected, output, error)
138 }
139 b'i' | b'I' => {
140 const SYMBOL: &str = "inf";
141 let kind = ScalarKind::Float;
142 let expected = &[Expected::Literal(SYMBOL)];
143 decode_symbol(raw, SYMBOL, kind, expected, output, error)
144 }
145 b'n' | b'N' => {
146 const SYMBOL: &str = "nan";
147 let kind = ScalarKind::Float;
148 let expected = &[Expected::Literal(SYMBOL)];
149 decode_symbol(raw, SYMBOL, kind, expected, output, error)
150 }
151 _ => decode_invalid(raw, output, error),
152 }
153}
154
155fn decode_sign_prefix<'i>(
156 raw: Raw<'i>,
157 value: &'i str,
158 output: &mut dyn StringBuilder<'i>,
159 error: &mut dyn ErrorSink,
160) -> ScalarKind {
161 let mut value = value;
162 let first = loop {
163 let Some(first) = value.as_bytes().first() else {
164 return decode_invalid(raw, output, error);
165 };
166 if !matches!(first, b'+' | b'-') {
167 break first;
168 }
169 let start = value.offset_from(&raw.as_str());
170 let end = start + 1;
171 error.report_error(
172 ParseError::new("redundant numeric sign")
173 .with_context(Span::new_unchecked(0, raw.len()))
174 .with_expected(&[])
175 .with_unexpected(Span::new_unchecked(start, end)),
176 );
177 value = &value[1..];
178 };
179 match first {
180 b'_' => decode_datetime_or_float_or_integer(value, raw, output, error),
182 b'0' => decode_zero_prefix(value, true, raw, output, error),
184 b'1'..=b'9' => decode_datetime_or_float_or_integer(value, raw, output, error),
185 b'.' => {
187 let kind = ScalarKind::Float;
188 let stream = raw.as_str();
189 if ensure_float(stream, raw, error) {
190 decode_float_or_integer(stream, raw, kind, output, error)
191 } else {
192 kind
193 }
194 }
195 b'i' | b'I' => {
196 const SYMBOL: &str = "inf";
197 let kind = ScalarKind::Float;
198 if value != SYMBOL {
199 let expected = &[Expected::Literal(SYMBOL)];
200 let start = value.offset_from(&raw.as_str());
201 let end = start + value.len();
202 error.report_error(
203 ParseError::new(kind.invalid_description())
204 .with_context(Span::new_unchecked(0, raw.len()))
205 .with_expected(expected)
206 .with_unexpected(Span::new_unchecked(start, end)),
207 );
208 decode_as(raw, SYMBOL, kind, output, error)
209 } else {
210 decode_as_is(raw, kind, output, error)
211 }
212 }
213 b'n' | b'N' => {
214 const SYMBOL: &str = "nan";
215 let kind = ScalarKind::Float;
216 if value != SYMBOL {
217 let expected = &[Expected::Literal(SYMBOL)];
218 let start = value.offset_from(&raw.as_str());
219 let end = start + value.len();
220 error.report_error(
221 ParseError::new(kind.invalid_description())
222 .with_context(Span::new_unchecked(0, raw.len()))
223 .with_expected(expected)
224 .with_unexpected(Span::new_unchecked(start, end)),
225 );
226 decode_as(raw, SYMBOL, kind, output, error)
227 } else {
228 decode_as_is(raw, kind, output, error)
229 }
230 }
231 _ => decode_invalid(raw, output, error),
232 }
233}
234
235fn decode_zero_prefix<'i>(
236 value: &'i str,
237 signed: bool,
238 raw: Raw<'i>,
239 output: &mut dyn StringBuilder<'i>,
240 error: &mut dyn ErrorSink,
241) -> ScalarKind {
242 debug_assert_eq!(value.as_bytes()[0], b'0');
243 if value.len() == 1 {
244 let kind = ScalarKind::Integer(IntegerRadix::Dec);
245 decode_float_or_integer(raw.as_str(), raw, kind, output, error)
247 } else {
248 let radix = value.as_bytes()[1];
249 match radix {
250 b'x' | b'X' => {
251 if value.contains(" ") {
252 return decode_invalid(raw, output, error);
254 }
255 if signed {
256 error.report_error(
257 ParseError::new("integers with a radix cannot be signed")
258 .with_context(Span::new_unchecked(0, raw.len()))
259 .with_expected(&[])
260 .with_unexpected(Span::new_unchecked(0, 1)),
261 );
262 }
263 if radix == b'X' {
264 let start = value.offset_from(&raw.as_str());
265 let end = start + 2;
266 error.report_error(
267 ParseError::new("radix must be lowercase")
268 .with_context(Span::new_unchecked(0, raw.len()))
269 .with_expected(&[Expected::Literal("0x")])
270 .with_unexpected(Span::new_unchecked(start, end)),
271 );
272 }
273 let radix = IntegerRadix::Hex;
274 let kind = ScalarKind::Integer(radix);
275 let stream = &value[2..];
276 if ensure_radixed_value(stream, raw, radix, error) {
277 decode_float_or_integer(stream, raw, kind, output, error)
278 } else {
279 kind
280 }
281 }
282 b'o' | b'O' => {
283 if value.contains(" ") {
284 return decode_invalid(raw, output, error);
286 }
287 if signed {
288 error.report_error(
289 ParseError::new("integers with a radix cannot be signed")
290 .with_context(Span::new_unchecked(0, raw.len()))
291 .with_expected(&[])
292 .with_unexpected(Span::new_unchecked(0, 1)),
293 );
294 }
295 if radix == b'O' {
296 let start = value.offset_from(&raw.as_str());
297 let end = start + 2;
298 error.report_error(
299 ParseError::new("radix must be lowercase")
300 .with_context(Span::new_unchecked(0, raw.len()))
301 .with_expected(&[Expected::Literal("0o")])
302 .with_unexpected(Span::new_unchecked(start, end)),
303 );
304 }
305 let radix = IntegerRadix::Oct;
306 let kind = ScalarKind::Integer(radix);
307 let stream = &value[2..];
308 if ensure_radixed_value(stream, raw, radix, error) {
309 decode_float_or_integer(stream, raw, kind, output, error)
310 } else {
311 kind
312 }
313 }
314 b'b' | b'B' => {
315 if value.contains(" ") {
316 return decode_invalid(raw, output, error);
318 }
319 if signed {
320 error.report_error(
321 ParseError::new("integers with a radix cannot be signed")
322 .with_context(Span::new_unchecked(0, raw.len()))
323 .with_expected(&[])
324 .with_unexpected(Span::new_unchecked(0, 1)),
325 );
326 }
327 if radix == b'B' {
328 let start = value.offset_from(&raw.as_str());
329 let end = start + 2;
330 error.report_error(
331 ParseError::new("radix must be lowercase")
332 .with_context(Span::new_unchecked(0, raw.len()))
333 .with_expected(&[Expected::Literal("0b")])
334 .with_unexpected(Span::new_unchecked(start, end)),
335 );
336 }
337 let radix = IntegerRadix::Bin;
338 let kind = ScalarKind::Integer(radix);
339 let stream = &value[2..];
340 if ensure_radixed_value(stream, raw, radix, error) {
341 decode_float_or_integer(stream, raw, kind, output, error)
342 } else {
343 kind
344 }
345 }
346 b'd' | b'D' => {
347 if value.contains(" ") {
348 return decode_invalid(raw, output, error);
350 }
351 if signed {
352 error.report_error(
353 ParseError::new("integers with a radix cannot be signed")
354 .with_context(Span::new_unchecked(0, raw.len()))
355 .with_expected(&[])
356 .with_unexpected(Span::new_unchecked(0, 1)),
357 );
358 }
359 let radix = IntegerRadix::Dec;
360 let kind = ScalarKind::Integer(radix);
361 let stream = &value[2..];
362 error.report_error(
363 ParseError::new("redundant integer number prefix")
364 .with_context(Span::new_unchecked(0, raw.len()))
365 .with_expected(&[])
366 .with_unexpected(Span::new_unchecked(0, 2)),
367 );
368 if ensure_radixed_value(stream, raw, radix, error) {
369 decode_float_or_integer(stream, raw, kind, output, error)
370 } else {
371 kind
372 }
373 }
374 _ => decode_datetime_or_float_or_integer(value, raw, output, error),
375 }
376 }
377}
378
379fn decode_datetime_or_float_or_integer<'i>(
380 value: &'i str,
381 raw: Raw<'i>,
382 output: &mut dyn StringBuilder<'i>,
383 error: &mut dyn ErrorSink,
384) -> ScalarKind {
385 let Some(digit_end) = value
386 .as_bytes()
387 .offset_for(|b| !(b'0'..=b'9').contains_token(b))
388 else {
389 let kind = ScalarKind::Integer(IntegerRadix::Dec);
390 let stream = raw.as_str();
391 if ensure_no_leading_zero(value, raw, error) {
392 return decode_float_or_integer(stream, raw, kind, output, error);
393 } else {
394 return kind;
395 }
396 };
397
398 #[cfg(feature = "unsafe")] let rest = unsafe { &value.get_unchecked(digit_end..) };
400 #[cfg(not(feature = "unsafe"))]
401 let rest = &value[digit_end..];
402
403 if rest.starts_with("-") || rest.starts_with(":") {
404 decode_as_is(raw, ScalarKind::DateTime, output, error)
405 } else if rest.contains(" ") {
406 decode_invalid(raw, output, error)
407 } else if is_float(rest) {
408 let kind = ScalarKind::Float;
409 let stream = raw.as_str();
410 if ensure_float(value, raw, error) {
411 decode_float_or_integer(stream, raw, kind, output, error)
412 } else {
413 kind
414 }
415 } else if rest.starts_with("_") {
416 let kind = ScalarKind::Integer(IntegerRadix::Dec);
417 let stream = raw.as_str();
418 if ensure_no_leading_zero(value, raw, error) {
419 decode_float_or_integer(stream, raw, kind, output, error)
420 } else {
421 kind
422 }
423 } else {
424 decode_invalid(raw, output, error)
425 }
426}
427
428#[must_use]
442fn ensure_float<'i>(mut value: &'i str, raw: Raw<'i>, error: &mut dyn ErrorSink) -> bool {
443 let mut is_valid = true;
444
445 is_valid &= ensure_dec_uint(&mut value, raw, false, "invalid mantissa", error);
446
447 if value.starts_with(".") {
448 let _ = value.next_token();
449 is_valid &= ensure_dec_uint(&mut value, raw, true, "invalid fraction", error);
450 }
451
452 if value.starts_with(['e', 'E']) {
453 let _ = value.next_token();
454 if value.starts_with(['+', '-']) {
455 let _ = value.next_token();
456 }
457 is_valid &= ensure_dec_uint(&mut value, raw, true, "invalid exponent", error);
458 }
459
460 if !value.is_empty() {
461 let start = value.offset_from(&raw.as_str());
462 let end = raw.len();
463 error.report_error(
464 ParseError::new(ScalarKind::Float.invalid_description())
465 .with_context(Span::new_unchecked(0, raw.len()))
466 .with_expected(&[])
467 .with_unexpected(Span::new_unchecked(start, end)),
468 );
469 is_valid = false;
470 }
471
472 is_valid
473}
474
475#[must_use]
476fn ensure_dec_uint<'i>(
477 value: &mut &'i str,
478 raw: Raw<'i>,
479 zero_prefix: bool,
480 invalid_description: &'static str,
481 error: &mut dyn ErrorSink,
482) -> bool {
483 let mut is_valid = true;
484
485 let start = *value;
486 let mut digit_count = 0;
487 while let Some(current) = value.chars().next() {
488 if current.is_ascii_digit() {
489 digit_count += 1;
490 } else if current == '_' {
491 } else {
492 break;
493 }
494 let _ = value.next_token();
495 }
496
497 match digit_count {
498 0 => {
499 let start = start.offset_from(&raw.as_str());
500 let end = start;
501 error.report_error(
502 ParseError::new(invalid_description)
503 .with_context(Span::new_unchecked(0, raw.len()))
504 .with_expected(&[Expected::Description("digits")])
505 .with_unexpected(Span::new_unchecked(start, end)),
506 );
507 is_valid = false;
508 }
509 1 => {}
510 _ if start.starts_with("0") && !zero_prefix => {
511 let start = start.offset_from(&raw.as_str());
512 let end = start + 1;
513 error.report_error(
514 ParseError::new("unexpected leading zero")
515 .with_context(Span::new_unchecked(0, raw.len()))
516 .with_expected(&[])
517 .with_unexpected(Span::new_unchecked(start, end)),
518 );
519 is_valid = false;
520 }
521 _ => {}
522 }
523
524 is_valid
525}
526
527#[must_use]
528fn ensure_no_leading_zero<'i>(value: &'i str, raw: Raw<'i>, error: &mut dyn ErrorSink) -> bool {
529 let mut is_valid = true;
530
531 if value.starts_with("0") {
532 let start = value.offset_from(&raw.as_str());
533 let end = start + 1;
534 error.report_error(
535 ParseError::new("unexpected leading zero")
536 .with_context(Span::new_unchecked(0, raw.len()))
537 .with_expected(&[])
538 .with_unexpected(Span::new_unchecked(start, end)),
539 );
540 is_valid = false;
541 }
542
543 is_valid
544}
545
546#[must_use]
547fn ensure_radixed_value(
548 value: &str,
549 raw: Raw<'_>,
550 radix: IntegerRadix,
551 error: &mut dyn ErrorSink,
552) -> bool {
553 let mut is_valid = true;
554
555 let invalid = ['+', '-'];
556 let value = if let Some(value) = value.strip_prefix(invalid) {
557 let pos = raw.as_str().find(invalid).unwrap();
558 error.report_error(
559 ParseError::new("unexpected sign")
560 .with_context(Span::new_unchecked(0, raw.len()))
561 .with_expected(&[])
562 .with_unexpected(Span::new_unchecked(pos, pos + 1)),
563 );
564 is_valid = false;
565 value
566 } else {
567 value
568 };
569
570 let valid = radix.validator();
571 for (index, c) in value.char_indices() {
572 if !valid(c) && c != '_' {
573 let pos = value.offset_from(&raw.as_str()) + index;
574 error.report_error(
575 ParseError::new(radix.invalid_description())
576 .with_context(Span::new_unchecked(0, raw.len()))
577 .with_unexpected(Span::new_unchecked(pos, pos)),
578 );
579 is_valid = false;
580 }
581 }
582
583 is_valid
584}
585
586fn decode_float_or_integer<'i>(
587 mut stream: &'i str,
588 raw: Raw<'i>,
589 kind: ScalarKind,
590 output: &mut dyn StringBuilder<'i>,
591 error: &mut dyn ErrorSink,
592) -> ScalarKind {
593 output.clear();
594
595 let underscore = "_";
596
597 let stream_start = stream.offset_from(&raw.as_str());
598 while !stream.is_empty() {
599 let sep_pos = stream.find_slice(underscore);
600 let sep_start = sep_pos
601 .clone()
602 .map(|r| r.start)
603 .unwrap_or_else(|| stream.len());
604
605 let part_start = stream.offset_from(&raw.as_str());
606 let part_end = part_start + sep_start;
607 let part = stream.next_slice(sep_start);
608
609 if sep_pos.is_some() {
610 let _ = stream.next_slice(underscore.len());
611
612 let mut is_invalid_sep = false;
613 if let Some(last_pos) = sep_start.checked_sub(1) {
614 let last_byte = raw.as_bytes()[part_start + last_pos];
615 if !is_any_digit(last_byte, kind) {
616 is_invalid_sep = true;
617 }
618 } else if part_start == stream_start {
619 is_invalid_sep = true;
620 }
621
622 if let Some(next_byte) = stream.as_bytes().first() {
623 if !is_any_digit(*next_byte, kind) {
624 is_invalid_sep = true;
625 }
626 } else if stream.is_empty() {
627 is_invalid_sep = true;
628 }
629
630 if is_invalid_sep {
631 let start = part_end;
632 let end = start + underscore.len();
633 error.report_error(
634 ParseError::new("`_` may only go between digits")
635 .with_context(Span::new_unchecked(0, raw.len()))
636 .with_expected(&[])
637 .with_unexpected(Span::new_unchecked(end - underscore.len(), end)),
638 );
639 }
640 }
641
642 if !part.is_empty() && !output.push_str(part) {
643 error.report_error(
644 ParseError::new(ALLOCATION_ERROR)
645 .with_unexpected(Span::new_unchecked(part_start, part_end)),
646 );
647 }
648 }
649
650 kind
651}
652
653fn is_any_digit(b: u8, kind: ScalarKind) -> bool {
654 if kind == ScalarKind::Float {
655 is_dec_integer_digit(b)
656 } else {
657 is_any_integer_digit(b)
658 }
659}
660
661fn is_any_integer_digit(b: u8) -> bool {
662 (b'0'..=b'9', b'a'..=b'f', b'A'..=b'F').contains_token(b)
663}
664
665fn is_dec_integer_digit(b: u8) -> bool {
666 (b'0'..=b'9').contains_token(b)
667}
668
669fn is_float(raw: &str) -> bool {
670 raw.as_bytes().find_slice((b'.', b'e', b'E')).is_some()
671}
672
673fn decode_as_is<'i>(
674 raw: Raw<'i>,
675 kind: ScalarKind,
676 output: &mut dyn StringBuilder<'i>,
677 error: &mut dyn ErrorSink,
678) -> ScalarKind {
679 let kind = decode_as(raw, raw.as_str(), kind, output, error);
680 kind
681}
682
683fn decode_as<'i>(
684 raw: Raw<'i>,
685 symbol: &'i str,
686 kind: ScalarKind,
687 output: &mut dyn StringBuilder<'i>,
688 error: &mut dyn ErrorSink,
689) -> ScalarKind {
690 output.clear();
691 if !output.push_str(symbol) {
692 error.report_error(
693 ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
694 );
695 }
696 kind
697}
698
699fn decode_symbol<'i>(
700 raw: Raw<'i>,
701 symbol: &'static str,
702 kind: ScalarKind,
703 expected: &'static [Expected],
704 output: &mut dyn StringBuilder<'i>,
705 error: &mut dyn ErrorSink,
706) -> ScalarKind {
707 if raw.as_str() != symbol {
708 if raw.as_str().contains(" ") {
709 return decode_invalid(raw, output, error);
710 } else {
711 error.report_error(
712 ParseError::new(kind.invalid_description())
713 .with_context(Span::new_unchecked(0, raw.len()))
714 .with_expected(expected)
715 .with_unexpected(Span::new_unchecked(0, raw.len())),
716 );
717 }
718 }
719
720 decode_as(raw, symbol, kind, output, error)
721}
722
723fn decode_invalid<'i>(
724 raw: Raw<'i>,
725 output: &mut dyn StringBuilder<'i>,
726 error: &mut dyn ErrorSink,
727) -> ScalarKind {
728 if raw.as_str().ends_with("'''") {
729 error.report_error(
730 ParseError::new("missing opening quote")
731 .with_context(Span::new_unchecked(0, raw.len()))
732 .with_expected(&[Expected::Literal(r#"'''"#)])
733 .with_unexpected(Span::new_unchecked(0, 0)),
734 );
735 } else if raw.as_str().ends_with(r#"""""#) {
736 error.report_error(
737 ParseError::new("missing opening quote")
738 .with_context(Span::new_unchecked(0, raw.len()))
739 .with_expected(&[Expected::Description("multi-line basic string")])
740 .with_expected(&[Expected::Literal(r#"""""#)])
741 .with_unexpected(Span::new_unchecked(0, 0)),
742 );
743 } else if raw.as_str().ends_with("'") {
744 error.report_error(
745 ParseError::new("missing opening quote")
746 .with_context(Span::new_unchecked(0, raw.len()))
747 .with_expected(&[Expected::Literal(r#"'"#)])
748 .with_unexpected(Span::new_unchecked(0, 0)),
749 );
750 } else if raw.as_str().ends_with(r#"""#) {
751 error.report_error(
752 ParseError::new("missing opening quote")
753 .with_context(Span::new_unchecked(0, raw.len()))
754 .with_expected(&[Expected::Literal(r#"""#)])
755 .with_unexpected(Span::new_unchecked(0, 0)),
756 );
757 } else {
758 error.report_error(
759 ParseError::new("string values must be quoted")
760 .with_context(Span::new_unchecked(0, raw.len()))
761 .with_expected(&[Expected::Description("literal string")])
762 .with_unexpected(Span::new_unchecked(0, raw.len())),
763 );
764 }
765
766 output.clear();
767 if !output.push_str(raw.as_str()) {
768 error.report_error(
769 ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
770 );
771 }
772 ScalarKind::String
773}
774
775#[cfg(test)]
776#[cfg(feature = "std")]
777mod test {
778 use super::*;
779 use alloc::borrow::Cow;
780
781 #[test]
782 fn many_redundant_signs() {
783 let signs = "-".repeat(5_000);
786 let input = format!("{signs}1");
787
788 let mut error = Vec::new();
789 let mut output = Cow::Borrowed("");
790 decode_unquoted_scalar(
791 Raw::new_unchecked(&input, None, Default::default()),
792 &mut output,
793 &mut error,
794 );
795 assert!(!error.is_empty());
796 }
797}