use crate::lazy_automaton::LazyAutomaton;
use crate::provider::*;
#[cfg(feature = "datagen")]
use alloc::borrow::Cow;
#[cfg(feature = "datagen")]
use icu_provider::DataError;
use writeable::{LengthHint, Writeable};
impl ListFormatterPatternsV2<'_> {
#[cfg(feature = "datagen")]
pub fn try_new(start: &str, middle: &str, end: &str, pair: &str) -> Result<Self, DataError> {
let err = DataError::custom("Invalid list pattern");
Ok(Self {
start: ListJoinerPattern::try_from_str(start, true, false)?,
middle: middle
.strip_prefix("{0}")
.ok_or(err)?
.strip_suffix("{1}")
.ok_or(err)?
.to_string()
.into(),
end: ListJoinerPattern::try_from_str(end, false, true)?.into(),
pair: if end != pair {
Some(ListJoinerPattern::try_from_str(pair, true, true)?.into())
} else {
None
},
})
}
pub(crate) fn length_hint(&self, len: usize) -> LengthHint {
match len {
0 | 1 => LengthHint::exact(0),
2 => self.pair.as_ref().unwrap_or(&self.end).size_hint(),
n => {
self.start.size_hint()
+ self.middle.writeable_length_hint() * (n - 3)
+ self.end.size_hint()
}
}
}
}
type PatternParts<'a> = (&'a str, &'a str, &'a str);
impl<'a> ConditionalListJoinerPattern<'a> {
pub(crate) fn parts<'b, W: Writeable + ?Sized>(
&'a self,
following_value: &'b W,
) -> PatternParts<'a> {
match &self.special_case {
Some(SpecialCasePattern { condition, pattern })
if condition.deref().matches_earliest_fwd_lazy(following_value) =>
{
pattern.parts()
}
_ => self.default.parts(),
}
}
fn size_hint(&'a self) -> LengthHint {
let mut hint = self.default.size_hint();
if let Some(special_case) = &self.special_case {
hint |= special_case.pattern.size_hint()
}
hint
}
}
impl<'data> ListJoinerPattern<'data> {
#[cfg(feature = "datagen")]
pub fn try_from_str(
pattern: &str,
allow_prefix: bool,
allow_suffix: bool,
) -> Result<Self, DataError> {
match (pattern.find("{0}"), pattern.find("{1}")) {
(Some(index_0), Some(index_1))
if index_0 < index_1
&& (allow_prefix || index_0 == 0)
&& (allow_suffix || index_1 == pattern.len() - 3) =>
{
if (index_0 > 0 && !cfg!(test)) || index_1 - 3 >= 256 {
return Err(DataError::custom(
"Found valid pattern that cannot be stored in ListFormatterPatternsV2",
)
.with_debug_context(pattern));
}
#[allow(clippy::indexing_slicing)] Ok(ListJoinerPattern {
string: Cow::Owned(alloc::format!(
"{}{}{}",
&pattern[0..index_0],
&pattern[index_0 + 3..index_1],
&pattern[index_1 + 3..]
)),
index_0: index_0 as u8,
index_1: (index_1 - 3) as u8,
})
}
_ => Err(DataError::custom("Invalid list pattern").with_debug_context(pattern)),
}
}
pub(crate) fn parts(&'data self) -> PatternParts<'data> {
#![allow(clippy::indexing_slicing)] let index_0 = self.index_0 as usize;
let index_1 = self.index_1 as usize;
(
&self.string[0..index_0],
&self.string[index_0..index_1],
&self.string[index_1..],
)
}
fn size_hint(&self) -> LengthHint {
LengthHint::exact(self.string.len())
}
}
#[cfg(feature = "datagen")]
impl<'data> From<ListJoinerPattern<'data>> for ConditionalListJoinerPattern<'data> {
fn from(default: ListJoinerPattern<'data>) -> Self {
Self {
default,
special_case: None,
}
}
}
#[cfg(all(test, feature = "datagen"))]
pub mod test {
use super::*;
pub fn test_patterns_general() -> ListFormatterPatternsV2<'static> {
ListFormatterPatternsV2::try_new("@{0}:{1}", "{0},{1}", "{0}.{1}!", "${0};{1}+").unwrap()
}
pub fn test_patterns_lengths() -> ListFormatterPatternsV2<'static> {
ListFormatterPatternsV2::try_new("{0}1{1}", "{0}12{1}", "{0}12{1}34", "{0}123{1}456")
.unwrap()
}
pub fn test_patterns_conditional() -> ListFormatterPatternsV2<'static> {
let mut patterns =
ListFormatterPatternsV2::try_new("{0}: {1}", "{0}, {1}", "{0}. {1}", "{0}. {1}")
.unwrap();
patterns.end.special_case = Some(SpecialCasePattern {
condition: SerdeDFA::new(Cow::Borrowed("^a")).unwrap(),
pattern: ListJoinerPattern::try_from_str("{0} :o {1}", false, false).unwrap(),
});
patterns
}
#[test]
fn rejects_bad_patterns() {
assert!(ListJoinerPattern::try_from_str("{0} and", true, true).is_err());
assert!(ListJoinerPattern::try_from_str("and {1}", true, true).is_err());
assert!(ListJoinerPattern::try_from_str("{1} and {0}", true, true).is_err());
assert!(ListJoinerPattern::try_from_str("{1{0}}", true, true).is_err());
assert!(ListJoinerPattern::try_from_str("{0\u{202e}} and {1}", true, true).is_err());
assert!(ListJoinerPattern::try_from_str("{{0}} {{1}}", true, true).is_ok());
assert!(ListJoinerPattern::try_from_str("{0} and {1} ", true, true).is_ok());
assert!(ListJoinerPattern::try_from_str("{0} and {1} ", true, false).is_err());
assert!(ListJoinerPattern::try_from_str(" {0} and {1}", true, true).is_ok());
assert!(ListJoinerPattern::try_from_str(" {0} and {1}", false, true).is_err());
}
#[test]
fn produces_correct_parts() {
assert_eq!(
test_patterns_general().pair.unwrap().parts(""),
("$", ";", "+")
);
}
#[test]
fn produces_correct_parts_conditionally() {
assert_eq!(test_patterns_conditional().end.parts("a"), ("", " :o ", ""));
assert_eq!(
test_patterns_conditional().end.parts("ab"),
("", " :o ", "")
);
assert_eq!(test_patterns_conditional().end.parts("b"), ("", ". ", ""));
assert_eq!(test_patterns_conditional().end.parts("ba"), ("", ". ", ""));
}
#[test]
fn size_hint_works() {
let pattern = test_patterns_lengths();
assert_eq!(pattern.length_hint(0), LengthHint::exact(0));
assert_eq!(pattern.length_hint(1), LengthHint::exact(0));
assert_eq!(pattern.length_hint(2), LengthHint::exact(6));
assert_eq!(pattern.length_hint(200), LengthHint::exact(1 + 2 * 197 + 4));
let pattern = test_patterns_conditional();
assert_eq!(
pattern.length_hint(200),
LengthHint::exact(2 + 197 * 2) + LengthHint::between(2, 4)
);
}
}