Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creates a Unicode string macro with retry #144

Merged
merged 1 commit into from
Jun 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 13 additions & 51 deletions rust_icu_upluralrules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use {
rust_icu_common as common,
rust_icu_sys::{self as sys, versioned_function, *},
rust_icu_uenum as uenum, rust_icu_ustring as ustring,
rust_icu_ustring::buffered_uchar_method_with_retry,
std::{convert::TryFrom, convert::TryInto, ffi, ptr},
};

Expand Down Expand Up @@ -88,58 +89,19 @@ impl UPluralRules {

/// Implements `uplrules_select`.
pub fn select_ustring(&self, number: f64) -> Result<ustring::UChar, common::Error> {
// This could have been a macro "buffered_uchar_method_with_retry", but for some
// reason the macro expansion is confused about sys::UChar and ustring::UChar types.
const BUFFER_CAPACITY: usize = 20;

let mut status = common::Error::OK_CODE;
let mut buf: Vec<sys::UChar> = vec![0; BUFFER_CAPACITY];

// Requires that any pointers that are passed in are valid.
let full_len: i32 = unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(uplrules_select)(
self.rep.as_ptr(),
number,
buf.as_mut_ptr(),
BUFFER_CAPACITY as i32,
&mut status,
)
};

// ICU methods are inconsistent in whether they silently truncate the output or treat
// the overflow as an error, so we need to check both cases.
if status == sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR
|| (common::Error::is_ok(status)
&& full_len
> BUFFER_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);

// Same unsafe requirements as above, plus full_len must be exactly the output
// buffer size.
unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(uplrules_select)(
self.rep.as_ptr(),
number,
buf.as_mut_ptr(),
full_len as i32,
&mut status,
)
};
}
common::Error::ok_or_warning(status)?;
// Adjust the size of the buffer here.
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))
buffered_uchar_method_with_retry!(
select_impl,
BUFFER_CAPACITY,
[rep: *const sys::UPluralRules, number: f64,],
[]
);

select_impl(
versioned_function!(uplrules_select),
self.rep.as_ptr(),
number,
)
}

/// Implements `uplrules_select`.
Expand Down
98 changes: 98 additions & 0 deletions rust_icu_ustring/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,104 @@ pub struct UChar {
rep: Vec<rust_icu_sys::UChar>,
}

/// Same as [common::buffered_string_method_with_retry], but for unicode strings.
///
/// Example use:
///
/// Declares an internal function `select_impl` with a templatized type signature, which is then
/// called in subsequent code.
///
/// ```rust ignore
/// pub fn select_ustring(&self, number: f64) -> Result<ustring::UChar, common::Error> {
/// const BUFFER_CAPACITY: usize = 20;
/// buffered_uchar_method_with_retry!(
/// select_impl,
/// BUFFER_CAPACITY,
/// [rep: *const sys::UPluralRules, number: f64,],
/// []
/// );
///
/// select_impl(
/// versioned_function!(uplrules_select),
/// self.rep.as_ptr(),
/// number,
/// )
/// }
/// ```
#[macro_export]
macro_rules! buffered_uchar_method_with_retry {

($method_name:ident, $buffer_capacity:expr,
[$($before_arg:ident: $before_arg_type:ty,)*],
[$($after_arg:ident: $after_arg_type:ty,)*]) => {
fn $method_name(
method_to_call: unsafe extern "C" fn(
$($before_arg_type,)*
*mut sys::UChar,
i32,
$($after_arg_type,)*
*mut sys::UErrorCode,
) -> i32,
$($before_arg: $before_arg_type,)*
$($after_arg: $after_arg_type,)*
) -> Result<ustring::UChar, common::Error> {
let mut status = common::Error::OK_CODE;
let mut buf: Vec<sys::UChar> = vec![0; $buffer_capacity];

// Requires that any pointers that are passed in are valid.
let full_len: i32 = unsafe {
assert!(common::Error::is_ok(status));
method_to_call(
$($before_arg,)*
buf.as_mut_ptr() as *mut sys::UChar,
$buffer_capacity as i32,
$($after_arg,)*
&mut status,
)
};

// ICU methods are inconsistent in whether they silently truncate the output or treat
// the overflow as an error, so we need to check both cases.
if status == sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR ||
(common::Error::is_ok(status) &&
full_len > $buffer_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);

// Same unsafe requirements as above, plus full_len must be exactly the output
// buffer size.
unsafe {
assert!(common::Error::is_ok(status));
method_to_call(
$($before_arg,)*
buf.as_mut_ptr() as *mut sys::UChar,
full_len as i32,
$($after_arg,)*
&mut status,
)
};
}

common::Error::ok_or_warning(status)?;

// Adjust the size of the buffer here.
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))
}
}
}

impl TryFrom<&str> for crate::UChar {
type Error = common::Error;

Expand Down