1use std::mem::MaybeUninit;
2
3use crate::SpecialChar;
4use crate::section::InlineSpan;
5use crate::simd::{ByteSet, find_byte, find_byte_set};
6
7#[inline]
10fn count_leading_byte(bytes: &[u8], needle: u8) -> usize {
11 let mut n = 0;
12 while n < bytes.len() && bytes[n] == needle {
13 n += 1;
14 }
15 n
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Inline<'src> {
21 Text(&'src str),
22 Bold(InlineSpan),
23 Italic(InlineSpan),
24 Link {
25 text: InlineSpan,
26 url: &'src str,
27 title: Option<&'src str>,
28 },
29 Image {
30 alt: &'src str,
31 url: &'src str,
32 title: Option<&'src str>,
33 },
34 Code(&'src str),
35 SoftBreak,
36 HardBreak,
37}
38
39impl<'src> From<&'src str> for Inline<'src> {
40 fn from(s: &'src str) -> Self {
41 Self::Text(s)
42 }
43}
44
45static SPECIAL_SET: ByteSet = ByteSet::new(&[
47 SpecialChar::Newline.byte(),
48 SpecialChar::Asterisk.byte(),
49 SpecialChar::Underscore.byte(),
50 SpecialChar::OpenBracket.byte(),
51 SpecialChar::ExclamationMark.byte(),
52 SpecialChar::Backslash.byte(),
53 SpecialChar::Backtick.byte(),
54]);
55
56static BRACKET_CLOSE_SET: ByteSet = ByteSet::new(&[
59 SpecialChar::OpenBracket.byte(),
60 SpecialChar::CloseBracket.byte(),
61 SpecialChar::Backslash.byte(),
62]);
63static PAREN_CLOSE_SET: ByteSet = ByteSet::new(&[
64 SpecialChar::OpenParen.byte(),
65 SpecialChar::CloseParen.byte(),
66 SpecialChar::Backslash.byte(),
67]);
68
69static STAR_DELIM_SET: ByteSet =
71 ByteSet::new(&[SpecialChar::Asterisk.byte(), SpecialChar::Backslash.byte()]);
72static UNDER_DELIM_SET: ByteSet = ByteSet::new(&[
73 SpecialChar::Underscore.byte(),
74 SpecialChar::Backslash.byte(),
75]);
76
77#[derive(Clone, Copy, PartialEq, Eq)]
79enum CharClass {
80 Whitespace,
81 Punctuation,
82 Other,
83}
84
85impl CharClass {
86 const fn of(ch: char) -> Self {
87 if ch.is_whitespace() {
88 Self::Whitespace
89 } else if ch.is_ascii_punctuation() || unicode_punctuation(ch) {
90 Self::Punctuation
91 } else {
92 Self::Other
93 }
94 }
95
96 #[inline]
98 const fn of_ascii(b: u8) -> Self {
99 if b.is_ascii_whitespace() {
100 Self::Whitespace
101 } else if b.is_ascii_punctuation() {
102 Self::Punctuation
103 } else {
104 Self::Other
105 }
106 }
107}
108
109const fn unicode_punctuation(ch: char) -> bool {
113 if ch.is_ascii() {
114 return false;
115 }
116 matches!(ch,
117 '\u{00A1}'..='\u{00BF}' | '\u{2010}'..='\u{2027}' | '\u{2030}'..='\u{205E}' | '\u{2190}'..='\u{23FF}' | '\u{2500}'..='\u{2BFF}' | '\u{3000}'..='\u{303F}' | '\u{FE30}'..='\u{FE6F}' | '\u{FF01}'..='\u{FF0F}' | '\u{FF1A}'..='\u{FF20}' | '\u{FF3B}'..='\u{FF40}' | '\u{FF5B}'..='\u{FF65}' )
129}
130
131#[derive(Clone, Copy)]
135enum DelimiterAvail {
136 Both,
138 ItalicOnly,
140 BoldOnly,
142 None,
144}
145
146impl DelimiterAvail {
147 const fn can_bold(self) -> bool {
148 matches!(self, Self::Both | Self::BoldOnly)
149 }
150
151 const fn can_italic(self) -> bool {
152 matches!(self, Self::Both | Self::ItalicOnly)
153 }
154
155 const fn bold_failed(&mut self) {
156 *self = match *self {
157 Self::Both => Self::ItalicOnly,
158 Self::BoldOnly => Self::None,
159 other => other,
160 };
161 }
162
163 const fn italic_failed(&mut self) {
164 *self = match *self {
165 Self::Both => Self::BoldOnly,
166 Self::ItalicOnly => Self::None,
167 other => other,
168 };
169 }
170
171 const fn from_count(count: usize) -> Self {
172 match count {
173 0 | 1 => Self::None,
174 2 | 3 => Self::ItalicOnly,
175 _ => Self::Both,
176 }
177 }
178}
179
180struct EmphasisState {
181 star: DelimiterAvail,
182 under: DelimiterAvail,
183}
184
185impl EmphasisState {
186 const fn assume_both() -> Self {
187 Self {
188 star: DelimiterAvail::Both,
189 under: DelimiterAvail::Both,
190 }
191 }
192
193 fn from_bytes(bytes: &[u8]) -> Self {
194 static EMPH_SET: ByteSet =
195 ByteSet::new(&[SpecialChar::Asterisk.byte(), SpecialChar::Underscore.byte()]);
196 let mut stars: u8 = 0;
197 let mut unders: u8 = 0;
198 let mut i = 0;
199 while let Some(pos) = find_byte_set(bytes, i, &EMPH_SET) {
200 if bytes[pos] == SpecialChar::Asterisk {
201 stars = stars.saturating_add(1);
202 } else {
203 unders = unders.saturating_add(1);
204 }
205 if stars >= 4 && unders >= 4 {
206 break;
207 }
208 i = pos + 1;
209 }
210 Self {
211 star: DelimiterAvail::from_count(stars as usize),
212 under: DelimiterAvail::from_count(unders as usize),
213 }
214 }
215
216 const fn avail_mut(&mut self, is_star: bool) -> &mut DelimiterAvail {
217 if is_star {
218 &mut self.star
219 } else {
220 &mut self.under
221 }
222 }
223}
224
225struct InlineBuf<'src, const CAP: usize> {
230 stack: [MaybeUninit<Inline<'src>>; CAP],
231 len: usize,
232 overflow: Vec<Inline<'src>>,
233}
234
235impl<'src, const CAP: usize> InlineBuf<'src, CAP> {
236 #[inline]
237 const fn new() -> Self {
238 Self {
239 stack: [const { MaybeUninit::uninit() }; CAP],
241 len: 0,
242 overflow: Vec::new(),
243 }
244 }
245
246 #[allow(clippy::inline_always)]
247 #[inline(always)]
248 fn push(&mut self, item: Inline<'src>) {
249 if self.len < CAP {
250 self.stack[self.len] = MaybeUninit::new(item);
251 self.len += 1;
252 } else {
253 self.push_slow(item);
254 }
255 }
256
257 #[cold]
258 fn push_slow(&mut self, item: Inline<'src>) {
259 if self.overflow.is_empty() {
260 self.overflow = Vec::with_capacity(CAP * 2);
262 let len = self.len;
265 let ptr = self.stack.as_ptr().cast::<Inline>();
266 let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
267 self.overflow.extend_from_slice(slice);
268 }
269 self.overflow.push(item);
270 }
271
272 #[inline]
274 const fn initialized_stack(&self) -> &[Inline<'src>] {
275 unsafe { std::slice::from_raw_parts(self.stack.as_ptr().cast::<Inline>(), self.len) }
277 }
278
279 #[inline]
280 fn flush_to_pool(self, pool: &mut Vec<Inline<'src>>) -> InlineSpan {
281 let start = pool_offset(pool.len());
282 if self.overflow.is_empty() {
283 pool.extend_from_slice(self.initialized_stack());
284 InlineSpan::new(start, pool_offset(self.len))
285 } else {
286 let len = pool_offset(self.overflow.len());
287 pool.extend(self.overflow);
288 InlineSpan::new(start, len)
289 }
290 }
291}
292
293#[allow(clippy::inline_always)]
296#[inline(always)]
297pub fn pool_offset(pool_len: usize) -> u32 {
298 u32::try_from(pool_len).expect("inline pool exceeds u32::MAX elements")
299}
300
301impl<'src> Inline<'src> {
302 const EMPH_SCAN_THRESHOLD: usize = 256;
304
305 #[must_use]
310 pub fn parse(input: &'src str, pool: &mut Vec<Self>) -> InlineSpan {
311 Self::parse_configured::<16, 32>(input, pool)
312 }
313
314 pub(crate) fn parse_configured<const MAX_DEPTH: u8, const CAP: usize>(
316 input: &'src str,
317 pool: &mut Vec<Self>,
318 ) -> InlineSpan {
319 let bytes = input.as_bytes();
320 if find_byte_set(bytes, 0, &SPECIAL_SET).is_none() {
322 if input.is_empty() {
323 return InlineSpan::EMPTY;
324 }
325 let start = pool_offset(pool.len());
326 pool.push(Self::Text(input));
327 return InlineSpan::new(start, 1);
328 }
329 let emph = if bytes.len() < Self::EMPH_SCAN_THRESHOLD {
330 EmphasisState::assume_both()
331 } else {
332 EmphasisState::from_bytes(bytes)
333 };
334 Self::parse_with_emph::<MAX_DEPTH, CAP>(input, bytes, emph, pool, 0)
335 }
336
337 fn parse_inner<const MAX_DEPTH: u8, const CAP: usize>(
338 input: &'src str,
339 pool: &mut Vec<Self>,
340 depth: u8,
341 ) -> InlineSpan {
342 let bytes = input.as_bytes();
343 Self::parse_with_emph::<MAX_DEPTH, CAP>(
344 input,
345 bytes,
346 EmphasisState::assume_both(),
347 pool,
348 depth,
349 )
350 }
351
352 fn parse_with_emph<const MAX_DEPTH: u8, const CAP: usize>(
353 input: &'src str,
354 bytes: &[u8],
355 emph: EmphasisState,
356 pool: &mut Vec<Self>,
357 depth: u8,
358 ) -> InlineSpan {
359 let mut buf = InlineBuf::<CAP>::new();
360 Self::parse_into_buf::<MAX_DEPTH, CAP>(input, bytes, emph, pool, &mut buf, depth);
361 buf.flush_to_pool(pool)
362 }
363
364 pub fn parse_flat_into(input: &'src str, pool: &mut Vec<Self>) {
371 Self::parse_flat_into_configured::<16, 32>(input, pool);
372 }
373
374 pub(crate) fn parse_flat_into_configured<const MAX_DEPTH: u8, const CAP: usize>(
376 input: &'src str,
377 pool: &mut Vec<Self>,
378 ) {
379 let bytes = input.as_bytes();
380 if find_byte_set(bytes, 0, &SPECIAL_SET).is_none() {
382 if !input.is_empty() {
383 pool.push(Self::Text(input));
384 }
385 return;
386 }
387 let emph = if bytes.len() < Self::EMPH_SCAN_THRESHOLD {
388 EmphasisState::assume_both()
389 } else {
390 EmphasisState::from_bytes(bytes)
391 };
392 let mut buf = InlineBuf::<CAP>::new();
394 Self::parse_into_buf::<MAX_DEPTH, CAP>(input, bytes, emph, pool, &mut buf, 0);
395 if buf.overflow.is_empty() {
397 pool.extend_from_slice(buf.initialized_stack());
398 } else {
399 pool.extend(buf.overflow);
400 }
401 }
402
403 fn parse_into_buf<const MAX_DEPTH: u8, const CAP: usize>(
404 input: &'src str,
405 bytes: &[u8],
406 mut emph: EmphasisState,
407 pool: &mut Vec<Self>,
408 buf: &mut InlineBuf<'src, CAP>,
409 depth: u8,
410 ) {
411 let mut plain_start = 0;
412 let mut i = 0;
413
414 while let Some(pos) = find_byte_set(bytes, i, &SPECIAL_SET) {
416 i = pos;
417 let b = bytes[i];
418
419 if b == SpecialChar::Newline {
420 Self::emit_line_break::<CAP>(input, bytes, plain_start, i, buf);
421 plain_start = i + 1;
422 i = plain_start;
423 continue;
424 }
425
426 if b == SpecialChar::Backslash
429 && let Some(&next) = bytes.get(i + 1)
430 && next.is_ascii_punctuation()
431 {
432 if let Some(text) = input.get(plain_start..i)
433 && !text.is_empty()
434 {
435 buf.push(Self::Text(text));
436 }
437 plain_start = i + 1;
438 i += 2;
439 continue;
440 }
441
442 if b == SpecialChar::Backtick
444 && let Some((code, end)) = Self::try_parse_inline_code(input, bytes, i)
445 {
446 if let Some(text) = input.get(plain_start..i)
447 && !text.is_empty()
448 {
449 buf.push(Self::Text(text));
450 }
451 buf.push(Self::Code(code));
452 plain_start = end;
453 i = end;
454 continue;
455 }
456
457 if b == SpecialChar::ExclamationMark
459 && bytes.get(i + 1) == SpecialChar::OpenBracket
460 && let Some((alt, url, title, end)) =
461 Self::try_parse_bracket_paren(input, bytes, i + 1)
462 {
463 if let Some(text) = input.get(plain_start..i)
464 && !text.is_empty()
465 {
466 buf.push(Self::Text(text));
467 }
468 buf.push(Self::Image { alt, url, title });
469 plain_start = end;
470 i = end;
471 continue;
472 }
473
474 if b == SpecialChar::OpenBracket
476 && let Some((text_str, url, title, end)) =
477 Self::try_parse_bracket_paren(input, bytes, i)
478 {
479 if let Some(text) = input.get(plain_start..i)
480 && !text.is_empty()
481 {
482 buf.push(Self::Text(text));
483 }
484 let text_span =
485 Self::parse_inner::<MAX_DEPTH, CAP>(text_str, pool, depth.saturating_add(1));
486 buf.push(Self::Link {
487 text: text_span,
488 url,
489 title,
490 });
491 plain_start = end;
492 i = end;
493 continue;
494 }
495
496 if let Some((elem, end)) = Self::try_parse_emphasis::<MAX_DEPTH, CAP>(
498 input, bytes, i, b, &mut emph, pool, depth,
499 ) {
500 if let Some(text) = input.get(plain_start..i)
501 && !text.is_empty()
502 {
503 buf.push(Self::Text(text));
504 }
505 buf.push(elem);
506 plain_start = end;
507 i = end;
508 continue;
509 }
510
511 i += 1;
512 }
513
514 if let Some(text) = input.get(plain_start..)
515 && !text.is_empty()
516 {
517 buf.push(Self::Text(text));
518 }
519 }
520
521 #[inline]
524 fn emit_line_break<const CAP: usize>(
525 input: &'src str,
526 bytes: &[u8],
527 plain_start: usize,
528 newline_pos: usize,
529 buf: &mut InlineBuf<'src, CAP>,
530 ) {
531 let preceding = bytes.get(plain_start..newline_pos).unwrap_or_default();
532 let (trim_end, is_hard) = if preceding.last() == SpecialChar::Backslash {
533 (newline_pos - 1, true)
534 } else {
535 let mut spaces = 0;
537 let mut j = preceding.len();
538 while j > 0 && preceding[j - 1] == SpecialChar::Space {
539 spaces += 1;
540 j -= 1;
541 }
542 if spaces >= 2 {
543 (newline_pos - spaces, true)
544 } else {
545 (newline_pos, false)
546 }
547 };
548 if let Some(text) = input.get(plain_start..trim_end)
549 && !text.is_empty()
550 {
551 buf.push(Self::Text(text));
552 }
553 buf.push(if is_hard {
554 Self::HardBreak
555 } else {
556 Self::SoftBreak
557 });
558 }
559
560 #[inline]
561 fn try_parse_emphasis<const MAX_DEPTH: u8, const CAP: usize>(
562 input: &'src str,
563 bytes: &[u8],
564 i: usize,
565 b: u8,
566 emph: &mut EmphasisState,
567 pool: &mut Vec<Self>,
568 depth: u8,
569 ) -> Option<(Self, usize)> {
570 let is_star = b == SpecialChar::Asterisk;
571 if !is_star && b != SpecialChar::Underscore {
572 return None;
573 }
574 if depth >= MAX_DEPTH {
576 return None;
577 }
578 let avail = emph.avail_mut(is_star);
579
580 if avail.can_bold() && bytes.get(i + 1) == Some(&b) {
582 if let Some((inner, end)) = Self::try_parse_delimited(input, bytes, i, b, 2) {
583 let span = Self::parse_inner::<MAX_DEPTH, CAP>(inner, pool, depth + 1);
584 return Some((Self::Bold(span), end));
585 }
586 avail.bold_failed();
587 }
588
589 if avail.can_italic() {
591 if let Some((inner, end)) = Self::try_parse_delimited(input, bytes, i, b, 1) {
592 let span = Self::parse_inner::<MAX_DEPTH, CAP>(inner, pool, depth + 1);
593 return Some((Self::Italic(span), end));
594 }
595 avail.italic_failed();
596 }
597
598 None
599 }
600
601 fn find_matching_close(
604 bytes: &[u8],
605 start: usize,
606 open: SpecialChar,
607 close: SpecialChar,
608 ) -> Option<usize> {
609 let set = if open == SpecialChar::OpenBracket {
611 &BRACKET_CLOSE_SET
612 } else {
613 &PAREN_CLOSE_SET
614 };
615 let mut depth = 0u32;
616 let mut j = start;
617 loop {
618 let pos = find_byte_set(bytes, j, set)?;
619 let b = bytes[pos];
620 if b == SpecialChar::Backslash
621 && bytes.get(pos + 1).is_some_and(u8::is_ascii_punctuation)
622 {
623 j = pos + 2;
624 continue;
625 }
626 if b == open {
627 depth += 1;
628 } else if b == close {
629 if depth == 0 {
630 return Some(pos);
631 }
632 depth -= 1;
633 }
634 j = pos + 1;
635 }
636 }
637
638 fn try_parse_bracket_paren(
639 input: &'src str,
640 bytes: &[u8],
641 start: usize,
642 ) -> Option<(&'src str, &'src str, Option<&'src str>, usize)> {
643 if bytes.get(start) != SpecialChar::OpenBracket {
644 return None;
645 }
646
647 let bracket_start = start + 1;
648 let bracket_end = Self::find_matching_close(
649 bytes,
650 bracket_start,
651 SpecialChar::OpenBracket,
652 SpecialChar::CloseBracket,
653 )?;
654
655 let paren_pos = bracket_end + 1;
656 if bytes.get(paren_pos) != SpecialChar::OpenParen {
657 return None;
658 }
659
660 let paren_start = paren_pos + 1;
661 let paren_end = Self::find_matching_close(
662 bytes,
663 paren_start,
664 SpecialChar::OpenParen,
665 SpecialChar::CloseParen,
666 )?;
667
668 let paren_content = input.get(paren_start..paren_end)?;
669 let (url, title) = Self::split_url_title(paren_content);
670
671 Some((
672 input.get(bracket_start..bracket_end)?,
673 url,
674 title,
675 paren_end + 1,
676 ))
677 }
678
679 fn split_url_title(content: &'src str) -> (&'src str, Option<&'src str>) {
695 let trimmed = content.trim();
696 if trimmed.len() < 3 {
699 return (trimmed, None);
700 }
701
702 let bytes = trimmed.as_bytes();
703 let last = bytes[bytes.len() - 1];
704 let (open, close) = match SpecialChar::from_byte(last) {
705 Some(SpecialChar::DoubleQuote) => (SpecialChar::DoubleQuote, SpecialChar::DoubleQuote),
706 Some(SpecialChar::SingleQuote) => (SpecialChar::SingleQuote, SpecialChar::SingleQuote),
707 Some(SpecialChar::CloseParen) => (SpecialChar::OpenParen, SpecialChar::CloseParen),
708 _ => return (trimmed, None),
710 };
711
712 let mut j = bytes.len() - 2;
714 loop {
715 if bytes[j] == open {
716 if j > 0 && bytes[j - 1].is_ascii_whitespace() {
718 let url = trimmed.get(..j).unwrap_or(trimmed).trim_end();
719 let title = trimmed.get(j + 1..bytes.len() - 1).unwrap_or("");
720 return (url, Some(title));
721 }
722 if open != close {
725 if j == 0 {
726 break;
727 }
728 j -= 1;
729 continue;
730 }
731 break;
733 }
734 if j == 0 {
735 break;
736 }
737 j -= 1;
738 }
739
740 (trimmed, None)
742 }
743
744 #[inline]
745 fn char_class_before(bytes: &[u8], pos: usize) -> CharClass {
748 if pos == 0 {
749 return CharClass::Whitespace;
750 }
751 let b = bytes[pos - 1];
752 if b < 0x80 {
754 return CharClass::of_ascii(b);
755 }
756 let mut start = pos - 1;
758 while start > 0 && bytes[start] & 0xC0 == 0x80 {
759 start -= 1;
760 }
761 let ch = std::str::from_utf8(&bytes[start..pos])
762 .ok()
763 .and_then(|s| s.chars().next())
764 .unwrap_or(' ');
765 CharClass::of(ch)
766 }
767
768 #[inline]
769 fn char_class_after(bytes: &[u8], pos: usize) -> CharClass {
772 if pos >= bytes.len() {
773 return CharClass::Whitespace;
774 }
775 let b = bytes[pos];
776 if b < 0x80 {
778 return CharClass::of_ascii(b);
779 }
780 let ch = std::str::from_utf8(&bytes[pos..])
782 .ok()
783 .and_then(|s| s.chars().next())
784 .unwrap_or(' ');
785 CharClass::of(ch)
786 }
787
788 fn try_parse_delimited(
789 input: &'src str,
790 bytes: &[u8],
791 start: usize,
792 marker: u8,
793 count: usize,
794 ) -> Option<(&'src str, usize)> {
795 let inner_start = start + count;
796 bytes.get(inner_start)?;
797
798 let is_star = marker == SpecialChar::Asterisk;
799
800 let before_open = Self::char_class_before(bytes, start);
806 let after_open = Self::char_class_after(bytes, inner_start);
807
808 let left_flanking = after_open != CharClass::Whitespace
809 && (after_open != CharClass::Punctuation || before_open != CharClass::Other);
810 if !left_flanking {
811 return None;
812 }
813 if !is_star {
814 let right_flanking_open = before_open != CharClass::Whitespace
816 && (before_open != CharClass::Punctuation || after_open != CharClass::Other);
817 if right_flanking_open && before_open != CharClass::Punctuation {
818 return None;
819 }
820 }
821
822 let delim_set = if is_star {
824 &STAR_DELIM_SET
825 } else {
826 &UNDER_DELIM_SET
827 };
828
829 let mut i = inner_start;
830 while let Some(pos) = find_byte_set(bytes, i, delim_set) {
831 i = pos;
832 let b = bytes[i];
833
834 if b == SpecialChar::Backslash && bytes.get(i + 1).is_some_and(u8::is_ascii_punctuation)
835 {
836 i += 2;
837 continue;
838 }
839
840 if b != marker {
841 i += 1;
842 continue;
843 }
844
845 let all_match = (1..count).all(|j| bytes.get(i + j) == Some(&marker));
847 if !all_match {
848 i += 1;
849 continue;
850 }
851
852 let close_end = i + count;
853 let before_close = Self::char_class_before(bytes, i);
854 let after_close = Self::char_class_after(bytes, close_end);
855
856 let right_flanking = before_close != CharClass::Whitespace
861 && (before_close != CharClass::Punctuation || after_close != CharClass::Other);
862 if !right_flanking {
863 i += 1;
864 continue;
865 }
866 if !is_star {
867 let left_flanking_close = after_close != CharClass::Whitespace
869 && (after_close != CharClass::Punctuation || before_close != CharClass::Other);
870 if left_flanking_close && after_close != CharClass::Punctuation {
871 i += 1;
872 continue;
873 }
874 }
875
876 return Some((input.get(inner_start..i)?, close_end));
877 }
878
879 None
880 }
881
882 fn try_parse_inline_code(
886 input: &'src str,
887 bytes: &[u8],
888 start: usize,
889 ) -> Option<(&'src str, usize)> {
890 let backtick_count = count_leading_byte(&bytes[start..], SpecialChar::Backtick.byte());
891 if backtick_count == 0 {
892 return None;
893 }
894
895 let content_start = start + backtick_count;
896 let mut i = content_start;
897 while i < bytes.len() {
898 i = find_byte(bytes, i, SpecialChar::Backtick.byte())?;
900
901 let close_count = count_leading_byte(&bytes[i..], SpecialChar::Backtick.byte());
903
904 if close_count == backtick_count {
905 let mut cs = content_start;
908 let mut ce = i;
909 if ce - cs >= 2
910 && bytes.get(cs) == SpecialChar::Space
911 && bytes.get(ce - 1) == SpecialChar::Space
912 {
913 cs += 1;
914 ce -= 1;
915 }
916 return Some((input.get(cs..ce)?, i + close_count));
917 }
918 i += close_count;
919 }
920
921 None
922 }
923}