use crate::complex::*;
use crate::indices::*;
use crate::provider::*;
use crate::scaffold::*;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use icu_locale_core::LanguageIdentifier;
use icu_provider::prelude::*;
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct WordBreakOptions<'a> {
pub content_locale: Option<&'a LanguageIdentifier>,
pub invariant_options: WordBreakInvariantOptions,
}
impl WordBreakOptions<'_> {
pub const fn default() -> Self {
Self {
content_locale: None,
invariant_options: WordBreakInvariantOptions::default(),
}
}
}
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct WordBreakInvariantOptions {}
impl WordBreakInvariantOptions {
pub const fn default() -> Self {
Self {}
}
}
#[derive(Debug)]
pub struct WordBreakIterator<'data, 's, Y: RuleBreakType>(WordBreakIteratorInner<'data, 's, Y>);
#[derive(Debug)]
enum WordBreakIteratorInner<'data, 's, Y: RuleBreakType> {
V1(crate::rule_segmenter_v1::RuleBreakIterator<'data, 's, Y>),
#[cfg(feature = "unstable")]
V2(crate::rule_segmenter_v2::RuleBreakIterator<'data, 's, Y, ComplexWord<Y>>),
}
impl<Y: RuleBreakType> Iterator for WordBreakIterator<'_, '_, Y> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
WordBreakIteratorInner::V1(ref mut iter) => iter.next(),
#[cfg(feature = "unstable")]
WordBreakIteratorInner::V2(ref mut iter) => iter.next(),
}
}
}
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum WordType {
None = 0,
Number = 1,
Letter = 2,
}
impl WordType {
pub fn is_word_like(&self) -> bool {
self != &WordType::None
}
}
impl<'data, 's, Y: RuleBreakType> WordBreakIterator<'data, 's, Y> {
#[inline]
pub fn word_type(&self) -> WordType {
let last_accepting_status = match self.0 {
WordBreakIteratorInner::V1(ref iter) => iter.rule_status(),
#[cfg(feature = "unstable")]
WordBreakIteratorInner::V2(ref iter) => iter.last_accepting_status(),
};
match last_accepting_status {
0 => WordType::None,
1 => WordType::Number,
_ => WordType::Letter,
}
}
pub fn iter_with_word_type(self) -> WordBreakIteratorWithWordType<'data, 's, Y> {
WordBreakIteratorWithWordType(self)
}
#[inline]
pub fn is_word_like(&self) -> bool {
self.word_type().is_word_like()
}
}
#[derive(Debug)]
pub struct WordBreakIteratorWithWordType<'data, 's, Y: RuleBreakType>(
WordBreakIterator<'data, 's, Y>,
);
impl<Y: RuleBreakType> Iterator for WordBreakIteratorWithWordType<'_, '_, Y> {
type Item = (usize, WordType);
fn next(&mut self) -> Option<Self::Item> {
let ret = self.0.next()?;
Some((ret, self.0.word_type()))
}
}
#[derive(Debug)]
pub struct WordSegmenter(WordSegmenterInner);
#[derive(Debug)]
enum WordSegmenterInner {
V1 {
payload: DataPayload<SegmenterBreakWordV1>,
complex: ComplexPayloads,
payload_locale_override: Option<DataPayload<SegmenterBreakWordOverrideV1>>,
},
#[cfg(feature = "unstable")]
V2 {
payload: DataPayload<SegmenterBreakWordV2>,
complex: ComplexPayloads,
},
}
#[derive(Clone, Debug, Copy)]
pub struct WordSegmenterBorrowed<'data>(WordSegmenterBorrowedInner<'data>);
#[derive(Clone, Debug, Copy)]
enum WordSegmenterBorrowedInner<'data> {
V1 {
data: &'data RuleBreakData<'data>,
complex: ComplexPayloadsBorrowed<'data>,
locale_override: Option<&'data RuleBreakDataOverride<'data>>,
},
#[cfg(feature = "unstable")]
V2 {
data: &'data SegmenterStateMachine<'data>,
complex: ComplexPayloadsBorrowed<'data>,
},
}
impl WordSegmenter {
#[cfg(feature = "compiled_data")]
#[cfg(feature = "auto")]
pub fn new_auto(_options: WordBreakInvariantOptions) -> WordSegmenterBorrowed<'static> {
let mut s = Self::new_for_non_complex_scripts(_options);
s.load_auto();
s
}
#[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<SegmenterBreakWordV1>
+ DataProvider<SegmenterBreakWordOverrideV1>
+ DataProvider<SegmenterDictionaryAutoV1>
+ DataProvider<SegmenterLstmAutoV1>
+ DataProvider<SegmenterBreakGraphemeClusterV1>
+ ?Sized,
{
let mut s = Self::try_new_for_non_complex_scripts_unstable(provider, options)?;
s.load_auto_unstable(provider)?;
Ok(s)
}
#[cfg(feature = "compiled_data")]
#[cfg(feature = "lstm")]
pub fn new_lstm(options: WordBreakInvariantOptions) -> WordSegmenterBorrowed<'static> {
let mut s = Self::new_for_non_complex_scripts(options);
s.load_lstm();
s
}
#[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<SegmenterBreakWordV1>
+ DataProvider<SegmenterBreakWordOverrideV1>
+ DataProvider<SegmenterLstmAutoV1>
+ DataProvider<SegmenterBreakGraphemeClusterV1>
+ ?Sized,
{
let mut s = Self::try_new_for_non_complex_scripts_unstable(provider, options)?;
s.load_lstm_unstable(provider)?;
Ok(s)
}
#[cfg(feature = "compiled_data")]
pub fn new_dictionary(options: WordBreakInvariantOptions) -> WordSegmenterBorrowed<'static> {
let mut s = Self::new_for_non_complex_scripts(options);
s.load_dictionary();
s
}
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<SegmenterBreakWordV1>
+ DataProvider<SegmenterBreakWordOverrideV1>
+ DataProvider<SegmenterDictionaryAutoV1>
+ DataProvider<SegmenterDictionaryExtendedV1>
+ DataProvider<SegmenterBreakGraphemeClusterV1>
+ ?Sized,
{
let mut s = Self::try_new_for_non_complex_scripts_unstable(provider, options)?;
s.load_dictionary_unstable(provider)?;
Ok(s)
}
#[cfg(feature = "compiled_data")]
pub const fn new_for_non_complex_scripts(
_options: WordBreakInvariantOptions,
) -> WordSegmenterBorrowed<'static> {
WordSegmenterBorrowed(WordSegmenterBorrowedInner::V1 {
data: Baked::SINGLETON_SEGMENTER_BREAK_WORD_V1,
complex: ComplexPayloadsBorrowed::new(),
locale_override: None,
})
}
icu_provider::gen_buffer_data_constructors!(
(options: WordBreakOptions) -> error: DataError,
functions: [
try_new_for_non_complex_scripts,
try_new_for_non_complex_scripts_with_buffer_provider,
try_new_for_non_complex_scripts_unstable,
Self
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_for_non_complex_scripts)]
pub fn try_new_for_non_complex_scripts_unstable<D>(
provider: &D,
options: WordBreakOptions,
) -> Result<Self, DataError>
where
D: DataProvider<SegmenterBreakWordV1>
+ DataProvider<SegmenterBreakWordOverrideV1>
+ DataProvider<SegmenterBreakGraphemeClusterV1>
+ ?Sized,
{
Ok(Self(WordSegmenterInner::V1 {
payload: provider.load(Default::default())?.payload,
complex: ComplexPayloads::try_new(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 = "unstable")]
pub const fn new_neo_for_non_complex_scripts(
_options: WordBreakInvariantOptions,
) -> WordSegmenterBorrowed<'static> {
WordSegmenterBorrowed(WordSegmenterBorrowedInner::V2 {
data: Baked::SINGLETON_SEGMENTER_BREAK_WORD_V2,
complex: ComplexPayloadsBorrowed::new(),
})
}
#[cfg(feature = "unstable")]
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_neo_for_non_complex_scripts)]
pub fn try_new_neo_for_non_complex_scripts_unstable<D>(
provider: &D,
_options: WordBreakOptions,
) -> Result<Self, DataError>
where
D: DataProvider<SegmenterBreakWordV2>
+ DataProvider<SegmenterBreakGraphemeClusterV1>
+ ?Sized,
{
Ok(Self(WordSegmenterInner::V2 {
payload: provider.load(Default::default())?.payload,
complex: ComplexPayloads::try_new(provider)?,
}))
}
#[cfg(feature = "lstm")]
pub fn load_lstm_unstable<D>(&mut self, provider: &D) -> Result<(), DataError>
where
D: DataProvider<SegmenterLstmAutoV1> + ?Sized,
{
let complex = match self.0 {
WordSegmenterInner::V1 {
ref mut complex, ..
} => complex,
#[cfg(feature = "unstable")]
WordSegmenterInner::V2 {
ref mut complex, ..
} => complex,
};
complex.with_southeast_asian_lstms(provider)?;
Ok(())
}
#[cfg(feature = "serde")]
#[cfg(feature = "lstm")]
pub fn load_lstm_with_buffer_provider(
&mut self,
provider: &(impl BufferProvider + ?Sized),
) -> Result<(), DataError> {
self.load_lstm_unstable(&provider.as_deserializing())
}
pub fn load_dictionary_unstable<D>(&mut self, provider: &D) -> Result<(), DataError>
where
D: DataProvider<SegmenterDictionaryAutoV1>
+ DataProvider<SegmenterDictionaryExtendedV1>
+ ?Sized,
{
let complex = match self.0 {
WordSegmenterInner::V1 {
ref mut complex, ..
} => complex,
#[cfg(feature = "unstable")]
WordSegmenterInner::V2 {
ref mut complex, ..
} => complex,
};
complex.with_southeast_asian_dictionaries(provider)?;
complex.with_japanese_dictionary(provider)?;
Ok(())
}
#[cfg(feature = "serde")]
pub fn load_dictionary_with_buffer_provider(
&mut self,
provider: &(impl BufferProvider + ?Sized),
) -> Result<(), DataError> {
self.load_dictionary_unstable(&provider.as_deserializing())
}
#[cfg(feature = "auto")]
pub fn load_auto_unstable<D>(&mut self, provider: &D) -> Result<(), DataError>
where
D: DataProvider<SegmenterDictionaryAutoV1> + DataProvider<SegmenterLstmAutoV1> + ?Sized,
{
let complex = match self.0 {
WordSegmenterInner::V1 {
ref mut complex, ..
} => complex,
#[cfg(feature = "unstable")]
WordSegmenterInner::V2 {
ref mut complex, ..
} => complex,
};
complex.with_southeast_asian_lstms(provider)?;
complex.with_japanese_dictionary(provider)?;
Ok(())
}
#[cfg(feature = "auto")]
#[cfg(feature = "serde")]
pub fn load_auto_with_buffer_provider(
&mut self,
provider: &(impl BufferProvider + ?Sized),
) -> Result<(), DataError> {
self.load_auto_unstable(&provider.as_deserializing())
}
pub fn as_borrowed(&self) -> WordSegmenterBorrowed<'_> {
WordSegmenterBorrowed(match &self.0 {
WordSegmenterInner::V1 {
payload,
complex,
payload_locale_override,
} => WordSegmenterBorrowedInner::V1 {
data: payload.get(),
complex: complex.as_borrowed(),
locale_override: payload_locale_override.as_ref().map(|p| p.get()),
},
#[cfg(feature = "unstable")]
WordSegmenterInner::V2 { payload, complex } => WordSegmenterBorrowedInner::V2 {
data: payload.get(),
complex: complex.as_borrowed(),
},
})
}
}
impl<'data> WordSegmenterBorrowed<'data> {
pub fn segment_str<'s>(self, input: &'s str) -> WordBreakIterator<'data, 's, Utf8> {
WordBreakIterator(match self.0 {
WordSegmenterBorrowedInner::V1 {
data,
complex,
locale_override,
} => WordBreakIteratorInner::V1(crate::rule_segmenter_v1::RuleBreakIterator {
iter: input.char_indices(),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data,
complex: Some(complex),
boundary_property: 0,
locale_override,
handle_complex: handle_complex_utf8,
}),
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 { data, complex } => {
WordBreakIteratorInner::V2(crate::rule_segmenter_v2::RuleBreakIterator::new(
input.char_indices(),
data,
None,
Some(complex),
))
}
})
}
pub fn segment_utf8<'s>(
self,
input: &'s [u8],
) -> WordBreakIterator<'data, 's, PotentiallyIllFormedUtf8> {
WordBreakIterator(match self.0 {
WordSegmenterBorrowedInner::V1 {
data,
complex,
locale_override,
} => WordBreakIteratorInner::V1(crate::rule_segmenter_v1::RuleBreakIterator {
iter: Utf8CharIndices::new(input),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data,
complex: Some(complex),
boundary_property: 0,
locale_override,
handle_complex: handle_complex_utf8,
}),
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 { data, complex } => {
WordBreakIteratorInner::V2(crate::rule_segmenter_v2::RuleBreakIterator::new(
Utf8CharIndices::new(input),
data,
None,
Some(complex),
))
}
})
}
pub fn segment_latin1<'s>(self, input: &'s [u8]) -> WordBreakIterator<'data, 's, Latin1> {
WordBreakIterator(match self.0 {
WordSegmenterBorrowedInner::V1 {
data,
complex,
locale_override,
} => WordBreakIteratorInner::V1(crate::rule_segmenter_v1::RuleBreakIterator {
iter: Latin1Indices::new(input),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data,
complex: Some(complex),
boundary_property: 0,
locale_override,
handle_complex: crate::rule_segmenter_v1::empty_handle_complex,
}),
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 { data, .. } => {
WordBreakIteratorInner::V2(crate::rule_segmenter_v2::RuleBreakIterator::new(
Latin1Indices::new(input),
data,
None,
None,
))
}
})
}
pub fn segment_utf16<'s>(self, input: &'s [u16]) -> WordBreakIterator<'data, 's, Utf16> {
WordBreakIterator(match self.0 {
WordSegmenterBorrowedInner::V1 {
data,
complex,
locale_override,
} => WordBreakIteratorInner::V1(crate::rule_segmenter_v1::RuleBreakIterator {
iter: Utf16Indices::new(input),
len: input.len(),
current_pos_data: None,
result_cache: Vec::new(),
data,
complex: Some(complex),
boundary_property: 0,
locale_override,
handle_complex: handle_complex_utf16,
}),
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 { data, complex } => {
WordBreakIteratorInner::V2(crate::rule_segmenter_v2::RuleBreakIterator::new(
Utf16Indices::new(input),
data,
None,
Some(complex),
))
}
})
}
}
impl WordSegmenterBorrowed<'static> {
#[cfg(feature = "lstm")]
#[cfg(feature = "compiled_data")]
pub fn load_lstm(&mut self) {
let complex = match self.0 {
WordSegmenterBorrowedInner::V1 {
ref mut complex, ..
} => complex,
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 {
ref mut complex, ..
} => complex,
};
complex.with_southeast_asian_lstms();
}
#[cfg(feature = "compiled_data")]
pub fn load_dictionary(&mut self) {
let complex = match self.0 {
WordSegmenterBorrowedInner::V1 {
ref mut complex, ..
} => complex,
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 {
ref mut complex, ..
} => complex,
};
complex.with_southeast_asian_dictionaries();
complex.with_japanese_dictionary();
}
#[cfg(feature = "auto")]
#[cfg(feature = "compiled_data")]
pub fn load_auto(&mut self) {
let complex = match self.0 {
WordSegmenterBorrowedInner::V1 {
ref mut complex, ..
} => complex,
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 {
ref mut complex, ..
} => complex,
};
complex.with_southeast_asian_lstms();
complex.with_japanese_dictionary();
}
pub fn static_to_owned(self) -> WordSegmenter {
WordSegmenter(match self.0 {
WordSegmenterBorrowedInner::V1 {
data,
complex,
locale_override,
} => WordSegmenterInner::V1 {
payload: DataPayload::from_static_ref(data),
complex: complex.static_to_owned(),
payload_locale_override: locale_override.map(DataPayload::from_static_ref),
},
#[cfg(feature = "unstable")]
WordSegmenterBorrowedInner::V2 { data, complex } => WordSegmenterInner::V2 {
payload: DataPayload::from_static_ref(data),
complex: complex.static_to_owned(),
},
})
}
}
fn handle_complex_utf8<T>(
iter: &mut crate::rule_segmenter_v1::RuleBreakIterator<'_, '_, T>,
left_codepoint: T::CharType,
) -> Option<usize>
where
T: RuleBreakType<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;
#[expect(clippy::unwrap_used)] let breaks = iter.complex.unwrap().segment_str(&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 += iter.get_current_codepoint().map_or(0, T::char_len);
iter.advance_iter();
if iter.is_eof() {
iter.result_cache.clear();
return Some(iter.len);
}
}
}
fn handle_complex_utf16<T>(
iter: &mut crate::rule_segmenter_v1::RuleBreakIterator<'_, '_, T>,
left_codepoint: T::CharType,
) -> Option<usize>
where
T: RuleBreakType<CharType = u32>,
{
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;
#[expect(clippy::unwrap_used)] let breaks = iter.complex.unwrap().segment_utf16(&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);
}
}
}
#[derive(Debug)]
#[cfg(feature = "unstable")]
struct ComplexWord<Y>(core::marker::PhantomData<Y>);
#[cfg(feature = "unstable")]
impl<Y: RuleBreakType> crate::rule_segmenter_v2::ComplexHandler<Y> for ComplexWord<Y> {
const BREAK_AT_BOUNDARIES: bool = true;
type Cache = [usize; 16];
const BREAK_STATUS: u8 = WordType::Letter as u8;
type ComplexPayloads<'s> = Y::ComplexPayloads<'s>;
type ComplexPayload<'s> = Y::ComplexPayload<'s>;
fn select<'data>(
complex_payloads: &Y::ComplexPayloads<'data>,
complex_script: ComplexScript,
) -> Option<Self::ComplexPayload<'data>> {
Y::select_complex(complex_payloads, complex_script)
}
fn handle<'data, 's>(
complex_payloads: &Self::ComplexPayload<'data>,
iter: &Y::IterAttr<'s>,
past_complex: &Y::IterAttr<'s>,
) -> ComplexIterator<'data, 's, Y> {
Y::handle_complex(complex_payloads, iter, past_complex)
}
}
#[test]
fn empty_string() {
let segmenter =
WordSegmenter::new_for_non_complex_scripts(WordBreakInvariantOptions::default());
let breaks: Vec<usize> = segmenter.segment_str("").collect();
assert_eq!(breaks, [0]);
}
#[test]
fn empty_string_neo() {
let segmenter =
WordSegmenter::new_neo_for_non_complex_scripts(WordBreakInvariantOptions::default());
let breaks: Vec<usize> = segmenter.segment_str("").collect();
assert_eq!(breaks, [0]);
}