From d0caaad1b666c9579035e15913483a300c0e9605 Mon Sep 17 00:00:00 2001 From: Filip Filmar Date: Tue, 23 Jun 2020 18:16:35 -0700 Subject: [PATCH] Implement `unum.h` partways Open, close and basic stylistic formatting is implemented and tests added. More methods are underway; but this is a good point to stop and check what we already have. Issue #141. --- rust_icu_uformattable/src/lib.rs | 6 +- rust_icu_ulistformatter/src/lib.rs | 2 + rust_icu_unum/src/lib.rs | 194 ++++++++++++++++++++++++++++- rust_icu_ustring/src/lib.rs | 6 +- 4 files changed, 197 insertions(+), 11 deletions(-) diff --git a/rust_icu_uformattable/src/lib.rs b/rust_icu_uformattable/src/lib.rs index f4b54ca3..a57c7451 100644 --- a/rust_icu_uformattable/src/lib.rs +++ b/rust_icu_uformattable/src/lib.rs @@ -133,14 +133,14 @@ impl<'a> crate::UFormattable<'a> { pub fn get_ustring(&self) -> Result { let mut status = common::Error::OK_CODE; let mut ustrlen = 0i32; - let raw: *const sys::UChar = unsafe { + let raw = unsafe { assert!(common::Error::is_ok(status)); versioned_function!(ufmt_getUChars)( self.rep.as_ptr(), &mut ustrlen, &mut status) - }; + } as *mut sys::UChar; common::Error::ok_or_warning(status)?; let ret = unsafe { - assert_ne!(raw, 0 as *const sys::UChar); + assert_ne!(raw, 0 as *mut sys::UChar); assert!(ustrlen >= 0); ustring::UChar::clone_from_raw_parts(raw, ustrlen) }; diff --git a/rust_icu_ulistformatter/src/lib.rs b/rust_icu_ulistformatter/src/lib.rs index f84b9979..183e2d04 100644 --- a/rust_icu_ulistformatter/src/lib.rs +++ b/rust_icu_ulistformatter/src/lib.rs @@ -99,6 +99,8 @@ impl UListFormatter { } /// Implements `ulistfmt_format`. + // TODO: this method call is repetitive, and should probably be pulled out into a macro. + // TODO: rename this function into format_uchar. pub fn format_uchar(&self, list: &[&str]) -> Result { let list_ustr = UCharArray::try_from(list)?; const CAPACITY: usize = 200; diff --git a/rust_icu_unum/src/lib.rs b/rust_icu_unum/src/lib.rs index 0d7218e0..bf34dc9f 100644 --- a/rust_icu_unum/src/lib.rs +++ b/rust_icu_unum/src/lib.rs @@ -21,7 +21,7 @@ use { rust_icu_sys::versioned_function, rust_icu_sys::*, rust_icu_uloc as uloc, rust_icu_ustring as ustring, - std::{convert::TryFrom, ffi, os::raw, ptr}, + std::{convert::TryFrom, convert::TryInto, ptr}, }; #[derive(Debug)] @@ -37,10 +37,45 @@ impl Drop for UNumberFormat { } impl UNumberFormat { + /// Implements `unum_open`, with a pattern. + pub fn try_new_decimal_pattern_ustring( + pattern: &ustring::UChar, + locale: &uloc::ULoc, + ) -> Result { + UNumberFormat::try_new_style_pattern_ustring( + sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL, + pattern, + locale, + ) + } + + /// Implements `unum_open`, with rule-based formatting, + pub fn try_new_decimal_rulebased_ustring( + rule: &ustring::UChar, + locale: &uloc::ULoc, + ) -> Result { + UNumberFormat::try_new_style_pattern_ustring( + sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED, + rule, + locale, + ) + } + + /// Implements `unum_open`, with style-based formatting. + pub fn try_new_with_style( + style: sys::UNumberFormatStyle, + locale: &uloc::ULoc, + ) -> Result { + let rule = ustring::UChar::try_from("")?; + assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED); + assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL); + UNumberFormat::try_new_style_pattern_ustring(style, &rule, locale) + } + /// Implements `unum_open` - pub fn try_new_ustring( + fn try_new_style_pattern_ustring( style: sys::UNumberFormatStyle, - pattern: ustring::UChar, + pattern: &ustring::UChar, locale: &uloc::ULoc, ) -> Result { let mut status = common::Error::OK_CODE; @@ -81,7 +116,158 @@ impl UNumberFormat { rep: ptr::NonNull::new(rep).unwrap(), }) } + + /// Implements `unum_format` + pub fn format(&self, number: i32) -> Result { + let result = self.format_ustring(number)?; + String::try_from(&result) + } + + /// Implements `unum_format` + // TODO: this method call is repetitive, and should probably be pulled out into a macro. + pub fn format_ustring(&self, number: i32) -> Result { + const CAPACITY: usize = 200; + let mut status = common::Error::OK_CODE; + let mut buf: Vec = vec![0; CAPACITY]; + + let full_len: i32 = unsafe { + assert!(common::Error::is_ok(status)); + versioned_function!(unum_format)( + self.rep.as_ptr(), + number, + buf.as_mut_ptr(), + buf.len() as i32, + // Unsure what this field should be for. + 0 as *mut sys::UFieldPosition, + &mut status, + ) + }; + if status == sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR + || (common::Error::is_ok(status) + && full_len > CAPACITY.try_into().map_err(|e| common::Error::wrapper(e))?) + { + assert!(full_len > 0); + let full_len: usize = full_len.try_into().map_err(|e| common::Error::wrapper(e))?; + buf.resize(full_len, 0); + unsafe { + assert!(common::Error::is_ok(status)); + versioned_function!(unum_format)( + self.rep.as_ptr(), + number, + buf.as_mut_ptr(), + buf.len() as i32, + 0 as *mut sys::UFieldPosition, + &mut status, + ) + }; + } + common::Error::ok_or_warning(status)?; + if full_len >= 0 { + let full_len: usize = full_len.try_into().map_err(|e| common::Error::wrapper(e))?; + buf.resize(full_len, 0); + } + Ok(ustring::UChar::from(buf)) + } } #[cfg(test)] -mod tests {} +mod tests { + + use super::*; + + #[test] + fn format_decimal_pattern_ustring() { + struct TestCase { + locale: &'static str, + pattern: &'static str, + expected: &'static str, + }; + + let tests = vec![TestCase { + locale: "sr-RS", + pattern: "", + expected: "42", + }]; + for test in tests { + let locale = uloc::ULoc::try_from(test.locale).expect("locale exists"); + let pattern = ustring::UChar::try_from(test.pattern).expect("pattern is set"); + let fmt = crate::UNumberFormat::try_new_decimal_pattern_ustring(&pattern, &locale) + .expect("formatter"); + + let result = fmt + .try_clone() + .expect("clone") + .format(42) + .expect("format success"); + assert_eq!(test.expected, result); + } + } + + // TODO: find example rules. + #[test] + #[should_panic(expected = "U_MEMORY_ALLOCATION_ERROR")] + fn format_decimal_rulebased_ustring() { + struct TestCase { + locale: &'static str, + rule: &'static str, + expected: &'static str, + }; + + let tests = vec![TestCase { + locale: "sr-RS", + rule: "", + expected: "42", + }]; + for test in tests { + let locale = uloc::ULoc::try_from(test.locale).expect("locale exists"); + let pattern = ustring::UChar::try_from(test.rule).expect("pattern is set"); + let fmt = crate::UNumberFormat::try_new_decimal_rulebased_ustring(&pattern, &locale) + .expect("formatter"); + + let result = fmt + .try_clone() + .expect("clone") + .format(42) + .expect("format success"); + assert_eq!(test.expected, result); + } + } + + // TODO: add more, and relevant test cases. + #[test] + fn format_style_ustring() { + struct TestCase { + locale: &'static str, + number: i32, + style: sys::UNumberFormatStyle, + expected: &'static str, + }; + + let tests = vec![ + TestCase { + locale: "sr-RS", + number: 42, + style: sys::UNumberFormatStyle::UNUM_CURRENCY, + expected: "42\u{a0}RSD", + }, + TestCase { + locale: "sr-RS", + number: 42, + style: sys::UNumberFormatStyle::UNUM_SPELLOUT, + expected: "четрдесет и два", + }, + ]; + for test in tests { + let locale = uloc::ULoc::try_from(test.locale).expect("locale exists"); + let fmt = + crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter"); + + let result = fmt + .try_clone() + .expect("clone") + .format(test.number) + .expect("format success"); + assert_eq!(test.expected, result); + } + } +} diff --git a/rust_icu_ustring/src/lib.rs b/rust_icu_ustring/src/lib.rs index 6e70e38b..19f05a77 100644 --- a/rust_icu_ustring/src/lib.rs +++ b/rust_icu_ustring/src/lib.rs @@ -178,22 +178,20 @@ impl crate::UChar { /// Does *not* take ownership of the buffer that was passed in. /// /// **DO NOT USE UNLESS YOU HAVE NO OTHER CHOICE.** - #[doc(hidden)] - pub unsafe fn clone_from_raw_parts(rep: *const sys::UChar, len: i32) -> crate::UChar { + pub unsafe fn clone_from_raw_parts(rep: *mut sys::UChar, len: i32) -> crate::UChar { assert!(len >= 0); // Always works for len: i32 >= 0. let cap = len as usize; // View the deconstructed buffer as a vector of UChars. Then make a // copy of it to return. This is not efficient, but is always safe. - let original = Vec::from_raw_parts(rep as *mut sys::UChar, cap, cap); + let original = Vec::from_raw_parts(rep, cap, cap); let copy = original.clone(); // Don't free the buffer we don't own. std::mem::forget(original); crate::UChar::from(copy) } - /// Converts into a zeroed-out string. /// /// This is a very weird ICU API thing, where there apparently exists a zero-terminated