1use core::convert::Infallible;
8use core::{cmp::Ordering, str::FromStr};
9use either::Either;
10use writeable::adapters::WriteableAsTryWriteableInfallible;
11use writeable::Writeable;
12
13use crate::common::*;
14use crate::Error;
15
16#[cfg(feature = "alloc")]
17use alloc::{boxed::Box, string::String};
18
19#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
52#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
53#[allow(clippy::exhaustive_enums)] pub enum DoublePlaceholderKey {
55 Place0,
57 Place1,
59}
60
61impl FromStr for DoublePlaceholderKey {
62 type Err = Error;
63 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 match s {
65 "0" => Ok(Self::Place0),
66 "1" => Ok(Self::Place1),
67 _ => Err(Error::InvalidPlaceholder),
68 }
69 }
70}
71
72impl<W0, W1> PlaceholderValueProvider<DoublePlaceholderKey> for (W0, W1)
73where
74 W0: Writeable,
75 W1: Writeable,
76{
77 type Error = Infallible;
78
79 type W<'a>
80 = WriteableAsTryWriteableInfallible<Either<&'a W0, &'a W1>>
81 where
82 Self: 'a;
83
84 type L<'a, 'l>
85 = &'l str
86 where
87 Self: 'a;
88
89 #[inline]
90 fn value_for(&self, key: DoublePlaceholderKey) -> Self::W<'_> {
91 let writeable = match key {
92 DoublePlaceholderKey::Place0 => Either::Left(&self.0),
93 DoublePlaceholderKey::Place1 => Either::Right(&self.1),
94 };
95 WriteableAsTryWriteableInfallible(writeable)
96 }
97 #[inline]
98 fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
99 literal
100 }
101}
102
103impl<W> PlaceholderValueProvider<DoublePlaceholderKey> for [W; 2]
104where
105 W: Writeable,
106{
107 type Error = Infallible;
108
109 type W<'a>
110 = WriteableAsTryWriteableInfallible<&'a W>
111 where
112 Self: 'a;
113
114 type L<'a, 'l>
115 = &'l str
116 where
117 Self: 'a;
118
119 #[inline]
120 fn value_for(&self, key: DoublePlaceholderKey) -> Self::W<'_> {
121 let [item0, item1] = self;
122 let writeable = match key {
123 DoublePlaceholderKey::Place0 => item0,
124 DoublePlaceholderKey::Place1 => item1,
125 };
126 WriteableAsTryWriteableInfallible(writeable)
127 }
128 #[inline]
129 fn map_literal<'a, 'l>(&'a self, literal: &'l str) -> Self::L<'a, 'l> {
130 literal
131 }
132}
133
134#[derive(Debug, Copy, Clone)]
136struct DoublePlaceholderInfo {
137 pub key: DoublePlaceholderKey,
139 pub offset: usize,
143}
144
145impl DoublePlaceholderInfo {
146 pub fn from_char(ch: char) -> Self {
147 Self {
148 key: if ((ch as usize) & 0x1) == 0 {
149 DoublePlaceholderKey::Place0
150 } else {
151 DoublePlaceholderKey::Place1
152 },
153 offset: (ch as usize) >> 1,
154 }
155 }
156 #[cfg(feature = "alloc")]
157 pub fn try_to_char(self) -> Result<char, Error> {
158 let usize_val = match self.key {
159 DoublePlaceholderKey::Place0 => 0,
160 DoublePlaceholderKey::Place1 => 1,
161 } | (self.offset << 1);
162 u32::try_from(usize_val)
163 .ok()
164 .and_then(|x| char::try_from(x).ok())
165 .ok_or(Error::InvalidPattern)
166 }
167 pub fn no_place0() -> Self {
169 Self {
170 key: DoublePlaceholderKey::Place0,
171 offset: 0,
172 }
173 }
174 pub fn swap(self) -> Self {
176 Self {
177 key: match self.key {
178 DoublePlaceholderKey::Place0 => DoublePlaceholderKey::Place1,
179 DoublePlaceholderKey::Place1 => DoublePlaceholderKey::Place0,
180 },
181 offset: self.offset,
182 }
183 }
184 #[cfg(feature = "alloc")]
186 pub fn clear(self) -> Self {
187 Self {
188 key: self.key,
189 offset: 0,
190 }
191 }
192}
193
194#[derive(Debug, Copy, Clone, PartialEq, Eq)]
303#[allow(clippy::exhaustive_enums)] pub enum DoublePlaceholder {}
305
306impl crate::private::Sealed for DoublePlaceholder {}
307
308impl PatternBackend for DoublePlaceholder {
309 type PlaceholderKey<'a> = DoublePlaceholderKey;
310 #[cfg(feature = "alloc")]
311 type PlaceholderKeyCow<'a> = DoublePlaceholderKey;
312 type Error<'a> = Infallible;
313 type Store = str;
314 type Iter<'a> = DoublePlaceholderPatternIterator<'a>;
315
316 fn validate_store(store: &Self::Store) -> Result<(), Error> {
317 let mut chars = store.chars();
318 let ph_first_char = chars.next().ok_or(Error::InvalidPattern)?;
319 let ph_second_char = chars.next().ok_or(Error::InvalidPattern)?;
320 let initial_offset = ph_first_char.len_utf8() + ph_second_char.len_utf8();
321 let ph_first = DoublePlaceholderInfo::from_char(ph_first_char);
322 let ph_second = DoublePlaceholderInfo::from_char(ph_second_char);
323 if ph_first.key == ph_second.key {
324 return Err(Error::InvalidPattern);
325 }
326 if ph_first.offset > ph_second.offset && ph_second.offset > 0 {
327 return Err(Error::InvalidPattern);
328 }
329 if ph_second.offset > store.len() - initial_offset + 1 {
330 return Err(Error::InvalidPattern);
331 }
332 if (ph_second_char as usize) >= 0xD800 {
333 return Err(Error::InvalidPattern);
334 }
335 Ok(())
336 }
337
338 fn iter_items(store: &Self::Store) -> Self::Iter<'_> {
339 let mut chars = store.chars();
340 let (mut ph_first, ph_first_len) = match chars.next() {
341 Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
342 None => {
343 debug_assert!(false);
344 (DoublePlaceholderInfo::no_place0(), 0)
345 }
346 };
347 let (mut ph_second, ph_second_len) = match chars.next() {
348 Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
349 None => {
350 debug_assert!(false);
351 (ph_first.swap(), ph_first_len)
352 }
353 };
354 let initial_offset = ph_first_len + ph_second_len;
355 ph_first.offset += initial_offset - 1;
356 ph_second.offset += initial_offset - 1;
357 DoublePlaceholderPatternIterator {
358 store,
359 ph_first,
360 ph_second,
361 current_offset: initial_offset,
362 }
363 }
364
365 #[cfg(feature = "alloc")]
366 fn try_from_items<
367 'cow,
368 'ph,
369 I: Iterator<Item = Result<PatternItemCow<'cow, Self::PlaceholderKey<'ph>>, Error>>,
370 >(
371 items: I,
372 ) -> Result<Box<str>, Error> {
373 let mut result = String::new();
374 let mut first_ph = None;
375 let mut second_ph = None;
376 for item in items {
377 match item? {
378 PatternItemCow::Literal(s) => result.push_str(&s),
379 PatternItemCow::Placeholder(ph_key) => {
380 if second_ph.is_some() {
381 return Err(Error::InvalidPattern);
382 }
383 let placeholder_offset = result.len() + 1;
384 if placeholder_offset >= 0xD800 {
385 return Err(Error::InvalidPattern);
386 }
387 let ph_info = DoublePlaceholderInfo {
388 key: ph_key,
389 offset: placeholder_offset,
390 };
391 if first_ph.is_none() {
392 first_ph.replace(ph_info);
393 } else {
394 second_ph.replace(ph_info);
395 }
396 }
397 }
398 }
399 let (first_ph, second_ph) = match (first_ph, second_ph) {
400 (Some(a), Some(b)) => (a, b),
401 (Some(a), None) => (a, a.swap().clear()),
402 (None, None) => (
403 DoublePlaceholderInfo::no_place0(),
404 DoublePlaceholderInfo::no_place0().swap(),
405 ),
406 (None, Some(_)) => unreachable!("first_ph always populated before second_ph"),
407 };
408 if first_ph.key == second_ph.key {
409 return Err(Error::InvalidPattern);
410 }
411
412 result.insert(0, second_ph.try_to_char()?);
413 result.insert(0, first_ph.try_to_char()?);
414
415 Ok(result.into_boxed_str())
416 }
417
418 fn empty() -> &'static Self::Store {
419 "\0\u{1}"
420 }
421}
422
423#[doc(hidden)] #[derive(Debug)]
425pub struct DoublePlaceholderPatternIterator<'a> {
426 store: &'a str,
427 ph_first: DoublePlaceholderInfo,
428 ph_second: DoublePlaceholderInfo,
429 current_offset: usize,
430}
431
432impl ExactSizeIterator for DoublePlaceholderPatternIterator<'_> {
436 fn len(&self) -> usize {
437 let mut chars = self.store.chars();
438 let (mut ph_first, ph_first_len) = match chars.next() {
439 Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
440 None => {
441 debug_assert!(false);
442 (DoublePlaceholderInfo::no_place0(), 0)
443 }
444 };
445 let (mut ph_second, ph_second_len) = match chars.next() {
446 Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
447 None => {
448 debug_assert!(false);
449 (ph_first.swap(), ph_first_len)
450 }
451 };
452 let initial_offset = ph_first_len + ph_second_len;
453 ph_first.offset += initial_offset - 1;
454 ph_second.offset += initial_offset - 1;
455 let store_len = self.store.len();
456
457 #[allow(clippy::comparison_chain)]
458 if ph_first.offset < initial_offset {
459 if initial_offset < store_len {
461 1
463 } else {
464 0
466 }
467 } else if ph_first.offset == initial_offset {
468 if ph_second.offset < initial_offset {
470 if ph_first.offset < store_len {
472 2
474 } else {
475 1
477 }
478 } else if ph_second.offset == ph_first.offset {
479 if ph_first.offset < store_len {
481 3
483 } else {
484 2
486 }
487 } else if ph_second.offset < store_len {
488 4
490 } else {
491 3
493 }
494 } else {
495 if ph_second.offset < initial_offset {
497 if ph_first.offset < store_len {
499 3
501 } else {
502 2
504 }
505 } else if ph_second.offset == ph_first.offset {
506 if ph_first.offset < store_len {
508 4
510 } else {
511 3
513 }
514 } else if ph_second.offset < store_len {
515 5
517 } else {
518 4
520 }
521 }
522 }
523}
524
525impl<'a> Iterator for DoublePlaceholderPatternIterator<'a> {
526 type Item = PatternItem<'a, DoublePlaceholderKey>;
527 fn next(&mut self) -> Option<Self::Item> {
528 match self.current_offset.cmp(&self.ph_first.offset) {
529 Ordering::Less => {
530 let literal_str = match self.store.get(self.current_offset..self.ph_first.offset) {
532 Some(s) => s,
533 None => {
534 debug_assert!(false, "offsets are in range");
535 ""
536 }
537 };
538 self.current_offset = self.ph_first.offset;
539 Some(PatternItem::Literal(literal_str))
540 }
541 Ordering::Equal => {
542 self.ph_first.offset = 0;
544 Some(PatternItem::Placeholder(self.ph_first.key))
545 }
546 Ordering::Greater => match self.current_offset.cmp(&self.ph_second.offset) {
547 Ordering::Less => {
548 let literal_str =
550 match self.store.get(self.current_offset..self.ph_second.offset) {
551 Some(s) => s,
552 None => {
553 debug_assert!(false, "offsets are in range");
554 ""
555 }
556 };
557 self.current_offset = self.ph_second.offset;
558 Some(PatternItem::Literal(literal_str))
559 }
560 Ordering::Equal => {
561 self.ph_second.offset = 0;
563 Some(PatternItem::Placeholder(self.ph_second.key))
564 }
565 Ordering::Greater => {
566 let literal_str = match self.store.get(self.current_offset..) {
568 Some(s) => s,
569 None => {
570 debug_assert!(false, "offsets are in range");
571 ""
572 }
573 };
574 if literal_str.is_empty() {
575 None
577 } else {
578 self.current_offset = self.store.len();
580 Some(PatternItem::Literal(literal_str))
581 }
582 }
583 },
584 }
585 }
586
587 fn size_hint(&self) -> (usize, Option<usize>) {
588 let len = self.len();
589 (len, Some(len))
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use crate::DoublePlaceholderPattern;
597
598 #[test]
599 fn test_permutations() {
600 #[derive(Debug)]
601 struct TestCase<'a> {
602 pattern: &'a str,
603 store: &'a str,
604 interpolated: &'a str,
606 }
607 let cases = [
608 TestCase {
609 pattern: "",
610 store: "\x00\x01",
611 interpolated: "",
612 },
613 TestCase {
614 pattern: "{0}",
615 store: "\x02\x01",
616 interpolated: "apple",
617 },
618 TestCase {
619 pattern: "X{0}",
620 store: "\x04\x01X",
621 interpolated: "Xapple",
622 },
623 TestCase {
624 pattern: "{0}Y",
625 store: "\x02\x01Y",
626 interpolated: "appleY",
627 },
628 TestCase {
629 pattern: "X{0}Y",
630 store: "\x04\x01XY",
631 interpolated: "XappleY",
632 },
633 TestCase {
634 pattern: "{1}",
635 store: "\x03\x00",
636 interpolated: "orange",
637 },
638 TestCase {
639 pattern: "X{1}",
640 store: "\x05\x00X",
641 interpolated: "Xorange",
642 },
643 TestCase {
644 pattern: "{1}Y",
645 store: "\x03\x00Y",
646 interpolated: "orangeY",
647 },
648 TestCase {
649 pattern: "X{1}Y",
650 store: "\x05\x00XY",
651 interpolated: "XorangeY",
652 },
653 TestCase {
654 pattern: "{0}{1}",
655 store: "\x02\x03",
656 interpolated: "appleorange",
657 },
658 TestCase {
659 pattern: "X{0}{1}",
660 store: "\x04\x05X",
661 interpolated: "Xappleorange",
662 },
663 TestCase {
664 pattern: "{0}Y{1}",
665 store: "\x02\x05Y",
666 interpolated: "appleYorange",
667 },
668 TestCase {
669 pattern: "{0}{1}Z",
670 store: "\x02\x03Z",
671 interpolated: "appleorangeZ",
672 },
673 TestCase {
674 pattern: "X{0}Y{1}",
675 store: "\x04\x07XY",
676 interpolated: "XappleYorange",
677 },
678 TestCase {
679 pattern: "X{0}{1}Z",
680 store: "\x04\x05XZ",
681 interpolated: "XappleorangeZ",
682 },
683 TestCase {
684 pattern: "{0}Y{1}Z",
685 store: "\x02\x05YZ",
686 interpolated: "appleYorangeZ",
687 },
688 TestCase {
689 pattern: "X{0}Y{1}Z",
690 store: "\x04\x07XYZ",
691 interpolated: "XappleYorangeZ",
692 },
693 TestCase {
694 pattern: "{1}{0}",
695 store: "\x03\x02",
696 interpolated: "orangeapple",
697 },
698 TestCase {
699 pattern: "X{1}{0}",
700 store: "\x05\x04X",
701 interpolated: "Xorangeapple",
702 },
703 TestCase {
704 pattern: "{1}Y{0}",
705 store: "\x03\x04Y",
706 interpolated: "orangeYapple",
707 },
708 TestCase {
709 pattern: "{1}{0}Z",
710 store: "\x03\x02Z",
711 interpolated: "orangeappleZ",
712 },
713 TestCase {
714 pattern: "X{1}Y{0}",
715 store: "\x05\x06XY",
716 interpolated: "XorangeYapple",
717 },
718 TestCase {
719 pattern: "X{1}{0}Z",
720 store: "\x05\x04XZ",
721 interpolated: "XorangeappleZ",
722 },
723 TestCase {
724 pattern: "{1}Y{0}Z",
725 store: "\x03\x04YZ",
726 interpolated: "orangeYappleZ",
727 },
728 TestCase {
729 pattern: "X{1}Y{0}Z",
730 store: "\x05\x06XYZ",
731 interpolated: "XorangeYappleZ",
732 },
733 TestCase {
734 pattern: "01234567890123456789012345678901234567890123456789012345678901234567890123456789{0}Y{1}Z",
735 store: "\u{A2}\u{A5}01234567890123456789012345678901234567890123456789012345678901234567890123456789YZ",
736 interpolated: "01234567890123456789012345678901234567890123456789012345678901234567890123456789appleYorangeZ",
737 },
738 ];
739 for cas in cases {
740 let TestCase {
741 pattern,
742 store,
743 interpolated,
744 } = cas;
745 let parsed =
746 DoublePlaceholderPattern::try_from_str(pattern, Default::default()).unwrap();
747 let actual = parsed
748 .interpolate(["apple", "orange"])
749 .write_to_string()
750 .into_owned();
751 assert_eq!(&parsed.store, store, "{cas:?}");
752 assert_eq!(actual, interpolated, "{cas:?}");
753 }
754 }
755
756 #[test]
757 fn test_invalid() {
758 let cases = [
759 "", "\x00", "\x00\x00", "\x04\x03", "\x04\x05", "\x04\u{10001}@", ];
766 let long_str = "0123456789".repeat(1000000);
767 for cas in cases {
768 let cas = cas.replace('@', &long_str);
769 assert!(DoublePlaceholder::validate_store(&cas).is_err(), "{cas:?}");
770 }
771 }
772}