use crate::complex::*;
use crate::indices::{Latin1Indices, Utf16Indices};
use crate::iterator_helpers::derive_usize_iterator_with_type;
use crate::provider::*;
use crate::rule_segmenter::*;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::str::CharIndices;
use icu_locale_core::LanguageIdentifier;
use icu_provider::prelude::*;
use utf8_iter::Utf8CharIndices;
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct WordBreakOptions<'a> {
pub content_locale: Option<&'a LanguageIdentifier>,
pub invariant_options: WordBreakInvariantOptions,
}
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct WordBreakInvariantOptions {}
#[derive(Debug)]
pub struct WordBreakIterator<'l, 's, Y: RuleBreakType<'l, 's> + ?Sized>(
RuleBreakIterator<'l, 's, Y>,
);
derive_usize_iterator_with_type!(WordBreakIterator);
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
#[zerovec::make_ule(WordTypeULE)]
pub enum WordType {
None = 0,
Number = 1,
Letter = 2,
}
impl WordType {
pub fn is_word_like(&self) -> bool {
self != &WordType::None
}
}
impl<'l, 's, Y: RuleBreakType<'l, 's> + ?Sized> WordBreakIterator<'l, 's, Y> {
#[inline]
pub fn word_type(&self) -> WordType {
self.0.word_type()
}
pub fn iter_with_word_type<'i: 'l + 's>(
&'i mut self,
) -> impl Iterator<Item = (usize, WordType)> + 'i {
core::iter::from_fn(move || self.next().map(|i| (i, self.word_type())))
}
#[inline]
pub fn is_word_like(&self) -> bool {
self.word_type().is_word_like()
}
}
pub type WordBreakIteratorUtf8<'l, 's> = WordBreakIterator<'l, 's, WordBreakTypeUtf8>;
pub type WordBreakIteratorPotentiallyIllFormedUtf8<'l, 's> =
WordBreakIterator<'l, 's, WordBreakTypePotentiallyIllFormedUtf8>;
pub type WordBreakIteratorLatin1<'l, 's> = WordBreakIterator<'l, 's, RuleBreakTypeLatin1>;
pub type WordBreakIteratorUtf16<'l, 's> = WordBreakIterator<'l, 's, WordBreakTypeUtf16>;
#[derive(Debug)]
pub struct WordSegmenter {
payload: DataPayload<WordBreakDataV2>,
complex: ComplexPayloads,
payload_locale_override: Option<DataPayload<WordBreakDataOverrideV1>>,
}
impl WordSegmenter {
#[cfg(feature = "compiled_data")]
#[cfg(feature = "auto")]
pub fn new_auto(_options: WordBreakInvariantOptions) -> Self {
Self {
payload: DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_WORD_BREAK_DATA_V2,
),
complex: ComplexPayloads::new_auto(),
payload_locale_override: None,
}
}
#[cfg(feature = "auto")]
icu_provider::gen_buffer_data_constructors!(
(options: WordBreakOptions) -> error: DataError,
functions: [
try_new_auto,
try_new_auto_with_buffer_provider,
try_new_auto_unstable,
Self
]
);
#[cfg(feature = "auto")]
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_auto)]
pub fn try_new_auto_unstable<D>(
provider: &D,
options: WordBreakOptions,
) -> Result<Self, DataError>
where
D: DataProvider<WordBreakDataV2>
+ DataProvider<WordBreakDataOverrideV1>
+ DataProvider<DictionaryForWordOnlyAutoV1>
+ DataProvider<LstmForWordLineAutoV1>
+ DataProvider<GraphemeClusterBreakDataV2>
+ ?Sized,
{
Ok(Self {
payload: provider.load(Default::default())?.payload,
complex: ComplexPayloads::try_new_auto(provider)?,
payload_locale_override: if let Some(locale) = options.content_locale {
let locale = DataLocale::from(locale);
let req = DataRequest {
id: DataIdentifierBorrowed::for_locale(&locale),
metadata: {
let mut metadata = DataRequestMetadata::default();
metadata.silent = true;
metadata
},
};
provider
.load(req)
.allow_identifier_not_found()?
.map(|r| r.payload)
} else {
None
},
})
}
#[cfg(feature = "compiled_data")]
#[cfg(feature = "lstm")]
pub fn new_lstm(_options: WordBreakInvariantOptions) -> Self {
Self {
payload: DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_WORD_BREAK_DATA_V2,
),
complex: ComplexPayloads::new_lstm(),
payload_locale_override: None,
}
}
#[cfg(feature = "lstm")]
icu_provider::gen_buffer_data_constructors!(
(options: WordBreakOptions) -> error: DataError,
functions: [
try_new_lstm,
try_new_lstm_with_buffer_provider,
try_new_lstm_unstable,
Self
]
);
#[cfg(feature = "lstm")]
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_lstm)]
pub fn try_new_lstm_unstable<D>(
provider: &D,
options: WordBreakOptions,
) -> Result<Self, DataError>
where
D: DataProvider<WordBreakDataV2>
+ DataProvider<WordBreakDataOverrideV1>
+ DataProvider<LstmForWordLineAutoV1>
+ DataProvider<GraphemeClusterBreakDataV2>
+ ?Sized,
{
Ok(Self {
payload: provider.load(Default::default())?.payload,
complex: ComplexPayloads::try_new_lstm(provider)?,
payload_locale_override: if let Some(locale) = options.content_locale {
let locale = DataLocale::from(locale);
let req = DataRequest {
id: DataIdentifierBorrowed::for_locale(&locale),
metadata: {
let mut metadata = DataRequestMetadata::default();
metadata.silent = true;
metadata
},
};
provider
.load(req)
.allow_identifier_not_found()?
.map(|r| r.payload)
} else {
None
},
})
}
#[cfg(feature = "compiled_data")]
pub fn new_dictionary(_options: WordBreakInvariantOptions) -> Self {
Self {
payload: DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_WORD_BREAK_DATA_V2,
),
complex: ComplexPayloads::new_dict(),
payload_locale_override: None,
}
}
icu_provider::gen_buffer_data_constructors!(
(options: WordBreakOptions) -> error: DataError,
functions: [
try_new_dictionary,
try_new_dictionary_with_buffer_provider,
try_new_dictionary_unstable,
Self
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_dictionary)]
pub fn try_new_dictionary_unstable<D>(
provider: &D,
options: WordBreakOptions,
) -> Result<Self, DataError>
where
D: DataProvider<WordBreakDataV2>
+ DataProvider<WordBreakDataOverrideV1>
+ DataProvider<DictionaryForWordOnlyAutoV1>
+ DataProvider<DictionaryForWordLineExtendedV1>
+ DataProvider<GraphemeClusterBreakDataV2>
+ ?Sized,
{
Ok(Self {
payload: provider.load(Default::default())?.payload,
complex: ComplexPayloads::try_new_dict(provider)?,
payload_locale_override: if let Some(locale) = options.content_locale {
let locale = DataLocale::from(locale);
let req = DataRequest {
id: DataIdentifierBorrowed::for_locale(&locale),
metadata: {
let mut metadata = DataRequestMetadata::default();
metadata.silent = true;
metadata
},
};
provider
.load(req)
.allow_identifier_not_found()?
.map(|r| r.payload)
} else {
None
},
})
}
pub fn segment_str<'l, 's>(&'l self, input: &'s str) -> WordBreakIteratorUtf8<'l, 's> {
let locale_override = self
.payload_locale_override
.as_ref()
.map(|payload| payload.get());
WordBreakIterator(RuleBreakIterator {
iter: input.char_indices(),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data: self.payload.get(),
complex: Some(&self.complex),
boundary_property: 0,
locale_override,
})
}
pub fn segment_utf8<'l, 's>(
&'l self,
input: &'s [u8],
) -> WordBreakIteratorPotentiallyIllFormedUtf8<'l, 's> {
let locale_override = self
.payload_locale_override
.as_ref()
.map(|payload| payload.get());
WordBreakIterator(RuleBreakIterator {
iter: Utf8CharIndices::new(input),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data: self.payload.get(),
complex: Some(&self.complex),
boundary_property: 0,
locale_override,
})
}
pub fn segment_latin1<'l, 's>(&'l self, input: &'s [u8]) -> WordBreakIteratorLatin1<'l, 's> {
let locale_override = self
.payload_locale_override
.as_ref()
.map(|payload| payload.get());
WordBreakIterator(RuleBreakIterator {
iter: Latin1Indices::new(input),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data: self.payload.get(),
complex: Some(&self.complex),
boundary_property: 0,
locale_override,
})
}
pub fn segment_utf16<'l, 's>(&'l self, input: &'s [u16]) -> WordBreakIteratorUtf16<'l, 's> {
let locale_override = self
.payload_locale_override
.as_ref()
.map(|payload| payload.get());
WordBreakIterator(RuleBreakIterator {
iter: Utf16Indices::new(input),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data: self.payload.get(),
complex: Some(&self.complex),
boundary_property: 0,
locale_override,
})
}
}
#[derive(Debug)]
pub struct WordBreakTypeUtf8;
impl crate::private::Sealed for WordBreakTypeUtf8 {}
impl<'l, 's> RuleBreakType<'l, 's> for WordBreakTypeUtf8 {
type IterAttr = CharIndices<'s>;
type CharType = char;
fn get_current_position_character_len(iter: &RuleBreakIterator<Self>) -> usize {
iter.get_current_codepoint().map_or(0, |c| c.len_utf8())
}
fn handle_complex_language(
iter: &mut RuleBreakIterator<'l, 's, Self>,
left_codepoint: Self::CharType,
) -> Option<usize> {
handle_complex_language_utf8(iter, left_codepoint)
}
}
#[derive(Debug)]
pub struct WordBreakTypePotentiallyIllFormedUtf8;
impl crate::private::Sealed for WordBreakTypePotentiallyIllFormedUtf8 {}
impl<'l, 's> RuleBreakType<'l, 's> for WordBreakTypePotentiallyIllFormedUtf8 {
type IterAttr = Utf8CharIndices<'s>;
type CharType = char;
fn get_current_position_character_len(iter: &RuleBreakIterator<Self>) -> usize {
iter.get_current_codepoint().map_or(0, |c| c.len_utf8())
}
fn handle_complex_language(
iter: &mut RuleBreakIterator<'l, 's, Self>,
left_codepoint: Self::CharType,
) -> Option<usize> {
handle_complex_language_utf8(iter, left_codepoint)
}
}
fn handle_complex_language_utf8<'l, 's, T>(
iter: &mut RuleBreakIterator<'l, 's, T>,
left_codepoint: T::CharType,
) -> Option<usize>
where
T: RuleBreakType<'l, 's, CharType = char>,
{
let start_iter = iter.iter.clone();
let start_point = iter.current_pos_data;
let mut s = String::new();
s.push(left_codepoint);
loop {
debug_assert!(!iter.is_eof());
s.push(iter.get_current_codepoint()?);
iter.advance_iter();
if let Some(current_break_property) = iter.get_current_break_property() {
if current_break_property != iter.data.complex_property {
break;
}
} else {
break;
}
}
iter.iter = start_iter;
iter.current_pos_data = start_point;
#[allow(clippy::unwrap_used)] let breaks = complex_language_segment_str(iter.complex.unwrap(), &s);
iter.result_cache = breaks;
let first_pos = *iter.result_cache.first()?;
let mut i = left_codepoint.len_utf8();
loop {
if i == first_pos {
iter.result_cache = iter.result_cache.iter().skip(1).map(|r| r - i).collect();
return iter.get_current_position();
}
debug_assert!(
i < first_pos,
"we should always arrive at first_pos: near index {:?}",
iter.get_current_position()
);
i += T::get_current_position_character_len(iter);
iter.advance_iter();
if iter.is_eof() {
iter.result_cache.clear();
return Some(iter.len);
}
}
}
#[derive(Debug)]
pub struct WordBreakTypeUtf16;
impl crate::private::Sealed for WordBreakTypeUtf16 {}
impl<'s> RuleBreakType<'_, 's> for WordBreakTypeUtf16 {
type IterAttr = Utf16Indices<'s>;
type CharType = u32;
fn get_current_position_character_len(iter: &RuleBreakIterator<Self>) -> usize {
match iter.get_current_codepoint() {
None => 0,
Some(ch) if ch >= 0x10000 => 2,
_ => 1,
}
}
fn handle_complex_language(
iter: &mut RuleBreakIterator<Self>,
left_codepoint: Self::CharType,
) -> Option<usize> {
let start_iter = iter.iter.clone();
let start_point = iter.current_pos_data;
let mut s = vec![left_codepoint as u16];
loop {
debug_assert!(!iter.is_eof());
s.push(iter.get_current_codepoint()? as u16);
iter.advance_iter();
if let Some(current_break_property) = iter.get_current_break_property() {
if current_break_property != iter.data.complex_property {
break;
}
} else {
break;
}
}
iter.iter = start_iter;
iter.current_pos_data = start_point;
#[allow(clippy::unwrap_used)] let breaks = complex_language_segment_utf16(iter.complex.unwrap(), &s);
iter.result_cache = breaks;
let first_pos = *iter.result_cache.first()?;
let mut i = 1;
loop {
if i == first_pos {
iter.result_cache = iter.result_cache.iter().skip(1).map(|r| r - i).collect();
return iter.get_current_position();
}
debug_assert!(
i < first_pos,
"we should always arrive at first_pos: near index {:?}",
iter.get_current_position()
);
i += 1;
iter.advance_iter();
if iter.is_eof() {
iter.result_cache.clear();
return Some(iter.len);
}
}
}
}
#[cfg(all(test, feature = "serde"))]
#[test]
fn empty_string() {
let segmenter = WordSegmenter::new_auto(WordBreakInvariantOptions::default());
let breaks: Vec<usize> = segmenter.segment_str("").collect();
assert_eq!(breaks, [0]);
}