diff options
Diffstat (limited to 'intl/icu_capi/src')
42 files changed, 8812 insertions, 0 deletions
diff --git a/intl/icu_capi/src/bidi.rs b/intl/icu_capi/src/bidi.rs new file mode 100644 index 0000000000..cf4c8af98b --- /dev/null +++ b/intl/icu_capi/src/bidi.rs @@ -0,0 +1,267 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::vec::Vec; + use diplomat_runtime::DiplomatWriteable; + + use core::fmt::Write; + use icu_properties::bidi::BidiClassAdapter; + use icu_properties::maps; + use icu_properties::BidiClass; + use unicode_bidi::BidiInfo; + use unicode_bidi::Level; + use unicode_bidi::Paragraph; + + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + + pub enum ICU4XBidiDirection { + Ltr, + Rtl, + Mixed, + } + + #[diplomat::opaque] + /// An ICU4X Bidi object, containing loaded bidi data + #[diplomat::rust_link(icu::properties::bidi::BidiClassAdapter, Struct)] + // #[diplomat::rust_link(icu::properties::maps::load_bidi_class, Struct)] + pub struct ICU4XBidi(pub maps::CodePointMapData<BidiClass>); + + impl ICU4XBidi { + /// Creates a new [`ICU4XBidi`] from locale data. + #[diplomat::rust_link(icu::properties::bidi::BidiClassAdapter::new, FnInStruct)] + pub fn create(provider: &ICU4XDataProvider) -> Result<Box<ICU4XBidi>, ICU4XError> { + Ok(Box::new(ICU4XBidi(call_constructor_unstable!( + maps::bidi_class [m => Ok(m.static_to_owned())], + maps::load_bidi_class, + provider, + )?))) + } + + /// Use the data loaded in this object to process a string and calculate bidi information + /// + /// Takes in a Level for the default level, if it is an invalid value it will default to LTR + #[diplomat::rust_link(unicode_bidi::BidiInfo::new_with_data_source, FnInStruct)] + #[diplomat::rust_link( + icu::properties::bidi::BidiClassAdapter::bidi_class, + FnInStruct, + hidden + )] + pub fn for_text<'text>( + &self, + text: &'text str, + default_level: u8, + ) -> Box<ICU4XBidiInfo<'text>> { + let data = self.0.as_borrowed(); + let adapter = BidiClassAdapter::new(data); + + Box::new(ICU4XBidiInfo(BidiInfo::new_with_data_source( + &adapter, + text, + Level::new(default_level).ok(), + ))) + } + /// Utility function for producing reorderings given a list of levels + /// + /// Produces a map saying which visual index maps to which source index. + /// + /// The levels array must not have values greater than 126 (this is the + /// Bidi maximum explicit depth plus one). + /// Failure to follow this invariant may lead to incorrect results, + /// but is still safe. + #[diplomat::rust_link(unicode_bidi::BidiInfo::reorder_visual, FnInStruct)] + pub fn reorder_visual(&self, levels: &[u8]) -> Box<ICU4XReorderedIndexMap> { + let levels = Level::from_slice_unchecked(levels); + Box::new(ICU4XReorderedIndexMap(BidiInfo::reorder_visual(levels))) + } + + /// Check if a Level returned by level_at is an RTL level. + /// + /// Invalid levels (numbers greater than 125) will be assumed LTR + #[diplomat::rust_link(unicode_bidi::Level::is_rtl, FnInStruct)] + pub fn level_is_rtl(level: u8) -> bool { + Level::new(level).unwrap_or_else(|_| Level::ltr()).is_rtl() + } + + /// Check if a Level returned by level_at is an LTR level. + /// + /// Invalid levels (numbers greater than 125) will be assumed LTR + #[diplomat::rust_link(unicode_bidi::Level::is_ltr, FnInStruct)] + pub fn level_is_ltr(level: u8) -> bool { + Level::new(level).unwrap_or_else(|_| Level::ltr()).is_ltr() + } + + /// Get a basic RTL Level value + #[diplomat::rust_link(unicode_bidi::Level::rtl, FnInStruct)] + pub fn level_rtl() -> u8 { + Level::rtl().number() + } + + /// Get a simple LTR Level value + #[diplomat::rust_link(unicode_bidi::Level::ltr, FnInStruct)] + pub fn level_ltr() -> u8 { + Level::ltr().number() + } + } + + /// Thin wrapper around a vector that maps visual indices to source indices + /// + /// `map[visualIndex] = sourceIndex` + /// + /// Produced by `reorder_visual()` on [`ICU4XBidi`]. + #[diplomat::opaque] + pub struct ICU4XReorderedIndexMap(pub Vec<usize>); + + impl ICU4XReorderedIndexMap { + /// Get this as a slice/array of indices + pub fn as_slice<'a>(&'a self) -> &'a [usize] { + &self.0 + } + + /// The length of this map + #[allow(clippy::len_without_is_empty)] + #[diplomat::attr(dart, rename = "length")] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Get element at `index`. Returns 0 when out of bounds + /// (note that 0 is also a valid in-bounds value, please use `len()` + /// to avoid out-of-bounds) + pub fn get(&self, index: usize) -> usize { + self.0.get(index).copied().unwrap_or(0) + } + } + + /// An object containing bidi information for a given string, produced by `for_text()` on `ICU4XBidi` + #[diplomat::rust_link(unicode_bidi::BidiInfo, Struct)] + #[diplomat::opaque] + pub struct ICU4XBidiInfo<'text>(pub BidiInfo<'text>); + + impl<'text> ICU4XBidiInfo<'text> { + /// The number of paragraphs contained here + pub fn paragraph_count(&self) -> usize { + self.0.paragraphs.len() + } + + /// Get the nth paragraph, returning `None` if out of bounds + pub fn paragraph_at(&'text self, n: usize) -> Option<Box<ICU4XBidiParagraph<'text>>> { + self.0 + .paragraphs + .get(n) + .map(|p| Box::new(ICU4XBidiParagraph(Paragraph::new(&self.0, p)))) + } + + /// The number of bytes in this full text + pub fn size(&self) -> usize { + self.0.levels.len() + } + + /// Get the BIDI level at a particular byte index in the full text. + /// This integer is conceptually a `unicode_bidi::Level`, + /// and can be further inspected using the static methods on ICU4XBidi. + /// + /// Returns 0 (equivalent to `Level::ltr()`) on error + pub fn level_at(&self, pos: usize) -> u8 { + if let Some(l) = self.0.levels.get(pos) { + l.number() + } else { + 0 + } + } + } + + /// Bidi information for a single processed paragraph + #[diplomat::opaque] + pub struct ICU4XBidiParagraph<'info>(pub Paragraph<'info, 'info>); + + impl<'info> ICU4XBidiParagraph<'info> { + /// Given a paragraph index `n` within the surrounding text, this sets this + /// object to the paragraph at that index. Returns `ICU4XError::OutOfBoundsError` when out of bounds. + /// + /// This is equivalent to calling `paragraph_at()` on `ICU4XBidiInfo` but doesn't + /// create a new object + pub fn set_paragraph_in_text(&mut self, n: usize) -> Result<(), ICU4XError> { + let para = self + .0 + .info + .paragraphs + .get(n) + .ok_or(ICU4XError::OutOfBoundsError)?; + self.0 = Paragraph::new(self.0.info, para); + Ok(()) + } + #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)] + /// The primary direction of this paragraph + pub fn direction(&self) -> ICU4XBidiDirection { + self.0.direction().into() + } + + /// The number of bytes in this paragraph + #[diplomat::rust_link(unicode_bidi::ParagraphInfo::len, FnInStruct)] + pub fn size(&self) -> usize { + self.0.para.len() + } + + /// The start index of this paragraph within the source text + pub fn range_start(&self) -> usize { + self.0.para.range.start + } + + /// The end index of this paragraph within the source text + pub fn range_end(&self) -> usize { + self.0.para.range.end + } + + /// Reorder a line based on display order. The ranges are specified relative to the source text and must be contained + /// within this paragraph's range. + #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)] + pub fn reorder_line( + &self, + range_start: usize, + range_end: usize, + out: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + if range_start < self.range_start() || range_end > self.range_end() { + return Err(ICU4XError::OutOfBoundsError); + } + + let info = self.0.info; + let para = self.0.para; + + let reordered = info.reorder_line(para, range_start..range_end); + + Ok(out.write_str(&reordered)?) + } + + /// Get the BIDI level at a particular byte index in this paragraph. + /// This integer is conceptually a `unicode_bidi::Level`, + /// and can be further inspected using the static methods on ICU4XBidi. + /// + /// Returns 0 (equivalent to `Level::ltr()`) on error + #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)] + pub fn level_at(&self, pos: usize) -> u8 { + if pos >= self.size() { + return 0; + } + + self.0.level_at(pos).number() + } + } +} + +use unicode_bidi::Direction; + +impl From<Direction> for ffi::ICU4XBidiDirection { + fn from(other: Direction) -> Self { + match other { + Direction::Ltr => Self::Ltr, + Direction::Rtl => Self::Rtl, + Direction::Mixed => Self::Mixed, + } + } +} diff --git a/intl/icu_capi/src/calendar.rs b/intl/icu_capi/src/calendar.rs new file mode 100644 index 0000000000..dd4b2dcbaa --- /dev/null +++ b/intl/icu_capi/src/calendar.rs @@ -0,0 +1,148 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::sync::Arc; + + use core::fmt::Write; + use icu_calendar::{AnyCalendar, AnyCalendarKind}; + + use crate::errors::ffi::ICU4XError; + use crate::locale::ffi::ICU4XLocale; + use crate::provider::ffi::ICU4XDataProvider; + + /// The various calendar types currently supported by [`ICU4XCalendar`] + #[diplomat::enum_convert(AnyCalendarKind, needs_wildcard)] + #[diplomat::rust_link(icu::calendar::AnyCalendarKind, Enum)] + pub enum ICU4XAnyCalendarKind { + /// The kind of an Iso calendar + Iso = 0, + /// The kind of a Gregorian calendar + Gregorian = 1, + /// The kind of a Buddhist calendar + Buddhist = 2, + /// The kind of a Japanese calendar with modern eras + Japanese = 3, + /// The kind of a Japanese calendar with modern and historic eras + JapaneseExtended = 4, + /// The kind of an Ethiopian calendar, with Amete Mihret era + Ethiopian = 5, + /// The kind of an Ethiopian calendar, with Amete Alem era + EthiopianAmeteAlem = 6, + /// The kind of a Indian calendar + Indian = 7, + /// The kind of a Coptic calendar + Coptic = 8, + /// The kind of a Dangi calendar + Dangi = 9, + /// The kind of a Chinese calendar + Chinese = 10, + /// The kind of a Hebrew calendar + Hebrew = 11, + /// The kind of a Islamic civil calendar + IslamicCivil = 12, + /// The kind of a Islamic observational calendar + IslamicObservational = 13, + /// The kind of a Islamic tabular calendar + IslamicTabular = 14, + /// The kind of a Islamic Umm al-Qura calendar + IslamicUmmAlQura = 15, + /// The kind of a Persian calendar + Persian = 16, + /// The kind of a Roc calendar + Roc = 17, + } + + impl ICU4XAnyCalendarKind { + /// Read the calendar type off of the -u-ca- extension on a locale. + /// + /// Errors if there is no calendar on the locale or if the locale's calendar + /// is not known or supported. + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::get_for_locale, FnInEnum)] + pub fn get_for_locale(locale: &ICU4XLocale) -> Result<ICU4XAnyCalendarKind, ()> { + AnyCalendarKind::get_for_locale(&locale.0) + .map(Into::into) + .ok_or(()) + } + + /// Obtain the calendar type given a BCP-47 -u-ca- extension string. + /// + /// Errors if the calendar is not known or supported. + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::get_for_bcp47_value, FnInEnum)] + #[diplomat::rust_link( + icu::calendar::AnyCalendarKind::get_for_bcp47_string, + FnInEnum, + hidden + )] + #[diplomat::rust_link( + icu::calendar::AnyCalendarKind::get_for_bcp47_bytes, + FnInEnum, + hidden + )] + pub fn get_for_bcp47(s: &str) -> Result<ICU4XAnyCalendarKind, ()> { + let s = s.as_bytes(); // #2520 + AnyCalendarKind::get_for_bcp47_bytes(s) + .map(Into::into) + .ok_or(()) + } + + /// Obtain the string suitable for use in the -u-ca- extension in a BCP47 locale. + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::as_bcp47_string, FnInEnum)] + #[diplomat::rust_link(icu::calendar::AnyCalendarKind::as_bcp47_value, FnInEnum, hidden)] + pub fn bcp47( + self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let kind = AnyCalendarKind::from(self); + Ok(write.write_str(kind.as_bcp47_string())?) + } + } + + #[diplomat::opaque] + #[diplomat::transparent_convert] + #[diplomat::rust_link(icu::calendar::AnyCalendar, Enum)] + pub struct ICU4XCalendar(pub Arc<AnyCalendar>); + + impl ICU4XCalendar { + /// Creates a new [`ICU4XCalendar`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::AnyCalendar::new_for_locale, FnInEnum)] + pub fn create_for_locale( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XCalendar>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XCalendar(Arc::new(call_constructor!( + AnyCalendar::new_for_locale [r => Ok(r)], + AnyCalendar::try_new_for_locale_with_any_provider, + AnyCalendar::try_new_for_locale_with_buffer_provider, + provider, + &locale + )?)))) + } + + /// Creates a new [`ICU4XCalendar`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::AnyCalendar::new, FnInEnum)] + pub fn create_for_kind( + provider: &ICU4XDataProvider, + kind: ICU4XAnyCalendarKind, + ) -> Result<Box<ICU4XCalendar>, ICU4XError> { + Ok(Box::new(ICU4XCalendar(Arc::new(call_constructor!( + AnyCalendar::new [r => Ok(r)], + AnyCalendar::try_new_with_any_provider, + AnyCalendar::try_new_with_buffer_provider, + provider, + kind.into() + )?)))) + } + + /// Returns the kind of this calendar + #[diplomat::rust_link(icu::calendar::AnyCalendar::kind, FnInEnum)] + pub fn kind(&self) -> ICU4XAnyCalendarKind { + self.0.kind().into() + } + } +} diff --git a/intl/icu_capi/src/casemap.rs b/intl/icu_capi/src/casemap.rs new file mode 100644 index 0000000000..cc20ccccec --- /dev/null +++ b/intl/icu_capi/src/casemap.rs @@ -0,0 +1,336 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use icu_casemap::titlecase::TitlecaseOptions; + +#[diplomat::bridge] +pub mod ffi { + use crate::{ + errors::ffi::ICU4XError, locale::ffi::ICU4XLocale, provider::ffi::ICU4XDataProvider, + }; + use alloc::boxed::Box; + use diplomat_runtime::DiplomatWriteable; + use icu_casemap::titlecase::{LeadingAdjustment, TrailingCase}; + use icu_casemap::{CaseMapCloser, CaseMapper, TitlecaseMapper}; + use writeable::Writeable; + + #[diplomat::enum_convert(LeadingAdjustment, needs_wildcard)] + #[diplomat::rust_link(icu::casemap::titlecase::LeadingAdjustment, Enum)] + pub enum ICU4XLeadingAdjustment { + Auto, + None, + ToCased, + } + + #[diplomat::enum_convert(TrailingCase, needs_wildcard)] + #[diplomat::rust_link(icu::casemap::titlecase::TrailingCase, Enum)] + pub enum ICU4XTrailingCase { + Lower, + Unchanged, + } + + #[diplomat::rust_link(icu::casemap::titlecase::TitlecaseOptions, Struct)] + pub struct ICU4XTitlecaseOptionsV1 { + pub leading_adjustment: ICU4XLeadingAdjustment, + pub trailing_case: ICU4XTrailingCase, + } + + impl ICU4XTitlecaseOptionsV1 { + #[diplomat::rust_link(icu::casemap::titlecase::TitlecaseOptions::default, FnInStruct)] + pub fn default_options() -> ICU4XTitlecaseOptionsV1 { + // named default_options to avoid keyword clashes + Self { + leading_adjustment: ICU4XLeadingAdjustment::Auto, + trailing_case: ICU4XTrailingCase::Lower, + } + } + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::casemap::CaseMapper, Struct)] + pub struct ICU4XCaseMapper(pub CaseMapper); + + impl ICU4XCaseMapper { + /// Construct a new ICU4XCaseMapper instance + #[diplomat::rust_link(icu::casemap::CaseMapper::new, FnInStruct)] + pub fn create(provider: &ICU4XDataProvider) -> Result<Box<ICU4XCaseMapper>, ICU4XError> { + Ok(Box::new(ICU4XCaseMapper(call_constructor!( + CaseMapper::new [r => Ok(r)], + CaseMapper::try_new_with_any_provider, + CaseMapper::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Returns the full lowercase mapping of the given string + #[diplomat::rust_link(icu::casemap::CaseMapper::lowercase, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::lowercase_to_string, FnInStruct, hidden)] + pub fn lowercase( + &self, + s: &str, + locale: &ICU4XLocale, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.lowercase(s, &locale.0.id).write_to(write)?; + + Ok(()) + } + + /// Returns the full uppercase mapping of the given string + #[diplomat::rust_link(icu::casemap::CaseMapper::uppercase, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::uppercase_to_string, FnInStruct, hidden)] + pub fn uppercase( + &self, + s: &str, + locale: &ICU4XLocale, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.uppercase(s, &locale.0.id).write_to(write)?; + + Ok(()) + } + + /// Returns the full titlecase mapping of the given string, performing head adjustment without + /// loading additional data. + /// (if head adjustment is enabled in the options) + /// + /// The `v1` refers to the version of the options struct, which may change as we add more options + #[diplomat::rust_link( + icu::casemap::CaseMapper::titlecase_segment_with_only_case_data, + FnInStruct + )] + #[diplomat::rust_link( + icu::casemap::CaseMapper::titlecase_segment_with_only_case_data_to_string, + FnInStruct, + hidden + )] + pub fn titlecase_segment_with_only_case_data_v1( + &self, + s: &str, + locale: &ICU4XLocale, + options: ICU4XTitlecaseOptionsV1, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0 + .titlecase_segment_with_only_case_data(s, &locale.0.id, options.into()) + .write_to(write)?; + + Ok(()) + } + + /// Case-folds the characters in the given string + #[diplomat::rust_link(icu::casemap::CaseMapper::fold, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::fold_string, FnInStruct, hidden)] + pub fn fold(&self, s: &str, write: &mut DiplomatWriteable) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.fold(s).write_to(write)?; + + Ok(()) + } + /// Case-folds the characters in the given string + /// using Turkic (T) mappings for dotted/dotless I. + #[diplomat::rust_link(icu::casemap::CaseMapper::fold_turkic, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapper::fold_turkic_string, FnInStruct, hidden)] + pub fn fold_turkic( + &self, + s: &str, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0.fold_turkic(s).write_to(write)?; + + Ok(()) + } + + /// Adds all simple case mappings and the full case folding for `c` to `builder`. + /// Also adds special case closure mappings. + /// + /// In other words, this adds all characters that this casemaps to, as + /// well as all characters that may casemap to this one. + /// + /// Note that since ICU4XCodePointSetBuilder does not contain strings, this will + /// ignore string mappings. + /// + /// Identical to the similarly named method on `ICU4XCaseMapCloser`, use that if you + /// plan on using string case closure mappings too. + #[cfg(feature = "icu_properties")] + #[diplomat::rust_link(icu::casemap::CaseMapper::add_case_closure_to, FnInStruct)] + pub fn add_case_closure_to( + &self, + c: char, + builder: &mut crate::collections_sets::ffi::ICU4XCodePointSetBuilder, + ) { + self.0.add_case_closure_to(c, &mut builder.0) + } + + /// Returns the simple lowercase mapping of the given character. + /// + /// This function only implements simple and common mappings. + /// Full mappings, which can map one char to a string, are not included. + /// For full mappings, use `ICU4XCaseMapper::lowercase`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_lowercase, FnInStruct)] + pub fn simple_lowercase(&self, ch: char) -> char { + self.0.simple_lowercase(ch) + } + + /// Returns the simple uppercase mapping of the given character. + /// + /// This function only implements simple and common mappings. + /// Full mappings, which can map one char to a string, are not included. + /// For full mappings, use `ICU4XCaseMapper::uppercase`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_uppercase, FnInStruct)] + pub fn simple_uppercase(&self, ch: char) -> char { + self.0.simple_uppercase(ch) + } + + /// Returns the simple titlecase mapping of the given character. + /// + /// This function only implements simple and common mappings. + /// Full mappings, which can map one char to a string, are not included. + /// For full mappings, use `ICU4XCaseMapper::titlecase_segment`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_titlecase, FnInStruct)] + pub fn simple_titlecase(&self, ch: char) -> char { + self.0.simple_titlecase(ch) + } + + /// Returns the simple casefolding of the given character. + /// + /// This function only implements simple folding. + /// For full folding, use `ICU4XCaseMapper::fold`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_fold, FnInStruct)] + pub fn simple_fold(&self, ch: char) -> char { + self.0.simple_fold(ch) + } + /// Returns the simple casefolding of the given character in the Turkic locale + /// + /// This function only implements simple folding. + /// For full folding, use `ICU4XCaseMapper::fold_turkic`. + #[diplomat::rust_link(icu::casemap::CaseMapper::simple_fold_turkic, FnInStruct)] + pub fn simple_fold_turkic(&self, ch: char) -> char { + self.0.simple_fold_turkic(ch) + } + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::casemap::CaseMapCloser, Struct)] + pub struct ICU4XCaseMapCloser(pub CaseMapCloser<CaseMapper>); + + impl ICU4XCaseMapCloser { + /// Construct a new ICU4XCaseMapper instance + #[diplomat::rust_link(icu::casemap::CaseMapCloser::new, FnInStruct)] + #[diplomat::rust_link(icu::casemap::CaseMapCloser::new_with_mapper, FnInStruct, hidden)] + pub fn create(provider: &ICU4XDataProvider) -> Result<Box<ICU4XCaseMapCloser>, ICU4XError> { + Ok(Box::new(ICU4XCaseMapCloser(call_constructor!( + CaseMapCloser::new [r => Ok(r)], + CaseMapCloser::try_new_with_any_provider, + CaseMapCloser::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Adds all simple case mappings and the full case folding for `c` to `builder`. + /// Also adds special case closure mappings. + #[cfg(feature = "icu_properties")] + #[diplomat::rust_link(icu::casemap::CaseMapCloser::add_case_closure_to, FnInStruct)] + pub fn add_case_closure_to( + &self, + c: char, + builder: &mut crate::collections_sets::ffi::ICU4XCodePointSetBuilder, + ) { + self.0.add_case_closure_to(c, &mut builder.0) + } + + /// Finds all characters and strings which may casemap to `s` as their full case folding string + /// and adds them to the set. + /// + /// Returns true if the string was found + #[cfg(feature = "icu_properties")] + #[diplomat::rust_link(icu::casemap::CaseMapCloser::add_string_case_closure_to, FnInStruct)] + pub fn add_string_case_closure_to( + &self, + s: &str, + builder: &mut crate::collections_sets::ffi::ICU4XCodePointSetBuilder, + ) -> bool { + // #2520 + // In the future we should be able to make assumptions based on backend + let s = core::str::from_utf8(s.as_bytes()).unwrap_or(""); + self.0.add_string_case_closure_to(s, &mut builder.0) + } + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::casemap::TitlecaseMapper, Struct)] + pub struct ICU4XTitlecaseMapper(pub TitlecaseMapper<CaseMapper>); + + impl ICU4XTitlecaseMapper { + /// Construct a new `ICU4XTitlecaseMapper` instance + #[diplomat::rust_link(icu::casemap::TitlecaseMapper::new, FnInStruct)] + #[diplomat::rust_link(icu::casemap::TitlecaseMapper::new_with_mapper, FnInStruct, hidden)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XTitlecaseMapper>, ICU4XError> { + Ok(Box::new(ICU4XTitlecaseMapper(call_constructor!( + TitlecaseMapper::new [r => Ok(r)], + TitlecaseMapper::try_new_with_any_provider, + TitlecaseMapper::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Returns the full titlecase mapping of the given string + /// + /// The `v1` refers to the version of the options struct, which may change as we add more options + #[diplomat::rust_link(icu::casemap::TitlecaseMapper::titlecase_segment, FnInStruct)] + #[diplomat::rust_link( + icu::casemap::TitlecaseMapper::titlecase_segment_to_string, + FnInStruct, + hidden + )] + pub fn titlecase_segment_v1( + &self, + s: &str, + locale: &ICU4XLocale, + options: ICU4XTitlecaseOptionsV1, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + // #2520 + // In the future we should be able to make assumptions based on backend + core::str::from_utf8(s.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + self.0 + .titlecase_segment(s, &locale.0.id, options.into()) + .write_to(write)?; + + Ok(()) + } + } +} + +impl From<ffi::ICU4XTitlecaseOptionsV1> for TitlecaseOptions { + fn from(other: ffi::ICU4XTitlecaseOptionsV1) -> Self { + let mut ret = Self::default(); + + ret.leading_adjustment = other.leading_adjustment.into(); + ret.trailing_case = other.trailing_case.into(); + ret + } +} diff --git a/intl/icu_capi/src/collator.rs b/intl/icu_capi/src/collator.rs new file mode 100644 index 0000000000..bbf56786b7 --- /dev/null +++ b/intl/icu_capi/src/collator.rs @@ -0,0 +1,237 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_collator::{Collator, CollatorOptions}; + + use crate::{ + common::ffi::ICU4XOrdering, errors::ffi::ICU4XError, locale::ffi::ICU4XLocale, + provider::ffi::ICU4XDataProvider, + }; + + #[diplomat::opaque] + #[diplomat::rust_link(icu::collator::Collator, Struct)] + pub struct ICU4XCollator(pub Collator); + + #[diplomat::rust_link(icu::collator::CollatorOptions, Struct)] + #[diplomat::rust_link(icu::collator::CollatorOptions::new, FnInStruct, hidden)] + pub struct ICU4XCollatorOptionsV1 { + pub strength: ICU4XCollatorStrength, + pub alternate_handling: ICU4XCollatorAlternateHandling, + pub case_first: ICU4XCollatorCaseFirst, + pub max_variable: ICU4XCollatorMaxVariable, + pub case_level: ICU4XCollatorCaseLevel, + pub numeric: ICU4XCollatorNumeric, + pub backward_second_level: ICU4XCollatorBackwardSecondLevel, + } + + #[diplomat::rust_link(icu::collator::Strength, Enum)] + pub enum ICU4XCollatorStrength { + Auto = 0, + Primary = 1, + Secondary = 2, + Tertiary = 3, + Quaternary = 4, + Identical = 5, + } + + #[diplomat::rust_link(icu::collator::AlternateHandling, Enum)] + pub enum ICU4XCollatorAlternateHandling { + Auto = 0, + NonIgnorable = 1, + Shifted = 2, + } + + #[diplomat::rust_link(icu::collator::CaseFirst, Enum)] + pub enum ICU4XCollatorCaseFirst { + Auto = 0, + Off = 1, + LowerFirst = 2, + UpperFirst = 3, + } + + #[diplomat::rust_link(icu::collator::MaxVariable, Enum)] + pub enum ICU4XCollatorMaxVariable { + Auto = 0, + Space = 1, + Punctuation = 2, + Symbol = 3, + Currency = 4, + } + + #[diplomat::rust_link(icu::collator::CaseLevel, Enum)] + pub enum ICU4XCollatorCaseLevel { + Auto = 0, + Off = 1, + On = 2, + } + + #[diplomat::rust_link(icu::collator::Numeric, Enum)] + pub enum ICU4XCollatorNumeric { + Auto = 0, + Off = 1, + On = 2, + } + + #[diplomat::rust_link(icu::collator::BackwardSecondLevel, Enum)] + pub enum ICU4XCollatorBackwardSecondLevel { + Auto = 0, + Off = 1, + On = 2, + } + + impl ICU4XCollator { + /// Construct a new Collator instance. + #[diplomat::rust_link(icu::collator::Collator::try_new, FnInStruct)] + pub fn create_v1( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + options: ICU4XCollatorOptionsV1, + ) -> Result<Box<ICU4XCollator>, ICU4XError> { + let locale = locale.to_datalocale(); + let options = CollatorOptions::from(options); + + Ok(Box::new(ICU4XCollator(call_constructor!( + Collator::try_new, + Collator::try_new_with_any_provider, + Collator::try_new_with_buffer_provider, + provider, + &locale, + options, + )?))) + } + + /// Compare potentially ill-formed UTF-8 strings. + /// + /// Ill-formed input is compared + /// as if errors had been replaced with REPLACEMENT CHARACTERs according + /// to the WHATWG Encoding Standard. + #[diplomat::rust_link(icu::collator::Collator::compare_utf8, FnInStruct)] + #[diplomat::attr(dart, disable)] + pub fn compare(&self, left: &str, right: &str) -> ICU4XOrdering { + let left = left.as_bytes(); // #2520 + let right = right.as_bytes(); // #2520 + self.0.compare_utf8(left, right).into() + } + + /// Compare guaranteed well-formed UTF-8 strings. + /// + /// Note: In C++, passing ill-formed UTF-8 strings is undefined behavior + /// (and may be memory-unsafe to do so, too). + #[diplomat::rust_link(icu::collator::Collator::compare, FnInStruct)] + #[diplomat::attr(dart, rename = "compare")] + pub fn compare_valid_utf8(&self, left: &str, right: &str) -> ICU4XOrdering { + self.0.compare(left, right).into() + } + + /// Compare potentially ill-formed UTF-16 strings, with unpaired surrogates + /// compared as REPLACEMENT CHARACTER. + #[diplomat::rust_link(icu::collator::Collator::compare_utf16, FnInStruct)] + pub fn compare_utf16(&self, left: &[u16], right: &[u16]) -> ICU4XOrdering { + self.0.compare_utf16(left, right).into() + } + } +} + +use icu_collator::{ + AlternateHandling, BackwardSecondLevel, CaseFirst, CaseLevel, CollatorOptions, MaxVariable, + Numeric, Strength, +}; + +impl From<ffi::ICU4XCollatorStrength> for Option<Strength> { + fn from(strength: ffi::ICU4XCollatorStrength) -> Option<Strength> { + match strength { + ffi::ICU4XCollatorStrength::Auto => None, + ffi::ICU4XCollatorStrength::Primary => Some(Strength::Primary), + ffi::ICU4XCollatorStrength::Secondary => Some(Strength::Secondary), + ffi::ICU4XCollatorStrength::Tertiary => Some(Strength::Tertiary), + ffi::ICU4XCollatorStrength::Quaternary => Some(Strength::Quaternary), + ffi::ICU4XCollatorStrength::Identical => Some(Strength::Identical), + } + } +} + +impl From<ffi::ICU4XCollatorAlternateHandling> for Option<AlternateHandling> { + fn from(alternate_handling: ffi::ICU4XCollatorAlternateHandling) -> Option<AlternateHandling> { + match alternate_handling { + ffi::ICU4XCollatorAlternateHandling::Auto => None, + ffi::ICU4XCollatorAlternateHandling::NonIgnorable => { + Some(AlternateHandling::NonIgnorable) + } + ffi::ICU4XCollatorAlternateHandling::Shifted => Some(AlternateHandling::Shifted), + } + } +} + +impl From<ffi::ICU4XCollatorCaseFirst> for Option<CaseFirst> { + fn from(case_first: ffi::ICU4XCollatorCaseFirst) -> Option<CaseFirst> { + match case_first { + ffi::ICU4XCollatorCaseFirst::Auto => None, + ffi::ICU4XCollatorCaseFirst::Off => Some(CaseFirst::Off), + ffi::ICU4XCollatorCaseFirst::LowerFirst => Some(CaseFirst::LowerFirst), + ffi::ICU4XCollatorCaseFirst::UpperFirst => Some(CaseFirst::UpperFirst), + } + } +} + +impl From<ffi::ICU4XCollatorMaxVariable> for Option<MaxVariable> { + fn from(max_variable: ffi::ICU4XCollatorMaxVariable) -> Option<MaxVariable> { + match max_variable { + ffi::ICU4XCollatorMaxVariable::Auto => None, + ffi::ICU4XCollatorMaxVariable::Space => Some(MaxVariable::Space), + ffi::ICU4XCollatorMaxVariable::Punctuation => Some(MaxVariable::Punctuation), + ffi::ICU4XCollatorMaxVariable::Symbol => Some(MaxVariable::Symbol), + ffi::ICU4XCollatorMaxVariable::Currency => Some(MaxVariable::Currency), + } + } +} + +impl From<ffi::ICU4XCollatorCaseLevel> for Option<CaseLevel> { + fn from(case_level: ffi::ICU4XCollatorCaseLevel) -> Option<CaseLevel> { + match case_level { + ffi::ICU4XCollatorCaseLevel::Auto => None, + ffi::ICU4XCollatorCaseLevel::Off => Some(CaseLevel::Off), + ffi::ICU4XCollatorCaseLevel::On => Some(CaseLevel::On), + } + } +} + +impl From<ffi::ICU4XCollatorNumeric> for Option<Numeric> { + fn from(numeric: ffi::ICU4XCollatorNumeric) -> Option<Numeric> { + match numeric { + ffi::ICU4XCollatorNumeric::Auto => None, + ffi::ICU4XCollatorNumeric::Off => Some(Numeric::Off), + ffi::ICU4XCollatorNumeric::On => Some(Numeric::On), + } + } +} + +impl From<ffi::ICU4XCollatorBackwardSecondLevel> for Option<BackwardSecondLevel> { + fn from( + backward_second_level: ffi::ICU4XCollatorBackwardSecondLevel, + ) -> Option<BackwardSecondLevel> { + match backward_second_level { + ffi::ICU4XCollatorBackwardSecondLevel::Auto => None, + ffi::ICU4XCollatorBackwardSecondLevel::Off => Some(BackwardSecondLevel::Off), + ffi::ICU4XCollatorBackwardSecondLevel::On => Some(BackwardSecondLevel::On), + } + } +} + +impl From<ffi::ICU4XCollatorOptionsV1> for CollatorOptions { + fn from(options: ffi::ICU4XCollatorOptionsV1) -> CollatorOptions { + let mut result = CollatorOptions::new(); + result.strength = options.strength.into(); + result.alternate_handling = options.alternate_handling.into(); + result.case_first = options.case_first.into(); + result.max_variable = options.max_variable.into(); + result.case_level = options.case_level.into(); + result.numeric = options.numeric.into(); + result.backward_second_level = options.backward_second_level.into(); + + result + } +} diff --git a/intl/icu_capi/src/collections_sets.rs b/intl/icu_capi/src/collections_sets.rs new file mode 100644 index 0000000000..68ce1dfd1a --- /dev/null +++ b/intl/icu_capi/src/collections_sets.rs @@ -0,0 +1,223 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::properties_sets::ffi::ICU4XCodePointSetData; + use alloc::boxed::Box; + use core::mem; + use icu_collections::codepointinvlist::CodePointInversionListBuilder; + use icu_properties::sets::CodePointSetData; + + #[diplomat::opaque] + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder, + Struct + )] + pub struct ICU4XCodePointSetBuilder(pub CodePointInversionListBuilder); + + impl ICU4XCodePointSetBuilder { + /// Make a new set builder containing nothing + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::new, + FnInStruct + )] + pub fn create() -> Box<Self> { + Box::new(Self(CodePointInversionListBuilder::new())) + } + + /// Build this into a set + /// + /// This object is repopulated with an empty builder + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::build, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::sets::CodePointSetData::from_code_point_inversion_list, + FnInStruct, + hidden + )] + pub fn build(&mut self) -> Box<ICU4XCodePointSetData> { + let inner = mem::take(&mut self.0); + let built = inner.build(); + let set = CodePointSetData::from_code_point_inversion_list(built); + Box::new(ICU4XCodePointSetData(set)) + } + + /// Complements this set + /// + /// (Elements in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement, + FnInStruct + )] + pub fn complement(&mut self) { + self.0.complement() + } + + /// Returns whether this set is empty + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::is_empty, + FnInStruct + )] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Add a single character to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_char, + FnInStruct + )] + pub fn add_char(&mut self, ch: char) { + self.0.add_char(ch) + } + + /// Add a single u32 value to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_u32, + FnInStruct + )] + #[diplomat::attr(dart, disable)] + pub fn add_u32(&mut self, ch: u32) { + self.0.add_u32(ch) + } + + /// Add an inclusive range of characters to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_range, + FnInStruct + )] + pub fn add_inclusive_range(&mut self, start: char, end: char) { + self.0.add_range(&(start..=end)) + } + + /// Add an inclusive range of u32s to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_range_u32, + FnInStruct + )] + #[diplomat::attr(dart, disable)] + pub fn add_inclusive_range_u32(&mut self, start: u32, end: u32) { + self.0.add_range_u32(&(start..=end)) + } + + /// Add all elements that belong to the provided set to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::add_set, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::sets::CodePointSetData::as_code_point_inversion_list, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::properties::sets::CodePointSetData::to_code_point_inversion_list, + FnInStruct, + hidden + )] + pub fn add_set(&mut self, data: &ICU4XCodePointSetData) { + // This is a ZeroFrom and always cheap for a CPIL, may be expensive + // for other impls. In the future we can make this builder support multiple impls + // if we ever add them + let list = data.0.to_code_point_inversion_list(); + self.0.add_set(&list); + } + + /// Remove a single character to the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::remove_char, + FnInStruct + )] + pub fn remove_char(&mut self, ch: char) { + self.0.remove_char(ch) + } + + /// Remove an inclusive range of characters from the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::remove_range, + FnInStruct + )] + pub fn remove_inclusive_range(&mut self, start: char, end: char) { + self.0.remove_range(&(start..=end)) + } + + /// Remove all elements that belong to the provided set from the set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::remove_set, + FnInStruct + )] + pub fn remove_set(&mut self, data: &ICU4XCodePointSetData) { + // (see comment in add_set) + let list = data.0.to_code_point_inversion_list(); + self.0.remove_set(&list); + } + + /// Removes all elements from the set except a single character + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::retain_char, + FnInStruct + )] + pub fn retain_char(&mut self, ch: char) { + self.0.retain_char(ch) + } + + /// Removes all elements from the set except an inclusive range of characters f + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::retain_range, + FnInStruct + )] + pub fn retain_inclusive_range(&mut self, start: char, end: char) { + self.0.retain_range(&(start..=end)) + } + + /// Removes all elements from the set except all elements in the provided set + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::retain_set, + FnInStruct + )] + pub fn retain_set(&mut self, data: &ICU4XCodePointSetData) { + // (see comment in add_set) + let list = data.0.to_code_point_inversion_list(); + self.0.retain_set(&list); + } + + /// Complement a single character to the set + /// + /// (Characters which are in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement_char, + FnInStruct + )] + pub fn complement_char(&mut self, ch: char) { + self.0.complement_char(ch) + } + + /// Complement an inclusive range of characters from the set + /// + /// (Characters which are in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement_range, + FnInStruct + )] + pub fn complement_inclusive_range(&mut self, start: char, end: char) { + self.0.complement_range(&(start..=end)) + } + + /// Complement all elements that belong to the provided set from the set + /// + /// (Characters which are in this set are removed and vice versa) + #[diplomat::rust_link( + icu::collections::codepointinvlist::CodePointInversionListBuilder::complement_set, + FnInStruct + )] + pub fn complement_set(&mut self, data: &ICU4XCodePointSetData) { + // (see comment in add_set) + let list = data.0.to_code_point_inversion_list(); + self.0.complement_set(&list); + } + } +} diff --git a/intl/icu_capi/src/common.rs b/intl/icu_capi/src/common.rs new file mode 100644 index 0000000000..d11ca26044 --- /dev/null +++ b/intl/icu_capi/src/common.rs @@ -0,0 +1,16 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + + #[diplomat::enum_convert(core::cmp::Ordering)] + #[diplomat::rust_link(core::cmp::Ordering, Enum)] + pub enum ICU4XOrdering { + Less = -1, + Equal = 0, + Greater = 1, + } +} diff --git a/intl/icu_capi/src/data_struct.rs b/intl/icu_capi/src/data_struct.rs new file mode 100644 index 0000000000..60765cec24 --- /dev/null +++ b/intl/icu_capi/src/data_struct.rs @@ -0,0 +1,90 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[cfg(feature = "icu_decimal")] +use alloc::borrow::{Cow, ToOwned}; + +#[diplomat::bridge] +pub mod ffi { + + #[cfg(feature = "icu_decimal")] + use crate::errors::ffi::ICU4XError; + use alloc::boxed::Box; + use icu_provider::AnyPayload; + #[cfg(feature = "icu_decimal")] + use icu_provider::DataPayload; + + #[diplomat::opaque] + /// A generic data struct to be used by ICU4X + /// + /// This can be used to construct a StructDataProvider. + #[diplomat::attr(dart, disable)] + pub struct ICU4XDataStruct(#[allow(dead_code)] AnyPayload); + + impl ICU4XDataStruct { + /// Construct a new DecimalSymbolsV1 data struct. + /// + /// C++ users: All string arguments must be valid UTF8 + #[diplomat::rust_link(icu::decimal::provider::DecimalSymbolsV1, Struct)] + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "icu_decimal")] + pub fn create_decimal_symbols_v1( + plus_sign_prefix: &str, + plus_sign_suffix: &str, + minus_sign_prefix: &str, + minus_sign_suffix: &str, + decimal_separator: &str, + grouping_separator: &str, + primary_group_size: u8, + secondary_group_size: u8, + min_group_size: u8, + digits: &[char], + ) -> Result<Box<ICU4XDataStruct>, ICU4XError> { + use super::str_to_cow; + use icu_decimal::provider::{ + AffixesV1, DecimalSymbolsV1, DecimalSymbolsV1Marker, GroupingSizesV1, + }; + let digits = if digits.len() == 10 { + let mut new_digits = ['\0'; 10]; + new_digits.copy_from_slice(digits); + new_digits + } else { + return Err(ICU4XError::DataStructValidityError); + }; + let plus_sign_affixes = AffixesV1 { + prefix: str_to_cow(plus_sign_prefix), + suffix: str_to_cow(plus_sign_suffix), + }; + let minus_sign_affixes = AffixesV1 { + prefix: str_to_cow(minus_sign_prefix), + suffix: str_to_cow(minus_sign_suffix), + }; + let grouping_sizes = GroupingSizesV1 { + primary: primary_group_size, + secondary: secondary_group_size, + min_grouping: min_group_size, + }; + + let symbols = DecimalSymbolsV1 { + plus_sign_affixes, + minus_sign_affixes, + decimal_separator: str_to_cow(decimal_separator), + grouping_separator: str_to_cow(grouping_separator), + grouping_sizes, + digits, + }; + + let payload: DataPayload<DecimalSymbolsV1Marker> = DataPayload::from_owned(symbols); + Ok(Box::new(ICU4XDataStruct(payload.wrap_into_any_payload()))) + } + } +} +#[cfg(feature = "icu_decimal")] +fn str_to_cow(s: &str) -> Cow<'static, str> { + if s.is_empty() { + Cow::default() + } else { + Cow::from(s.to_owned()) + } +} diff --git a/intl/icu_capi/src/date.rs b/intl/icu_capi/src/date.rs new file mode 100644 index 0000000000..39fd03835c --- /dev/null +++ b/intl/icu_capi/src/date.rs @@ -0,0 +1,300 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::sync::Arc; + use core::fmt::Write; + use icu_calendar::types::IsoWeekday; + use icu_calendar::AnyCalendar; + use icu_calendar::{Date, Iso}; + use tinystr::TinyAsciiStr; + + use crate::calendar::ffi::ICU4XCalendar; + use crate::errors::ffi::ICU4XError; + + #[cfg(feature = "icu_calendar")] + use crate::week::ffi::ICU4XWeekCalculator; + + #[diplomat::enum_convert(IsoWeekday)] + pub enum ICU4XIsoWeekday { + Monday = 1, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, + } + #[diplomat::opaque] + #[diplomat::transparent_convert] + /// An ICU4X Date object capable of containing a ISO-8601 date + #[diplomat::rust_link(icu::calendar::Date, Struct)] + pub struct ICU4XIsoDate(pub Date<Iso>); + + impl ICU4XIsoDate { + /// Creates a new [`ICU4XIsoDate`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::Date::try_new_iso_date, FnInStruct)] + pub fn create(year: i32, month: u8, day: u8) -> Result<Box<ICU4XIsoDate>, ICU4XError> { + Ok(Box::new(ICU4XIsoDate(Date::try_new_iso_date( + year, month, day, + )?))) + } + + /// Creates a new [`ICU4XIsoDate`] representing January 1, 1970. + #[diplomat::rust_link(icu::calendar::Date::unix_epoch, FnInStruct)] + pub fn create_for_unix_epoch() -> Box<ICU4XIsoDate> { + Box::new(ICU4XIsoDate(Date::unix_epoch())) + } + + /// Convert this date to one in a different calendar + #[diplomat::rust_link(icu::calendar::Date::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box<ICU4XDate> { + Box::new(ICU4XDate(self.0.to_calendar(calendar.0.clone()))) + } + + #[diplomat::rust_link(icu::calendar::Date::to_any, FnInStruct)] + pub fn to_any(&self) -> Box<ICU4XDate> { + Box::new(ICU4XDate(self.0.to_any().wrap_calendar_in_arc())) + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result<crate::week::ffi::ICU4XWeekOf, ICU4XError> { + Ok(self.0.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month(&self) -> u32 { + self.0.month().ordinal + } + + /// Returns the year number for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year(&self) -> i32 { + self.0.year().number + } + + /// Returns if the year is a leap year for this date + #[diplomat::rust_link(icu::calendar::Date::is_in_leap_year, FnInStruct)] + pub fn is_in_leap_year(&self) -> bool { + self.0.is_in_leap_year() + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.days_in_year() + } + } + + #[diplomat::opaque] + #[diplomat::transparent_convert] + /// An ICU4X Date object capable of containing a date and time for any calendar. + #[diplomat::rust_link(icu::calendar::Date, Struct)] + pub struct ICU4XDate(pub Date<Arc<AnyCalendar>>); + + impl ICU4XDate { + /// Creates a new [`ICU4XDate`] representing the ISO date and time + /// given but in a given calendar + #[diplomat::rust_link(icu::calendar::Date::new_from_iso, FnInStruct)] + pub fn create_from_iso_in_calendar( + year: i32, + month: u8, + day: u8, + calendar: &ICU4XCalendar, + ) -> Result<Box<ICU4XDate>, ICU4XError> { + let cal = calendar.0.clone(); + Ok(Box::new(ICU4XDate( + Date::try_new_iso_date(year, month, day)?.to_calendar(cal), + ))) + } + + /// Creates a new [`ICU4XDate`] from the given codes, which are interpreted in the given calendar system + #[diplomat::rust_link(icu::calendar::Date::try_new_from_codes, FnInStruct)] + pub fn create_from_codes_in_calendar( + era_code: &str, + year: i32, + month_code: &str, + day: u8, + calendar: &ICU4XCalendar, + ) -> Result<Box<ICU4XDate>, ICU4XError> { + let era_code = era_code.as_bytes(); // #2520 + let month_code = month_code.as_bytes(); // #2520 + let era = TinyAsciiStr::from_bytes(era_code)?.into(); + let month = TinyAsciiStr::from_bytes(month_code)?.into(); + let cal = calendar.0.clone(); + Ok(Box::new(ICU4XDate(Date::try_new_from_codes( + era, year, month, day, cal, + )?))) + } + + /// Convert this date to one in a different calendar + #[diplomat::rust_link(icu::calendar::Date::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box<ICU4XDate> { + Box::new(ICU4XDate(self.0.to_calendar(calendar.0.clone()))) + } + + /// Converts this date to ISO + #[diplomat::rust_link(icu::calendar::Date::to_iso, FnInStruct)] + pub fn to_iso(&self) -> Box<ICU4XIsoDate> { + Box::new(ICU4XIsoDate(self.0.to_iso())) + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result<crate::week::ffi::ICU4XWeekOf, ICU4XError> { + Ok(self.0.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + /// + /// Note that for lunar calendars this may not lead to the same month + /// having the same ordinal month across years; use month_code if you care + /// about month identity. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn ordinal_month(&self) -> u32 { + self.0.month().ordinal + } + + /// Returns the month code for this date. Typically something + /// like "M01", "M02", but can be more complicated for lunar calendars. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month_code( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let code = self.0.month().code; + write.write_str(&code.0)?; + Ok(()) + } + + /// Returns the year number in the current era for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year_in_era(&self) -> i32 { + self.0.year().number + } + + /// Returns the era for this date, + #[diplomat::rust_link(icu::Date::year, FnInStruct)] + #[diplomat::rust_link(icu::types::Era, Struct, compact)] + pub fn era( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let era = self.0.year().era; + write.write_str(&era.0)?; + Ok(()) + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.days_in_year() + } + + /// Returns the [`ICU4XCalendar`] object backing this date + #[diplomat::rust_link(icu::calendar::Date::calendar, FnInStruct)] + #[diplomat::rust_link(icu::calendar::Date::calendar_wrapper, FnInStruct, hidden)] + pub fn calendar(&self) -> Box<ICU4XCalendar> { + Box::new(ICU4XCalendar(self.0.calendar_wrapper().clone())) + } + } +} diff --git a/intl/icu_capi/src/datetime.rs b/intl/icu_capi/src/datetime.rs new file mode 100644 index 0000000000..627b9c16a3 --- /dev/null +++ b/intl/icu_capi/src/datetime.rs @@ -0,0 +1,415 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use alloc::sync::Arc; + use core::convert::TryInto; + use core::fmt::Write; + + use icu_calendar::types::Time; + use icu_calendar::AnyCalendar; + use icu_calendar::{DateTime, Iso}; + use tinystr::TinyAsciiStr; + + use crate::calendar::ffi::ICU4XCalendar; + use crate::date::ffi::{ICU4XDate, ICU4XIsoDate, ICU4XIsoWeekday}; + use crate::errors::ffi::ICU4XError; + use crate::time::ffi::ICU4XTime; + + #[cfg(feature = "icu_calendar")] + use crate::week::ffi::ICU4XWeekCalculator; + + #[diplomat::opaque] + /// An ICU4X DateTime object capable of containing a ISO-8601 date and time. + #[diplomat::rust_link(icu::calendar::DateTime, Struct)] + pub struct ICU4XIsoDateTime(pub DateTime<Iso>); + + impl ICU4XIsoDateTime { + /// Creates a new [`ICU4XIsoDateTime`] from the specified date and time. + #[diplomat::rust_link(icu::calendar::DateTime::try_new_iso_datetime, FnInStruct)] + pub fn create( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + ) -> Result<Box<ICU4XIsoDateTime>, ICU4XError> { + let mut dt = DateTime::try_new_iso_datetime(year, month, day, hour, minute, second)?; + dt.time.nanosecond = nanosecond.try_into()?; + Ok(Box::new(ICU4XIsoDateTime(dt))) + } + + /// Creates a new [`ICU4XIsoDateTime`] from an [`ICU4XIsoDate`] and [`ICU4XTime`] object + #[diplomat::rust_link(icu::calendar::DateTime::new, FnInStruct)] + #[diplomat::attr(dart, rename = "create_from_date_and_time")] + pub fn crate_from_date_and_time( + date: &ICU4XIsoDate, + time: &ICU4XTime, + ) -> Box<ICU4XIsoDateTime> { + let dt = DateTime::new(date.0, time.0); + Box::new(ICU4XIsoDateTime(dt)) + } + + /// Construct from the minutes since the local unix epoch for this date (Jan 1 1970, 00:00) + #[diplomat::rust_link( + icu::calendar::DateTime::from_minutes_since_local_unix_epoch, + FnInStruct + )] + pub fn create_from_minutes_since_local_unix_epoch(minutes: i32) -> Box<ICU4XIsoDateTime> { + Box::new(ICU4XIsoDateTime( + DateTime::from_minutes_since_local_unix_epoch(minutes), + )) + } + + /// Gets the date contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::date, StructField)] + pub fn date(&self) -> Box<ICU4XIsoDate> { + Box::new(ICU4XIsoDate(self.0.date)) + } + + /// Gets the time contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::time, StructField)] + pub fn time(&self) -> Box<ICU4XTime> { + Box::new(ICU4XTime(self.0.time)) + } + + /// Converts this to an [`ICU4XDateTime`] capable of being mixed with dates of + /// other calendars + #[diplomat::rust_link(icu::calendar::DateTime::to_any, FnInStruct)] + #[diplomat::rust_link(icu::calendar::DateTime::new_from_iso, FnInStruct, hidden)] + pub fn to_any(&self) -> Box<ICU4XDateTime> { + Box::new(ICU4XDateTime(self.0.to_any().wrap_calendar_in_arc())) + } + + /// Gets the minutes since the local unix epoch for this date (Jan 1 1970, 00:00) + #[diplomat::rust_link(icu::calendar::DateTime::minutes_since_local_unix_epoch, FnInStruct)] + pub fn minutes_since_local_unix_epoch(&self) -> i32 { + self.0.minutes_since_local_unix_epoch() + } + + /// Convert this datetime to one in a different calendar + #[diplomat::rust_link(icu::calendar::DateTime::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box<ICU4XDateTime> { + Box::new(ICU4XDateTime(self.0.to_calendar(calendar.0.clone()))) + } + + /// Returns the hour in this time + #[diplomat::rust_link(icu::calendar::types::Time::hour, StructField)] + pub fn hour(&self) -> u8 { + self.0.time.hour.into() + } + /// Returns the minute in this time + #[diplomat::rust_link(icu::calendar::types::Time::minute, StructField)] + pub fn minute(&self) -> u8 { + self.0.time.minute.into() + } + /// Returns the second in this time + #[diplomat::rust_link(icu::calendar::types::Time::second, StructField)] + pub fn second(&self) -> u8 { + self.0.time.second.into() + } + /// Returns the nanosecond in this time + #[diplomat::rust_link(icu::calendar::types::Time::nanosecond, StructField)] + pub fn nanosecond(&self) -> u32 { + self.0.time.nanosecond.into() + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.date.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.date.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.date.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result<crate::week::ffi::ICU4XWeekOf, ICU4XError> { + Ok(self.0.date.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month(&self) -> u32 { + self.0.date.month().ordinal + } + + /// Returns the year number for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year(&self) -> i32 { + self.0.date.year().number + } + + /// Returns whether this date is in a leap year + #[diplomat::rust_link(icu::calendar::Date::is_in_leap_year, FnInStruct)] + pub fn is_in_leap_year(&self) -> bool { + self.0.date.is_in_leap_year() + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.date.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.date.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.date.days_in_year() + } + } + + #[diplomat::opaque] + /// An ICU4X DateTime object capable of containing a date and time for any calendar. + #[diplomat::rust_link(icu::calendar::DateTime, Struct)] + pub struct ICU4XDateTime(pub DateTime<Arc<AnyCalendar>>); + + impl ICU4XDateTime { + /// Creates a new [`ICU4XDateTime`] representing the ISO date and time + /// given but in a given calendar + #[diplomat::rust_link(icu::DateTime::new_from_iso, FnInStruct)] + #[allow(clippy::too_many_arguments)] + pub fn create_from_iso_in_calendar( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + calendar: &ICU4XCalendar, + ) -> Result<Box<ICU4XDateTime>, ICU4XError> { + let cal = calendar.0.clone(); + let mut dt = DateTime::try_new_iso_datetime(year, month, day, hour, minute, second)?; + dt.time.nanosecond = nanosecond.try_into()?; + Ok(Box::new(ICU4XDateTime(dt.to_calendar(cal)))) + } + /// Creates a new [`ICU4XDateTime`] from the given codes, which are interpreted in the given calendar system + #[diplomat::rust_link(icu::calendar::DateTime::try_new_from_codes, FnInStruct)] + #[allow(clippy::too_many_arguments)] + pub fn create_from_codes_in_calendar( + era_code: &str, + year: i32, + month_code: &str, + day: u8, + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + calendar: &ICU4XCalendar, + ) -> Result<Box<ICU4XDateTime>, ICU4XError> { + let era_code = era_code.as_bytes(); // #2520 + let month_code = month_code.as_bytes(); // #2520 + let era = TinyAsciiStr::from_bytes(era_code)?.into(); + let month = TinyAsciiStr::from_bytes(month_code)?.into(); + let cal = calendar.0.clone(); + let hour = hour.try_into()?; + let minute = minute.try_into()?; + let second = second.try_into()?; + let nanosecond = nanosecond.try_into()?; + let time = Time { + hour, + minute, + second, + nanosecond, + }; + Ok(Box::new(ICU4XDateTime(DateTime::try_new_from_codes( + era, year, month, day, time, cal, + )?))) + } + /// Creates a new [`ICU4XDateTime`] from an [`ICU4XDate`] and [`ICU4XTime`] object + #[diplomat::rust_link(icu::calendar::DateTime::new, FnInStruct)] + pub fn create_from_date_and_time(date: &ICU4XDate, time: &ICU4XTime) -> Box<ICU4XDateTime> { + let dt = DateTime::new(date.0.clone(), time.0); + Box::new(ICU4XDateTime(dt)) + } + + /// Gets a copy of the date contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::date, StructField)] + pub fn date(&self) -> Box<ICU4XDate> { + Box::new(ICU4XDate(self.0.date.clone())) + } + + /// Gets the time contained in this object + #[diplomat::rust_link(icu::calendar::DateTime::time, StructField)] + pub fn time(&self) -> Box<ICU4XTime> { + Box::new(ICU4XTime(self.0.time)) + } + + /// Converts this date to ISO + #[diplomat::rust_link(icu::calendar::DateTime::to_iso, FnInStruct)] + pub fn to_iso(&self) -> Box<ICU4XIsoDateTime> { + Box::new(ICU4XIsoDateTime(self.0.to_iso())) + } + + /// Convert this datetime to one in a different calendar + #[diplomat::rust_link(icu::calendar::DateTime::to_calendar, FnInStruct)] + pub fn to_calendar(&self, calendar: &ICU4XCalendar) -> Box<ICU4XDateTime> { + Box::new(ICU4XDateTime(self.0.to_calendar(calendar.0.clone()))) + } + + /// Returns the hour in this time + #[diplomat::rust_link(icu::calendar::types::Time::hour, StructField)] + pub fn hour(&self) -> u8 { + self.0.time.hour.into() + } + /// Returns the minute in this time + #[diplomat::rust_link(icu::calendar::types::Time::minute, StructField)] + pub fn minute(&self) -> u8 { + self.0.time.minute.into() + } + /// Returns the second in this time + #[diplomat::rust_link(icu::calendar::types::Time::second, StructField)] + pub fn second(&self) -> u8 { + self.0.time.second.into() + } + /// Returns the nanosecond in this time + #[diplomat::rust_link(icu::calendar::types::Time::nanosecond, StructField)] + pub fn nanosecond(&self) -> u32 { + self.0.time.nanosecond.into() + } + + /// Returns the 1-indexed day in the month for this date + #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)] + pub fn day_of_month(&self) -> u32 { + self.0.date.day_of_month().0 + } + + /// Returns the day in the week for this day + #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)] + pub fn day_of_week(&self) -> ICU4XIsoWeekday { + self.0.date.day_of_week().into() + } + + /// Returns the week number in this month, 1-indexed, based on what + /// is considered the first day of the week (often a locale preference). + /// + /// `first_weekday` can be obtained via `first_weekday()` on [`ICU4XWeekCalculator`] + #[diplomat::rust_link(icu::calendar::Date::week_of_month, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_month, + FnInStruct, + hidden + )] + pub fn week_of_month(&self, first_weekday: ICU4XIsoWeekday) -> u32 { + self.0.date.week_of_month(first_weekday.into()).0 + } + + /// Returns the week number in this year, using week data + #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::week_of_year, + FnInStruct, + hidden + )] + #[cfg(feature = "icu_calendar")] + pub fn week_of_year( + &self, + calculator: &ICU4XWeekCalculator, + ) -> Result<crate::week::ffi::ICU4XWeekOf, ICU4XError> { + Ok(self.0.date.week_of_year(&calculator.0)?.into()) + } + + /// Returns 1-indexed number of the month of this date in its year + /// + /// Note that for lunar calendars this may not lead to the same month + /// having the same ordinal month across years; use month_code if you care + /// about month identity. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn ordinal_month(&self) -> u32 { + self.0.date.month().ordinal + } + + /// Returns the month code for this date. Typically something + /// like "M01", "M02", but can be more complicated for lunar calendars. + #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)] + pub fn month_code( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let code = self.0.date.month().code; + write.write_str(&code.0)?; + Ok(()) + } + + /// Returns the year number in the current era for this date + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn year_in_era(&self) -> i32 { + self.0.date.year().number + } + + /// Returns the era for this date, + #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)] + pub fn era( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let era = self.0.date.year().era; + write.write_str(&era.0)?; + Ok(()) + } + + /// Returns the number of months in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)] + pub fn months_in_year(&self) -> u8 { + self.0.date.months_in_year() + } + + /// Returns the number of days in the month represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)] + pub fn days_in_month(&self) -> u8 { + self.0.date.days_in_month() + } + + /// Returns the number of days in the year represented by this date + #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)] + pub fn days_in_year(&self) -> u16 { + self.0.date.days_in_year() + } + + /// Returns the [`ICU4XCalendar`] object backing this date + #[diplomat::rust_link(icu::calendar::Date::calendar, FnInStruct)] + #[diplomat::rust_link(icu::calendar::Date::calendar_wrapper, FnInStruct, hidden)] + pub fn calendar(&self) -> Box<ICU4XCalendar> { + Box::new(ICU4XCalendar(self.0.date.calendar_wrapper().clone())) + } + } +} diff --git a/intl/icu_capi/src/datetime_formatter.rs b/intl/icu_capi/src/datetime_formatter.rs new file mode 100644 index 0000000000..c68b3381e9 --- /dev/null +++ b/intl/icu_capi/src/datetime_formatter.rs @@ -0,0 +1,358 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_calendar::{Date, DateTime, Gregorian}; + use icu_datetime::{ + options::length, DateFormatter, DateTimeFormatter, TimeFormatter, TypedDateFormatter, + TypedDateTimeFormatter, + }; + + use crate::{ + date::ffi::{ICU4XDate, ICU4XIsoDate}, + datetime::ffi::ICU4XDateTime, + datetime::ffi::ICU4XIsoDateTime, + errors::ffi::ICU4XError, + locale::ffi::ICU4XLocale, + provider::ffi::ICU4XDataProvider, + time::ffi::ICU4XTime, + }; + use writeable::Writeable; + + #[diplomat::opaque] + /// An ICU4X TimeFormatter object capable of formatting an [`ICU4XTime`] type (and others) as a string + #[diplomat::rust_link(icu::datetime::TimeFormatter, Struct)] + pub struct ICU4XTimeFormatter(pub TimeFormatter); + + #[diplomat::enum_convert(length::Time, needs_wildcard)] + #[diplomat::rust_link(icu::datetime::options::length::Time, Enum)] + pub enum ICU4XTimeLength { + Full, + Long, + Medium, + Short, + } + + impl ICU4XTimeFormatter { + /// Creates a new [`ICU4XTimeFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::TimeFormatter::try_new_with_length, FnInStruct)] + pub fn create_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + length: ICU4XTimeLength, + ) -> Result<Box<ICU4XTimeFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XTimeFormatter(call_constructor!( + TimeFormatter::try_new_with_length, + TimeFormatter::try_new_with_length_with_any_provider, + TimeFormatter::try_new_with_length_with_buffer_provider, + provider, + &locale, + length.into() + )?))) + } + + /// Formats a [`ICU4XTime`] to a string. + #[diplomat::rust_link(icu::datetime::TimeFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::TimeFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_time( + &self, + value: &ICU4XTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TimeFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::TimeFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_datetime( + &self, + value: &ICU4XDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TimeFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::TimeFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X TypedDateFormatter object capable of formatting a [`ICU4XIsoDateTime`] as a string, + /// using the Gregorian Calendar. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter, Struct)] + pub struct ICU4XGregorianDateFormatter(pub TypedDateFormatter<Gregorian>); + + #[diplomat::enum_convert(length::Date, needs_wildcard)] + #[diplomat::rust_link(icu::datetime::options::length::Date, Enum)] + pub enum ICU4XDateLength { + Full, + Long, + Medium, + Short, + } + + impl ICU4XGregorianDateFormatter { + /// Creates a new [`ICU4XGregorianDateFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter::try_new_with_length, FnInStruct)] + pub fn create_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + length: ICU4XDateLength, + ) -> Result<Box<ICU4XGregorianDateFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XGregorianDateFormatter(call_constructor!( + TypedDateFormatter::try_new_with_length, + TypedDateFormatter::try_new_with_length_with_any_provider, + TypedDateFormatter::try_new_with_length_with_buffer_provider, + provider, + &locale, + length.into() + )?))) + } + + /// Formats a [`ICU4XIsoDate`] to a string. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::TypedDateFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_date( + &self, + value: &ICU4XIsoDate, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let greg = Date::new_from_iso(value.0, Gregorian); + self.0.format(&greg).write_to(write)?; + Ok(()) + } + /// Formats a [`ICU4XIsoDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TypedDateFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::TypedDateFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let greg = DateTime::new_from_iso(value.0, Gregorian); + self.0.format(&greg).write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X TypedDateTimeFormatter object capable of formatting a [`ICU4XIsoDateTime`] as a string, + /// using the Gregorian Calendar. + #[diplomat::rust_link(icu::datetime::TypedDateTimeFormatter, Struct)] + pub struct ICU4XGregorianDateTimeFormatter(pub TypedDateTimeFormatter<Gregorian>); + + impl ICU4XGregorianDateTimeFormatter { + /// Creates a new [`ICU4XGregorianDateFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::TypedDateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + ) -> Result<Box<ICU4XGregorianDateTimeFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + let options = length::Bag::from_date_time_style(date_length.into(), time_length.into()); + + Ok(Box::new(ICU4XGregorianDateTimeFormatter( + call_constructor!( + TypedDateTimeFormatter::try_new, + TypedDateTimeFormatter::try_new_with_any_provider, + TypedDateTimeFormatter::try_new_with_buffer_provider, + provider, + &locale, + options.into() + )?, + ))) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::TypedDateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::TypedDateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let greg = DateTime::new_from_iso(value.0, Gregorian); + self.0.format(&greg).write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X DateFormatter object capable of formatting a [`ICU4XDate`] as a string, + /// using some calendar specified at runtime in the locale. + #[diplomat::rust_link(icu::datetime::DateFormatter, Struct)] + pub struct ICU4XDateFormatter(pub DateFormatter); + + impl ICU4XDateFormatter { + /// Creates a new [`ICU4XDateFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::DateFormatter::try_new_with_length, FnInStruct)] + pub fn create_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + ) -> Result<Box<ICU4XDateFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XDateFormatter(call_constructor!( + DateFormatter::try_new_with_length, + DateFormatter::try_new_with_length_with_any_provider, + DateFormatter::try_new_with_length_with_buffer_provider, + provider, + &locale, + date_length.into() + )?))) + } + + /// Formats a [`ICU4XDate`] to a string. + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_date( + &self, + value: &ICU4XDate, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDate`] to a string. + /// + /// Will convert to this formatter's calendar first + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_iso_date( + &self, + value: &ICU4XIsoDate, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let any = value.0.to_any(); + self.0.format(&any)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_datetime( + &self, + value: &ICU4XDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + /// + /// Will convert to this formatter's calendar first + #[diplomat::rust_link(icu::datetime::DateFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::datetime::DateFormatter::format_to_string, FnInStruct, hidden)] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let any = value.0.to_any(); + self.0.format(&any)?.write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An ICU4X DateFormatter object capable of formatting a [`ICU4XDateTime`] as a string, + /// using some calendar specified at runtime in the locale. + #[diplomat::rust_link(icu::datetime::DateTimeFormatter, Struct)] + pub struct ICU4XDateTimeFormatter(pub DateTimeFormatter); + + impl ICU4XDateTimeFormatter { + /// Creates a new [`ICU4XDateTimeFormatter`] from locale data. + #[diplomat::rust_link(icu::datetime::DateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + ) -> Result<Box<ICU4XDateTimeFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + let options = length::Bag::from_date_time_style(date_length.into(), time_length.into()); + + Ok(Box::new(ICU4XDateTimeFormatter(call_constructor!( + DateTimeFormatter::try_new, + DateTimeFormatter::try_new_with_any_provider, + DateTimeFormatter::try_new_with_buffer_provider, + provider, + &locale, + options.into() + )?))) + } + + /// Formats a [`ICU4XDateTime`] to a string. + #[diplomat::rust_link(icu::datetime::DateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::DateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_datetime( + &self, + value: &ICU4XDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDateTime`] to a string. + /// + /// Will convert to this formatter's calendar first + #[diplomat::rust_link(icu::datetime::DateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::DateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime( + &self, + value: &ICU4XIsoDateTime, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let any = value.0.to_any(); + self.0.format(&any)?.write_to(write)?; + Ok(()) + } + } +} diff --git a/intl/icu_capi/src/decimal.rs b/intl/icu_capi/src/decimal.rs new file mode 100644 index 0000000000..290f1c5a63 --- /dev/null +++ b/intl/icu_capi/src/decimal.rs @@ -0,0 +1,109 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_decimal::{ + options::{FixedDecimalFormatterOptions, GroupingStrategy}, + provider::DecimalSymbolsV1Marker, + FixedDecimalFormatter, + }; + use icu_provider_adapters::any_payload::AnyPayloadProvider; + use writeable::Writeable; + + use crate::{ + data_struct::ffi::ICU4XDataStruct, errors::ffi::ICU4XError, + fixed_decimal::ffi::ICU4XFixedDecimal, locale::ffi::ICU4XLocale, + provider::ffi::ICU4XDataProvider, + }; + + #[diplomat::opaque] + /// An ICU4X Fixed Decimal Format object, capable of formatting a [`ICU4XFixedDecimal`] as a string. + #[diplomat::rust_link(icu::decimal::FixedDecimalFormatter, Struct)] + pub struct ICU4XFixedDecimalFormatter(pub FixedDecimalFormatter); + + #[diplomat::rust_link(icu::decimal::options::GroupingStrategy, Enum)] + pub enum ICU4XFixedDecimalGroupingStrategy { + Auto, + Never, + Always, + Min2, + } + + impl ICU4XFixedDecimalFormatter { + /// Creates a new [`ICU4XFixedDecimalFormatter`] from locale data. + #[diplomat::rust_link(icu::decimal::FixedDecimalFormatter::try_new, FnInStruct)] + pub fn create_with_grouping_strategy( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + grouping_strategy: ICU4XFixedDecimalGroupingStrategy, + ) -> Result<Box<ICU4XFixedDecimalFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + let grouping_strategy = match grouping_strategy { + ICU4XFixedDecimalGroupingStrategy::Auto => GroupingStrategy::Auto, + ICU4XFixedDecimalGroupingStrategy::Never => GroupingStrategy::Never, + ICU4XFixedDecimalGroupingStrategy::Always => GroupingStrategy::Always, + ICU4XFixedDecimalGroupingStrategy::Min2 => GroupingStrategy::Min2, + }; + let mut options = FixedDecimalFormatterOptions::default(); + options.grouping_strategy = grouping_strategy; + Ok(Box::new(ICU4XFixedDecimalFormatter(call_constructor!( + FixedDecimalFormatter::try_new, + FixedDecimalFormatter::try_new_with_any_provider, + FixedDecimalFormatter::try_new_with_buffer_provider, + provider, + &locale, + options, + )?))) + } + + /// Creates a new [`ICU4XFixedDecimalFormatter`] from preconstructed locale data in the form of an [`ICU4XDataStruct`] + /// constructed from `ICU4XDataStruct::create_decimal_symbols()`. + /// + /// The contents of the data struct will be consumed: if you wish to use the struct again it will have to be reconstructed. + /// Passing a consumed struct to this method will return an error. + #[diplomat::attr(dart, disable)] + pub fn create_with_decimal_symbols_v1( + data_struct: &ICU4XDataStruct, + grouping_strategy: ICU4XFixedDecimalGroupingStrategy, + ) -> Result<Box<ICU4XFixedDecimalFormatter>, ICU4XError> { + let grouping_strategy = match grouping_strategy { + ICU4XFixedDecimalGroupingStrategy::Auto => GroupingStrategy::Auto, + ICU4XFixedDecimalGroupingStrategy::Never => GroupingStrategy::Never, + ICU4XFixedDecimalGroupingStrategy::Always => GroupingStrategy::Always, + ICU4XFixedDecimalGroupingStrategy::Min2 => GroupingStrategy::Min2, + }; + let mut options = FixedDecimalFormatterOptions::default(); + options.grouping_strategy = grouping_strategy; + Ok(Box::new(ICU4XFixedDecimalFormatter( + FixedDecimalFormatter::try_new_with_any_provider( + &AnyPayloadProvider::from_any_payload::<DecimalSymbolsV1Marker>( + // Note: This clone is free, since cloning AnyPayload is free. + data_struct.0.clone(), + ), + &Default::default(), + options, + )?, + ))) + } + + /// Formats a [`ICU4XFixedDecimal`] to a string. + #[diplomat::rust_link(icu::decimal::FixedDecimalFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::decimal::FixedDecimalFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format( + &self, + value: &ICU4XFixedDecimal, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + } +} diff --git a/intl/icu_capi/src/displaynames.rs b/intl/icu_capi/src/displaynames.rs new file mode 100644 index 0000000000..eec14ed839 --- /dev/null +++ b/intl/icu_capi/src/displaynames.rs @@ -0,0 +1,156 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::locale::ffi::ICU4XLocale; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use diplomat_runtime::DiplomatWriteable; + #[allow(unused_imports)] // feature-specific + use icu_displaynames::{DisplayNamesOptions, Fallback, LanguageDisplay}; + use icu_displaynames::{LocaleDisplayNamesFormatter, RegionDisplayNames}; + use icu_locid::subtags::Region; + use writeable::Writeable; + + // FFI version of `LocaleDisplayNamesFormatter`. + #[diplomat::opaque] + #[diplomat::rust_link(icu::displaynames::LocaleDisplayNamesFormatter, Struct)] + pub struct ICU4XLocaleDisplayNamesFormatter(pub LocaleDisplayNamesFormatter); + + // FFI version of `RegionDisplayNames`. + #[diplomat::opaque] + #[diplomat::rust_link(icu::displaynames::RegionDisplayNames, Struct)] + pub struct ICU4XRegionDisplayNames(pub RegionDisplayNames); + + // FFI version of `DisplayNamesOptions`. + #[diplomat::rust_link(icu::displaynames::options::DisplayNamesOptions, Struct)] + pub struct ICU4XDisplayNamesOptionsV1 { + /// The optional formatting style to use for display name. + pub style: ICU4XDisplayNamesStyle, + /// The fallback return when the system does not have the + /// requested display name, defaults to "code". + pub fallback: ICU4XDisplayNamesFallback, + /// The language display kind, defaults to "dialect". + pub language_display: ICU4XLanguageDisplay, + } + + // FFI version of `Style` option for the display names. + #[diplomat::rust_link(icu::displaynames::options::Style, Enum)] + pub enum ICU4XDisplayNamesStyle { + Auto, + Narrow, + Short, + Long, + Menu, + } + + // FFI version of `Fallback` option for the display names. + #[diplomat::rust_link(icu::displaynames::options::Fallback, Enum)] + #[diplomat::enum_convert(Fallback, needs_wildcard)] + pub enum ICU4XDisplayNamesFallback { + Code, + None, + } + + // FFI version of `LanguageDisplay`. + #[diplomat::rust_link(icu::displaynames::options::LanguageDisplay, Enum)] + #[diplomat::enum_convert(LanguageDisplay, needs_wildcard)] + pub enum ICU4XLanguageDisplay { + Dialect, + Standard, + } + + impl ICU4XLocaleDisplayNamesFormatter { + /// Creates a new `LocaleDisplayNamesFormatter` from locale data and an options bag. + #[diplomat::rust_link(icu::displaynames::LocaleDisplayNamesFormatter::try_new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + options: ICU4XDisplayNamesOptionsV1, + ) -> Result<Box<ICU4XLocaleDisplayNamesFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + let options = DisplayNamesOptions::from(options); + + Ok(Box::new(ICU4XLocaleDisplayNamesFormatter( + call_constructor!( + LocaleDisplayNamesFormatter::try_new, + LocaleDisplayNamesFormatter::try_new_with_any_provider, + LocaleDisplayNamesFormatter::try_new_with_buffer_provider, + provider, + &locale, + options, + )?, + ))) + } + + /// Returns the locale-specific display name of a locale. + #[diplomat::rust_link(icu::displaynames::LocaleDisplayNamesFormatter::of, FnInStruct)] + pub fn of( + &self, + locale: &ICU4XLocale, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.of(&locale.0).write_to(write)?; + Ok(()) + } + } + + impl ICU4XRegionDisplayNames { + /// Creates a new `RegionDisplayNames` from locale data and an options bag. + #[diplomat::rust_link(icu::displaynames::RegionDisplayNames::try_new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XRegionDisplayNames>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XRegionDisplayNames(call_constructor!( + RegionDisplayNames::try_new, + RegionDisplayNames::try_new_with_any_provider, + RegionDisplayNames::try_new_with_buffer_provider, + provider, + &locale, + Default::default() + )?))) + } + + /// Returns the locale specific display name of a region. + /// Note that the funtion returns an empty string in case the display name for a given + /// region code is not found. + #[diplomat::rust_link(icu::displaynames::RegionDisplayNames::of, FnInStruct)] + pub fn of(&self, region: &str, write: &mut DiplomatWriteable) -> Result<(), ICU4XError> { + self.0 + .of(region.parse::<Region>()?) + .unwrap_or("") + .write_to(write)?; + Ok(()) + } + } +} + +#[allow(unused_imports)] // feature-specific +use icu_displaynames::{DisplayNamesOptions, Fallback, LanguageDisplay, Style}; + +impl From<ffi::ICU4XDisplayNamesStyle> for Option<Style> { + fn from(style: ffi::ICU4XDisplayNamesStyle) -> Option<Style> { + match style { + ffi::ICU4XDisplayNamesStyle::Auto => None, + ffi::ICU4XDisplayNamesStyle::Narrow => Some(Style::Narrow), + ffi::ICU4XDisplayNamesStyle::Short => Some(Style::Short), + ffi::ICU4XDisplayNamesStyle::Long => Some(Style::Long), + ffi::ICU4XDisplayNamesStyle::Menu => Some(Style::Menu), + } + } +} + +impl From<ffi::ICU4XDisplayNamesOptionsV1> for DisplayNamesOptions { + fn from(other: ffi::ICU4XDisplayNamesOptionsV1) -> DisplayNamesOptions { + let mut options = DisplayNamesOptions::default(); + options.style = other.style.into(); + options.fallback = other.fallback.into(); + options.language_display = other.language_display.into(); + options + } +} diff --git a/intl/icu_capi/src/errors.rs b/intl/icu_capi/src/errors.rs new file mode 100644 index 0000000000..bf17a650f3 --- /dev/null +++ b/intl/icu_capi/src/errors.rs @@ -0,0 +1,407 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use self::ffi::ICU4XError; +use core::fmt; +#[cfg(feature = "icu_decimal")] +use fixed_decimal::FixedDecimalError; +#[cfg(any( + feature = "icu_datetime", + feature = "icu_timezone", + feature = "icu_calendar" +))] +use icu_calendar::CalendarError; +#[cfg(feature = "icu_collator")] +use icu_collator::CollatorError; +#[cfg(feature = "icu_datetime")] +use icu_datetime::DateTimeError; +#[cfg(any(feature = "icu_decimal", feature = "icu_datetime"))] +use icu_decimal::DecimalError; +#[cfg(feature = "icu_list")] +use icu_list::ListError; +use icu_locid::ParserError; +#[cfg(feature = "icu_locid_transform")] +use icu_locid_transform::LocaleTransformError; +#[cfg(feature = "icu_normalizer")] +use icu_normalizer::NormalizerError; +#[cfg(any(feature = "icu_plurals", feature = "icu_datetime"))] +use icu_plurals::PluralsError; +#[cfg(feature = "icu_properties")] +use icu_properties::PropertiesError; +use icu_provider::{DataError, DataErrorKind}; +#[cfg(feature = "icu_segmenter")] +use icu_segmenter::SegmenterError; +#[cfg(any(feature = "icu_timezone", feature = "icu_datetime"))] +use icu_timezone::TimeZoneError; +use tinystr::TinyStrError; + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[repr(C)] + /// A common enum for errors that ICU4X may return, organized by API + /// + /// The error names are stable and can be checked against as strings in the JS API + #[diplomat::rust_link(fixed_decimal::FixedDecimalError, Enum, compact)] + #[diplomat::rust_link(icu::calendar::CalendarError, Enum, compact)] + #[diplomat::rust_link(icu::collator::CollatorError, Enum, compact)] + #[diplomat::rust_link(icu::datetime::DateTimeError, Enum, compact)] + #[diplomat::rust_link(icu::decimal::DecimalError, Enum, compact)] + #[diplomat::rust_link(icu::list::ListError, Enum, compact)] + #[diplomat::rust_link(icu::locid::ParserError, Enum, compact)] + #[diplomat::rust_link(icu::locid_transform::LocaleTransformError, Enum, compact)] + #[diplomat::rust_link(icu::normalizer::NormalizerError, Enum, compact)] + #[diplomat::rust_link(icu::plurals::PluralsError, Enum, compact)] + #[diplomat::rust_link(icu::properties::PropertiesError, Enum, compact)] + #[diplomat::rust_link(icu::provider::DataError, Struct, compact)] + #[diplomat::rust_link(icu::provider::DataErrorKind, Enum, compact)] + #[diplomat::rust_link(icu::segmenter::SegmenterError, Enum, compact)] + #[diplomat::rust_link(icu::timezone::TimeZoneError, Enum, compact)] + pub enum ICU4XError { + // general errors + /// The error is not currently categorized as ICU4XError. + /// Please file a bug + UnknownError = 0x00, + /// An error arising from writing to a string + /// Typically found when not enough space is allocated + /// Most APIs that return a string may return this error + WriteableError = 0x01, + // Some input was out of bounds + OutOfBoundsError = 0x02, + + // general data errors + // See DataError + DataMissingDataKeyError = 0x1_00, + DataMissingVariantError = 0x1_01, + DataMissingLocaleError = 0x1_02, + DataNeedsVariantError = 0x1_03, + DataNeedsLocaleError = 0x1_04, + DataExtraneousLocaleError = 0x1_05, + DataFilteredResourceError = 0x1_06, + DataMismatchedTypeError = 0x1_07, + DataMissingPayloadError = 0x1_08, + DataInvalidStateError = 0x1_09, + DataCustomError = 0x1_0A, + DataIoError = 0x1_0B, + DataUnavailableBufferFormatError = 0x1_0C, + DataMismatchedAnyBufferError = 0x1_0D, + + // locale errors + /// The subtag being requested was not set + LocaleUndefinedSubtagError = 0x2_00, + /// The locale or subtag string failed to parse + LocaleParserLanguageError = 0x2_01, + LocaleParserSubtagError = 0x2_02, + LocaleParserExtensionError = 0x2_03, + + // data struct errors + /// Attempted to construct an invalid data struct + DataStructValidityError = 0x3_00, + + // property errors + PropertyUnknownScriptIdError = 0x4_00, + PropertyUnknownGeneralCategoryGroupError = 0x4_01, + PropertyUnexpectedPropertyNameError = 0x4_02, + + // fixed_decimal errors + FixedDecimalLimitError = 0x5_00, + FixedDecimalSyntaxError = 0x5_01, + + // plural errors + PluralsParserError = 0x6_00, + + // datetime errors + CalendarParseError = 0x7_00, + CalendarOverflowError = 0x7_01, + CalendarUnderflowError = 0x7_02, + CalendarOutOfRangeError = 0x7_03, + CalendarUnknownEraError = 0x7_04, + CalendarUnknownMonthCodeError = 0x7_05, + CalendarMissingInputError = 0x7_06, + CalendarUnknownKindError = 0x7_07, + CalendarMissingError = 0x7_08, + + // datetime format errors + DateTimePatternError = 0x8_00, + DateTimeMissingInputFieldError = 0x8_01, + DateTimeSkeletonError = 0x8_02, + DateTimeUnsupportedFieldError = 0x8_03, + DateTimeUnsupportedOptionsError = 0x8_04, + DateTimeMissingWeekdaySymbolError = 0x8_05, + DateTimeMissingMonthSymbolError = 0x8_06, + DateTimeFixedDecimalError = 0x8_07, + DateTimeMismatchedCalendarError = 0x8_08, + + // tinystr errors + TinyStrTooLargeError = 0x9_00, + TinyStrContainsNullError = 0x9_01, + TinyStrNonAsciiError = 0x9_02, + + // timezone errors + TimeZoneOffsetOutOfBoundsError = 0xA_00, + TimeZoneInvalidOffsetError = 0xA_01, + TimeZoneMissingInputError = 0xA_02, + TimeZoneInvalidIdError = 0xA_03, + + // normalizer errors + NormalizerFutureExtensionError = 0xB_00, + NormalizerValidationError = 0xB_01, + } +} + +impl ICU4XError { + #[cfg(feature = "logging")] + #[inline] + pub(crate) fn log_original<T: core::fmt::Display + ?Sized>(self, e: &T) -> Self { + use core::any; + log::warn!( + "Returning ICU4XError::{:?} based on original {}: {}", + self, + any::type_name::<T>(), + e + ); + self + } + + #[cfg(not(feature = "logging"))] + #[inline] + pub(crate) fn log_original<T: core::fmt::Display + ?Sized>(self, _e: &T) -> Self { + self + } +} + +impl From<fmt::Error> for ICU4XError { + fn from(e: fmt::Error) -> Self { + ICU4XError::WriteableError.log_original(&e) + } +} + +impl From<DataError> for ICU4XError { + fn from(e: DataError) -> Self { + match e.kind { + DataErrorKind::MissingDataKey => ICU4XError::DataMissingDataKeyError, + DataErrorKind::MissingLocale => ICU4XError::DataMissingLocaleError, + DataErrorKind::NeedsLocale => ICU4XError::DataNeedsLocaleError, + DataErrorKind::ExtraneousLocale => ICU4XError::DataExtraneousLocaleError, + DataErrorKind::FilteredResource => ICU4XError::DataFilteredResourceError, + DataErrorKind::MismatchedType(..) => ICU4XError::DataMismatchedTypeError, + DataErrorKind::MissingPayload => ICU4XError::DataMissingPayloadError, + DataErrorKind::InvalidState => ICU4XError::DataInvalidStateError, + DataErrorKind::Custom => ICU4XError::DataCustomError, + #[cfg(all( + feature = "provider_fs", + not(any(target_arch = "wasm32", target_os = "none")) + ))] + DataErrorKind::Io(..) => ICU4XError::DataIoError, + // datagen only + // DataErrorKind::MissingSourceData(..) => .., + DataErrorKind::UnavailableBufferFormat(..) => { + ICU4XError::DataUnavailableBufferFormatError + } + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_collator")] +impl From<CollatorError> for ICU4XError { + fn from(e: CollatorError) -> Self { + match e { + CollatorError::NotFound => ICU4XError::DataMissingPayloadError, + CollatorError::MalformedData => ICU4XError::DataInvalidStateError, + CollatorError::Data(_) => ICU4XError::DataIoError, + _ => ICU4XError::DataIoError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_properties")] +impl From<PropertiesError> for ICU4XError { + fn from(e: PropertiesError) -> Self { + match e { + PropertiesError::PropDataLoad(e) => e.into(), + PropertiesError::UnknownScriptId(..) => ICU4XError::PropertyUnknownScriptIdError, + PropertiesError::UnknownGeneralCategoryGroup(..) => { + ICU4XError::PropertyUnknownGeneralCategoryGroupError + } + PropertiesError::UnexpectedPropertyName => { + ICU4XError::PropertyUnexpectedPropertyNameError + } + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(any( + feature = "icu_datetime", + feature = "icu_timezone", + feature = "icu_calendar" +))] +impl From<CalendarError> for ICU4XError { + fn from(e: CalendarError) -> Self { + match e { + CalendarError::Parse => ICU4XError::CalendarParseError, + CalendarError::Overflow { field: _, max: _ } => ICU4XError::CalendarOverflowError, + CalendarError::Underflow { field: _, min: _ } => ICU4XError::CalendarUnderflowError, + CalendarError::OutOfRange => ICU4XError::CalendarOutOfRangeError, + CalendarError::UnknownEra(..) => ICU4XError::CalendarUnknownEraError, + CalendarError::UnknownMonthCode(..) => ICU4XError::CalendarUnknownMonthCodeError, + CalendarError::MissingInput(_) => ICU4XError::CalendarMissingInputError, + CalendarError::UnknownAnyCalendarKind(_) => ICU4XError::CalendarUnknownKindError, + CalendarError::MissingCalendar => ICU4XError::CalendarMissingError, + CalendarError::Data(e) => e.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_datetime")] +impl From<DateTimeError> for ICU4XError { + fn from(e: DateTimeError) -> Self { + match e { + DateTimeError::Pattern(_) => ICU4XError::DateTimePatternError, + DateTimeError::Format(err) => err.into(), + DateTimeError::Data(err) => err.into(), + DateTimeError::MissingInputField(_) => ICU4XError::DateTimeMissingInputFieldError, + // TODO(#1324): Add back skeleton errors + // DateTimeFormatterError::Skeleton(_) => ICU4XError::DateTimeFormatSkeletonError, + DateTimeError::UnsupportedField(_) => ICU4XError::DateTimeUnsupportedFieldError, + DateTimeError::UnsupportedOptions => ICU4XError::DateTimeUnsupportedOptionsError, + DateTimeError::PluralRules(err) => err.into(), + DateTimeError::DateTimeInput(err) => err.into(), + DateTimeError::MissingWeekdaySymbol(_) => ICU4XError::DateTimeMissingWeekdaySymbolError, + DateTimeError::MissingMonthSymbol(_) => ICU4XError::DateTimeMissingMonthSymbolError, + DateTimeError::FixedDecimal => ICU4XError::DateTimeFixedDecimalError, + DateTimeError::FixedDecimalFormatter(err) => err.into(), + DateTimeError::MismatchedAnyCalendar(_, _) => { + ICU4XError::DateTimeMismatchedCalendarError + } + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_decimal")] +impl From<FixedDecimalError> for ICU4XError { + fn from(e: FixedDecimalError) -> Self { + match e { + FixedDecimalError::Limit => ICU4XError::FixedDecimalLimitError, + FixedDecimalError::Syntax => ICU4XError::FixedDecimalSyntaxError, + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(any(feature = "icu_plurals", feature = "icu_datetime"))] +impl From<PluralsError> for ICU4XError { + fn from(e: PluralsError) -> Self { + match e { + PluralsError::Data(e) => e.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(any(feature = "icu_decimal", feature = "icu_datetime"))] +impl From<DecimalError> for ICU4XError { + fn from(e: DecimalError) -> Self { + match e { + DecimalError::Data(e) => e.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_locid_transform")] +impl From<LocaleTransformError> for ICU4XError { + fn from(e: LocaleTransformError) -> Self { + match e { + LocaleTransformError::Data(e) => e.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_segmenter")] +impl From<SegmenterError> for ICU4XError { + fn from(e: SegmenterError) -> Self { + match e { + SegmenterError::Data(e) => e.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_list")] +impl From<ListError> for ICU4XError { + fn from(e: ListError) -> Self { + match e { + ListError::Data(e) => e.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +impl From<ParserError> for ICU4XError { + fn from(e: ParserError) -> Self { + match e { + ParserError::InvalidLanguage => ICU4XError::LocaleParserLanguageError, + ParserError::InvalidSubtag => ICU4XError::LocaleParserSubtagError, + ParserError::InvalidExtension => ICU4XError::LocaleParserExtensionError, + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +impl From<TinyStrError> for ICU4XError { + fn from(e: TinyStrError) -> Self { + match e { + TinyStrError::TooLarge { .. } => ICU4XError::TinyStrTooLargeError, + TinyStrError::ContainsNull => ICU4XError::TinyStrContainsNullError, + TinyStrError::NonAscii => ICU4XError::TinyStrNonAsciiError, + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(any(feature = "icu_timezone", feature = "icu_datetime"))] +impl From<TimeZoneError> for ICU4XError { + fn from(e: TimeZoneError) -> Self { + match e { + TimeZoneError::OffsetOutOfBounds => ICU4XError::TimeZoneOffsetOutOfBoundsError, + TimeZoneError::InvalidOffset => ICU4XError::TimeZoneInvalidOffsetError, + TimeZoneError::Data(err) => err.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} + +#[cfg(feature = "icu_normalizer")] +impl From<NormalizerError> for ICU4XError { + fn from(e: NormalizerError) -> Self { + match e { + NormalizerError::FutureExtension => ICU4XError::NormalizerFutureExtensionError, + NormalizerError::ValidationError => ICU4XError::NormalizerValidationError, + NormalizerError::Data(err) => err.into(), + _ => ICU4XError::UnknownError, + } + .log_original(&e) + } +} diff --git a/intl/icu_capi/src/fallbacker.rs b/intl/icu_capi/src/fallbacker.rs new file mode 100644 index 0000000000..b3f21b7530 --- /dev/null +++ b/intl/icu_capi/src/fallbacker.rs @@ -0,0 +1,195 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_locid_transform::fallback::LocaleFallbackConfig; + use icu_locid_transform::fallback::LocaleFallbackIterator; + use icu_locid_transform::fallback::LocaleFallbackPriority; + use icu_locid_transform::fallback::LocaleFallbackerWithConfig; + use icu_locid_transform::LocaleFallbacker; + + use crate::{ + errors::ffi::ICU4XError, locale::ffi::ICU4XLocale, provider::ffi::ICU4XDataProvider, + }; + + /// An object that runs the ICU4X locale fallback algorithm. + #[diplomat::opaque] + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbacker, Struct)] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackerBorrowed, + Struct, + hidden + )] + pub struct ICU4XLocaleFallbacker(pub LocaleFallbacker); + + /// Priority mode for the ICU4X fallback algorithm. + #[diplomat::enum_convert(LocaleFallbackPriority, needs_wildcard)] + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbackPriority, Enum)] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackPriority::const_default, + FnInEnum, + hidden + )] + pub enum ICU4XLocaleFallbackPriority { + Language = 0, + Region = 1, + Collation = 2, + } + + /// What additional data is required to load when performing fallback. + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbackSupplement, Enum)] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackSupplement::const_default, + FnInEnum, + hidden + )] + pub enum ICU4XLocaleFallbackSupplement { + None = 0, + Collation = 1, + } + + /// Collection of configurations for the ICU4X fallback algorithm. + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbackConfig, Struct)] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackConfig::const_default, + FnInStruct, + hidden + )] + pub struct ICU4XLocaleFallbackConfig<'a> { + /// Choice of priority mode. + pub priority: ICU4XLocaleFallbackPriority, + /// An empty string is considered `None`. + pub extension_key: &'a str, + /// Fallback supplement data key to customize fallback rules. + pub fallback_supplement: ICU4XLocaleFallbackSupplement, + } + + /// An object that runs the ICU4X locale fallback algorithm with specific configurations. + #[diplomat::opaque] + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbacker, Struct)] + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbackerWithConfig, Struct)] + pub struct ICU4XLocaleFallbackerWithConfig<'a>(pub LocaleFallbackerWithConfig<'a>); + + /// An iterator over the locale under fallback. + #[diplomat::opaque] + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbackIterator, Struct)] + pub struct ICU4XLocaleFallbackIterator<'a>(pub LocaleFallbackIterator<'a, 'a>); + + impl ICU4XLocaleFallbacker { + /// Creates a new `ICU4XLocaleFallbacker` from a data provider. + #[diplomat::rust_link(icu::locid_transform::fallback::LocaleFallbacker::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLocaleFallbacker>, ICU4XError> { + Ok(Box::new(ICU4XLocaleFallbacker(call_constructor!( + LocaleFallbacker::new [r => Ok(r.static_to_owned())], + LocaleFallbacker::try_new_with_any_provider, + LocaleFallbacker::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Creates a new `ICU4XLocaleFallbacker` without data for limited functionality. + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbacker::new_without_data, + FnInStruct + )] + pub fn create_without_data() -> Box<ICU4XLocaleFallbacker> { + Box::new(ICU4XLocaleFallbacker(LocaleFallbacker::new_without_data())) + } + + /// Associates this `ICU4XLocaleFallbacker` with configuration options. + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbacker::for_config, + FnInStruct + )] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackerBorrowed::for_config, + FnInStruct, + hidden + )] + pub fn for_config<'a, 'temp>( + &'a self, + config: ICU4XLocaleFallbackConfig<'temp>, + ) -> Result<Box<ICU4XLocaleFallbackerWithConfig<'a>>, ICU4XError> { + Ok(Box::new(ICU4XLocaleFallbackerWithConfig( + self.0.for_config(LocaleFallbackConfig::try_from(config)?), + ))) + } + } + + impl<'a> ICU4XLocaleFallbackerWithConfig<'a> { + /// Creates an iterator from a locale with each step of fallback. + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbacker::fallback_for, + FnInStruct + )] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackerBorrowed::fallback_for, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackerWithConfig::fallback_for, + FnInStruct, + hidden + )] + pub fn fallback_for_locale<'b: 'a, 'temp>( + &'b self, + locale: &'temp ICU4XLocale, + ) -> Box<ICU4XLocaleFallbackIterator<'a>> { + Box::new(ICU4XLocaleFallbackIterator( + self.0.fallback_for((&locale.0).into()), + )) + } + } + + impl<'a> ICU4XLocaleFallbackIterator<'a> { + /// Gets a snapshot of the current state of the locale. + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackIterator::get, + FnInStruct + )] + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackIterator::take, + FnInStruct, + hidden + )] + pub fn get(&self) -> Box<ICU4XLocale> { + Box::new(ICU4XLocale(self.0.get().clone().into_locale())) + } + + /// Performs one step of the fallback algorithm, mutating the locale. + #[diplomat::rust_link( + icu::locid_transform::fallback::LocaleFallbackIterator::step, + FnInStruct + )] + pub fn step(&mut self) { + self.0.step(); + } + } +} + +impl TryFrom<ffi::ICU4XLocaleFallbackConfig<'_>> + for icu_locid_transform::fallback::LocaleFallbackConfig +{ + type Error = crate::errors::ffi::ICU4XError; + fn try_from(other: ffi::ICU4XLocaleFallbackConfig) -> Result<Self, Self::Error> { + let mut result = Self::default(); + result.priority = other.priority.into(); + result.extension_key = match other.extension_key { + "" => None, + s => Some(s.parse()?), + }; + result.fallback_supplement = match other.fallback_supplement { + ffi::ICU4XLocaleFallbackSupplement::None => None, + ffi::ICU4XLocaleFallbackSupplement::Collation => { + Some(icu_locid_transform::fallback::LocaleFallbackSupplement::Collation) + } + }; + Ok(result) + } +} diff --git a/intl/icu_capi/src/fixed_decimal.rs b/intl/icu_capi/src/fixed_decimal.rs new file mode 100644 index 0000000000..6439698323 --- /dev/null +++ b/intl/icu_capi/src/fixed_decimal.rs @@ -0,0 +1,341 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use fixed_decimal::Sign; +use fixed_decimal::SignDisplay; + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use fixed_decimal::{DoublePrecision, FixedDecimal}; + use writeable::Writeable; + + use crate::errors::ffi::ICU4XError; + + #[diplomat::opaque] + #[diplomat::rust_link(fixed_decimal::FixedDecimal, Struct)] + pub struct ICU4XFixedDecimal(pub FixedDecimal); + + /// The sign of a FixedDecimal, as shown in formatting. + #[diplomat::rust_link(fixed_decimal::Sign, Enum)] + pub enum ICU4XFixedDecimalSign { + /// No sign (implicitly positive, e.g., 1729). + None, + /// A negative sign, e.g., -1729. + Negative, + /// An explicit positive sign, e.g., +1729. + Positive, + } + + /// ECMA-402 compatible sign display preference. + #[diplomat::rust_link(fixed_decimal::SignDisplay, Enum)] + pub enum ICU4XFixedDecimalSignDisplay { + Auto, + Never, + Always, + ExceptZero, + Negative, + } + + /// Increment used in a rounding operation. + #[diplomat::rust_link(fixed_decimal::RoundingIncrement, Enum)] + pub enum ICU4XRoundingIncrement { + MultiplesOf1, + MultiplesOf2, + MultiplesOf5, + MultiplesOf25, + } + + impl ICU4XFixedDecimal { + /// Construct an [`ICU4XFixedDecimal`] from an integer. + #[diplomat::rust_link(fixed_decimal::FixedDecimal, Struct)] + #[diplomat::attr(dart, disable)] + pub fn create_from_i32(v: i32) -> Box<ICU4XFixedDecimal> { + Box::new(ICU4XFixedDecimal(FixedDecimal::from(v))) + } + + /// Construct an [`ICU4XFixedDecimal`] from an integer. + #[diplomat::rust_link(fixed_decimal::FixedDecimal, Struct)] + #[diplomat::attr(dart, disable)] + pub fn create_from_u32(v: u32) -> Box<ICU4XFixedDecimal> { + Box::new(ICU4XFixedDecimal(FixedDecimal::from(v))) + } + + /// Construct an [`ICU4XFixedDecimal`] from an integer. + #[diplomat::rust_link(fixed_decimal::FixedDecimal, Struct)] + #[diplomat::attr(dart, rename = "create_from_int")] + pub fn create_from_i64(v: i64) -> Box<ICU4XFixedDecimal> { + Box::new(ICU4XFixedDecimal(FixedDecimal::from(v))) + } + + /// Construct an [`ICU4XFixedDecimal`] from an integer. + #[diplomat::rust_link(fixed_decimal::FixedDecimal, Struct)] + #[diplomat::attr(dart, disable)] + pub fn create_from_u64(v: u64) -> Box<ICU4XFixedDecimal> { + Box::new(ICU4XFixedDecimal(FixedDecimal::from(v))) + } + + /// Construct an [`ICU4XFixedDecimal`] from an integer-valued float + #[diplomat::rust_link(fixed_decimal::FixedDecimal::try_from_f64, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FloatPrecision, Enum)] + #[diplomat::rust_link(fixed_decimal::DoublePrecision, Enum, hidden)] + #[diplomat::attr(dart, disable)] + #[diplomat::attr(dart, rename = "create_from_double_with_integer_precision")] + pub fn create_from_f64_with_integer_precision( + f: f64, + ) -> Result<Box<ICU4XFixedDecimal>, ICU4XError> { + let precision = DoublePrecision::Integer; + Ok(Box::new(ICU4XFixedDecimal(FixedDecimal::try_from_f64( + f, precision, + )?))) + } + + /// Construct an [`ICU4XFixedDecimal`] from an float, with a given power of 10 for the lower magnitude + #[diplomat::rust_link(fixed_decimal::FixedDecimal::try_from_f64, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FloatPrecision, Enum)] + #[diplomat::rust_link(fixed_decimal::DoublePrecision, Enum, hidden)] + #[diplomat::attr(dart, rename = "create_from_double_with_lower_magnitude")] + pub fn create_from_f64_with_lower_magnitude( + f: f64, + magnitude: i16, + ) -> Result<Box<ICU4XFixedDecimal>, ICU4XError> { + let precision = DoublePrecision::Magnitude(magnitude); + Ok(Box::new(ICU4XFixedDecimal(FixedDecimal::try_from_f64( + f, precision, + )?))) + } + + /// Construct an [`ICU4XFixedDecimal`] from an float, for a given number of significant digits + #[diplomat::rust_link(fixed_decimal::FixedDecimal::try_from_f64, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FloatPrecision, Enum)] + #[diplomat::rust_link(fixed_decimal::DoublePrecision, Enum, hidden)] + #[diplomat::attr(dart, rename = "create_from_double_with_significant_digits")] + pub fn create_from_f64_with_significant_digits( + f: f64, + digits: u8, + ) -> Result<Box<ICU4XFixedDecimal>, ICU4XError> { + let precision = DoublePrecision::SignificantDigits(digits); + Ok(Box::new(ICU4XFixedDecimal(FixedDecimal::try_from_f64( + f, precision, + )?))) + } + + /// Construct an [`ICU4XFixedDecimal`] from an float, with enough digits to recover + /// the original floating point in IEEE 754 without needing trailing zeros + #[diplomat::rust_link(fixed_decimal::FixedDecimal::try_from_f64, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FloatPrecision, Enum)] + #[diplomat::rust_link(fixed_decimal::DoublePrecision, Enum, hidden)] + #[diplomat::attr(dart, rename = "create_from_double_with_double_precision")] + pub fn create_from_f64_with_floating_precision( + f: f64, + ) -> Result<Box<ICU4XFixedDecimal>, ICU4XError> { + let precision = DoublePrecision::Floating; + Ok(Box::new(ICU4XFixedDecimal(FixedDecimal::try_from_f64( + f, precision, + )?))) + } + + /// Construct an [`ICU4XFixedDecimal`] from a string. + #[diplomat::rust_link(fixed_decimal::FixedDecimal::from_str, FnInStruct)] + pub fn create_from_string(v: &str) -> Result<Box<ICU4XFixedDecimal>, ICU4XError> { + let v = v.as_bytes(); // #2520 + Ok(Box::new(ICU4XFixedDecimal(FixedDecimal::try_from(v)?))) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::digit_at, FnInStruct)] + pub fn digit_at(&self, magnitude: i16) -> u8 { + self.0.digit_at(magnitude) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::magnitude_range, FnInStruct)] + pub fn magnitude_start(&self) -> i16 { + *self.0.magnitude_range().start() + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::magnitude_range, FnInStruct)] + pub fn magnitude_end(&self) -> i16 { + *self.0.magnitude_range().end() + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::nonzero_magnitude_start, FnInStruct)] + pub fn nonzero_magnitude_start(&self) -> i16 { + self.0.nonzero_magnitude_start() + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::nonzero_magnitude_end, FnInStruct)] + pub fn nonzero_magnitude_end(&self) -> i16 { + self.0.nonzero_magnitude_end() + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::is_zero, FnInStruct)] + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + /// Multiply the [`ICU4XFixedDecimal`] by a given power of ten. + #[diplomat::rust_link(fixed_decimal::FixedDecimal::multiply_pow10, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::multiplied_pow10, FnInStruct, hidden)] + pub fn multiply_pow10(&mut self, power: i16) { + self.0.multiply_pow10(power) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::sign, FnInStruct)] + pub fn sign(&self) -> ICU4XFixedDecimalSign { + self.0.sign().into() + } + + /// Set the sign of the [`ICU4XFixedDecimal`]. + #[diplomat::rust_link(fixed_decimal::FixedDecimal::set_sign, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::with_sign, FnInStruct, hidden)] + pub fn set_sign(&mut self, sign: ICU4XFixedDecimalSign) { + self.0.set_sign(sign.into()) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::apply_sign_display, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::with_sign_display, FnInStruct, hidden)] + pub fn apply_sign_display(&mut self, sign_display: ICU4XFixedDecimalSignDisplay) { + self.0.apply_sign_display(sign_display.into()) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::trim_start, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::trimmed_start, FnInStruct, hidden)] + pub fn trim_start(&mut self) { + self.0.trim_start() + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::trim_end, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::trimmed_end, FnInStruct, hidden)] + pub fn trim_end(&mut self) { + self.0.trim_end() + } + + /// Zero-pad the [`ICU4XFixedDecimal`] on the left to a particular position + #[diplomat::rust_link(fixed_decimal::FixedDecimal::pad_start, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::padded_start, FnInStruct, hidden)] + pub fn pad_start(&mut self, position: i16) { + self.0.pad_start(position) + } + + /// Zero-pad the [`ICU4XFixedDecimal`] on the right to a particular position + #[diplomat::rust_link(fixed_decimal::FixedDecimal::pad_end, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::padded_end, FnInStruct, hidden)] + pub fn pad_end(&mut self, position: i16) { + self.0.pad_end(position) + } + + /// Truncate the [`ICU4XFixedDecimal`] on the left to a particular position, deleting digits if necessary. This is useful for, e.g. abbreviating years + /// ("2022" -> "22") + #[diplomat::rust_link(fixed_decimal::FixedDecimal::set_max_position, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::with_max_position, FnInStruct, hidden)] + pub fn set_max_position(&mut self, position: i16) { + self.0.set_max_position(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::trunc, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::trunced, FnInStruct, hidden)] + pub fn trunc(&mut self, position: i16) { + self.0.trunc(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_trunc, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_trunced, FnInStruct, hidden)] + pub fn half_trunc(&mut self, position: i16) { + self.0.half_trunc(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::expand, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::expanded, FnInStruct, hidden)] + pub fn expand(&mut self, position: i16) { + self.0.expand(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_expand, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_expanded, FnInStruct, hidden)] + pub fn half_expand(&mut self, position: i16) { + self.0.half_expand(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::ceil, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::ceiled, FnInStruct, hidden)] + pub fn ceil(&mut self, position: i16) { + self.0.ceil(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_ceil, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_ceiled, FnInStruct, hidden)] + pub fn half_ceil(&mut self, position: i16) { + self.0.half_ceil(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::floor, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::floored, FnInStruct, hidden)] + pub fn floor(&mut self, position: i16) { + self.0.floor(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_floor, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_floored, FnInStruct, hidden)] + pub fn half_floor(&mut self, position: i16) { + self.0.half_floor(position) + } + + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_even, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::half_evened, FnInStruct, hidden)] + pub fn half_even(&mut self, position: i16) { + self.0.half_even(position) + } + + /// Concatenates `other` to the end of `self`. + /// + /// If successful, `other` will be set to 0 and a successful status is returned. + /// + /// If not successful, `other` will be unchanged and an error is returned. + #[diplomat::rust_link(fixed_decimal::FixedDecimal::concatenate_end, FnInStruct)] + #[diplomat::rust_link(fixed_decimal::FixedDecimal::concatenated_end, FnInStruct, hidden)] + pub fn concatenate_end(&mut self, other: &mut ICU4XFixedDecimal) -> Result<(), ()> { + let x = core::mem::take(&mut other.0); + self.0.concatenate_end(x).map_err(|y| { + other.0 = y; + }) + } + + /// Format the [`ICU4XFixedDecimal`] as a string. + #[diplomat::rust_link(fixed_decimal::FixedDecimal::write_to, FnInStruct)] + pub fn to_string(&self, to: &mut diplomat_runtime::DiplomatWriteable) { + let _ = self.0.write_to(to); + } + } +} + +impl From<ffi::ICU4XFixedDecimalSign> for Sign { + fn from(other: ffi::ICU4XFixedDecimalSign) -> Self { + match other { + ffi::ICU4XFixedDecimalSign::None => Self::None, + ffi::ICU4XFixedDecimalSign::Negative => Self::Negative, + ffi::ICU4XFixedDecimalSign::Positive => Self::Positive, + } + } +} + +impl From<Sign> for ffi::ICU4XFixedDecimalSign { + fn from(other: Sign) -> Self { + match other { + Sign::None => Self::None, + Sign::Negative => Self::Negative, + Sign::Positive => Self::Positive, + } + } +} + +impl From<ffi::ICU4XFixedDecimalSignDisplay> for SignDisplay { + fn from(other: ffi::ICU4XFixedDecimalSignDisplay) -> Self { + match other { + ffi::ICU4XFixedDecimalSignDisplay::Auto => Self::Auto, + ffi::ICU4XFixedDecimalSignDisplay::Never => Self::Never, + ffi::ICU4XFixedDecimalSignDisplay::Always => Self::Always, + ffi::ICU4XFixedDecimalSignDisplay::ExceptZero => Self::ExceptZero, + ffi::ICU4XFixedDecimalSignDisplay::Negative => Self::Negative, + } + } +} diff --git a/intl/icu_capi/src/iana_bcp47_mapper.rs b/intl/icu_capi/src/iana_bcp47_mapper.rs new file mode 100644 index 0000000000..c77730410d --- /dev/null +++ b/intl/icu_capi/src/iana_bcp47_mapper.rs @@ -0,0 +1,82 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use icu_timezone::IanaBcp47RoundTripMapper; + use icu_timezone::IanaToBcp47Mapper; + use icu_timezone::TimeZoneBcp47Id; + + /// An object capable of mapping from an IANA time zone ID to a BCP-47 ID. + /// + /// This can be used via `try_set_iana_time_zone_id()` on [`ICU4XCustomTimeZone`]. + /// + /// [`ICU4XCustomTimeZone`]: crate::timezone::ffi::ICU4XCustomTimeZone + #[diplomat::opaque] + #[diplomat::rust_link(icu::timezone::IanaToBcp47Mapper, Struct)] + #[diplomat::rust_link(icu::timezone::IanaToBcp47Mapper::as_borrowed, FnInStruct, hidden)] + pub struct ICU4XIanaToBcp47Mapper(pub IanaToBcp47Mapper); + + impl ICU4XIanaToBcp47Mapper { + #[diplomat::rust_link(icu::timezone::IanaToBcp47Mapper::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XIanaToBcp47Mapper>, ICU4XError> { + Ok(Box::new(ICU4XIanaToBcp47Mapper(call_constructor!( + IanaToBcp47Mapper::new [r => Ok(r)], + IanaToBcp47Mapper::try_new_with_any_provider, + IanaToBcp47Mapper::try_new_with_buffer_provider, + provider, + )?))) + } + } + + /// An object capable of mapping from a BCP-47 time zone ID to an IANA ID. + #[diplomat::opaque] + #[diplomat::rust_link(icu::timezone::IanaBcp47RoundTripMapper, Struct)] + #[diplomat::rust_link( + icu::timezone::IanaBcp47RoundTripMapper::as_borrowed, + FnInStruct, + hidden + )] + pub struct ICU4XBcp47ToIanaMapper(pub IanaBcp47RoundTripMapper); + + impl ICU4XBcp47ToIanaMapper { + #[diplomat::rust_link(icu::timezone::IanaBcp47RoundTripMapper::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XBcp47ToIanaMapper>, ICU4XError> { + Ok(Box::new(ICU4XBcp47ToIanaMapper(call_constructor!( + IanaBcp47RoundTripMapper::new [r => Ok(r)], + IanaBcp47RoundTripMapper::try_new_with_any_provider, + IanaBcp47RoundTripMapper::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Writes out the canonical IANA time zone ID corresponding to the given BCP-47 ID. + #[diplomat::rust_link( + icu::datetime::time_zone::IanaBcp47RoundTripMapper::bcp47_to_iana, + FnInStruct + )] + pub fn get( + &self, + value: &str, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + use core::str::FromStr; + use writeable::Writeable; + let handle = self.0.as_borrowed(); + TimeZoneBcp47Id::from_str(value) + .ok() + .and_then(|bcp47_id| handle.bcp47_to_iana(bcp47_id)) + .ok_or(ICU4XError::TimeZoneInvalidIdError)? + .write_to(write)?; + Ok(()) + } + } +} diff --git a/intl/icu_capi/src/lib.rs b/intl/icu_capi/src/lib.rs new file mode 100644 index 0000000000..74060998bc --- /dev/null +++ b/intl/icu_capi/src/lib.rs @@ -0,0 +1,145 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +// https://github.com/unicode-org/icu4x/blob/main/docs/process/boilerplate.md#library-annotations +#![no_std] +#![cfg_attr( + not(test), + deny( + clippy::indexing_slicing, + clippy::unwrap_used, + clippy::expect_used, + clippy::panic, + // Exhaustiveness and Debug is not required for Diplomat types + ) +)] +#![allow(clippy::upper_case_acronyms)] +#![allow(clippy::needless_lifetimes)] +#![allow(clippy::result_unit_err)] + +//! This crate contains the source of truth for the [Diplomat](https://github.com/rust-diplomat/diplomat)-generated +//! FFI bindings. This generates the C, C++, JavaScript, and TypeScript bindings. This crate also contains the `extern "C"` +//! FFI for ICU4X. +//! +//! While the types in this crate are public, APIs from this crate are *not intended to be used from Rust* +//! and as such this crate may unpredictably change its Rust API across compatible semver versions. The `extern "C"` APIs exposed +//! by this crate, while not directly documented, are stable within the same major semver version, as are the bindings exposed under +//! the `cpp/` and `js/` folders. +//! +//! This crate may still be explored for documentation on docs.rs, and there are generated language-specific docs available as well. +//! C++ has sphinx docs in `cpp/docs/`, and the header files also contain documentation comments. The JS version has sphinx docs under +//! `js/docs`, and the TypeScript sources in `js/include` are compatible with `tsdoc`. +//! +//! This crate is `no_std` and will not typically build as a staticlib on its own. If you wish to link to it you should prefer +//! using `icu_capi_staticlib`, or for more esoteric platforms you may write a shim crate depending on this crate that hooks in +//! an allocator and panic hook. +//! +//! More information on using ICU4X from C++ can be found in [our tutorial]. +//! +//! [our tutorial]: https://github.com/unicode-org/icu4x/blob/main/docs/tutorials/cpp.md +// Renamed so you can't accidentally use it +#[cfg(target_arch = "wasm32")] +extern crate std as rust_std; + +extern crate alloc; + +// Common modules + +pub mod common; +pub mod data_struct; +pub mod errors; +pub mod locale; +#[cfg(feature = "logging")] +pub mod logging; +#[macro_use] +pub mod provider; + +// Components + +#[cfg(feature = "icu_properties")] +pub mod bidi; +#[cfg(any( + feature = "icu_datetime", + feature = "icu_timezone", + feature = "icu_calendar" +))] +pub mod calendar; +#[cfg(feature = "icu_casemap")] +pub mod casemap; +#[cfg(feature = "icu_collator")] +pub mod collator; +#[cfg(feature = "icu_properties")] +pub mod collections_sets; +#[cfg(any( + feature = "icu_datetime", + feature = "icu_timezone", + feature = "icu_calendar" +))] +pub mod date; +#[cfg(any( + feature = "icu_datetime", + feature = "icu_timezone", + feature = "icu_calendar" +))] +pub mod datetime; +#[cfg(feature = "icu_datetime")] +pub mod datetime_formatter; +#[cfg(feature = "icu_decimal")] +pub mod decimal; +#[cfg(feature = "icu_displaynames")] +pub mod displaynames; +#[cfg(feature = "icu_locid_transform")] +pub mod fallbacker; +#[cfg(feature = "icu_decimal")] +pub mod fixed_decimal; +#[cfg(any(feature = "icu_datetime", feature = "icu_timezone"))] +pub mod iana_bcp47_mapper; +#[cfg(feature = "icu_list")] +pub mod list; +#[cfg(feature = "icu_locid_transform")] +pub mod locale_directionality; +#[cfg(feature = "icu_locid_transform")] +pub mod locid_transform; +#[cfg(feature = "icu_timezone")] +pub mod metazone_calculator; +#[cfg(feature = "icu_normalizer")] +pub mod normalizer; +#[cfg(feature = "icu_normalizer")] +pub mod normalizer_properties; +#[cfg(feature = "icu_plurals")] +pub mod pluralrules; +#[cfg(feature = "icu_properties")] +pub mod properties_iter; +#[cfg(feature = "icu_properties")] +pub mod properties_maps; +#[cfg(feature = "icu_properties")] +pub mod properties_names; +#[cfg(feature = "icu_properties")] +pub mod properties_sets; +#[cfg(feature = "icu_properties")] +pub mod properties_unisets; +#[cfg(feature = "icu_properties")] +pub mod script; +#[cfg(feature = "icu_segmenter")] +pub mod segmenter_grapheme; +#[cfg(feature = "icu_segmenter")] +pub mod segmenter_line; +#[cfg(feature = "icu_segmenter")] +pub mod segmenter_sentence; +#[cfg(feature = "icu_segmenter")] +pub mod segmenter_word; +#[cfg(any( + feature = "icu_datetime", + feature = "icu_timezone", + feature = "icu_calendar" +))] +pub mod time; +#[cfg(any(feature = "icu_datetime", feature = "icu_timezone"))] +pub mod timezone; +#[cfg(feature = "icu_datetime")] +pub mod timezone_formatter; +#[cfg(feature = "icu_calendar")] +pub mod week; +#[cfg(feature = "icu_datetime")] +pub mod zoned_formatter; diff --git a/intl/icu_capi/src/list.rs b/intl/icu_capi/src/list.rs new file mode 100644 index 0000000000..278b12ffb5 --- /dev/null +++ b/intl/icu_capi/src/list.rs @@ -0,0 +1,125 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::{ + errors::ffi::ICU4XError, locale::ffi::ICU4XLocale, provider::ffi::ICU4XDataProvider, + }; + use alloc::boxed::Box; + use alloc::string::String; + use alloc::vec::Vec; + use diplomat_runtime::DiplomatWriteable; + use icu_list::{ListFormatter, ListLength}; + use writeable::Writeable; + + /// A list of strings + #[diplomat::opaque] + pub struct ICU4XList(pub Vec<String>); + + impl ICU4XList { + /// Create a new list of strings + pub fn create() -> Box<ICU4XList> { + Box::new(ICU4XList(Vec::new())) + } + + /// Create a new list of strings with preallocated space to hold + /// at least `capacity` elements + pub fn create_with_capacity(capacity: usize) -> Box<ICU4XList> { + Box::new(ICU4XList(Vec::with_capacity(capacity))) + } + + /// Push a string to the list + /// + /// For C++ users, potentially invalid UTF8 will be handled via + /// REPLACEMENT CHARACTERs + pub fn push(&mut self, val: &str) { + let val = val.as_bytes(); // #2520 + self.0.push(String::from_utf8_lossy(val).into_owned()); + } + + /// The number of elements in this list + #[allow(clippy::len_without_is_empty)] // don't need to follow Rust conventions over FFI + #[diplomat::attr(dart, rename = "length")] + pub fn len(&self) -> usize { + self.0.len() + } + } + + #[diplomat::rust_link(icu::list::ListLength, Enum)] + #[diplomat::enum_convert(ListLength, needs_wildcard)] + pub enum ICU4XListLength { + Wide, + Short, + Narrow, + } + #[diplomat::opaque] + #[diplomat::rust_link(icu::list::ListFormatter, Struct)] + pub struct ICU4XListFormatter(pub ListFormatter); + + impl ICU4XListFormatter { + /// Construct a new ICU4XListFormatter instance for And patterns + #[diplomat::rust_link(icu::list::ListFormatter::try_new_and_with_length, FnInStruct)] + pub fn create_and_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + length: ICU4XListLength, + ) -> Result<Box<ICU4XListFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XListFormatter(call_constructor!( + ListFormatter::try_new_and_with_length, + ListFormatter::try_new_and_with_length_with_any_provider, + ListFormatter::try_new_and_with_length_with_buffer_provider, + provider, + &locale, + length.into() + )?))) + } + /// Construct a new ICU4XListFormatter instance for And patterns + #[diplomat::rust_link(icu::list::ListFormatter::try_new_or_with_length, FnInStruct)] + pub fn create_or_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + length: ICU4XListLength, + ) -> Result<Box<ICU4XListFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XListFormatter(call_constructor!( + ListFormatter::try_new_or_with_length, + ListFormatter::try_new_or_with_length_with_any_provider, + ListFormatter::try_new_or_with_length_with_buffer_provider, + provider, + &locale, + length.into() + )?))) + } + /// Construct a new ICU4XListFormatter instance for And patterns + #[diplomat::rust_link(icu::list::ListFormatter::try_new_unit_with_length, FnInStruct)] + pub fn create_unit_with_length( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + length: ICU4XListLength, + ) -> Result<Box<ICU4XListFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XListFormatter(call_constructor!( + ListFormatter::try_new_unit_with_length, + ListFormatter::try_new_unit_with_length_with_any_provider, + ListFormatter::try_new_unit_with_length_with_buffer_provider, + provider, + &locale, + length.into() + )?))) + } + + #[diplomat::rust_link(icu::list::ListFormatter::format, FnInStruct)] + #[diplomat::rust_link(icu::list::ListFormatter::format_to_string, FnInStruct, hidden)] + pub fn format( + &self, + list: &ICU4XList, + write: &mut DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(list.0.iter()).write_to(write)?; + Ok(()) + } + } +} diff --git a/intl/icu_capi/src/locale.rs b/intl/icu_capi/src/locale.rs new file mode 100644 index 0000000000..07089cae65 --- /dev/null +++ b/intl/icu_capi/src/locale.rs @@ -0,0 +1,212 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use alloc::boxed::Box; + use core::str; + use diplomat_runtime::DiplomatWriteable; + use icu_locid::extensions::unicode::Key; + use icu_locid::subtags::{Language, Region, Script}; + use icu_locid::Locale; + use writeable::Writeable; + + use crate::common::ffi::ICU4XOrdering; + + #[diplomat::opaque] + /// An ICU4X Locale, capable of representing strings like `"en-US"`. + #[diplomat::rust_link(icu::locid::Locale, Struct)] + pub struct ICU4XLocale(pub Locale); + + impl ICU4XLocale { + /// Construct an [`ICU4XLocale`] from an locale identifier. + /// + /// This will run the complete locale parsing algorithm. If code size and + /// performance are critical and the locale is of a known shape (such as + /// `aa-BB`) use `create_und`, `set_language`, `set_script`, and `set_region`. + #[diplomat::rust_link(icu::locid::Locale::try_from_bytes, FnInStruct)] + #[diplomat::rust_link(icu::locid::Locale::from_str, FnInStruct, hidden)] + pub fn create_from_string(name: &str) -> Result<Box<ICU4XLocale>, ICU4XError> { + let name = name.as_bytes(); // #2520 + Ok(Box::new(ICU4XLocale(Locale::try_from_bytes(name)?))) + } + + /// Construct a default undefined [`ICU4XLocale`] "und". + #[diplomat::rust_link(icu::locid::Locale::UND, AssociatedConstantInStruct)] + pub fn create_und() -> Box<ICU4XLocale> { + Box::new(ICU4XLocale(Locale::UND)) + } + + /// Clones the [`ICU4XLocale`]. + #[diplomat::rust_link(icu::locid::Locale, Struct)] + #[allow(clippy::should_implement_trait)] + pub fn clone(&self) -> Box<ICU4XLocale> { + Box::new(ICU4XLocale(self.0.clone())) + } + + /// Write a string representation of the `LanguageIdentifier` part of + /// [`ICU4XLocale`] to `write`. + #[diplomat::rust_link(icu::locid::Locale::id, StructField)] + pub fn basename( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.id.write_to(write)?; + Ok(()) + } + + /// Write a string representation of the unicode extension to `write` + #[diplomat::rust_link(icu::locid::Locale::extensions, StructField)] + pub fn get_unicode_extension( + &self, + bytes: &str, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let bytes = bytes.as_bytes(); // #2520 + self.0 + .extensions + .unicode + .keywords + .get(&Key::try_from_bytes(bytes)?) + .ok_or(ICU4XError::LocaleUndefinedSubtagError)? + .write_to(write)?; + Ok(()) + } + + /// Write a string representation of [`ICU4XLocale`] language to `write` + #[diplomat::rust_link(icu::locid::Locale::id, StructField)] + pub fn language( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.id.language.write_to(write)?; + Ok(()) + } + + /// Set the language part of the [`ICU4XLocale`]. + #[diplomat::rust_link(icu::locid::Locale::try_from_bytes, FnInStruct)] + pub fn set_language(&mut self, bytes: &str) -> Result<(), ICU4XError> { + let bytes = bytes.as_bytes(); // #2520 + self.0.id.language = if bytes.is_empty() { + Language::UND + } else { + Language::try_from_bytes(bytes)? + }; + Ok(()) + } + + /// Write a string representation of [`ICU4XLocale`] region to `write` + #[diplomat::rust_link(icu::locid::Locale::id, StructField)] + pub fn region( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + if let Some(region) = self.0.id.region { + region.write_to(write)?; + Ok(()) + } else { + Err(ICU4XError::LocaleUndefinedSubtagError) + } + } + + /// Set the region part of the [`ICU4XLocale`]. + #[diplomat::rust_link(icu::locid::Locale::try_from_bytes, FnInStruct)] + pub fn set_region(&mut self, bytes: &str) -> Result<(), ICU4XError> { + let bytes = bytes.as_bytes(); // #2520 + self.0.id.region = if bytes.is_empty() { + None + } else { + Some(Region::try_from_bytes(bytes)?) + }; + Ok(()) + } + + /// Write a string representation of [`ICU4XLocale`] script to `write` + #[diplomat::rust_link(icu::locid::Locale::id, StructField)] + pub fn script( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + if let Some(script) = self.0.id.script { + script.write_to(write)?; + Ok(()) + } else { + Err(ICU4XError::LocaleUndefinedSubtagError) + } + } + + /// Set the script part of the [`ICU4XLocale`]. Pass an empty string to remove the script. + #[diplomat::rust_link(icu::locid::Locale::try_from_bytes, FnInStruct)] + pub fn set_script(&mut self, bytes: &str) -> Result<(), ICU4XError> { + let bytes = bytes.as_bytes(); // #2520 + self.0.id.script = if bytes.is_empty() { + None + } else { + Some(Script::try_from_bytes(bytes)?) + }; + Ok(()) + } + + /// Best effort locale canonicalizer that doesn't need any data + /// + /// Use ICU4XLocaleCanonicalizer for better control and functionality + #[diplomat::rust_link(icu::locid::Locale::canonicalize, FnInStruct)] + pub fn canonicalize(bytes: &str, write: &mut DiplomatWriteable) -> Result<(), ICU4XError> { + let bytes = bytes.as_bytes(); // #2520 + Locale::canonicalize(bytes)?.write_to(write)?; + Ok(()) + } + /// Write a string representation of [`ICU4XLocale`] to `write` + #[diplomat::rust_link(icu::locid::Locale::write_to, FnInStruct)] + pub fn to_string( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.write_to(write)?; + Ok(()) + } + + #[diplomat::rust_link(icu::locid::Locale::normalizing_eq, FnInStruct)] + pub fn normalizing_eq(&self, other: &str) -> bool { + let other = other.as_bytes(); // #2520 + if let Ok(other) = str::from_utf8(other) { + self.0.normalizing_eq(other) + } else { + // invalid UTF8 won't be allowed in locales anyway + false + } + } + + #[diplomat::rust_link(icu::locid::Locale::strict_cmp, FnInStruct)] + pub fn strict_cmp(&self, other: &str) -> ICU4XOrdering { + let other = other.as_bytes(); // #2520 + self.0.strict_cmp(other).into() + } + + /// Deprecated + /// + /// Use `create_from_string("en"). + #[cfg(feature = "provider_test")] + #[diplomat::attr(dart, disable)] + pub fn create_en() -> Box<ICU4XLocale> { + Box::new(ICU4XLocale(icu_locid::locale!("en"))) + } + + /// Deprecated + /// + /// Use `create_from_string("bn"). + #[cfg(feature = "provider_test")] + #[diplomat::attr(dart, disable)] + pub fn create_bn() -> Box<ICU4XLocale> { + Box::new(ICU4XLocale(icu_locid::locale!("bn"))) + } + } +} + +impl ffi::ICU4XLocale { + pub fn to_datalocale(&self) -> icu_provider::DataLocale { + (&self.0).into() + } +} diff --git a/intl/icu_capi/src/locale_directionality.rs b/intl/icu_capi/src/locale_directionality.rs new file mode 100644 index 0000000000..2ec83caff2 --- /dev/null +++ b/intl/icu_capi/src/locale_directionality.rs @@ -0,0 +1,101 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::{ + errors::ffi::ICU4XError, + locale::ffi::ICU4XLocale, + locid_transform::ffi::ICU4XLocaleExpander, + provider::{ffi::ICU4XDataProvider, ICU4XDataProviderInner}, + }; + use alloc::boxed::Box; + use icu_locid_transform::{Direction, LocaleDirectionality}; + + #[diplomat::rust_link(icu::locid_transform::Direction, Enum)] + pub enum ICU4XLocaleDirection { + LeftToRight, + RightToLeft, + Unknown, + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::locid_transform::LocaleDirectionality, Struct)] + pub struct ICU4XLocaleDirectionality(pub LocaleDirectionality); + + impl ICU4XLocaleDirectionality { + /// Construct a new ICU4XLocaleDirectionality instance + #[diplomat::rust_link(icu::locid_transform::LocaleDirectionality::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLocaleDirectionality>, ICU4XError> { + Ok(Box::new(ICU4XLocaleDirectionality(call_constructor!( + LocaleDirectionality::new [r => Ok(r)], + LocaleDirectionality::try_new_with_any_provider, + LocaleDirectionality::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Construct a new ICU4XLocaleDirectionality instance with a custom expander + #[diplomat::rust_link( + icu::locid_transform::LocaleDirectionality::new_with_expander, + FnInStruct + )] + pub fn create_with_expander( + provider: &ICU4XDataProvider, + expander: &ICU4XLocaleExpander, + ) -> Result<Box<ICU4XLocaleDirectionality>, ICU4XError> { + #[allow(unused_imports)] + use icu_provider::prelude::*; + Ok(Box::new(ICU4XLocaleDirectionality(match &provider.0 { + ICU4XDataProviderInner::Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + ICU4XDataProviderInner::Empty => { + LocaleDirectionality::try_new_with_expander_unstable( + &icu_provider_adapters::empty::EmptyDataProvider::new(), + expander.0.clone(), + )? + } + #[cfg(feature = "buffer_provider")] + ICU4XDataProviderInner::Buffer(buffer_provider) => { + LocaleDirectionality::try_new_with_expander_unstable( + &buffer_provider.as_deserializing(), + expander.0.clone(), + )? + } + #[cfg(feature = "compiled_data")] + ICU4XDataProviderInner::Compiled => { + LocaleDirectionality::new_with_expander(expander.0.clone()) + } + }))) + } + + #[diplomat::rust_link(icu::locid_transform::LocaleDirectionality::get, FnInStruct)] + pub fn get(&self, locale: &ICU4XLocale) -> ICU4XLocaleDirection { + match self.0.get(&locale.0) { + Some(Direction::LeftToRight) => ICU4XLocaleDirection::LeftToRight, + Some(Direction::RightToLeft) => ICU4XLocaleDirection::RightToLeft, + _ => ICU4XLocaleDirection::Unknown, + } + } + + #[diplomat::rust_link( + icu::locid_transform::LocaleDirectionality::is_left_to_right, + FnInStruct + )] + pub fn is_left_to_right(&self, locale: &ICU4XLocale) -> bool { + self.0.is_left_to_right(&locale.0) + } + + #[diplomat::rust_link( + icu::locid_transform::LocaleDirectionality::is_right_to_left, + FnInStruct + )] + pub fn is_right_to_left(&self, locale: &ICU4XLocale) -> bool { + self.0.is_right_to_left(&locale.0) + } + } +} diff --git a/intl/icu_capi/src/locid_transform.rs b/intl/icu_capi/src/locid_transform.rs new file mode 100644 index 0000000000..d42583b072 --- /dev/null +++ b/intl/icu_capi/src/locid_transform.rs @@ -0,0 +1,115 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_locid_transform::{LocaleCanonicalizer, LocaleExpander, TransformResult}; + + use crate::{locale::ffi::ICU4XLocale, provider::ffi::ICU4XDataProvider}; + + use crate::errors::ffi::ICU4XError; + + /// FFI version of `TransformResult`. + #[diplomat::rust_link(icu::locid_transform::TransformResult, Enum)] + #[diplomat::enum_convert(TransformResult)] + pub enum ICU4XTransformResult { + Modified, + Unmodified, + } + + /// A locale canonicalizer. + #[diplomat::rust_link(icu::locid_transform::LocaleCanonicalizer, Struct)] + #[diplomat::opaque] + pub struct ICU4XLocaleCanonicalizer(LocaleCanonicalizer); + + impl ICU4XLocaleCanonicalizer { + /// Create a new [`ICU4XLocaleCanonicalizer`]. + #[diplomat::rust_link(icu::locid_transform::LocaleCanonicalizer::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLocaleCanonicalizer>, ICU4XError> { + Ok(Box::new(ICU4XLocaleCanonicalizer(call_constructor!( + LocaleCanonicalizer::new [r => Ok(r)], + LocaleCanonicalizer::try_new_with_any_provider, + LocaleCanonicalizer::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Create a new [`ICU4XLocaleCanonicalizer`] with extended data. + #[diplomat::rust_link( + icu::locid_transform::LocaleCanonicalizer::new_with_expander, + FnInStruct + )] + pub fn create_extended( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLocaleCanonicalizer>, ICU4XError> { + let expander = call_constructor!( + LocaleExpander::new_extended [r => Ok(r)], + LocaleExpander::try_new_with_any_provider, + LocaleExpander::try_new_with_buffer_provider, + provider, + )?; + Ok(Box::new(ICU4XLocaleCanonicalizer(call_constructor!( + LocaleCanonicalizer::new_with_expander [r => Ok(r)], + LocaleCanonicalizer::try_new_with_expander_with_any_provider, + LocaleCanonicalizer::try_new_with_expander_with_buffer_provider, + provider, + expander + )?))) + } + + /// FFI version of `LocaleCanonicalizer::canonicalize()`. + #[diplomat::rust_link(icu::locid_transform::LocaleCanonicalizer::canonicalize, FnInStruct)] + pub fn canonicalize(&self, locale: &mut ICU4XLocale) -> ICU4XTransformResult { + self.0.canonicalize(&mut locale.0).into() + } + } + + /// A locale expander. + #[diplomat::rust_link(icu::locid_transform::LocaleExpander, Struct)] + #[diplomat::opaque] + pub struct ICU4XLocaleExpander(pub LocaleExpander); + + impl ICU4XLocaleExpander { + /// Create a new [`ICU4XLocaleExpander`]. + #[diplomat::rust_link(icu::locid_transform::LocaleExpander::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLocaleExpander>, ICU4XError> { + Ok(Box::new(ICU4XLocaleExpander(call_constructor!( + LocaleExpander::new [r => Ok(r)], + LocaleExpander::try_new_with_any_provider, + LocaleExpander::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Create a new [`ICU4XLocaleExpander`] with extended data. + #[diplomat::rust_link(icu::locid_transform::LocaleExpander::new_extended, FnInStruct)] + pub fn create_extended( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLocaleExpander>, ICU4XError> { + Ok(Box::new(ICU4XLocaleExpander(call_constructor!( + LocaleExpander::new_extended [r => Ok(r)], + LocaleExpander::try_new_with_any_provider, + LocaleExpander::try_new_with_buffer_provider, + provider, + )?))) + } + + /// FFI version of `LocaleExpander::maximize()`. + #[diplomat::rust_link(icu::locid_transform::LocaleExpander::maximize, FnInStruct)] + pub fn maximize(&self, locale: &mut ICU4XLocale) -> ICU4XTransformResult { + self.0.maximize(&mut locale.0).into() + } + + /// FFI version of `LocaleExpander::minimize()`. + #[diplomat::rust_link(icu::locid_transform::LocaleExpander::minimize, FnInStruct)] + pub fn minimize(&self, locale: &mut ICU4XLocale) -> ICU4XTransformResult { + self.0.minimize(&mut locale.0).into() + } + } +} diff --git a/intl/icu_capi/src/logging.rs b/intl/icu_capi/src/logging.rs new file mode 100644 index 0000000000..c84205bec5 --- /dev/null +++ b/intl/icu_capi/src/logging.rs @@ -0,0 +1,36 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + + #[diplomat::opaque] + /// An object allowing control over the logging used + #[diplomat::attr(dart, disable)] + pub struct ICU4XLogger; + + impl ICU4XLogger { + /// Initialize the logger using `simple_logger` + /// + /// Requires the `simple_logger` Cargo feature. + /// + /// Returns `false` if there was already a logger set. + #[cfg(all(not(target_arch = "wasm32"), feature = "simple_logger"))] + pub fn init_simple_logger() -> bool { + simple_logger::init().is_ok() + } + + /// Deprecated: since ICU4X 1.4, this now happens automatically if the `log` feature is enabled. + #[cfg(target_arch = "wasm32")] + pub fn init_console_logger() -> bool { + false + } + } +} + +// semver? +#[no_mangle] +#[cfg(target_arch = "wasm32")] +pub unsafe extern "C" fn icu4x_init() {} diff --git a/intl/icu_capi/src/metazone_calculator.rs b/intl/icu_capi/src/metazone_calculator.rs new file mode 100644 index 0000000000..e55ede26bd --- /dev/null +++ b/intl/icu_capi/src/metazone_calculator.rs @@ -0,0 +1,34 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use icu_timezone::MetazoneCalculator; + + /// An object capable of computing the metazone from a timezone. + /// + /// This can be used via `maybe_calculate_metazone()` on [`ICU4XCustomTimeZone`]. + /// + /// [`ICU4XCustomTimeZone`]: crate::timezone::ffi::ICU4XCustomTimeZone + #[diplomat::opaque] + #[diplomat::rust_link(icu::timezone::MetazoneCalculator, Struct)] + pub struct ICU4XMetazoneCalculator(pub MetazoneCalculator); + + impl ICU4XMetazoneCalculator { + #[diplomat::rust_link(icu::timezone::MetazoneCalculator::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XMetazoneCalculator>, ICU4XError> { + Ok(Box::new(ICU4XMetazoneCalculator(call_constructor!( + MetazoneCalculator::new [r => Ok(r)], + MetazoneCalculator::try_new_with_any_provider, + MetazoneCalculator::try_new_with_buffer_provider, + provider, + )?))) + } + } +} diff --git a/intl/icu_capi/src/normalizer.rs b/intl/icu_capi/src/normalizer.rs new file mode 100644 index 0000000000..13a1cf0292 --- /dev/null +++ b/intl/icu_capi/src/normalizer.rs @@ -0,0 +1,152 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::{errors::ffi::ICU4XError, provider::ffi::ICU4XDataProvider}; + use alloc::boxed::Box; + use diplomat_runtime::DiplomatWriteable; + use icu_normalizer::{ComposingNormalizer, DecomposingNormalizer}; + + #[diplomat::opaque] + #[diplomat::rust_link(icu::normalizer::ComposingNormalizer, Struct)] + pub struct ICU4XComposingNormalizer(pub ComposingNormalizer); + + impl ICU4XComposingNormalizer { + /// Construct a new ICU4XComposingNormalizer instance for NFC + #[diplomat::rust_link(icu::normalizer::ComposingNormalizer::new_nfc, FnInStruct)] + pub fn create_nfc( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XComposingNormalizer>, ICU4XError> { + Ok(Box::new(ICU4XComposingNormalizer(call_constructor!( + ComposingNormalizer::new_nfc [r => Ok(r)], + ComposingNormalizer::try_new_nfc_with_any_provider, + ComposingNormalizer::try_new_nfc_with_buffer_provider, + provider, + )?))) + } + + /// Construct a new ICU4XComposingNormalizer instance for NFKC + #[diplomat::rust_link(icu::normalizer::ComposingNormalizer::new_nfkc, FnInStruct)] + pub fn create_nfkc( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XComposingNormalizer>, ICU4XError> { + Ok(Box::new(ICU4XComposingNormalizer(call_constructor!( + ComposingNormalizer::new_nfkc [r => Ok(r)], + ComposingNormalizer::try_new_nfkc_with_any_provider, + ComposingNormalizer::try_new_nfkc_with_buffer_provider, + provider, + )?))) + } + + /// Normalize a (potentially ill-formed) UTF8 string + /// + /// Errors are mapped to REPLACEMENT CHARACTER + #[diplomat::rust_link(icu::normalizer::ComposingNormalizer::normalize_utf8, FnInStruct)] + #[diplomat::rust_link(icu::normalizer::ComposingNormalizer::normalize, FnInStruct, hidden)] + #[diplomat::rust_link( + icu::normalizer::ComposingNormalizer::normalize_to, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::normalizer::ComposingNormalizer::normalize_utf8_to, + FnInStruct, + hidden + )] + pub fn normalize(&self, s: &str, write: &mut DiplomatWriteable) -> Result<(), ICU4XError> { + let s = s.as_bytes(); // #2520 + self.0.normalize_utf8_to(s, write)?; + Ok(()) + } + + /// Check if a (potentially ill-formed) UTF8 string is normalized + /// + /// Errors are mapped to REPLACEMENT CHARACTER + #[diplomat::rust_link(icu::normalizer::ComposingNormalizer::is_normalized_utf8, FnInStruct)] + #[diplomat::rust_link( + icu::normalizer::ComposingNormalizer::is_normalized, + FnInStruct, + hidden + )] + pub fn is_normalized(&self, s: &str) -> bool { + let s = s.as_bytes(); // #2520 + self.0.is_normalized_utf8(s) + } + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::normalizer::DecomposingNormalizer, Struct)] + pub struct ICU4XDecomposingNormalizer(pub DecomposingNormalizer); + + impl ICU4XDecomposingNormalizer { + /// Construct a new ICU4XDecomposingNormalizer instance for NFC + #[diplomat::rust_link(icu::normalizer::DecomposingNormalizer::new_nfd, FnInStruct)] + pub fn create_nfd( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XDecomposingNormalizer>, ICU4XError> { + Ok(Box::new(ICU4XDecomposingNormalizer(call_constructor!( + DecomposingNormalizer::new_nfd [r => Ok(r)], + DecomposingNormalizer::try_new_nfd_with_any_provider, + DecomposingNormalizer::try_new_nfd_with_buffer_provider, + provider, + )?))) + } + + /// Construct a new ICU4XDecomposingNormalizer instance for NFKC + #[diplomat::rust_link(icu::normalizer::DecomposingNormalizer::new_nfkd, FnInStruct)] + pub fn create_nfkd( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XDecomposingNormalizer>, ICU4XError> { + Ok(Box::new(ICU4XDecomposingNormalizer(call_constructor!( + DecomposingNormalizer::new_nfkd [r => Ok(r)], + DecomposingNormalizer::try_new_nfkd_with_any_provider, + DecomposingNormalizer::try_new_nfkd_with_buffer_provider, + provider, + )?))) + } + + /// Normalize a (potentially ill-formed) UTF8 string + /// + /// Errors are mapped to REPLACEMENT CHARACTER + #[diplomat::rust_link(icu::normalizer::DecomposingNormalizer::normalize_utf8, FnInStruct)] + #[diplomat::rust_link( + icu::normalizer::DecomposingNormalizer::normalize, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::normalizer::DecomposingNormalizer::normalize_to, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::normalizer::DecomposingNormalizer::normalize_utf8_to, + FnInStruct, + hidden + )] + pub fn normalize(&self, s: &str, write: &mut DiplomatWriteable) -> Result<(), ICU4XError> { + let s = s.as_bytes(); // #2520 + self.0.normalize_utf8_to(s, write)?; + Ok(()) + } + + /// Check if a (potentially ill-formed) UTF8 string is normalized + /// + /// Errors are mapped to REPLACEMENT CHARACTER + #[diplomat::rust_link( + icu::normalizer::DecomposingNormalizer::is_normalized_utf8, + FnInStruct + )] + #[diplomat::rust_link( + icu::normalizer::DecomposingNormalizer::is_normalized, + FnInStruct, + hidden + )] + pub fn is_normalized(&self, s: &str) -> bool { + let s = s.as_bytes(); // #2520 + self.0.is_normalized_utf8(s) + } + } +} diff --git a/intl/icu_capi/src/normalizer_properties.rs b/intl/icu_capi/src/normalizer_properties.rs new file mode 100644 index 0000000000..dbb1dfbdbc --- /dev/null +++ b/intl/icu_capi/src/normalizer_properties.rs @@ -0,0 +1,144 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::{errors::ffi::ICU4XError, provider::ffi::ICU4XDataProvider}; + use alloc::boxed::Box; + use icu_normalizer::properties::{ + CanonicalCombiningClassMap, CanonicalComposition, CanonicalDecomposition, Decomposed, + }; + + /// Lookup of the Canonical_Combining_Class Unicode property + #[diplomat::opaque] + #[diplomat::rust_link(icu::normalizer::properties::CanonicalCombiningClassMap, Struct)] + pub struct ICU4XCanonicalCombiningClassMap(pub CanonicalCombiningClassMap); + + impl ICU4XCanonicalCombiningClassMap { + /// Construct a new ICU4XCanonicalCombiningClassMap instance for NFC + #[diplomat::rust_link( + icu::normalizer::properties::CanonicalCombiningClassMap::new, + FnInStruct + )] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCanonicalCombiningClassMap>, ICU4XError> { + Ok(Box::new(ICU4XCanonicalCombiningClassMap( + call_constructor!( + CanonicalCombiningClassMap::new [r => Ok(r)], + CanonicalCombiningClassMap::try_new_with_any_provider, + CanonicalCombiningClassMap::try_new_with_buffer_provider, + provider + )?, + ))) + } + + #[diplomat::rust_link( + icu::normalizer::properties::CanonicalCombiningClassMap::get, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::properties::CanonicalCombiningClass, + Struct, + compact + )] + pub fn get(&self, ch: char) -> u8 { + self.0.get(ch).0 + } + #[diplomat::rust_link( + icu::normalizer::properties::CanonicalCombiningClassMap::get32, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::properties::CanonicalCombiningClass, + Struct, + compact + )] + pub fn get32(&self, ch: u32) -> u8 { + self.0.get32(ch).0 + } + } + + /// The raw canonical composition operation. + /// + /// Callers should generally use ICU4XComposingNormalizer unless they specifically need raw composition operations + #[diplomat::opaque] + #[diplomat::rust_link(icu::normalizer::properties::CanonicalComposition, Struct)] + pub struct ICU4XCanonicalComposition(pub CanonicalComposition); + + impl ICU4XCanonicalComposition { + /// Construct a new ICU4XCanonicalComposition instance for NFC + #[diplomat::rust_link(icu::normalizer::properties::CanonicalComposition::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCanonicalComposition>, ICU4XError> { + Ok(Box::new(ICU4XCanonicalComposition(call_constructor!( + CanonicalComposition::new [r => Ok(r)], + CanonicalComposition::try_new_with_any_provider, + CanonicalComposition::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Performs canonical composition (including Hangul) on a pair of characters + /// or returns NUL if these characters don’t compose. Composition exclusions are taken into account. + #[diplomat::rust_link( + icu::normalizer::properties::CanonicalComposition::compose, + FnInStruct + )] + pub fn compose(&self, starter: char, second: char) -> char { + self.0.compose(starter, second).unwrap_or('\0') + } + } + + /// The outcome of non-recursive canonical decomposition of a character. + /// `second` will be NUL when the decomposition expands to a single character + /// (which may or may not be the original one) + #[diplomat::rust_link(icu::normalizer::properties::Decomposed, Enum)] + pub struct ICU4XDecomposed { + first: char, + second: char, + } + + /// The raw (non-recursive) canonical decomposition operation. + /// + /// Callers should generally use ICU4XDecomposingNormalizer unless they specifically need raw composition operations + #[diplomat::opaque] + #[diplomat::rust_link(icu::normalizer::properties::CanonicalDecomposition, Struct)] + pub struct ICU4XCanonicalDecomposition(pub CanonicalDecomposition); + + impl ICU4XCanonicalDecomposition { + /// Construct a new ICU4XCanonicalDecomposition instance for NFC + #[diplomat::rust_link(icu::normalizer::properties::CanonicalDecomposition::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCanonicalDecomposition>, ICU4XError> { + Ok(Box::new(ICU4XCanonicalDecomposition(call_constructor!( + CanonicalDecomposition::new [r => Ok(r)], + CanonicalDecomposition::try_new_with_any_provider, + CanonicalDecomposition::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Performs non-recursive canonical decomposition (including for Hangul). + #[diplomat::rust_link( + icu::normalizer::properties::CanonicalDecomposition::decompose, + FnInStruct + )] + pub fn decompose(&self, c: char) -> ICU4XDecomposed { + match self.0.decompose(c) { + Decomposed::Default => ICU4XDecomposed { + first: c, + second: '\0', + }, + Decomposed::Singleton(s) => ICU4XDecomposed { + first: s, + second: '\0', + }, + Decomposed::Expansion(first, second) => ICU4XDecomposed { first, second }, + } + } + } +} diff --git a/intl/icu_capi/src/pluralrules.rs b/intl/icu_capi/src/pluralrules.rs new file mode 100644 index 0000000000..78720f536e --- /dev/null +++ b/intl/icu_capi/src/pluralrules.rs @@ -0,0 +1,150 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use core::str::{self}; + + use alloc::boxed::Box; + + use fixed_decimal::FixedDecimal; + use icu_plurals::{PluralCategory, PluralOperands, PluralRules}; + + use crate::{locale::ffi::ICU4XLocale, provider::ffi::ICU4XDataProvider}; + + use crate::errors::ffi::ICU4XError; + + /// FFI version of `PluralCategory`. + #[diplomat::rust_link(icu::plurals::PluralCategory, Enum)] + #[diplomat::enum_convert(PluralCategory)] + pub enum ICU4XPluralCategory { + Zero, + One, + Two, + Few, + Many, + Other, + } + + impl ICU4XPluralCategory { + /// Construct from a string in the format + /// [specified in TR35](https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules) + #[diplomat::rust_link(icu::plurals::PluralCategory::get_for_cldr_string, FnInEnum)] + #[diplomat::rust_link(icu::plurals::PluralCategory::get_for_cldr_bytes, FnInEnum)] + pub fn get_for_cldr_string(s: &str) -> Result<ICU4XPluralCategory, ()> { + let s = s.as_bytes(); // #2520 + PluralCategory::get_for_cldr_bytes(s) + .ok_or(()) + .map(Into::into) + } + } + + /// FFI version of `PluralRules`. + #[diplomat::rust_link(icu::plurals::PluralRules, Struct)] + #[diplomat::opaque] + pub struct ICU4XPluralRules(PluralRules); + + impl ICU4XPluralRules { + /// Construct an [`ICU4XPluralRules`] for the given locale, for cardinal numbers + #[diplomat::rust_link(icu::plurals::PluralRules::try_new_cardinal, FnInStruct)] + #[diplomat::rust_link(icu::plurals::PluralRules::try_new, FnInStruct, hidden)] + #[diplomat::rust_link(icu::plurals::PluralRuleType, Enum, hidden)] + pub fn create_cardinal( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XPluralRules>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XPluralRules(call_constructor!( + PluralRules::try_new_cardinal, + PluralRules::try_new_cardinal_with_any_provider, + PluralRules::try_new_cardinal_with_buffer_provider, + provider, + &locale + )?))) + } + + /// Construct an [`ICU4XPluralRules`] for the given locale, for ordinal numbers + #[diplomat::rust_link(icu::plurals::PluralRules::try_new_ordinal, FnInStruct)] + #[diplomat::rust_link(icu::plurals::PluralRules::try_new, FnInStruct, hidden)] + #[diplomat::rust_link(icu::plurals::PluralRuleType, Enum, hidden)] + pub fn create_ordinal( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XPluralRules>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XPluralRules(call_constructor!( + PluralRules::try_new_ordinal, + PluralRules::try_new_ordinal_with_any_provider, + PluralRules::try_new_ordinal_with_buffer_provider, + provider, + &locale + )?))) + } + + /// Get the category for a given number represented as operands + #[diplomat::rust_link(icu::plurals::PluralRules::category_for, FnInStruct)] + pub fn category_for(&self, op: &ICU4XPluralOperands) -> ICU4XPluralCategory { + self.0.category_for(op.0).into() + } + + /// Get all of the categories needed in the current locale + #[diplomat::rust_link(icu::plurals::PluralRules::categories, FnInStruct)] + pub fn categories(&self) -> ICU4XPluralCategories { + ICU4XPluralCategories::from_iter(self.0.categories()) + } + } + + /// FFI version of `PluralOperands`. + #[diplomat::opaque] + #[diplomat::rust_link(icu::plurals::PluralOperands, Struct)] + pub struct ICU4XPluralOperands(pub icu_plurals::PluralOperands); + + impl ICU4XPluralOperands { + /// Construct for a given string representing a number + #[diplomat::rust_link(icu::plurals::PluralOperands::from_str, FnInStruct)] + pub fn create_from_string(s: &str) -> Result<Box<ICU4XPluralOperands>, ICU4XError> { + let s = s.as_bytes(); // #2520 + Ok(Box::new(ICU4XPluralOperands(PluralOperands::from( + // XXX should this have its own errors? + &FixedDecimal::try_from(s).map_err(|_| ICU4XError::PluralsParserError)?, + )))) + } + } + + /// FFI version of `PluralRules::categories()` data. + pub struct ICU4XPluralCategories { + pub zero: bool, + pub one: bool, + pub two: bool, + pub few: bool, + pub many: bool, + pub other: bool, + } + + impl ICU4XPluralCategories { + fn from_iter(i: impl Iterator<Item = PluralCategory>) -> Self { + i.fold( + ICU4XPluralCategories { + zero: false, + one: false, + two: false, + few: false, + many: false, + other: false, + }, + |mut categories, category| { + match category { + PluralCategory::Zero => categories.zero = true, + PluralCategory::One => categories.one = true, + PluralCategory::Two => categories.two = true, + PluralCategory::Few => categories.few = true, + PluralCategory::Many => categories.many = true, + PluralCategory::Other => categories.other = true, + }; + categories + }, + ) + } + } +} diff --git a/intl/icu_capi/src/properties_iter.rs b/intl/icu_capi/src/properties_iter.rs new file mode 100644 index 0000000000..e0095789f5 --- /dev/null +++ b/intl/icu_capi/src/properties_iter.rs @@ -0,0 +1,48 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use core::ops::RangeInclusive; + + /// Result of a single iteration of [`CodePointRangeIterator`]. + /// Logically can be considered to be an `Option<RangeInclusive<u32>>`, + /// + /// `start` and `end` represent an inclusive range of code points [start, end], + /// and `done` will be true if the iterator has already finished. The last contentful + /// iteration will NOT produce a range done=true, in other words `start` and `end` are useful + /// values if and only if `done=false`. + pub struct CodePointRangeIteratorResult { + pub start: u32, + pub end: u32, + pub done: bool, + } + + /// An iterator over code point ranges, produced by `ICU4XCodePointSetData` or + /// one of the `ICU4XCodePointMapData` types + #[diplomat::opaque] + pub struct CodePointRangeIterator<'a>(pub Box<dyn Iterator<Item = RangeInclusive<u32>> + 'a>); + + impl<'a> CodePointRangeIterator<'a> { + /// Advance the iterator by one and return the next range. + /// + /// If the iterator is out of items, `done` will be true + #[allow(clippy::should_implement_trait)] // Rust isn't calling this code + pub fn next(&mut self) -> CodePointRangeIteratorResult { + self.0 + .next() + .map(|r| CodePointRangeIteratorResult { + start: *r.start(), + end: *r.end(), + done: false, + }) + .unwrap_or(CodePointRangeIteratorResult { + start: 0, + end: 0, + done: true, + }) + } + } +} diff --git a/intl/icu_capi/src/properties_maps.rs b/intl/icu_capi/src/properties_maps.rs new file mode 100644 index 0000000000..3d5cd6dfce --- /dev/null +++ b/intl/icu_capi/src/properties_maps.rs @@ -0,0 +1,311 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use icu_collections::codepointtrie::TrieValue; + use icu_properties::{maps, GeneralCategory, GeneralCategoryGroup}; + + use crate::errors::ffi::ICU4XError; + use crate::properties_iter::ffi::CodePointRangeIterator; + use crate::properties_sets::ffi::ICU4XCodePointSetData; + + #[diplomat::opaque] + /// An ICU4X Unicode Map Property object, capable of querying whether a code point (key) to obtain the Unicode property value, for a specific Unicode property. + /// + /// For properties whose values fit into 8 bits. + #[diplomat::rust_link(icu::properties, Mod)] + #[diplomat::rust_link(icu::properties::maps::CodePointMapData, Struct)] + #[diplomat::rust_link(icu::properties::maps::CodePointMapData::from_data, FnInStruct, hidden)] + #[diplomat::rust_link( + icu::properties::maps::CodePointMapData::try_into_converted, + FnInStruct, + hidden + )] + #[diplomat::rust_link(icu::properties::maps::CodePointMapDataBorrowed, Struct)] + pub struct ICU4XCodePointMapData8(maps::CodePointMapData<u8>); + + fn convert_8<P: TrieValue>(data: maps::CodePointMapData<P>) -> Box<ICU4XCodePointMapData8> { + #[allow(clippy::expect_used)] // infallible for the chosen properties + Box::new(ICU4XCodePointMapData8( + data.try_into_converted() + .expect("try_into_converted to u8 must be infallible"), + )) + } + + impl ICU4XCodePointMapData8 { + /// Gets the value for a code point. + #[diplomat::rust_link(icu::properties::maps::CodePointMapDataBorrowed::get, FnInStruct)] + pub fn get(&self, cp: char) -> u8 { + self.0.as_borrowed().get(cp) + } + + /// Gets the value for a code point (specified as a 32 bit integer, in UTF-32) + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::get32, + FnInStruct, + hidden + )] + pub fn get32(&self, cp: u32) -> u8 { + self.0.as_borrowed().get32(cp) + } + + /// Converts a general category to its corresponding mask value + /// + /// Nonexistant general categories will map to the empty mask + #[diplomat::rust_link(icu::properties::GeneralCategoryGroup, Struct)] + pub fn general_category_to_mask(gc: u8) -> u32 { + if let Ok(gc) = GeneralCategory::try_from(gc) { + let group: GeneralCategoryGroup = gc.into(); + group.into() + } else { + 0 + } + } + + /// Produces an iterator over ranges of code points that map to `value` + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::iter_ranges_for_value, + FnInStruct + )] + pub fn iter_ranges_for_value<'a>(&'a self, value: u8) -> Box<CodePointRangeIterator<'a>> { + Box::new(CodePointRangeIterator(Box::new( + self.0.as_borrowed().iter_ranges_for_value(value), + ))) + } + + /// Produces an iterator over ranges of code points that do not map to `value` + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::iter_ranges_for_value_complemented, + FnInStruct + )] + pub fn iter_ranges_for_value_complemented<'a>( + &'a self, + value: u8, + ) -> Box<CodePointRangeIterator<'a>> { + Box::new(CodePointRangeIterator(Box::new( + self.0 + .as_borrowed() + .iter_ranges_for_value_complemented(value), + ))) + } + + /// Given a mask value (the nth bit marks property value = n), produce an iterator over ranges of code points + /// whose property values are contained in the mask. + /// + /// The main mask property supported is that for General_Category, which can be obtained via `general_category_to_mask()` or + /// by using `ICU4XGeneralCategoryNameToMaskMapper` + /// + /// Should only be used on maps for properties with values less than 32 (like Generak_Category), + /// other maps will have unpredictable results + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::iter_ranges_for_group, + FnInStruct + )] + pub fn iter_ranges_for_mask<'a>(&'a self, mask: u32) -> Box<CodePointRangeIterator<'a>> { + let ranges = self + .0 + .as_borrowed() + .iter_ranges_mapped(move |v| { + let val_mask = 1_u32.checked_shl(v.into()).unwrap_or(0); + val_mask & mask != 0 + }) + .filter(|v| v.value) + .map(|v| v.range); + Box::new(CodePointRangeIterator(Box::new(ranges))) + } + + /// Gets a [`ICU4XCodePointSetData`] representing all entries in this map that map to the given value + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::get_set_for_value, + FnInStruct + )] + pub fn get_set_for_value(&self, value: u8) -> Box<ICU4XCodePointSetData> { + Box::new(ICU4XCodePointSetData( + self.0.as_borrowed().get_set_for_value(value), + )) + } + + #[diplomat::rust_link(icu::properties::maps::general_category, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_general_category, Fn, hidden)] + pub fn load_general_category( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::general_category [r => Ok(r.static_to_owned())], + maps::load_general_category, + provider, + )?)) + } + + #[diplomat::rust_link(icu::properties::maps::bidi_class, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_bidi_class, Fn, hidden)] + pub fn load_bidi_class( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::bidi_class [r => Ok(r.static_to_owned())], + maps::load_bidi_class, + provider, + )?)) + } + + #[diplomat::rust_link(icu::properties::maps::east_asian_width, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_east_asian_width, Fn, hidden)] + pub fn load_east_asian_width( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::east_asian_width [r => Ok(r.static_to_owned())], + maps::load_east_asian_width, + provider, + )?)) + } + + #[diplomat::rust_link(icu::properties::maps::indic_syllabic_category, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_indic_syllabic_category, Fn, hidden)] + pub fn load_indic_syllabic_category( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::indic_syllabic_category [r => Ok(r.static_to_owned())], + maps::load_indic_syllabic_category, + provider, + )?)) + } + + #[diplomat::rust_link(icu::properties::maps::line_break, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_line_break, Fn, hidden)] + pub fn load_line_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::line_break [r => Ok(r.static_to_owned())], + maps::load_line_break, + provider, + )?)) + } + + #[diplomat::rust_link(icu::properties::maps::grapheme_cluster_break, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_grapheme_cluster_break, Fn, hidden)] + #[diplomat::attr(dart, rename = "grapheme_cluster_break")] + pub fn try_grapheme_cluster_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::grapheme_cluster_break [r => Ok(r.static_to_owned())], + maps::load_grapheme_cluster_break, + provider, + )?)) + } + + #[diplomat::rust_link(icu::properties::maps::word_break, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_word_break, Fn, hidden)] + pub fn load_word_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::word_break [r => Ok(r.static_to_owned())], + maps::load_word_break, + provider, + )?)) + } + + #[diplomat::rust_link(icu::properties::maps::sentence_break, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_sentence_break, Fn, hidden)] + pub fn load_sentence_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData8>, ICU4XError> { + Ok(convert_8(call_constructor_unstable!( + maps::sentence_break [r => Ok(r.static_to_owned())], + maps::load_sentence_break, + provider, + )?)) + } + } + + #[diplomat::opaque] + /// An ICU4X Unicode Map Property object, capable of querying whether a code point (key) to obtain the Unicode property value, for a specific Unicode property. + /// + /// For properties whose values fit into 16 bits. + #[diplomat::rust_link(icu::properties, Mod)] + #[diplomat::rust_link(icu::properties::maps::CodePointMapData, Struct)] + #[diplomat::rust_link(icu::properties::maps::CodePointMapDataBorrowed, Struct)] + pub struct ICU4XCodePointMapData16(maps::CodePointMapData<u16>); + + impl ICU4XCodePointMapData16 { + /// Gets the value for a code point. + #[diplomat::rust_link(icu::properties::maps::CodePointMapDataBorrowed::get, FnInStruct)] + pub fn get(&self, cp: char) -> u16 { + self.0.as_borrowed().get(cp) + } + + /// Gets the value for a code point (specified as a 32 bit integer, in UTF-32) + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::get32, + FnInStruct, + hidden + )] + pub fn get32(&self, cp: u32) -> u16 { + self.0.as_borrowed().get32(cp) + } + + /// Produces an iterator over ranges of code points that map to `value` + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::iter_ranges_for_value, + FnInStruct + )] + pub fn iter_ranges_for_value<'a>(&'a self, value: u16) -> Box<CodePointRangeIterator<'a>> { + Box::new(CodePointRangeIterator(Box::new( + self.0.as_borrowed().iter_ranges_for_value(value), + ))) + } + + /// Produces an iterator over ranges of code points that do not map to `value` + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::iter_ranges_for_value_complemented, + FnInStruct + )] + pub fn iter_ranges_for_value_complemented<'a>( + &'a self, + value: u16, + ) -> Box<CodePointRangeIterator<'a>> { + Box::new(CodePointRangeIterator(Box::new( + self.0 + .as_borrowed() + .iter_ranges_for_value_complemented(value), + ))) + } + + /// Gets a [`ICU4XCodePointSetData`] representing all entries in this map that map to the given value + #[diplomat::rust_link( + icu::properties::maps::CodePointMapDataBorrowed::get_set_for_value, + FnInStruct + )] + pub fn get_set_for_value(&self, value: u16) -> Box<ICU4XCodePointSetData> { + Box::new(ICU4XCodePointSetData( + self.0.as_borrowed().get_set_for_value(value), + )) + } + + #[diplomat::rust_link(icu::properties::maps::script, Fn)] + #[diplomat::rust_link(icu::properties::maps::load_script, Fn, hidden)] + pub fn load_script( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointMapData16>, ICU4XError> { + #[allow(clippy::expect_used)] // script is a 16-bit property + Ok(Box::new(ICU4XCodePointMapData16( + call_constructor_unstable!( + maps::script [r => Ok(r.static_to_owned())], + maps::load_script, + provider, + )? + .try_into_converted() + .expect("try_into_converted to u16 must be infallible"), + ))) + } + } +} diff --git a/intl/icu_capi/src/properties_names.rs b/intl/icu_capi/src/properties_names.rs new file mode 100644 index 0000000000..a5ab0e143a --- /dev/null +++ b/intl/icu_capi/src/properties_names.rs @@ -0,0 +1,259 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use icu_properties::{ + names::PropertyValueNameToEnumMapper, BidiClass, EastAsianWidth, GeneralCategory, + GeneralCategoryGroup, GraphemeClusterBreak, IndicSyllabicCategory, LineBreak, Script, + SentenceBreak, WordBreak, + }; + + use crate::errors::ffi::ICU4XError; + + /// A type capable of looking up a property value from a string name. + #[diplomat::opaque] + #[diplomat::rust_link(icu::properties::names::PropertyValueNameToEnumMapper, Struct)] + #[diplomat::rust_link(icu::properties::names::PropertyValueNameToEnumMapperBorrowed, Struct)] + #[diplomat::rust_link( + icu::properties::names::PropertyValueNameToEnumMapper::from_data, + FnInStruct, + hidden + )] + pub struct ICU4XPropertyValueNameToEnumMapper(PropertyValueNameToEnumMapper<u16>); + + impl ICU4XPropertyValueNameToEnumMapper { + /// Get the property value matching the given name, using strict matching + /// + /// Returns -1 if the name is unknown for this property + #[diplomat::rust_link( + icu::properties::names::PropertyValueNameToEnumMapperBorrowed::get_strict, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::names::PropertyValueNameToEnumMapperBorrowed::get_strict_u16, + FnInStruct, + hidden + )] + pub fn get_strict(&self, name: &str) -> i16 { + self.0 + .as_borrowed() + .get_strict(name) + .map(|x| x as i16) + .unwrap_or(-1) + } + + /// Get the property value matching the given name, using loose matching + /// + /// Returns -1 if the name is unknown for this property + #[diplomat::rust_link( + icu::properties::names::PropertyValueNameToEnumMapperBorrowed::get_loose, + FnInStruct + )] + #[diplomat::rust_link( + icu::properties::names::PropertyValueNameToEnumMapperBorrowed::get_loose_u16, + FnInStruct, + hidden + )] + pub fn get_loose(&self, name: &str) -> i16 { + self.0 + .as_borrowed() + .get_loose(name) + .map(|x| x as i16) + .unwrap_or(-1) + } + + #[diplomat::rust_link( + icu::properties::GeneralCategory::get_name_to_enum_mapper, + FnInStruct + )] + pub fn load_general_category( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + GeneralCategory::name_to_enum_mapper [r => Ok(r.static_to_owned())], + GeneralCategory::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link(icu::properties::BidiClass::name_to_enum_mapper, FnInStruct)] + pub fn load_bidi_class( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + BidiClass::name_to_enum_mapper [r => Ok(r.static_to_owned())], + BidiClass::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link(icu::properties::EastAsianWidth::name_to_enum_mapper, FnInStruct)] + pub fn load_east_asian_width( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + EastAsianWidth::name_to_enum_mapper [r => Ok(r.static_to_owned())], + EastAsianWidth::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link( + icu::properties::IndicSyllabicCategory::name_to_enum_mapper, + FnInStruct + )] + pub fn load_indic_syllabic_category( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + IndicSyllabicCategory::name_to_enum_mapper [r => Ok(r.static_to_owned())], + IndicSyllabicCategory::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link(icu::properties::LineBreak::name_to_enum_mapper, FnInStruct)] + pub fn load_line_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + LineBreak::name_to_enum_mapper [r => Ok(r.static_to_owned())], + LineBreak::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link( + icu::properties::GraphemeClusterBreak::get_name_to_enum_mapper, + FnInStruct + )] + pub fn load_grapheme_cluster_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + GraphemeClusterBreak::name_to_enum_mapper [r => Ok(r.static_to_owned())], + GraphemeClusterBreak::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link(icu::properties::WordBreak::name_to_enum_mapper, FnInStruct)] + pub fn load_word_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + WordBreak::name_to_enum_mapper [r => Ok(r.static_to_owned())], + WordBreak::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link(icu::properties::SentenceBreak::name_to_enum_mapper, FnInStruct)] + pub fn load_sentence_break( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + SentenceBreak::name_to_enum_mapper [r => Ok(r.static_to_owned())], + SentenceBreak::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + + #[diplomat::rust_link(icu::properties::Script::name_to_enum_mapper, FnInStruct)] + pub fn load_script( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XPropertyValueNameToEnumMapper>, ICU4XError> { + Ok(Box::new(ICU4XPropertyValueNameToEnumMapper( + call_constructor_unstable!( + Script::name_to_enum_mapper [r => Ok(r.static_to_owned())], + Script::get_name_to_enum_mapper, + provider, + )? + .erase(), + ))) + } + } + + /// A type capable of looking up General Category mask values from a string name. + #[diplomat::opaque] + #[diplomat::rust_link( + icu::properties::GeneralCategoryGroup::get_name_to_enum_mapper, + FnInStruct + )] + #[diplomat::rust_link(icu::properties::names::PropertyValueNameToEnumMapper, Struct)] + pub struct ICU4XGeneralCategoryNameToMaskMapper( + PropertyValueNameToEnumMapper<GeneralCategoryGroup>, + ); + + impl ICU4XGeneralCategoryNameToMaskMapper { + /// Get the mask value matching the given name, using strict matching + /// + /// Returns 0 if the name is unknown for this property + // #[diplomat::rust_link(icu::properties::maps::PropertyValueNameToEnumMapperBorrowed::get_strict, FnInStruct)] + // #[diplomat::rust_link(icu::properties::maps::PropertyValueNameToEnumMapperBorrowed::get_strict_u16, FnInStruct, hidden)] + pub fn get_strict(&self, name: &str) -> u32 { + self.0 + .as_borrowed() + .get_strict(name) + .map(Into::into) + .unwrap_or(0) + } + + /// Get the mask value matching the given name, using loose matching + /// + /// Returns 0 if the name is unknown for this property + // #[diplomat::rust_link(icu::properties::maps::PropertyValueNameToEnumMapperBorrowed::get_loose, FnInStruct)] + // #[diplomat::rust_link(icu::properties::maps::PropertyValueNameToEnumMapperBorrowed::get_loose_u16, FnInStruct, hidden)] + pub fn get_loose(&self, name: &str) -> u32 { + self.0 + .as_borrowed() + .get_loose(name) + .map(Into::into) + .unwrap_or(0) + } + + #[diplomat::rust_link( + icu::properties::GeneralCategoryGroup::get_name_to_enum_mapper, + FnInStruct + )] + pub fn load( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XGeneralCategoryNameToMaskMapper>, ICU4XError> { + Ok(Box::new(ICU4XGeneralCategoryNameToMaskMapper( + call_constructor_unstable!( + GeneralCategoryGroup::name_to_enum_mapper [r => Ok(r.static_to_owned())], + GeneralCategoryGroup::get_name_to_enum_mapper, + provider, + )?, + ))) + } + } +} diff --git a/intl/icu_capi/src/properties_sets.rs b/intl/icu_capi/src/properties_sets.rs new file mode 100644 index 0000000000..af14b9cbc8 --- /dev/null +++ b/intl/icu_capi/src/properties_sets.rs @@ -0,0 +1,887 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use core::str; + use icu_properties::sets; + + use crate::errors::ffi::ICU4XError; + use crate::properties_iter::ffi::CodePointRangeIterator; + + #[diplomat::opaque] + /// An ICU4X Unicode Set Property object, capable of querying whether a code point is contained in a set based on a Unicode property. + #[diplomat::rust_link(icu::properties, Mod)] + #[diplomat::rust_link(icu::properties::sets::CodePointSetData, Struct)] + #[diplomat::rust_link(icu::properties::sets::CodePointSetData::from_data, FnInStruct, hidden)] + #[diplomat::rust_link(icu::properties::sets::CodePointSetDataBorrowed, Struct)] + pub struct ICU4XCodePointSetData(pub sets::CodePointSetData); + + impl ICU4XCodePointSetData { + /// Checks whether the code point is in the set. + #[diplomat::rust_link( + icu::properties::sets::CodePointSetDataBorrowed::contains, + FnInStruct + )] + pub fn contains(&self, cp: char) -> bool { + self.0.as_borrowed().contains(cp) + } + /// Checks whether the code point (specified as a 32 bit integer, in UTF-32) is in the set. + #[diplomat::rust_link( + icu::properties::sets::CodePointSetDataBorrowed::contains32, + FnInStruct, + hidden + )] + #[diplomat::attr(dart, disable)] + pub fn contains32(&self, cp: u32) -> bool { + self.0.as_borrowed().contains32(cp) + } + + /// Produces an iterator over ranges of code points contained in this set + #[diplomat::rust_link( + icu::properties::sets::CodePointSetDataBorrowed::iter_ranges, + FnInStruct + )] + pub fn iter_ranges<'a>(&'a self) -> Box<CodePointRangeIterator<'a>> { + Box::new(CodePointRangeIterator(Box::new( + self.0.as_borrowed().iter_ranges(), + ))) + } + + /// Produces an iterator over ranges of code points not contained in this set + #[diplomat::rust_link( + icu::properties::sets::CodePointSetDataBorrowed::iter_ranges_complemented, + FnInStruct + )] + pub fn iter_ranges_complemented<'a>(&'a self) -> Box<CodePointRangeIterator<'a>> { + Box::new(CodePointRangeIterator(Box::new( + self.0.as_borrowed().iter_ranges_complemented(), + ))) + } + + /// which is a mask with the same format as the `U_GC_XX_MASK` mask in ICU4C + #[diplomat::rust_link(icu::properties::sets::for_general_category_group, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_for_general_category_group, Fn, hidden)] + pub fn load_for_general_category_group( + provider: &ICU4XDataProvider, + group: u32, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::for_general_category_group [r => Ok(r)], + sets::load_for_general_category_group, + provider, + group.into(), + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::ascii_hex_digit, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_ascii_hex_digit, Fn, hidden)] + pub fn load_ascii_hex_digit( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::ascii_hex_digit [r => Ok(r.static_to_owned())], + sets::load_ascii_hex_digit, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::alnum, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_alnum, Fn, hidden)] + pub fn load_alnum( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::alnum [r => Ok(r.static_to_owned())], + sets::load_alnum, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::alphabetic, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_alphabetic, Fn, hidden)] + pub fn load_alphabetic( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::alphabetic [r => Ok(r.static_to_owned())], + sets::load_alphabetic, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::bidi_control, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_bidi_control, Fn, hidden)] + pub fn load_bidi_control( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::bidi_control [r => Ok(r.static_to_owned())], + sets::load_bidi_control, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::bidi_mirrored, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_bidi_mirrored, Fn, hidden)] + pub fn load_bidi_mirrored( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::bidi_mirrored [r => Ok(r.static_to_owned())], + sets::load_bidi_mirrored, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::blank, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_blank, Fn, hidden)] + pub fn load_blank( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::blank [r => Ok(r.static_to_owned())], + sets::load_blank, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::cased, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_cased, Fn, hidden)] + pub fn load_cased( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::cased [r => Ok(r.static_to_owned())], + sets::load_cased, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::case_ignorable, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_case_ignorable, Fn, hidden)] + pub fn load_case_ignorable( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::case_ignorable [r => Ok(r.static_to_owned())], + sets::load_case_ignorable, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::full_composition_exclusion, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_full_composition_exclusion, Fn, hidden)] + pub fn load_full_composition_exclusion( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::full_composition_exclusion [r => Ok(r.static_to_owned())], + sets::load_full_composition_exclusion, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::changes_when_casefolded, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_changes_when_casefolded, Fn, hidden)] + pub fn load_changes_when_casefolded( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::changes_when_casefolded [r => Ok(r.static_to_owned())], + sets::load_changes_when_casefolded, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::changes_when_casemapped, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_changes_when_casemapped, Fn, hidden)] + pub fn load_changes_when_casemapped( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::changes_when_casemapped [r => Ok(r.static_to_owned())], + sets::load_changes_when_casemapped, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::changes_when_nfkc_casefolded, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_changes_when_nfkc_casefolded, Fn, hidden)] + pub fn load_changes_when_nfkc_casefolded( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::changes_when_nfkc_casefolded [r => Ok(r.static_to_owned())], + sets::load_changes_when_nfkc_casefolded, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::changes_when_lowercased, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_changes_when_lowercased, Fn, hidden)] + pub fn load_changes_when_lowercased( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::changes_when_lowercased [r => Ok(r.static_to_owned())], + sets::load_changes_when_lowercased, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::changes_when_titlecased, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_changes_when_titlecased, Fn, hidden)] + pub fn load_changes_when_titlecased( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::changes_when_titlecased [r => Ok(r.static_to_owned())], + sets::load_changes_when_titlecased, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::changes_when_uppercased, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_changes_when_uppercased, Fn, hidden)] + pub fn load_changes_when_uppercased( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::changes_when_uppercased [r => Ok(r.static_to_owned())], + sets::load_changes_when_uppercased, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::dash, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_dash, Fn, hidden)] + pub fn load_dash( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::dash [r => Ok(r.static_to_owned())], + sets::load_dash, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::deprecated, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_deprecated, Fn, hidden)] + pub fn load_deprecated( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::deprecated [r => Ok(r.static_to_owned())], + sets::load_deprecated, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::default_ignorable_code_point, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_default_ignorable_code_point, Fn, hidden)] + pub fn load_default_ignorable_code_point( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::default_ignorable_code_point [r => Ok(r.static_to_owned())], + sets::load_default_ignorable_code_point, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::diacritic, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_diacritic, Fn, hidden)] + pub fn load_diacritic( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::diacritic [r => Ok(r.static_to_owned())], + sets::load_diacritic, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::emoji_modifier_base, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_emoji_modifier_base, Fn, hidden)] + pub fn load_emoji_modifier_base( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::emoji_modifier_base [r => Ok(r.static_to_owned())], + sets::load_emoji_modifier_base, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::emoji_component, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_emoji_component, Fn, hidden)] + pub fn load_emoji_component( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::emoji_component [r => Ok(r.static_to_owned())], + sets::load_emoji_component, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::emoji_modifier, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_emoji_modifier, Fn, hidden)] + pub fn load_emoji_modifier( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::emoji_modifier [r => Ok(r.static_to_owned())], + sets::load_emoji_modifier, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::emoji, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_emoji, Fn, hidden)] + pub fn load_emoji( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::emoji [r => Ok(r.static_to_owned())], + sets::load_emoji, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::emoji_presentation, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_emoji_presentation, Fn, hidden)] + pub fn load_emoji_presentation( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::emoji_presentation [r => Ok(r.static_to_owned())], + sets::load_emoji_presentation, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::extender, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_extender, Fn, hidden)] + pub fn load_extender( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::extender [r => Ok(r.static_to_owned())], + sets::load_extender, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::extended_pictographic, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_extended_pictographic, Fn, hidden)] + pub fn load_extended_pictographic( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::extended_pictographic [r => Ok(r.static_to_owned())], + sets::load_extended_pictographic, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::graph, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_graph, Fn, hidden)] + pub fn load_graph( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::graph [r => Ok(r.static_to_owned())], + sets::load_graph, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::grapheme_base, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_grapheme_base, Fn, hidden)] + pub fn load_grapheme_base( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::grapheme_base [r => Ok(r.static_to_owned())], + sets::load_grapheme_base, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::grapheme_extend, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_grapheme_extend, Fn, hidden)] + pub fn load_grapheme_extend( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::grapheme_extend [r => Ok(r.static_to_owned())], + sets::load_grapheme_extend, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::grapheme_link, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_grapheme_link, Fn, hidden)] + pub fn load_grapheme_link( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::grapheme_link [r => Ok(r.static_to_owned())], + sets::load_grapheme_link, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::hex_digit, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_hex_digit, Fn, hidden)] + pub fn load_hex_digit( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::hex_digit [r => Ok(r.static_to_owned())], + sets::load_hex_digit, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::hyphen, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_hyphen, Fn, hidden)] + pub fn load_hyphen( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::hyphen [r => Ok(r.static_to_owned())], + sets::load_hyphen, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::id_continue, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_id_continue, Fn, hidden)] + pub fn load_id_continue( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::id_continue [r => Ok(r.static_to_owned())], + sets::load_id_continue, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::ideographic, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_ideographic, Fn, hidden)] + pub fn load_ideographic( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::ideographic [r => Ok(r.static_to_owned())], + sets::load_ideographic, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::id_start, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_id_start, Fn, hidden)] + pub fn load_id_start( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::id_start [r => Ok(r.static_to_owned())], + sets::load_id_start, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::ids_binary_operator, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_ids_binary_operator, Fn, hidden)] + pub fn load_ids_binary_operator( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::ids_binary_operator [r => Ok(r.static_to_owned())], + sets::load_ids_binary_operator, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::ids_trinary_operator, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_ids_trinary_operator, Fn, hidden)] + pub fn load_ids_trinary_operator( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::ids_trinary_operator [r => Ok(r.static_to_owned())], + sets::load_ids_trinary_operator, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::join_control, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_join_control, Fn, hidden)] + pub fn load_join_control( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::join_control [r => Ok(r.static_to_owned())], + sets::load_join_control, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::logical_order_exception, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_logical_order_exception, Fn, hidden)] + pub fn load_logical_order_exception( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::logical_order_exception [r => Ok(r.static_to_owned())], + sets::load_logical_order_exception, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::lowercase, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_lowercase, Fn, hidden)] + pub fn load_lowercase( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::lowercase [r => Ok(r.static_to_owned())], + sets::load_lowercase, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::math, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_math, Fn, hidden)] + pub fn load_math( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::math [r => Ok(r.static_to_owned())], + sets::load_math, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::noncharacter_code_point, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_noncharacter_code_point, Fn, hidden)] + pub fn load_noncharacter_code_point( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::noncharacter_code_point [r => Ok(r.static_to_owned())], + sets::load_noncharacter_code_point, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::nfc_inert, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_nfc_inert, Fn, hidden)] + pub fn load_nfc_inert( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::nfc_inert [r => Ok(r.static_to_owned())], + sets::load_nfc_inert, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::nfd_inert, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_nfd_inert, Fn, hidden)] + pub fn load_nfd_inert( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::nfd_inert [r => Ok(r.static_to_owned())], + sets::load_nfd_inert, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::nfkc_inert, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_nfkc_inert, Fn, hidden)] + pub fn load_nfkc_inert( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::nfkc_inert [r => Ok(r.static_to_owned())], + sets::load_nfkc_inert, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::nfkd_inert, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_nfkd_inert, Fn, hidden)] + pub fn load_nfkd_inert( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::nfkd_inert [r => Ok(r.static_to_owned())], + sets::load_nfkd_inert, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::pattern_syntax, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_pattern_syntax, Fn, hidden)] + pub fn load_pattern_syntax( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::pattern_syntax [r => Ok(r.static_to_owned())], + sets::load_pattern_syntax, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::pattern_white_space, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_pattern_white_space, Fn, hidden)] + pub fn load_pattern_white_space( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::pattern_white_space [r => Ok(r.static_to_owned())], + sets::load_pattern_white_space, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::prepended_concatenation_mark, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_prepended_concatenation_mark, Fn, hidden)] + pub fn load_prepended_concatenation_mark( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::prepended_concatenation_mark [r => Ok(r.static_to_owned())], + sets::load_prepended_concatenation_mark, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::print, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_print, Fn, hidden)] + pub fn load_print( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::print [r => Ok(r.static_to_owned())], + sets::load_print, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::quotation_mark, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_quotation_mark, Fn, hidden)] + pub fn load_quotation_mark( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::quotation_mark [r => Ok(r.static_to_owned())], + sets::load_quotation_mark, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::radical, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_radical, Fn, hidden)] + pub fn load_radical( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::radical [r => Ok(r.static_to_owned())], + sets::load_radical, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::regional_indicator, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_regional_indicator, Fn, hidden)] + pub fn load_regional_indicator( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::regional_indicator [r => Ok(r.static_to_owned())], + sets::load_regional_indicator, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::soft_dotted, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_soft_dotted, Fn, hidden)] + pub fn load_soft_dotted( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::soft_dotted [r => Ok(r.static_to_owned())], + sets::load_soft_dotted, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::segment_starter, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_segment_starter, Fn, hidden)] + pub fn load_segment_starter( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::segment_starter [r => Ok(r.static_to_owned())], + sets::load_segment_starter, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::case_sensitive, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_case_sensitive, Fn, hidden)] + pub fn load_case_sensitive( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::case_sensitive [r => Ok(r.static_to_owned())], + sets::load_case_sensitive, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::sentence_terminal, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_sentence_terminal, Fn, hidden)] + pub fn load_sentence_terminal( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::sentence_terminal [r => Ok(r.static_to_owned())], + sets::load_sentence_terminal, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::terminal_punctuation, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_terminal_punctuation, Fn, hidden)] + pub fn load_terminal_punctuation( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::terminal_punctuation [r => Ok(r.static_to_owned())], + sets::load_terminal_punctuation, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::unified_ideograph, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_unified_ideograph, Fn, hidden)] + pub fn load_unified_ideograph( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::unified_ideograph [r => Ok(r.static_to_owned())], + sets::load_unified_ideograph, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::uppercase, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_uppercase, Fn, hidden)] + pub fn load_uppercase( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::uppercase [r => Ok(r.static_to_owned())], + sets::load_uppercase, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::variation_selector, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_variation_selector, Fn, hidden)] + pub fn load_variation_selector( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::variation_selector [r => Ok(r.static_to_owned())], + sets::load_variation_selector, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::white_space, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_white_space, Fn, hidden)] + pub fn load_white_space( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::white_space [r => Ok(r.static_to_owned())], + sets::load_white_space, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::xdigit, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_xdigit, Fn, hidden)] + pub fn load_xdigit( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::xdigit [r => Ok(r.static_to_owned())], + sets::load_xdigit, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::xid_continue, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_xid_continue, Fn, hidden)] + pub fn load_xid_continue( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::xid_continue [r => Ok(r.static_to_owned())], + sets::load_xid_continue, + provider + )?))) + } + + #[diplomat::rust_link(icu::properties::sets::xid_start, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_xid_start, Fn, hidden)] + pub fn load_xid_start( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::xid_start [r => Ok(r.static_to_owned())], + sets::load_xid_start, + provider + )?))) + } + + /// Loads data for a property specified as a string as long as it is one of the + /// [ECMA-262 binary properties][ecma] (not including Any, ASCII, and Assigned pseudoproperties). + /// + /// Returns `ICU4XError::PropertyUnexpectedPropertyNameError` in case the string does not + /// match any property in the list + /// + /// [ecma]: https://tc39.es/ecma262/#table-binary-unicode-properties + #[diplomat::rust_link(icu::properties::sets::for_ecma262, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_for_ecma262, Fn, hidden)] + pub fn load_for_ecma262( + provider: &ICU4XDataProvider, + property_name: &str, + ) -> Result<Box<ICU4XCodePointSetData>, ICU4XError> { + let name = property_name.as_bytes(); // #2520 + let name = if let Ok(s) = str::from_utf8(name) { + s + } else { + return Err(ICU4XError::TinyStrNonAsciiError); + }; + Ok(Box::new(ICU4XCodePointSetData(call_constructor_unstable!( + sets::load_for_ecma262 [r => r.map(|r| r.static_to_owned())], + sets::load_for_ecma262_unstable, + provider, + name + )?))) + } + } +} diff --git a/intl/icu_capi/src/properties_unisets.rs b/intl/icu_capi/src/properties_unisets.rs new file mode 100644 index 0000000000..7d2cf8cccc --- /dev/null +++ b/intl/icu_capi/src/properties_unisets.rs @@ -0,0 +1,149 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::locale::ffi::ICU4XLocale; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use core::str; + use icu_properties::{exemplar_chars, sets}; + + use crate::errors::ffi::ICU4XError; + + #[diplomat::opaque] + /// An ICU4X Unicode Set Property object, capable of querying whether a code point is contained in a set based on a Unicode property. + #[diplomat::rust_link(icu::properties, Mod)] + #[diplomat::rust_link(icu::properties::sets::UnicodeSetData, Struct)] + #[diplomat::rust_link(icu::properties::sets::UnicodeSetData::from_data, FnInStruct, hidden)] + #[diplomat::rust_link(icu::properties::sets::UnicodeSetDataBorrowed, Struct)] + pub struct ICU4XUnicodeSetData(pub sets::UnicodeSetData); + + impl ICU4XUnicodeSetData { + /// Checks whether the string is in the set. + #[diplomat::rust_link(icu::properties::sets::UnicodeSetDataBorrowed::contains, FnInStruct)] + pub fn contains(&self, s: &str) -> bool { + let s = s.as_bytes(); // #2520 + let s = if let Ok(s) = str::from_utf8(s) { + s + } else { + return false; + }; + self.0.as_borrowed().contains(s) + } + /// Checks whether the code point is in the set. + #[diplomat::rust_link( + icu::properties::sets::UnicodeSetDataBorrowed::contains_char, + FnInStruct + )] + pub fn contains_char(&self, cp: char) -> bool { + self.0.as_borrowed().contains_char(cp) + } + /// Checks whether the code point (specified as a 32 bit integer, in UTF-32) is in the set. + #[diplomat::rust_link( + icu::properties::sets::UnicodeSetDataBorrowed::contains32, + FnInStruct, + hidden + )] + #[diplomat::attr(dart, disable)] + pub fn contains32(&self, cp: u32) -> bool { + self.0.as_borrowed().contains32(cp) + } + + #[diplomat::rust_link(icu::properties::sets::basic_emoji, Fn)] + #[diplomat::rust_link(icu::properties::sets::load_basic_emoji, Fn, hidden)] + pub fn load_basic_emoji( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XUnicodeSetData>, ICU4XError> { + Ok(Box::new(ICU4XUnicodeSetData(call_constructor_unstable!( + sets::basic_emoji [r => Ok(r.static_to_owned())], + sets::load_basic_emoji, + provider, + )?))) + } + + #[diplomat::rust_link(icu::properties::exemplar_chars::exemplars_main, Fn)] + #[diplomat::rust_link(icu::properties::exemplar_chars::load_exemplars_main, Fn, hidden)] + pub fn load_exemplars_main( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XUnicodeSetData>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XUnicodeSetData(call_constructor_unstable!( + exemplar_chars::exemplars_main, + exemplar_chars::load_exemplars_main, + provider, + &locale + )?))) + } + + #[diplomat::rust_link(icu::properties::exemplar_chars::exemplars_auxiliary, Fn)] + #[diplomat::rust_link( + icu::properties::exemplar_chars::load_exemplars_auxiliary, + Fn, + hidden + )] + pub fn load_exemplars_auxiliary( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XUnicodeSetData>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XUnicodeSetData(call_constructor_unstable!( + exemplar_chars::exemplars_auxiliary, + exemplar_chars::load_exemplars_auxiliary, + provider, + &locale + )?))) + } + + #[diplomat::rust_link(icu::properties::exemplar_chars::exemplars_punctuation, Fn)] + #[diplomat::rust_link( + icu::properties::exemplar_chars::load_exemplars_punctuation, + Fn, + hidden + )] + pub fn load_exemplars_punctuation( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XUnicodeSetData>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XUnicodeSetData(call_constructor_unstable!( + exemplar_chars::exemplars_punctuation, + exemplar_chars::load_exemplars_punctuation, + provider, + &locale + )?))) + } + + #[diplomat::rust_link(icu::properties::exemplar_chars::exemplars_numbers, Fn)] + #[diplomat::rust_link(icu::properties::exemplar_chars::load_exemplars_numbers, Fn, hidden)] + pub fn load_exemplars_numbers( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XUnicodeSetData>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XUnicodeSetData(call_constructor_unstable!( + exemplar_chars::exemplars_numbers, + exemplar_chars::load_exemplars_numbers, + provider, + &locale + )?))) + } + + #[diplomat::rust_link(icu::properties::exemplar_chars::exemplars_index, Fn)] + #[diplomat::rust_link(icu::properties::exemplar_chars::load_exemplars_index, Fn, hidden)] + pub fn load_exemplars_index( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XUnicodeSetData>, ICU4XError> { + let locale = locale.to_datalocale(); + Ok(Box::new(ICU4XUnicodeSetData(call_constructor_unstable!( + exemplar_chars::exemplars_index, + exemplar_chars::load_exemplars_index, + provider, + &locale + )?))) + } + } +} diff --git a/intl/icu_capi/src/provider.rs b/intl/icu_capi/src/provider.rs new file mode 100644 index 0000000000..18234f374c --- /dev/null +++ b/intl/icu_capi/src/provider.rs @@ -0,0 +1,344 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[allow(unused_imports)] // feature-specific +use alloc::boxed::Box; +use icu_provider::prelude::*; +#[allow(unused_imports)] // feature-specific +use icu_provider::MaybeSendSync; +use icu_provider_adapters::empty::EmptyDataProvider; +#[allow(unused_imports)] // feature-specific +use yoke::{trait_hack::YokeTraitHack, Yokeable}; +#[allow(unused_imports)] // feature-specific +use zerofrom::ZeroFrom; + +pub enum ICU4XDataProviderInner { + Destroyed, + Empty, + #[cfg(feature = "compiled_data")] + Compiled, + #[cfg(feature = "buffer_provider")] + Buffer(Box<dyn BufferProvider + 'static>), +} + +#[diplomat::bridge] +pub mod ffi { + use super::ICU4XDataProviderInner; + use crate::errors::ffi::ICU4XError; + use alloc::boxed::Box; + #[allow(unused_imports)] // feature-gated + use icu_provider_adapters::fallback::LocaleFallbackProvider; + #[allow(unused_imports)] // feature-gated + use icu_provider_adapters::fork::predicates::MissingLocalePredicate; + + #[diplomat::opaque] + /// An ICU4X data provider, capable of loading ICU4X data keys from some source. + #[diplomat::rust_link(icu_provider, Mod)] + pub struct ICU4XDataProvider(pub ICU4XDataProviderInner); + + #[cfg(feature = "buffer_provider")] + fn convert_buffer_provider<D: icu_provider::BufferProvider + 'static>( + x: D, + ) -> ICU4XDataProvider { + ICU4XDataProvider(super::ICU4XDataProviderInner::Buffer(Box::new(x))) + } + + impl ICU4XDataProvider { + /// Constructs an [`ICU4XDataProvider`] that uses compiled data. + /// + /// Requires the `compiled_data` feature. + /// + /// This provider cannot be modified or combined with other providers, so `enable_fallback`, + /// `enabled_fallback_with`, `fork_by_locale`, and `fork_by_key` will return `Err`s. + #[cfg(feature = "compiled_data")] + pub fn create_compiled() -> Box<ICU4XDataProvider> { + Box::new(Self(ICU4XDataProviderInner::Compiled)) + } + + /// Constructs an `FsDataProvider` and returns it as an [`ICU4XDataProvider`]. + /// Requires the `provider_fs` Cargo feature. + /// Not supported in WASM. + #[diplomat::rust_link(icu_provider_fs::FsDataProvider, Struct)] + #[cfg(all( + feature = "provider_fs", + not(any(target_arch = "wasm32", target_os = "none")) + ))] + #[diplomat::attr(dart, disable)] + pub fn create_fs(path: &str) -> Result<Box<ICU4XDataProvider>, ICU4XError> { + // #2520 + // In the future we can start using OsString APIs to support non-utf8 paths + core::str::from_utf8(path.as_bytes()) + .map_err(|e| ICU4XError::DataIoError.log_original(&e))?; + + Ok(Box::new(convert_buffer_provider( + icu_provider_fs::FsDataProvider::try_new(path)?, + ))) + } + + /// Deprecated + /// + /// Use `create_compiled()`. + #[cfg(all( + feature = "provider_test", + any(feature = "any_provider", feature = "buffer_provider") + ))] + #[diplomat::attr(dart, disable)] + pub fn create_test() -> Box<ICU4XDataProvider> { + Self::create_compiled() + } + + /// Constructs a `BlobDataProvider` and returns it as an [`ICU4XDataProvider`]. + #[diplomat::rust_link(icu_provider_blob::BlobDataProvider, Struct)] + #[cfg(feature = "buffer_provider")] + pub fn create_from_byte_slice( + blob: &'static [u8], + ) -> Result<Box<ICU4XDataProvider>, ICU4XError> { + Ok(Box::new(convert_buffer_provider( + icu_provider_blob::BlobDataProvider::try_new_from_static_blob(blob)?, + ))) + } + + /// Constructs an empty [`ICU4XDataProvider`]. + #[diplomat::rust_link(icu_provider_adapters::empty::EmptyDataProvider, Struct)] + #[diplomat::rust_link( + icu_provider_adapters::empty::EmptyDataProvider::new, + FnInStruct, + hidden + )] + pub fn create_empty() -> Box<ICU4XDataProvider> { + Box::new(ICU4XDataProvider(ICU4XDataProviderInner::Empty)) + } + + /// Creates a provider that tries the current provider and then, if the current provider + /// doesn't support the data key, another provider `other`. + /// + /// This takes ownership of the `other` provider, leaving an empty provider in its place. + /// + /// The providers must be the same type (Any or Buffer). This condition is satisfied if + /// both providers originate from the same constructor, such as `create_from_byte_slice` + /// or `create_fs`. If the condition is not upheld, a runtime error occurs. + #[diplomat::rust_link(icu_provider_adapters::fork::ForkByKeyProvider, Typedef)] + #[diplomat::rust_link( + icu_provider_adapters::fork::predicates::MissingDataKeyPredicate, + Struct, + hidden + )] + pub fn fork_by_key(&mut self, other: &mut ICU4XDataProvider) -> Result<(), ICU4XError> { + #[allow(unused_imports)] + use ICU4XDataProviderInner::*; + *self = match ( + core::mem::replace(&mut self.0, Destroyed), + core::mem::replace(&mut other.0, Destroyed), + ) { + (Destroyed, _) | (_, Destroyed) => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + #[cfg(feature = "compiled_data")] + (Compiled, _) | (_, Compiled) => Err(icu_provider::DataError::custom( + "The compiled provider cannot be modified", + ))?, + (Empty, Empty) => ICU4XDataProvider(ICU4XDataProviderInner::Empty), + #[cfg(feature = "buffer_provider")] + (Empty, b) | (b, Empty) => ICU4XDataProvider(b), + #[cfg(feature = "buffer_provider")] + (Buffer(a), Buffer(b)) => convert_buffer_provider( + icu_provider_adapters::fork::ForkByKeyProvider::new(a, b), + ), + }; + Ok(()) + } + + /// Same as `fork_by_key` but forks by locale instead of key. + #[diplomat::rust_link( + icu_provider_adapters::fork::predicates::MissingLocalePredicate, + Struct + )] + pub fn fork_by_locale(&mut self, other: &mut ICU4XDataProvider) -> Result<(), ICU4XError> { + #[allow(unused_imports)] + use ICU4XDataProviderInner::*; + *self = match ( + core::mem::replace(&mut self.0, Destroyed), + core::mem::replace(&mut other.0, Destroyed), + ) { + (Destroyed, _) | (_, Destroyed) => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + #[cfg(feature = "compiled_data")] + (Compiled, _) | (_, Compiled) => Err(icu_provider::DataError::custom( + "The compiled provider cannot be modified", + ))?, + (Empty, Empty) => ICU4XDataProvider(ICU4XDataProviderInner::Empty), + #[cfg(feature = "buffer_provider")] + (Empty, b) | (b, Empty) => ICU4XDataProvider(b), + #[cfg(feature = "buffer_provider")] + (Buffer(a), Buffer(b)) => convert_buffer_provider( + icu_provider_adapters::fork::ForkByErrorProvider::new_with_predicate( + a, + b, + MissingLocalePredicate, + ), + ), + }; + Ok(()) + } + + /// Enables locale fallbacking for data requests made to this provider. + /// + /// Note that the test provider (from `create_test`) already has fallbacking enabled. + #[diplomat::rust_link( + icu_provider_adapters::fallback::LocaleFallbackProvider::try_new, + FnInStruct + )] + #[diplomat::rust_link( + icu_provider_adapters::fallback::LocaleFallbackProvider, + Struct, + compact + )] + pub fn enable_locale_fallback(&mut self) -> Result<(), ICU4XError> { + use ICU4XDataProviderInner::*; + *self = match core::mem::replace(&mut self.0, Destroyed) { + Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + #[cfg(feature = "compiled_data")] + Compiled => Err(icu_provider::DataError::custom( + "The compiled provider cannot be modified", + ))?, + Empty => Err(icu_provider::DataErrorKind::MissingDataKey.into_error())?, + #[cfg(feature = "buffer_provider")] + Buffer(inner) => convert_buffer_provider( + LocaleFallbackProvider::try_new_with_buffer_provider(inner)?, + ), + }; + Ok(()) + } + + #[diplomat::rust_link( + icu_provider_adapters::fallback::LocaleFallbackProvider::new_with_fallbacker, + FnInStruct + )] + #[diplomat::rust_link( + icu_provider_adapters::fallback::LocaleFallbackProvider, + Struct, + compact + )] + #[allow(unused_variables)] // feature-gated + #[cfg(feature = "icu_locid_transform")] + pub fn enable_locale_fallback_with( + &mut self, + fallbacker: &crate::fallbacker::ffi::ICU4XLocaleFallbacker, + ) -> Result<(), ICU4XError> { + use ICU4XDataProviderInner::*; + *self = match core::mem::replace(&mut self.0, Destroyed) { + Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + #[cfg(feature = "compiled_data")] + Compiled => Err(icu_provider::DataError::custom( + "The compiled provider cannot be modified", + ))?, + Empty => Err(icu_provider::DataErrorKind::MissingDataKey.into_error())?, + #[cfg(feature = "buffer_provider")] + Buffer(inner) => convert_buffer_provider( + LocaleFallbackProvider::new_with_fallbacker(inner, fallbacker.0.clone()), + ), + }; + Ok(()) + } + } +} + +macro_rules! load { + () => { + fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { + use ICU4XDataProviderInner::*; + match self { + Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + Empty => EmptyDataProvider::new().load(req), + #[cfg(feature = "buffer_provider")] + Buffer(buffer_provider) => buffer_provider.as_deserializing().load(req), + #[cfg(feature = "compiled_data")] + Compiled => unreachable!(), + } + } + }; +} + +#[macro_export] +macro_rules! call_constructor { + ($compiled:path [$pre_transform:ident => $transform:expr], $any:path, $buffer:path, $provider:expr $(, $args:expr)* $(,)?) => { + match &$provider.0 { + $crate::provider::ICU4XDataProviderInner::Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + $crate::provider::ICU4XDataProviderInner::Empty => $any(&icu_provider_adapters::empty::EmptyDataProvider::new(), $($args,)*), + #[cfg(feature = "buffer_provider")] + $crate::provider::ICU4XDataProviderInner::Buffer(buffer_provider) => $buffer(buffer_provider, $($args,)*), + #[cfg(feature = "compiled_data")] + $crate::provider::ICU4XDataProviderInner::Compiled => { let $pre_transform = $compiled($($args,)*); $transform }, + } + }; + ($compiled:path, $any:path, $buffer:path, $provider:expr $(, $args:expr)* $(,)?) => { + match &$provider.0 { + $crate::provider::ICU4XDataProviderInner::Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + $crate::provider::ICU4XDataProviderInner::Empty => $any(&icu_provider_adapters::empty::EmptyDataProvider::new(), $($args,)*), + #[cfg(feature = "buffer_provider")] + $crate::provider::ICU4XDataProviderInner::Buffer(buffer_provider) => $buffer(buffer_provider, $($args,)*), + #[cfg(feature = "compiled_data")] + $crate::provider::ICU4XDataProviderInner::Compiled => $compiled($($args,)*), + } + }; +} + +#[macro_export] +macro_rules! call_constructor_unstable { + ($compiled:path [$pre_transform:ident => $transform:expr], $unstable:path, $provider:expr $(, $args:expr)* $(,)?) => { + match &$provider.0 { + $crate::provider::ICU4XDataProviderInner::Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + $crate::provider::ICU4XDataProviderInner::Empty => $unstable(&icu_provider_adapters::empty::EmptyDataProvider::new(), $($args,)*), + #[cfg(feature = "buffer_provider")] + $crate::provider::ICU4XDataProviderInner::Buffer(buffer_provider) => $unstable(&icu_provider::AsDeserializingBufferProvider::as_deserializing(buffer_provider), $($args,)*), + #[cfg(feature = "compiled_data")] + $crate::provider::ICU4XDataProviderInner::Compiled => { let $pre_transform = $compiled($($args,)*); $transform }, + } + }; + ($compiled:path, $unstable:path, $provider:expr $(, $args:expr)* $(,)?) => { + match &$provider.0 { + $crate::provider::ICU4XDataProviderInner::Destroyed => Err(icu_provider::DataError::custom( + "This provider has been destroyed", + ))?, + $crate::provider::ICU4XDataProviderInner::Empty => $unstable(&icu_provider_adapters::empty::EmptyDataProvider::new(), $($args,)*), + #[cfg(feature = "buffer_provider")] + $crate::provider::ICU4XDataProviderInner::Buffer(buffer_provider) => $unstable(&icu_provider::AsDeserializingBufferProvider::as_deserializing(buffer_provider), $($args,)*), + #[cfg(feature = "compiled_data")] + $crate::provider::ICU4XDataProviderInner::Compiled => $compiled($($args,)*), + } + }; +} + +#[cfg(not(feature = "buffer_provider"))] +impl<M> DataProvider<M> for ICU4XDataProviderInner +where + M: KeyedDataMarker, +{ + load!(); +} + +#[cfg(feature = "buffer_provider")] +impl<M> DataProvider<M> for ICU4XDataProviderInner +where + M: KeyedDataMarker, + // Actual bound: + // for<'de> <M::Yokeable as Yokeable<'de>>::Output: Deserialize<'de>, + // Necessary workaround bound (see `yoke::trait_hack` docs): + for<'de> YokeTraitHack<<M::Yokeable as Yokeable<'de>>::Output>: serde::Deserialize<'de>, +{ + load!(); +} diff --git a/intl/icu_capi/src/script.rs b/intl/icu_capi/src/script.rs new file mode 100644 index 0000000000..aeb7e8bdde --- /dev/null +++ b/intl/icu_capi/src/script.rs @@ -0,0 +1,156 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use icu_properties::{script, sets::CodePointSetData, Script}; + + use crate::errors::ffi::ICU4XError; + use crate::properties_iter::ffi::CodePointRangeIterator; + use crate::properties_sets::ffi::ICU4XCodePointSetData; + + #[diplomat::opaque] + /// An ICU4X ScriptWithExtensions map object, capable of holding a map of codepoints to scriptextensions values + #[diplomat::rust_link(icu::properties::script::ScriptWithExtensions, Struct)] + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensions::from_data, + FnInStruct, + hidden + )] + pub struct ICU4XScriptWithExtensions(pub script::ScriptWithExtensions); + + #[diplomat::opaque] + /// A slightly faster ICU4XScriptWithExtensions object + #[diplomat::rust_link(icu::properties::script::ScriptWithExtensionsBorrowed, Struct)] + pub struct ICU4XScriptWithExtensionsBorrowed<'a>(pub script::ScriptWithExtensionsBorrowed<'a>); + #[diplomat::opaque] + /// An object that represents the Script_Extensions property for a single character + #[diplomat::rust_link(icu::properties::script::ScriptExtensionsSet, Struct)] + pub struct ICU4XScriptExtensionsSet<'a>(pub script::ScriptExtensionsSet<'a>); + + impl ICU4XScriptWithExtensions { + #[diplomat::rust_link(icu::properties::script::script_with_extensions, Fn)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XScriptWithExtensions>, ICU4XError> { + Ok(Box::new(ICU4XScriptWithExtensions(call_constructor!( + script::script_with_extensions [r => Ok(r.static_to_owned())], + script::load_script_with_extensions_with_any_provider, + script::load_script_with_extensions_with_buffer_provider, + provider + )?))) + } + + /// Get the Script property value for a code point + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensionsBorrowed::get_script_val, + FnInStruct + )] + pub fn get_script_val(&self, code_point: u32) -> u16 { + self.0.as_borrowed().get_script_val(code_point).0 + } + + /// Check if the Script_Extensions property of the given code point covers the given script + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensionsBorrowed::has_script, + FnInStruct + )] + pub fn has_script(&self, code_point: u32, script: u16) -> bool { + self.0.as_borrowed().has_script(code_point, Script(script)) + } + + /// Borrow this object for a slightly faster variant with more operations + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensions::as_borrowed, + FnInStruct + )] + pub fn as_borrowed<'a>(&'a self) -> Box<ICU4XScriptWithExtensionsBorrowed<'a>> { + Box::new(ICU4XScriptWithExtensionsBorrowed(self.0.as_borrowed())) + } + + /// Get a list of ranges of code points that contain this script in their Script_Extensions values + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensionsBorrowed::get_script_extensions_ranges, + FnInStruct + )] + pub fn iter_ranges_for_script<'a>( + &'a self, + script: u16, + ) -> Box<CodePointRangeIterator<'a>> { + Box::new(CodePointRangeIterator(Box::new( + self.0 + .as_borrowed() + .get_script_extensions_ranges(Script(script)), + ))) + } + } + + impl<'a> ICU4XScriptWithExtensionsBorrowed<'a> { + /// Get the Script property value for a code point + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensionsBorrowed::get_script_val, + FnInStruct + )] + pub fn get_script_val(&self, code_point: u32) -> u16 { + self.0.get_script_val(code_point).0 + } + /// Get the Script property value for a code point + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensionsBorrowed::get_script_extensions_val, + FnInStruct + )] + pub fn get_script_extensions_val( + &self, + code_point: u32, + ) -> Box<ICU4XScriptExtensionsSet<'a>> { + Box::new(ICU4XScriptExtensionsSet( + self.0.get_script_extensions_val(code_point), + )) + } + /// Check if the Script_Extensions property of the given code point covers the given script + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensionsBorrowed::has_script, + FnInStruct + )] + pub fn has_script(&self, code_point: u32, script: u16) -> bool { + self.0.has_script(code_point, Script(script)) + } + + /// Build the CodePointSetData corresponding to a codepoints matching a particular script + /// in their Script_Extensions + #[diplomat::rust_link( + icu::properties::script::ScriptWithExtensionsBorrowed::get_script_extensions_set, + FnInStruct + )] + pub fn get_script_extensions_set(&self, script: u16) -> Box<ICU4XCodePointSetData> { + let list = self + .0 + .get_script_extensions_set(Script(script)) + .into_owned(); + let set = CodePointSetData::from_code_point_inversion_list(list); + Box::new(ICU4XCodePointSetData(set)) + } + } + impl<'a> ICU4XScriptExtensionsSet<'a> { + /// Check if the Script_Extensions property of the given code point covers the given script + #[diplomat::rust_link(icu::properties::script::ScriptExtensionsSet::contains, FnInStruct)] + pub fn contains(&self, script: u16) -> bool { + self.0.contains(&Script(script)) + } + + /// Get the number of scripts contained in here + #[diplomat::rust_link(icu::properties::script::ScriptExtensionsSet::iter, FnInStruct)] + pub fn count(&self) -> usize { + self.0.array_len() + } + + /// Get script at index, returning an error if out of bounds + #[diplomat::rust_link(icu::properties::script::ScriptExtensionsSet::iter, FnInStruct)] + pub fn script_at(&self, index: usize) -> Result<u16, ()> { + self.0.array_get(index).map(|x| x.0).ok_or(()) + } + } +} diff --git a/intl/icu_capi/src/segmenter_grapheme.rs b/intl/icu_capi/src/segmenter_grapheme.rs new file mode 100644 index 0000000000..5d3f65fc05 --- /dev/null +++ b/intl/icu_capi/src/segmenter_grapheme.rs @@ -0,0 +1,154 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use core::convert::TryFrom; + use icu_segmenter::{ + GraphemeClusterBreakIteratorLatin1, GraphemeClusterBreakIteratorPotentiallyIllFormedUtf8, + GraphemeClusterBreakIteratorUtf16, GraphemeClusterSegmenter, + }; + + #[diplomat::opaque] + /// An ICU4X grapheme-cluster-break segmenter, capable of finding grapheme cluster breakpoints + /// in strings. + #[diplomat::rust_link(icu::segmenter::GraphemeClusterSegmenter, Struct)] + pub struct ICU4XGraphemeClusterSegmenter(GraphemeClusterSegmenter); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIterator, Struct)] + #[diplomat::rust_link( + icu::segmenter::GraphemeClusterBreakIteratorPotentiallyIllFormedUtf8, + Typedef, + hidden + )] + pub struct ICU4XGraphemeClusterBreakIteratorUtf8<'a>( + GraphemeClusterBreakIteratorPotentiallyIllFormedUtf8<'a, 'a>, + ); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIteratorUtf16, Typedef, hidden)] + pub struct ICU4XGraphemeClusterBreakIteratorUtf16<'a>( + GraphemeClusterBreakIteratorUtf16<'a, 'a>, + ); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIteratorLatin1, Typedef, hidden)] + pub struct ICU4XGraphemeClusterBreakIteratorLatin1<'a>( + GraphemeClusterBreakIteratorLatin1<'a, 'a>, + ); + + impl ICU4XGraphemeClusterSegmenter { + /// Construct an [`ICU4XGraphemeClusterSegmenter`]. + #[diplomat::rust_link(icu::segmenter::GraphemeClusterSegmenter::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XGraphemeClusterSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XGraphemeClusterSegmenter(call_constructor!( + GraphemeClusterSegmenter::new [r => Ok(r)], + GraphemeClusterSegmenter::try_new_with_any_provider, + GraphemeClusterSegmenter::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Segments a (potentially ill-formed) UTF-8 string. + #[diplomat::rust_link( + icu::segmenter::GraphemeClusterSegmenter::segment_str, + FnInStruct, + hidden + )] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterSegmenter::segment_utf8, FnInStruct)] + pub fn segment_utf8<'a>( + &'a self, + input: &'a str, + ) -> Box<ICU4XGraphemeClusterBreakIteratorUtf8<'a>> { + let input = input.as_bytes(); // #2520 + Box::new(ICU4XGraphemeClusterBreakIteratorUtf8( + self.0.segment_utf8(input), + )) + } + + /// Segments a UTF-16 string. + #[diplomat::rust_link(icu::segmenter::GraphemeClusterSegmenter::segment_utf16, FnInStruct)] + pub fn segment_utf16<'a>( + &'a self, + input: &'a [u16], + ) -> Box<ICU4XGraphemeClusterBreakIteratorUtf16<'a>> { + Box::new(ICU4XGraphemeClusterBreakIteratorUtf16( + self.0.segment_utf16(input), + )) + } + + /// Segments a Latin-1 string. + #[diplomat::rust_link(icu::segmenter::GraphemeClusterSegmenter::segment_latin1, FnInStruct)] + pub fn segment_latin1<'a>( + &'a self, + input: &'a [u8], + ) -> Box<ICU4XGraphemeClusterBreakIteratorLatin1<'a>> { + Box::new(ICU4XGraphemeClusterBreakIteratorLatin1( + self.0.segment_latin1(input), + )) + } + } + + impl<'a> ICU4XGraphemeClusterBreakIteratorUtf8<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::GraphemeClusterBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } + + impl<'a> ICU4XGraphemeClusterBreakIteratorUtf16<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::GraphemeClusterBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } + + impl<'a> ICU4XGraphemeClusterBreakIteratorLatin1<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::GraphemeClusterBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::GraphemeClusterBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } +} diff --git a/intl/icu_capi/src/segmenter_line.rs b/intl/icu_capi/src/segmenter_line.rs new file mode 100644 index 0000000000..81aa9b49ff --- /dev/null +++ b/intl/icu_capi/src/segmenter_line.rs @@ -0,0 +1,271 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use icu_segmenter::LineBreakOptions; +use icu_segmenter::LineBreakStrictness; +use icu_segmenter::LineBreakWordOption; + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use core::convert::TryFrom; + use icu_segmenter::{ + LineBreakIteratorLatin1, LineBreakIteratorPotentiallyIllFormedUtf8, LineBreakIteratorUtf16, + LineSegmenter, + }; + + #[diplomat::opaque] + /// An ICU4X line-break segmenter, capable of finding breakpoints in strings. + #[diplomat::rust_link(icu::segmenter::LineSegmenter, Struct)] + pub struct ICU4XLineSegmenter(LineSegmenter); + + #[diplomat::rust_link(icu::segmenter::LineBreakStrictness, Enum)] + pub enum ICU4XLineBreakStrictness { + Loose, + Normal, + Strict, + Anywhere, + } + + #[diplomat::rust_link(icu::segmenter::LineBreakWordOption, Enum)] + pub enum ICU4XLineBreakWordOption { + Normal, + BreakAll, + KeepAll, + } + + #[diplomat::rust_link(icu::segmenter::LineBreakOptions, Struct)] + pub struct ICU4XLineBreakOptionsV1 { + pub strictness: ICU4XLineBreakStrictness, + pub word_option: ICU4XLineBreakWordOption, + pub ja_zh: bool, + } + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::LineBreakIterator, Struct)] + #[diplomat::rust_link( + icu::segmenter::LineBreakIteratorPotentiallyIllFormedUtf8, + Typedef, + compact + )] + pub struct ICU4XLineBreakIteratorUtf8<'a>(LineBreakIteratorPotentiallyIllFormedUtf8<'a, 'a>); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::LineBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::LineBreakIteratorUtf16, Typedef, compact)] + pub struct ICU4XLineBreakIteratorUtf16<'a>(LineBreakIteratorUtf16<'a, 'a>); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::LineBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::LineBreakIteratorLatin1, Typedef, compact)] + pub struct ICU4XLineBreakIteratorLatin1<'a>(LineBreakIteratorLatin1<'a, 'a>); + + impl ICU4XLineSegmenter { + /// Construct a [`ICU4XLineSegmenter`] with default options. It automatically loads the best + /// available payload data for Burmese, Khmer, Lao, and Thai. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::new_auto, FnInStruct)] + pub fn create_auto( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLineSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XLineSegmenter(call_constructor!( + LineSegmenter::new_auto [r => Ok(r)], + LineSegmenter::try_new_auto_with_any_provider, + LineSegmenter::try_new_auto_with_buffer_provider, + provider + )?))) + } + + /// Construct a [`ICU4XLineSegmenter`] with default options and LSTM payload data for + /// Burmese, Khmer, Lao, and Thai. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::new_lstm, FnInStruct)] + pub fn create_lstm( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLineSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XLineSegmenter(call_constructor!( + LineSegmenter::new_lstm [r => Ok(r)], + LineSegmenter::try_new_lstm_with_any_provider, + LineSegmenter::try_new_lstm_with_buffer_provider, + provider, + )?))) + } + + /// Construct a [`ICU4XLineSegmenter`] with default options and dictionary payload data for + /// Burmese, Khmer, Lao, and Thai.. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::new_dictionary, FnInStruct)] + pub fn create_dictionary( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XLineSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XLineSegmenter(call_constructor!( + LineSegmenter::new_dictionary [r => Ok(r)], + LineSegmenter::try_new_dictionary_with_any_provider, + LineSegmenter::try_new_dictionary_with_buffer_provider, + provider, + )?))) + } + + /// Construct a [`ICU4XLineSegmenter`] with custom options. It automatically loads the best + /// available payload data for Burmese, Khmer, Lao, and Thai. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::new_auto_with_options, FnInStruct)] + pub fn create_auto_with_options_v1( + provider: &ICU4XDataProvider, + options: ICU4XLineBreakOptionsV1, + ) -> Result<Box<ICU4XLineSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XLineSegmenter(call_constructor!( + LineSegmenter::new_auto_with_options [r => Ok(r)], + LineSegmenter::try_new_auto_with_options_with_any_provider, + LineSegmenter::try_new_auto_with_options_with_buffer_provider, + provider, + options.into(), + )?))) + } + + /// Construct a [`ICU4XLineSegmenter`] with custom options and LSTM payload data for + /// Burmese, Khmer, Lao, and Thai. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::new_lstm_with_options, FnInStruct)] + pub fn create_lstm_with_options_v1( + provider: &ICU4XDataProvider, + options: ICU4XLineBreakOptionsV1, + ) -> Result<Box<ICU4XLineSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XLineSegmenter(call_constructor!( + LineSegmenter::new_lstm_with_options [r => Ok(r)], + LineSegmenter::try_new_lstm_with_options_with_any_provider, + LineSegmenter::try_new_lstm_with_options_with_buffer_provider, + provider, + options.into(), + )?))) + } + + /// Construct a [`ICU4XLineSegmenter`] with custom options and dictionary payload data for + /// Burmese, Khmer, Lao, and Thai. + #[diplomat::rust_link( + icu::segmenter::LineSegmenter::new_dictionary_with_options, + FnInStruct + )] + pub fn create_dictionary_with_options_v1( + provider: &ICU4XDataProvider, + options: ICU4XLineBreakOptionsV1, + ) -> Result<Box<ICU4XLineSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XLineSegmenter(call_constructor!( + LineSegmenter::new_dictionary_with_options [r => Ok(r)], + LineSegmenter::try_new_dictionary_with_options_with_any_provider, + LineSegmenter::try_new_dictionary_with_options_with_buffer_provider, + provider, + options.into(), + )?))) + } + + /// Segments a (potentially ill-formed) UTF-8 string. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::segment_utf8, FnInStruct)] + #[diplomat::rust_link(icu::segmenter::LineSegmenter::segment_str, FnInStruct, hidden)] + pub fn segment_utf8<'a>(&'a self, input: &'a str) -> Box<ICU4XLineBreakIteratorUtf8<'a>> { + let input = input.as_bytes(); // #2520 + Box::new(ICU4XLineBreakIteratorUtf8(self.0.segment_utf8(input))) + } + + /// Segments a UTF-16 string. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::segment_utf16, FnInStruct)] + pub fn segment_utf16<'a>( + &'a self, + input: &'a [u16], + ) -> Box<ICU4XLineBreakIteratorUtf16<'a>> { + Box::new(ICU4XLineBreakIteratorUtf16(self.0.segment_utf16(input))) + } + + /// Segments a Latin-1 string. + #[diplomat::rust_link(icu::segmenter::LineSegmenter::segment_latin1, FnInStruct)] + pub fn segment_latin1<'a>( + &'a self, + input: &'a [u8], + ) -> Box<ICU4XLineBreakIteratorLatin1<'a>> { + Box::new(ICU4XLineBreakIteratorLatin1(self.0.segment_latin1(input))) + } + } + + impl<'a> ICU4XLineBreakIteratorUtf8<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::LineBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::LineBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } + + impl<'a> ICU4XLineBreakIteratorUtf16<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::LineBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::LineBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } + + impl<'a> ICU4XLineBreakIteratorLatin1<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::LineBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::LineBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } +} + +impl From<ffi::ICU4XLineBreakStrictness> for LineBreakStrictness { + fn from(other: ffi::ICU4XLineBreakStrictness) -> Self { + match other { + ffi::ICU4XLineBreakStrictness::Loose => Self::Loose, + ffi::ICU4XLineBreakStrictness::Normal => Self::Normal, + ffi::ICU4XLineBreakStrictness::Strict => Self::Strict, + ffi::ICU4XLineBreakStrictness::Anywhere => Self::Anywhere, + } + } +} + +impl From<ffi::ICU4XLineBreakWordOption> for LineBreakWordOption { + fn from(other: ffi::ICU4XLineBreakWordOption) -> Self { + match other { + ffi::ICU4XLineBreakWordOption::Normal => Self::Normal, + ffi::ICU4XLineBreakWordOption::BreakAll => Self::BreakAll, + ffi::ICU4XLineBreakWordOption::KeepAll => Self::KeepAll, + } + } +} + +impl From<ffi::ICU4XLineBreakOptionsV1> for LineBreakOptions { + fn from(other: ffi::ICU4XLineBreakOptionsV1) -> Self { + let mut options = LineBreakOptions::default(); + options.strictness = other.strictness.into(); + options.word_option = other.word_option.into(); + options.ja_zh = other.ja_zh; + options + } +} diff --git a/intl/icu_capi/src/segmenter_sentence.rs b/intl/icu_capi/src/segmenter_sentence.rs new file mode 100644 index 0000000000..be01e0da8c --- /dev/null +++ b/intl/icu_capi/src/segmenter_sentence.rs @@ -0,0 +1,141 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use core::convert::TryFrom; + use icu_segmenter::{ + SentenceBreakIteratorLatin1, SentenceBreakIteratorPotentiallyIllFormedUtf8, + SentenceBreakIteratorUtf16, SentenceSegmenter, + }; + + #[diplomat::opaque] + /// An ICU4X sentence-break segmenter, capable of finding sentence breakpoints in strings. + #[diplomat::rust_link(icu::segmenter::SentenceSegmenter, Struct)] + pub struct ICU4XSentenceSegmenter(SentenceSegmenter); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIterator, Struct)] + #[diplomat::rust_link( + icu::segmenter::SentenceBreakIteratorPotentiallyIllFormedUtf8, + Typedef, + hidden + )] + pub struct ICU4XSentenceBreakIteratorUtf8<'a>( + SentenceBreakIteratorPotentiallyIllFormedUtf8<'a, 'a>, + ); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIteratorUtf16, Typedef, hidden)] + pub struct ICU4XSentenceBreakIteratorUtf16<'a>(SentenceBreakIteratorUtf16<'a, 'a>); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIteratorLatin1, Typedef, hidden)] + pub struct ICU4XSentenceBreakIteratorLatin1<'a>(SentenceBreakIteratorLatin1<'a, 'a>); + + impl ICU4XSentenceSegmenter { + /// Construct an [`ICU4XSentenceSegmenter`]. + #[diplomat::rust_link(icu::segmenter::SentenceSegmenter::new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XSentenceSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XSentenceSegmenter(call_constructor!( + SentenceSegmenter::new [r => Ok(r)], + SentenceSegmenter::try_new_with_any_provider, + SentenceSegmenter::try_new_with_buffer_provider, + provider, + )?))) + } + + /// Segments a (potentially ill-formed) UTF-8 string. + #[diplomat::rust_link(icu::segmenter::SentenceSegmenter::segment_utf8, FnInStruct)] + #[diplomat::rust_link(icu::segmenter::SentenceSegmenter::segment_str, FnInStruct, hidden)] + pub fn segment_utf8<'a>( + &'a self, + input: &'a str, + ) -> Box<ICU4XSentenceBreakIteratorUtf8<'a>> { + let input = input.as_bytes(); // #2520 + Box::new(ICU4XSentenceBreakIteratorUtf8(self.0.segment_utf8(input))) + } + + /// Segments a UTF-16 string. + #[diplomat::rust_link(icu::segmenter::SentenceSegmenter::segment_utf16, FnInStruct)] + pub fn segment_utf16<'a>( + &'a self, + input: &'a [u16], + ) -> Box<ICU4XSentenceBreakIteratorUtf16<'a>> { + Box::new(ICU4XSentenceBreakIteratorUtf16(self.0.segment_utf16(input))) + } + + /// Segments a Latin-1 string. + #[diplomat::rust_link(icu::segmenter::SentenceSegmenter::segment_latin1, FnInStruct)] + pub fn segment_latin1<'a>( + &'a self, + input: &'a [u8], + ) -> Box<ICU4XSentenceBreakIteratorLatin1<'a>> { + Box::new(ICU4XSentenceBreakIteratorLatin1( + self.0.segment_latin1(input), + )) + } + } + + impl<'a> ICU4XSentenceBreakIteratorUtf8<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::SentenceBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } + + impl<'a> ICU4XSentenceBreakIteratorUtf16<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::SentenceBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } + + impl<'a> ICU4XSentenceBreakIteratorLatin1<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::SentenceBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::SentenceBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + } +} diff --git a/intl/icu_capi/src/segmenter_word.rs b/intl/icu_capi/src/segmenter_word.rs new file mode 100644 index 0000000000..226bcdd334 --- /dev/null +++ b/intl/icu_capi/src/segmenter_word.rs @@ -0,0 +1,213 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use core::convert::TryFrom; + use icu_segmenter::{ + WordBreakIteratorLatin1, WordBreakIteratorPotentiallyIllFormedUtf8, WordBreakIteratorUtf16, + WordSegmenter, WordType, + }; + + #[diplomat::enum_convert(WordType, needs_wildcard)] + #[diplomat::rust_link(icu::segmenter::WordType, Enum)] + pub enum ICU4XSegmenterWordType { + None = 0, + Number = 1, + Letter = 2, + } + + #[diplomat::opaque] + /// An ICU4X word-break segmenter, capable of finding word breakpoints in strings. + #[diplomat::rust_link(icu::segmenter::WordSegmenter, Struct)] + pub struct ICU4XWordSegmenter(WordSegmenter); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::WordBreakIterator, Struct)] + #[diplomat::rust_link( + icu::segmenter::WordBreakIteratorPotentiallyIllFormedUtf8, + Typedef, + hidden + )] + pub struct ICU4XWordBreakIteratorUtf8<'a>(WordBreakIteratorPotentiallyIllFormedUtf8<'a, 'a>); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::WordBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::WordBreakIteratorUtf16, Typedef, hidden)] + pub struct ICU4XWordBreakIteratorUtf16<'a>(WordBreakIteratorUtf16<'a, 'a>); + + #[diplomat::opaque] + #[diplomat::rust_link(icu::segmenter::WordBreakIterator, Struct)] + #[diplomat::rust_link(icu::segmenter::WordBreakIteratorLatin1, Typedef, hidden)] + pub struct ICU4XWordBreakIteratorLatin1<'a>(WordBreakIteratorLatin1<'a, 'a>); + + impl ICU4XWordSegmenter { + /// Construct an [`ICU4XWordSegmenter`] with automatically selecting the best available LSTM + /// or dictionary payload data. + /// + /// Note: currently, it uses dictionary for Chinese and Japanese, and LSTM for Burmese, + /// Khmer, Lao, and Thai. + #[diplomat::rust_link(icu::segmenter::WordSegmenter::new_auto, FnInStruct)] + pub fn create_auto( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XWordSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XWordSegmenter(call_constructor!( + WordSegmenter::new_auto [r => Ok(r)], + WordSegmenter::try_new_auto_with_any_provider, + WordSegmenter::try_new_auto_with_buffer_provider, + provider + )?))) + } + + /// Construct an [`ICU4XWordSegmenter`] with LSTM payload data for Burmese, Khmer, Lao, and + /// Thai. + /// + /// Warning: [`ICU4XWordSegmenter`] created by this function doesn't handle Chinese or + /// Japanese. + #[diplomat::rust_link(icu::segmenter::WordSegmenter::new_lstm, FnInStruct)] + pub fn create_lstm( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XWordSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XWordSegmenter(call_constructor!( + WordSegmenter::new_lstm [r => Ok(r)], + WordSegmenter::try_new_lstm_with_any_provider, + WordSegmenter::try_new_lstm_with_buffer_provider, + provider, + )?))) + } + + /// Construct an [`ICU4XWordSegmenter`] with dictionary payload data for Chinese, Japanese, + /// Burmese, Khmer, Lao, and Thai. + #[diplomat::rust_link(icu::segmenter::WordSegmenter::new_dictionary, FnInStruct)] + pub fn create_dictionary( + provider: &ICU4XDataProvider, + ) -> Result<Box<ICU4XWordSegmenter>, ICU4XError> { + Ok(Box::new(ICU4XWordSegmenter(call_constructor!( + WordSegmenter::new_dictionary [r => Ok(r)], + WordSegmenter::try_new_dictionary_with_any_provider, + WordSegmenter::try_new_dictionary_with_buffer_provider, + provider, + )?))) + } + + /// Segments a (potentially ill-formed) UTF-8 string. + #[diplomat::rust_link(icu::segmenter::WordSegmenter::segment_utf8, FnInStruct)] + #[diplomat::rust_link(icu::segmenter::WordSegmenter::segment_str, FnInStruct, hidden)] + pub fn segment_utf8<'a>(&'a self, input: &'a str) -> Box<ICU4XWordBreakIteratorUtf8<'a>> { + let input = input.as_bytes(); // #2520 + Box::new(ICU4XWordBreakIteratorUtf8(self.0.segment_utf8(input))) + } + + /// Segments a UTF-16 string. + #[diplomat::rust_link(icu::segmenter::WordSegmenter::segment_utf16, FnInStruct)] + pub fn segment_utf16<'a>( + &'a self, + input: &'a [u16], + ) -> Box<ICU4XWordBreakIteratorUtf16<'a>> { + Box::new(ICU4XWordBreakIteratorUtf16(self.0.segment_utf16(input))) + } + + /// Segments a Latin-1 string. + #[diplomat::rust_link(icu::segmenter::WordSegmenter::segment_latin1, FnInStruct)] + pub fn segment_latin1<'a>( + &'a self, + input: &'a [u8], + ) -> Box<ICU4XWordBreakIteratorLatin1<'a>> { + Box::new(ICU4XWordBreakIteratorLatin1(self.0.segment_latin1(input))) + } + } + + impl<'a> ICU4XWordBreakIteratorUtf8<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::WordBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + + /// Return the status value of break boundary. + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::word_type, FnInStruct)] + pub fn word_type(&self) -> ICU4XSegmenterWordType { + self.0.word_type().into() + } + + /// Return true when break boundary is word-like such as letter/number/CJK + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::is_word_like, FnInStruct)] + pub fn is_word_like(&self) -> bool { + self.0.is_word_like() + } + } + + impl<'a> ICU4XWordBreakIteratorUtf16<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::WordBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + + /// Return the status value of break boundary. + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::word_type, FnInStruct)] + pub fn word_type(&self) -> ICU4XSegmenterWordType { + self.0.word_type().into() + } + + /// Return true when break boundary is word-like such as letter/number/CJK + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::is_word_like, FnInStruct)] + pub fn is_word_like(&self) -> bool { + self.0.is_word_like() + } + } + + impl<'a> ICU4XWordBreakIteratorLatin1<'a> { + /// Finds the next breakpoint. Returns -1 if at the end of the string or if the index is + /// out of range of a 32-bit signed integer. + #[allow(clippy::should_implement_trait)] + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::next, FnInStruct)] + #[diplomat::rust_link( + icu::segmenter::WordBreakIterator::Item, + AssociatedTypeInStruct, + hidden + )] + pub fn next(&mut self) -> i32 { + self.0 + .next() + .and_then(|u| i32::try_from(u).ok()) + .unwrap_or(-1) + } + + /// Return the status value of break boundary. + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::word_type, FnInStruct)] + pub fn word_type(&self) -> ICU4XSegmenterWordType { + self.0.word_type().into() + } + + /// Return true when break boundary is word-like such as letter/number/CJK + #[diplomat::rust_link(icu::segmenter::WordBreakIterator::is_word_like, FnInStruct)] + pub fn is_word_like(&self) -> bool { + self.0.is_word_like() + } + } +} diff --git a/intl/icu_capi/src/time.rs b/intl/icu_capi/src/time.rs new file mode 100644 index 0000000000..2a6e6e4d6e --- /dev/null +++ b/intl/icu_capi/src/time.rs @@ -0,0 +1,68 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + + use icu_calendar::types::Time; + + use crate::errors::ffi::ICU4XError; + + #[diplomat::opaque] + /// An ICU4X Time object representing a time in terms of hour, minute, second, nanosecond + #[diplomat::rust_link(icu::calendar::types::Time, Struct)] + pub struct ICU4XTime(pub Time); + + impl ICU4XTime { + /// Creates a new [`ICU4XTime`] given field values + #[diplomat::rust_link(icu::calendar::types::Time, Struct)] + pub fn create( + hour: u8, + minute: u8, + second: u8, + nanosecond: u32, + ) -> Result<Box<ICU4XTime>, ICU4XError> { + let hour = hour.try_into()?; + let minute = minute.try_into()?; + let second = second.try_into()?; + let nanosecond = nanosecond.try_into()?; + let time = Time { + hour, + minute, + second, + nanosecond, + }; + Ok(Box::new(ICU4XTime(time))) + } + + /// Creates a new [`ICU4XTime`] representing midnight (00:00.000). + #[diplomat::rust_link(icu::calendar::types::Time, Struct)] + pub fn create_midnight() -> Result<Box<ICU4XTime>, ICU4XError> { + let time = Time::midnight(); + Ok(Box::new(ICU4XTime(time))) + } + + /// Returns the hour in this time + #[diplomat::rust_link(icu::calendar::types::Time::hour, StructField)] + pub fn hour(&self) -> u8 { + self.0.hour.into() + } + /// Returns the minute in this time + #[diplomat::rust_link(icu::calendar::types::Time::minute, StructField)] + pub fn minute(&self) -> u8 { + self.0.minute.into() + } + /// Returns the second in this time + #[diplomat::rust_link(icu::calendar::types::Time::second, StructField)] + pub fn second(&self) -> u8 { + self.0.second.into() + } + /// Returns the nanosecond in this time + #[diplomat::rust_link(icu::calendar::types::Time::nanosecond, StructField)] + pub fn nanosecond(&self) -> u32 { + self.0.nanosecond.into() + } + } +} diff --git a/intl/icu_capi/src/timezone.rs b/intl/icu_capi/src/timezone.rs new file mode 100644 index 0000000000..4c8b2659b7 --- /dev/null +++ b/intl/icu_capi/src/timezone.rs @@ -0,0 +1,328 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use icu_timezone::CustomTimeZone; + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use alloc::boxed::Box; + use core::fmt::Write; + use core::str::{self}; + use icu_timezone::CustomTimeZone; + use icu_timezone::GmtOffset; + use icu_timezone::ZoneVariant; + + #[diplomat::opaque] + #[diplomat::rust_link(icu::timezone::CustomTimeZone, Struct)] + pub struct ICU4XCustomTimeZone(pub CustomTimeZone); + + impl ICU4XCustomTimeZone { + /// Creates a time zone from an offset string. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::from_str, FnInStruct)] + #[diplomat::rust_link(icu::timezone::CustomTimeZone::try_from_bytes, FnInStruct, hidden)] + #[diplomat::rust_link(icu::timezone::GmtOffset::from_str, FnInStruct, hidden)] + #[diplomat::rust_link(icu::timezone::GmtOffset::try_from_bytes, FnInStruct, hidden)] + pub fn create_from_string(s: &str) -> Result<Box<ICU4XCustomTimeZone>, ICU4XError> { + let bytes = s.as_bytes(); + Ok(Box::new(ICU4XCustomTimeZone::from( + CustomTimeZone::try_from_bytes(bytes)?, + ))) + } + + /// Creates a time zone with no information. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::new_empty, FnInStruct)] + pub fn create_empty() -> Box<ICU4XCustomTimeZone> { + Box::new(CustomTimeZone::new_empty().into()) + } + + /// Creates a time zone for UTC. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::utc, FnInStruct)] + #[diplomat::rust_link(icu::timezone::GmtOffset::utc, FnInStruct, hidden)] + pub fn create_utc() -> Box<ICU4XCustomTimeZone> { + Box::new(CustomTimeZone::utc().into()) + } + + /// Sets the `gmt_offset` field from offset seconds. + /// + /// Errors if the offset seconds are out of range. + #[diplomat::rust_link(icu::timezone::GmtOffset::try_from_offset_seconds, FnInStruct)] + #[diplomat::rust_link(icu::timezone::GmtOffset, Struct, compact)] + #[diplomat::rust_link( + icu::timezone::GmtOffset::from_offset_seconds_unchecked, + FnInStruct, + hidden + )] + #[diplomat::rust_link(icu::timezone::CustomTimeZone::new_with_offset, FnInStruct, hidden)] + pub fn try_set_gmt_offset_seconds( + &mut self, + offset_seconds: i32, + ) -> Result<(), ICU4XError> { + self.0.gmt_offset = Some(GmtOffset::try_from_offset_seconds(offset_seconds)?); + Ok(()) + } + + /// Clears the `gmt_offset` field. + #[diplomat::rust_link(icu::timezone::GmtOffset::offset_seconds, FnInStruct)] + #[diplomat::rust_link(icu::timezone::GmtOffset, Struct, compact)] + pub fn clear_gmt_offset(&mut self) { + self.0.gmt_offset.take(); + } + + /// Returns the value of the `gmt_offset` field as offset seconds. + /// + /// Errors if the `gmt_offset` field is empty. + #[diplomat::rust_link(icu::timezone::GmtOffset::offset_seconds, FnInStruct)] + #[diplomat::rust_link(icu::timezone::GmtOffset, Struct, compact)] + pub fn gmt_offset_seconds(&self) -> Result<i32, ICU4XError> { + self.0 + .gmt_offset + .ok_or(ICU4XError::TimeZoneMissingInputError) + .map(GmtOffset::offset_seconds) + } + + /// Returns whether the `gmt_offset` field is positive. + /// + /// Errors if the `gmt_offset` field is empty. + #[diplomat::rust_link(icu::timezone::GmtOffset::is_positive, FnInStruct)] + pub fn is_gmt_offset_positive(&self) -> Result<bool, ICU4XError> { + self.0 + .gmt_offset + .ok_or(ICU4XError::TimeZoneMissingInputError) + .map(GmtOffset::is_positive) + } + + /// Returns whether the `gmt_offset` field is zero. + /// + /// Errors if the `gmt_offset` field is empty (which is not the same as zero). + #[diplomat::rust_link(icu::timezone::GmtOffset::is_zero, FnInStruct)] + pub fn is_gmt_offset_zero(&self) -> Result<bool, ICU4XError> { + self.0 + .gmt_offset + .ok_or(ICU4XError::TimeZoneMissingInputError) + .map(GmtOffset::is_zero) + } + + /// Returns whether the `gmt_offset` field has nonzero minutes. + /// + /// Errors if the `gmt_offset` field is empty. + #[diplomat::rust_link(icu::timezone::GmtOffset::has_minutes, FnInStruct)] + pub fn gmt_offset_has_minutes(&self) -> Result<bool, ICU4XError> { + self.0 + .gmt_offset + .ok_or(ICU4XError::TimeZoneMissingInputError) + .map(GmtOffset::has_minutes) + } + + /// Returns whether the `gmt_offset` field has nonzero seconds. + /// + /// Errors if the `gmt_offset` field is empty. + #[diplomat::rust_link(icu::timezone::GmtOffset::has_seconds, FnInStruct)] + pub fn gmt_offset_has_seconds(&self) -> Result<bool, ICU4XError> { + self.0 + .gmt_offset + .ok_or(ICU4XError::TimeZoneMissingInputError) + .map(GmtOffset::has_seconds) + } + + /// Sets the `time_zone_id` field from a BCP-47 string. + /// + /// Errors if the string is not a valid BCP-47 time zone ID. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::time_zone_id, StructField)] + #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id, Struct, compact)] + #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::from_str, FnInStruct, hidden)] + pub fn try_set_time_zone_id(&mut self, id: &str) -> Result<(), ICU4XError> { + self.0.time_zone_id = Some(id.parse()?); + Ok(()) + } + + /// Sets the `time_zone_id` field from an IANA string by looking up + /// the corresponding BCP-47 string. + /// + /// Errors if the string is not a valid BCP-47 time zone ID. + #[diplomat::rust_link(icu::timezone::IanaToBcp47MapperBorrowed::get, FnInStruct)] + pub fn try_set_iana_time_zone_id( + &mut self, + mapper: &crate::iana_bcp47_mapper::ffi::ICU4XIanaToBcp47Mapper, + id: &str, + ) -> Result<(), ICU4XError> { + match mapper.0.as_borrowed().get(id) { + Some(id) => { + self.0.time_zone_id = Some(id); + Ok(()) + } + None => Err(ICU4XError::TimeZoneInvalidIdError), + } + } + + /// Clears the `time_zone_id` field. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::time_zone_id, StructField)] + #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id, Struct, compact)] + pub fn clear_time_zone_id(&mut self) { + self.0.time_zone_id.take(); + } + + /// Writes the value of the `time_zone_id` field as a string. + /// + /// Errors if the `time_zone_id` field is empty. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::time_zone_id, StructField)] + #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id, Struct, compact)] + pub fn time_zone_id( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + write.write_str( + self.0 + .time_zone_id + .ok_or(ICU4XError::TimeZoneMissingInputError)? + .0 + .as_str(), + )?; + Ok(()) + } + + /// Sets the `metazone_id` field from a string. + /// + /// Errors if the string is not a valid BCP-47 metazone ID. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::metazone_id, StructField)] + #[diplomat::rust_link(icu::timezone::MetazoneId, Struct, compact)] + #[diplomat::rust_link(icu::timezone::MetazoneId::from_str, FnInStruct, hidden)] + pub fn try_set_metazone_id(&mut self, id: &str) -> Result<(), ICU4XError> { + self.0.metazone_id = Some(id.parse()?); + Ok(()) + } + + /// Clears the `metazone_id` field. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::metazone_id, StructField)] + #[diplomat::rust_link(icu::timezone::MetazoneId, Struct, compact)] + pub fn clear_metazone_id(&mut self) { + self.0.metazone_id.take(); + } + + /// Writes the value of the `metazone_id` field as a string. + /// + /// Errors if the `metazone_id` field is empty. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::metazone_id, StructField)] + #[diplomat::rust_link(icu::timezone::MetazoneId, Struct, compact)] + pub fn metazone_id( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + write.write_str( + self.0 + .metazone_id + .ok_or(ICU4XError::TimeZoneMissingInputError)? + .0 + .as_str(), + )?; + Ok(()) + } + + /// Sets the `zone_variant` field from a string. + /// + /// Errors if the string is not a valid zone variant. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::zone_variant, StructField)] + #[diplomat::rust_link(icu::timezone::ZoneVariant, Struct, compact)] + #[diplomat::rust_link(icu::timezone::ZoneVariant::from_str, FnInStruct, hidden)] + pub fn try_set_zone_variant(&mut self, id: &str) -> Result<(), ICU4XError> { + self.0.zone_variant = Some(id.parse()?); + Ok(()) + } + + /// Clears the `zone_variant` field. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::zone_variant, StructField)] + #[diplomat::rust_link(icu::timezone::ZoneVariant, Struct, compact)] + pub fn clear_zone_variant(&mut self) { + self.0.zone_variant.take(); + } + + /// Writes the value of the `zone_variant` field as a string. + /// + /// Errors if the `zone_variant` field is empty. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::zone_variant, StructField)] + #[diplomat::rust_link(icu::timezone::ZoneVariant, Struct, compact)] + pub fn zone_variant( + &self, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + write.write_str( + self.0 + .zone_variant + .ok_or(ICU4XError::TimeZoneMissingInputError)? + .0 + .as_str(), + )?; + Ok(()) + } + + /// Sets the `zone_variant` field to standard time. + #[diplomat::rust_link(icu::timezone::ZoneVariant::standard, FnInStruct)] + #[diplomat::rust_link(icu::timezone::CustomTimeZone::zone_variant, StructField, compact)] + pub fn set_standard_time(&mut self) { + self.0.zone_variant = Some(ZoneVariant::standard()) + } + + /// Sets the `zone_variant` field to daylight time. + #[diplomat::rust_link(icu::timezone::ZoneVariant::daylight, FnInStruct)] + #[diplomat::rust_link(icu::timezone::CustomTimeZone::zone_variant, StructField, compact)] + pub fn set_daylight_time(&mut self) { + self.0.zone_variant = Some(ZoneVariant::daylight()) + } + + /// Returns whether the `zone_variant` field is standard time. + /// + /// Errors if the `zone_variant` field is empty. + #[diplomat::rust_link(icu::timezone::ZoneVariant::standard, FnInStruct)] + #[diplomat::rust_link(icu::timezone::CustomTimeZone::zone_variant, StructField, compact)] + pub fn is_standard_time(&self) -> Result<bool, ICU4XError> { + Ok(self + .0 + .zone_variant + .ok_or(ICU4XError::TimeZoneMissingInputError)? + == ZoneVariant::standard()) + } + + /// Returns whether the `zone_variant` field is daylight time. + /// + /// Errors if the `zone_variant` field is empty. + #[diplomat::rust_link(icu::timezone::ZoneVariant::daylight, FnInStruct)] + #[diplomat::rust_link(icu::timezone::CustomTimeZone::zone_variant, StructField, compact)] + pub fn is_daylight_time(&self) -> Result<bool, ICU4XError> { + Ok(self + .0 + .zone_variant + .ok_or(ICU4XError::TimeZoneMissingInputError)? + == ZoneVariant::daylight()) + } + + /// Sets the metazone based on the time zone and the local timestamp. + #[diplomat::rust_link(icu::timezone::CustomTimeZone::maybe_calculate_metazone, FnInStruct)] + #[diplomat::rust_link( + icu::timezone::MetazoneCalculator::compute_metazone_from_time_zone, + FnInStruct, + compact + )] + #[cfg(feature = "icu_timezone")] + pub fn maybe_calculate_metazone( + &mut self, + metazone_calculator: &crate::metazone_calculator::ffi::ICU4XMetazoneCalculator, + local_datetime: &crate::datetime::ffi::ICU4XIsoDateTime, + ) { + self.0 + .maybe_calculate_metazone(&metazone_calculator.0, &local_datetime.0); + } + } +} + +impl From<CustomTimeZone> for ffi::ICU4XCustomTimeZone { + fn from(other: CustomTimeZone) -> Self { + Self(other) + } +} + +impl From<ffi::ICU4XCustomTimeZone> for CustomTimeZone { + fn from(other: ffi::ICU4XCustomTimeZone) -> Self { + other.0 + } +} diff --git a/intl/icu_capi/src/timezone_formatter.rs b/intl/icu_capi/src/timezone_formatter.rs new file mode 100644 index 0000000000..0cd4b04bc3 --- /dev/null +++ b/intl/icu_capi/src/timezone_formatter.rs @@ -0,0 +1,298 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use icu_datetime::time_zone::{FallbackFormat, TimeZoneFormatterOptions}; + +macro_rules! call_method { + ($self:ident, $compiled:ident, $unstable:ident, $provider:expr) => { + match &$provider.0 { + $crate::provider::ICU4XDataProviderInner::Destroyed => Err( + icu_provider::DataError::custom("This provider has been destroyed"), + )?, + $crate::provider::ICU4XDataProviderInner::Empty => $self + .0 + .$unstable(&icu_provider_adapters::empty::EmptyDataProvider::new()), + #[cfg(feature = "buffer_provider")] + $crate::provider::ICU4XDataProviderInner::Buffer(buffer_provider) => $self.0.$unstable( + &icu_provider::AsDeserializingBufferProvider::as_deserializing(buffer_provider), + ), + #[cfg(feature = "compiled_data")] + $crate::provider::ICU4XDataProviderInner::Compiled => $self.0.$compiled(), + } + }; +} + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::locale::ffi::ICU4XLocale; + use crate::provider::ffi::ICU4XDataProvider; + use crate::timezone::ffi::ICU4XCustomTimeZone; + use alloc::boxed::Box; + use icu_datetime::time_zone::FallbackFormat; + use icu_datetime::time_zone::IsoFormat; + use icu_datetime::time_zone::IsoMinutes; + use icu_datetime::time_zone::IsoSeconds; + use icu_datetime::time_zone::TimeZoneFormatter; + use writeable::Writeable; + + #[diplomat::opaque] + /// An ICU4X TimeZoneFormatter object capable of formatting an [`ICU4XCustomTimeZone`] type (and others) as a string + #[diplomat::rust_link(icu::datetime::time_zone::TimeZoneFormatter, Struct)] + pub struct ICU4XTimeZoneFormatter(pub TimeZoneFormatter); + + #[diplomat::enum_convert(IsoFormat, needs_wildcard)] + #[diplomat::rust_link(icu::datetime::time_zone::IsoFormat, Enum)] + pub enum ICU4XIsoTimeZoneFormat { + Basic, + Extended, + UtcBasic, + UtcExtended, + } + + #[diplomat::enum_convert(IsoMinutes, needs_wildcard)] + #[diplomat::rust_link(icu::datetime::time_zone::IsoMinutes, Enum)] + pub enum ICU4XIsoTimeZoneMinuteDisplay { + Required, + Optional, + } + + #[diplomat::enum_convert(IsoSeconds, needs_wildcard)] + #[diplomat::rust_link(icu::datetime::time_zone::IsoSeconds, Enum)] + pub enum ICU4XIsoTimeZoneSecondDisplay { + Optional, + Never, + } + + pub struct ICU4XIsoTimeZoneOptions { + pub format: ICU4XIsoTimeZoneFormat, + pub minutes: ICU4XIsoTimeZoneMinuteDisplay, + pub seconds: ICU4XIsoTimeZoneSecondDisplay, + } + + impl ICU4XTimeZoneFormatter { + /// Creates a new [`ICU4XTimeZoneFormatter`] from locale data. + /// + /// Uses localized GMT as the fallback format. + #[diplomat::rust_link(icu::datetime::time_zone::TimeZoneFormatter::try_new, FnInStruct)] + #[diplomat::rust_link(icu::datetime::time_zone::FallbackFormat, Enum, compact)] + #[diplomat::rust_link(icu::datetime::time_zone::TimeZoneFormatterOptions, Struct, hidden)] + pub fn create_with_localized_gmt_fallback( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XTimeZoneFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XTimeZoneFormatter(call_constructor!( + TimeZoneFormatter::try_new, + TimeZoneFormatter::try_new_with_any_provider, + TimeZoneFormatter::try_new_with_buffer_provider, + provider, + &locale, + FallbackFormat::LocalizedGmt.into(), + )?))) + } + + /// Creates a new [`ICU4XTimeZoneFormatter`] from locale data. + /// + /// Uses ISO-8601 as the fallback format. + #[diplomat::rust_link(icu::datetime::time_zone::TimeZoneFormatter::try_new, FnInStruct)] + #[diplomat::rust_link(icu::datetime::time_zone::FallbackFormat, Enum, compact)] + #[diplomat::rust_link(icu::datetime::time_zone::TimeZoneFormatterOptions, Struct, hidden)] + pub fn create_with_iso_8601_fallback( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + options: ICU4XIsoTimeZoneOptions, + ) -> Result<Box<ICU4XTimeZoneFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XTimeZoneFormatter(call_constructor!( + TimeZoneFormatter::try_new, + TimeZoneFormatter::try_new_with_any_provider, + TimeZoneFormatter::try_new_with_buffer_provider, + provider, + &locale, + options.into(), + )?))) + } + + /// Loads generic non-location long format. Example: "Pacific Time" + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::include_generic_non_location_long, + FnInStruct + )] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::load_generic_non_location_long, + FnInStruct, + hidden + )] + pub fn load_generic_non_location_long( + &mut self, + provider: &ICU4XDataProvider, + ) -> Result<(), ICU4XError> { + call_method!( + self, + include_generic_non_location_long, + load_generic_non_location_long, + provider + )?; + Ok(()) + } + + /// Loads generic non-location short format. Example: "PT" + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::include_generic_non_location_short, + FnInStruct + )] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::load_generic_non_location_short, + FnInStruct, + hidden + )] + pub fn load_generic_non_location_short( + &mut self, + provider: &ICU4XDataProvider, + ) -> Result<(), ICU4XError> { + call_method!( + self, + include_generic_non_location_short, + load_generic_non_location_short, + provider + )?; + Ok(()) + } + + /// Loads specific non-location long format. Example: "Pacific Standard Time" + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::include_specific_non_location_long, + FnInStruct + )] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::load_specific_non_location_long, + FnInStruct, + hidden + )] + pub fn load_specific_non_location_long( + &mut self, + provider: &ICU4XDataProvider, + ) -> Result<(), ICU4XError> { + call_method!( + self, + include_specific_non_location_long, + load_specific_non_location_long, + provider + )?; + Ok(()) + } + + /// Loads specific non-location short format. Example: "PST" + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::include_specific_non_location_short, + FnInStruct + )] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::load_specific_non_location_short, + FnInStruct, + hidden + )] + pub fn load_specific_non_location_short( + &mut self, + provider: &ICU4XDataProvider, + ) -> Result<(), ICU4XError> { + call_method!( + self, + include_specific_non_location_short, + load_specific_non_location_short, + provider + )?; + Ok(()) + } + + /// Loads generic location format. Example: "Los Angeles Time" + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::include_generic_location_format, + FnInStruct + )] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::load_generic_location_format, + FnInStruct, + hidden + )] + pub fn load_generic_location_format( + &mut self, + provider: &ICU4XDataProvider, + ) -> Result<(), ICU4XError> { + call_method!( + self, + include_generic_location_format, + load_generic_location_format, + provider + )?; + Ok(()) + } + + /// Loads localized GMT format. Example: "GMT-07:00" + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::include_localized_gmt_format, + FnInStruct + )] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::load_localized_gmt_format, + FnInStruct, + hidden + )] + pub fn include_localized_gmt_format(&mut self) -> Result<(), ICU4XError> { + self.0.include_localized_gmt_format()?; + Ok(()) + } + + /// Loads ISO-8601 format. Example: "-07:00" + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::include_iso_8601_format, + FnInStruct + )] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::load_iso_8601_format, + FnInStruct, + hidden + )] + pub fn load_iso_8601_format( + &mut self, + options: ICU4XIsoTimeZoneOptions, + ) -> Result<(), ICU4XError> { + self.0.include_iso_8601_format( + options.format.into(), + options.minutes.into(), + options.seconds.into(), + )?; + Ok(()) + } + + /// Formats a [`ICU4XCustomTimeZone`] to a string. + #[diplomat::rust_link(icu::datetime::time_zone::TimeZoneFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::time_zone::TimeZoneFormatter::format_to_string, + FnInStruct + )] + pub fn format_custom_time_zone( + &self, + value: &ICU4XCustomTimeZone, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&value.0).write_to(write)?; + Ok(()) + } + } +} + +impl From<ffi::ICU4XIsoTimeZoneOptions> for TimeZoneFormatterOptions { + fn from(other: ffi::ICU4XIsoTimeZoneOptions) -> Self { + FallbackFormat::Iso8601( + other.format.into(), + other.minutes.into(), + other.seconds.into(), + ) + .into() + } +} diff --git a/intl/icu_capi/src/week.rs b/intl/icu_capi/src/week.rs new file mode 100644 index 0000000000..f82b5aca07 --- /dev/null +++ b/intl/icu_capi/src/week.rs @@ -0,0 +1,93 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use icu_calendar::week::WeekOf; + +#[diplomat::bridge] +pub mod ffi { + use crate::date::ffi::ICU4XIsoWeekday; + use crate::errors::ffi::ICU4XError; + use crate::locale::ffi::ICU4XLocale; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use icu_calendar::week::{RelativeUnit, WeekCalculator}; + + #[diplomat::rust_link(icu::calendar::week::RelativeUnit, Enum)] + #[diplomat::enum_convert(RelativeUnit)] + pub enum ICU4XWeekRelativeUnit { + Previous, + Current, + Next, + } + + #[diplomat::rust_link(icu::calendar::week::WeekOf, Struct)] + pub struct ICU4XWeekOf { + pub week: u16, + pub unit: ICU4XWeekRelativeUnit, + } + /// A Week calculator, useful to be passed in to `week_of_year()` on Date and DateTime types + #[diplomat::opaque] + #[diplomat::rust_link(icu::calendar::week::WeekCalculator, Struct)] + pub struct ICU4XWeekCalculator(pub WeekCalculator); + + impl ICU4XWeekCalculator { + /// Creates a new [`ICU4XWeekCalculator`] from locale data. + #[diplomat::rust_link(icu::calendar::week::WeekCalculator::try_new, FnInStruct)] + pub fn create( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + ) -> Result<Box<ICU4XWeekCalculator>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XWeekCalculator(call_constructor!( + WeekCalculator::try_new, + WeekCalculator::try_new_with_any_provider, + WeekCalculator::try_new_with_buffer_provider, + provider, + &locale, + )?))) + } + + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::first_weekday, + StructField, + compact + )] + #[diplomat::rust_link( + icu::calendar::week::WeekCalculator::min_week_days, + StructField, + compact + )] + pub fn create_from_first_day_of_week_and_min_week_days( + first_weekday: ICU4XIsoWeekday, + min_week_days: u8, + ) -> Box<ICU4XWeekCalculator> { + let mut calculator = WeekCalculator::default(); + calculator.first_weekday = first_weekday.into(); + calculator.min_week_days = min_week_days; + Box::new(ICU4XWeekCalculator(calculator)) + } + + /// Returns the weekday that starts the week for this object's locale + #[diplomat::rust_link(icu::calendar::week::WeekCalculator::first_weekday, StructField)] + pub fn first_weekday(&self) -> ICU4XIsoWeekday { + self.0.first_weekday.into() + } + /// The minimum number of days overlapping a year required for a week to be + /// considered part of that year + #[diplomat::rust_link(icu::calendar::week::WeekCalculator::min_week_days, StructField)] + pub fn min_week_days(&self) -> u8 { + self.0.min_week_days + } + } +} + +impl From<WeekOf> for ffi::ICU4XWeekOf { + fn from(other: WeekOf) -> Self { + ffi::ICU4XWeekOf { + week: other.week, + unit: other.unit.into(), + } + } +} diff --git a/intl/icu_capi/src/zoned_formatter.rs b/intl/icu_capi/src/zoned_formatter.rs new file mode 100644 index 0000000000..6cafaa7666 --- /dev/null +++ b/intl/icu_capi/src/zoned_formatter.rs @@ -0,0 +1,198 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[diplomat::bridge] +pub mod ffi { + use alloc::boxed::Box; + use icu_calendar::{DateTime, Gregorian}; + use icu_datetime::{options::length, TypedZonedDateTimeFormatter, ZonedDateTimeFormatter}; + use writeable::Writeable; + + use crate::{ + datetime::ffi::ICU4XDateTime, datetime::ffi::ICU4XIsoDateTime, + datetime_formatter::ffi::ICU4XDateLength, datetime_formatter::ffi::ICU4XTimeLength, + errors::ffi::ICU4XError, locale::ffi::ICU4XLocale, provider::ffi::ICU4XDataProvider, + timezone::ffi::ICU4XCustomTimeZone, timezone_formatter::ffi::ICU4XIsoTimeZoneOptions, + }; + + // TODO(https://github.com/rust-diplomat/diplomat/issues/248) + #[allow(unused_imports)] + use crate::{ + timezone_formatter::ffi::ICU4XIsoTimeZoneFormat, + timezone_formatter::ffi::ICU4XIsoTimeZoneMinuteDisplay, + timezone_formatter::ffi::ICU4XIsoTimeZoneSecondDisplay, + }; + + #[diplomat::opaque] + /// An object capable of formatting a date time with time zone to a string. + #[diplomat::rust_link(icu::datetime::TypedZonedDateTimeFormatter, Struct)] + pub struct ICU4XGregorianZonedDateTimeFormatter(pub TypedZonedDateTimeFormatter<Gregorian>); + + impl ICU4XGregorianZonedDateTimeFormatter { + /// Creates a new [`ICU4XGregorianZonedDateTimeFormatter`] from locale data. + /// + /// This function has `date_length` and `time_length` arguments and uses default options + /// for the time zone. + #[diplomat::rust_link(icu::datetime::TypedZonedDateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + ) -> Result<Box<ICU4XGregorianZonedDateTimeFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XGregorianZonedDateTimeFormatter( + call_constructor!( + TypedZonedDateTimeFormatter::<Gregorian>::try_new, + TypedZonedDateTimeFormatter::<Gregorian>::try_new_with_any_provider, + TypedZonedDateTimeFormatter::<Gregorian>::try_new_with_buffer_provider, + provider, + &locale, + length::Bag::from_date_time_style(date_length.into(), time_length.into()) + .into(), + Default::default(), + )?, + ))) + } + + /// Creates a new [`ICU4XGregorianZonedDateTimeFormatter`] from locale data. + /// + /// This function has `date_length` and `time_length` arguments and uses an ISO-8601 style + /// fallback for the time zone with the given configurations. + #[diplomat::rust_link(icu::datetime::TypedZonedDateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths_and_iso_8601_time_zone_fallback( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + zone_options: ICU4XIsoTimeZoneOptions, + ) -> Result<Box<ICU4XGregorianZonedDateTimeFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XGregorianZonedDateTimeFormatter( + call_constructor!( + TypedZonedDateTimeFormatter::<Gregorian>::try_new, + TypedZonedDateTimeFormatter::<Gregorian>::try_new_with_any_provider, + TypedZonedDateTimeFormatter::<Gregorian>::try_new_with_buffer_provider, + provider, + &locale, + length::Bag::from_date_time_style(date_length.into(), time_length.into()) + .into(), + zone_options.into(), + )?, + ))) + } + + /// Formats a [`ICU4XIsoDateTime`] and [`ICU4XCustomTimeZone`] to a string. + #[diplomat::rust_link(icu::datetime::TypedZonedDateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::TypedZonedDateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime_with_custom_time_zone( + &self, + datetime: &ICU4XIsoDateTime, + time_zone: &ICU4XCustomTimeZone, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + let greg = DateTime::new_from_iso(datetime.0, Gregorian); + self.0.format(&greg, &time_zone.0).write_to(write)?; + Ok(()) + } + } + + #[diplomat::opaque] + /// An object capable of formatting a date time with time zone to a string. + #[diplomat::rust_link(icu::datetime::ZonedDateTimeFormatter, Struct)] + pub struct ICU4XZonedDateTimeFormatter(pub ZonedDateTimeFormatter); + + impl ICU4XZonedDateTimeFormatter { + /// Creates a new [`ICU4XZonedDateTimeFormatter`] from locale data. + /// + /// This function has `date_length` and `time_length` arguments and uses default options + /// for the time zone. + #[diplomat::rust_link(icu::datetime::ZonedDateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + ) -> Result<Box<ICU4XZonedDateTimeFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XZonedDateTimeFormatter(call_constructor!( + ZonedDateTimeFormatter::try_new, + ZonedDateTimeFormatter::try_new_with_any_provider, + ZonedDateTimeFormatter::try_new_with_buffer_provider, + provider, + &locale, + length::Bag::from_date_time_style(date_length.into(), time_length.into()).into(), + Default::default(), + )?))) + } + + /// Creates a new [`ICU4XZonedDateTimeFormatter`] from locale data. + /// + /// This function has `date_length` and `time_length` arguments and uses an ISO-8601 style + /// fallback for the time zone with the given configurations. + #[diplomat::rust_link(icu::datetime::ZonedDateTimeFormatter::try_new, FnInStruct)] + pub fn create_with_lengths_and_iso_8601_time_zone_fallback( + provider: &ICU4XDataProvider, + locale: &ICU4XLocale, + date_length: ICU4XDateLength, + time_length: ICU4XTimeLength, + zone_options: ICU4XIsoTimeZoneOptions, + ) -> Result<Box<ICU4XZonedDateTimeFormatter>, ICU4XError> { + let locale = locale.to_datalocale(); + + Ok(Box::new(ICU4XZonedDateTimeFormatter(call_constructor!( + ZonedDateTimeFormatter::try_new, + ZonedDateTimeFormatter::try_new_with_any_provider, + ZonedDateTimeFormatter::try_new_with_buffer_provider, + provider, + &locale, + length::Bag::from_date_time_style(date_length.into(), time_length.into()).into(), + zone_options.into(), + )?))) + } + + /// Formats a [`ICU4XDateTime`] and [`ICU4XCustomTimeZone`] to a string. + #[diplomat::rust_link(icu::datetime::ZonedDateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::ZonedDateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_datetime_with_custom_time_zone( + &self, + datetime: &ICU4XDateTime, + time_zone: &ICU4XCustomTimeZone, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0.format(&datetime.0, &time_zone.0)?.write_to(write)?; + Ok(()) + } + + /// Formats a [`ICU4XIsoDateTime`] and [`ICU4XCustomTimeZone`] to a string. + #[diplomat::rust_link(icu::datetime::ZonedDateTimeFormatter::format, FnInStruct)] + #[diplomat::rust_link( + icu::datetime::ZonedDateTimeFormatter::format_to_string, + FnInStruct, + hidden + )] + pub fn format_iso_datetime_with_custom_time_zone( + &self, + datetime: &ICU4XIsoDateTime, + time_zone: &ICU4XCustomTimeZone, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + self.0 + .format(&datetime.0.to_any(), &time_zone.0)? + .write_to(write)?; + Ok(()) + } + } +} |