Skip to content

Commit

Permalink
Implement unum.h partways
Browse files Browse the repository at this point in the history
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 google#141.
  • Loading branch information
filmil committed Jun 24, 2020
1 parent 12b99a1 commit d0caaad
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 11 deletions.
6 changes: 3 additions & 3 deletions rust_icu_uformattable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ impl<'a> crate::UFormattable<'a> {
pub fn get_ustring(&self) -> Result<ustring::UChar, common::Error> {
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)
};
Expand Down
2 changes: 2 additions & 0 deletions rust_icu_ulistformatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ustring::UChar, common::Error> {
let list_ustr = UCharArray::try_from(list)?;
const CAPACITY: usize = 200;
Expand Down
194 changes: 190 additions & 4 deletions rust_icu_unum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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, common::Error> {
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, common::Error> {
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<UNumberFormat, common::Error> {
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<UNumberFormat, common::Error> {
let mut status = common::Error::OK_CODE;
Expand Down Expand Up @@ -81,7 +116,158 @@ impl UNumberFormat {
rep: ptr::NonNull::new(rep).unwrap(),
})
}

/// Implements `unum_format`
pub fn format(&self, number: i32) -> Result<String, common::Error> {
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<ustring::UChar, common::Error> {
const CAPACITY: usize = 200;
let mut status = common::Error::OK_CODE;
let mut buf: Vec<sys::UChar> = 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);
}
}
}
6 changes: 2 additions & 4 deletions rust_icu_ustring/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d0caaad

Please sign in to comment.